linny-r 2.0.9 → 2.0.11

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.
@@ -256,8 +256,8 @@ class Finder {
256
256
  }
257
257
  }
258
258
  // Also allow search for scale unit names.
259
- if(et.indexOf('U') >= 0) {
260
- imgs += '<img src="images/scale.png">';
259
+ if(et === 'U') {
260
+ imgs = '<img src="images/scale.png">';
261
261
  for(let k in MODEL.products) if(MODEL.products.hasOwnProperty(k)) {
262
262
  if(fp && !k.startsWith(UI.BLACK_BOX) && patternMatch(
263
263
  MODEL.products[k].scale_unit, this.filter_pattern)) {
@@ -266,6 +266,37 @@ class Finder {
266
266
  addDistinct('Q', this.filtered_types);
267
267
  }
268
268
  }
269
+ for(let k in MODEL.datasets) if(MODEL.datasets.hasOwnProperty(k)) {
270
+ if(fp && !k.startsWith(UI.BLACK_BOX)) {
271
+ const ds = MODEL.datasets[k];
272
+ if(ds !== MODEL.equations_dataset && patternMatch(
273
+ ds.scale_unit, this.filter_pattern)) {
274
+ enl.push(k);
275
+ this.entities.push(MODEL.datasets[k]);
276
+ addDistinct('D', this.filtered_types);
277
+ }
278
+ }
279
+ }
280
+ }
281
+ // Also allow search for dataset modifier selectors.
282
+ if(et.indexOf('S') >= 0) {
283
+ imgs = '<img src="images/dataset.png">';
284
+ for(let k in MODEL.datasets) if(MODEL.datasets.hasOwnProperty(k)) {
285
+ if(fp && !k.startsWith(UI.BLACK_BOX)) {
286
+ const ds = MODEL.datasets[k];
287
+ if(ds !== MODEL.equations_dataset) {
288
+ for(let mk in ds.modifiers) if(ds.modifiers.hasOwnProperty(mk)) {
289
+ if(patternMatch(
290
+ ds.modifiers[mk].selector, this.filter_pattern)) {
291
+ enl.push(k);
292
+ this.entities.push(MODEL.datasets[k]);
293
+ addDistinct('D', this.filtered_types);
294
+ break;
295
+ }
296
+ }
297
+ }
298
+ }
299
+ }
269
300
  }
270
301
  // Also allow search for link multiplier symbols.
271
302
  if(et.indexOf('M') >= 0) {
@@ -341,11 +372,11 @@ class Finder {
341
372
 
342
373
  get entityGroup() {
343
374
  // Returns the list of filtered entities if all are of the same type,
344
- // while excluding (no actor), (top cluster), datasets and equations.
375
+ // while excluding (no actor), (top cluster), and equations.
345
376
  const
346
377
  eg = [],
347
378
  ft = this.filtered_types[0];
348
- if(this.filtered_types.length === 1 && 'DE'.indexOf(ft) < 0) {
379
+ if(this.filtered_types.length === 1 && ft !== 'E') {
349
380
  for(const e of this.entities) {
350
381
  // Exclude "no actor" and top cluster.
351
382
  if(e.name && e.name !== '(no_actor)' && e.name !== '(top_cluster)' &&
@@ -601,7 +632,7 @@ class Finder {
601
632
  // look only for the entity types denoted by these letters.
602
633
  let ft = this.filter_input.value,
603
634
  et = VM.entity_letters;
604
- if(/^(\*|U|M|[ABCDELPQ]+)\?/i.test(ft)) {
635
+ if(/^(\*|U|M|S|[ABCDELPQ]+)\?/i.test(ft)) {
605
636
  ft = ft.split('?');
606
637
  // NOTE: *? denotes "all entity types except constraints".
607
638
  et = (ft[0] === '*' ? 'ACDELPQ' : ft[0].toUpperCase());
@@ -644,6 +675,7 @@ class Finder {
644
675
  if(UI.hidden('dataset-dlg')) {
645
676
  UI.buttons.dataset.dispatchEvent(new Event('click'));
646
677
  }
678
+ DATASET_MANAGER.expandToShow(obj.name);
647
679
  DATASET_MANAGER.selected_dataset = obj;
648
680
  DATASET_MANAGER.updateDialog();
649
681
  } else if(obj instanceof DatasetModifier) {
@@ -777,9 +809,162 @@ class Finder {
777
809
  UI.showLinkPropertiesDialog(e, 'R', false, group);
778
810
  } else if(e instanceof Cluster) {
779
811
  UI.showClusterPropertiesDialog(e, group);
812
+ } else if(e instanceof Dataset) {
813
+ this.showDatasetGroupDialog(e, group);
780
814
  }
781
815
  }
782
816
 
817
+ showDatasetGroupDialog(ds, dsl) {
818
+ // Initialize fields with properties of first element of `dsl`.
819
+ if(!dsl.length) return;
820
+ const md = UI.modals.datasetgroup;
821
+ md.group = dsl;
822
+ md.selected_ds = ds;
823
+ md.element('no-time-msg').style.display = (ds.array ? 'block' : 'none');
824
+ md.show('prefix', ds);
825
+ }
826
+
827
+ updateDatasetGroupProperties() {
828
+ // Update properties of selected group of datasets.
829
+ const md = UI.modals.datasetgroup;
830
+ if(!md.group.length) return;
831
+ // Reduce multiple spaces to a single space.
832
+ let prefix = md.element('prefix').value.replaceAll(/\s+/gi, ' ').trim();
833
+ // Trim trailing colons (also when they have spaces between them).
834
+ while(prefix.endsWith(':')) prefix = prefix.slice(0, -1).trim();
835
+ // Count the updated chart variables and expressions.
836
+ let cv_cnt = 0,
837
+ xr_cnt = 0;
838
+ // Only rename datasets if prefix has been changed.
839
+ if(prefix !== md.shared_prefix) {
840
+ // Check whether prefix is valid.
841
+ if(prefix && !UI.validName(prefix)) {
842
+ UI.warn(`Invalid prefix "${prefix}"`);
843
+ return;
844
+ }
845
+ // Add the prefixer ": " to make it a true prefix.
846
+ if(prefix) prefix += UI.PREFIXER;
847
+ let old_prefix = md.shared_prefix;
848
+ if(old_prefix) old_prefix += UI.PREFIXER;
849
+ // Check whether prefix will create name conflicts.
850
+ let nc = 0;
851
+ for(const ds of md.group) {
852
+ let nn = ds.name;
853
+ if(nn.startsWith(old_prefix)) {
854
+ nn = nn.replace(old_prefix, prefix);
855
+ const obj = MODEL.objectByName(nn);
856
+ if(obj && obj !== ds) {
857
+ console.log('Anticipated name conflict with', obj.type,
858
+ obj.displayName);
859
+ nc++;
860
+ }
861
+ }
862
+ }
863
+ if(nc > 0) {
864
+ UI.warn(`Prefix "${prefix}" will result in` +
865
+ pluralS(nc, 'name conflict'));
866
+ return;
867
+ }
868
+ // Rename the datasets -- this may affect the group.
869
+ MODEL.renamePrefixedDatasets(old_prefix, prefix, md.group);
870
+ cv_cnt += MODEL.variable_count;
871
+ xr_cnt += MODEL.expression_count;
872
+ }
873
+ // Validate input field values.
874
+ const dv = UI.validNumericInput('datasetgroup-default', 'default value');
875
+ if(dv === false) return;
876
+ const ts = UI.validNumericInput('datasetgroup-time-scale', 'time step');
877
+ if(ts === false) return;
878
+ // No issues => update *only the modified* properties of all datasets in
879
+ // the group.
880
+ const data = {
881
+ 'default': dv,
882
+ 'unit': md.element('unit').value.trim(),
883
+ 'periodic': UI.boxChecked('datasetgroup-periodic'),
884
+ 'array': UI.boxChecked('datasetgroup-array'),
885
+ 'time-scale': ts,
886
+ 'time-unit': md.element('time-unit').value,
887
+ 'method': md.element('method').value
888
+ };
889
+ for(let name in md.fields) if(md.changed[name]) {
890
+ const
891
+ prop = md.fields[name],
892
+ value = data[name];
893
+ for(const ds of md.group) ds[prop] = value;
894
+ }
895
+ // Also update the dataset modifiers.
896
+ const dsv_list = MODEL.datasetVariables;
897
+ for(const ds of md.group) {
898
+ for(const k of Object.keys(md.selectors)) {
899
+ const sel = md.selectors[k];
900
+ if(ds.modifiers.hasOwnProperty(k)) {
901
+ // If dataset `ds` has selector with key `k`,
902
+ // first check if it has been deleted.
903
+ if(sel.deleted) {
904
+ // If so, delete this modifier it from `ds`.
905
+ if(k === ds.default_selector) ds.default_selector = '';
906
+ delete ds.modifiers[k];
907
+ } else {
908
+ // If not deleted, check whether the selector was renamed.
909
+ const dsm = ds.modifiers[k];
910
+ let s = k;
911
+ if(sel.new_s) {
912
+ // If so, let `s` be the key for new selector.
913
+ s = UI.nameToID(sel.new_s);
914
+ dsm.selector = sel.new_s;
915
+ if(s !== k) {
916
+ // Add modifier with its own selector key.
917
+ ds.modifiers[s] = ds.modifiers[k];
918
+ delete ds.modifiers[k];
919
+ }
920
+ // Always update all chart variables referencing dataset + old selector.
921
+ for(const v of dsv_list) {
922
+ if(v.object === ds && v.attribute === sel.sel) {
923
+ v.attribute = sel.new_s;
924
+ cv_cnt++;
925
+ }
926
+ }
927
+ // Also replace old selector in all expressions (count these as well).
928
+ xr_cnt += MODEL.replaceAttributeInExpressions(
929
+ ds.name + '|' + sel.sel, sel.new_s);
930
+ }
931
+ // NOTE: Keep original expression unless a new expression is specified.
932
+ if(sel.new_x) {
933
+ dsm.expression.text = sel.new_x;
934
+ // Clear code so the expresion will be recompiled.
935
+ dsm.expression.code = null;
936
+ }
937
+ }
938
+ } else {
939
+ // If dataset `ds` has NO selector with key `k`, add the (new) selector.
940
+ let s = sel.sel,
941
+ id = k;
942
+ if(sel.new_s) {
943
+ s = sel.new_s;
944
+ id = UI.nameToID(sel.new_s);
945
+ }
946
+ const dsm = new DatasetModifier(ds, s);
947
+ dsm.expression.text = (sel.new_x === false ? sel.expr : sel.new_x);
948
+ ds.modifiers[id] = dsm;
949
+ }
950
+ }
951
+ // Set the new default selector (if changed).
952
+ if(md.new_defsel !== false) ds.default_selector = md.new_defsel;
953
+ }
954
+ // Notify modeler of changes (if any).
955
+ const msg = [];
956
+ if(cv_cnt) msg.push(pluralS(cv_cnt, ' chart variable'));
957
+ if(xr_cnt) msg.push(pluralS(xr_cnt, ' expression variable'));
958
+ if(msg.length) {
959
+ UI.notify('Updated ' + msg.join(' and '));
960
+ }
961
+ MODEL.cleanUpScaleUnits();
962
+ MODEL.updateDimensions();
963
+ md.hide();
964
+ // Also update the draggable dialogs that may be affected.
965
+ UI.updateControllerDialogs('CDEFIJX');
966
+ }
967
+
783
968
  copyAttributesToClipboard(shift) {
784
969
  // Copy relevant entity attributes as tab-separated text to clipboard.
785
970
  // NOTE: All entity types have "get" method `attributes` that returns an
@@ -289,6 +289,8 @@ class GUIRepositoryBrowser extends RepositoryBrowser {
289
289
  'click', () => REPOSITORY_BROWSER.performInclusion());
290
290
  this.include_modal.cancel.addEventListener(
291
291
  'click', () => REPOSITORY_BROWSER.cancelInclusion());
292
+ this.include_modal.element('prefix').addEventListener(
293
+ 'blur', () => REPOSITORY_BROWSER.suggestBindings());
292
294
  this.include_modal.element('actor').addEventListener(
293
295
  'blur', () => REPOSITORY_BROWSER.updateActors());
294
296
 
@@ -614,6 +616,22 @@ class GUIRepositoryBrowser extends RepositoryBrowser {
614
616
  md.show('prefix');
615
617
  }
616
618
 
619
+ suggestBindings() {
620
+ // Select for each "Cluster: XXX" drop-down the one that matches the
621
+ // value of the prefix input field.
622
+ const
623
+ md = this.include_modal,
624
+ prefix = md.element('prefix').value.trim(),
625
+ sa = md.element('scroll-area'),
626
+ sels = sa.querySelectorAll('select');
627
+ for(const sel of sels) {
628
+ const
629
+ oid = UI.nameToID(prefix) + ':_' + sel.id,
630
+ ids = [...sel.options].map(o => o.value);
631
+ if(ids.indexOf(oid) >= 0) sel.value = oid;
632
+ }
633
+ }
634
+
617
635
  updateActors() {
618
636
  // Add actor (if specified) to model, and then updates the selector options
619
637
  // for each actor binding selector.