An operator who does not chase parks does not want NEW POTA shouting from every activator spot. Two switches (Settings → DX Cluster, on by default): off, the marker is withdrawn at the display layer — applySpotDisplay, the same chokepoint the grid, the band map and the filter read through — so the badge, the colour, the filter chip and the reference column all go quiet together, and a new-band + new-POTA spot reads NEW BAND alone. The facts keep being computed; only the telling stops, so ticking the box back on needs no rescan.
116 lines
5.2 KiB
TypeScript
116 lines
5.2 KiB
TypeScript
// Two operator options that change how a spot LOOKS, shared by the DX-cluster
|
|
// list and the band map so the two panels can never disagree about the same
|
|
// spot — the lesson already learnt with the marker colours.
|
|
//
|
|
// muteWorked — a station already worked gets no colour and no badge. It
|
|
// stays in the list, it simply stops competing for attention.
|
|
// slotHighlight — a callsign not yet worked on THIS band and mode is coloured,
|
|
// whatever the entity says. For an operator filling slots, a
|
|
// common entity on a new band+mode is the whole point, and the
|
|
// entity-level status calls it "worked".
|
|
//
|
|
// They compose deliberately: mute what is done, light up what is not.
|
|
|
|
export type SpotDisplayOptions = {
|
|
muteWorked: boolean; slotHighlight: boolean;
|
|
// Chase switches (Settings → DX Cluster), ON by default. An operator who does
|
|
// not chase parks does not want NEW POTA shouting from every activator spot:
|
|
// off, the marker is withdrawn at the display layer — a "new band + new POTA"
|
|
// spot simply reads NEW BAND — and the reference column goes quiet. The facts
|
|
// keep being computed; only the telling stops, so ticking the box back on
|
|
// needs no rescan.
|
|
chasePota: boolean; chaseSota: boolean;
|
|
};
|
|
|
|
// chasePota/chaseSota read the switches directly — for the places that show a
|
|
// REFERENCE rather than a status (the POTA and SOTA columns).
|
|
export function chasePota(): boolean {
|
|
try { return localStorage.getItem('opslog.chasePota') !== '0'; } catch { return true; }
|
|
}
|
|
export function chaseSota(): boolean {
|
|
try { return localStorage.getItem('opslog.chaseSota') !== '0'; } catch { return true; }
|
|
}
|
|
|
|
// Both options are withdrawn from the filter panel for now. The machinery below
|
|
// is deliberately kept whole — it is correct and hard-won — so putting the two
|
|
// switches back is this one flag and the block they came from in App.tsx.
|
|
//
|
|
// The saved preferences are left untouched in localStorage rather than cleared:
|
|
// an operator who had either turned on gets them back exactly as they were the
|
|
// day the options return, instead of silently starting from off.
|
|
export const SPOT_DISPLAY_OPTIONS_EXPOSED = false;
|
|
|
|
export function readSpotDisplayOptions(): SpotDisplayOptions {
|
|
// The EXPOSED flag only withdraws the two original switches; the chase
|
|
// switches are live regardless.
|
|
if (!SPOT_DISPLAY_OPTIONS_EXPOSED) {
|
|
return { muteWorked: false, slotHighlight: false, chasePota: chasePota(), chaseSota: chaseSota() };
|
|
}
|
|
try {
|
|
return {
|
|
muteWorked: localStorage.getItem('opslog.clusterMuteWorked') === '1',
|
|
slotHighlight: localStorage.getItem('opslog.clusterSlotHighlight') === '1',
|
|
chasePota: chasePota(), chaseSota: chaseSota(),
|
|
};
|
|
} catch {
|
|
return { muteWorked: false, slotHighlight: false, chasePota: true, chaseSota: true };
|
|
}
|
|
}
|
|
|
|
type Entry = {
|
|
status?: string;
|
|
worked_call?: boolean;
|
|
worked_slot?: boolean;
|
|
new_county?: boolean;
|
|
new_pota?: boolean;
|
|
new_pfx?: boolean;
|
|
new_grid?: boolean;
|
|
} | undefined;
|
|
|
|
// applySpotDisplay rewrites a status entry per the options, so every consumer —
|
|
// colour, badge, status text — follows from one decision instead of each panel
|
|
// re-deriving it.
|
|
export function applySpotDisplay<T extends Entry>(s: T, o: SpotDisplayOptions): T {
|
|
if (!s) return s;
|
|
let e = s;
|
|
|
|
// Slot promotion runs first: a callsign not yet worked on this band and mode
|
|
// is not done, whatever the entity says, so it earns a status before the mute
|
|
// below can take its colour away.
|
|
if (o.slotHighlight && e.worked_slot === false && (!e.status || e.status === 'worked')) {
|
|
e = { ...e, status: 'new-call' } as NonNullable<T>;
|
|
}
|
|
|
|
// Mute drops the blue already-worked-callsign mark, and NOTHING else.
|
|
//
|
|
// It used to blank the status as well, on the theory that a spot bringing no
|
|
// novelty should stop painting entirely. That was wrong twice over. The status
|
|
// is what the cluster list reads to DIM a row, so blanking it turned every
|
|
// quiet grey row bright white — the option made the list louder, not quieter.
|
|
// And an empty status means "entity not resolved" everywhere else, so muted
|
|
// spots had to carry a flag saying they did not really mean that.
|
|
//
|
|
// Leaving the status alone costs nothing: a worked entity already renders with
|
|
// no colour and gets dimmed, so removing the blue is the entire job.
|
|
if (o.muteWorked) {
|
|
e = { ...e, worked_call: false } as NonNullable<T>;
|
|
}
|
|
if (!o.chasePota && e.new_pota) {
|
|
e = { ...e, new_pota: false } as NonNullable<T>;
|
|
}
|
|
return e;
|
|
}
|
|
|
|
// spotIsWorked answers "does this spot bring me nothing?", which is what the
|
|
// "hide worked" filter is really asking.
|
|
//
|
|
// The extra markers are ORTHOGONAL to the entity status — a spot can be a worked
|
|
// entity AND a new park, prefix, county or grid — so they win. Without that, a
|
|
// new prefix in an entity worked years ago vanished the moment the filter went
|
|
// on, which is the opposite of what an operator hunting prefixes wants.
|
|
export function spotIsWorked(e: Entry): boolean {
|
|
if (!e) return false;
|
|
if (e.new_pota || e.new_county || e.new_pfx || e.new_grid) return false;
|
|
return !!e.worked_call || e.status === 'worked';
|
|
}
|