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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "linny-r",
3
- "version": "1.8.2",
3
+ "version": "1.9.1",
4
4
  "description": "Executable graphical language with WYSIWYG editor for MILP models",
5
5
  "main": "server.js",
6
6
  "scripts": {
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 &ne; 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="../images/by-sa.svg" style="height:21px; margin-top:3px">
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 &ne; 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="../images/by-sa.svg" style="height:21px; margin-top:3px">
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">
@@ -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-2022 Delft University of Technology
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({action: 'change', solver: sid});
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 &minus; t&#8320; + 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% - 84px)' : 'calc(100% - 142px)');
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: do not list black-boxed entities
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: do not list the equations dataset
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 links is "black-boxed" when BOTH nodes are "black-boxed"
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: likewise, constraint identifiers can be prefixed by %
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: reset `selected_entity` if not in the new list
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: no "occurrence" of datasets or equations
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: a small left-pointing triangle denotes that the right-hand
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>&#x25C2;</td><td style="width:95%">' +
467
497
  e.displayName;
468
- // NOTE: equations may have LONG names while the equations dataset name
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
- // Displays documentation for the entity identified by `id`
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
- // Looks up entity, selects it in the left pane, and updates the
518
- // right pane; opens the "edit properties" modal dialog on double-click
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
- // Shows selected occurrence
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: return the object to save a second lookup by revealExpression
629
+ // NOTE: Return the object to save a second lookup by revealExpression.
599
630
  return obj;
600
631
  }
601
632