|
|
|
@@ -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) {
|
|
|
|
|