linny-r 1.9.2 → 2.0.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.
Files changed (37) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +4 -4
  3. package/package.json +1 -1
  4. package/server.js +1 -1
  5. package/static/images/eq-negated.png +0 -0
  6. package/static/images/power.png +0 -0
  7. package/static/images/tex.png +0 -0
  8. package/static/index.html +225 -10
  9. package/static/linny-r.css +458 -8
  10. package/static/scripts/linny-r-ctrl.js +6 -4
  11. package/static/scripts/linny-r-gui-actor-manager.js +1 -1
  12. package/static/scripts/linny-r-gui-chart-manager.js +20 -13
  13. package/static/scripts/linny-r-gui-constraint-editor.js +410 -50
  14. package/static/scripts/linny-r-gui-controller.js +127 -12
  15. package/static/scripts/linny-r-gui-dataset-manager.js +28 -20
  16. package/static/scripts/linny-r-gui-documentation-manager.js +11 -3
  17. package/static/scripts/linny-r-gui-equation-manager.js +1 -1
  18. package/static/scripts/linny-r-gui-experiment-manager.js +1 -1
  19. package/static/scripts/linny-r-gui-expression-editor.js +7 -1
  20. package/static/scripts/linny-r-gui-file-manager.js +31 -13
  21. package/static/scripts/linny-r-gui-finder.js +1 -1
  22. package/static/scripts/linny-r-gui-model-autosaver.js +1 -1
  23. package/static/scripts/linny-r-gui-monitor.js +1 -1
  24. package/static/scripts/linny-r-gui-paper.js +108 -25
  25. package/static/scripts/linny-r-gui-power-grid-manager.js +529 -0
  26. package/static/scripts/linny-r-gui-receiver.js +1 -1
  27. package/static/scripts/linny-r-gui-repository-browser.js +1 -1
  28. package/static/scripts/linny-r-gui-scale-unit-manager.js +1 -1
  29. package/static/scripts/linny-r-gui-sensitivity-analysis.js +1 -1
  30. package/static/scripts/linny-r-gui-tex-manager.js +110 -0
  31. package/static/scripts/linny-r-gui-undo-redo.js +1 -1
  32. package/static/scripts/linny-r-milp.js +1 -1
  33. package/static/scripts/linny-r-model.js +1016 -155
  34. package/static/scripts/linny-r-utils.js +3 -3
  35. package/static/scripts/linny-r-vm.js +714 -248
  36. package/static/show-diff.html +1 -1
  37. package/static/show-png.html +1 -1
@@ -13,7 +13,7 @@ handler functions.
13
13
  */
14
14
 
15
15
  /*
16
- Copyright (c) 2017-2023 Delft University of Technology
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'];
@@ -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',
@@ -2242,8 +2252,9 @@ class GUIController extends Controller {
2242
2252
  while(i < inp.length && inp[i].disabled) i++;
2243
2253
  if(i < inp.length) {
2244
2254
  inp[i].focus();
2245
- } else if('constraint-modal xp-clusters-modal'.indexOf(topmod.id) >= 0) {
2246
- // NOTE: Constraint modal and "ignore clusters" modal must NOT close
2255
+ } else if(['constraint-modal', 'boundline-data-modal',
2256
+ 'xp-clusters-modal'].indexOf(topmod.id) >= 0) {
2257
+ // NOTE: Constraint modal, boundline data modal and "ignore clusters" modal must NOT close
2247
2258
  // when Enter is pressed, but only de-focus the input field.
2248
2259
  e.target.blur();
2249
2260
  } else {
@@ -2763,8 +2774,8 @@ class GUIController extends Controller {
2763
2774
  }
2764
2775
 
2765
2776
  hideStayOnTopDialogs() {
2766
- // Hide and reset all stay-on-top dialogs (even when not showing)
2767
- // NOTE: this routine is called when a new model is loaded
2777
+ // Hide and reset all stay-on-top dialogs (even when not showing).
2778
+ // NOTE: This routine is called when a new model is loaded.
2768
2779
  DATASET_MANAGER.dialog.style.display = 'none';
2769
2780
  this.buttons.dataset.classList.remove('stay-activ');
2770
2781
  DATASET_MANAGER.reset();
@@ -2786,6 +2797,9 @@ class GUIController extends Controller {
2786
2797
  DOCUMENTATION_MANAGER.dialog.style.display = 'none';
2787
2798
  this.buttons.documentation.classList.remove('stay-activ');
2788
2799
  DOCUMENTATION_MANAGER.reset();
2800
+ TEX_MANAGER.dialog.style.display = 'none';
2801
+ this.buttons.tex.classList.remove('stay-activ');
2802
+ TEX_MANAGER.reset();
2789
2803
  FINDER.dialog.style.display = 'none';
2790
2804
  this.buttons.finder.classList.remove('stay-activ');
2791
2805
  FINDER.reset();
@@ -3558,10 +3572,13 @@ console.log('HERE name conflicts', name_conflicts, mapping);
3558
3572
  this.setBox('settings-decimal-comma', model.decimal_comma);
3559
3573
  this.setBox('settings-align-to-grid', model.align_to_grid);
3560
3574
  this.setBox('settings-block-arrows', model.show_block_arrows);
3561
- this.setBox('settings-diagnose', MODEL.always_diagnose);
3575
+ this.setBox('settings-diagnose', model.always_diagnose);
3576
+ this.setBox('settings-power', model.with_power_flow);
3562
3577
  this.setBox('settings-cost-prices', model.infer_cost_prices);
3563
3578
  this.setBox('settings-report-results', model.report_results);
3564
3579
  this.setBox('settings-encrypt', model.encrypt);
3580
+ const pg_btn = md.element('power-btn');
3581
+ pg_btn.style.display = (model.with_power_flow ? 'inline-block' : 'none');
3565
3582
  md.show('name');
3566
3583
  }
3567
3584
 
@@ -3616,9 +3633,9 @@ console.log('HERE name conflicts', name_conflicts, mapping);
3616
3633
  model.report_results = UI.boxChecked('settings-report-results');
3617
3634
  model.encrypt = UI.boxChecked('settings-encrypt');
3618
3635
  model.decimal_comma = UI.boxChecked('settings-decimal-comma');
3619
- MODEL.always_diagnose = this.boxChecked('settings-diagnose');
3636
+ model.always_diagnose = this.boxChecked('settings-diagnose');
3620
3637
  // Notify modeler that diagnosis changes the value of +INF.
3621
- if(MODEL.always_diagnose) {
3638
+ if(model.always_diagnose) {
3622
3639
  UI.notify('To diagnose unbounded problems, values beyond 1e+10 ' +
3623
3640
  'are considered as infinite (\u221E)');
3624
3641
  }
@@ -3627,6 +3644,9 @@ console.log('HERE name conflicts', name_conflicts, mapping);
3627
3644
  redraw = !model.align_to_grid && cb;
3628
3645
  model.align_to_grid = cb;
3629
3646
  model.grid_pixels = Math.floor(px);
3647
+ cb = UI.boxChecked('settings-power');
3648
+ redraw = redraw || cb !== model.with_power_flow;
3649
+ model.with_power_flow = cb;
3630
3650
  cb = UI.boxChecked('settings-cost-prices');
3631
3651
  redraw = redraw || cb !== model.infer_cost_prices;
3632
3652
  model.infer_cost_prices = cb;
@@ -3674,6 +3694,20 @@ console.log('HERE name conflicts', name_conflicts, mapping);
3674
3694
  if(redraw) this.drawDiagram(model);
3675
3695
  }
3676
3696
 
3697
+ togglePowerGridButton() {
3698
+ // Responds to clicking the "power grid options" checkbox by toggling
3699
+ // the "View/edit power grids" button.
3700
+ const
3701
+ cb = this.modals.settings.element('power'),
3702
+ pb = this.modals.settings.element('power-btn');
3703
+ // NOTE: When clicked, state has not been updated yet.
3704
+ if(cb.classList.contains('clear')) {
3705
+ pb.style.display = 'inline-block';
3706
+ } else {
3707
+ pb.style.display = 'none';
3708
+ }
3709
+ }
3710
+
3677
3711
  // Solver preferences modal
3678
3712
 
3679
3713
  showSolverPreferencesDialog() {
@@ -3749,6 +3783,17 @@ console.log('HERE name conflicts', name_conflicts, mapping);
3749
3783
  md.group = group;
3750
3784
  md.element('name').value = p.name;
3751
3785
  md.element('actor').value = (p.hasActor ? p.actor.name : '');
3786
+ md.element('length').value = p.length_in_km;
3787
+ md.grid_id = (p.grid ? p.grid.id : '');
3788
+ this.hideGridPlateMenu('process');
3789
+ this.updateGridFields();
3790
+ const tex = md.element('tex-id');
3791
+ tex.value = p.TEX_id;
3792
+ if(TEX_MANAGER.visible) {
3793
+ tex.style.display = 'block';
3794
+ } else {
3795
+ tex.style.display = 'none';
3796
+ }
3752
3797
  // Focus on lower bound when showing the dialog for a group.
3753
3798
  if(group.length > 0) {
3754
3799
  attr = 'LB';
@@ -3766,6 +3811,61 @@ console.log('HERE name conflicts', name_conflicts, mapping);
3766
3811
  md.element(attr + '-x').dispatchEvent(new Event('click'));
3767
3812
  }
3768
3813
  }
3814
+
3815
+ showGridPlateMenu(modal) {
3816
+ const md = this.modals[modal];
3817
+ POWER_GRID_MANAGER.updateGridMenu(modal);
3818
+ md.element('grid-plate-menu').style.display = 'block';
3819
+ }
3820
+
3821
+ hideGridPlateMenu(modal) {
3822
+ const md = this.modals[modal];
3823
+ md.element('grid-plate-menu').style.display = 'none';
3824
+ }
3825
+
3826
+ setGridPlate(div) {
3827
+ const
3828
+ parts = div.id.split('-'),
3829
+ modal = parts[0],
3830
+ md = this.modals[modal],
3831
+ id = parts.pop(),
3832
+ grid = MODEL.powerGridByID(id);
3833
+ // NOTE: Store power grid identifier as property of the modal.
3834
+ md.grid_id = (grid ? id : '');
3835
+ this.updateGridFields();
3836
+ }
3837
+
3838
+ updateGridFields() {
3839
+ // Adjust the powergrid-related elements of the dialog according to
3840
+ // the value of the `grid_id` property of the modal.
3841
+ const
3842
+ md = this.modals.process,
3843
+ plate = md.element('grid-plate'),
3844
+ overlay = md.element('grid-overlay'),
3845
+ notab = ['LB', 'IL', 'LCF'],
3846
+ pg = MODEL.powerGridByID(md.grid_id);
3847
+ if(pg) {
3848
+ plate.className = 'grid-kV-plate';
3849
+ plate.style.backgroundColor = pg.color;
3850
+ plate.innerHTML = pg.voltage;
3851
+ overlay.style.display = 'block';
3852
+ // Disable tab stop for the properties that are now not shown.
3853
+ for(let i = 0; i < notab.length; i++) {
3854
+ md.element(notab[i]).tabIndex = -1;
3855
+ }
3856
+ } else {
3857
+ plate.innerHTML = '(&#x21AF;)';
3858
+ plate.className = 'no-grid-plate';
3859
+ overlay.style.display = 'none';
3860
+ // Enable tab stop for the properties that are now not shown.
3861
+ for(let i = 0; i < notab.length; i++) {
3862
+ md.element(notab[i]).tabIndex = 0;
3863
+ }
3864
+ }
3865
+ this.hideGridPlateMenu('process');
3866
+ // Show plate "button" only when power grids option is set for model.
3867
+ plate.style.display = (MODEL.with_power_flow ? 'block' : 'none');
3868
+ }
3769
3869
 
3770
3870
  updateProcessProperties() {
3771
3871
  // Validates process properties, and only updates the edited process
@@ -3826,6 +3926,10 @@ console.log('HERE name conflicts', name_conflicts, mapping);
3826
3926
  p.integer_level = this.boxChecked('process-integer');
3827
3927
  p.level_to_zero = this.boxChecked('process-shut-down');
3828
3928
  p.collapsed = this.boxChecked('process-collapsed');
3929
+ p.power_grid = MODEL.powerGridByID(md.grid_id);
3930
+ p.length_in_km = safeStrToFloat(md.element('length').value, 0);
3931
+ p.TEX_id = md.element('tex-id').value;
3932
+ if(TEX_MANAGER.visible) TEX_MANAGER.update(p);
3829
3933
  if(md.group.length > 1) {
3830
3934
  // Redraw the entire diagram, as multiple processes may have changed.
3831
3935
  md.updateModifiedProperties(p);
@@ -3852,6 +3956,13 @@ console.log('HERE name conflicts', name_conflicts, mapping);
3852
3956
  md.element('P-unit').innerHTML =
3853
3957
  (p.scale_unit === '1' ? '' : '/' + p.scale_unit);
3854
3958
  md.element('currency').innerHTML = MODEL.currency_unit;
3959
+ const tex = md.element('tex-id');
3960
+ tex.value = p.TEX_id;
3961
+ if(TEX_MANAGER.visible) {
3962
+ tex.style.display = 'block';
3963
+ } else {
3964
+ tex.style.display = 'none';
3965
+ }
3855
3966
  // NOTE: IO parameter status is not "group-edited"!
3856
3967
  this.setImportExportBox('product', MODEL.ioType(p));
3857
3968
  // Focus on lower bound when showing the dialog for a group.
@@ -3924,9 +4035,11 @@ console.log('HERE name conflicts', name_conflicts, mapping);
3924
4035
  p.upper_bound)) return false;
3925
4036
  if(p.name.startsWith('$')) {
3926
4037
  // NOTE: For actor cash flow data products, price and initial
3927
- // level must remain blank.
4038
+ // level must remain blank...
3928
4039
  md.element('P').value = '';
3929
4040
  md.element('IL').value = '';
4041
+ // ... and the unit must be the model's currency unit.
4042
+ md.element('unit').value = MODEL.currency_unit;
3930
4043
  }
3931
4044
  if(!this.updateExpressionInput('product-IL', 'initial level',
3932
4045
  p.initial_level)) return false;
@@ -3943,10 +4056,10 @@ console.log('HERE name conflicts', name_conflicts, mapping);
3943
4056
  }
3944
4057
  // At this point, all input has been validated, so entity properties
3945
4058
  // can be modified.
4059
+ p.changeScaleUnit(md.element('unit').value);
3946
4060
  if(!p.name.startsWith('$')) {
3947
4061
  // NOTE: For actor cash flow data products, these properties must
3948
4062
  // also retain their initial value.
3949
- p.changeScaleUnit(md.element('unit').value);
3950
4063
  p.is_source = this.boxChecked('product-source');
3951
4064
  p.is_sink = this.boxChecked('product-sink');
3952
4065
  // NOTE: Do not unset `is_data` if product has ingoing data arrows.
@@ -3964,6 +4077,8 @@ console.log('HERE name conflicts', name_conflicts, mapping);
3964
4077
  p.no_links = this.boxChecked('product-no-links');
3965
4078
  let must_redraw = (pnl !== p.no_links);
3966
4079
  MODEL.ioUpdate(p, this.getImportExportBox('product'));
4080
+ p.TEX_id = md.element('tex-id').value;
4081
+ if(TEX_MANAGER.visible) TEX_MANAGER.update(p);
3967
4082
  // If a group was edited, update all entities in this group.
3968
4083
  if(md.group.length > 0) md.updateModifiedProperties(p);
3969
4084
  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-2023 Delft University of Technology
14
+ Copyright (c) 2017-2024 Delft University of Technology
15
15
 
16
16
  Permission is hereby granted, free of charge, to any person obtaining a copy
17
17
  of this software and associated documentation files (the "Software"), to deal
@@ -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 or the
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 = this.new_selector_modal.element('name').value,
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
- this.new_selector_modal.hide();
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 = this.rename_selector_modal.element('name').value,
808
- // NOTE: normal dataset selector, so remove all invalid characters
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
- this.rename_selector_modal.hide();
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: update dimensions only if dataset now has 2 or more modifiers
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
- this.rename_selector_modal.hide();
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-2023 Delft University of Technology
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
- this.edit_btn.classList.remove('disab');
340
- this.edit_btn.classList.add('enab');
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-2023 Delft University of Technology
14
+ Copyright (c) 2017-2024 Delft University of Technology
15
15
 
16
16
  Permission is hereby granted, free of charge, to any person obtaining a copy
17
17
  of this software and associated documentation files (the "Software"), to deal
@@ -11,7 +11,7 @@ for the Linny-R Experiment Manager dialog.
11
11
  */
12
12
 
13
13
  /*
14
- Copyright (c) 2017-2023 Delft University of Technology
14
+ Copyright (c) 2017-2024 Delft University of Technology
15
15
 
16
16
  Permission is hereby granted, free of charge, to any person obtaining a copy
17
17
  of this software and associated documentation files (the "Software"), to deal
@@ -11,7 +11,7 @@ functionality for the Linny-R Expression Editor dialog.
11
11
  */
12
12
 
13
13
  /*
14
- Copyright (c) 2017-2023 Delft University of Technology
14
+ Copyright (c) 2017-2024 Delft University of Technology
15
15
 
16
16
  Permission is hereby granted, free of charge, to any person obtaining a copy
17
17
  of this software and associated documentation files (the "Software"), to deal
@@ -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;
@@ -11,7 +11,7 @@ functionality for the Linny-R File Manager.
11
11
  */
12
12
 
13
13
  /*
14
- Copyright (c) 2017-2023 Delft University of Technology
14
+ Copyright (c) 2017-2024 Delft University of Technology
15
15
 
16
16
  Permission is hereby granted, free of charge, to any person obtaining a copy
17
17
  of this software and associated documentation files (the "Software"), to deal
@@ -49,6 +49,14 @@ class GUIFileManager {
49
49
  getRemoteData(dataset, url) {
50
50
  // Gets data from a URL, or from a file on the local host
51
51
  if(url === '') return;
52
+ if(url.indexOf('%') >= 0) {
53
+ // Expand %i, %j and %k if used in the URL.
54
+ const letters = ['i', 'j', 'k'];
55
+ for(let i = 0; i < letters.length; i++) {
56
+ const l = letters[i];
57
+ url = url.replaceAll('%' + l, valueOfIndexVariable(l));
58
+ }
59
+ }
52
60
  // NOTE: add this dataset to the "loading" list...
53
61
  addDistinct(dataset, MODEL.loading_datasets);
54
62
  // ... and allow for 3 more seconds (6 times 500 ms) to complete
@@ -63,19 +71,29 @@ class GUIFileManager {
63
71
  })
64
72
  .then((data) => {
65
73
  if(data !== '' && UI.postResponseOK(data)) {
66
- // Server must return either semicolon-separated or
67
- // newline-separated string of numbers
68
- if(data.indexOf(';') < 0) {
69
- // If no semicolon found, replace newlines by semicolons
70
- data = data.trim().split('\n').join(';');
71
- }
72
- // Remove all white space
73
- data = data.replace(/\s+/g, '');
74
- // Show data in text area when the SERIES dialog is visible
75
- if(!UI.hidden('series-modal')) {
76
- DATASET_MANAGER.series_data.value = data.split(';').join('\n');
74
+ if(dataset instanceof BoundLine) {
75
+ // Server must return semicolon-separated list of white-
76
+ // space-separated list of numbers.
77
+ dataset.unpackPointDataString(data);
78
+ // Show data in boundline data modal when it is visible.
79
+ if(!UI.hidden('boundline-data-modal')) {
80
+ CONSTRAINT_EDITOR.stopEditing(false);
81
+ }
77
82
  } else {
78
- dataset.unpackDataString(data);
83
+ // Server must return either semicolon-separated or
84
+ // newline-separated string of numbers
85
+ if(data.indexOf(';') < 0) {
86
+ // If no semicolon found, replace newlines by semicolons
87
+ data = data.trim().split('\n').join(';');
88
+ }
89
+ // Remove all white space
90
+ data = data.replace(/\s+/g, '');
91
+ // Show data in text area when the SERIES dialog is visible
92
+ if(!UI.hidden('series-modal')) {
93
+ DATASET_MANAGER.series_data.value = data.split(';').join('\n');
94
+ } else {
95
+ dataset.unpackDataString(data);
96
+ }
79
97
  }
80
98
  // NOTE: remove dataset from the "loading" list
81
99
  const i = MODEL.loading_datasets.indexOf(dataset);
@@ -13,7 +13,7 @@ model.
13
13
  */
14
14
 
15
15
  /*
16
- Copyright (c) 2017-2023 Delft University of Technology
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
@@ -12,7 +12,7 @@ dialogs, the main drawing canvas, and event handler functions.
12
12
  */
13
13
 
14
14
  /*
15
- Copyright (c) 2017-2023 Delft University of Technology
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
@@ -11,7 +11,7 @@ for the Linny-R Monitor dialog.
11
11
  */
12
12
 
13
13
  /*
14
- Copyright (c) 2017-2023 Delft University of Technology
14
+ Copyright (c) 2017-2024 Delft University of Technology
15
15
 
16
16
  Permission is hereby granted, free of charge, to any person obtaining a copy
17
17
  of this software and associated documentation files (the "Software"), to deal