diff --git a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/generated/plugin-loader.js b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/generated/plugin-loader.js index 99fa5838..026d8e7c 100644 --- a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/generated/plugin-loader.js +++ b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/generated/plugin-loader.js @@ -10,7 +10,7 @@ const pluginGroups = { }; const loaded = /* @__PURE__ */ new Set(); const loading = /* @__PURE__ */ new Map(); -const modulePlugins = /* @__PURE__ */ new Set(["/ft8.js", "/ft4.js", "/ft2.js", "/wspr.js", "/cw.js", "/vdes.js"]); +const modulePlugins = /* @__PURE__ */ new Set(["/ft8.js", "/ft4.js", "/ft2.js", "/wspr.js", "/cw.js", "/vdes.js", "/wefax.js"]); function loadLegacyScript(path) { return new Promise((resolve, reject) => { const script = document.createElement("script"); diff --git a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/generated/wefax.js b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/generated/wefax.js index 2816fffc..5e07f93a 100644 --- a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/generated/wefax.js +++ b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/generated/wefax.js @@ -1,304 +1,324 @@ "use strict"; -var wefaxDom = { - status: document.getElementById("wefax-status"), - liveView: document.getElementById("wefax-live-view"), - historyView: document.getElementById("wefax-history-view"), - liveContainer: document.getElementById("wefax-live-container"), - liveInfo: document.getElementById("wefax-live-info"), - liveCanvas: document.getElementById("wefax-live-canvas"), - liveLatest: document.getElementById("wefax-live-latest"), - historyList: document.getElementById("wefax-history-list"), - historyCount: document.getElementById("wefax-history-count"), - filterInput: document.getElementById("wefax-filter"), - sortSelect: document.getElementById("wefax-sort"), - toggleBtn: document.getElementById("wefax-decode-toggle-btn"), - clearBtn: document.getElementById("wefax-clear-btn"), - viewLiveBtn: document.getElementById("wefax-view-live"), - viewHistoryBtn: document.getElementById("wefax-view-history") -}; -var wefaxImageHistory = []; -var WEFAX_MAX_IMAGES = 100; -var wefaxLiveCtx = null; -var wefaxLiveLineCount = 0; -var wefaxLivePixelsPerLine = 1809; -var wefaxActiveView = "live"; -var wefaxFilterText = ""; -function currentWefaxHistoryRetentionMs() { - return window.getDecodeHistoryRetentionMs ? window.getDecodeHistoryRetentionMs() : 24 * 60 * 60 * 1e3; -} -function pruneWefaxHistory() { - var cutoff = Date.now() - currentWefaxHistoryRetentionMs(); - wefaxImageHistory = wefaxImageHistory.filter(function(m) { - return (m._tsMs || 0) > cutoff; - }); -} -function escapeHtml(s) { - return String(s).replace(/&/g, "&").replace(//g, ">").replace(/"/g, """); -} -function scheduleWefaxUi(key, job) { - if (typeof window.trxScheduleUiFrameJob === "function") { - window.trxScheduleUiFrameJob(key, job); - return; +(() => { + // src/plugins/wefax.ts + var wefaxWindow = window; + var wefaxDom = { + status: document.getElementById("wefax-status"), + liveView: document.getElementById("wefax-live-view"), + historyView: document.getElementById("wefax-history-view"), + liveContainer: document.getElementById("wefax-live-container"), + liveInfo: document.getElementById("wefax-live-info"), + liveCanvas: document.getElementById("wefax-live-canvas"), + liveLatest: document.getElementById("wefax-live-latest"), + historyList: document.getElementById("wefax-history-list"), + historyCount: document.getElementById("wefax-history-count"), + filterInput: document.getElementById("wefax-filter"), + sortSelect: document.getElementById("wefax-sort"), + toggleBtn: document.getElementById("wefax-decode-toggle-btn"), + clearBtn: document.getElementById("wefax-clear-btn"), + viewLiveBtn: document.getElementById("wefax-view-live"), + viewHistoryBtn: document.getElementById("wefax-view-history") + }; + var wefaxImageHistory = []; + var WEFAX_MAX_IMAGES = 100; + var wefaxLiveCtx = null; + var wefaxLiveLineCount = 0; + var wefaxLivePixelsPerLine = 1809; + var wefaxActiveView = "live"; + var wefaxFilterText = ""; + function currentWefaxHistoryRetentionMs() { + return wefaxWindow.getDecodeHistoryRetentionMs?.() ?? 24 * 60 * 60 * 1e3; } - job(); -} -function switchWefaxView(view) { - wefaxActiveView = view; - if (wefaxDom.liveView) wefaxDom.liveView.style.display = view === "live" ? "" : "none"; - if (wefaxDom.historyView) wefaxDom.historyView.style.display = view === "history" ? "" : "none"; - [wefaxDom.viewLiveBtn, wefaxDom.viewHistoryBtn].forEach(function(btn) { - if (btn) btn.classList.remove("sat-view-active"); - }); - if (view === "live" && wefaxDom.viewLiveBtn) wefaxDom.viewLiveBtn.classList.add("sat-view-active"); - if (view === "history" && wefaxDom.viewHistoryBtn) wefaxDom.viewHistoryBtn.classList.add("sat-view-active"); - if (view === "history") renderWefaxHistoryTable(); -} -if (wefaxDom.viewLiveBtn) wefaxDom.viewLiveBtn.addEventListener("click", function() { - switchWefaxView("live"); -}); -if (wefaxDom.viewHistoryBtn) wefaxDom.viewHistoryBtn.addEventListener("click", function() { - switchWefaxView("history"); -}); -function resetLiveCanvas(pixelsPerLine) { - wefaxLivePixelsPerLine = pixelsPerLine; - wefaxLiveLineCount = 0; - wefaxDom.liveCanvas.width = pixelsPerLine; - wefaxDom.liveCanvas.height = 800; - wefaxLiveCtx = wefaxDom.liveCanvas.getContext("2d"); - wefaxLiveCtx.fillStyle = "#000"; - wefaxLiveCtx.fillRect(0, 0, wefaxDom.liveCanvas.width, wefaxDom.liveCanvas.height); - if (wefaxDom.liveContainer) wefaxDom.liveContainer.style.display = ""; -} -function paintLine(lineBytes) { - if (!wefaxLiveCtx) return; - var y = wefaxLiveLineCount; - if (y >= wefaxDom.liveCanvas.height) { - var old = wefaxLiveCtx.getImageData(0, 0, wefaxDom.liveCanvas.width, wefaxDom.liveCanvas.height); - wefaxDom.liveCanvas.height *= 2; - wefaxLiveCtx.putImageData(old, 0, 0); - } - var w = wefaxLivePixelsPerLine; - var imgData = wefaxLiveCtx.createImageData(w, 1); - var d = imgData.data; - for (var x = 0; x < w; x++) { - var v = x < lineBytes.length ? lineBytes[x] : 0; - var i = x * 4; - d[i] = v; - d[i + 1] = v; - d[i + 2] = v; - d[i + 3] = 255; - } - wefaxLiveCtx.putImageData(imgData, 0, y); - wefaxLiveLineCount++; -} -function renderWefaxLatestCard() { - if (!wefaxDom.liveLatest) return; - if (wefaxImageHistory.length === 0) { - wefaxDom.liveLatest.innerHTML = '
No images decoded yet. Enable the decoder and tune to a WEFAX station.
'; - return; - } - var img = wefaxImageHistory[0]; - var ts = img._ts || "--"; - var date = img._tsMs ? new Date(img._tsMs).toLocaleDateString() : ""; - var meta = [ - img.ioc + " IOC", - img.lpm + " LPM", - img.line_count + " lines", - date + " " + ts - ].join(" · "); - var imgSrc = img._dataUrl ? img._dataUrl : img.path ? "/images/" + escapeHtml(img.path.split("/").pop()) : null; - var html = '
'; - html += '
Latest decoded image
'; - html += '
' + escapeHtml(meta) + "
"; - if (imgSrc) { - html += 'View full image'; - } - html += "
"; - wefaxDom.liveLatest.innerHTML = html; -} -function getWefaxFilteredHistory() { - var items = wefaxImageHistory; - if (wefaxFilterText) { - items = items.filter(function(i) { - var haystack = [ - String(i.ioc || ""), - String(i.lpm || ""), - String(i.line_count || "") - ].join(" ").toUpperCase(); - return haystack.indexOf(wefaxFilterText) >= 0; + function pruneWefaxHistory() { + const cutoff = Date.now() - currentWefaxHistoryRetentionMs(); + wefaxImageHistory = wefaxImageHistory.filter(function(m) { + return (m._tsMs || 0) > cutoff; }); } - var sortVal = wefaxDom.sortSelect ? wefaxDom.sortSelect.value : "newest"; - if (sortVal === "oldest") items = items.slice().reverse(); - return items; -} -function renderWefaxHistoryRow(img) { - var row = document.createElement("div"); - row.className = "sat-history-row"; - var ts = img._ts || "--"; - var date = img._tsMs ? new Date(img._tsMs).toLocaleDateString([], { month: "short", day: "numeric" }) : ""; - var ioc = img.ioc || "--"; - var lpm = img.lpm || "--"; - var lines = img.line_count || 0; - var imgSrc = img._dataUrl ? img._dataUrl : img.path ? "/images/" + escapeHtml(img.path.split("/").pop()) : null; - var link = imgSrc ? 'View' : "--"; - row.innerHTML = [ - "" + escapeHtml(date + " " + ts) + "", - "" + escapeHtml(String(ioc)) + "", - "" + escapeHtml(String(lpm)) + "", - "" + lines + "", - "" + link + "" - ].join(""); - return row; -} -function renderWefaxHistoryTable() { - if (!wefaxDom.historyList) return; - pruneWefaxHistory(); - var items = getWefaxFilteredHistory(); - var fragment = document.createDocumentFragment(); - for (var i = 0; i < items.length; i++) { - fragment.appendChild(renderWefaxHistoryRow(items[i])); + function escapeHtml(s) { + return String(s).replace(/&/g, "&").replace(//g, ">").replace(/"/g, """); } - wefaxDom.historyList.replaceChildren(fragment); - if (wefaxDom.historyCount) { - var total = wefaxImageHistory.length; - var shown = items.length; - wefaxDom.historyCount.textContent = total === 0 ? "No images yet" : shown === total ? total + " image" + (total === 1 ? "" : "s") : shown + " of " + total + " images"; + function scheduleWefaxUi(key, job) { + if (typeof wefaxWindow.trxScheduleUiFrameJob === "function") { + wefaxWindow.trxScheduleUiFrameJob(key, job); + return; + } + job(); } -} -function addWefaxImage(msg) { - var tsMs = Number.isFinite(msg.ts_ms) ? Number(msg.ts_ms) : Date.now(); - msg._tsMs = tsMs; - msg._ts = new Date(tsMs).toLocaleTimeString([], { - hour: "2-digit", - minute: "2-digit", - second: "2-digit" + function switchWefaxView(view) { + wefaxActiveView = view; + if (wefaxDom.liveView) wefaxDom.liveView.style.display = view === "live" ? "" : "none"; + if (wefaxDom.historyView) wefaxDom.historyView.style.display = view === "history" ? "" : "none"; + [wefaxDom.viewLiveBtn, wefaxDom.viewHistoryBtn].forEach(function(btn) { + if (btn) btn.classList.remove("sat-view-active"); + }); + if (view === "live" && wefaxDom.viewLiveBtn) wefaxDom.viewLiveBtn.classList.add("sat-view-active"); + if (view === "history" && wefaxDom.viewHistoryBtn) wefaxDom.viewHistoryBtn.classList.add("sat-view-active"); + if (view === "history") renderWefaxHistoryTable(); + } + if (wefaxDom.viewLiveBtn) wefaxDom.viewLiveBtn.addEventListener("click", function() { + switchWefaxView("live"); }); - if (wefaxLiveCtx && wefaxLiveLineCount > 0) { - var trimmed = wefaxLiveCtx.getImageData(0, 0, wefaxDom.liveCanvas.width, wefaxLiveLineCount); - wefaxDom.liveCanvas.height = wefaxLiveLineCount; - wefaxLiveCtx.putImageData(trimmed, 0, 0); - try { - msg._dataUrl = wefaxDom.liveCanvas.toDataURL("image/png"); - } catch (e) { + if (wefaxDom.viewHistoryBtn) wefaxDom.viewHistoryBtn.addEventListener("click", function() { + switchWefaxView("history"); + }); + function resetLiveCanvas(pixelsPerLine) { + const canvas = wefaxDom.liveCanvas; + if (!canvas) return; + wefaxLivePixelsPerLine = pixelsPerLine; + wefaxLiveLineCount = 0; + canvas.width = pixelsPerLine; + canvas.height = 800; + wefaxLiveCtx = canvas.getContext("2d"); + if (!wefaxLiveCtx) return; + wefaxLiveCtx.fillStyle = "#000"; + wefaxLiveCtx.fillRect(0, 0, canvas.width, canvas.height); + if (wefaxDom.liveContainer) wefaxDom.liveContainer.style.display = ""; + } + function paintLine(lineBytes) { + const canvas = wefaxDom.liveCanvas; + if (!wefaxLiveCtx || !canvas) return; + const y = wefaxLiveLineCount; + if (y >= canvas.height) { + const old = wefaxLiveCtx.getImageData(0, 0, canvas.width, canvas.height); + canvas.height *= 2; + wefaxLiveCtx = canvas.getContext("2d"); + if (!wefaxLiveCtx) return; + wefaxLiveCtx.putImageData(old, 0, 0); + } + const w = wefaxLivePixelsPerLine; + const imgData = wefaxLiveCtx.createImageData(w, 1); + const d = imgData.data; + for (let x = 0; x < w; x++) { + const v = lineBytes[x] ?? 0; + const i = x * 4; + d[i] = v; + d[i + 1] = v; + d[i + 2] = v; + d[i + 3] = 255; + } + wefaxLiveCtx.putImageData(imgData, 0, y); + wefaxLiveLineCount++; + } + function renderWefaxLatestCard() { + if (!wefaxDom.liveLatest) return; + if (wefaxImageHistory.length === 0) { + wefaxDom.liveLatest.innerHTML = '
No images decoded yet. Enable the decoder and tune to a WEFAX station.
'; + return; + } + const img = wefaxImageHistory[0]; + if (!img) return; + const ts = img._ts || "--"; + const date = img._tsMs ? new Date(img._tsMs).toLocaleDateString() : ""; + const meta = [ + `${String(img.ioc ?? "--")} IOC`, + `${String(img.lpm ?? "--")} LPM`, + `${String(img.line_count ?? 0)} lines`, + `${date} ${ts}` + ].join(" · "); + const imgSrc = img._dataUrl ? img._dataUrl : img.path ? "/images/" + escapeHtml(img.path.split("/").pop()) : null; + let html = '
'; + html += '
Latest decoded image
'; + html += '
' + escapeHtml(meta) + "
"; + if (imgSrc) { + html += 'View full image'; + } + html += "
"; + wefaxDom.liveLatest.innerHTML = html; + } + function getWefaxFilteredHistory() { + let items = wefaxImageHistory; + if (wefaxFilterText) { + items = items.filter(function(i) { + const haystack = [ + String(i.ioc || ""), + String(i.lpm || ""), + String(i.line_count || "") + ].join(" ").toUpperCase(); + return haystack.indexOf(wefaxFilterText) >= 0; + }); + } + const sortVal = wefaxDom.sortSelect ? wefaxDom.sortSelect.value : "newest"; + if (sortVal === "oldest") items = items.slice().reverse(); + return items; + } + function renderWefaxHistoryRow(img) { + const row = document.createElement("div"); + row.className = "sat-history-row"; + const ts = img._ts || "--"; + const date = img._tsMs ? new Date(img._tsMs).toLocaleDateString([], { month: "short", day: "numeric" }) : ""; + const ioc = img.ioc || "--"; + const lpm = img.lpm || "--"; + const lines = img.line_count || 0; + const imgSrc = img._dataUrl ? img._dataUrl : img.path ? "/images/" + escapeHtml(img.path.split("/").pop()) : null; + const link = imgSrc ? 'View' : "--"; + row.innerHTML = [ + "" + escapeHtml(date + " " + ts) + "", + "" + escapeHtml(String(ioc)) + "", + "" + escapeHtml(String(lpm)) + "", + `${String(lines)}`, + "" + link + "" + ].join(""); + return row; + } + function renderWefaxHistoryTable() { + if (!wefaxDom.historyList) return; + pruneWefaxHistory(); + const items = getWefaxFilteredHistory(); + const fragment = document.createDocumentFragment(); + for (const item of items) { + fragment.appendChild(renderWefaxHistoryRow(item)); + } + wefaxDom.historyList.replaceChildren(fragment); + if (wefaxDom.historyCount) { + const total = wefaxImageHistory.length; + const shown = items.length; + wefaxDom.historyCount.textContent = total === 0 ? "No images yet" : shown === total ? `${String(total)} image${total === 1 ? "" : "s"}` : `${String(shown)} of ${String(total)} images`; } } - wefaxImageHistory.unshift(msg); - if (wefaxImageHistory.length > WEFAX_MAX_IMAGES) { - wefaxImageHistory = wefaxImageHistory.slice(0, WEFAX_MAX_IMAGES); - } - scheduleWefaxUi("wefax-latest", renderWefaxLatestCard); - if (wefaxActiveView === "history") { - scheduleWefaxUi("wefax-history", renderWefaxHistoryTable); - } -} -window.onServerWefaxProgress = function(msg) { - if (msg.state && !msg.line_data) { - if (wefaxDom.status) { - wefaxDom.status.textContent = msg.state; - wefaxDom.status.style.color = msg.state.indexOf("Idle") === 0 ? "" : "var(--text-accent)"; - } - return; - } - if (msg.line_count <= 1 || !wefaxLiveCtx) { - resetLiveCanvas(msg.pixels_per_line || 1809); - } - if (msg.line_data) { - var binary = atob(msg.line_data); - var bytes = new Uint8Array(binary.length); - for (var i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i); - paintLine(bytes); - } - if (wefaxDom.liveInfo) { - wefaxDom.liveInfo.textContent = "Line " + msg.line_count + " · " + msg.ioc + " IOC · " + msg.lpm + " LPM"; - } - if (wefaxDom.status) { - wefaxDom.status.textContent = "Receiving — line " + msg.line_count; - wefaxDom.status.style.color = "var(--text-accent)"; - } -}; -window.onServerWefax = function(msg) { - addWefaxImage(msg); - if (wefaxDom.liveContainer) wefaxDom.liveContainer.style.display = "none"; - if (wefaxDom.status) { - wefaxDom.status.textContent = "Complete — " + msg.line_count + " lines"; - wefaxDom.status.style.color = ""; - } -}; -window.restoreWefaxHistory = function(messages) { - if (!messages || !messages.length) return; - for (var i = 0; i < messages.length; i++) { - var tsMs = Number.isFinite(messages[i].ts_ms) ? Number(messages[i].ts_ms) : Date.now(); - messages[i]._tsMs = tsMs; - messages[i]._ts = new Date(tsMs).toLocaleTimeString([], { + function addWefaxImage(msg) { + const tsMs = Number.isFinite(msg.ts_ms) ? Number(msg.ts_ms) : Date.now(); + msg._tsMs = tsMs; + msg._ts = new Date(tsMs).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit", second: "2-digit" }); - } - wefaxImageHistory = messages.concat(wefaxImageHistory); - pruneWefaxHistory(); - scheduleWefaxUi("wefax-latest", renderWefaxLatestCard); - if (wefaxActiveView === "history") { - scheduleWefaxUi("wefax-history", renderWefaxHistoryTable); - } -}; -window.pruneWefaxHistoryView = function() { - pruneWefaxHistory(); - renderWefaxHistoryTable(); - renderWefaxLatestCard(); -}; -window.resetWefaxHistoryView = function() { - wefaxImageHistory = []; - if (wefaxDom.historyList) wefaxDom.historyList.innerHTML = ""; - if (wefaxDom.liveContainer) wefaxDom.liveContainer.style.display = "none"; - wefaxLiveCtx = null; - wefaxLiveLineCount = 0; - renderWefaxLatestCard(); - renderWefaxHistoryTable(); - if (wefaxDom.status) { - wefaxDom.status.textContent = "Idle"; - wefaxDom.status.style.color = ""; - } -}; -if (wefaxDom.filterInput) { - wefaxDom.filterInput.addEventListener("input", function() { - wefaxFilterText = wefaxDom.filterInput.value.trim().toUpperCase(); - scheduleWefaxUi("wefax-history", renderWefaxHistoryTable); - }); -} -if (wefaxDom.sortSelect) { - wefaxDom.sortSelect.addEventListener("change", function() { - scheduleWefaxUi("wefax-history", renderWefaxHistoryTable); - }); -} -window.syncWefaxToggle = function(enabled) { - if (!wefaxDom.toggleBtn) return; - wefaxDom.toggleBtn.dataset.enabled = enabled ? "true" : "false"; - wefaxDom.toggleBtn.textContent = enabled ? "Disable WEFAX" : "Enable WEFAX"; - wefaxDom.toggleBtn.style.borderColor = enabled ? "#00d17f" : ""; - wefaxDom.toggleBtn.style.color = enabled ? "#00d17f" : ""; -}; -if (wefaxDom.toggleBtn) { - wefaxDom.toggleBtn.addEventListener("click", async function() { - try { - if (window.takeSchedulerControlForDecoderDisable) { - await window.takeSchedulerControlForDecoderDisable(wefaxDom.toggleBtn); + const canvas = wefaxDom.liveCanvas; + if (wefaxLiveCtx && canvas && wefaxLiveLineCount > 0) { + const trimmed = wefaxLiveCtx.getImageData(0, 0, canvas.width, wefaxLiveLineCount); + canvas.height = wefaxLiveLineCount; + wefaxLiveCtx = canvas.getContext("2d"); + if (!wefaxLiveCtx) return; + wefaxLiveCtx.putImageData(trimmed, 0, 0); + try { + msg._dataUrl = canvas.toDataURL("image/png"); + } catch { } - await postPath("/toggle_wefax_decode"); - } catch (e) { - console.error("WEFAX toggle failed", e); } - }); -} -if (wefaxDom.clearBtn) { - wefaxDom.clearBtn.addEventListener("click", async function() { - try { - await postPath("/clear_wefax_decode"); - window.resetWefaxHistoryView(); - } catch (e) { - console.error("WEFAX clear failed", e); + wefaxImageHistory.unshift(msg); + if (wefaxImageHistory.length > WEFAX_MAX_IMAGES) { + wefaxImageHistory = wefaxImageHistory.slice(0, WEFAX_MAX_IMAGES); } - }); -} -renderWefaxLatestCard(); + scheduleWefaxUi("wefax-latest", renderWefaxLatestCard); + if (wefaxActiveView === "history") { + scheduleWefaxUi("wefax-history", renderWefaxHistoryTable); + } + } + wefaxWindow.onServerWefaxProgress = function(msg) { + if (msg.state && !msg.line_data) { + if (wefaxDom.status) { + wefaxDom.status.textContent = msg.state; + wefaxDom.status.style.color = msg.state.indexOf("Idle") === 0 ? "" : "var(--text-accent)"; + } + return; + } + if ((msg.line_count ?? 0) <= 1 || !wefaxLiveCtx) { + resetLiveCanvas(msg.pixels_per_line || 1809); + } + if (msg.line_data) { + const binary = atob(msg.line_data); + const bytes = new Uint8Array(binary.length); + for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i); + paintLine(bytes); + } + if (wefaxDom.liveInfo) { + wefaxDom.liveInfo.textContent = `Line ${String(msg.line_count ?? 0)} · ${String(msg.ioc ?? "--")} IOC · ${String(msg.lpm ?? "--")} LPM`; + } + if (wefaxDom.status) { + wefaxDom.status.textContent = `Receiving — line ${String(msg.line_count ?? 0)}`; + wefaxDom.status.style.color = "var(--text-accent)"; + } + }; + wefaxWindow.onServerWefax = function(msg) { + addWefaxImage(msg); + if (wefaxDom.liveContainer) wefaxDom.liveContainer.style.display = "none"; + if (wefaxDom.status) { + wefaxDom.status.textContent = `Complete — ${String(msg.line_count ?? 0)} lines`; + wefaxDom.status.style.color = ""; + } + }; + wefaxWindow.restoreWefaxHistory = function(messages) { + if (!messages.length) return; + for (const message of messages) { + const tsMs = Number.isFinite(message.ts_ms) ? Number(message.ts_ms) : Date.now(); + message._tsMs = tsMs; + message._ts = new Date(tsMs).toLocaleTimeString([], { + hour: "2-digit", + minute: "2-digit", + second: "2-digit" + }); + } + wefaxImageHistory = messages.concat(wefaxImageHistory); + pruneWefaxHistory(); + scheduleWefaxUi("wefax-latest", renderWefaxLatestCard); + if (wefaxActiveView === "history") { + scheduleWefaxUi("wefax-history", renderWefaxHistoryTable); + } + }; + wefaxWindow.pruneWefaxHistoryView = function() { + pruneWefaxHistory(); + renderWefaxHistoryTable(); + renderWefaxLatestCard(); + }; + wefaxWindow.resetWefaxHistoryView = function() { + wefaxImageHistory = []; + if (wefaxDom.historyList) wefaxDom.historyList.innerHTML = ""; + if (wefaxDom.liveContainer) wefaxDom.liveContainer.style.display = "none"; + wefaxLiveCtx = null; + wefaxLiveLineCount = 0; + renderWefaxLatestCard(); + renderWefaxHistoryTable(); + if (wefaxDom.status) { + wefaxDom.status.textContent = "Idle"; + wefaxDom.status.style.color = ""; + } + }; + if (wefaxDom.filterInput) { + const filterInput = wefaxDom.filterInput; + wefaxDom.filterInput.addEventListener("input", function() { + wefaxFilterText = filterInput.value.trim().toUpperCase(); + scheduleWefaxUi("wefax-history", renderWefaxHistoryTable); + }); + } + if (wefaxDom.sortSelect) { + wefaxDom.sortSelect.addEventListener("change", function() { + scheduleWefaxUi("wefax-history", renderWefaxHistoryTable); + }); + } + wefaxWindow.syncWefaxToggle = function(enabled) { + if (!wefaxDom.toggleBtn) return; + wefaxDom.toggleBtn.dataset.enabled = enabled ? "true" : "false"; + wefaxDom.toggleBtn.textContent = enabled ? "Disable WEFAX" : "Enable WEFAX"; + wefaxDom.toggleBtn.style.borderColor = enabled ? "#00d17f" : ""; + wefaxDom.toggleBtn.style.color = enabled ? "#00d17f" : ""; + }; + if (wefaxDom.toggleBtn) { + const toggleButton = wefaxDom.toggleBtn; + wefaxDom.toggleBtn.addEventListener("click", () => { + void (async () => { + try { + if (wefaxWindow.takeSchedulerControlForDecoderDisable) { + await wefaxWindow.takeSchedulerControlForDecoderDisable(toggleButton); + } + await wefaxWindow.postPath?.("/toggle_wefax_decode"); + } catch (e) { + console.error("WEFAX toggle failed", e); + } + })(); + }); + } + if (wefaxDom.clearBtn) { + wefaxDom.clearBtn.addEventListener("click", () => { + void (async () => { + try { + await wefaxWindow.postPath?.("/clear_wefax_decode"); + wefaxWindow.resetWefaxHistoryView?.(); + } catch (e) { + console.error("WEFAX clear failed", e); + } + })(); + }); + } + renderWefaxLatestCard(); +})(); diff --git a/src/trx-client/trx-frontend/trx-frontend-http/frontend/build.mjs b/src/trx-client/trx-frontend/trx-frontend-http/frontend/build.mjs index 6ce24053..b1663bcc 100644 --- a/src/trx-client/trx-frontend/trx-frontend-http/frontend/build.mjs +++ b/src/trx-client/trx-frontend/trx-frontend-http/frontend/build.mjs @@ -31,7 +31,6 @@ await build({ "sat-scheduler": path.join(sourceDir, "plugins", "sat-scheduler.js"), scheduler: path.join(sourceDir, "plugins", "scheduler.js"), vchan: path.join(sourceDir, "plugins", "vchan.js"), - wefax: path.join(sourceDir, "plugins", "wefax.js"), }, outdir: outputDir, bundle: false, @@ -51,6 +50,7 @@ await build({ ft8: path.join(sourceDir, "plugins", "ft8.ts"), "leaflet-ais-tracksymbol": path.join(sourceDir, "leaflet-ais-tracksymbol.ts"), vdes: path.join(sourceDir, "plugins", "vdes.ts"), + wefax: path.join(sourceDir, "plugins", "wefax.ts"), }, outdir: outputDir, bundle: true, diff --git a/src/trx-client/trx-frontend/trx-frontend-http/frontend/src/plugin-loader.ts b/src/trx-client/trx-frontend/trx-frontend-http/frontend/src/plugin-loader.ts index 2cc0a37a..fbb32d89 100644 --- a/src/trx-client/trx-frontend/trx-frontend-http/frontend/src/plugin-loader.ts +++ b/src/trx-client/trx-frontend/trx-frontend-http/frontend/src/plugin-loader.ts @@ -16,7 +16,7 @@ const pluginGroups: Readonly> = { const loaded = new Set(); const loading = new Map>(); -const modulePlugins = new Set(["/ft8.js", "/ft4.js", "/ft2.js", "/wspr.js", "/cw.js", "/vdes.js"]); +const modulePlugins = new Set(["/ft8.js", "/ft4.js", "/ft2.js", "/wspr.js", "/cw.js", "/vdes.js", "/wefax.js"]); function loadLegacyScript(path: string): Promise { return new Promise((resolve, reject) => { diff --git a/src/trx-client/trx-frontend/trx-frontend-http/frontend/src/plugins/wefax.js b/src/trx-client/trx-frontend/trx-frontend-http/frontend/src/plugins/wefax.ts similarity index 61% rename from src/trx-client/trx-frontend/trx-frontend-http/frontend/src/plugins/wefax.js rename to src/trx-client/trx-frontend/trx-frontend-http/frontend/src/plugins/wefax.ts index d216a380..4bf51969 100644 --- a/src/trx-client/trx-frontend/trx-frontend-http/frontend/src/plugins/wefax.js +++ b/src/trx-client/trx-frontend/trx-frontend-http/frontend/src/plugins/wefax.ts @@ -2,6 +2,43 @@ // // SPDX-License-Identifier: GPL-2.0-or-later +export {}; + +interface WefaxImage { + ts_ms?: number; + ioc?: number; + lpm?: number; + line_count?: number; + path?: string; + _tsMs?: number; + _ts?: string; + _dataUrl?: string; +} + +interface WefaxProgress { + state?: string; + line_data?: string; + line_count?: number; + pixels_per_line?: number; + ioc?: number; + lpm?: number; +} + +interface WefaxBridge { + getDecodeHistoryRetentionMs?: () => number; + trxScheduleUiFrameJob?: (key: string, job: () => void) => void; + postPath?: (path: string) => Promise; + takeSchedulerControlForDecoderDisable?: (button: HTMLElement) => Promise; + onServerWefaxProgress?: (message: WefaxProgress) => void; + onServerWefax?: (message: WefaxImage) => void; + restoreWefaxHistory?: (messages: WefaxImage[]) => void; + pruneWefaxHistoryView?: () => void; + resetWefaxHistoryView?: () => void; + syncWefaxToggle?: (enabled: boolean) => void; +} + +const wefaxWindow = window as unknown as WefaxBridge; + // --------------------------------------------------------------------------- // wefax.js — WEFAX decoder plugin for trx-frontend-http // Live view: decoder state, live canvas, latest image card @@ -9,18 +46,18 @@ // --------------------------------------------------------------------------- // ── DOM references (cached once) ─────────────────────────────────── -var wefaxDom = { +const wefaxDom = { status: document.getElementById('wefax-status'), liveView: document.getElementById('wefax-live-view'), historyView: document.getElementById('wefax-history-view'), liveContainer: document.getElementById('wefax-live-container'), liveInfo: document.getElementById('wefax-live-info'), - liveCanvas: document.getElementById('wefax-live-canvas'), + liveCanvas: document.getElementById('wefax-live-canvas') as HTMLCanvasElement | null, liveLatest: document.getElementById('wefax-live-latest'), historyList: document.getElementById('wefax-history-list'), historyCount: document.getElementById('wefax-history-count'), - filterInput: document.getElementById('wefax-filter'), - sortSelect: document.getElementById('wefax-sort'), + filterInput: document.getElementById('wefax-filter') as HTMLInputElement | null, + sortSelect: document.getElementById('wefax-sort') as HTMLSelectElement | null, toggleBtn: document.getElementById('wefax-decode-toggle-btn'), clearBtn: document.getElementById('wefax-clear-btn'), viewLiveBtn: document.getElementById('wefax-view-live'), @@ -28,25 +65,25 @@ var wefaxDom = { }; // ── State ─────────────────────────────────────────────────────────── -var wefaxImageHistory = []; -var WEFAX_MAX_IMAGES = 100; -var wefaxLiveCtx = null; -var wefaxLiveLineCount = 0; -var wefaxLivePixelsPerLine = 1809; -var wefaxActiveView = 'live'; -var wefaxFilterText = ''; +let wefaxImageHistory: WefaxImage[] = []; +const WEFAX_MAX_IMAGES = 100; +let wefaxLiveCtx: CanvasRenderingContext2D | null = null; +let wefaxLiveLineCount = 0; +let wefaxLivePixelsPerLine = 1809; +let wefaxActiveView: 'live' | 'history' = 'live'; +let wefaxFilterText = ''; // ── Helpers ───────────────────────────────────────────────────────── -function currentWefaxHistoryRetentionMs() { - return window.getDecodeHistoryRetentionMs ? window.getDecodeHistoryRetentionMs() : 24 * 60 * 60 * 1000; +function currentWefaxHistoryRetentionMs(): number { + return wefaxWindow.getDecodeHistoryRetentionMs?.() ?? 24 * 60 * 60 * 1000; } function pruneWefaxHistory() { - var cutoff = Date.now() - currentWefaxHistoryRetentionMs(); + const cutoff = Date.now() - currentWefaxHistoryRetentionMs(); wefaxImageHistory = wefaxImageHistory.filter(function (m) { return (m._tsMs || 0) > cutoff; }); } -function escapeHtml(s) { +function escapeHtml(s: unknown): string { return String(s) .replace(/&/g, '&') .replace(/ void): void { + if (typeof wefaxWindow.trxScheduleUiFrameJob === 'function') { + wefaxWindow.trxScheduleUiFrameJob(key, job); return; } job(); } // ── View switching ────────────────────────────────────────────────── -function switchWefaxView(view) { +function switchWefaxView(view: 'live' | 'history'): void { wefaxActiveView = view; if (wefaxDom.liveView) wefaxDom.liveView.style.display = view === 'live' ? '' : 'none'; if (wefaxDom.historyView) wefaxDom.historyView.style.display = view === 'history' ? '' : 'none'; @@ -81,33 +118,39 @@ if (wefaxDom.viewLiveBtn) wefaxDom.viewLiveBtn.addEventListener('click', func if (wefaxDom.viewHistoryBtn) wefaxDom.viewHistoryBtn.addEventListener('click', function () { switchWefaxView('history'); }); // ── Live canvas rendering ─────────────────────────────────────────── -function resetLiveCanvas(pixelsPerLine) { +function resetLiveCanvas(pixelsPerLine: number): void { + const canvas = wefaxDom.liveCanvas; + if (!canvas) return; wefaxLivePixelsPerLine = pixelsPerLine; wefaxLiveLineCount = 0; - wefaxDom.liveCanvas.width = pixelsPerLine; - wefaxDom.liveCanvas.height = 800; - wefaxLiveCtx = wefaxDom.liveCanvas.getContext('2d'); + canvas.width = pixelsPerLine; + canvas.height = 800; + wefaxLiveCtx = canvas.getContext('2d'); + if (!wefaxLiveCtx) return; wefaxLiveCtx.fillStyle = '#000'; - wefaxLiveCtx.fillRect(0, 0, wefaxDom.liveCanvas.width, wefaxDom.liveCanvas.height); + wefaxLiveCtx.fillRect(0, 0, canvas.width, canvas.height); if (wefaxDom.liveContainer) wefaxDom.liveContainer.style.display = ''; } -function paintLine(lineBytes) { - if (!wefaxLiveCtx) return; - var y = wefaxLiveLineCount; +function paintLine(lineBytes: Uint8Array): void { + const canvas = wefaxDom.liveCanvas; + if (!wefaxLiveCtx || !canvas) return; + const y = wefaxLiveLineCount; - if (y >= wefaxDom.liveCanvas.height) { - var old = wefaxLiveCtx.getImageData(0, 0, wefaxDom.liveCanvas.width, wefaxDom.liveCanvas.height); - wefaxDom.liveCanvas.height *= 2; + if (y >= canvas.height) { + const old = wefaxLiveCtx.getImageData(0, 0, canvas.width, canvas.height); + canvas.height *= 2; + wefaxLiveCtx = canvas.getContext('2d'); + if (!wefaxLiveCtx) return; wefaxLiveCtx.putImageData(old, 0, 0); } - var w = wefaxLivePixelsPerLine; - var imgData = wefaxLiveCtx.createImageData(w, 1); - var d = imgData.data; - for (var x = 0; x < w; x++) { - var v = x < lineBytes.length ? lineBytes[x] : 0; - var i = x * 4; + const w = wefaxLivePixelsPerLine; + const imgData = wefaxLiveCtx.createImageData(w, 1); + const d = imgData.data; + for (let x = 0; x < w; x++) { + const v = lineBytes[x] ?? 0; + const i = x * 4; d[i] = v; d[i + 1] = v; d[i + 2] = v; d[i + 3] = 255; } wefaxLiveCtx.putImageData(imgData, 0, y); @@ -123,23 +166,24 @@ function renderWefaxLatestCard() { return; } - var img = wefaxImageHistory[0]; - var ts = img._ts || '--'; - var date = img._tsMs ? new Date(img._tsMs).toLocaleDateString() : ''; - var meta = [ - img.ioc + ' IOC', - img.lpm + ' LPM', - img.line_count + ' lines', - date + ' ' + ts, + const img = wefaxImageHistory[0]; + if (!img) return; + const ts = img._ts || '--'; + const date = img._tsMs ? new Date(img._tsMs).toLocaleDateString() : ''; + const meta = [ + `${String(img.ioc ?? '--')} IOC`, + `${String(img.lpm ?? '--')} LPM`, + `${String(img.line_count ?? 0)} lines`, + `${date} ${ts}`, ].join(' \u00b7 '); - var imgSrc = img._dataUrl + const imgSrc = img._dataUrl ? img._dataUrl : img.path ? '/images/' + escapeHtml(img.path.split('/').pop()) : null; - var html = '
'; + let html = '
'; html += '
Latest decoded image
'; html += '
' + escapeHtml(meta) + '
'; if (imgSrc) { @@ -151,11 +195,11 @@ function renderWefaxLatestCard() { // ── History view: table ───────────────────────────────────────────── function getWefaxFilteredHistory() { - var items = wefaxImageHistory; + let items = wefaxImageHistory; if (wefaxFilterText) { items = items.filter(function (i) { - var haystack = [ + const haystack = [ String(i.ioc || ''), String(i.lpm || ''), String(i.line_count || ''), @@ -164,28 +208,28 @@ function getWefaxFilteredHistory() { }); } - var sortVal = wefaxDom.sortSelect ? wefaxDom.sortSelect.value : 'newest'; + const sortVal = wefaxDom.sortSelect ? wefaxDom.sortSelect.value : 'newest'; if (sortVal === 'oldest') items = items.slice().reverse(); return items; } -function renderWefaxHistoryRow(img) { - var row = document.createElement('div'); +function renderWefaxHistoryRow(img: WefaxImage): HTMLElement { + const row = document.createElement('div'); row.className = 'sat-history-row'; - var ts = img._ts || '--'; - var date = img._tsMs ? new Date(img._tsMs).toLocaleDateString([], { month: 'short', day: 'numeric' }) : ''; - var ioc = img.ioc || '--'; - var lpm = img.lpm || '--'; - var lines = img.line_count || 0; + const ts = img._ts || '--'; + const date = img._tsMs ? new Date(img._tsMs).toLocaleDateString([], { month: 'short', day: 'numeric' }) : ''; + const ioc = img.ioc || '--'; + const lpm = img.lpm || '--'; + const lines = img.line_count || 0; - var imgSrc = img._dataUrl + const imgSrc = img._dataUrl ? img._dataUrl : img.path ? '/images/' + escapeHtml(img.path.split('/').pop()) : null; - var link = imgSrc + const link = imgSrc ? 'View' : '--'; @@ -193,7 +237,7 @@ function renderWefaxHistoryRow(img) { '' + escapeHtml(date + ' ' + ts) + '', '' + escapeHtml(String(ioc)) + '', '' + escapeHtml(String(lpm)) + '', - '' + lines + '', + `${String(lines)}`, '' + link + '', ].join(''); @@ -203,28 +247,28 @@ function renderWefaxHistoryRow(img) { function renderWefaxHistoryTable() { if (!wefaxDom.historyList) return; pruneWefaxHistory(); - var items = getWefaxFilteredHistory(); - var fragment = document.createDocumentFragment(); - for (var i = 0; i < items.length; i++) { - fragment.appendChild(renderWefaxHistoryRow(items[i])); + const items = getWefaxFilteredHistory(); + const fragment = document.createDocumentFragment(); + for (const item of items) { + fragment.appendChild(renderWefaxHistoryRow(item)); } wefaxDom.historyList.replaceChildren(fragment); if (wefaxDom.historyCount) { - var total = wefaxImageHistory.length; - var shown = items.length; + const total = wefaxImageHistory.length; + const shown = items.length; wefaxDom.historyCount.textContent = total === 0 ? 'No images yet' : shown === total - ? total + ' image' + (total === 1 ? '' : 's') - : shown + ' of ' + total + ' images'; + ? `${String(total)} image${total === 1 ? '' : 's'}` + : `${String(shown)} of ${String(total)} images`; } } // ── Add image to history ──────────────────────────────────────────── -function addWefaxImage(msg) { - var tsMs = Number.isFinite(msg.ts_ms) ? Number(msg.ts_ms) : Date.now(); +function addWefaxImage(msg: WefaxImage): void { + const tsMs = Number.isFinite(msg.ts_ms) ? Number(msg.ts_ms) : Date.now(); msg._tsMs = tsMs; msg._ts = new Date(tsMs).toLocaleTimeString([], { hour: '2-digit', @@ -233,11 +277,14 @@ function addWefaxImage(msg) { }); // Capture the live canvas as a data URI for thumbnails. - if (wefaxLiveCtx && wefaxLiveLineCount > 0) { - var trimmed = wefaxLiveCtx.getImageData(0, 0, wefaxDom.liveCanvas.width, wefaxLiveLineCount); - wefaxDom.liveCanvas.height = wefaxLiveLineCount; + const canvas = wefaxDom.liveCanvas; + if (wefaxLiveCtx && canvas && wefaxLiveLineCount > 0) { + const trimmed = wefaxLiveCtx.getImageData(0, 0, canvas.width, wefaxLiveLineCount); + canvas.height = wefaxLiveLineCount; + wefaxLiveCtx = canvas.getContext('2d'); + if (!wefaxLiveCtx) return; wefaxLiveCtx.putImageData(trimmed, 0, 0); - try { msg._dataUrl = wefaxDom.liveCanvas.toDataURL('image/png'); } catch (e) {} + try { msg._dataUrl = canvas.toDataURL('image/png'); } catch {} } wefaxImageHistory.unshift(msg); @@ -252,7 +299,7 @@ function addWefaxImage(msg) { } // ── SSE event handlers (public API) ───────────────────────────────── -window.onServerWefaxProgress = function (msg) { +wefaxWindow.onServerWefaxProgress = function (msg: WefaxProgress) { // State-only update (no image data): show decoder state in status. if (msg.state && !msg.line_data) { if (wefaxDom.status) { @@ -263,43 +310,43 @@ window.onServerWefaxProgress = function (msg) { return; } - if (msg.line_count <= 1 || !wefaxLiveCtx) { + if ((msg.line_count ?? 0) <= 1 || !wefaxLiveCtx) { resetLiveCanvas(msg.pixels_per_line || 1809); } if (msg.line_data) { - var binary = atob(msg.line_data); - var bytes = new Uint8Array(binary.length); - for (var i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i); + const binary = atob(msg.line_data); + const bytes = new Uint8Array(binary.length); + for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i); paintLine(bytes); } if (wefaxDom.liveInfo) { wefaxDom.liveInfo.textContent = - 'Line ' + msg.line_count + ' \u00b7 ' + msg.ioc + ' IOC \u00b7 ' + msg.lpm + ' LPM'; + `Line ${String(msg.line_count ?? 0)} \u00b7 ${String(msg.ioc ?? '--')} IOC \u00b7 ${String(msg.lpm ?? '--')} LPM`; } if (wefaxDom.status) { - wefaxDom.status.textContent = 'Receiving \u2014 line ' + msg.line_count; + wefaxDom.status.textContent = `Receiving \u2014 line ${String(msg.line_count ?? 0)}`; wefaxDom.status.style.color = 'var(--text-accent)'; } }; -window.onServerWefax = function (msg) { +wefaxWindow.onServerWefax = function (msg: WefaxImage) { addWefaxImage(msg); if (wefaxDom.liveContainer) wefaxDom.liveContainer.style.display = 'none'; if (wefaxDom.status) { - wefaxDom.status.textContent = 'Complete \u2014 ' + msg.line_count + ' lines'; + wefaxDom.status.textContent = `Complete \u2014 ${String(msg.line_count ?? 0)} lines`; wefaxDom.status.style.color = ''; } }; -window.restoreWefaxHistory = function (messages) { - if (!messages || !messages.length) return; - for (var i = 0; i < messages.length; i++) { - var tsMs = Number.isFinite(messages[i].ts_ms) ? Number(messages[i].ts_ms) : Date.now(); - messages[i]._tsMs = tsMs; - messages[i]._ts = new Date(tsMs).toLocaleTimeString([], { +wefaxWindow.restoreWefaxHistory = function (messages: WefaxImage[]) { + if (!messages.length) return; + for (const message of messages) { + const tsMs = Number.isFinite(message.ts_ms) ? Number(message.ts_ms) : Date.now(); + message._tsMs = tsMs; + message._ts = new Date(tsMs).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit', @@ -313,13 +360,13 @@ window.restoreWefaxHistory = function (messages) { } }; -window.pruneWefaxHistoryView = function () { +wefaxWindow.pruneWefaxHistoryView = function () { pruneWefaxHistory(); renderWefaxHistoryTable(); renderWefaxLatestCard(); }; -window.resetWefaxHistoryView = function () { +wefaxWindow.resetWefaxHistoryView = function () { wefaxImageHistory = []; if (wefaxDom.historyList) wefaxDom.historyList.innerHTML = ''; if (wefaxDom.liveContainer) wefaxDom.liveContainer.style.display = 'none'; @@ -335,8 +382,9 @@ window.resetWefaxHistoryView = function () { // ── Filter / sort handlers ────────────────────────────────────────── if (wefaxDom.filterInput) { + const filterInput = wefaxDom.filterInput; wefaxDom.filterInput.addEventListener('input', function () { - wefaxFilterText = wefaxDom.filterInput.value.trim().toUpperCase(); + wefaxFilterText = filterInput.value.trim().toUpperCase(); scheduleWefaxUi('wefax-history', renderWefaxHistoryTable); }); } @@ -350,7 +398,7 @@ if (wefaxDom.sortSelect) { // Sync the Enable/Disable button from the SSE state update. This is // belt-and-suspenders alongside app.js _decoderToggles — guarantees the // WEFAX button always reflects the server state. -window.syncWefaxToggle = function (enabled) { +wefaxWindow.syncWefaxToggle = function (enabled: boolean) { if (!wefaxDom.toggleBtn) return; wefaxDom.toggleBtn.dataset.enabled = enabled ? 'true' : 'false'; wefaxDom.toggleBtn.textContent = enabled ? 'Disable WEFAX' : 'Enable WEFAX'; @@ -360,26 +408,27 @@ window.syncWefaxToggle = function (enabled) { // ── Button handlers ───────────────────────────────────────────────── if (wefaxDom.toggleBtn) { - wefaxDom.toggleBtn.addEventListener('click', async function () { + const toggleButton = wefaxDom.toggleBtn; + wefaxDom.toggleBtn.addEventListener('click', () => { void (async () => { try { - if (window.takeSchedulerControlForDecoderDisable) { - await window.takeSchedulerControlForDecoderDisable(wefaxDom.toggleBtn); + if (wefaxWindow.takeSchedulerControlForDecoderDisable) { + await wefaxWindow.takeSchedulerControlForDecoderDisable(toggleButton); } - await postPath('/toggle_wefax_decode'); + await wefaxWindow.postPath?.('/toggle_wefax_decode'); } catch (e) { console.error('WEFAX toggle failed', e); } - }); + })(); }); } if (wefaxDom.clearBtn) { - wefaxDom.clearBtn.addEventListener('click', async function () { + wefaxDom.clearBtn.addEventListener('click', () => { void (async () => { try { - await postPath('/clear_wefax_decode'); - window.resetWefaxHistoryView(); + await wefaxWindow.postPath?.('/clear_wefax_decode'); + wefaxWindow.resetWefaxHistoryView?.(); } catch (e) { console.error('WEFAX clear failed', e); } - }); + })(); }); } // ── Initial render ────────────────────────────────────────────────── diff --git a/src/trx-client/trx-frontend/trx-frontend-http/frontend/tests/wefax.test.mjs b/src/trx-client/trx-frontend/trx-frontend-http/frontend/tests/wefax.test.mjs new file mode 100644 index 00000000..08c41edf --- /dev/null +++ b/src/trx-client/trx-frontend/trx-frontend-http/frontend/tests/wefax.test.mjs @@ -0,0 +1,30 @@ +// SPDX-FileCopyrightText: 2026 Stan Grams +// +// SPDX-License-Identifier: GPL-2.0-or-later + +import assert from "node:assert/strict"; +import { readFile } from "node:fs/promises"; +import test from "node:test"; +import vm from "node:vm"; + +test("WEFAX entry exposes typed lifecycle handlers and renders decoder state", async () => { + const status = { textContent: "", style: { color: "" } }; + const window = {}; + const context = vm.createContext({ + window, + document: { getElementById: (id) => id === "wefax-status" ? status : null }, + Date, + Number, + String, + Uint8Array, + console, + }); + const source = await readFile(new URL("../../assets/web/generated/wefax.js", import.meta.url), "utf8"); + new vm.Script(source).runInContext(context); + + assert.equal(typeof window.onServerWefaxProgress, "function"); + assert.equal(typeof window.restoreWefaxHistory, "function"); + window.onServerWefaxProgress({ state: "Scanning 12 MHz" }); + assert.equal(status.textContent, "Scanning 12 MHz"); + assert.equal(status.style.color, "var(--text-accent)"); +});