From e5ff30823d0f0ca24340124463af1c17d9f06566 Mon Sep 17 00:00:00 2001 From: rouggy Date: Sat, 25 Jul 2026 12:22:17 +0200 Subject: [PATCH] feat: scroll-tune the top frequency readout (100/10/1 kHz per digit) Rolling the mouse wheel over the hundreds / tens / units-of-kHz digit of the header (and compact top-bar) frequency steps the frequency by 100 / 10 / 1 kHz (wheel up = higher). The display updates optimistically on every notch and the rig QSYs over CAT, debounced so a fast scroll doesn't flood the link. Only the kHz digits are wheel-sensitive; MHz and Hz stay static. New FreqWheelDisplay component (replaces the plain fmtFreqDots span in both the full header and the compact top bar) + nudgeFreqHz handler. --- changelog.json | 2 ++ frontend/src/App.tsx | 58 ++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 58 insertions(+), 2 deletions(-) diff --git a/changelog.json b/changelog.json index 7ba8878..6fe2827 100644 --- a/changelog.json +++ b/changelog.json @@ -3,6 +3,7 @@ "version": "0.21.1", "date": "2026-07-24", "en": [ + "The top frequency readout is now scroll-tunable: roll the mouse wheel over the hundreds / tens / units-of-kHz digit to step the frequency by 100 / 10 / 1 kHz, and the rig follows over CAT.", "New: 4O3A Tuner Genius XL control. Enable it in Settings → Tuner Genius (IP only; port is fixed at 9010). Live SWR and forward power with Tune, Bypass and Operate/Standby, the two channels A/B (source, frequency and antenna) shown and click-selectable. Available as a docked widget, a card in the FlexRadio panel (like the PowerGenius) and a card in Station Control. Controlled directly over TCP, so it uses just one of the box's connection slots.", "FlexRadio panel tidy-up: cards collapse from the chevron in their header (Transmit and Receive fold together), all meters are the same size, the MIC and COMP meters only show in phone modes, and the S-meter dBm now sits next to the S-value instead of on a second line.", "Fixed the colour theme sometimes reverting to the default when reopening OpsLog — it's now restored reliably.", @@ -13,6 +14,7 @@ "Fixed award references (in the entry strip's Awards tab) not clearing when the callsign changes — clicking one spot then another no longer keeps the previous station's references." ], "fr": [ + "L'affichage de fréquence en haut est maintenant accordable à la molette : roule la molette sur le chiffre des centaines / dizaines / unités de kHz pour changer la fréquence par pas de 100 / 10 / 1 kHz, et la radio suit en CAT.", "Nouveau : contrôle du 4O3A Tuner Genius XL. Active-le dans Réglages → Tuner Genius (IP seulement ; port fixé à 9010). ROS et puissance directe en direct avec Accord, Bypass et Operate/Standby, et les deux canaux A/B (source, fréquence et antenne) affichés et sélectionnables d'un clic. Disponible en widget ancré, en carte dans le panneau FlexRadio (comme le PowerGenius) et en carte dans Station Control. Piloté directement en TCP, il n'utilise qu'une des connexions de la boîte.", "Nettoyage du panneau FlexRadio : les cartes se replient via le chevron de leur en-tête (Transmit et Receive se replient ensemble), tous les meters ont la même taille, les meters MIC et COMP ne s'affichent qu'en phonie, et le dBm du S-mètre est maintenant à côté de la valeur S plutôt que sur une deuxième ligne.", "Correction du thème qui revenait parfois au défaut à la réouverture d'OpsLog — il est maintenant restauré de façon fiable.", diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 1b5c6a5..6fd267b 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -211,6 +211,36 @@ function fmtFreqDots(mhzStr: string): string { const frac = (fracRaw + '000000').slice(0, 6); return `${intPart}.${frac.slice(0, 3)}.${frac.slice(3, 6)}`; } + +// FreqWheelDisplay renders a frequency (MHz string) like fmtFreqDots — MHz.kHz.Hz +// — but makes the three kHz digits scroll-sensitive: rolling the mouse wheel over +// the hundreds / tens / units-of-kHz digit steps the frequency by 100 / 10 / 1 kHz +// (up = wheel up). onNudge receives the delta in Hz. The MHz and Hz digits are +// static (only kHz stepping was requested). Used in the header + compact top bar. +function FreqWheelDisplay({ mhz, onNudge, className, placeholder = '—.———.———' }: { + mhz: string; onNudge: (deltaHz: number) => void; className?: string; placeholder?: string; +}) { + if (!mhz) return {placeholder}; + const [intPart, fracRaw = ''] = mhz.split('.'); + const frac = (fracRaw + '000000').slice(0, 6); + const khz = frac.slice(0, 3); // [hundreds, tens, units] of kHz + const hz = frac.slice(3, 6); + const stepHz = [100_000, 10_000, 1_000]; // per kHz digit: 100 / 10 / 1 kHz + return ( + + {intPart}. + {khz.split('').map((d, i) => ( + { e.preventDefault(); e.stopPropagation(); onNudge(e.deltaY < 0 ? stepHz[i] : -stepHz[i]); }} + className="cursor-ns-resize rounded-[2px] hover:bg-primary/25 transition-colors" + title="Scroll to change frequency"> + {d} + + ))} + .{hz} + + ); +} // shortCatError condenses a backend error into a few words for the topbar // pill. The full message stays in the tooltip. Recognises the common cases // (OmniRig not installed, not registered) and otherwise truncates. @@ -3543,6 +3573,30 @@ export default function App() { noteManualEdit(); SetCATFrequency(Math.round(mhz * 1_000_000)).catch(() => {}); }; + // Mouse-wheel over a kHz digit of the top frequency readout: step the frequency + // and (if CAT is connected) QSY the rig. The display updates optimistically on + // every notch; the actual radio tune is debounced so a fast scroll doesn't flood + // the CAT link. nudgeAccumRef holds the live value across a burst (setFreqMhz is + // async, so we can't re-read it between notches). + const nudgeAccumRef = useRef(null); + const nudgeCatTimer = useRef(null); + const nudgeFreqHz = (deltaHz: number) => { + let cur = nudgeAccumRef.current; + if (cur == null) cur = freqMhz ? Math.round(parseFloat(freqMhz) * 1_000_000) : 0; + if (!cur) return; + const newHz = Math.max(0, cur + deltaHz); + nudgeAccumRef.current = newHz; + setFreqMhz((newHz / 1_000_000).toFixed(6)); + noteManualEdit(); + const b = bandForMHz(newHz / 1_000_000); if (b) setBand(b); + if (nudgeCatTimer.current) window.clearTimeout(nudgeCatTimer.current); + nudgeCatTimer.current = window.setTimeout(() => { + const hz = nudgeAccumRef.current; + nudgeAccumRef.current = null; + nudgeCatTimer.current = null; + if (hz && catState.enabled && catState.connected) SetCATFrequency(hz).catch(() => {}); + }, 150); + }; const freqBlock = (
@@ -3936,7 +3990,7 @@ export default function App() { OpsLog
- {freqMhz ? fmtFreqDots(freqMhz) : '—.———.———'} + MHz {band} {mode} @@ -3966,7 +4020,7 @@ export default function App() { {/* Toasts and errors live in the STATUS BAR at the bottom now — the header band was too narrow and long messages were cut off. */}
- {freqMhz ? fmtFreqDots(freqMhz) : '—.———.———'} + {catState.split && rxFreqMhz && ( RX