[feat](trx-frontend-http): add strongest decoded signal summary to map

Add a new "Strongest decoded signal" section below the existing
"Longest decode paths" section on the map tab, showing the top 5
stations with the highest SNR across the current map history window.
Reuses existing map-qso-card styling with SNR displayed in place of
distance.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
This commit is contained in:
2026-03-17 01:12:15 +01:00
parent 4c69273584
commit 088f05050c
2 changed files with 119 additions and 0 deletions
@@ -4789,6 +4789,7 @@ function syncDecodeContactPathVisibility() {
ensureDecodeContactPathRendered(entry); ensureDecodeContactPathRendered(entry);
} }
renderMapQsoSummary(); renderMapQsoSummary();
renderMapSignalSummary();
updateMapPathsAnimationClass(); updateMapPathsAnimationClass();
} }
@@ -6491,6 +6492,115 @@ function renderMapQsoSummary() {
listEl.replaceChildren(fragment); listEl.replaceChildren(fragment);
} }
function renderMapSignalSummary() {
const listEl = document.getElementById("map-signal-summary-list");
if (!listEl) return;
const bestByStation = new Map();
for (const entry of locatorMarkers.values()) {
if (!entry || (entry.sourceType !== "ft8" && entry.sourceType !== "ft4" && entry.sourceType !== "ft2" && entry.sourceType !== "wspr")) continue;
if (!(entry.stationDetails instanceof Map)) continue;
for (const detail of entry.stationDetails.values()) {
if (!Number.isFinite(detail?.snr_db)) continue;
const station = String(detail?.source || detail?.station || "").trim().toUpperCase();
if (!station) continue;
const snrDb = Number(detail.snr_db);
const tsMs = Number.isFinite(detail?.ts_ms) ? Number(detail.ts_ms) : 0;
const prev = bestByStation.get(station);
if (!prev || snrDb > prev.snrDb || (snrDb === prev.snrDb && tsMs > prev.tsMs)) {
bestByStation.set(station, {
station,
snrDb,
tsMs,
grid: entry.grid,
sourceType: entry.sourceType,
bandLabel: bandForHz(Number(detail?.freq_hz))?.label || null,
});
}
}
}
const entries = Array.from(bestByStation.values())
.sort((a, b) => {
const delta = b.snrDb - a.snrDb;
if (Math.abs(delta) > 0.001) return delta;
return b.tsMs - a.tsMs;
})
.slice(0, MAP_QSO_SUMMARY_LIMIT);
if (entries.length === 0) {
const empty = document.createElement("div");
empty.className = "map-qso-summary-empty";
empty.textContent = "No decoded signals with SNR data in the current map history.";
listEl.replaceChildren(empty);
return;
}
const fragment = document.createDocumentFragment();
entries.forEach((entry, index) => {
const card = document.createElement("button");
card.type = "button";
card.className = "map-qso-card";
const head = document.createElement("div");
head.className = "map-qso-card-head";
const rank = document.createElement("span");
rank.className = "map-qso-card-rank";
rank.textContent = `#${index + 1}`;
head.appendChild(rank);
const snr = document.createElement("span");
snr.className = "map-qso-card-distance";
snr.textContent = `${entry.snrDb >= 0 ? "+" : ""}${entry.snrDb.toFixed(0)} dB`;
head.appendChild(snr);
const body = document.createElement("div");
body.className = "map-qso-card-body";
const pair = document.createElement("div");
pair.className = "map-qso-card-pair";
pair.textContent = entry.station;
body.appendChild(pair);
const meta = document.createElement("div");
meta.className = "map-qso-card-meta";
const sourceType = document.createElement("span");
sourceType.className = "map-qso-card-pill";
sourceType.textContent = String(entry.sourceType || "ft8").toUpperCase();
meta.appendChild(sourceType);
if (entry.bandLabel) {
const band = document.createElement("span");
band.className = "map-qso-card-pill map-qso-card-band";
band.style.setProperty("--band-color", locatorBandChipColor(entry.bandLabel));
band.textContent = entry.bandLabel;
meta.appendChild(band);
}
const ageText = formatTimeAgo(Number(entry.tsMs));
if (ageText) {
const age = document.createElement("span");
age.className = "map-qso-card-pill";
age.textContent = ageText;
meta.appendChild(age);
}
body.appendChild(meta);
const grids = document.createElement("div");
grids.className = "map-qso-card-grids";
grids.textContent = entry.grid || "--";
body.appendChild(grids);
card.appendChild(head);
card.appendChild(body);
fragment.appendChild(card);
});
listEl.replaceChildren(fragment);
}
function buildBookmarkLocatorPopupHtml(grid, bookmarks) { function buildBookmarkLocatorPopupHtml(grid, bookmarks) {
const list = Array.isArray(bookmarks) ? bookmarks : []; const list = Array.isArray(bookmarks) ? bookmarks : [];
const rows = list const rows = list
@@ -782,6 +782,15 @@
</div> </div>
<div id="map-qso-summary-list" class="map-qso-summary-list"></div> <div id="map-qso-summary-list" class="map-qso-summary-list"></div>
</section> </section>
<section class="map-qso-summary" aria-labelledby="map-signal-summary-title">
<div class="map-qso-summary-head">
<div>
<div id="map-signal-summary-title" class="map-qso-summary-title">Strongest decoded signal</div>
<div class="map-qso-summary-subtitle">Top 5 strongest signals in the current map history</div>
</div>
</div>
<div id="map-signal-summary-list" class="map-qso-summary-list"></div>
</section>
</div> </div>
<div id="tab-settings" class="tab-panel" style="display:none;"> <div id="tab-settings" class="tab-panel" style="display:none;">
<div class="sub-tab-bar"> <div class="sub-tab-bar">