[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 <codex@openai.com> Signed-off-by: Stan Grams <sjg@haxx.space>
This commit is contained in:
@@ -3017,6 +3017,7 @@ const mapFilter = { ais: true, aprs: true, ft8: true, wspr: true };
|
|||||||
const APRS_TRACK_MAX_POINTS = 64;
|
const APRS_TRACK_MAX_POINTS = 64;
|
||||||
const AIS_TRACK_MAX_POINTS = 64;
|
const AIS_TRACK_MAX_POINTS = 64;
|
||||||
const aisMarkers = new Map();
|
const aisMarkers = new Map();
|
||||||
|
let selectedAisTrackMmsi = null;
|
||||||
|
|
||||||
window.clearMapMarkersByType = function(type) {
|
window.clearMapMarkersByType = function(type) {
|
||||||
if (type === "aprs") {
|
if (type === "aprs") {
|
||||||
@@ -3045,6 +3046,7 @@ window.clearMapMarkersByType = function(type) {
|
|||||||
mapMarkers.delete(entry.track);
|
mapMarkers.delete(entry.track);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
selectedAisTrackMmsi = null;
|
||||||
aisMarkers.clear();
|
aisMarkers.clear();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -3115,27 +3117,63 @@ function initAprsMap() {
|
|||||||
aprsMap.on("popupopen", function(e) {
|
aprsMap.on("popupopen", function(e) {
|
||||||
const marker = e.popup._source;
|
const marker = e.popup._source;
|
||||||
if (aprsRadioPath) { aprsRadioPath.remove(); aprsRadioPath = null; }
|
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) {
|
if (marker === aprsMapReceiverMarker) {
|
||||||
e.popup.setContent(buildReceiverPopupHtml());
|
e.popup.setContent(buildReceiverPopupHtml());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!marker || !marker._aprsCall) return;
|
if (!marker) return;
|
||||||
const entry = stationMarkers.get(marker._aprsCall);
|
|
||||||
if (!entry) return;
|
|
||||||
const ll = marker.getLatLng();
|
const ll = marker.getLatLng();
|
||||||
e.popup.setContent(buildAprsPopupHtml(marker._aprsCall, ll.lat, ll.lng, entry.info || "", entry.pkt));
|
|
||||||
if (serverLat != null && serverLon != null) {
|
if (marker._aprsCall) {
|
||||||
aprsRadioPath = L.polyline(
|
const entry = stationMarkers.get(marker._aprsCall);
|
||||||
[[serverLat, serverLon], [ll.lat, ll.lng]],
|
if (!entry) return;
|
||||||
{ className: "aprs-radio-path", weight: 2, interactive: false }
|
e.popup.setContent(buildAprsPopupHtml(marker._aprsCall, ll.lat, ll.lng, entry.info || "", entry.pkt));
|
||||||
).addTo(aprsMap);
|
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() {
|
aprsMap.on("popupclose", function() {
|
||||||
if (aprsRadioPath) { aprsRadioPath.remove(); aprsRadioPath = null; }
|
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
|
// Materialise any stations that were buffered before the map was ready
|
||||||
@@ -3154,6 +3192,12 @@ function initAprsMap() {
|
|||||||
aisFilter.addEventListener("change", () => {
|
aisFilter.addEventListener("change", () => {
|
||||||
mapFilter.ais = aisFilter.checked;
|
mapFilter.ais = aisFilter.checked;
|
||||||
applyMapFilter();
|
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) {
|
if (aprsFilter) {
|
||||||
@@ -3443,10 +3487,6 @@ function ensureAisTrack(mmsi, entry) {
|
|||||||
track.__trxType = "ais";
|
track.__trxType = "ais";
|
||||||
track._aisMmsi = mmsi;
|
track._aisMmsi = mmsi;
|
||||||
entry.track = track;
|
entry.track = track;
|
||||||
mapMarkers.add(track);
|
|
||||||
if (mapFilter.ais) {
|
|
||||||
track.addTo(aprsMap);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
window.aisMapAddVessel = function(msg) {
|
window.aisMapAddVessel = function(msg) {
|
||||||
|
|||||||
@@ -464,16 +464,16 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="aprs-filter-row">
|
<div class="aprs-filter-row">
|
||||||
<button id="aprs-type-all" class="aprs-chip active" type="button">All</button>
|
<button id="aprs-type-all" class="aprs-chip active" type="button">All</button>
|
||||||
<button id="aprs-type-position" class="aprs-chip" type="button">Position</button>
|
<button id="aprs-type-position" class="aprs-chip" type="button">Pos</button>
|
||||||
<button id="aprs-type-message" class="aprs-chip" type="button">Message</button>
|
<button id="aprs-type-message" class="aprs-chip" type="button">Msg</button>
|
||||||
<button id="aprs-type-weather" class="aprs-chip" type="button">Weather</button>
|
<button id="aprs-type-weather" class="aprs-chip" type="button">Wx</button>
|
||||||
<button id="aprs-type-telemetry" class="aprs-chip" type="button">Telemetry</button>
|
<button id="aprs-type-telemetry" class="aprs-chip" type="button">Tlm</button>
|
||||||
<button id="aprs-type-other" class="aprs-chip" type="button">Other</button>
|
<button id="aprs-type-other" class="aprs-chip" type="button">Other</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="aprs-filter-row">
|
<div class="aprs-filter-row">
|
||||||
<button id="aprs-only-pos-btn" class="aprs-chip" type="button">Only Pos</button>
|
<button id="aprs-only-pos-btn" class="aprs-chip" type="button">Pos Only</button>
|
||||||
<button id="aprs-hide-crc-btn" class="aprs-chip" type="button">Hide CRC</button>
|
<button id="aprs-hide-crc-btn" class="aprs-chip" type="button">No CRC</button>
|
||||||
<button id="aprs-collapse-dup-btn" class="aprs-chip" type="button">Collapse Dupes</button>
|
<button id="aprs-collapse-dup-btn" class="aprs-chip" type="button">Dupes</button>
|
||||||
</div>
|
</div>
|
||||||
<div id="aprs-packets"></div>
|
<div id="aprs-packets"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1088,6 +1088,22 @@ small { color: var(--text-muted); }
|
|||||||
.sub-tab:hover:not(.active) { color: var(--text); }
|
.sub-tab:hover:not(.active) { color: var(--text); }
|
||||||
#aprs-map { min-height: 150px; border-radius: 6px; }
|
#aprs-map { min-height: 150px; border-radius: 6px; }
|
||||||
.aprs-controls { display: flex; gap: 0.6rem; align-items: center; margin-bottom: 0.75rem; }
|
.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 {
|
.aprs-summary {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||||
@@ -1126,15 +1142,15 @@ small { color: var(--text-muted); }
|
|||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
min-height: 1.9rem;
|
min-height: 1.65rem;
|
||||||
padding: 0.18rem 0.55rem;
|
padding: 0.08rem 0.5rem;
|
||||||
border-radius: 999px;
|
border-radius: 0.4rem;
|
||||||
border: 1px solid var(--filter-border);
|
border: 1px solid var(--filter-border);
|
||||||
background: var(--filter-bg);
|
background: var(--filter-bg);
|
||||||
color: var(--filter-fg);
|
color: var(--filter-fg);
|
||||||
font-size: 0.75rem;
|
font-size: 0.68rem;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
letter-spacing: 0.02em;
|
letter-spacing: 0.03em;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
.aprs-chip.active {
|
.aprs-chip.active {
|
||||||
@@ -1177,24 +1193,24 @@ small { color: var(--text-muted); }
|
|||||||
#subtab-ais {
|
#subtab-ais {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
min-height: calc(100vh - 18rem);
|
|
||||||
}
|
}
|
||||||
#subtab-aprs {
|
#subtab-aprs {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
min-height: calc(100vh - 18rem);
|
|
||||||
}
|
}
|
||||||
#aprs-packets,
|
#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; }
|
#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 {
|
#aprs-packets {
|
||||||
flex: 1 1 auto;
|
flex: 0 1 auto;
|
||||||
min-height: calc(100vh - 28rem);
|
height: calc(100vh - 28rem);
|
||||||
max-height: none;
|
min-height: 16rem;
|
||||||
|
max-height: calc(100vh - 28rem);
|
||||||
}
|
}
|
||||||
#ais-messages {
|
#ais-messages {
|
||||||
flex: 1 1 auto;
|
flex: 0 1 auto;
|
||||||
min-height: calc(100vh - 24rem);
|
height: calc(100vh - 24rem);
|
||||||
max-height: none;
|
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 { 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; }
|
.aprs-packet:last-child { border-bottom: none; }
|
||||||
|
|||||||
Reference in New Issue
Block a user