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)");
+});