From bccb66f250a6dffd73daf812b7aca05b079f8b9e Mon Sep 17 00:00:00 2001 From: Stan Grams Date: Tue, 31 Mar 2026 02:32:16 +0200 Subject: [PATCH] [feat](trx-frontend-http): replace header REC button with Recorder tab page Co-Authored-By: Claude Opus 4.6 Signed-off-by: Stan Grams --- .../trx-frontend-http/assets/web/app.js | 112 +++++++++++++++--- .../trx-frontend-http/assets/web/index.html | 24 +++- .../trx-frontend-http/assets/web/style.css | 63 ++++++++-- 3 files changed, 166 insertions(+), 33 deletions(-) diff --git a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/app.js b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/app.js index 8f7f673..fd330c2 100644 --- a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/app.js +++ b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/app.js @@ -254,7 +254,8 @@ function applyAuthRestrictions() { "settings-clear-ft2-history", "settings-clear-wspr-history", "settings-clear-sat-history", - "header-rec-btn" + "recorder-start-btn", + "recorder-stop-btn" ]; pluginToggleBtns.forEach(id => { const btn = document.getElementById(id); @@ -4300,12 +4301,13 @@ if (spectrumBwSweetBtn) { } // --- Tab navigation --- -const TAB_ORDER = ["main", "bookmarks", "digital-modes", "map", "statistics", "settings", "about"]; +const TAB_ORDER = ["main", "bookmarks", "digital-modes", "map", "statistics", "recorder", "settings", "about"]; const TAB_PATHS = { main: "/", bookmarks: "/bookmarks", "digital-modes": "/digital-modes", map: "/map", + recorder: "/recorder", settings: "/settings", about: "/about", }; @@ -4353,6 +4355,9 @@ function navigateToTab(name, options = {}) { if (name === "statistics") { scheduleStatsRender(); } + if (name === "recorder") { + refreshRecorderStatus(); + } } document.querySelector(".tab-bar").addEventListener("click", (e) => { @@ -8857,31 +8862,100 @@ if (headerAudioToggle) { headerAudioToggle.addEventListener("click", startRxAudio); } -// ── Recorder button ──────────────────────────────────────────────────────── -const headerRecBtn = document.getElementById("header-rec-btn"); +// ── Recorder page ────────────────────────────────────────────────────────── let recorderActive = false; -function syncRecorderBtn() { - if (!headerRecBtn) return; - headerRecBtn.classList.toggle("rec-active", recorderActive); +const recorderStartBtn = document.getElementById("recorder-start-btn"); +const recorderStopBtn = document.getElementById("recorder-stop-btn"); +const recorderStatusInd = document.getElementById("recorder-status-indicator"); + +function syncRecorderUi() { + if (recorderStartBtn) recorderStartBtn.disabled = recorderActive; + if (recorderStopBtn) recorderStopBtn.disabled = !recorderActive; + if (recorderStatusInd) { + recorderStatusInd.textContent = recorderActive ? "Recording" : ""; + recorderStatusInd.classList.toggle("rec-active", recorderActive); + } + // Sync the tab icon indicator. + const tabBtn = document.querySelector('.tab[data-tab="recorder"]'); + if (tabBtn) tabBtn.classList.toggle("rec-active", recorderActive); } -if (headerRecBtn) { - headerRecBtn.addEventListener("click", async () => { + +if (recorderStartBtn) { + recorderStartBtn.addEventListener("click", async () => { try { - if (recorderActive) { - await postPath("/api/recorder/stop"); - } else { - await postPath("/api/recorder/start"); - } + await postPath("/api/recorder/start"); } catch (e) { - console.error("Recorder toggle failed", e); + console.error("Recorder start failed", e); } }); } +if (recorderStopBtn) { + recorderStopBtn.addEventListener("click", async () => { + try { + await postPath("/api/recorder/stop"); + } catch (e) { + console.error("Recorder stop failed", e); + } + }); +} + window._syncRecorderState = function (enabled) { recorderActive = enabled; - syncRecorderBtn(); + syncRecorderUi(); }; +async function refreshRecorderStatus() { + try { + const [statusResp, filesResp] = await Promise.all([ + fetch("/api/recorder/status"), + fetch("/api/recorder/files"), + ]); + if (statusResp.ok) { + const active = await statusResp.json(); + renderRecorderActive(active); + } + if (filesResp.ok) { + const files = await filesResp.json(); + renderRecorderFiles(files); + } + } catch (e) { + console.error("Recorder status fetch failed", e); + } +} + +function renderRecorderActive(list) { + const el = document.getElementById("recorder-active-list"); + if (!el) return; + if (!list.length) { + el.innerHTML = '

No active recordings.

'; + return; + } + let html = ''; + for (const r of list) { + const started = new Date(r.started_at * 1000).toLocaleTimeString(); + const fname = r.path.split("/").pop(); + html += ``; + } + html += "
RigVChanFileStarted
${escapeMapHtml(r.rig_id)}${r.vchan_id ? escapeMapHtml(r.vchan_id) : "-"}${escapeMapHtml(fname)}${started}
"; + el.innerHTML = html; +} + +function renderRecorderFiles(list) { + const el = document.getElementById("recorder-files-list"); + if (!el) return; + if (!list.length) { + el.innerHTML = '

No recorded files.

'; + return; + } + let html = ''; + for (const f of list) { + const size = f.size < 1048576 ? (f.size / 1024).toFixed(1) + " KB" : (f.size / 1048576).toFixed(1) + " MB"; + html += ``; + } + html += "
FileSize
${escapeMapHtml(f.name)}${size}
"; + el.innerHTML = html; +} + const rxVolPct = document.getElementById("rx-vol-pct"); const txVolPct = document.getElementById("tx-vol-pct"); @@ -10513,14 +10587,14 @@ function createBookmarkChip(bm, colorMap, options = {}) { `` + - `${esc(freqStr)}` + + `${escapeMapHtml(freqStr)}` + `` + - `${esc(bm.name)}` + `${escapeMapHtml(bm.name)}` ) : ( "\u00a0" + esc(bm.name) + "" + "\u00a0" + escapeMapHtml(bm.name) + "" ); span.innerHTML = labelHtml; diff --git a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/index.html b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/index.html index 57b273a..0692a19 100644 --- a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/index.html +++ b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/index.html @@ -51,6 +51,9 @@ Statistics + -
@@ -1059,6 +1061,26 @@
+