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.
- package/package.json +6 -2
- package/static/images/solve-not-same-changed.png +0 -0
- package/static/images/solve-not-same-not-changed.png +0 -0
- package/static/images/solve-same-changed.png +0 -0
- package/static/images/solve-same-not-changed.png +0 -0
- package/static/index.html +81 -11
- package/static/linny-r.css +250 -7
- package/static/scripts/linny-r-ctrl.js +76 -13
- package/static/scripts/linny-r-gui-chart-manager.js +13 -12
- package/static/scripts/linny-r-gui-constraint-editor.js +4 -4
- package/static/scripts/linny-r-gui-controller.js +416 -43
- package/static/scripts/linny-r-gui-dataset-manager.js +122 -93
- package/static/scripts/linny-r-gui-experiment-manager.js +22 -12
- package/static/scripts/linny-r-gui-expression-editor.js +26 -12
- package/static/scripts/linny-r-gui-finder.js +190 -5
- package/static/scripts/linny-r-gui-repository-browser.js +18 -0
- package/static/scripts/linny-r-model.js +192 -84
- package/static/scripts/linny-r-utils.js +13 -2
- package/static/scripts/linny-r-vm.js +137 -95
@@ -256,8 +256,8 @@ class Finder {
|
|
256
256
|
}
|
257
257
|
}
|
258
258
|
// Also allow search for scale unit names.
|
259
|
-
if(et
|
260
|
-
imgs
|
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),
|
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 &&
|
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.
|