linny-r 1.3.0 → 1.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/console.js +162 -7
- package/package.json +1 -1
- package/server.js +41 -16
- package/static/images/paperclip.png +0 -0
- package/static/index.html +64 -3
- package/static/linny-r.css +65 -3
- package/static/scripts/linny-r-ctrl.js +16 -1
- package/static/scripts/linny-r-gui.js +421 -84
- package/static/scripts/linny-r-milp.js +144 -13
- package/static/scripts/linny-r-model.js +109 -21
- package/static/scripts/linny-r-utils.js +10 -2
- package/static/scripts/linny-r-vm.js +205 -89
@@ -3237,6 +3237,14 @@ class GUIController extends Controller {
|
|
3237
3237
|
this.modals.replace.cancel.addEventListener('click',
|
3238
3238
|
() => UI.modals.replace.hide());
|
3239
3239
|
|
3240
|
+
// The PASTE dialog appears when name conflicts must be resolved
|
3241
|
+
this.paste_modal = new ModalDialog('paste');
|
3242
|
+
this.paste_modal.ok.addEventListener('click',
|
3243
|
+
() => UI.setPasteMapping());
|
3244
|
+
this.paste_modal.cancel.addEventListener('click',
|
3245
|
+
() => UI.paste_modal.hide());
|
3246
|
+
|
3247
|
+
// The CHECK UPDATE dialog appears when a new version is detected
|
3240
3248
|
this.check_update_modal = new ModalDialog('check-update');
|
3241
3249
|
this.check_update_modal.ok.addEventListener('click',
|
3242
3250
|
() => UI.shutDownServer());
|
@@ -3555,81 +3563,6 @@ class GUIController extends Controller {
|
|
3555
3563
|
// Methods related to draggable & resizable dialogs
|
3556
3564
|
//
|
3557
3565
|
|
3558
|
-
toggleDialog(e) {
|
3559
|
-
e = e || window.event;
|
3560
|
-
e.preventDefault();
|
3561
|
-
e.stopImmediatePropagation();
|
3562
|
-
// Infer dialog identifier from target element
|
3563
|
-
const
|
3564
|
-
dlg = e.target.id.split('-')[0],
|
3565
|
-
tde = document.getElementById(dlg + '-dlg'),
|
3566
|
-
was_hidden = this.hidden(tde.id);
|
3567
|
-
let mgr = tde.getAttribute('data-manager');
|
3568
|
-
if(mgr) mgr = window[mgr];
|
3569
|
-
// NOTE: prevent modeler from viewing charts while an experiment is running
|
3570
|
-
if(dlg === 'chart' && was_hidden && MODEL.running_experiment) {
|
3571
|
-
UI.notify(UI.NOTICE.NO_CHARTS);
|
3572
|
-
mgr.visible = false;
|
3573
|
-
return;
|
3574
|
-
}
|
3575
|
-
this.toggle(tde.id);
|
3576
|
-
if(mgr) mgr.visible = was_hidden;
|
3577
|
-
// Open at position after last drag (recorded in DOM data attributes)
|
3578
|
-
let t = tde.getAttribute('data-top'),
|
3579
|
-
l = tde.getAttribute('data-left');
|
3580
|
-
// Make dialog appear in screen center the first time it is shown
|
3581
|
-
if(t === null || l === null) {
|
3582
|
-
const cs = window.getComputedStyle(tde);
|
3583
|
-
t = ((window.innerHeight - parseFloat(cs.height)) / 2) + 'px';
|
3584
|
-
l = ((window.innerWidth - parseFloat(cs.width)) / 2) + 'px';
|
3585
|
-
tde.style.top = t;
|
3586
|
-
tde.style.left = l;
|
3587
|
-
}
|
3588
|
-
if(!this.hidden(tde.id)) {
|
3589
|
-
// Add dialog to "showing" list, and adjust z-indices
|
3590
|
-
this.dr_dialog_order.push(tde);
|
3591
|
-
this.reorderDialogs();
|
3592
|
-
// Update the diagram if its manager has been specified
|
3593
|
-
if(mgr) {
|
3594
|
-
mgr.visible = true;
|
3595
|
-
mgr.updateDialog();
|
3596
|
-
if(mgr === DOCUMENTATION_MANAGER) {
|
3597
|
-
if(this.info_line.innerHTML.length === 0) {
|
3598
|
-
mgr.title.innerHTML = 'About Linny-R';
|
3599
|
-
mgr.viewer.innerHTML = mgr.about_linny_r;
|
3600
|
-
mgr.edit_btn.classList.remove('enab');
|
3601
|
-
mgr.edit_btn.classList.add('disab');
|
3602
|
-
}
|
3603
|
-
UI.drawDiagram(MODEL);
|
3604
|
-
}
|
3605
|
-
}
|
3606
|
-
} else {
|
3607
|
-
const doi = this.dr_dialog_order.indexOf(tde);
|
3608
|
-
// NOTE: doi should ALWAYS be >= 0 because dialog WAS showing
|
3609
|
-
if(doi >= 0) {
|
3610
|
-
this.dr_dialog_order.splice(doi, 1);
|
3611
|
-
this.reorderDialogs();
|
3612
|
-
}
|
3613
|
-
if(mgr) {
|
3614
|
-
mgr.visible = true;
|
3615
|
-
if(mgr === DOCUMENTATION_MANAGER) {
|
3616
|
-
mgr.visible = false;
|
3617
|
-
mgr.title.innerHTML = 'Documentation';
|
3618
|
-
UI.drawDiagram(MODEL);
|
3619
|
-
}
|
3620
|
-
}
|
3621
|
-
}
|
3622
|
-
UI.buttons[dlg].classList.toggle('stay-activ');
|
3623
|
-
}
|
3624
|
-
|
3625
|
-
reorderDialogs() {
|
3626
|
-
let z = 10;
|
3627
|
-
for(let i = 0; i < this.dr_dialog_order.length; i++) {
|
3628
|
-
this.dr_dialog_order[i].style.zIndex = z;
|
3629
|
-
z += 5;
|
3630
|
-
}
|
3631
|
-
}
|
3632
|
-
|
3633
3566
|
draggableDialog(d) {
|
3634
3567
|
// Make dialog draggable
|
3635
3568
|
const
|
@@ -3755,7 +3688,7 @@ class GUIController extends Controller {
|
|
3755
3688
|
UI.dr_dialog.style.width = Math.max(minw, w + dw) + 'px';
|
3756
3689
|
UI.dr_dialog.style.height = Math.max(minh, h + dh) + 'px';
|
3757
3690
|
// Update the dialog if its manager has been specified
|
3758
|
-
const mgr = UI.dr_dialog.
|
3691
|
+
const mgr = UI.dr_dialog.dataset.manager;
|
3759
3692
|
if(mgr) window[mgr].updateDialog();
|
3760
3693
|
}
|
3761
3694
|
|
@@ -3766,6 +3699,90 @@ class GUIController extends Controller {
|
|
3766
3699
|
}
|
3767
3700
|
}
|
3768
3701
|
|
3702
|
+
toggleDialog(e) {
|
3703
|
+
// Hide dialog if visible, or show it if not, and update the
|
3704
|
+
// order of appearance so that this dialog appears on top
|
3705
|
+
e = e || window.event;
|
3706
|
+
e.preventDefault();
|
3707
|
+
e.stopImmediatePropagation();
|
3708
|
+
// Infer dialog identifier from target element
|
3709
|
+
const
|
3710
|
+
dlg = e.target.id.split('-')[0],
|
3711
|
+
tde = document.getElementById(dlg + '-dlg');
|
3712
|
+
// NOTE: manager attribute is a string, e.g. 'MONITOR' or 'CHART_MANAGER'
|
3713
|
+
let mgr = tde.dataset.manager,
|
3714
|
+
was_hidden = this.hidden(tde.id);
|
3715
|
+
if(mgr) {
|
3716
|
+
// Dialog has a manager object => let `mgr` point to it
|
3717
|
+
mgr = window[mgr];
|
3718
|
+
// Manager object attributes are more reliable than DOM element
|
3719
|
+
// style attributes, so update the visibility status
|
3720
|
+
was_hidden = !mgr.visible;
|
3721
|
+
}
|
3722
|
+
// NOTE: modeler should not view charts while an experiment is
|
3723
|
+
// running, so do NOT toggle when the Chart Manager is NOT visible
|
3724
|
+
if(dlg === 'chart' && was_hidden && MODEL.running_experiment) {
|
3725
|
+
UI.notify(UI.NOTICE.NO_CHARTS);
|
3726
|
+
return;
|
3727
|
+
}
|
3728
|
+
// Otherwise, toggle the dialog visibility
|
3729
|
+
this.toggle(tde.id);
|
3730
|
+
UI.buttons[dlg].classList.toggle('stay-activ');
|
3731
|
+
if(mgr) mgr.visible = was_hidden;
|
3732
|
+
let t, l;
|
3733
|
+
if(top in tde.dataset && left in tde.dataset) {
|
3734
|
+
// Open at position after last drag (recorded in DOM data attributes)
|
3735
|
+
t = tde.dataset.top;
|
3736
|
+
l = tde.dataset.left;
|
3737
|
+
} else {
|
3738
|
+
// Make dialog appear in screen center the first time it is shown
|
3739
|
+
const cs = window.getComputedStyle(tde);
|
3740
|
+
t = ((window.innerHeight - parseFloat(cs.height)) / 2) + 'px';
|
3741
|
+
l = ((window.innerWidth - parseFloat(cs.width)) / 2) + 'px';
|
3742
|
+
tde.style.top = t;
|
3743
|
+
tde.style.left = l;
|
3744
|
+
}
|
3745
|
+
if(was_hidden) {
|
3746
|
+
// Add activated dialog to "showing" list, and adjust z-indices
|
3747
|
+
this.dr_dialog_order.push(tde);
|
3748
|
+
this.reorderDialogs();
|
3749
|
+
// Update the diagram if its manager has been specified
|
3750
|
+
if(mgr) {
|
3751
|
+
mgr.updateDialog();
|
3752
|
+
if(mgr === DOCUMENTATION_MANAGER) {
|
3753
|
+
if(this.info_line.innerHTML.length === 0) {
|
3754
|
+
mgr.title.innerHTML = 'About Linny-R';
|
3755
|
+
mgr.viewer.innerHTML = mgr.about_linny_r;
|
3756
|
+
mgr.edit_btn.classList.remove('enab');
|
3757
|
+
mgr.edit_btn.classList.add('disab');
|
3758
|
+
}
|
3759
|
+
UI.drawDiagram(MODEL);
|
3760
|
+
}
|
3761
|
+
}
|
3762
|
+
} else {
|
3763
|
+
const doi = this.dr_dialog_order.indexOf(tde);
|
3764
|
+
// NOTE: doi should ALWAYS be >= 0 because dialog WAS showing
|
3765
|
+
if(doi >= 0) {
|
3766
|
+
this.dr_dialog_order.splice(doi, 1);
|
3767
|
+
this.reorderDialogs();
|
3768
|
+
}
|
3769
|
+
if(mgr === DOCUMENTATION_MANAGER) {
|
3770
|
+
mgr.title.innerHTML = 'Documentation';
|
3771
|
+
UI.drawDiagram(MODEL);
|
3772
|
+
}
|
3773
|
+
}
|
3774
|
+
}
|
3775
|
+
|
3776
|
+
reorderDialogs() {
|
3777
|
+
// Set z-index of draggable dialogs according to their order
|
3778
|
+
// (most recently shown or clicked on top)
|
3779
|
+
let z = 10;
|
3780
|
+
for(let i = 0; i < this.dr_dialog_order.length; i++) {
|
3781
|
+
this.dr_dialog_order[i].style.zIndex = z;
|
3782
|
+
z += 5;
|
3783
|
+
}
|
3784
|
+
}
|
3785
|
+
|
3769
3786
|
//
|
3770
3787
|
// Button functionality
|
3771
3788
|
//
|
@@ -5222,7 +5239,6 @@ class GUIController extends Controller {
|
|
5222
5239
|
copySelection() {
|
5223
5240
|
// Save selection as XML in local storage of the browser
|
5224
5241
|
const xml = MODEL.selectionAsXML;
|
5225
|
-
//console.log('HERE copy xml', xml);
|
5226
5242
|
if(xml) {
|
5227
5243
|
window.localStorage.setItem('Linny-R-selection-XML', xml);
|
5228
5244
|
this.updateButtons();
|
@@ -5243,15 +5259,333 @@ class GUIController extends Controller {
|
|
5243
5259
|
return false;
|
5244
5260
|
}
|
5245
5261
|
|
5246
|
-
|
5262
|
+
promptForMapping(mapping) {
|
5263
|
+
// Prompt user to specify name conflict resolution strategy
|
5264
|
+
console.log('HERE prompt for mapping', mapping);
|
5265
|
+
const md = this.paste_modal;
|
5266
|
+
md.mapping = mapping;
|
5267
|
+
md.element('from-prefix').innerText = mapping.from_prefix || '';
|
5268
|
+
md.element('to-prefix').innerText = mapping.to_prefix || '';
|
5269
|
+
md.element('ftp').style.display = (mapping.from_prefix ? 'block' : 'none');
|
5270
|
+
md.element('from-actor').innerText = mapping.from_actor || '';
|
5271
|
+
md.element('to-actor').innerText = mapping.to_actor || '';
|
5272
|
+
md.element('fta').style.display = (mapping.from_actor ? 'block' : 'none');
|
5273
|
+
md.element('actor').value = mapping.actor || '';
|
5274
|
+
md.element('prefix').value = mapping.prefix || '';
|
5275
|
+
const
|
5276
|
+
ft = Object.keys(mapping.from_to).sort(ciCompare),
|
5277
|
+
sl = [];
|
5278
|
+
if(ft.length) {
|
5279
|
+
// Add selectors for unresolved FROM/TO nodes
|
5280
|
+
for(let i = 0; i < ft.length; i++) {
|
5281
|
+
const ti = mapping.from_to[ft[i]];
|
5282
|
+
if(ft[i] === ti) {
|
5283
|
+
sl.push('<div class="paste-option"><span>', ft[i], '</span> ',
|
5284
|
+
'<div class="paste-select"><select id="paste-ft-', i,
|
5285
|
+
'" style="font-size: 12px"><option value="', ti, '">', ti,
|
5286
|
+
'</option></select></div></div>');
|
5287
|
+
}
|
5288
|
+
}
|
5289
|
+
md.element('scroll-area').innerHTML = sl.join('');
|
5290
|
+
}
|
5291
|
+
// Open dialog, which will call pasteSelection(...) on OK
|
5292
|
+
this.paste_modal.show();
|
5293
|
+
}
|
5294
|
+
|
5295
|
+
setPasteMapping() {
|
5296
|
+
// Updates the paste mapping as specified by the modeler and then
|
5297
|
+
// proceeds to paste
|
5298
|
+
const
|
5299
|
+
md = this.paste_modal,
|
5300
|
+
mapping = Object.assign(md.mapping, {});
|
5301
|
+
mapping.actor = md.element('actor').value;
|
5302
|
+
mapping.prefix = md.element('prefix').value.trim();
|
5303
|
+
mapping.increment = true;
|
5304
|
+
this.pasteSelection(mapping);
|
5305
|
+
}
|
5306
|
+
|
5307
|
+
pasteSelection(mapping={}) {
|
5247
5308
|
// If selection has been saved as XML in local storage, test to
|
5248
5309
|
// see whether PASTE would result in name conflicts, and if so,
|
5249
5310
|
// open the name conflict resolution window
|
5250
|
-
|
5251
|
-
|
5252
|
-
|
5253
|
-
|
5311
|
+
let xml = window.localStorage.getItem('Linny-R-selection-XML');
|
5312
|
+
console.log('HERE xml', xml);
|
5313
|
+
try {
|
5314
|
+
xml = parseXML(xml);
|
5315
|
+
} catch(e) {
|
5316
|
+
console.log(e);
|
5317
|
+
this.alert('Paste failed due to invalid XML');
|
5318
|
+
return;
|
5319
|
+
}
|
5320
|
+
|
5321
|
+
// For now, while still implementing:
|
5322
|
+
this.notify('Paste not fully implemented yet -- WORK IN PROGRESS!');
|
5323
|
+
|
5324
|
+
const
|
5325
|
+
entities_node = childNodeByTag(xml, 'entities'),
|
5326
|
+
from_tos_node = childNodeByTag(xml, 'from-tos'),
|
5327
|
+
extras_node = childNodeByTag(xml, 'extras'),
|
5328
|
+
selection_node = childNodeByTag(xml, 'selection'),
|
5329
|
+
actor_names = [],
|
5330
|
+
new_entities = [],
|
5331
|
+
name_map = {},
|
5332
|
+
name_conflicts = [];
|
5333
|
+
|
5334
|
+
// AUXILIARY FUNCTIONS
|
5335
|
+
|
5336
|
+
function fullName(node) {
|
5337
|
+
// Returns full entity name inferred from XML node data
|
5338
|
+
if(node.nodeName === 'from-to') {
|
5339
|
+
const
|
5340
|
+
n = xmlDecoded(nodeParameterValue(node, 'name')),
|
5341
|
+
an = xmlDecoded(nodeParameterValue(node, 'actor-name'));
|
5342
|
+
if(an && an !== UI.NO_ACTOR) {
|
5343
|
+
addDistinct(an, actor_names);
|
5344
|
+
return `${n} (${an})`;
|
5345
|
+
}
|
5346
|
+
return n;
|
5347
|
+
}
|
5348
|
+
if(node.nodeName !== 'link' && node.nodeName !== 'constraint') {
|
5349
|
+
const
|
5350
|
+
n = xmlDecoded(nodeContentByTag(node, 'name')),
|
5351
|
+
an = xmlDecoded(nodeContentByTag(node, 'actor-name'));
|
5352
|
+
if(an && an !== UI.NO_ACTOR) {
|
5353
|
+
addDistinct(an, actor_names);
|
5354
|
+
return `${n} (${an})`;
|
5355
|
+
}
|
5356
|
+
return n;
|
5357
|
+
} else {
|
5358
|
+
let fn = xmlDecoded(nodeContentByTag(node, 'from-name')),
|
5359
|
+
fa = xmlDecoded(nodeContentByTag(node, 'from-owner')),
|
5360
|
+
tn = xmlDecoded(nodeContentByTag(node, 'to-name')),
|
5361
|
+
ta = xmlDecoded(nodeContentByTag(node, 'to-owner')),
|
5362
|
+
arrow = (node.nodeName === 'link' ? UI.LINK_ARROW : UI.CONSTRAINT_ARROW);
|
5363
|
+
if(fa && fa !== UI.NO_ACTOR) {
|
5364
|
+
addDistinct(fa, actor_names);
|
5365
|
+
fn = `${fn} (${fa})`;
|
5366
|
+
}
|
5367
|
+
if(ta && ta !== UI.NO_ACTOR) {
|
5368
|
+
addDistinct(ta, actor_names);
|
5369
|
+
tn = `${tn} (${ta})`;
|
5370
|
+
}
|
5371
|
+
return `${fn}${arrow}${tn}`;
|
5372
|
+
}
|
5373
|
+
}
|
5374
|
+
|
5375
|
+
function nameAndActor(name) {
|
5376
|
+
// Returns tuple [entity name, actor name] if `name` ends with
|
5377
|
+
// a parenthesized string that identifies an actor in the selection
|
5378
|
+
const ai = name.lastIndexOf(' (');
|
5379
|
+
if(ai < 0) return [name, ''];
|
5380
|
+
let actor = name.slice(ai + 2, -1);
|
5381
|
+
// Test whether parenthesized string denotes an actor
|
5382
|
+
if(actor_names.indexOf(actor) >= 0 || actor === mapping.actor ||
|
5383
|
+
actor === mapping.from_actor || actor === mapping.to_actor) {
|
5384
|
+
name = name.substring(0, ai);
|
5385
|
+
} else {
|
5386
|
+
actor = '';
|
5387
|
+
}
|
5388
|
+
return [name, actor];
|
5389
|
+
}
|
5390
|
+
|
5391
|
+
function mappedName(n) {
|
5392
|
+
// Returns full name `n` modified according to the mapping
|
5393
|
+
// NOTE: links and constraints require two mappings (recursion!)
|
5394
|
+
if(n.indexOf(UI.LINK_ARROW) > 0) {
|
5395
|
+
const ft = n.split(UI.LINK_ARROW);
|
5396
|
+
return mappedName(ft[0]) + UI.LINK_ARROW + mappedName(ft[1]);
|
5397
|
+
}
|
5398
|
+
if(n.indexOf(UI.CONSTRAINT_ARROW) > 0) {
|
5399
|
+
const ft = n.split(UI.CONSTRAINT_ARROW);
|
5400
|
+
return mappedName(ft[0]) + UI.CONSTRAINT_ARROW + mappedName(ft[1]);
|
5401
|
+
}
|
5402
|
+
// Mapping precedence order:
|
5403
|
+
// (1) prefix inherited from cluster
|
5404
|
+
// (2) actor name inherited from cluster
|
5405
|
+
// (3) actor name specified by modeler
|
5406
|
+
// (4) prefix specified by modeler
|
5407
|
+
// (5) auto-increment tail number
|
5408
|
+
// (6) nearest eligible node
|
5409
|
+
if(mapping.from_prefix && n.startsWith(mapping.from_prefix)) {
|
5410
|
+
return n.replace(mapping.from_prefix, mapping.to_prefix);
|
5411
|
+
}
|
5412
|
+
if(mapping.from_actor) {
|
5413
|
+
const ai = n.lastIndexOf(mapping.from_actor);
|
5414
|
+
if(ai > 0) return n.substring(0, ai) + mapping.to_actor;
|
5415
|
+
}
|
5416
|
+
// NOTE: specified actor cannot override existing actor
|
5417
|
+
if(mapping.actor && !nameAndActor(n)[1]) {
|
5418
|
+
return `${n} (${mapping.actor})`;
|
5419
|
+
}
|
5420
|
+
if(mapping.prefix) {
|
5421
|
+
return mapping.prefix + UI.PREFIXER + n;
|
5422
|
+
}
|
5423
|
+
let nr = endsWithDigits(n);
|
5424
|
+
if(mapping.increment && nr) {
|
5425
|
+
return n.replace(new RegExp(nr + '$'), parseInt(nr) + 1);
|
5426
|
+
}
|
5427
|
+
// No mapping => return original name
|
5428
|
+
return n;
|
5429
|
+
}
|
5430
|
+
|
5431
|
+
function nameConflicts(node) {
|
5432
|
+
// Maps names of entities defined by the child nodes of `node`
|
5433
|
+
// while detecting name conflicts
|
5434
|
+
for(let i = 0; i < node.childNodes.length; i++) {
|
5435
|
+
const c = node.childNodes[i];
|
5436
|
+
if(c.nodeName !== 'link' && c.nodeName !== 'constraint') {
|
5437
|
+
const
|
5438
|
+
fn = fullName(c),
|
5439
|
+
mn = mappedName(fn);
|
5440
|
+
// Name conflict occurs when the mapped name is already in use
|
5441
|
+
// in the target model, or when the original name is mapped onto
|
5442
|
+
// different names (this might occur due to modeler input)
|
5443
|
+
if(MODEL.objectByName(mn) || (name_map[fn] && name_map[fn] !== mn)) {
|
5444
|
+
addDistinct(fn, name_conflicts);
|
5445
|
+
} else {
|
5446
|
+
name_map[fn] = mn;
|
5447
|
+
}
|
5448
|
+
}
|
5449
|
+
}
|
5450
|
+
}
|
5451
|
+
|
5452
|
+
function addEntityFromNode(node) {
|
5453
|
+
// Adds entity to model based on XML node data and mapping
|
5454
|
+
// NOTE: do not add if an entity having this type and mapped name
|
5455
|
+
// already exists; name conflicts accross entity types may occur
|
5456
|
+
// and result in error messages
|
5457
|
+
const
|
5458
|
+
et = node.nodeName,
|
5459
|
+
fn = fullName(node),
|
5460
|
+
mn = mappedName(fn);
|
5461
|
+
let obj;
|
5462
|
+
if(et === 'process' && !MODEL.processByID(UI.nameToID(mn))) {
|
5463
|
+
const
|
5464
|
+
na = nameAndActor(mn),
|
5465
|
+
new_actor = !MODEL.actorByID(UI.nameToID(na[1]));
|
5466
|
+
obj = MODEL.addProcess(na[0], na[1], node);
|
5467
|
+
if(obj) {
|
5468
|
+
obj.code = '';
|
5469
|
+
obj.setCode();
|
5470
|
+
if(new_actor) new_entities.push(obj.actor);
|
5471
|
+
new_entities.push(obj);
|
5472
|
+
}
|
5473
|
+
} else if(et === 'product' && !MODEL.productByID(UI.nameToID(mn))) {
|
5474
|
+
obj = MODEL.addProduct(mn, node);
|
5475
|
+
if(obj) {
|
5476
|
+
obj.code = '';
|
5477
|
+
obj.setCode();
|
5478
|
+
new_entities.push(obj);
|
5479
|
+
}
|
5480
|
+
} else if(et === 'cluster' && !MODEL.clusterByID(UI.nameToID(mn))) {
|
5481
|
+
const
|
5482
|
+
na = nameAndActor(mn),
|
5483
|
+
new_actor = !MODEL.actorByID(UI.nameToID(na[1]));
|
5484
|
+
obj = MODEL.addCluster(na[0], na[1], node);
|
5485
|
+
if(obj) {
|
5486
|
+
if(new_actor) new_entities.push(obj.actor);
|
5487
|
+
new_entities.push(obj);
|
5488
|
+
}
|
5489
|
+
} else if(et === 'dataset' && !MODEL.datasetByID(UI.nameToID(mn))) {
|
5490
|
+
obj = MODEL.addDataset(mn, node);
|
5491
|
+
if(obj) new_entities.push(obj);
|
5492
|
+
} else if(et === 'link' || et === 'constraint') {
|
5493
|
+
const
|
5494
|
+
ft = mn.split(et === 'link' ? UI.LINK_ARROW : UI.CONSTRAINT_ARROW),
|
5495
|
+
fl = MODEL.objectByName(ft[0]),
|
5496
|
+
tl = MODEL.objectByName(ft[1]);
|
5497
|
+
if(fl && tl) {
|
5498
|
+
obj = (et === 'link' ?
|
5499
|
+
MODEL.addLink(fl, tl, node) :
|
5500
|
+
MODEL.addConstraint(fl, tl, node));
|
5501
|
+
if(obj) new_entities.push(obj);
|
5502
|
+
} else {
|
5503
|
+
UI.alert(`Failed to paste ${et} ${fn} as ${mn}`);
|
5504
|
+
}
|
5505
|
+
}
|
5254
5506
|
}
|
5507
|
+
|
5508
|
+
const
|
5509
|
+
mts = nodeParameterValue(xml, 'model-timestamp'),
|
5510
|
+
cn = nodeParameterValue(xml, 'cluster-name'),
|
5511
|
+
ca = nodeParameterValue(xml, 'cluster-actor'),
|
5512
|
+
fc = MODEL.focal_cluster,
|
5513
|
+
fcn = fc.name,
|
5514
|
+
fca = fc.actor.name,
|
5515
|
+
sp = this.sharedPrefix(cn, fcn),
|
5516
|
+
fpn = (cn === UI.TOP_CLUSTER_NAME ? '' : cn.replace(sp, '')),
|
5517
|
+
tpn = (fcn === UI.TOP_CLUSTER_NAME ? '' : fcn.replace(sp, ''));
|
5518
|
+
// Infer mapping from XML data and focal cluster name & actor name
|
5519
|
+
mapping.shared_prefix = sp;
|
5520
|
+
mapping.from_prefix = (fpn ? sp + fpn + UI.PREFIXER : sp);
|
5521
|
+
mapping.to_prefix = (tpn ? sp + tpn + UI.PREFIXER : sp);
|
5522
|
+
mapping.from_actor = (ca === UI.NO_ACTOR ? '' : ca);
|
5523
|
+
mapping.to_actor = (fca === UI.NO_ACTOR ? '' : fca);
|
5524
|
+
// Prompt for mapping when pasting to the same model and cluster
|
5525
|
+
if(parseInt(mts) === MODEL.time_created.getTime() &&
|
5526
|
+
ca === fca && mapping.from_prefix === mapping.to_prefix &&
|
5527
|
+
!(mapping.prefix || mapping.actor || mapping.increment)) {
|
5528
|
+
this.promptForMapping(mapping);
|
5529
|
+
return;
|
5530
|
+
}
|
5531
|
+
// Also prompt if FROM and/or TO nodes are not selected, and map to
|
5532
|
+
// existing entities
|
5533
|
+
if(from_tos_node.childNodes.length && !mapping.from_to) {
|
5534
|
+
const ft_map = {};
|
5535
|
+
for(let i = 0; i < from_tos_node.childNodes.length; i++) {
|
5536
|
+
const
|
5537
|
+
c = from_tos_node.childNodes[i],
|
5538
|
+
fn = fullName(c),
|
5539
|
+
mn = mappedName(fn);
|
5540
|
+
if(MODEL.objectByName(mn)) ft_map[fn] = mn;
|
5541
|
+
}
|
5542
|
+
// Prompt only for FROM/TO nodes that map to existing nodes
|
5543
|
+
if(Object.keys(ft_map).length) {
|
5544
|
+
mapping.from_to = ft_map;
|
5545
|
+
this.promptForMapping(mapping);
|
5546
|
+
return;
|
5547
|
+
}
|
5548
|
+
}
|
5549
|
+
|
5550
|
+
// Only check for selected entities and from-to's; extra's should be
|
5551
|
+
// used if they exist, or should be created when copying to a different
|
5552
|
+
// model
|
5553
|
+
name_map.length = 0;
|
5554
|
+
nameConflicts(entities_node);
|
5555
|
+
nameConflicts(from_tos_node);
|
5556
|
+
if(name_conflicts.length) {
|
5557
|
+
UI.notify(pluralS(name_conflicts.length, 'name conflict'));
|
5558
|
+
console.log('HERE name conflicts', name_conflicts);
|
5559
|
+
return;
|
5560
|
+
}
|
5561
|
+
|
5562
|
+
// No conflicts => add all
|
5563
|
+
console.log('HERE name map', name_map);
|
5564
|
+
for(let i = 0; i < extras_node.childNodes.length; i++) {
|
5565
|
+
addEntityFromNode(extras_node.childNodes[i]);
|
5566
|
+
}
|
5567
|
+
for(let i = 0; i < from_tos_node.childNodes.length; i++) {
|
5568
|
+
addEntityFromNode(from_tos_node.childNodes[i]);
|
5569
|
+
}
|
5570
|
+
for(let i = 0; i < entities_node.childNodes.length; i++) {
|
5571
|
+
addEntityFromNode(entities_node.childNodes[i]);
|
5572
|
+
}
|
5573
|
+
// Update diagram, showing newly added nodes as selection
|
5574
|
+
MODEL.clearSelection();
|
5575
|
+
for(let i = 0; i < selection_node.childNodes.length; i++) {
|
5576
|
+
const
|
5577
|
+
n = xmlDecoded(nodeContent(selection_node.childNodes[i])),
|
5578
|
+
obj = MODEL.objectByName(mappedName(n));
|
5579
|
+
if(obj) {
|
5580
|
+
// NOTE: selected products must be positioned
|
5581
|
+
if(obj instanceof Product) MODEL.focal_cluster.addProductPosition(obj);
|
5582
|
+
MODEL.select(obj);
|
5583
|
+
}
|
5584
|
+
}
|
5585
|
+
// Force redrawing the selection to ensure that links to positioned
|
5586
|
+
// products are displayed as arrows instead of block arrows
|
5587
|
+
fc.clearAllProcesses();
|
5588
|
+
UI.drawDiagram(MODEL);
|
5255
5589
|
}
|
5256
5590
|
|
5257
5591
|
//
|
@@ -5975,7 +6309,7 @@ class GUIMonitor {
|
|
5975
6309
|
(event) => {
|
5976
6310
|
const el = event.target;
|
5977
6311
|
el.classList.add('sel-pb');
|
5978
|
-
MONITOR.showBlock(el.
|
6312
|
+
MONITOR.showBlock(el.dataset.blk);
|
5979
6313
|
},
|
5980
6314
|
false);
|
5981
6315
|
this.progress_bar.appendChild(n);
|
@@ -6226,6 +6560,7 @@ UI.logHeapSize(`BEFORE creating post data`);
|
|
6226
6560
|
token: VM.solver_token,
|
6227
6561
|
block: VM.block_count,
|
6228
6562
|
round: VM.round_sequence[VM.current_round],
|
6563
|
+
columns: VM.columnsInBlock,
|
6229
6564
|
data: VM.lines,
|
6230
6565
|
timeout: top
|
6231
6566
|
});
|
@@ -7678,7 +8013,9 @@ class ActorManager {
|
|
7678
8013
|
xp = new ExpressionParser(x);
|
7679
8014
|
if(n !== UI.NO_ACTOR) {
|
7680
8015
|
nn = this.actor_name.value.trim();
|
7681
|
-
|
8016
|
+
// NOTE: prohibit colons in actor names to avoid confusion with
|
8017
|
+
// prefixed entities
|
8018
|
+
if(!UI.validName(nn) || nn.indexOf(':') >= 0) {
|
7682
8019
|
UI.warn(UI.WARNING.INVALID_ACTOR_NAME);
|
7683
8020
|
return false;
|
7684
8021
|
}
|