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.
@@ -11,7 +11,7 @@ for the Linny-R Dataset Manager dialog.
11
11
  */
12
12
 
13
13
  /*
14
- Copyright (c) 2017-2024 Delft University of Technology
14
+ Copyright (c) 2017-2025 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
@@ -32,7 +32,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
32
32
  SOFTWARE.
33
33
  */
34
34
 
35
- // CLASS GUIDatasetManager provides the dataset dialog functionality
35
+ // CLASS GUIDatasetManager provides the dataset dialog functionality.
36
36
  class GUIDatasetManager extends DatasetManager {
37
37
  constructor() {
38
38
  super();
@@ -44,7 +44,7 @@ class GUIDatasetManager extends DatasetManager {
44
44
  'click', (event) => UI.toggleDialog(event));
45
45
  document.getElementById('ds-new-btn').addEventListener(
46
46
  // Shift-click on New button => add prefix of selected dataset
47
- // (if any) to the name field of the dialog
47
+ // (if any) to the name field of the dialog.
48
48
  'click', () => DATASET_MANAGER.promptForDataset(event.shiftKey));
49
49
  document.getElementById('ds-data-btn').addEventListener(
50
50
  'click', () => DATASET_MANAGER.editData());
@@ -56,16 +56,12 @@ class GUIDatasetManager extends DatasetManager {
56
56
  'click', () => DATASET_MANAGER.load_csv_modal.show());
57
57
  document.getElementById('ds-delete-btn').addEventListener(
58
58
  'click', () => DATASET_MANAGER.deleteDataset());
59
- document.getElementById('ds-filter-btn').addEventListener(
60
- 'click', () => DATASET_MANAGER.toggleFilter());
61
- // Update when filter input text changes
62
- this.filter_text = document.getElementById('ds-filter-text');
63
- this.filter_text.addEventListener(
64
- 'input', () => DATASET_MANAGER.changeFilter());
65
59
  this.dataset_table = document.getElementById('dataset-table');
66
- // Data properties pane
60
+ // Data properties pane below the dataset scroll area.
67
61
  this.properties = document.getElementById('dataset-properties');
68
- // Toggle buttons at bottom of dialog
62
+ // Number of prefixed datasets is displayed at bottom of left pane.
63
+ this.prefixed_count = document.getElementById('dataset-prefixed-count');
64
+ // Toggle buttons at bottom of dialog.
69
65
  this.blackbox = document.getElementById('dataset-blackbox');
70
66
  this.blackbox.addEventListener(
71
67
  'click', () => DATASET_MANAGER.toggleBlackBox());
@@ -75,7 +71,7 @@ class GUIDatasetManager extends DatasetManager {
75
71
  this.io_box = document.getElementById('dataset-io');
76
72
  this.io_box.addEventListener(
77
73
  'click', () => DATASET_MANAGER.toggleImportExport());
78
- // Modifier pane buttons
74
+ // Modifier pane buttons.
79
75
  document.getElementById('ds-add-modif-btn').addEventListener(
80
76
  'click', () => DATASET_MANAGER.promptForSelector('new'));
81
77
  document.getElementById('ds-rename-modif-btn').addEventListener(
@@ -86,9 +82,9 @@ class GUIDatasetManager extends DatasetManager {
86
82
  'click', () => DATASET_MANAGER.deleteModifier());
87
83
  document.getElementById('ds-convert-modif-btn').addEventListener(
88
84
  'click', () => DATASET_MANAGER.promptToConvertModifiers());
89
- // Modifier table
85
+ // Modifier table.
90
86
  this.modifier_table = document.getElementById('dataset-modif-table');
91
- // Modal dialogs
87
+ // Modal dialogs.
92
88
  this.new_modal = new ModalDialog('new-dataset');
93
89
  this.new_modal.ok.addEventListener(
94
90
  'click', () => DATASET_MANAGER.newDataset());
@@ -144,12 +140,13 @@ class GUIDatasetManager extends DatasetManager {
144
140
  reset() {
145
141
  super.reset();
146
142
  this.selected_prefix_row = null;
143
+ this.selected_dataset = null;
147
144
  this.selected_modifier = null;
148
145
  this.edited_expression = null;
149
- this.filter_pattern = null;
150
146
  this.clicked_object = null;
151
147
  this.last_time_clicked = 0;
152
148
  this.focal_table = null;
149
+ this.prefixed_datasets = [];
153
150
  this.expanded_rows = [];
154
151
  }
155
152
 
@@ -170,7 +167,8 @@ class GUIDatasetManager extends DatasetManager {
170
167
  }
171
168
 
172
169
  enterKey() {
173
- // Open "edit" dialog for the selected dataset or modifier expression
170
+ // Open "edit" dialog for the selected dataset or modifier expression.
171
+ if(!this.focal_table) this.focal_table = this.dataset_table;
174
172
  const srl = this.focal_table.getElementsByClassName('sel-set');
175
173
  if(srl.length > 0) {
176
174
  const r = this.focal_table.rows[srl[0].rowIndex];
@@ -190,7 +188,8 @@ class GUIDatasetManager extends DatasetManager {
190
188
  }
191
189
 
192
190
  upDownKey(dir) {
193
- // Select row above or below the selected one (if possible)
191
+ // Select row above or below the selected one (if possible).
192
+ if(!this.focal_table) this.focal_table = this.dataset_table;
194
193
  const srl = this.focal_table.getElementsByClassName('sel-set');
195
194
  if(srl.length > 0) {
196
195
  let r = this.focal_table.rows[srl[0].rowIndex + dir];
@@ -206,11 +205,24 @@ class GUIDatasetManager extends DatasetManager {
206
205
  }
207
206
  }
208
207
 
208
+ expandToShow(name) {
209
+ // Expand all prefix rows for dataset having `name` so as to make it visible.
210
+ const pn = UI.prefixesAndName(name);
211
+ if(pn.length > 1) {
212
+ pn.pop();
213
+ while(pn.length) {
214
+ addDistinct(pn.join(UI.PREFIXER).toLowerCase(), this.expanded_rows);
215
+ pn.pop();
216
+ }
217
+ this.updateDialog();
218
+ }
219
+ }
220
+
209
221
  hideCollapsedRows() {
210
- // Hides all rows except top level and immediate children of expanded.
222
+ // Hide all rows except top level and immediate children of expanded.
211
223
  for(const row of this.dataset_table.rows) {
212
224
  const
213
- // Get the first DIV in the first TD of this row
225
+ // Get the first DIV in the first TD of this row.
214
226
  first_div = row.firstChild.firstElementChild,
215
227
  btn = first_div.dataset.prefix === 'x';
216
228
  let p = row.dataset.prefix,
@@ -218,18 +230,18 @@ class GUIDatasetManager extends DatasetManager {
218
230
  show = !p || x;
219
231
  if(btn) {
220
232
  const btn_div = row.getElementsByClassName('tree-btn')[0];
221
- // Special expand/collapse row
233
+ // Special expand/collapse row.
222
234
  if(show) {
223
- // Set triangle to point down
235
+ // Set triangle to point down.
224
236
  btn_div.innerText = '\u25BC';
225
237
  } else {
226
- // Set triangle to point right
238
+ // Set triangle to point right.
227
239
  btn_div.innerText = '\u25BA';
228
- // See whether "parent prefix" is expanded
240
+ // See whether "parent prefix" is expanded.
229
241
  p = p.split(UI.PREFIXER);
230
242
  p.pop();
231
243
  p = p.join(UI.PREFIXER);
232
- // If so, then also show the row
244
+ // If so, then also show the row.
233
245
  show = (!p || this.expanded_rows.indexOf(p) >= 0);
234
246
  }
235
247
  }
@@ -238,7 +250,7 @@ class GUIDatasetManager extends DatasetManager {
238
250
  }
239
251
 
240
252
  togglePrefixRow(e) {
241
- // Shows list items of the next prefix level
253
+ // Show list items of the next prefix level.
242
254
  let r = e.target;
243
255
  while(r.tagName !== 'TR') r = r.parentNode;
244
256
  const
@@ -246,7 +258,7 @@ class GUIDatasetManager extends DatasetManager {
246
258
  i = this.expanded_rows.indexOf(p);
247
259
  if(i >= 0) {
248
260
  this.expanded_rows.splice(i, 1);
249
- // Also remove all prefixes that have `p` as prefix
261
+ // Also remove all prefixes that have `p` as prefix.
250
262
  for(let j = this.expanded_rows.length - 1; j >= 0; j--) {
251
263
  if(this.expanded_rows[j].startsWith(p + UI.PREFIXER)) {
252
264
  this.expanded_rows.splice(j, 1);
@@ -259,7 +271,7 @@ class GUIDatasetManager extends DatasetManager {
259
271
  }
260
272
 
261
273
  rowByPrefix(prefix) {
262
- // Returns first table row with the specified prefix
274
+ // Return the first table row with the specified prefix.
263
275
  if(!prefix) return null;
264
276
  let lcp = prefix.toLowerCase(),
265
277
  pl = lcp.split(': ');
@@ -276,17 +288,29 @@ class GUIDatasetManager extends DatasetManager {
276
288
  for(const r of this.dataset_table.rows) if(r.dataset.prefix === lcp) return r;
277
289
  return null;
278
290
  }
291
+
292
+ datasetsByPrefix(prefix) {
293
+ // Return the list of datasets having the specified prefix.
294
+ const
295
+ pid = UI.nameToID(prefix + UI.PREFIXER),
296
+ dsl = [];
297
+ for(const k of Object.keys(MODEL.datasets)) {
298
+ if(k.startsWith(pid)) dsl.push(k);
299
+ }
300
+ return dsl;
301
+ }
279
302
 
280
303
  selectPrefixRow(e) {
281
- // Selects expand/collapse prefix row
304
+ // Select expand/collapse prefix row.
282
305
  this.focal_table = this.dataset_table;
283
- // NOTE: `e` can also be a string specifying the prefix to select
306
+ // NOTE: `e` can also be a string specifying the prefix to select.
284
307
  let r = e.target || this.rowByPrefix(e);
285
308
  if(!r) return;
286
- // Modeler may have clicked on the expand/collapse triangle;
309
+ // Modeler may have clicked on the expand/collapse triangle.
287
310
  const toggle = r.classList.contains('tree-btn');
288
311
  while(r.tagName !== 'TR') r = r.parentNode;
289
312
  this.selected_prefix_row = r;
313
+ this.prefixed_datasets = this.datasetsByPrefix(r.dataset.prefix);
290
314
  const sel = this.dataset_table.getElementsByClassName('sel-set');
291
315
  this.selected_dataset = null;
292
316
  if(sel.length > 0) {
@@ -296,7 +320,7 @@ class GUIDatasetManager extends DatasetManager {
296
320
  r.classList.add('sel-set');
297
321
  if(!e.target) r.scrollIntoView({block: 'center'});
298
322
  if(toggle || e.altKey || this.doubleClicked(r)) this.togglePrefixRow(e);
299
- UI.enableButtons('ds-rename');
323
+ this.updatePanes();
300
324
  }
301
325
 
302
326
  updateDialog() {
@@ -306,18 +330,13 @@ class GUIDatasetManager extends DatasetManager {
306
330
  dnl = [],
307
331
  sd = this.selected_dataset,
308
332
  ioclass = ['', 'import', 'export'];
309
- for(let d in MODEL.datasets) if(MODEL.datasets.hasOwnProperty(d) &&
310
- // NOTE: do not list "black-boxed" entities
311
- !d.startsWith(UI.BLACK_BOX) &&
312
- // NOTE: do not list the equations dataset
313
- MODEL.datasets[d] !== MODEL.equations_dataset) {
314
- if(!this.filter_pattern || this.filter_pattern.length === 0 ||
315
- patternMatch(MODEL.datasets[d].displayName, this.filter_pattern)) {
316
- dnl.push(d);
317
- }
333
+ for(let d in MODEL.datasets) if(MODEL.datasets.hasOwnProperty(d)) {
334
+ // NOTE: Do not list "black-boxed" entities or the equations dataset.
335
+ if(!d.startsWith(UI.BLACK_BOX) &&
336
+ MODEL.datasets[d] !== MODEL.equations_dataset) dnl.push(d);
318
337
  }
319
338
  dnl.sort((a, b) => UI.compareFullNames(a, b, true));
320
- // First determine indentation levels, prefixes and names
339
+ // First determine indentation levels, prefixes and names.
321
340
  const
322
341
  indent = [],
323
342
  pref_ids = [],
@@ -326,11 +345,11 @@ class GUIDatasetManager extends DatasetManager {
326
345
  xids = [];
327
346
  for(const dn of dnl) {
328
347
  const pref = UI.prefixesAndName(MODEL.datasets[dn].name);
329
- // NOTE: only the name part (so no prefixes at all) will be shown
348
+ // NOTE: Only the name part (so no prefixes at all) will be shown.
330
349
  names.push(pref.pop());
331
350
  indent.push(pref.length);
332
- // NOTE: ignore case but join again with ": " because prefixes
333
- // can contain any character; only the prefixer is "reserved"
351
+ // NOTE: Ignore case but join again with ": " because prefixes
352
+ // can contain any character; only the prefixer is "reserved".
334
353
  const pref_id = pref.join(UI.PREFIXER).toLowerCase();
335
354
  pref_ids.push(pref_id);
336
355
  pref_names[pref_id] = pref;
@@ -348,11 +367,11 @@ class GUIDatasetManager extends DatasetManager {
348
367
  } else {
349
368
  ind_div = '';
350
369
  }
351
- // NOTE: empty string should not add a collapse/expand row
370
+ // NOTE: Empty string should not add a collapse/expand row.
352
371
  if(pid && pid != prev_id && xids.indexOf(pid) < 0) {
353
372
  // NOTE: XX: aa may be followed by XX: YY: ZZ: bb, which requires
354
373
  // *two* collapsable lines: XX: YY and XX: YY: ZZ: before adding
355
- // XX: YY: ZZ: bb
374
+ // XX: YY: ZZ: bb.
356
375
  const
357
376
  ps = pid.split(UI.PREFIXER),
358
377
  pps = prev_id.split(UI.PREFIXER),
@@ -360,20 +379,20 @@ class GUIDatasetManager extends DatasetManager {
360
379
  pns = pn.join(UI.PREFIXER),
361
380
  lpl = [];
362
381
  let lindent = 0;
363
- // Ignore identical leading prefixes
382
+ // Ignore identical leading prefixes.
364
383
  while(ps.length > 0 && pps.length > 0 && ps[0] === pps[0]) {
365
384
  lpl.push(ps.shift());
366
385
  pps.shift();
367
386
  pn.shift();
368
387
  lindent++;
369
388
  }
370
- // Add a "collapse" row for each new prefix
389
+ // Add a "collapse" row for each new prefix.
371
390
  while(ps.length > 0) {
372
391
  lpl.push(ps.shift());
373
392
  lindent++;
374
393
  const lpid = lpl.join(UI.PREFIXER);
375
394
  dl.push(['<tr data-prefix="', lpid,
376
- '" data-prefix-name="', pns, '" class="dataset"',
395
+ '" data-prefix-name="', pns.slice(0, lpid.length), '" class="dataset"',
377
396
  'onclick="DATASET_MANAGER.selectPrefixRow(event);"><td>',
378
397
  // NOTE: data-prefix="x" signals that this is an extra row
379
398
  (lindent > 0 ?
@@ -383,7 +402,7 @@ class GUIDatasetManager extends DatasetManager {
383
402
  '<div data-prefix="x" class="tree-btn">',
384
403
  (this.expanded_rows.indexOf(lpid) >= 0 ? '\u25BC' : '\u25BA'),
385
404
  '</div>', pn.shift(), '</td></tr>'].join(''));
386
- // Add to the list to prevent multiple c/x-rows for the same prefix
405
+ // Add to the list to prevent multiple c/x-rows for the same prefix.
387
406
  xids.push(lpid);
388
407
  }
389
408
  }
@@ -422,6 +441,7 @@ class GUIDatasetManager extends DatasetManager {
422
441
  sd = this.selected_dataset,
423
442
  btns = 'ds-data ds-clone ds-delete ds-rename';
424
443
  if(sd) {
444
+ this.prefixed_count.style.display = 'none';
425
445
  this.properties.style.display = 'block';
426
446
  document.getElementById('dataset-default').innerHTML =
427
447
  VM.sig4Dig(sd.default_value) +
@@ -454,8 +474,22 @@ class GUIDatasetManager extends DatasetManager {
454
474
  UI.enableButtons(btns);
455
475
  } else {
456
476
  this.properties.style.display = 'none';
477
+ const
478
+ pdsl = this.prefixed_datasets.length,
479
+ npds = pluralS(pdsl, 'dataset');
480
+ this.prefixed_count.innerText = npds;
481
+ this.prefixed_count.style.display = (pdsl ? 'block' : 'none');
457
482
  UI.disableButtons(btns);
458
- if(this.selected_prefix_row) UI.enableButtons('ds-rename');
483
+ if(this.selected_prefix_row) {
484
+ UI.enableButtons('ds-rename ds-delete', true);
485
+ document.getElementById('ds-rename-btn')
486
+ .title = `Rename ${npds} by changing prefix "${this.selectedPrefix}"`;
487
+ document.getElementById('ds-delete-btn')
488
+ .title = `Delete ${npds} having prefix "${this.selectedPrefix}"`;
489
+ } else {
490
+ document.getElementById('ds-rename-btn').title = 'Rename selected dataset';
491
+ document.getElementById('ds-delete-btn').title = 'Delete selected dataset';
492
+ }
459
493
  }
460
494
  this.updateModifiers();
461
495
  }
@@ -540,37 +574,14 @@ class GUIDatasetManager extends DatasetManager {
540
574
  if(d) DOCUMENTATION_MANAGER.update(d, shift);
541
575
  }
542
576
 
543
- toggleFilter() {
544
- const
545
- btn = document.getElementById('ds-filter-btn'),
546
- bar = document.getElementById('ds-filter-bar'),
547
- dsa = document.getElementById('dataset-scroll-area');
548
- if(btn.classList.toggle('stay-activ')) {
549
- bar.style.display = 'block';
550
- dsa.style.top = '81px';
551
- dsa.style.height = 'calc(100% - 141px)';
552
- this.changeFilter();
553
- } else {
554
- bar.style.display = 'none';
555
- dsa.style.top = '62px';
556
- dsa.style.height = 'calc(100% - 122px)';
557
- this.filter_pattern = null;
558
- this.updateDialog();
559
- }
560
- }
561
-
562
- changeFilter() {
563
- this.filter_pattern = patternList(this.filter_text.value);
564
- this.updateDialog();
565
- }
566
-
567
577
  selectDataset(event, id) {
568
- // Select dataset, or edit it when Alt- or double-clicked
578
+ // Select dataset, or edit it when Alt- or double-clicked.
569
579
  this.focal_table = this.dataset_table;
570
580
  const
571
581
  d = MODEL.datasets[id] || null,
572
582
  edit = event.altKey || this.doubleClicked(d);
573
583
  this.selected_dataset = d;
584
+ this.prefixed_datasets.length = 0;
574
585
  if(d && edit) {
575
586
  this.last_time_clicked = 0;
576
587
  this.editData();
@@ -733,18 +744,34 @@ class GUIDatasetManager extends DatasetManager {
733
744
  this.updateDialog();
734
745
  }
735
746
  }
747
+
748
+ get selectedAsList() {
749
+ // Return list of datasets selected directly or by prefix.
750
+ const dsl = [];
751
+ // Prevent including the equations dataset (just in case).
752
+ if(this.selected_dataset && this.selected_dataset !== MODEL.equations_dataset) {
753
+ dsl.push(this.selected_dataset);
754
+ } else {
755
+ // NOTE: List of prefixed datasets contains keys, not objects.
756
+ for(const k of this.prefixed_datasets) {
757
+ const ds = MODEL.datasets[k];
758
+ if(ds !== MODEL.equations_dataset) dsl.push();
759
+ }
760
+ }
761
+ return dsl;
762
+ }
736
763
 
737
764
  deleteDataset() {
738
- const d = this.selected_dataset;
739
- // Double-check, just in case...
740
- if(d && d !== MODEL.equations_dataset) {
741
- MODEL.removeImport(d);
742
- MODEL.removeExport(d);
743
- delete MODEL.datasets[d.identifier];
744
- this.selected_dataset = null;
745
- this.updateDialog();
746
- MODEL.updateDimensions();
765
+ // Delete selected dataset(s).
766
+ for(const ds of this.selectedAsList) {
767
+ MODEL.removeImport(ds);
768
+ MODEL.removeExport(ds);
769
+ delete MODEL.datasets[ds.identifier];
747
770
  }
771
+ this.selected_dataset = null;
772
+ this.prefixed_datasets.length = 0;
773
+ this.updateDialog();
774
+ MODEL.updateDimensions();
748
775
  }
749
776
 
750
777
  toggleBlackBox() {
@@ -1047,7 +1074,7 @@ class GUIDatasetManager extends DatasetManager {
1047
1074
  }
1048
1075
 
1049
1076
  editData() {
1050
- // Show the Edit time series dialog
1077
+ // Show the Edit time series dialog.
1051
1078
  const
1052
1079
  ds = this.selected_dataset,
1053
1080
  md = this.series_modal,
@@ -1057,7 +1084,7 @@ class GUIDatasetManager extends DatasetManager {
1057
1084
  md.element('unit').value = ds.scale_unit;
1058
1085
  cover.style.display = (ds.array ? 'block' : 'none');
1059
1086
  md.element('time-scale').value = VM.sig4Dig(ds.time_scale);
1060
- // Add options for time unit selector
1087
+ // Add options for time unit selector.
1061
1088
  const ol = [];
1062
1089
  for(let u in VM.time_unit_shorthand) {
1063
1090
  if(VM.time_unit_shorthand.hasOwnProperty(u)) {
@@ -1067,7 +1094,7 @@ class GUIDatasetManager extends DatasetManager {
1067
1094
  }
1068
1095
  }
1069
1096
  md.element('time-unit').innerHTML = ol.join('');
1070
- // Add options for(dis)aggregation method selector
1097
+ // Add options for(dis)aggregation method selector.
1071
1098
  ol.length = 0;
1072
1099
  for(let i = 0; i < this.methods.length; i++) {
1073
1100
  ol.push(['<option value="', this.methods[i],
@@ -1075,12 +1102,12 @@ class GUIDatasetManager extends DatasetManager {
1075
1102
  '">', this.method_names[i], '</option>'].join(''));
1076
1103
  }
1077
1104
  md.element('method').innerHTML = ol.join('');
1078
- // Update the "periodic" box
1105
+ // Update the "periodic" box.
1079
1106
  UI.setBox('series-periodic', ds.periodic);
1080
- // Update the "array" box
1107
+ // Update the "array" box.
1081
1108
  UI.setBox('series-array', ds.array);
1082
1109
  md.element('url').value = ds.url;
1083
- // Show data as decimal numbers (JS default notation) on separate lines
1110
+ // Show data as decimal numbers (JS default notation) on separate lines.
1084
1111
  this.series_data.value = ds.data.join('\n');
1085
1112
  md.show('default');
1086
1113
  }
@@ -1203,7 +1230,8 @@ class GUIDatasetManager extends DatasetManager {
1203
1230
  UI.warn(`Invalid default value "${v}" in column ${i}`);
1204
1231
  return false;
1205
1232
  } else {
1206
- dsa.push([sf]);
1233
+ // Push empty list, as this will become the actual dataset without default value.
1234
+ dsa.push([]);
1207
1235
  }
1208
1236
  }
1209
1237
  for(let i = 2; i < n; i++) {
@@ -1212,7 +1240,7 @@ class GUIDatasetManager extends DatasetManager {
1212
1240
  UI.warn(`Number of values (${dsv.length}) on line ${i} does not match number of dataset names (${ncol})`);
1213
1241
  return false;
1214
1242
  }
1215
- for(let j = 0; j < dsv.length; j++) {
1243
+ for(let j = 0; j < ncol; j++) {
1216
1244
  const
1217
1245
  v = dsv[j].trim(),
1218
1246
  sf = safeStrToFloat(v, '');
@@ -1220,6 +1248,7 @@ class GUIDatasetManager extends DatasetManager {
1220
1248
  UI.warn(`Invalid numerical value "${v}" for <strong>${dsn[j]}</strong> on line ${i}`);
1221
1249
  return false;
1222
1250
  }
1251
+ dsa[j].push(sf);
1223
1252
  }
1224
1253
  }
1225
1254
  // Add or update datasets.
@@ -441,19 +441,25 @@ class GUIExperimentManager extends ExperimentManager {
441
441
  }
442
442
 
443
443
  newExperiment() {
444
- const n = this.new_modal.element('name').value.trim();
445
- const x = MODEL.addExperiment(n);
446
- if(x) {
447
- this.new_modal.hide();
448
- this.selected_experiment = x;
449
- this.updateDialog();
444
+ // NOTE: Title must be a "clean" name: no \ or | and spacing reduced to
445
+ // a single space to permit using it unambiguously in experiment result
446
+ // specifiers of variable names.
447
+ const n = UI.cleanName(this.new_modal.element('name').value);
448
+ if(n) {
449
+ const x = MODEL.addExperiment(n);
450
+ if(x) {
451
+ this.new_modal.hide();
452
+ this.selected_experiment = x;
453
+ this.updateDialog();
454
+ }
455
+ } else {
456
+ this.new_modal.element('name').focus();
457
+ return;
450
458
  }
451
459
  }
452
460
 
453
461
  promptForName() {
454
462
  if(this.selected_experiment) {
455
- this.rename_modal.element('former-name').innerHTML =
456
- this.selected_experiment.title;
457
463
  this.rename_modal.element('name').value = '';
458
464
  this.rename_modal.show('name');
459
465
  }
@@ -464,12 +470,16 @@ class GUIExperimentManager extends ExperimentManager {
464
470
  const
465
471
  nel = this.rename_modal.element('name'),
466
472
  n = UI.cleanName(nel.value);
467
- // Show modeler the "cleaned" new name
473
+ // Show modeler the "cleaned" new name.
468
474
  nel.value = n;
469
- // Keep prompt open if title is empty string
475
+ // Keep prompt open if cleaned title is empty string, or identifies
476
+ // an existing experiment.
477
+ nel.focus();
470
478
  if(n) {
471
- // Warn modeler if name already in use for some experiment
472
- if(MODEL.indexOfExperiment(n) >= 0) {
479
+ // Warn modeler if name already in use for some experiment, other than
480
+ // the selected experiment (as upper/lower case changes must be possible).
481
+ if(MODEL.indexOfExperiment(n) >= 0 &&
482
+ n.toLowerCase() !== this.selected_experiment.title.toLowerCase()) {
473
483
  UI.warn(`An experiment with title "${n}" already exists`);
474
484
  } else {
475
485
  this.selected_experiment.title = n;
@@ -197,9 +197,9 @@ NOTE: Grouping groups results in a single group, e.g., (1;2);(3;4;5) evaluates a
197
197
  let n = '',
198
198
  a = '';
199
199
  if(ids[0] === 'link') {
200
- n = document.getElementById('link-from-name').innerHTML +
200
+ n = document.getElementById('link-from-name').innerText +
201
201
  UI.LINK_ARROW +
202
- document.getElementById('link-to-name').innerHTML;
202
+ document.getElementById('link-to-name').innerText;
203
203
  } else {
204
204
  n = document.getElementById(ids[0] + '-name').value;
205
205
  if(ids[0] === 'process') {
@@ -218,6 +218,7 @@ NOTE: Grouping groups results in a single group, e.g., (1;2);(3;4;5) evaluates a
218
218
  this.obj.value = 0;
219
219
  this.updateVariableBar();
220
220
  this.clearStatusBar();
221
+ this.showPrefix(UI.entityPrefix(prop));
221
222
  md.show('text');
222
223
  }
223
224
 
@@ -257,15 +258,20 @@ NOTE: Grouping groups results in a single group, e.g., (1;2);(3;4;5) evaluates a
257
258
  // the dataset and the selector as extra parameters for the parser.
258
259
  let own = null,
259
260
  sel = '';
260
- if(!this.edited_input_id && DATASET_MANAGER.edited_expression) {
261
- own = DATASET_MANAGER.selected_dataset;
262
- sel = DATASET_MANAGER.selected_modifier.selector;
263
- } else if(!this.edited_input_id && EQUATION_MANAGER.edited_expression) {
264
- own = MODEL.equations_dataset;
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;
261
+ if(!this.edited_input_id) {
262
+ if(DATASET_MANAGER.edited_expression) {
263
+ own = DATASET_MANAGER.selected_dataset;
264
+ sel = DATASET_MANAGER.selected_modifier.selector;
265
+ } else if(EQUATION_MANAGER.edited_expression) {
266
+ own = MODEL.equations_dataset;
267
+ sel = EQUATION_MANAGER.selected_modifier.selector;
268
+ } else if(CONSTRAINT_EDITOR.edited_expression) {
269
+ own = CONSTRAINT_EDITOR.selected;
270
+ sel = CONSTRAINT_EDITOR.selected_selector;
271
+ } else if(UI.modals.datasetgroup.showing) {
272
+ own = UI.modals.datasetgroup.selected_ds;
273
+ sel = UI.modals.datasetgroup.selected_selector;
274
+ }
269
275
  } else {
270
276
  own = UI.edited_object;
271
277
  sel = this.edited_input_id.split('-').pop();
@@ -298,6 +304,8 @@ NOTE: Grouping groups results in a single group, e.g., (1;2);(3;4;5) evaluates a
298
304
  } else if(CONSTRAINT_EDITOR.edited_expression) {
299
305
  // NOTE: Boundline selector expressions may result in a grouping.
300
306
  CONSTRAINT_EDITOR.modifyExpression(xp.expr, xp.concatenating);
307
+ } else if(UI.modals.datasetgroup.showing) {
308
+ UI.modals.datasetgroup.modifyExpression(xp.expr);
301
309
  }
302
310
  UI.modals.expression.hide();
303
311
  return true;
@@ -308,7 +316,13 @@ NOTE: Grouping groups results in a single group, e.g., (1;2);(3;4;5) evaluates a
308
316
  this.status.style.backgroundColor = UI.color.dialog_background;
309
317
  this.status.innerHTML = '&nbsp;';
310
318
  }
311
-
319
+
320
+ showPrefix(prefix) {
321
+ // When editing an expression for a prefixed entity, show the prefix
322
+ // on the status line.
323
+ if(prefix) this.status.innerHTML = '<em>Prefix:</em> ' + prefix;
324
+ }
325
+
312
326
  namesByType(type) {
313
327
  // Returns a list of entity names of the specified types
314
328
  // (used only to generate the options of SELECT elements)