// 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(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; } // 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; } if (!o.chasePota && e.new_pota) { e = { ...e, new_pota: false } as NonNullable; } 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'; }