linny-r 2.0.8 → 2.0.9
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/README.md +3 -40
- package/package.json +1 -1
- package/server.js +19 -157
- package/static/index.html +58 -20
- package/static/linny-r.css +20 -16
- package/static/scripts/iro.min.js +7 -7
- package/static/scripts/linny-r-ctrl.js +50 -72
- package/static/scripts/linny-r-gui-actor-manager.js +23 -33
- package/static/scripts/linny-r-gui-chart-manager.js +43 -41
- package/static/scripts/linny-r-gui-constraint-editor.js +6 -10
- package/static/scripts/linny-r-gui-controller.js +254 -230
- package/static/scripts/linny-r-gui-dataset-manager.js +17 -36
- package/static/scripts/linny-r-gui-documentation-manager.js +11 -17
- package/static/scripts/linny-r-gui-equation-manager.js +22 -22
- package/static/scripts/linny-r-gui-experiment-manager.js +102 -129
- package/static/scripts/linny-r-gui-file-manager.js +42 -48
- package/static/scripts/linny-r-gui-finder.js +105 -51
- package/static/scripts/linny-r-gui-model-autosaver.js +2 -4
- package/static/scripts/linny-r-gui-monitor.js +35 -41
- package/static/scripts/linny-r-gui-paper.js +42 -70
- package/static/scripts/linny-r-gui-power-grid-manager.js +31 -34
- package/static/scripts/linny-r-gui-receiver.js +1 -2
- package/static/scripts/linny-r-gui-repository-browser.js +44 -46
- package/static/scripts/linny-r-gui-scale-unit-manager.js +32 -32
- package/static/scripts/linny-r-gui-sensitivity-analysis.js +61 -67
- package/static/scripts/linny-r-gui-undo-redo.js +94 -95
- package/static/scripts/linny-r-milp.js +20 -24
- package/static/scripts/linny-r-model.js +1832 -2248
- package/static/scripts/linny-r-utils.js +27 -27
- package/static/scripts/linny-r-vm.js +801 -905
- package/static/show-png.html +0 -113
@@ -298,8 +298,7 @@ class GroupPropertiesDialog extends ModalDialog {
|
|
298
298
|
const
|
299
299
|
propname = this.fields[name],
|
300
300
|
prop = obj[propname];
|
301
|
-
for(
|
302
|
-
const ge = this.group[i];
|
301
|
+
for(const ge of this.group) {
|
303
302
|
// NOTE: For links, special care must be taken.
|
304
303
|
if(!(ge instanceof Link) ||
|
305
304
|
this.validLinkProperty(ge, propname, prop)) {
|
@@ -332,12 +331,9 @@ class GUIController extends Controller {
|
|
332
331
|
['chrome', 'Chrome'],
|
333
332
|
['firefox', 'Firefox'],
|
334
333
|
['safari', 'Safari']];
|
335
|
-
for(
|
336
|
-
|
337
|
-
|
338
|
-
this.browser_name = b[1];
|
339
|
-
break;
|
340
|
-
}
|
334
|
+
for(const b of browsers) if(ua.indexOf(b[0]) >= 0) {
|
335
|
+
this.browser_name = b[1];
|
336
|
+
break;
|
341
337
|
}
|
342
338
|
// Display version number as clickable link just below the Linny-R logo.
|
343
339
|
this.version_number = LINNY_R_VERSION;
|
@@ -363,12 +359,22 @@ class GUIController extends Controller {
|
|
363
359
|
this.mouse_y = 0;
|
364
360
|
this.mouse_down_x = 0;
|
365
361
|
this.mouse_down_y = 0;
|
362
|
+
// When clicking on a node, difference between cursor coordinates
|
363
|
+
// and node coordinates is recorded.
|
366
364
|
this.move_dx = 0;
|
367
365
|
this.move_dy = 0;
|
368
|
-
|
369
|
-
|
366
|
+
// When moving the cursor, the cumulative movement since the last
|
367
|
+
// mouse DOWN or UP event is recorded.
|
368
|
+
this.net_move_x = 0;
|
369
|
+
this.net_move_y = 0;
|
370
|
+
// When mouse button is pressed while some add button is active,
|
371
|
+
// the coordinates of the cursor are recorded.
|
370
372
|
this.add_x = 0;
|
371
373
|
this.add_y = 0;
|
374
|
+
// When mouse button is pressed while no node is under the cursor,
|
375
|
+
// cursor coordinates are recorded as origin of the drag rectangle.
|
376
|
+
this.start_sel_x = -1;
|
377
|
+
this.start_sel_y = -1;
|
372
378
|
this.on_node = null;
|
373
379
|
this.on_arrow = null;
|
374
380
|
this.on_link = null;
|
@@ -393,7 +399,6 @@ class GUIController extends Controller {
|
|
393
399
|
'D': 'dataset',
|
394
400
|
'E': 'equation',
|
395
401
|
'F': 'finder',
|
396
|
-
'G': 'savediagram', // G for "Graph" (as Scalable Vector Graphics image)
|
397
402
|
'H': 'receiver', // activate receiver (H for "Host")
|
398
403
|
'I': 'documentation',
|
399
404
|
'J': 'sensitivity', // J for "Jitter"
|
@@ -421,7 +426,7 @@ class GUIController extends Controller {
|
|
421
426
|
this.edit_btns = ['replace', 'clone', 'paste', 'delete', 'undo', 'redo'];
|
422
427
|
this.model_btns = ['settings', 'save', 'repository', 'actors',
|
423
428
|
'dataset', 'equation', 'chart', 'sensitivity', 'experiment',
|
424
|
-
'
|
429
|
+
'savediagram', 'finder', 'monitor', 'tex', 'solve'];
|
425
430
|
this.other_btns = ['new', 'load', 'receiver', 'documentation',
|
426
431
|
'parent', 'lift', 'solve', 'stop', 'reset', 'zoomin', 'zoomout',
|
427
432
|
'stepback', 'stepforward', 'autosave', 'recall'];
|
@@ -429,8 +434,7 @@ class GUIController extends Controller {
|
|
429
434
|
this.edit_btns, this.model_btns, this.other_btns);
|
430
435
|
|
431
436
|
// Add all button DOM elements as controller properties.
|
432
|
-
for(
|
433
|
-
const b = this.all_btns[i];
|
437
|
+
for(const b of this.all_btns) {
|
434
438
|
this.buttons[b] = document.getElementById(b + '-btn');
|
435
439
|
}
|
436
440
|
this.active_button = null;
|
@@ -450,10 +454,9 @@ class GUIController extends Controller {
|
|
450
454
|
const main_modals = ['logon', 'model', 'load', 'password', 'settings',
|
451
455
|
'actors', 'add-process', 'add-product', 'move', 'note', 'clone',
|
452
456
|
'replace', 'expression', 'server', 'solver'];
|
453
|
-
for(
|
454
|
-
this.modals[main_modals[i]] = new ModalDialog(main_modals[i]);
|
455
|
-
}
|
457
|
+
for(const m of main_modals) this.modals[m] = new ModalDialog(m);
|
456
458
|
|
459
|
+
// Property dialogs for entities may permit group editing.
|
457
460
|
this.modals.cluster = new GroupPropertiesDialog('cluster', {
|
458
461
|
'collapsed': 'collapsed',
|
459
462
|
'ignore': 'ignore',
|
@@ -534,12 +537,18 @@ class GUIController extends Controller {
|
|
534
537
|
this.cc.addEventListener('drop', (event) => UI.drop(event));
|
535
538
|
|
536
539
|
// Disable dragging on all images.
|
537
|
-
const
|
538
|
-
|
539
|
-
|
540
|
-
for(let i = 0; i < imgs.length; i++) {
|
541
|
-
imgs[i].addEventListener('dragstart', nodrag);
|
540
|
+
const nodrag = (event) => { event.preventDefault(); return false; };
|
541
|
+
for(const img of document.getElementsByTagName('img')) {
|
542
|
+
img.addEventListener('dragstart', nodrag);
|
542
543
|
}
|
544
|
+
|
545
|
+
// Moving cursor over Linny-R logo etc. should display information
|
546
|
+
// in Information & Documentation manager.
|
547
|
+
const lrf = () => DOCUMENTATION_MANAGER.clearEntity(true);
|
548
|
+
document.getElementById('static-icon').addEventListener('mousemove', lrf);
|
549
|
+
document.getElementById('linny-r-name').addEventListener('mousemove', lrf);
|
550
|
+
document.getElementById('linny-r-version-number')
|
551
|
+
.addEventListener('mousemove', lrf);
|
543
552
|
|
544
553
|
// Make all buttons respond to a mouse click.
|
545
554
|
this.buttons['new'].addEventListener('click',
|
@@ -552,10 +561,8 @@ class GUIController extends Controller {
|
|
552
561
|
() => FILE_MANAGER.saveModel(event.shiftKey));
|
553
562
|
this.buttons.actors.addEventListener('click',
|
554
563
|
() => ACTOR_MANAGER.showDialog());
|
555
|
-
this.buttons.diagram.addEventListener('click',
|
556
|
-
() => FILE_MANAGER.renderDiagramAsPNG(event.shiftKey));
|
557
564
|
this.buttons.savediagram.addEventListener('click',
|
558
|
-
() => FILE_MANAGER.saveDiagramAsSVG(event
|
565
|
+
() => FILE_MANAGER.saveDiagramAsSVG(event));
|
559
566
|
this.buttons.receiver.addEventListener('click',
|
560
567
|
() => RECEIVER.toggle());
|
561
568
|
// NOTE: All draggable & resizable dialogs "toggle" show/hide.
|
@@ -651,11 +658,9 @@ class GUIController extends Controller {
|
|
651
658
|
() => AUTO_SAVE.getAutoSavedModels());
|
652
659
|
|
653
660
|
// Make "stay active" buttons respond to Shift-click.
|
654
|
-
const
|
655
|
-
|
656
|
-
|
657
|
-
for(let i = 0; i < tbs.length; i++) {
|
658
|
-
tbs[i].addEventListener('click', tf);
|
661
|
+
const tf = (event) => UI.toggleButton(event);
|
662
|
+
for(const tb of document.getElementsByClassName('toggle')) {
|
663
|
+
tb.addEventListener('click', tf);
|
659
664
|
}
|
660
665
|
|
661
666
|
// Add listeners to OK and CANCEL buttons on main modal dialogs.
|
@@ -857,18 +862,16 @@ class GUIController extends Controller {
|
|
857
862
|
|
858
863
|
// Make checkboxes respond to click.
|
859
864
|
// NOTE: Checkbox-specific events must be bound AFTER this general setting.
|
860
|
-
const
|
861
|
-
|
862
|
-
|
863
|
-
for(let i = 0; i < cbs.length; i++) {
|
864
|
-
cbs[i].addEventListener('click', cbf);
|
865
|
+
const cbf = (event) => UI.toggleBox(event);
|
866
|
+
for(const cb of document.getElementsByClassName('box')) {
|
867
|
+
cb.addEventListener('click', cbf);
|
865
868
|
}
|
866
|
-
// Make infoline respond to `mouseenter
|
869
|
+
// Make infoline respond to `mouseenter`.
|
867
870
|
this.info_line = document.getElementById('info-line');
|
868
871
|
this.info_line.addEventListener('mouseenter',
|
869
872
|
(event) => DOCUMENTATION_MANAGER.showInfoMessages(event.shiftKey));
|
870
873
|
// Ensure that all modal windows respond to ESCape
|
871
|
-
// (and more in general to other special keys)
|
874
|
+
// (and more in general to other special keys).
|
872
875
|
document.addEventListener('keydown', (event) => UI.checkModals(event));
|
873
876
|
}
|
874
877
|
|
@@ -993,8 +996,7 @@ class GUIController extends Controller {
|
|
993
996
|
|
994
997
|
drawLinkArrows(cluster, link) {
|
995
998
|
// Draw all arrows in `cluster` that represent `link`.
|
996
|
-
for(
|
997
|
-
const a = cluster.arrows[i];
|
999
|
+
for(const a of cluster.arrows) {
|
998
1000
|
if(a.links.indexOf(link) >= 0) this.paper.drawArrow(a);
|
999
1001
|
}
|
1000
1002
|
}
|
@@ -1011,8 +1013,7 @@ class GUIController extends Controller {
|
|
1011
1013
|
if(VM.server === 'local host') {
|
1012
1014
|
host.title = 'Linny-R directory is ' + VM.working_directory;
|
1013
1015
|
}
|
1014
|
-
for(
|
1015
|
-
const s = VM.solver_list[i];
|
1016
|
+
for(const s of VM.solver_list) {
|
1016
1017
|
html.push(['<option value="', s,
|
1017
1018
|
(s === VM.solver_id ? '"selected="selected' : ''),
|
1018
1019
|
'">', VM.solver_names[s], '</option>'].join(''));
|
@@ -1562,10 +1563,10 @@ class GUIController extends Controller {
|
|
1562
1563
|
|
1563
1564
|
reorderDialogs() {
|
1564
1565
|
// Set z-index of draggable dialogs according to their order
|
1565
|
-
// (most recently shown or clicked on top)
|
1566
|
+
// (most recently shown or clicked on top).
|
1566
1567
|
let z = 10;
|
1567
|
-
for(
|
1568
|
-
|
1568
|
+
for(const dd of this.dr_dialog_order) {
|
1569
|
+
dd.style.zIndex = z;
|
1569
1570
|
z += 5;
|
1570
1571
|
}
|
1571
1572
|
}
|
@@ -1575,18 +1576,16 @@ class GUIController extends Controller {
|
|
1575
1576
|
//
|
1576
1577
|
|
1577
1578
|
enableButtons(btns) {
|
1578
|
-
|
1579
|
-
|
1580
|
-
const b = document.getElementById(btns[i] + '-btn');
|
1579
|
+
for(const btn of btns.trim().split(/\s+/)) {
|
1580
|
+
const b = document.getElementById(btn + '-btn');
|
1581
1581
|
b.classList.remove('disab', 'activ');
|
1582
1582
|
b.classList.add('enab');
|
1583
1583
|
}
|
1584
1584
|
}
|
1585
1585
|
|
1586
1586
|
disableButtons(btns) {
|
1587
|
-
|
1588
|
-
|
1589
|
-
const b = document.getElementById(btns[i] + '-btn');
|
1587
|
+
for(const btn of btns.trim().split(/\s+/)) {
|
1588
|
+
const b = document.getElementById(btn + '-btn');
|
1590
1589
|
b.classList.remove('enab', 'activ', 'stay-activ');
|
1591
1590
|
b.classList.add('disab');
|
1592
1591
|
}
|
@@ -1598,7 +1597,7 @@ class GUIController extends Controller {
|
|
1598
1597
|
node_btns = 'process product link constraint cluster note ',
|
1599
1598
|
edit_btns = 'replace clone paste delete undo redo ',
|
1600
1599
|
model_btns = 'settings save actors dataset equation chart ' +
|
1601
|
-
'
|
1600
|
+
'savediagram finder monitor solve';
|
1602
1601
|
if(MODEL === null) {
|
1603
1602
|
this.disableButtons(node_btns + edit_btns + model_btns);
|
1604
1603
|
return;
|
@@ -1680,10 +1679,9 @@ class GUIController extends Controller {
|
|
1680
1679
|
}
|
1681
1680
|
|
1682
1681
|
get stayActiveButton() {
|
1683
|
-
// Return the button that is "stay active", or NULL if none
|
1684
|
-
const
|
1685
|
-
|
1686
|
-
const b = document.getElementById(btns[i] + '-btn');
|
1682
|
+
// Return the button that is "stay active", or NULL if none .
|
1683
|
+
for(const btn of ['process', 'product', 'link', 'constraint', 'cluster', 'note']) {
|
1684
|
+
const b = document.getElementById(btn + '-btn');
|
1687
1685
|
if(b.classList.contains('stay-activ')) return b;
|
1688
1686
|
}
|
1689
1687
|
return null;
|
@@ -1707,12 +1705,20 @@ class GUIController extends Controller {
|
|
1707
1705
|
//
|
1708
1706
|
|
1709
1707
|
updateCursorPosition(e) {
|
1710
|
-
//
|
1708
|
+
// Update the cursor coordinates, and display them on the status bar.
|
1711
1709
|
const cp = this.paper.cursorPosition(e.pageX, e.pageY);
|
1710
|
+
// Keep track of the cumulative relative movement since the last
|
1711
|
+
// mousedown event.
|
1712
|
+
this.net_move_x += cp[0] - this.mouse_x;
|
1713
|
+
this.net_move_y += cp[1] - this.mouse_y;
|
1714
|
+
// Only now update the mouse coordinates.
|
1712
1715
|
this.mouse_x = cp[0];
|
1713
1716
|
this.mouse_y = cp[1];
|
1717
|
+
// Show the coordinates on the status bar.
|
1714
1718
|
document.getElementById('pos-x').innerHTML = 'X = ' + this.mouse_x;
|
1715
|
-
document.getElementById('pos-y').innerHTML = 'Y = ' + this.mouse_y;
|
1719
|
+
document.getElementById('pos-y').innerHTML = 'Y = ' + this.mouse_y;
|
1720
|
+
// Reset all "object under cursor detection variables" so that they
|
1721
|
+
// will be re-established correctly by mouseMove.
|
1716
1722
|
this.on_note = null;
|
1717
1723
|
this.on_node = null;
|
1718
1724
|
this.on_cluster = null;
|
@@ -1723,76 +1729,82 @@ class GUIController extends Controller {
|
|
1723
1729
|
}
|
1724
1730
|
|
1725
1731
|
mouseMove(e) {
|
1726
|
-
//
|
1732
|
+
// Respond to mouse cursor moving over Linny-R diagram area.
|
1733
|
+
// First translate browser cursor coordinates to diagram coordinates.
|
1727
1734
|
this.updateCursorPosition(e);
|
1728
1735
|
|
1729
|
-
// NOTE:
|
1736
|
+
// NOTE: Prevent errors in case MODEL is still undefined.
|
1730
1737
|
if(!MODEL) return;
|
1731
1738
|
|
1732
1739
|
//console.log(e);
|
1733
1740
|
const fc = MODEL.focal_cluster;
|
1741
|
+
// NOTE: Proceed from last added to first added node.
|
1734
1742
|
for(let i = fc.processes.length-1; i >= 0; i--) {
|
1735
|
-
const
|
1736
|
-
if(
|
1737
|
-
this.on_node =
|
1743
|
+
const p = fc.processes[i];
|
1744
|
+
if(p.containsPoint(this.mouse_x, this.mouse_y)) {
|
1745
|
+
this.on_node = p;
|
1738
1746
|
break;
|
1739
1747
|
}
|
1740
1748
|
}
|
1741
1749
|
if(!this.on_node) {
|
1742
1750
|
for(let i = fc.product_positions.length-1; i >= 0; i--) {
|
1743
|
-
|
1744
|
-
|
1745
|
-
|
1751
|
+
// NOTE: Set product coordinates to its position in focal cluster.
|
1752
|
+
const p = fc.product_positions[i].product.setPositionInFocalCluster();
|
1753
|
+
if(p.product.containsPoint(this.mouse_x, this.mouse_y)) {
|
1754
|
+
this.on_node = p.product;
|
1746
1755
|
break;
|
1747
1756
|
}
|
1748
1757
|
}
|
1749
1758
|
}
|
1750
|
-
for(
|
1751
|
-
const arr = fc.arrows[i];
|
1759
|
+
for(const arr of fc.arrows) {
|
1752
1760
|
if(arr) {
|
1753
1761
|
this.on_arrow = arr;
|
1754
|
-
// NOTE:
|
1755
|
-
|
1756
|
-
|
1757
|
-
|
1762
|
+
// NOTE: Arrow may represent multiple links, and `containsPoint`
|
1763
|
+
// returns the link if this can be established unambiguously, or
|
1764
|
+
// NULL otherwise.
|
1765
|
+
const l = arr.containsPoint(this.mouse_x, this.mouse_y);
|
1766
|
+
if(l) {
|
1767
|
+
this.on_link = l;
|
1758
1768
|
break;
|
1759
1769
|
}
|
1760
1770
|
}
|
1761
1771
|
}
|
1762
1772
|
this.on_constraint = this.constraintStillUnderCursor();
|
1763
1773
|
if(fc.related_constraints != null) {
|
1764
|
-
for(
|
1765
|
-
|
1766
|
-
|
1767
|
-
this.on_constraint = obj;
|
1774
|
+
for(const c of fc.related_constraints) {
|
1775
|
+
if(c.containsPoint(this.mouse_x, this.mouse_y)) {
|
1776
|
+
this.on_constraint = c;
|
1768
1777
|
break;
|
1769
1778
|
}
|
1770
1779
|
}
|
1771
1780
|
}
|
1772
1781
|
for(let i = fc.sub_clusters.length-1; i >= 0; i--) {
|
1773
|
-
const
|
1774
|
-
|
1775
|
-
|
1776
|
-
|
1777
|
-
|
1778
|
-
|
1779
|
-
|
1780
|
-
|
1782
|
+
const c = fc.sub_clusters[i];
|
1783
|
+
if(c.containsPoint(this.mouse_x, this.mouse_y)) {
|
1784
|
+
// NOTE: Cluster that is being dragged is superseded by other clusters
|
1785
|
+
// so that a cluster it is being dragged over will be detected instead.
|
1786
|
+
if(!this.on_cluster || c !== this.dragged_node) {
|
1787
|
+
this.on_cluster = c;
|
1788
|
+
// NOTE: Cluster edge responds differently to doubble-click.
|
1789
|
+
this.on_cluster_edge = c.onEdge(this.mouse_x, this.mouse_y);
|
1790
|
+
}
|
1781
1791
|
}
|
1782
1792
|
}
|
1783
1793
|
// Unset and redraw target cluster if cursor no longer over it.
|
1784
|
-
if(
|
1794
|
+
if(this.on_cluster !== this.target_cluster) {
|
1785
1795
|
const c = this.target_cluster;
|
1786
1796
|
this.target_cluster = null;
|
1787
|
-
|
1788
|
-
|
1789
|
-
|
1790
|
-
|
1797
|
+
if(c) {
|
1798
|
+
UI.paper.drawCluster(c);
|
1799
|
+
// NOTE: Element is persistent, so semi-transparency must also be
|
1800
|
+
// undone.
|
1801
|
+
c.shape.element.setAttribute('opacity', 1);
|
1802
|
+
}
|
1791
1803
|
}
|
1792
1804
|
for(let i = fc.notes.length-1; i >= 0; i--) {
|
1793
|
-
const
|
1794
|
-
if(
|
1795
|
-
this.on_note =
|
1805
|
+
const n = fc.notes[i];
|
1806
|
+
if(n.containsPoint(this.mouse_x, this.mouse_y)) {
|
1807
|
+
this.on_note = n;
|
1796
1808
|
break;
|
1797
1809
|
}
|
1798
1810
|
}
|
@@ -1857,10 +1869,12 @@ class GUIController extends Controller {
|
|
1857
1869
|
this.setMessage('');
|
1858
1870
|
}
|
1859
1871
|
}
|
1860
|
-
// When dragging selection
|
1872
|
+
// When dragging a selection over a cluster, change cursor to "cell" to
|
1861
1873
|
// indicate that selected process(es) will be moved into the cluster.
|
1862
1874
|
if(this.dragged_node) {
|
1863
|
-
|
1875
|
+
// NOTE: Cursor will always be over the dragged node, so do not indicate
|
1876
|
+
// "drop here?" unless dragged over a different cluster.
|
1877
|
+
if(this.on_cluster && this.on_cluster !== this.dragged_node) {
|
1864
1878
|
cr = 'cell';
|
1865
1879
|
this.target_cluster = this.on_cluster;
|
1866
1880
|
// Redraw the target cluster so it will appear on top (and highlighted).
|
@@ -1874,10 +1888,16 @@ class GUIController extends Controller {
|
|
1874
1888
|
}
|
1875
1889
|
|
1876
1890
|
mouseDown(e) {
|
1877
|
-
//
|
1878
|
-
//
|
1879
|
-
//
|
1891
|
+
// Respond to mousedown event in model diagram area.
|
1892
|
+
// NOTE: While dragging the selection rectangle, the mouseup event will
|
1893
|
+
// not be observed when it occurred outside the drawing area. In such
|
1894
|
+
// cases, the mousedown event must be ignored so that only the mouseup
|
1895
|
+
// will be processed.
|
1880
1896
|
if(this.start_sel_x >= 0 && this.start_sel_y >= 0) return;
|
1897
|
+
// Reset the cumulative movement since mousedown.
|
1898
|
+
this.net_move_x = 0;
|
1899
|
+
this.net_move_y = 0;
|
1900
|
+
// Get the paper coordinates indicated by the cursor.
|
1881
1901
|
const cp = this.paper.cursorPosition(e.pageX, e.pageY);
|
1882
1902
|
this.mouse_down_x = cp[0];
|
1883
1903
|
this.mouse_down_y = cp[1];
|
@@ -1891,7 +1911,7 @@ class GUIController extends Controller {
|
|
1891
1911
|
}
|
1892
1912
|
// NOTE: Only left button is detected (browser catches right menu button).
|
1893
1913
|
if(e.ctrlKey) {
|
1894
|
-
// Remove clicked item from selection
|
1914
|
+
// Remove clicked item from selection.
|
1895
1915
|
if(MODEL.selection) {
|
1896
1916
|
// NOTE: First check constraints -- see mouseMove() for motivation.
|
1897
1917
|
if(this.on_constraint) {
|
@@ -1943,37 +1963,15 @@ class GUIController extends Controller {
|
|
1943
1963
|
UI.drawDiagram(MODEL);
|
1944
1964
|
}
|
1945
1965
|
|
1946
|
-
// If one of the top six sidebar buttons is active, prompt for new node
|
1947
|
-
//
|
1966
|
+
// If one of the top six sidebar buttons is active, prompt for new node.
|
1967
|
+
// Note that this does not apply for links or constraints.
|
1948
1968
|
if(this.active_button && this.active_button !== this.buttons.link &&
|
1949
1969
|
this.active_button !== this.buttons.constraint) {
|
1950
1970
|
this.add_x = this.mouse_x;
|
1951
1971
|
this.add_y = this.mouse_y;
|
1952
|
-
const
|
1972
|
+
const ot = this.active_button.id.split('-')[0];
|
1953
1973
|
if(!this.stayActive) this.resetActiveButton();
|
1954
|
-
if(
|
1955
|
-
setTimeout(() => {
|
1956
|
-
const md = UI.modals['add-process'];
|
1957
|
-
md.element('name').value = '';
|
1958
|
-
md.element('actor').value = '';
|
1959
|
-
md.show('name');
|
1960
|
-
});
|
1961
|
-
} else if(obj === 'product') {
|
1962
|
-
setTimeout(() => {
|
1963
|
-
const md = UI.modals['add-product'];
|
1964
|
-
md.element('name').value = '';
|
1965
|
-
md.element('unit').value = MODEL.default_unit;
|
1966
|
-
UI.setBox('add-product-data', false);
|
1967
|
-
md.show('name');
|
1968
|
-
});
|
1969
|
-
} else if(obj === 'cluster') {
|
1970
|
-
setTimeout(() => {
|
1971
|
-
const md = UI.modals.cluster;
|
1972
|
-
md.element('name').value = '';
|
1973
|
-
md.element('actor').value = '';
|
1974
|
-
md.show('name');
|
1975
|
-
});
|
1976
|
-
} else if(obj === 'note') {
|
1974
|
+
if(ot === 'note') {
|
1977
1975
|
setTimeout(() => {
|
1978
1976
|
const md = UI.modals.note;
|
1979
1977
|
md.element('action').innerHTML = 'Add';
|
@@ -1981,6 +1979,33 @@ class GUIController extends Controller {
|
|
1981
1979
|
md.element('text').value = '';
|
1982
1980
|
md.show('text');
|
1983
1981
|
});
|
1982
|
+
} else {
|
1983
|
+
// Align position to the grid.
|
1984
|
+
this.add_x = MODEL.aligned(this.add_x);
|
1985
|
+
this.add_y = MODEL.aligned(this.add_y);
|
1986
|
+
if(ot === 'process') {
|
1987
|
+
setTimeout(() => {
|
1988
|
+
const md = UI.modals['add-process'];
|
1989
|
+
md.element('name').value = '';
|
1990
|
+
md.element('actor').value = '';
|
1991
|
+
md.show('name');
|
1992
|
+
});
|
1993
|
+
} else if(ot === 'product') {
|
1994
|
+
setTimeout(() => {
|
1995
|
+
const md = UI.modals['add-product'];
|
1996
|
+
md.element('name').value = '';
|
1997
|
+
md.element('unit').value = MODEL.default_unit;
|
1998
|
+
UI.setBox('add-product-data', false);
|
1999
|
+
md.show('name');
|
2000
|
+
});
|
2001
|
+
} else if(ot === 'cluster') {
|
2002
|
+
setTimeout(() => {
|
2003
|
+
const md = UI.modals.cluster;
|
2004
|
+
md.element('name').value = '';
|
2005
|
+
md.element('actor').value = '';
|
2006
|
+
md.show('name');
|
2007
|
+
});
|
2008
|
+
}
|
1984
2009
|
}
|
1985
2010
|
return;
|
1986
2011
|
}
|
@@ -2020,7 +2045,7 @@ class GUIController extends Controller {
|
|
2020
2045
|
} else if(this.on_node) {
|
2021
2046
|
if(this.active_button === this.buttons.link) {
|
2022
2047
|
this.linking_node = this.on_node;
|
2023
|
-
// NOTE:
|
2048
|
+
// NOTE: Return without updating buttons.
|
2024
2049
|
return;
|
2025
2050
|
} else if(this.active_button === this.buttons.constraint) {
|
2026
2051
|
// Allow constraints only on nodes having upper bounds defined.
|
@@ -2031,6 +2056,7 @@ class GUIController extends Controller {
|
|
2031
2056
|
}
|
2032
2057
|
} else {
|
2033
2058
|
this.dragged_node = this.on_node;
|
2059
|
+
// NOTE: Keep track of relative movement of the dragged node.
|
2034
2060
|
this.move_dx = this.mouse_x - this.on_node.x;
|
2035
2061
|
this.move_dy = this.mouse_y - this.on_node.y;
|
2036
2062
|
if(MODEL.selection.indexOf(this.on_node) < 0) MODEL.select(this.on_node);
|
@@ -2055,6 +2081,10 @@ class GUIController extends Controller {
|
|
2055
2081
|
mouseUp(e) {
|
2056
2082
|
// Responds to mouseup event.
|
2057
2083
|
const cp = this.paper.cursorPosition(e.pageX, e.pageY);
|
2084
|
+
// Keep track of the cumulative relative movement since the last
|
2085
|
+
// mousedown event.
|
2086
|
+
this.net_move_x += cp[0] - this.mouse_x;
|
2087
|
+
this.net_move_y += cp[1] - this.mouse_y;
|
2058
2088
|
this.mouse_up_x = cp[0];
|
2059
2089
|
this.mouse_up_y = cp[1];
|
2060
2090
|
// First check whether user is selecting a rectangle.
|
@@ -2071,44 +2101,32 @@ class GUIController extends Controller {
|
|
2071
2101
|
// If rectangle has size greater than 2x2 pixels, select all elements
|
2072
2102
|
// having their center inside the selection rectangle.
|
2073
2103
|
if(brx - tlx > 2 && bry - tly > 2) {
|
2074
|
-
const
|
2075
|
-
|
2076
|
-
|
2077
|
-
|
2078
|
-
|
2079
|
-
}
|
2104
|
+
const
|
2105
|
+
ol = [],
|
2106
|
+
fc = MODEL.focal_cluster;
|
2107
|
+
for(const p of fc.processes) {
|
2108
|
+
if(p.x >= tlx && p.x <= brx && p.y >= tly && p.y < bry) ol.push(p);
|
2080
2109
|
}
|
2081
|
-
for(
|
2082
|
-
|
2083
|
-
|
2084
|
-
ol.push(obj.product);
|
2110
|
+
for(const pp of fc.product_positions) {
|
2111
|
+
if(pp.x >= tlx && pp.x <= brx && pp.y >= tly && pp.y < bry) {
|
2112
|
+
ol.push(pp.product);
|
2085
2113
|
}
|
2086
2114
|
}
|
2087
|
-
for(
|
2088
|
-
|
2089
|
-
if(obj.x >= tlx && obj.x <= brx && obj.y >= tly && obj.y < bry) {
|
2090
|
-
ol.push(obj);
|
2091
|
-
}
|
2115
|
+
for(const c of fc.sub_clusters) {
|
2116
|
+
if(c.x >= tlx && c.x <= brx && c.y >= tly && c.y < bry) ol.push(c);
|
2092
2117
|
}
|
2093
|
-
for(
|
2094
|
-
|
2095
|
-
if(obj.x >= tlx && obj.x <= brx && obj.y >= tly && obj.y < bry) {
|
2096
|
-
ol.push(obj);
|
2097
|
-
}
|
2118
|
+
for(const n of fc.notes) {
|
2119
|
+
if(n.x >= tlx && n.x <= brx && n.y >= tly && n.y < bry) ol.push(n);
|
2098
2120
|
}
|
2099
|
-
for(let
|
2100
|
-
const
|
2121
|
+
for(let k in MODEL.links) if(MODEL.links.hasOwnProperty(k)) {
|
2122
|
+
const l = MODEL.links[k];
|
2101
2123
|
// Only add a link if both its nodes are selected as well.
|
2102
|
-
if(fc.linkInList(
|
2103
|
-
ol.push(obj);
|
2104
|
-
}
|
2124
|
+
if(fc.linkInList(l, ol)) ol.push(l);
|
2105
2125
|
}
|
2106
|
-
for(let
|
2107
|
-
const
|
2126
|
+
for(let k in MODEL.constraints) if(MODEL.constraints.hasOwnProperty(k)) {
|
2127
|
+
const c = MODEL.constraints[k];
|
2108
2128
|
// Only add a constraint if both its nodes are selected as well.
|
2109
|
-
if(fc.linkInList(
|
2110
|
-
ol.push(obj);
|
2111
|
-
}
|
2129
|
+
if(fc.linkInList(c, ol)) ol.push(c);
|
2112
2130
|
}
|
2113
2131
|
// Having compiled the object list, actually select them.
|
2114
2132
|
MODEL.selectList(ol);
|
@@ -2123,9 +2141,9 @@ class GUIController extends Controller {
|
|
2123
2141
|
} else if(this.linking_node) {
|
2124
2142
|
// If so, check whether the cursor is over a node of the appropriate type.
|
2125
2143
|
if(this.on_node && MODEL.canLink(this.linking_node, this.on_node)) {
|
2126
|
-
const
|
2127
|
-
UNDO_STACK.push('add',
|
2128
|
-
MODEL.select(
|
2144
|
+
const l = MODEL.addLink(this.linking_node, this.on_node);
|
2145
|
+
UNDO_STACK.push('add', l);
|
2146
|
+
MODEL.select(l);
|
2129
2147
|
this.paper.drawModel(MODEL);
|
2130
2148
|
}
|
2131
2149
|
this.linking_node = null;
|
@@ -2149,31 +2167,25 @@ class GUIController extends Controller {
|
|
2149
2167
|
// Then check whether the user is moving a node (possibly part of a
|
2150
2168
|
// larger selection).
|
2151
2169
|
} else if(this.dragged_node) {
|
2152
|
-
//
|
2153
|
-
//
|
2154
|
-
|
2155
|
-
|
2156
|
-
// Set cursor to pointer, as it should be on some node while dragging.
|
2157
|
-
this.paper.container.style.cursor = 'pointer';
|
2158
|
-
// @@TO DO: if on top of a cluster, move it there.
|
2159
|
-
// NOTE: Cursor will always be over the selected cluster (while dragging).
|
2160
|
-
if(this.on_cluster && !this.on_cluster.selected) {
|
2161
|
-
UNDO_STACK.push('drop', this.on_cluster);
|
2162
|
-
MODEL.dropSelectionIntoCluster(this.on_cluster);
|
2163
|
-
this.on_node = null;
|
2164
|
-
this.on_note = null;
|
2165
|
-
this.target_cluster = null;
|
2166
|
-
// Redraw cluster to erase its orange "target corona".
|
2167
|
-
UI.paper.drawCluster(this.on_cluster);
|
2168
|
-
}
|
2169
|
-
|
2170
|
-
// Check wether the cursor has been moved.
|
2170
|
+
// NOTE: When double-clicking with a sensitive mouse, the cursor
|
2171
|
+
// may move a few pixels, and then this should NOT be considered
|
2172
|
+
// as an intentional move. Hence, check wether the cursor has been
|
2173
|
+
// moved *significantly* since the mouseDown event.
|
2171
2174
|
const
|
2172
|
-
|
2173
|
-
|
2174
|
-
|
2175
|
-
|
2176
|
-
|
2175
|
+
mdx = this.mouse_down_x - this.mouse_x,
|
2176
|
+
mdy = this.mouse_down_y - this.mouse_y,
|
2177
|
+
absdx = Math.abs(this.net_move_x),
|
2178
|
+
absdy = Math.abs(this.net_move_y),
|
2179
|
+
sigmv = (MODEL.align_to_grid ? MODEL.grid_pixels / 4 : 2.5);
|
2180
|
+
if(this.doubleClicked) {
|
2181
|
+
// Ignore insignificant move.
|
2182
|
+
if(absdx < sigmv && absdy < sigmv) {
|
2183
|
+
// Undo the move and remove the action from the UNDO-stack.
|
2184
|
+
// NOTE: Do not use the regular `undo` routine as this would
|
2185
|
+
// make the action redoable.
|
2186
|
+
MODEL.moveSelection(mdx, mdy);
|
2187
|
+
UNDO_STACK.pop('move');
|
2188
|
+
}
|
2177
2189
|
// Double-clicking opens properties dialog, except for clusters;
|
2178
2190
|
// then "drill down", i.e., make the double-clicked cluster focal.
|
2179
2191
|
if(this.dragged_node instanceof Cluster) {
|
@@ -2197,6 +2209,30 @@ class GUIController extends Controller {
|
|
2197
2209
|
} else {
|
2198
2210
|
this.showNotePropertiesDialog(this.dragged_node);
|
2199
2211
|
}
|
2212
|
+
} else {
|
2213
|
+
// Move the selection, even if the movement is very small, because the
|
2214
|
+
// final movement since last mouse event may make the *cumulative*
|
2215
|
+
// movement since the last mouseDown significant.
|
2216
|
+
MODEL.moveSelection(
|
2217
|
+
this.mouse_up_x - this.mouse_x, this.mouse_up_y - this.mouse_y);
|
2218
|
+
if(this.net_move_x < 0.5 && this.net_move_y < 0.5) {
|
2219
|
+
// No effective move of the selection => remove the UNDO.
|
2220
|
+
UNDO_STACK.pop('move');
|
2221
|
+
}
|
2222
|
+
// Set cursor to pointer, as it should be on some node while dragging.
|
2223
|
+
this.paper.container.style.cursor = 'pointer';
|
2224
|
+
// NOTE: Cursor will always be over the selected cluster (while dragging).
|
2225
|
+
if(this.on_cluster && !this.on_cluster.selected) {
|
2226
|
+
UNDO_STACK.push('drop', this.on_cluster);
|
2227
|
+
MODEL.dropSelectionIntoCluster(this.on_cluster);
|
2228
|
+
this.on_node = null;
|
2229
|
+
this.on_note = null;
|
2230
|
+
this.target_cluster = null;
|
2231
|
+
// Redraw cluster to erase its orange "target corona".
|
2232
|
+
UI.paper.drawCluster(this.on_cluster);
|
2233
|
+
}
|
2234
|
+
// Only now align to grid.
|
2235
|
+
MODEL.alignToGrid();
|
2200
2236
|
}
|
2201
2237
|
this.dragged_node = null;
|
2202
2238
|
|
@@ -2210,6 +2246,8 @@ class GUIController extends Controller {
|
|
2210
2246
|
this.showConstraintPropertiesDialog(this.on_constraint);
|
2211
2247
|
}
|
2212
2248
|
}
|
2249
|
+
// Finally, reset "selecting with rectangle" (just to be sure), and
|
2250
|
+
// update the UI button states.
|
2213
2251
|
this.start_sel_x = -1;
|
2214
2252
|
this.start_sel_y = -1;
|
2215
2253
|
this.updateButtons();
|
@@ -2253,9 +2291,8 @@ class GUIController extends Controller {
|
|
2253
2291
|
topmod = null,
|
2254
2292
|
code = e.code,
|
2255
2293
|
alt = e.altKey;
|
2256
|
-
for(
|
2294
|
+
for(const m of modals) {
|
2257
2295
|
const
|
2258
|
-
m = modals[i],
|
2259
2296
|
cs = window.getComputedStyle(m),
|
2260
2297
|
z = parseInt(cs.zIndex);
|
2261
2298
|
if(cs.display !== 'none' && z > maxz) {
|
@@ -2569,7 +2606,7 @@ class GUIController extends Controller {
|
|
2569
2606
|
validNames(nn, an='') {
|
2570
2607
|
// Check whether names meet conventions; if not, warn user
|
2571
2608
|
if(!UI.validName(nn) || nn.indexOf(UI.BLACK_BOX) >= 0) {
|
2572
|
-
|
2609
|
+
this.warningInvalidName(nn);
|
2573
2610
|
return false;
|
2574
2611
|
}
|
2575
2612
|
if(an === '' || an === UI.NO_ACTOR) return true;
|
@@ -2627,12 +2664,12 @@ class GUIController extends Controller {
|
|
2627
2664
|
}
|
2628
2665
|
|
2629
2666
|
updateScaleUnitList() {
|
2630
|
-
// Update the HTML datalist element to reflect all scale units
|
2667
|
+
// Update the HTML datalist element to reflect all scale units.
|
2631
2668
|
const
|
2632
2669
|
ul = [],
|
2633
2670
|
keys = Object.keys(MODEL.scale_units).sort(ciCompare);
|
2634
|
-
for(
|
2635
|
-
ul.push(`<option value="${MODEL.scale_units[
|
2671
|
+
for(const k of keys) {
|
2672
|
+
ul.push(`<option value="${MODEL.scale_units[k].name}">`);
|
2636
2673
|
}
|
2637
2674
|
document.getElementById('units-data').innerHTML = ul.join('');
|
2638
2675
|
}
|
@@ -3001,6 +3038,7 @@ class GUIController extends Controller {
|
|
3001
3038
|
const vn = this.validName(nn);
|
3002
3039
|
if(!vn) {
|
3003
3040
|
UNDO_STACK.pop();
|
3041
|
+
this.warningInvalidName(nn);
|
3004
3042
|
return false;
|
3005
3043
|
}
|
3006
3044
|
// NOTE: Pre-check if product exists.
|
@@ -3229,8 +3267,8 @@ class GUIController extends Controller {
|
|
3229
3267
|
if(elig.length) {
|
3230
3268
|
sl.push('<div class="paste-select"><select id="paste-ft-', i,
|
3231
3269
|
'" style="font-size: 12px">');
|
3232
|
-
for(
|
3233
|
-
const dn =
|
3270
|
+
for(const e of elig) {
|
3271
|
+
const dn = e.displayName;
|
3234
3272
|
sl.push('<option value="', dn, '">', dn, '</option>');
|
3235
3273
|
}
|
3236
3274
|
sl.push('</select></div>');
|
@@ -3404,8 +3442,7 @@ class GUIController extends Controller {
|
|
3404
3442
|
function nameConflicts(node) {
|
3405
3443
|
// Maps names of entities defined by the child nodes of `node`
|
3406
3444
|
// while detecting name conflicts.
|
3407
|
-
for(
|
3408
|
-
const c = node.childNodes[i];
|
3445
|
+
for(const c of node.childNodes) {
|
3409
3446
|
if(c.nodeName !== 'link' && c.nodeName !== 'constraint') {
|
3410
3447
|
const
|
3411
3448
|
fn = fullName(c),
|
@@ -3505,9 +3542,8 @@ class GUIController extends Controller {
|
|
3505
3542
|
// Prompt for names of selected cluster nodes.
|
3506
3543
|
if(selc_node.childNodes.length && !mapping.prefix) {
|
3507
3544
|
mapping.top_clusters = {};
|
3508
|
-
for(
|
3545
|
+
for(const c of selc_node.childNodes) {
|
3509
3546
|
const
|
3510
|
-
c = selc_node.childNodes[i],
|
3511
3547
|
fn = fullName(c),
|
3512
3548
|
mn = mappedName(fn);
|
3513
3549
|
mapping.top_clusters[fn] = mn;
|
@@ -3522,9 +3558,8 @@ class GUIController extends Controller {
|
|
3522
3558
|
const
|
3523
3559
|
ft_map = {},
|
3524
3560
|
ft_type = {};
|
3525
|
-
for(
|
3561
|
+
for(const c of from_tos_node.childNodes) {
|
3526
3562
|
const
|
3527
|
-
c = from_tos_node.childNodes[i],
|
3528
3563
|
fn = fullName(c),
|
3529
3564
|
mn = mappedName(fn);
|
3530
3565
|
if(MODEL.objectByName(mn)) {
|
@@ -3554,20 +3589,14 @@ console.log('HERE name conflicts', name_conflicts, mapping);
|
|
3554
3589
|
}
|
3555
3590
|
|
3556
3591
|
// No conflicts => add all
|
3557
|
-
for(
|
3558
|
-
|
3559
|
-
|
3560
|
-
for(let i = 0; i < from_tos_node.childNodes.length; i++) {
|
3561
|
-
addEntityFromNode(from_tos_node.childNodes[i]);
|
3562
|
-
}
|
3563
|
-
for(let i = 0; i < entities_node.childNodes.length; i++) {
|
3564
|
-
addEntityFromNode(entities_node.childNodes[i]);
|
3565
|
-
}
|
3592
|
+
for(const c of extras_node.childNodes) addEntityFromNode(c);
|
3593
|
+
for(const c of from_tos_node.childNodes) addEntityFromNode(c);
|
3594
|
+
for(const c of entities_node.childNodes) addEntityFromNode(c);
|
3566
3595
|
// Update diagram, showing newly added nodes as selection.
|
3567
3596
|
MODEL.clearSelection();
|
3568
|
-
for(
|
3597
|
+
for(const c of selection_node.childNodes) {
|
3569
3598
|
const
|
3570
|
-
n = xmlDecoded(nodeContent(
|
3599
|
+
n = xmlDecoded(nodeContent(c)),
|
3571
3600
|
obj = MODEL.objectByName(mappedName(n));
|
3572
3601
|
if(obj) {
|
3573
3602
|
// NOTE: Selected products must be positioned.
|
@@ -3681,6 +3710,12 @@ console.log('HERE name conflicts', name_conflicts, mapping);
|
|
3681
3710
|
cb = UI.boxChecked('settings-power');
|
3682
3711
|
redraw = redraw || cb !== model.with_power_flow;
|
3683
3712
|
model.with_power_flow = cb;
|
3713
|
+
// NOTE: Clear the "ignore" options if no power flow constraints.
|
3714
|
+
if(!model.with_power_flow) {
|
3715
|
+
model.ignore_grid_capacity = false;
|
3716
|
+
model.ignore_KVL = false;
|
3717
|
+
model.ignore_power_losses = false;
|
3718
|
+
}
|
3684
3719
|
cb = UI.boxChecked('settings-cost-prices');
|
3685
3720
|
redraw = redraw || cb !== model.infer_cost_prices;
|
3686
3721
|
model.infer_cost_prices = cb;
|
@@ -3749,8 +3784,7 @@ console.log('HERE name conflicts', name_conflicts, mapping);
|
|
3749
3784
|
const
|
3750
3785
|
md = this.modals.solver,
|
3751
3786
|
html = ['<option value="">(default)</option>'];
|
3752
|
-
for(
|
3753
|
-
const s = VM.solver_list[i];
|
3787
|
+
for(const s of VM.solver_list) {
|
3754
3788
|
html.push(['<option value="', s,
|
3755
3789
|
(s === MODEL.preferred_solver ? '"selected="selected' : ''),
|
3756
3790
|
'">', VM.solver_names[s], '</option>'].join(''));
|
@@ -3885,17 +3919,13 @@ console.log('HERE name conflicts', name_conflicts, mapping);
|
|
3885
3919
|
plate.innerHTML = pg.voltage;
|
3886
3920
|
overlay.style.display = 'block';
|
3887
3921
|
// Disable tab stop for the properties that are now not shown.
|
3888
|
-
for(
|
3889
|
-
md.element(notab[i]).tabIndex = -1;
|
3890
|
-
}
|
3922
|
+
for(const nt of notab) md.element(nt).tabIndex = -1;
|
3891
3923
|
} else {
|
3892
3924
|
plate.innerHTML = '(↯)';
|
3893
3925
|
plate.className = 'no-grid-plate';
|
3894
3926
|
overlay.style.display = 'none';
|
3895
3927
|
// Enable tab stop for the properties that are now not shown.
|
3896
|
-
for(
|
3897
|
-
md.element(notab[i]).tabIndex = 0;
|
3898
|
-
}
|
3928
|
+
for(const nt of notab) md.element(nt).tabIndex = 0;
|
3899
3929
|
}
|
3900
3930
|
this.hideGridPlateMenu('process');
|
3901
3931
|
// Show plate "button" only when power grids option is set for model.
|
@@ -4131,9 +4161,7 @@ console.log('HERE name conflicts', name_conflicts, mapping);
|
|
4131
4161
|
|
4132
4162
|
showClusterPropertiesDialog(c, group=[]) {
|
4133
4163
|
let bb = false;
|
4134
|
-
for(
|
4135
|
-
bb = group[i].is_black_boxed;
|
4136
|
-
}
|
4164
|
+
for(const g of group) bb = bb || g.is_black_boxed;
|
4137
4165
|
if(bb || c.is_black_boxed) {
|
4138
4166
|
this.notify('Black-boxed clusters cannot be edited');
|
4139
4167
|
return;
|
@@ -4375,31 +4403,27 @@ console.log('HERE name conflicts', name_conflicts, mapping);
|
|
4375
4403
|
this.drawObject(p);
|
4376
4404
|
// Make list of nodes related to P by links
|
4377
4405
|
const rel_nodes = [];
|
4378
|
-
for(
|
4379
|
-
|
4380
|
-
}
|
4381
|
-
for(let i = 0; i < p.outputs.length; i++) {
|
4382
|
-
rel_nodes.push(p.outputs[i].to_node);
|
4383
|
-
}
|
4406
|
+
for(const l of p.inputs) rel_nodes.push(l.from_node);
|
4407
|
+
for(const l of p.outputs) rel_nodes.push(l.to_node);
|
4384
4408
|
const options = [];
|
4385
|
-
for(let
|
4409
|
+
for(let k in MODEL.products) if(MODEL.products.hasOwnProperty(k) &&
|
4386
4410
|
// NOTE: do not show "black-boxed" products
|
4387
|
-
!
|
4388
|
-
const po = MODEL.products[
|
4411
|
+
!k.startsWith(UI.BLACK_BOX)) {
|
4412
|
+
const po = MODEL.products[k];
|
4389
4413
|
// Skip the product that is to be replaced, an also products having a
|
4390
4414
|
// different type (regular product or data product)
|
4391
4415
|
if(po !== p && po.is_data === p.is_data) {
|
4392
4416
|
// NOTE: also skip products PO that are linked to a node Q that is
|
4393
4417
|
// already linked to P (as replacing would then create a two-way link)
|
4394
4418
|
let no_rel = true;
|
4395
|
-
for(
|
4396
|
-
if(rel_nodes.indexOf(
|
4419
|
+
for(const l of po.inputs) {
|
4420
|
+
if(rel_nodes.indexOf(l.from_node) >= 0) {
|
4397
4421
|
no_rel = false;
|
4398
4422
|
break;
|
4399
4423
|
}
|
4400
4424
|
}
|
4401
|
-
for(
|
4402
|
-
if(rel_nodes.indexOf(
|
4425
|
+
for(const l of po.outputs) {
|
4426
|
+
if(rel_nodes.indexOf(l.to_node) >= 0) {
|
4403
4427
|
no_rel = false;
|
4404
4428
|
break;
|
4405
4429
|
}
|