linny-r 1.3.4 → 1.4.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/server.js +48 -6
- package/static/index.html +14 -12
- package/static/linny-r.css +28 -2
- package/static/scripts/linny-r-ctrl.js +51 -8
- package/static/scripts/linny-r-gui.js +150 -75
- package/static/scripts/linny-r-model.js +467 -154
- package/static/scripts/linny-r-utils.js +141 -12
- package/static/scripts/linny-r-vm.js +931 -435
@@ -916,6 +916,8 @@ class Paper {
|
|
916
916
|
for(let i = 0; i < fc.sub_clusters.length; i++) {
|
917
917
|
fc.sub_clusters[i].clearHiddenIO();
|
918
918
|
}
|
919
|
+
// NOTE: also ensure that notes will update their fields
|
920
|
+
fc.resetNoteFields();
|
919
921
|
// Draw link arrows and constraints first, as all other entities are
|
920
922
|
// slightly transparent so they cannot completely hide these lines
|
921
923
|
for(let i = 0; i < fc.arrows.length; i++) {
|
@@ -953,6 +955,7 @@ class Paper {
|
|
953
955
|
// Links and constraints are drawn separately, so do not draw those
|
954
956
|
// contained in the selection
|
955
957
|
if(!(obj instanceof Link || obj instanceof Constraint)) {
|
958
|
+
if(obj instanceof Note) obj.parsed = false;
|
956
959
|
UI.drawObject(obj, dx, dy);
|
957
960
|
}
|
958
961
|
}
|
@@ -1519,6 +1522,7 @@ class Paper {
|
|
1519
1522
|
} else if(mf[0] > 1) {
|
1520
1523
|
// Multi-flow arrow with flow data computed
|
1521
1524
|
let clr = this.palette.active_process;
|
1525
|
+
// Highlight if related process(es) are at upper bound
|
1522
1526
|
if(mf[3]) ffill.fill = this.palette.at_process_ub_bar;
|
1523
1527
|
s = VM.sig4Dig(mf[1]);
|
1524
1528
|
bb = this.numberSize(s, 10, 700);
|
@@ -2828,6 +2832,21 @@ class GUIController extends Controller {
|
|
2828
2832
|
constructor() {
|
2829
2833
|
super();
|
2830
2834
|
this.console = false;
|
2835
|
+
const
|
2836
|
+
ua = window.navigator.userAgent.toLowerCase(),
|
2837
|
+
browsers = [
|
2838
|
+
['edg', 'Edge'],
|
2839
|
+
['opr', 'Opera'],
|
2840
|
+
['chrome', 'Chrome'],
|
2841
|
+
['firefox', 'Firefox'],
|
2842
|
+
['safari', 'Safari']];
|
2843
|
+
for(let i = 0; i < browsers.length; i++) {
|
2844
|
+
const b = browsers[i];
|
2845
|
+
if(ua.indexOf(b[0]) >= 0) {
|
2846
|
+
this.browser_name = b[1];
|
2847
|
+
break;
|
2848
|
+
}
|
2849
|
+
}
|
2831
2850
|
// Display version number as clickable link (just below the Linny-R logo)
|
2832
2851
|
this.version_number = LINNY_R_VERSION;
|
2833
2852
|
this.version_div = document.getElementById('linny-r-version-number');
|
@@ -3484,8 +3503,7 @@ class GUIController extends Controller {
|
|
3484
3503
|
if(VM.issue_index === -1) {
|
3485
3504
|
VM.issue_index = 0;
|
3486
3505
|
} else if(change) {
|
3487
|
-
VM.issue_index
|
3488
|
-
setTimeout(() => UI.jumpToIssue(), 10);
|
3506
|
+
VM.issue_index = Math.min(VM.issue_index + change, count - 1);
|
3489
3507
|
}
|
3490
3508
|
nr.innerText = VM.issue_index + 1;
|
3491
3509
|
if(VM.issue_index <= 0) {
|
@@ -3493,12 +3511,13 @@ class GUIController extends Controller {
|
|
3493
3511
|
} else {
|
3494
3512
|
prev.classList.remove('disab');
|
3495
3513
|
}
|
3496
|
-
if(
|
3514
|
+
if(VM.issue_index >= count - 1) {
|
3497
3515
|
next.classList.add('disab');
|
3498
3516
|
} else {
|
3499
3517
|
next.classList.remove('disab');
|
3500
3518
|
}
|
3501
3519
|
panel.style.display = 'table-cell';
|
3520
|
+
if(change) UI.jumpToIssue();
|
3502
3521
|
} else {
|
3503
3522
|
panel.style.display = 'none';
|
3504
3523
|
VM.issue_index = -1;
|
@@ -4770,7 +4789,7 @@ class GUIController extends Controller {
|
|
4770
4789
|
|
4771
4790
|
updateExpressionInput(id, name, x) {
|
4772
4791
|
// Updates expression object `x` if input field identified by `id`
|
4773
|
-
// contains a well-formed expression
|
4792
|
+
// contains a well-formed expression. If error, focuses on the field
|
4774
4793
|
// and shows the error while specifying the name of the field.
|
4775
4794
|
const
|
4776
4795
|
inp = document.getElementById(id),
|
@@ -4882,7 +4901,7 @@ class GUIController extends Controller {
|
|
4882
4901
|
// Logs MB's of used heap memory to console (to detect memory leaks)
|
4883
4902
|
// NOTE: this feature is supported only by Chrome
|
4884
4903
|
if(msg) msg += ' -- ';
|
4885
|
-
if(
|
4904
|
+
if(performance.memory !== undefined) {
|
4886
4905
|
console.log(msg + 'Allocated memory: ' + Math.round(
|
4887
4906
|
performance.memory.usedJSHeapSize/1048576.0).toFixed(1) + ' MB');
|
4888
4907
|
}
|
@@ -5006,14 +5025,19 @@ class GUIController extends Controller {
|
|
5006
5025
|
an,
|
5007
5026
|
md;
|
5008
5027
|
if(type === 'note') {
|
5028
|
+
|
5009
5029
|
md = this.modals.note;
|
5010
|
-
|
5030
|
+
n = this.dbl_clicked_node;
|
5031
|
+
const
|
5032
|
+
editing = md.element('action').innerHTML === 'Edit',
|
5033
|
+
cx = new Expression(editing ? n : null, '', 'C');
|
5011
5034
|
if(this.updateExpressionInput('note-C', 'note color', cx)) {
|
5012
|
-
if(
|
5035
|
+
if(editing) {
|
5013
5036
|
n = this.dbl_clicked_node;
|
5014
5037
|
this.dbl_clicked_node = null;
|
5015
5038
|
UNDO_STACK.push('modify', n);
|
5016
5039
|
n.contents = md.element('text').value;
|
5040
|
+
n.color.owner = n;
|
5017
5041
|
n.color.text = md.element('C').value;
|
5018
5042
|
n.color.compile();
|
5019
5043
|
n.parsed = false;
|
@@ -5024,10 +5048,10 @@ class GUIController extends Controller {
|
|
5024
5048
|
n.y = this.add_y;
|
5025
5049
|
n.contents = md.element('text').value;
|
5026
5050
|
n.color.text = md.element('C').value;
|
5027
|
-
n.color.compile();
|
5028
5051
|
n.parsed = false;
|
5029
5052
|
n.resize();
|
5030
|
-
|
5053
|
+
n.color.compile();
|
5054
|
+
UNDO_STACK.push('add', n);
|
5031
5055
|
}
|
5032
5056
|
}
|
5033
5057
|
} else if(type === 'cluster') {
|
@@ -5242,7 +5266,8 @@ class GUIController extends Controller {
|
|
5242
5266
|
if(xml) {
|
5243
5267
|
window.localStorage.setItem('Linny-R-selection-XML', xml);
|
5244
5268
|
this.updateButtons();
|
5245
|
-
|
5269
|
+
const bn = (this.browser_name ? ` of ${this.browser_name}` : '');
|
5270
|
+
this.notify('Selection copied to local storage' + bn);
|
5246
5271
|
}
|
5247
5272
|
}
|
5248
5273
|
|
@@ -5261,7 +5286,6 @@ class GUIController extends Controller {
|
|
5261
5286
|
|
5262
5287
|
promptForMapping(mapping) {
|
5263
5288
|
// Prompt user to specify name conflict resolution strategy
|
5264
|
-
console.log('HERE prompt for mapping', mapping);
|
5265
5289
|
const md = this.paste_modal;
|
5266
5290
|
md.mapping = mapping;
|
5267
5291
|
md.element('from-prefix').innerText = mapping.from_prefix || '';
|
@@ -5301,10 +5325,20 @@ class GUIController extends Controller {
|
|
5301
5325
|
for(let i = 0; i < ft.length; i++) {
|
5302
5326
|
const ti = mapping.from_to[ft[i]];
|
5303
5327
|
if(ft[i] === ti) {
|
5304
|
-
|
5305
|
-
|
5306
|
-
|
5307
|
-
|
5328
|
+
const elig = MODEL.eligibleFromToNodes(mapping.from_to_type[ti]);
|
5329
|
+
sl.push('<div class="paste-option"><span>', ft[i], '</span> ');
|
5330
|
+
if(elig.length) {
|
5331
|
+
sl.push('<div class="paste-select"><select id="paste-ft-', i,
|
5332
|
+
'" style="font-size: 12px">');
|
5333
|
+
for(let j = 0; j < elig.length; j++) {
|
5334
|
+
const dn = elig[j].displayName;
|
5335
|
+
sl.push('<option value="', dn, '">', dn, '</option>');
|
5336
|
+
}
|
5337
|
+
sl.push('</select></div>');
|
5338
|
+
} else {
|
5339
|
+
sl.push('<span><em>(no eligible node)</em></span');
|
5340
|
+
}
|
5341
|
+
sl.push('</div>');
|
5308
5342
|
}
|
5309
5343
|
}
|
5310
5344
|
}
|
@@ -5330,6 +5364,12 @@ class GUIController extends Controller {
|
|
5330
5364
|
const cn = md.element('selc-' + i).value.trim();
|
5331
5365
|
if(this.validName(cn)) mapping.top_clusters[tc[i]] = cn;
|
5332
5366
|
}
|
5367
|
+
for(let i = 0; i < ft.length; i++) if(mapping.from_to[ft[i]] === ft[i]) {
|
5368
|
+
const
|
5369
|
+
ftn = md.element('ft-' + i).value,
|
5370
|
+
fto = MODEL.objectByName(ftn);
|
5371
|
+
if(fto) mapping.from_to[ft[i]] = ftn;
|
5372
|
+
}
|
5333
5373
|
this.pasteSelection(mapping);
|
5334
5374
|
}
|
5335
5375
|
|
@@ -5338,7 +5378,6 @@ class GUIController extends Controller {
|
|
5338
5378
|
// see whether PASTE would result in name conflicts, and if so,
|
5339
5379
|
// open the name conflict resolution window
|
5340
5380
|
let xml = window.localStorage.getItem('Linny-R-selection-XML');
|
5341
|
-
console.log('HERE xml', xml);
|
5342
5381
|
try {
|
5343
5382
|
xml = parseXML(xml);
|
5344
5383
|
} catch(e) {
|
@@ -5347,9 +5386,6 @@ console.log('HERE xml', xml);
|
|
5347
5386
|
return;
|
5348
5387
|
}
|
5349
5388
|
|
5350
|
-
// For now, while still implementing:
|
5351
|
-
this.notify('Paste not fully implemented yet -- WORK IN PROGRESS!');
|
5352
|
-
|
5353
5389
|
const
|
5354
5390
|
entities_node = childNodeByTag(xml, 'entities'),
|
5355
5391
|
from_tos_node = childNodeByTag(xml, 'from-tos'),
|
@@ -5454,6 +5490,12 @@ console.log('HERE xml', xml);
|
|
5454
5490
|
if(mapping.increment && nr) {
|
5455
5491
|
return n.replace(new RegExp(nr + '$'), parseInt(nr) + 1);
|
5456
5492
|
}
|
5493
|
+
if(mapping.top_clusters && mapping.top_clusters[n]) {
|
5494
|
+
return mapping.top_clusters[n];
|
5495
|
+
}
|
5496
|
+
if(mapping.from_to && mapping.from_to[n]) {
|
5497
|
+
return mapping.from_to[n];
|
5498
|
+
}
|
5457
5499
|
// No mapping => return original name
|
5458
5500
|
return n;
|
5459
5501
|
}
|
@@ -5572,36 +5614,41 @@ console.log('HERE xml', xml);
|
|
5572
5614
|
// Also prompt if FROM and/or TO nodes are not selected, and map to
|
5573
5615
|
// existing entities
|
5574
5616
|
if(from_tos_node.childNodes.length && !mapping.from_to) {
|
5575
|
-
const
|
5617
|
+
const
|
5618
|
+
ft_map = {},
|
5619
|
+
ft_type = {};
|
5576
5620
|
for(let i = 0; i < from_tos_node.childNodes.length; i++) {
|
5577
5621
|
const
|
5578
5622
|
c = from_tos_node.childNodes[i],
|
5579
5623
|
fn = fullName(c),
|
5580
5624
|
mn = mappedName(fn);
|
5581
|
-
if(MODEL.objectByName(mn))
|
5625
|
+
if(MODEL.objectByName(mn)) {
|
5626
|
+
ft_map[fn] = mn;
|
5627
|
+
ft_type[fn] = (nodeParameterValue(c, 'is-data') === '1' ?
|
5628
|
+
'Data' : nodeParameterValue(c, 'type'));
|
5629
|
+
}
|
5582
5630
|
}
|
5583
5631
|
// Prompt only for FROM/TO nodes that map to existing nodes
|
5584
5632
|
if(Object.keys(ft_map).length) {
|
5585
5633
|
mapping.from_to = ft_map;
|
5634
|
+
mapping.from_to_type = ft_type;
|
5586
5635
|
this.promptForMapping(mapping);
|
5587
5636
|
return;
|
5588
5637
|
}
|
5589
5638
|
}
|
5590
5639
|
|
5591
|
-
// Only check for selected entities
|
5640
|
+
// Only check for selected entities; from-to's and extra's should be
|
5592
5641
|
// used if they exist, or should be created when copying to a different
|
5593
5642
|
// model
|
5594
5643
|
name_map.length = 0;
|
5595
5644
|
nameConflicts(entities_node);
|
5596
|
-
nameConflicts(from_tos_node);
|
5597
5645
|
if(name_conflicts.length) {
|
5598
|
-
UI.
|
5599
|
-
console.log('HERE name conflicts', name_conflicts);
|
5646
|
+
UI.warn(pluralS(name_conflicts.length, 'name conflict'));
|
5647
|
+
console.log('HERE name conflicts', name_conflicts, mapping);
|
5600
5648
|
return;
|
5601
5649
|
}
|
5602
5650
|
|
5603
5651
|
// No conflicts => add all
|
5604
|
-
console.log('HERE name map', name_map);
|
5605
5652
|
for(let i = 0; i < extras_node.childNodes.length; i++) {
|
5606
5653
|
addEntityFromNode(extras_node.childNodes[i]);
|
5607
5654
|
}
|
@@ -5627,6 +5674,7 @@ console.log('HERE name map', name_map);
|
|
5627
5674
|
// products are displayed as arrows instead of block arrows
|
5628
5675
|
fc.clearAllProcesses();
|
5629
5676
|
UI.drawDiagram(MODEL);
|
5677
|
+
this.paste_modal.hide();
|
5630
5678
|
}
|
5631
5679
|
|
5632
5680
|
//
|
@@ -5653,6 +5701,7 @@ console.log('HERE name map', name_map);
|
|
5653
5701
|
this.setBox('settings-decimal-comma', model.decimal_comma);
|
5654
5702
|
this.setBox('settings-align-to-grid', model.align_to_grid);
|
5655
5703
|
this.setBox('settings-cost-prices', model.infer_cost_prices);
|
5704
|
+
this.setBox('settings-report-results', model.report_results);
|
5656
5705
|
this.setBox('settings-block-arrows', model.show_block_arrows);
|
5657
5706
|
md.show('name');
|
5658
5707
|
}
|
@@ -5705,6 +5754,7 @@ console.log('HERE name map', name_map);
|
|
5705
5754
|
if(!model.scale_units.hasOwnProperty(dsu)) model.addScaleUnit(dsu);
|
5706
5755
|
model.default_unit = dsu;
|
5707
5756
|
model.currency_unit = md.element('currency-unit').value.trim();
|
5757
|
+
model.report_results = UI.boxChecked('settings-report-results');
|
5708
5758
|
model.encrypt = UI.boxChecked('settings-encrypt');
|
5709
5759
|
model.decimal_comma = UI.boxChecked('settings-decimal-comma');
|
5710
5760
|
// Some changes may necessitate redrawing the diagram
|
@@ -5766,6 +5816,8 @@ console.log('HERE name map', name_map);
|
|
5766
5816
|
const md = this.modals.note;
|
5767
5817
|
if(n) {
|
5768
5818
|
md.element('action').innerHTML = 'Edit';
|
5819
|
+
const nr = n.number;
|
5820
|
+
md.element('number').innerHTML = (nr ? '#' + nr : '');
|
5769
5821
|
md.element('text').value = n.contents;
|
5770
5822
|
md.element('C').value = n.color.text;
|
5771
5823
|
} else {
|
@@ -5787,6 +5839,10 @@ console.log('HERE name map', name_map);
|
|
5787
5839
|
} else {
|
5788
5840
|
md.element('actor').value = '';
|
5789
5841
|
}
|
5842
|
+
const sim = p.similarNumberedEntities;
|
5843
|
+
if(sim.length) {
|
5844
|
+
console.log('HERE!', sim);
|
5845
|
+
}
|
5790
5846
|
md.element('LB').value = p.lower_bound.text;
|
5791
5847
|
md.element('UB').value = p.upper_bound.text;
|
5792
5848
|
this.setEqualBounds('process', p.equal_bounds);
|
@@ -6017,7 +6073,7 @@ console.log('HERE name map', name_map);
|
|
6017
6073
|
const
|
6018
6074
|
from_process = l.from_node instanceof Process,
|
6019
6075
|
to_process = l.to_node instanceof Process,
|
6020
|
-
md = this.modals.link;
|
6076
|
+
md = this.modals.link;
|
6021
6077
|
md.show();
|
6022
6078
|
md.element('from-name').innerHTML = l.from_node.displayName;
|
6023
6079
|
md.element('to-name').innerHTML = l.to_node.displayName;
|
@@ -6064,6 +6120,7 @@ console.log('HERE name map', name_map);
|
|
6064
6120
|
md.element('output-soc').style.display = 'none';
|
6065
6121
|
}
|
6066
6122
|
}
|
6123
|
+
this.edited_object = l;
|
6067
6124
|
if(alt) md.element(attr + '-x').dispatchEvent(new Event('click'));
|
6068
6125
|
}
|
6069
6126
|
|
@@ -6108,7 +6165,7 @@ console.log('HERE name map', name_map);
|
|
6108
6165
|
// @@TO DO: prepare for undo
|
6109
6166
|
const
|
6110
6167
|
md = this.modals.link,
|
6111
|
-
l = this.
|
6168
|
+
l = this.edited_object;
|
6112
6169
|
// Check whether all input fields are valid
|
6113
6170
|
if(!this.updateExpressionInput('link-R', 'rate', l.relative_rate)) {
|
6114
6171
|
return false;
|
@@ -6415,7 +6472,8 @@ class GUIMonitor {
|
|
6415
6472
|
if(this.call_stack_shown) return;
|
6416
6473
|
const
|
6417
6474
|
csl = VM.call_stack.length,
|
6418
|
-
|
6475
|
+
top = VM.call_stack[csl - 1],
|
6476
|
+
err = top.vector[t],
|
6419
6477
|
// Make separate lists of variable names and their expressions
|
6420
6478
|
vlist = [],
|
6421
6479
|
xlist = [];
|
@@ -6480,6 +6538,9 @@ class GUIMonitor {
|
|
6480
6538
|
tbl.push('<div style="color: gray; margin-top: 8px; font-size: 10px">',
|
6481
6539
|
VM.out_of_bounds_msg.replace(VM.out_of_bounds_array, anc), '</div>');
|
6482
6540
|
}
|
6541
|
+
// Dump the code for the last expression to the console
|
6542
|
+
console.log('Code for', top.text, top.code);
|
6543
|
+
// Show the call stack dialog
|
6483
6544
|
document.getElementById('call-stack-table').innerHTML = tbl.join('');
|
6484
6545
|
document.getElementById('call-stack-modal').style.display = 'block';
|
6485
6546
|
this.call_stack_shown = true;
|
@@ -7244,10 +7305,12 @@ NOTE: Grouping groups results in a single group, e.g., (1;2);(3;4;5) evaluates a
|
|
7244
7305
|
if(this.edited_input_id) {
|
7245
7306
|
document.getElementById(this.edited_input_id).value = xp.expr;
|
7246
7307
|
// NOTE: entity properties must be exogenous parameters
|
7247
|
-
|
7248
|
-
|
7249
|
-
|
7250
|
-
|
7308
|
+
const eo = UI.edited_object;
|
7309
|
+
if(eo && xp.is_level_based &&
|
7310
|
+
!(eo instanceof Dataset || eo instanceof Note)) {
|
7311
|
+
UI.warn(['Expression for', this.property.innerHTML,
|
7312
|
+
'of<strong>', eo.displayName,
|
7313
|
+
'</strong>contains a solution-dependent variable'].join(' '));
|
7251
7314
|
}
|
7252
7315
|
this.edited_input_id = '';
|
7253
7316
|
} else if(DATASET_MANAGER.edited_expression) {
|
@@ -8743,6 +8806,7 @@ class ConstraintEditor {
|
|
8743
8806
|
// NOTE: this could be expanded to apply to the selected BL only
|
8744
8807
|
UI.setBox('ce-no-slack', this.constraint.no_slack);
|
8745
8808
|
// NOTE: share of cost can only be transferred between two processes
|
8809
|
+
// @@TO DO: CHECK WHETHER THIS LIMITATION IS VALID -- for now, allow both
|
8746
8810
|
if(true||this.from_node instanceof Process && this.from_node instanceof Process) {
|
8747
8811
|
this.soc_direct.value = this.constraint.soc_direction;
|
8748
8812
|
// NOTE: share of cost is input as a percentage
|
@@ -9894,6 +9958,7 @@ class GUIDatasetManager extends DatasetManager {
|
|
9894
9958
|
ps = pid.split(UI.PREFIXER),
|
9895
9959
|
pps = prev_id.split(UI.PREFIXER),
|
9896
9960
|
pn = pref_names[pid],
|
9961
|
+
pns = pn.join(UI.PREFIXER),
|
9897
9962
|
lpl = [];
|
9898
9963
|
let lindent = 0;
|
9899
9964
|
// Ignore identical leading prefixes
|
@@ -9908,8 +9973,9 @@ class GUIDatasetManager extends DatasetManager {
|
|
9908
9973
|
lpl.push(ps.shift());
|
9909
9974
|
lindent++;
|
9910
9975
|
const lpid = lpl.join(UI.PREFIXER);
|
9911
|
-
dl.push(['<tr data-prefix="', lpid,
|
9912
|
-
'"
|
9976
|
+
dl.push(['<tr data-prefix="', lpid,
|
9977
|
+
'" data-prefix-name="', pns, '" class="dataset"',
|
9978
|
+
'onclick="DATASET_MANAGER.selectPrefixRow(event);"><td>',
|
9913
9979
|
// NOTE: data-prefix="x" signals that this is an extra row
|
9914
9980
|
(lindent > 0 ?
|
9915
9981
|
'<div data-prefix="x" style="width: ' + lindent * indent_px +
|
@@ -10022,6 +10088,7 @@ class GUIDatasetManager extends DatasetManager {
|
|
10022
10088
|
for(let i = 0; i < msl.length; i++) {
|
10023
10089
|
const
|
10024
10090
|
m = sd.modifiers[UI.nameToID(msl[i])],
|
10091
|
+
wild = m.hasWildcards,
|
10025
10092
|
defsel = (m.selector === sd.default_selector),
|
10026
10093
|
clk = '" onclick="DATASET_MANAGER.selectModifier(event, \'' +
|
10027
10094
|
m.selector + '\'';
|
@@ -10029,13 +10096,14 @@ class GUIDatasetManager extends DatasetManager {
|
|
10029
10096
|
ml.push(['<tr id="dsmtr', i, '" class="dataset-modif',
|
10030
10097
|
(m === sm ? ' sel-set' : ''),
|
10031
10098
|
'"><td class="dataset-selector',
|
10032
|
-
(
|
10099
|
+
(wild ? ' wildcard' : ''),
|
10033
10100
|
'" title="Shift-click to ', (defsel ? 'clear' : 'set as'),
|
10034
10101
|
' default modifier',
|
10035
10102
|
clk, ', false);">',
|
10036
10103
|
(defsel ? '<img src="images/solve.png" style="height: 14px;' +
|
10037
10104
|
' width: 14px; margin: 0 1px -3px -1px;">' : ''),
|
10038
|
-
m.selector,
|
10105
|
+
(wild ? wildcardFormat(m.selector, true) : m.selector),
|
10106
|
+
'</td><td class="dataset-expression',
|
10039
10107
|
clk, ');">', m.expression.text, '</td></tr>'].join(''));
|
10040
10108
|
}
|
10041
10109
|
this.modifier_table.innerHTML = ml.join('');
|
@@ -10138,24 +10206,15 @@ class GUIDatasetManager extends DatasetManager {
|
|
10138
10206
|
}
|
10139
10207
|
} else {
|
10140
10208
|
this.selected_modifier = null;
|
10141
|
-
}
|
10209
|
+
}
|
10142
10210
|
this.updateModifiers();
|
10143
10211
|
}
|
10144
10212
|
|
10145
10213
|
get selectedPrefix() {
|
10146
10214
|
// Returns the selected prefix (with its trailing colon-space)
|
10147
|
-
|
10148
|
-
|
10149
|
-
|
10150
|
-
const td = tr.firstElementChild;
|
10151
|
-
if(td && td.firstElementChild.dataset.prefix === 'x') {
|
10152
|
-
prefix = td.lastChild.textContent + UI.PREFIXER + prefix;
|
10153
|
-
tr = tr.previousSibling;
|
10154
|
-
} else {
|
10155
|
-
tr = null;
|
10156
|
-
}
|
10157
|
-
}
|
10158
|
-
return prefix;
|
10215
|
+
const tr = this.selected_prefix_row;
|
10216
|
+
if(tr && tr.dataset.prefixName) return tr.dataset.prefixName + UI.PREFIXER;
|
10217
|
+
return '';
|
10159
10218
|
}
|
10160
10219
|
|
10161
10220
|
promptForDataset(shift=false) {
|
@@ -10164,9 +10223,7 @@ class GUIDatasetManager extends DatasetManager {
|
|
10164
10223
|
let prefix = '';
|
10165
10224
|
if(shift) {
|
10166
10225
|
if(this.selected_dataset) {
|
10167
|
-
|
10168
|
-
p[p.length - 1] = '';
|
10169
|
-
prefix = p.join(UI.PREFIXER);
|
10226
|
+
prefix = UI.completePrefix(this.selected_dataset.name);
|
10170
10227
|
} else if(this.selected_prefix) {
|
10171
10228
|
prefix = this.selectedPrefix;
|
10172
10229
|
}
|
@@ -10181,6 +10238,7 @@ class GUIDatasetManager extends DatasetManager {
|
|
10181
10238
|
if(d) {
|
10182
10239
|
this.new_modal.hide();
|
10183
10240
|
this.selected_dataset = d;
|
10241
|
+
this.focal_table = this.dataset_table;
|
10184
10242
|
this.updateDialog();
|
10185
10243
|
}
|
10186
10244
|
}
|
@@ -10220,12 +10278,14 @@ class GUIDatasetManager extends DatasetManager {
|
|
10220
10278
|
let e = this.rename_modal.element('name'),
|
10221
10279
|
prefix = e.value.trim();
|
10222
10280
|
e.focus();
|
10281
|
+
// Trim trailing colon if user entered it
|
10223
10282
|
while(prefix.endsWith(':')) prefix = prefix.slice(0, -1);
|
10224
10283
|
// NOTE: prefix may be empty string, but otherwise should be a valid name
|
10225
10284
|
if(prefix && !UI.validName(prefix)) {
|
10226
10285
|
UI.warn('Invalid prefix');
|
10227
10286
|
return;
|
10228
10287
|
}
|
10288
|
+
// Now add the colon-plus-space prefix separator
|
10229
10289
|
prefix += UI.PREFIXER;
|
10230
10290
|
const
|
10231
10291
|
oldpref = this.selectedPrefix,
|
@@ -10245,7 +10305,7 @@ class GUIDatasetManager extends DatasetManager {
|
|
10245
10305
|
if(MODEL.datasets[nk]) nc++;
|
10246
10306
|
}
|
10247
10307
|
if(nc) {
|
10248
|
-
UI.warn('Renaming ' + pluralS(dsl.length, 'dataset') +
|
10308
|
+
UI.warn('Renaming ' + pluralS(dsl.length, 'dataset').toLowerCase() +
|
10249
10309
|
' would cause ' + pluralS(nc, 'name conflict'));
|
10250
10310
|
return;
|
10251
10311
|
}
|
@@ -10258,7 +10318,7 @@ class GUIDatasetManager extends DatasetManager {
|
|
10258
10318
|
const d = MODEL.datasets[dsl[i]];
|
10259
10319
|
d.rename(d.displayName.replace(oldpref, prefix), false);
|
10260
10320
|
}
|
10261
|
-
let msg = 'Renamed ' + pluralS(dsl.length, 'dataset');
|
10321
|
+
let msg = 'Renamed ' + pluralS(dsl.length, 'dataset').toLowerCase();
|
10262
10322
|
if(MODEL.variable_count) msg += ', and updated ' +
|
10263
10323
|
pluralS(MODEL.variable_count, 'variable') + ' in ' +
|
10264
10324
|
pluralS(MODEL.expression_count, 'expression');
|
@@ -10379,14 +10439,14 @@ class GUIDatasetManager extends DatasetManager {
|
|
10379
10439
|
renameModifier() {
|
10380
10440
|
if(!this.selected_modifier) return;
|
10381
10441
|
const
|
10382
|
-
|
10442
|
+
wild = this.selected_modifier.hasWildcards,
|
10383
10443
|
sel = this.rename_selector_modal.element('name').value,
|
10384
10444
|
// NOTE: normal dataset selector, so remove all invalid characters
|
10385
|
-
clean_sel = sel.replace(/[^a-zA-z0-9
|
10445
|
+
clean_sel = sel.replace(/[^a-zA-z0-9\%\+\-\?\*]/g, ''),
|
10386
10446
|
// Keep track of old name
|
10387
10447
|
oldm = this.selected_modifier,
|
10388
10448
|
// NOTE: addModifier returns existing one if selector not changed
|
10389
|
-
m = this.selected_dataset.addModifier(
|
10449
|
+
m = this.selected_dataset.addModifier(clean_sel);
|
10390
10450
|
// NULL can result when new name is invalid
|
10391
10451
|
if(!m) return;
|
10392
10452
|
// If selected modifier was the dataset default selector, update it
|
@@ -10398,11 +10458,12 @@ class GUIDatasetManager extends DatasetManager {
|
|
10398
10458
|
if(m === oldm) {
|
10399
10459
|
m.selector = clean_sel;
|
10400
10460
|
this.updateDialog();
|
10461
|
+
this.rename_selector_modal.hide();
|
10401
10462
|
return;
|
10402
10463
|
}
|
10403
10464
|
// Rest is needed only when a new modifier has been added
|
10404
10465
|
m.expression = oldm.expression;
|
10405
|
-
if(
|
10466
|
+
if(wild) {
|
10406
10467
|
// Wildcard selector means: recompile the modifier expression
|
10407
10468
|
m.expression.attribute = m.selector;
|
10408
10469
|
m.expression.compile();
|
@@ -10410,11 +10471,10 @@ class GUIDatasetManager extends DatasetManager {
|
|
10410
10471
|
this.deleteModifier();
|
10411
10472
|
this.selected_modifier = m;
|
10412
10473
|
// Update all chartvariables referencing this dataset + old selector
|
10413
|
-
const vl = MODEL.
|
10474
|
+
const vl = MODEL.datasetVariables;
|
10414
10475
|
let cv_cnt = 0;
|
10415
10476
|
for(let i = 0; i < vl.length; i++) {
|
10416
|
-
if(v.object === this.selected_dataset &&
|
10417
|
-
v.attribute === oldm.selector) {
|
10477
|
+
if(v.object === this.selected_dataset && v.attribute === oldm.selector) {
|
10418
10478
|
v.attribute = m.selector;
|
10419
10479
|
cv_cnt++;
|
10420
10480
|
}
|
@@ -10818,6 +10878,7 @@ class EquationManager {
|
|
10818
10878
|
for(let i = 0; i < msl.length; i++) {
|
10819
10879
|
const
|
10820
10880
|
m = ed.modifiers[UI.nameToID(msl[i])],
|
10881
|
+
wild = (m.selector.indexOf('??') >= 0),
|
10821
10882
|
clk = '" onclick="EQUATION_MANAGER.selectModifier(event, \'' +
|
10822
10883
|
m.selector + '\'';
|
10823
10884
|
if(m === sm) smid += i;
|
@@ -10825,8 +10886,9 @@ class EquationManager {
|
|
10825
10886
|
(m === sm ? ' sel-set' : ''),
|
10826
10887
|
'"><td class="equation-selector',
|
10827
10888
|
(m.expression.isStatic ? '' : ' it'),
|
10828
|
-
clk, ', false);">',
|
10829
|
-
m.selector
|
10889
|
+
(wild ? ' wildcard' : ''), clk, ', false);">',
|
10890
|
+
(wild ? wildcardFormat(m.selector) : m.selector),
|
10891
|
+
'</td><td class="equation-expression',
|
10830
10892
|
clk, ');">', m.expression.text, '</td></tr>'].join(''));
|
10831
10893
|
}
|
10832
10894
|
this.table.innerHTML = ml.join('');
|
@@ -10956,8 +11018,7 @@ class EquationManager {
|
|
10956
11018
|
const c = MODEL.charts[i];
|
10957
11019
|
for(let j = 0; j < c.variables.length; j++) {
|
10958
11020
|
const v = c.variables[j];
|
10959
|
-
if(v.object === MODEL.equations_dataset &&
|
10960
|
-
v.attribute === olds) {
|
11021
|
+
if(v.object === MODEL.equations_dataset && v.attribute === olds) {
|
10961
11022
|
v.attribute = m.selector;
|
10962
11023
|
cv_cnt++;
|
10963
11024
|
}
|
@@ -15932,8 +15993,8 @@ class GUIReceiver {
|
|
15932
15993
|
}
|
15933
15994
|
|
15934
15995
|
log(msg) {
|
15935
|
-
// Logs a message displayed on the status line while solving
|
15936
|
-
if(this.active) {
|
15996
|
+
// Logs a message displayed on the status line while solving.
|
15997
|
+
if(this.active || MODEL.report_results) {
|
15937
15998
|
if(!msg.startsWith('[')) {
|
15938
15999
|
const
|
15939
16000
|
d = new Date(),
|
@@ -16076,21 +16137,34 @@ class GUIReceiver {
|
|
16076
16137
|
report() {
|
16077
16138
|
// Posts the run results to the local server, or signals an error
|
16078
16139
|
let form,
|
16079
|
-
run = ''
|
16140
|
+
run = '',
|
16141
|
+
path = this.channel,
|
16142
|
+
file = this.file_name;
|
16080
16143
|
// NOTE: Always set `solving` to FALSE
|
16081
16144
|
this.solving = false;
|
16082
|
-
|
16145
|
+
// NOTE: When reporting receiver while is not active, report the
|
16146
|
+
// results of the running experiment.
|
16147
|
+
if(this.experiment || !this.active) {
|
16083
16148
|
if(MODEL.running_experiment) {
|
16084
16149
|
run = MODEL.running_experiment.active_combination_index;
|
16085
16150
|
this.log(`Reporting: ${this.file_name} (run #${run})`);
|
16086
16151
|
}
|
16087
16152
|
}
|
16153
|
+
// NOTE: If receiver is not active, path and file must be set.
|
16154
|
+
if(!this.active) {
|
16155
|
+
path = 'user/reports';
|
16156
|
+
// NOTE: The @ will be replaced by the run number, so that that
|
16157
|
+
// number precedes the clock time. The @ will be unique because
|
16158
|
+
// `asFileName()` replaces special characters by underscores.
|
16159
|
+
file = REPOSITORY_BROWSER.asFileName(MODEL.name || 'model') +
|
16160
|
+
'@-' + compactClockTime();
|
16161
|
+
}
|
16088
16162
|
if(MODEL.solved && !VM.halted) {
|
16089
16163
|
// Normal execution termination => report results
|
16090
16164
|
const od = MODEL.outputData;
|
16091
16165
|
form = {
|
16092
|
-
path:
|
16093
|
-
file:
|
16166
|
+
path: path,
|
16167
|
+
file: file,
|
16094
16168
|
action: 'report',
|
16095
16169
|
run: run,
|
16096
16170
|
data: od[0],
|
@@ -16119,10 +16193,11 @@ class GUIReceiver {
|
|
16119
16193
|
.then((data) => {
|
16120
16194
|
// For experiments, only display server response if warning or error
|
16121
16195
|
UI.postResponseOK(data, !RECEIVER.experiment);
|
16122
|
-
// If execution completed, perform the call-back action
|
16196
|
+
// If execution completed, perform the call-back action if the
|
16197
|
+
// receiver is active (so not when auto-reporting a run).
|
16123
16198
|
// NOTE: for experiments, call-back is performed upon completion by
|
16124
|
-
// the Experiment Manager
|
16125
|
-
if(!RECEIVER.experiment) RECEIVER.callBack();
|
16199
|
+
// the Experiment Manager.
|
16200
|
+
if(RECEIVER.active && !RECEIVER.experiment) RECEIVER.callBack();
|
16126
16201
|
})
|
16127
16202
|
.catch(() => UI.warn(UI.WARNING.NO_CONNECTION, err));
|
16128
16203
|
}
|