[feat](trx-frontend): rich popup for receiver/owner marker on map
Replace plain-text receiver marker popup with a styled info card matching the APRS popup layout. Shows callsign, trx-server version and build date, owner callsign (when different), QTH coordinates, and all configured rigs with manufacturer/model; active rig is badged. Rig data (manufacturer, model, display_name, active state) is stored in serverRigs/serverActiveRigId on each /rigs refresh. Popup content is rebuilt live on popupopen so it always reflects current state. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Signed-off-by: Stan Grams <sjg@haxx.space>
This commit is contained in:
@@ -639,6 +639,8 @@ async function refreshRigList() {
|
|||||||
displayNames[r.rig_id] = r.rig_id;
|
displayNames[r.rig_id] = r.rig_id;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
serverRigs = rigs;
|
||||||
|
serverActiveRigId = data.active_rig_id || null;
|
||||||
applyRigList(data.active_rig_id, rigIds, displayNames);
|
applyRigList(data.active_rig_id, rigIds, displayNames);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Non-fatal: SSE/status path still drives main UI.
|
// Non-fatal: SSE/status path still drives main UI.
|
||||||
@@ -1245,6 +1247,8 @@ let serverVersion = null;
|
|||||||
let serverBuildDate = null;
|
let serverBuildDate = null;
|
||||||
let serverCallsign = null;
|
let serverCallsign = null;
|
||||||
let ownerCallsign = null;
|
let ownerCallsign = null;
|
||||||
|
let serverRigs = [];
|
||||||
|
let serverActiveRigId = null;
|
||||||
let serverLat = null;
|
let serverLat = null;
|
||||||
let serverLon = null;
|
let serverLon = null;
|
||||||
let initialMapZoom = 10;
|
let initialMapZoom = 10;
|
||||||
@@ -2442,17 +2446,21 @@ function initAprsMap() {
|
|||||||
updateMapBaseLayerForTheme(currentTheme());
|
updateMapBaseLayerForTheme(currentTheme());
|
||||||
|
|
||||||
if (hasLocation) {
|
if (hasLocation) {
|
||||||
const popupText = serverCallsign ? serverCallsign : "Receiver";
|
|
||||||
aprsMapReceiverMarker = L.circleMarker([serverLat, serverLon], {
|
aprsMapReceiverMarker = L.circleMarker([serverLat, serverLon], {
|
||||||
radius: 8, color: "#3388ff", fillColor: "#3388ff", fillOpacity: 0.8
|
radius: 8, color: "#3388ff", fillColor: "#3388ff", fillOpacity: 0.8
|
||||||
}).addTo(aprsMap).bindPopup(popupText);
|
}).addTo(aprsMap).bindPopup("");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rebuild APRS popup content on open so age and distance are always fresh
|
// Rebuild popup content on open (keeps age/distance/rig list fresh)
|
||||||
// and draw an animated radio path from receiver to the station
|
|
||||||
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 (marker === aprsMapReceiverMarker) {
|
||||||
|
e.popup.setContent(buildReceiverPopupHtml());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!marker || !marker._aprsCall) return;
|
if (!marker || !marker._aprsCall) return;
|
||||||
const entry = stationMarkers.get(marker._aprsCall);
|
const entry = stationMarkers.get(marker._aprsCall);
|
||||||
if (!entry) return;
|
if (!entry) return;
|
||||||
@@ -2572,6 +2580,33 @@ function formatTimeAgo(tsMs) {
|
|||||||
return remMins > 0 ? `${hrs}h ${remMins}min ago` : `${hrs}h ago`;
|
return remMins > 0 ? `${hrs}h ${remMins}min ago` : `${hrs}h ago`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function buildReceiverPopupHtml() {
|
||||||
|
const call = serverCallsign || ownerCallsign || "Receiver";
|
||||||
|
let meta = "";
|
||||||
|
if (serverVersion) {
|
||||||
|
meta = `trx-server v${escapeMapHtml(serverVersion)}`;
|
||||||
|
if (serverBuildDate) meta += ` · ${escapeMapHtml(serverBuildDate)}`;
|
||||||
|
}
|
||||||
|
let rows = "";
|
||||||
|
if (ownerCallsign && ownerCallsign !== serverCallsign) {
|
||||||
|
rows += `<tr><td class="aprs-popup-label">Owner</td><td>${escapeMapHtml(ownerCallsign)}</td></tr>`;
|
||||||
|
}
|
||||||
|
if (serverLat != null && serverLon != null) {
|
||||||
|
rows += `<tr><td class="aprs-popup-label">QTH</td><td>${serverLat.toFixed(5)}, ${serverLon.toFixed(5)}</td></tr>`;
|
||||||
|
}
|
||||||
|
for (const rig of serverRigs) {
|
||||||
|
const name = rig.display_name || `${rig.manufacturer} ${rig.model}`.trim();
|
||||||
|
const active = rig.rig_id === serverActiveRigId
|
||||||
|
? ` <span class="receiver-popup-active">active</span>` : "";
|
||||||
|
rows += `<tr><td class="aprs-popup-label">Rig</td><td>${escapeMapHtml(name)}${active}</td></tr>`;
|
||||||
|
}
|
||||||
|
return `<div class="aprs-popup">` +
|
||||||
|
`<div class="aprs-popup-call">${escapeMapHtml(call)}</div>` +
|
||||||
|
(meta ? `<div class="aprs-popup-meta">${meta}</div>` : "") +
|
||||||
|
(rows ? `<table class="aprs-popup-table">${rows}</table>` : "") +
|
||||||
|
`</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
function buildAprsPopupHtml(call, lat, lon, info, pkt) {
|
function buildAprsPopupHtml(call, lat, lon, info, pkt) {
|
||||||
const age = pkt?._tsMs ? formatTimeAgo(pkt._tsMs) : (pkt?._ts || null);
|
const age = pkt?._tsMs ? formatTimeAgo(pkt._tsMs) : (pkt?._ts || null);
|
||||||
const distKm = (serverLat != null && serverLon != null)
|
const distKm = (serverLat != null && serverLon != null)
|
||||||
|
|||||||
Reference in New Issue
Block a user