revert(cluster): keep the filter selections across restarts
They were made session-only on a misreading: the ask was about the FIRST launch of a fresh install, not every launch. Restored as they were — restored on reopen, and travelling with a copied data/ folder. A first-ever launch already starts with nothing filtered: every selection defaults to empty or off and there is no code that switches one on. The one route by which a new installation can come up filtered is a copied data/ folder, which carries the previous machine's selections by design. The filtered-out message and the clear-every-filter button stay: they are what makes a filter that IS set visible, whenever it was set.
This commit is contained in:
+32
-34
@@ -1438,18 +1438,14 @@ export default function App() {
|
||||
// Ring buffer — only keep the last N spots; cluster firehose can be heavy.
|
||||
const [spots, setSpots] = useState<ClusterSpot[]>([]);
|
||||
const SPOTS_CAP = 1000;
|
||||
// Cluster filters start OFF at every launch, and are deliberately not restored.
|
||||
//
|
||||
// They used to persist, which produced the least diagnosable report there is:
|
||||
// the counter says 76 spots LIVE, the grid is empty, and the cause is a band
|
||||
// lock switched on weeks ago. A filter you did not just set is invisible — you
|
||||
// go looking for a broken cluster instead. Nobody loses work by re-ticking a
|
||||
// chip; people lose an evening to an empty spot list.
|
||||
//
|
||||
// Only the DISPLAY choices below (grouping, panel shown) still persist: they
|
||||
// change how spots look, never whether they appear at all.
|
||||
// Cluster filter selections persist across restarts (writeUiPref → localStorage
|
||||
// + DB, so they also travel with a copied data/ folder). Loaders read the cache
|
||||
// synchronously at first render; a single effect below writes them back.
|
||||
const lsBool = (k: string, d: boolean) => { const v = localStorage.getItem(k); return v === null ? d : v === '1'; };
|
||||
const [clusterFilterSource, setClusterFilterSource] = useState<number | ''>('');
|
||||
const lsSet = <T,>(k: string): Set<T> => { try { const a = JSON.parse(localStorage.getItem(k) || '[]'); return new Set(Array.isArray(a) ? a : []); } catch { return new Set<T>(); } };
|
||||
const [clusterFilterSource, setClusterFilterSource] = useState<number | ''>(() => {
|
||||
const v = localStorage.getItem('opslog.clusterFilterSource'); const n = v ? parseInt(v, 10) : NaN; return Number.isFinite(n) ? n : '';
|
||||
});
|
||||
const [clusterGroup, setClusterGroup] = useState(() => lsBool('opslog.clusterGroup', true));
|
||||
const [clusterCmd, setClusterCmd] = useState('');
|
||||
// Cluster console: the raw traffic. Spots are parsed out of the stream into the
|
||||
@@ -1506,11 +1502,11 @@ export default function App() {
|
||||
if (atBottom) el.scrollTop = el.scrollHeight;
|
||||
}, [clusterLines]);
|
||||
// Multi-band filter: empty set = all bands. The user toggles chips.
|
||||
const [clusterBands, setClusterBands] = useState<Set<string>>(() => new Set());
|
||||
const [clusterBands, setClusterBands] = useState<Set<string>>(() => lsSet<string>('opslog.clusterBands'));
|
||||
// Lock-to-entry: when on, the band filter follows the entry's current
|
||||
// band and the mode filter follows the entry's current mode.
|
||||
const [clusterLockBand, setClusterLockBand] = useState(false);
|
||||
const [clusterLockMode, setClusterLockMode] = useState(false);
|
||||
const [clusterLockBand, setClusterLockBand] = useState(() => lsBool('opslog.clusterLockBand', false));
|
||||
const [clusterLockMode, setClusterLockMode] = useState(() => lsBool('opslog.clusterLockMode', false));
|
||||
// Status filter chips. Empty set = show every status (including
|
||||
// already-worked). Otherwise only matching spots pass.
|
||||
type SpotStatusKey = 'new' | 'new-band' | 'new-mode' | 'new-slot' | 'worked';
|
||||
@@ -1527,21 +1523,21 @@ export default function App() {
|
||||
// LoTW-only, and the spotter's continent. Both narrow the list by a property
|
||||
// of the station rather than by what the spot is worth, which is why they sit
|
||||
// beside Hide worked and not among the status chips.
|
||||
const [clusterLotwOnly, setClusterLotwOnly] = useState(false);
|
||||
const [clusterSpotterConts, setClusterSpotterConts] = useState<Set<string>>(() => new Set());
|
||||
const [clusterLotwOnly, setClusterLotwOnly] = useState(() => localStorage.getItem('opslog.clusterLotwOnly') === '1');
|
||||
const [clusterSpotterConts, setClusterSpotterConts] = useState<Set<string>>(() => lsSet<string>('opslog.clusterSpotterCont'));
|
||||
// Read through lib/spotDisplay, not straight from localStorage: while the two
|
||||
// options are withdrawn it answers false, so the cluster list cannot end up
|
||||
// applying an option the operator can no longer see or switch off.
|
||||
const [clusterMuteWorked, setClusterMuteWorked] = useState(() => readSpotDisplayOptions().muteWorked);
|
||||
const [clusterSlotHighlight, setClusterSlotHighlight] = useState(() => readSpotDisplayOptions().slotHighlight);
|
||||
const [clusterStatusFilter, setClusterStatusFilter] = useState<Set<SpotFilterKey>>(() => new Set());
|
||||
const [clusterStatusFilter, setClusterStatusFilter] = useState<Set<SpotFilterKey>>(() => lsSet<SpotFilterKey>('opslog.clusterStatusFilter'));
|
||||
// Mode filter chips. Empty set = show every mode. Categories map the
|
||||
// inferred per-spot mode onto SSB (phone) / CW / DATA (digital).
|
||||
type SpotModeCat = 'SSB' | 'CW' | 'DATA';
|
||||
const [clusterModeFilter, setClusterModeFilter] = useState<Set<SpotModeCat>>(() => new Set());
|
||||
const [clusterSearch, setClusterSearch] = useState('');
|
||||
const [clusterModeFilter, setClusterModeFilter] = useState<Set<SpotModeCat>>(() => lsSet<SpotModeCat>('opslog.clusterModeFilter'));
|
||||
const [clusterSearch, setClusterSearch] = useState(() => localStorage.getItem('opslog.clusterSearch') || '');
|
||||
// Hide spots already worked (exact call worked, or this band+mode slot done).
|
||||
const [clusterHideWorked, setClusterHideWorked] = useState(false);
|
||||
const [clusterHideWorked, setClusterHideWorked] = useState(() => lsBool('opslog.clusterHideWorked', false));
|
||||
|
||||
// Everything currently narrowing the spot list, in words. Shown when spots
|
||||
// arrived and none survived — the operator needs to know WHICH filter to
|
||||
@@ -1578,20 +1574,22 @@ export default function App() {
|
||||
setClusterSearch('');
|
||||
}, []);
|
||||
|
||||
// Grouping is a display choice, not a filter — it stays remembered.
|
||||
useEffect(() => { writeUiPref('opslog.clusterGroup', clusterGroup ? '1' : '0'); }, [clusterGroup]);
|
||||
|
||||
// Once, at startup: erase the filter values earlier versions stored, in the
|
||||
// local cache AND in the mirrored settings. Without this an upgrade would
|
||||
// leave rows nothing reads — and a downgrade would bring the invisible band
|
||||
// lock straight back.
|
||||
// Persist every cluster filter selection whenever it changes, so it is still
|
||||
// set after a close/reopen.
|
||||
useEffect(() => {
|
||||
for (const k of ['opslog.clusterFilterSource', 'opslog.clusterSearch']) writeUiPref(k, '');
|
||||
for (const k of ['opslog.clusterLockBand', 'opslog.clusterLockMode',
|
||||
'opslog.clusterHideWorked', 'opslog.clusterLotwOnly']) writeUiPref(k, '0');
|
||||
for (const k of ['opslog.clusterBands', 'opslog.clusterStatusFilter',
|
||||
'opslog.clusterModeFilter', 'opslog.clusterSpotterCont']) writeUiPref(k, '[]');
|
||||
}, []);
|
||||
writeUiPref('opslog.clusterFilterSource', clusterFilterSource === '' ? '' : String(clusterFilterSource));
|
||||
writeUiPref('opslog.clusterGroup', clusterGroup ? '1' : '0');
|
||||
writeUiPref('opslog.clusterBands', JSON.stringify([...clusterBands]));
|
||||
writeUiPref('opslog.clusterLockBand', clusterLockBand ? '1' : '0');
|
||||
writeUiPref('opslog.clusterLockMode', clusterLockMode ? '1' : '0');
|
||||
writeUiPref('opslog.clusterStatusFilter', JSON.stringify([...clusterStatusFilter]));
|
||||
writeUiPref('opslog.clusterSpotterCont', JSON.stringify([...clusterSpotterConts]));
|
||||
writeUiPref('opslog.clusterModeFilter', JSON.stringify([...clusterModeFilter]));
|
||||
writeUiPref('opslog.clusterSearch', clusterSearch);
|
||||
writeUiPref('opslog.clusterHideWorked', clusterHideWorked ? '1' : '0');
|
||||
}, [clusterFilterSource, clusterGroup, clusterBands, clusterLockBand, clusterLockMode,
|
||||
clusterStatusFilter, clusterModeFilter, clusterSearch, clusterHideWorked,
|
||||
clusterLotwOnly, clusterSpotterConts]);
|
||||
// Bands shown side-by-side in the Band Map tab (portable).
|
||||
const [bandMapBands, setBandMapBands] = useState<string[]>(() => {
|
||||
try { const v = JSON.parse(localStorage.getItem('opslog.bandMapBands') || '[]'); return Array.isArray(v) ? v : []; }
|
||||
@@ -5084,7 +5082,7 @@ export default function App() {
|
||||
there is one place to flip when they come back. */}
|
||||
{SPOT_DISPLAY_OPTIONS_EXPOSED && fSwitch(t('clu.muteWorkedShort'), clusterMuteWorked, (v) => { setClusterMuteWorked(v); writeUiPref('opslog.clusterMuteWorked', v ? '1' : '0'); })}
|
||||
{SPOT_DISPLAY_OPTIONS_EXPOSED && fSwitch(t('clu.slotHighlightShort'), clusterSlotHighlight, (v) => { setClusterSlotHighlight(v); writeUiPref('opslog.clusterSlotHighlight', v ? '1' : '0'); })}
|
||||
{fSwitch(t('clu.lotwOnly'), clusterLotwOnly, setClusterLotwOnly)}
|
||||
{fSwitch(t('clu.lotwOnly'), clusterLotwOnly, (v) => { setClusterLotwOnly(v); writeUiPref('opslog.clusterLotwOnly', v ? '1' : '0'); })}
|
||||
</div>
|
||||
|
||||
{/* The SPOTTER's continent, not the DX's: this asks whether anyone near
|
||||
|
||||
@@ -34,12 +34,10 @@ const PORTABLE_KEYS = [
|
||||
'opslog.dateFormat', // how dates are DISPLAYED (iso / fr / us); storage stays ISO
|
||||
'opslog.mapGreyline', // world map: grey line (day/night terminator) shown
|
||||
'opslog.awardRefSort', 'opslog.awardRefSortDir', // award reference table: sort column and direction
|
||||
'opslog.clusterGroup', // cluster: group spots by callsign
|
||||
// NOTE: the cluster FILTERS (band, mode, the two rig locks, status, search,
|
||||
// LoTW-only, spotter continent, source) are deliberately absent. They start
|
||||
// off at every launch — a filter restored from a previous session is invisible
|
||||
// to whoever set it, and an empty spot list beside a live counter reads as a
|
||||
// broken cluster. See the comment on their state in App.tsx.
|
||||
// Cluster filter selections — restored on reopen.
|
||||
'opslog.clusterFilterSource', 'opslog.clusterGroup', 'opslog.clusterBands',
|
||||
'opslog.clusterLockBand', 'opslog.clusterLockMode', 'opslog.clusterStatusFilter',
|
||||
'opslog.clusterModeFilter', 'opslog.clusterSearch', 'opslog.clusterHideWorked',
|
||||
'opslog.activeTab', // last selected tab
|
||||
'opslog.mainSplit', // Main tab: width share of the left pane (percent)
|
||||
'opslog.clusterMuteWorked', // cluster/band map: no colour or badge on worked spots
|
||||
|
||||
Reference in New Issue
Block a user