[feat](trx-frontend-http): vchan freq display sync, BW accent, scheduler multi-channel

Virtual channel display:
- vchan.js: wrap refreshFreqDisplay() so the main freq field always shows
  the active virtual channel's frequency instead of channel 0's; expose
  vchanSyncAccentUI() to add vchan-ch-active CSS class (colored border) to
  #freq and #spectrum-bw-input when on a non-primary channel
- style.css: --vchan-color (#38bdf8 sky-blue), .vchan-ch-active box-shadow,
  vchan-picker active button left-border accent

Scheduler multi-channel slots:
- scheduler.rs: add center_hz (Option<u64>) and bookmark_ids (Vec<String>)
  to ScheduleEntry; SchedulerStatus gains last_center_hz and
  last_bookmark_ids; background task sends SetCenterFreq before SetFreq
  when center_hz is set and records extra bookmark_ids in status
- scheduler.js: center-freq input and extra-channel bookmark picker (tag
  list with + / × buttons) in the add-entry form; extra channels shown in
  the entries table
- index.html: center freq field + extra bookmark picker widgets; table
  gains Center freq and Extra channels columns

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:22:36 +01:00
parent cef1741e40
commit af45c32222
5 changed files with 209 additions and 16 deletions
@@ -90,6 +90,8 @@ function vchanRender() {
addBtn.title = "Allocate new virtual channel at current frequency";
addBtn.addEventListener("click", vchanAllocate);
picker.appendChild(addBtn);
vchanSyncAccentUI();
}
async function vchanAllocate() {
@@ -170,7 +172,7 @@ function vchanApplyCapabilities(caps) {
}
// ---------------------------------------------------------------------------
// Freq / mode interception
// Freq / mode interception + UI accent
// ---------------------------------------------------------------------------
// Returns true when the active channel is a non-primary (virtual) channel.
@@ -179,6 +181,40 @@ function vchanIsOnVirtual() {
return vchanActiveId !== vchanChannels[0].id;
}
function vchanActiveChannel() {
return vchanChannels.find(c => c.id === vchanActiveId) || null;
}
// Update the main freq input to show the virtual channel's frequency.
function vchanUpdateFreqDisplay() {
const ch = vchanActiveChannel();
if (!ch) return;
const el = document.getElementById("freq");
if (!el) return;
if (typeof formatFreqForStep === "function" && typeof jogUnit !== "undefined") {
el.value = formatFreqForStep(ch.freq_hz, jogUnit);
} else {
el.value = (ch.freq_hz / 1e6).toFixed(6).replace(/\.?0+$/, "");
}
}
// Add / remove the vchan accent class from the freq and BW inputs.
function vchanSyncAccentUI() {
const onVirtual = vchanIsOnVirtual();
const freqEl = document.getElementById("freq");
const bwEl = document.getElementById("spectrum-bw-input");
if (freqEl) freqEl.classList.toggle("vchan-ch-active", onVirtual);
if (bwEl) bwEl.classList.toggle("vchan-ch-active", onVirtual);
if (onVirtual) {
vchanUpdateFreqDisplay();
} else if (typeof _origRefreshFreqDisplay === "function") {
_origRefreshFreqDisplay();
}
}
// Saved reference to the original refreshFreqDisplay from app.js.
let _origRefreshFreqDisplay = null;
async function vchanSetChannelFreq(freqHz) {
if (!vchanRigId || !vchanActiveId) return;
try {
@@ -235,3 +271,17 @@ window.vchanInterceptMode = async function(mode) {
if (typeof _orig === "function") return _orig(freqHz);
};
})();
// Wrap refreshFreqDisplay so the main freq field stays in sync with the
// active virtual channel's frequency (SSE rig-state updates would otherwise
// constantly overwrite it with channel 0's freq).
(function() {
_origRefreshFreqDisplay = window.refreshFreqDisplay;
window.refreshFreqDisplay = function() {
if (vchanIsOnVirtual()) {
vchanUpdateFreqDisplay();
return;
}
if (typeof _origRefreshFreqDisplay === "function") _origRefreshFreqDisplay();
};
})();