linny-r 1.2.1 → 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/README.md +6 -6
- package/console.js +139 -7
- package/package.json +2 -2
- package/server.js +15 -11
- package/static/images/paperclip.png +0 -0
- package/static/images/paste.png +0 -0
- package/static/index.html +40 -8
- package/static/linny-r.css +97 -19
- package/static/scripts/linny-r-ctrl.js +22 -4
- package/static/scripts/linny-r-gui.js +723 -140
- package/static/scripts/linny-r-model.js +185 -29
- package/static/scripts/linny-r-utils.js +49 -11
- package/static/scripts/linny-r-vm.js +32 -20
package/README.md
CHANGED
@@ -25,7 +25,7 @@ Technical documentation will be developed on GitHub: https://github.com/pwgbots/
|
|
25
25
|
Linny-R is developed as a JavaScript package, and requires that **Node.js** is installed on your computer.
|
26
26
|
This software can be downloaded from <a href="https://nodejs.org" target="_blank">https://nodejs.org</a>.
|
27
27
|
Make sure that you choose the correct installer for your computer.
|
28
|
-
Linny-R is developed using the _current_ release. Presently (
|
28
|
+
Linny-R is developed using the _current_ release. Presently (June 2023) this is 20.3.0.
|
29
29
|
|
30
30
|
Run the installer and accept the default settings.
|
31
31
|
There is **no** need to install the optional _Tools for Native Modules_.
|
@@ -36,7 +36,7 @@ Verify the installation by typing:
|
|
36
36
|
|
37
37
|
``node --version``
|
38
38
|
|
39
|
-
The response should be the version number of Node.js, for example: v20.
|
39
|
+
The response should be the version number of Node.js, for example: v20.3.0.
|
40
40
|
|
41
41
|
## Installing Linny-R
|
42
42
|
It is advisable to install Linny-R in a directory on your computer, not in a cloud.
|
@@ -170,8 +170,8 @@ Open the Command Line Interface (CLI) of your computer, change to your `WORKING_
|
|
170
170
|
This response should be something similar to:
|
171
171
|
|
172
172
|
<pre>
|
173
|
-
Node.js server for Linny-R version 1.1
|
174
|
-
Node.js version: v20.
|
173
|
+
Node.js server for Linny-R version 1.2.1
|
174
|
+
Node.js version: v20.3.0
|
175
175
|
... etc.
|
176
176
|
</pre>
|
177
177
|
|
@@ -286,8 +286,8 @@ To install Inkscape, please look here: https://inkscape.org/release
|
|
286
286
|
Linny-R will automatically detect whether Inkscape is installed by searching for it in the environment variable PATH on your computer.
|
287
287
|
On a macOS computer, Linny-R will look for Inkscape in /Applications/Inkscape.app/Contents/MacOS.
|
288
288
|
|
289
|
-
**NOTE:** The current installation wizard for Inkscape (version 1.2)
|
290
|
-
|
289
|
+
**NOTE:** The current installation wizard for Inkscape (version 1.2.2) may **not** add the application to the PATH variable.
|
290
|
+
Please check whether you need to do this yourself.
|
291
291
|
|
292
292
|
## Using Linny-R console
|
293
293
|
|
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)
|
@@ -196,7 +200,7 @@ class ConsoleMonitor {
|
|
196
200
|
return true;
|
197
201
|
}
|
198
202
|
|
199
|
-
submitBlockToSolver(
|
203
|
+
submitBlockToSolver() {
|
200
204
|
let top = MODEL.timeout_period;
|
201
205
|
if(VM.max_solver_time && top > VM.max_solver_time) {
|
202
206
|
top = VM.max_solver_time;
|
@@ -211,7 +215,7 @@ class ConsoleMonitor {
|
|
211
215
|
token: VM.solver_token,
|
212
216
|
block: VM.block_count,
|
213
217
|
round: VM.round_sequence[VM.current_round],
|
214
|
-
data:
|
218
|
+
data: VM.lines,
|
215
219
|
timeout: top
|
216
220
|
}));
|
217
221
|
VM.processServerResponse(data);
|
@@ -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
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "linny-r",
|
3
|
-
"version": "1.
|
3
|
+
"version": "1.3.1",
|
4
4
|
"description": "Executable graphical language with WYSIWYG editor for MILP models",
|
5
5
|
"main": "server.js",
|
6
6
|
"scripts": {
|
@@ -29,4 +29,4 @@
|
|
29
29
|
"url": "https://github.com/pwgbots/linny-r/issues"
|
30
30
|
},
|
31
31
|
"homepage": "https://github.com/pwgbots/linny-r#readme"
|
32
|
-
}
|
32
|
+
}
|
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/images/paste.png
CHANGED
Binary file
|
package/static/index.html
CHANGED
@@ -28,7 +28,7 @@ implementation is made available via the Linny-R web site.
|
|
28
28
|
-->
|
29
29
|
|
30
30
|
<!--
|
31
|
-
Copyright (c) 2017-
|
31
|
+
Copyright (c) 2017-2023 Delft University of Technology
|
32
32
|
|
33
33
|
Permission is hereby granted, free of charge, to any person obtaining
|
34
34
|
a copy of this software and associated documentation files (the
|
@@ -239,7 +239,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
239
239
|
<img id="load-btn" class="btn enab" src="images/open.png"
|
240
240
|
title="Load model (Ctrl-L)">
|
241
241
|
<img id="settings-btn" class="btn enab" src="images/settings.png"
|
242
|
-
title="Change settings (
|
242
|
+
title="Change model settings (Alt-M)">
|
243
243
|
<img id="save-btn" class="btn enab" src="images/save.png"
|
244
244
|
title="Save model (Ctrl-S)">
|
245
245
|
<img id="repository-btn" class="btn enab" src="images/repository.png"
|
@@ -321,6 +321,11 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
321
321
|
<td id="step-sep">
|
322
322
|
<img id="stepforward-btn" class="sbtn senab" src="images/forward.png">
|
323
323
|
</td>
|
324
|
+
<td id="issue-panel">
|
325
|
+
<span id="prev-issue">◁</span>
|
326
|
+
<span id="issue-nr"></span>
|
327
|
+
<span id="next-issue">▷</span>
|
328
|
+
</td>
|
324
329
|
<td id="info-line" title="Status bar"></td>
|
325
330
|
<td id="autosave-sep">
|
326
331
|
<img id="autosave-btn" class="btn enab" src="images/restore.png">
|
@@ -347,7 +352,9 @@ and move the cursor over the status bar">
|
|
347
352
|
<img id="note-btn" class="btn toggle enab sep" src="images/note.png"
|
348
353
|
title="Add note">
|
349
354
|
<img id="clone-btn" class="btn disab sep" src="images/clone.png"
|
350
|
-
title="
|
355
|
+
title="Copy selection (Ctrl-C) – Alt-click to clone (Alt-C)">
|
356
|
+
<img id="paste-btn" class="btn disab sep" src="images/paste.png"
|
357
|
+
title="Paste selection (Ctrl-V) -- WORK IN PROGESS!">
|
351
358
|
<img id="delete-btn" class="btn disab sep" src="images/delete.png"
|
352
359
|
title="Delete">
|
353
360
|
<img id="undo-btn" class="btn enab" src="images/undo.png"
|
@@ -1530,7 +1537,8 @@ NOTE: Unit symbols are case-sensitive, so BTU ≠ Btu">
|
|
1530
1537
|
<div id="ds-filter-bar">
|
1531
1538
|
<input id="ds-filter-text" type="text"
|
1532
1539
|
placeholder="(name filtering pattern)"
|
1533
|
-
title="Pattern may contain logical & (AND), | (OR) and ^ (NOT)
|
1540
|
+
title="Pattern may contain logical & (AND), | (OR) and ^ (NOT)
|
1541
|
+
Start with = to find exact match, with ~ to match first characters">
|
1534
1542
|
</div>
|
1535
1543
|
<div id="dataset-scroll-area">
|
1536
1544
|
<table id="dataset-table">
|
@@ -1584,6 +1592,9 @@ NOTE: Unit symbols are case-sensitive, so BTU ≠ Btu">
|
|
1584
1592
|
<img id="ds-delete-modif-btn" class="btn disab"
|
1585
1593
|
src="images/delete.png" title="Delete selected modifier"
|
1586
1594
|
style="margin-left: 20px">
|
1595
|
+
<img id="ds-convert-modif-btn" class="btn enab blink"
|
1596
|
+
src="images/dataset.png" title="Convert modifiers to prefixed datasets"
|
1597
|
+
style="margin-left: 20px; display: none">
|
1587
1598
|
</div>
|
1588
1599
|
<div id="dataset-resize" class="resizer"></div>
|
1589
1600
|
</div>
|
@@ -1604,7 +1615,7 @@ NOTE: Unit symbols are case-sensitive, so BTU ≠ Btu">
|
|
1604
1615
|
<div id="rename-dataset-modal" class="modal">
|
1605
1616
|
<div id="rename-dataset-dlg" class="inp-dlg">
|
1606
1617
|
<div class="dlg-title">
|
1607
|
-
Rename dataset
|
1618
|
+
<span id="rename-dataset-title">Rename dataset</span>
|
1608
1619
|
<img class="cancel-btn" src="images/cancel.png">
|
1609
1620
|
<img class="ok-btn" src="images/ok.png">
|
1610
1621
|
</div>
|
@@ -1643,6 +1654,23 @@ NOTE: * and ? will be interpreted as wildcards"
|
|
1643
1654
|
</div>
|
1644
1655
|
</div>
|
1645
1656
|
|
1657
|
+
<!-- the CONVERT MODIFIER prompts for the new name of a dataset -->
|
1658
|
+
<div id="convert-modifiers-modal" class="modal">
|
1659
|
+
<div id="convert-modifiers-dlg" class="inp-dlg">
|
1660
|
+
<div class="dlg-title">
|
1661
|
+
Convert modifiers to datasets
|
1662
|
+
<img class="cancel-btn" src="images/cancel.png">
|
1663
|
+
<img class="ok-btn" src="images/ok.png">
|
1664
|
+
</div>
|
1665
|
+
<div style="margin: 4px">
|
1666
|
+
Each modifier will become a dataset
|
1667
|
+
<tt><em>prefix</em>: <em>selector</em></tt>
|
1668
|
+
using this prefix:
|
1669
|
+
</div>
|
1670
|
+
<input id="convert-modifiers-prefix" type="text" autocomplete="off">
|
1671
|
+
</div>
|
1672
|
+
</div>
|
1673
|
+
|
1646
1674
|
<!-- the SERIES dialog presents the properties of the time series
|
1647
1675
|
data of the selected dataset, including a text area for copy/paste
|
1648
1676
|
of numbers while showing the line count and checking for correct
|
@@ -1676,8 +1704,11 @@ NOTE: * and ? will be interpreted as wildcards"
|
|
1676
1704
|
<!-- options are added by DATASET_MANAGER -->
|
1677
1705
|
</select>
|
1678
1706
|
<div id="series-remote">
|
1679
|
-
|
1680
|
-
|
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>
|
1681
1712
|
<div id="series-data-lbl">Series data:</div>
|
1682
1713
|
<textarea id="series-data" autocomplete="off"
|
1683
1714
|
autocorrect="off" autocapitalize="off" spellcheck="false">
|
@@ -1859,7 +1890,8 @@ NOTE: * and ? will be interpreted as wildcards"
|
|
1859
1890
|
<div style="padding: 4px; width: 100px">
|
1860
1891
|
<div id="variable-color-div">
|
1861
1892
|
<div style="display:inline-block; vertical-align:top">Color:</div>
|
1862
|
-
<div id="variable-color"></div>
|
1893
|
+
<div id="variable-color" title="Click to copy, Shift-click to paste"></div>
|
1894
|
+
<div id="variable-paste-color"></div>
|
1863
1895
|
</div>
|
1864
1896
|
<div id="variable-scale-div" style="margin-top: 3px">
|
1865
1897
|
<div style="display:inline-block; vertical-align:top; margin-top: 2px">
|
package/static/linny-r.css
CHANGED
@@ -10,7 +10,7 @@ file that implements the graphical user interface for Linny-R.
|
|
10
10
|
*/
|
11
11
|
|
12
12
|
/*
|
13
|
-
Copyright (c) 2017-
|
13
|
+
Copyright (c) 2017-2023 Delft University of Technology
|
14
14
|
|
15
15
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
16
16
|
of this software and associated documentation files (the "Software"), to deal
|
@@ -413,6 +413,7 @@ textarea {
|
|
413
413
|
#zoom-sep,
|
414
414
|
#step-sep,
|
415
415
|
#info-line,
|
416
|
+
#issue-panel,
|
416
417
|
#autosave-sep {
|
417
418
|
border-right: 1px solid #e2d8e8;
|
418
419
|
}
|
@@ -507,6 +508,36 @@ div.notification-msg.first-msg {
|
|
507
508
|
color: black;
|
508
509
|
}
|
509
510
|
|
511
|
+
#issue-panel {
|
512
|
+
display: none;
|
513
|
+
background-color: Yellow;
|
514
|
+
white-space: nowrap;
|
515
|
+
padding: 0 3px;
|
516
|
+
}
|
517
|
+
|
518
|
+
#issue-nr {
|
519
|
+
padding: 0 4px;
|
520
|
+
cursor: pointer;
|
521
|
+
}
|
522
|
+
|
523
|
+
#prev-issue,
|
524
|
+
#next-issue {
|
525
|
+
cursor: pointer;
|
526
|
+
}
|
527
|
+
|
528
|
+
#prev-issue:hover,
|
529
|
+
#issue-nr:hover,
|
530
|
+
#next-issue:hover {
|
531
|
+
color: Orange;
|
532
|
+
}
|
533
|
+
|
534
|
+
#prev-issue.disab,
|
535
|
+
#next-issue.disab {
|
536
|
+
cursor: default;
|
537
|
+
pointer-events: none;
|
538
|
+
color: #e8d898;
|
539
|
+
}
|
540
|
+
|
510
541
|
/* Modal dialogs cover the entire browser window with a dark,
|
511
542
|
semi-transparent DIV that ignores mouse events. Their z-index
|
512
543
|
is 100+ so that they cover "stay-on-top" dialogs. */
|
@@ -2091,84 +2122,103 @@ tr.def-sel {
|
|
2091
2122
|
font-weight: bold;
|
2092
2123
|
}
|
2093
2124
|
|
2094
|
-
|
2125
|
+
tr.dataset > td > div {
|
2126
|
+
display: inline-block;
|
2127
|
+
}
|
2128
|
+
|
2129
|
+
div.ds-indent {
|
2130
|
+
color: #909090;
|
2131
|
+
text-align: right;
|
2132
|
+
font-weight: normal !important;
|
2133
|
+
margin-right: 2px;
|
2134
|
+
margin-left: 3px;
|
2135
|
+
}
|
2136
|
+
|
2137
|
+
div.tree-btn {
|
2138
|
+
color: #909090;
|
2139
|
+
margin-right: 3px;
|
2140
|
+
cursor: pointer;
|
2141
|
+
margin-left: -13px;
|
2142
|
+
}
|
2143
|
+
|
2144
|
+
div.modif::before {
|
2095
2145
|
content: ' \2045';
|
2096
2146
|
color: #b00080;
|
2097
2147
|
margin-right: 2px;
|
2098
2148
|
}
|
2099
2149
|
|
2100
|
-
|
2150
|
+
div.outcome::before {
|
2101
2151
|
content: ' \25C8';
|
2102
2152
|
color: #b00080;
|
2103
2153
|
margin-right: 1px;
|
2104
2154
|
}
|
2105
2155
|
|
2106
|
-
|
2156
|
+
div.array::before {
|
2107
2157
|
content: ' \2263';
|
2108
2158
|
color: #b00080;
|
2109
2159
|
margin-right: 1px;
|
2110
2160
|
}
|
2111
2161
|
|
2112
|
-
|
2162
|
+
div.series::before {
|
2113
2163
|
content: ' \28B8';
|
2114
2164
|
color: #b00080;
|
2115
2165
|
margin-left: -4px;
|
2116
2166
|
}
|
2117
2167
|
|
2118
|
-
|
2168
|
+
div.outcome.modif::before {
|
2119
2169
|
content: ' \25C8\2045';
|
2120
2170
|
color: #b00080;
|
2121
2171
|
margin-right: 2px;
|
2122
2172
|
}
|
2123
2173
|
|
2124
|
-
|
2174
|
+
div.array.modif::before {
|
2125
2175
|
content: ' \2263\2045';
|
2126
2176
|
color: #b00080;
|
2127
2177
|
margin-right: 1px;
|
2128
2178
|
}
|
2129
2179
|
|
2130
|
-
|
2180
|
+
div.series.modif::before {
|
2131
2181
|
content: ' \28B8\2045';
|
2132
2182
|
color: #b00080;
|
2133
2183
|
margin-left: -4px;
|
2134
2184
|
}
|
2135
2185
|
|
2136
|
-
|
2186
|
+
div.blackbox::before {
|
2137
2187
|
content: ' \25FC';
|
2138
2188
|
color: #b00080;
|
2139
2189
|
}
|
2140
2190
|
|
2141
|
-
|
2191
|
+
div.blackbox.series::before {
|
2142
2192
|
content: ' \25FC\28B8';
|
2143
2193
|
color: #b00080;
|
2144
2194
|
}
|
2145
2195
|
|
2146
|
-
|
2196
|
+
div.blackbox.array::before {
|
2147
2197
|
content: ' \25FC\2263';
|
2148
2198
|
color: #b00080;
|
2149
2199
|
}
|
2150
2200
|
|
2151
|
-
|
2201
|
+
div.blackbox.outcome::before {
|
2152
2202
|
content: ' \25FC\25C8';
|
2153
2203
|
color: #b00080;
|
2154
2204
|
}
|
2155
2205
|
|
2156
|
-
|
2206
|
+
div.blackbox.modif::before {
|
2157
2207
|
content: ' \25FC\2045';
|
2158
2208
|
color: #b00080;
|
2159
2209
|
}
|
2160
2210
|
|
2161
|
-
|
2211
|
+
div.blackbox.series.modif::before {
|
2162
2212
|
content: ' \25FC\28B8\2045';
|
2163
2213
|
color: #b00080;
|
2164
2214
|
}
|
2165
2215
|
|
2166
|
-
|
2216
|
+
div.blackbox.array.modif::before {
|
2167
2217
|
content: ' \25FC\2263\2045';
|
2168
2218
|
color: #b00080;
|
2169
2219
|
}
|
2170
2220
|
|
2171
|
-
|
2221
|
+
div.blackbox.outcome.modif::before {
|
2172
2222
|
content: ' \25FC\25C8\2045';
|
2173
2223
|
color: #b00080;
|
2174
2224
|
}
|
@@ -2294,6 +2344,17 @@ td.equation-expression {
|
|
2294
2344
|
bottom: 5px;
|
2295
2345
|
}
|
2296
2346
|
|
2347
|
+
/* CONVERT MODIFIERS modal dialog */
|
2348
|
+
#convert-modifiers-dlg {
|
2349
|
+
width: 220px;
|
2350
|
+
height: 84px;
|
2351
|
+
}
|
2352
|
+
|
2353
|
+
#convert-modifiers-prefix {
|
2354
|
+
margin: 3px;
|
2355
|
+
width: calc(100% - 8px);
|
2356
|
+
}
|
2357
|
+
|
2297
2358
|
/* SERIES modal dialog */
|
2298
2359
|
#series-dlg {
|
2299
2360
|
width: 165px;
|
@@ -2407,12 +2468,18 @@ td.equation-expression {
|
|
2407
2468
|
#series-remote {
|
2408
2469
|
position: absolute;
|
2409
2470
|
top: 134px;
|
2410
|
-
left:
|
2411
|
-
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;
|
2412
2479
|
}
|
2413
2480
|
|
2414
2481
|
#series-url {
|
2415
|
-
width: 100
|
2482
|
+
width: calc(100% - 17px);
|
2416
2483
|
font-size: 12px;
|
2417
2484
|
}
|
2418
2485
|
|
@@ -2738,6 +2805,17 @@ img.v-disab {
|
|
2738
2805
|
height: 15px;
|
2739
2806
|
border: 1px solid Black;
|
2740
2807
|
background-color: #c00000;
|
2808
|
+
cursor: pointer;
|
2809
|
+
}
|
2810
|
+
|
2811
|
+
#variable-paste-color {
|
2812
|
+
display: none;
|
2813
|
+
vertical-align: top;
|
2814
|
+
margin-left: 3px;
|
2815
|
+
width: 13px;
|
2816
|
+
height: 13px;
|
2817
|
+
border: 1px solid Silver;
|
2818
|
+
background-color: #c00000;
|
2741
2819
|
}
|
2742
2820
|
|
2743
2821
|
#variable-scale {
|