build: enable frontend module code splitting
This commit is contained in:
+349
-352
@@ -1,365 +1,362 @@
|
||||
"use strict";
|
||||
(() => {
|
||||
// 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;
|
||||
// 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" || 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;
|
||||
});
|
||||
}
|
||||
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();
|
||||
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 = '<div class="bgd-checklist-empty">' + (all.length === 0 ? "No supported bookmarks available." : "No bookmarks match filter.") + "</div>";
|
||||
return;
|
||||
}
|
||||
function setBackgroundDecodeRig(rigId) {
|
||||
const nextRigId = rigId || null;
|
||||
if (nextRigId === currentRigId) return;
|
||||
currentRigId = nextRigId;
|
||||
if (!currentRigId) return;
|
||||
loadBackgroundDecode();
|
||||
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 = '<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>";
|
||||
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: [] };
|
||||
}
|
||||
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();
|
||||
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 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();
|
||||
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 ? '<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) {
|
||||
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, ">").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);
|
||||
});
|
||||
}
|
||||
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();
|
||||
const enabledCb = document.getElementById("background-decode-enabled");
|
||||
if (enabledCb && !enabledCb._wired) {
|
||||
enabledCb._wired = true;
|
||||
enabledCb.addEventListener("change", function() {
|
||||
markBgdDirty();
|
||||
});
|
||||
}
|
||||
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();
|
||||
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();
|
||||
});
|
||||
}
|
||||
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" || 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 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 = '<div class="bgd-checklist-empty">' + (all.length === 0 ? "No supported bookmarks available." : "No bookmarks match filter.") + "</div>";
|
||||
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 = '<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>";
|
||||
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 ? '<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) {
|
||||
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, ">").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;
|
||||
})();
|
||||
}
|
||||
bgdWindow.initBackgroundDecode = initBackgroundDecode;
|
||||
bgdWindow.wireBackgroundDecodeEvents = wireBackgroundDecodeEvents;
|
||||
bgdWindow.setBackgroundDecodeRig = setBackgroundDecodeRig;
|
||||
})();
|
||||
|
||||
Reference in New Issue
Block a user