From ff915953b99c13d0721ad92cdf56c91c528f49e8 Mon Sep 17 00:00:00 2001 From: Stan Grams Date: Mon, 2 Mar 2026 23:46:58 +0100 Subject: [PATCH] [style](trx-frontend): refine AIS map tracks and history layout Show AIS vessel tracks only for the selected marker, keep the APRS and AIS history panes viewport-sized with internal scrolling, and tighten the APRS history controls with shorter bookmark-scale buttons. Co-authored-by: OpenAI Codex Signed-off-by: Stan Grams --- .../trx-frontend-http/assets/web/app.js | 66 +++++++++++++++---- .../trx-frontend-http/assets/web/index.html | 14 ++-- .../trx-frontend-http/assets/web/style.css | 42 ++++++++---- 3 files changed, 89 insertions(+), 33 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 cfae5c7..f2a042c 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 @@ -3017,6 +3017,7 @@ const mapFilter = { ais: true, aprs: true, ft8: true, wspr: true }; const APRS_TRACK_MAX_POINTS = 64; const AIS_TRACK_MAX_POINTS = 64; const aisMarkers = new Map(); +let selectedAisTrackMmsi = null; window.clearMapMarkersByType = function(type) { if (type === "aprs") { @@ -3045,6 +3046,7 @@ window.clearMapMarkersByType = function(type) { mapMarkers.delete(entry.track); } }); + selectedAisTrackMmsi = null; aisMarkers.clear(); return; } @@ -3115,27 +3117,63 @@ function initAprsMap() { aprsMap.on("popupopen", function(e) { const marker = e.popup._source; if (aprsRadioPath) { aprsRadioPath.remove(); aprsRadioPath = null; } + if (selectedAisTrackMmsi) { + const prevEntry = aisMarkers.get(String(selectedAisTrackMmsi)); + if (prevEntry && prevEntry.track && aprsMap && aprsMap.hasLayer(prevEntry.track)) { + prevEntry.track.removeFrom(aprsMap); + } + selectedAisTrackMmsi = null; + } if (marker === aprsMapReceiverMarker) { e.popup.setContent(buildReceiverPopupHtml()); return; } - if (!marker || !marker._aprsCall) return; - const entry = stationMarkers.get(marker._aprsCall); - if (!entry) return; + if (!marker) return; + const ll = marker.getLatLng(); - e.popup.setContent(buildAprsPopupHtml(marker._aprsCall, ll.lat, ll.lng, entry.info || "", entry.pkt)); - if (serverLat != null && serverLon != null) { - aprsRadioPath = L.polyline( - [[serverLat, serverLon], [ll.lat, ll.lng]], - { className: "aprs-radio-path", weight: 2, interactive: false } - ).addTo(aprsMap); + + if (marker._aprsCall) { + const entry = stationMarkers.get(marker._aprsCall); + if (!entry) return; + e.popup.setContent(buildAprsPopupHtml(marker._aprsCall, ll.lat, ll.lng, entry.info || "", entry.pkt)); + if (serverLat != null && serverLon != null) { + aprsRadioPath = L.polyline( + [[serverLat, serverLon], [ll.lat, ll.lng]], + { className: "aprs-radio-path", weight: 2, interactive: false } + ).addTo(aprsMap); + } + return; + } + + if (marker._aisMmsi) { + const entry = aisMarkers.get(String(marker._aisMmsi)); + if (!entry || !entry.msg) return; + e.popup.setContent(buildAisPopupHtml(entry.msg)); + ensureAisTrack(String(marker._aisMmsi), entry); + if (entry.track && aprsMap && mapFilter.ais && !aprsMap.hasLayer(entry.track)) { + entry.track.addTo(aprsMap); + } + selectedAisTrackMmsi = String(marker._aisMmsi); + if (serverLat != null && serverLon != null) { + aprsRadioPath = L.polyline( + [[serverLat, serverLon], [ll.lat, ll.lng]], + { className: "aprs-radio-path", weight: 2, interactive: false } + ).addTo(aprsMap); + } } }); aprsMap.on("popupclose", function() { if (aprsRadioPath) { aprsRadioPath.remove(); aprsRadioPath = null; } + if (selectedAisTrackMmsi) { + const entry = aisMarkers.get(String(selectedAisTrackMmsi)); + if (entry && entry.track && aprsMap && aprsMap.hasLayer(entry.track)) { + entry.track.removeFrom(aprsMap); + } + selectedAisTrackMmsi = null; + } }); // Materialise any stations that were buffered before the map was ready @@ -3154,6 +3192,12 @@ function initAprsMap() { aisFilter.addEventListener("change", () => { mapFilter.ais = aisFilter.checked; applyMapFilter(); + if (!mapFilter.ais && selectedAisTrackMmsi) { + const entry = aisMarkers.get(String(selectedAisTrackMmsi)); + if (entry && entry.track && aprsMap && aprsMap.hasLayer(entry.track)) { + entry.track.removeFrom(aprsMap); + } + } }); } if (aprsFilter) { @@ -3443,10 +3487,6 @@ function ensureAisTrack(mmsi, entry) { track.__trxType = "ais"; track._aisMmsi = mmsi; entry.track = track; - mapMarkers.add(track); - if (mapFilter.ais) { - track.addTo(aprsMap); - } } window.aisMapAddVessel = function(msg) { 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 8b6d78a..2b9a54c 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 @@ -464,16 +464,16 @@
- - - - + + + +
- - - + + +
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 432ca29..3e30c35 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 @@ -1088,6 +1088,22 @@ small { color: var(--text-muted); } .sub-tab:hover:not(.active) { color: var(--text); } #aprs-map { min-height: 150px; border-radius: 6px; } .aprs-controls { display: flex; gap: 0.6rem; align-items: center; margin-bottom: 0.75rem; } +#subtab-aprs .aprs-controls > button { + min-height: 1.65rem; + padding: 0.08rem 0.5rem; + border-radius: 0.4rem; + border: 1px solid var(--filter-border); + background: var(--filter-bg); + color: var(--filter-fg); + font-size: 0.68rem; + font-weight: 700; + letter-spacing: 0.03em; + cursor: pointer; +} +#subtab-aprs .aprs-controls > button:hover { + border-color: var(--accent-green); + color: var(--text); +} .aprs-summary { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); @@ -1126,15 +1142,15 @@ small { color: var(--text-muted); } display: inline-flex; align-items: center; justify-content: center; - min-height: 1.9rem; - padding: 0.18rem 0.55rem; - border-radius: 999px; + min-height: 1.65rem; + padding: 0.08rem 0.5rem; + border-radius: 0.4rem; border: 1px solid var(--filter-border); background: var(--filter-bg); color: var(--filter-fg); - font-size: 0.75rem; + font-size: 0.68rem; font-weight: 700; - letter-spacing: 0.02em; + letter-spacing: 0.03em; cursor: pointer; } .aprs-chip.active { @@ -1177,24 +1193,24 @@ small { color: var(--text-muted); } #subtab-ais { display: flex; flex-direction: column; - min-height: calc(100vh - 18rem); } #subtab-aprs { display: flex; flex-direction: column; - min-height: calc(100vh - 18rem); } #aprs-packets, #ais-messages { max-height: 360px; overflow-y: auto; border: 1px solid var(--border-light); border-radius: 6px; background: var(--input-bg); font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; } #aprs-packets { - flex: 1 1 auto; - min-height: calc(100vh - 28rem); - max-height: none; + flex: 0 1 auto; + height: calc(100vh - 28rem); + min-height: 16rem; + max-height: calc(100vh - 28rem); } #ais-messages { - flex: 1 1 auto; - min-height: calc(100vh - 24rem); - max-height: none; + flex: 0 1 auto; + height: calc(100vh - 24rem); + min-height: 16rem; + max-height: calc(100vh - 24rem); } .aprs-packet { font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; font-size: 0.82rem; padding: 0.45rem 0.55rem; border-bottom: 1px solid var(--border); line-height: 1.35; } .aprs-packet:last-child { border-bottom: none; }