import { aprsAgeText, aprsPacketCategory, collapseAprsDuplicates, normalizeAprsPacket, renderAprsPacketRow } from "./chunk-REYSUJQ4.js"; import { forActiveRig, isActiveRigDecode } from "./chunk-S57W63QN.js"; import { hostCore, hostState } from "./chunk-KL66PICH.js"; // src/plugins/aprs.ts var aprsWindow = window; var escapeAprsHtml = (input) => hostCore.escapeMapHtml(input); var showAprsHint = (message, durationMs) => { hostCore.showHint(message, durationMs); }; var aprsStatus = document.getElementById("aprs-status"); var aprsPacketsEl = document.getElementById("aprs-packets"); var aprsFilterInput = document.getElementById("aprs-filter"); var aprsBarOverlay = document.getElementById("aprs-bar-overlay"); var aprsOnlyPosBtn = document.getElementById("aprs-only-pos-btn"); var aprsHideCrcBtn = document.getElementById("aprs-hide-crc-btn"); var aprsCollapseDupBtn = document.getElementById("aprs-collapse-dup-btn"); var aprsTotalCountEl = document.getElementById("aprs-total-count"); var aprsVisibleCountEl = document.getElementById("aprs-visible-count"); var aprsLatestSeenEl = document.getElementById("aprs-latest-seen"); var APRS_BAR_WINDOW_MS = 15 * 60 * 1e3; var aprsFilterText = ""; var aprsPacketHistory = []; var aprsBarDismissedAtMs = 0; var aprsOnlyPos = false; var aprsHideCrc = false; var aprsCollapseDup = false; var aprsTypeFilter = "all"; function currentAprsHistoryRetentionMs() { return typeof aprsWindow.getDecodeHistoryRetentionMs === "function" ? aprsWindow.getDecodeHistoryRetentionMs() : 24 * 60 * 60 * 1e3; } function pruneAprsPacketHistory() { const cutoffMs = Date.now() - currentAprsHistoryRetentionMs(); aprsPacketHistory = aprsPacketHistory.filter((pkt) => (pkt._tsMs ?? 0) >= cutoffMs); } function scheduleAprsUi(key, job) { if (typeof aprsWindow.trxScheduleUiFrameJob === "function") { aprsWindow.trxScheduleUiFrameJob(key, job); return; } job(); } function scheduleAprsHistoryRender() { scheduleAprsUi("aprs-history", () => { renderAprsHistory(); }); } function scheduleAprsBarUpdate() { scheduleAprsUi("aprs-bar", () => { updateAprsBar(); }); } function aprsDistanceText(pkt) { if (hostState.serverLat == null || hostState.serverLon == null || pkt.lat == null || pkt.lon == null) return ""; const distKm = hostCore.haversineKm(hostState.serverLat, hostState.serverLon, pkt.lat, pkt.lon); if (!Number.isFinite(distKm)) return ""; if (distKm < 1) return `${Math.round(distKm * 1e3)} m from TRX`; return `${distKm.toFixed(1)} km from TRX`; } function aprsFilterMatch(pkt) { if (aprsOnlyPos && (pkt.lat == null || pkt.lon == null)) return false; if (aprsHideCrc && !pkt.crcOk) return false; if (aprsTypeFilter !== "all" && aprsPacketCategory(pkt) !== aprsTypeFilter) return false; if (!aprsFilterText) return true; const haystack = [ pkt.srcCall, pkt.destCall, pkt.path, pkt.info, pkt.type, pkt.lat != null ? pkt.lat.toFixed(4) : "", pkt.lon != null ? pkt.lon.toFixed(4) : "", aprsPacketCategory(pkt) ].filter(Boolean).join(" ").toUpperCase(); return haystack.includes(aprsFilterText); } function aprsRigPackets() { return forActiveRig(aprsPacketHistory); } function aprsVisiblePackets() { const rigPackets = aprsRigPackets(); const packets = aprsCollapseDup ? collapseAprsDuplicates(rigPackets) : rigPackets; return packets.filter(aprsFilterMatch); } function updateAprsSummary() { const rigPackets = aprsRigPackets(); const visible = aprsVisiblePackets(); if (aprsTotalCountEl) { aprsTotalCountEl.textContent = `${rigPackets.length} total`; } if (aprsVisibleCountEl) { aprsVisibleCountEl.textContent = `${visible.length} shown`; } if (aprsLatestSeenEl) { const latest = rigPackets[0]; if (!latest) { aprsLatestSeenEl.textContent = "No packets yet"; } else { aprsLatestSeenEl.textContent = `${latest.srcCall} ${aprsAgeText(latest._tsMs)}`; } } } function updateAprsChipState() { document.querySelectorAll("[id^='aprs-type-']").forEach((btn) => { btn.classList.toggle("active", btn.id === `aprs-type-${aprsTypeFilter}`); }); aprsOnlyPosBtn?.classList.toggle("active", aprsOnlyPos); aprsHideCrcBtn?.classList.toggle("active", aprsHideCrc); aprsCollapseDupBtn?.classList.toggle("active", aprsCollapseDup); } async function copyAprsCoords(text) { try { const clipboard = Reflect.get(navigator, "clipboard"); if (!clipboard) return; await clipboard.writeText(text); showAprsHint("Coordinates copied", 1200); } catch { showAprsHint("Copy failed", 1500); } } function renderAprsRow(pkt, isFresh) { return renderAprsPacketRow(pkt, { fresh: isFresh, distance: aprsDistanceText(pkt), onMap: (lat, lon) => { aprsWindow.navigateToAprsMap?.(lat, lon); }, onCopy: (text) => { void copyAprsCoords(text); }, onLog: (call, gridsquare) => { aprsWindow.logContact?.({ call, gridsquare: gridsquare ?? void 0, decoder: "aprs" }); } }); } function renderAprsHistory() { pruneAprsPacketHistory(); if (!aprsPacketsEl) { updateAprsSummary(); updateAprsChipState(); return; } const visible = aprsVisiblePackets(); const fragment = document.createDocumentFragment(); for (const [index, packet] of visible.entries()) { fragment.appendChild(renderAprsRow(packet, index === 0)); } aprsPacketsEl.replaceChildren(fragment); updateAprsSummary(); updateAprsChipState(); } function updateAprsBar() { if (!aprsBarOverlay) return; const isPkt = (document.getElementById("mode")?.value || "").toUpperCase() === "PKT"; const cutoffMs = Date.now() - APRS_BAR_WINDOW_MS; const okFrames = aprsPacketHistory.filter( (p) => p.crcOk && (p._tsMs ?? 0) >= cutoffMs && isActiveRigDecode(p.rig_id) ); const frames = collapseAprsDuplicates(okFrames).slice(0, 8); const newestTsMs = frames.reduce((latest, pkt) => Math.max(latest, Number(pkt._tsMs) || 0), 0); if (!isPkt || frames.length === 0 || newestTsMs <= aprsBarDismissedAtMs) { aprsBarOverlay.style.display = "none"; aprsBarOverlay.innerHTML = ""; return; } let html = `
APRSLiveLast 15 minutesClear
`; for (const pkt of frames) { const ts = pkt._ts ? `${pkt._ts}` : ""; const call = `${escapeAprsHtml(pkt.srcCall ?? "")}`; const dest = escapeAprsHtml(pkt.destCall || ""); const info = escapeAprsHtml(pkt.info || ""); const pin = pkt.lat != null && pkt.lon != null ? `` : ""; html += `
${ts}${pin}${call}>${dest}: ${info}
`; } aprsBarOverlay.innerHTML = html; aprsBarOverlay.style.display = "flex"; } aprsWindow.updateAprsBar = updateAprsBar; aprsWindow.clearAprsBar = function() { resetAprsHistoryView(); }; aprsWindow.closeAprsBar = function() { aprsBarDismissedAtMs = Date.now(); if (aprsBarOverlay) { aprsBarOverlay.style.display = "none"; aprsBarOverlay.innerHTML = ""; } }; function resetAprsHistoryView() { if (aprsPacketsEl) aprsPacketsEl.innerHTML = ""; aprsPacketHistory = []; updateAprsBar(); renderAprsHistory(); aprsWindow.clearMapMarkersByType?.("aprs"); } function pruneAprsHistoryView() { pruneAprsPacketHistory(); updateAprsBar(); renderAprsHistory(); } function plotAprsPacket(pkt) { if (pkt.lat == null || pkt.lon == null || !aprsWindow.aprsMapAddStation) return; aprsWindow.aprsMapAddStation(pkt.srcCall ?? "", pkt.lat, pkt.lon, pkt.info ?? "", pkt.symbolTable, pkt.symbolCode, pkt); } function addAprsPacket(pkt) { const tsMs = Number.isFinite(pkt.ts_ms) ? Number(pkt.ts_ms) : Date.now(); pkt._tsMs = tsMs; pkt._ts = new Date(tsMs).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit", second: "2-digit" }); aprsPacketHistory.unshift(pkt); pruneAprsPacketHistory(); plotAprsPacket(pkt); if (pkt.crcOk) scheduleAprsBarUpdate(); scheduleAprsHistoryRender(); } function normalizeServerAprsPacket(pkt) { return normalizeAprsPacket(pkt, aprsWindow.getDecodeRigMeta?.() ?? null); } function onServerAprsBatch(packets) { if (!Array.isArray(packets) || packets.length === 0) return; const normalized = []; let hasCrcOk = false; for (const pkt of packets) { const next = normalizeServerAprsPacket(pkt); if (aprsStatus && isActiveRigDecode(next.rig_id)) aprsStatus.textContent = "Receiving"; const tsMs = Number.isFinite(next.ts_ms) ? Number(next.ts_ms) : Date.now(); next._tsMs = tsMs; next._ts = new Date(tsMs).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit", second: "2-digit" }); plotAprsPacket(next); if (next.crcOk) hasCrcOk = true; normalized.push(next); } normalized.reverse(); aprsPacketHistory = normalized.concat(aprsPacketHistory); pruneAprsPacketHistory(); if (hasCrcOk) scheduleAprsBarUpdate(); scheduleAprsHistoryRender(); } document.getElementById("settings-clear-aprs-history")?.addEventListener("click", () => { void (async () => { if (!await aprsWindow.trxUi.confirm({ title: "Clear APRS history?", message: "All stored APRS packets will be permanently removed.", confirmLabel: "Clear history" })) return; try { await hostCore.postPath("/clear_aprs_decode"); resetAprsHistoryView(); } catch (e) { console.error("APRS history clear failed", e); } })(); }); if (aprsOnlyPosBtn) { aprsOnlyPosBtn.addEventListener("click", () => { aprsOnlyPos = !aprsOnlyPos; renderAprsHistory(); }); } if (aprsHideCrcBtn) { aprsHideCrcBtn.addEventListener("click", () => { aprsHideCrc = !aprsHideCrc; renderAprsHistory(); }); } if (aprsCollapseDupBtn) { aprsCollapseDupBtn.addEventListener("click", () => { aprsCollapseDup = !aprsCollapseDup; renderAprsHistory(); }); } ["all", "position", "message", "weather", "telemetry", "other"].forEach((type) => { const btn = document.getElementById(`aprs-type-${type}`); if (!btn) return; btn.addEventListener("click", () => { aprsTypeFilter = type; renderAprsHistory(); }); }); if (aprsFilterInput) { aprsFilterInput.addEventListener("input", () => { aprsFilterText = aprsFilterInput.value.trim().toUpperCase(); renderAprsHistory(); }); } function onServerAprs(pkt) { const packet = normalizeServerAprsPacket(pkt); if (aprsStatus && isActiveRigDecode(packet.rig_id)) aprsStatus.textContent = "Receiving"; addAprsPacket(packet); } renderAprsHistory(); window.trxPluginRuntime.registerDecoder({ id: "aprs", onMessage: onServerAprs, onBatch: onServerAprsBatch, restore: onServerAprsBatch, reset: resetAprsHistoryView, prune: pruneAprsHistoryView, rerender: () => { updateAprsBar(); renderAprsHistory(); }, // Oldest first, so station tracks are rebuilt in the order they happened. syncMap: () => { for (const entry of [...aprsPacketHistory].reverse()) plotAprsPacket(entry); } });