linny-r 1.3.3 → 1.4.0
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/console.js +17 -0
- package/package.json +1 -1
- package/server.js +17 -2
- package/static/index.html +6 -12
- package/static/linny-r.css +28 -2
- package/static/scripts/linny-r-ctrl.js +40 -8
- package/static/scripts/linny-r-gui.js +174 -73
- package/static/scripts/linny-r-model.js +491 -154
- package/static/scripts/linny-r-utils.js +134 -12
- package/static/scripts/linny-r-vm.js +815 -396
@@ -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),
|
@@ -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 || '';
|
@@ -5273,21 +5297,52 @@ class GUIController extends Controller {
|
|
5273
5297
|
md.element('actor').value = mapping.actor || '';
|
5274
5298
|
md.element('prefix').value = mapping.prefix || '';
|
5275
5299
|
const
|
5276
|
-
|
5300
|
+
tc = (mapping.top_clusters ?
|
5301
|
+
Object.keys(mapping.top_clusters).sort(ciCompare) : []),
|
5302
|
+
ft = (mapping.from_to ?
|
5303
|
+
Object.keys(mapping.from_to).sort(ciCompare) : []),
|
5277
5304
|
sl = [];
|
5305
|
+
if(tc.length) {
|
5306
|
+
sl.push('<div style="font-weight: bold; margin:4px 2px 2px 2px">',
|
5307
|
+
'Names for top-level clusters:</div>');
|
5308
|
+
// Add text inputs for selected cluster nodes
|
5309
|
+
for(let i = 0; i < tc.length; i++) {
|
5310
|
+
const
|
5311
|
+
ti = mapping.top_clusters[tc[i]],
|
5312
|
+
state = (ti === tc[i] ? 'color: #e09000; ' :
|
5313
|
+
this.validName(ti) ? 'color: #0000c0; ' :
|
5314
|
+
'font-style: italic; color: red; ');
|
5315
|
+
sl.push('<div class="paste-option"><span>', tc[i], '</span> ',
|
5316
|
+
'<div class="paste-select"><input id="paste-selc-', i,
|
5317
|
+
'" type="text" style="', state, 'font-size: 12px" value="',
|
5318
|
+
ti, '"></div></div>');
|
5319
|
+
}
|
5320
|
+
}
|
5278
5321
|
if(ft.length) {
|
5322
|
+
sl.push('<div style="font-weight: bold; margin:4px 2px 2px 2px">',
|
5323
|
+
'Mapping of nodes to link from/to:</div>');
|
5279
5324
|
// Add selectors for unresolved FROM/TO nodes
|
5280
5325
|
for(let i = 0; i < ft.length; i++) {
|
5281
5326
|
const ti = mapping.from_to[ft[i]];
|
5282
5327
|
if(ft[i] === ti) {
|
5283
|
-
|
5284
|
-
|
5285
|
-
|
5286
|
-
|
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>');
|
5287
5342
|
}
|
5288
5343
|
}
|
5289
|
-
md.element('scroll-area').innerHTML = sl.join('');
|
5290
5344
|
}
|
5345
|
+
md.element('scroll-area').innerHTML = sl.join('');
|
5291
5346
|
// Open dialog, which will call pasteSelection(...) on OK
|
5292
5347
|
this.paste_modal.show();
|
5293
5348
|
}
|
@@ -5297,10 +5352,24 @@ class GUIController extends Controller {
|
|
5297
5352
|
// proceeds to paste
|
5298
5353
|
const
|
5299
5354
|
md = this.paste_modal,
|
5300
|
-
mapping = Object.assign(md.mapping, {})
|
5355
|
+
mapping = Object.assign(md.mapping, {}),
|
5356
|
+
tc = (mapping.top_clusters ?
|
5357
|
+
Object.keys(mapping.top_clusters).sort(ciCompare) : []),
|
5358
|
+
ft = (mapping.from_to ?
|
5359
|
+
Object.keys(mapping.from_to).sort(ciCompare) : []);
|
5301
5360
|
mapping.actor = md.element('actor').value;
|
5302
5361
|
mapping.prefix = md.element('prefix').value.trim();
|
5303
5362
|
mapping.increment = true;
|
5363
|
+
for(let i = 0; i < tc.length; i++) {
|
5364
|
+
const cn = md.element('selc-' + i).value.trim();
|
5365
|
+
if(this.validName(cn)) mapping.top_clusters[tc[i]] = cn;
|
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
|
+
}
|
5304
5373
|
this.pasteSelection(mapping);
|
5305
5374
|
}
|
5306
5375
|
|
@@ -5309,7 +5378,6 @@ class GUIController extends Controller {
|
|
5309
5378
|
// see whether PASTE would result in name conflicts, and if so,
|
5310
5379
|
// open the name conflict resolution window
|
5311
5380
|
let xml = window.localStorage.getItem('Linny-R-selection-XML');
|
5312
|
-
console.log('HERE xml', xml);
|
5313
5381
|
try {
|
5314
5382
|
xml = parseXML(xml);
|
5315
5383
|
} catch(e) {
|
@@ -5318,13 +5386,11 @@ console.log('HERE xml', xml);
|
|
5318
5386
|
return;
|
5319
5387
|
}
|
5320
5388
|
|
5321
|
-
// For now, while still implementing:
|
5322
|
-
this.notify('Paste not fully implemented yet -- WORK IN PROGRESS!');
|
5323
|
-
|
5324
5389
|
const
|
5325
5390
|
entities_node = childNodeByTag(xml, 'entities'),
|
5326
5391
|
from_tos_node = childNodeByTag(xml, 'from-tos'),
|
5327
5392
|
extras_node = childNodeByTag(xml, 'extras'),
|
5393
|
+
selc_node = childNodeByTag(xml, 'selected-clusters'),
|
5328
5394
|
selection_node = childNodeByTag(xml, 'selection'),
|
5329
5395
|
actor_names = [],
|
5330
5396
|
new_entities = [],
|
@@ -5335,7 +5401,7 @@ console.log('HERE xml', xml);
|
|
5335
5401
|
|
5336
5402
|
function fullName(node) {
|
5337
5403
|
// Returns full entity name inferred from XML node data
|
5338
|
-
if(node.nodeName === 'from-to') {
|
5404
|
+
if(node.nodeName === 'from-to' || node.nodeName === 'selc') {
|
5339
5405
|
const
|
5340
5406
|
n = xmlDecoded(nodeParameterValue(node, 'name')),
|
5341
5407
|
an = xmlDecoded(nodeParameterValue(node, 'actor-name'));
|
@@ -5424,6 +5490,12 @@ console.log('HERE xml', xml);
|
|
5424
5490
|
if(mapping.increment && nr) {
|
5425
5491
|
return n.replace(new RegExp(nr + '$'), parseInt(nr) + 1);
|
5426
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
|
+
}
|
5427
5499
|
// No mapping => return original name
|
5428
5500
|
return n;
|
5429
5501
|
}
|
@@ -5525,42 +5597,58 @@ console.log('HERE xml', xml);
|
|
5525
5597
|
if(parseInt(mts) === MODEL.time_created.getTime() &&
|
5526
5598
|
ca === fca && mapping.from_prefix === mapping.to_prefix &&
|
5527
5599
|
!(mapping.prefix || mapping.actor || mapping.increment)) {
|
5600
|
+
// Prompt for names of selected cluster nodes
|
5601
|
+
if(selc_node.childNodes.length && !mapping.prefix) {
|
5602
|
+
mapping.top_clusters = {};
|
5603
|
+
for(let i = 0; i < selc_node.childNodes.length; i++) {
|
5604
|
+
const
|
5605
|
+
c = selc_node.childNodes[i],
|
5606
|
+
fn = fullName(c),
|
5607
|
+
mn = mappedName(fn);
|
5608
|
+
mapping.top_clusters[fn] = mn;
|
5609
|
+
}
|
5610
|
+
}
|
5528
5611
|
this.promptForMapping(mapping);
|
5529
5612
|
return;
|
5530
5613
|
}
|
5531
5614
|
// Also prompt if FROM and/or TO nodes are not selected, and map to
|
5532
5615
|
// existing entities
|
5533
5616
|
if(from_tos_node.childNodes.length && !mapping.from_to) {
|
5534
|
-
const
|
5617
|
+
const
|
5618
|
+
ft_map = {},
|
5619
|
+
ft_type = {};
|
5535
5620
|
for(let i = 0; i < from_tos_node.childNodes.length; i++) {
|
5536
5621
|
const
|
5537
5622
|
c = from_tos_node.childNodes[i],
|
5538
5623
|
fn = fullName(c),
|
5539
5624
|
mn = mappedName(fn);
|
5540
|
-
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
|
+
}
|
5541
5630
|
}
|
5542
5631
|
// Prompt only for FROM/TO nodes that map to existing nodes
|
5543
5632
|
if(Object.keys(ft_map).length) {
|
5544
5633
|
mapping.from_to = ft_map;
|
5634
|
+
mapping.from_to_type = ft_type;
|
5545
5635
|
this.promptForMapping(mapping);
|
5546
5636
|
return;
|
5547
5637
|
}
|
5548
5638
|
}
|
5549
5639
|
|
5550
|
-
// Only check for selected entities
|
5640
|
+
// Only check for selected entities; from-to's and extra's should be
|
5551
5641
|
// used if they exist, or should be created when copying to a different
|
5552
5642
|
// model
|
5553
5643
|
name_map.length = 0;
|
5554
5644
|
nameConflicts(entities_node);
|
5555
|
-
nameConflicts(from_tos_node);
|
5556
5645
|
if(name_conflicts.length) {
|
5557
|
-
UI.
|
5558
|
-
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);
|
5559
5648
|
return;
|
5560
5649
|
}
|
5561
5650
|
|
5562
5651
|
// No conflicts => add all
|
5563
|
-
console.log('HERE name map', name_map);
|
5564
5652
|
for(let i = 0; i < extras_node.childNodes.length; i++) {
|
5565
5653
|
addEntityFromNode(extras_node.childNodes[i]);
|
5566
5654
|
}
|
@@ -5586,6 +5674,7 @@ console.log('HERE name map', name_map);
|
|
5586
5674
|
// products are displayed as arrows instead of block arrows
|
5587
5675
|
fc.clearAllProcesses();
|
5588
5676
|
UI.drawDiagram(MODEL);
|
5677
|
+
this.paste_modal.hide();
|
5589
5678
|
}
|
5590
5679
|
|
5591
5680
|
//
|
@@ -5725,6 +5814,8 @@ console.log('HERE name map', name_map);
|
|
5725
5814
|
const md = this.modals.note;
|
5726
5815
|
if(n) {
|
5727
5816
|
md.element('action').innerHTML = 'Edit';
|
5817
|
+
const nr = n.number;
|
5818
|
+
md.element('number').innerHTML = (nr ? '#' + nr : '');
|
5728
5819
|
md.element('text').value = n.contents;
|
5729
5820
|
md.element('C').value = n.color.text;
|
5730
5821
|
} else {
|
@@ -5746,6 +5837,10 @@ console.log('HERE name map', name_map);
|
|
5746
5837
|
} else {
|
5747
5838
|
md.element('actor').value = '';
|
5748
5839
|
}
|
5840
|
+
const sim = p.similarNumberedEntities;
|
5841
|
+
if(sim.length) {
|
5842
|
+
console.log('HERE!', sim);
|
5843
|
+
}
|
5749
5844
|
md.element('LB').value = p.lower_bound.text;
|
5750
5845
|
md.element('UB').value = p.upper_bound.text;
|
5751
5846
|
this.setEqualBounds('process', p.equal_bounds);
|
@@ -5976,7 +6071,7 @@ console.log('HERE name map', name_map);
|
|
5976
6071
|
const
|
5977
6072
|
from_process = l.from_node instanceof Process,
|
5978
6073
|
to_process = l.to_node instanceof Process,
|
5979
|
-
md = this.modals.link;
|
6074
|
+
md = this.modals.link;
|
5980
6075
|
md.show();
|
5981
6076
|
md.element('from-name').innerHTML = l.from_node.displayName;
|
5982
6077
|
md.element('to-name').innerHTML = l.to_node.displayName;
|
@@ -6023,6 +6118,7 @@ console.log('HERE name map', name_map);
|
|
6023
6118
|
md.element('output-soc').style.display = 'none';
|
6024
6119
|
}
|
6025
6120
|
}
|
6121
|
+
this.edited_object = l;
|
6026
6122
|
if(alt) md.element(attr + '-x').dispatchEvent(new Event('click'));
|
6027
6123
|
}
|
6028
6124
|
|
@@ -6067,7 +6163,7 @@ console.log('HERE name map', name_map);
|
|
6067
6163
|
// @@TO DO: prepare for undo
|
6068
6164
|
const
|
6069
6165
|
md = this.modals.link,
|
6070
|
-
l = this.
|
6166
|
+
l = this.edited_object;
|
6071
6167
|
// Check whether all input fields are valid
|
6072
6168
|
if(!this.updateExpressionInput('link-R', 'rate', l.relative_rate)) {
|
6073
6169
|
return false;
|
@@ -6374,7 +6470,8 @@ class GUIMonitor {
|
|
6374
6470
|
if(this.call_stack_shown) return;
|
6375
6471
|
const
|
6376
6472
|
csl = VM.call_stack.length,
|
6377
|
-
|
6473
|
+
top = VM.call_stack[csl - 1],
|
6474
|
+
err = top.vector[t],
|
6378
6475
|
// Make separate lists of variable names and their expressions
|
6379
6476
|
vlist = [],
|
6380
6477
|
xlist = [];
|
@@ -6439,6 +6536,9 @@ class GUIMonitor {
|
|
6439
6536
|
tbl.push('<div style="color: gray; margin-top: 8px; font-size: 10px">',
|
6440
6537
|
VM.out_of_bounds_msg.replace(VM.out_of_bounds_array, anc), '</div>');
|
6441
6538
|
}
|
6539
|
+
// Dump the code for the last expression to the console
|
6540
|
+
console.log('Code for', top.text, top.code);
|
6541
|
+
// Show the call stack dialog
|
6442
6542
|
document.getElementById('call-stack-table').innerHTML = tbl.join('');
|
6443
6543
|
document.getElementById('call-stack-modal').style.display = 'block';
|
6444
6544
|
this.call_stack_shown = true;
|
@@ -6525,11 +6625,12 @@ class GUIMonitor {
|
|
6525
6625
|
})
|
6526
6626
|
.then((data) => {
|
6527
6627
|
try {
|
6528
|
-
const
|
6529
|
-
|
6530
|
-
|
6531
|
-
|
6628
|
+
const
|
6629
|
+
jsr = JSON.parse(data),
|
6630
|
+
svr = `Solver on ${jsr.server} is ${jsr.solver}`;
|
6631
|
+
if(jsr.solver !== VM.solver_name) UI.notify(svr);
|
6532
6632
|
VM.solver_name = jsr.solver;
|
6633
|
+
document.getElementById('host-logo').title = svr;
|
6533
6634
|
} catch(err) {
|
6534
6635
|
console.log(err, data);
|
6535
6636
|
UI.alert('ERROR: Unexpected data from server: ' +
|
@@ -7202,10 +7303,12 @@ NOTE: Grouping groups results in a single group, e.g., (1;2);(3;4;5) evaluates a
|
|
7202
7303
|
if(this.edited_input_id) {
|
7203
7304
|
document.getElementById(this.edited_input_id).value = xp.expr;
|
7204
7305
|
// NOTE: entity properties must be exogenous parameters
|
7205
|
-
|
7206
|
-
|
7207
|
-
|
7208
|
-
|
7306
|
+
const eo = UI.edited_object;
|
7307
|
+
if(eo && xp.is_level_based &&
|
7308
|
+
!(eo instanceof Dataset || eo instanceof Note)) {
|
7309
|
+
UI.warn(['Expression for', this.property.innerHTML,
|
7310
|
+
'of<strong>', eo.displayName,
|
7311
|
+
'</strong>contains a solution-dependent variable'].join(' '));
|
7209
7312
|
}
|
7210
7313
|
this.edited_input_id = '';
|
7211
7314
|
} else if(DATASET_MANAGER.edited_expression) {
|
@@ -8701,6 +8804,7 @@ class ConstraintEditor {
|
|
8701
8804
|
// NOTE: this could be expanded to apply to the selected BL only
|
8702
8805
|
UI.setBox('ce-no-slack', this.constraint.no_slack);
|
8703
8806
|
// NOTE: share of cost can only be transferred between two processes
|
8807
|
+
// @@TO DO: CHECK WHETHER THIS LIMITATION IS VALID -- for now, allow both
|
8704
8808
|
if(true||this.from_node instanceof Process && this.from_node instanceof Process) {
|
8705
8809
|
this.soc_direct.value = this.constraint.soc_direction;
|
8706
8810
|
// NOTE: share of cost is input as a percentage
|
@@ -9852,6 +9956,7 @@ class GUIDatasetManager extends DatasetManager {
|
|
9852
9956
|
ps = pid.split(UI.PREFIXER),
|
9853
9957
|
pps = prev_id.split(UI.PREFIXER),
|
9854
9958
|
pn = pref_names[pid],
|
9959
|
+
pns = pn.join(UI.PREFIXER),
|
9855
9960
|
lpl = [];
|
9856
9961
|
let lindent = 0;
|
9857
9962
|
// Ignore identical leading prefixes
|
@@ -9866,8 +9971,9 @@ class GUIDatasetManager extends DatasetManager {
|
|
9866
9971
|
lpl.push(ps.shift());
|
9867
9972
|
lindent++;
|
9868
9973
|
const lpid = lpl.join(UI.PREFIXER);
|
9869
|
-
dl.push(['<tr data-prefix="', lpid,
|
9870
|
-
'"
|
9974
|
+
dl.push(['<tr data-prefix="', lpid,
|
9975
|
+
'" data-prefix-name="', pns, '" class="dataset"',
|
9976
|
+
'onclick="DATASET_MANAGER.selectPrefixRow(event);"><td>',
|
9871
9977
|
// NOTE: data-prefix="x" signals that this is an extra row
|
9872
9978
|
(lindent > 0 ?
|
9873
9979
|
'<div data-prefix="x" style="width: ' + lindent * indent_px +
|
@@ -9980,6 +10086,7 @@ class GUIDatasetManager extends DatasetManager {
|
|
9980
10086
|
for(let i = 0; i < msl.length; i++) {
|
9981
10087
|
const
|
9982
10088
|
m = sd.modifiers[UI.nameToID(msl[i])],
|
10089
|
+
wild = m.hasWildcards,
|
9983
10090
|
defsel = (m.selector === sd.default_selector),
|
9984
10091
|
clk = '" onclick="DATASET_MANAGER.selectModifier(event, \'' +
|
9985
10092
|
m.selector + '\'';
|
@@ -9987,13 +10094,14 @@ class GUIDatasetManager extends DatasetManager {
|
|
9987
10094
|
ml.push(['<tr id="dsmtr', i, '" class="dataset-modif',
|
9988
10095
|
(m === sm ? ' sel-set' : ''),
|
9989
10096
|
'"><td class="dataset-selector',
|
9990
|
-
(
|
10097
|
+
(wild ? ' wildcard' : ''),
|
9991
10098
|
'" title="Shift-click to ', (defsel ? 'clear' : 'set as'),
|
9992
10099
|
' default modifier',
|
9993
10100
|
clk, ', false);">',
|
9994
10101
|
(defsel ? '<img src="images/solve.png" style="height: 14px;' +
|
9995
10102
|
' width: 14px; margin: 0 1px -3px -1px;">' : ''),
|
9996
|
-
m.selector,
|
10103
|
+
(wild ? wildcardFormat(m.selector, true) : m.selector),
|
10104
|
+
'</td><td class="dataset-expression',
|
9997
10105
|
clk, ');">', m.expression.text, '</td></tr>'].join(''));
|
9998
10106
|
}
|
9999
10107
|
this.modifier_table.innerHTML = ml.join('');
|
@@ -10096,24 +10204,15 @@ class GUIDatasetManager extends DatasetManager {
|
|
10096
10204
|
}
|
10097
10205
|
} else {
|
10098
10206
|
this.selected_modifier = null;
|
10099
|
-
}
|
10207
|
+
}
|
10100
10208
|
this.updateModifiers();
|
10101
10209
|
}
|
10102
10210
|
|
10103
10211
|
get selectedPrefix() {
|
10104
10212
|
// Returns the selected prefix (with its trailing colon-space)
|
10105
|
-
|
10106
|
-
|
10107
|
-
|
10108
|
-
const td = tr.firstElementChild;
|
10109
|
-
if(td && td.firstElementChild.dataset.prefix === 'x') {
|
10110
|
-
prefix = td.lastChild.textContent + UI.PREFIXER + prefix;
|
10111
|
-
tr = tr.previousSibling;
|
10112
|
-
} else {
|
10113
|
-
tr = null;
|
10114
|
-
}
|
10115
|
-
}
|
10116
|
-
return prefix;
|
10213
|
+
const tr = this.selected_prefix_row;
|
10214
|
+
if(tr && tr.dataset.prefixName) return tr.dataset.prefixName + UI.PREFIXER;
|
10215
|
+
return '';
|
10117
10216
|
}
|
10118
10217
|
|
10119
10218
|
promptForDataset(shift=false) {
|
@@ -10122,9 +10221,7 @@ class GUIDatasetManager extends DatasetManager {
|
|
10122
10221
|
let prefix = '';
|
10123
10222
|
if(shift) {
|
10124
10223
|
if(this.selected_dataset) {
|
10125
|
-
|
10126
|
-
p[p.length - 1] = '';
|
10127
|
-
prefix = p.join(UI.PREFIXER);
|
10224
|
+
prefix = UI.completePrefix(this.selected_dataset.name);
|
10128
10225
|
} else if(this.selected_prefix) {
|
10129
10226
|
prefix = this.selectedPrefix;
|
10130
10227
|
}
|
@@ -10139,6 +10236,7 @@ class GUIDatasetManager extends DatasetManager {
|
|
10139
10236
|
if(d) {
|
10140
10237
|
this.new_modal.hide();
|
10141
10238
|
this.selected_dataset = d;
|
10239
|
+
this.focal_table = this.dataset_table;
|
10142
10240
|
this.updateDialog();
|
10143
10241
|
}
|
10144
10242
|
}
|
@@ -10178,12 +10276,14 @@ class GUIDatasetManager extends DatasetManager {
|
|
10178
10276
|
let e = this.rename_modal.element('name'),
|
10179
10277
|
prefix = e.value.trim();
|
10180
10278
|
e.focus();
|
10279
|
+
// Trim trailing colon if user entered it
|
10181
10280
|
while(prefix.endsWith(':')) prefix = prefix.slice(0, -1);
|
10182
10281
|
// NOTE: prefix may be empty string, but otherwise should be a valid name
|
10183
10282
|
if(prefix && !UI.validName(prefix)) {
|
10184
10283
|
UI.warn('Invalid prefix');
|
10185
10284
|
return;
|
10186
10285
|
}
|
10286
|
+
// Now add the colon-plus-space prefix separator
|
10187
10287
|
prefix += UI.PREFIXER;
|
10188
10288
|
const
|
10189
10289
|
oldpref = this.selectedPrefix,
|
@@ -10203,7 +10303,7 @@ class GUIDatasetManager extends DatasetManager {
|
|
10203
10303
|
if(MODEL.datasets[nk]) nc++;
|
10204
10304
|
}
|
10205
10305
|
if(nc) {
|
10206
|
-
UI.warn('Renaming ' + pluralS(dsl.length, 'dataset') +
|
10306
|
+
UI.warn('Renaming ' + pluralS(dsl.length, 'dataset').toLowerCase() +
|
10207
10307
|
' would cause ' + pluralS(nc, 'name conflict'));
|
10208
10308
|
return;
|
10209
10309
|
}
|
@@ -10216,7 +10316,7 @@ class GUIDatasetManager extends DatasetManager {
|
|
10216
10316
|
const d = MODEL.datasets[dsl[i]];
|
10217
10317
|
d.rename(d.displayName.replace(oldpref, prefix), false);
|
10218
10318
|
}
|
10219
|
-
let msg = 'Renamed ' + pluralS(dsl.length, 'dataset');
|
10319
|
+
let msg = 'Renamed ' + pluralS(dsl.length, 'dataset').toLowerCase();
|
10220
10320
|
if(MODEL.variable_count) msg += ', and updated ' +
|
10221
10321
|
pluralS(MODEL.variable_count, 'variable') + ' in ' +
|
10222
10322
|
pluralS(MODEL.expression_count, 'expression');
|
@@ -10337,14 +10437,14 @@ class GUIDatasetManager extends DatasetManager {
|
|
10337
10437
|
renameModifier() {
|
10338
10438
|
if(!this.selected_modifier) return;
|
10339
10439
|
const
|
10340
|
-
|
10440
|
+
wild = this.selected_modifier.hasWildcards,
|
10341
10441
|
sel = this.rename_selector_modal.element('name').value,
|
10342
10442
|
// NOTE: normal dataset selector, so remove all invalid characters
|
10343
|
-
clean_sel = sel.replace(/[^a-zA-z0-9
|
10443
|
+
clean_sel = sel.replace(/[^a-zA-z0-9\%\+\-\?\*]/g, ''),
|
10344
10444
|
// Keep track of old name
|
10345
10445
|
oldm = this.selected_modifier,
|
10346
10446
|
// NOTE: addModifier returns existing one if selector not changed
|
10347
|
-
m = this.selected_dataset.addModifier(
|
10447
|
+
m = this.selected_dataset.addModifier(clean_sel);
|
10348
10448
|
// NULL can result when new name is invalid
|
10349
10449
|
if(!m) return;
|
10350
10450
|
// If selected modifier was the dataset default selector, update it
|
@@ -10356,11 +10456,12 @@ class GUIDatasetManager extends DatasetManager {
|
|
10356
10456
|
if(m === oldm) {
|
10357
10457
|
m.selector = clean_sel;
|
10358
10458
|
this.updateDialog();
|
10459
|
+
this.rename_selector_modal.hide();
|
10359
10460
|
return;
|
10360
10461
|
}
|
10361
10462
|
// Rest is needed only when a new modifier has been added
|
10362
10463
|
m.expression = oldm.expression;
|
10363
|
-
if(
|
10464
|
+
if(wild) {
|
10364
10465
|
// Wildcard selector means: recompile the modifier expression
|
10365
10466
|
m.expression.attribute = m.selector;
|
10366
10467
|
m.expression.compile();
|
@@ -10368,11 +10469,10 @@ class GUIDatasetManager extends DatasetManager {
|
|
10368
10469
|
this.deleteModifier();
|
10369
10470
|
this.selected_modifier = m;
|
10370
10471
|
// Update all chartvariables referencing this dataset + old selector
|
10371
|
-
const vl = MODEL.
|
10472
|
+
const vl = MODEL.datasetVariables;
|
10372
10473
|
let cv_cnt = 0;
|
10373
10474
|
for(let i = 0; i < vl.length; i++) {
|
10374
|
-
if(v.object === this.selected_dataset &&
|
10375
|
-
v.attribute === oldm.selector) {
|
10475
|
+
if(v.object === this.selected_dataset && v.attribute === oldm.selector) {
|
10376
10476
|
v.attribute = m.selector;
|
10377
10477
|
cv_cnt++;
|
10378
10478
|
}
|
@@ -10776,6 +10876,7 @@ class EquationManager {
|
|
10776
10876
|
for(let i = 0; i < msl.length; i++) {
|
10777
10877
|
const
|
10778
10878
|
m = ed.modifiers[UI.nameToID(msl[i])],
|
10879
|
+
wild = (m.selector.indexOf('??') >= 0),
|
10779
10880
|
clk = '" onclick="EQUATION_MANAGER.selectModifier(event, \'' +
|
10780
10881
|
m.selector + '\'';
|
10781
10882
|
if(m === sm) smid += i;
|
@@ -10783,8 +10884,9 @@ class EquationManager {
|
|
10783
10884
|
(m === sm ? ' sel-set' : ''),
|
10784
10885
|
'"><td class="equation-selector',
|
10785
10886
|
(m.expression.isStatic ? '' : ' it'),
|
10786
|
-
clk, ', false);">',
|
10787
|
-
m.selector
|
10887
|
+
(wild ? ' wildcard' : ''), clk, ', false);">',
|
10888
|
+
(wild ? wildcardFormat(m.selector) : m.selector),
|
10889
|
+
'</td><td class="equation-expression',
|
10788
10890
|
clk, ');">', m.expression.text, '</td></tr>'].join(''));
|
10789
10891
|
}
|
10790
10892
|
this.table.innerHTML = ml.join('');
|
@@ -10914,8 +11016,7 @@ class EquationManager {
|
|
10914
11016
|
const c = MODEL.charts[i];
|
10915
11017
|
for(let j = 0; j < c.variables.length; j++) {
|
10916
11018
|
const v = c.variables[j];
|
10917
|
-
if(v.object === MODEL.equations_dataset &&
|
10918
|
-
v.attribute === olds) {
|
11019
|
+
if(v.object === MODEL.equations_dataset && v.attribute === olds) {
|
10919
11020
|
v.attribute = m.selector;
|
10920
11021
|
cv_cnt++;
|
10921
11022
|
}
|