linny-r 1.3.0 → 1.3.1
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/console.js +137 -5
- package/package.json +1 -1
- package/server.js +15 -11
- package/static/images/paperclip.png +0 -0
- package/static/index.html +5 -2
- package/static/linny-r.css +9 -3
- package/static/scripts/linny-r-gui.js +86 -77
- package/static/scripts/linny-r-vm.js +3 -2
package/console.js
CHANGED
@@ -113,10 +113,14 @@ Possible options are:
|
|
113
113
|
channel=[identifier] will start listening at the specified channel
|
114
114
|
(FUTURE OPTION)
|
115
115
|
check will report whether current version is up-to-date
|
116
|
-
|
116
|
+
data-dir=[path] will look for series data files in [path] instead of
|
117
|
+
(main)/user/data
|
118
|
+
model=[path] will load model file specified by [path]
|
117
119
|
module=[name@repo] will load model [name] from repository [repo]
|
118
120
|
(if @repo is blank, repository "local host" is used)
|
119
121
|
(FUTURE OPTION)
|
122
|
+
report=[name] will write run results to [name]-series.txt and
|
123
|
+
[name]-stats.txt in (workspace)/reports
|
120
124
|
run will run the loaded model
|
121
125
|
solver=[name] will select solver [name], or warn if not found
|
122
126
|
(name choices: Gurobi or LP_solve)
|
@@ -247,11 +251,81 @@ class ConsoleMonitor {
|
|
247
251
|
} // END of class ConsoleMonitor
|
248
252
|
|
249
253
|
|
254
|
+
// NOTE: This implementation is very incomplete, still!
|
255
|
+
class ConsoleRepositoryBrowser {
|
256
|
+
constructor() {
|
257
|
+
this.repositories = [];
|
258
|
+
this.repository_index = -1;
|
259
|
+
this.module_index = -1;
|
260
|
+
// Get the repository list from the modules
|
261
|
+
this.getRepositories();
|
262
|
+
this.reset();
|
263
|
+
}
|
264
|
+
|
265
|
+
reset() {
|
266
|
+
this.visible = false;
|
267
|
+
}
|
268
|
+
|
269
|
+
get isLocalHost() {
|
270
|
+
// Returns TRUE if first repository on the list is 'local host'
|
271
|
+
return this.repositories.length > 0 &&
|
272
|
+
this.repositories[0].name === 'local host';
|
273
|
+
}
|
274
|
+
|
275
|
+
getRepositories() {
|
276
|
+
// Gets the list of repository names from the server
|
277
|
+
this.repositories.length = 0;
|
278
|
+
// @@TO DO!!
|
279
|
+
}
|
280
|
+
|
281
|
+
repositoryByName(n) {
|
282
|
+
// Returns the repository having name `n` if already known, otherwise NULL
|
283
|
+
for(let i = 0; i < this.repositories.length; i++) {
|
284
|
+
if(this.repositories[i].name === n) {
|
285
|
+
return this.repositories[i];
|
286
|
+
}
|
287
|
+
}
|
288
|
+
return null;
|
289
|
+
}
|
290
|
+
|
291
|
+
asFileName(s) {
|
292
|
+
// NOTE: asFileName is implemented as function (see below) to permit
|
293
|
+
// its use prior to instantiation of the RepositoryBrowser
|
294
|
+
return stringToFileName(s);
|
295
|
+
}
|
296
|
+
|
297
|
+
}
|
298
|
+
|
299
|
+
function stringToFileName(s) {
|
300
|
+
// Returns string `s` with whitespace converted to a single dash, and
|
301
|
+
// special characters converted to underscores
|
302
|
+
return s.normalize('NFKD').trim()
|
303
|
+
.replace(/[\s\-]+/g, '-')
|
304
|
+
.replace(/[^A-Za-z0-9_\-]/g, '_')
|
305
|
+
.replace(/^[\-\_]+|[\-\_]+$/g, '');
|
306
|
+
}
|
307
|
+
|
250
308
|
// CLASS ConsoleFileManager allows loading and saving models and diagrams, and
|
251
309
|
// handles the interaction with the MILP solver via `exec` calls and files
|
252
310
|
// stored on the modeler's computer
|
253
311
|
class ConsoleFileManager {
|
254
312
|
|
313
|
+
anyOSpath(p) {
|
314
|
+
// Helper function that converts any path notation to platform notation
|
315
|
+
// based on the predominant separator
|
316
|
+
const
|
317
|
+
s_parts = p.split('/'),
|
318
|
+
bs_parts = p.split('\\'),
|
319
|
+
parts = (s_parts.length > bs_parts.length ? s_parts : bs_parts);
|
320
|
+
// On macOS machines, paths start with a slash, so first substring is empty
|
321
|
+
if(parts[0].endsWith(':') && path.sep === '\\') {
|
322
|
+
// On Windows machines, add a backslash after the disk (if specified)
|
323
|
+
parts[0] += path.sep;
|
324
|
+
}
|
325
|
+
// Reassemble path for the OS of this machine
|
326
|
+
return path.join(...parts);
|
327
|
+
}
|
328
|
+
|
255
329
|
getRemoteData(dataset, url) {
|
256
330
|
// Gets data from a URL, or from a file on the local host
|
257
331
|
if(url === '') return;
|
@@ -281,7 +355,13 @@ class ConsoleFileManager {
|
|
281
355
|
console.log('ERROR: Invalid URL', url);
|
282
356
|
}
|
283
357
|
} else {
|
284
|
-
|
358
|
+
let fp = this.anyOSpath(url);
|
359
|
+
if(!(fp.startsWith('/') || fp.startsWith('\\') || fp.indexOf(':\\') > 0)) {
|
360
|
+
// Relative path => add path to specified data path or to the
|
361
|
+
// default location user/data
|
362
|
+
fp = path.join(SETTINGS.data_path || WORKSPACE.data, fp);
|
363
|
+
console.log('Full path: ', fp);
|
364
|
+
}
|
285
365
|
fs.readFile(fp, 'utf8', (err, data) => {
|
286
366
|
if(err) {
|
287
367
|
console.log(err);
|
@@ -354,6 +434,17 @@ class ConsoleFileManager {
|
|
354
434
|
});
|
355
435
|
}
|
356
436
|
|
437
|
+
writeStringToFile(s, fp) {
|
438
|
+
// Write string `s` to path `fp`
|
439
|
+
try {
|
440
|
+
fs.writeFileSync(fp, s);
|
441
|
+
console.log(pluralS(s.length, 'character') + ' written to file ' + fp);
|
442
|
+
} catch(err) {
|
443
|
+
console.log(err);
|
444
|
+
console.log('ERROR: Failed to write data to file ' + fp);
|
445
|
+
}
|
446
|
+
}
|
447
|
+
|
357
448
|
} // END of class ConsoleFileManager
|
358
449
|
|
359
450
|
// CLASS ConsoleReceiver defines a listener/interpreter for channel commands
|
@@ -702,7 +793,9 @@ function commandLineSettings() {
|
|
702
793
|
const settings = {
|
703
794
|
cli_name: (PLATFORM.startsWith('win') ? 'Command Prompt' : 'Terminal'),
|
704
795
|
check: false,
|
796
|
+
data_path: '',
|
705
797
|
preferred_solver: '',
|
798
|
+
report: '',
|
706
799
|
run: false,
|
707
800
|
solver: '',
|
708
801
|
solver_path: '',
|
@@ -759,6 +852,31 @@ function commandLineSettings() {
|
|
759
852
|
console.log(`ERROR: File "${av[1]}" not found`);
|
760
853
|
process.exit();
|
761
854
|
}
|
855
|
+
} else if(av[0] === 'data-dir') {
|
856
|
+
// Set path (if valid) to override default data directory
|
857
|
+
const dp = av[1];
|
858
|
+
try {
|
859
|
+
// See whether the directory already exists
|
860
|
+
try {
|
861
|
+
fs.accessSync(dp, fs.constants.R_OK | fs.constants.W_O);
|
862
|
+
} catch(err) {
|
863
|
+
// If not, try to create it
|
864
|
+
fs.mkdirSync(dp);
|
865
|
+
console.log('Created data directory:', dp);
|
866
|
+
}
|
867
|
+
settings.data_path = dp;
|
868
|
+
} catch(err) {
|
869
|
+
console.log(err.message);
|
870
|
+
console.log('ERROR: Failed to create data directory:', dp);
|
871
|
+
}
|
872
|
+
} else if(av[0] === 'report') {
|
873
|
+
// Set report file name (if valid)
|
874
|
+
const rfn = stringToFileName(av[1]);
|
875
|
+
if(/^[A-Za-z0-9]+/.test(rfn)) {
|
876
|
+
settings.report = path.join(settings.user_dir, 'reports', rfn);
|
877
|
+
} else {
|
878
|
+
console.log(`WARNING: Invalid report file name "{$rfn}"`);
|
879
|
+
}
|
762
880
|
} else if(av[0] === 'module') {
|
763
881
|
// Add default repository is none specified
|
764
882
|
if(av[1].indexOf('@') < 0) av[1] += '@local host';
|
@@ -882,10 +1000,13 @@ function createWorkspace() {
|
|
882
1000
|
}
|
883
1001
|
// Define the sub-directory paths
|
884
1002
|
const ws = {
|
1003
|
+
autosave: path.join(SETTINGS.user_dir, 'autosave'),
|
885
1004
|
channel: path.join(SETTINGS.user_dir, 'channel'),
|
886
1005
|
callback: path.join(SETTINGS.user_dir, 'callback'),
|
1006
|
+
data: path.join(SETTINGS.user_dir, 'data'),
|
887
1007
|
diagrams: path.join(SETTINGS.user_dir, 'diagrams'),
|
888
1008
|
modules: path.join(SETTINGS.user_dir, 'modules'),
|
1009
|
+
reports: path.join(SETTINGS.user_dir, 'reports'),
|
889
1010
|
solver_output: path.join(SETTINGS.user_dir, 'solver'),
|
890
1011
|
};
|
891
1012
|
// Create these sub-directories if not aready there
|
@@ -968,7 +1089,7 @@ PROMPTER.questionPrompt = (str) => {
|
|
968
1089
|
// Initialize the Linny-R console components as global variables
|
969
1090
|
global.UI = new Controller();
|
970
1091
|
global.VM = new VirtualMachine();
|
971
|
-
|
1092
|
+
global.REPOSITORY_BROWSER = new ConsoleRepositoryBrowser();
|
972
1093
|
global.FILE_MANAGER = new ConsoleFileManager();
|
973
1094
|
global.DATASET_MANAGER = new DatasetManager();
|
974
1095
|
global.CHART_MANAGER = new ChartManager();
|
@@ -990,8 +1111,19 @@ if(SETTINGS.model_path) {
|
|
990
1111
|
MONITOR.show_log = SETTINGS.verbose;
|
991
1112
|
VM.callback = () => {
|
992
1113
|
const od = model.outputData;
|
993
|
-
|
994
|
-
|
1114
|
+
// Output data is two-string list [time series, statistics]
|
1115
|
+
if(SETTINGS.report) {
|
1116
|
+
// Output time series
|
1117
|
+
FILE_MANAGER.writeStringToFile(od[0],
|
1118
|
+
SETTINGS.report + '-series.txt');
|
1119
|
+
// Output statistics
|
1120
|
+
FILE_MANAGER.writeStringToFile(od[1],
|
1121
|
+
SETTINGS.report + '-stats.txt');
|
1122
|
+
} else {
|
1123
|
+
// Output strings to console
|
1124
|
+
console.log(od[0]);
|
1125
|
+
console.log(od[1]);
|
1126
|
+
}
|
995
1127
|
VM.callback = null;
|
996
1128
|
};
|
997
1129
|
VM.solveModel();
|
package/package.json
CHANGED
package/server.js
CHANGED
@@ -847,20 +847,19 @@ function repoDelete(res, name, file) {
|
|
847
847
|
// Dataset dialog
|
848
848
|
|
849
849
|
function anyOSpath(p) {
|
850
|
-
// Helper function that converts
|
851
|
-
//
|
852
|
-
|
853
|
-
|
850
|
+
// Helper function that converts any path notation to platform notation
|
851
|
+
// based on the predominant separator
|
852
|
+
const
|
853
|
+
s_parts = p.split('/'),
|
854
|
+
bs_parts = p.split('\\'),
|
855
|
+
parts = (s_parts.length > bs_parts.length ? s_parts : bs_parts);
|
854
856
|
// On macOS machines, paths start with a slash, so first substring is empty
|
855
|
-
if(
|
856
|
-
// In that case, add the leading slash
|
857
|
-
return '/' + path.join(...p);
|
858
|
-
} else if(p[0].endsWith(':') && path.sep === '\\') {
|
857
|
+
if(parts[0].endsWith(':') && path.sep === '\\') {
|
859
858
|
// On Windows machines, add a backslash after the disk (if specified)
|
860
|
-
|
859
|
+
parts[0] += path.sep;
|
861
860
|
}
|
862
861
|
// Reassemble path for the OS of this machine
|
863
|
-
return path.join(...
|
862
|
+
return path.join(...parts);
|
864
863
|
}
|
865
864
|
|
866
865
|
function loadData(res, url) {
|
@@ -881,7 +880,11 @@ function loadData(res, url) {
|
|
881
880
|
servePlainText(res, `ERROR: Invalid URL <tt>${url}</tt>`);
|
882
881
|
}
|
883
882
|
} else {
|
884
|
-
|
883
|
+
let fp = anyOSpath(url);
|
884
|
+
if(!(fp.startsWith('/') || fp.startsWith('\\') || fp.indexOf(':\\') > 0)) {
|
885
|
+
// Relative path => add path to user/data directory
|
886
|
+
fp = path.join(WORKSPACE.data, fp);
|
887
|
+
}
|
885
888
|
fs.readFile(fp, 'utf8', (err, data) => {
|
886
889
|
if(err) {
|
887
890
|
console.log(err);
|
@@ -1568,6 +1571,7 @@ function createWorkspace() {
|
|
1568
1571
|
autosave: path.join(SETTINGS.user_dir, 'autosave'),
|
1569
1572
|
channel: path.join(SETTINGS.user_dir, 'channel'),
|
1570
1573
|
callback: path.join(SETTINGS.user_dir, 'callback'),
|
1574
|
+
data: path.join(SETTINGS.user_dir, 'data'),
|
1571
1575
|
diagrams: path.join(SETTINGS.user_dir, 'diagrams'),
|
1572
1576
|
modules: path.join(SETTINGS.user_dir, 'modules'),
|
1573
1577
|
solver_output: path.join(SETTINGS.user_dir, 'solver'),
|
Binary file
|
package/static/index.html
CHANGED
@@ -1704,8 +1704,11 @@ NOTE: * and ? will be interpreted as wildcards"
|
|
1704
1704
|
<!-- options are added by DATASET_MANAGER -->
|
1705
1705
|
</select>
|
1706
1706
|
<div id="series-remote">
|
1707
|
-
|
1708
|
-
|
1707
|
+
<img id="series-clip" src="images/paperclip.png">
|
1708
|
+
<input id="series-url" type="text"
|
1709
|
+
placeholder="URL or path on local host"
|
1710
|
+
title="Path can be absolute or relative to (Linny-R)/user/data">
|
1711
|
+
</div>
|
1709
1712
|
<div id="series-data-lbl">Series data:</div>
|
1710
1713
|
<textarea id="series-data" autocomplete="off"
|
1711
1714
|
autocorrect="off" autocapitalize="off" spellcheck="false">
|
package/static/linny-r.css
CHANGED
@@ -2468,12 +2468,18 @@ td.equation-expression {
|
|
2468
2468
|
#series-remote {
|
2469
2469
|
position: absolute;
|
2470
2470
|
top: 134px;
|
2471
|
-
left:
|
2472
|
-
width: calc(100% -
|
2471
|
+
left: 2px;
|
2472
|
+
width: calc(100% - 5px);
|
2473
|
+
}
|
2474
|
+
|
2475
|
+
#series-clip {
|
2476
|
+
height: 12px;
|
2477
|
+
width: 132x;
|
2478
|
+
vertical-align: middle;
|
2473
2479
|
}
|
2474
2480
|
|
2475
2481
|
#series-url {
|
2476
|
-
width: 100
|
2482
|
+
width: calc(100% - 17px);
|
2477
2483
|
font-size: 12px;
|
2478
2484
|
}
|
2479
2485
|
|
@@ -3555,81 +3555,6 @@ class GUIController extends Controller {
|
|
3555
3555
|
// Methods related to draggable & resizable dialogs
|
3556
3556
|
//
|
3557
3557
|
|
3558
|
-
toggleDialog(e) {
|
3559
|
-
e = e || window.event;
|
3560
|
-
e.preventDefault();
|
3561
|
-
e.stopImmediatePropagation();
|
3562
|
-
// Infer dialog identifier from target element
|
3563
|
-
const
|
3564
|
-
dlg = e.target.id.split('-')[0],
|
3565
|
-
tde = document.getElementById(dlg + '-dlg'),
|
3566
|
-
was_hidden = this.hidden(tde.id);
|
3567
|
-
let mgr = tde.getAttribute('data-manager');
|
3568
|
-
if(mgr) mgr = window[mgr];
|
3569
|
-
// NOTE: prevent modeler from viewing charts while an experiment is running
|
3570
|
-
if(dlg === 'chart' && was_hidden && MODEL.running_experiment) {
|
3571
|
-
UI.notify(UI.NOTICE.NO_CHARTS);
|
3572
|
-
mgr.visible = false;
|
3573
|
-
return;
|
3574
|
-
}
|
3575
|
-
this.toggle(tde.id);
|
3576
|
-
if(mgr) mgr.visible = was_hidden;
|
3577
|
-
// Open at position after last drag (recorded in DOM data attributes)
|
3578
|
-
let t = tde.getAttribute('data-top'),
|
3579
|
-
l = tde.getAttribute('data-left');
|
3580
|
-
// Make dialog appear in screen center the first time it is shown
|
3581
|
-
if(t === null || l === null) {
|
3582
|
-
const cs = window.getComputedStyle(tde);
|
3583
|
-
t = ((window.innerHeight - parseFloat(cs.height)) / 2) + 'px';
|
3584
|
-
l = ((window.innerWidth - parseFloat(cs.width)) / 2) + 'px';
|
3585
|
-
tde.style.top = t;
|
3586
|
-
tde.style.left = l;
|
3587
|
-
}
|
3588
|
-
if(!this.hidden(tde.id)) {
|
3589
|
-
// Add dialog to "showing" list, and adjust z-indices
|
3590
|
-
this.dr_dialog_order.push(tde);
|
3591
|
-
this.reorderDialogs();
|
3592
|
-
// Update the diagram if its manager has been specified
|
3593
|
-
if(mgr) {
|
3594
|
-
mgr.visible = true;
|
3595
|
-
mgr.updateDialog();
|
3596
|
-
if(mgr === DOCUMENTATION_MANAGER) {
|
3597
|
-
if(this.info_line.innerHTML.length === 0) {
|
3598
|
-
mgr.title.innerHTML = 'About Linny-R';
|
3599
|
-
mgr.viewer.innerHTML = mgr.about_linny_r;
|
3600
|
-
mgr.edit_btn.classList.remove('enab');
|
3601
|
-
mgr.edit_btn.classList.add('disab');
|
3602
|
-
}
|
3603
|
-
UI.drawDiagram(MODEL);
|
3604
|
-
}
|
3605
|
-
}
|
3606
|
-
} else {
|
3607
|
-
const doi = this.dr_dialog_order.indexOf(tde);
|
3608
|
-
// NOTE: doi should ALWAYS be >= 0 because dialog WAS showing
|
3609
|
-
if(doi >= 0) {
|
3610
|
-
this.dr_dialog_order.splice(doi, 1);
|
3611
|
-
this.reorderDialogs();
|
3612
|
-
}
|
3613
|
-
if(mgr) {
|
3614
|
-
mgr.visible = true;
|
3615
|
-
if(mgr === DOCUMENTATION_MANAGER) {
|
3616
|
-
mgr.visible = false;
|
3617
|
-
mgr.title.innerHTML = 'Documentation';
|
3618
|
-
UI.drawDiagram(MODEL);
|
3619
|
-
}
|
3620
|
-
}
|
3621
|
-
}
|
3622
|
-
UI.buttons[dlg].classList.toggle('stay-activ');
|
3623
|
-
}
|
3624
|
-
|
3625
|
-
reorderDialogs() {
|
3626
|
-
let z = 10;
|
3627
|
-
for(let i = 0; i < this.dr_dialog_order.length; i++) {
|
3628
|
-
this.dr_dialog_order[i].style.zIndex = z;
|
3629
|
-
z += 5;
|
3630
|
-
}
|
3631
|
-
}
|
3632
|
-
|
3633
3558
|
draggableDialog(d) {
|
3634
3559
|
// Make dialog draggable
|
3635
3560
|
const
|
@@ -3755,7 +3680,7 @@ class GUIController extends Controller {
|
|
3755
3680
|
UI.dr_dialog.style.width = Math.max(minw, w + dw) + 'px';
|
3756
3681
|
UI.dr_dialog.style.height = Math.max(minh, h + dh) + 'px';
|
3757
3682
|
// Update the dialog if its manager has been specified
|
3758
|
-
const mgr = UI.dr_dialog.
|
3683
|
+
const mgr = UI.dr_dialog.dataset.manager;
|
3759
3684
|
if(mgr) window[mgr].updateDialog();
|
3760
3685
|
}
|
3761
3686
|
|
@@ -3766,6 +3691,90 @@ class GUIController extends Controller {
|
|
3766
3691
|
}
|
3767
3692
|
}
|
3768
3693
|
|
3694
|
+
toggleDialog(e) {
|
3695
|
+
// Hide dialog if visible, or show it if not, and update the
|
3696
|
+
// order of appearance so that this dialog appears on top
|
3697
|
+
e = e || window.event;
|
3698
|
+
e.preventDefault();
|
3699
|
+
e.stopImmediatePropagation();
|
3700
|
+
// Infer dialog identifier from target element
|
3701
|
+
const
|
3702
|
+
dlg = e.target.id.split('-')[0],
|
3703
|
+
tde = document.getElementById(dlg + '-dlg');
|
3704
|
+
// NOTE: manager attribute is a string, e.g. 'MONITOR' or 'CHART_MANAGER'
|
3705
|
+
let mgr = tde.dataset.manager,
|
3706
|
+
was_hidden = this.hidden(tde.id);
|
3707
|
+
if(mgr) {
|
3708
|
+
// Dialog has a manager object => let `mgr` point to it
|
3709
|
+
mgr = window[mgr];
|
3710
|
+
// Manager object attributes are more reliable than DOM element
|
3711
|
+
// style attributes, so update the visibility status
|
3712
|
+
was_hidden = !mgr.visible;
|
3713
|
+
}
|
3714
|
+
// NOTE: modeler should not view charts while an experiment is
|
3715
|
+
// running, so do NOT toggle when the Chart Manager is NOT visible
|
3716
|
+
if(dlg === 'chart' && was_hidden && MODEL.running_experiment) {
|
3717
|
+
UI.notify(UI.NOTICE.NO_CHARTS);
|
3718
|
+
return;
|
3719
|
+
}
|
3720
|
+
// Otherwise, toggle the dialog visibility
|
3721
|
+
this.toggle(tde.id);
|
3722
|
+
UI.buttons[dlg].classList.toggle('stay-activ');
|
3723
|
+
if(mgr) mgr.visible = was_hidden;
|
3724
|
+
let t, l;
|
3725
|
+
if(top in tde.dataset && left in tde.dataset) {
|
3726
|
+
// Open at position after last drag (recorded in DOM data attributes)
|
3727
|
+
t = tde.dataset.top;
|
3728
|
+
l = tde.dataset.left;
|
3729
|
+
} else {
|
3730
|
+
// Make dialog appear in screen center the first time it is shown
|
3731
|
+
const cs = window.getComputedStyle(tde);
|
3732
|
+
t = ((window.innerHeight - parseFloat(cs.height)) / 2) + 'px';
|
3733
|
+
l = ((window.innerWidth - parseFloat(cs.width)) / 2) + 'px';
|
3734
|
+
tde.style.top = t;
|
3735
|
+
tde.style.left = l;
|
3736
|
+
}
|
3737
|
+
if(was_hidden) {
|
3738
|
+
// Add activated dialog to "showing" list, and adjust z-indices
|
3739
|
+
this.dr_dialog_order.push(tde);
|
3740
|
+
this.reorderDialogs();
|
3741
|
+
// Update the diagram if its manager has been specified
|
3742
|
+
if(mgr) {
|
3743
|
+
mgr.updateDialog();
|
3744
|
+
if(mgr === DOCUMENTATION_MANAGER) {
|
3745
|
+
if(this.info_line.innerHTML.length === 0) {
|
3746
|
+
mgr.title.innerHTML = 'About Linny-R';
|
3747
|
+
mgr.viewer.innerHTML = mgr.about_linny_r;
|
3748
|
+
mgr.edit_btn.classList.remove('enab');
|
3749
|
+
mgr.edit_btn.classList.add('disab');
|
3750
|
+
}
|
3751
|
+
UI.drawDiagram(MODEL);
|
3752
|
+
}
|
3753
|
+
}
|
3754
|
+
} else {
|
3755
|
+
const doi = this.dr_dialog_order.indexOf(tde);
|
3756
|
+
// NOTE: doi should ALWAYS be >= 0 because dialog WAS showing
|
3757
|
+
if(doi >= 0) {
|
3758
|
+
this.dr_dialog_order.splice(doi, 1);
|
3759
|
+
this.reorderDialogs();
|
3760
|
+
}
|
3761
|
+
if(mgr === DOCUMENTATION_MANAGER) {
|
3762
|
+
mgr.title.innerHTML = 'Documentation';
|
3763
|
+
UI.drawDiagram(MODEL);
|
3764
|
+
}
|
3765
|
+
}
|
3766
|
+
}
|
3767
|
+
|
3768
|
+
reorderDialogs() {
|
3769
|
+
// Set z-index of draggable dialogs according to their order
|
3770
|
+
// (most recently shown or clicked on top)
|
3771
|
+
let z = 10;
|
3772
|
+
for(let i = 0; i < this.dr_dialog_order.length; i++) {
|
3773
|
+
this.dr_dialog_order[i].style.zIndex = z;
|
3774
|
+
z += 5;
|
3775
|
+
}
|
3776
|
+
}
|
3777
|
+
|
3769
3778
|
//
|
3770
3779
|
// Button functionality
|
3771
3780
|
//
|
@@ -5975,7 +5984,7 @@ class GUIMonitor {
|
|
5975
5984
|
(event) => {
|
5976
5985
|
const el = event.target;
|
5977
5986
|
el.classList.add('sel-pb');
|
5978
|
-
MONITOR.showBlock(el.
|
5987
|
+
MONITOR.showBlock(el.dataset.blk);
|
5979
5988
|
},
|
5980
5989
|
false);
|
5981
5990
|
this.progress_bar.appendChild(n);
|
@@ -4856,11 +4856,12 @@ Solver status = ${json.status}`);
|
|
4856
4856
|
return;
|
4857
4857
|
} else {
|
4858
4858
|
// Wait no longer, but warn user that data may be incomplete
|
4859
|
-
dsl = [];
|
4859
|
+
const dsl = [];
|
4860
4860
|
for(let i = 0; i < MODEL.loading_datasets.length; i++) {
|
4861
4861
|
dsl.push(MODEL.loading_datasets[i].displayName);
|
4862
4862
|
}
|
4863
|
-
UI.warn(
|
4863
|
+
UI.warn('Loading of ' + pluralS(dsl.length, 'dataset') + ' (' +
|
4864
|
+
dsl.join(', ') + ') takes too long');
|
4864
4865
|
}
|
4865
4866
|
}
|
4866
4867
|
if(MONITOR.connectToServer()) {
|