refactor: convert background decode controls to TypeScript
This commit is contained in:
+345
-335
@@ -1,355 +1,365 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
(function() {
|
(() => {
|
||||||
"use strict";
|
// src/plugins/background-decode.ts
|
||||||
function bgdSupportedIds() {
|
var bgdWindow = window;
|
||||||
return (window.decoderRegistry || []).filter(function(d) {
|
(function() {
|
||||||
return d.background_decode;
|
"use strict";
|
||||||
}).map(function(d) {
|
function bgdSupportedIds() {
|
||||||
return d.id;
|
return (bgdWindow.decoderRegistry || []).filter(function(d) {
|
||||||
});
|
return d.background_decode;
|
||||||
}
|
}).map(function(d) {
|
||||||
let backgroundDecodeRole = null;
|
return d.id;
|
||||||
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;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const saveBtn = document.getElementById("background-decode-save-btn");
|
let backgroundDecodeRole = null;
|
||||||
const resetBtn = document.getElementById("background-decode-reset-btn");
|
let currentRigId = null;
|
||||||
if (saveBtn) saveBtn.style.display = isControl ? "" : "none";
|
let currentConfig = null;
|
||||||
if (resetBtn) resetBtn.style.display = isControl ? "" : "none";
|
let bookmarkList = [];
|
||||||
}
|
let statusInterval = null;
|
||||||
function renderBookmarkChecklist(filterText) {
|
let bgdDirty = false;
|
||||||
const container = document.getElementById("bgd-bookmark-checklist");
|
function initBackgroundDecode(rigId, role) {
|
||||||
if (!container) return;
|
backgroundDecodeRole = role;
|
||||||
container.innerHTML = "";
|
currentRigId = rigId || null;
|
||||||
const selectedIds = new Set(
|
if (currentRigId) loadBackgroundDecode();
|
||||||
currentConfig && Array.isArray(currentConfig.bookmark_ids) ? currentConfig.bookmark_ids : []
|
startStatusPolling();
|
||||||
);
|
|
||||||
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 = '<div class="bgd-checklist-empty">' + (all.length === 0 ? "No supported bookmarks available." : "No bookmarks match filter.") + "</div>";
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
filtered.forEach(function(bookmark) {
|
function setBackgroundDecodeRig(rigId) {
|
||||||
var row = document.createElement("label");
|
const nextRigId = rigId || null;
|
||||||
row.className = "bgd-checklist-row";
|
if (nextRigId === currentRigId) return;
|
||||||
var decoders = bookmarkDecoderKinds(bookmark);
|
currentRigId = nextRigId;
|
||||||
var checked = selectedIds.has(bookmark.id) ? " checked" : "";
|
if (!currentRigId) return;
|
||||||
row.innerHTML = '<input type="checkbox"' + checked + ' data-bm-id="' + escHtml(bookmark.id) + '" /><span class="bgd-checklist-name">' + escHtml(bookmark.name) + '</span><span class="bgd-checklist-meta">' + escHtml(formatFreq(bookmark.freq_hz) + " " + bookmark.mode + " · " + decoders.join("/").toUpperCase()) + "</span>";
|
loadBackgroundDecode();
|
||||||
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: [] };
|
|
||||||
}
|
}
|
||||||
if (!Array.isArray(currentConfig.bookmark_ids)) currentConfig.bookmark_ids = [];
|
function apiGetConfig(rigId) {
|
||||||
if (checked && !currentConfig.bookmark_ids.includes(bookmarkId)) {
|
return fetch("/background-decode/" + encodeURIComponent(rigId)).then(function(r) {
|
||||||
currentConfig.bookmark_ids.push(bookmarkId);
|
if (!r.ok) throw new Error(`HTTP ${String(r.status)}`);
|
||||||
} else if (!checked) {
|
return r.json();
|
||||||
currentConfig.bookmark_ids = currentConfig.bookmark_ids.filter(function(id) {
|
|
||||||
return id !== bookmarkId;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
markBgdDirty();
|
function apiPutConfig(rigId, config) {
|
||||||
}
|
return fetch("/background-decode/" + encodeURIComponent(rigId), {
|
||||||
function saveBackgroundDecode() {
|
method: "PUT",
|
||||||
const rigId = currentRigId;
|
headers: { "Content-Type": "application/json" },
|
||||||
if (!rigId) return;
|
body: JSON.stringify(config)
|
||||||
const payload = {
|
}).then(function(r) {
|
||||||
remote: rigId,
|
if (!r.ok) throw new Error(`HTTP ${String(r.status)}`);
|
||||||
enabled: !!document.getElementById("background-decode-enabled").checked,
|
return r.json();
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
const summary = [];
|
function apiResetConfig(rigId) {
|
||||||
if (status.active_rig) {
|
return fetch("/background-decode/" + encodeURIComponent(rigId), {
|
||||||
if (Number.isFinite(status.center_hz)) summary.push("Center " + formatFreq(status.center_hz));
|
method: "DELETE"
|
||||||
if (Number.isFinite(status.sample_rate) && status.sample_rate > 0) summary.push("Span ±" + formatFreq(status.sample_rate / 2));
|
}).then(function(r) {
|
||||||
} else {
|
if (!r.ok) throw new Error(`HTTP ${String(r.status)}`);
|
||||||
summary.push("This rig is not currently selected for audio.");
|
return r.json();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
let html = summary.length ? '<div style="margin-bottom:0.8rem;color:var(--text-muted);">' + escHtml(summary.join(" · ")) + "</div>" : "";
|
function apiGetStatus(rigId) {
|
||||||
html += '<div class="bgd-status-list">';
|
return fetch("/background-decode/" + encodeURIComponent(rigId) + "/status").then(function(r) {
|
||||||
entries.forEach(function(entry) {
|
if (!r.ok) throw new Error(`HTTP ${String(r.status)}`);
|
||||||
const name = entry.bookmark_name || entry.bookmark_id || "Unknown bookmark";
|
return r.json();
|
||||||
const parts = [];
|
});
|
||||||
if (Number.isFinite(entry.freq_hz)) parts.push(formatFreq(entry.freq_hz));
|
}
|
||||||
if (entry.mode) parts.push(entry.mode);
|
function apiGetBookmarks() {
|
||||||
if (Array.isArray(entry.decoder_kinds) && entry.decoder_kinds.length) {
|
return fetch("/bookmarks").then(function(r) {
|
||||||
parts.push(entry.decoder_kinds.join("/").toUpperCase());
|
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 += '<div class="bgd-status-row"><div><div class="bgd-status-name">' + escHtml(name) + '</div><div class="bgd-status-meta">' + escHtml(parts.join(" · ")) + '</div></div><div class="bgd-status-state" data-state="' + escHtml(entry.state || "inactive") + '"><svg class="bgd-state-dot" viewBox="0 0 8 8"><circle cx="4" cy="4" r="3.5"/></svg>' + escHtml(prettyState(entry.state)) + "</div></div>";
|
setCheckbox("background-decode-enabled", currentConfig.enabled);
|
||||||
});
|
renderBookmarkChecklist();
|
||||||
html += "</div>";
|
const isControl = backgroundDecodeRole === "control" || bgdWindow.authEnabled === false;
|
||||||
card.innerHTML = html;
|
const panel = document.getElementById("background-decode-panel");
|
||||||
}
|
if (panel) {
|
||||||
function prettyState(state) {
|
panel.querySelectorAll("input, select, button.sch-write").forEach(function(el) {
|
||||||
switch (state) {
|
el.disabled = !isControl;
|
||||||
case "active":
|
});
|
||||||
return "✓ Active";
|
}
|
||||||
case "out_of_span":
|
const saveBtn = document.getElementById("background-decode-save-btn");
|
||||||
return "△ Out of span";
|
const resetBtn = document.getElementById("background-decode-reset-btn");
|
||||||
case "waiting_for_spectrum":
|
if (saveBtn) saveBtn.style.display = isControl ? "" : "none";
|
||||||
return "△ Waiting";
|
if (resetBtn) resetBtn.style.display = isControl ? "" : "none";
|
||||||
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 renderBookmarkChecklist(filterText = "") {
|
||||||
function setCheckbox(id, value) {
|
const container = document.getElementById("bgd-bookmark-checklist");
|
||||||
const el = document.getElementById(id);
|
if (!container) return;
|
||||||
if (el) el.checked = !!value;
|
container.innerHTML = "";
|
||||||
}
|
const selectedIds = new Set(
|
||||||
function formatFreq(hz) {
|
currentConfig && Array.isArray(currentConfig.bookmark_ids) ? currentConfig.bookmark_ids : []
|
||||||
if (!Number.isFinite(hz) || hz <= 0) return "--";
|
);
|
||||||
if (hz >= 1e6) return (hz / 1e6).toFixed(3).replace(/\.?0+$/, "") + " MHz";
|
const all = supportedBookmarks();
|
||||||
if (hz >= 1e3) return (hz / 1e3).toFixed(1).replace(/\.?0+$/, "") + " kHz";
|
const filter = (filterText || "").trim().toLowerCase();
|
||||||
return hz + " Hz";
|
const filtered = filter ? all.filter(function(bm) {
|
||||||
}
|
const text = (bm.name + " " + formatFreq(bm.freq_hz) + " " + bm.mode).toLowerCase();
|
||||||
function escHtml(value) {
|
return text.indexOf(filter) >= 0;
|
||||||
return String(value == null ? "" : value).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
}) : all;
|
||||||
}
|
if (filtered.length === 0) {
|
||||||
function markBgdDirty() {
|
container.innerHTML = '<div class="bgd-checklist-empty">' + (all.length === 0 ? "No supported bookmarks available." : "No bookmarks match filter.") + "</div>";
|
||||||
if (bgdDirty) return;
|
return;
|
||||||
bgdDirty = true;
|
}
|
||||||
var btn = document.getElementById("background-decode-save-btn");
|
filtered.forEach(function(bookmark) {
|
||||||
if (btn) btn.classList.add("sch-dirty");
|
const row = document.createElement("label");
|
||||||
}
|
row.className = "bgd-checklist-row";
|
||||||
function clearBgdDirty() {
|
const decoders = bookmarkDecoderKinds(bookmark);
|
||||||
bgdDirty = false;
|
const checked = selectedIds.has(bookmark.id) ? " checked" : "";
|
||||||
var btn = document.getElementById("background-decode-save-btn");
|
row.innerHTML = '<input type="checkbox"' + checked + ' data-bm-id="' + escHtml(bookmark.id) + '" /><span class="bgd-checklist-name">' + escHtml(bookmark.name) + '</span><span class="bgd-checklist-meta">' + escHtml(formatFreq(bookmark.freq_hz) + " " + bookmark.mode + " · " + decoders.join("/").toUpperCase()) + "</span>";
|
||||||
if (btn) btn.classList.remove("sch-dirty");
|
row.querySelector("input")?.addEventListener("change", function(e) {
|
||||||
}
|
onChecklistToggle(bookmark.id, e.currentTarget.checked);
|
||||||
function showToast(msg, isError) {
|
});
|
||||||
const el = document.getElementById("background-decode-toast");
|
container.appendChild(row);
|
||||||
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);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const enabledCb = document.getElementById("background-decode-enabled");
|
function onChecklistToggle(bookmarkId, checked) {
|
||||||
if (enabledCb && !enabledCb._wired) {
|
if (!currentConfig) {
|
||||||
enabledCb._wired = true;
|
currentConfig = { remote: currentRigId, enabled: false, bookmark_ids: [] };
|
||||||
enabledCb.addEventListener("change", function() {
|
}
|
||||||
markBgdDirty();
|
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");
|
async function resetBackgroundDecode() {
|
||||||
if (selectAllBtn && !selectAllBtn._wired) {
|
const rigId = currentRigId;
|
||||||
selectAllBtn._wired = true;
|
if (!rigId) return;
|
||||||
selectAllBtn.addEventListener("click", selectAllBookmarks);
|
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");
|
function startStatusPolling() {
|
||||||
if (deselectAllBtn && !deselectAllBtn._wired) {
|
if (statusInterval) clearInterval(statusInterval);
|
||||||
deselectAllBtn._wired = true;
|
statusInterval = setInterval(pollBackgroundDecodeStatus, 15e3);
|
||||||
deselectAllBtn.addEventListener("click", deselectAllBookmarks);
|
|
||||||
}
|
}
|
||||||
const saveBtn = document.getElementById("background-decode-save-btn");
|
function pollBackgroundDecodeStatus() {
|
||||||
if (saveBtn && !saveBtn._wired) {
|
const rigId = currentRigId;
|
||||||
saveBtn._wired = true;
|
if (!rigId) return;
|
||||||
saveBtn.addEventListener("click", saveBackgroundDecode);
|
apiGetStatus(rigId).then(renderStatus).catch(function() {
|
||||||
|
});
|
||||||
}
|
}
|
||||||
const resetBtn = document.getElementById("background-decode-reset-btn");
|
function renderStatus(status) {
|
||||||
if (resetBtn && !resetBtn._wired) {
|
const card = document.getElementById("background-decode-status-card");
|
||||||
resetBtn._wired = true;
|
if (!card) return;
|
||||||
resetBtn.addEventListener("click", resetBackgroundDecode);
|
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 ? '<div style="margin-bottom:0.8rem;color:var(--text-muted);">' + escHtml(summary.join(" · ")) + "</div>" : "";
|
||||||
|
html += '<div class="bgd-status-list">';
|
||||||
|
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 += '<div class="bgd-status-row"><div><div class="bgd-status-name">' + escHtml(name) + '</div><div class="bgd-status-meta">' + escHtml(parts.join(" · ")) + '</div></div><div class="bgd-status-state" data-state="' + escHtml(entry.state || "inactive") + '"><svg class="bgd-state-dot" viewBox="0 0 8 8"><circle cx="4" cy="4" r="3.5"/></svg>' + escHtml(prettyState(entry.state)) + "</div></div>";
|
||||||
|
});
|
||||||
|
html += "</div>";
|
||||||
|
card.innerHTML = html;
|
||||||
}
|
}
|
||||||
}
|
function prettyState(state) {
|
||||||
window.initBackgroundDecode = initBackgroundDecode;
|
switch (state) {
|
||||||
window.wireBackgroundDecodeEvents = wireBackgroundDecodeEvents;
|
case "active":
|
||||||
window.setBackgroundDecodeRig = setBackgroundDecodeRig;
|
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, ">").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;
|
||||||
|
})();
|
||||||
})();
|
})();
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ const pluginGroups = {
|
|||||||
};
|
};
|
||||||
const loaded = /* @__PURE__ */ new Set();
|
const loaded = /* @__PURE__ */ new Set();
|
||||||
const loading = /* @__PURE__ */ new Map();
|
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) {
|
function loadLegacyScript(path) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const script = document.createElement("script");
|
const script = document.createElement("script");
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ await build({
|
|||||||
"webgl-renderer": path.join(sourceDir, "webgl-renderer.ts"),
|
"webgl-renderer": path.join(sourceDir, "webgl-renderer.ts"),
|
||||||
ais: path.join(sourceDir, "plugins", "ais.js"),
|
ais: path.join(sourceDir, "plugins", "ais.js"),
|
||||||
aprs: path.join(sourceDir, "plugins", "aprs.js"),
|
aprs: path.join(sourceDir, "plugins", "aprs.js"),
|
||||||
"background-decode": path.join(sourceDir, "plugins", "background-decode.js"),
|
|
||||||
bookmarks: path.join(sourceDir, "plugins", "bookmarks.js"),
|
bookmarks: path.join(sourceDir, "plugins", "bookmarks.js"),
|
||||||
"hf-aprs": path.join(sourceDir, "plugins", "hf-aprs.js"),
|
"hf-aprs": path.join(sourceDir, "plugins", "hf-aprs.js"),
|
||||||
sat: path.join(sourceDir, "plugins", "sat.js"),
|
sat: path.join(sourceDir, "plugins", "sat.js"),
|
||||||
@@ -51,6 +50,7 @@ await build({
|
|||||||
"leaflet-ais-tracksymbol": path.join(sourceDir, "leaflet-ais-tracksymbol.ts"),
|
"leaflet-ais-tracksymbol": path.join(sourceDir, "leaflet-ais-tracksymbol.ts"),
|
||||||
vdes: path.join(sourceDir, "plugins", "vdes.ts"),
|
vdes: path.join(sourceDir, "plugins", "vdes.ts"),
|
||||||
wefax: path.join(sourceDir, "plugins", "wefax.ts"),
|
wefax: path.join(sourceDir, "plugins", "wefax.ts"),
|
||||||
|
"background-decode": path.join(sourceDir, "plugins", "background-decode.ts"),
|
||||||
},
|
},
|
||||||
outdir: outputDir,
|
outdir: outputDir,
|
||||||
bundle: true,
|
bundle: true,
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ const pluginGroups: Readonly<Record<PluginGroup, readonly string[]>> = {
|
|||||||
|
|
||||||
const loaded = new Set<string>();
|
const loaded = new Set<string>();
|
||||||
const loading = new Map<string, Promise<void>>();
|
const loading = new Map<string, Promise<void>>();
|
||||||
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<void> {
|
function loadLegacyScript(path: string): Promise<void> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
|
|||||||
+137
-85
@@ -2,30 +2,75 @@
|
|||||||
//
|
//
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// 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<boolean> };
|
||||||
|
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 () {
|
(function () {
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
function bgdSupportedIds() {
|
function bgdSupportedIds(): string[] {
|
||||||
return (window.decoderRegistry || [])
|
return (bgdWindow.decoderRegistry || [])
|
||||||
.filter(function (d) { return d.background_decode; })
|
.filter(function (d) { return d.background_decode; })
|
||||||
.map(function (d) { return d.id; });
|
.map(function (d) { return d.id; });
|
||||||
}
|
}
|
||||||
|
|
||||||
let backgroundDecodeRole = null;
|
let backgroundDecodeRole: string | null = null;
|
||||||
let currentRigId = null;
|
let currentRigId: string | null = null;
|
||||||
let currentConfig = null;
|
let currentConfig: BackgroundDecodeConfig | null = null;
|
||||||
let bookmarkList = [];
|
let bookmarkList: Bookmark[] = [];
|
||||||
let statusInterval = null;
|
let statusInterval: ReturnType<typeof setInterval> | null = null;
|
||||||
let bgdDirty = false;
|
let bgdDirty = false;
|
||||||
|
|
||||||
function initBackgroundDecode(rigId, role) {
|
function initBackgroundDecode(rigId: string | null, role: string | null): void {
|
||||||
backgroundDecodeRole = role;
|
backgroundDecodeRole = role;
|
||||||
currentRigId = rigId || null;
|
currentRigId = rigId || null;
|
||||||
if (currentRigId) loadBackgroundDecode();
|
if (currentRigId) loadBackgroundDecode();
|
||||||
startStatusPolling();
|
startStatusPolling();
|
||||||
}
|
}
|
||||||
|
|
||||||
function setBackgroundDecodeRig(rigId) {
|
function setBackgroundDecodeRig(rigId: string | null): void {
|
||||||
const nextRigId = rigId || null;
|
const nextRigId = rigId || null;
|
||||||
if (nextRigId === currentRigId) return;
|
if (nextRigId === currentRigId) return;
|
||||||
currentRigId = nextRigId;
|
currentRigId = nextRigId;
|
||||||
@@ -33,44 +78,44 @@
|
|||||||
loadBackgroundDecode();
|
loadBackgroundDecode();
|
||||||
}
|
}
|
||||||
|
|
||||||
function apiGetConfig(rigId) {
|
function apiGetConfig(rigId: string): Promise<BackgroundDecodeConfig> {
|
||||||
return fetch("/background-decode/" + encodeURIComponent(rigId)).then(function (r) {
|
return fetch("/background-decode/" + encodeURIComponent(rigId)).then(function (r) {
|
||||||
if (!r.ok) throw new Error("HTTP " + r.status);
|
if (!r.ok) throw new Error(`HTTP ${String(r.status)}`);
|
||||||
return r.json();
|
return r.json() as Promise<BackgroundDecodeConfig>;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function apiPutConfig(rigId, config) {
|
function apiPutConfig(rigId: string, config: BackgroundDecodeConfig): Promise<BackgroundDecodeConfig> {
|
||||||
return fetch("/background-decode/" + encodeURIComponent(rigId), {
|
return fetch("/background-decode/" + encodeURIComponent(rigId), {
|
||||||
method: "PUT",
|
method: "PUT",
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify(config),
|
body: JSON.stringify(config),
|
||||||
}).then(function (r) {
|
}).then(function (r) {
|
||||||
if (!r.ok) throw new Error("HTTP " + r.status);
|
if (!r.ok) throw new Error(`HTTP ${String(r.status)}`);
|
||||||
return r.json();
|
return r.json() as Promise<BackgroundDecodeConfig>;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function apiResetConfig(rigId) {
|
function apiResetConfig(rigId: string): Promise<BackgroundDecodeConfig> {
|
||||||
return fetch("/background-decode/" + encodeURIComponent(rigId), {
|
return fetch("/background-decode/" + encodeURIComponent(rigId), {
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
}).then(function (r) {
|
}).then(function (r) {
|
||||||
if (!r.ok) throw new Error("HTTP " + r.status);
|
if (!r.ok) throw new Error(`HTTP ${String(r.status)}`);
|
||||||
return r.json();
|
return r.json() as Promise<BackgroundDecodeConfig>;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function apiGetStatus(rigId) {
|
function apiGetStatus(rigId: string): Promise<BackgroundDecodeStatus> {
|
||||||
return fetch("/background-decode/" + encodeURIComponent(rigId) + "/status").then(function (r) {
|
return fetch("/background-decode/" + encodeURIComponent(rigId) + "/status").then(function (r) {
|
||||||
if (!r.ok) throw new Error("HTTP " + r.status);
|
if (!r.ok) throw new Error(`HTTP ${String(r.status)}`);
|
||||||
return r.json();
|
return r.json() as Promise<BackgroundDecodeStatus>;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function apiGetBookmarks() {
|
function apiGetBookmarks(): Promise<Bookmark[]> {
|
||||||
return fetch("/bookmarks").then(function (r) {
|
return fetch("/bookmarks").then(function (r) {
|
||||||
if (!r.ok) throw new Error("HTTP " + r.status);
|
if (!r.ok) throw new Error(`HTTP ${String(r.status)}`);
|
||||||
return r.json();
|
return r.json() as Promise<Bookmark[]>;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,35 +124,35 @@
|
|||||||
if (!rigId) return;
|
if (!rigId) return;
|
||||||
Promise.all([apiGetConfig(rigId), apiGetBookmarks()])
|
Promise.all([apiGetConfig(rigId), apiGetBookmarks()])
|
||||||
.then(function ([config, bookmarks]) {
|
.then(function ([config, bookmarks]) {
|
||||||
currentConfig = config || { remote: rigId, enabled: false, bookmark_ids: [] };
|
currentConfig = config;
|
||||||
bookmarkList = Array.isArray(bookmarks) ? bookmarks : [];
|
bookmarkList = Array.isArray(bookmarks) ? bookmarks : [];
|
||||||
renderBackgroundDecode();
|
renderBackgroundDecode();
|
||||||
clearBgdDirty();
|
clearBgdDirty();
|
||||||
pollBackgroundDecodeStatus();
|
pollBackgroundDecodeStatus();
|
||||||
})
|
})
|
||||||
.catch(function (err) {
|
.catch(function (err: unknown) {
|
||||||
console.error("background decode load failed", err);
|
console.error("background decode load failed", err);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function supportedBookmarks() {
|
function supportedBookmarks(): Bookmark[] {
|
||||||
return bookmarkList.filter(function (bookmark) {
|
return bookmarkList.filter(function (bookmark) {
|
||||||
return bookmarkDecoderKinds(bookmark).length > 0;
|
return bookmarkDecoderKinds(bookmark).length > 0;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function bookmarkDecoderKinds(bookmark) {
|
function bookmarkDecoderKinds(bookmark: Bookmark): string[] {
|
||||||
var ids = bgdSupportedIds();
|
const ids = bgdSupportedIds();
|
||||||
var decoders = Array.isArray(bookmark && bookmark.decoders) ? bookmark.decoders : [];
|
const decoders = bookmark.decoders ?? [];
|
||||||
var explicit = decoders
|
const explicit = decoders
|
||||||
.map(function (item) { return String(item || "").trim().toLowerCase(); })
|
.map(function (item) { return item.trim().toLowerCase(); })
|
||||||
.filter(function (item, index, arr) {
|
.filter(function (item, index, arr) {
|
||||||
return ids.indexOf(item) >= 0 && arr.indexOf(item) === index;
|
return ids.indexOf(item) >= 0 && arr.indexOf(item) === index;
|
||||||
});
|
});
|
||||||
if (explicit.length > 0) return explicit;
|
if (explicit.length > 0) return explicit;
|
||||||
// Fall back: infer from mode via mode-bound entries in the registry.
|
// Fall back: infer from mode via mode-bound entries in the registry.
|
||||||
var mode = String(bookmark && bookmark.mode || "").trim().toUpperCase();
|
const mode = bookmark.mode.trim().toUpperCase();
|
||||||
return (window.decoderRegistry || [])
|
return (bgdWindow.decoderRegistry || [])
|
||||||
.filter(function (d) {
|
.filter(function (d) {
|
||||||
return d.activation === "mode_bound" && d.background_decode
|
return d.activation === "mode_bound" && d.background_decode
|
||||||
&& d.active_modes.indexOf(mode) >= 0;
|
&& d.active_modes.indexOf(mode) >= 0;
|
||||||
@@ -119,13 +164,13 @@
|
|||||||
if (!currentConfig) {
|
if (!currentConfig) {
|
||||||
currentConfig = { remote: currentRigId, enabled: false, bookmark_ids: [] };
|
currentConfig = { remote: currentRigId, enabled: false, bookmark_ids: [] };
|
||||||
}
|
}
|
||||||
setCheckbox("background-decode-enabled", !!currentConfig.enabled);
|
setCheckbox("background-decode-enabled", currentConfig.enabled);
|
||||||
renderBookmarkChecklist();
|
renderBookmarkChecklist();
|
||||||
|
|
||||||
const isControl = backgroundDecodeRole === "control" || (typeof authEnabled !== "undefined" && !authEnabled);
|
const isControl = backgroundDecodeRole === "control" || bgdWindow.authEnabled === false;
|
||||||
const panel = document.getElementById("background-decode-panel");
|
const panel = document.getElementById("background-decode-panel");
|
||||||
if (panel) {
|
if (panel) {
|
||||||
panel.querySelectorAll("input, select, button.sch-write").forEach(function (el) {
|
panel.querySelectorAll<HTMLInputElement | HTMLSelectElement | HTMLButtonElement>("input, select, button.sch-write").forEach(function (el) {
|
||||||
el.disabled = !isControl;
|
el.disabled = !isControl;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -135,7 +180,7 @@
|
|||||||
if (resetBtn) resetBtn.style.display = isControl ? "" : "none";
|
if (resetBtn) resetBtn.style.display = isControl ? "" : "none";
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderBookmarkChecklist(filterText) {
|
function renderBookmarkChecklist(filterText = ""): void {
|
||||||
const container = document.getElementById("bgd-bookmark-checklist");
|
const container = document.getElementById("bgd-bookmark-checklist");
|
||||||
if (!container) return;
|
if (!container) return;
|
||||||
container.innerHTML = "";
|
container.innerHTML = "";
|
||||||
@@ -148,7 +193,7 @@
|
|||||||
|
|
||||||
const filtered = filter
|
const filtered = filter
|
||||||
? all.filter(function (bm) {
|
? 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;
|
return text.indexOf(filter) >= 0;
|
||||||
})
|
})
|
||||||
: all;
|
: all;
|
||||||
@@ -161,22 +206,22 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
filtered.forEach(function (bookmark) {
|
filtered.forEach(function (bookmark) {
|
||||||
var row = document.createElement("label");
|
const row = document.createElement("label");
|
||||||
row.className = "bgd-checklist-row";
|
row.className = "bgd-checklist-row";
|
||||||
var decoders = bookmarkDecoderKinds(bookmark);
|
const decoders = bookmarkDecoderKinds(bookmark);
|
||||||
var checked = selectedIds.has(bookmark.id) ? " checked" : "";
|
const checked = selectedIds.has(bookmark.id) ? " checked" : "";
|
||||||
row.innerHTML =
|
row.innerHTML =
|
||||||
'<input type="checkbox"' + checked + ' data-bm-id="' + escHtml(bookmark.id) + '" />' +
|
'<input type="checkbox"' + checked + ' data-bm-id="' + escHtml(bookmark.id) + '" />' +
|
||||||
'<span class="bgd-checklist-name">' + escHtml(bookmark.name) + '</span>' +
|
'<span class="bgd-checklist-name">' + escHtml(bookmark.name) + '</span>' +
|
||||||
'<span class="bgd-checklist-meta">' + escHtml(formatFreq(bookmark.freq_hz) + " " + bookmark.mode + " · " + decoders.join("/").toUpperCase()) + '</span>';
|
'<span class="bgd-checklist-meta">' + escHtml(formatFreq(bookmark.freq_hz) + " " + bookmark.mode + " · " + decoders.join("/").toUpperCase()) + '</span>';
|
||||||
row.querySelector("input").addEventListener("change", function (e) {
|
row.querySelector<HTMLInputElement>("input")?.addEventListener("change", function (e) {
|
||||||
onChecklistToggle(bookmark.id, e.target.checked);
|
onChecklistToggle(bookmark.id, (e.currentTarget as HTMLInputElement).checked);
|
||||||
});
|
});
|
||||||
container.appendChild(row);
|
container.appendChild(row);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function onChecklistToggle(bookmarkId, checked) {
|
function onChecklistToggle(bookmarkId: string, checked: boolean): void {
|
||||||
if (!currentConfig) {
|
if (!currentConfig) {
|
||||||
currentConfig = { remote: currentRigId, enabled: false, bookmark_ids: [] };
|
currentConfig = { remote: currentRigId, enabled: false, bookmark_ids: [] };
|
||||||
}
|
}
|
||||||
@@ -192,12 +237,12 @@
|
|||||||
function saveBackgroundDecode() {
|
function saveBackgroundDecode() {
|
||||||
const rigId = currentRigId;
|
const rigId = currentRigId;
|
||||||
if (!rigId) return;
|
if (!rigId) return;
|
||||||
const payload = {
|
const payload: BackgroundDecodeConfig = {
|
||||||
remote: rigId,
|
remote: rigId,
|
||||||
enabled: !!document.getElementById("background-decode-enabled").checked,
|
enabled: (document.getElementById("background-decode-enabled") as HTMLInputElement | null)?.checked ?? false,
|
||||||
bookmark_ids: Array.isArray(currentConfig && currentConfig.bookmark_ids) ? currentConfig.bookmark_ids.slice() : [],
|
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;
|
if (btn) btn.disabled = true;
|
||||||
apiPutConfig(rigId, payload)
|
apiPutConfig(rigId, payload)
|
||||||
.then(function (saved) {
|
.then(function (saved) {
|
||||||
@@ -205,10 +250,10 @@
|
|||||||
renderBackgroundDecode();
|
renderBackgroundDecode();
|
||||||
clearBgdDirty();
|
clearBgdDirty();
|
||||||
pollBackgroundDecodeStatus();
|
pollBackgroundDecodeStatus();
|
||||||
showToast("Background decode saved.");
|
showToast("Background decode saved.", false);
|
||||||
})
|
})
|
||||||
.catch(function (err) {
|
.catch(function (err: unknown) {
|
||||||
showToast("Save failed: " + err.message, true);
|
showToast(`Save failed: ${errorMessage(err)}`, true);
|
||||||
})
|
})
|
||||||
.finally(function () {
|
.finally(function () {
|
||||||
if (btn) btn.disabled = false;
|
if (btn) btn.disabled = false;
|
||||||
@@ -218,17 +263,17 @@
|
|||||||
async function resetBackgroundDecode() {
|
async function resetBackgroundDecode() {
|
||||||
const rigId = currentRigId;
|
const rigId = currentRigId;
|
||||||
if (!rigId) return;
|
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)
|
apiResetConfig(rigId)
|
||||||
.then(function (saved) {
|
.then(function (saved) {
|
||||||
currentConfig = saved;
|
currentConfig = saved;
|
||||||
renderBackgroundDecode();
|
renderBackgroundDecode();
|
||||||
clearBgdDirty();
|
clearBgdDirty();
|
||||||
pollBackgroundDecodeStatus();
|
pollBackgroundDecodeStatus();
|
||||||
showToast("Background decode reset.");
|
showToast("Background decode reset.", false);
|
||||||
})
|
})
|
||||||
.catch(function (err) {
|
.catch(function (err: unknown) {
|
||||||
showToast("Reset failed: " + err.message, true);
|
showToast(`Reset failed: ${errorMessage(err)}`, true);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -245,18 +290,18 @@
|
|||||||
.catch(function () {});
|
.catch(function () {});
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderStatus(status) {
|
function renderStatus(status: BackgroundDecodeStatus): void {
|
||||||
const card = document.getElementById("background-decode-status-card");
|
const card = document.getElementById("background-decode-status-card");
|
||||||
if (!card) return;
|
if (!card) return;
|
||||||
const entries = Array.isArray(status && status.entries) ? status.entries : [];
|
const entries = status.entries ?? [];
|
||||||
if (!entries.length) {
|
if (!entries.length) {
|
||||||
card.textContent = "No background decode bookmarks configured.";
|
card.textContent = "No background decode bookmarks configured.";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const summary = [];
|
const summary = [];
|
||||||
if (status.active_rig) {
|
if (status.active_rig) {
|
||||||
if (Number.isFinite(status.center_hz)) summary.push("Center " + formatFreq(status.center_hz));
|
if (typeof status.center_hz === "number" && 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.sample_rate === "number" && Number.isFinite(status.sample_rate) && status.sample_rate > 0) summary.push("Span ±" + formatFreq(status.sample_rate / 2));
|
||||||
} else {
|
} else {
|
||||||
summary.push("This rig is not currently selected for audio.");
|
summary.push("This rig is not currently selected for audio.");
|
||||||
}
|
}
|
||||||
@@ -265,7 +310,7 @@
|
|||||||
entries.forEach(function (entry) {
|
entries.forEach(function (entry) {
|
||||||
const name = entry.bookmark_name || entry.bookmark_id || "Unknown bookmark";
|
const name = entry.bookmark_name || entry.bookmark_id || "Unknown bookmark";
|
||||||
const parts = [];
|
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 (entry.mode) parts.push(entry.mode);
|
||||||
if (Array.isArray(entry.decoder_kinds) && entry.decoder_kinds.length) {
|
if (Array.isArray(entry.decoder_kinds) && entry.decoder_kinds.length) {
|
||||||
parts.push(entry.decoder_kinds.join("/").toUpperCase());
|
parts.push(entry.decoder_kinds.join("/").toUpperCase());
|
||||||
@@ -285,7 +330,7 @@
|
|||||||
card.innerHTML = html;
|
card.innerHTML = html;
|
||||||
}
|
}
|
||||||
|
|
||||||
function prettyState(state) {
|
function prettyState(state: string | undefined): string {
|
||||||
switch (state) {
|
switch (state) {
|
||||||
case "active": return "\u2713 Active";
|
case "active": return "\u2713 Active";
|
||||||
case "out_of_span": return "\u25B3 Out of span";
|
case "out_of_span": return "\u25B3 Out of span";
|
||||||
@@ -301,40 +346,47 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function setCheckbox(id, value) {
|
function setCheckbox(id: string, value: boolean): void {
|
||||||
const el = document.getElementById(id);
|
const el = document.getElementById(id) as HTMLInputElement | null;
|
||||||
if (el) el.checked = !!value;
|
if (el) el.checked = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatFreq(hz) {
|
function formatFreq(hz: number): string {
|
||||||
if (!Number.isFinite(hz) || hz <= 0) return "--";
|
if (!Number.isFinite(hz) || hz <= 0) return "--";
|
||||||
if (hz >= 1e6) return (hz / 1e6).toFixed(3).replace(/\.?0+$/, "") + " MHz";
|
if (hz >= 1e6) return (hz / 1e6).toFixed(3).replace(/\.?0+$/, "") + " MHz";
|
||||||
if (hz >= 1e3) return (hz / 1e3).toFixed(1).replace(/\.?0+$/, "") + " kHz";
|
if (hz >= 1e3) return (hz / 1e3).toFixed(1).replace(/\.?0+$/, "") + " kHz";
|
||||||
return hz + " Hz";
|
return `${String(hz)} Hz`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function escHtml(value) {
|
function escHtml(value: unknown): string {
|
||||||
return String(value == null ? "" : value)
|
const text = typeof value === "string" || typeof value === "number" || typeof value === "boolean"
|
||||||
|
? String(value)
|
||||||
|
: "";
|
||||||
|
return text
|
||||||
.replace(/&/g, "&")
|
.replace(/&/g, "&")
|
||||||
.replace(/</g, "<")
|
.replace(/</g, "<")
|
||||||
.replace(/>/g, ">")
|
.replace(/>/g, ">")
|
||||||
.replace(/"/g, """);
|
.replace(/"/g, """);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function errorMessage(error: unknown): string {
|
||||||
|
return error instanceof Error ? error.message : String(error);
|
||||||
|
}
|
||||||
|
|
||||||
function markBgdDirty() {
|
function markBgdDirty() {
|
||||||
if (bgdDirty) return;
|
if (bgdDirty) return;
|
||||||
bgdDirty = true;
|
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");
|
if (btn) btn.classList.add("sch-dirty");
|
||||||
}
|
}
|
||||||
|
|
||||||
function clearBgdDirty() {
|
function clearBgdDirty() {
|
||||||
bgdDirty = false;
|
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");
|
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");
|
const el = document.getElementById("background-decode-toast");
|
||||||
if (!el) return;
|
if (!el) return;
|
||||||
el.textContent = msg;
|
el.textContent = msg;
|
||||||
@@ -349,9 +401,9 @@
|
|||||||
if (!currentConfig) {
|
if (!currentConfig) {
|
||||||
currentConfig = { remote: currentRigId, enabled: false, bookmark_ids: [] };
|
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;
|
currentConfig.bookmark_ids = ids;
|
||||||
renderBookmarkChecklist(document.getElementById("bgd-bookmark-filter")?.value);
|
renderBookmarkChecklist((document.getElementById("bgd-bookmark-filter") as HTMLInputElement | null)?.value);
|
||||||
markBgdDirty();
|
markBgdDirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -360,12 +412,12 @@
|
|||||||
currentConfig = { remote: currentRigId, enabled: false, bookmark_ids: [] };
|
currentConfig = { remote: currentRigId, enabled: false, bookmark_ids: [] };
|
||||||
}
|
}
|
||||||
currentConfig.bookmark_ids = [];
|
currentConfig.bookmark_ids = [];
|
||||||
renderBookmarkChecklist(document.getElementById("bgd-bookmark-filter")?.value);
|
renderBookmarkChecklist((document.getElementById("bgd-bookmark-filter") as HTMLInputElement | null)?.value);
|
||||||
markBgdDirty();
|
markBgdDirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
function wireBackgroundDecodeEvents() {
|
function wireBackgroundDecodeEvents() {
|
||||||
const filterInput = document.getElementById("bgd-bookmark-filter");
|
const filterInput = document.getElementById("bgd-bookmark-filter") as (HTMLInputElement & WiredElement) | null;
|
||||||
if (filterInput && !filterInput._wired) {
|
if (filterInput && !filterInput._wired) {
|
||||||
filterInput._wired = true;
|
filterInput._wired = true;
|
||||||
filterInput.addEventListener("input", function () {
|
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) {
|
if (enabledCb && !enabledCb._wired) {
|
||||||
enabledCb._wired = true;
|
enabledCb._wired = true;
|
||||||
enabledCb.addEventListener("change", function () { markBgdDirty(); });
|
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) {
|
if (selectAllBtn && !selectAllBtn._wired) {
|
||||||
selectAllBtn._wired = true;
|
selectAllBtn._wired = true;
|
||||||
selectAllBtn.addEventListener("click", selectAllBookmarks);
|
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) {
|
if (deselectAllBtn && !deselectAllBtn._wired) {
|
||||||
deselectAllBtn._wired = true;
|
deselectAllBtn._wired = true;
|
||||||
deselectAllBtn.addEventListener("click", deselectAllBookmarks);
|
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) {
|
if (saveBtn && !saveBtn._wired) {
|
||||||
saveBtn._wired = true;
|
saveBtn._wired = true;
|
||||||
saveBtn.addEventListener("click", saveBackgroundDecode);
|
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) {
|
if (resetBtn && !resetBtn._wired) {
|
||||||
resetBtn._wired = true;
|
resetBtn._wired = true;
|
||||||
resetBtn.addEventListener("click", resetBackgroundDecode);
|
resetBtn.addEventListener("click", () => { void resetBackgroundDecode(); });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
window.initBackgroundDecode = initBackgroundDecode;
|
bgdWindow.initBackgroundDecode = initBackgroundDecode;
|
||||||
window.wireBackgroundDecodeEvents = wireBackgroundDecodeEvents;
|
bgdWindow.wireBackgroundDecodeEvents = wireBackgroundDecodeEvents;
|
||||||
window.setBackgroundDecodeRig = setBackgroundDecodeRig;
|
bgdWindow.setBackgroundDecodeRig = setBackgroundDecodeRig;
|
||||||
})();
|
})();
|
||||||
+46
@@ -0,0 +1,46 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2026 Stan Grams <sjg@haxx.space>
|
||||||
|
//
|
||||||
|
// 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"));
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user