linny-r 2.0.12 → 2.1.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/update.png +0 -0
- package/static/index.html +34 -3
- package/static/linny-r.css +42 -3
- package/static/scripts/linny-r-ctrl.js +6 -0
- package/static/scripts/linny-r-gui-actor-manager.js +6 -6
- package/static/scripts/linny-r-gui-chart-manager.js +54 -32
- package/static/scripts/linny-r-gui-controller.js +52 -35
- package/static/scripts/linny-r-gui-dataset-manager.js +7 -16
- package/static/scripts/linny-r-gui-monitor.js +3 -2
- package/static/scripts/linny-r-gui-paper.js +10 -1
- package/static/scripts/linny-r-gui-repository-browser.js +304 -17
- package/static/scripts/linny-r-gui-undo-redo.js +41 -52
- package/static/scripts/linny-r-model.js +429 -308
- package/static/scripts/linny-r-utils.js +21 -23
- package/static/scripts/linny-r-vm.js +103 -17
package/package.json
CHANGED
Binary file
|
package/static/index.html
CHANGED
@@ -914,6 +914,8 @@ NOTE: Products directly linked to such processes should have a proportional unit
|
|
914
914
|
title="Include module in current model">
|
915
915
|
<img id="repo-load-btn" class="btn disab" src="images/open.png"
|
916
916
|
title="Load model from repository">
|
917
|
+
<img id="repo-update-btn" class="btn disab" src="images/update.png"
|
918
|
+
title="Update clusters that have been included in the model">
|
917
919
|
<img id="repo-store-btn" class="btn enab" src="images/store.png"
|
918
920
|
title="Store model as module in repository">
|
919
921
|
<img id="repo-black-box-btn" class="btn enab" src="images/black-box.png"
|
@@ -1061,9 +1063,6 @@ NOTE: Products directly linked to such processes should have a proportional unit
|
|
1061
1063
|
<img class="cancel-btn" src="images/cancel.png">
|
1062
1064
|
<img class="ok-btn" src="images/ok.png">
|
1063
1065
|
</div>
|
1064
|
-
<div style="margin:2px; background-color: Yellow">
|
1065
|
-
<strong>NOTE:</strong> <em>This action cannot be undone!</em>
|
1066
|
-
</div>
|
1067
1066
|
<div style="margin:2px">
|
1068
1067
|
<label>Cluster:</label>
|
1069
1068
|
<input id="include-prefix" type="text"
|
@@ -1082,6 +1081,38 @@ NOTE: Products directly linked to such processes should have a proportional unit
|
|
1082
1081
|
</div>
|
1083
1082
|
</div>
|
1084
1083
|
|
1084
|
+
<!-- the UPDATE dialog prompts for a module name -->
|
1085
|
+
<div id="update-modal" class="modal">
|
1086
|
+
<div id="update-dlg" class="inp-dlg" style="min-width: 320px">
|
1087
|
+
<div class="dlg-title">
|
1088
|
+
Update clusters previously included from a module
|
1089
|
+
<img class="cancel-btn" src="images/cancel.png">
|
1090
|
+
<img class="ok-btn" src="images/ok.png">
|
1091
|
+
</div>
|
1092
|
+
<div style="padding: 2px">
|
1093
|
+
<div>
|
1094
|
+
Update using module: <span id="update-name"></span>
|
1095
|
+
</div>
|
1096
|
+
<div>
|
1097
|
+
<label>Clusters based on:</label>
|
1098
|
+
<select id="update-module">
|
1099
|
+
</select>
|
1100
|
+
<div id="update-count" class="update-cc"></div>
|
1101
|
+
</div>
|
1102
|
+
<div id="update-issues">
|
1103
|
+
<div id="update-issues-header"></div>
|
1104
|
+
<div id="update-issues-area"></div>
|
1105
|
+
</div>
|
1106
|
+
<div id="update-remove">
|
1107
|
+
<div id="update-remove-header">
|
1108
|
+
<span id="update-remove-count"></span> will be removed
|
1109
|
+
</div>
|
1110
|
+
<div id="update-remove-area"></div>
|
1111
|
+
</div>
|
1112
|
+
</div>
|
1113
|
+
</div>
|
1114
|
+
</div>
|
1115
|
+
|
1085
1116
|
<!-- the CONFIRM LOAD modal asks to confirm to load model from repository -->
|
1086
1117
|
<div id="confirm-load-from-repo-modal" class="modal">
|
1087
1118
|
<div id="confirm-load-from-repo-dlg" class="inp-dlg">
|
package/static/linny-r.css
CHANGED
@@ -607,6 +607,11 @@ span.node-details {
|
|
607
607
|
margin-left: 15px;
|
608
608
|
}
|
609
609
|
|
610
|
+
span.mod-name {
|
611
|
+
font-style: normal;
|
612
|
+
color: #403080;
|
613
|
+
}
|
614
|
+
|
610
615
|
#issue-panel {
|
611
616
|
display: none;
|
612
617
|
background-color: Yellow;
|
@@ -707,6 +712,11 @@ img.inline-cancel-btn:hover {
|
|
707
712
|
filter: brightness(120%);
|
708
713
|
}
|
709
714
|
|
715
|
+
img.ok-btn.disab {
|
716
|
+
filter: saturate(0%) brightness(150%) contrast(70%);
|
717
|
+
pointer-events: none;
|
718
|
+
}
|
719
|
+
|
710
720
|
#actor-group,
|
711
721
|
#constraint-group,
|
712
722
|
#cluster-group,
|
@@ -4152,8 +4162,9 @@ td.sa-not-run {
|
|
4152
4162
|
|
4153
4163
|
#xp-ignore-count {
|
4154
4164
|
position: absolute;
|
4155
|
-
right:
|
4165
|
+
right: 2px;
|
4156
4166
|
bottom: 3px;
|
4167
|
+
width: 18px;
|
4157
4168
|
height: 16px;
|
4158
4169
|
font-size: 9px;
|
4159
4170
|
color: #806070;
|
@@ -4882,6 +4893,7 @@ span.sd-clear {
|
|
4882
4893
|
}
|
4883
4894
|
|
4884
4895
|
#paste-dlg,
|
4896
|
+
#update-dlg,
|
4885
4897
|
#include-dlg {
|
4886
4898
|
width: 320px;
|
4887
4899
|
height: min-content;
|
@@ -4907,9 +4919,29 @@ span.sd-clear {
|
|
4907
4919
|
margin-left: 9px;
|
4908
4920
|
}
|
4909
4921
|
|
4922
|
+
#update-name {
|
4923
|
+
font-family: monospace;
|
4924
|
+
font-size: 14px;
|
4925
|
+
}
|
4926
|
+
|
4927
|
+
div.update-cc {
|
4928
|
+
display: inline-block;
|
4929
|
+
margin-left: 6px;
|
4930
|
+
font-style: italic;
|
4931
|
+
color: Gray;
|
4932
|
+
}
|
4933
|
+
|
4934
|
+
#update-remove-header,
|
4935
|
+
#update-issues-header {
|
4936
|
+
font-weight: bold;
|
4937
|
+
margin: 4px 2px 2px 2px;
|
4938
|
+
}
|
4939
|
+
|
4910
4940
|
#paste-scroll-area,
|
4911
|
-
#include-scroll-area
|
4912
|
-
|
4941
|
+
#include-scroll-area,
|
4942
|
+
#update-remove-area,
|
4943
|
+
#update-issues-area {
|
4944
|
+
margin: 0 2px;
|
4913
4945
|
height: min-content;
|
4914
4946
|
max-height: 350px !important;
|
4915
4947
|
width: calc(100% - 4px);
|
@@ -4918,6 +4950,13 @@ span.sd-clear {
|
|
4918
4950
|
overflow-y: auto;
|
4919
4951
|
}
|
4920
4952
|
|
4953
|
+
#update-remove-area,
|
4954
|
+
#update-issues-area {
|
4955
|
+
max-height: 150px !important;
|
4956
|
+
background-color: white;
|
4957
|
+
border: 1px solid Silver;
|
4958
|
+
}
|
4959
|
+
|
4921
4960
|
div.paste-tactic {
|
4922
4961
|
display: inline-block;
|
4923
4962
|
vertical-align: top;
|
@@ -311,6 +311,12 @@ class Controller {
|
|
311
311
|
(name.startsWith(this.BLACK_BOX) || name[0].match(/[\w]/));
|
312
312
|
}
|
313
313
|
|
314
|
+
realActorName(name) {
|
315
|
+
// Return `name` unless it is '(no actor)'; then return empty string.
|
316
|
+
if(name === this.NO_ACTOR) return '';
|
317
|
+
return name;
|
318
|
+
}
|
319
|
+
|
314
320
|
prefixesAndName(name, key=false) {
|
315
321
|
// Returns name split exclusively at '[non-space]: [non-space]'
|
316
322
|
let sep = this.PREFIXER,
|
@@ -224,10 +224,10 @@ class ActorManager {
|
|
224
224
|
}
|
225
225
|
|
226
226
|
showEditActorDialog(name, expr) {
|
227
|
-
// Display modal for editing properties of one actor
|
227
|
+
// Display modal for editing properties of one actor.
|
228
228
|
this.actor_span.innerHTML = name;
|
229
229
|
this.actor_name.value = name;
|
230
|
-
// Do not allow modification of the name '(no actor)'
|
230
|
+
// Do not allow modification of the name '(no actor)'.
|
231
231
|
if(name === UI.NO_ACTOR) {
|
232
232
|
this.actor_name.disabled = true;
|
233
233
|
this.actor_io.style.display = 'none';
|
@@ -242,22 +242,22 @@ class ActorManager {
|
|
242
242
|
|
243
243
|
modifyActorEntry() {
|
244
244
|
// This method is called when the modeler submits the "actor properties"
|
245
|
-
// dialog
|
245
|
+
// dialog.
|
246
246
|
let n = this.actor_span.innerHTML,
|
247
247
|
nn = UI.NO_ACTOR,
|
248
248
|
x = this.actor_weight.value.trim(),
|
249
249
|
xp = new ExpressionParser(x);
|
250
250
|
if(n !== UI.NO_ACTOR) {
|
251
251
|
nn = this.actor_name.value.trim();
|
252
|
-
// NOTE:
|
253
|
-
// prefixed entities
|
252
|
+
// NOTE: Prohibit colons in actor names to avoid confusion with
|
253
|
+
// prefixed entities.
|
254
254
|
if(!UI.validName(nn) || nn.indexOf(':') >= 0) {
|
255
255
|
UI.warn(UI.WARNING.INVALID_ACTOR_NAME);
|
256
256
|
return false;
|
257
257
|
}
|
258
258
|
}
|
259
259
|
if(xp.error) {
|
260
|
-
// NOTE:
|
260
|
+
// NOTE: Do not pass the actor, as its name is being edited as well.
|
261
261
|
UI.warningInvalidWeightExpression(null, xp.error);
|
262
262
|
return false;
|
263
263
|
}
|
@@ -112,6 +112,10 @@ class GUIChartManager extends ChartManager {
|
|
112
112
|
this.svg_container.addEventListener(
|
113
113
|
'mouseleave', (event) => CHART_MANAGER.updateTimeStep(event, false));
|
114
114
|
this.time_step = document.getElementById('chart-time-step');
|
115
|
+
this.prefix_div = document.getElementById('chart-prefix-div');
|
116
|
+
this.prefix_selector = document.getElementById('chart-prefix');
|
117
|
+
this.prefix_selector.addEventListener(
|
118
|
+
'change', () => CHART_MANAGER.selectPrefix());
|
115
119
|
document.getElementById('chart-toggle-chevron').addEventListener(
|
116
120
|
'click', () => CHART_MANAGER.toggleControlPanel());
|
117
121
|
document.getElementById('chart-stats-btn').addEventListener(
|
@@ -354,8 +358,9 @@ class GUIChartManager extends ChartManager {
|
|
354
358
|
}
|
355
359
|
|
356
360
|
updateDialog() {
|
357
|
-
//
|
361
|
+
// Refresh all dialog fields to display actual MODEL chart properties.
|
358
362
|
this.updateSelector();
|
363
|
+
this.prefix_div.style.display = 'none';
|
359
364
|
let c = null;
|
360
365
|
if(this.chart_index >= 0) {
|
361
366
|
c = MODEL.charts[this.chart_index];
|
@@ -390,6 +395,19 @@ class GUIChartManager extends ChartManager {
|
|
390
395
|
'</td></tr>'].join(''));
|
391
396
|
}
|
392
397
|
this.variables_table.innerHTML = ol.join('');
|
398
|
+
const
|
399
|
+
cp = c.prefix,
|
400
|
+
pp = c.possiblePrefixes,
|
401
|
+
html = [];
|
402
|
+
if(pp.length) {
|
403
|
+
for(const p of pp) {
|
404
|
+
const cap = capitalized(p);
|
405
|
+
html.push('<option value="', cap, '"', (cap === cp ? ' selected' : ''), '>',
|
406
|
+
cap, '</option>');
|
407
|
+
}
|
408
|
+
this.prefix_div.style.display = 'inline-block';
|
409
|
+
}
|
410
|
+
this.prefix_selector.innerHTML = html.join('');
|
393
411
|
} else {
|
394
412
|
this.variable_index = -1;
|
395
413
|
}
|
@@ -439,6 +457,15 @@ class GUIChartManager extends ChartManager {
|
|
439
457
|
this.stretchChart(0);
|
440
458
|
}
|
441
459
|
|
460
|
+
selectPrefix() {
|
461
|
+
// Set the preferred prefix for this chart. This will override the
|
462
|
+
// title prefix (if any).
|
463
|
+
if(this.chart_index >= 0) {
|
464
|
+
MODEL.charts[this.chart_index].preferred_prefix = this.prefix_selector.value;
|
465
|
+
}
|
466
|
+
this.updateDialog();
|
467
|
+
}
|
468
|
+
|
442
469
|
showSortingMenu() {
|
443
470
|
// Show the pane with sort type buttons only if variable is selected.
|
444
471
|
this.sorting_menu.style.display =
|
@@ -594,27 +621,31 @@ class GUIChartManager extends ChartManager {
|
|
594
621
|
|
595
622
|
cloneChart() {
|
596
623
|
// Create a new chart that is identical to the current one.
|
597
|
-
if(this.chart_index
|
598
|
-
|
599
|
-
|
600
|
-
|
601
|
-
|
602
|
-
|
603
|
-
|
604
|
-
|
605
|
-
|
606
|
-
|
607
|
-
|
608
|
-
|
609
|
-
|
610
|
-
|
611
|
-
|
612
|
-
|
613
|
-
|
614
|
-
|
615
|
-
|
616
|
-
|
617
|
-
|
624
|
+
if(this.chart_index < 0) return;
|
625
|
+
const
|
626
|
+
c = MODEL.charts[this.chart_index],
|
627
|
+
pp = c.possiblePrefixes;
|
628
|
+
let nt = c.title;
|
629
|
+
if(pp) {
|
630
|
+
// Remove title prefix (if any), and add selected one.
|
631
|
+
nt = c.prefix + UI.PREFIXER + nt.split(UI.PREFIXER).pop();
|
632
|
+
}
|
633
|
+
// If title is not new, keep adding a suffix until it is new.
|
634
|
+
while(MODEL.indexOfChart(nt) >= 0) nt += '-copy';
|
635
|
+
const nc = MODEL.addChart(nt);
|
636
|
+
// Copy properties of `c` to `nc`;
|
637
|
+
nc.histogram = c.histogram;
|
638
|
+
nc.bins = c.bins;
|
639
|
+
nc.show_title = c.show_title;
|
640
|
+
nc.legend_position = c.legend_position;
|
641
|
+
for(const cv of c.variables) {
|
642
|
+
const nv = new ChartVariable(nc);
|
643
|
+
nv.setProperties(cv.object, cv.attribute, cv.stacked,
|
644
|
+
cv.color, cv.scale_factor, cv.line_width, cv.sorted);
|
645
|
+
nc.variables.push(nv);
|
646
|
+
}
|
647
|
+
this.chart_index = MODEL.indexOfChart(nc.title);
|
648
|
+
this.updateDialog();
|
618
649
|
}
|
619
650
|
|
620
651
|
toggleRunResults() {
|
@@ -637,13 +668,7 @@ class GUIChartManager extends ChartManager {
|
|
637
668
|
deleteChart() {
|
638
669
|
// Delete the shown chart (if any).
|
639
670
|
if(this.chart_index >= 0) {
|
640
|
-
|
641
|
-
if(MODEL.charts[this.chart_index].title === this.new_chart_title) {
|
642
|
-
MODEL.charts[this.chart_index].reset();
|
643
|
-
} else {
|
644
|
-
MODEL.charts.splice(this.chart_index, 1);
|
645
|
-
this.chart_index = -1;
|
646
|
-
}
|
671
|
+
MODEL.deleteChart(this.chart_index);
|
647
672
|
// Also update the experiment viewer, because this chart may be
|
648
673
|
// one of the output charts of the selected experiment.
|
649
674
|
UI.updateControllerDialogs('CFX');
|
@@ -742,9 +767,6 @@ class GUIChartManager extends ChartManager {
|
|
742
767
|
if(indices.length < 2) {
|
743
768
|
if(indices.length) {
|
744
769
|
chart.addWildcardVariables(dsm, indices);
|
745
|
-
} else if(dsm.selector.startsWith(':')) {
|
746
|
-
UI.notify('Plotting methods is work-in-progress!');
|
747
|
-
console.log('HERE dsm', dsm, 'expr', dsm.expression.text, 'indices', indices);
|
748
770
|
} else {
|
749
771
|
UI.notify(`Variable "${dsm.displayName}" cannot be plotted`);
|
750
772
|
}
|
@@ -726,7 +726,7 @@ class GUIController extends Controller {
|
|
726
726
|
this.dbl_clicked_node = null;
|
727
727
|
this.target_cluster = null;
|
728
728
|
this.constraint_under_cursor = null;
|
729
|
-
this.last_up_down_without_move =
|
729
|
+
this.last_up_down_without_move = {up: 0, down: 0};
|
730
730
|
// Keyboard shortcuts: Ctrl-x associates with menu button ID.
|
731
731
|
this.shortcuts = {
|
732
732
|
'A': 'actors',
|
@@ -1672,15 +1672,15 @@ class GUIController extends Controller {
|
|
1672
1672
|
}
|
1673
1673
|
}
|
1674
1674
|
|
1675
|
-
|
1676
|
-
// Return TRUE when a "double-click" occurred
|
1675
|
+
doubleClicked(ud) {
|
1676
|
+
// Return TRUE when a "double-click" occurred.
|
1677
1677
|
const
|
1678
1678
|
now = Date.now(),
|
1679
|
-
dt = now - this.last_up_down_without_move;
|
1680
|
-
this.last_up_down_without_move = now;
|
1679
|
+
dt = now - this.last_up_down_without_move[ud];
|
1680
|
+
this.last_up_down_without_move[ud] = now;
|
1681
1681
|
// Consider click to be "double" if it occurred less than 300 ms ago
|
1682
1682
|
if(dt < 300) {
|
1683
|
-
this.last_up_down_without_move = 0;
|
1683
|
+
this.last_up_down_without_move[ud] = 0;
|
1684
1684
|
return true;
|
1685
1685
|
}
|
1686
1686
|
return false;
|
@@ -2230,7 +2230,9 @@ class GUIController extends Controller {
|
|
2230
2230
|
}
|
2231
2231
|
// When dragging a selection over a cluster, change cursor to "cell" to
|
2232
2232
|
// indicate that selected process(es) will be moved into the cluster.
|
2233
|
-
|
2233
|
+
// NOTE: Do not do this when the dragged selection is just a single note!
|
2234
|
+
if(this.dragged_node &&
|
2235
|
+
!(this.dragged_node instanceof Note && MODEL.selection.length < 2)) {
|
2234
2236
|
// NOTE: Cursor will always be over the dragged node, so do not indicate
|
2235
2237
|
// "drop here?" unless dragged over a different cluster.
|
2236
2238
|
if(this.on_cluster && this.on_cluster !== this.dragged_node) {
|
@@ -2312,14 +2314,8 @@ class GUIController extends Controller {
|
|
2312
2314
|
return;
|
2313
2315
|
} // END IF Ctrl
|
2314
2316
|
|
2315
|
-
// Clear selection unless SHIFT pressed or
|
2316
|
-
|
2317
|
-
if(!(e.shiftKey ||
|
2318
|
-
(this.on_node && MODEL.selection.indexOf(this.on_node) >= 0) ||
|
2319
|
-
(this.on_cluster && MODEL.selection.indexOf(this.on_cluster) >= 0) ||
|
2320
|
-
(this.on_note && MODEL.selection.indexOf(this.on_note) >= 0) ||
|
2321
|
-
(this.on_link && MODEL.selection.indexOf(this.on_link) >= 0) ||
|
2322
|
-
(this.on_constraint && MODEL.selection.indexOf(this.on_constraint) >= 0))) {
|
2317
|
+
// Clear selection unless SHIFT pressed or double-clicking.
|
2318
|
+
if(!(this.doubleClicked('down') || e.shiftKey)) {
|
2323
2319
|
MODEL.clearSelection();
|
2324
2320
|
UI.drawDiagram(MODEL);
|
2325
2321
|
}
|
@@ -2538,7 +2534,7 @@ class GUIController extends Controller {
|
|
2538
2534
|
absdx = Math.abs(this.net_move_x),
|
2539
2535
|
absdy = Math.abs(this.net_move_y),
|
2540
2536
|
sigmv = (MODEL.align_to_grid ? MODEL.grid_pixels / 4 : 2.5);
|
2541
|
-
if(this.doubleClicked) {
|
2537
|
+
if(this.doubleClicked('up')) {
|
2542
2538
|
// Ignore insignificant move.
|
2543
2539
|
if(absdx < sigmv && absdy < sigmv) {
|
2544
2540
|
// Undo the move and remove the action from the UNDO-stack.
|
@@ -2584,13 +2580,15 @@ class GUIController extends Controller {
|
|
2584
2580
|
this.paper.container.style.cursor = 'pointer';
|
2585
2581
|
// NOTE: Cursor will always be over the selected cluster (while dragging).
|
2586
2582
|
if(this.on_cluster && !this.on_cluster.selected) {
|
2587
|
-
|
2588
|
-
|
2589
|
-
|
2590
|
-
|
2591
|
-
|
2592
|
-
|
2593
|
-
|
2583
|
+
if(!(this.dragged_node instanceof Note && MODEL.selection.length < 2)) {
|
2584
|
+
UNDO_STACK.push('drop', this.on_cluster);
|
2585
|
+
MODEL.dropSelectionIntoCluster(this.on_cluster);
|
2586
|
+
// Redraw cluster to erase its orange "target corona".
|
2587
|
+
UI.paper.drawCluster(this.on_cluster);
|
2588
|
+
this.on_node = null;
|
2589
|
+
this.on_note = null;
|
2590
|
+
this.target_cluster = null;
|
2591
|
+
}
|
2594
2592
|
}
|
2595
2593
|
// Only now align to grid.
|
2596
2594
|
MODEL.alignToGrid();
|
@@ -2599,11 +2597,11 @@ class GUIController extends Controller {
|
|
2599
2597
|
|
2600
2598
|
// Then check whether the user is clicking on a link.
|
2601
2599
|
} else if(this.on_link) {
|
2602
|
-
if(this.doubleClicked) {
|
2600
|
+
if(this.doubleClicked('up')) {
|
2603
2601
|
this.showLinkPropertiesDialog(this.on_link);
|
2604
2602
|
}
|
2605
2603
|
} else if(this.on_constraint) {
|
2606
|
-
if(this.doubleClicked) {
|
2604
|
+
if(this.doubleClicked('up')) {
|
2607
2605
|
this.showConstraintPropertiesDialog(this.on_constraint);
|
2608
2606
|
}
|
2609
2607
|
}
|
@@ -2979,7 +2977,7 @@ class GUIController extends Controller {
|
|
2979
2977
|
//
|
2980
2978
|
|
2981
2979
|
validNames(nn, an='') {
|
2982
|
-
// Check whether names meet conventions
|
2980
|
+
// Check whether names meet conventions. If not, warn user.
|
2983
2981
|
if(!UI.validName(nn) || nn.indexOf(UI.BLACK_BOX) >= 0) {
|
2984
2982
|
this.warningInvalidName(nn);
|
2985
2983
|
return false;
|
@@ -3148,10 +3146,10 @@ class GUIController extends Controller {
|
|
3148
3146
|
UI.info_line.classList.remove(...UI.info_line.classList);
|
3149
3147
|
}
|
3150
3148
|
|
3151
|
-
setMessage(msg, type=null) {
|
3149
|
+
setMessage(msg, type=null, cause=null) {
|
3152
3150
|
// Display `msg` on infoline unless no type (= plain text) and some
|
3153
3151
|
// info, warning or error message is already displayed.
|
3154
|
-
super.setMessage(msg, type);
|
3152
|
+
super.setMessage(msg, type, cause);
|
3155
3153
|
const types = ['notification', 'warning', 'error'];
|
3156
3154
|
let d = new Date(),
|
3157
3155
|
t = d.getTime(),
|
@@ -3666,7 +3664,7 @@ class GUIController extends Controller {
|
|
3666
3664
|
// proceed to paste.
|
3667
3665
|
const
|
3668
3666
|
md = this.paste_modal,
|
3669
|
-
mapping = Object.assign(md.mapping
|
3667
|
+
mapping = Object.assign({}, md.mapping),
|
3670
3668
|
tc = (mapping.top_clusters ?
|
3671
3669
|
Object.keys(mapping.top_clusters).sort(ciCompare) : []),
|
3672
3670
|
ft = (mapping.from_to ?
|
@@ -3713,6 +3711,14 @@ class GUIController extends Controller {
|
|
3713
3711
|
|
3714
3712
|
// AUXILIARY FUNCTIONS
|
3715
3713
|
|
3714
|
+
function namedObjects() {
|
3715
|
+
// Return TRUE iff XML contains named objects.
|
3716
|
+
for(const cn of entities_node.childNodes) {
|
3717
|
+
if(cn.nodeName !== 'note') return true;
|
3718
|
+
}
|
3719
|
+
return false;
|
3720
|
+
}
|
3721
|
+
|
3716
3722
|
function fullName(node) {
|
3717
3723
|
// Return full entity name inferred from XML node data.
|
3718
3724
|
if(node.nodeName === 'from-to' || node.nodeName === 'selc') {
|
@@ -3848,7 +3854,18 @@ class GUIController extends Controller {
|
|
3848
3854
|
fn = fullName(node),
|
3849
3855
|
mn = mappedName(fn);
|
3850
3856
|
let obj;
|
3851
|
-
if(et === '
|
3857
|
+
if(et === 'note') {
|
3858
|
+
// Ensure that copy had new time stamp.
|
3859
|
+
let cn = childNodeByTag(node, 'timestamp').firstChild;
|
3860
|
+
cn.nodeValue = new Date().getTime().toString();
|
3861
|
+
cn = childNodeByTag(node, 'x-coord').firstChild;
|
3862
|
+
// Move note a bit right and down.
|
3863
|
+
cn.nodeValue = (safeStrToInt(cn.nodeValue, 0) + 12).toString();
|
3864
|
+
cn = childNodeByTag(node, 'y-coord').firstChild;
|
3865
|
+
cn.nodeValue = (safeStrToInt(cn.nodeValue, 0) + 12).toString();
|
3866
|
+
obj = MODEL.addNote(node);
|
3867
|
+
if(obj) new_entities.push(obj);
|
3868
|
+
} else if(et === 'process' && !MODEL.processByID(UI.nameToID(mn))) {
|
3852
3869
|
const
|
3853
3870
|
na = nameAndActor(mn),
|
3854
3871
|
new_actor = !MODEL.actorByID(UI.nameToID(na[1]));
|
@@ -3908,12 +3925,13 @@ class GUIController extends Controller {
|
|
3908
3925
|
mapping.shared_prefix = sp;
|
3909
3926
|
mapping.from_prefix = (fpn ? sp + fpn + UI.PREFIXER : sp);
|
3910
3927
|
mapping.to_prefix = (tpn ? sp + tpn + UI.PREFIXER : sp);
|
3911
|
-
mapping.from_actor =
|
3912
|
-
mapping.to_actor =
|
3928
|
+
mapping.from_actor = UI.realActorName(ca);
|
3929
|
+
mapping.to_actor = UI.realActorName(fca);
|
3913
3930
|
// Prompt for mapping when pasting to the same model and cluster.
|
3914
3931
|
if(parseInt(mts) === MODEL.time_created.getTime() &&
|
3915
3932
|
ca === fca && mapping.from_prefix === mapping.to_prefix &&
|
3916
|
-
!(mapping.prefix || mapping.actor || mapping.increment)
|
3933
|
+
!(mapping.prefix || mapping.actor || mapping.increment) &&
|
3934
|
+
namedObjects()) {
|
3917
3935
|
// Prompt for names of selected cluster nodes.
|
3918
3936
|
if(selc_node.childNodes.length && !mapping.prefix) {
|
3919
3937
|
mapping.top_clusters = {};
|
@@ -4546,8 +4564,7 @@ console.log('HERE name conflicts', name_conflicts, mapping);
|
|
4546
4564
|
md.group = group;
|
4547
4565
|
md.element('action').innerText = 'Edit';
|
4548
4566
|
md.element('name').value = c.name;
|
4549
|
-
md.element('actor').value = (c.actor.name
|
4550
|
-
'' : c.actor.name);
|
4567
|
+
md.element('actor').value = UI.realActorName(c.actor.name);
|
4551
4568
|
md.element('options').style.display = 'block';
|
4552
4569
|
this.setBox('cluster-collapsed', c.collapsed);
|
4553
4570
|
this.setBox('cluster-ignore', c.ignore);
|
@@ -289,17 +289,6 @@ class GUIDatasetManager extends DatasetManager {
|
|
289
289
|
return null;
|
290
290
|
}
|
291
291
|
|
292
|
-
datasetsByPrefix(prefix) {
|
293
|
-
// Return the list of datasets having the specified prefix.
|
294
|
-
const
|
295
|
-
pid = UI.nameToID(prefix + UI.PREFIXER),
|
296
|
-
dsl = [];
|
297
|
-
for(const k of Object.keys(MODEL.datasets)) {
|
298
|
-
if(k.startsWith(pid)) dsl.push(k);
|
299
|
-
}
|
300
|
-
return dsl;
|
301
|
-
}
|
302
|
-
|
303
292
|
selectPrefixRow(e) {
|
304
293
|
// Select expand/collapse prefix row.
|
305
294
|
this.focal_table = this.dataset_table;
|
@@ -310,7 +299,7 @@ class GUIDatasetManager extends DatasetManager {
|
|
310
299
|
const toggle = r.classList.contains('tree-btn');
|
311
300
|
while(r.tagName !== 'TR') r = r.parentNode;
|
312
301
|
this.selected_prefix_row = r;
|
313
|
-
this.prefixed_datasets =
|
302
|
+
this.prefixed_datasets = MODEL.datasetKeysByPrefix(r.dataset.prefix);
|
314
303
|
const sel = this.dataset_table.getElementsByClassName('sel-set');
|
315
304
|
this.selected_dataset = null;
|
316
305
|
if(sel.length > 0) {
|
@@ -746,7 +735,7 @@ class GUIDatasetManager extends DatasetManager {
|
|
746
735
|
}
|
747
736
|
|
748
737
|
get selectedAsList() {
|
749
|
-
|
738
|
+
// Return list of datasets selected directly or by prefix.
|
750
739
|
const dsl = [];
|
751
740
|
// Prevent including the equations dataset (just in case).
|
752
741
|
if(this.selected_dataset && this.selected_dataset !== MODEL.equations_dataset) {
|
@@ -1243,12 +1232,14 @@ class GUIDatasetManager extends DatasetManager {
|
|
1243
1232
|
for(let j = 0; j < ncol; j++) {
|
1244
1233
|
const
|
1245
1234
|
v = dsv[j].trim(),
|
1246
|
-
sf = safeStrToFloat(v,
|
1247
|
-
|
1235
|
+
sf = safeStrToFloat(v, NaN);
|
1236
|
+
// NOTE: Ignore empty strings, but this may "shift up" numerical values on later rows.
|
1237
|
+
if(isNaN(sf) && v !== '') {
|
1248
1238
|
UI.warn(`Invalid numerical value "${v}" for <strong>${dsn[j]}</strong> on line ${i}`);
|
1249
1239
|
return false;
|
1240
|
+
} else if(!isNaN(sf)) {
|
1241
|
+
dsa[j].push(sf);
|
1250
1242
|
}
|
1251
|
-
dsa[j].push(sf);
|
1252
1243
|
}
|
1253
1244
|
}
|
1254
1245
|
// Add or update datasets.
|
@@ -209,8 +209,9 @@ class GUIMonitor {
|
|
209
209
|
`ERROR at t=${t}: ` + VM.errorMessage(err);
|
210
210
|
for(const x of VM.call_stack) {
|
211
211
|
// For equations, only show the attribute.
|
212
|
-
const ons = (x.object === MODEL.equations_dataset ?
|
213
|
-
x.
|
212
|
+
const ons = (x.object === MODEL.equations_dataset ?
|
213
|
+
(x.attribute.startsWith(':') ? x.method_object_prefix : '') :
|
214
|
+
x.object.displayName + '|');
|
214
215
|
vlist.push(ons + x.attribute);
|
215
216
|
// Trim spaces around all object-attribute separators in the expression.
|
216
217
|
xlist.push(x.text.replace(/\s*\|\s*/g, '|'));
|
@@ -1955,7 +1955,7 @@ class Paper {
|
|
1955
1955
|
let l = (MODEL.solved ? proc.actualLevel(MODEL.t) : VM.NOT_COMPUTED),
|
1956
1956
|
lb = proc.lower_bound.result(MODEL.t),
|
1957
1957
|
ub = (proc.equal_bounds ? lb : proc.upper_bound.result(MODEL.t));
|
1958
|
-
// NOTE:
|
1958
|
+
// NOTE: By default, lower bound = 0 (but do show exceptional values).
|
1959
1959
|
if(lb === VM.UNDEFINED && !proc.lower_bound.defined) lb = 0;
|
1960
1960
|
let hw,
|
1961
1961
|
hh,
|
@@ -2766,6 +2766,15 @@ class Paper {
|
|
2766
2766
|
'h-', w - shadow_width, 'v-', shadow_width,
|
2767
2767
|
'h', w - 2*shadow_width, 'z'],
|
2768
2768
|
{fill:stroke_color, stroke:stroke_color, 'stroke-width':stroke_width});
|
2769
|
+
if(clstr.module) {
|
2770
|
+
// Add three white dots at middle of bottom shade.
|
2771
|
+
const
|
2772
|
+
ely = y + hh - shadow_width / 2,
|
2773
|
+
elfill = {fill: 'white'};
|
2774
|
+
clstr.shape.addEllipse(x - 4, ely, 1, 1, elfill);
|
2775
|
+
clstr.shape.addEllipse(x, ely, 1, 1, elfill);
|
2776
|
+
clstr.shape.addEllipse(x + 4, ely, 1, 1, elfill);
|
2777
|
+
}
|
2769
2778
|
// Set fill color if slack used by some product contained by this cluster
|
2770
2779
|
if(MODEL.t in clstr.slack_info) {
|
2771
2780
|
const s = clstr.slack_info[MODEL.t];
|