linny-r 1.8.0 → 1.8.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/package.json
CHANGED
package/static/index.html
CHANGED
@@ -375,7 +375,7 @@ and move the cursor over the status bar">
|
|
375
375
|
<img id="redo-btn" class="btn disab" src="images/redo.png"
|
376
376
|
title="Redo (Ctrl-Y)">
|
377
377
|
<img id="solve-btn" class="btn enab" src="images/solve.png"
|
378
|
-
title="Run simulation (Ctrl-R)">
|
378
|
+
title="Run simulation (Ctrl-R) – Alt-click to diagnose infeasible/unbounded problem (Alt-R)">
|
379
379
|
<img id="stop-btn" class="btn enab off" src="images/stop.png"
|
380
380
|
title="Stop solving (Ctrl-Q)">
|
381
381
|
<img id="reset-btn" class="btn enab off" src="images/reset.png"
|
@@ -576,11 +576,9 @@ and move the cursor over the status bar">
|
|
576
576
|
<table style="width:100%; white-space: nowrap">
|
577
577
|
<tr>
|
578
578
|
<td style="padding:0px">
|
579
|
-
<div id="settings-
|
580
|
-
</td>
|
581
|
-
<td style="padding-bottom:4px">
|
582
|
-
Use decimal <u>comma</u> when copying data to clipboard
|
579
|
+
<div id="settings-block-arrows" class="box clear"></div>
|
583
580
|
</td>
|
581
|
+
<td style="padding-bottom:4px">Show hidden flows as block arrows</td>
|
584
582
|
</tr>
|
585
583
|
<tr>
|
586
584
|
<td style="padding:0px">
|
@@ -632,25 +630,34 @@ and move the cursor over the status bar">
|
|
632
630
|
title="Set solver preferences">
|
633
631
|
</td>
|
634
632
|
</tr>
|
633
|
+
<tr title="When checked, finite process bounds and slack variables are always added">
|
634
|
+
<td style="padding:0px">
|
635
|
+
<div id="settings-diagnose" class="box clear"></div>
|
636
|
+
</td>
|
637
|
+
<td style="padding-bottom:4px">Diagnose infeasible/unbounded problems</td>
|
638
|
+
</td>
|
639
|
+
</tr>
|
635
640
|
<tr>
|
636
641
|
<td style="padding:0px">
|
637
642
|
<div id="settings-cost-prices" class="box clear"></div>
|
638
643
|
</td>
|
639
644
|
<td style="padding-bottom:4px">Infer and display cost prices</td>
|
640
645
|
</tr>
|
641
|
-
<tr
|
646
|
+
<tr>
|
642
647
|
<td style="padding:0px">
|
643
|
-
<div id="settings-
|
648
|
+
<div id="settings-decimal-comma" class="box clear"></div>
|
644
649
|
</td>
|
645
650
|
<td style="padding-bottom:4px">
|
646
|
-
|
651
|
+
Use decimal <u>comma</u> when copying data to clipboard
|
647
652
|
</td>
|
648
653
|
</tr>
|
649
|
-
<tr>
|
654
|
+
<tr title="Reports will be saved in user/reports/, and removed after 24 h">
|
650
655
|
<td style="padding:0px">
|
651
|
-
<div id="settings-
|
656
|
+
<div id="settings-report-results" class="box clear"></div>
|
657
|
+
</td>
|
658
|
+
<td style="padding-bottom:4px">
|
659
|
+
Report results after each run
|
652
660
|
</td>
|
653
|
-
<td style="padding-bottom:4px">Show hidden flows as block arrows</td>
|
654
661
|
</tr>
|
655
662
|
<tr>
|
656
663
|
<td style="padding:0px">
|
@@ -743,13 +750,6 @@ NOTE: Unit symbols are case-sensitive, so BTU ≠ Btu">
|
|
743
750
|
placeholder="1e-4" type="text" autocomplete="off">
|
744
751
|
</td>
|
745
752
|
</tr>
|
746
|
-
<tr title="When checked, finite process bounds and slack variables are always added">
|
747
|
-
<td style="padding:0px">
|
748
|
-
<div id="solver-diagnose" class="box clear"></div>
|
749
|
-
</td>
|
750
|
-
<td style="padding-bottom:4px">Diagnose infeasible/unbounded problems</td>
|
751
|
-
</td>
|
752
|
-
</tr>
|
753
753
|
</table>
|
754
754
|
</div>
|
755
755
|
</div>
|
@@ -572,6 +572,7 @@ class Controller {
|
|
572
572
|
VM.reset();
|
573
573
|
// Redraw model in the browser (GUI only).
|
574
574
|
MODEL.clearSelection();
|
575
|
+
this.clearStatusLine();
|
575
576
|
this.drawDiagram(MODEL);
|
576
577
|
}
|
577
578
|
|
@@ -605,6 +606,7 @@ class Controller {
|
|
605
606
|
setProgressNeedle() {}
|
606
607
|
updateTimeStep() {}
|
607
608
|
updateIssuePanel() {}
|
609
|
+
clearStatusLine() {}
|
608
610
|
updateDraggableDialogs() {}
|
609
611
|
logHeapSize() {}
|
610
612
|
|
@@ -909,7 +909,7 @@ class GUIController extends Controller {
|
|
909
909
|
// Reset the Virtual Machine.
|
910
910
|
VM.reset();
|
911
911
|
this.updateIssuePanel();
|
912
|
-
this.
|
912
|
+
this.clearStatusLine();
|
913
913
|
this.updateButtons();
|
914
914
|
// Undoable operations no longer apply!
|
915
915
|
UNDO_STACK.clear();
|
@@ -1733,8 +1733,8 @@ class GUIController extends Controller {
|
|
1733
1733
|
}
|
1734
1734
|
for(let i = fc.sub_clusters.length-1; i >= 0; i--) {
|
1735
1735
|
const obj = fc.sub_clusters[i];
|
1736
|
-
// NOTE:
|
1737
|
-
// being dragged over will be detected instead
|
1736
|
+
// NOTE: Ignore cluster that is being dragged, so that a cluster
|
1737
|
+
// it is being dragged over will be detected instead.
|
1738
1738
|
if(obj != this.dragged_node &&
|
1739
1739
|
obj.containsPoint(this.mouse_x, this.mouse_y)) {
|
1740
1740
|
this.on_cluster = obj;
|
@@ -1742,12 +1742,13 @@ class GUIController extends Controller {
|
|
1742
1742
|
break;
|
1743
1743
|
}
|
1744
1744
|
}
|
1745
|
-
//
|
1745
|
+
// Unset and redraw target cluster if cursor no longer over it.
|
1746
1746
|
if(!this.on_cluster && this.target_cluster) {
|
1747
1747
|
const c = this.target_cluster;
|
1748
1748
|
this.target_cluster = null;
|
1749
1749
|
UI.paper.drawCluster(c);
|
1750
|
-
// NOTE:
|
1750
|
+
// NOTE: Element is persistent, so semi-transparency must also be
|
1751
|
+
// undone.
|
1751
1752
|
c.shape.element.setAttribute('opacity', 1);
|
1752
1753
|
}
|
1753
1754
|
for(let i = fc.notes.length-1; i >= 0; i--) {
|
@@ -1758,15 +1759,15 @@ class GUIController extends Controller {
|
|
1758
1759
|
}
|
1759
1760
|
}
|
1760
1761
|
if(this.active_button === this.buttons.link && this.linking_node) {
|
1761
|
-
// Draw red dotted line from linking node to cursor
|
1762
|
+
// Draw red dotted line from linking node to cursor.
|
1762
1763
|
this.paper.dragLineToCursor(this.linking_node, this.mouse_x, this.mouse_y);
|
1763
1764
|
} else if(this.start_sel_x >= 0 && this.start_sel_y >= 0) {
|
1764
|
-
// Draw selecting rectangle in red dotted lines
|
1765
|
+
// Draw selecting rectangle in red dotted lines.
|
1765
1766
|
this.paper.dragRectToCursor(this.start_sel_x, this.start_sel_y,
|
1766
1767
|
this.mouse_x, this.mouse_y);
|
1767
1768
|
} else if(this.active_button === this.buttons.constraint &&
|
1768
1769
|
this.constraining_node) {
|
1769
|
-
// Draw red dotted line from constraining node to cursor
|
1770
|
+
// Draw red dotted line from constraining node to cursor.
|
1770
1771
|
this.paper.dragLineToCursor(this.constraining_node,
|
1771
1772
|
this.mouse_x, this.mouse_y);
|
1772
1773
|
} else if(this.dragged_node) {
|
@@ -1774,14 +1775,14 @@ class GUIController extends Controller {
|
|
1774
1775
|
this.mouse_y - this.move_dy - this.dragged_node.y);
|
1775
1776
|
}
|
1776
1777
|
let cr = 'pointer';
|
1777
|
-
// NOTE:
|
1778
|
-
// with nodes
|
1778
|
+
// NOTE: First check ON_CONSTRAINT because constraint thumbnails overlap
|
1779
|
+
// with nodes.
|
1779
1780
|
if(this.on_constraint) {
|
1780
1781
|
DOCUMENTATION_MANAGER.update(this.on_constraint, e.shiftKey);
|
1781
|
-
// NOTE:
|
1782
|
+
// NOTE: Skip the "on node" check if the node is being dragged.
|
1782
1783
|
} else if(this.on_node && this.on_node !== this.dragged_node) {
|
1783
1784
|
if((this.active_button === this.buttons.link) && this.linking_node) {
|
1784
|
-
// Cannot link process to process
|
1785
|
+
// Cannot link process to process.
|
1785
1786
|
cr = (MODEL.canLink(this.linking_node, this.on_node) ?
|
1786
1787
|
'crosshair' : 'not-allowed');
|
1787
1788
|
} else if(this.active_button === this.buttons.constraint) {
|
@@ -1789,21 +1790,21 @@ class GUIController extends Controller {
|
|
1789
1790
|
cr = (this.constraining_node.canConstrain(this.on_node) ?
|
1790
1791
|
'crosshair' : 'not-allowed');
|
1791
1792
|
} else if(!this.on_node.hasBounds) {
|
1792
|
-
// Products can only constrain when they have bounds
|
1793
|
+
// Products can only constrain when they have bounds.
|
1793
1794
|
cr = 'not-allowed';
|
1794
1795
|
}
|
1795
1796
|
}
|
1796
|
-
// NOTE:
|
1797
|
+
// NOTE: Do not overwite status line when cursor is on a block arrow.
|
1797
1798
|
if(!this.on_block_arrow) {
|
1798
1799
|
DOCUMENTATION_MANAGER.update(this.on_node, e.shiftKey);
|
1799
1800
|
}
|
1800
1801
|
} else if(this.on_note) {
|
1801
|
-
// When shift-moving over a note, show the model's documentation
|
1802
|
+
// When shift-moving over a note, show the model's documentation.
|
1802
1803
|
DOCUMENTATION_MANAGER.update(MODEL, e.shiftKey);
|
1803
1804
|
} else {
|
1804
1805
|
if((this.active_button === this.buttons.link && this.linking_node) ||
|
1805
1806
|
(this.active_button === this.buttons.constraint && this.constraining_node)) {
|
1806
|
-
// Cannot link to clusters or notes
|
1807
|
+
// Cannot link to clusters or notes.
|
1807
1808
|
cr = (this.on_cluster || this.on_note ? 'not-allowed' : 'crosshair');
|
1808
1809
|
} else if(!this.on_note && !this.on_constraint && !this.on_link &&
|
1809
1810
|
!this.on_cluster_edge) {
|
@@ -1819,38 +1820,42 @@ class GUIController extends Controller {
|
|
1819
1820
|
}
|
1820
1821
|
}
|
1821
1822
|
// When dragging selection that contains a process, change cursor to
|
1822
|
-
// indicate that selected process(es) will be moved into the cluster
|
1823
|
-
if(this.dragged_node
|
1824
|
-
|
1825
|
-
|
1826
|
-
|
1827
|
-
|
1823
|
+
// indicate that selected process(es) will be moved into the cluster.
|
1824
|
+
if(this.dragged_node) {
|
1825
|
+
if(this.on_cluster) {
|
1826
|
+
cr = 'cell';
|
1827
|
+
this.target_cluster = this.on_cluster;
|
1828
|
+
// Redraw the target cluster so it will appear on top (and highlighted).
|
1829
|
+
UI.paper.drawCluster(this.target_cluster);
|
1830
|
+
} else {
|
1831
|
+
cr = 'grab';
|
1832
|
+
}
|
1828
1833
|
}
|
1829
1834
|
}
|
1830
1835
|
this.paper.container.style.cursor = cr;
|
1831
1836
|
}
|
1832
1837
|
|
1833
1838
|
mouseDown(e) {
|
1834
|
-
// Responds to mousedown event in model diagram area
|
1839
|
+
// Responds to mousedown event in model diagram area.
|
1835
1840
|
// In case mouseup event occurred outside drawing area,ignore this
|
1836
|
-
// mousedown event, so that only the mouseup will be processed
|
1841
|
+
// mousedown event, so that only the mouseup will be processed.
|
1837
1842
|
if(this.start_sel_x >= 0 && this.start_sel_y >= 0) return;
|
1838
1843
|
const cp = this.paper.cursorPosition(e.pageX, e.pageY);
|
1839
1844
|
this.mouse_down_x = cp[0];
|
1840
1845
|
this.mouse_down_y = cp[1];
|
1841
1846
|
// De-activate "stay active" buttons if dysfunctional, or if SHIFT,
|
1842
|
-
// ALT or CTRL is pressed
|
1847
|
+
// ALT or CTRL is pressed.
|
1843
1848
|
if((e.shiftKey || e.altKey || e.ctrlKey ||
|
1844
1849
|
this.on_note || this.on_cluster || this.on_link || this.on_constraint ||
|
1845
1850
|
(this.on_node && this.active_button !== this.buttons.link &&
|
1846
1851
|
this.active_button !== this.buttons.constraint)) && this.stayActive) {
|
1847
1852
|
resetActiveButton();
|
1848
1853
|
}
|
1849
|
-
// NOTE:
|
1854
|
+
// NOTE: Only left button is detected (browser catches right menu button).
|
1850
1855
|
if(e.ctrlKey) {
|
1851
1856
|
// Remove clicked item from selection
|
1852
1857
|
if(MODEL.selection) {
|
1853
|
-
// NOTE:
|
1858
|
+
// NOTE: First check constraints -- see mouseMove() for motivation.
|
1854
1859
|
if(this.on_constraint) {
|
1855
1860
|
if(MODEL.selection.indexOf(this.on_constraint) >= 0) {
|
1856
1861
|
MODEL.deselect(this.on_constraint);
|
@@ -1889,7 +1894,7 @@ class GUIController extends Controller {
|
|
1889
1894
|
} // END IF Ctrl
|
1890
1895
|
|
1891
1896
|
// Clear selection unless SHIFT pressed or mouseDown while hovering
|
1892
|
-
// over a SELECTED node or link
|
1897
|
+
// over a SELECTED node or link.
|
1893
1898
|
if(!(e.shiftKey ||
|
1894
1899
|
(this.on_node && MODEL.selection.indexOf(this.on_node) >= 0) ||
|
1895
1900
|
(this.on_cluster && MODEL.selection.indexOf(this.on_cluster) >= 0) ||
|
@@ -1901,7 +1906,7 @@ class GUIController extends Controller {
|
|
1901
1906
|
}
|
1902
1907
|
|
1903
1908
|
// If one of the top six sidebar buttons is active, prompt for new node
|
1904
|
-
// (not link or constraint)
|
1909
|
+
// (not link or constraint).
|
1905
1910
|
if(this.active_button && this.active_button !== this.buttons.link &&
|
1906
1911
|
this.active_button !== this.buttons.constraint) {
|
1907
1912
|
this.add_x = this.mouse_x;
|
@@ -1943,16 +1948,16 @@ class GUIController extends Controller {
|
|
1943
1948
|
}
|
1944
1949
|
|
1945
1950
|
// ALT key pressed => open properties dialog if cursor hovers over
|
1946
|
-
// some element
|
1951
|
+
// some element.
|
1947
1952
|
if(e.altKey) {
|
1948
|
-
// NOTE:
|
1953
|
+
// NOTE: First check constraints -- see mouseMove() for motivation.
|
1949
1954
|
if(this.on_constraint) {
|
1950
1955
|
this.showConstraintPropertiesDialog(this.on_constraint);
|
1951
1956
|
} else if(this.on_node) {
|
1952
1957
|
if(this.on_node instanceof Process) {
|
1953
1958
|
this.showProcessPropertiesDialog(this.on_node);
|
1954
1959
|
} else if(e.shiftKey) {
|
1955
|
-
// Shift-Alt on product is like Shift-Double-click
|
1960
|
+
// Shift-Alt on product is like Shift-Double-click.
|
1956
1961
|
this.showReplaceProductDialog(this.on_node);
|
1957
1962
|
} else {
|
1958
1963
|
this.showProductPropertiesDialog(this.on_node);
|
@@ -1964,7 +1969,7 @@ class GUIController extends Controller {
|
|
1964
1969
|
} else if(this.on_link) {
|
1965
1970
|
this.showLinkPropertiesDialog(this.on_link);
|
1966
1971
|
}
|
1967
|
-
// NOTE:
|
1972
|
+
// NOTE: First check constraints -- see mouseMove() for motivation.
|
1968
1973
|
} else if(this.on_constraint) {
|
1969
1974
|
MODEL.select(this.on_constraint);
|
1970
1975
|
} else if(this.on_note) {
|
@@ -1973,17 +1978,17 @@ class GUIController extends Controller {
|
|
1973
1978
|
this.move_dy = this.mouse_y - this.on_note.y;
|
1974
1979
|
MODEL.select(this.on_note);
|
1975
1980
|
UNDO_STACK.push('move', this.dragged_node, true);
|
1976
|
-
// Cursor on node => add link or constraint, or start moving
|
1981
|
+
// Cursor on node => add link or constraint, or start moving.
|
1977
1982
|
} else if(this.on_node) {
|
1978
1983
|
if(this.active_button === this.buttons.link) {
|
1979
1984
|
this.linking_node = this.on_node;
|
1980
1985
|
// NOTE: return without updating buttons
|
1981
1986
|
return;
|
1982
1987
|
} else if(this.active_button === this.buttons.constraint) {
|
1983
|
-
// Allow constraints only on nodes having upper bounds defined
|
1988
|
+
// Allow constraints only on nodes having upper bounds defined.
|
1984
1989
|
if(this.on_node.upper_bound.defined) {
|
1985
1990
|
this.constraining_node = this.on_node;
|
1986
|
-
// NOTE:
|
1991
|
+
// NOTE: Here, too, return without updating buttons.
|
1987
1992
|
return;
|
1988
1993
|
}
|
1989
1994
|
} else {
|
@@ -1991,7 +1996,7 @@ class GUIController extends Controller {
|
|
1991
1996
|
this.move_dx = this.mouse_x - this.on_node.x;
|
1992
1997
|
this.move_dy = this.mouse_y - this.on_node.y;
|
1993
1998
|
if(MODEL.selection.indexOf(this.on_node) < 0) MODEL.select(this.on_node);
|
1994
|
-
// Pass dragged node for UNDO
|
1999
|
+
// Pass dragged node for UNDO.
|
1995
2000
|
UNDO_STACK.push('move', this.dragged_node, true);
|
1996
2001
|
}
|
1997
2002
|
} else if(this.on_cluster) {
|
@@ -2010,23 +2015,23 @@ class GUIController extends Controller {
|
|
2010
2015
|
}
|
2011
2016
|
|
2012
2017
|
mouseUp(e) {
|
2013
|
-
// Responds to mouseup event
|
2018
|
+
// Responds to mouseup event.
|
2014
2019
|
const cp = this.paper.cursorPosition(e.pageX, e.pageY);
|
2015
2020
|
this.mouse_up_x = cp[0];
|
2016
2021
|
this.mouse_up_y = cp[1];
|
2017
|
-
// First check whether user is selecting a rectangle
|
2022
|
+
// First check whether user is selecting a rectangle.
|
2018
2023
|
if(this.start_sel_x >= 0 && this.start_sel_y >= 0) {
|
2019
2024
|
// Clear previous selection unless user is adding to it (by still
|
2020
|
-
// holding SHIFT button down)
|
2025
|
+
// holding SHIFT button down).
|
2021
2026
|
if(!e.shiftKey) MODEL.clearSelection();
|
2022
|
-
// Compute defining points of rectangle (top left and bottom right)
|
2027
|
+
// Compute defining points of rectangle (top left and bottom right).
|
2023
2028
|
const
|
2024
2029
|
tlx = Math.min(this.start_sel_x, this.mouse_up_x),
|
2025
2030
|
tly = Math.min(this.start_sel_y, this.mouse_up_y),
|
2026
2031
|
brx = Math.max(this.start_sel_x, this.mouse_up_x),
|
2027
2032
|
bry = Math.max(this.start_sel_y, this.mouse_up_y);
|
2028
2033
|
// If rectangle has size greater than 2x2 pixels, select all elements
|
2029
|
-
// having their center inside the selection rectangle
|
2034
|
+
// having their center inside the selection rectangle.
|
2030
2035
|
if(brx - tlx > 2 && bry - tly > 2) {
|
2031
2036
|
const ol = [], fc = MODEL.focal_cluster;
|
2032
2037
|
for(let i = 0; i < fc.processes.length; i++) {
|
@@ -2055,19 +2060,19 @@ class GUIController extends Controller {
|
|
2055
2060
|
}
|
2056
2061
|
for(let i in MODEL.links) if(MODEL.links.hasOwnProperty(i)) {
|
2057
2062
|
const obj = MODEL.links[i];
|
2058
|
-
// Only add a link if both its nodes are selected as well
|
2063
|
+
// Only add a link if both its nodes are selected as well.
|
2059
2064
|
if(fc.linkInList(obj, ol)) {
|
2060
2065
|
ol.push(obj);
|
2061
2066
|
}
|
2062
2067
|
}
|
2063
2068
|
for(let i in MODEL.constraints) if(MODEL.constraints.hasOwnProperty(i)) {
|
2064
2069
|
const obj = MODEL.constraints[i];
|
2065
|
-
// Only add a constraint if both its nodes are selected as well
|
2070
|
+
// Only add a constraint if both its nodes are selected as well.
|
2066
2071
|
if(fc.linkInList(obj, ol)) {
|
2067
2072
|
ol.push(obj);
|
2068
2073
|
}
|
2069
2074
|
}
|
2070
|
-
// Having compiled the object list, actually select them
|
2075
|
+
// Having compiled the object list, actually select them.
|
2071
2076
|
MODEL.selectList(ol);
|
2072
2077
|
this.paper.drawSelection(MODEL);
|
2073
2078
|
}
|
@@ -2075,10 +2080,10 @@ class GUIController extends Controller {
|
|
2075
2080
|
this.start_sel_y = -1;
|
2076
2081
|
this.paper.hideDragRect();
|
2077
2082
|
|
2078
|
-
// Then check whether user is drawing a flow link
|
2079
|
-
//
|
2083
|
+
// Then check whether user is drawing a flow link (by dragging its
|
2084
|
+
// endpoint).
|
2080
2085
|
} else if(this.linking_node) {
|
2081
|
-
// If so, check whether the cursor is over a node of the appropriate type
|
2086
|
+
// If so, check whether the cursor is over a node of the appropriate type.
|
2082
2087
|
if(this.on_node && MODEL.canLink(this.linking_node, this.on_node)) {
|
2083
2088
|
const obj = MODEL.addLink(this.linking_node, this.on_node);
|
2084
2089
|
UNDO_STACK.push('add', obj);
|
@@ -2089,8 +2094,8 @@ class GUIController extends Controller {
|
|
2089
2094
|
if(!this.stayActive) this.resetActiveButton();
|
2090
2095
|
this.paper.hideDragLine();
|
2091
2096
|
|
2092
|
-
// Then check whether user is drawing a constraint link
|
2093
|
-
//
|
2097
|
+
// Then check whether user is drawing a constraint link (again: by
|
2098
|
+
// dragging its endpoint).
|
2094
2099
|
} else if(this.constraining_node) {
|
2095
2100
|
if(this.on_node && this.constraining_node.canConstrain(this.on_node)) {
|
2096
2101
|
// display constraint editor
|
@@ -2104,35 +2109,37 @@ class GUIController extends Controller {
|
|
2104
2109
|
UI.drawDiagram(MODEL);
|
2105
2110
|
|
2106
2111
|
// Then check whether the user is moving a node (possibly part of a
|
2107
|
-
// larger selection)
|
2112
|
+
// larger selection).
|
2108
2113
|
} else if(this.dragged_node) {
|
2109
2114
|
// Always perform the move operation (this will do nothing if the
|
2110
|
-
// cursor did not move)
|
2115
|
+
// cursor did not move).
|
2111
2116
|
MODEL.moveSelection(
|
2112
2117
|
this.mouse_up_x - this.mouse_x, this.mouse_up_y - this.mouse_y);
|
2113
|
-
//
|
2114
|
-
|
2118
|
+
// Set cursor to pointer, as it should be on some node while dragging.
|
2119
|
+
this.paper.container.style.cursor = 'pointer';
|
2120
|
+
// @@TO DO: if on top of a cluster, move it there.
|
2121
|
+
// NOTE: Cursor will always be over the selected cluster (while dragging).
|
2115
2122
|
if(this.on_cluster && !this.on_cluster.selected) {
|
2116
2123
|
UNDO_STACK.push('drop', this.on_cluster);
|
2117
2124
|
MODEL.dropSelectionIntoCluster(this.on_cluster);
|
2118
2125
|
this.on_node = null;
|
2119
2126
|
this.on_note = null;
|
2120
2127
|
this.target_cluster = null;
|
2121
|
-
// Redraw cluster to erase its "target corona"
|
2128
|
+
// Redraw cluster to erase its orange "target corona".
|
2122
2129
|
UI.paper.drawCluster(this.on_cluster);
|
2123
2130
|
}
|
2124
2131
|
|
2125
|
-
// Check wether the cursor has been moved
|
2132
|
+
// Check wether the cursor has been moved.
|
2126
2133
|
const
|
2127
2134
|
absdx = Math.abs(this.mouse_down_x - this.mouse_x),
|
2128
2135
|
absdy = Math.abs(this.mouse_down_y - this.mouse_y);
|
2129
|
-
// If no *significant* move made, remove the move undo
|
2136
|
+
// If no *significant* move made, remove the move undo.
|
2130
2137
|
if(absdx + absdy === 0) UNDO_STACK.pop('move');
|
2131
2138
|
if(this.doubleClicked && absdx + absdy < 3) {
|
2132
2139
|
// Double-clicking opens properties dialog, except for clusters;
|
2133
|
-
// then "drill down", i.e., make the double-clicked cluster focal
|
2140
|
+
// then "drill down", i.e., make the double-clicked cluster focal.
|
2134
2141
|
if(this.dragged_node instanceof Cluster) {
|
2135
|
-
// NOTE:
|
2142
|
+
// NOTE: Bottom & right cluster edges remain sensitive!
|
2136
2143
|
if(this.on_cluster_edge) {
|
2137
2144
|
this.showClusterPropertiesDialog(this.dragged_node);
|
2138
2145
|
} else {
|
@@ -2142,7 +2149,7 @@ class GUIController extends Controller {
|
|
2142
2149
|
if(e.shiftKey) {
|
2143
2150
|
// Shift-double-clicking on a *product* prompts for "remapping"
|
2144
2151
|
// the product position to another product (and potentially
|
2145
|
-
// deleting the original one if it has no more occurrences)
|
2152
|
+
// deleting the original one if it has no more occurrences).
|
2146
2153
|
this.showReplaceProductDialog(this.dragged_node);
|
2147
2154
|
} else {
|
2148
2155
|
this.showProductPropertiesDialog(this.dragged_node);
|
@@ -2155,7 +2162,7 @@ class GUIController extends Controller {
|
|
2155
2162
|
}
|
2156
2163
|
this.dragged_node = null;
|
2157
2164
|
|
2158
|
-
// Then check whether the user is clicking on a link
|
2165
|
+
// Then check whether the user is clicking on a link.
|
2159
2166
|
} else if(this.on_link) {
|
2160
2167
|
if(this.doubleClicked) {
|
2161
2168
|
this.showLinkPropertiesDialog(this.on_link);
|
@@ -2171,8 +2178,8 @@ class GUIController extends Controller {
|
|
2171
2178
|
}
|
2172
2179
|
|
2173
2180
|
dragOver(e) {
|
2174
|
-
//
|
2175
|
-
// a placeholder in the focal cluster
|
2181
|
+
// Accept products that are dragged from the Finder and do not have
|
2182
|
+
// a placeholder in the focal cluster.
|
2176
2183
|
this.updateCursorPosition(e);
|
2177
2184
|
const p = MODEL.products[e.dataTransfer.getData('text')];
|
2178
2185
|
if(p && MODEL.focal_cluster.indexOfProduct(p) < 0) e.preventDefault();
|
@@ -2180,7 +2187,7 @@ class GUIController extends Controller {
|
|
2180
2187
|
|
2181
2188
|
drop(e) {
|
2182
2189
|
// Adds a product that is dragged from the Finder to the focal cluster
|
2183
|
-
// at the cursor position if it does not have a placeholder yet
|
2190
|
+
// at the cursor position if it does not have a placeholder yet.
|
2184
2191
|
const p = MODEL.products[e.dataTransfer.getData('text')];
|
2185
2192
|
if(p && MODEL.focal_cluster.indexOfProduct(p) < 0) {
|
2186
2193
|
e.preventDefault();
|
@@ -2189,7 +2196,7 @@ class GUIController extends Controller {
|
|
2189
2196
|
this.selectNode(p);
|
2190
2197
|
this.drawDiagram(MODEL);
|
2191
2198
|
}
|
2192
|
-
// NOTE:
|
2199
|
+
// NOTE: Update afterwards, as the modeler may target a precise (X, Y).
|
2193
2200
|
this.updateCursorPosition(e);
|
2194
2201
|
}
|
2195
2202
|
|
@@ -2198,14 +2205,16 @@ class GUIController extends Controller {
|
|
2198
2205
|
//
|
2199
2206
|
|
2200
2207
|
checkModals(e) {
|
2201
|
-
// Respond to Escape, Enter and shortcut keys
|
2208
|
+
// Respond to Escape, Enter and shortcut keys.
|
2202
2209
|
const
|
2203
2210
|
ttype = e.target.type,
|
2204
2211
|
ttag = e.target.tagName,
|
2205
2212
|
modals = document.getElementsByClassName('modal');
|
2206
|
-
// Modal dialogs: hide on ESC and move to next input on ENTER
|
2213
|
+
// Modal dialogs: hide on ESC and move to next input on ENTER.
|
2207
2214
|
let maxz = 0,
|
2208
|
-
topmod = null
|
2215
|
+
topmod = null,
|
2216
|
+
code = e.code,
|
2217
|
+
alt = e.altKey;
|
2209
2218
|
for(let i = 0; i < modals.length; i++) {
|
2210
2219
|
const
|
2211
2220
|
m = modals[i],
|
@@ -2216,11 +2225,11 @@ class GUIController extends Controller {
|
|
2216
2225
|
maxz = z;
|
2217
2226
|
}
|
2218
2227
|
}
|
2219
|
-
// NOTE:
|
2220
|
-
if(
|
2228
|
+
// NOTE: Consider only the top modal (if any is showing).
|
2229
|
+
if(code === 'Escape') {
|
2221
2230
|
e.stopImmediatePropagation();
|
2222
2231
|
if(topmod) topmod.style.display = 'none';
|
2223
|
-
} else if(
|
2232
|
+
} else if(code === 'Enter' && ttype !== 'textarea') {
|
2224
2233
|
e.preventDefault();
|
2225
2234
|
if(topmod) {
|
2226
2235
|
const inp = Array.from(topmod.getElementsByTagName('input'));
|
@@ -2229,73 +2238,78 @@ class GUIController extends Controller {
|
|
2229
2238
|
if(i < inp.length) {
|
2230
2239
|
inp[i].focus();
|
2231
2240
|
} else if('constraint-modal xp-clusters-modal'.indexOf(topmod.id) >= 0) {
|
2232
|
-
// NOTE:
|
2233
|
-
// when Enter is pressed
|
2241
|
+
// NOTE: Constraint modal and "ignore clusters" modal must NOT close
|
2242
|
+
// when Enter is pressed, but only de-focus the input field.
|
2234
2243
|
e.target.blur();
|
2235
2244
|
} else {
|
2236
2245
|
const btns = topmod.getElementsByClassName('ok-btn');
|
2237
2246
|
if(btns.length > 0) btns[0].dispatchEvent(new Event('click'));
|
2238
2247
|
}
|
2239
2248
|
} else if(this.dr_dialog_order.length > 0) {
|
2240
|
-
// Send ENTER key event to the top draggable dialog
|
2249
|
+
// Send ENTER key event to the top draggable dialog.
|
2241
2250
|
const last = this.dr_dialog_order.length - 1;
|
2242
2251
|
if(last >= 0) {
|
2243
2252
|
const mgr = window[this.dr_dialog_order[last].dataset.manager];
|
2244
2253
|
if(mgr && 'enterKey' in mgr) mgr.enterKey();
|
2245
2254
|
}
|
2246
2255
|
}
|
2247
|
-
} else if(
|
2256
|
+
} else if(code === 'Backspace' &&
|
2248
2257
|
ttype !== 'text' && ttype !== 'password' && ttype !== 'textarea') {
|
2249
|
-
// Prevent backspace to be interpreted (by FireFox) as "go back in browser"
|
2258
|
+
// Prevent backspace to be interpreted (by FireFox) as "go back in browser".
|
2250
2259
|
e.preventDefault();
|
2251
2260
|
} else if(ttag === 'BODY') {
|
2252
|
-
// Constraint Editor accepts arrow keys
|
2261
|
+
// Constraint Editor accepts arrow keys.
|
2253
2262
|
if(topmod && topmod.id === 'constraint-modal') {
|
2254
|
-
if(
|
2263
|
+
if(code.startsWith('Arrow')) {
|
2255
2264
|
e.preventDefault();
|
2256
2265
|
CONSTRAINT_EDITOR.arrowKey(e);
|
2257
2266
|
return;
|
2258
2267
|
}
|
2259
2268
|
}
|
2260
|
-
// Up and down arrow keys
|
2261
|
-
if(
|
2269
|
+
// Up and down arrow keys.
|
2270
|
+
if(code === 'ArrowUp' || code === 'ArrowDown') {
|
2262
2271
|
e.preventDefault();
|
2263
|
-
// Send event to the top draggable dialog
|
2272
|
+
// Send event to the top draggable dialog.
|
2264
2273
|
const last = this.dr_dialog_order.length - 1;
|
2265
2274
|
if(last >= 0) {
|
2266
2275
|
const mgr = window[this.dr_dialog_order[last].dataset.manager];
|
2267
|
-
// NOTE:
|
2276
|
+
// NOTE: Pass key direction as -1 for UP and +1 for DOWN.
|
2268
2277
|
if(mgr && 'upDownKey' in mgr) mgr.upDownKey(e.keyCode - 39);
|
2269
2278
|
}
|
2270
2279
|
}
|
2271
|
-
//
|
2272
|
-
if(
|
2273
|
-
|
2280
|
+
// End, Home, and left and right arrow keys.
|
2281
|
+
if(code === 'End') {
|
2282
|
+
e.preventDefault();
|
2274
2283
|
MODEL.t = MODEL.end_period - MODEL.start_period + 1;
|
2275
2284
|
UI.updateTimeStep();
|
2276
2285
|
UI.drawDiagram(MODEL);
|
2277
|
-
} else if(
|
2286
|
+
} else if(code === 'Home') {
|
2287
|
+
e.preventDefault();
|
2278
2288
|
MODEL.t = 1;
|
2279
2289
|
UI.updateTimeStep();
|
2280
2290
|
UI.drawDiagram(MODEL);
|
2281
|
-
} else if(
|
2291
|
+
} else if(code === 'ArrowLeft') {
|
2292
|
+
e.preventDefault();
|
2282
2293
|
this.stepBack(e);
|
2283
|
-
} else if(
|
2294
|
+
} else if(code === 'ArrowRight') {
|
2295
|
+
e.preventDefault();
|
2284
2296
|
this.stepForward(e);
|
2285
|
-
} else if(
|
2286
|
-
//
|
2297
|
+
} else if(alt && code === 'KeyR') {
|
2298
|
+
// Alt-R means: run to diagnose infeasible/unbounded problem.
|
2299
|
+
VM.solveModel(true);
|
2300
|
+
} else if(alt && ['KeyC', 'KeyM'].indexOf(code) >= 0) {
|
2301
|
+
// Special shortcut keys for "clone selection" and "model settings".
|
2287
2302
|
const be = new Event('click');
|
2288
|
-
|
2289
|
-
if(e.keyCode === 67) {
|
2303
|
+
if(code === 'KeyC') {
|
2290
2304
|
this.buttons.clone.dispatchEvent(be);
|
2291
2305
|
} else {
|
2292
2306
|
this.buttons.settings.dispatchEvent(be);
|
2293
2307
|
}
|
2294
|
-
} else if(!e.shiftKey && !
|
2295
|
-
(!topmod || [
|
2296
|
-
// Interpret special keys as shortcuts unless a modal dialog is open
|
2297
|
-
if(
|
2298
|
-
// DEL button => delete selection
|
2308
|
+
} else if(!e.shiftKey && !alt &&
|
2309
|
+
(!topmod || ['KeyA', 'KeyC', 'KeyV'].indexOf(code) < 0)) {
|
2310
|
+
// Interpret special keys as shortcuts unless a modal dialog is open.
|
2311
|
+
if(code === 'Delete') {
|
2312
|
+
// DEL button => delete selection.
|
2299
2313
|
e.preventDefault();
|
2300
2314
|
if(!this.hidden('constraint-modal')) {
|
2301
2315
|
CONSTRAINT_EDITOR.deleteBoundLine();
|
@@ -2304,18 +2318,18 @@ class GUIController extends Controller {
|
|
2304
2318
|
} else {
|
2305
2319
|
this.buttons['delete'].dispatchEvent(new Event('click'));
|
2306
2320
|
}
|
2307
|
-
} else if (
|
2308
|
-
// Ctrl-. (dot) moves entire diagram to upper-left corner
|
2321
|
+
} else if (code === 'Period' && (e.ctrlKey || e.metaKey)) {
|
2322
|
+
// Ctrl-. (dot) moves entire diagram to upper-left corner.
|
2309
2323
|
e.preventDefault();
|
2310
2324
|
this.paper.fitToSize();
|
2311
2325
|
MODEL.alignToGrid();
|
2312
|
-
} else if (
|
2313
|
-
// ALWAYS prevent browser to do respond to Ctrl-letter commands
|
2314
|
-
// NOTE:
|
2326
|
+
} else if (code >= 'KeyA' && code <= 'KeyZ' && (e.ctrlKey || e.metaKey)) {
|
2327
|
+
// ALWAYS prevent browser to do respond to Ctrl-letter commands.
|
2328
|
+
// NOTE: This cannot prevent a new tab from opening on Ctrl-T.
|
2315
2329
|
e.preventDefault();
|
2316
|
-
let shortcut =
|
2330
|
+
let shortcut = code.substring(3);
|
2317
2331
|
if(shortcut === 'Z' && e.shiftKey) {
|
2318
|
-
// Interpret Shift-Ctrl-Z as Ctrl-Y (redo last undone operation)
|
2332
|
+
// Interpret Shift-Ctrl-Z as Ctrl-Y (redo last undone operation).
|
2319
2333
|
shortcut = 'Y';
|
2320
2334
|
}
|
2321
2335
|
if(this.shortcuts.hasOwnProperty(shortcut)) {
|
@@ -2666,9 +2680,15 @@ class GUIController extends Controller {
|
|
2666
2680
|
//
|
2667
2681
|
// Informing the modeler via the status line
|
2668
2682
|
//
|
2669
|
-
|
2683
|
+
|
2684
|
+
clearStatusLine() {
|
2685
|
+
// Clear message on the status line.
|
2686
|
+
this.info_line.innerHTML = '';
|
2687
|
+
UI.info_line.classList.remove(...UI.info_line.classList);
|
2688
|
+
}
|
2689
|
+
|
2670
2690
|
setMessage(msg, type=null) {
|
2671
|
-
//
|
2691
|
+
// Display `msg` on infoline unless no type (= plain text) and some
|
2672
2692
|
// info, warning or error message is already displayed.
|
2673
2693
|
super.setMessage(msg, type);
|
2674
2694
|
const types = ['notification', 'warning', 'error'];
|
@@ -2692,8 +2712,8 @@ class GUIController extends Controller {
|
|
2692
2712
|
// Queue warnings if an error message is still being displayed.
|
2693
2713
|
setTimeout(() => {
|
2694
2714
|
UI.info_line.innerHTML = msg;
|
2695
|
-
UI.info_line.classList.remove(...
|
2696
|
-
UI.info_line.classList.add(type);
|
2715
|
+
UI.info_line.classList.remove(...UI.info_line.classList);
|
2716
|
+
if(type) UI.info_line.classList.add(type);
|
2697
2717
|
UI.updateIssuePanel();
|
2698
2718
|
}, rt);
|
2699
2719
|
} else if(lmti < 0 || mti > lmti || rt <= 0) {
|
@@ -2715,8 +2735,8 @@ class GUIController extends Controller {
|
|
2715
2735
|
UI.updateIssuePanel();
|
2716
2736
|
}, this.message_display_time);
|
2717
2737
|
}
|
2718
|
-
UI.info_line.classList.remove(...
|
2719
|
-
UI.info_line.classList.add(type);
|
2738
|
+
UI.info_line.classList.remove(...UI.info_line.classList);
|
2739
|
+
if(type) UI.info_line.classList.add(type);
|
2720
2740
|
UI.info_line.innerHTML = msg;
|
2721
2741
|
}
|
2722
2742
|
}
|
@@ -2869,11 +2889,15 @@ class GUIController extends Controller {
|
|
2869
2889
|
}
|
2870
2890
|
}
|
2871
2891
|
} else if(type === 'process' || type === 'product') {
|
2892
|
+
/* NOT CLEAR WHAT THIS CODE DOES, SO DISABLE IT
|
2872
2893
|
if(this.dbl_clicked_node) {
|
2873
2894
|
n = this.dbl_clicked_node;
|
2874
2895
|
md = this.modals['add-' + type];
|
2875
2896
|
this.dbl_clicked_node = null;
|
2876
2897
|
} else {
|
2898
|
+
*/
|
2899
|
+
if(true) { // added line
|
2900
|
+
this.dbl_clicked_node = null; // added line
|
2877
2901
|
if(type === 'process') {
|
2878
2902
|
md = this.modals['add-process'];
|
2879
2903
|
nn = md.element('name').value;
|
@@ -2927,7 +2951,7 @@ class GUIController extends Controller {
|
|
2927
2951
|
UNDO_STACK.pop();
|
2928
2952
|
return false;
|
2929
2953
|
}
|
2930
|
-
// NOTE:
|
2954
|
+
// NOTE: Pre-check if product exists.
|
2931
2955
|
const pp = MODEL.objectByName(nn);
|
2932
2956
|
n = MODEL.addProduct(nn);
|
2933
2957
|
if(n) {
|
@@ -2948,7 +2972,7 @@ class GUIController extends Controller {
|
|
2948
2972
|
}
|
2949
2973
|
if(n) {
|
2950
2974
|
// If process, and X and Y are set, it exists; then if not in the
|
2951
|
-
// focal cluster, ask whether to move it there
|
2975
|
+
// focal cluster, ask whether to move it there.
|
2952
2976
|
if(n instanceof Process && (n.x !== 0 || n.y !== 0)) {
|
2953
2977
|
if(n.cluster !== MODEL.focal_cluster) {
|
2954
2978
|
this.confirmToMoveNode(n);
|
@@ -2966,9 +2990,9 @@ class GUIController extends Controller {
|
|
2966
2990
|
MODEL.inferIgnoredEntities();
|
2967
2991
|
if(n) {
|
2968
2992
|
md.hide();
|
2969
|
-
// Select the newly added entity
|
2970
|
-
// NOTE: If the focal cluster was selected (via the top tool bar),
|
2971
|
-
// cannot be selected
|
2993
|
+
// Select the newly added entity.
|
2994
|
+
// NOTE: If the focal cluster was selected (via the top tool bar),
|
2995
|
+
// it cannot be selected.
|
2972
2996
|
if(n !== MODEL.focal_cluster) this.selectNode(n);
|
2973
2997
|
}
|
2974
2998
|
}
|
@@ -3526,12 +3550,13 @@ console.log('HERE name conflicts', name_conflicts, mapping);
|
|
3526
3550
|
md.element('block-length').value = model.block_length;
|
3527
3551
|
md.element('look-ahead').value = model.look_ahead;
|
3528
3552
|
md.element('time-limit').value = model.timeout_period;
|
3529
|
-
this.setBox('settings-encrypt', model.encrypt);
|
3530
3553
|
this.setBox('settings-decimal-comma', model.decimal_comma);
|
3531
3554
|
this.setBox('settings-align-to-grid', model.align_to_grid);
|
3555
|
+
this.setBox('settings-block-arrows', model.show_block_arrows);
|
3556
|
+
this.setBox('settings-diagnose', MODEL.always_diagnose);
|
3532
3557
|
this.setBox('settings-cost-prices', model.infer_cost_prices);
|
3533
3558
|
this.setBox('settings-report-results', model.report_results);
|
3534
|
-
this.setBox('settings-
|
3559
|
+
this.setBox('settings-encrypt', model.encrypt);
|
3535
3560
|
md.show('name');
|
3536
3561
|
}
|
3537
3562
|
|
@@ -3586,6 +3611,12 @@ console.log('HERE name conflicts', name_conflicts, mapping);
|
|
3586
3611
|
model.report_results = UI.boxChecked('settings-report-results');
|
3587
3612
|
model.encrypt = UI.boxChecked('settings-encrypt');
|
3588
3613
|
model.decimal_comma = UI.boxChecked('settings-decimal-comma');
|
3614
|
+
MODEL.always_diagnose = this.boxChecked('settings-diagnose');
|
3615
|
+
// Notify modeler that diagnosis changes the value of +INF.
|
3616
|
+
if(MODEL.always_diagnose) {
|
3617
|
+
UI.notify('To diagnose unbounded problems, values beyond 1e+10 ' +
|
3618
|
+
'are considered as infinite (\u221E)');
|
3619
|
+
}
|
3589
3620
|
// Some changes may necessitate redrawing the diagram
|
3590
3621
|
let cb = UI.boxChecked('settings-align-to-grid'),
|
3591
3622
|
redraw = !model.align_to_grid && cb;
|
@@ -3653,7 +3684,6 @@ console.log('HERE name conflicts', name_conflicts, mapping);
|
|
3653
3684
|
md.element('preference').innerHTML = html.join('');
|
3654
3685
|
md.element('int-feasibility').value = MODEL.integer_tolerance;
|
3655
3686
|
md.element('mip-gap').value = MODEL.MIP_gap;
|
3656
|
-
this.setBox('solver-diagnose', MODEL.always_diagnose);
|
3657
3687
|
md.show();
|
3658
3688
|
}
|
3659
3689
|
|
@@ -3682,11 +3712,6 @@ console.log('HERE name conflicts', name_conflicts, mapping);
|
|
3682
3712
|
}
|
3683
3713
|
MODEL.integer_tolerance = Math.max(1e-9, Math.min(0.1, itol));
|
3684
3714
|
MODEL.MIP_gap = Math.max(0, Math.min(0.5, mgap));
|
3685
|
-
MODEL.always_diagnose = this.boxChecked('solver-diagnose');
|
3686
|
-
if(MODEL.always_diagnose) {
|
3687
|
-
UI.notify('To diagnose unbounded problems, values beyond 1e+10 ' +
|
3688
|
-
'are considered as infinite (\u221E)');
|
3689
|
-
}
|
3690
3715
|
// Close the dialog.
|
3691
3716
|
md.hide();
|
3692
3717
|
}
|
@@ -283,7 +283,7 @@ class DocumentationManager {
|
|
283
283
|
if(list.indexOf(this.entity) >= 0) {
|
284
284
|
this.stopEditing();
|
285
285
|
this.entity = null;
|
286
|
-
this.title.innerHTML = '
|
286
|
+
this.title.innerHTML = 'Information and documentation';
|
287
287
|
this.viewer.innerHTML = this.about_linny_r;
|
288
288
|
}
|
289
289
|
}
|
@@ -74,16 +74,16 @@ Attributes, however, are case sensitive!">[Actor X|CF]</code> for cash flow.
|
|
74
74
|
<code title="Number of last round in the sequence (1=a, 2=b, etc.)">lr</code>,
|
75
75
|
<code title="Number of rounds in the sequence">nr</code>,
|
76
76
|
<code title="Number of current experiment run (starts at 0)">x</code>,
|
77
|
-
<code title="Number of runs in the experiment">nx</code>,
|
78
|
-
<span title="Index variables of iterator dimensions
|
77
|
+
<code title="Number of runs in the current experiment">nx</code>,
|
78
|
+
<span title="Index variables of iterator dimensions">
|
79
79
|
<code>i</code>, <code>j</code>, <code>k</code>,
|
80
80
|
</span>
|
81
|
-
<code title="Number of time steps in 1 year
|
82
|
-
<code title="Number of time steps in 1 week
|
83
|
-
<code title="Number of time steps in 1 day
|
84
|
-
<code title="Number of time steps in 1 hour
|
85
|
-
<code title="Number of time steps in 1 minute
|
86
|
-
<code title="Number of time steps in 1 second
|
81
|
+
<code title="Number of time steps in 1 year">yr</code>,
|
82
|
+
<code title="Number of time steps in 1 week">wk</code>,
|
83
|
+
<code title="Number of time steps in 1 day">d</code>,
|
84
|
+
<code title="Number of time steps in 1 hour">h</code>,
|
85
|
+
<code title="Number of time steps in 1 minute">m</code>,
|
86
|
+
<code title="Number of time steps in 1 second">s</code>,
|
87
87
|
<code title="A random number from the uniform distribution U(0, 1)">random</code>),
|
88
88
|
constants (<code title="Mathematical constant π = ${Math.PI}">pi</code>,
|
89
89
|
<code title="Logical constant true = 1
|
@@ -178,8 +178,8 @@ NOTE: Grouping groups results in a single group, e.g., (1;2);(3;4;5) evaluates a
|
|
178
178
|
}
|
179
179
|
|
180
180
|
editExpression(event) {
|
181
|
-
// Infer which entity property expression is to edited from the
|
182
|
-
// that was clicked, and then opens the dialog.
|
181
|
+
// Infer which entity property expression is to be edited from the
|
182
|
+
// button that was clicked, and then opens the dialog.
|
183
183
|
const
|
184
184
|
btn = event.target,
|
185
185
|
ids = btn.id.split('-'), // 3-tuple [entity type, attribute, 'x']
|