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);