[feat](trx-frontend-http): add keyboard hotkeys for radio controls
Map R=retune, B=previous state, [/]=bandwidth ±10kHz, arrows=tune/center, M=mode picker, Z=mono/stereo, N=noise blanker, Q=squelch toggle. Document all shortcuts in the F1 help overlay. https://claude.ai/code/session_01WDC889uQGW9XoPQqSZ6bVt Signed-off-by: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -631,6 +631,45 @@ let jogAngle = 0;
|
||||
let lastClientCount = null;
|
||||
let lastLocked = false;
|
||||
let sdrSquelchSupported = false;
|
||||
// ── Previous-state tracking for "B" hotkey ────────────────────────────────────
|
||||
let previousTuneState = null; // { freqHz, bandwidthHz, mode, centerHz }
|
||||
|
||||
function savePreviousTuneState() {
|
||||
previousTuneState = {
|
||||
freqHz: lastFreqHz,
|
||||
bandwidthHz: currentBandwidthHz,
|
||||
mode: modeEl ? modeEl.value : "",
|
||||
centerHz: lastSpectrumData ? Number(lastSpectrumData.center_hz) : null,
|
||||
};
|
||||
}
|
||||
|
||||
async function restorePreviousTuneState() {
|
||||
if (!previousTuneState) {
|
||||
showHint("No previous state", 1500);
|
||||
return;
|
||||
}
|
||||
const saved = previousTuneState;
|
||||
savePreviousTuneState(); // save current as previous so B toggles back
|
||||
if (saved.mode && modeEl && modeEl.value !== saved.mode) {
|
||||
modeEl.value = saved.mode;
|
||||
await postPath(`/set_mode?mode=${encodeURIComponent(saved.mode)}`);
|
||||
updateWfmControls();
|
||||
}
|
||||
if (Number.isFinite(saved.bandwidthHz) && saved.bandwidthHz !== currentBandwidthHz) {
|
||||
currentBandwidthHz = saved.bandwidthHz;
|
||||
window.currentBandwidthHz = currentBandwidthHz;
|
||||
syncBandwidthInput(currentBandwidthHz);
|
||||
await postPath(`/set_bandwidth?hz=${saved.bandwidthHz}`);
|
||||
}
|
||||
if (Number.isFinite(saved.freqHz)) {
|
||||
setRigFrequency(saved.freqHz);
|
||||
}
|
||||
if (Number.isFinite(saved.centerHz)) {
|
||||
await postPath(`/set_center_freq?hz=${saved.centerHz}`);
|
||||
}
|
||||
showHint("Restored previous", 1500);
|
||||
}
|
||||
|
||||
let lastRigIds = [];
|
||||
let lastRigDisplayNames = {};
|
||||
let lastActiveRigId = null;
|
||||
@@ -1850,6 +1889,7 @@ function applyLocalTunedFrequency(hz, forceDisplay = false) {
|
||||
const freqChanged = lastFreqHz !== hz;
|
||||
if (!freqChanged && !forceDisplay) return;
|
||||
if (freqChanged) {
|
||||
if (lastFreqHz != null) savePreviousTuneState();
|
||||
primaryRds = null;
|
||||
resetRdsDisplay();
|
||||
resetWfmStereoIndicator();
|
||||
@@ -10347,17 +10387,151 @@ window.addEventListener("keydown", (event) => {
|
||||
return;
|
||||
}
|
||||
|
||||
// Spectrum keyboard navigation
|
||||
if (lastSpectrumData && spectrumCanvas) {
|
||||
// Arrow Left/Right — pan spectrum
|
||||
if (key === "arrowleft" || key === "arrowright") {
|
||||
// R — retune current frequency (re-send same settings)
|
||||
if (key === "r") {
|
||||
event.preventDefault();
|
||||
const step = 0.1 / spectrumZoom;
|
||||
spectrumPanFrac += key === "arrowleft" ? -step : step;
|
||||
scheduleSpectrumDraw();
|
||||
scheduleOverviewDraw();
|
||||
if (lastFreqHz != null) {
|
||||
showHint("Retuning…", 1200);
|
||||
setRigFrequency(lastFreqHz);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// B — jump to previous frequency/bw/mode/decode state
|
||||
if (key === "b") {
|
||||
event.preventDefault();
|
||||
void restorePreviousTuneState();
|
||||
return;
|
||||
}
|
||||
|
||||
// [ — narrow bandwidth by 10 kHz
|
||||
if (key === "[") {
|
||||
event.preventDefault();
|
||||
const [, minBw] = mwDefaultsForMode(modeEl ? modeEl.value : "USB");
|
||||
const next = Math.max(minBw, currentBandwidthHz - 10_000);
|
||||
if (next !== currentBandwidthHz) {
|
||||
currentBandwidthHz = next;
|
||||
window.currentBandwidthHz = currentBandwidthHz;
|
||||
syncBandwidthInput(next);
|
||||
positionFastOverlay(lastFreqHz, next);
|
||||
if (lastSpectrumData) scheduleSpectrumDraw();
|
||||
postPath(`/set_bandwidth?hz=${next}`).catch(() => {});
|
||||
showHint(`BW ${formatBwLabel(next)}`, 1200);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// ] — widen bandwidth by 10 kHz
|
||||
if (key === "]") {
|
||||
event.preventDefault();
|
||||
const [, , maxBw] = mwDefaultsForMode(modeEl ? modeEl.value : "USB");
|
||||
const next = Math.min(maxBw, currentBandwidthHz + 10_000);
|
||||
if (next !== currentBandwidthHz) {
|
||||
currentBandwidthHz = next;
|
||||
window.currentBandwidthHz = currentBandwidthHz;
|
||||
syncBandwidthInput(next);
|
||||
positionFastOverlay(lastFreqHz, next);
|
||||
if (lastSpectrumData) scheduleSpectrumDraw();
|
||||
postPath(`/set_bandwidth?hz=${next}`).catch(() => {});
|
||||
showHint(`BW ${formatBwLabel(next)}`, 1200);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Left/Right arrows — retune by current jog step
|
||||
if (key === "arrowleft" || key === "arrowright") {
|
||||
event.preventDefault();
|
||||
jogFreq(key === "arrowright" ? 1 : -1);
|
||||
return;
|
||||
}
|
||||
|
||||
// Up/Down arrows — shift center (spectrum) frequency
|
||||
if (key === "arrowup" || key === "arrowdown") {
|
||||
event.preventDefault();
|
||||
void shiftSpectrumCenter(key === "arrowup" ? 1 : -1);
|
||||
return;
|
||||
}
|
||||
|
||||
// M — open mode picker
|
||||
if (key === "m") {
|
||||
event.preventDefault();
|
||||
if (modeEl && !modeEl.disabled) {
|
||||
modeEl.focus();
|
||||
modeEl.click();
|
||||
// Attempt to programmatically open the <select> via showPicker (modern browsers)
|
||||
if (typeof modeEl.showPicker === "function") {
|
||||
try { modeEl.showPicker(); } catch (_) {}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Z — toggle mono/stereo (WFM)
|
||||
if (key === "z") {
|
||||
event.preventDefault();
|
||||
if (wfmAudioModeEl) {
|
||||
const next = wfmAudioModeEl.value === "mono" ? "stereo" : "mono";
|
||||
wfmAudioModeEl.value = next;
|
||||
saveSetting("wfmAudioMode", next);
|
||||
const enabled = next !== "mono";
|
||||
postPath(`/set_wfm_stereo?enabled=${enabled ? "true" : "false"}`).catch(() => {});
|
||||
showHint(next === "stereo" ? "Stereo" : "Mono", 1200);
|
||||
} else {
|
||||
showHint("Stereo N/A", 1200);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// N — toggle noise blanker
|
||||
if (key === "n") {
|
||||
event.preventDefault();
|
||||
if (sdrNbSupported && sdrNbEnabledEl) {
|
||||
sdrNbEnabledEl.checked = !sdrNbEnabledEl.checked;
|
||||
submitSdrNbState();
|
||||
showHint(sdrNbEnabledEl.checked ? "NB On" : "NB Off", 1200);
|
||||
} else {
|
||||
showHint("NB N/A", 1200);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Q — toggle squelch (cycle 0 → auto → 0)
|
||||
if (key === "q") {
|
||||
event.preventDefault();
|
||||
if (sdrSquelchSupported && sdrSquelchEl) {
|
||||
const current = clampSdrSquelchPercent(Number(sdrSquelchEl.value));
|
||||
let nextPct;
|
||||
if (current > 0) {
|
||||
nextPct = 0; // turn off
|
||||
} else {
|
||||
// Auto: estimate from noise floor
|
||||
let auto = 30;
|
||||
const data = lastSpectrumData || window.lastSpectrumData;
|
||||
if (data && Array.isArray(data.bins) && data.bins.length > 0) {
|
||||
const noiseDb = estimateNoiseFloorDb(data.bins);
|
||||
if (noiseDb != null && Number.isFinite(noiseDb)) {
|
||||
const thresholdDb = noiseDb + 6;
|
||||
const clamped = Math.max(SDR_SQUELCH_MIN_DB, Math.min(SDR_SQUELCH_MAX_DB, thresholdDb));
|
||||
auto = clampSdrSquelchPercent(
|
||||
((clamped - SDR_SQUELCH_MIN_DB) / (SDR_SQUELCH_MAX_DB - SDR_SQUELCH_MIN_DB)) * 100,
|
||||
);
|
||||
}
|
||||
}
|
||||
nextPct = auto;
|
||||
}
|
||||
sdrSquelchEl.value = String(nextPct);
|
||||
updateSdrSquelchPctLabel();
|
||||
saveSetting("sdrSquelchPct", nextPct);
|
||||
submitSdrSquelchPercent(nextPct);
|
||||
showHint(nextPct > 0 ? `Squelch ${nextPct}%` : "Squelch Off", 1200);
|
||||
} else {
|
||||
showHint("Squelch N/A", 1200);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Spectrum keyboard navigation
|
||||
if (lastSpectrumData && spectrumCanvas) {
|
||||
// +/= — zoom in
|
||||
if (key === "+" || key === "=") {
|
||||
event.preventDefault();
|
||||
|
||||
@@ -1165,7 +1165,18 @@
|
||||
<div class="shortcut-overlay-title">Keyboard Shortcuts</div>
|
||||
<table class="shortcut-table">
|
||||
<tbody>
|
||||
<tr><td class="shortcut-key"><kbd>R</kbd></td><td>Retune current frequency</td></tr>
|
||||
<tr><td class="shortcut-key"><kbd>B</kbd></td><td>Jump to previous freq/bw/mode</td></tr>
|
||||
<tr><td class="shortcut-key"><kbd>[</kbd> / <kbd>]</kbd></td><td>Narrow / widen bandwidth (±10 kHz)</td></tr>
|
||||
<tr><td class="shortcut-key"><kbd>←</kbd> / <kbd>→</kbd></td><td>Tune by current step</td></tr>
|
||||
<tr><td class="shortcut-key"><kbd>↑</kbd> / <kbd>↓</kbd></td><td>Shift center frequency</td></tr>
|
||||
<tr><td class="shortcut-key"><kbd>M</kbd></td><td>Open mode picker</td></tr>
|
||||
<tr><td class="shortcut-key"><kbd>Z</kbd></td><td>Toggle mono / stereo (WFM)</td></tr>
|
||||
<tr><td class="shortcut-key"><kbd>N</kbd></td><td>Toggle noise blanker</td></tr>
|
||||
<tr><td class="shortcut-key"><kbd>Q</kbd></td><td>Toggle squelch (off / auto)</td></tr>
|
||||
<tr><td class="shortcut-key"><kbd>S</kbd></td><td>Spectrum screenshot</td></tr>
|
||||
<tr><td class="shortcut-key"><kbd>+</kbd> / <kbd>-</kbd></td><td>Zoom spectrum in / out</td></tr>
|
||||
<tr><td class="shortcut-key"><kbd>0</kbd></td><td>Reset spectrum zoom</td></tr>
|
||||
<tr><td class="shortcut-key"><kbd>F1</kbd></td><td>Toggle this help</td></tr>
|
||||
<tr><td class="shortcut-key"><kbd>Esc</kbd></td><td>Close overlay / exit fullscreen</td></tr>
|
||||
</tbody>
|
||||
|
||||
Reference in New Issue
Block a user