feat(ui): the FT decodes panel, two DXHunter themes, and the maps
Decodes: - one click SELECTS, two transmit. A single click handed the decode straight to WSJT-X as a reply, so brushing a row while reading the band started calling a station. - the list empties for a receiver that changes band, and a receiver column appears when more than one is feeding one merged list. - the period clock turns red while transmitting: it is the one thing on the screen that moves, so it is where the eye already is. - a WL badge, after the LoTW "L" — one letter, always in the same place, so the column does not shift from row to row. - the auto-call switch, its target and its count, and the chase list: naming the station you are waiting for is done while watching the band, not in a settings tree. Themes: DXHunter's slate with its own blue, and the same slate with OpsLog's orange. Counted across its sources rather than guessed from one panel — blue is 132 uses to violet's 25, and the violet is the PSK Reporter panel alone. Watchlist: drawn as DXHunter draws it — the callsign in the interface font rather than monospaced, which is the difference that shows with the two windows side by side. FT Map: arcs no longer run off the side of the map. The map shows one world, and a path crossing the antimeridian was drawn past 180° into the blank space beside it — from VK that is most of them. Cluster: "superfox", "fox/hound" and "F/H" in a comment are read as FT8. They are WSJT-X's DXpedition transmit modes, and the comment fell through to the band plan and came out DATA — which then decided the band+mode verdict. A decoder is named by what it IS: Nexus sends its packets as "Tempo", the engine inside it, and OpsLog showed a program nobody has heard of.
This commit is contained in:
+55
-6
@@ -54,7 +54,7 @@ import {
|
||||
GetFlexState, FlexAmpOperate,
|
||||
GetPSKReporterStatus, GetLiveOpenings, GetChaseNew,
|
||||
QSLViaRepairStatus, RepairQSLVia, DismissQSLViaRepair,
|
||||
GetAutoCallStatus, SetAutoCall, SetAutoCallOnly, TakeAutoCallTarget, ResetAutoCall,
|
||||
GetAutoCallStatus, SetAutoCall, SetAutoCallOnly, TakeAutoCallTarget, HaltAutoCall, WatchlistEntries,
|
||||
} from '../wailsjs/go/main/App';
|
||||
import { Combobox } from '@/components/ui/combobox';
|
||||
import { applyAwardRefs, parseAwardRefs as parseManualRefs, spotRefList , withIOTARef, withRDARef } from '@/lib/awardRefs';
|
||||
@@ -123,6 +123,7 @@ import { GridSquareMap } from '@/components/GridSquareMap';
|
||||
import { loadClusterMacros, visibleClusterMacros } from '@/lib/clusterMacros';
|
||||
import { DecodesPanel, type Decode as DecodeRow, type TxMsg as TxMsgRow } from '@/components/DecodesPanel';
|
||||
import { PSKReporterPanel } from '@/components/PSKReporterPanel';
|
||||
import { decoderName } from '@/lib/decoderName';
|
||||
import { subscribeRotorHeading, pokeRotorHeading } from '@/lib/rotorHeading';
|
||||
import { writeUiPref } from '@/lib/uiPref';
|
||||
import { formatDateTimeUTC } from '@/lib/dateFormat';
|
||||
@@ -2406,6 +2407,17 @@ export default function App() {
|
||||
// the watchlist panel: a call can be added from the cluster or the
|
||||
// DXpeditions tab, and the old inline message lived on a page nobody was
|
||||
// looking at.
|
||||
// The watch list as patterns, for the decodes badge. Loaded once and kept in
|
||||
// step with the same event the notice below listens to — a call added from
|
||||
// the cluster has to light up in the decodes list too.
|
||||
const [watchPatterns, setWatchPatterns] = useState<string[]>([]);
|
||||
const loadWatchPatterns = useCallback(() => {
|
||||
WatchlistEntries()
|
||||
.then((es: any[]) => setWatchPatterns((es ?? []).map((e: any) => String(e?.callsign ?? '')).filter(Boolean)))
|
||||
.catch(() => {});
|
||||
}, []);
|
||||
useEffect(() => { loadWatchPatterns(); }, [loadWatchPatterns]);
|
||||
useEffect(() => EventsOn('watchlist:changed', () => loadWatchPatterns()), [loadWatchPatterns]);
|
||||
const [wlNotice, setWlNotice] = useState<{ call: string; added: boolean } | null>(null);
|
||||
const wlNoticeTimer = useRef<number | undefined>(undefined);
|
||||
useEffect(() => EventsOn('watchlist:changed', (e: any) => {
|
||||
@@ -2597,6 +2609,9 @@ export default function App() {
|
||||
// Staged like the cluster's, so a period arriving as one burst of fifty
|
||||
// packets costs one status lookup and one render, not fifty of each.
|
||||
const pendingDecodesRef = useRef<DecodeRow[]>([]);
|
||||
// The band each receiver was last decoding on. A band change empties that
|
||||
// receiver's list — see the flush below.
|
||||
const decoderBandRef = useRef<Map<string, string>>(new Map());
|
||||
const pendingDecodeTimer = useRef<number | undefined>(undefined);
|
||||
|
||||
// Rotor quick-turn buttons (Settings → Rotator). Same reload trigger as the
|
||||
@@ -3718,9 +3733,42 @@ export default function App() {
|
||||
});
|
||||
}
|
||||
} catch { /* status unresolved — the decode still shows, just unflagged */ }
|
||||
// A BAND CHANGE empties that receiver's list.
|
||||
//
|
||||
// What is on 20 m says nothing about 40 m, and half a screen of stations
|
||||
// that are no longer reachable is worse than an empty one: the period
|
||||
// headings still march on, the rows still carry status badges, and the
|
||||
// operator reads a band they have left. Per receiver — in a split view
|
||||
// the other one has not moved — and only when the new band is known.
|
||||
const moved = new Map<string, string>();
|
||||
for (const d of batch) {
|
||||
const inst = d.instance ?? '';
|
||||
const band = (d.band ?? '').toLowerCase();
|
||||
if (!band) continue;
|
||||
const was = decoderBandRef.current.get(inst);
|
||||
decoderBandRef.current.set(inst, band);
|
||||
if (was && was !== band) moved.set(inst, band);
|
||||
}
|
||||
if (moved.size > 0) {
|
||||
for (const [inst, band] of moved) {
|
||||
LogUIError('decodes', `${decoderName(inst) || 'decoder'} moved to ${band.toUpperCase()} — its earlier decodes cleared`, '');
|
||||
}
|
||||
setTxMsgs((arr) => arr.filter((m) => !moved.has(m.instance ?? '')));
|
||||
}
|
||||
setDecodes((arr) => {
|
||||
const cutoff = Date.now() - DECODE_KEEP_MS;
|
||||
const next = [...arr, ...batch].filter((d) => Date.parse(d.at) >= cutoff);
|
||||
// Rows from a receiver that has just changed band go with it — the
|
||||
// batch itself is already on the new band.
|
||||
const kept = moved.size === 0 ? arr : arr.filter((d) => !moved.has(d.instance ?? ''));
|
||||
// The batch can straddle the change — a 300 ms flush can hold the last
|
||||
// decodes of the old band and the first of the new. Only the new band
|
||||
// survives for a receiver that moved.
|
||||
const fresh = moved.size === 0 ? batch
|
||||
: batch.filter((d) => {
|
||||
const b2 = moved.get(d.instance ?? '');
|
||||
return !b2 || (d.band ?? '').toLowerCase() === b2;
|
||||
});
|
||||
const next = [...kept, ...fresh].filter((d) => Date.parse(d.at) >= cutoff);
|
||||
return next;
|
||||
});
|
||||
};
|
||||
@@ -6385,15 +6433,16 @@ export default function App() {
|
||||
// An empty instance lets the backend fall back to whichever application
|
||||
// last reported its status — the normal single-receiver case.
|
||||
onHalt={(instance) => {
|
||||
// Halt means stop, including whatever auto-call had started — and it
|
||||
// clears the engine's state, so a target it had given up on is not
|
||||
// still sitting there when the operator switches it back on.
|
||||
ResetAutoCall().catch(() => {});
|
||||
// Halt is a verdict on the station being called: it is set aside for
|
||||
// the session rather than released, or the next period would call it
|
||||
// straight back — which is exactly what the operator just stopped.
|
||||
HaltAutoCall().catch(() => {});
|
||||
HaltDecodeTx(instance, false).catch((e: any) => setError(String(e?.message ?? e)));
|
||||
}}
|
||||
autoCallOn={!!autoCallStatus?.enabled}
|
||||
onToggleAutoCall={toggleAutoCall}
|
||||
autoCall={autoCallStatus}
|
||||
watchlist={watchPatterns}
|
||||
autoCallOnly={autoCallStatus?.only ?? ''}
|
||||
onSetAutoCallOnly={(list) => {
|
||||
setAutoCallStatus((st: any) => ({ ...st, only: list.toUpperCase() }));
|
||||
|
||||
Reference in New Issue
Block a user