From ec59908e0ddd538b577f46096f171ed793e70555 Mon Sep 17 00:00:00 2001 From: Stan Grams Date: Sat, 1 Aug 2026 12:36:27 +0200 Subject: [PATCH] refactor: convert background decode controls to TypeScript --- .../assets/web/generated/background-decode.js | 680 +++++++++--------- .../assets/web/generated/plugin-loader.js | 2 +- .../trx-frontend-http/frontend/build.mjs | 2 +- .../frontend/src/plugin-loader.ts | 2 +- ...kground-decode.js => background-decode.ts} | 222 +++--- .../frontend/tests/background-decode.test.mjs | 46 ++ 6 files changed, 531 insertions(+), 423 deletions(-) rename src/trx-client/trx-frontend/trx-frontend-http/frontend/src/plugins/{background-decode.js => background-decode.ts} (60%) create mode 100644 src/trx-client/trx-frontend/trx-frontend-http/frontend/tests/background-decode.test.mjs diff --git a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/generated/background-decode.js b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/generated/background-decode.js index 49400519..a425cb37 100644 --- a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/generated/background-decode.js +++ b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/generated/background-decode.js @@ -1,355 +1,365 @@ "use strict"; -(function() { - "use strict"; - function bgdSupportedIds() { - return (window.decoderRegistry || []).filter(function(d) { - return d.background_decode; - }).map(function(d) { - return d.id; - }); - } - let backgroundDecodeRole = null; - let currentRigId = null; - let currentConfig = null; - let bookmarkList = []; - let statusInterval = null; - let bgdDirty = false; - function initBackgroundDecode(rigId, role) { - backgroundDecodeRole = role; - currentRigId = rigId || null; - if (currentRigId) loadBackgroundDecode(); - startStatusPolling(); - } - function setBackgroundDecodeRig(rigId) { - const nextRigId = rigId || null; - if (nextRigId === currentRigId) return; - currentRigId = nextRigId; - if (!currentRigId) return; - loadBackgroundDecode(); - } - function apiGetConfig(rigId) { - return fetch("/background-decode/" + encodeURIComponent(rigId)).then(function(r) { - if (!r.ok) throw new Error("HTTP " + r.status); - return r.json(); - }); - } - function apiPutConfig(rigId, config) { - return fetch("/background-decode/" + encodeURIComponent(rigId), { - method: "PUT", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify(config) - }).then(function(r) { - if (!r.ok) throw new Error("HTTP " + r.status); - return r.json(); - }); - } - function apiResetConfig(rigId) { - return fetch("/background-decode/" + encodeURIComponent(rigId), { - method: "DELETE" - }).then(function(r) { - if (!r.ok) throw new Error("HTTP " + r.status); - return r.json(); - }); - } - function apiGetStatus(rigId) { - return fetch("/background-decode/" + encodeURIComponent(rigId) + "/status").then(function(r) { - if (!r.ok) throw new Error("HTTP " + r.status); - return r.json(); - }); - } - function apiGetBookmarks() { - return fetch("/bookmarks").then(function(r) { - if (!r.ok) throw new Error("HTTP " + r.status); - return r.json(); - }); - } - function loadBackgroundDecode() { - const rigId = currentRigId; - if (!rigId) return; - Promise.all([apiGetConfig(rigId), apiGetBookmarks()]).then(function([config, bookmarks]) { - currentConfig = config || { remote: rigId, enabled: false, bookmark_ids: [] }; - bookmarkList = Array.isArray(bookmarks) ? bookmarks : []; - renderBackgroundDecode(); - clearBgdDirty(); - pollBackgroundDecodeStatus(); - }).catch(function(err) { - console.error("background decode load failed", err); - }); - } - function supportedBookmarks() { - return bookmarkList.filter(function(bookmark) { - return bookmarkDecoderKinds(bookmark).length > 0; - }); - } - function bookmarkDecoderKinds(bookmark) { - var ids = bgdSupportedIds(); - var decoders = Array.isArray(bookmark && bookmark.decoders) ? bookmark.decoders : []; - var explicit = decoders.map(function(item) { - return String(item || "").trim().toLowerCase(); - }).filter(function(item, index, arr) { - return ids.indexOf(item) >= 0 && arr.indexOf(item) === index; - }); - if (explicit.length > 0) return explicit; - var mode = String(bookmark && bookmark.mode || "").trim().toUpperCase(); - return (window.decoderRegistry || []).filter(function(d) { - return d.activation === "mode_bound" && d.background_decode && d.active_modes.indexOf(mode) >= 0; - }).map(function(d) { - return d.id; - }); - } - function renderBackgroundDecode() { - if (!currentConfig) { - currentConfig = { remote: currentRigId, enabled: false, bookmark_ids: [] }; - } - setCheckbox("background-decode-enabled", !!currentConfig.enabled); - renderBookmarkChecklist(); - const isControl = backgroundDecodeRole === "control" || typeof authEnabled !== "undefined" && !authEnabled; - const panel = document.getElementById("background-decode-panel"); - if (panel) { - panel.querySelectorAll("input, select, button.sch-write").forEach(function(el) { - el.disabled = !isControl; +(() => { + // src/plugins/background-decode.ts + var bgdWindow = window; + (function() { + "use strict"; + function bgdSupportedIds() { + return (bgdWindow.decoderRegistry || []).filter(function(d) { + return d.background_decode; + }).map(function(d) { + return d.id; }); } - const saveBtn = document.getElementById("background-decode-save-btn"); - const resetBtn = document.getElementById("background-decode-reset-btn"); - if (saveBtn) saveBtn.style.display = isControl ? "" : "none"; - if (resetBtn) resetBtn.style.display = isControl ? "" : "none"; - } - function renderBookmarkChecklist(filterText) { - const container = document.getElementById("bgd-bookmark-checklist"); - if (!container) return; - container.innerHTML = ""; - const selectedIds = new Set( - currentConfig && Array.isArray(currentConfig.bookmark_ids) ? currentConfig.bookmark_ids : [] - ); - const all = supportedBookmarks(); - const filter = (filterText || "").trim().toLowerCase(); - const filtered = filter ? all.filter(function(bm) { - var text = (bm.name + " " + formatFreq(bm.freq_hz) + " " + bm.mode).toLowerCase(); - return text.indexOf(filter) >= 0; - }) : all; - if (filtered.length === 0) { - container.innerHTML = '
' + (all.length === 0 ? "No supported bookmarks available." : "No bookmarks match filter.") + "
"; - return; + let backgroundDecodeRole = null; + let currentRigId = null; + let currentConfig = null; + let bookmarkList = []; + let statusInterval = null; + let bgdDirty = false; + function initBackgroundDecode(rigId, role) { + backgroundDecodeRole = role; + currentRigId = rigId || null; + if (currentRigId) loadBackgroundDecode(); + startStatusPolling(); } - filtered.forEach(function(bookmark) { - var row = document.createElement("label"); - row.className = "bgd-checklist-row"; - var decoders = bookmarkDecoderKinds(bookmark); - var checked = selectedIds.has(bookmark.id) ? " checked" : ""; - row.innerHTML = '' + escHtml(bookmark.name) + '' + escHtml(formatFreq(bookmark.freq_hz) + " " + bookmark.mode + " · " + decoders.join("/").toUpperCase()) + ""; - row.querySelector("input").addEventListener("change", function(e) { - onChecklistToggle(bookmark.id, e.target.checked); - }); - container.appendChild(row); - }); - } - function onChecklistToggle(bookmarkId, checked) { - if (!currentConfig) { - currentConfig = { remote: currentRigId, enabled: false, bookmark_ids: [] }; + function setBackgroundDecodeRig(rigId) { + const nextRigId = rigId || null; + if (nextRigId === currentRigId) return; + currentRigId = nextRigId; + if (!currentRigId) return; + loadBackgroundDecode(); } - if (!Array.isArray(currentConfig.bookmark_ids)) currentConfig.bookmark_ids = []; - if (checked && !currentConfig.bookmark_ids.includes(bookmarkId)) { - currentConfig.bookmark_ids.push(bookmarkId); - } else if (!checked) { - currentConfig.bookmark_ids = currentConfig.bookmark_ids.filter(function(id) { - return id !== bookmarkId; + function apiGetConfig(rigId) { + return fetch("/background-decode/" + encodeURIComponent(rigId)).then(function(r) { + if (!r.ok) throw new Error(`HTTP ${String(r.status)}`); + return r.json(); }); } - markBgdDirty(); - } - function saveBackgroundDecode() { - const rigId = currentRigId; - if (!rigId) return; - const payload = { - remote: rigId, - enabled: !!document.getElementById("background-decode-enabled").checked, - bookmark_ids: Array.isArray(currentConfig && currentConfig.bookmark_ids) ? currentConfig.bookmark_ids.slice() : [] - }; - const btn = document.getElementById("background-decode-save-btn"); - if (btn) btn.disabled = true; - apiPutConfig(rigId, payload).then(function(saved) { - currentConfig = saved; - renderBackgroundDecode(); - clearBgdDirty(); - pollBackgroundDecodeStatus(); - showToast("Background decode saved."); - }).catch(function(err) { - showToast("Save failed: " + err.message, true); - }).finally(function() { - if (btn) btn.disabled = false; - }); - } - async function resetBackgroundDecode() { - const rigId = currentRigId; - if (!rigId) return; - if (!await window.trxUi.confirm({ title: "Reset background decoding?", message: "The current background decode configuration will be permanently reset.", confirmLabel: "Reset" })) return; - apiResetConfig(rigId).then(function(saved) { - currentConfig = saved; - renderBackgroundDecode(); - clearBgdDirty(); - pollBackgroundDecodeStatus(); - showToast("Background decode reset."); - }).catch(function(err) { - showToast("Reset failed: " + err.message, true); - }); - } - function startStatusPolling() { - if (statusInterval) clearInterval(statusInterval); - statusInterval = setInterval(pollBackgroundDecodeStatus, 15e3); - } - function pollBackgroundDecodeStatus() { - const rigId = currentRigId; - if (!rigId) return; - apiGetStatus(rigId).then(renderStatus).catch(function() { - }); - } - function renderStatus(status) { - const card = document.getElementById("background-decode-status-card"); - if (!card) return; - const entries = Array.isArray(status && status.entries) ? status.entries : []; - if (!entries.length) { - card.textContent = "No background decode bookmarks configured."; - return; + function apiPutConfig(rigId, config) { + return fetch("/background-decode/" + encodeURIComponent(rigId), { + method: "PUT", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(config) + }).then(function(r) { + if (!r.ok) throw new Error(`HTTP ${String(r.status)}`); + return r.json(); + }); } - const summary = []; - if (status.active_rig) { - if (Number.isFinite(status.center_hz)) summary.push("Center " + formatFreq(status.center_hz)); - if (Number.isFinite(status.sample_rate) && status.sample_rate > 0) summary.push("Span ±" + formatFreq(status.sample_rate / 2)); - } else { - summary.push("This rig is not currently selected for audio."); + function apiResetConfig(rigId) { + return fetch("/background-decode/" + encodeURIComponent(rigId), { + method: "DELETE" + }).then(function(r) { + if (!r.ok) throw new Error(`HTTP ${String(r.status)}`); + return r.json(); + }); } - let html = summary.length ? '
' + escHtml(summary.join(" · ")) + "
" : ""; - html += '
'; - entries.forEach(function(entry) { - const name = entry.bookmark_name || entry.bookmark_id || "Unknown bookmark"; - const parts = []; - if (Number.isFinite(entry.freq_hz)) parts.push(formatFreq(entry.freq_hz)); - if (entry.mode) parts.push(entry.mode); - if (Array.isArray(entry.decoder_kinds) && entry.decoder_kinds.length) { - parts.push(entry.decoder_kinds.join("/").toUpperCase()); + function apiGetStatus(rigId) { + return fetch("/background-decode/" + encodeURIComponent(rigId) + "/status").then(function(r) { + if (!r.ok) throw new Error(`HTTP ${String(r.status)}`); + return r.json(); + }); + } + function apiGetBookmarks() { + return fetch("/bookmarks").then(function(r) { + if (!r.ok) throw new Error(`HTTP ${String(r.status)}`); + return r.json(); + }); + } + function loadBackgroundDecode() { + const rigId = currentRigId; + if (!rigId) return; + Promise.all([apiGetConfig(rigId), apiGetBookmarks()]).then(function([config, bookmarks]) { + currentConfig = config; + bookmarkList = Array.isArray(bookmarks) ? bookmarks : []; + renderBackgroundDecode(); + clearBgdDirty(); + pollBackgroundDecodeStatus(); + }).catch(function(err) { + console.error("background decode load failed", err); + }); + } + function supportedBookmarks() { + return bookmarkList.filter(function(bookmark) { + return bookmarkDecoderKinds(bookmark).length > 0; + }); + } + function bookmarkDecoderKinds(bookmark) { + const ids = bgdSupportedIds(); + const decoders = bookmark.decoders ?? []; + const explicit = decoders.map(function(item) { + return item.trim().toLowerCase(); + }).filter(function(item, index, arr) { + return ids.indexOf(item) >= 0 && arr.indexOf(item) === index; + }); + if (explicit.length > 0) return explicit; + const mode = bookmark.mode.trim().toUpperCase(); + return (bgdWindow.decoderRegistry || []).filter(function(d) { + return d.activation === "mode_bound" && d.background_decode && d.active_modes.indexOf(mode) >= 0; + }).map(function(d) { + return d.id; + }); + } + function renderBackgroundDecode() { + if (!currentConfig) { + currentConfig = { remote: currentRigId, enabled: false, bookmark_ids: [] }; } - html += '
' + escHtml(name) + '
' + escHtml(parts.join(" · ")) + '
' + escHtml(prettyState(entry.state)) + "
"; - }); - html += "
"; - card.innerHTML = html; - } - function prettyState(state) { - switch (state) { - case "active": - return "✓ Active"; - case "out_of_span": - return "△ Out of span"; - case "waiting_for_spectrum": - return "△ Waiting"; - case "waiting_for_user": - return "△ No user"; - case "missing_bookmark": - return "✗ Missing"; - case "no_supported_decoders": - return "✗ Unsupported"; - case "disabled": - return "△ Disabled"; - case "handled_by_scheduler": - return "△ Scheduler"; - case "scheduler_has_control": - return "△ Scheduler"; - case "handled_by_virtual_channel": - return "△ VChan"; - default: - return "△ Inactive"; + setCheckbox("background-decode-enabled", currentConfig.enabled); + renderBookmarkChecklist(); + const isControl = backgroundDecodeRole === "control" || bgdWindow.authEnabled === false; + const panel = document.getElementById("background-decode-panel"); + if (panel) { + panel.querySelectorAll("input, select, button.sch-write").forEach(function(el) { + el.disabled = !isControl; + }); + } + const saveBtn = document.getElementById("background-decode-save-btn"); + const resetBtn = document.getElementById("background-decode-reset-btn"); + if (saveBtn) saveBtn.style.display = isControl ? "" : "none"; + if (resetBtn) resetBtn.style.display = isControl ? "" : "none"; } - } - function setCheckbox(id, value) { - const el = document.getElementById(id); - if (el) el.checked = !!value; - } - function formatFreq(hz) { - if (!Number.isFinite(hz) || hz <= 0) return "--"; - if (hz >= 1e6) return (hz / 1e6).toFixed(3).replace(/\.?0+$/, "") + " MHz"; - if (hz >= 1e3) return (hz / 1e3).toFixed(1).replace(/\.?0+$/, "") + " kHz"; - return hz + " Hz"; - } - function escHtml(value) { - return String(value == null ? "" : value).replace(/&/g, "&").replace(//g, ">").replace(/"/g, """); - } - function markBgdDirty() { - if (bgdDirty) return; - bgdDirty = true; - var btn = document.getElementById("background-decode-save-btn"); - if (btn) btn.classList.add("sch-dirty"); - } - function clearBgdDirty() { - bgdDirty = false; - var btn = document.getElementById("background-decode-save-btn"); - if (btn) btn.classList.remove("sch-dirty"); - } - function showToast(msg, isError) { - const el = document.getElementById("background-decode-toast"); - if (!el) return; - el.textContent = msg; - el.style.background = isError ? "var(--color-error, #c00)" : "var(--accent-green)"; - el.style.display = "block"; - setTimeout(function() { - el.style.display = "none"; - }, 3e3); - } - function selectAllBookmarks() { - if (!currentConfig) { - currentConfig = { remote: currentRigId, enabled: false, bookmark_ids: [] }; - } - var ids = supportedBookmarks().map(function(bm) { - return bm.id; - }); - currentConfig.bookmark_ids = ids; - renderBookmarkChecklist(document.getElementById("bgd-bookmark-filter")?.value); - markBgdDirty(); - } - function deselectAllBookmarks() { - if (!currentConfig) { - currentConfig = { remote: currentRigId, enabled: false, bookmark_ids: [] }; - } - currentConfig.bookmark_ids = []; - renderBookmarkChecklist(document.getElementById("bgd-bookmark-filter")?.value); - markBgdDirty(); - } - function wireBackgroundDecodeEvents() { - const filterInput = document.getElementById("bgd-bookmark-filter"); - if (filterInput && !filterInput._wired) { - filterInput._wired = true; - filterInput.addEventListener("input", function() { - renderBookmarkChecklist(filterInput.value); + function renderBookmarkChecklist(filterText = "") { + const container = document.getElementById("bgd-bookmark-checklist"); + if (!container) return; + container.innerHTML = ""; + const selectedIds = new Set( + currentConfig && Array.isArray(currentConfig.bookmark_ids) ? currentConfig.bookmark_ids : [] + ); + const all = supportedBookmarks(); + const filter = (filterText || "").trim().toLowerCase(); + const filtered = filter ? all.filter(function(bm) { + const text = (bm.name + " " + formatFreq(bm.freq_hz) + " " + bm.mode).toLowerCase(); + return text.indexOf(filter) >= 0; + }) : all; + if (filtered.length === 0) { + container.innerHTML = '
' + (all.length === 0 ? "No supported bookmarks available." : "No bookmarks match filter.") + "
"; + return; + } + filtered.forEach(function(bookmark) { + const row = document.createElement("label"); + row.className = "bgd-checklist-row"; + const decoders = bookmarkDecoderKinds(bookmark); + const checked = selectedIds.has(bookmark.id) ? " checked" : ""; + row.innerHTML = '' + escHtml(bookmark.name) + '' + escHtml(formatFreq(bookmark.freq_hz) + " " + bookmark.mode + " · " + decoders.join("/").toUpperCase()) + ""; + row.querySelector("input")?.addEventListener("change", function(e) { + onChecklistToggle(bookmark.id, e.currentTarget.checked); + }); + container.appendChild(row); }); } - const enabledCb = document.getElementById("background-decode-enabled"); - if (enabledCb && !enabledCb._wired) { - enabledCb._wired = true; - enabledCb.addEventListener("change", function() { - markBgdDirty(); + function onChecklistToggle(bookmarkId, checked) { + if (!currentConfig) { + currentConfig = { remote: currentRigId, enabled: false, bookmark_ids: [] }; + } + if (!Array.isArray(currentConfig.bookmark_ids)) currentConfig.bookmark_ids = []; + if (checked && !currentConfig.bookmark_ids.includes(bookmarkId)) { + currentConfig.bookmark_ids.push(bookmarkId); + } else if (!checked) { + currentConfig.bookmark_ids = currentConfig.bookmark_ids.filter(function(id) { + return id !== bookmarkId; + }); + } + markBgdDirty(); + } + function saveBackgroundDecode() { + const rigId = currentRigId; + if (!rigId) return; + const payload = { + remote: rigId, + enabled: document.getElementById("background-decode-enabled")?.checked ?? false, + bookmark_ids: currentConfig?.bookmark_ids.slice() ?? [] + }; + const btn = document.getElementById("background-decode-save-btn"); + if (btn) btn.disabled = true; + apiPutConfig(rigId, payload).then(function(saved) { + currentConfig = saved; + renderBackgroundDecode(); + clearBgdDirty(); + pollBackgroundDecodeStatus(); + showToast("Background decode saved.", false); + }).catch(function(err) { + showToast(`Save failed: ${errorMessage(err)}`, true); + }).finally(function() { + if (btn) btn.disabled = false; }); } - const selectAllBtn = document.getElementById("bgd-select-all-btn"); - if (selectAllBtn && !selectAllBtn._wired) { - selectAllBtn._wired = true; - selectAllBtn.addEventListener("click", selectAllBookmarks); + async function resetBackgroundDecode() { + const rigId = currentRigId; + if (!rigId) return; + if (!await bgdWindow.trxUi.confirm({ title: "Reset background decoding?", message: "The current background decode configuration will be permanently reset.", confirmLabel: "Reset" })) return; + apiResetConfig(rigId).then(function(saved) { + currentConfig = saved; + renderBackgroundDecode(); + clearBgdDirty(); + pollBackgroundDecodeStatus(); + showToast("Background decode reset.", false); + }).catch(function(err) { + showToast(`Reset failed: ${errorMessage(err)}`, true); + }); } - const deselectAllBtn = document.getElementById("bgd-deselect-all-btn"); - if (deselectAllBtn && !deselectAllBtn._wired) { - deselectAllBtn._wired = true; - deselectAllBtn.addEventListener("click", deselectAllBookmarks); + function startStatusPolling() { + if (statusInterval) clearInterval(statusInterval); + statusInterval = setInterval(pollBackgroundDecodeStatus, 15e3); } - const saveBtn = document.getElementById("background-decode-save-btn"); - if (saveBtn && !saveBtn._wired) { - saveBtn._wired = true; - saveBtn.addEventListener("click", saveBackgroundDecode); + function pollBackgroundDecodeStatus() { + const rigId = currentRigId; + if (!rigId) return; + apiGetStatus(rigId).then(renderStatus).catch(function() { + }); } - const resetBtn = document.getElementById("background-decode-reset-btn"); - if (resetBtn && !resetBtn._wired) { - resetBtn._wired = true; - resetBtn.addEventListener("click", resetBackgroundDecode); + function renderStatus(status) { + const card = document.getElementById("background-decode-status-card"); + if (!card) return; + const entries = status.entries ?? []; + if (!entries.length) { + card.textContent = "No background decode bookmarks configured."; + return; + } + const summary = []; + if (status.active_rig) { + if (typeof status.center_hz === "number" && Number.isFinite(status.center_hz)) summary.push("Center " + formatFreq(status.center_hz)); + if (typeof status.sample_rate === "number" && Number.isFinite(status.sample_rate) && status.sample_rate > 0) summary.push("Span ±" + formatFreq(status.sample_rate / 2)); + } else { + summary.push("This rig is not currently selected for audio."); + } + let html = summary.length ? '
' + escHtml(summary.join(" · ")) + "
" : ""; + html += '
'; + entries.forEach(function(entry) { + const name = entry.bookmark_name || entry.bookmark_id || "Unknown bookmark"; + const parts = []; + if (typeof entry.freq_hz === "number" && Number.isFinite(entry.freq_hz)) parts.push(formatFreq(entry.freq_hz)); + if (entry.mode) parts.push(entry.mode); + if (Array.isArray(entry.decoder_kinds) && entry.decoder_kinds.length) { + parts.push(entry.decoder_kinds.join("/").toUpperCase()); + } + html += '
' + escHtml(name) + '
' + escHtml(parts.join(" · ")) + '
' + escHtml(prettyState(entry.state)) + "
"; + }); + html += "
"; + card.innerHTML = html; } - } - window.initBackgroundDecode = initBackgroundDecode; - window.wireBackgroundDecodeEvents = wireBackgroundDecodeEvents; - window.setBackgroundDecodeRig = setBackgroundDecodeRig; + function prettyState(state) { + switch (state) { + case "active": + return "✓ Active"; + case "out_of_span": + return "△ Out of span"; + case "waiting_for_spectrum": + return "△ Waiting"; + case "waiting_for_user": + return "△ No user"; + case "missing_bookmark": + return "✗ Missing"; + case "no_supported_decoders": + return "✗ Unsupported"; + case "disabled": + return "△ Disabled"; + case "handled_by_scheduler": + return "△ Scheduler"; + case "scheduler_has_control": + return "△ Scheduler"; + case "handled_by_virtual_channel": + return "△ VChan"; + default: + return "△ Inactive"; + } + } + function setCheckbox(id, value) { + const el = document.getElementById(id); + if (el) el.checked = value; + } + function formatFreq(hz) { + if (!Number.isFinite(hz) || hz <= 0) return "--"; + if (hz >= 1e6) return (hz / 1e6).toFixed(3).replace(/\.?0+$/, "") + " MHz"; + if (hz >= 1e3) return (hz / 1e3).toFixed(1).replace(/\.?0+$/, "") + " kHz"; + return `${String(hz)} Hz`; + } + function escHtml(value) { + const text = typeof value === "string" || typeof value === "number" || typeof value === "boolean" ? String(value) : ""; + return text.replace(/&/g, "&").replace(//g, ">").replace(/"/g, """); + } + function errorMessage(error) { + return error instanceof Error ? error.message : String(error); + } + function markBgdDirty() { + if (bgdDirty) return; + bgdDirty = true; + const btn = document.getElementById("background-decode-save-btn"); + if (btn) btn.classList.add("sch-dirty"); + } + function clearBgdDirty() { + bgdDirty = false; + const btn = document.getElementById("background-decode-save-btn"); + if (btn) btn.classList.remove("sch-dirty"); + } + function showToast(msg, isError) { + const el = document.getElementById("background-decode-toast"); + if (!el) return; + el.textContent = msg; + el.style.background = isError ? "var(--color-error, #c00)" : "var(--accent-green)"; + el.style.display = "block"; + setTimeout(function() { + el.style.display = "none"; + }, 3e3); + } + function selectAllBookmarks() { + if (!currentConfig) { + currentConfig = { remote: currentRigId, enabled: false, bookmark_ids: [] }; + } + const ids = supportedBookmarks().map(function(bm) { + return bm.id; + }); + currentConfig.bookmark_ids = ids; + renderBookmarkChecklist(document.getElementById("bgd-bookmark-filter")?.value); + markBgdDirty(); + } + function deselectAllBookmarks() { + if (!currentConfig) { + currentConfig = { remote: currentRigId, enabled: false, bookmark_ids: [] }; + } + currentConfig.bookmark_ids = []; + renderBookmarkChecklist(document.getElementById("bgd-bookmark-filter")?.value); + markBgdDirty(); + } + function wireBackgroundDecodeEvents() { + const filterInput = document.getElementById("bgd-bookmark-filter"); + if (filterInput && !filterInput._wired) { + filterInput._wired = true; + filterInput.addEventListener("input", function() { + renderBookmarkChecklist(filterInput.value); + }); + } + const enabledCb = document.getElementById("background-decode-enabled"); + if (enabledCb && !enabledCb._wired) { + enabledCb._wired = true; + enabledCb.addEventListener("change", function() { + markBgdDirty(); + }); + } + const selectAllBtn = document.getElementById("bgd-select-all-btn"); + if (selectAllBtn && !selectAllBtn._wired) { + selectAllBtn._wired = true; + selectAllBtn.addEventListener("click", selectAllBookmarks); + } + const deselectAllBtn = document.getElementById("bgd-deselect-all-btn"); + if (deselectAllBtn && !deselectAllBtn._wired) { + deselectAllBtn._wired = true; + deselectAllBtn.addEventListener("click", deselectAllBookmarks); + } + const saveBtn = document.getElementById("background-decode-save-btn"); + if (saveBtn && !saveBtn._wired) { + saveBtn._wired = true; + saveBtn.addEventListener("click", saveBackgroundDecode); + } + const resetBtn = document.getElementById("background-decode-reset-btn"); + if (resetBtn && !resetBtn._wired) { + resetBtn._wired = true; + resetBtn.addEventListener("click", () => { + void resetBackgroundDecode(); + }); + } + } + bgdWindow.initBackgroundDecode = initBackgroundDecode; + bgdWindow.wireBackgroundDecodeEvents = wireBackgroundDecodeEvents; + bgdWindow.setBackgroundDecodeRig = setBackgroundDecodeRig; + })(); })(); diff --git a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/generated/plugin-loader.js b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/generated/plugin-loader.js index 026d8e7c..36edf444 100644 --- a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/generated/plugin-loader.js +++ b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/generated/plugin-loader.js @@ -10,7 +10,7 @@ const pluginGroups = { }; const loaded = /* @__PURE__ */ new Set(); const loading = /* @__PURE__ */ new Map(); -const modulePlugins = /* @__PURE__ */ new Set(["/ft8.js", "/ft4.js", "/ft2.js", "/wspr.js", "/cw.js", "/vdes.js", "/wefax.js"]); +const modulePlugins = /* @__PURE__ */ new Set(["/ft8.js", "/ft4.js", "/ft2.js", "/wspr.js", "/cw.js", "/vdes.js", "/wefax.js", "/background-decode.js"]); function loadLegacyScript(path) { return new Promise((resolve, reject) => { const script = document.createElement("script"); diff --git a/src/trx-client/trx-frontend/trx-frontend-http/frontend/build.mjs b/src/trx-client/trx-frontend/trx-frontend-http/frontend/build.mjs index b1663bcc..b1753331 100644 --- a/src/trx-client/trx-frontend/trx-frontend-http/frontend/build.mjs +++ b/src/trx-client/trx-frontend/trx-frontend-http/frontend/build.mjs @@ -24,7 +24,6 @@ await build({ "webgl-renderer": path.join(sourceDir, "webgl-renderer.ts"), ais: path.join(sourceDir, "plugins", "ais.js"), aprs: path.join(sourceDir, "plugins", "aprs.js"), - "background-decode": path.join(sourceDir, "plugins", "background-decode.js"), bookmarks: path.join(sourceDir, "plugins", "bookmarks.js"), "hf-aprs": path.join(sourceDir, "plugins", "hf-aprs.js"), sat: path.join(sourceDir, "plugins", "sat.js"), @@ -51,6 +50,7 @@ await build({ "leaflet-ais-tracksymbol": path.join(sourceDir, "leaflet-ais-tracksymbol.ts"), vdes: path.join(sourceDir, "plugins", "vdes.ts"), wefax: path.join(sourceDir, "plugins", "wefax.ts"), + "background-decode": path.join(sourceDir, "plugins", "background-decode.ts"), }, outdir: outputDir, bundle: true, diff --git a/src/trx-client/trx-frontend/trx-frontend-http/frontend/src/plugin-loader.ts b/src/trx-client/trx-frontend/trx-frontend-http/frontend/src/plugin-loader.ts index fbb32d89..7edc91ce 100644 --- a/src/trx-client/trx-frontend/trx-frontend-http/frontend/src/plugin-loader.ts +++ b/src/trx-client/trx-frontend/trx-frontend-http/frontend/src/plugin-loader.ts @@ -16,7 +16,7 @@ const pluginGroups: Readonly> = { const loaded = new Set(); const loading = new Map>(); -const modulePlugins = new Set(["/ft8.js", "/ft4.js", "/ft2.js", "/wspr.js", "/cw.js", "/vdes.js", "/wefax.js"]); +const modulePlugins = new Set(["/ft8.js", "/ft4.js", "/ft2.js", "/wspr.js", "/cw.js", "/vdes.js", "/wefax.js", "/background-decode.js"]); function loadLegacyScript(path: string): Promise { return new Promise((resolve, reject) => { diff --git a/src/trx-client/trx-frontend/trx-frontend-http/frontend/src/plugins/background-decode.js b/src/trx-client/trx-frontend/trx-frontend-http/frontend/src/plugins/background-decode.ts similarity index 60% rename from src/trx-client/trx-frontend/trx-frontend-http/frontend/src/plugins/background-decode.js rename to src/trx-client/trx-frontend/trx-frontend-http/frontend/src/plugins/background-decode.ts index 5499f2ea..79a374eb 100644 --- a/src/trx-client/trx-frontend/trx-frontend-http/frontend/src/plugins/background-decode.js +++ b/src/trx-client/trx-frontend/trx-frontend-http/frontend/src/plugins/background-decode.ts @@ -2,30 +2,75 @@ // // SPDX-License-Identifier: GPL-2.0-or-later +export {}; + +interface DecoderDescriptor { + id: string; + background_decode?: boolean; + activation?: string; + active_modes: string[]; +} +interface Bookmark { + id: string; + name: string; + freq_hz: number; + mode: string; + decoders?: string[]; +} +interface BackgroundDecodeConfig { + remote: string | null; + enabled: boolean; + bookmark_ids: string[]; +} +interface BackgroundStatusEntry { + bookmark_name?: string; + bookmark_id?: string; + freq_hz?: number; + mode?: string; + decoder_kinds?: string[]; + state?: string; +} +interface BackgroundDecodeStatus { + entries?: BackgroundStatusEntry[]; + active_rig?: boolean; + center_hz?: number; + sample_rate?: number; +} +interface BackgroundBridge { + decoderRegistry?: DecoderDescriptor[]; + authEnabled?: boolean; + trxUi: { confirm(options: { title: string; message: string; confirmLabel: string }): Promise }; + initBackgroundDecode?: (rigId: string | null, role: string | null) => void; + wireBackgroundDecodeEvents?: () => void; + setBackgroundDecodeRig?: (rigId: string | null) => void; +} +interface WiredElement extends HTMLElement { _wired?: boolean } +const bgdWindow = window as unknown as BackgroundBridge; + (function () { "use strict"; - function bgdSupportedIds() { - return (window.decoderRegistry || []) + function bgdSupportedIds(): string[] { + return (bgdWindow.decoderRegistry || []) .filter(function (d) { return d.background_decode; }) .map(function (d) { return d.id; }); } - let backgroundDecodeRole = null; - let currentRigId = null; - let currentConfig = null; - let bookmarkList = []; - let statusInterval = null; + let backgroundDecodeRole: string | null = null; + let currentRigId: string | null = null; + let currentConfig: BackgroundDecodeConfig | null = null; + let bookmarkList: Bookmark[] = []; + let statusInterval: ReturnType | null = null; let bgdDirty = false; - function initBackgroundDecode(rigId, role) { + function initBackgroundDecode(rigId: string | null, role: string | null): void { backgroundDecodeRole = role; currentRigId = rigId || null; if (currentRigId) loadBackgroundDecode(); startStatusPolling(); } - function setBackgroundDecodeRig(rigId) { + function setBackgroundDecodeRig(rigId: string | null): void { const nextRigId = rigId || null; if (nextRigId === currentRigId) return; currentRigId = nextRigId; @@ -33,44 +78,44 @@ loadBackgroundDecode(); } - function apiGetConfig(rigId) { + function apiGetConfig(rigId: string): Promise { return fetch("/background-decode/" + encodeURIComponent(rigId)).then(function (r) { - if (!r.ok) throw new Error("HTTP " + r.status); - return r.json(); + if (!r.ok) throw new Error(`HTTP ${String(r.status)}`); + return r.json() as Promise; }); } - function apiPutConfig(rigId, config) { + function apiPutConfig(rigId: string, config: BackgroundDecodeConfig): Promise { return fetch("/background-decode/" + encodeURIComponent(rigId), { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify(config), }).then(function (r) { - if (!r.ok) throw new Error("HTTP " + r.status); - return r.json(); + if (!r.ok) throw new Error(`HTTP ${String(r.status)}`); + return r.json() as Promise; }); } - function apiResetConfig(rigId) { + function apiResetConfig(rigId: string): Promise { return fetch("/background-decode/" + encodeURIComponent(rigId), { method: "DELETE", }).then(function (r) { - if (!r.ok) throw new Error("HTTP " + r.status); - return r.json(); + if (!r.ok) throw new Error(`HTTP ${String(r.status)}`); + return r.json() as Promise; }); } - function apiGetStatus(rigId) { + function apiGetStatus(rigId: string): Promise { return fetch("/background-decode/" + encodeURIComponent(rigId) + "/status").then(function (r) { - if (!r.ok) throw new Error("HTTP " + r.status); - return r.json(); + if (!r.ok) throw new Error(`HTTP ${String(r.status)}`); + return r.json() as Promise; }); } - function apiGetBookmarks() { + function apiGetBookmarks(): Promise { return fetch("/bookmarks").then(function (r) { - if (!r.ok) throw new Error("HTTP " + r.status); - return r.json(); + if (!r.ok) throw new Error(`HTTP ${String(r.status)}`); + return r.json() as Promise; }); } @@ -79,35 +124,35 @@ if (!rigId) return; Promise.all([apiGetConfig(rigId), apiGetBookmarks()]) .then(function ([config, bookmarks]) { - currentConfig = config || { remote: rigId, enabled: false, bookmark_ids: [] }; + currentConfig = config; bookmarkList = Array.isArray(bookmarks) ? bookmarks : []; renderBackgroundDecode(); clearBgdDirty(); pollBackgroundDecodeStatus(); }) - .catch(function (err) { + .catch(function (err: unknown) { console.error("background decode load failed", err); }); } - function supportedBookmarks() { + function supportedBookmarks(): Bookmark[] { return bookmarkList.filter(function (bookmark) { return bookmarkDecoderKinds(bookmark).length > 0; }); } - function bookmarkDecoderKinds(bookmark) { - var ids = bgdSupportedIds(); - var decoders = Array.isArray(bookmark && bookmark.decoders) ? bookmark.decoders : []; - var explicit = decoders - .map(function (item) { return String(item || "").trim().toLowerCase(); }) + function bookmarkDecoderKinds(bookmark: Bookmark): string[] { + const ids = bgdSupportedIds(); + const decoders = bookmark.decoders ?? []; + const explicit = decoders + .map(function (item) { return item.trim().toLowerCase(); }) .filter(function (item, index, arr) { return ids.indexOf(item) >= 0 && arr.indexOf(item) === index; }); if (explicit.length > 0) return explicit; // Fall back: infer from mode via mode-bound entries in the registry. - var mode = String(bookmark && bookmark.mode || "").trim().toUpperCase(); - return (window.decoderRegistry || []) + const mode = bookmark.mode.trim().toUpperCase(); + return (bgdWindow.decoderRegistry || []) .filter(function (d) { return d.activation === "mode_bound" && d.background_decode && d.active_modes.indexOf(mode) >= 0; @@ -119,13 +164,13 @@ if (!currentConfig) { currentConfig = { remote: currentRigId, enabled: false, bookmark_ids: [] }; } - setCheckbox("background-decode-enabled", !!currentConfig.enabled); + setCheckbox("background-decode-enabled", currentConfig.enabled); renderBookmarkChecklist(); - const isControl = backgroundDecodeRole === "control" || (typeof authEnabled !== "undefined" && !authEnabled); + const isControl = backgroundDecodeRole === "control" || bgdWindow.authEnabled === false; const panel = document.getElementById("background-decode-panel"); if (panel) { - panel.querySelectorAll("input, select, button.sch-write").forEach(function (el) { + panel.querySelectorAll("input, select, button.sch-write").forEach(function (el) { el.disabled = !isControl; }); } @@ -135,7 +180,7 @@ if (resetBtn) resetBtn.style.display = isControl ? "" : "none"; } - function renderBookmarkChecklist(filterText) { + function renderBookmarkChecklist(filterText = ""): void { const container = document.getElementById("bgd-bookmark-checklist"); if (!container) return; container.innerHTML = ""; @@ -148,7 +193,7 @@ const filtered = filter ? all.filter(function (bm) { - var text = (bm.name + " " + formatFreq(bm.freq_hz) + " " + bm.mode).toLowerCase(); + const text = (bm.name + " " + formatFreq(bm.freq_hz) + " " + bm.mode).toLowerCase(); return text.indexOf(filter) >= 0; }) : all; @@ -161,22 +206,22 @@ } filtered.forEach(function (bookmark) { - var row = document.createElement("label"); + const row = document.createElement("label"); row.className = "bgd-checklist-row"; - var decoders = bookmarkDecoderKinds(bookmark); - var checked = selectedIds.has(bookmark.id) ? " checked" : ""; + const decoders = bookmarkDecoderKinds(bookmark); + const checked = selectedIds.has(bookmark.id) ? " checked" : ""; row.innerHTML = '' + '' + escHtml(bookmark.name) + '' + '' + escHtml(formatFreq(bookmark.freq_hz) + " " + bookmark.mode + " · " + decoders.join("/").toUpperCase()) + ''; - row.querySelector("input").addEventListener("change", function (e) { - onChecklistToggle(bookmark.id, e.target.checked); + row.querySelector("input")?.addEventListener("change", function (e) { + onChecklistToggle(bookmark.id, (e.currentTarget as HTMLInputElement).checked); }); container.appendChild(row); }); } - function onChecklistToggle(bookmarkId, checked) { + function onChecklistToggle(bookmarkId: string, checked: boolean): void { if (!currentConfig) { currentConfig = { remote: currentRigId, enabled: false, bookmark_ids: [] }; } @@ -192,12 +237,12 @@ function saveBackgroundDecode() { const rigId = currentRigId; if (!rigId) return; - const payload = { + const payload: BackgroundDecodeConfig = { remote: rigId, - enabled: !!document.getElementById("background-decode-enabled").checked, - bookmark_ids: Array.isArray(currentConfig && currentConfig.bookmark_ids) ? currentConfig.bookmark_ids.slice() : [], + enabled: (document.getElementById("background-decode-enabled") as HTMLInputElement | null)?.checked ?? false, + bookmark_ids: currentConfig?.bookmark_ids.slice() ?? [], }; - const btn = document.getElementById("background-decode-save-btn"); + const btn = document.getElementById("background-decode-save-btn") as HTMLButtonElement | null; if (btn) btn.disabled = true; apiPutConfig(rigId, payload) .then(function (saved) { @@ -205,10 +250,10 @@ renderBackgroundDecode(); clearBgdDirty(); pollBackgroundDecodeStatus(); - showToast("Background decode saved."); + showToast("Background decode saved.", false); }) - .catch(function (err) { - showToast("Save failed: " + err.message, true); + .catch(function (err: unknown) { + showToast(`Save failed: ${errorMessage(err)}`, true); }) .finally(function () { if (btn) btn.disabled = false; @@ -218,17 +263,17 @@ async function resetBackgroundDecode() { const rigId = currentRigId; if (!rigId) return; - if (!await window.trxUi.confirm({ title: "Reset background decoding?", message: "The current background decode configuration will be permanently reset.", confirmLabel: "Reset" })) return; + if (!await bgdWindow.trxUi.confirm({ title: "Reset background decoding?", message: "The current background decode configuration will be permanently reset.", confirmLabel: "Reset" })) return; apiResetConfig(rigId) .then(function (saved) { currentConfig = saved; renderBackgroundDecode(); clearBgdDirty(); pollBackgroundDecodeStatus(); - showToast("Background decode reset."); + showToast("Background decode reset.", false); }) - .catch(function (err) { - showToast("Reset failed: " + err.message, true); + .catch(function (err: unknown) { + showToast(`Reset failed: ${errorMessage(err)}`, true); }); } @@ -245,18 +290,18 @@ .catch(function () {}); } - function renderStatus(status) { + function renderStatus(status: BackgroundDecodeStatus): void { const card = document.getElementById("background-decode-status-card"); if (!card) return; - const entries = Array.isArray(status && status.entries) ? status.entries : []; + const entries = status.entries ?? []; if (!entries.length) { card.textContent = "No background decode bookmarks configured."; return; } const summary = []; if (status.active_rig) { - if (Number.isFinite(status.center_hz)) summary.push("Center " + formatFreq(status.center_hz)); - if (Number.isFinite(status.sample_rate) && status.sample_rate > 0) summary.push("Span ±" + formatFreq(status.sample_rate / 2)); + if (typeof status.center_hz === "number" && Number.isFinite(status.center_hz)) summary.push("Center " + formatFreq(status.center_hz)); + if (typeof status.sample_rate === "number" && Number.isFinite(status.sample_rate) && status.sample_rate > 0) summary.push("Span ±" + formatFreq(status.sample_rate / 2)); } else { summary.push("This rig is not currently selected for audio."); } @@ -265,7 +310,7 @@ entries.forEach(function (entry) { const name = entry.bookmark_name || entry.bookmark_id || "Unknown bookmark"; const parts = []; - if (Number.isFinite(entry.freq_hz)) parts.push(formatFreq(entry.freq_hz)); + if (typeof entry.freq_hz === "number" && Number.isFinite(entry.freq_hz)) parts.push(formatFreq(entry.freq_hz)); if (entry.mode) parts.push(entry.mode); if (Array.isArray(entry.decoder_kinds) && entry.decoder_kinds.length) { parts.push(entry.decoder_kinds.join("/").toUpperCase()); @@ -285,7 +330,7 @@ card.innerHTML = html; } - function prettyState(state) { + function prettyState(state: string | undefined): string { switch (state) { case "active": return "\u2713 Active"; case "out_of_span": return "\u25B3 Out of span"; @@ -301,40 +346,47 @@ } } - function setCheckbox(id, value) { - const el = document.getElementById(id); - if (el) el.checked = !!value; + function setCheckbox(id: string, value: boolean): void { + const el = document.getElementById(id) as HTMLInputElement | null; + if (el) el.checked = value; } - function formatFreq(hz) { + function formatFreq(hz: number): string { if (!Number.isFinite(hz) || hz <= 0) return "--"; if (hz >= 1e6) return (hz / 1e6).toFixed(3).replace(/\.?0+$/, "") + " MHz"; if (hz >= 1e3) return (hz / 1e3).toFixed(1).replace(/\.?0+$/, "") + " kHz"; - return hz + " Hz"; + return `${String(hz)} Hz`; } - function escHtml(value) { - return String(value == null ? "" : value) + function escHtml(value: unknown): string { + const text = typeof value === "string" || typeof value === "number" || typeof value === "boolean" + ? String(value) + : ""; + return text .replace(/&/g, "&") .replace(//g, ">") .replace(/"/g, """); } + function errorMessage(error: unknown): string { + return error instanceof Error ? error.message : String(error); + } + function markBgdDirty() { if (bgdDirty) return; bgdDirty = true; - var btn = document.getElementById("background-decode-save-btn"); + const btn = document.getElementById("background-decode-save-btn"); if (btn) btn.classList.add("sch-dirty"); } function clearBgdDirty() { bgdDirty = false; - var btn = document.getElementById("background-decode-save-btn"); + const btn = document.getElementById("background-decode-save-btn"); if (btn) btn.classList.remove("sch-dirty"); } - function showToast(msg, isError) { + function showToast(msg: string, isError: boolean): void { const el = document.getElementById("background-decode-toast"); if (!el) return; el.textContent = msg; @@ -349,9 +401,9 @@ if (!currentConfig) { currentConfig = { remote: currentRigId, enabled: false, bookmark_ids: [] }; } - var ids = supportedBookmarks().map(function (bm) { return bm.id; }); + const ids = supportedBookmarks().map(function (bm) { return bm.id; }); currentConfig.bookmark_ids = ids; - renderBookmarkChecklist(document.getElementById("bgd-bookmark-filter")?.value); + renderBookmarkChecklist((document.getElementById("bgd-bookmark-filter") as HTMLInputElement | null)?.value); markBgdDirty(); } @@ -360,12 +412,12 @@ currentConfig = { remote: currentRigId, enabled: false, bookmark_ids: [] }; } currentConfig.bookmark_ids = []; - renderBookmarkChecklist(document.getElementById("bgd-bookmark-filter")?.value); + renderBookmarkChecklist((document.getElementById("bgd-bookmark-filter") as HTMLInputElement | null)?.value); markBgdDirty(); } function wireBackgroundDecodeEvents() { - const filterInput = document.getElementById("bgd-bookmark-filter"); + const filterInput = document.getElementById("bgd-bookmark-filter") as (HTMLInputElement & WiredElement) | null; if (filterInput && !filterInput._wired) { filterInput._wired = true; filterInput.addEventListener("input", function () { @@ -373,38 +425,38 @@ }); } - const enabledCb = document.getElementById("background-decode-enabled"); + const enabledCb = document.getElementById("background-decode-enabled") as (HTMLInputElement & WiredElement) | null; if (enabledCb && !enabledCb._wired) { enabledCb._wired = true; enabledCb.addEventListener("change", function () { markBgdDirty(); }); } - const selectAllBtn = document.getElementById("bgd-select-all-btn"); + const selectAllBtn = document.getElementById("bgd-select-all-btn") as WiredElement | null; if (selectAllBtn && !selectAllBtn._wired) { selectAllBtn._wired = true; selectAllBtn.addEventListener("click", selectAllBookmarks); } - const deselectAllBtn = document.getElementById("bgd-deselect-all-btn"); + const deselectAllBtn = document.getElementById("bgd-deselect-all-btn") as WiredElement | null; if (deselectAllBtn && !deselectAllBtn._wired) { deselectAllBtn._wired = true; deselectAllBtn.addEventListener("click", deselectAllBookmarks); } - const saveBtn = document.getElementById("background-decode-save-btn"); + const saveBtn = document.getElementById("background-decode-save-btn") as WiredElement | null; if (saveBtn && !saveBtn._wired) { saveBtn._wired = true; saveBtn.addEventListener("click", saveBackgroundDecode); } - const resetBtn = document.getElementById("background-decode-reset-btn"); + const resetBtn = document.getElementById("background-decode-reset-btn") as WiredElement | null; if (resetBtn && !resetBtn._wired) { resetBtn._wired = true; - resetBtn.addEventListener("click", resetBackgroundDecode); + resetBtn.addEventListener("click", () => { void resetBackgroundDecode(); }); } } - window.initBackgroundDecode = initBackgroundDecode; - window.wireBackgroundDecodeEvents = wireBackgroundDecodeEvents; - window.setBackgroundDecodeRig = setBackgroundDecodeRig; + bgdWindow.initBackgroundDecode = initBackgroundDecode; + bgdWindow.wireBackgroundDecodeEvents = wireBackgroundDecodeEvents; + bgdWindow.setBackgroundDecodeRig = setBackgroundDecodeRig; })(); diff --git a/src/trx-client/trx-frontend/trx-frontend-http/frontend/tests/background-decode.test.mjs b/src/trx-client/trx-frontend/trx-frontend-http/frontend/tests/background-decode.test.mjs new file mode 100644 index 00000000..2b69346b --- /dev/null +++ b/src/trx-client/trx-frontend/trx-frontend-http/frontend/tests/background-decode.test.mjs @@ -0,0 +1,46 @@ +// SPDX-FileCopyrightText: 2026 Stan Grams +// +// SPDX-License-Identifier: GPL-2.0-or-later + +import assert from "node:assert/strict"; +import { readFile } from "node:fs/promises"; +import test from "node:test"; +import vm from "node:vm"; + +test("background decode loads configuration for the explicitly selected rig", async () => { + const requested = []; + const window = { + decoderRegistry: [], + trxUi: { confirm: async () => true }, + }; + const context = vm.createContext({ + window, + document: { getElementById: () => null }, + fetch: async (url) => { + requested.push(url); + return { + ok: true, + json: async () => url === "/bookmarks" + ? [] + : url.endsWith("/status") + ? { entries: [] } + : { remote: "rig/a", enabled: false, bookmark_ids: [] }, + }; + }, + setInterval: () => 1, + clearInterval() {}, + setTimeout, + URL, + Set, + Error, + console, + }); + const source = await readFile(new URL("../../assets/web/generated/background-decode.js", import.meta.url), "utf8"); + new vm.Script(source).runInContext(context); + + window.initBackgroundDecode("rig/a", "control"); + await new Promise((resolve) => setTimeout(resolve, 0)); + assert.ok(requested.includes("/background-decode/rig%2Fa")); + assert.ok(requested.includes("/bookmarks")); + assert.ok(requested.includes("/background-decode/rig%2Fa/status")); +});