linny-r 1.4.2 → 1.4.4

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.
Files changed (50) hide show
  1. package/README.md +162 -74
  2. package/package.json +1 -1
  3. package/server.js +145 -49
  4. package/static/images/check-off-not-same-changed.png +0 -0
  5. package/static/images/check-off-not-same-not-changed.png +0 -0
  6. package/static/images/check-off-same-changed.png +0 -0
  7. package/static/images/check-off-same-not-changed.png +0 -0
  8. package/static/images/check-on-not-same-changed.png +0 -0
  9. package/static/images/check-on-not-same-not-changed.png +0 -0
  10. package/static/images/check-on-same-changed.png +0 -0
  11. package/static/images/check-on-same-not-changed.png +0 -0
  12. package/static/images/eq-not-same-changed.png +0 -0
  13. package/static/images/eq-not-same-not-changed.png +0 -0
  14. package/static/images/eq-same-changed.png +0 -0
  15. package/static/images/eq-same-not-changed.png +0 -0
  16. package/static/images/ne-not-same-changed.png +0 -0
  17. package/static/images/ne-not-same-not-changed.png +0 -0
  18. package/static/images/ne-same-changed.png +0 -0
  19. package/static/images/ne-same-not-changed.png +0 -0
  20. package/static/images/octaeder.svg +993 -0
  21. package/static/images/sort-asc-lead.png +0 -0
  22. package/static/images/sort-asc.png +0 -0
  23. package/static/images/sort-desc-lead.png +0 -0
  24. package/static/images/sort-desc.png +0 -0
  25. package/static/images/sort-not.png +0 -0
  26. package/static/index.html +72 -647
  27. package/static/linny-r.css +199 -417
  28. package/static/scripts/linny-r-gui-actor-manager.js +340 -0
  29. package/static/scripts/linny-r-gui-chart-manager.js +944 -0
  30. package/static/scripts/linny-r-gui-constraint-editor.js +681 -0
  31. package/static/scripts/linny-r-gui-controller.js +4005 -0
  32. package/static/scripts/linny-r-gui-dataset-manager.js +1176 -0
  33. package/static/scripts/linny-r-gui-documentation-manager.js +739 -0
  34. package/static/scripts/linny-r-gui-equation-manager.js +307 -0
  35. package/static/scripts/linny-r-gui-experiment-manager.js +1944 -0
  36. package/static/scripts/linny-r-gui-expression-editor.js +449 -0
  37. package/static/scripts/linny-r-gui-file-manager.js +392 -0
  38. package/static/scripts/linny-r-gui-finder.js +727 -0
  39. package/static/scripts/linny-r-gui-model-autosaver.js +230 -0
  40. package/static/scripts/linny-r-gui-monitor.js +448 -0
  41. package/static/scripts/linny-r-gui-paper.js +2789 -0
  42. package/static/scripts/linny-r-gui-receiver.js +323 -0
  43. package/static/scripts/linny-r-gui-repository-browser.js +819 -0
  44. package/static/scripts/linny-r-gui-scale-unit-manager.js +244 -0
  45. package/static/scripts/linny-r-gui-sensitivity-analysis.js +778 -0
  46. package/static/scripts/linny-r-gui-undo-redo.js +560 -0
  47. package/static/scripts/linny-r-model.js +27 -11
  48. package/static/scripts/linny-r-utils.js +17 -2
  49. package/static/scripts/linny-r-vm.js +31 -12
  50. package/static/scripts/linny-r-gui.js +0 -16761
package/server.js CHANGED
@@ -87,9 +87,10 @@ function getVersionInfo() {
87
87
  info.current_time = new Date(Date.parse(obj.time[info.current]));
88
88
  info.up_to_date = info.current === info.latest;
89
89
  } catch(err) {
90
- // `latest` = 0 indicates that version check failed
90
+ // `latest` = 0 indicates that version check failed.
91
91
  info.latest = 0;
92
92
  }
93
+ clearNewerVersion();
93
94
  if(!info.latest) {
94
95
  console.log(connectionErrorText('Could not connect to https://registry.npmjs.org/'));
95
96
  } else if(!info.up_to_date) {
@@ -145,12 +146,13 @@ const SOLVER = new MILPSolver(SETTINGS, WORKSPACE);
145
146
  // Create launch script
146
147
  createLaunchScript();
147
148
 
148
- // Create the HTTP server
149
+ // Create the HTTP server.
149
150
  const SERVER = http.createServer((req, res) => {
150
151
  const u = new URL(req.url, 'http://127.0.0.1:' + SETTINGS.port);
151
- // When POST, first get all the full body
152
+ // When POST, first get all the full body.
152
153
  if(req.method === 'POST') {
153
154
  let body = '';
155
+ // @@TO DO: For big data requests, string may become too long.
154
156
  req.on('data', (data) => body += data);
155
157
  req.on('end', () => processRequest(req, res, u.pathname, body));
156
158
  } else if(req.method === 'GET') {
@@ -198,7 +200,7 @@ function logAction(msg) {
198
200
 
199
201
  function autoCheck(res) {
200
202
  // Serves a string with the current version number plus info on a
201
- // newer release if this is available
203
+ // newer release if this is available.
202
204
  let check = VERSION_INFO.current + '|';
203
205
  if(VERSION_INFO.up_to_date) {
204
206
  check += 'up-to-date';
@@ -208,22 +210,46 @@ function autoCheck(res) {
208
210
  servePlainText(res, check);
209
211
  }
210
212
 
211
- // HTML page to show then the server is shut down by the user
213
+ function setNewerVersion() {
214
+ // Creates the file "newer_version" in the working directory, so that
215
+ // when the server is run from the standard batch script it will detect
216
+ // that an update is required.
217
+ const nvf = path.join(WORKING_DIRECTORY, 'newer_version');
218
+ try {
219
+ fs.writeFileSync(nvf, VERSION_INFO.latest);
220
+ } catch(err) {
221
+ console.log('WARNING: Failed to create file:', nvf);
222
+ console.log(err);
223
+ }
224
+ }
225
+
226
+ function clearNewerVersion() {
227
+ // Forestalls auto-update by deleting the file "newer_version" that may
228
+ // have been created at start-up from the working directory.
229
+ try {
230
+ fs.unlink(path.join(WORKING_DIRECTORY, 'newer_version'));
231
+ } catch(err) {
232
+ // No action, as error is nogt fatal.
233
+ }
234
+ }
235
+
236
+ // HTML page to show when the server is shut down by the user.
212
237
  // NOTE: on a macOS machine, this is slightly more work
213
- const
214
- OS_TEXT = (PLATFORM === 'darwin' ? [
238
+ const OS_TEXT = {close: '', reopen: ''};
239
+ if(PLATFORM === 'darwin') {
240
+ OS_TEXT.close =
215
241
  `<p>You can close the <em>Terminal</em> window that shows
216
242
  <tt>[Process Terminated]</tt> at the bottom.
217
- </p>`,
243
+ </p>`;
244
+ OS_TEXT.reopen =
218
245
  `open <em>Terminal</em> again, change to your Linny-R directory by typing:
219
246
  </p>
220
247
  <p><code>cd ${WORKING_DIRECTORY}</code></p>
221
- <p>`
222
- ] : [
223
- '',
224
- 'switch to your <em>Command Prompt</em> window '
225
- ]),
226
- SHUTDOWN_MESSAGE = `<!DOCTYPE html>
248
+ <p>`;
249
+ } else {
250
+ OS_TEXT.reopen = 'switch to your <em>Command Prompt</em> window ';
251
+ }
252
+ const SHUTDOWN_MESSAGE = `<!DOCTYPE html>
227
253
  <html lang="en-US">
228
254
  <head>
229
255
  <meta http-equiv="content-type" content="text/html; charset=UTF-8">
@@ -243,13 +269,8 @@ const
243
269
  </style>
244
270
  </head>
245
271
  <body>
246
- <h3>Linny-R server (127.0.0.1) is shutting down</h3>` + OS_TEXT[0] + `
247
- <p>To restart Linny-R, ` + OS_TEXT[1] + ` and then at the prompt` +
248
- (VERSION_INFO.up_to_date ? '' : `
249
- first type:</p>
250
- <p><code>npm update linny-r</code><p>
251
- to upgrade to Linny-R version ${VERSION_INFO.latest}, and then`) +
252
- ` type:</p>
272
+ <h3>Linny-R server (127.0.0.1) is shutting down</h3>${OS_TEXT.close}
273
+ <p>To restart Linny-R, ${OS_TEXT.reopen} and then at the prompt type:</p>
253
274
  <p><code>node node_modules${path.sep}linny-r${path.sep}server</code></p>
254
275
  <p>
255
276
  Then switch back to this window, and click this
@@ -1064,7 +1085,7 @@ function rcvrReport(res, rpath, rfile, run, data, stats, log) {
1064
1085
  }
1065
1086
  }
1066
1087
  }
1067
- if(n) console.log(n + 'report file' + (n > 1 ? 's' : '') + 'purged');
1088
+ if(n) console.log(n + ' report file' + (n > 1 ? 's' : '') + ' purged');
1068
1089
  } catch(err) {
1069
1090
  // Log error, but do not abort.
1070
1091
  console.log(err);
@@ -1207,16 +1228,20 @@ function processRequest(req, res, cmd, data) {
1207
1228
  // NOTE: `data` is a string of form field1=value1&field2=value2& ... etc.
1208
1229
  // regardless of the request method (GET or POST)
1209
1230
  if(permittedFile(cmd)) {
1210
- // Path contains valid MIME file type extension => serve if allowed
1231
+ // Path contains valid MIME file type extension => serve if allowed.
1211
1232
  serveStaticFile(res, cmd);
1212
- } else if(cmd === '/solver/') {
1233
+ return;
1234
+ }
1235
+ // Be permissive w.r.t. leading and trailing slashes.
1236
+ cmd = cmd.replace(/^\/+/, '').replace(/\/+$/, '');
1237
+ if(cmd === 'solver') {
1213
1238
  const
1214
1239
  sp = new URLSearchParams(data),
1215
1240
  action = sp.get('action');
1216
- // NOTE: on remote servers, solver actions require authentication
1241
+ // NOTE: On remote servers, solver actions require authentication.
1217
1242
  if(action === 'logon') {
1218
- // No authentication -- simply return the passed token, "local host" as
1219
- // server name, and the identifier of the solver
1243
+ // No authentication -- simply return the passed token, "local host"
1244
+ // as server name, and the identifier of the solver.
1220
1245
  serveJSON(res,
1221
1246
  {token: 'local host', server: 'local host', solver: SOLVER.id});
1222
1247
  } else if(action === 'png') {
@@ -1229,19 +1254,40 @@ function processRequest(req, res, cmd, data) {
1229
1254
  console.log(msg);
1230
1255
  serveJSON(res, {error: msg});
1231
1256
  }
1232
- } else if(cmd === '/shutdown') {
1233
- // Shut down this server
1257
+ } else if(cmd === 'shutdown') {
1258
+ // Shut down this server WITHOUT updating, and show page with
1259
+ // "shut down" message and restart button.
1260
+ clearNewerVersion();
1234
1261
  serveHTML(res, SHUTDOWN_MESSAGE);
1235
1262
  SERVER.close();
1236
- } else if(cmd === '/auto-check') {
1263
+ } else if(cmd === 'version') {
1264
+ logAction('HERE version = ' + VERSION_INFO.current);
1265
+ servePlainText(res, 'Current version is ' + VERSION_INFO.current);
1266
+ } else if(cmd === 'update') {
1267
+ // Shut down this server silently. When the server was started from
1268
+ // a batch script, this will update via npm, and then restart.
1269
+ // NOTE: Self-protect against overwriting development scripts.
1270
+ if(WORKING_DIRECTORY.indexOf('LTR3') >= 0) {
1271
+ servePlainText(res, 'No version update in development environment');
1272
+ } else {
1273
+ setNewerVersion();
1274
+ servePlainText(res, 'Installing Linny-R version ' + VERSION_INFO.latest);
1275
+ SERVER.close();
1276
+ }
1277
+ } else if(cmd === 'no-update') {
1278
+ // Remove file "newer_version" so no update will take place when
1279
+ // server is shut down.
1280
+ clearNewerVersion();
1281
+ servePlainText(res, 'No update to version ' + VERSION_INFO.latest);
1282
+ } else if(cmd === 'auto-check') {
1237
1283
  autoCheck(res);
1238
- } else if(cmd === '/autosave/') {
1284
+ } else if(cmd === 'autosave') {
1239
1285
  autoSave(res, new URLSearchParams(data));
1240
- } else if(cmd === '/repo/') {
1286
+ } else if(cmd === 'repo') {
1241
1287
  repo(res, new URLSearchParams(data));
1242
- } else if(cmd === '/load-data/') {
1288
+ } else if(cmd === 'load-data') {
1243
1289
  loadData(res, (new URLSearchParams(data)).get('url'));
1244
- } else if(cmd === '/receiver/') {
1290
+ } else if(cmd === 'receiver') {
1245
1291
  receiver(res, new URLSearchParams(data));
1246
1292
  } else {
1247
1293
  serveJSON(res, {error: `Unknown Linny-R request: "${cmd}"`});
@@ -1249,7 +1295,7 @@ function processRequest(req, res, cmd, data) {
1249
1295
  }
1250
1296
 
1251
1297
  function servePlainText(res, msg) {
1252
- // Serve string `msg` as plain text
1298
+ // Serve string `msg` as plain text.
1253
1299
  res.setHeader('Content-Type', 'text/plain');
1254
1300
  res.writeHead(200);
1255
1301
  res.end(msg);
@@ -1334,8 +1380,9 @@ function convertSVGtoPNG(req, res, sp) {
1334
1380
  // Enclose paths in double quotes if they contain spaces
1335
1381
  if(cmd.indexOf(' ') >= 0) cmd = `"${cmd}"`;
1336
1382
  if(svg.indexOf(' ') >= 0) svg = `"${svg}"`;
1337
- child_process.exec(cmd + ' --export-type=png --export-dpi=' +
1338
- SETTINGS.dpi + ' ' + svg,
1383
+ cmd += ` --export-type=png --export-dpi=${SETTINGS.dpi} ${svg}`;
1384
+ console.log(cmd);
1385
+ child_process.exec(cmd,
1339
1386
  (error, stdout, stderr) => {
1340
1387
  let ext = '.svg';
1341
1388
  console.log(stdout);
@@ -1343,8 +1390,24 @@ function convertSVGtoPNG(req, res, sp) {
1343
1390
  console.log('WARNING: Failed to run Inkscape --', error);
1344
1391
  console.log(stderr);
1345
1392
  } else {
1346
- ext = '.png';
1347
- // Delete the SVG
1393
+ // Look for the PNG file.
1394
+ const mode = fs.constants.R_OK | fs.constants.W_O;
1395
+ try {
1396
+ fs.accessSync(fp + '.png', mode);
1397
+ ext = '.png';
1398
+ } catch(err) {
1399
+ // NOTE: Inkscape 1.3 adds ".png" to the original file name,
1400
+ // so this may end on ".svg.png"
1401
+ try {
1402
+ fs.accessSync(fp + '.svg.png', mode);
1403
+ // If found, rename the PNG file.
1404
+ fs.renameSync(fp + '.svg.png', fp + '.png');
1405
+ ext = '.png';
1406
+ } catch(err) {
1407
+ console.log('WARNING: Inkscape did not output a PNG file');
1408
+ }
1409
+ }
1410
+ // Delete the SVG file.
1348
1411
  try {
1349
1412
  fs.unlinkSync(fp + '.svg');
1350
1413
  } catch(error) {
@@ -1623,9 +1686,17 @@ function commandLineSettings() {
1623
1686
  }
1624
1687
  // Verify that Inkscape is installed
1625
1688
  if(settings.inkscape) {
1626
- // NOTE: on Windows, the command line version is a .com file
1627
- const ip = path.join(settings.inkscape,
1689
+ // NOTE: On Windows, the command line version is a .com file
1690
+ let ip = path.join(settings.inkscape,
1628
1691
  'inkscape' + (PLATFORM.startsWith('win') ? '.com' : ''));
1692
+ try {
1693
+ fs.accessSync(ip, fs.constants.X_OK);
1694
+ } catch(err) {
1695
+ // NOTE: As of Inkscape version 1.3, the command line executable
1696
+ // is called inkscapecom(.com)
1697
+ ip = path.join(settings.inkscape,
1698
+ 'inkscapecom' + (PLATFORM.startsWith('win') ? '.com' : ''));
1699
+ }
1629
1700
  try {
1630
1701
  fs.accessSync(ip, fs.constants.X_OK);
1631
1702
  console.log('Path to Inkscape:', settings.inkscape);
@@ -1698,28 +1769,53 @@ function createLaunchScript() {
1698
1769
  // Creates platform-specific script with Linny-R start-up command
1699
1770
  const lines = [
1700
1771
  '# The first line (without the comment symbol #) should be like this:',
1701
- '# cd ',
1772
+ '',
1702
1773
  '',
1703
1774
  '# Then this command to launch the Linny-R server should work:',
1704
- 'node ' + path.join('node_modules', 'linny-r', 'server') + ' launch'
1775
+ '',
1776
+ '# After shut-down, check whether new version should be installed:'
1705
1777
  ];
1778
+ lines[2] = 'cd ' + WORKING_DIRECTORY;
1706
1779
  let sp;
1707
1780
  if(PLATFORM.startsWith('win')) {
1708
1781
  sp = path.join(WORKING_DIRECTORY, 'linny-r.bat');
1709
- lines[1] += 'C:\\path\\to\\main\\Linny-R\\directory';
1782
+ lines.push(
1783
+ ':loop',
1784
+ 'if exist newer_version (',
1785
+ ' del newer_version',
1786
+ ' npm update linny-r',
1787
+ ' node node_modules\\linny-r\\server',
1788
+ ' goto loop',
1789
+ ')');
1790
+ lines[1] = '# cd C:\\path\\to\\main\\Linny-R\\directory';
1791
+ lines[4] = 'node node_modules\\linny-r\\server launch';
1710
1792
  } else {
1711
1793
  sp = path.join(WORKING_DIRECTORY, 'linny-r.command');
1712
- lines[1] += '/path/to/main/Linny-R/directory';
1794
+ lines.push(
1795
+ 'while test -f newer_version; do',
1796
+ ' unlink newer_version',
1797
+ ' npm update linny-r',
1798
+ ' node node_modules/linny-r/server',
1799
+ 'done');
1800
+ lines[1] = '# cd /path/to/main/Linny-R/directory';
1801
+ lines[4] = 'node node_modules/linny-r/server launch';
1713
1802
  }
1714
- lines[2] = 'cd ' + WORKING_DIRECTORY;
1715
1803
  try {
1804
+ let make_script = false,
1805
+ code = lines.join(os.EOL);
1806
+ if(PLATFORM.startsWith('win')) code = code.replaceAll('#', '::');
1716
1807
  try {
1717
1808
  fs.accessSync(sp);
1809
+ // Only write the script content if the file has not been customized
1810
+ // by the user...
1811
+ const data = fs.readFileSync(sp, 'utf-8');
1812
+ make_script = code.indexOf(data) >= 0;
1718
1813
  } catch(err) {
1719
- // Only write the script content if the file it does not yet exist
1814
+ // ... or if it does not exist yet.
1815
+ make_script = true;
1816
+ }
1817
+ if(make_script) {
1720
1818
  console.log('Creating launch script:', sp);
1721
- let code = lines.join(os.EOL);
1722
- if(PLATFORM.startsWith('win')) code = code.replaceAll('#', '::');
1723
1819
  fs.writeFileSync(sp, code, 'utf8');
1724
1820
  }
1725
1821
  } catch(err) {