linny-r 1.6.7 → 1.7.0

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/server.js CHANGED
@@ -137,13 +137,13 @@ const MILPSolver = require('./static/scripts/linny-r-milp.js');
137
137
  // - workspace=[path] will create workspace in [path] instead of (main)/user
138
138
  const SETTINGS = commandLineSettings();
139
139
 
140
- // The workspace defines the paths to directories where Linny-R can write files
140
+ // The workspace defines the paths to directories where Linny-R can write files.
141
141
  const WORKSPACE = createWorkspace();
142
142
 
143
- // Initialize the solver
144
- const SOLVER = new MILPSolver(SETTINGS, WORKSPACE);
143
+ // Initialize the solver.
144
+ const SOLVER = new MILPSolver(SETTINGS.preferred_solver, WORKSPACE);
145
145
 
146
- // Create launch script
146
+ // Create launch script.
147
147
  createLaunchScript();
148
148
 
149
149
  // Create the HTTP server.
@@ -1262,11 +1262,24 @@ function processRequest(req, res, cmd, data) {
1262
1262
  // NOTE: On remote servers, solver actions require authentication.
1263
1263
  if(action === 'logon') {
1264
1264
  // No authentication -- simply return the passed token, "local host"
1265
- // as server name, and the identifier of the solver.
1266
- serveJSON(res,
1267
- {token: 'local host', server: 'local host', solver: SOLVER.id});
1265
+ // as server name, the name of the solver, the list of installed
1266
+ // solvers, and the Linny-R directory.
1267
+ serveJSON(res, {
1268
+ token: 'local host',
1269
+ server: 'local host',
1270
+ solver: SOLVER.id,
1271
+ solver_list: Object.keys(SOLVER.solver_list),
1272
+ path: WORKING_DIRECTORY
1273
+ });
1268
1274
  } else if(action === 'png') {
1269
1275
  convertSVGtoPNG(req, res, sp);
1276
+ } else if(action === 'change') {
1277
+ const sid = sp.get('solver');
1278
+ if(SOLVER.changeDefault(sid)) {
1279
+ servePlainText(res, 'Default solver set to ' + SOLVER.name);
1280
+ } else {
1281
+ servePlainText(res, 'WARNING: Failed to change solver to ' + sid);
1282
+ }
1270
1283
  } else if(action === 'solve') {
1271
1284
  serveJSON(res, SOLVER.solveBlock(sp));
1272
1285
  } else {
@@ -1282,7 +1295,6 @@ function processRequest(req, res, cmd, data) {
1282
1295
  serveHTML(res, SHUTDOWN_MESSAGE);
1283
1296
  SERVER.close();
1284
1297
  } else if(cmd === 'version') {
1285
- logAction('HERE version = ' + VERSION_INFO.current);
1286
1298
  servePlainText(res, 'Current version is ' + VERSION_INFO.current);
1287
1299
  } else if(cmd === 'update') {
1288
1300
  // Shut down this server silently. When the server was started from
@@ -1529,8 +1541,6 @@ function commandLineSettings() {
1529
1541
  launch: false,
1530
1542
  port: 5050,
1531
1543
  preferred_solver: '',
1532
- solver: '',
1533
- solver_path: '',
1534
1544
  user_dir: path.join(WORKING_DIRECTORY, 'user')
1535
1545
  };
1536
1546
  const
@@ -1600,142 +1610,36 @@ Possible options are:
1600
1610
  }
1601
1611
  }
1602
1612
  }
1603
- // Check whether MILP solver(s) and Inkscape have been installed
1613
+ // Check whether Inkscape has been installed.
1604
1614
  const path_list = process.env.PATH.split(path.delimiter);
1605
- let gurobi_path = '',
1606
- scip_path = '',
1607
- cplex_path = '',
1608
- match,
1609
- max_v = -1;
1610
1615
  for(let i = 0; i < path_list.length; i++) {
1611
- match = path_list[i].match(/gurobi(\d+)/i);
1612
- if(match && parseInt(match[1]) > max_v) {
1613
- gurobi_path = path_list[i];
1614
- max_v = parseInt(match[1]);
1615
- }
1616
- match = path_list[i].match(/[\/\\]cplex[\/\\]bin/i);
1617
- if(match) {
1618
- cplex_path = path_list[i];
1619
- } else {
1620
- // NOTE: CPLEX may create its own environment variable for its paths
1621
- match = path_list[i].match(/%(.*cplex.*)%/i);
1622
- if(match) {
1623
- const cpl = process.env[match[1]].split(path.delimiter);
1624
- for(let i = 0; i < cpl.length; i++) {
1625
- match = cpl[i].match(/[\/\\]cplex[\/\\]bin/i);
1626
- if(match) {
1627
- cplex_path = cpl[i];
1628
- break;
1629
- }
1630
- }
1631
- }
1632
- }
1633
- match = path_list[i].match(/[\/\\]scip[^\/\\]+[\/\\]bin/i);
1634
- if(match) scip_path = path_list[i];
1616
+ // Check whether it is an Inkscape path.
1635
1617
  match = path_list[i].match(/inkscape/i);
1618
+ // If so, use it (so the *last* matching path will be used).
1636
1619
  if(match) settings.inkscape = path_list[i];
1637
1620
  }
1638
- if(!gurobi_path && !PLATFORM.startsWith('win')) {
1639
- console.log('Looking for Gurobi in /usr/local/bin');
1640
- try {
1641
- // On macOS and Unix, Gurobi is in the user's local binaries
1642
- const gp = '/usr/local/bin';
1643
- fs.accessSync(gp + '/gurobi_cl');
1644
- gurobi_path = gp;
1645
- } catch(err) {
1646
- // No real error, so no action needed
1647
- }
1648
- }
1649
- if(gurobi_path) {
1650
- console.log('Path to Gurobi:', gurobi_path);
1651
- // Check if command line version is executable
1652
- const sp = path.join(gurobi_path,
1653
- 'gurobi_cl' + (PLATFORM.startsWith('win') ? '.exe' : ''));
1654
- try {
1655
- fs.accessSync(sp, fs.constants.X_OK);
1656
- if(settings.solver !== 'gurobi')
1657
- settings.solver = 'gurobi';
1658
- settings.solver_path = sp;
1659
- } catch(err) {
1660
- console.log(err.message);
1661
- console.log(
1662
- 'WARNING: Failed to access the Gurobi command line application');
1663
- }
1664
- }
1665
- // Check if cplex(.exe) exists in its directory
1666
- let sp = path.join(cplex_path, 'cplex' + (PLATFORM.startsWith('win') ? '.exe' : ''));
1667
- const need_cplex = !settings.solver || settings.preferred_solver === 'cplex';
1668
- try {
1669
- fs.accessSync(sp, fs.constants.X_OK);
1670
- console.log('Path to CPLEX:', sp);
1671
- if(need_cplex) {
1672
- settings.solver = 'cplex';
1673
- settings.solver_path = sp;
1674
- }
1675
- } catch(err) {
1676
- // Only report error if CPLEX is needed
1677
- if(need_cplex) {
1678
- console.log(err.message);
1679
- console.log('WARNING: CPLEX application not found in', sp);
1680
- }
1681
- }
1682
- // Check if scip(.exe) exists in its directory
1683
- sp = path.join(scip_path, 'scip' + (PLATFORM.startsWith('win') ? '.exe' : ''));
1684
- const need_scip = !settings.solver || settings.preferred_solver === 'scip';
1685
- try {
1686
- fs.accessSync(sp, fs.constants.X_OK);
1687
- console.log('Path to SCIP:', sp);
1688
- if(need_scip) {
1689
- settings.solver = 'scip';
1690
- settings.solver_path = sp;
1691
- }
1692
- } catch(err) {
1693
- // Only report error if SCIP is needed
1694
- if(need_scip) {
1695
- console.log(err.message);
1696
- console.log('WARNING: SCIP application not found in', sp);
1697
- }
1698
- }
1699
- // Check if lp_solve(.exe) exists in working directory
1700
- sp = path.join(WORKING_DIRECTORY,
1701
- 'lp_solve' + (PLATFORM.startsWith('win') ? '.exe' : ''));
1702
- const need_lps = !settings.solver || settings.preferred_solver === 'lp_solve';
1703
- try {
1704
- fs.accessSync(sp, fs.constants.X_OK);
1705
- console.log('Path to LP_solve:', sp);
1706
- if(need_lps) {
1707
- settings.solver = 'lp_solve';
1708
- settings.solver_path = sp;
1709
- }
1710
- } catch(err) {
1711
- // Only report error if LP_solve is needed
1712
- if(need_lps) {
1713
- console.log(err.message);
1714
- console.log('WARNING: LP_solve application not found in', sp);
1715
- }
1716
- }
1717
- // On macOS, Inkscape is not added to the PATH environment variable
1621
+ // NOTE: On macOS, Inkscape is not added to the PATH environment variable.
1718
1622
  if(!settings.inkscape && PLATFORM === 'darwin') {
1719
1623
  console.log('Looking for Inkscape in Applications...');
1720
1624
  try {
1721
- // Look in the default directory
1625
+ // Look in the default directory.
1722
1626
  const ip = '/Applications/Inkscape.app/Contents/MacOS';
1723
1627
  fs.accessSync(ip);
1724
1628
  settings.inkscape = ip;
1725
1629
  } catch(err) {
1726
- // No real error, so no action needed
1630
+ // No real error, so no action needed.
1727
1631
  }
1728
1632
  }
1729
- // Verify that Inkscape is installed
1633
+ // Verify that Inkscape is installed.
1730
1634
  if(settings.inkscape) {
1731
- // NOTE: On Windows, the command line version is a .com file
1635
+ // NOTE: On Windows, the command line version is a .com file.
1732
1636
  let ip = path.join(settings.inkscape,
1733
1637
  'inkscape' + (PLATFORM.startsWith('win') ? '.com' : ''));
1734
1638
  try {
1735
1639
  fs.accessSync(ip, fs.constants.X_OK);
1736
1640
  } catch(err) {
1737
1641
  // NOTE: As of Inkscape version 1.3, the command line executable
1738
- // is called inkscapecom(.com)
1642
+ // is called inkscapecom(.com).
1739
1643
  ip = path.join(settings.inkscape,
1740
1644
  'inkscapecom' + (PLATFORM.startsWith('win') ? '.com' : ''));
1741
1645
  }
@@ -1759,14 +1663,14 @@ Possible options are:
1759
1663
  }
1760
1664
 
1761
1665
  function createWorkspace() {
1762
- // Verifies that Linny-R has write access to the user workspace, defines
1763
- // paths to sub-directories, and creates them if necessary
1666
+ // Verify that Linny-R has write access to the user workspace, define
1667
+ // paths to sub-directories, and create them if necessary.
1764
1668
  try {
1765
- // See whether the user directory already exists
1669
+ // See whether the user directory already exists.
1766
1670
  try {
1767
1671
  fs.accessSync(SETTINGS.user_dir, fs.constants.R_OK | fs.constants.W_O);
1768
1672
  } catch(err) {
1769
- // If not, try to create it
1673
+ // If not, try to create it.
1770
1674
  fs.mkdirSync(SETTINGS.user_dir);
1771
1675
  console.log('Created user directory:', SETTINGS.user_dir);
1772
1676
  }
@@ -1776,7 +1680,7 @@ function createWorkspace() {
1776
1680
  SETTINGS.user_dir);
1777
1681
  process.exit();
1778
1682
  }
1779
- // Define the sub-directory paths
1683
+ // Define the sub-directory paths.
1780
1684
  const ws = {
1781
1685
  autosave: path.join(SETTINGS.user_dir, 'autosave'),
1782
1686
  channel: path.join(SETTINGS.user_dir, 'channel'),
@@ -1787,7 +1691,7 @@ function createWorkspace() {
1787
1691
  reports: path.join(SETTINGS.user_dir, 'reports'),
1788
1692
  solver_output: path.join(SETTINGS.user_dir, 'solver')
1789
1693
  };
1790
- // Create these sub-directories if not aready there
1694
+ // Create these sub-directories if not aready there.
1791
1695
  try {
1792
1696
  for(let p in ws) if(ws.hasOwnProperty(p)) {
1793
1697
  try {
@@ -1801,9 +1705,11 @@ function createWorkspace() {
1801
1705
  console.log(err.message);
1802
1706
  console.log('WARNING: No access to workspace directory');
1803
1707
  }
1804
- // The file containing name, URL and access token for remote repositories
1708
+ // The file containing name, URL and access token for remote repositories.
1805
1709
  ws.repositories = path.join(SETTINGS.user_dir, 'repositories.cfg');
1806
- // Return the updated workspace object
1710
+ // For completeness, add path to Linny-R directory.
1711
+ ws.working_directory = WORKING_DIRECTORY;
1712
+ // Return the updated workspace object.
1807
1713
  return ws;
1808
1714
  }
1809
1715
 
package/static/index.html CHANGED
@@ -162,16 +162,17 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
162
162
  if(info.length > 1) {
163
163
  LINNY_R_VERSION = info[0];
164
164
  const v = 'Version ' + LINNY_R_VERSION;
165
- // Update the "home page" of the documentation manager
165
+ // Update the "home page" of the documentation manager.
166
166
  DOCUMENTATION_MANAGER.about_linny_r =
167
167
  DOCUMENTATION_MANAGER.about_linny_r.replace(
168
168
  '[LINNY_R_VERSION]', v);
169
- // Update the version number in the browser's upper left corner
169
+ // Update the version number in the browser's upper left corner.
170
170
  document.getElementById('linny-r-version-number').innerHTML = v;
171
- // NOTE: server detects "version 0" when npmjs website was
172
- // not reached; if so, do not suggest a new version exists
171
+ // NOTE: Server detects "version 0" when npmjs website was
172
+ // not reached; if so, do not suggest a new version exists.
173
173
  if(info[1] !== 'up-to-date' && info[1] !== '0') {
174
- // Inform user that newer version exists
174
+ // Inform user that newer version exists.
175
+ UI.newer_version = info[1];
175
176
  UI.check_update_modal.element('msg').innerHTML = [
176
177
  '<a href="', GITHUB_REPOSITORY,
177
178
  '/releases/tag/v', info[1], '" ',
@@ -423,6 +424,28 @@ and move the cursor over the status bar">
423
424
  </div>
424
425
  </div>
425
426
 
427
+ <!-- the SERVER dialog permits solver selection, update and shut-down -->
428
+ <div id="server-modal" class="modal">
429
+ <div id="server-dlg" class="inp-dlg">
430
+ <div class="dlg-title">
431
+ Server and solver
432
+ <img class="cancel-btn" src="images/cancel.png">
433
+ <img class="ok-btn" src="images/ok.png">
434
+ </div>
435
+ <div id="server-host-div" title="Linny-R directory">Server on local host</div>
436
+ <div id="server-no-solver-div">No solver detected</div>
437
+ <div id="server-solver-div" hidden="hidden">
438
+ <div id="server-sel">
439
+ <label>Default solver:</label>
440
+ <select id="server-solver">
441
+ </select>
442
+ </div>
443
+ </div>
444
+ <button id="server-update" hidden="hidden">Update Linny-R to new version</button>
445
+ <button id="server-shut-down">Shut down server</button>
446
+ </div>
447
+ </div>
448
+
426
449
  <!-- the MODEL dialog prompts for (optional) model name and author name -->
427
450
  <div id="model-modal" class="modal">
428
451
  <div id="model-dlg" class="inp-dlg">
@@ -605,6 +628,8 @@ and move the cursor over the status bar">
605
628
  <td colspan="2">Solver time limit:&nbsp;
606
629
  <input id="settings-time-limit" type="text" autocomplete="off"
607
630
  style="width:35px;text-align:center"> seconds per block
631
+ <img id="settings-solver-prefs-btn" class="sbtn" src="images/settings.png"
632
+ title="Set solver preferences">
608
633
  </td>
609
634
  </tr>
610
635
  <tr>
@@ -688,6 +713,40 @@ NOTE: Unit symbols are case-sensitive, so BTU &ne; Btu">
688
713
  </div>
689
714
  </div>
690
715
 
716
+ <!-- the SOLVER dialog permits specifying solver preference and tolerances -->
717
+ <div id="solver-modal" class="modal">
718
+ <div id="solver-dlg" class="inp-dlg">
719
+ <div class="dlg-title">
720
+ Solver preferences
721
+ <img class="cancel-btn" src="images/cancel.png">
722
+ <img class="ok-btn" src="images/ok.png">
723
+ </div>
724
+ <table style="width: 100%">
725
+ <tr title="This solver will be used if it is installed">
726
+ <td>
727
+ <label>Preferred solver:</label>
728
+ <select id="solver-preference">
729
+ </select>
730
+ </td>
731
+ </tr>
732
+ <tr title="Tolerance may range from 1e-9 to 0.1">
733
+ <td>
734
+ <label>Integer feasibility tolerance:</label>
735
+ <input id="solver-int-feasibility" style="width: 55px"
736
+ placeholder="5e-7" type="text" autocomplete="off">
737
+ </td>
738
+ </tr>
739
+ <tr title="Relative gap may range from 0 to 0.5">
740
+ <td>
741
+ <label>Relative MIP gap:</label>
742
+ <input id="solver-mip-gap" style="width: 55px"
743
+ placeholder="1e-4" type="text" autocomplete="off">
744
+ </td>
745
+ </tr>
746
+ </table>
747
+ </div>
748
+ </div>
749
+
691
750
  <!-- the REPOSITORY BROWSER dialog allows interaction with repositories -->
692
751
  <div id="repository-dlg" class="inp-dlg">
693
752
  <div id="repository-hdr" class="dragger dlg-title">Repository browser
@@ -944,6 +944,7 @@ img.inline-cancel-btn {
944
944
  height: min-content;
945
945
  }
946
946
 
947
+ #settings-solver-prefs-btn,
947
948
  #settings-scale-units-btn {
948
949
  margin-left: 0px;
949
950
  margin-right: 7px;
@@ -952,6 +953,7 @@ img.inline-cancel-btn {
952
953
  filter: brightness(85%);
953
954
  }
954
955
 
956
+ #settings-solver-prefs-btn:hover,
955
957
  #settings-scale-units-btn:hover {
956
958
  filter: brightness(200%);
957
959
  }
@@ -994,6 +996,16 @@ input.pws-5 {
994
996
  background-color: #c0ffc0;
995
997
  }
996
998
 
999
+ /* styles for the SOLVER PREFERENCES dialog */
1000
+ #solver-modal {
1001
+ z-index: 110; /* on top of the SETTINGS dialog */
1002
+ }
1003
+
1004
+ #solver-dlg {
1005
+ width: 212px;
1006
+ height: min-content;
1007
+ }
1008
+
997
1009
  /* styles for the ACTOR DIALOG
998
1010
  NOTE: the z-index is very high for this dialog, as it should appear
999
1011
  on top of the ACTOR LIST DIALOG
@@ -1190,6 +1202,35 @@ td.export {
1190
1202
  color: #0000b0;
1191
1203
  }
1192
1204
 
1205
+ /* styles for the SERVER dialog */
1206
+ #server-dlg {
1207
+ width: 200px;
1208
+ height: min-content;
1209
+ }
1210
+ #server-host-div,
1211
+ #server-solver-div {
1212
+ margin: 3px;
1213
+ }
1214
+
1215
+ #server-no-solver-div {
1216
+ margin: 3px;
1217
+ padding-top: 2px;
1218
+ padding-bottom: 2px;
1219
+ font-style: italic;
1220
+ margin-bottom: 2px;
1221
+ }
1222
+
1223
+ #server-solver {
1224
+ font-size: 12px;
1225
+ margin-bottom: 3px;
1226
+ }
1227
+
1228
+ #server-update,
1229
+ #server-shut-down {
1230
+ margin: 3px;
1231
+ font-size: 12px;
1232
+ }
1233
+
1193
1234
  /* styles for the MODEL dialog */
1194
1235
  #model-dlg {
1195
1236
  width: 270px;
@@ -260,7 +260,7 @@ class Controller {
260
260
  return VM.sig2Dig(n) + ' ' + 'kMGTP'.charAt(m) + 'B';
261
261
  }
262
262
 
263
- // Shapes are only used to draw model diagrams
263
+ // Shapes are only used to draw model diagrams.
264
264
 
265
265
  createShape(mdl) {
266
266
  if(this.paper) return new Shape(mdl);
@@ -275,7 +275,7 @@ class Controller {
275
275
  if(shape) shape.removeFromDOM();
276
276
  }
277
277
 
278
- // Methods to ensure proper naming of entities
278
+ // Methods to ensure proper naming of entities.
279
279
 
280
280
  cleanName(name) {
281
281
  // Returns `name` without the object-attribute separator |, backslashes,
@@ -429,7 +429,7 @@ class Controller {
429
429
 
430
430
 
431
431
  nameToID(name) {
432
- // Returns a name in lower case with link arrow replaced by three
432
+ // Return a name in lower case with link arrow replaced by three
433
433
  // underscores, constraint link arrow by four underscores, and spaces
434
434
  // converted to underscores; in this way, IDs will always be valid
435
435
  // JavaScript object properties.
@@ -442,7 +442,7 @@ class Controller {
442
442
  // Empty string signals failure.
443
443
  return '';
444
444
  }
445
- // NOTE: replace single quotes by Unicode apostrophe so that they
445
+ // NOTE: Replace single quotes by Unicode apostrophe so that they
446
446
  // cannot interfere with JavaScript strings delimited by single quotes.
447
447
  return name.toLowerCase().replace(/\s/g, '_').replace("'", '\u2019');
448
448
  }
@@ -485,8 +485,8 @@ class Controller {
485
485
  // Methods to notify modeler
486
486
 
487
487
  setMessage(msg, type, cause=null) {
488
- // Only log errors and warnings on the browser console
489
- // NOTE: optionally, the JavaScript error can be passed via `cause`
488
+ // Only log errors and warnings on the browser console.
489
+ // NOTE: Optionally, the JavaScript error can be passed via `cause`.
490
490
  if(type === 'error' || type === 'warning') {
491
491
  // Add type unless message already starts with it
492
492
  type = type.toUpperCase() + ':';
@@ -547,8 +547,8 @@ class Controller {
547
547
  }
548
548
 
549
549
  postResponseOK(text, notify=false) {
550
- // Checks whether server reponse text is warning or error, and notifies
551
- // the modeler if second argument is TRUE
550
+ // Check whether server reponse text is warning or error, and notify
551
+ // the modeler if second argument is TRUE.
552
552
  let mtype = 'notification';
553
553
  if(text.startsWith('ERROR:')) {
554
554
  mtype = 'error';
@@ -563,20 +563,20 @@ class Controller {
563
563
  }
564
564
 
565
565
  loginPrompt() {
566
- // The VM needs credentials - his should only occur for the GUI
566
+ // The VM needs credentials - his should only occur for the GUI.
567
567
  console.log('WARNING: VM needs credentials, but GUI not active');
568
568
  }
569
569
 
570
570
  resetModel() {
571
- // Resets the Virtual Machine (clears solution)
571
+ // Reset the Virtual Machine (clears solution).
572
572
  VM.reset();
573
- // Redraw model in the browser (GUI only)
573
+ // Redraw model in the browser (GUI only).
574
574
  MODEL.clearSelection();
575
575
  this.drawDiagram(MODEL);
576
576
  }
577
577
 
578
578
  stopSolving() {
579
- // Notify user only if VM was halted
579
+ // Notify user only if VM was halted.
580
580
  if(VM.halted) {
581
581
  this.notify('Solver HALTED');
582
582
  } else {
@@ -585,8 +585,9 @@ class Controller {
585
585
  }
586
586
 
587
587
  // NOTE: The following UI functions are implemented as "dummy" methods
588
- // because they are called by the Virtual Machine and/or by other controllers
589
- // while they can only be meaningfully performed by the GUI controller
588
+ // because they are called by the Virtual Machine and/or by other
589
+ // controllers while they can only be meaningfully performed by the
590
+ // GUI controller.
590
591
  addListeners() {}
591
592
  readyToReset() {}
592
593
  updateScaleUnitList() {}
@@ -616,27 +617,27 @@ class RepositoryBrowser {
616
617
  this.repositories = [];
617
618
  this.repository_index = -1;
618
619
  this.module_index = -1;
619
- // Get the repository list from the server
620
+ // Get the repository list from the server.
620
621
  this.getRepositories();
621
622
  this.reset();
622
623
  }
623
624
 
624
625
  reset() {
625
626
  this.visible = false;
626
- // NOTE: do NOT reset repository list or module index, because:
627
- // (1) they are properties of the local host, and hence model-independent
627
+ // NOTE: Do NOT reset repository list or module index, because:
628
+ // (1) they are properties of the local host, and hence model-independent;
628
629
  // (2) they must be known when loading a module as model, whereas the
629
- // loadingModel method hides and resets all stay-on-top dialogs
630
+ // loadingModel method hides and resets all stay-on-top dialogs.
630
631
  }
631
632
 
632
633
  get isLocalHost() {
633
- // Returns TRUE if first repository on the list is 'local host'
634
+ // Return TRUE if first repository on the list is 'local host'.
634
635
  return this.repositories.length > 0 &&
635
636
  this.repositories[0].name === 'local host';
636
637
  }
637
638
 
638
639
  getRepositories() {
639
- // Gets the list of repository names from the server
640
+ // Get the list of repository names from the server.
640
641
  this.repositories.length = 0;
641
642
  fetch('repo/', postData({action: 'list'}))
642
643
  .then((response) => {
@@ -647,14 +648,14 @@ class RepositoryBrowser {
647
648
  })
648
649
  .then((data) => {
649
650
  if(UI.postResponseOK(data)) {
650
- // NOTE: trim to prevent empty name strings
651
+ // NOTE: Trim to prevent empty name strings.
651
652
  const rl = data.trim().split('\n');
652
653
  for(let i = 0; i < rl.length; i++) {
653
654
  this.addRepository(rl[i].trim());
654
655
  }
655
656
  }
656
- // NOTE: set index to first repository on list (typically local host)
657
- // unless the list is empty
657
+ // NOTE: Set index to first repository on list (typically the
658
+ // local host repository) unless the list is empty.
658
659
  this.repository_index = Math.min(0, this.repositories.length - 1);
659
660
  this.updateDialog();
660
661
  })
@@ -662,7 +663,7 @@ class RepositoryBrowser {
662
663
  }
663
664
 
664
665
  repositoryByName(n) {
665
- // Returns the repository having name `n` if already known, otherwise NULL
666
+ // Return the repository having name `n` if already known, or NULL.
666
667
  for(let i = 0; i < this.repositories.length; i++) {
667
668
  if(this.repositories[i].name === n) {
668
669
  return this.repositories[i];
@@ -672,8 +673,8 @@ class RepositoryBrowser {
672
673
  }
673
674
 
674
675
  asFileName(s) {
675
- // Returns string `s` with whitespace converted to a single dash, and
676
- // special characters converted to underscores
676
+ // Return string `s` with whitespace converted to a single dash, and
677
+ // special characters converted to underscores.
677
678
  return s.normalize('NFKD').trim()
678
679
  .replace(/[\s\-]+/g, '-')
679
680
  .replace(/[^A-Za-z0-9_\-]/g, '_')
@@ -681,12 +682,13 @@ class RepositoryBrowser {
681
682
  }
682
683
 
683
684
  loadModuleAsModel() {
684
- // Loads selected module as model
685
+ // Load selected module as model.
685
686
  if(this.repository_index >= 0 && this.module_index >= 0) {
686
- // NOTE: when loading new model, the stay-on-top dialogs must be reset
687
+ // NOTE: When loading new model, the stay-on-top dialogs must be
688
+ // reset (GUI only; for console this is a "dummy" method).
687
689
  UI.hideStayOnTopDialogs();
688
690
  const r = this.repositories[this.repository_index];
689
- // NOTE: pass FALSE to indicate "no inclusion; load XML as model"
691
+ // NOTE: pass FALSE to indicate "no inclusion; load XML as model".
690
692
  r.loadModule(this.module_index, false);
691
693
  }
692
694
  }