linny-r 1.9.3 → 2.0.2
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/LICENSE +1 -1
- package/README.md +172 -126
- package/console.js +2 -1
- package/package.json +1 -1
- package/post-install.js +93 -37
- package/server.js +73 -29
- package/static/images/eq-negated.png +0 -0
- package/static/images/power.png +0 -0
- package/static/images/tex.png +0 -0
- package/static/index.html +226 -11
- package/static/linny-r.css +458 -8
- package/static/scripts/linny-r-ctrl.js +6 -4
- package/static/scripts/linny-r-gui-actor-manager.js +1 -1
- package/static/scripts/linny-r-gui-chart-manager.js +20 -13
- package/static/scripts/linny-r-gui-constraint-editor.js +410 -50
- package/static/scripts/linny-r-gui-controller.js +138 -21
- package/static/scripts/linny-r-gui-dataset-manager.js +28 -20
- package/static/scripts/linny-r-gui-documentation-manager.js +11 -3
- package/static/scripts/linny-r-gui-equation-manager.js +1 -1
- package/static/scripts/linny-r-gui-experiment-manager.js +1 -1
- package/static/scripts/linny-r-gui-expression-editor.js +7 -1
- package/static/scripts/linny-r-gui-file-manager.js +63 -19
- package/static/scripts/linny-r-gui-finder.js +1 -1
- package/static/scripts/linny-r-gui-model-autosaver.js +1 -1
- package/static/scripts/linny-r-gui-monitor.js +1 -1
- package/static/scripts/linny-r-gui-paper.js +108 -25
- package/static/scripts/linny-r-gui-power-grid-manager.js +529 -0
- package/static/scripts/linny-r-gui-receiver.js +1 -1
- package/static/scripts/linny-r-gui-repository-browser.js +1 -1
- package/static/scripts/linny-r-gui-scale-unit-manager.js +1 -1
- package/static/scripts/linny-r-gui-sensitivity-analysis.js +1 -1
- package/static/scripts/linny-r-gui-tex-manager.js +110 -0
- package/static/scripts/linny-r-gui-undo-redo.js +1 -1
- package/static/scripts/linny-r-milp.js +1 -1
- package/static/scripts/linny-r-model.js +982 -123
- package/static/scripts/linny-r-utils.js +3 -3
- package/static/scripts/linny-r-vm.js +731 -252
- package/static/show-diff.html +1 -1
- package/static/show-png.html +1 -1
@@ -13,7 +13,7 @@ handler functions.
|
|
13
13
|
*/
|
14
14
|
|
15
15
|
/*
|
16
|
-
Copyright (c) 2017-
|
16
|
+
Copyright (c) 2017-2024 Delft University of Technology
|
17
17
|
|
18
18
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
19
19
|
of this software and associated documentation files (the "Software"), to deal
|
@@ -421,7 +421,7 @@ class GUIController extends Controller {
|
|
421
421
|
this.edit_btns = ['clone', 'paste', 'delete', 'undo', 'redo'];
|
422
422
|
this.model_btns = ['settings', 'save', 'repository', 'actors',
|
423
423
|
'dataset', 'equation', 'chart', 'sensitivity', 'experiment',
|
424
|
-
'diagram', 'savediagram', 'finder', 'monitor', 'solve'];
|
424
|
+
'diagram', 'savediagram', 'finder', 'monitor', 'tex', 'solve'];
|
425
425
|
this.other_btns = ['new', 'load', 'receiver', 'documentation',
|
426
426
|
'parent', 'lift', 'solve', 'stop', 'reset', 'zoomin', 'zoomout',
|
427
427
|
'stepback', 'stepforward', 'autosave', 'recall'];
|
@@ -549,7 +549,7 @@ class GUIController extends Controller {
|
|
549
549
|
this.buttons.settings.addEventListener('click',
|
550
550
|
() => UI.showSettingsDialog(MODEL));
|
551
551
|
this.buttons.save.addEventListener('click',
|
552
|
-
() => FILE_MANAGER.saveModel());
|
552
|
+
() => FILE_MANAGER.saveModel(event.shiftKey));
|
553
553
|
this.buttons.actors.addEventListener('click',
|
554
554
|
() => ACTOR_MANAGER.showDialog());
|
555
555
|
this.buttons.diagram.addEventListener('click',
|
@@ -569,6 +569,7 @@ class GUIController extends Controller {
|
|
569
569
|
this.buttons.finder.addEventListener('click', tdf);
|
570
570
|
this.buttons.monitor.addEventListener('click', tdf);
|
571
571
|
this.buttons.documentation.addEventListener('click', tdf);
|
572
|
+
this.buttons.tex.addEventListener('click', tdf);
|
572
573
|
// Cluster navigation elements:
|
573
574
|
this.focal_name.addEventListener('click',
|
574
575
|
() => UI.showClusterPropertiesDialog(MODEL.focal_cluster));
|
@@ -717,6 +718,10 @@ class GUIController extends Controller {
|
|
717
718
|
() => SCALE_UNIT_MANAGER.show());
|
718
719
|
this.modals.settings.element('solver-prefs-btn').addEventListener('click',
|
719
720
|
() => UI.showSolverPreferencesDialog());
|
721
|
+
this.modals.settings.element('power').addEventListener('click',
|
722
|
+
() => UI.togglePowerGridButton());
|
723
|
+
this.modals.settings.element('power-btn').addEventListener('click',
|
724
|
+
() => POWER_GRID_MANAGER.show());
|
720
725
|
// Make solver modal elements responsive.
|
721
726
|
this.modals.solver.ok.addEventListener('click',
|
722
727
|
() => UI.updateSolverPreferences());
|
@@ -773,7 +778,12 @@ class GUIController extends Controller {
|
|
773
778
|
this.modals.process.element('UB-x').addEventListener('click', eoxedit);
|
774
779
|
this.modals.process.element('IL-x').addEventListener('click', eoxedit);
|
775
780
|
this.modals.process.element('LCF-x').addEventListener('click', eoxedit);
|
776
|
-
|
781
|
+
// Processes can represent power grid elements.
|
782
|
+
this.modals.process.element('grid-plate').addEventListener(
|
783
|
+
'mouseenter', () => UI.showGridPlateMenu('process'));
|
784
|
+
// Make the grid plate menu responsive.
|
785
|
+
this.modals.process.element('grid-plate-menu').addEventListener(
|
786
|
+
'mouseleave', () => UI.hideGridPlateMenu('process'));
|
777
787
|
this.modals.product.ok.addEventListener('click',
|
778
788
|
() => UI.updateProductProperties());
|
779
789
|
this.modals.product.cancel.addEventListener('click',
|
@@ -1089,10 +1099,10 @@ class GUIController extends Controller {
|
|
1089
1099
|
// Only then try to restart.
|
1090
1100
|
if(SOLVER.user_id) return;
|
1091
1101
|
UI.updating_modal.show();
|
1092
|
-
setTimeout(() => UI.tryToRestart(
|
1102
|
+
setTimeout(() => UI.tryToRestart(), 5000);
|
1093
1103
|
}
|
1094
1104
|
|
1095
|
-
tryToRestart(
|
1105
|
+
tryToRestart() {
|
1096
1106
|
// Fetch the current version number from the server. This may take
|
1097
1107
|
// a wile, as the server was shut down and restarts only after npm
|
1098
1108
|
// has updated the Linny-R software. Typically, this takes only a few
|
@@ -1122,6 +1132,7 @@ class GUIController extends Controller {
|
|
1122
1132
|
'confirm when prompted by your browser.');
|
1123
1133
|
// Hide "update" button in server dialog.
|
1124
1134
|
UI.modals.server.element('update').style.display = 'none';
|
1135
|
+
return;
|
1125
1136
|
} else {
|
1126
1137
|
// Inform user that install appears to have failed.
|
1127
1138
|
msg.push(
|
@@ -1135,11 +1146,7 @@ class GUIController extends Controller {
|
|
1135
1146
|
}
|
1136
1147
|
})
|
1137
1148
|
.catch((err) => {
|
1138
|
-
|
1139
|
-
setTimeout(() => UI.tryToRestart(trials + 1), 5000);
|
1140
|
-
} else {
|
1141
|
-
UI.warn(UI.WARNING.NO_CONNECTION, err);
|
1142
|
-
}
|
1149
|
+
UI.warn(UI.WARNING.NO_CONNECTION, err);
|
1143
1150
|
});
|
1144
1151
|
}
|
1145
1152
|
|
@@ -2242,8 +2249,9 @@ class GUIController extends Controller {
|
|
2242
2249
|
while(i < inp.length && inp[i].disabled) i++;
|
2243
2250
|
if(i < inp.length) {
|
2244
2251
|
inp[i].focus();
|
2245
|
-
} else if('constraint-modal
|
2246
|
-
|
2252
|
+
} else if(['constraint-modal', 'boundline-data-modal',
|
2253
|
+
'xp-clusters-modal'].indexOf(topmod.id) >= 0) {
|
2254
|
+
// NOTE: Constraint modal, boundline data modal and "ignore clusters" modal must NOT close
|
2247
2255
|
// when Enter is pressed, but only de-focus the input field.
|
2248
2256
|
e.target.blur();
|
2249
2257
|
} else {
|
@@ -2299,9 +2307,14 @@ class GUIController extends Controller {
|
|
2299
2307
|
} else if(code === 'ArrowRight') {
|
2300
2308
|
e.preventDefault();
|
2301
2309
|
this.stepForward(e);
|
2310
|
+
} else if(e.ctrlKey && code === 'KeyS') {
|
2311
|
+
// Ctrl-S means: save model. Treat separately because Shift-key
|
2312
|
+
// alters the way in which the model file is saved.
|
2313
|
+
e.preventDefault();
|
2314
|
+
FILE_MANAGER.saveModel(e.shiftKey);
|
2302
2315
|
} else if(alt && code === 'KeyR') {
|
2303
2316
|
// Alt-R means: run to diagnose infeasible/unbounded problem.
|
2304
|
-
|
2317
|
+
VM.solveModel(true);
|
2305
2318
|
} else if(alt && ['KeyC', 'KeyM'].indexOf(code) >= 0) {
|
2306
2319
|
// Special shortcut keys for "clone selection" and "model settings".
|
2307
2320
|
const be = new Event('click');
|
@@ -2763,8 +2776,8 @@ class GUIController extends Controller {
|
|
2763
2776
|
}
|
2764
2777
|
|
2765
2778
|
hideStayOnTopDialogs() {
|
2766
|
-
// Hide and reset all stay-on-top dialogs (even when not showing)
|
2767
|
-
// NOTE:
|
2779
|
+
// Hide and reset all stay-on-top dialogs (even when not showing).
|
2780
|
+
// NOTE: This routine is called when a new model is loaded.
|
2768
2781
|
DATASET_MANAGER.dialog.style.display = 'none';
|
2769
2782
|
this.buttons.dataset.classList.remove('stay-activ');
|
2770
2783
|
DATASET_MANAGER.reset();
|
@@ -2786,6 +2799,9 @@ class GUIController extends Controller {
|
|
2786
2799
|
DOCUMENTATION_MANAGER.dialog.style.display = 'none';
|
2787
2800
|
this.buttons.documentation.classList.remove('stay-activ');
|
2788
2801
|
DOCUMENTATION_MANAGER.reset();
|
2802
|
+
TEX_MANAGER.dialog.style.display = 'none';
|
2803
|
+
this.buttons.tex.classList.remove('stay-activ');
|
2804
|
+
TEX_MANAGER.reset();
|
2789
2805
|
FINDER.dialog.style.display = 'none';
|
2790
2806
|
this.buttons.finder.classList.remove('stay-activ');
|
2791
2807
|
FINDER.reset();
|
@@ -3558,10 +3574,13 @@ console.log('HERE name conflicts', name_conflicts, mapping);
|
|
3558
3574
|
this.setBox('settings-decimal-comma', model.decimal_comma);
|
3559
3575
|
this.setBox('settings-align-to-grid', model.align_to_grid);
|
3560
3576
|
this.setBox('settings-block-arrows', model.show_block_arrows);
|
3561
|
-
this.setBox('settings-diagnose',
|
3577
|
+
this.setBox('settings-diagnose', model.always_diagnose);
|
3578
|
+
this.setBox('settings-power', model.with_power_flow);
|
3562
3579
|
this.setBox('settings-cost-prices', model.infer_cost_prices);
|
3563
3580
|
this.setBox('settings-report-results', model.report_results);
|
3564
3581
|
this.setBox('settings-encrypt', model.encrypt);
|
3582
|
+
const pg_btn = md.element('power-btn');
|
3583
|
+
pg_btn.style.display = (model.with_power_flow ? 'inline-block' : 'none');
|
3565
3584
|
md.show('name');
|
3566
3585
|
}
|
3567
3586
|
|
@@ -3616,9 +3635,9 @@ console.log('HERE name conflicts', name_conflicts, mapping);
|
|
3616
3635
|
model.report_results = UI.boxChecked('settings-report-results');
|
3617
3636
|
model.encrypt = UI.boxChecked('settings-encrypt');
|
3618
3637
|
model.decimal_comma = UI.boxChecked('settings-decimal-comma');
|
3619
|
-
|
3638
|
+
model.always_diagnose = this.boxChecked('settings-diagnose');
|
3620
3639
|
// Notify modeler that diagnosis changes the value of +INF.
|
3621
|
-
if(
|
3640
|
+
if(model.always_diagnose) {
|
3622
3641
|
UI.notify('To diagnose unbounded problems, values beyond 1e+10 ' +
|
3623
3642
|
'are considered as infinite (\u221E)');
|
3624
3643
|
}
|
@@ -3627,6 +3646,9 @@ console.log('HERE name conflicts', name_conflicts, mapping);
|
|
3627
3646
|
redraw = !model.align_to_grid && cb;
|
3628
3647
|
model.align_to_grid = cb;
|
3629
3648
|
model.grid_pixels = Math.floor(px);
|
3649
|
+
cb = UI.boxChecked('settings-power');
|
3650
|
+
redraw = redraw || cb !== model.with_power_flow;
|
3651
|
+
model.with_power_flow = cb;
|
3630
3652
|
cb = UI.boxChecked('settings-cost-prices');
|
3631
3653
|
redraw = redraw || cb !== model.infer_cost_prices;
|
3632
3654
|
model.infer_cost_prices = cb;
|
@@ -3674,6 +3696,20 @@ console.log('HERE name conflicts', name_conflicts, mapping);
|
|
3674
3696
|
if(redraw) this.drawDiagram(model);
|
3675
3697
|
}
|
3676
3698
|
|
3699
|
+
togglePowerGridButton() {
|
3700
|
+
// Responds to clicking the "power grid options" checkbox by toggling
|
3701
|
+
// the "View/edit power grids" button.
|
3702
|
+
const
|
3703
|
+
cb = this.modals.settings.element('power'),
|
3704
|
+
pb = this.modals.settings.element('power-btn');
|
3705
|
+
// NOTE: When clicked, state has not been updated yet.
|
3706
|
+
if(cb.classList.contains('clear')) {
|
3707
|
+
pb.style.display = 'inline-block';
|
3708
|
+
} else {
|
3709
|
+
pb.style.display = 'none';
|
3710
|
+
}
|
3711
|
+
}
|
3712
|
+
|
3677
3713
|
// Solver preferences modal
|
3678
3714
|
|
3679
3715
|
showSolverPreferencesDialog() {
|
@@ -3749,6 +3785,17 @@ console.log('HERE name conflicts', name_conflicts, mapping);
|
|
3749
3785
|
md.group = group;
|
3750
3786
|
md.element('name').value = p.name;
|
3751
3787
|
md.element('actor').value = (p.hasActor ? p.actor.name : '');
|
3788
|
+
md.element('length').value = p.length_in_km;
|
3789
|
+
md.grid_id = (p.grid ? p.grid.id : '');
|
3790
|
+
this.hideGridPlateMenu('process');
|
3791
|
+
this.updateGridFields();
|
3792
|
+
const tex = md.element('tex-id');
|
3793
|
+
tex.value = p.TEX_id;
|
3794
|
+
if(TEX_MANAGER.visible) {
|
3795
|
+
tex.style.display = 'block';
|
3796
|
+
} else {
|
3797
|
+
tex.style.display = 'none';
|
3798
|
+
}
|
3752
3799
|
// Focus on lower bound when showing the dialog for a group.
|
3753
3800
|
if(group.length > 0) {
|
3754
3801
|
attr = 'LB';
|
@@ -3766,6 +3813,61 @@ console.log('HERE name conflicts', name_conflicts, mapping);
|
|
3766
3813
|
md.element(attr + '-x').dispatchEvent(new Event('click'));
|
3767
3814
|
}
|
3768
3815
|
}
|
3816
|
+
|
3817
|
+
showGridPlateMenu(modal) {
|
3818
|
+
const md = this.modals[modal];
|
3819
|
+
POWER_GRID_MANAGER.updateGridMenu(modal);
|
3820
|
+
md.element('grid-plate-menu').style.display = 'block';
|
3821
|
+
}
|
3822
|
+
|
3823
|
+
hideGridPlateMenu(modal) {
|
3824
|
+
const md = this.modals[modal];
|
3825
|
+
md.element('grid-plate-menu').style.display = 'none';
|
3826
|
+
}
|
3827
|
+
|
3828
|
+
setGridPlate(div) {
|
3829
|
+
const
|
3830
|
+
parts = div.id.split('-'),
|
3831
|
+
modal = parts[0],
|
3832
|
+
md = this.modals[modal],
|
3833
|
+
id = parts.pop(),
|
3834
|
+
grid = MODEL.powerGridByID(id);
|
3835
|
+
// NOTE: Store power grid identifier as property of the modal.
|
3836
|
+
md.grid_id = (grid ? id : '');
|
3837
|
+
this.updateGridFields();
|
3838
|
+
}
|
3839
|
+
|
3840
|
+
updateGridFields() {
|
3841
|
+
// Adjust the powergrid-related elements of the dialog according to
|
3842
|
+
// the value of the `grid_id` property of the modal.
|
3843
|
+
const
|
3844
|
+
md = this.modals.process,
|
3845
|
+
plate = md.element('grid-plate'),
|
3846
|
+
overlay = md.element('grid-overlay'),
|
3847
|
+
notab = ['LB', 'IL', 'LCF'],
|
3848
|
+
pg = MODEL.powerGridByID(md.grid_id);
|
3849
|
+
if(pg) {
|
3850
|
+
plate.className = 'grid-kV-plate';
|
3851
|
+
plate.style.backgroundColor = pg.color;
|
3852
|
+
plate.innerHTML = pg.voltage;
|
3853
|
+
overlay.style.display = 'block';
|
3854
|
+
// Disable tab stop for the properties that are now not shown.
|
3855
|
+
for(let i = 0; i < notab.length; i++) {
|
3856
|
+
md.element(notab[i]).tabIndex = -1;
|
3857
|
+
}
|
3858
|
+
} else {
|
3859
|
+
plate.innerHTML = '(↯)';
|
3860
|
+
plate.className = 'no-grid-plate';
|
3861
|
+
overlay.style.display = 'none';
|
3862
|
+
// Enable tab stop for the properties that are now not shown.
|
3863
|
+
for(let i = 0; i < notab.length; i++) {
|
3864
|
+
md.element(notab[i]).tabIndex = 0;
|
3865
|
+
}
|
3866
|
+
}
|
3867
|
+
this.hideGridPlateMenu('process');
|
3868
|
+
// Show plate "button" only when power grids option is set for model.
|
3869
|
+
plate.style.display = (MODEL.with_power_flow ? 'block' : 'none');
|
3870
|
+
}
|
3769
3871
|
|
3770
3872
|
updateProcessProperties() {
|
3771
3873
|
// Validates process properties, and only updates the edited process
|
@@ -3826,6 +3928,10 @@ console.log('HERE name conflicts', name_conflicts, mapping);
|
|
3826
3928
|
p.integer_level = this.boxChecked('process-integer');
|
3827
3929
|
p.level_to_zero = this.boxChecked('process-shut-down');
|
3828
3930
|
p.collapsed = this.boxChecked('process-collapsed');
|
3931
|
+
p.power_grid = MODEL.powerGridByID(md.grid_id);
|
3932
|
+
p.length_in_km = safeStrToFloat(md.element('length').value, 0);
|
3933
|
+
p.TEX_id = md.element('tex-id').value;
|
3934
|
+
if(TEX_MANAGER.visible) TEX_MANAGER.update(p);
|
3829
3935
|
if(md.group.length > 1) {
|
3830
3936
|
// Redraw the entire diagram, as multiple processes may have changed.
|
3831
3937
|
md.updateModifiedProperties(p);
|
@@ -3852,6 +3958,13 @@ console.log('HERE name conflicts', name_conflicts, mapping);
|
|
3852
3958
|
md.element('P-unit').innerHTML =
|
3853
3959
|
(p.scale_unit === '1' ? '' : '/' + p.scale_unit);
|
3854
3960
|
md.element('currency').innerHTML = MODEL.currency_unit;
|
3961
|
+
const tex = md.element('tex-id');
|
3962
|
+
tex.value = p.TEX_id;
|
3963
|
+
if(TEX_MANAGER.visible) {
|
3964
|
+
tex.style.display = 'block';
|
3965
|
+
} else {
|
3966
|
+
tex.style.display = 'none';
|
3967
|
+
}
|
3855
3968
|
// NOTE: IO parameter status is not "group-edited"!
|
3856
3969
|
this.setImportExportBox('product', MODEL.ioType(p));
|
3857
3970
|
// Focus on lower bound when showing the dialog for a group.
|
@@ -3924,9 +4037,11 @@ console.log('HERE name conflicts', name_conflicts, mapping);
|
|
3924
4037
|
p.upper_bound)) return false;
|
3925
4038
|
if(p.name.startsWith('$')) {
|
3926
4039
|
// NOTE: For actor cash flow data products, price and initial
|
3927
|
-
// level must remain blank
|
4040
|
+
// level must remain blank...
|
3928
4041
|
md.element('P').value = '';
|
3929
4042
|
md.element('IL').value = '';
|
4043
|
+
// ... and the unit must be the model's currency unit.
|
4044
|
+
md.element('unit').value = MODEL.currency_unit;
|
3930
4045
|
}
|
3931
4046
|
if(!this.updateExpressionInput('product-IL', 'initial level',
|
3932
4047
|
p.initial_level)) return false;
|
@@ -3943,10 +4058,10 @@ console.log('HERE name conflicts', name_conflicts, mapping);
|
|
3943
4058
|
}
|
3944
4059
|
// At this point, all input has been validated, so entity properties
|
3945
4060
|
// can be modified.
|
4061
|
+
p.changeScaleUnit(md.element('unit').value);
|
3946
4062
|
if(!p.name.startsWith('$')) {
|
3947
4063
|
// NOTE: For actor cash flow data products, these properties must
|
3948
4064
|
// also retain their initial value.
|
3949
|
-
p.changeScaleUnit(md.element('unit').value);
|
3950
4065
|
p.is_source = this.boxChecked('product-source');
|
3951
4066
|
p.is_sink = this.boxChecked('product-sink');
|
3952
4067
|
// NOTE: Do not unset `is_data` if product has ingoing data arrows.
|
@@ -3964,6 +4079,8 @@ console.log('HERE name conflicts', name_conflicts, mapping);
|
|
3964
4079
|
p.no_links = this.boxChecked('product-no-links');
|
3965
4080
|
let must_redraw = (pnl !== p.no_links);
|
3966
4081
|
MODEL.ioUpdate(p, this.getImportExportBox('product'));
|
4082
|
+
p.TEX_id = md.element('tex-id').value;
|
4083
|
+
if(TEX_MANAGER.visible) TEX_MANAGER.update(p);
|
3967
4084
|
// If a group was edited, update all entities in this group.
|
3968
4085
|
if(md.group.length > 0) md.updateModifiedProperties(p);
|
3969
4086
|
if(must_redraw || md.group.length > 1) {
|
@@ -11,7 +11,7 @@ for the Linny-R Dataset Manager dialog.
|
|
11
11
|
*/
|
12
12
|
|
13
13
|
/*
|
14
|
-
Copyright (c) 2017-
|
14
|
+
Copyright (c) 2017-2024 Delft University of Technology
|
15
15
|
|
16
16
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
17
17
|
of this software and associated documentation files (the "Software"), to deal
|
@@ -577,8 +577,8 @@ class GUIDatasetManager extends DatasetManager {
|
|
577
577
|
}
|
578
578
|
|
579
579
|
selectModifier(event, id, x=true) {
|
580
|
-
// Select modifier, or when double-clicked, edit its expression
|
581
|
-
// name of the modifier
|
580
|
+
// Select modifier, or when double-clicked, edit its expression when
|
581
|
+
// x = TRUE, or the name of the modifier when x = FALSE.
|
582
582
|
this.focal_table = this.modifier_table;
|
583
583
|
if(this.selected_dataset) {
|
584
584
|
const m = this.selected_dataset.modifiers[UI.nameToID(id)],
|
@@ -781,13 +781,17 @@ class GUIDatasetManager extends DatasetManager {
|
|
781
781
|
if(this.selected_modifier) ms = this.selected_modifier.selector;
|
782
782
|
md = this.rename_selector_modal;
|
783
783
|
}
|
784
|
+
md.element('type').innerText = 'dataset modifier';
|
784
785
|
md.element('name').value = ms;
|
785
786
|
md.show('name');
|
786
787
|
}
|
787
788
|
|
788
789
|
newModifier() {
|
790
|
+
const md = this.new_selector_modal;
|
791
|
+
// NOTE: Selector modal is also used by constraint editor.
|
792
|
+
if(md.element('type').innerText !== 'dataset modifier') return;
|
789
793
|
const
|
790
|
-
sel =
|
794
|
+
sel = md.element('name').value,
|
791
795
|
m = this.selected_dataset.addModifier(sel);
|
792
796
|
if(m) {
|
793
797
|
this.selected_modifier = m;
|
@@ -795,72 +799,76 @@ class GUIDatasetManager extends DatasetManager {
|
|
795
799
|
// (ignoring those with wildcards)
|
796
800
|
const sl = this.selected_dataset.plainSelectors;
|
797
801
|
if(sl.length > 1) MODEL.expandDimension(sl);
|
798
|
-
|
802
|
+
md.hide();
|
799
803
|
this.updateModifiers();
|
800
804
|
}
|
801
805
|
}
|
802
806
|
|
803
807
|
renameModifier() {
|
804
808
|
if(!this.selected_modifier) return;
|
809
|
+
const md = this.rename_selector_modal;
|
810
|
+
// NOTE: Selector modal is also used by constraint editor.
|
811
|
+
if(md.element('type').innerText !== 'dataset modifier') return;
|
805
812
|
const
|
806
813
|
wild = this.selected_modifier.hasWildcards,
|
807
|
-
sel =
|
808
|
-
// NOTE:
|
814
|
+
sel = md.element('name').value,
|
815
|
+
// NOTE: Normal dataset selector, so remove all invalid characters.
|
809
816
|
clean_sel = sel.replace(/[^a-zA-z0-9\%\+\-\?\*]/g, ''),
|
810
817
|
// Keep track of old name
|
811
818
|
oldm = this.selected_modifier,
|
812
|
-
// NOTE: addModifier returns existing one if selector not changed
|
819
|
+
// NOTE: addModifier returns existing one if selector not changed.
|
813
820
|
m = this.selected_dataset.addModifier(clean_sel);
|
814
821
|
// NULL can result when new name is invalid
|
815
822
|
if(!m) return;
|
816
|
-
// If selected modifier was the dataset default selector, update it
|
823
|
+
// If selected modifier was the dataset default selector, update it.
|
817
824
|
if(oldm.selector === this.selected_dataset.default_selector) {
|
818
825
|
this.selected_dataset.default_selector = m.selector;
|
819
826
|
}
|
820
827
|
MODEL.renameSelectorInExperiments(oldm.selector, clean_sel);
|
821
|
-
// If only case has changed, just update the selector
|
828
|
+
// If only case has changed, just update the selector.
|
822
829
|
if(m === oldm) {
|
823
830
|
m.selector = clean_sel;
|
824
831
|
this.updateDialog();
|
825
|
-
|
832
|
+
md.hide();
|
826
833
|
return;
|
827
834
|
}
|
828
|
-
// Rest is needed only when a new modifier has been added
|
835
|
+
// Rest is needed only when a new modifier has been added.
|
829
836
|
m.expression = oldm.expression;
|
830
837
|
if(wild) {
|
831
|
-
// Wildcard selector means: recompile the modifier expression
|
838
|
+
// Wildcard selector means: recompile the modifier expression.
|
832
839
|
m.expression.attribute = m.selector;
|
833
840
|
m.expression.compile();
|
834
841
|
}
|
835
842
|
this.deleteModifier();
|
836
843
|
this.selected_modifier = m;
|
837
|
-
// Update all chartvariables referencing this dataset + old selector
|
844
|
+
// Update all chartvariables referencing this dataset + old selector.
|
838
845
|
const vl = MODEL.datasetVariables;
|
839
846
|
let cv_cnt = 0;
|
840
847
|
for(let i = 0; i < vl.length; i++) {
|
848
|
+
const v = vl[i];
|
841
849
|
if(v.object === this.selected_dataset && v.attribute === oldm.selector) {
|
842
850
|
v.attribute = m.selector;
|
843
851
|
cv_cnt++;
|
844
852
|
}
|
845
853
|
}
|
846
|
-
// Also replace old selector in all expressions (count these as well)
|
854
|
+
// Also replace old selector in all expressions (count these as well).
|
847
855
|
const xr_cnt = MODEL.replaceAttributeInExpressions(
|
848
856
|
oldm.dataset.name + '|' + oldm.selector, m.selector);
|
849
|
-
// Notify modeler of changes (if any)
|
857
|
+
// Notify modeler of changes (if any).
|
850
858
|
const msg = [];
|
851
859
|
if(cv_cnt) msg.push(pluralS(cv_cnt, ' chart variable'));
|
852
860
|
if(xr_cnt) msg.push(pluralS(xr_cnt, ' expression variable'));
|
853
861
|
if(msg.length) {
|
854
862
|
UI.notify('Updated ' + msg.join(' and '));
|
855
863
|
// Also update these stay-on-top dialogs, as they may display a
|
856
|
-
// variable name for this dataset + modifier
|
864
|
+
// variable name for this dataset + modifier.
|
857
865
|
UI.updateControllerDialogs('CDEFJX');
|
858
866
|
}
|
859
|
-
// NOTE:
|
860
|
-
// (ignoring those with wildcards)
|
867
|
+
// NOTE: Update dimensions only if dataset now has 2 or more modifiers
|
868
|
+
// (ignoring those with wildcards).
|
861
869
|
const sl = this.selected_dataset.plainSelectors;
|
862
870
|
if(sl.length > 1) MODEL.expandDimension(sl);
|
863
|
-
|
871
|
+
md.hide();
|
864
872
|
this.updateModifiers();
|
865
873
|
}
|
866
874
|
|
@@ -12,7 +12,7 @@ viewing and editing documentation text for model entities.
|
|
12
12
|
*/
|
13
13
|
|
14
14
|
/*
|
15
|
-
Copyright (c) 2017-
|
15
|
+
Copyright (c) 2017-2024 Delft University of Technology
|
16
16
|
|
17
17
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
18
18
|
of this software and associated documentation files (the "Software"), to deal
|
@@ -336,8 +336,14 @@ class DocumentationManager {
|
|
336
336
|
this.markup = (e.comments ? e.comments : '');
|
337
337
|
this.editor.value = this.markup;
|
338
338
|
this.viewer.innerHTML = this.markdown;
|
339
|
-
|
340
|
-
|
339
|
+
if(e.grid && MODEL.solved && MODEL.with_power_flow) {
|
340
|
+
// Show cycle flows instead of comments.
|
341
|
+
const cf = POWER_GRID_MANAGER.allCycleFlows(e);
|
342
|
+
if(cf) this.viewer.innerHTML = cf;
|
343
|
+
} else {
|
344
|
+
this.edit_btn.classList.remove('disab');
|
345
|
+
this.edit_btn.classList.add('enab');
|
346
|
+
}
|
341
347
|
// NOTE: Permit documentation of the model by raising the dialog.
|
342
348
|
if(this.entity === MODEL) this.dialog.style.zIndex = 101;
|
343
349
|
} else if(e instanceof DatasetModifier) {
|
@@ -354,6 +360,8 @@ class DocumentationManager {
|
|
354
360
|
}
|
355
361
|
}
|
356
362
|
}
|
363
|
+
// When TEX renderer is visible, also update it.
|
364
|
+
if(TEX_MANAGER.visible) TEX_MANAGER.update(e, shift);
|
357
365
|
}
|
358
366
|
|
359
367
|
rewrite(str) {
|
@@ -11,7 +11,7 @@ for the Linny-R Equation Manager dialog.
|
|
11
11
|
*/
|
12
12
|
|
13
13
|
/*
|
14
|
-
Copyright (c) 2017-
|
14
|
+
Copyright (c) 2017-2024 Delft University of Technology
|
15
15
|
|
16
16
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
17
17
|
of this software and associated documentation files (the "Software"), to deal
|
@@ -11,7 +11,7 @@ for the Linny-R Experiment Manager dialog.
|
|
11
11
|
*/
|
12
12
|
|
13
13
|
/*
|
14
|
-
Copyright (c) 2017-
|
14
|
+
Copyright (c) 2017-2024 Delft University of Technology
|
15
15
|
|
16
16
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
17
17
|
of this software and associated documentation files (the "Software"), to deal
|
@@ -11,7 +11,7 @@ functionality for the Linny-R Expression Editor dialog.
|
|
11
11
|
*/
|
12
12
|
|
13
13
|
/*
|
14
|
-
Copyright (c) 2017-
|
14
|
+
Copyright (c) 2017-2024 Delft University of Technology
|
15
15
|
|
16
16
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
17
17
|
of this software and associated documentation files (the "Software"), to deal
|
@@ -263,6 +263,9 @@ NOTE: Grouping groups results in a single group, e.g., (1;2);(3;4;5) evaluates a
|
|
263
263
|
} else if(!this.edited_input_id && EQUATION_MANAGER.edited_expression) {
|
264
264
|
own = MODEL.equations_dataset;
|
265
265
|
sel = EQUATION_MANAGER.selected_modifier.selector;
|
266
|
+
} else if(!this.edited_input_id && CONSTRAINT_EDITOR.edited_expression) {
|
267
|
+
own = CONSTRAINT_EDITOR.selected;
|
268
|
+
sel = CONSTRAINT_EDITOR.selected_selector;
|
266
269
|
} else {
|
267
270
|
own = UI.edited_object;
|
268
271
|
sel = this.edited_input_id.split('-').pop();
|
@@ -292,6 +295,9 @@ NOTE: Grouping groups results in a single group, e.g., (1;2);(3;4;5) evaluates a
|
|
292
295
|
DATASET_MANAGER.modifyExpression(xp.expr);
|
293
296
|
} else if(EQUATION_MANAGER.edited_expression) {
|
294
297
|
EQUATION_MANAGER.modifyEquation(xp.expr);
|
298
|
+
} else if(CONSTRAINT_EDITOR.edited_expression) {
|
299
|
+
// NOTE: Boundline selector expressions may result in a grouping.
|
300
|
+
CONSTRAINT_EDITOR.modifyExpression(xp.expr, xp.concatenating);
|
295
301
|
}
|
296
302
|
UI.modals.expression.hide();
|
297
303
|
return true;
|