fix(ui): make rig switching atomic and visible

This commit is contained in:
sjg
2026-08-01 11:15:39 +02:00
parent d07dbdc645
commit af74430551
4 changed files with 89 additions and 27 deletions
@@ -443,6 +443,7 @@ const signalSplitValueEl = document.getElementById("signal-split-value");
const overviewPeakHoldEl = document.getElementById("overview-peak-hold");
const themeToggleBtn = document.getElementById("theme-toggle");
const headerRigSwitchSelect = document.getElementById("header-rig-switch-select");
const headerRigSummary = document.getElementById("header-rig-summary");
const headerStylePickSelect = document.getElementById("header-style-pick-select");
const rdsPsOverlay = document.getElementById("rds-ps-overlay");
const tabMainEl = document.getElementById("tab-main");
@@ -902,6 +903,7 @@ async function restorePreviousTuneState() {
let lastRigIds = [];
let lastRigDisplayNames = {};
let lastActiveRigId = null;
let rigSwitchInProgress = false;
let lastCityLabel = "";
let sseSessionId = null;
const originalTitle = document.title;
@@ -1246,6 +1248,20 @@ function populateRigPicker(selectEl, rigIds, activeRigId, disabled) {
selectEl.disabled = disabled;
}
function updateRigIdentitySummary(rigId, pending = false) {
if (!headerRigSummary) return;
const rig = serverRigs.find((entry) => entry?.remote === rigId);
if (!rig) {
headerRigSummary.textContent = pending ? "Switching rigs…" : "No rig details available";
return;
}
const hardware = [rig.manufacturer, rig.model].map(value => String(value || "").trim()).filter(Boolean).join(" ") || rig.remote;
const modes = Array.isArray(rig.supported_modes) ? rig.supported_modes.map(normalizeMode).filter(Boolean) : [];
const features = [rig.tx ? "TX" : "RX", rig.filter_controls ? "SDR filters" : null, ...modes.slice(0, 5)];
if (modes.length > 5) features.push(`+${modes.length - 5} modes`);
headerRigSummary.textContent = `${pending ? "Switching to " : ""}${hardware} · ${features.filter(Boolean).join(" · ")}`;
}
function updateRigSubtitle(activeRigId) {
if (!rigSubtitle) return;
const name = (activeRigId && lastRigDisplayNames[activeRigId]) || activeRigId || "--";
@@ -1282,6 +1298,7 @@ function applyRigList(activeRigId, rigIds, displayNames) {
const disableSwitch = lastRigIds.length === 0 || !authRole || authRole === "rx";
populateRigPicker(headerRigSwitchSelect, lastRigIds, lastActiveRigId, disableSwitch);
updateRigSubtitle(lastActiveRigId);
updateRigIdentitySummary(lastActiveRigId);
window.trxUi?.setActiveRig(lastActiveRigId);
if (rigListChanged) {
if (typeof setSchedulerRig === "function") setSchedulerRig(lastActiveRigId);
@@ -3881,12 +3898,16 @@ function scheduleUiFrameJob(key, job) {
window.trxScheduleUiFrameJob = scheduleUiFrameJob;
async function postPath(path) {
async function postPath(path, options = {}) {
if (rigSwitchInProgress && !options.allowDuringRigSwitch) {
throw new Error("Wait for the rig switch to finish");
}
const targetRigId = options.remote === undefined ? lastActiveRigId : options.remote;
// Auto-append remote so each tab targets its own rig.
// Skip when the caller already included remote (e.g. /select_rig).
if (lastActiveRigId && !path.includes("remote=")) {
if (targetRigId && !path.includes("remote=")) {
const sep = path.includes("?") ? "&" : "?";
path = `${path}${sep}remote=${encodeURIComponent(lastActiveRigId)}`;
path = `${path}${sep}remote=${encodeURIComponent(targetRigId)}`;
}
const resp = await fetch(path, { method: "POST" });
if (authEnabled && resp.status === 401) {
@@ -3931,36 +3952,45 @@ async function switchRigFromSelect(selectEl) {
return;
}
const prevRig = lastActiveRigId;
lastActiveRigId = selectEl.value;
if (prevRig && prevRig !== lastActiveRigId) {
resetDecoderStateOnRigSwitch();
}
updateRigSubtitle(lastActiveRigId);
if (typeof setSchedulerRig === "function") setSchedulerRig(lastActiveRigId);
if (typeof setBackgroundDecodeRig === "function") setBackgroundDecodeRig(lastActiveRigId);
if (typeof bmFetch === "function") bmFetch(document.getElementById("bm-category-filter")?.value || "");
window.trx.modules.map?.syncAprsReceiverMarker();
// Switch this session's rig and reconnect SSE to the new rig's
// state channel.
const nextRig = selectEl.value;
if (nextRig === prevRig || rigSwitchInProgress) return;
rigSwitchInProgress = true;
setControlPending(selectEl, true);
selectEl.closest(".header-rig-switch")?.classList.add("is-switching");
updateRigIdentitySummary(nextRig, true);
showHint(`Switching to ${lastRigDisplayNames[nextRig] || nextRig}`);
try {
const sidParam = sseSessionId ? `&session_id=${encodeURIComponent(sseSessionId)}` : "";
await postPath(`/select_rig?remote=${encodeURIComponent(selectEl.value)}${sidParam}`);
await postPath(`/select_rig?remote=${encodeURIComponent(nextRig)}${sidParam}`, { allowDuringRigSwitch: true, remote: null });
lastActiveRigId = nextRig;
resetDecoderStateOnRigSwitch();
updateRigSubtitle(lastActiveRigId);
updateRigIdentitySummary(lastActiveRigId);
window.trxUi?.setActiveRig(lastActiveRigId);
if (typeof setSchedulerRig === "function") setSchedulerRig(lastActiveRigId);
if (typeof setBackgroundDecodeRig === "function") setBackgroundDecodeRig(lastActiveRigId);
if (typeof bmFetch === "function") bmFetch(document.getElementById("bm-category-filter")?.value || "");
window.trx.modules.map?.syncAprsReceiverMarker();
connect();
stopSpectrumStreaming();
startSpectrumStreaming();
stopMeterStreaming();
startMeterStreaming();
if (rxActive) {
stopRxAudio();
startRxAudio();
}
showHint(`Rig: ${lastRigDisplayNames[lastActiveRigId] || lastActiveRigId}`, 1500);
} catch (err) {
console.error("select_rig failed:", err);
selectEl.value = prevRig || "";
updateRigIdentitySummary(prevRig);
window.trxUi?.notify("Rig could not be switched", { kind: "error" });
} finally {
rigSwitchInProgress = false;
setControlPending(selectEl, false);
selectEl.closest(".header-rig-switch")?.classList.remove("is-switching");
}
// Reconnect spectrum SSE to the new rig's spectrum channel.
stopSpectrumStreaming();
startSpectrumStreaming();
// Reconnect meter SSE to the new rig's meter channel.
stopMeterStreaming();
startMeterStreaming();
// Reconnect audio to the new rig if audio is active.
if (rxActive) {
stopRxAudio();
startRxAudio();
}
showHint(`Rig: ${lastActiveRigId}`, 1500);
}
if (headerRigSwitchSelect) {