Complete TypeScript frontend migration #22
@@ -488,12 +488,9 @@ function currentDecodeHistoryRetentionMs() {
|
||||
}
|
||||
window.getDecodeHistoryRetentionMs = currentDecodeHistoryRetentionMs;
|
||||
window.applyDecodeHistoryRetention = function() {
|
||||
window.trxPluginRuntime.prune("aprs");
|
||||
window.trxPluginRuntime.prune("hf_aprs");
|
||||
window.trxPluginRuntime.prune("ais");
|
||||
window.trxPluginRuntime.prune("vdes");
|
||||
if (typeof window.pruneFt8HistoryView === "function") window.pruneFt8HistoryView();
|
||||
if (typeof window.pruneWsprHistoryView === "function") window.pruneWsprHistoryView();
|
||||
for (const decoder of ["aprs", "hf_aprs", "ais", "vdes", "ft8", "ft4", "ft2", "wspr", "wefax"]) {
|
||||
window.trxPluginRuntime.prune(decoder);
|
||||
}
|
||||
};
|
||||
function syncTopBarAccess() {
|
||||
const loggedOut = authEnabled && !authRole;
|
||||
@@ -5857,18 +5854,7 @@ function updateDecodeStatus(text) {
|
||||
}
|
||||
}
|
||||
function dispatchDecodeMessage(msg, skipStats) {
|
||||
if (msg.type === "ais" || msg.type === "vdes" || msg.type === "aprs" || msg.type === "hf_aprs") {
|
||||
window.trxPluginRuntime.dispatch(msg.type, msg);
|
||||
}
|
||||
if (msg.type === "cw" && window.onServerCw) window.onServerCw(msg);
|
||||
if (msg.type === "ft8" && window.onServerFt8) window.onServerFt8(msg);
|
||||
if (msg.type === "ft4" && window.onServerFt4) window.onServerFt4(msg);
|
||||
if (msg.type === "ft2" && window.onServerFt2) window.onServerFt2(msg);
|
||||
if (msg.type === "wspr" && window.onServerWspr) window.onServerWspr(msg);
|
||||
if (msg.type === "lrpt_image" && window.onServerLrptImage) window.onServerLrptImage(msg);
|
||||
if (msg.type === "lrpt_progress" && window.onServerLrptProgress) window.onServerLrptProgress(msg);
|
||||
if (msg.type === "wefax" && window.onServerWefax) window.onServerWefax(msg);
|
||||
if (msg.type === "wefax_progress" && window.onServerWefaxProgress) window.onServerWefaxProgress(msg);
|
||||
if (msg.type) window.trxPluginRuntime.dispatch(msg.type, msg);
|
||||
if (!skipStats && msg.type && msg.type !== "lrpt_image" && msg.type !== "lrpt_progress" && msg.type !== "wefax" && msg.type !== "wefax_progress") {
|
||||
window.trx.modules.map?.statsRecordDecode(msg.type, msg.rig_id || msg.remote || null);
|
||||
window.trx.modules.map?.scheduleStatsRender();
|
||||
@@ -5884,27 +5870,9 @@ function dispatchDecodeBatch(batch) {
|
||||
window.trx.modules.map?.scheduleStatsRender();
|
||||
const type = String(batch[0]?.type || "");
|
||||
const uniformType = batch.every((msg) => String(msg?.type || "") === type);
|
||||
if (uniformType) {
|
||||
if (type === "ais" || type === "vdes" || type === "aprs" || type === "hf_aprs") {
|
||||
window.trxPluginRuntime.dispatchBatch(type, batch);
|
||||
return;
|
||||
}
|
||||
if (type === "ft8" && window.onServerFt8Batch) {
|
||||
window.onServerFt8Batch(batch);
|
||||
return;
|
||||
}
|
||||
if (type === "ft4" && window.onServerFt4Batch) {
|
||||
window.onServerFt4Batch(batch);
|
||||
return;
|
||||
}
|
||||
if (type === "ft2" && window.onServerFt2Batch) {
|
||||
window.onServerFt2Batch(batch);
|
||||
return;
|
||||
}
|
||||
if (type === "wspr" && window.onServerWsprBatch) {
|
||||
window.onServerWsprBatch(batch);
|
||||
return;
|
||||
}
|
||||
if (uniformType && type) {
|
||||
window.trxPluginRuntime.dispatchBatch(type, batch);
|
||||
return;
|
||||
}
|
||||
for (const msg of batch) {
|
||||
dispatchDecodeMessage(msg, true);
|
||||
@@ -5954,34 +5922,7 @@ function restoreDecodeHistoryGroup(kind, messages) {
|
||||
}
|
||||
window.trx.modules.map?.scheduleStatsRender();
|
||||
}
|
||||
if (kind === "ais" || kind === "vdes" || kind === "aprs" || kind === "hf_aprs") {
|
||||
window.trxPluginRuntime.restore(kind, messages);
|
||||
return;
|
||||
}
|
||||
if (kind === "cw" && window.restoreCwHistory) {
|
||||
window.restoreCwHistory(messages);
|
||||
return;
|
||||
}
|
||||
if (kind === "ft8" && window.restoreFt8History) {
|
||||
window.restoreFt8History(messages);
|
||||
return;
|
||||
}
|
||||
if (kind === "ft4" && window.restoreFt4History) {
|
||||
window.restoreFt4History(messages);
|
||||
return;
|
||||
}
|
||||
if (kind === "ft2" && window.restoreFt2History) {
|
||||
window.restoreFt2History(messages);
|
||||
return;
|
||||
}
|
||||
if (kind === "wspr" && window.restoreWsprHistory) {
|
||||
window.restoreWsprHistory(messages);
|
||||
return;
|
||||
}
|
||||
if (kind === "wefax" && window.restoreWefaxHistory) {
|
||||
window.restoreWefaxHistory(messages);
|
||||
return;
|
||||
}
|
||||
window.trxPluginRuntime.restore(kind, messages);
|
||||
}
|
||||
function connectDecode() {
|
||||
if (decodeSource) {
|
||||
@@ -5991,14 +5932,7 @@ function connectDecode() {
|
||||
decodeHistoryReplayActive = false;
|
||||
decodeMapSyncPending = false;
|
||||
window.trxPluginRuntime.clearQueued();
|
||||
window.trxPluginRuntime.reset("ais");
|
||||
window.trxPluginRuntime.reset("vdes");
|
||||
window.trxPluginRuntime.reset("aprs");
|
||||
window.trxPluginRuntime.reset("hf_aprs");
|
||||
if (window.resetCwHistoryView) window.resetCwHistoryView();
|
||||
if (window.resetFt8HistoryView) window.resetFt8HistoryView();
|
||||
if (window.resetFt4HistoryView) window.resetFt4HistoryView();
|
||||
if (window.resetWsprHistoryView) window.resetWsprHistoryView();
|
||||
window.trxPluginRuntime.resetAll();
|
||||
let historySettled = false;
|
||||
let historyWorkerDone = false;
|
||||
let historyFallbackStarted = false;
|
||||
|
||||
@@ -82,7 +82,7 @@
|
||||
}
|
||||
cwWindow.updateCwBar = updateCwBar;
|
||||
cwWindow.clearCwBar = function() {
|
||||
cwWindow.resetCwHistoryView?.();
|
||||
resetCwHistoryView();
|
||||
};
|
||||
cwWindow.closeCwBar = function() {
|
||||
cwBarDismissedAtMs = Date.now();
|
||||
@@ -302,26 +302,26 @@
|
||||
void setCwTone(tone);
|
||||
});
|
||||
}
|
||||
cwWindow.resetCwHistoryView = function() {
|
||||
function resetCwHistoryView() {
|
||||
if (cwOutputEl) cwOutputEl.innerHTML = "";
|
||||
cwLastAppendTime = 0;
|
||||
cwBarHistory = [];
|
||||
cwBarCurrentLine = null;
|
||||
updateCwBar();
|
||||
drawCwTonePicker();
|
||||
};
|
||||
}
|
||||
document.getElementById("settings-clear-cw-history")?.addEventListener("click", () => {
|
||||
void (async () => {
|
||||
if (!await cwWindow.trxUi.confirm({ title: "Clear CW history?", message: "All stored CW decodes will be permanently removed.", confirmLabel: "Clear history" })) return;
|
||||
try {
|
||||
await cwWindow.postPath?.("/clear_cw_decode");
|
||||
cwWindow.resetCwHistoryView?.();
|
||||
resetCwHistoryView();
|
||||
} catch (error) {
|
||||
console.error("CW history clear failed", error);
|
||||
}
|
||||
})();
|
||||
});
|
||||
cwWindow.onServerCw = function(evt) {
|
||||
function onServerCw(evt) {
|
||||
if (cwStatusEl) cwStatusEl.textContent = "Receiving";
|
||||
if (evt.text && cwOutputEl) {
|
||||
const now = Date.now();
|
||||
@@ -375,14 +375,20 @@
|
||||
cwTonePickerRaf = null;
|
||||
drawCwTonePicker();
|
||||
});
|
||||
};
|
||||
cwWindow.restoreCwHistory = function(events) {
|
||||
}
|
||||
function restoreCwHistory(events) {
|
||||
if (!Array.isArray(events) || events.length === 0) return;
|
||||
if (cwStatusEl) cwStatusEl.textContent = "Receiving";
|
||||
for (const evt of events) {
|
||||
cwWindow.onServerCw?.(evt);
|
||||
onServerCw(evt);
|
||||
}
|
||||
};
|
||||
}
|
||||
cwWindow.trxPluginRuntime.registerDecoder({
|
||||
id: "cw",
|
||||
onMessage: onServerCw,
|
||||
restore: restoreCwHistory,
|
||||
reset: resetCwHistoryView
|
||||
});
|
||||
cwWindow.refreshCwTonePicker = function refreshCwTonePicker() {
|
||||
ensureCwToneCanvasResolution();
|
||||
drawCwTonePicker();
|
||||
|
||||
@@ -167,19 +167,19 @@
|
||||
}
|
||||
return { count: recent.length, newestTsMs: recent.reduce((latest, message) => Math.max(latest, finiteNumber(message._tsMs ?? message.ts_ms) ?? 0), 0), html };
|
||||
};
|
||||
const capitalized = `${id[0]?.toUpperCase() ?? ""}${id.slice(1)}`;
|
||||
bridge[`onServer${capitalized}Batch`] = (messages) => {
|
||||
if (Array.isArray(messages)) receiveBatch(messages);
|
||||
};
|
||||
bridge[`restore${capitalized}History`] = receiveBatch;
|
||||
bridge[`prune${capitalized}HistoryView`] = () => {
|
||||
prune();
|
||||
render();
|
||||
};
|
||||
bridge[`reset${capitalized}HistoryView`] = reset;
|
||||
bridge[`onServer${capitalized}`] = (message) => {
|
||||
receiveBatch([message]);
|
||||
};
|
||||
bridge.trxPluginRuntime.registerDecoder({
|
||||
id,
|
||||
onMessage: (message) => {
|
||||
receiveBatch([message]);
|
||||
},
|
||||
onBatch: receiveBatch,
|
||||
restore: receiveBatch,
|
||||
prune: () => {
|
||||
prune();
|
||||
render();
|
||||
},
|
||||
reset
|
||||
});
|
||||
bridge.registerFt8FamilyBarRenderer?.(id, barFrames);
|
||||
const updatePeriod = () => {
|
||||
if (period) period.textContent = `Next slot ${((periodMs - Date.now() % periodMs) / 1e3).toFixed(periodDigits)}s`;
|
||||
|
||||
@@ -167,19 +167,19 @@
|
||||
}
|
||||
return { count: recent.length, newestTsMs: recent.reduce((latest, message) => Math.max(latest, finiteNumber(message._tsMs ?? message.ts_ms) ?? 0), 0), html };
|
||||
};
|
||||
const capitalized = `${id[0]?.toUpperCase() ?? ""}${id.slice(1)}`;
|
||||
bridge[`onServer${capitalized}Batch`] = (messages) => {
|
||||
if (Array.isArray(messages)) receiveBatch(messages);
|
||||
};
|
||||
bridge[`restore${capitalized}History`] = receiveBatch;
|
||||
bridge[`prune${capitalized}HistoryView`] = () => {
|
||||
prune();
|
||||
render();
|
||||
};
|
||||
bridge[`reset${capitalized}HistoryView`] = reset;
|
||||
bridge[`onServer${capitalized}`] = (message) => {
|
||||
receiveBatch([message]);
|
||||
};
|
||||
bridge.trxPluginRuntime.registerDecoder({
|
||||
id,
|
||||
onMessage: (message) => {
|
||||
receiveBatch([message]);
|
||||
},
|
||||
onBatch: receiveBatch,
|
||||
restore: receiveBatch,
|
||||
prune: () => {
|
||||
prune();
|
||||
render();
|
||||
},
|
||||
reset
|
||||
});
|
||||
bridge.registerFt8FamilyBarRenderer?.(id, barFrames);
|
||||
const updatePeriod = () => {
|
||||
if (period) period.textContent = `Next slot ${((periodMs - Date.now() % periodMs) / 1e3).toFixed(periodDigits)}s`;
|
||||
|
||||
@@ -91,7 +91,7 @@
|
||||
};
|
||||
bridge.updateFt8Bar = update;
|
||||
bridge.clearFt8Bar = () => {
|
||||
({ ft8: bridge.resetFt8HistoryView, ft4: bridge.resetFt4HistoryView, ft2: bridge.resetFt2HistoryView })[active]?.();
|
||||
bridge.trxPluginRuntime.reset(active);
|
||||
};
|
||||
bridge.closeFt8Bar = () => {
|
||||
dismissed[active] = Date.now();
|
||||
@@ -209,19 +209,19 @@
|
||||
}
|
||||
return { count: recent.length, newestTsMs: recent.reduce((latest, message) => Math.max(latest, finiteNumber(message._tsMs ?? message.ts_ms) ?? 0), 0), html };
|
||||
};
|
||||
const capitalized = `${id[0]?.toUpperCase() ?? ""}${id.slice(1)}`;
|
||||
bridge[`onServer${capitalized}Batch`] = (messages) => {
|
||||
if (Array.isArray(messages)) receiveBatch(messages);
|
||||
};
|
||||
bridge[`restore${capitalized}History`] = receiveBatch;
|
||||
bridge[`prune${capitalized}HistoryView`] = () => {
|
||||
prune();
|
||||
render();
|
||||
};
|
||||
bridge[`reset${capitalized}HistoryView`] = reset;
|
||||
bridge[`onServer${capitalized}`] = (message) => {
|
||||
receiveBatch([message]);
|
||||
};
|
||||
bridge.trxPluginRuntime.registerDecoder({
|
||||
id,
|
||||
onMessage: (message) => {
|
||||
receiveBatch([message]);
|
||||
},
|
||||
onBatch: receiveBatch,
|
||||
restore: receiveBatch,
|
||||
prune: () => {
|
||||
prune();
|
||||
render();
|
||||
},
|
||||
reset
|
||||
});
|
||||
bridge.registerFt8FamilyBarRenderer?.(id, barFrames);
|
||||
const updatePeriod = () => {
|
||||
if (period) period.textContent = `Next slot ${((periodMs - Date.now() % periodMs) / 1e3).toFixed(periodDigits)}s`;
|
||||
|
||||
@@ -207,29 +207,39 @@
|
||||
});
|
||||
}
|
||||
}
|
||||
satWindow.onServerLrptProgress = function(msg) {
|
||||
function onServerLrptProgress(msg) {
|
||||
if (satDom.status && (msg.mcu_count ?? 0) > 0) {
|
||||
satDom.status.textContent = `Receiving — ${String(msg.mcu_count ?? 0)} MCU rows decoded`;
|
||||
}
|
||||
};
|
||||
satWindow.onServerLrptImage = function(msg) {
|
||||
}
|
||||
function onServerLrptImage(msg) {
|
||||
if (satDom.status) satDom.status.textContent = "Image received (Meteor LRPT)";
|
||||
addSatImage(msg, "lrpt");
|
||||
if (msg.geo_bounds && msg.path && satWindow.addSatMapOverlay) {
|
||||
satWindow.addSatMapOverlay(msg);
|
||||
}
|
||||
};
|
||||
satWindow.resetSatHistoryView = function() {
|
||||
}
|
||||
function resetSatHistoryView() {
|
||||
satImageHistory = [];
|
||||
if (satDom.historyList) satDom.historyList.innerHTML = "";
|
||||
renderSatLatestCard();
|
||||
renderSatHistoryTable();
|
||||
satWindow.clearSatMapOverlays?.();
|
||||
};
|
||||
satWindow.pruneSatHistoryView = function() {
|
||||
}
|
||||
function pruneSatHistoryView() {
|
||||
renderSatHistoryTable();
|
||||
renderSatLatestCard();
|
||||
};
|
||||
}
|
||||
satWindow.trxPluginRuntime.registerDecoder({
|
||||
id: "lrpt_image",
|
||||
onMessage: onServerLrptImage,
|
||||
reset: resetSatHistoryView,
|
||||
prune: pruneSatHistoryView
|
||||
});
|
||||
satWindow.trxPluginRuntime.registerDecoder({
|
||||
id: "lrpt_progress",
|
||||
onMessage: onServerLrptProgress
|
||||
});
|
||||
var lrptDecodeToggleBtn = document.getElementById("lrpt-decode-toggle-btn");
|
||||
lrptDecodeToggleBtn?.addEventListener("click", () => {
|
||||
void (async () => {
|
||||
@@ -257,7 +267,7 @@
|
||||
if (!await satWindow.trxUi.confirm({ title: "Clear satellite history?", message: "All stored satellite decodes will be permanently removed.", confirmLabel: "Clear history" })) return;
|
||||
try {
|
||||
await satWindow.postPath?.("/clear_lrpt_decode");
|
||||
satWindow.resetSatHistoryView?.();
|
||||
resetSatHistoryView();
|
||||
} catch (e) {
|
||||
console.error("Weather satellite history clear failed", e);
|
||||
}
|
||||
|
||||
@@ -205,7 +205,7 @@
|
||||
scheduleWefaxUi("wefax-history", renderWefaxHistoryTable);
|
||||
}
|
||||
}
|
||||
wefaxWindow.onServerWefaxProgress = function(msg) {
|
||||
function onServerWefaxProgress(msg) {
|
||||
if (msg.state && !msg.line_data) {
|
||||
if (wefaxDom.status) {
|
||||
wefaxDom.status.textContent = msg.state;
|
||||
@@ -229,16 +229,16 @@
|
||||
wefaxDom.status.textContent = `Receiving — line ${String(msg.line_count ?? 0)}`;
|
||||
wefaxDom.status.style.color = "var(--text-accent)";
|
||||
}
|
||||
};
|
||||
wefaxWindow.onServerWefax = function(msg) {
|
||||
}
|
||||
function onServerWefax(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) {
|
||||
}
|
||||
function restoreWefaxHistory(messages) {
|
||||
if (!messages.length) return;
|
||||
for (const message of messages) {
|
||||
const tsMs = Number.isFinite(message.ts_ms) ? Number(message.ts_ms) : Date.now();
|
||||
@@ -255,13 +255,13 @@
|
||||
if (wefaxActiveView === "history") {
|
||||
scheduleWefaxUi("wefax-history", renderWefaxHistoryTable);
|
||||
}
|
||||
};
|
||||
wefaxWindow.pruneWefaxHistoryView = function() {
|
||||
}
|
||||
function pruneWefaxHistoryView() {
|
||||
pruneWefaxHistory();
|
||||
renderWefaxHistoryTable();
|
||||
renderWefaxLatestCard();
|
||||
};
|
||||
wefaxWindow.resetWefaxHistoryView = function() {
|
||||
}
|
||||
function resetWefaxHistoryView() {
|
||||
wefaxImageHistory = [];
|
||||
if (wefaxDom.historyList) wefaxDom.historyList.innerHTML = "";
|
||||
if (wefaxDom.liveContainer) wefaxDom.liveContainer.style.display = "none";
|
||||
@@ -273,7 +273,7 @@
|
||||
wefaxDom.status.textContent = "Idle";
|
||||
wefaxDom.status.style.color = "";
|
||||
}
|
||||
};
|
||||
}
|
||||
if (wefaxDom.filterInput) {
|
||||
const filterInput = wefaxDom.filterInput;
|
||||
wefaxDom.filterInput.addEventListener("input", function() {
|
||||
@@ -313,7 +313,7 @@
|
||||
void (async () => {
|
||||
try {
|
||||
await wefaxWindow.postPath?.("/clear_wefax_decode");
|
||||
wefaxWindow.resetWefaxHistoryView?.();
|
||||
resetWefaxHistoryView();
|
||||
} catch (e) {
|
||||
console.error("WEFAX clear failed", e);
|
||||
}
|
||||
@@ -321,4 +321,15 @@
|
||||
});
|
||||
}
|
||||
renderWefaxLatestCard();
|
||||
wefaxWindow.trxPluginRuntime.registerDecoder({
|
||||
id: "wefax",
|
||||
onMessage: onServerWefax,
|
||||
restore: restoreWefaxHistory,
|
||||
prune: pruneWefaxHistoryView,
|
||||
reset: resetWefaxHistoryView
|
||||
});
|
||||
wefaxWindow.trxPluginRuntime.registerDecoder({
|
||||
id: "wefax_progress",
|
||||
onMessage: onServerWefaxProgress
|
||||
});
|
||||
})();
|
||||
|
||||
@@ -97,7 +97,7 @@
|
||||
}
|
||||
};
|
||||
}
|
||||
wsprWindow.onServerWsprBatch = function(messages) {
|
||||
function onServerWsprBatch(messages) {
|
||||
if (!Array.isArray(messages) || messages.length === 0) return;
|
||||
if (wsprStatus) wsprStatus.textContent = "Receiving";
|
||||
const normalized = [];
|
||||
@@ -116,15 +116,11 @@
|
||||
wsprMessageHistory = normalized.concat(wsprMessageHistory);
|
||||
pruneWsprMessageHistory();
|
||||
scheduleWsprHistoryRender();
|
||||
};
|
||||
wsprWindow.restoreWsprHistory = function(messages) {
|
||||
const callback = wsprWindow.onServerWsprBatch;
|
||||
if (typeof callback === "function") callback(messages);
|
||||
};
|
||||
wsprWindow.pruneWsprHistoryView = function() {
|
||||
}
|
||||
function pruneWsprHistoryView() {
|
||||
pruneWsprMessageHistory();
|
||||
renderWsprHistory();
|
||||
};
|
||||
}
|
||||
function escapeWsprHtml(input) {
|
||||
return input.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """);
|
||||
}
|
||||
@@ -205,12 +201,12 @@
|
||||
const message = row.dataset.message || "";
|
||||
row.style.display = message.includes(wsprFilterText) ? "" : "none";
|
||||
}
|
||||
wsprWindow.resetWsprHistoryView = function() {
|
||||
function resetWsprHistoryView() {
|
||||
if (wsprMessagesEl) wsprMessagesEl.innerHTML = "";
|
||||
wsprMessageHistory = [];
|
||||
renderWsprHistory();
|
||||
if (wsprWindow.clearMapMarkersByType) wsprWindow.clearMapMarkersByType("wspr");
|
||||
};
|
||||
}
|
||||
if (wsprFilterInput) {
|
||||
wsprFilterInput.addEventListener("input", () => {
|
||||
wsprFilterText = wsprFilterInput.value.trim().toUpperCase();
|
||||
@@ -246,13 +242,13 @@
|
||||
if (!await wsprWindow.trxUi.confirm({ title: "Clear WSPR history?", message: "All stored WSPR decodes will be permanently removed.", confirmLabel: "Clear history" })) return;
|
||||
try {
|
||||
await wsprWindow.postPath?.("/clear_wspr_decode");
|
||||
wsprWindow.resetWsprHistoryView?.();
|
||||
resetWsprHistoryView();
|
||||
} catch (error) {
|
||||
console.error("WSPR history clear failed", error);
|
||||
}
|
||||
})();
|
||||
});
|
||||
wsprWindow.onServerWspr = function(msg) {
|
||||
function onServerWspr(msg) {
|
||||
if (wsprStatus) wsprStatus.textContent = "Receiving";
|
||||
const next = normalizeServerWsprMessage(msg);
|
||||
if (next.grids.length > 0 && wsprWindow.mapAddLocator) {
|
||||
@@ -262,5 +258,13 @@
|
||||
});
|
||||
}
|
||||
addWsprMessage(next.history);
|
||||
};
|
||||
}
|
||||
wsprWindow.trxPluginRuntime.registerDecoder({
|
||||
id: "wspr",
|
||||
onMessage: onServerWspr,
|
||||
onBatch: onServerWsprBatch,
|
||||
restore: onServerWsprBatch,
|
||||
prune: pruneWsprHistoryView,
|
||||
reset: resetWsprHistoryView
|
||||
});
|
||||
})();
|
||||
|
||||
@@ -567,12 +567,9 @@ function currentDecodeHistoryRetentionMs() {
|
||||
window.getDecodeHistoryRetentionMs = currentDecodeHistoryRetentionMs;
|
||||
|
||||
window.applyDecodeHistoryRetention = function() {
|
||||
window.trxPluginRuntime.prune("aprs");
|
||||
window.trxPluginRuntime.prune("hf_aprs");
|
||||
window.trxPluginRuntime.prune("ais");
|
||||
window.trxPluginRuntime.prune("vdes");
|
||||
if (typeof window.pruneFt8HistoryView === "function") window.pruneFt8HistoryView();
|
||||
if (typeof window.pruneWsprHistoryView === "function") window.pruneWsprHistoryView();
|
||||
for (const decoder of ["aprs", "hf_aprs", "ais", "vdes", "ft8", "ft4", "ft2", "wspr", "wefax"]) {
|
||||
window.trxPluginRuntime.prune(decoder);
|
||||
}
|
||||
};
|
||||
|
||||
function syncTopBarAccess() {
|
||||
@@ -6215,18 +6212,7 @@ function updateDecodeStatus(text) {
|
||||
}
|
||||
}
|
||||
function dispatchDecodeMessage(msg, skipStats) {
|
||||
if (msg.type === "ais" || msg.type === "vdes" || msg.type === "aprs" || msg.type === "hf_aprs") {
|
||||
window.trxPluginRuntime.dispatch(msg.type, msg);
|
||||
}
|
||||
if (msg.type === "cw" && window.onServerCw) window.onServerCw(msg);
|
||||
if (msg.type === "ft8" && window.onServerFt8) window.onServerFt8(msg);
|
||||
if (msg.type === "ft4" && window.onServerFt4) window.onServerFt4(msg);
|
||||
if (msg.type === "ft2" && window.onServerFt2) window.onServerFt2(msg);
|
||||
if (msg.type === "wspr" && window.onServerWspr) window.onServerWspr(msg);
|
||||
if (msg.type === "lrpt_image" && window.onServerLrptImage) window.onServerLrptImage(msg);
|
||||
if (msg.type === "lrpt_progress" && window.onServerLrptProgress) window.onServerLrptProgress(msg);
|
||||
if (msg.type === "wefax" && window.onServerWefax) window.onServerWefax(msg);
|
||||
if (msg.type === "wefax_progress" && window.onServerWefaxProgress) window.onServerWefaxProgress(msg);
|
||||
if (msg.type) window.trxPluginRuntime.dispatch(msg.type, msg);
|
||||
if (!skipStats && msg.type && msg.type !== "lrpt_image" && msg.type !== "lrpt_progress" && msg.type !== "wefax" && msg.type !== "wefax_progress") {
|
||||
window.trx.modules.map?.statsRecordDecode(msg.type, msg.rig_id || msg.remote || null);
|
||||
window.trx.modules.map?.scheduleStatsRender();
|
||||
@@ -6244,27 +6230,9 @@ function dispatchDecodeBatch(batch) {
|
||||
window.trx.modules.map?.scheduleStatsRender();
|
||||
const type = String(batch[0]?.type || "");
|
||||
const uniformType = batch.every((msg) => String(msg?.type || "") === type);
|
||||
if (uniformType) {
|
||||
if (type === "ais" || type === "vdes" || type === "aprs" || type === "hf_aprs") {
|
||||
window.trxPluginRuntime.dispatchBatch(type, batch);
|
||||
return;
|
||||
}
|
||||
if (type === "ft8" && window.onServerFt8Batch) {
|
||||
window.onServerFt8Batch(batch);
|
||||
return;
|
||||
}
|
||||
if (type === "ft4" && window.onServerFt4Batch) {
|
||||
window.onServerFt4Batch(batch);
|
||||
return;
|
||||
}
|
||||
if (type === "ft2" && window.onServerFt2Batch) {
|
||||
window.onServerFt2Batch(batch);
|
||||
return;
|
||||
}
|
||||
if (type === "wspr" && window.onServerWsprBatch) {
|
||||
window.onServerWsprBatch(batch);
|
||||
return;
|
||||
}
|
||||
if (uniformType && type) {
|
||||
window.trxPluginRuntime.dispatchBatch(type, batch);
|
||||
return;
|
||||
}
|
||||
for (const msg of batch) {
|
||||
dispatchDecodeMessage(msg, true);
|
||||
@@ -6318,34 +6286,7 @@ function restoreDecodeHistoryGroup(kind, messages) {
|
||||
}
|
||||
window.trx.modules.map?.scheduleStatsRender();
|
||||
}
|
||||
if (kind === "ais" || kind === "vdes" || kind === "aprs" || kind === "hf_aprs") {
|
||||
window.trxPluginRuntime.restore(kind, messages);
|
||||
return;
|
||||
}
|
||||
if (kind === "cw" && window.restoreCwHistory) {
|
||||
window.restoreCwHistory(messages);
|
||||
return;
|
||||
}
|
||||
if (kind === "ft8" && window.restoreFt8History) {
|
||||
window.restoreFt8History(messages);
|
||||
return;
|
||||
}
|
||||
if (kind === "ft4" && window.restoreFt4History) {
|
||||
window.restoreFt4History(messages);
|
||||
return;
|
||||
}
|
||||
if (kind === "ft2" && window.restoreFt2History) {
|
||||
window.restoreFt2History(messages);
|
||||
return;
|
||||
}
|
||||
if (kind === "wspr" && window.restoreWsprHistory) {
|
||||
window.restoreWsprHistory(messages);
|
||||
return;
|
||||
}
|
||||
if (kind === "wefax" && window.restoreWefaxHistory) {
|
||||
window.restoreWefaxHistory(messages);
|
||||
return;
|
||||
}
|
||||
window.trxPluginRuntime.restore(kind, messages);
|
||||
}
|
||||
|
||||
function connectDecode() {
|
||||
@@ -6354,14 +6295,7 @@ function connectDecode() {
|
||||
decodeHistoryReplayActive = false;
|
||||
decodeMapSyncPending = false;
|
||||
window.trxPluginRuntime.clearQueued();
|
||||
window.trxPluginRuntime.reset("ais");
|
||||
window.trxPluginRuntime.reset("vdes");
|
||||
window.trxPluginRuntime.reset("aprs");
|
||||
window.trxPluginRuntime.reset("hf_aprs");
|
||||
if (window.resetCwHistoryView) window.resetCwHistoryView();
|
||||
if (window.resetFt8HistoryView) window.resetFt8HistoryView();
|
||||
if (window.resetFt4HistoryView) window.resetFt4HistoryView();
|
||||
if (window.resetWsprHistoryView) window.resetWsprHistoryView();
|
||||
window.trxPluginRuntime.resetAll();
|
||||
|
||||
// Buffer live messages until history fetch settles so history always appears
|
||||
// before any live updates, regardless of network ordering.
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
import type { PluginRuntimeWindow } from "./runtime-contract.js";
|
||||
|
||||
export {};
|
||||
|
||||
type Rgba = [number, number, number, number];
|
||||
@@ -36,12 +38,9 @@ interface CwBridge {
|
||||
updateCwBar?: () => void;
|
||||
clearCwBar?: () => void;
|
||||
closeCwBar?: () => void;
|
||||
resetCwHistoryView?: () => void;
|
||||
onServerCw?: (event: CwEvent) => void;
|
||||
restoreCwHistory?: (events: CwEvent[]) => void;
|
||||
refreshCwTonePicker?: () => void;
|
||||
}
|
||||
const cwWindow = window as unknown as CwBridge;
|
||||
const cwWindow = window as unknown as CwBridge & PluginRuntimeWindow;
|
||||
|
||||
// --- CW (Morse) Decoder Plugin (server-side decode) ---
|
||||
const cwStatusEl = document.getElementById("cw-status");
|
||||
@@ -154,7 +153,7 @@ function updateCwBar(): void {
|
||||
}
|
||||
cwWindow.updateCwBar = updateCwBar;
|
||||
cwWindow.clearCwBar = function() {
|
||||
cwWindow.resetCwHistoryView?.();
|
||||
resetCwHistoryView();
|
||||
};
|
||||
cwWindow.closeCwBar = function() {
|
||||
cwBarDismissedAtMs = Date.now();
|
||||
@@ -401,21 +400,21 @@ if (cwToneCanvas) {
|
||||
});
|
||||
}
|
||||
|
||||
cwWindow.resetCwHistoryView = function() {
|
||||
function resetCwHistoryView(): void {
|
||||
if (cwOutputEl) cwOutputEl.innerHTML = "";
|
||||
cwLastAppendTime = 0;
|
||||
cwBarHistory = [];
|
||||
cwBarCurrentLine = null;
|
||||
updateCwBar();
|
||||
drawCwTonePicker();
|
||||
};
|
||||
}
|
||||
|
||||
document.getElementById("settings-clear-cw-history")?.addEventListener("click", () => {
|
||||
void (async () => {
|
||||
if (!await cwWindow.trxUi.confirm({ title: "Clear CW history?", message: "All stored CW decodes will be permanently removed.", confirmLabel: "Clear history" })) return;
|
||||
try {
|
||||
await cwWindow.postPath?.("/clear_cw_decode");
|
||||
cwWindow.resetCwHistoryView?.();
|
||||
resetCwHistoryView();
|
||||
} catch (error: unknown) {
|
||||
console.error("CW history clear failed", error);
|
||||
}
|
||||
@@ -423,7 +422,7 @@ document.getElementById("settings-clear-cw-history")?.addEventListener("click",
|
||||
});
|
||||
|
||||
// --- Server-side CW decode handler ---
|
||||
cwWindow.onServerCw = function(evt: CwEvent) {
|
||||
function onServerCw(evt: CwEvent): void {
|
||||
if (cwStatusEl) cwStatusEl.textContent = "Receiving";
|
||||
if (evt.text && cwOutputEl) {
|
||||
// Append decoded text to output
|
||||
@@ -479,15 +478,22 @@ cwWindow.onServerCw = function(evt: CwEvent) {
|
||||
cwTonePickerRaf = null;
|
||||
drawCwTonePicker();
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
cwWindow.restoreCwHistory = function(events: CwEvent[]) {
|
||||
function restoreCwHistory(events: CwEvent[]): void {
|
||||
if (!Array.isArray(events) || events.length === 0) return;
|
||||
if (cwStatusEl) cwStatusEl.textContent = "Receiving";
|
||||
for (const evt of events) {
|
||||
cwWindow.onServerCw?.(evt);
|
||||
onServerCw(evt);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
cwWindow.trxPluginRuntime.registerDecoder({
|
||||
id: "cw",
|
||||
onMessage: onServerCw,
|
||||
restore: restoreCwHistory,
|
||||
reset: resetCwHistoryView,
|
||||
});
|
||||
|
||||
cwWindow.refreshCwTonePicker = function refreshCwTonePicker() {
|
||||
ensureCwToneCanvasResolution();
|
||||
|
||||
@@ -2,9 +2,11 @@
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
import type { PluginRuntimeWindow } from "./runtime-contract.js";
|
||||
|
||||
export type FtxDecoderId = "ft2" | "ft4" | "ft8";
|
||||
|
||||
interface FtxMessage {
|
||||
export interface FtxMessage {
|
||||
message?: string | undefined;
|
||||
ts_ms?: number | undefined;
|
||||
_tsMs?: number | undefined;
|
||||
@@ -46,9 +48,6 @@ interface FtxBridge {
|
||||
postPath?: (path: string) => Promise<unknown>;
|
||||
fmtTime?: (timestampMs: number) => string;
|
||||
trxUi: ConfirmApi;
|
||||
resetFt2HistoryView?: () => void;
|
||||
resetFt4HistoryView?: () => void;
|
||||
resetFt8HistoryView?: () => void;
|
||||
clearFt8Bar?: () => void;
|
||||
closeFt8Bar?: () => void;
|
||||
[key: string]: unknown;
|
||||
@@ -61,7 +60,7 @@ interface FtxConfig {
|
||||
periodDigits?: number;
|
||||
}
|
||||
|
||||
const bridge = window as unknown as FtxBridge;
|
||||
const bridge = window as unknown as FtxBridge & PluginRuntimeWindow;
|
||||
|
||||
function finiteNumber(value: unknown): number | null {
|
||||
const number = typeof value === "number" ? value : Number(value);
|
||||
@@ -164,7 +163,7 @@ export function initializeFt8FamilyBar(): void {
|
||||
bridge.setFt8FamilyBarDecoder = (decoder) => { active = decoder; update(); };
|
||||
bridge.updateFt8Bar = update;
|
||||
bridge.clearFt8Bar = () => {
|
||||
({ ft8: bridge.resetFt8HistoryView, ft4: bridge.resetFt4HistoryView, ft2: bridge.resetFt2HistoryView })[active]?.();
|
||||
bridge.trxPluginRuntime.reset(active);
|
||||
};
|
||||
bridge.closeFt8Bar = () => {
|
||||
dismissed[active] = Date.now();
|
||||
@@ -285,12 +284,14 @@ export function initializeFtxDecoder(config: FtxConfig): void {
|
||||
return { count: recent.length, newestTsMs: recent.reduce((latest, message) => Math.max(latest, finiteNumber(message._tsMs ?? message.ts_ms) ?? 0), 0), html };
|
||||
};
|
||||
|
||||
const capitalized = `${id[0]?.toUpperCase() ?? ""}${id.slice(1)}`;
|
||||
bridge[`onServer${capitalized}Batch`] = (messages: FtxMessage[]) => { if (Array.isArray(messages)) receiveBatch(messages); };
|
||||
bridge[`restore${capitalized}History`] = receiveBatch;
|
||||
bridge[`prune${capitalized}HistoryView`] = () => { prune(); render(); };
|
||||
bridge[`reset${capitalized}HistoryView`] = reset;
|
||||
bridge[`onServer${capitalized}`] = (message: FtxMessage) => { receiveBatch([message]); };
|
||||
bridge.trxPluginRuntime.registerDecoder({
|
||||
id,
|
||||
onMessage: (message: FtxMessage) => { receiveBatch([message]); },
|
||||
onBatch: receiveBatch,
|
||||
restore: receiveBatch,
|
||||
prune: () => { prune(); render(); },
|
||||
reset,
|
||||
});
|
||||
bridge.registerFt8FamilyBarRenderer?.(id, barFrames);
|
||||
|
||||
const updatePeriod = (): void => {
|
||||
|
||||
@@ -3,17 +3,14 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
import type { LrptProgress, SatelliteImage, SatelliteLiveUpdate, SatellitePass, SatellitePassResponse } from "./satellite-types";
|
||||
import type { PluginRuntimeWindow } from "./runtime-contract.js";
|
||||
|
||||
type SatelliteView = "live" | "history" | "predictions";
|
||||
interface SatelliteBridge {
|
||||
trxScheduleUiFrameJob?: (key: string, job: () => void) => void;
|
||||
clearSatPredictionDom?: () => void;
|
||||
updateSatLiveState?: (update: SatelliteLiveUpdate) => void;
|
||||
onServerLrptProgress?: (message: LrptProgress) => void;
|
||||
onServerLrptImage?: (message: SatelliteImage) => void;
|
||||
addSatMapOverlay?: (image: SatelliteImage) => void;
|
||||
resetSatHistoryView?: () => void;
|
||||
pruneSatHistoryView?: () => void;
|
||||
clearSatMapOverlays?: () => void;
|
||||
takeSchedulerControlForDecoderDisable?: (button: HTMLElement) => Promise<unknown>;
|
||||
postPath?: (path: string) => Promise<unknown>;
|
||||
@@ -22,7 +19,7 @@ interface SatelliteBridge {
|
||||
enableMapSourceFilter?: (source: string) => void;
|
||||
navigateToAprsMap?: (lat: number, lon: number) => void;
|
||||
}
|
||||
const satWindow = window as unknown as SatelliteBridge;
|
||||
const satWindow = window as unknown as SatelliteBridge & PluginRuntimeWindow;
|
||||
|
||||
// --- SAT Plugin ---
|
||||
// Live view: decoder state, latest image card
|
||||
@@ -265,32 +262,43 @@ function addSatImage(img: SatelliteImage, decoder: "lrpt"): void {
|
||||
}
|
||||
|
||||
// ── Server callbacks ────────────────────────────────────────────────
|
||||
satWindow.onServerLrptProgress = function (msg: LrptProgress) {
|
||||
function onServerLrptProgress(msg: LrptProgress): void {
|
||||
if (satDom.status && (msg.mcu_count ?? 0) > 0) {
|
||||
satDom.status.textContent = `Receiving \u2014 ${String(msg.mcu_count ?? 0)} MCU rows decoded`;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
satWindow.onServerLrptImage = function (msg: SatelliteImage) {
|
||||
function onServerLrptImage(msg: SatelliteImage): void {
|
||||
if (satDom.status) satDom.status.textContent = "Image received (Meteor LRPT)";
|
||||
addSatImage(msg, "lrpt");
|
||||
if (msg.geo_bounds && msg.path && satWindow.addSatMapOverlay) {
|
||||
satWindow.addSatMapOverlay(msg);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
satWindow.resetSatHistoryView = function () {
|
||||
function resetSatHistoryView(): void {
|
||||
satImageHistory = [];
|
||||
if (satDom.historyList) satDom.historyList.innerHTML = "";
|
||||
renderSatLatestCard();
|
||||
renderSatHistoryTable();
|
||||
satWindow.clearSatMapOverlays?.();
|
||||
};
|
||||
}
|
||||
|
||||
satWindow.pruneSatHistoryView = function () {
|
||||
function pruneSatHistoryView(): void {
|
||||
renderSatHistoryTable();
|
||||
renderSatLatestCard();
|
||||
};
|
||||
}
|
||||
|
||||
satWindow.trxPluginRuntime.registerDecoder({
|
||||
id: "lrpt_image",
|
||||
onMessage: onServerLrptImage,
|
||||
reset: resetSatHistoryView,
|
||||
prune: pruneSatHistoryView,
|
||||
});
|
||||
satWindow.trxPluginRuntime.registerDecoder({
|
||||
id: "lrpt_progress",
|
||||
onMessage: onServerLrptProgress,
|
||||
});
|
||||
|
||||
// ── Toggle buttons ──────────────────────────────────────────────────
|
||||
const lrptDecodeToggleBtn = document.getElementById("lrpt-decode-toggle-btn");
|
||||
@@ -320,7 +328,7 @@ document
|
||||
if (!await satWindow.trxUi.confirm({ title: "Clear satellite history?", message: "All stored satellite decodes will be permanently removed.", confirmLabel: "Clear history" })) return;
|
||||
try {
|
||||
await satWindow.postPath?.("/clear_lrpt_decode");
|
||||
satWindow.resetSatHistoryView?.();
|
||||
resetSatHistoryView();
|
||||
} catch (e) {
|
||||
console.error("Weather satellite history clear failed", e);
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
import type { PluginRuntimeWindow } from "./runtime-contract.js";
|
||||
|
||||
export {};
|
||||
|
||||
interface WefaxImage {
|
||||
@@ -29,15 +31,10 @@ interface WefaxBridge {
|
||||
trxScheduleUiFrameJob?: (key: string, job: () => void) => void;
|
||||
postPath?: (path: string) => Promise<unknown>;
|
||||
takeSchedulerControlForDecoderDisable?: (button: HTMLElement) => Promise<unknown>;
|
||||
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;
|
||||
const wefaxWindow = window as unknown as WefaxBridge & PluginRuntimeWindow;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// wefax.js — WEFAX decoder plugin for trx-frontend-http
|
||||
@@ -299,7 +296,7 @@ function addWefaxImage(msg: WefaxImage): void {
|
||||
}
|
||||
|
||||
// ── SSE event handlers (public API) ─────────────────────────────────
|
||||
wefaxWindow.onServerWefaxProgress = function (msg: WefaxProgress) {
|
||||
function onServerWefaxProgress(msg: WefaxProgress): void {
|
||||
// State-only update (no image data): show decoder state in status.
|
||||
if (msg.state && !msg.line_data) {
|
||||
if (wefaxDom.status) {
|
||||
@@ -329,9 +326,9 @@ wefaxWindow.onServerWefaxProgress = function (msg: WefaxProgress) {
|
||||
wefaxDom.status.textContent = `Receiving \u2014 line ${String(msg.line_count ?? 0)}`;
|
||||
wefaxDom.status.style.color = 'var(--text-accent)';
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
wefaxWindow.onServerWefax = function (msg: WefaxImage) {
|
||||
function onServerWefax(msg: WefaxImage): void {
|
||||
addWefaxImage(msg);
|
||||
|
||||
if (wefaxDom.liveContainer) wefaxDom.liveContainer.style.display = 'none';
|
||||
@@ -339,9 +336,9 @@ wefaxWindow.onServerWefax = function (msg: WefaxImage) {
|
||||
wefaxDom.status.textContent = `Complete \u2014 ${String(msg.line_count ?? 0)} lines`;
|
||||
wefaxDom.status.style.color = '';
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
wefaxWindow.restoreWefaxHistory = function (messages: WefaxImage[]) {
|
||||
function restoreWefaxHistory(messages: WefaxImage[]): void {
|
||||
if (!messages.length) return;
|
||||
for (const message of messages) {
|
||||
const tsMs = Number.isFinite(message.ts_ms) ? Number(message.ts_ms) : Date.now();
|
||||
@@ -358,15 +355,15 @@ wefaxWindow.restoreWefaxHistory = function (messages: WefaxImage[]) {
|
||||
if (wefaxActiveView === 'history') {
|
||||
scheduleWefaxUi('wefax-history', renderWefaxHistoryTable);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
wefaxWindow.pruneWefaxHistoryView = function () {
|
||||
function pruneWefaxHistoryView(): void {
|
||||
pruneWefaxHistory();
|
||||
renderWefaxHistoryTable();
|
||||
renderWefaxLatestCard();
|
||||
};
|
||||
}
|
||||
|
||||
wefaxWindow.resetWefaxHistoryView = function () {
|
||||
function resetWefaxHistoryView(): void {
|
||||
wefaxImageHistory = [];
|
||||
if (wefaxDom.historyList) wefaxDom.historyList.innerHTML = '';
|
||||
if (wefaxDom.liveContainer) wefaxDom.liveContainer.style.display = 'none';
|
||||
@@ -378,7 +375,7 @@ wefaxWindow.resetWefaxHistoryView = function () {
|
||||
wefaxDom.status.textContent = 'Idle';
|
||||
wefaxDom.status.style.color = '';
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// ── Filter / sort handlers ──────────────────────────────────────────
|
||||
if (wefaxDom.filterInput) {
|
||||
@@ -424,7 +421,7 @@ if (wefaxDom.clearBtn) {
|
||||
wefaxDom.clearBtn.addEventListener('click', () => { void (async () => {
|
||||
try {
|
||||
await wefaxWindow.postPath?.('/clear_wefax_decode');
|
||||
wefaxWindow.resetWefaxHistoryView?.();
|
||||
resetWefaxHistoryView();
|
||||
} catch (e) {
|
||||
console.error('WEFAX clear failed', e);
|
||||
}
|
||||
@@ -433,3 +430,15 @@ if (wefaxDom.clearBtn) {
|
||||
|
||||
// ── Initial render ──────────────────────────────────────────────────
|
||||
renderWefaxLatestCard();
|
||||
|
||||
wefaxWindow.trxPluginRuntime.registerDecoder({
|
||||
id: "wefax",
|
||||
onMessage: onServerWefax,
|
||||
restore: restoreWefaxHistory,
|
||||
prune: pruneWefaxHistoryView,
|
||||
reset: resetWefaxHistoryView,
|
||||
});
|
||||
wefaxWindow.trxPluginRuntime.registerDecoder({
|
||||
id: "wefax_progress",
|
||||
onMessage: onServerWefaxProgress,
|
||||
});
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
import type { PluginRuntimeWindow } from "./runtime-contract.js";
|
||||
|
||||
export {};
|
||||
|
||||
interface WsprMessage {
|
||||
@@ -25,14 +27,9 @@ interface WsprBridge {
|
||||
takeSchedulerControlForDecoderDisable?: (button: HTMLElement) => Promise<void>;
|
||||
postPath?: (path: string) => Promise<unknown>;
|
||||
trxUi: { confirm(options: { title: string; message: string; confirmLabel: string }): Promise<boolean> };
|
||||
onServerWsprBatch?: (messages: WsprMessage[]) => void;
|
||||
restoreWsprHistory?: (messages: WsprMessage[]) => void;
|
||||
pruneWsprHistoryView?: () => void;
|
||||
resetWsprHistoryView?: () => void;
|
||||
onServerWspr?: (message: WsprMessage) => void;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
const wsprWindow = window as unknown as WsprBridge;
|
||||
const wsprWindow = window as unknown as WsprBridge & PluginRuntimeWindow;
|
||||
|
||||
// --- WSPR Decoder Plugin (server-side decode) ---
|
||||
const wsprStatus = document.getElementById("wspr-status");
|
||||
@@ -142,7 +139,7 @@ function normalizeServerWsprMessage(msg: WsprMessage): { raw: string; grids: str
|
||||
};
|
||||
}
|
||||
|
||||
wsprWindow.onServerWsprBatch = function(messages: WsprMessage[]) {
|
||||
function onServerWsprBatch(messages: WsprMessage[]): void {
|
||||
if (!Array.isArray(messages) || messages.length === 0) return;
|
||||
if (wsprStatus) wsprStatus.textContent = "Receiving";
|
||||
const normalized: WsprMessage[] = [];
|
||||
@@ -161,17 +158,12 @@ wsprWindow.onServerWsprBatch = function(messages: WsprMessage[]) {
|
||||
wsprMessageHistory = normalized.concat(wsprMessageHistory);
|
||||
pruneWsprMessageHistory();
|
||||
scheduleWsprHistoryRender();
|
||||
};
|
||||
}
|
||||
|
||||
wsprWindow.restoreWsprHistory = function(messages: WsprMessage[]) {
|
||||
const callback = wsprWindow.onServerWsprBatch;
|
||||
if (typeof callback === "function") callback(messages);
|
||||
};
|
||||
|
||||
wsprWindow.pruneWsprHistoryView = function() {
|
||||
function pruneWsprHistoryView(): void {
|
||||
pruneWsprMessageHistory();
|
||||
renderWsprHistory();
|
||||
};
|
||||
}
|
||||
|
||||
function escapeWsprHtml(input: string): string {
|
||||
return input
|
||||
@@ -266,12 +258,12 @@ function applyWsprFilterToRow(row: HTMLElement): void {
|
||||
row.style.display = message.includes(wsprFilterText) ? "" : "none";
|
||||
}
|
||||
|
||||
wsprWindow.resetWsprHistoryView = function() {
|
||||
function resetWsprHistoryView(): void {
|
||||
if (wsprMessagesEl) wsprMessagesEl.innerHTML = "";
|
||||
wsprMessageHistory = [];
|
||||
renderWsprHistory();
|
||||
if (wsprWindow.clearMapMarkersByType) wsprWindow.clearMapMarkersByType("wspr");
|
||||
};
|
||||
}
|
||||
|
||||
if (wsprFilterInput) {
|
||||
wsprFilterInput.addEventListener("input", () => {
|
||||
@@ -311,14 +303,14 @@ document.getElementById("settings-clear-wspr-history")?.addEventListener("click"
|
||||
if (!await wsprWindow.trxUi.confirm({ title: "Clear WSPR history?", message: "All stored WSPR decodes will be permanently removed.", confirmLabel: "Clear history" })) return;
|
||||
try {
|
||||
await wsprWindow.postPath?.("/clear_wspr_decode");
|
||||
wsprWindow.resetWsprHistoryView?.();
|
||||
resetWsprHistoryView();
|
||||
} catch (error: unknown) {
|
||||
console.error("WSPR history clear failed", error);
|
||||
}
|
||||
})();
|
||||
});
|
||||
|
||||
wsprWindow.onServerWspr = function(msg: WsprMessage) {
|
||||
function onServerWspr(msg: WsprMessage): void {
|
||||
if (wsprStatus) wsprStatus.textContent = "Receiving";
|
||||
const next = normalizeServerWsprMessage(msg);
|
||||
if (next.grids.length > 0 && wsprWindow.mapAddLocator) {
|
||||
@@ -328,4 +320,13 @@ wsprWindow.onServerWspr = function(msg: WsprMessage) {
|
||||
});
|
||||
}
|
||||
addWsprMessage(next.history);
|
||||
};
|
||||
}
|
||||
|
||||
wsprWindow.trxPluginRuntime.registerDecoder({
|
||||
id: "wspr",
|
||||
onMessage: onServerWspr,
|
||||
onBatch: onServerWsprBatch,
|
||||
restore: onServerWsprBatch,
|
||||
prune: pruneWsprHistoryView,
|
||||
reset: resetWsprHistoryView,
|
||||
});
|
||||
|
||||
@@ -37,13 +37,15 @@ test("CW entry appends server-decoded text and registers lifecycle callbacks", a
|
||||
Math,
|
||||
console,
|
||||
});
|
||||
const runtime = await readFile(new URL("../../assets/web/generated/plugin-runtime.js", import.meta.url), "utf8");
|
||||
const source = await readFile(new URL("../../assets/web/generated/cw.js", import.meta.url), "utf8");
|
||||
new vm.Script(runtime).runInContext(context);
|
||||
new vm.Script(source).runInContext(context);
|
||||
|
||||
window.onServerCw({ text: "CQ", wpm: 18, tone_hz: 700, signal_on: true });
|
||||
window.trxPluginRuntime.dispatch("cw", { text: "CQ", wpm: 18, tone_hz: 700, signal_on: true });
|
||||
assert.equal(status.textContent, "Receiving");
|
||||
assert.equal(output.children.length, 1);
|
||||
assert.equal(output.children[0].textContent, "CQ");
|
||||
assert.equal(typeof window.resetCwHistoryView, "function");
|
||||
assert.equal(typeof window.restoreCwHistory, "function");
|
||||
assert.equal(window.onServerCw, undefined);
|
||||
assert.equal(window.trxPluginRuntime.hasDecoder("cw"), true);
|
||||
});
|
||||
|
||||
@@ -29,10 +29,12 @@ test("FT2 entry normalizes audio offsets and registers typed callbacks", async (
|
||||
Array,
|
||||
console,
|
||||
});
|
||||
const runtime = await readFile(new URL("../../assets/web/generated/plugin-runtime.js", import.meta.url), "utf8");
|
||||
const source = await readFile(new URL("../../assets/web/generated/ft2.js", import.meta.url), "utf8");
|
||||
new vm.Script(runtime).runInContext(context);
|
||||
new vm.Script(source).runInContext(context);
|
||||
|
||||
window.onServerFt2({ message: "CQ SP0ABC JO91", freq_hz: 1_250, ts_ms: Date.now(), snr_db: -8 });
|
||||
window.trxPluginRuntime.dispatch("ft2", { message: "CQ SP0ABC JO91", freq_hz: 1_250, ts_ms: Date.now(), snr_db: -8 });
|
||||
assert.equal(mapMessages.length, 1);
|
||||
assert.equal(mapMessages[0][4].freq_hz, 14_075_250);
|
||||
const bar = barRenderer();
|
||||
@@ -59,12 +61,14 @@ test("FT8 entry installs shared parsing without relying on script globals", asyn
|
||||
Set,
|
||||
console,
|
||||
});
|
||||
const runtime = await readFile(new URL("../../assets/web/generated/plugin-runtime.js", import.meta.url), "utf8");
|
||||
const source = await readFile(new URL("../../assets/web/generated/ft8.js", import.meta.url), "utf8");
|
||||
new vm.Script(runtime).runInContext(context);
|
||||
new vm.Script(source).runInContext(context);
|
||||
|
||||
const details = window.ft8ExtractLocatorDetails("K1ABC SP0ABC JO91");
|
||||
assert.equal(details[0].source, "SP0ABC");
|
||||
assert.equal(details[0].target, "K1ABC");
|
||||
window.onServerFt8({ message: "K1ABC SP0ABC JO91", freq_hz: 500, ts_ms: Date.now() });
|
||||
window.trxPluginRuntime.dispatch("ft8", { message: "K1ABC SP0ABC JO91", freq_hz: 500, ts_ms: Date.now() });
|
||||
assert.equal(forwarded[0][4].freq_hz, 7_074_500);
|
||||
});
|
||||
|
||||
@@ -26,12 +26,14 @@ test("satellite entry registers lifecycle callbacks and forwards georeferenced i
|
||||
Error,
|
||||
console,
|
||||
});
|
||||
const runtime = await readFile(new URL("../../assets/web/generated/plugin-runtime.js", import.meta.url), "utf8");
|
||||
const source = await readFile(new URL("../../assets/web/generated/sat.js", import.meta.url), "utf8");
|
||||
new vm.Script(runtime).runInContext(context);
|
||||
new vm.Script(source).runInContext(context);
|
||||
|
||||
window.onServerLrptImage({ path: "/images/lrpt.png", geo_bounds: [50, 10, 55, 20], mcu_count: 100 });
|
||||
window.trxPluginRuntime.dispatch("lrpt_image", { path: "/images/lrpt.png", geo_bounds: [50, 10, 55, 20], mcu_count: 100 });
|
||||
assert.equal(overlays.length, 1);
|
||||
assert.deepEqual(overlays[0].geo_bounds, [50, 10, 55, 20]);
|
||||
assert.equal(typeof window.updateSatLiveState, "function");
|
||||
assert.equal(typeof window.resetSatHistoryView, "function");
|
||||
assert.equal(window.onServerLrptImage, undefined);
|
||||
});
|
||||
|
||||
@@ -19,12 +19,14 @@ test("WEFAX entry exposes typed lifecycle handlers and renders decoder state", a
|
||||
Uint8Array,
|
||||
console,
|
||||
});
|
||||
const runtime = await readFile(new URL("../../assets/web/generated/plugin-runtime.js", import.meta.url), "utf8");
|
||||
const source = await readFile(new URL("../../assets/web/generated/wefax.js", import.meta.url), "utf8");
|
||||
new vm.Script(runtime).runInContext(context);
|
||||
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(window.onServerWefaxProgress, undefined);
|
||||
assert.equal(window.trxPluginRuntime.hasDecoder("wefax_progress"), true);
|
||||
window.trxPluginRuntime.dispatch("wefax_progress", { state: "Scanning 12 MHz" });
|
||||
assert.equal(status.textContent, "Scanning 12 MHz");
|
||||
assert.equal(status.style.color, "var(--text-accent)");
|
||||
});
|
||||
|
||||
@@ -25,10 +25,12 @@ test("WSPR entry forwards decoded locators at their absolute frequency", async (
|
||||
Element: class Element {},
|
||||
console,
|
||||
});
|
||||
const runtime = await readFile(new URL("../../assets/web/generated/plugin-runtime.js", import.meta.url), "utf8");
|
||||
const source = await readFile(new URL("../../assets/web/generated/wspr.js", import.meta.url), "utf8");
|
||||
new vm.Script(runtime).runInContext(context);
|
||||
new vm.Script(source).runInContext(context);
|
||||
|
||||
window.onServerWspr({ message: "SP0ABC JO91 37", freq_hz: 1_420, ts_ms: Date.now() });
|
||||
window.trxPluginRuntime.dispatch("wspr", { message: "SP0ABC JO91 37", freq_hz: 1_420, ts_ms: Date.now() });
|
||||
assert.equal(forwarded.length, 1);
|
||||
assert.equal(forwarded[0][1][0], "JO91");
|
||||
assert.equal(forwarded[0][4].freq_hz, 14_097_020);
|
||||
|
||||
Reference in New Issue
Block a user