-
Notifications
You must be signed in to change notification settings - Fork 0
[codex] Fix web WASM loader and dialogs #15
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,7 +4,9 @@ | |
| <meta charset="utf-8" /> | ||
| <meta name="viewport" content="width=device-width, initial-scale=1.0" /> | ||
| <title>i3rs Web</title> | ||
| <link rel="icon" href="i3rs.ico" /> | ||
| <link data-trunk rel="rust" href="Cargo.toml" data-target-name="i3rs_app" /> | ||
| <link data-trunk rel="copy-file" href="packaging/icons/i3rs.ico" /> | ||
| <link data-trunk rel="copy-file" href="../../test_data/VIR_LAP.ld" /> | ||
| <link data-trunk rel="copy-file" href="../../test_data/VIR_LAP.ldx" /> | ||
| <style> | ||
|
|
@@ -21,24 +23,41 @@ | |
| } | ||
|
|
||
| body { | ||
| position: relative; | ||
| } | ||
|
|
||
| .load-overlay { | ||
| position: fixed; | ||
| inset: 0; | ||
| z-index: 10; | ||
| display: grid; | ||
| grid-template-rows: auto 1fr; | ||
| place-items: center; | ||
| padding: 1rem; | ||
| background: rgba(15, 17, 21, 0.5); | ||
| backdrop-filter: blur(3px); | ||
| } | ||
|
|
||
| .load-overlay[hidden] { | ||
| display: none; | ||
| } | ||
|
|
||
| .app-shell { | ||
| padding: 0.75rem 1rem; | ||
| border-bottom: 1px solid rgba(255, 255, 255, 0.08); | ||
| background: linear-gradient(180deg, rgba(255, 255, 255, 0.04), rgba(255, 255, 255, 0)); | ||
| .load-modal { | ||
| width: min(40rem, 100%); | ||
| padding: 1rem; | ||
| border: 1px solid rgba(255, 255, 255, 0.12); | ||
| border-radius: 0.5rem; | ||
| background: #171a20; | ||
| box-shadow: 0 1.5rem 4rem rgba(0, 0, 0, 0.35); | ||
| } | ||
|
|
||
| .app-shell h1 { | ||
| .load-modal h1 { | ||
| margin: 0; | ||
| font-size: 1rem; | ||
| font-weight: 650; | ||
| letter-spacing: 0.02em; | ||
| } | ||
|
|
||
| .app-shell p { | ||
| .load-modal p { | ||
| margin: 0.3rem 0 0; | ||
| font-size: 0.9rem; | ||
| opacity: 0.75; | ||
|
|
@@ -133,30 +152,32 @@ | |
| </style> | ||
| </head> | ||
| <body> | ||
| <div class="app-shell"> | ||
| <h1>i3rs Web</h1> | ||
| <p>Open a MoTeC <code>.ld</code> file from the File menu inside the app.</p> | ||
| <div class="load-controls"> | ||
| <label> | ||
| LD file | ||
| <input id="ld-file" type="file" accept=".ld" /> | ||
| </label> | ||
| <label> | ||
| Optional LDX file | ||
| <input id="ldx-file" type="file" accept=".ldx" /> | ||
| </label> | ||
| <button id="load-session" type="button">Load Session</button> | ||
| <button id="load-vir-lap" type="button">Load VIR_LAP Sample</button> | ||
| </div> | ||
| <div id="load-status" class="load-status" data-state="idle" aria-live="polite"> | ||
| <span class="load-status-message">Web app starting...</span> | ||
| <canvas id="i3rs-canvas"></canvas> | ||
| <div id="load-overlay" class="load-overlay"> | ||
| <div class="load-modal" role="dialog" aria-labelledby="load-title" aria-modal="true"> | ||
| <h1 id="load-title">Open telemetry</h1> | ||
| <p>Select a MoTeC <code>.ld</code> file and optional <code>.ldx</code> sidecar together.</p> | ||
| <div class="load-controls"> | ||
| <label for="session-files"> | ||
| Session files | ||
| <input | ||
| id="session-files" | ||
| name="session-files" | ||
| type="file" | ||
| accept=".ld,.ldx" | ||
| multiple | ||
| /> | ||
| </label> | ||
| <button id="load-vir-lap" type="button">Load VIR_LAP Sample</button> | ||
| </div> | ||
| <div id="load-status" class="load-status" data-state="idle" aria-live="polite"> | ||
| <span class="load-status-message">Web app starting...</span> | ||
| </div> | ||
| </div> | ||
| </div> | ||
| <canvas id="i3rs-canvas"></canvas> | ||
| <script> | ||
| const ldInput = document.getElementById("ld-file"); | ||
| const ldxInput = document.getElementById("ldx-file"); | ||
| const loadButton = document.getElementById("load-session"); | ||
| const loadOverlay = document.getElementById("load-overlay"); | ||
| const sessionFilesInput = document.getElementById("session-files"); | ||
| const sampleButton = document.getElementById("load-vir-lap"); | ||
| const statusEl = document.getElementById("load-status"); | ||
| const statusMessageEl = statusEl.querySelector(".load-status-message"); | ||
|
|
@@ -165,9 +186,13 @@ <h1>i3rs Web</h1> | |
|
|
||
| const readAsUint8Array = async (file) => new Uint8Array(await file.arrayBuffer()); | ||
| const readAsText = async (file) => await file.text(); | ||
| const extensionOf = (file) => file.name.split(".").pop()?.toLowerCase() ?? ""; | ||
| const waitForPaint = async () => | ||
| await new Promise((resolve) => requestAnimationFrame(() => requestAnimationFrame(resolve))); | ||
| const notifyLoadState = (state) => { | ||
| if (state.state !== "success") { | ||
| loadOverlay.hidden = false; | ||
| } | ||
| window.i3rsLoadState = state; | ||
| statusEl.dataset.state = state.state; | ||
| statusMessageEl.textContent = state.message; | ||
|
|
@@ -180,7 +205,7 @@ <h1>i3rs Web</h1> | |
| } | ||
| }; | ||
| const setBusy = (busy) => { | ||
| loadButton.disabled = busy; | ||
| sessionFilesInput.disabled = busy; | ||
| sampleButton.disabled = busy; | ||
| }; | ||
| const setLoadState = (state, message, extra = {}) => { | ||
|
|
@@ -221,6 +246,8 @@ <h1>i3rs Web</h1> | |
| parseDurationMs, | ||
| summary, | ||
| }); | ||
| await waitForPaint(); | ||
| loadOverlay.hidden = true; | ||
| return summary; | ||
| }; | ||
|
|
||
|
|
@@ -241,12 +268,19 @@ <h1>i3rs Web</h1> | |
| setLoadState("error", "Web app startup timed out."); | ||
| }; | ||
|
|
||
| loadButton.addEventListener("click", async () => { | ||
| const ldFile = ldInput.files && ldInput.files[0]; | ||
| const loadSelectedFiles = async () => { | ||
| const selectedFiles = Array.from(sessionFilesInput.files ?? []); | ||
| const ldFiles = selectedFiles.filter((file) => extensionOf(file) === "ld"); | ||
| const ldxFiles = selectedFiles.filter((file) => extensionOf(file) === "ldx"); | ||
| const ldFile = ldFiles[0]; | ||
| if (!ldFile) { | ||
| setLoadState("error", "Choose an .ld file first."); | ||
| return; | ||
| } | ||
| if (ldFiles.length > 1) { | ||
| setLoadState("error", "Choose only one .ld file."); | ||
| return; | ||
| } | ||
|
|
||
| setBusy(true); | ||
| try { | ||
|
|
@@ -256,7 +290,7 @@ <h1>i3rs Web</h1> | |
| phase: "read", | ||
| }); | ||
| const ldBytes = await readAsUint8Array(ldFile); | ||
| const ldxFile = ldxInput.files && ldxInput.files[0]; | ||
| const ldxFile = ldxFiles[0]; | ||
| let ldxText = null; | ||
| if (ldxFile) { | ||
| setLoadState("loading", `Reading ${ldxFile.name}...`, { | ||
|
|
@@ -277,6 +311,12 @@ <h1>i3rs Web</h1> | |
| } finally { | ||
| setBusy(false); | ||
| } | ||
| }; | ||
|
|
||
| sessionFilesInput.addEventListener("change", () => { | ||
| if (sessionFilesInput.files && sessionFilesInput.files.length > 0) { | ||
| void loadSelectedFiles(); | ||
| } | ||
|
Comment on lines
+316
to
+319
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The new auto-load flow only runs on the file input Useful? React with 👍 / 👎. |
||
| }); | ||
|
|
||
| sampleButton.addEventListener("click", async () => { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The combined picker now accepts multiple files, but only
.ldcount is validated;.ldxusesldxFiles[0]and silently ignores additional sidecars. If a user selects more than one.ldx, the app may attach the wrong sidecar without warning, leading to incorrect channel metadata for the loaded session.Useful? React with 👍 / 👎.