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
@@ -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
|
+
}
|
316
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
|
+
}
|
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',
|
@@ -873,6 +1225,12 @@ class GUIController extends Controller {
|
|
873
1225
|
// Ensure that all modal windows respond to ESCape
|
874
1226
|
// (and more in general to other special keys).
|
875
1227
|
document.addEventListener('keydown', (event) => UI.checkModals(event));
|
1228
|
+
// Ensure that all modal dialogs "swallow" mousedown events, as otherwise
|
1229
|
+
// these may alo be processed by the main window drawing canvas.
|
1230
|
+
for(const modal of document.getElementsByClassName('modal')) {
|
1231
|
+
modal.addEventListener('mousedown', (event) => event.stopPropagation());
|
1232
|
+
}
|
1233
|
+
|
876
1234
|
}
|
877
1235
|
|
878
1236
|
setConstraintUnderCursor(c) {
|
@@ -1575,18 +1933,19 @@ class GUIController extends Controller {
|
|
1575
1933
|
// Button functionality
|
1576
1934
|
//
|
1577
1935
|
|
1578
|
-
enableButtons(btns) {
|
1936
|
+
enableButtons(btns, blink=false) {
|
1579
1937
|
for(const btn of btns.trim().split(/\s+/)) {
|
1580
1938
|
const b = document.getElementById(btn + '-btn');
|
1581
|
-
b.classList.remove('disab', 'activ');
|
1939
|
+
b.classList.remove('disab', 'activ', 'blink');
|
1582
1940
|
b.classList.add('enab');
|
1941
|
+
if(blink) b.classList.add('blink');
|
1583
1942
|
}
|
1584
1943
|
}
|
1585
1944
|
|
1586
1945
|
disableButtons(btns) {
|
1587
1946
|
for(const btn of btns.trim().split(/\s+/)) {
|
1588
1947
|
const b = document.getElementById(btn + '-btn');
|
1589
|
-
b.classList.remove('enab', 'activ', 'stay-activ');
|
1948
|
+
b.classList.remove('enab', 'activ', 'stay-activ', 'blink');
|
1590
1949
|
b.classList.add('disab');
|
1591
1950
|
}
|
1592
1951
|
}
|
@@ -2154,9 +2513,9 @@ class GUIController extends Controller {
|
|
2154
2513
|
// dragging its endpoint).
|
2155
2514
|
} else if(this.constraining_node) {
|
2156
2515
|
if(this.on_node && this.constraining_node.canConstrain(this.on_node)) {
|
2157
|
-
//
|
2158
|
-
CONSTRAINT_EDITOR.from_name.
|
2159
|
-
CONSTRAINT_EDITOR.to_name.
|
2516
|
+
// Display constraint editor.
|
2517
|
+
CONSTRAINT_EDITOR.from_name.innerText = this.constraining_node.displayName;
|
2518
|
+
CONSTRAINT_EDITOR.to_name.innerText = this.on_node.displayName;
|
2160
2519
|
CONSTRAINT_EDITOR.showDialog();
|
2161
2520
|
}
|
2162
2521
|
this.linking_node = null;
|
@@ -2280,17 +2639,11 @@ class GUIController extends Controller {
|
|
2280
2639
|
// Handler for keyboard events
|
2281
2640
|
//
|
2282
2641
|
|
2283
|
-
|
2284
|
-
//
|
2285
|
-
const
|
2286
|
-
ttype = e.target.type,
|
2287
|
-
ttag = e.target.tagName,
|
2288
|
-
modals = document.getElementsByClassName('modal');
|
2289
|
-
// Modal dialogs: hide on ESC and move to next input on ENTER.
|
2642
|
+
get topModal() {
|
2643
|
+
// Return the topmost visible modal dialog, or NULL if none are showing.
|
2644
|
+
const modals = document.getElementsByClassName('modal');
|
2290
2645
|
let maxz = 0,
|
2291
|
-
topmod = null
|
2292
|
-
code = e.code,
|
2293
|
-
alt = e.altKey;
|
2646
|
+
topmod = null;
|
2294
2647
|
for(const m of modals) {
|
2295
2648
|
const
|
2296
2649
|
cs = window.getComputedStyle(m),
|
@@ -2300,6 +2653,18 @@ class GUIController extends Controller {
|
|
2300
2653
|
maxz = z;
|
2301
2654
|
}
|
2302
2655
|
}
|
2656
|
+
return topmod;
|
2657
|
+
}
|
2658
|
+
|
2659
|
+
checkModals(e) {
|
2660
|
+
// Respond to Escape, Enter and shortcut keys.
|
2661
|
+
const
|
2662
|
+
ttype = e.target.type,
|
2663
|
+
ttag = e.target.tagName,
|
2664
|
+
code = e.code,
|
2665
|
+
alt = e.altKey,
|
2666
|
+
topmod = this.topModal;
|
2667
|
+
// Modal dialogs: hide on ESC and move to next input on ENTER.
|
2303
2668
|
// NOTE: Consider only the top modal (if any is showing).
|
2304
2669
|
if(code === 'Escape') {
|
2305
2670
|
e.stopImmediatePropagation();
|
@@ -2312,15 +2677,16 @@ class GUIController extends Controller {
|
|
2312
2677
|
while(i < inp.length && inp[i].disabled) i++;
|
2313
2678
|
if(i < inp.length) {
|
2314
2679
|
inp[i].focus();
|
2315
|
-
} else if(['constraint-modal', 'boundline-data-modal',
|
2680
|
+
} else if(['datasetgroup-modal', 'constraint-modal', 'boundline-data-modal',
|
2316
2681
|
'xp-clusters-modal'].indexOf(topmod.id) >= 0) {
|
2317
|
-
// NOTE:
|
2318
|
-
//
|
2682
|
+
// NOTE: These modals must NOT close when Enter is pressed, but only
|
2683
|
+
// de-focus the input field.
|
2319
2684
|
e.target.blur();
|
2320
2685
|
} else {
|
2321
2686
|
const btns = topmod.getElementsByClassName('ok-btn');
|
2322
2687
|
if(btns.length > 0) btns[0].dispatchEvent(new Event('click'));
|
2323
2688
|
}
|
2689
|
+
if(topmod.id === 'datasetgroup-modal') UI.modals.datasetgroup.enterKey();
|
2324
2690
|
} else if(this.dr_dialog_order.length > 0) {
|
2325
2691
|
// Send ENTER key event to the top draggable dialog.
|
2326
2692
|
const last = this.dr_dialog_order.length - 1;
|
@@ -2334,15 +2700,22 @@ class GUIController extends Controller {
|
|
2334
2700
|
// Prevent backspace to be interpreted (by FireFox) as "go back in browser".
|
2335
2701
|
e.preventDefault();
|
2336
2702
|
} else if(ttag === 'BODY') {
|
2337
|
-
// Constraint Editor
|
2338
|
-
if(topmod
|
2339
|
-
if(code.startsWith('Arrow')) {
|
2703
|
+
// Dataset group modal and Constraint Editor accept arrow keys.
|
2704
|
+
if(topmod) {
|
2705
|
+
if(topmod.id === 'constraint-modal' && code.startsWith('Arrow')) {
|
2340
2706
|
e.preventDefault();
|
2341
2707
|
CONSTRAINT_EDITOR.arrowKey(e);
|
2342
2708
|
return;
|
2343
2709
|
}
|
2710
|
+
if(topmod.id === 'datasetgroup-modal' &&
|
2711
|
+
(code === 'ArrowUp' || code === 'ArrowDown')) {
|
2712
|
+
e.preventDefault();
|
2713
|
+
// NOTE: Pass key direction as -1 for UP and +1 for DOWN.
|
2714
|
+
UI.modals.datasetgroup.upDownKey(e.keyCode - 39);
|
2715
|
+
return;
|
2716
|
+
}
|
2344
2717
|
}
|
2345
|
-
//
|
2718
|
+
// Lists in draggable dialogs respond to up and down arrow keys.
|
2346
2719
|
if(code === 'ArrowUp' || code === 'ArrowDown') {
|
2347
2720
|
e.preventDefault();
|
2348
2721
|
// Send event to the top draggable dialog.
|