feat(cluster): configurable spot lifetime
Spots were bounded by COUNT alone, so on a quiet band a two-hour-old spot sat on the band map looking like something to chase. Settings → DX Cluster now takes a lifetime: presets at 5/10/15/30/60 minutes, or any value up to twelve hours, and 0 keeps the old behaviour. Spots are REMOVED from the shared list rather than hidden at render. The cluster list, every band map and the counts all read that one array, so pruning it once is what makes the setting mean the same thing in every view — the lesson already paid for when the band map ignored the cluster's filters. It also gives the memory back. A spot whose timestamp will not parse is never dropped: an unreadable stamp is a reason to distrust the clock, not to throw away the spot. Swept every 30 seconds — close enough that a 5-minute setting is honoured, and invisible next to the spot stream. Clamped in the backend as well as the UI.
This commit is contained in:
@@ -52,7 +52,7 @@ import {
|
||||
GetADIFMonitor, SaveADIFMonitor, PickADIFMonitorFile,
|
||||
GetRelayAuto, SaveRelayAuto, GetStationDevices,
|
||||
GetAwardDefs, GetTrackedAwards, SaveTrackedAwards,
|
||||
GetBandOpenSettings, SaveBandOpenSettings, GetPSKReporterStatus, GetChaseNewGrids, SetChaseNewGrids, GetGridCacheStatus,
|
||||
GetBandOpenSettings, SaveBandOpenSettings, GetPSKReporterStatus, GetChaseNewGrids, SetChaseNewGrids, GetGridCacheStatus, GetSpotTTLMinutes, SetSpotTTLMinutes,
|
||||
} from '../../wailsjs/go/main/App';
|
||||
import type { profile as profileModels } from '../../wailsjs/go/models';
|
||||
import type { LookupSettingsForm, StationSettingsForm, ListsSettingsForm, ModePresetForm } from '@/types';
|
||||
@@ -1556,6 +1556,8 @@ export function SettingsModal({ onClose, onSaved, initialSection, onMainPaneChan
|
||||
// feed up or down — so the write has to go where those live.
|
||||
const [bandOpen, setBandOpen] = useState<any>({ enabled: false, bands: [], available: [] });
|
||||
const [chaseGrids, setChaseGrids] = useState(false);
|
||||
const [spotTTL, setSpotTTL] = useState(0);
|
||||
const [spotTTLText, setSpotTTLText] = useState('0');
|
||||
const [gridStat, setGridStat] = useState<any>(null);
|
||||
const [pskrStatus, setPskrStatus] = useState<any>(null);
|
||||
const saveBandOpen = async (next: any) => {
|
||||
@@ -1566,6 +1568,7 @@ export function SettingsModal({ onClose, onSaved, initialSection, onMainPaneChan
|
||||
(async () => {
|
||||
try { setBandOpen(await GetBandOpenSettings()); } catch { /* defaults stand */ }
|
||||
try { setChaseGrids(await GetChaseNewGrids()); } catch { /* defaults stand */ }
|
||||
try { const n = await GetSpotTTLMinutes(); setSpotTTL(n); setSpotTTLText(String(n)); } catch { /* defaults stand */ }
|
||||
})();
|
||||
// Poll the feed while the panel is open: a live count is the only thing that
|
||||
// distinguishes "connected" from "connected and receiving nothing".
|
||||
@@ -4237,6 +4240,35 @@ export function SettingsModal({ onClose, onSaved, initialSection, onMainPaneChan
|
||||
{/* Grid chasing. Here rather than in the filter panel because it is set
|
||||
up once: it decides whether locators learnt from decodes are KEPT
|
||||
across restarts, not what the list shows right now. */}
|
||||
{/* Spot lifetime. Applies to the cluster list AND every band map: the
|
||||
spots are removed from the shared list rather than hidden, so the
|
||||
setting cannot mean one thing in one view and another elsewhere. */}
|
||||
<div className="border-t border-border/60 pt-3 space-y-2">
|
||||
<div className="flex items-center gap-3 flex-wrap">
|
||||
<span className="text-sm">{t('clu.spotTtl')}</span>
|
||||
<div className="inline-flex rounded-md border border-border overflow-hidden text-xs">
|
||||
{[0, 5, 10, 15, 30, 60].map((v) => (
|
||||
<button key={v} type="button"
|
||||
onClick={() => { setSpotTTL(v); setSpotTTLText(String(v)); SetSpotTTLMinutes(v).catch(() => {}); }}
|
||||
className={cn('px-2.5 py-1.5 font-medium', spotTTL === v ? 'bg-primary text-primary-foreground' : 'text-muted-foreground hover:bg-muted')}>
|
||||
{v === 0 ? t('clu.spotTtlNever') : `${v}′`}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
{/* Raw text in local state: binding the input straight to the
|
||||
clamped number makes an empty field impossible to type into. */}
|
||||
<Input className="h-8 w-20" value={spotTTLText}
|
||||
onChange={(e) => {
|
||||
const raw = e.target.value.replace(/[^0-9]/g, '');
|
||||
setSpotTTLText(raw);
|
||||
const n = Math.min(720, parseInt(raw, 10) || 0);
|
||||
setSpotTTL(n);
|
||||
SetSpotTTLMinutes(n).catch(() => {});
|
||||
}} />
|
||||
<span className="text-xs text-muted-foreground">{t('clu.spotTtlHint')}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="border-t border-border/60 pt-3 space-y-2">
|
||||
<label className="flex items-start gap-2 text-sm cursor-pointer">
|
||||
<Checkbox checked={chaseGrids} className="mt-0.5"
|
||||
|
||||
Reference in New Issue
Block a user