Finishes the half of this that was already computing on the backend and reaching nobody. A Grid column in the Geo group, NEW GRID as a badge in Status, a filled cell in the marker's own colour, and a filter chip. new_grid joins lib/spotMarkers rather than getting colours of its own, so the badge, the cell fill and the chip cannot drift apart - and the per-marker colour setting will drive it with the rest from one table. Magenta: the last hue in the categorical set not already spoken for, and one that does not read as a status, because a new square is never urgent the way a new entity is. The band map leaves it to the cluster, as it already leaves the prefix. The pill is 22 px tall and its accent strip stops being readable past three segments. A row carrying a new grid is no longer "dull", or the dimming would grey out the one thing worth looking at. The column is off by default, like the other Geo columns: it is only ever filled for stations this receiver decoded over the UDP link, so for an operator who does not run digital it would be a permanently empty column.
67 lines
2.8 KiB
TypeScript
67 lines
2.8 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 };
|
|
|
|
export function readSpotDisplayOptions(): SpotDisplayOptions {
|
|
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;
|
|
}
|