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 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 (April 2023) this is 20.0.0.
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.0.0.
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.21
174
- Node.js version: v20.0.0
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) does **not** add the application to the PATH variable,
290
- so you need to do this yourself.
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
- model=[path] will load model file in [path]
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(bcode) {
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: bcode,
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
- const fp = anyOSpath(url);
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
- //global.REPOSITORY_BROWSER = new ConsoleRepositoryBrowser(), // still to re-code
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
- console.log(od[0]);
994
- console.log(od[1]);
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.2.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 Unix path notation (with slashes) to
851
- // Windows notation if needed
852
- if(p.indexOf('/') < 0) return p;
853
- p = p.split('/');
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(p[0].length === 0) {
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
- path[0] += path.sep;
859
+ parts[0] += path.sep;
861
860
  }
862
861
  // Reassemble path for the OS of this machine
863
- return path.join(...p);
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
- const fp = anyOSpath(url);
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
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-2022 Delft University of Technology
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 (Ctrl-V)">
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">&#x25C1;</span>
326
+ <span id="issue-nr"></span>
327
+ <span id="next-issue">&#x25B7;</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="Clone selection (Ctrl-C)">
355
+ title="Copy selection (Ctrl-C) &ndash; 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 &ne; 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 &ne; 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 &ne; 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>:&nbsp;<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
- <input id="series-url" type="text"
1680
- placeholder="(URL or path on local host)"> </div>
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">
@@ -10,7 +10,7 @@ file that implements the graphical user interface for Linny-R.
10
10
  */
11
11
 
12
12
  /*
13
- Copyright (c) 2017-2022 Delft University of Technology
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
- td.modif::before {
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
- td.outcome::before {
2150
+ div.outcome::before {
2101
2151
  content: ' \25C8';
2102
2152
  color: #b00080;
2103
2153
  margin-right: 1px;
2104
2154
  }
2105
2155
 
2106
- td.array::before {
2156
+ div.array::before {
2107
2157
  content: ' \2263';
2108
2158
  color: #b00080;
2109
2159
  margin-right: 1px;
2110
2160
  }
2111
2161
 
2112
- td.series::before {
2162
+ div.series::before {
2113
2163
  content: ' \28B8';
2114
2164
  color: #b00080;
2115
2165
  margin-left: -4px;
2116
2166
  }
2117
2167
 
2118
- td.outcome.modif::before {
2168
+ div.outcome.modif::before {
2119
2169
  content: ' \25C8\2045';
2120
2170
  color: #b00080;
2121
2171
  margin-right: 2px;
2122
2172
  }
2123
2173
 
2124
- td.array.modif::before {
2174
+ div.array.modif::before {
2125
2175
  content: ' \2263\2045';
2126
2176
  color: #b00080;
2127
2177
  margin-right: 1px;
2128
2178
  }
2129
2179
 
2130
- td.series.modif::before {
2180
+ div.series.modif::before {
2131
2181
  content: ' \28B8\2045';
2132
2182
  color: #b00080;
2133
2183
  margin-left: -4px;
2134
2184
  }
2135
2185
 
2136
- td.blackbox::before {
2186
+ div.blackbox::before {
2137
2187
  content: ' \25FC';
2138
2188
  color: #b00080;
2139
2189
  }
2140
2190
 
2141
- td.blackbox.series::before {
2191
+ div.blackbox.series::before {
2142
2192
  content: ' \25FC\28B8';
2143
2193
  color: #b00080;
2144
2194
  }
2145
2195
 
2146
- td.blackbox.array::before {
2196
+ div.blackbox.array::before {
2147
2197
  content: ' \25FC\2263';
2148
2198
  color: #b00080;
2149
2199
  }
2150
2200
 
2151
- td.blackbox.outcome::before {
2201
+ div.blackbox.outcome::before {
2152
2202
  content: ' \25FC\25C8';
2153
2203
  color: #b00080;
2154
2204
  }
2155
2205
 
2156
- td.blackbox.modif::before {
2206
+ div.blackbox.modif::before {
2157
2207
  content: ' \25FC\2045';
2158
2208
  color: #b00080;
2159
2209
  }
2160
2210
 
2161
- td.blackbox.series.modif::before {
2211
+ div.blackbox.series.modif::before {
2162
2212
  content: ' \25FC\28B8\2045';
2163
2213
  color: #b00080;
2164
2214
  }
2165
2215
 
2166
- td.blackbox.array.modif::before {
2216
+ div.blackbox.array.modif::before {
2167
2217
  content: ' \25FC\2263\2045';
2168
2218
  color: #b00080;
2169
2219
  }
2170
2220
 
2171
- td.blackbox.outcome.modif::before {
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: 3px;
2411
- width: calc(100% - 8px);
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 {