import { hostState } from "./chunk-KL66PICH.js"; // 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; }); } 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 ${String(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 ${String(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 ${String(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 ${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: [] }; } setCheckbox("background-decode-enabled", currentConfig.enabled); renderBookmarkChecklist(); const isControl = backgroundDecodeRole === "control" || hostState.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 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); }); } 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; }); } 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); }); } 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 = 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; } 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.trx ??= {}; bgdWindow.trx.modules ??= {}; bgdWindow.trx.modules.backgroundDecode = { initialize: initBackgroundDecode, wireEvents: wireBackgroundDecodeEvents, setRig: setBackgroundDecodeRig }; })();