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.
This commit is contained in:
+56
-2
@@ -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 <span className={className}>{placeholder}</span>;
|
||||
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 (
|
||||
<span className={className}>
|
||||
{intPart}.
|
||||
{khz.split('').map((d, i) => (
|
||||
<span key={i}
|
||||
onWheel={(e) => { 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}
|
||||
</span>
|
||||
))}
|
||||
.{hz}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
// 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<number | null>(null);
|
||||
const nudgeCatTimer = useRef<number | null>(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 = (
|
||||
<div className="flex flex-col w-32">
|
||||
<Label className="mb-1 h-3.5 flex items-center gap-1">{t('field.txFreq')} <LockBtn k="freq" title="frequency" /></Label>
|
||||
@@ -3936,7 +3990,7 @@ export default function App() {
|
||||
<span className="font-bold text-xs tracking-tight">OpsLog</span>
|
||||
</div>
|
||||
<div className="flex items-baseline gap-1.5 font-mono ml-2">
|
||||
<span className="text-sm font-semibold text-primary">{freqMhz ? fmtFreqDots(freqMhz) : '—.———.———'}</span>
|
||||
<FreqWheelDisplay mhz={freqMhz} onNudge={nudgeFreqHz} className="text-sm font-semibold text-primary" />
|
||||
<span className="text-[9px] text-muted-foreground">MHz</span>
|
||||
<Badge variant="accent" className="font-mono ml-2 text-[9px] py-0">{band}</Badge>
|
||||
<Badge className="bg-success-muted text-success-muted-foreground hover:bg-success-muted font-mono text-[9px] py-0" variant="outline">{mode}</Badge>
|
||||
@@ -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. */}
|
||||
<div className="flex flex-col items-end leading-none">
|
||||
<span className="text-2xl font-semibold text-primary tracking-wide">{freqMhz ? fmtFreqDots(freqMhz) : '—.———.———'}</span>
|
||||
<FreqWheelDisplay mhz={freqMhz} onNudge={nudgeFreqHz} className="text-2xl font-semibold text-primary tracking-wide" />
|
||||
{catState.split && rxFreqMhz && (
|
||||
<span className="text-[10px] text-muted-foreground mt-0.5">
|
||||
<span className="text-danger font-semibold mr-1">RX</span>
|
||||
|
||||
Reference in New Issue
Block a user