From 65aeddd0efa64d495a4704291264f6b3ff170417 Mon Sep 17 00:00:00 2001 From: salman Date: Thu, 27 Aug 2026 17:25:33 +0200 Subject: [PATCH 1/9] Modernize application layout --- TrimSP.html | 235 +++++++------- ZGUI.css | 892 +++++++++++++++++++++++++++++++++------------------- 2 files changed, 692 insertions(+), 435 deletions(-) diff --git a/TrimSP.html b/TrimSP.html index 1d11d41..cfb02ce 100644 --- a/TrimSP.html +++ b/TrimSP.html @@ -2,8 +2,8 @@ + - - TRIMSP-NL Workbench - -
-
-
- - - - +
+
+ +
- - - - - -
- - - - - - - - - - - - - - +
+ +
+ +
+ + + +
+
+
+
+
+
+
+ Target +

Layer stack

+
+ +
+
+
- - -
- - - -
- - -
Layer #CompositionDensity [g/cm3]Thickness [Å]
-
+
LayerCompositionDensity [g/cm3]Thickness [Å]
-
- - - - - - - - - - - - - - - - - - - -
Projectile parameters
Projectile -
Number of projectiles
Starting depth [Å]
Depth increment [Å]
Energy [eV]
Energy sigma [eV]
Angle [deg]
Angle sigma [deg]
RNG / Seed - - -
-
+
+
+
+
+
+ Beam +

Projectile

+
+
+
+ + + + + + + + + + +
+
+
Enable scan on @@ -158,7 +165,15 @@
- +
+
+
+
+ Transport model +

Advanced parameters

+
+
+
-
EF @@ -373,29 +388,33 @@
-

- For further details see:
- W. Eckstein, Computer Simulation of Ion-Solid Interactions,
- Springer Series in Materials Science, Vol. 10 (Springer-Verlag, Berlin, 1991).
- - https://doi.org/10.1007/978-3-642-73513-4 - -

-

- If you use this software in published work, please cite the relevant - TRIM.SP references and the TRIMSP-NL Workbench paper. -

-

- Source repository: - - gitea.psi.ch/LMU/TRIMSP - -

+ + + +
- - - + +
Folder: - Backend: - @@ -429,12 +448,8 @@ if (webOrApp) { console.log('this script is not running in Node.js'); // Run as a web app - //let row = document.getElementById("FileRow"); - //row.parentNode.removeChild(row); - //row = document.getElementById("FolderRow"); - //row.parentNode.removeChild(row); - document.getElementById("FileRow").style.visibility = "hidden";; - document.getElementById("FolderRow").style.visibility = "hidden";; + document.getElementById("FileRow").hidden = true; + document.getElementById("FolderRow").hidden = true; } else { console.log('this script is running in Node.js'); // Run as standalone app diff --git a/ZGUI.css b/ZGUI.css index 388ae22..b970e28 100644 --- a/ZGUI.css +++ b/ZGUI.css @@ -2,371 +2,613 @@ box-sizing: border-box; } +:root { + --trim-bg: #e9eef0; + --trim-panel: #fffdf8; + --trim-panel-soft: #f3f6f4; + --trim-ink: #1d2b32; + --trim-muted: #617077; + --trim-deep: #173943; + --trim-accent: #bd5f37; + --trim-accent-hover: #9f4827; + --trim-border: #c8d3d5; + --trim-focus: #2d7888; + --trim-status-height: 38px; +} + +html { + height: 100%; +} + +body { + background: + radial-gradient(circle at 8% 5%, rgba(189, 95, 55, 0.16), transparent 28rem), + linear-gradient(145deg, #f5f1e9 0%, var(--trim-bg) 58%, #dce6e7 100%); + color: var(--trim-ink); + font-family: "Trebuchet MS", "Lucida Sans", Geneva, sans-serif; + height: 100vh; + height: 100dvh; + margin: 0; + overflow: hidden; + padding: 8px 10px calc(var(--trim-status-height) + 8px); +} + td { - white-space: wrap; + white-space: normal; } -.guitable { - background-color: #DDDDDD; - border: 1px solid #000000; - /* border-collapse: collapse; */ - border-radius: 10px; - border: none; - border-spacing: 0px; - padding: 5px; - margin-left: auto; - margin-right: auto; - margin-top: 0.5em; - margin-bottom: 0.5em; +button, +input, +select { + font: inherit; } -.guitable tr:last-child td:first-child { - border-bottom-left-radius: 12px; +input[type="text"], +input[type="number"], +select { + background: #fff; + border: 1px solid #aebdc0; + border-radius: 5px; + color: var(--trim-ink); + min-height: 29px; + padding: 3px 7px; } -.guitable tr:last-child td:last-child { - border-bottom-right-radius: 12px; +input[type="text"]:focus, +input[type="number"]:focus, +select:focus, +button:focus-visible, +input[type="button"]:focus-visible { + border-color: var(--trim-focus); + box-shadow: 0 0 0 3px rgba(45, 120, 136, 0.17); + outline: none; } -.guitable tr:first-child td:first-child { - border-top-left-radius: 12px; +input:disabled { + background: #e8edef; + color: #59676d; + cursor: not-allowed; } -.guitable tr:first-child td:last-child { - border-top-right-radius: 12px; +button, +input[type="button"] { + background: #fff; + border: 1px solid #8da5aa; + border-radius: 5px; + color: var(--trim-deep); + cursor: pointer; + font-weight: 700; + padding: 6px 11px; } -.guitable tr:nth-child(odd) { - background-color: #DDDDDD; +button:hover, +input[type="button"]:hover { + background: #eaf1f1; } -.guitable tr:nth-child(even) { - background-color: #DDDDDD; -} - -/* For mobile phones: */ -[class*="col-"] { +.app-shell { + height: 100%; + margin: 0 auto; + max-width: 1240px; + min-height: 0; width: 100%; } -@media only screen and (min-width: 700px) { +.app-card { + background: linear-gradient(180deg, var(--trim-panel) 0%, #fafbf8 100%); + border: 1px solid rgba(97, 112, 119, 0.3); + border-radius: 13px; + box-shadow: 0 18px 45px rgba(29, 43, 50, 0.17); + display: flex; + flex-direction: column; + height: 100%; + min-height: 0; + overflow: hidden; +} - /* For tablets: */ - .col-s-1 { - width: 50%; - background-color: #DDDDDD; +.tab { + background: var(--trim-deep); + display: flex; + flex: 0 0 auto; + gap: 3px; + overflow-x: auto; + padding: 4px 6px; +} + +.tab button { + background: transparent; + border: 0; + border-radius: 6px; + color: #dce8e9; + font-size: 0.93rem; + padding: 6px 11px; + transition: background 0.16s ease, color 0.16s ease; + white-space: nowrap; +} + +.tab button:hover { + background: rgba(255, 255, 255, 0.13); + color: #fff; +} + +.tab button.active { + background: var(--trim-panel); + color: var(--trim-deep); +} + +.main-action-bar { + align-items: center; + background: var(--trim-panel-soft); + border-bottom: 1px solid var(--trim-border); + box-shadow: 0 5px 15px rgba(29, 43, 50, 0.07); + display: flex; + flex: 0 0 auto; + gap: 16px; + justify-content: space-between; + padding: 7px 12px; + z-index: 10; +} + +.action-bar-title { + align-items: center; + display: flex; + gap: 10px; + min-width: 0; +} + +.app-brand-icon { + border-radius: 7px; + flex: 0 0 auto; + height: 32px; + width: 32px; +} + +.action-bar-title strong { + color: var(--trim-deep); + display: block; + font-family: Georgia, "Times New Roman", serif; + font-size: 1rem; +} + +.action-bar-eyebrow { + color: var(--trim-muted); + display: block; + font-size: 0.7rem; + font-weight: 700; + letter-spacing: 0.08em; + text-transform: uppercase; +} + +.run-action { + background: var(--trim-accent); + border-color: var(--trim-accent); + color: #fff; + flex: 0 0 auto; + min-width: 145px; + padding: 8px 14px; +} + +.run-action:hover { + background: var(--trim-accent-hover); +} + +.tabcontent { + background: var(--trim-panel); + border: 0; + flex: 1 1 auto; + min-height: 0; + overflow: auto; + padding: 10px 12px 14px; +} + +/* Compact configuration panels keep the default window free of scrollbars. */ +.output-settings { + align-items: end; + display: grid; + gap: 10px; + grid-template-columns: minmax(180px, 0.8fr) minmax(320px, 1.8fr); + margin-bottom: 9px; +} + +.compact-field, +.settings-field { + min-width: 0; +} + +.compact-field > span, +.compact-field > label, +.settings-field > span { + color: var(--trim-muted); + display: block; + font-size: 0.75rem; + font-weight: 700; + margin-bottom: 2px; +} + +.compact-field > input, +.settings-field > input, +.settings-field > select { + width: 100%; +} + +.field-with-action { + display: flex; + gap: 6px; +} + +.field-with-action input[type="text"] { + flex: 1 1 auto; + min-width: 0; +} + +.layers-workspace { + align-items: start; + display: grid; + gap: 10px; + grid-template-columns: minmax(0, 1.15fr) minmax(300px, 0.85fr); +} + +.settings-panel, +.reference-panel { + background: #fff; + border: 1px solid var(--trim-border); + border-radius: 9px; + min-width: 0; + padding: 9px 10px; +} + +.section-heading { + align-items: center; + display: flex; + justify-content: space-between; + margin-bottom: 6px; + min-height: 32px; +} + +.section-heading h2, +.reference-panel h2 { + color: var(--trim-deep); + font-family: Georgia, "Times New Roman", serif; + font-size: 1rem; + line-height: 1.15; + margin: 0; +} + +.section-eyebrow { + color: var(--trim-accent); + display: block; + font-size: 0.67rem; + font-weight: 700; + letter-spacing: 0.08em; + margin-bottom: 1px; + text-transform: uppercase; +} + +.layer-count { + align-items: center; + color: var(--trim-muted); + display: flex; + font-size: 0.75rem; + font-weight: 700; + gap: 6px; +} + +.layer-count input { + width: 58px; +} + +.layer-table-wrap { + overflow-x: auto; +} + +.projectile-grid { + display: grid; + gap: 6px 9px; + grid-template-columns: repeat(2, minmax(0, 1fr)); +} + +#Plots > table { + border-collapse: separate; + border-spacing: 0; + width: 100%; +} + +#LTable { + border: 0; + border-collapse: separate; + border-spacing: 0; + overflow: hidden; + width: 100%; +} + +#LTable td { + border-bottom: 1px solid var(--trim-border); + border-left: 0; + border-right: 0; + border-top: 0; + padding: 5px 6px; +} + +#LTable tr:first-child td { + background: #e7eeed; + color: var(--trim-deep); + font-size: 0.84rem; +} + +#LTable tr:not(:first-child):hover td { + background: #f5f8f5; +} + +#ScansControls, +#ScansLine, +#ScansTable { + margin-top: 12px; +} + +#ScansControls table, +#ScansTable { + background: var(--trim-panel-soft); + border: 1px solid var(--trim-border); + border-radius: 8px; + padding: 8px; +} + +#ScansControls td, +#ScansTable td { + padding: 6px; +} + +#Plots > table td { + padding: 8px; + vertical-align: top; + width: 50%; +} + +.advanced-layout { + align-items: start; + display: grid; + gap: 10px; + grid-template-columns: minmax(560px, 2.2fr) minmax(240px, 0.8fr); +} + +.advanced-parameters > table { + border-collapse: separate; + border-spacing: 5px 2px; + width: 100%; +} + +.advanced-parameters { + padding: 7px 9px; +} + +.advanced-parameters .section-heading { + margin-bottom: 2px; +} + +.advanced-parameters > table td { + padding: 2px 3px; +} + +.advanced-parameters > table td:nth-child(odd) { + color: var(--trim-deep); + font-size: 0.8rem; + font-weight: 700; +} + +.advanced-parameters input { + min-height: 27px; + max-width: 94px; + width: 100%; +} + +.reference-panel { + background: + linear-gradient(145deg, rgba(189, 95, 55, 0.08), transparent 55%), + var(--trim-panel-soft); + color: #46565d; + font-size: 0.75rem; + line-height: 1.32; + padding: 8px 9px; +} + +.reference-panel p { + margin: 6px 0; +} + +.reference-link { + color: #1f6675; + display: block; + overflow-wrap: anywhere; +} + +.reference-divider { + border-top: 1px solid var(--trim-border); + margin: 7px 0 6px; +} + +.reference-label { + color: var(--trim-deep); + display: block; + font-size: 0.7rem; + font-weight: 700; + margin-bottom: 3px; + text-transform: uppercase; +} + +.tooltip { + cursor: help; + position: relative; +} + +.tooltip .tooltiptext { + background: #142d35; + border-radius: 7px; + box-shadow: 0 8px 24px rgba(17, 34, 40, 0.25); + color: #fff; + font-weight: 400; + left: 0; + line-height: 1.35; + padding: 9px 11px; + position: absolute; + text-align: left; + top: calc(100% + 5px); + display: none; + width: 330px; + z-index: 100; +} + +.tooltip:hover .tooltiptext, +.tooltip:focus-within .tooltiptext { + display: block; +} + +.transient-warning { + background: #fff1c9; + border: 1px solid #c79824; + border-radius: 7px; + box-shadow: 0 7px 20px rgba(29, 43, 50, 0.17); + color: #523c08; + line-height: 1.35; + max-width: 380px; + opacity: 0; + padding: 9px 12px; + pointer-events: none; + position: fixed; + transition: opacity 0.2s ease; + z-index: 1000; +} + +.transient-warning.visible { + opacity: 1; +} + +.estimated-density { + background: #fff1c9 !important; + border-color: #c79824 !important; +} + +.status-bar { + align-items: center; + background: #edf2f2; + border-top: 1px solid #aebec1; + bottom: 0; + box-shadow: 0 -3px 12px rgba(29, 43, 50, 0.1); + color: var(--trim-ink); + display: flex; + flex-wrap: nowrap; + font-size: 0.82rem; + gap: 14px; + left: 0; + min-height: var(--trim-status-height); + padding: 5px 12px; + position: fixed; + right: 0; + z-index: 900; +} + +.status-item { + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.status-item strong { + color: var(--trim-deep); +} + +.status-progress { + flex: 1 1 220px; + min-width: 150px; +} + +.status-progress-track { + background: #d3dcde; + border: 1px solid #aebec1; + border-radius: 999px; + height: 18px; + overflow: hidden; + width: 100%; +} + +.status-progress-fill { + background: linear-gradient(90deg, #2d7888, #45a0a6); + color: #fff; + font-size: 0.72rem; + font-weight: 700; + height: 100%; + line-height: 16px; + padding: 0 4px; + text-align: center; + transition: width 0.2s ease; + width: 0%; +} + +@media only screen and (max-width: 760px) { + body { + padding: 0 0 var(--trim-status-height); } - .col-s-2 { - width: 50%; - background-color: #DDDDDD; + .app-card { + border: 0; + border-radius: 0; } - .col-s-full { - width: 100%; - background-color: #F2F2F2; + .main-action-bar { + align-items: stretch; + flex-direction: column; + gap: 8px; } - [class*="col-"] { - width: 100%; + .run-action { + width: 100%; } - @media only screen and (min-width: 990px) { - - /* For desktop: */ - .col-1 { - width: 50%; - background-color: #F2F2F2; - } - - .col-2 { - width: 50%; - background-color: #F2F2F2; - } - - .col-full { - width: 100%; - background-color: #F2F2F2; - } + .output-settings, + .layers-workspace, + .projectile-grid { + grid-template-columns: 1fr; } - [class*="col-"] { - float: left; - padding: 5px; - border: none; + .advanced-layout { + grid-template-columns: 1fr; } - .row::after { - content: ""; - clear: both; - display: table; + .advanced-parameters { + overflow-x: auto; } - .transient-warning { - position: fixed; - max-width: 360px; - padding: 10px 14px; - background-color: #fff4cc; - border: 1px solid #c5a100; - border-radius: 8px; - color: #4d3b00; - box-shadow: 0 6px 18px rgba(0, 0, 0, 0.18); - font-size: 0.95rem; - line-height: 1.35; - opacity: 0; - pointer-events: none; - transition: opacity 0.25s ease; - z-index: 1000; + #Plots > table, + #Plots > table > tbody, + #Plots > table > tbody > tr, + #Plots > table > tbody > tr > td { + display: block; + width: 100%; } - .transient-warning.visible { - opacity: 1; - } - - .estimated-density { - background-color: #fff4cc; - border: 1px solid #c5a100; - } - - .status-bar { - position: fixed; - left: 0; - right: 0; - bottom: 0; - display: flex; - flex-wrap: wrap; - gap: 8px 16px; - align-items: center; - padding: 8px 14px; - background-color: #eef2f5; - border-top: 1px solid #b8c3cc; - color: #23313d; - font-size: 0.88rem; - box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.08); - z-index: 900; - } - - .status-item { - min-width: 180px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - } - - .status-item strong { - color: #10202c; + .status-item:nth-child(-n+3) { + display: none; } .status-progress { - flex: 1 1 260px; - min-width: 220px; + min-width: 120px; + } +} + +@media only screen and (min-width: 761px) and (max-width: 900px) { + .advanced-layout { + grid-template-columns: 1fr; } - .status-progress-track { - width: 100%; - height: 22px; - background-color: #d7dee5; - border-radius: 8px; - overflow: hidden; - border: 1px solid #b8c3cc; + .reference-panel { + display: grid; + gap: 8px 14px; + grid-template-columns: 1fr 1fr; } - .status-progress-fill { - width: 0%; - height: 100%; - background-color: #2e8b57; - color: black; - padding-left: 3px; - text-align: center; - line-height: 20px; - font-size: 0.78rem; - font-weight: 600; - transition: width 0.2s ease; + .reference-panel .section-eyebrow, + .reference-panel h2, + .reference-divider { + grid-column: 1 / -1; } - - .group_name { - padding-left: 1em; - padding-right: 1em; - font-size: 105%; - font-weight: bold; - width: 1%; - white-space: nowrap; - /* text-align: center; */ - } - - .yellow_td { - padding-left: 1em; - padding-right: 1em; - background-color: yellow; - width: 1%; - white-space: nowrap; - } - - .gray_td { - padding-left: 1em; - padding-right: 1em; - background-color: gray; - width: 1%; - white-space: nowrap; - } - - .nocol_td { - padding-left: 1em; - padding-right: 1em; - width: 1%; - white-space: nowrap; - } - - /* Style the tab */ - .tab { - overflow: hidden; - border: 1px solid #ccc; - background-color: #f1f1f1; - } - - /* Style the buttons that are used to open the tab content */ - .tab button { - background-color: inherit; - float: left; - border: none; - outline: none; - cursor: pointer; - padding: 14px 16px; - transition: 0.3s; - font-size: 105%; - font-weight: bold; - } - - /* Change background color of buttons on hover */ - .tab button:hover { - background-color: #ddd; - } - - /* Create an active/current tablink class */ - .tab button.active { - background-color: #ccc; - } - - /* Style the tab content */ - .tabcontent { - display: none; - padding: 6px 12px; - border: 1px solid #ccc; - border-top: none; - } - - /* Go from zero to full opacity */ - @keyframes fadeEffect { - from { - opacity: 0; - } - - to { - opacity: 1; - } - } - - /* Chrome, Safari, Opera */ - @-webkit-keyframes example { - 0% { - background-color: red; - left: 20px; - top: 0px; - } - - 25% { - background-color: orange; - left: 25px; - top: 0px; - } - - 50% { - background-color: green; - left: 30px; - top: 0px; - } - - 75% { - background-color: orange; - left: 25px; - top: 0px; - } - - 100% { - background-color: red; - left: 20px; - top: 0px; - } - } - - /* Standard syntax */ - @keyframes example { - 0% { - background-color: red; - left: 20px; - top: 0px; - } - - 25% { - background-color: orange; - left: 25px; - top: 0px; - } - - 50% { - background-color: green; - left: 30px; - top: 0px; - } - - 75% { - background-color: orange; - left: 25px; - top: 0px; - } - - 100% { - background-color: red; - left: 20px; - top: 0px; - } - } - - #myBar { - width: 10%; - height: 30px; - background-color: #4CAF50; - text-align: center; - /* To center it horizontally (if you want) */ - line-height: 30px; - /* To center it vertically */ - color: white; - } - - /* Tooltip container */ - .tooltip { - position: relative; - display: inline-block; - /* border-bottom: 1px dotted black; /* If you want dots under the hoverable text */ - } - - /* Tooltip text */ - .tooltip .tooltiptext { - visibility: hidden; - width: 320px; - background-color: black; - color: #fff; - text-align: left; - padding: 5px; - border-radius: 6px; - - /* Position the tooltip text - see examples below! */ - position: absolute; - z-index: 1; - } - - /* Show the tooltip text when you mouse over the tooltip container */ - .tooltip:hover .tooltiptext { - visibility: visible; - } -} \ No newline at end of file +} From 1f6ecc7982d38ea9a901d76a08730a65ff1155b3 Mon Sep 17 00:00:00 2001 From: salman Date: Thu, 27 Aug 2026 19:24:27 +0200 Subject: [PATCH 2/9] Modernize scan workflow and web deployment --- README.md | 56 ++++++++++++++++------ TrimSP.html | 75 ++++++++++++++--------------- TrimSPlib.js | 108 ++++++++++++++++++++++++++++++++---------- ZGUI.css | 92 ++++++++++++++++++++++++++++++----- scripts/deploy-web.sh | 85 +++++++++++++++++++++++++++++++++ singleTrimSP.cgi | 82 ++++++++++++++++++++++++++++++++ 6 files changed, 410 insertions(+), 88 deletions(-) create mode 100755 scripts/deploy-web.sh create mode 100644 singleTrimSP.cgi diff --git a/README.md b/README.md index 527aa44..55469dd 100644 --- a/README.md +++ b/README.md @@ -228,24 +228,52 @@ which matches how the packaged app currently handles simulation input/output. ###### Deploying on a web server ###### -The browser version requires a server-side setup in addition to the static -HTML/JavaScript files. +The repository includes the browser assets, CGI runner, and a deployment +script for an Apache-style server. With the default settings, run: -In particular: +```bash +sudo ./scripts/deploy-web.sh +``` -* the browser build uses `TrimSPweb.js` rather than the Electron-specific - `TrimSPelec.js` -* the simulation backend must be available on the server -* a web-exposed helper/CGI layer is required to create input files, run the - backend, and return the generated output +The script builds the static Fortran engine and installs: -Notes: +```text +/var/www/html/TRIMSP-NL/ browser application +/var/www/cgi-bin/singleTrimSP.cgi CGI runner +/usr/local/bin/trimspNL TRIM.SP-NL engine +``` -* this repository does not currently include the CGI/backend helper used by - the online deployment -* a plain static web server is therefore not sufficient for running web - simulations from this checkout -* static hosting can still be used to test page loading only +Deployment paths and ownership can be changed through environment variables: + +| Variable | Default | Purpose | +| --- | --- | --- | +| `TRIMSP_WEB_TARGET` | `/var/www/html/TRIMSP-NL` | Browser asset directory | +| `TRIMSP_CGI_TARGET` | `/var/www/cgi-bin/singleTrimSP.cgi` | Installed CGI path | +| `TRIMSP_BIN_TARGET` | `/usr/local/bin/trimspNL` | Installed engine path | +| `TRIMSP_WEB_OWNER` | `apache` | Web file owner | +| `TRIMSP_WEB_GROUP` | `apache` | Web file group | +| `TRIMSP_BUILD_STATIC` | `1` | Build the static engine before deployment | +| `TRIMSP_BINARY_SOURCE` | `fortran/trimspNL-static` | Binary used when installing | + +For example, to deploy without rebuilding and use an existing binary: + +```bash +sudo TRIMSP_BUILD_STATIC=0 \ + TRIMSP_BINARY_SOURCE=/path/to/trimspNL \ + ./scripts/deploy-web.sh +``` + +The web server must execute the CGI through `/cgi-bin/singleTrimSP.cgi` and +serve simulation files written below filesystem `/tmp` through URL `/tmp/`. +Review that mapping carefully so it exposes only the intended simulation +artifacts. On SELinux systems, inspect denied operations with: + +```bash +ausearch -m avc -ts recent +``` + +The Apache account needs permission to execute the CGI and engine and to create +the per-run directories in `/tmp`. ### Contact ### diff --git a/TrimSP.html b/TrimSP.html index cfb02ce..83f5f14 100644 --- a/TrimSP.html +++ b/TrimSP.html @@ -111,16 +111,27 @@
- Enable scan on - - - - - - - - - - + + +
+
+ + + +
+ + +
Single simulation using the Layers settings.
+
diff --git a/TrimSPlib.js b/TrimSPlib.js index dcb902d..e6536be 100644 --- a/TrimSPlib.js +++ b/TrimSPlib.js @@ -3351,11 +3351,12 @@ function adjust_table() function adjust_scans() { - // Deal with scans checkbox and type var scanSeq = document.getElementById("scanSeq").checked; - var sender = document.getElementById("scanSeq"); var scanType = document.getElementById("scanType").value; var scanThreads = document.getElementById("scanThreads"); + var scanControls = document.getElementById("ScansControls"); + var scanRange = document.getElementById("ScansTable"); + var scanList = document.getElementById("ScansLine"); // Default to half the available local worker slots, rounded down, while // keeping at least one process. Web mode keeps the default at 1 because @@ -3366,6 +3367,10 @@ function adjust_scans() availableThreads = Math.max(1, getParallelJobCount()); } scanThreads.max = availableThreads; + scanThreads.disabled = amIWeb(); + scanThreads.title = amIWeb() + ? 'The web backend currently runs scan points sequentially.' + : `This computer provides up to ${availableThreads} worker slots.`; if (scanThreads.value === "" || Number(scanThreads.value) < 1) { scanThreads.value = Math.max(1, Math.floor(availableThreads / 2)); @@ -3374,23 +3379,85 @@ function adjust_scans() } } - // consider only if scans checkbox is checked - if (scanSeq) { - ShowHide(1,'ScansControls'); - // Which type of scan - if (scanType == "scanLoop") { - ShowHide(1,'ScansTable'); - ShowHide(0,'ScansLine'); + scanControls.hidden = !scanSeq; + if (!scanSeq) { + scanRange.hidden = true; + scanList.hidden = true; + } else if (scanType === "scanLoop") { + scanRange.hidden = false; + scanList.hidden = true; + } else { + scanRange.hidden = true; + scanList.hidden = false; + } + + updateScanSummary(); +} + +function updateScanSummary() +{ + var summary = document.getElementById("scanSummary"); + if (!summary) { return; } + + if (!document.getElementById("scanSeq").checked) { + summary.dataset.level = "idle"; + summary.textContent = "Single simulation using the Layers settings."; + return; + } + + var scanType = document.getElementById("scanType").value; + var parameterSelect = document.getElementById("comboScan"); + var parameter = parameterSelect.options[parameterSelect.selectedIndex].text; + var workers = Number(document.getElementById("scanThreads").value) || 1; + var pointCount = 0; + var detail = ""; + var warning = ""; + + if (scanType === "scanLoop") { + var from = Number(document.getElementById("scanFrom").value); + var step = Number(document.getElementById("scanStep").value); + var to = Number(document.getElementById("scanTo").value); + if (!Number.isFinite(from) || !Number.isFinite(step) || !Number.isFinite(to)) { + warning = "Enter numeric From, Step, and To values."; + } else if (step <= 0) { + warning = "Step must be greater than zero."; + } else if (to < from) { + warning = "To must be greater than or equal to From."; } else { - ShowHide(1,'ScansLine'); - ShowHide(0,'ScansTable'); + pointCount = Math.floor((to - from) / step) + 1; + detail = `${from} to ${to}, step ${step}`; } } else { - // Hide both - ShowHide(0,'ScansControls'); - ShowHide(0,'ScansTable'); - ShowHide(0,'ScansLine'); + var values = document.getElementById("scanList").value + .split(",") + .map(value => value.trim()) + .filter(value => value !== ""); + var depthValues = document.getElementById("scanListdz").value + .split(",") + .map(value => value.trim()) + .filter(value => value !== ""); + if (values.length === 0) { + warning = "Enter at least one scan value."; + } else if (values.some(value => !Number.isFinite(Number(value)))) { + warning = "Every scan value must be numeric."; + } else if (depthValues.length > 0 && depthValues.length !== values.length) { + warning = "Depth increments must match the number of scan values."; + } else { + pointCount = values.length; + detail = values.join(", "); + } } + + if (warning) { + summary.dataset.level = "warning"; + summary.textContent = warning; + return; + } + + summary.dataset.level = "ready"; + var pointLabel = pointCount === 1 ? "point" : "points"; + var workerLabel = workers === 1 ? "worker" : "workers"; + summary.textContent = `${parameter} | ${pointCount} ${pointLabel} | ${workers} ${workerLabel} | ${detail}`; } function ProjSmartDefaults() @@ -3419,17 +3486,6 @@ function ProjNumberLimit() } -function ShowHide(onoff,item) { -// this function can switch item to visible/hidden onoff=1/0 - if(onoff){ -// document.getElementById(item).style.display = 'block'; - document.getElementById(item).style.visibility = "visible"; - } else { -// document.getElementById(item).style.display = 'none'; - document.getElementById(item).style.visibility = "hidden"; - } -} - function prep_cfg(toggle) { // This function collects various values and returns // toggle=1 : returns object with all values diff --git a/ZGUI.css b/ZGUI.css index b970e28..25fe291 100644 --- a/ZGUI.css +++ b/ZGUI.css @@ -339,23 +339,90 @@ input[type="button"]:hover { background: #f5f8f5; } -#ScansControls, -#ScansLine, -#ScansTable { - margin-top: 12px; +.scan-panel { + margin: 0 auto; + max-width: 900px; + padding: 12px 14px; } -#ScansControls table, -#ScansTable { +.scan-heading { + align-items: center; + display: flex; + justify-content: space-between; + margin-bottom: 12px; +} + +.scan-heading h2 { + color: var(--trim-deep); + font-family: Georgia, "Times New Roman", serif; + font-size: 1.08rem; + margin: 0; +} + +.scan-enable { + align-items: center; + background: var(--trim-panel-soft); + border: 1px solid var(--trim-border); + border-radius: 999px; + cursor: pointer; + display: flex; + font-size: 0.8rem; + font-weight: 700; + gap: 7px; + padding: 6px 10px; +} + +.scan-enable input { + accent-color: var(--trim-accent); + height: 16px; + margin: 0; + width: 16px; +} + +.scan-controls-grid { + display: grid; + gap: 9px; + grid-template-columns: minmax(160px, 0.8fr) minmax(240px, 1.4fr) minmax(100px, 0.5fr); +} + +.scan-values-panel { background: var(--trim-panel-soft); border: 1px solid var(--trim-border); border-radius: 8px; - padding: 8px; + display: grid; + gap: 9px; + margin-top: 10px; + padding: 10px; } -#ScansControls td, -#ScansTable td { - padding: 6px; +.scan-range-grid { + grid-template-columns: repeat(3, minmax(0, 1fr)); +} + +.scan-list-grid { + grid-template-columns: minmax(260px, 1.5fr) minmax(220px, 1fr); +} + +.scan-summary { + background: #eef4f4; + border-left: 3px solid var(--trim-focus); + border-radius: 5px; + color: #31515a; + font-size: 0.8rem; + margin-top: 11px; + min-height: 31px; + padding: 7px 10px; +} + +.scan-summary[data-level="idle"] { + border-left-color: #91a3a7; + color: var(--trim-muted); +} + +.scan-summary[data-level="warning"] { + background: #fff1c9; + border-left-color: #c79824; + color: #674c0a; } #Plots > table td { @@ -566,7 +633,10 @@ input[type="button"]:hover { .output-settings, .layers-workspace, - .projectile-grid { + .projectile-grid, + .scan-controls-grid, + .scan-range-grid, + .scan-list-grid { grid-template-columns: 1fr; } diff --git a/scripts/deploy-web.sh b/scripts/deploy-web.sh new file mode 100755 index 0000000..41dfc39 --- /dev/null +++ b/scripts/deploy-web.sh @@ -0,0 +1,85 @@ +#!/usr/bin/env bash +# Copyright (C) 2026 Zaher Salman +# SPDX-License-Identifier: GPL-2.0-only + +set -euo pipefail + +project_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +web_target="${TRIMSP_WEB_TARGET:-/var/www/html/TRIMSP-NL}" +cgi_target="${TRIMSP_CGI_TARGET:-/var/www/cgi-bin/singleTrimSP.cgi}" +trimsp_target="${TRIMSP_BIN_TARGET:-/usr/local/bin/trimspNL}" +web_owner="${TRIMSP_WEB_OWNER:-apache}" +web_group="${TRIMSP_WEB_GROUP:-apache}" +build_static="${TRIMSP_BUILD_STATIC:-1}" +binary_source="${TRIMSP_BINARY_SOURCE:-$project_root/fortran/trimspNL-static}" +plotly_source="$project_root/node_modules/plotly.js-dist/plotly.js" + +if [[ "$build_static" == "1" ]]; then + make -C "$project_root/fortran" trimspNL-static +elif [[ "$build_static" != "0" ]]; then + printf 'TRIMSP_BUILD_STATIC must be 0 or 1, not %s\n' "$build_static" >&2 + exit 2 +fi + +if [[ ! -f "$binary_source" ]]; then + printf 'TRIM.SP-NL binary not found: %s\n' "$binary_source" >&2 + printf 'Build it first or set TRIMSP_BINARY_SOURCE.\n' >&2 + exit 1 +fi + +if [[ ! -f "$plotly_source" ]]; then + printf 'Plotly browser bundle not found: %s\n' "$plotly_source" >&2 + printf 'Run npm install before deploying.\n' >&2 + exit 1 +fi + +perl -c "$project_root/singleTrimSP.cgi" >/dev/null + +# Install only files used by the browser application. Electron-specific code, +# package metadata, tests, and Fortran sources stay outside the document root. +install -d -m 0755 "$web_target" "$(dirname "$cgi_target")" "$(dirname "$trimsp_target")" +install -m 0644 \ + "$project_root/TrimSP.html" \ + "$project_root/TrimSPlib.js" \ + "$project_root/TrimSPweb.js" \ + "$project_root/myplots.js" \ + "$project_root/ZGUI.css" \ + "$project_root/plotly.css" \ + "$project_root/icon.png" \ + "$web_target/" +install -m 0644 "$plotly_source" "$web_target/plotly.js" +ln -sfn TrimSP.html "$web_target/index.html" + +install -m 0755 "$binary_source" "$trimsp_target" + +# Keep the repository CGI generic while embedding the selected installed +# binary path in the deployed copy. +cgi_stage="$(mktemp)" +trap 'rm -f "$cgi_stage"' EXIT +escaped_trimsp_target="${trimsp_target//\\/\\\\}" +escaped_trimsp_target="${escaped_trimsp_target//&/\\&}" +escaped_trimsp_target="${escaped_trimsp_target//|/\\|}" +sed "s|^\\\$trimbin=.*|\$trimbin=\"timeout 300 $escaped_trimsp_target\";|" \ + "$project_root/singleTrimSP.cgi" > "$cgi_stage" +install -m 0755 "$cgi_stage" "$cgi_target" + +if getent passwd "$web_owner" >/dev/null 2>&1 && getent group "$web_group" >/dev/null 2>&1; then + chown -R "$web_owner:$web_group" "$web_target" + chown "$web_owner:$web_group" "$cgi_target" +else + printf 'Warning: owner/group %s:%s does not exist; ownership was not changed.\n' \ + "$web_owner" "$web_group" >&2 +fi + +if command -v restorecon >/dev/null 2>&1; then + restorecon -RF "$web_target" "$cgi_target" "$trimsp_target" || true +fi + +printf 'Installed TRIMSP-NL web assets in %s\n' "$web_target" +printf 'Installed TRIMSP-NL CGI runner at %s\n' "$cgi_target" +printf 'Installed TRIM.SP-NL engine at %s\n' "$trimsp_target" +printf '\nThe web server must provide:\n' +printf ' /cgi-bin/singleTrimSP.cgi -> %s\n' "$cgi_target" +printf ' /tmp/ -> /tmp/ (simulation results and archives)\n' +printf 'For SELinux deployments, verify CGI execution and write access with:\n' +printf ' ausearch -m avc -ts recent\n' diff --git a/singleTrimSP.cgi b/singleTrimSP.cgi new file mode 100644 index 0000000..a33042c --- /dev/null +++ b/singleTrimSP.cgi @@ -0,0 +1,82 @@ +#!/usr/bin/perl +# +# Copyright Zaher Salman 2023-. +# zaher.salman@psi.ch + +######## Relevant subdirectories and urls ######## +# May need modification on different servers +$trimbin="timeout 300 /usr/local/bin/trimspNL"; +$workroot="/tmp"; + +# Nothing need to be changed from here on +####################################################################### +######## Main body of code ######## +use CGI; + +my $in = new CGI; +# Collect parameters and their values from CGI +my ($name,$value); +$content = ""; +my $filename = ""; +my $rngType = "pcg32"; +my $lcount = 0; +foreach $name ($in->param) { + $value=$in->param($name); + if ($name eq "fn") { + # This is the file name + $filename = $value; + @prefix = split(/\//,$filename); + $randName = $prefix[2]; + $FILENAME = $prefix[3]; + } elsif ($name eq "rngType") { + # RNG backend passed as the optional second trimspNL argument. + if ($value =~ /^(ranlux|pcg32|xoshiro|xoshiro256)$/) { + $rngType = $value; + } + } elsif ( index($name, "line") == 0) { + # This is a line start counting + #if ($lcount <=14) { + # Limit number of lines (15) to avoid abuse + $content = $content.$value."\n"; + #} + $lcount++; + } +} +$workPath = $workroot."/".$randName; +if (-d $workPath) { +# Directory exists, do nothing +} else { + $cmd="mkdir ".$workPath; + system($cmd); +} +$filename = $workPath."/".$FILENAME; +$fname = $filename; +$fname =~ s/\.inp$//; +$runBase = $FILENAME; +$runBase =~ s/\.inp$//; +open (OUTF,q{>}, $filename); +print OUTF ($content); +close(OUTF); + +print ("Content-type: text/ascii \n\n"); +if (index($filename,"_Seq_Results.dat") != -1) { + # This is the final call of a scan + $cmd = "cd $workPath; rm -f edist;"; + $cmd = $cmd."tar -cvzf ../$randName.tgz *; cd ..; rm -rf $randName;"; +} elsif (index($filename,"TrimSP.cfg") != -1) { + # Save config file only + $cmd = "cd $workPath;"; +} else { + # This is a call to simulate one step in a scan + $cmd = "cd $workPath; $trimbin $runBase $rngType >> /tmp/debug;"; +} +system($cmd); +#print "Written file $filename\n"; +#print $content; +print "command $cmd\n"; +foreach $name ($in->param) { + $value=$in->param($name); + print $name,"=",$value,"\n"; +} + +exit(0); From 8061f389fe14cf7135eb0187d3b3d49421e20f38 Mon Sep 17 00:00:00 2001 From: salman Date: Thu, 27 Aug 2026 21:42:31 +0200 Subject: [PATCH 3/9] Improve web results and diagnostics --- README.md | 28 +++-- TrimSP.html | 30 ++++- TrimSPlib.js | 28 ++++- TrimSPweb.js | 174 +++++++++++++---------------- ZGUI.css | 167 +++++++++++++++++++++++++--- myplots.js | 27 ++--- scripts/deploy-web.sh | 35 ++---- singleTrimSP.cgi | 247 ++++++++++++++++++++++++++++++------------ 8 files changed, 493 insertions(+), 243 deletions(-) diff --git a/README.md b/README.md index 55469dd..552c35b 100644 --- a/README.md +++ b/README.md @@ -235,38 +235,37 @@ script for an Apache-style server. With the default settings, run: sudo ./scripts/deploy-web.sh ``` -The script builds the static Fortran engine and installs: +The script expects the TRIM.SP-NL engine to be installed already and installs: ```text /var/www/html/TRIMSP-NL/ browser application /var/www/cgi-bin/singleTrimSP.cgi CGI runner -/usr/local/bin/trimspNL TRIM.SP-NL engine ``` +By default, the CGI uses the existing executable at +`/usr/local/bin/trimspNL`. Set `TRIMSP_BIN_PATH` if it is installed elsewhere. + Deployment paths and ownership can be changed through environment variables: | Variable | Default | Purpose | | --- | --- | --- | | `TRIMSP_WEB_TARGET` | `/var/www/html/TRIMSP-NL` | Browser asset directory | | `TRIMSP_CGI_TARGET` | `/var/www/cgi-bin/singleTrimSP.cgi` | Installed CGI path | -| `TRIMSP_BIN_TARGET` | `/usr/local/bin/trimspNL` | Installed engine path | | `TRIMSP_WEB_OWNER` | `apache` | Web file owner | | `TRIMSP_WEB_GROUP` | `apache` | Web file group | -| `TRIMSP_BUILD_STATIC` | `1` | Build the static engine before deployment | -| `TRIMSP_BINARY_SOURCE` | `fortran/trimspNL-static` | Binary used when installing | +| `TRIMSP_BIN_PATH` | `/usr/local/bin/trimspNL` | Existing engine executable used by the CGI | -For example, to deploy without rebuilding and use an existing binary: +For example, to use an engine installed in `/opt/trimsp/bin`: ```bash -sudo TRIMSP_BUILD_STATIC=0 \ - TRIMSP_BINARY_SOURCE=/path/to/trimspNL \ +sudo TRIMSP_BIN_PATH=/opt/trimsp/bin/trimspNL \ ./scripts/deploy-web.sh ``` -The web server must execute the CGI through `/cgi-bin/singleTrimSP.cgi` and -serve simulation files written below filesystem `/tmp` through URL `/tmp/`. -Review that mapping carefully so it exposes only the intended simulation -artifacts. On SELinux systems, inspect denied operations with: +The web server must execute the CGI through `/cgi-bin/singleTrimSP.cgi`. The +CGI keeps simulation files in filesystem `/tmp` and serves only requested run +outputs and archives; `/tmp` does not need to be exposed through the web +server. On SELinux systems, inspect denied operations with: ```bash ausearch -m avc -ts recent @@ -275,6 +274,11 @@ ausearch -m avc -ts recent The Apache account needs permission to execute the CGI and engine and to create the per-run directories in `/tmp`. +After a web run, the status bar provides the results archive through +**Outputs** and shows CGI status plus captured TRIM.SP-NL output through +**Details**. Diagnostic output is kept in the browser session only and is not +included in the `.tgz` archive. + ### Contact ### Zaher Salman diff --git a/TrimSP.html b/TrimSP.html index 83f5f14..321ce10 100644 --- a/TrimSP.html +++ b/TrimSP.html @@ -158,12 +158,14 @@
-
- - - - -
+
+
+
+
+
+
+
+
@@ -426,7 +428,22 @@
0%
+ + +
+
+ Web backend +

Run details

+
+ +
+

CGI responses and TRIM.SP-NL output for this browser session.

+
No web requests have been made yet.
+
+ + diff --git a/scripts/deploy-web.sh b/scripts/deploy-web.sh index a94ccac..b814e6b 100755 --- a/scripts/deploy-web.sh +++ b/scripts/deploy-web.sh @@ -31,6 +31,7 @@ perl -c "$project_root/singleTrimSP.cgi" >/dev/null install -d -m 0755 "$web_target" "$(dirname "$cgi_target")" install -m 0644 \ "$project_root/TrimSP.html" \ + "$project_root/quick-start.html" \ "$project_root/TrimSPlib.js" \ "$project_root/TrimSPweb.js" \ "$project_root/myplots.js" \ From 3b34ce31754240aab279b2383d876b7bc49e9875 Mon Sep 17 00:00:00 2001 From: salman Date: Fri, 28 Aug 2026 16:05:58 +0200 Subject: [PATCH 7/9] Hide web-only status actions in Electron --- ZGUI.css | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ZGUI.css b/ZGUI.css index 9f9bcde..ac563cf 100644 --- a/ZGUI.css +++ b/ZGUI.css @@ -662,6 +662,11 @@ input[type="button"]:hover { gap: 6px; } +/* Output downloads and CGI diagnostics exist only in the browser backend. */ +.status-actions[hidden] { + display: none; +} + .status-action-link, .status-details-button { border: 1px solid #8da5aa; From 6a33f04a6770dd24c56192441eac3718249a0c8a Mon Sep 17 00:00:00 2001 From: salman Date: Fri, 28 Aug 2026 16:13:56 +0200 Subject: [PATCH 8/9] Fix shared link URL and notification placement --- CITATION.cff | 2 +- README.md | 2 +- TrimSPelec.js | 2 +- TrimSPlib.js | 18 ++++++++++++++++-- ZGUI.css | 3 ++- main.js | 4 ++-- package.json | 2 +- 7 files changed, 24 insertions(+), 9 deletions(-) diff --git a/CITATION.cff b/CITATION.cff index 1616ad3..d0d8af3 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -4,7 +4,7 @@ title: "TRIMSP-NL Workbench" type: software version: 1.5.0 date-released: 2026-08-28 -url: "http://musruser.psi.ch/TrimSP-NL/" +url: "http://musruser.psi.ch/TRIMSP-NL/" repository-code: "https://gitea.psi.ch/LMU/TRIMSP" license: GPL-3.0-or-later authors: diff --git a/README.md b/README.md index a4b50d6..7849bfb 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,7 @@ Repository metadata is also provided in [CITATION.cff](CITATION.cff). ### Supported platforms ### * [Linux] -* Online at: http://musruser.psi.ch/TrimSP-NL/ +* Online at: http://musruser.psi.ch/TRIMSP-NL/ * Source repository: https://gitea.psi.ch/LMU/TRIMSP ### Installation ### diff --git a/TrimSPelec.js b/TrimSPelec.js index a27a7fa..a6dc358 100644 --- a/TrimSPelec.js +++ b/TrimSPelec.js @@ -18,7 +18,7 @@ window.openElectronAuxiliaryWindow = openElectronAuxiliaryWindow; // Electron links always point to the public web application rather than a // machine-local file:// URL, so they can be sent directly to another user. -var TRIMSP_CANONICAL_SHARE_URL = 'http://musruser.psi.ch/TrimSP-NL/'; +var TRIMSP_CANONICAL_SHARE_URL = 'http://musruser.psi.ch/TRIMSP-NL/'; function encodeElectronSharedConfiguration(config) { let json = JSON.stringify(config); diff --git a/TrimSPlib.js b/TrimSPlib.js index e90ff32..e92052c 100644 --- a/TrimSPlib.js +++ b/TrimSPlib.js @@ -3128,8 +3128,22 @@ function showTransientWarning(message, targetElement, duration = 4000) { warning.textContent = message; if (targetElement) { const rect = targetElement.getBoundingClientRect(); - warning.style.left = `${Math.max(12, rect.left + window.scrollX)}px`; - warning.style.top = `${rect.bottom + window.scrollY + 8}px`; + const viewportMargin = 12; + const targetGap = 8; + const maximumLeft = Math.max(viewportMargin, + window.innerWidth - warning.offsetWidth - viewportMargin); + const left = Math.min(Math.max(viewportMargin, rect.left), maximumLeft); + let top = rect.bottom + targetGap; + + // Keep notifications inside the viewport, including for controls near its + // right and bottom edges. Fixed-position elements use viewport coordinates. + if (top + warning.offsetHeight > window.innerHeight - viewportMargin) { + top = rect.top - warning.offsetHeight - targetGap; + } + top = Math.max(viewportMargin, + Math.min(top, window.innerHeight - warning.offsetHeight - viewportMargin)); + warning.style.left = `${left}px`; + warning.style.top = `${top}px`; } warning.classList.add('visible'); diff --git a/ZGUI.css b/ZGUI.css index ac563cf..a6a6bae 100644 --- a/ZGUI.css +++ b/ZGUI.css @@ -580,7 +580,8 @@ input[type="button"]:hover { box-shadow: 0 7px 20px rgba(29, 43, 50, 0.17); color: #523c08; line-height: 1.35; - max-width: 380px; + box-sizing: border-box; + max-width: min(380px, calc(100vw - 24px)); opacity: 0; padding: 9px 12px; pointer-events: none; diff --git a/main.js b/main.js index 8700046..6b3f5f8 100644 --- a/main.js +++ b/main.js @@ -156,7 +156,7 @@ function showAboutDialog(parentWindow) { '', 'Project repository:', 'Gitea: gitea.psi.ch/LMU/TRIMSP', - 'Web application: musruser.psi.ch/TrimSP-NL/' + 'Web application: musruser.psi.ch/TRIMSP-NL/' ].join('\n'); dialog.showMessageBox(parentWindow, { @@ -507,7 +507,7 @@ ipcMain.on('openQuickStartWindow', (event, options) => { ipcMain.handle('copyConfigurationLink', async (event, url) => { if (typeof url !== 'string' || url.length > 500000 - || !url.startsWith('http://musruser.psi.ch/TrimSP-NL/')) { + || !url.startsWith('http://musruser.psi.ch/TRIMSP-NL/')) { throw new Error('Invalid configuration link'); } clipboard.writeText(url); diff --git a/package.json b/package.json index 10c0428..2603194 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,7 @@ "plotly.js-dist": "^1.58.4" }, "config": {}, - "homepage": "http://musruser.psi.ch/TrimSP-NL/", + "homepage": "http://musruser.psi.ch/TRIMSP-NL/", "repository": { "type": "git", "url": "https://gitea.psi.ch/LMU/TRIMSP.git" From 46dc388a5692753ae1532ce34a4f6f07abf04c91 Mon Sep 17 00:00:00 2001 From: salman Date: Sun, 6 Sep 2026 21:37:27 +0200 Subject: [PATCH 9/9] Add Workbench citation, gitea link, and Quick Start contact --- TrimSP.html | 44 +++++++++++++++++++++++++++----------------- ZGUI.css | 20 ++++++++++++++++++-- quick-start.html | 3 +++ 3 files changed, 48 insertions(+), 19 deletions(-) diff --git a/TrimSP.html b/TrimSP.html index 08f53e5..7fc8566 100644 --- a/TrimSP.html +++ b/TrimSP.html @@ -408,23 +408,33 @@ diff --git a/ZGUI.css b/ZGUI.css index a6a6bae..6fe4304 100644 --- a/ZGUI.css +++ b/ZGUI.css @@ -786,7 +786,8 @@ input[type="button"]:hover { body.quick-start-document { background: var(--trim-panel); - display: block; + display: flex; + flex-direction: column; height: 100%; min-height: 100%; overflow: hidden; @@ -794,7 +795,8 @@ body.quick-start-document { } .quick-start-document .quick-start-body { - height: 100%; + flex: 1 1 auto; + min-height: 0; } .quick-start-body { @@ -836,6 +838,20 @@ body.quick-start-document { border-left-color: var(--trim-accent); } +.quick-start-footer { + background: var(--trim-panel-soft); + border-top: 1px solid var(--trim-border); + flex: 0 0 auto; + font-size: 90%; + opacity: 0.75; + padding: 8px 16px; + text-align: center; +} + +.quick-start-footer a { + color: var(--trim-deep); +} + .run-details-heading { align-items: center; display: flex; diff --git a/quick-start.html b/quick-start.html index 741c4d7..d165982 100644 --- a/quick-start.html +++ b/quick-start.html @@ -44,6 +44,9 @@ +