[feat](trx-frontend-http): rename Decoders tab to Digital modes and filter by active rig

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
This commit is contained in:
2026-03-26 19:44:01 +01:00
parent 1a744e427a
commit 5be4019c04
5 changed files with 26 additions and 16 deletions
@@ -3490,7 +3490,8 @@ async function switchRigFromSelect(selectEl) {
if (typeof setSchedulerRig === "function") setSchedulerRig(lastActiveRigId);
if (typeof setBackgroundDecodeRig === "function") setBackgroundDecodeRig(lastActiveRigId);
if (typeof bmFetch === "function") bmFetch(document.getElementById("bm-category-filter")?.value || "");
// Decode SSE and history are rig-independent — no reconnect needed.
// Reconnect decode stream so history + live messages filter to the new rig.
connectDecode();
// Switch this session's rig and reconnect SSE to the new rig's
// state channel.
try {
@@ -4018,11 +4019,11 @@ if (spectrumBwSweetBtn) {
}
// --- Tab navigation ---
const TAB_ORDER = ["main", "bookmarks", "decoders", "map", "settings", "about"];
const TAB_ORDER = ["main", "bookmarks", "digital-modes", "map", "settings", "about"];
const TAB_PATHS = {
main: "/",
bookmarks: "/bookmarks",
decoders: "/decoders",
"digital-modes": "/digital-modes",
map: "/map",
settings: "/settings",
about: "/about",
@@ -8196,6 +8197,7 @@ function updateDecodeStatus(text) {
if (ft2 && ft2.textContent !== "Receiving") ft2.textContent = text;
}
function dispatchDecodeMessage(msg) {
if (lastActiveRigId && msg.rig_id && msg.rig_id !== lastActiveRigId) return;
if (msg.type === "ais" && window.onServerAis) window.onServerAis(msg);
if (msg.type === "vdes" && window.onServerVdes) window.onServerVdes(msg);
if (msg.type === "aprs" && window.onServerAprs) window.onServerAprs(msg);
@@ -8209,6 +8211,10 @@ function dispatchDecodeMessage(msg) {
function dispatchDecodeBatch(batch) {
if (!Array.isArray(batch) || batch.length === 0) return;
if (lastActiveRigId) {
batch = batch.filter((m) => !m.rig_id || m.rig_id === lastActiveRigId);
if (batch.length === 0) return;
}
const type = String(batch[0]?.type || "");
const uniformType = batch.every((msg) => String(msg?.type || "") === type);
if (uniformType) {
@@ -8270,6 +8276,7 @@ function scheduleDecodeHistoryDrainStep(callback) {
}
function decodeHistoryUrl() {
if (lastActiveRigId) return "/decode/history?remote=" + encodeURIComponent(lastActiveRigId);
return "/decode/history";
}
@@ -39,9 +39,9 @@
<svg class="tab-icon" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 2h8v12l-4-2.5L4 14V2z"/></svg>
<span class="tab-label">Bookmarks</span>
</button>
<button class="tab" data-tab="decoders">
<button class="tab" data-tab="digital-modes">
<svg class="tab-icon" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true"><rect x="1" y="11" width="2.5" height="4" rx="0.5"/><rect x="4.75" y="8" width="2.5" height="7" rx="0.5"/><rect x="8.5" y="5" width="2.5" height="10" rx="0.5"/><rect x="12.25" y="2" width="2.5" height="13" rx="0.5"/></svg>
<span class="tab-label">Decoders</span>
<span class="tab-label">Digital modes</span>
</button>
<button class="tab" data-tab="map">
<svg class="tab-icon" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M8 2a4 4 0 0 1 4 4c0 3-4 8-4 8S4 9 4 6a4 4 0 0 1 4-4z"/><circle cx="8" cy="6" r="1.2" fill="currentColor" stroke="none"/></svg>
@@ -419,7 +419,7 @@
<label class="bm-label">Locator
<input type="text" id="bm-locator" class="status-input" maxlength="6" placeholder="e.g. JO93" />
</label>
<div class="bm-label">Decoders
<div class="bm-label">Digital modes
<div class="bm-decoder-checks">
<label class="bm-decoder-check"><input type="checkbox" id="bm-dec-aprs" value="aprs" /> APRS</label>
<label class="bm-decoder-check"><input type="checkbox" id="bm-dec-ais" value="ais" /> AIS</label>
@@ -451,7 +451,7 @@
<th>BW</th>
<th>Locator</th>
<th>Category</th>
<th>Decoders</th>
<th>Digital modes</th>
<th>Comment</th>
<th>Actions</th>
</tr>
@@ -469,7 +469,7 @@
</div>
</div>
</div>
<div id="tab-decoders" class="tab-panel" style="display:none;">
<div id="tab-digital-modes" class="tab-panel" style="display:none;">
<div class="sub-tab-bar">
<button class="sub-tab active" data-subtab="overview">Overview</button>
<button class="sub-tab" data-subtab="ais">AIS</button>
@@ -2617,7 +2617,7 @@ button:focus-visible, input:focus-visible, select:focus-visible {
.tab-icon { display: block; }
/* Shorten long tab labels to keep bottom nav compact */
.tab[data-tab="bookmarks"] .tab-label { font-size: 0.6rem; }
.tab[data-tab="decoders"] .tab-label { font-size: 0.6rem; }
.tab[data-tab="digital-modes"] .tab-label { font-size: 0.6rem; }
.top-bar-actions {
width: 100%;
justify-content: flex-start;
@@ -2639,7 +2639,7 @@ button:focus-visible, input:focus-visible, select:focus-visible {
min-width: 0;
}
#tab-main,
#tab-decoders,
#tab-digital-modes,
#tab-map,
#tab-about {
scroll-margin-top: 0.75rem;
@@ -2917,7 +2917,7 @@ button:focus-visible, input:focus-visible, select:focus-visible {
.bm-col-bw::before { content: "Bandwidth"; }
.bm-col-loc::before { content: "Locator"; }
.bm-col-cat::before { content: "Category"; }
.bm-col-dec::before { content: "Decoders"; }
.bm-col-dec::before { content: "Digital modes"; }
.bm-col-cmt { grid-column: 1 / -1; }
.bm-col-cmt::before { content: "Comment"; }
.bm-col-act { grid-column: 1 / -1; display: flex; flex-wrap: wrap; gap: 0.4rem; padding-top: 0.25rem; }
@@ -1945,7 +1945,7 @@ pub async fn set_vchan_mode(
pub fn configure(cfg: &mut web::ServiceConfig) {
cfg.service(index)
.service(map_index)
.service(decoders_index)
.service(digital_modes_index)
.service(settings_index)
.service(about_index)
.service(status_api)
@@ -2057,8 +2057,8 @@ async fn map_index() -> impl Responder {
no_cache_response("text/html; charset=utf-8", status::index_html())
}
#[get("/decoders")]
async fn decoders_index() -> impl Responder {
#[get("/digital-modes")]
async fn digital_modes_index() -> impl Responder {
no_cache_response("text/html; charset=utf-8", status::index_html())
}
@@ -477,7 +477,7 @@ impl RouteAccess {
if path == "/"
|| path == "/index.html"
|| path == "/map"
|| path == "/decoders"
|| path == "/digital-modes"
|| path == "/settings"
|| path == "/about"
|| path.starts_with("/auth/")
@@ -666,7 +666,10 @@ mod tests {
fn test_route_access_public_paths() {
assert_eq!(RouteAccess::from_path("/"), RouteAccess::Public);
assert_eq!(RouteAccess::from_path("/map"), RouteAccess::Public);
assert_eq!(RouteAccess::from_path("/decoders"), RouteAccess::Public);
assert_eq!(
RouteAccess::from_path("/digital-modes"),
RouteAccess::Public
);
assert_eq!(RouteAccess::from_path("/settings"), RouteAccess::Public);
assert_eq!(RouteAccess::from_path("/about"), RouteAccess::Public);
assert_eq!(RouteAccess::from_path("/auth/login"), RouteAccess::Public);