linny-r 2.0.9 → 2.0.10
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 +79 -0
- package/static/linny-r.css +240 -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 +390 -30
- package/static/scripts/linny-r-gui-dataset-manager.js +48 -31
- 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-model.js +112 -38
- package/static/scripts/linny-r-vm.js +99 -93
@@ -1114,13 +1114,13 @@ class ConstraintEditor {
|
|
1114
1114
|
}
|
1115
1115
|
|
1116
1116
|
showDialog(group=[]) {
|
1117
|
-
this.from_node = MODEL.objectByName(this.from_name.
|
1118
|
-
this.to_node = MODEL.objectByName(this.to_name.
|
1119
|
-
// Double-check that these nodes exist
|
1117
|
+
this.from_node = MODEL.objectByName(this.from_name.innerText);
|
1118
|
+
this.to_node = MODEL.objectByName(this.to_name.innerText);
|
1119
|
+
// Double-check that these nodes exist.
|
1120
1120
|
if(!(this.from_node && this.to_node)) {
|
1121
1121
|
throw 'ERROR: Unknown constraint node(s)';
|
1122
1122
|
}
|
1123
|
-
// See if existing constraint is edited
|
1123
|
+
// See if existing constraint is edited.
|
1124
1124
|
this.edited_constraint = this.from_node.doesConstrain(this.to_node);
|
1125
1125
|
if(this.edited_constraint) {
|
1126
1126
|
// Make a working copy, as the constraint must be changed only when
|
@@ -75,6 +75,11 @@ class ModalDialog {
|
|
75
75
|
// Make this modal dialog invisible.
|
76
76
|
this.modal.style.display = 'none';
|
77
77
|
}
|
78
|
+
|
79
|
+
get showing() {
|
80
|
+
// Return TRUE iff this modal dialog is visible.
|
81
|
+
return this.modal.style.display === 'block';
|
82
|
+
}
|
78
83
|
|
79
84
|
} // END of class ModalDialog
|
80
85
|
|
@@ -85,15 +90,19 @@ class ModalDialog {
|
|
85
90
|
// such that fields[name] is the entity property name that corresponds
|
86
91
|
// with the DOM input element for that property. For example, for the
|
87
92
|
// process group properties dialog, fields['LB'] = 'lower_bound' to
|
88
|
-
// indicate that the DOM element having id="
|
93
|
+
// indicate that the DOM element having id="process-LB" corresponds to
|
89
94
|
// the property `p.lower_bound` of process `p`.
|
90
95
|
class GroupPropertiesDialog extends ModalDialog {
|
91
96
|
constructor(id, fields) {
|
92
97
|
super(id);
|
98
|
+
// `fields` is the object that relates HTML elements to properties.
|
93
99
|
this.fields = fields;
|
94
100
|
// `group` holds the entities (all of the same type) that should be
|
95
101
|
// updated when the OK-button of the dialog is clicked.
|
96
102
|
this.group = [];
|
103
|
+
// `selected_ds` is the dataset that was selected in the Finder when
|
104
|
+
// opening this dialog, or the first dataset in the group list.
|
105
|
+
this.selected_ds = null;
|
97
106
|
// `initial_values` is a "dictionary" with (field name, value) entries
|
98
107
|
// that hold the initial values of the group-editable properties.
|
99
108
|
this.initial = {};
|
@@ -123,28 +132,58 @@ class GroupPropertiesDialog extends ModalDialog {
|
|
123
132
|
e.addEventListener('keydown', fnc);
|
124
133
|
}
|
125
134
|
}
|
135
|
+
const spe = this.element('prefix');
|
136
|
+
if(spe) {
|
137
|
+
spe.addEventListener('keydown', fnc);
|
138
|
+
document.getElementById('dsg-add-modif-btn').addEventListener(
|
139
|
+
'click', () => UI.modals.datasetgroup.promptForSelector('Add'));
|
140
|
+
document.getElementById('dsg-rename-modif-btn').addEventListener(
|
141
|
+
'click', () => UI.modals.datasetgroup.promptForSelector('Rename'));
|
142
|
+
document.getElementById('dsg-edit-modif-btn').addEventListener(
|
143
|
+
'click', () => UI.modals.datasetgroup.editExpression());
|
144
|
+
document.getElementById('dsg-delete-modif-btn').addEventListener(
|
145
|
+
'click', () => UI.modals.datasetgroup.deleteModifier());
|
146
|
+
this.selector_modal = new ModalDialog('group-selector');
|
147
|
+
this.selector_modal.ok.addEventListener(
|
148
|
+
'click', () => UI.modals.datasetgroup.selectorAction());
|
149
|
+
this.selector_modal.cancel.addEventListener(
|
150
|
+
'click', () => UI.modals.datasetgroup.selector_modal.hide());
|
151
|
+
}
|
126
152
|
}
|
127
153
|
|
128
154
|
resetFields() {
|
129
155
|
// Remove all class names from fields that relate to their "same"
|
130
156
|
// and "changed status, and reset group-related properties.
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
cl.
|
157
|
+
function stripClassList(e) {
|
158
|
+
if(e) {
|
159
|
+
const cl = e.classList;
|
160
|
+
while(cl.length > 0 && cl.item(cl.length - 1).indexOf('same-') >= 0) {
|
161
|
+
cl.remove(cl.item(cl.length - 1));
|
162
|
+
}
|
135
163
|
}
|
136
164
|
}
|
165
|
+
for(let name in this.fields) if(this.initial.hasOwnProperty(name)) {
|
166
|
+
stripClassList(this.element(name));
|
167
|
+
}
|
168
|
+
stripClassList(this.element('prefix'));
|
137
169
|
this.element('group').innerText = '';
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
e = this.element('
|
143
|
-
if(e) e.
|
170
|
+
for(const id of ['name', 'actor', 'cluster']) {
|
171
|
+
const e = this.element(id);
|
172
|
+
if(e) e.disabled = false;
|
173
|
+
}
|
174
|
+
const e = this.element('io');
|
175
|
+
if(e) e.style.display = 'block';
|
144
176
|
this.group.length = 0;
|
145
177
|
this.initial = {};
|
146
178
|
this.same = {};
|
147
179
|
this.changed = {};
|
180
|
+
this.shared_prefix = '';
|
181
|
+
this.selectors = {};
|
182
|
+
this.selected_selector = '';
|
183
|
+
this.default_selectors = [];
|
184
|
+
this.new_defsel = false;
|
185
|
+
this.same_defsel = true;
|
186
|
+
this.last_time_clicked = 0;
|
148
187
|
}
|
149
188
|
|
150
189
|
setFields(obj) {
|
@@ -196,23 +235,212 @@ class GroupPropertiesDialog extends ModalDialog {
|
|
196
235
|
this.same[name] = same;
|
197
236
|
}
|
198
237
|
}
|
238
|
+
// For the dataset group dialog, more fields must be determined.
|
239
|
+
if(obj instanceof Dataset) {
|
240
|
+
// Determine the longest prefix shared by ALL datasets in the group.
|
241
|
+
this.shared_prefix = UI.sharedPrefix(obj.name, obj.name);
|
242
|
+
for(const ds of this.group) {
|
243
|
+
const sp = UI.sharedPrefix(obj.name, ds.name);
|
244
|
+
if(sp && this.shared_prefix.startsWith(sp)) {
|
245
|
+
this.shared_prefix = sp;
|
246
|
+
} else if(!sp.startsWith(this.shared_prefix)) {
|
247
|
+
this.shared_prefix = '';
|
248
|
+
break;
|
249
|
+
}
|
250
|
+
}
|
251
|
+
this.element('prefix').value = this.shared_prefix;
|
252
|
+
// Determine the set of all dataset modifier selectors while counting
|
253
|
+
// the number of occurrences of each selector and checking whether
|
254
|
+
// the modifier expressions are identical.
|
255
|
+
// NOTE: Here, too, take `obj` as the reference object.
|
256
|
+
this.selectors = {};
|
257
|
+
this.selected_selector = '';
|
258
|
+
this.default_selectors = [];
|
259
|
+
this.new_defsel = false;
|
260
|
+
this.same_defsel = true;
|
261
|
+
if(obj.default_selector) {
|
262
|
+
this.default_selectors.push(UI.nameToID(obj.default_selector));
|
263
|
+
}
|
264
|
+
for(const k of Object.keys(obj.modifiers)) {
|
265
|
+
const dsm = obj.modifiers[k];
|
266
|
+
this.selectors[k] = {
|
267
|
+
count: 1,
|
268
|
+
sel: dsm.selector,
|
269
|
+
expr: dsm.expression.text,
|
270
|
+
same_x: true,
|
271
|
+
new_s: false,
|
272
|
+
new_x: false,
|
273
|
+
deleted: false
|
274
|
+
};
|
275
|
+
}
|
276
|
+
// Then iterate over all datasets, excluding `obj`.
|
277
|
+
for(const ds of this.group) if(ds !== obj) {
|
278
|
+
const defsel = UI.nameToID(ds.default_selector);
|
279
|
+
if(this.default_selectors.indexOf(defsel) < 0) this.same_defsel = false;
|
280
|
+
if(defsel) addDistinct(defsel, this.default_selectors);
|
281
|
+
for(const k of Object.keys(ds.modifiers)) {
|
282
|
+
const
|
283
|
+
dsm = ds.modifiers[k],
|
284
|
+
s = this.selectors[k];
|
285
|
+
if(s) {
|
286
|
+
s.count++;
|
287
|
+
s.same_x = s.same_x && dsm.expression.text === s.expr;
|
288
|
+
} else {
|
289
|
+
this.selectors[k] = {
|
290
|
+
count: 1,
|
291
|
+
sel: dsm.selector,
|
292
|
+
expr: dsm.expression.text,
|
293
|
+
same_x: true,
|
294
|
+
new_s: false,
|
295
|
+
new_x: false,
|
296
|
+
deleted: false
|
297
|
+
};
|
298
|
+
}
|
299
|
+
}
|
300
|
+
}
|
301
|
+
// Selectors are not "same" when they do not apply to all datasets
|
302
|
+
// in the group.
|
303
|
+
const n = this.group.length;
|
304
|
+
for(const k of Object.keys(this.selectors)) {
|
305
|
+
const s = this.selectors[k];
|
306
|
+
s.same_s = s.count === n;
|
307
|
+
}
|
308
|
+
this.updateModifierList();
|
309
|
+
}
|
310
|
+
}
|
311
|
+
|
312
|
+
updateModifierList() {
|
313
|
+
// Display the modifier set for a dataset group.
|
314
|
+
const
|
315
|
+
trl = [],
|
316
|
+
not = (x) => { return (x === false ? 'not-' : ''); },
|
317
|
+
mdef = (this.new_defsel !== false ? this.new_defsel :
|
318
|
+
(this.default_selectors.length ? this.default_selectors[0] : '')),
|
319
|
+
sdef = not(this.same_defsel),
|
320
|
+
cdef = not(this.new_defsel);
|
321
|
+
for(const k of Object.keys(this.selectors)) {
|
322
|
+
const
|
323
|
+
s = this.selectors[k],
|
324
|
+
ms = (s.new_s === false ? s.sel : s.new_s),
|
325
|
+
mx = (s.new_x === false ? s.expr : s.new_x),
|
326
|
+
wild = (ms.indexOf('*') >= 0 || ms.indexOf('?') >= 0),
|
327
|
+
clk = `" onclick="UI.modals.datasetgroup.selectGroupSelector(event, \'${k}\'`;
|
328
|
+
// Do not display deleted modifiers.
|
329
|
+
if(s.deleted) continue;
|
330
|
+
trl.push(['<tr id="dsgs-', k, '" class="dataset-modif',
|
331
|
+
(k === this.selected_selector ? ' sel-set' : ''),
|
332
|
+
'"><td class="dataset-selector',
|
333
|
+
` ${not(s.same_s)}same-${not(s.new_s)}changed`,
|
334
|
+
(wild ? ' wildcard' : ''),
|
335
|
+
'" title="Shift-click to ', (s.defsel ? 'clear' : 'set as'),
|
336
|
+
' default modifier', clk, ', false);">',
|
337
|
+
(k === mdef ||
|
338
|
+
(this.new_defsel === false && this.default_selectors.indexOf(k) >= 0) ?
|
339
|
+
`<img src="images/solve-${sdef}same-${cdef}changed.png" ` +
|
340
|
+
'style="height: 14px; width: 14px; margin: 0 1px -3px -1px;">' : ''),
|
341
|
+
(wild ? wildcardFormat(ms, true) : ms),
|
342
|
+
'</td><td class="dataset-expression',
|
343
|
+
` ${not(s.same_x)}same-${not(s.new_x)}changed`, clk,
|
344
|
+
', true);">', mx, '</td></tr>'
|
345
|
+
].join(''));
|
346
|
+
}
|
347
|
+
this.element('modif-table').innerHTML = trl.join('');
|
348
|
+
if(this.selected_selector) UI.scrollIntoView(
|
349
|
+
document.getElementById('dsg-' + this.selected_selector));
|
350
|
+
const btns = 'dsg-rename-modif dsg-edit-modif dsg-delete-modif';
|
351
|
+
if(this.selected_selector) {
|
352
|
+
UI.enableButtons(btns);
|
353
|
+
} else {
|
354
|
+
UI.disableButtons(btns);
|
355
|
+
}
|
199
356
|
}
|
200
357
|
|
358
|
+
selectGroupSelector(event, id, x=true) {
|
359
|
+
// Select modifier selector, or when double-clicked, edit its expression when
|
360
|
+
// x = TRUE, or the name of the modifier when x = FALSE.
|
361
|
+
const edit = event.altKey || this.doubleClicked(id);
|
362
|
+
this.selected_selector = id;
|
363
|
+
if(edit) {
|
364
|
+
this.last_time_clicked = 0;
|
365
|
+
if(x) {
|
366
|
+
this.editExpression();
|
367
|
+
} else {
|
368
|
+
this.promptForSelector('Rename');
|
369
|
+
}
|
370
|
+
return;
|
371
|
+
}
|
372
|
+
if(event.shiftKey) {
|
373
|
+
// Toggle new default selector.
|
374
|
+
this.new_defsel = (this.new_defsel === id ? '' : id);
|
375
|
+
}
|
376
|
+
this.updateModifierList();
|
377
|
+
}
|
378
|
+
|
379
|
+
doubleClicked(s) {
|
380
|
+
const
|
381
|
+
now = Date.now(),
|
382
|
+
dt = now - this.last_time_clicked;
|
383
|
+
this.last_time_clicked = now;
|
384
|
+
if(s === this.selected_selector) {
|
385
|
+
// Consider click to be "double" if it occurred less than 300 ms ago.
|
386
|
+
if(dt < 300) {
|
387
|
+
this.last_time_clicked = 0;
|
388
|
+
return true;
|
389
|
+
}
|
390
|
+
}
|
391
|
+
this.clicked_selector = s;
|
392
|
+
return false;
|
393
|
+
}
|
394
|
+
|
395
|
+
enterKey() {
|
396
|
+
// Open "edit" dialog for the selected modifier.
|
397
|
+
const
|
398
|
+
tbl = this.element('modif-table'),
|
399
|
+
srl = tbl.getElementsByClassName('sel-set');
|
400
|
+
if(srl.length > 0) {
|
401
|
+
const r = tbl.rows[srl[0].rowIndex];
|
402
|
+
if(r) {
|
403
|
+
// Emulate a double-click on the second cell to edit the expression.
|
404
|
+
this.last_time_clicked = Date.now();
|
405
|
+
r.cells[1].dispatchEvent(new Event('click'));
|
406
|
+
}
|
407
|
+
}
|
408
|
+
}
|
409
|
+
|
410
|
+
upDownKey(dir) {
|
411
|
+
// Select row above or below the selected one (if possible).
|
412
|
+
const
|
413
|
+
tbl = this.element('modif-table'),
|
414
|
+
srl = tbl.getElementsByClassName('sel-set');
|
415
|
+
if(srl.length > 0) {
|
416
|
+
let r = tbl.rows[srl[0].rowIndex + dir];
|
417
|
+
while(r && r.style.display === 'none') {
|
418
|
+
r = (dir > 0 ? r.nextSibling : r.previousSibling);
|
419
|
+
}
|
420
|
+
if(r) {
|
421
|
+
UI.scrollIntoView(r);
|
422
|
+
// NOTE: Cell, not row, listens for onclick event.
|
423
|
+
r.cells[1].dispatchEvent(new Event('click'));
|
424
|
+
}
|
425
|
+
}
|
426
|
+
}
|
427
|
+
|
201
428
|
show(attr, obj) {
|
202
429
|
// Make dialog visible with same/changed status and disabled name,
|
203
430
|
// actor and cluster fields.
|
204
431
|
// NOTE: Cluster dialog is also used to *add* a new cluster, and in
|
205
|
-
// that case no fields should be set
|
432
|
+
// that case no fields should be set.
|
206
433
|
if(obj) this.setFields(obj);
|
207
434
|
if(obj && this.group.length > 0) {
|
208
435
|
this.element('group').innerText = `(N=${this.group.length})`;
|
209
|
-
// Disable name, actor and cluster fields if they exist.
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
436
|
+
// Disable name, actor, and cluster fields if they exist.
|
437
|
+
for(const id of ['name', 'actor', 'cluster']) {
|
438
|
+
const e = this.element(id);
|
439
|
+
if(e) e.disabled = true;
|
440
|
+
}
|
441
|
+
// Hide io field if it exists.
|
442
|
+
const e = this.element('io');
|
443
|
+
if(e) e.style.display = 'none';
|
216
444
|
// Set the right colors to reflect same and changed status.
|
217
445
|
this.highlightModifiedFields();
|
218
446
|
}
|
@@ -231,6 +459,7 @@ class GroupPropertiesDialog extends ModalDialog {
|
|
231
459
|
// Set the CSS classes of fields so that they reflect their "same"
|
232
460
|
// and "changed" status.
|
233
461
|
if(this.group.length === 0) return;
|
462
|
+
const not = {false: 'not-', true: ''};
|
234
463
|
for(let name in this.initial) if(this.initial.hasOwnProperty(name)) {
|
235
464
|
const
|
236
465
|
iv = this.initial[name],
|
@@ -238,7 +467,6 @@ class GroupPropertiesDialog extends ModalDialog {
|
|
238
467
|
// for which `same[name]` is TRUE iff all entities had identical
|
239
468
|
// values for the property identified by `name` when the dialog
|
240
469
|
// was opened.
|
241
|
-
not = {false: 'not-', true: ''},
|
242
470
|
same = `${not[this.same[name]]}same`,
|
243
471
|
el = this.element(name);
|
244
472
|
let changed = false,
|
@@ -261,10 +489,20 @@ class GroupPropertiesDialog extends ModalDialog {
|
|
261
489
|
// Compute current value as Boolean.
|
262
490
|
const v = (type === 'box' ? state ==='checked' : state === 'eq');
|
263
491
|
changed = (v !== iv);
|
492
|
+
// When array box for dataset group is (un)checked, the time aspects
|
493
|
+
// cover div must be hidden (shown).
|
494
|
+
if(name === 'array') {
|
495
|
+
this.element('no-time-msg').style.display = (v ? 'block' : 'none');
|
496
|
+
}
|
264
497
|
}
|
265
498
|
this.changed[name] = changed;
|
266
499
|
el.className = `${type} ${state} ${same}-${not[changed]}changed`.trim();
|
267
500
|
}
|
501
|
+
const spe = this.element('prefix');
|
502
|
+
if(spe) {
|
503
|
+
const changed = spe.value !== this.shared_prefix;
|
504
|
+
spe.className = `same-${not[changed]}changed`;
|
505
|
+
}
|
268
506
|
}
|
269
507
|
|
270
508
|
validLinkProperty(link, property, value=0) {
|
@@ -313,7 +551,105 @@ class GroupPropertiesDialog extends ModalDialog {
|
|
313
551
|
}
|
314
552
|
}
|
315
553
|
}
|
554
|
+
|
555
|
+
promptForSelector(action) {
|
556
|
+
// Open the group selector modal for the specified action.
|
557
|
+
let ms = '',
|
558
|
+
md = this.selector_modal;
|
559
|
+
if(action === 'Rename') {
|
560
|
+
ms = this.selectors[this.selected_selector].sel;
|
561
|
+
}
|
562
|
+
md.element('action').innerText = action;
|
563
|
+
md.element('name').value = ms;
|
564
|
+
md.show('name');
|
565
|
+
}
|
566
|
+
|
567
|
+
selectorAction() {
|
568
|
+
// Perform the specified selector action.
|
569
|
+
const
|
570
|
+
md = this.selector_modal,
|
571
|
+
action = md.element('action').innerText,
|
572
|
+
ne = md.element('name'),
|
573
|
+
ms = MODEL.validSelector(ne.value);
|
574
|
+
if(!ms) {
|
575
|
+
ne.focus();
|
576
|
+
return;
|
577
|
+
}
|
578
|
+
const ok = (action === 'Add' ? this.addSelector(ms) : this.renameSelector(ms));
|
579
|
+
if(ok) {
|
580
|
+
md.hide();
|
581
|
+
this.updateModifierList();
|
582
|
+
}
|
583
|
+
}
|
584
|
+
|
585
|
+
addSelector(ms) {
|
586
|
+
// Create a new selector and adds it to the list.
|
587
|
+
const k = UI.nameToID(ms);
|
588
|
+
if(!this.selectors.hasOwnProperty(k)) {
|
589
|
+
this.selectors[k] = {
|
590
|
+
count: 1,
|
591
|
+
sel: ms,
|
592
|
+
expr: '',
|
593
|
+
same_x: true,
|
594
|
+
new_s: ms,
|
595
|
+
new_x: '',
|
596
|
+
deleted: false
|
597
|
+
};
|
598
|
+
}
|
599
|
+
this.selected_selector = k;
|
600
|
+
return true;
|
601
|
+
}
|
602
|
+
|
603
|
+
renameSelector(ms) {
|
604
|
+
// Record the new name for this selector as property `new_s`.
|
605
|
+
if(this.selected_selector) {
|
606
|
+
const sel = this.selectors[this.selected_selector];
|
607
|
+
// NOTES:
|
608
|
+
// (1) When renaming, the old name is be preserved.
|
609
|
+
// (2) Name changes do not affect the key of the selector.
|
610
|
+
// (3) When the new name is identical to the original, record this
|
611
|
+
// by setting `new_s` to FALSE.
|
612
|
+
sel.new_s = (ms === sel.sel ? false : ms);
|
613
|
+
}
|
614
|
+
return true;
|
615
|
+
}
|
316
616
|
|
617
|
+
editExpression() {
|
618
|
+
// Open the Expression editor for the selected expression.
|
619
|
+
const sel = this.selectors[this.selected_selector];
|
620
|
+
if(sel) {
|
621
|
+
const md = UI.modals.expression;
|
622
|
+
md.element('property').innerHTML = '(dataset group)' +
|
623
|
+
UI.OA_SEPARATOR + sel.sel;
|
624
|
+
md.element('text').value = sel.new_x || sel.expr;
|
625
|
+
document.getElementById('variable-obj').value = 0;
|
626
|
+
X_EDIT.updateVariableBar();
|
627
|
+
X_EDIT.clearStatusBar();
|
628
|
+
X_EDIT.showPrefix(this.shared_prefix);
|
629
|
+
md.show('text');
|
630
|
+
}
|
631
|
+
}
|
632
|
+
|
633
|
+
modifyExpression(x) {
|
634
|
+
// Record the new expression for the selected selector.
|
635
|
+
// NOTE: Expressions are compiled when changes are saved.
|
636
|
+
const sel = this.selectors[this.selected_selector];
|
637
|
+
// NOTE: When the new expression is identical to the original,
|
638
|
+
// record this by setting `new_x` to FALSE.
|
639
|
+
if(sel) sel.new_x = (x === sel.expr ? false : x);
|
640
|
+
this.updateModifierList();
|
641
|
+
}
|
642
|
+
|
643
|
+
deleteModifier() {
|
644
|
+
// Record that the selected modifier should be deleted.
|
645
|
+
const sel = this.selectors[this.selected_selector];
|
646
|
+
if(sel) {
|
647
|
+
sel.deleted = true;
|
648
|
+
this.selected_selector = '';
|
649
|
+
this.updateModifierList();
|
650
|
+
}
|
651
|
+
}
|
652
|
+
|
317
653
|
} // END of class GroupPropertiesDialog
|
318
654
|
|
319
655
|
|
@@ -499,6 +835,17 @@ class GUIController extends Controller {
|
|
499
835
|
'no-links': 'no_links'
|
500
836
|
});
|
501
837
|
|
838
|
+
// The Dataset group modal.
|
839
|
+
this.modals.datasetgroup = new GroupPropertiesDialog('datasetgroup', {
|
840
|
+
'default': 'default_value',
|
841
|
+
'unit': 'scale_unit',
|
842
|
+
'periodic': 'periodic',
|
843
|
+
'array': 'array',
|
844
|
+
'time-scale': 'time_scale',
|
845
|
+
'time-unit': 'time_unit',
|
846
|
+
'method': 'method'
|
847
|
+
});
|
848
|
+
|
502
849
|
// Initially, no dialog being dragged or resized.
|
503
850
|
this.dr_dialog = null;
|
504
851
|
|
@@ -810,6 +1157,11 @@ class GUIController extends Controller {
|
|
810
1157
|
this.modals.product.element('io').addEventListener('click',
|
811
1158
|
() => UI.toggleImportExportBox('product'));
|
812
1159
|
|
1160
|
+
this.modals.datasetgroup.ok.addEventListener('click',
|
1161
|
+
() => FINDER.updateDatasetGroupProperties());
|
1162
|
+
this.modals.datasetgroup.cancel.addEventListener('click',
|
1163
|
+
() => UI.modals.datasetgroup.hide());
|
1164
|
+
|
813
1165
|
this.modals.link.ok.addEventListener('click',
|
814
1166
|
() => UI.updateLinkProperties());
|
815
1167
|
this.modals.link.cancel.addEventListener('click',
|
@@ -2154,9 +2506,9 @@ class GUIController extends Controller {
|
|
2154
2506
|
// dragging its endpoint).
|
2155
2507
|
} else if(this.constraining_node) {
|
2156
2508
|
if(this.on_node && this.constraining_node.canConstrain(this.on_node)) {
|
2157
|
-
//
|
2158
|
-
CONSTRAINT_EDITOR.from_name.
|
2159
|
-
CONSTRAINT_EDITOR.to_name.
|
2509
|
+
// Display constraint editor.
|
2510
|
+
CONSTRAINT_EDITOR.from_name.innerText = this.constraining_node.displayName;
|
2511
|
+
CONSTRAINT_EDITOR.to_name.innerText = this.on_node.displayName;
|
2160
2512
|
CONSTRAINT_EDITOR.showDialog();
|
2161
2513
|
}
|
2162
2514
|
this.linking_node = null;
|
@@ -2312,15 +2664,16 @@ class GUIController extends Controller {
|
|
2312
2664
|
while(i < inp.length && inp[i].disabled) i++;
|
2313
2665
|
if(i < inp.length) {
|
2314
2666
|
inp[i].focus();
|
2315
|
-
} else if(['constraint-modal', 'boundline-data-modal',
|
2667
|
+
} else if(['datasetgroup-modal', 'constraint-modal', 'boundline-data-modal',
|
2316
2668
|
'xp-clusters-modal'].indexOf(topmod.id) >= 0) {
|
2317
|
-
// NOTE:
|
2318
|
-
//
|
2669
|
+
// NOTE: These modals must NOT close when Enter is pressed, but only
|
2670
|
+
// de-focus the input field.
|
2319
2671
|
e.target.blur();
|
2320
2672
|
} else {
|
2321
2673
|
const btns = topmod.getElementsByClassName('ok-btn');
|
2322
2674
|
if(btns.length > 0) btns[0].dispatchEvent(new Event('click'));
|
2323
2675
|
}
|
2676
|
+
if(topmod.id === 'datasetgroup-modal') UI.modals.datasetgroup.enterKey();
|
2324
2677
|
} else if(this.dr_dialog_order.length > 0) {
|
2325
2678
|
// Send ENTER key event to the top draggable dialog.
|
2326
2679
|
const last = this.dr_dialog_order.length - 1;
|
@@ -2334,15 +2687,22 @@ class GUIController extends Controller {
|
|
2334
2687
|
// Prevent backspace to be interpreted (by FireFox) as "go back in browser".
|
2335
2688
|
e.preventDefault();
|
2336
2689
|
} else if(ttag === 'BODY') {
|
2337
|
-
// Constraint Editor
|
2338
|
-
if(topmod
|
2339
|
-
if(code.startsWith('Arrow')) {
|
2690
|
+
// Dataset group modal and Constraint Editor accept arrow keys.
|
2691
|
+
if(topmod) {
|
2692
|
+
if(topmod.id === 'constraint-modal' && code.startsWith('Arrow')) {
|
2340
2693
|
e.preventDefault();
|
2341
2694
|
CONSTRAINT_EDITOR.arrowKey(e);
|
2342
2695
|
return;
|
2343
2696
|
}
|
2697
|
+
if(topmod.id === 'datasetgroup-modal' &&
|
2698
|
+
(code === 'ArrowUp' || code === 'ArrowDown')) {
|
2699
|
+
e.preventDefault();
|
2700
|
+
// NOTE: Pass key direction as -1 for UP and +1 for DOWN.
|
2701
|
+
UI.modals.datasetgroup.upDownKey(e.keyCode - 39);
|
2702
|
+
return;
|
2703
|
+
}
|
2344
2704
|
}
|
2345
|
-
//
|
2705
|
+
// Lists in draggable dialogs respond to up and down arrow keys.
|
2346
2706
|
if(code === 'ArrowUp' || code === 'ArrowDown') {
|
2347
2707
|
e.preventDefault();
|
2348
2708
|
// Send event to the top draggable dialog.
|