From e2517ec1842f157cf7008ed7d1ef6b7cd0382da0 Mon Sep 17 00:00:00 2001 From: Stan Grams Date: Sat, 14 Mar 2026 16:46:06 +0100 Subject: [PATCH] [feat](trx-frontend-http): add longest map qso summary Add a map summary section below the map that lists the five longest directed FT8 and WSPR contacts in the current view, including distance, band, age, and locator details. Verification: node --check src/trx-client/trx-frontend/trx-frontend-http/assets/web/app.js Verification: git diff --check -- src/trx-client/trx-frontend/trx-frontend-http/assets/web/index.html src/trx-client/trx-frontend/trx-frontend-http/assets/web/style.css src/trx-client/trx-frontend/trx-frontend-http/assets/web/app.js Co-authored-by: OpenAI Codex Signed-off-by: Stan Grams --- .../trx-frontend-http/assets/web/app.js | 99 +++++++++++++- .../trx-frontend-http/assets/web/index.html | 9 ++ .../trx-frontend-http/assets/web/style.css | 128 ++++++++++++++++++ 3 files changed, 233 insertions(+), 3 deletions(-) diff --git a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/app.js b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/app.js index 2951b9c..445fed6 100644 --- a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/app.js +++ b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/app.js @@ -3780,6 +3780,7 @@ let mapP2pRadioPathsEnabled = loadSetting("mapP2pRadioPathsEnabled", true) !== f let mapDecodeContactPathsEnabled = loadSetting("mapDecodeContactPathsEnabled", true) !== false; let mapOverlayPanelVisible = loadSetting("mapOverlayPanelVisible", true) !== false; const MAP_HISTORY_LIMIT_OPTIONS = [15, 30, 60, 180, 360, 720, 1440]; +const MAP_QSO_SUMMARY_LIMIT = 5; const stationMarkers = new Map(); const locatorMarkers = new Map(); const decodeContactPaths = new Map(); @@ -4398,6 +4399,7 @@ function syncDecodeContactPathVisibility() { } ensureDecodeContactPathRendered(entry); } + renderMapQsoSummary(); } function setMapRadioPathTo(lat, lon, color, className = "aprs-radio-path") { @@ -5942,6 +5944,7 @@ function rebuildDecodeContactPaths() { source, target, sourceGrid: grid, + sourceType: entry.sourceType, tsMs, bandLabel: band?.label || null, }); @@ -5955,6 +5958,7 @@ function rebuildDecodeContactPaths() { const sourceCenter = locatorToLatLon(msg.sourceGrid); const targetCenter = locatorToLatLon(targetLocator.grid); if (!sourceCenter || !targetCenter) continue; + const distanceKm = haversineKm(sourceCenter.lat, sourceCenter.lon, targetCenter.lat, targetCenter.lon); const key = [msg.source, msg.target].sort().join("::"); const prev = decodeContactPaths.get(key); if (prev && prev.tsMs > msg.tsMs) continue; @@ -5963,13 +5967,13 @@ function rebuildDecodeContactPaths() { target: msg.target, sourceGrid: msg.sourceGrid, targetGrid: targetLocator.grid, + sourceType: msg.sourceType, bandLabel: msg.bandLabel, from: sourceCenter, to: targetCenter, tsMs: msg.tsMs, - distanceText: formatDecodeContactDistance( - haversineKm(sourceCenter.lat, sourceCenter.lon, targetCenter.lat, targetCenter.lon) - ), + distanceKm, + distanceText: formatDecodeContactDistance(distanceKm), line: null, labelMarker: null, }); @@ -5977,6 +5981,95 @@ function rebuildDecodeContactPaths() { syncDecodeContactPathVisibility(); } +function renderMapQsoSummary() { + const listEl = document.getElementById("map-qso-summary-list"); + if (!listEl) return; + + const entries = Array.from(decodeContactPaths.values()) + .filter((entry) => entry + && Number.isFinite(entry.distanceKm) + && decodeLocatorPathVisibility(entry.sourceGrid) + && decodeLocatorPathVisibility(entry.targetGrid)) + .sort((a, b) => { + const distanceDelta = Number(b.distanceKm) - Number(a.distanceKm); + if (Math.abs(distanceDelta) > 0.001) return distanceDelta; + return Number(b.tsMs || 0) - Number(a.tsMs || 0); + }) + .slice(0, MAP_QSO_SUMMARY_LIMIT); + + if (entries.length === 0) { + const empty = document.createElement("div"); + empty.className = "map-qso-summary-empty"; + empty.textContent = "No directed FT8 or WSPR contacts match the current map history and filters."; + listEl.replaceChildren(empty); + return; + } + + const fragment = document.createDocumentFragment(); + entries.forEach((entry, index) => { + const card = document.createElement("article"); + card.className = "map-qso-card"; + + const head = document.createElement("div"); + head.className = "map-qso-card-head"; + + const rank = document.createElement("span"); + rank.className = "map-qso-card-rank"; + rank.textContent = `#${index + 1}`; + head.appendChild(rank); + + const distance = document.createElement("span"); + distance.className = "map-qso-card-distance"; + distance.textContent = entry.distanceText || "--"; + head.appendChild(distance); + + const body = document.createElement("div"); + body.className = "map-qso-card-body"; + + const pair = document.createElement("div"); + pair.className = "map-qso-card-pair"; + pair.textContent = `${entry.source || "Unknown"} -> ${entry.target || "Unknown"}`; + body.appendChild(pair); + + const meta = document.createElement("div"); + meta.className = "map-qso-card-meta"; + + const sourceType = document.createElement("span"); + sourceType.className = "map-qso-card-pill"; + sourceType.textContent = String(entry.sourceType || "ft8").toUpperCase(); + meta.appendChild(sourceType); + + if (entry.bandLabel) { + const band = document.createElement("span"); + band.className = "map-qso-card-pill map-qso-card-band"; + band.style.setProperty("--band-color", locatorBandChipColor(entry.bandLabel)); + band.textContent = entry.bandLabel; + meta.appendChild(band); + } + + const ageText = formatTimeAgo(Number(entry.tsMs)); + if (ageText) { + const age = document.createElement("span"); + age.className = "map-qso-card-pill"; + age.textContent = ageText; + meta.appendChild(age); + } + + body.appendChild(meta); + + const grids = document.createElement("div"); + grids.className = "map-qso-card-grids"; + grids.textContent = `${entry.sourceGrid || "--"} -> ${entry.targetGrid || "--"}`; + body.appendChild(grids); + + card.appendChild(head); + card.appendChild(body); + fragment.appendChild(card); + }); + + listEl.replaceChildren(fragment); +} + function buildBookmarkLocatorPopupHtml(grid, bookmarks) { const list = Array.isArray(bookmarks) ? bookmarks : []; const rows = list diff --git a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/index.html b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/index.html index 758bf89..db693a5 100644 --- a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/index.html +++ b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/index.html @@ -717,6 +717,15 @@
+
+
+
+
Longest QSOs
+
Top 5 directed decode contacts in the current map view
+
+
+
+