import { aprsAgeText, aprsCategoryLabel, aprsHexBytes, aprsPacketCategory, collapseAprsDuplicates, normalizeAprsPacket, renderAprsInfo, renderLocalAprsSymbol } from "./chunk-M2I6DH4X.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 aprsVisiblePackets() { const packets = aprsCollapseDup ? collapseAprsDuplicates(aprsPacketHistory) : aprsPacketHistory; return packets.filter(aprsFilterMatch); } function updateAprsSummary() { const visible = aprsVisiblePackets(); if (aprsTotalCountEl) { aprsTotalCountEl.textContent = `${aprsPacketHistory.length} total`; } if (aprsVisibleCountEl) { aprsVisibleCountEl.textContent = `${visible.length} shown`; } if (aprsLatestSeenEl) { const latest = aprsPacketHistory[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); } function renderAprsRow(pkt, isFresh) { const row = document.createElement("div"); row.className = "aprs-packet"; if (!pkt.crcOk) row.classList.add("aprs-packet-crc"); if (isFresh) row.classList.add("aprs-packet-new"); const ts = pkt._ts || (/* @__PURE__ */ new Date()).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit", second: "2-digit" }); const age = aprsAgeText(pkt._tsMs); const category = aprsPacketCategory(pkt); const categoryLabel = aprsCategoryLabel(category); const categoryClass = `aprs-badge aprs-badge-type aprs-badge-type-${category}`; const pathBadge = pkt.path ? `${escapeAprsHtml(pkt.path)}` : ""; const crcBadge = pkt.crcOk ? "" : 'CRC Fail'; const symbolHtml = renderLocalAprsSymbol(pkt, escapeAprsHtml); const posLink = pkt.lat != null && pkt.lon != null ? `${pkt.lat.toFixed(4)}, ${pkt.lon.toFixed(4)}` : ""; const distance = aprsDistanceText(pkt); const qrzHref = `https://qrzcq.com/call/${encodeURIComponent(pkt.srcCall || "")}`; row.innerHTML = `
${ts}` + symbolHtml + `${escapeAprsHtml(pkt.srcCall ?? "")}>${escapeAprsHtml(pkt.destCall || "")}${escapeAprsHtml(categoryLabel)}` + pathBadge + crcBadge + `
${escapeAprsHtml(age)}` + (distance ? `${escapeAprsHtml(distance)}` : "") + `${escapeAprsHtml(pkt.type || "--")}
${renderAprsInfo(pkt)}` + (posLink ? `${posLink}` : "") + `
` + (pkt.lat != null && pkt.lon != null ? `` : "") + (pkt.lat != null && pkt.lon != null ? `` : "") + `QRZ
Details
Source${escapeAprsHtml(pkt.srcCall || "--")}Destination${escapeAprsHtml(pkt.destCall || "--")}Type${escapeAprsHtml(pkt.type || "--")}Path${escapeAprsHtml(pkt.path || "--")}Age${escapeAprsHtml(age)}CRC${pkt.crcOk ? "OK" : "Failed"}Position${pkt.lat != null && pkt.lon != null ? `${pkt.lat.toFixed(5)}, ${pkt.lon.toFixed(5)}` : "--"}Info${escapeAprsHtml(pkt.info || "--")}Info Bytes${escapeAprsHtml(aprsHexBytes(pkt.info_bytes))}
`; row.querySelectorAll("[data-aprs-map]").forEach((el) => { el.addEventListener("click", (evt) => { evt.preventDefault(); const raw = el.dataset.aprsMap ?? ""; const [lat, lon] = raw.split(",").map(Number); if (aprsWindow.navigateToAprsMap && typeof lat === "number" && typeof lon === "number" && Number.isFinite(lat) && Number.isFinite(lon)) { aprsWindow.navigateToAprsMap(lat, lon); } }); }); const copyBtn = row.querySelector("[data-aprs-copy]"); if (copyBtn) { copyBtn.addEventListener("click", () => { void (async () => { const raw = copyBtn.dataset.aprsCopy ?? ""; try { const clipboard = Reflect.get(navigator, "clipboard"); if (clipboard) { await clipboard.writeText(raw); showAprsHint("Coordinates copied", 1200); } } catch { showAprsHint("Copy failed", 1500); } })(); }); } return row; } 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); 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 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(); if (pkt.lat != null && pkt.lon != null && aprsWindow.aprsMapAddStation) { aprsWindow.aprsMapAddStation(pkt.srcCall ?? "", pkt.lat, pkt.lon, pkt.info ?? "", pkt.symbolTable, pkt.symbolCode, 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; if (aprsStatus) aprsStatus.textContent = "Receiving"; const normalized = []; let hasCrcOk = false; for (const pkt of packets) { const next = normalizeServerAprsPacket(pkt); 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" }); if (next.lat != null && next.lon != null && aprsWindow.aprsMapAddStation) { aprsWindow.aprsMapAddStation(next.srcCall ?? "", next.lat, next.lon, next.info ?? "", next.symbolTable, next.symbolCode, 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) { if (aprsStatus) aprsStatus.textContent = "Receiving"; addAprsPacket(normalizeServerAprsPacket(pkt)); } renderAprsHistory(); window.trxPluginRuntime.registerDecoder({ id: "aprs", onMessage: onServerAprs, onBatch: onServerAprsBatch, restore: onServerAprsBatch, reset: resetAprsHistoryView, prune: pruneAprsHistoryView });