[feat](trx-client): capability-gated UI controls and filter panel (UC-05..07)
Add /set_bandwidth and /set_fir_taps HTTP endpoints to api.rs. Add applyCapabilities(caps) function to app.js that shows/hides: - PTT button and TX meters: capabilities.tx - TX limit row: capabilities.tx_limit - VFO row: capabilities.vfo_switch - Signal meter row: capabilities.signal_meter - Filters panel: capabilities.filter_controls Called from render() whenever capabilities are present; runs on both initial /status response and every SSE event. Add a Filters panel to index.html with bandwidth slider (1..500 kHz) and FIR taps select (16/32/64/128/256); hidden by default, revealed by applyCapabilities when filter_controls is set. Each control dispatches to the corresponding HTTP endpoint on change. Sync filter state from update.filter in render() to keep slider/select in sync with server-side DSP state. Fix missing struct fields in test helpers across remote_client.rs, trx-frontend-http-json/server.rs, trx-frontend-rigctl/server.rs, and trx-core controller tests (handlers.rs, machine.rs). Update aidocs/UI-CAPS.md: all tasks UC-01..UC-09 marked [x]. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Signed-off-by: Stan Grams <sjg@haxx.space>
This commit is contained in:
@@ -219,6 +219,38 @@ function applyAuthRestrictions() {
|
||||
}
|
||||
}
|
||||
|
||||
function applyCapabilities(caps) {
|
||||
if (!caps) return;
|
||||
|
||||
// PTT / TX controls
|
||||
const pttBtn = document.getElementById("ptt-btn");
|
||||
const txMetersRow = document.getElementById("tx-meters");
|
||||
if (pttBtn) pttBtn.style.display = caps.tx ? "" : "none";
|
||||
if (txMetersRow) txMetersRow.style.display = caps.tx ? "" : "none";
|
||||
|
||||
// TX limit row
|
||||
const txLimitRow = document.getElementById("tx-limit-row");
|
||||
if (txLimitRow && !caps.tx_limit) txLimitRow.style.display = "none";
|
||||
|
||||
// VFO row
|
||||
const vfoRow = document.getElementById("vfo-row");
|
||||
if (vfoRow) vfoRow.style.display = caps.vfo_switch ? "" : "none";
|
||||
|
||||
// Signal meter row
|
||||
const sigRow = document.querySelector(".full-row.label-below-row");
|
||||
// Find signal row by content check rather than class (it may share classes)
|
||||
document.querySelectorAll(".full-row.label-below-row").forEach(row => {
|
||||
const label = row.querySelector(".label span");
|
||||
if (label && label.textContent === "Signal") {
|
||||
row.style.display = caps.signal_meter ? "" : "none";
|
||||
}
|
||||
});
|
||||
|
||||
// Filters panel
|
||||
const filtersPanel = document.getElementById("filters-panel");
|
||||
if (filtersPanel) filtersPanel.style.display = caps.filter_controls ? "" : "none";
|
||||
}
|
||||
|
||||
const freqEl = document.getElementById("freq");
|
||||
const wavelengthEl = document.getElementById("wavelength");
|
||||
const modeEl = document.getElementById("mode");
|
||||
@@ -706,6 +738,20 @@ function render(update) {
|
||||
if (update.info && update.info.capabilities) {
|
||||
updateJogStepSupport(update.info.capabilities);
|
||||
updateSupportedBands(update.info.capabilities);
|
||||
applyCapabilities(update.info.capabilities);
|
||||
}
|
||||
// Sync filter state (SDR backends only)
|
||||
if (update.filter) {
|
||||
const bwSlider = document.getElementById("bw-slider");
|
||||
const bwValue = document.getElementById("bw-value");
|
||||
const firSelect = document.getElementById("fir-taps-select");
|
||||
if (bwSlider && typeof update.filter.bandwidth_hz === "number") {
|
||||
bwSlider.value = update.filter.bandwidth_hz;
|
||||
if (bwValue) bwValue.textContent = (update.filter.bandwidth_hz / 1000).toFixed(1) + " kHz";
|
||||
}
|
||||
if (firSelect && typeof update.filter.fir_taps === "number") {
|
||||
firSelect.value = String(update.filter.fir_taps);
|
||||
}
|
||||
}
|
||||
if (update.status && update.status.freq && typeof update.status.freq.hz === "number") {
|
||||
lastFreqHz = update.status.freq.hz;
|
||||
@@ -1260,6 +1306,41 @@ lockBtn.addEventListener("click", async () => {
|
||||
}
|
||||
});
|
||||
|
||||
// --- Filter controls ---
|
||||
(function () {
|
||||
const bwSlider = document.getElementById("bw-slider");
|
||||
const bwValue = document.getElementById("bw-value");
|
||||
const firSelect = document.getElementById("fir-taps-select");
|
||||
|
||||
if (bwSlider) {
|
||||
bwSlider.addEventListener("input", () => {
|
||||
const hz = Number(bwSlider.value);
|
||||
if (bwValue) bwValue.textContent = (hz / 1000).toFixed(1) + " kHz";
|
||||
});
|
||||
bwSlider.addEventListener("change", async () => {
|
||||
const hz = Number(bwSlider.value);
|
||||
try {
|
||||
await postPath(`/set_bandwidth?hz=${encodeURIComponent(hz)}`);
|
||||
} catch (err) {
|
||||
showHint("Bandwidth set failed", 2000);
|
||||
console.error(err);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (firSelect) {
|
||||
firSelect.addEventListener("change", async () => {
|
||||
const taps = Number(firSelect.value);
|
||||
try {
|
||||
await postPath(`/set_fir_taps?taps=${encodeURIComponent(taps)}`);
|
||||
} catch (err) {
|
||||
showHint("FIR taps set failed", 2000);
|
||||
console.error(err);
|
||||
}
|
||||
});
|
||||
}
|
||||
})();
|
||||
|
||||
// --- Tab navigation ---
|
||||
document.querySelector(".tab-bar").addEventListener("click", (e) => {
|
||||
const btn = e.target.closest(".tab[data-tab]");
|
||||
|
||||
@@ -133,6 +133,26 @@
|
||||
<button id="tx-limit-btn" type="button">Set</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="filters-panel" style="display:none;">
|
||||
<div class="label"><span>Filters</span></div>
|
||||
<div class="inline" style="gap: 0.8rem; flex-wrap: wrap; align-items: center;">
|
||||
<label style="display:flex; align-items:center; gap:0.4rem;">
|
||||
<span style="color:var(--text-muted); font-size:0.85rem; white-space:nowrap;">BW</span>
|
||||
<input type="range" id="bw-slider" min="1000" max="500000" step="1000" value="3000" style="width:120px;" />
|
||||
<span id="bw-value" style="min-width:4rem; font-size:0.9rem;">3.0 kHz</span>
|
||||
</label>
|
||||
<label style="display:flex; align-items:center; gap:0.4rem;">
|
||||
<span style="color:var(--text-muted); font-size:0.85rem; white-space:nowrap;">FIR taps</span>
|
||||
<select id="fir-taps-select" class="status-input" style="width:auto; height:var(--control-height);">
|
||||
<option value="16">16</option>
|
||||
<option value="32">32</option>
|
||||
<option value="64" selected>64</option>
|
||||
<option value="128">128</option>
|
||||
<option value="256">256</option>
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="full-row label-below-row" id="audio-row">
|
||||
<div class="label"><span>Audio</span></div>
|
||||
|
||||
Reference in New Issue
Block a user