feat: New fit to band and hide FTx option in bandmap tab

This commit is contained in:
2026-07-05 13:29:02 +02:00
parent 8cf53a0aa2
commit 25a53e4110
3 changed files with 42 additions and 8 deletions
+19 -7
View File
@@ -35,6 +35,12 @@ interface Props {
onClose?: () => void;
side?: 'left' | 'right';
onToggleSide?: () => void;
// hideDigital drops every DATA-class (FT8/FT4/JS8/…) spot so the crowded
// watering holes don't hog the map. fitToBand overrides zoom so the whole
// band edge-to-edge fits the visible height (no scrolling). Both are driven
// globally from the band-map tab toolbar.
hideDigital?: boolean;
fitToBand?: boolean;
}
const BAND_RANGES: Record<string, [number, number]> = {
@@ -147,7 +153,7 @@ const BOT_PAD = 14; // the top-most freq label isn't clipped at y=0
// last; ties broken by closeness to the rig freq).
const MAX_VISIBLE_SPOTS = 30;
export function BandMap({ band, spots, spotStatus, currentFreqHz, onSpotClick, onClose, side = 'right', onToggleSide }: Props) {
export function BandMap({ band, spots, spotStatus, currentFreqHz, onSpotClick, onClose, side = 'right', onToggleSide, hideDigital = false, fitToBand = false }: Props) {
const { t } = useI18n();
const range = BAND_RANGES[band];
const segments = SEGMENT_COLORS[band] ?? [];
@@ -167,7 +173,12 @@ export function BandMap({ band, spots, spotStatus, currentFreqHz, onSpotClick, o
const fallback: [number, number] = range ?? [0, 1];
const [lo, hi] = fallback;
const span = hi - lo;
const pxPerKHz = PX_PER_KHZ[zoomIdx];
// Fit-to-band computes the exact px/kHz so the whole band edge-to-edge fills
// the visible height; otherwise use the discrete zoom step.
const fitPxPerKHz = fitToBand && containerH > 0 && span > 0
? Math.max(0.05, (containerH - TOP_PAD - BOT_PAD) / span)
: 0;
const pxPerKHz = fitPxPerKHz || PX_PER_KHZ[zoomIdx];
// Anti-overlap layout: each label wants to sit at its true freq, but
// never closer than PILL_H from the previous one. Sorted top-to-bottom
@@ -199,7 +210,8 @@ export function BandMap({ band, spots, spotStatus, currentFreqHz, onSpotClick, o
// spots carry no mode word and the band-plan fallback labels them the
// generic "DATA" rather than "FT8". CW and SSB are always shown in full.
const isFlood = (s: Spot) => spotModeCategory(inferSpotMode(s.comment ?? '', s.freq_hz)) === 'DATA';
const ftSpots = inBand.filter(isFlood);
// hideDigital removes them entirely; otherwise they're capped below.
const ftSpots = hideDigital ? [] : inBand.filter(isFlood);
const otherSpots = inBand.filter((s) => !isFlood(s));
// Rank a DATA spot by usefulness (new entity → unworked → worked); ties
@@ -277,7 +289,7 @@ export function BandMap({ band, spots, spotStatus, currentFreqHz, onSpotClick, o
totalH: Math.max(innerH + TOP_PAD + BOT_PAD, lastLabelBottom + BOT_PAD),
hidden: hiddenCount,
};
}, [spots, range, lo, hi, span, pxPerKHz, containerH, spotStatus, currentFreqHz]);
}, [spots, range, lo, hi, span, pxPerKHz, containerH, spotStatus, currentFreqHz, hideDigital]);
// freqToY for elements rendered outside the memo (ticks, rig pointer).
// Must mirror the same offset so the rig triangle sits on the right kHz.
@@ -350,13 +362,13 @@ export function BandMap({ band, spots, spotStatus, currentFreqHz, onSpotClick, o
<div className="h-full w-full flex flex-col min-h-0 bg-card">
<div className="px-2 py-1.5 text-xs font-semibold uppercase tracking-wider text-muted-foreground bg-muted/40 border-b border-border flex flex-nowrap items-center gap-0.5 shrink-0">
<span className="flex-1 min-w-0 truncate">{t('bmp.map')} · {band}</span>
<button type="button" onClick={() => setZoomIdx((z) => Math.max(0, z - 1))} disabled={zoomIdx === 0}
<button type="button" onClick={() => setZoomIdx((z) => Math.max(0, z - 1))} disabled={fitToBand || zoomIdx === 0}
className="size-5 shrink-0 inline-flex items-center justify-center rounded hover:bg-muted disabled:opacity-30"
title={t('bmp.zoomOut')}>
<Minus className="size-3" />
</button>
<span className="shrink-0 font-mono text-[10px] normal-case tracking-normal whitespace-nowrap px-0.5">{pxPerKHz}px/kHz</span>
<button type="button" onClick={() => setZoomIdx((z) => Math.min(PX_PER_KHZ.length - 1, z + 1))} disabled={zoomIdx === PX_PER_KHZ.length - 1}
<span className="shrink-0 font-mono text-[10px] normal-case tracking-normal whitespace-nowrap px-0.5">{fitToBand ? t('bmp.fit') : `${pxPerKHz}px/kHz`}</span>
<button type="button" onClick={() => setZoomIdx((z) => Math.min(PX_PER_KHZ.length - 1, z + 1))} disabled={fitToBand || zoomIdx === PX_PER_KHZ.length - 1}
className="size-5 shrink-0 inline-flex items-center justify-center rounded hover:bg-muted disabled:opacity-30"
title={t('bmp.zoomIn')}>
<Plus className="size-3" />