feat: Winkeyer
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { Minus, Plus, Crosshair, X } from 'lucide-react';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { spotStatusKey, inferSpotMode } from '@/lib/spot';
|
||||
import { spotStatusKey, inferSpotMode, spotModeCategory } from '@/lib/spot';
|
||||
|
||||
// BandMap — vertical spectrum panel inspired by Log4OM.
|
||||
// - Full band is always visible; zoom changes pixels-per-kHz, scroll
|
||||
@@ -136,12 +136,12 @@ const PILL_GAP = 32; // px between scale border and first pill (room for leade
|
||||
const LABEL_W = 200;
|
||||
const TOP_PAD = 14; // px of breathing room above/below the band edges so
|
||||
const BOT_PAD = 14; // the top-most freq label isn't clipped at y=0
|
||||
// Max FT-mode (FT8/FT4/…) pills drawn at once. These pile up on a single
|
||||
// watering-hole frequency and otherwise spawn hundreds of spots that fan out
|
||||
// and cover the whole map. ONLY the FT family is capped — CW, SSB and other
|
||||
// digital spots are always shown in full. When more than this FT spots are in
|
||||
// band we keep the most useful (new entities first, worked last; ties broken
|
||||
// by closeness to the rig freq).
|
||||
// Max DATA-class (digital: FT8/FT4/JS8/RTTY/PSK/…) pills drawn at once.
|
||||
// These pile up on the watering-hole frequencies and otherwise spawn
|
||||
// hundreds of spots that fan out and cover the whole map. ONLY digital is
|
||||
// capped — CW and SSB are always shown in full. When more than this digital
|
||||
// spots are in band we keep the most useful (new entities first, worked
|
||||
// last; ties broken by closeness to the rig freq).
|
||||
const MAX_VISIBLE_SPOTS = 30;
|
||||
|
||||
export function BandMap({ band, spots, spotStatus, currentFreqHz, onSpotClick, onClose }: Props) {
|
||||
@@ -189,14 +189,16 @@ export function BandMap({ band, spots, spotStatus, currentFreqHz, onSpotClick, o
|
||||
inBand.push(s);
|
||||
}
|
||||
|
||||
// Only the FT family (FT8/FT4/…) is capped — it's what floods a single
|
||||
// watering-hole frequency. Everything else (CW, SSB, RTTY, PSK, …) is
|
||||
// always shown in full.
|
||||
const isFlood = (s: Spot) => /^FT/.test(inferSpotMode(s.comment ?? '', s.freq_hz));
|
||||
// Only DATA-class spots (every digital mode — FT8/FT4/JS8/RTTY/PSK/…)
|
||||
// are capped — they're what floods the watering-hole frequencies. We key
|
||||
// off the mode CATEGORY (not a literal "FT8" string) because many FT8
|
||||
// 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);
|
||||
const otherSpots = inBand.filter((s) => !isFlood(s));
|
||||
|
||||
// Rank an FT spot by usefulness (new entity → unworked → worked); ties
|
||||
// Rank a DATA spot by usefulness (new entity → unworked → worked); ties
|
||||
// break by proximity to the rig frequency. Keep the top MAX_VISIBLE_SPOTS.
|
||||
const rank = (s: Spot) => {
|
||||
const k = spotStatusKey(s.dx_call, s.band ?? '', s.comment ?? '', s.freq_hz);
|
||||
@@ -497,7 +499,7 @@ export function BandMap({ band, spots, spotStatus, currentFreqHz, onSpotClick, o
|
||||
</div>
|
||||
<div className="px-3 py-1 text-[9px] text-muted-foreground bg-muted/30 border-t border-border font-mono text-center shrink-0">
|
||||
scroll · ctrl+wheel = zoom · ◎ = jump to rig
|
||||
{hidden > 0 && <span className="text-amber-600"> · {hidden} FT spot{hidden > 1 ? 's' : ''} hidden (top {MAX_VISIBLE_SPOTS} shown)</span>}
|
||||
{hidden > 0 && <span className="text-amber-600"> · {hidden} data spot{hidden > 1 ? 's' : ''} hidden (top {MAX_VISIBLE_SPOTS} shown)</span>}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user