linny-r 1.8.2 → 1.9.1
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 +1 -1
- package/static/images/sel-order.png +0 -0
- package/static/index.html +31 -2
- package/static/linny-r.css +25 -0
- package/static/scripts/linny-r-config.js +1 -1
- package/static/scripts/linny-r-gui-controller.js +8 -1
- package/static/scripts/linny-r-gui-equation-manager.js +20 -2
- package/static/scripts/linny-r-gui-experiment-manager.js +25 -1
- package/static/scripts/linny-r-gui-expression-editor.js +2 -1
- package/static/scripts/linny-r-gui-file-manager.js +6 -1
- package/static/scripts/linny-r-gui-finder.js +74 -43
- package/static/scripts/linny-r-gui-model-autosaver.js +4 -1
- package/static/scripts/linny-r-gui-monitor.js +1 -0
- package/static/scripts/linny-r-gui-repository-browser.js +6 -5
- package/static/scripts/linny-r-milp.js +8 -6
- package/static/scripts/linny-r-model.js +45 -28
- package/static/scripts/linny-r-utils.js +35 -9
- package/static/scripts/linny-r-vm.js +108 -60
package/package.json
CHANGED
Binary file
|
package/static/index.html
CHANGED
@@ -750,6 +750,13 @@ NOTE: Unit symbols are case-sensitive, so BTU ≠ Btu">
|
|
750
750
|
placeholder="1e-4" type="text" autocomplete="off">
|
751
751
|
</td>
|
752
752
|
</tr>
|
753
|
+
<tr title="When checked, small slack uses are reported in monitor">
|
754
|
+
<td style="padding: 0px; width: 20px">
|
755
|
+
<div id="solver-show-notices" class="box clear"></div>
|
756
|
+
</td>
|
757
|
+
<td style="padding-bottom:4px">Report small slack uses</td>
|
758
|
+
</td>
|
759
|
+
</tr>
|
753
760
|
</table>
|
754
761
|
</div>
|
755
762
|
</div>
|
@@ -876,7 +883,7 @@ NOTE: Unit symbols are case-sensitive, so BTU ≠ Btu">
|
|
876
883
|
<a href="https://creativecommons.org/licenses/by-sa/4.0"
|
877
884
|
target="_blank">
|
878
885
|
Creative Commons Attribution-ShareAlike (CC BY-SA) license</a>.
|
879
|
-
<img src="
|
886
|
+
<img src="images/by-sa.svg" style="height:21px; margin-top:3px">
|
880
887
|
</td>
|
881
888
|
</tr>
|
882
889
|
</table>
|
@@ -908,7 +915,7 @@ NOTE: Unit symbols are case-sensitive, so BTU ≠ Btu">
|
|
908
915
|
<a href="https://creativecommons.org/licenses/by-sa/4.0"
|
909
916
|
target="_blank">
|
910
917
|
Creative Commons Attribution-ShareAlike (CC BY-SA) license</a>.
|
911
|
-
<img src="
|
918
|
+
<img src="images/by-sa.svg" style="height:21px; margin-top:3px">
|
912
919
|
</td>
|
913
920
|
</tr>
|
914
921
|
</table>
|
@@ -1848,6 +1855,8 @@ NOTE: * and ? will be interpreted as wildcards"
|
|
1848
1855
|
title="Edit selected equation">
|
1849
1856
|
<img id="eq-delete-btn" class="btn disab" src="images/delete.png"
|
1850
1857
|
title="Delete selected equation">
|
1858
|
+
<img id="eq-view-btn" class="btn enab" src="images/zoom-in.png"
|
1859
|
+
title="Switch to multi-line expression display">
|
1851
1860
|
</div>
|
1852
1861
|
<img id="equation-outcome" class="not-selected" src="images/outcome.png"
|
1853
1862
|
title="Click to consider/ignore selected equation as experiment outcome">
|
@@ -2266,6 +2275,8 @@ NOTE: * and ? will be interpreted as wildcards"
|
|
2266
2275
|
title="Rename selected experiment">
|
2267
2276
|
<img id="xp-view-btn" class="btn disab" src="images/table.png"
|
2268
2277
|
title="View selected experiment">
|
2278
|
+
<img id="xp-order-btn" class="btn enab" src="images/sel-order.png"
|
2279
|
+
title="Specify ordering of selectors">
|
2269
2280
|
<img id="xp-reset-btn" class="btn enab off" src="images/reset.png"
|
2270
2281
|
title="Clear results for selected experiment">
|
2271
2282
|
<img id="xp-delete-btn" class="btn disab" src="images/delete.png"
|
@@ -2429,6 +2440,24 @@ NOTE: * and ? will be interpreted as wildcards"
|
|
2429
2440
|
</div>
|
2430
2441
|
</div>
|
2431
2442
|
|
2443
|
+
<!-- the SELECTOR ORDER dialog permits specifying how selectors should
|
2444
|
+
be sorted. The selectors may contain a leading or trailing * as
|
2445
|
+
wildcard, and can be separated by spacrs, comma's or semicolons.
|
2446
|
+
-->
|
2447
|
+
<div id="sel-order-modal" class="modal">
|
2448
|
+
<div id="sel-order-dlg" class="inp-dlg">
|
2449
|
+
<div class="dlg-title">
|
2450
|
+
Ordering of selectors
|
2451
|
+
<img class="ok-btn" src="images/ok.png">
|
2452
|
+
<img class="cancel-btn" src="images/cancel.png">
|
2453
|
+
</div>
|
2454
|
+
<textarea id="sel-order-lines" autocomplete="off"
|
2455
|
+
placeholder="Selectors must be separated by spaces or new lines."
|
2456
|
+
autocorrect="off" autocapitalize="off" spellcheck="false">
|
2457
|
+
</textarea>
|
2458
|
+
</div>
|
2459
|
+
</div>
|
2460
|
+
|
2432
2461
|
<!-- the PARAMETER dialog prompts for a dataset or chart name -->
|
2433
2462
|
<div id="xp-parameter-modal" class="modal">
|
2434
2463
|
<div id="xp-parameter-dlg" class="inp-dlg">
|
package/static/linny-r.css
CHANGED
@@ -2506,12 +2506,26 @@ td.equation-expression {
|
|
2506
2506
|
white-space: nowrap;
|
2507
2507
|
overflow: hidden;
|
2508
2508
|
text-overflow: ellipsis;
|
2509
|
+
font-family: monospace;
|
2510
|
+
font-size: 12px;
|
2509
2511
|
}
|
2510
2512
|
|
2511
2513
|
td.equation-expression {
|
2512
2514
|
min-width: 60%;
|
2513
2515
|
}
|
2514
2516
|
|
2517
|
+
td.equation-expression-multi {
|
2518
|
+
border-left: solid 1px Silver;
|
2519
|
+
white-space: normal;
|
2520
|
+
overflow-x: hidden;
|
2521
|
+
overflow-y: auto;
|
2522
|
+
text-overflow: ellipsis;
|
2523
|
+
font-family: monospace;
|
2524
|
+
font-size: 11px;
|
2525
|
+
line-height: 13px;
|
2526
|
+
max-height: 65px;
|
2527
|
+
}
|
2528
|
+
|
2515
2529
|
#dataset-blackbox {
|
2516
2530
|
position: absolute;
|
2517
2531
|
bottom: 6px;
|
@@ -4006,6 +4020,17 @@ div.no-colors {
|
|
4006
4020
|
width: calc(100% - 6px);
|
4007
4021
|
}
|
4008
4022
|
|
4023
|
+
#sel-order-dlg {
|
4024
|
+
width: 220px;
|
4025
|
+
height: min-content;
|
4026
|
+
}
|
4027
|
+
|
4028
|
+
#sel-order-lines {
|
4029
|
+
margin: 2px;
|
4030
|
+
width: calc(100% - 4px);
|
4031
|
+
height: 220px;
|
4032
|
+
}
|
4033
|
+
|
4009
4034
|
#xp-parameter-dlg {
|
4010
4035
|
width: 270px;
|
4011
4036
|
height: 45px;
|
@@ -11,7 +11,7 @@ warning or information messages are displayed.
|
|
11
11
|
*/
|
12
12
|
|
13
13
|
/*
|
14
|
-
Copyright (c) 2017-
|
14
|
+
Copyright (c) 2017-2024 Delft University of Technology
|
15
15
|
|
16
16
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
17
17
|
of this software and associated documentation files (the "Software"), to deal
|
@@ -1025,7 +1025,12 @@ class GUIController extends Controller {
|
|
1025
1025
|
' as preferred solver');
|
1026
1026
|
return;
|
1027
1027
|
}
|
1028
|
-
const pd = postData({
|
1028
|
+
const pd = postData({
|
1029
|
+
action: 'change',
|
1030
|
+
solver: sid,
|
1031
|
+
user: VM.solver_user,
|
1032
|
+
token: VM.solver_token
|
1033
|
+
});
|
1029
1034
|
fetch('solver/', pd)
|
1030
1035
|
.then((response) => {
|
1031
1036
|
if(!response.ok) {
|
@@ -3684,6 +3689,7 @@ console.log('HERE name conflicts', name_conflicts, mapping);
|
|
3684
3689
|
md.element('preference').innerHTML = html.join('');
|
3685
3690
|
md.element('int-feasibility').value = MODEL.integer_tolerance;
|
3686
3691
|
md.element('mip-gap').value = MODEL.MIP_gap;
|
3692
|
+
this.setBox('solver-show-notices', MODEL.show_notices);
|
3687
3693
|
md.show();
|
3688
3694
|
}
|
3689
3695
|
|
@@ -3712,6 +3718,7 @@ console.log('HERE name conflicts', name_conflicts, mapping);
|
|
3712
3718
|
}
|
3713
3719
|
MODEL.integer_tolerance = Math.max(1e-9, Math.min(0.1, itol));
|
3714
3720
|
MODEL.MIP_gap = Math.max(0, Math.min(0.5, mgap));
|
3721
|
+
MODEL.show_notices = this.boxChecked('solver-show-notices');
|
3715
3722
|
// Close the dialog.
|
3716
3723
|
md.hide();
|
3717
3724
|
}
|
@@ -54,6 +54,9 @@ class EquationManager {
|
|
54
54
|
'click', () => EQUATION_MANAGER.editEquation());
|
55
55
|
document.getElementById('eq-delete-btn').addEventListener(
|
56
56
|
'click', () => EQUATION_MANAGER.deleteEquation());
|
57
|
+
this.view_btn = document.getElementById('eq-view-btn');
|
58
|
+
this.view_btn.addEventListener(
|
59
|
+
'click', () => EQUATION_MANAGER.toggleMultiLineDisplay());
|
57
60
|
this.outcome_btn = document.getElementById('equation-outcome');
|
58
61
|
this.outcome_btn.addEventListener(
|
59
62
|
'click', () => EQUATION_MANAGER.toggleOutcome());
|
@@ -78,6 +81,7 @@ class EquationManager {
|
|
78
81
|
'click', () => EQUATION_MANAGER.clone_modal.hide());
|
79
82
|
|
80
83
|
// Initialize the dialog properties
|
84
|
+
this.multi_line = false;
|
81
85
|
this.reset();
|
82
86
|
}
|
83
87
|
|
@@ -130,6 +134,19 @@ class EquationManager {
|
|
130
134
|
}
|
131
135
|
}
|
132
136
|
|
137
|
+
toggleMultiLineDisplay() {
|
138
|
+
// Toggle between single-line view and multi-line expressions.
|
139
|
+
this.multi_line = !this.multi_line;
|
140
|
+
if(this.multi_line) {
|
141
|
+
this.view_btn.src = 'images/zoom-out.png';
|
142
|
+
this.view_btn.title = 'Switch to single-line expression display';
|
143
|
+
} else {
|
144
|
+
this.view_btn.src = 'images/zoom-in.png';
|
145
|
+
this.view_btn.title = 'Switch to multi-line expression display';
|
146
|
+
}
|
147
|
+
this.updateDialog();
|
148
|
+
}
|
149
|
+
|
133
150
|
updateDialog() {
|
134
151
|
// Updates equation list, highlighting selected equation (if any)
|
135
152
|
const
|
@@ -148,10 +165,11 @@ class EquationManager {
|
|
148
165
|
m = ed.modifiers[UI.nameToID(msl[i])],
|
149
166
|
wild = (m.selector.indexOf('??') >= 0),
|
150
167
|
method = m.selector.startsWith(':'),
|
168
|
+
multi = (this.multi_line ? '-multi' : ''),
|
151
169
|
issue = (m.expression.compile_issue ? ' compile-issue' :
|
152
170
|
(m.expression.compute_issue ? ' compute-issue' : '')),
|
153
171
|
clk = '" onclick="EQUATION_MANAGER.selectModifier(event, \'' +
|
154
|
-
m.selector + '\'',
|
172
|
+
escapedSingleQuotes(m.selector) + '\'',
|
155
173
|
mover = (method ? ' onmouseover="EQUATION_MANAGER.showInfo(\'' +
|
156
174
|
m.identifier + '\', event.shiftKey);"' : '');
|
157
175
|
if(m === sm) smid += i;
|
@@ -165,7 +183,7 @@ class EquationManager {
|
|
165
183
|
(wild ? ' wildcard' : ''), clk, ', false);"', mover, '>',
|
166
184
|
(m.outcome_equation ? '<span class="outcome"></span>' : ''),
|
167
185
|
(wild ? wildcardFormat(m.selector) : m.selector),
|
168
|
-
'</td><td class="equation-expression', issue,
|
186
|
+
'</td><td class="equation-expression', multi, issue,
|
169
187
|
(issue ? '"title="' +
|
170
188
|
safeDoubleQuotes(m.expression.compile_issue ||
|
171
189
|
m.expression.compute_issue) : ''),
|
@@ -46,6 +46,9 @@ class GUIExperimentManager extends ExperimentManager {
|
|
46
46
|
this.view_btn = document.getElementById('xp-view-btn');
|
47
47
|
this.view_btn.addEventListener(
|
48
48
|
'click', () => EXPERIMENT_MANAGER.viewerMode());
|
49
|
+
this.sel_order_btn = document.getElementById('xp-order-btn');
|
50
|
+
this.sel_order_btn.addEventListener(
|
51
|
+
'click', () => EXPERIMENT_MANAGER.showSelectorOrder());
|
49
52
|
this.reset_btn = document.getElementById('xp-reset-btn');
|
50
53
|
this.reset_btn.addEventListener(
|
51
54
|
'click', () => EXPERIMENT_MANAGER.clearRunResults());
|
@@ -154,6 +157,13 @@ class GUIExperimentManager extends ExperimentManager {
|
|
154
157
|
this.rename_modal.cancel.addEventListener(
|
155
158
|
'click', () => EXPERIMENT_MANAGER.rename_modal.hide());
|
156
159
|
|
160
|
+
this.sel_order_modal = new ModalDialog('sel-order');
|
161
|
+
this.sel_order_modal.ok.addEventListener(
|
162
|
+
'click', () => EXPERIMENT_MANAGER.modifySelectorOrder());
|
163
|
+
this.sel_order_modal.cancel.addEventListener(
|
164
|
+
'click', () => EXPERIMENT_MANAGER.sel_order_modal.hide());
|
165
|
+
this.sel_order_lines = this.sel_order_modal.element('lines');
|
166
|
+
|
157
167
|
this.parameter_modal = new ModalDialog('xp-parameter');
|
158
168
|
this.parameter_modal.ok.addEventListener(
|
159
169
|
'click', () => EXPERIMENT_MANAGER.addParameter());
|
@@ -473,9 +483,23 @@ class GUIExperimentManager extends ExperimentManager {
|
|
473
483
|
}
|
474
484
|
}
|
475
485
|
}
|
486
|
+
|
487
|
+
showSelectorOrder() {
|
488
|
+
// Show selector order modal.
|
489
|
+
this.sel_order_lines.value = MODEL.selector_order_string;
|
490
|
+
this.sel_order_modal.show();
|
491
|
+
}
|
476
492
|
|
493
|
+
modifySelectorOrder() {
|
494
|
+
// Save text area contents as new selector order string.
|
495
|
+
MODEL.selector_order_string = this.sel_order_lines.value.trim();
|
496
|
+
MODEL.selector_order_list = MODEL.selector_order_string.trim().split(/\s+/);
|
497
|
+
this.sel_order_modal.hide();
|
498
|
+
UI.updateControllerDialogs('DX');
|
499
|
+
}
|
500
|
+
|
477
501
|
designMode() {
|
478
|
-
// Switch to default view
|
502
|
+
// Switch to default view.
|
479
503
|
this.viewer.style.display = 'none';
|
480
504
|
this.design.style.display = 'block';
|
481
505
|
}
|
@@ -66,6 +66,7 @@ Attributes, however, are case sensitive!">[Actor X|CF]</code> for cash flow.
|
|
66
66
|
<code title="Relative time step (t − t₀ + 1)">rt</code>,
|
67
67
|
<code title="Number of current block">b</code>,
|
68
68
|
<code title="Time step within current block">bt</code>,
|
69
|
+
<code title="Time step within current chunk">ct</code>,
|
69
70
|
<code title="Duration of 1 time step (in hours)">dt</code>,
|
70
71
|
<code title="Run length (# time steps)">N</code>,
|
71
72
|
<code title="Block length (# time steps)">n</code>,
|
@@ -380,7 +381,7 @@ NOTE: Grouping groups results in a single group, e.g., (1;2);(3;4;5) evaluates a
|
|
380
381
|
}
|
381
382
|
va.innerHTML = options.join('');
|
382
383
|
// NOTE: Chart Manager variable dialog is 60px wider
|
383
|
-
va.style.width = (prefix ? 'calc(100% -
|
384
|
+
va.style.width = (prefix ? 'calc(100% - 86px)' : 'calc(100% - 142px)');
|
384
385
|
return;
|
385
386
|
}
|
386
387
|
// Add "empty" as first and initial option, as it denotes "use default"
|
@@ -333,7 +333,12 @@ class GUIFileManager {
|
|
333
333
|
return response.text();
|
334
334
|
})
|
335
335
|
.then((data) => {
|
336
|
-
UI.postResponseOK(data)
|
336
|
+
if(!UI.postResponseOK(data) && data.indexOf('not implemented') >= 0) {
|
337
|
+
// Switch off auto-save when server does not implement it.
|
338
|
+
AUTO_SAVE.interval = 0;
|
339
|
+
AUTO_SAVE.not_implemented = true;
|
340
|
+
console.log('Auto-save disabled');
|
341
|
+
}
|
337
342
|
bcl.remove('stay-activ');
|
338
343
|
})
|
339
344
|
.catch((err) => {
|
@@ -40,7 +40,7 @@ class Finder {
|
|
40
40
|
this.dialog = UI.draggableDialog('finder');
|
41
41
|
UI.resizableDialog('finder', 'FINDER');
|
42
42
|
this.close_btn = document.getElementById('finder-close-btn');
|
43
|
-
// Make toolbar buttons responsive
|
43
|
+
// Make toolbar buttons responsive.
|
44
44
|
this.close_btn.addEventListener('click', (e) => UI.toggleDialog(e));
|
45
45
|
this.filter_input = document.getElementById('finder-filter-text');
|
46
46
|
this.filter_input.addEventListener('input', () => FINDER.changeFilter());
|
@@ -78,6 +78,7 @@ class Finder {
|
|
78
78
|
this.filtered_types.length = 0;
|
79
79
|
this.selected_entity = null;
|
80
80
|
this.filter_input.value = '';
|
81
|
+
this.filter_string = '';
|
81
82
|
this.filter_pattern = null;
|
82
83
|
this.entity_types = VM.entity_letters;
|
83
84
|
this.find_links = true;
|
@@ -105,7 +106,7 @@ class Finder {
|
|
105
106
|
}
|
106
107
|
|
107
108
|
enterKey() {
|
108
|
-
// Open "edit properties" dialog for the selected entity
|
109
|
+
// Open "edit properties" dialog for the selected entity.
|
109
110
|
const srl = this.entity_table.getElementsByClassName('sel-set');
|
110
111
|
if(srl.length > 0) {
|
111
112
|
const r = this.entity_table.rows[srl[0].rowIndex];
|
@@ -118,7 +119,7 @@ class Finder {
|
|
118
119
|
}
|
119
120
|
|
120
121
|
upDownKey(dir) {
|
121
|
-
// Select row above or below the selected one (if possible)
|
122
|
+
// Select row above or below the selected one (if possible).
|
122
123
|
const srl = this.entity_table.getElementsByClassName('sel-set');
|
123
124
|
if(srl.length > 0) {
|
124
125
|
const r = this.entity_table.rows[srl[0].rowIndex + dir];
|
@@ -139,7 +140,7 @@ class Finder {
|
|
139
140
|
let imgs = '';
|
140
141
|
this.entities.length = 0;
|
141
142
|
this.filtered_types.length = 0;
|
142
|
-
// No list unless a pattern OR a specified SUB-set of entity types
|
143
|
+
// No list unless a pattern OR a specified SUB-set of entity types.
|
143
144
|
if(fp || et && et !== VM.entity_letters) {
|
144
145
|
if(et.indexOf('A') >= 0) {
|
145
146
|
imgs += '<img src="images/actor.png">';
|
@@ -151,7 +152,7 @@ class Finder {
|
|
151
152
|
}
|
152
153
|
}
|
153
154
|
}
|
154
|
-
// NOTE:
|
155
|
+
// NOTE: Do not list black-boxed entities.
|
155
156
|
if(et.indexOf('P') >= 0) {
|
156
157
|
imgs += '<img src="images/process.png">';
|
157
158
|
for(let k in MODEL.processes) if(MODEL.processes.hasOwnProperty(k)) {
|
@@ -191,7 +192,7 @@ class Finder {
|
|
191
192
|
const ds = MODEL.datasets[k];
|
192
193
|
if(!k.startsWith(UI.BLACK_BOX) && (!fp || patternMatch(
|
193
194
|
ds.displayName, this.filter_pattern))) {
|
194
|
-
// NOTE:
|
195
|
+
// NOTE: Do not list the equations dataset.
|
195
196
|
if(ds !== MODEL.equations_dataset) {
|
196
197
|
enl.push(k);
|
197
198
|
this.entities.push(MODEL.datasets[k]);
|
@@ -217,11 +218,11 @@ class Finder {
|
|
217
218
|
if(et.indexOf('L') >= 0) {
|
218
219
|
imgs += '<img src="images/link.png">';
|
219
220
|
for(let k in MODEL.links) if(MODEL.links.hasOwnProperty(k)) {
|
220
|
-
// NOTE: "black-boxed" link identifiers are not prefixed => other test
|
221
|
+
// NOTE: "black-boxed" link identifiers are not prefixed => other test.
|
221
222
|
const
|
222
223
|
l = MODEL.links[k],
|
223
224
|
ldn = l.displayName,
|
224
|
-
// A
|
225
|
+
// A link is "black-boxed" when BOTH nodes are "black-boxed".
|
225
226
|
bb = ldn.split(UI.BLACK_BOX).length > 2;
|
226
227
|
if(!bb && (!fp || patternMatch(ldn, this.filter_pattern))) {
|
227
228
|
enl.push(k);
|
@@ -233,7 +234,7 @@ class Finder {
|
|
233
234
|
if(et.indexOf('B') >= 0) {
|
234
235
|
imgs += '<img src="images/constraint.png">';
|
235
236
|
for(let k in MODEL.constraints) {
|
236
|
-
// NOTE:
|
237
|
+
// NOTE: Likewise, constraint identifiers can be prefixed by %.
|
237
238
|
if(MODEL.constraints.hasOwnProperty(k)) {
|
238
239
|
if(!k.startsWith(UI.BLACK_BOX) && (!fp || patternMatch(
|
239
240
|
MODEL.constraints[k].displayName, this.filter_pattern))) {
|
@@ -244,6 +245,35 @@ class Finder {
|
|
244
245
|
}
|
245
246
|
}
|
246
247
|
}
|
248
|
+
// Also allow search for scale unit names.
|
249
|
+
if(et.indexOf('U') >= 0) {
|
250
|
+
imgs += '<img src="images/scale.png">';
|
251
|
+
for(let k in MODEL.products) if(MODEL.products.hasOwnProperty(k)) {
|
252
|
+
if(fp && !k.startsWith(UI.BLACK_BOX) && patternMatch(
|
253
|
+
MODEL.products[k].scale_unit, this.filter_pattern)) {
|
254
|
+
enl.push(k);
|
255
|
+
this.entities.push(MODEL.products[k]);
|
256
|
+
addDistinct('Q', this.filtered_types);
|
257
|
+
}
|
258
|
+
}
|
259
|
+
}
|
260
|
+
// Also allow search for link multiplier symbols.
|
261
|
+
if(et.indexOf('M') >= 0) {
|
262
|
+
if(imgs.indexOf('/link.') < 0) imgs += '<img src="images/link.png">';
|
263
|
+
for(let k in MODEL.links) if(MODEL.links.hasOwnProperty(k)) {
|
264
|
+
// NOTE: "black-boxed" link identifiers are not prefixed => other test.
|
265
|
+
const
|
266
|
+
l = MODEL.links[k],
|
267
|
+
m = VM.LM_LETTERS.charAt(l.multiplier),
|
268
|
+
// A link is "black-boxed" when BOTH nodes are "black-boxed".
|
269
|
+
bb = l.displayName.split(UI.BLACK_BOX).length > 2;
|
270
|
+
if(fp && !bb && this.filter_string.indexOf(m) >= 0) {
|
271
|
+
enl.push(k);
|
272
|
+
this.entities.push(l);
|
273
|
+
addDistinct('L', this.filtered_types);
|
274
|
+
}
|
275
|
+
}
|
276
|
+
}
|
247
277
|
enl.sort((a, b) => UI.compareFullNames(a, b, true));
|
248
278
|
}
|
249
279
|
document.getElementById('finder-entity-imgs').innerHTML = imgs;
|
@@ -259,7 +289,7 @@ class Finder {
|
|
259
289
|
e.type.toLowerCase(), '.png">', e.displayName,
|
260
290
|
'</td></tr>'].join(''));
|
261
291
|
}
|
262
|
-
// NOTE:
|
292
|
+
// NOTE: Reset `selected_entity` if not in the new list.
|
263
293
|
if(seid === 'etr') this.selected_entity = null;
|
264
294
|
this.entity_table.innerHTML = el.join('');
|
265
295
|
UI.scrollIntoView(document.getElementById(seid));
|
@@ -316,18 +346,18 @@ class Finder {
|
|
316
346
|
let hdr = '(no entity selected)';
|
317
347
|
if(se) {
|
318
348
|
hdr = `<em>${se.type}:</em> <strong>${se.displayName}</strong>`;
|
319
|
-
// Make occurrence list
|
349
|
+
// Make occurrence list.
|
320
350
|
if(se instanceof Process || se instanceof Cluster) {
|
321
|
-
// Processes and clusters "occur" in their parent cluster
|
351
|
+
// Processes and clusters "occur" in their parent cluster.
|
322
352
|
if(se.cluster) occ.push(se.cluster.identifier);
|
323
353
|
} else if(se instanceof Product) {
|
324
|
-
// Products "occur" in clusters where they have a position
|
354
|
+
// Products "occur" in clusters where they have a position.
|
325
355
|
const cl = se.productPositionClusters;
|
326
356
|
for(let i = 0; i < cl.length; i++) {
|
327
357
|
occ.push(cl[i].identifier);
|
328
358
|
}
|
329
359
|
} else if(se instanceof Actor) {
|
330
|
-
// Actors "occur" in clusters where they "own" processes or clusters
|
360
|
+
// Actors "occur" in clusters where they "own" processes or clusters.
|
331
361
|
for(let k in MODEL.processes) if(MODEL.processes.hasOwnProperty(k)) {
|
332
362
|
const p = MODEL.processes[k];
|
333
363
|
if(p.actor === se) occ.push(p.identifier);
|
@@ -337,13 +367,13 @@ class Finder {
|
|
337
367
|
if(c.actor === se) occ.push(c.identifier);
|
338
368
|
}
|
339
369
|
} else if(se instanceof Link || se instanceof Constraint) {
|
340
|
-
// Links and constraints "occur" in their "best" parent cluster
|
370
|
+
// Links and constraints "occur" in their "best" parent cluster.
|
341
371
|
const c = MODEL.inferParentCluster(se);
|
342
372
|
if(c) occ.push(c.identifier);
|
343
373
|
}
|
344
|
-
// NOTE:
|
374
|
+
// NOTE: No "occurrence" of datasets or equations.
|
345
375
|
// @@TO DO: identify MODULES (?)
|
346
|
-
// All entities can also occur as chart variables
|
376
|
+
// All entities can also occur as chart variables.
|
347
377
|
for(let j = 0; j < MODEL.charts.length; j++) {
|
348
378
|
const c = MODEL.charts[j];
|
349
379
|
for(let k = 0; k < c.variables.length; k++) {
|
@@ -355,12 +385,12 @@ class Finder {
|
|
355
385
|
}
|
356
386
|
}
|
357
387
|
}
|
358
|
-
// Now also look for occurrences of entity references in expressions
|
388
|
+
// Now also look for occurrences of entity references in expressions.
|
359
389
|
const
|
360
390
|
raw = escapeRegex(se.displayName),
|
361
391
|
re = new RegExp(
|
362
392
|
'\\[\\s*!?' + raw.replace(/\s+/g, '\\s+') + '\\s*[\\|\\@\\]]');
|
363
|
-
// Check actor weight expressions
|
393
|
+
// Check actor weight expressions.
|
364
394
|
for(let k in MODEL.actors) if(MODEL.actors.hasOwnProperty(k)) {
|
365
395
|
const a = MODEL.actors[k];
|
366
396
|
if(re.test(a.weight.text)) {
|
@@ -368,7 +398,7 @@ class Finder {
|
|
368
398
|
xol.push(a.identifier);
|
369
399
|
}
|
370
400
|
}
|
371
|
-
// Check all process attribute expressions
|
401
|
+
// Check all process attribute expressions.
|
372
402
|
for(let k in MODEL.processes) if(MODEL.processes.hasOwnProperty(k)) {
|
373
403
|
const p = MODEL.processes[k];
|
374
404
|
if(re.test(p.lower_bound.text)) {
|
@@ -384,7 +414,7 @@ class Finder {
|
|
384
414
|
xol.push(p.identifier);
|
385
415
|
}
|
386
416
|
}
|
387
|
-
// Check all product attribute expressions
|
417
|
+
// Check all product attribute expressions.
|
388
418
|
for(let k in MODEL.products) if(MODEL.products.hasOwnProperty(k)) {
|
389
419
|
const p = MODEL.products[k];
|
390
420
|
if(re.test(p.lower_bound.text)) {
|
@@ -404,7 +434,7 @@ class Finder {
|
|
404
434
|
xol.push(p.identifier);
|
405
435
|
}
|
406
436
|
}
|
407
|
-
// Check all notes in clusters for their color expressions and field
|
437
|
+
// Check all notes in clusters for their color expressions and field.
|
408
438
|
for(let k in MODEL.clusters) if(MODEL.clusters.hasOwnProperty(k)) {
|
409
439
|
const c = MODEL.clusters[k];
|
410
440
|
for(let i = 0; i < c.notes.length; i++) {
|
@@ -416,7 +446,7 @@ class Finder {
|
|
416
446
|
}
|
417
447
|
}
|
418
448
|
}
|
419
|
-
// Check all link rate expressions
|
449
|
+
// Check all link rate expressions.
|
420
450
|
for(let k in MODEL.links) if(MODEL.links.hasOwnProperty(k)) {
|
421
451
|
const l = MODEL.links[k];
|
422
452
|
if(re.test(l.relative_rate.text)) {
|
@@ -428,7 +458,7 @@ class Finder {
|
|
428
458
|
xol.push(l.identifier);
|
429
459
|
}
|
430
460
|
}
|
431
|
-
// Check all dataset modifier expressions
|
461
|
+
// Check all dataset modifier expressions.
|
432
462
|
for(let k in MODEL.datasets) if(MODEL.datasets.hasOwnProperty(k)) {
|
433
463
|
const ds = MODEL.datasets[k];
|
434
464
|
for(let m in ds.modifiers) if(ds.modifiers.hasOwnProperty(m)) {
|
@@ -451,22 +481,22 @@ class Finder {
|
|
451
481
|
'</td></tr>'].join(''));
|
452
482
|
}
|
453
483
|
this.item_table.innerHTML = el.join('');
|
454
|
-
// Clear the table row list
|
484
|
+
// Clear the table row list.
|
455
485
|
el.length = 0;
|
456
|
-
// Now fill it with entity+attribute having a matching expression
|
486
|
+
// Now fill it with entity+attribute having a matching expression.
|
457
487
|
for(let i = 0; i < xal.length; i++) {
|
458
488
|
const
|
459
489
|
id = xol[i],
|
460
490
|
e = MODEL.objectByID(id),
|
461
491
|
attr = (e instanceof Note ? '' : xal[i]);
|
462
492
|
let img = e.type.toLowerCase(),
|
463
|
-
// NOTE:
|
464
|
-
// part has the left hand part as its attribute
|
493
|
+
// NOTE: A small left-pointing triangle denotes that the right-hand
|
494
|
+
// part has the left hand part as its attribute.
|
465
495
|
cs = '',
|
466
496
|
td = attr + '</td><td>◂</td><td style="width:95%">' +
|
467
497
|
e.displayName;
|
468
|
-
// NOTE:
|
469
|
-
// is irrelevant, hence use 3 columns (no triangle)
|
498
|
+
// NOTE: Equations may have LONG names while the equations dataset
|
499
|
+
// name is irrelevant, hence use 3 columns (no triangle).
|
470
500
|
if(e === MODEL.equations_dataset) {
|
471
501
|
img = 'equation';
|
472
502
|
cs = ' colspan="3"';
|
@@ -484,7 +514,7 @@ class Finder {
|
|
484
514
|
}
|
485
515
|
|
486
516
|
drag(ev) {
|
487
|
-
// Start dragging the selected entity
|
517
|
+
// Start dragging the selected entity.
|
488
518
|
let t = ev.target;
|
489
519
|
while(t && t.nodeName !== 'TD') t = t.parentNode;
|
490
520
|
ev.dataTransfer.setData('text', MODEL.objectByName(t.innerText).identifier);
|
@@ -493,30 +523,31 @@ class Finder {
|
|
493
523
|
|
494
524
|
changeFilter() {
|
495
525
|
// Filter expression can start with 1+ entity letters plus `?` to
|
496
|
-
// look only for the entity types denoted by these letters
|
526
|
+
// look only for the entity types denoted by these letters.
|
497
527
|
let ft = this.filter_input.value,
|
498
528
|
et = VM.entity_letters;
|
499
|
-
if(/^(\*|[ABCDELPQ]+)\?/i.test(ft)) {
|
529
|
+
if(/^(\*|U|M|[ABCDELPQ]+)\?/i.test(ft)) {
|
500
530
|
ft = ft.split('?');
|
501
|
-
// NOTE: *? denotes "all entity types except constraints"
|
531
|
+
// NOTE: *? denotes "all entity types except constraints".
|
502
532
|
et = (ft[0] === '*' ? 'ACDELPQ' : ft[0].toUpperCase());
|
503
533
|
ft = ft.slice(1).join('=');
|
504
534
|
}
|
535
|
+
this.filter_string = ft;
|
505
536
|
this.filter_pattern = patternList(ft);
|
506
537
|
this.entity_types = et;
|
507
538
|
this.updateDialog();
|
508
539
|
}
|
509
540
|
|
510
541
|
showInfo(id, shift) {
|
511
|
-
//
|
542
|
+
// Display documentation for the entity identified by `id`.
|
512
543
|
const e = MODEL.objectByID(id);
|
513
544
|
if(e) DOCUMENTATION_MANAGER.update(e, shift);
|
514
545
|
}
|
515
546
|
|
516
547
|
selectEntity(id, alt=false) {
|
517
|
-
//
|
518
|
-
//
|
519
|
-
// and Alt-click if the entity is editable
|
548
|
+
// Look up entity, select it in the left pane, and update the right
|
549
|
+
// pane. Open the "edit properties" modal dialog on double-click
|
550
|
+
// and Alt-click if the entity is editable.
|
520
551
|
const obj = MODEL.objectByID(id);
|
521
552
|
this.selected_entity = obj;
|
522
553
|
this.updateDialog();
|
@@ -551,7 +582,7 @@ class Finder {
|
|
551
582
|
}
|
552
583
|
|
553
584
|
reveal(id) {
|
554
|
-
//
|
585
|
+
// Show selected occurrence.
|
555
586
|
const
|
556
587
|
se = this.selected_entity,
|
557
588
|
obj = (se ? MODEL.objectByID(id) : null);
|
@@ -559,7 +590,7 @@ class Finder {
|
|
559
590
|
// If cluster, make it focal...
|
560
591
|
if(obj instanceof Cluster) {
|
561
592
|
UI.makeFocalCluster(obj);
|
562
|
-
// ... and select the entity unless it is an actor or dataset
|
593
|
+
// ... and select the entity unless it is an actor or dataset.
|
563
594
|
if(!(se instanceof Actor || se instanceof Dataset)) {
|
564
595
|
MODEL.select(se);
|
565
596
|
if(se instanceof Link || se instanceof Constraint) {
|
@@ -572,7 +603,7 @@ class Finder {
|
|
572
603
|
} else if(obj instanceof Process || obj instanceof Note) {
|
573
604
|
// If occurrence is a process or a note, then make its cluster focal...
|
574
605
|
UI.makeFocalCluster(obj.cluster);
|
575
|
-
// ... and select it
|
606
|
+
// ... and select it.
|
576
607
|
MODEL.select(obj);
|
577
608
|
UI.scrollIntoView(obj.shape.element.childNodes[0]);
|
578
609
|
} else if(obj instanceof Product) {
|
@@ -586,7 +617,7 @@ class Finder {
|
|
586
617
|
if(a) UI.scrollIntoView(a.shape.element.childNodes[0]);
|
587
618
|
}
|
588
619
|
} else if(obj instanceof Chart) {
|
589
|
-
// If occurrence is a chart, select and show it in the chart manager
|
620
|
+
// If occurrence is a chart, select and show it in the chart manager.
|
590
621
|
CHART_MANAGER.chart_index = MODEL.charts.indexOf(obj);
|
591
622
|
if(CHART_MANAGER.chart_index >= 0) {
|
592
623
|
if(UI.hidden('chart-dlg')) {
|
@@ -595,7 +626,7 @@ class Finder {
|
|
595
626
|
}
|
596
627
|
CHART_MANAGER.updateDialog();
|
597
628
|
}
|
598
|
-
// NOTE:
|
629
|
+
// NOTE: Return the object to save a second lookup by revealExpression.
|
599
630
|
return obj;
|
600
631
|
}
|
601
632
|
|