From 86768c8e7f288551c61ad119cd8ff0e36335cfd5 Mon Sep 17 00:00:00 2001 From: Stan Grams Date: Sat, 14 Mar 2026 17:01:42 +0100 Subject: [PATCH] [fix](trx-frontend-http): focus longest qso map paths Let users click longest-QSO cards to isolate a single contact path on the map and click again to restore all visible contact paths. Also remove the extra inner panel styling from decode map tooltips so the popup renders as a single container. 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/app.js src/trx-client/trx-frontend/trx-frontend-http/assets/web/style.css Co-authored-by: OpenAI Codex Signed-off-by: Stan Grams --- .../trx-frontend-http/assets/web/app.js | 35 +++++++++++++++---- .../trx-frontend-http/assets/web/style.css | 22 +++++++++--- 2 files changed, 47 insertions(+), 10 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 962581e..486a5c5 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 @@ -3796,6 +3796,7 @@ const MAP_QSO_SUMMARY_LIMIT = 5; const stationMarkers = new Map(); const locatorMarkers = new Map(); const decodeContactPaths = new Map(); +let selectedMapQsoKey = null; const mapMarkers = new Set(); const DEFAULT_MAP_SOURCE_FILTER = { ais: true, vdes: true, aprs: true, bookmark: false, ft8: true, wspr: true }; const mapFilter = { ...DEFAULT_MAP_SOURCE_FILTER }; @@ -4490,11 +4491,22 @@ function ensureDecodeContactPathRendered(entry) { if (typeof entry.line.bringToBack === "function") entry.line.bringToBack(); } +function decodeContactPathBaseVisible(entry) { + return mapDecodeContactPathsEnabled + && decodeLocatorPathVisibility(entry.sourceGrid) + && decodeLocatorPathVisibility(entry.targetGrid); +} + function syncDecodeContactPathVisibility() { + if (selectedMapQsoKey) { + const selectedEntry = decodeContactPaths.get(selectedMapQsoKey); + if (!selectedEntry || !decodeContactPathBaseVisible(selectedEntry)) { + selectedMapQsoKey = null; + } + } for (const entry of decodeContactPaths.values()) { - const visible = mapDecodeContactPathsEnabled - && decodeLocatorPathVisibility(entry.sourceGrid) - && decodeLocatorPathVisibility(entry.targetGrid); + const visible = decodeContactPathBaseVisible(entry) + && (!selectedMapQsoKey || entry.pathKey === selectedMapQsoKey); if (!visible) { clearDecodeContactPathRender(entry); continue; @@ -6073,6 +6085,7 @@ function rebuildDecodeContactPaths() { const prev = decodeContactPaths.get(key); if (prev && prev.tsMs > msg.tsMs) continue; decodeContactPaths.set(key, { + pathKey: key, source: msg.source, target: msg.target, sourceGrid: msg.sourceGrid, @@ -6098,8 +6111,7 @@ function renderMapQsoSummary() { const entries = Array.from(decodeContactPaths.values()) .filter((entry) => entry && Number.isFinite(entry.distanceKm) - && decodeLocatorPathVisibility(entry.sourceGrid) - && decodeLocatorPathVisibility(entry.targetGrid)) + && decodeContactPathBaseVisible(entry)) .sort((a, b) => { const distanceDelta = Number(b.distanceKm) - Number(a.distanceKm); if (Math.abs(distanceDelta) > 0.001) return distanceDelta; @@ -6107,6 +6119,10 @@ function renderMapQsoSummary() { }) .slice(0, MAP_QSO_SUMMARY_LIMIT); + if (selectedMapQsoKey && !entries.some((entry) => entry.pathKey === selectedMapQsoKey)) { + selectedMapQsoKey = null; + } + if (entries.length === 0) { const empty = document.createElement("div"); empty.className = "map-qso-summary-empty"; @@ -6117,8 +6133,15 @@ function renderMapQsoSummary() { const fragment = document.createDocumentFragment(); entries.forEach((entry, index) => { - const card = document.createElement("article"); + const card = document.createElement("button"); + card.type = "button"; card.className = "map-qso-card"; + card.classList.toggle("is-selected", entry.pathKey === selectedMapQsoKey); + card.setAttribute("aria-pressed", entry.pathKey === selectedMapQsoKey ? "true" : "false"); + card.addEventListener("click", () => { + selectedMapQsoKey = selectedMapQsoKey === entry.pathKey ? null : entry.pathKey; + syncDecodeContactPathVisibility(); + }); const head = document.createElement("div"); head.className = "map-qso-card-head"; diff --git a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/style.css b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/style.css index 4608ca9..1a1a5fd 100644 --- a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/style.css +++ b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/style.css @@ -1329,6 +1329,8 @@ small { color: var(--text-muted); } } .map-qso-card { display: flex; + appearance: none; + width: 100%; flex-direction: column; gap: 0.5rem; min-width: 0; @@ -1336,6 +1338,21 @@ small { color: var(--text-muted); } border-radius: 0.75rem; border: 1px solid color-mix(in srgb, var(--border-light) 74%, transparent); background: color-mix(in srgb, var(--card-bg) 78%, transparent); + text-align: left; + cursor: pointer; + transition: border-color 120ms ease, background 120ms ease, transform 120ms ease, box-shadow 120ms ease; +} +.map-qso-card:hover { + border-color: color-mix(in srgb, var(--accent-green) 38%, var(--border-light)); + background: color-mix(in srgb, var(--card-bg) 70%, transparent); +} +.map-qso-card.is-selected { + border-color: color-mix(in srgb, var(--accent-green) 62%, var(--border-light)); + background: color-mix(in srgb, var(--accent-green) 10%, var(--card-bg)); + box-shadow: 0 0 0 1px color-mix(in srgb, var(--accent-green) 18%, transparent); +} +.map-qso-card:active { + transform: translateY(1px); } .map-qso-card-head { display: flex; @@ -1934,11 +1951,8 @@ body.map-fake-fullscreen-active { max-width: 26rem; max-height: min(22rem, 60vh); overflow: auto; - padding: 0.55rem 0.65rem; - border-radius: 0.65rem; - background: color-mix(in srgb, var(--card-bg) 84%, transparent); + padding: 0.1rem 0.05rem 0.05rem; color: var(--text); - box-shadow: 0 10px 24px rgba(0, 0, 0, 0.28); } .decode-locator-tip-title { color: var(--accent-yellow);