[feat](trx-frontend-http): intercept freq/mode changes for virtual channels

When on a non-primary (virtual) channel, redirect freq and mode changes
to the channel metadata API instead of the server:

- vchan.js: add vchanIsOnVirtual(), vchanSetChannelFreq/Mode(); expose
  window.vchanInterceptMode() hook; wrap window.setRigFrequency so all
  callers (jog, freq input, bookmarks, spectrum click) are automatically
  redirected without modification
- app.js: check vchanInterceptMode() in applyModeFromPicker() before
  posting /set_mode
- bookmarks.js: check vchanInterceptMode() for mode in bmApply();
  setRigFrequency() redirect is automatic via the vchan.js wrapper;
  bandwidth and decoder toggles still apply regardless of channel

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
This commit is contained in:
2026-03-11 07:08:36 +01:00
parent 832e42ca3b
commit cef1741e40
3 changed files with 79 additions and 2 deletions
@@ -3119,6 +3119,10 @@ async function applyModeFromPicker() {
modeEl.disabled = true;
showHint("Setting mode…");
try {
if (typeof vchanInterceptMode === "function" && await vchanInterceptMode(mode)) {
showHint("Channel mode set", 1500);
return;
}
await postPath(`/set_mode?mode=${encodeURIComponent(mode)}`);
showHint("Mode set", 1500);
if (mode.toUpperCase() === "WFM") {
@@ -277,7 +277,11 @@ async function bmDelete(id) {
async function bmApply(bm) {
try {
const onVirtual = typeof vchanInterceptMode === "function"
&& await vchanInterceptMode(bm.mode);
if (!onVirtual) {
await postPath("/set_mode?mode=" + encodeURIComponent(bm.mode));
}
if (typeof modeEl !== "undefined" && modeEl) {
modeEl.value = String(bm.mode || "").toUpperCase();
}
@@ -290,12 +294,14 @@ async function bmApply(bm) {
syncBandwidthInput(bm.bandwidth_hz);
}
}
// setRigFrequency is wrapped by vchan.js to redirect to the channel API
// when on a virtual channel, so this call works correctly in both cases.
if (typeof setRigFrequency === "function") {
await setRigFrequency(bm.freq_hz);
} else {
await postPath("/set_freq?hz=" + bm.freq_hz);
}
// Toggle decoders when in DIG mode
// Toggle decoders when in DIG mode.
if (bm.mode === "DIG" && Array.isArray(bm.decoders)) {
const statusResp = await fetch("/status");
if (statusResp.ok) {
@@ -168,3 +168,70 @@ function vchanApplyCapabilities(caps) {
if (!row) return;
row.style.display = (caps && caps.filter_controls) ? "" : "none";
}
// ---------------------------------------------------------------------------
// Freq / mode interception
// ---------------------------------------------------------------------------
// Returns true when the active channel is a non-primary (virtual) channel.
function vchanIsOnVirtual() {
if (!vchanActiveId || vchanChannels.length === 0) return false;
return vchanActiveId !== vchanChannels[0].id;
}
async function vchanSetChannelFreq(freqHz) {
if (!vchanRigId || !vchanActiveId) return;
try {
const resp = await fetch(
`/channels/${encodeURIComponent(vchanRigId)}/${encodeURIComponent(vchanActiveId)}/freq`,
{
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ freq_hz: Math.round(freqHz) }),
}
);
if (!resp.ok) console.warn("vchan: set freq failed", resp.status);
} catch (e) {
console.error("vchan: set freq error", e);
}
}
async function vchanSetChannelMode(mode) {
if (!vchanRigId || !vchanActiveId) return;
try {
const resp = await fetch(
`/channels/${encodeURIComponent(vchanRigId)}/${encodeURIComponent(vchanActiveId)}/mode`,
{
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ mode }),
}
);
if (!resp.ok) console.warn("vchan: set mode failed", resp.status);
} catch (e) {
console.error("vchan: set mode error", e);
}
}
// Called by app.js (applyModeFromPicker) and bookmarks.js (bmApply) before
// sending /set_mode to the server. Returns true if the change was handled
// by the virtual channel (caller should skip the server request).
window.vchanInterceptMode = async function(mode) {
if (!vchanIsOnVirtual()) return false;
await vchanSetChannelMode(mode);
return true;
};
// Wrap setRigFrequency (defined in app.js, loaded before this file) so that
// frequency changes are redirected to the active virtual channel instead of
// the server when on a non-primary channel.
(function() {
const _orig = window.setRigFrequency;
window.setRigFrequency = async function(freqHz) {
if (vchanIsOnVirtual()) {
await vchanSetChannelFreq(freqHz);
return;
}
if (typeof _orig === "function") return _orig(freqHz);
};
})();