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.
@@ -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.getAttribute('data-manager');
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
- pasteSelection() {
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
- const xml = window.localStorage.getItem('Linny-R-selection-XML');
5251
- if(xml) {
5252
- // @@ TO DO!
5253
- this.notify('Paste not implemented yet -- WORK IN PROGRESS!');
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.getAttribute('data-blk'));
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
- if(!UI.validName(nn)) {
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
  }