[feat](trx-frontend-http): rebuild the general radio controls row
CI / lint (push) Successful in 2m17s
CI / test (push) Successful in 8m8s
CI / frontend (push) Failing after 36s
CI / reuse (push) Successful in 4s

Mode was a full-width select: 483px of the row to display "FM".  The
modes are three or four characters and there are at most twelve, so they
become a segmented group like the Unit and Step Scale pickers beside
them — a third of the width, and one click instead of two.

The <select> stays as the mode's value.  A dozen call sites and several
plugins read #mode.value, so replacing it outright would have reached
much further than a layout change should; it is hidden from sight and
from assistive tech, the buttons write to it, and everything downstream
runs unchanged.  Every writer re-syncs the buttons, the plugins through
a new trxCore.syncModePicker.

The row itself was a grid with a track per column, but the WFM, SAM and
transmit columns are hidden on most rigs, so it ended in some 500px of
hole.  It packs left now.  Same fault one level down: the power buttons
sat in three fixed tracks, so a rig with neither transmit nor lock kept
two empty ones and left its label chip stranded at the far edge.

Unit and Step Scale move out of the frequency row and in beside the
wheel and the +/- they modify, which were some 600px away.

Signed-off-by: Stan Grams <sjg@haxx.space>
This commit is contained in:
sjg
2026-08-03 22:09:08 +02:00
parent 23cc0db1fa
commit e70e82c8c0
13 changed files with 230 additions and 44 deletions
@@ -2293,6 +2293,7 @@ async function restorePreviousTuneState() {
savePreviousTuneState();
if (saved.mode && modeEl && modeEl.value !== saved.mode) {
modeEl.value = saved.mode;
syncModePicker();
await postPath(`/set_mode?mode=${encodeURIComponent(saved.mode)}`);
updateWfmControls();
}
@@ -4127,6 +4128,7 @@ function setDisabled(disabled) {
[freqEl, centerFreqEl, modeEl, pttBtn, powerBtn, txLimitInput, txLimitBtn, lockBtn].forEach((el) => {
if (el) el.disabled = disabled;
});
syncModePicker();
}
var serverVersion = null;
var serverBuildDate = null;
@@ -4458,6 +4460,7 @@ function render(update) {
opt.textContent = m;
modeEl.appendChild(opt);
});
renderModePicker();
}
}
if (update.info && update.info.capabilities) {
@@ -4584,6 +4587,7 @@ function render(update) {
const onVirtual = window.trx.modules.vchan?.isOnVirtual() === true;
if (!onVirtual) {
modeEl.value = modeUpper2;
syncModePicker();
if (modeUpper2 === "WFM" && lastModeName !== "WFM") {
setJogDivisor(10);
resetRdsDisplay();
@@ -5341,6 +5345,33 @@ if (jogMultEl) {
}
jogStep = Math.max(Math.round(jogUnit / jogMult), minFreqStepHz);
}
var modePickerEl = requiredElement("mode-picker");
function renderModePicker() {
modePickerEl.replaceChildren();
for (const option of Array.from(modeEl.options)) {
const btn = document.createElement("button");
btn.type = "button";
btn.dataset.mode = option.value;
btn.textContent = option.textContent;
btn.addEventListener("click", () => {
if (btn.disabled || modeEl.value === option.value) return;
modeEl.value = option.value;
syncModePicker();
void applyModeFromPicker();
});
modePickerEl.appendChild(btn);
}
syncModePicker();
}
function syncModePicker() {
const active = (modeEl.value || "").toUpperCase();
modePickerEl.querySelectorAll("button").forEach((btn) => {
const selected = (btn.dataset.mode || "").toUpperCase() === active;
btn.classList.toggle("active", selected);
btn.disabled = modeEl.disabled;
btn.setAttribute("aria-pressed", String(selected));
});
}
async function applyModeFromPicker() {
const mode = modeEl.value || "";
if (!mode) {
@@ -5349,6 +5380,7 @@ async function applyModeFromPicker() {
}
updateWfmControls();
setControlPending(modeEl, true);
syncModePicker();
showHint("Setting mode…");
try {
if (await window.trx.modules.vchan?.interceptMode(mode)) {
@@ -5366,6 +5398,7 @@ async function applyModeFromPicker() {
console.error(err);
} finally {
setControlPending(modeEl, false);
syncModePicker();
}
}
modeEl.addEventListener("change", applyModeFromPicker);
@@ -5955,6 +5988,7 @@ var trxCore = Object.freeze({
syncBandwidthInput,
scheduleSpectrumDraw,
onDecoderRegistryReady,
syncModePicker,
formatFreqForStep: formatFrequencyForStep,
refreshFreqDisplay,
setJogDivisor,
@@ -328,6 +328,7 @@ function bmApply(bm) {
const modeEl = document.getElementById("mode");
if (modeEl) {
modeEl.value = (bm.mode || "").toUpperCase();
hostCore.syncModePicker();
}
if (bm.bandwidth_hz) {
hostState.currentBandwidthHz = bm.bandwidth_hz;
@@ -286,7 +286,10 @@ function vchanSyncModeDisplay() {
if (!modeEl) return;
if (vchanIsOnVirtual()) {
const ch = vchanActiveChannel();
if (ch && ch.mode) modeEl.value = ch.mode.toUpperCase();
if (ch && ch.mode) {
modeEl.value = ch.mode.toUpperCase();
hostCore.syncModePicker();
}
}
const modeUpper = (modeEl.value || "").toUpperCase();
if (typeof hostState.lastModeName === "string") {