Reported on BH2SWB and 4X9AA: both flagged NEW PFX, neither ever worked, both vanished the moment "hide worked" went on. The predicate hid a spot when worked_call OR status === 'worked' — and that second one is the ENTITY's status, not the callsign's. So any spot in an entity worked years ago disappeared, however new it was for something else. The extra markers are orthogonal to the entity status; the band map already says so in as many words. They now win: a new prefix, county, grid or park is never hidden, whatever the entity says. A worked entity that is new for nothing is still hidden, which is what the filter is for. Extracted to spotIsWorked in lib/spotDisplay, beside the other rule the cluster and band map share, rather than left inline in a predicate that has already grown once. This repo has no frontend test runner, so this is verified by build and reading rather than by a test.
90 lines
4.0 KiB
TypeScript
90 lines
4.0 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 };
|
|
|
|
// 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 {
|
|
if (!SPOT_DISPLAY_OPTIONS_EXPOSED) return { muteWorked: false, slotHighlight: false };
|
|
try {
|
|
return {
|
|
muteWorked: localStorage.getItem('opslog.clusterMuteWorked') === '1',
|
|
slotHighlight: localStorage.getItem('opslog.clusterSlotHighlight') === '1',
|
|
};
|
|
} catch {
|
|
return { muteWorked: false, slotHighlight: false };
|
|
}
|
|
}
|
|
|
|
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>;
|
|
}
|
|
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';
|
|
}
|