feat(cluster): the spot list size is a setting
The list is a ring buffer that held a thousand spots, and on a busy evening a thousand arrive in a couple of minutes. So the buffer decided how long a spot lived, and the spot LIFETIME setting never got the chance to expire anything: fifteen minutes meant nothing when the oldest spot was pushed out after two. Now settable (Preferences → Cluster, beside the lifetime, since between them they decide the same thing), 100 to 10 000. The ceiling is a real limit rather than a round number: every spot is matched against the worked index and the alert rules, and is a row the cluster grid and every open band map re-render.
This commit is contained in:
@@ -312,6 +312,7 @@ const (
|
|||||||
keyClusterSelfSpot = "cluster.self_spot" // "1" → announce ourselves on the cluster as we log
|
keyClusterSelfSpot = "cluster.self_spot" // "1" → announce ourselves on the cluster as we log
|
||||||
keyClusterSelfSpotMin = "cluster.self_spot_minutes" // shortest gap between two self-spots
|
keyClusterSelfSpotMin = "cluster.self_spot_minutes" // shortest gap between two self-spots
|
||||||
keyClusterSpotTTL = "cluster.spot_ttl_min" // drop spots older than this; 0 = keep them
|
keyClusterSpotTTL = "cluster.spot_ttl_min" // drop spots older than this; 0 = keep them
|
||||||
|
keyClusterSpotMax = "cluster.spot_max" // how many spots the list holds at once
|
||||||
|
|
||||||
keyScpEnabled = "scp.enabled" // Super Check Partial / N+1 suggestions on
|
keyScpEnabled = "scp.enabled" // Super Check Partial / N+1 suggestions on
|
||||||
|
|
||||||
@@ -20294,6 +20295,50 @@ func (a *App) SetKenwoodKeySpeed(wpm int) error {
|
|||||||
// and exists only so a mistyped value cannot mean "never".
|
// and exists only so a mistyped value cannot mean "never".
|
||||||
const spotTTLMax = 720
|
const spotTTLMax = 720
|
||||||
|
|
||||||
|
// The spot list is a ring buffer, and its size decided the lifetime far more
|
||||||
|
// often than the lifetime setting did: on a busy evening a thousand spots
|
||||||
|
// arrive in a couple of minutes, so a fifteen-minute lifetime never got the
|
||||||
|
// chance to expire anything. Hence a setting.
|
||||||
|
//
|
||||||
|
// The ceiling is a real limit, not a round number. Every spot is matched against
|
||||||
|
// the worked index and the alert rules, and each one is a row the cluster grid
|
||||||
|
// and every open band map re-render; past ten thousand that work starts to show
|
||||||
|
// on the very evenings the list is worth having.
|
||||||
|
const (
|
||||||
|
spotMaxDefault = 1000
|
||||||
|
spotMaxCeiling = 10000
|
||||||
|
spotMaxFloor = 100
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetSpotMax returns how many spots the list holds.
|
||||||
|
func (a *App) GetSpotMax() int {
|
||||||
|
n, _ := strconv.Atoi(a.settingOr(keyClusterSpotMax, ""))
|
||||||
|
if n <= 0 {
|
||||||
|
return spotMaxDefault
|
||||||
|
}
|
||||||
|
return clampSpotMax(n)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetSpotMax sets it. Clamped here as well as in the UI, for the same reason as
|
||||||
|
// the lifetime: the value decides how much work arrives on every spot, and a
|
||||||
|
// stale frontend must not be able to widen it past the ceiling.
|
||||||
|
func (a *App) SetSpotMax(n int) error {
|
||||||
|
a.setSetting(keyClusterSpotMax, strconv.Itoa(clampSpotMax(n)))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func clampSpotMax(n int) int {
|
||||||
|
if n < spotMaxFloor {
|
||||||
|
return spotMaxFloor
|
||||||
|
}
|
||||||
|
if n > spotMaxCeiling {
|
||||||
|
return spotMaxCeiling
|
||||||
|
}
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// GetSpotTTLMinutes returns how long a spot stays in the list, in minutes.
|
// GetSpotTTLMinutes returns how long a spot stays in the list, in minutes.
|
||||||
// 0 means spots are kept until the count cap pushes them out, which is what
|
// 0 means spots are kept until the count cap pushes them out, which is what
|
||||||
// OpsLog always did.
|
// OpsLog always did.
|
||||||
|
|||||||
+4
-2
@@ -11,7 +11,8 @@
|
|||||||
"Yaesu console: the NAR button (narrow IF filter) is shown only when the radio answers the command, and says what it is. A button that did nothing when pressed read as a fault in the rig.",
|
"Yaesu console: the NAR button (narrow IF filter) is shown only when the radio answers the command, and says what it is. A button that did nothing when pressed read as a fault in the rig.",
|
||||||
"The frequency readout now steps the Hz digits under the mouse wheel too, not just the kHz ones — 100, 10 and 1 Hz. Zero-beating a CW signal is a few tens of Hz, and it was the one move the display would not make.",
|
"The frequency readout now steps the Hz digits under the mouse wheel too, not just the kHz ones — 100, 10 and 1 Hz. Zero-beating a CW signal is a few tens of Hz, and it was the one move the display would not make.",
|
||||||
"The split-pile-up chaser only appears in CW: the marker comes from a skimmer decoding a report, so on SSB or FT8 there is nothing for it to chase.",
|
"The split-pile-up chaser only appears in CW: the marker comes from a skimmer decoding a report, so on SSB or FT8 there is nothing for it to chase.",
|
||||||
"Cluster: a single click now only fills the callsign, and a DOUBLE click works the spot — QSY, mode and the rest. Running down the list used to drag the radio along with every line looked at."
|
"Cluster: a single click now only fills the callsign, and a DOUBLE click works the spot — QSY, mode and the rest. Running down the list used to drag the radio along with every line looked at.",
|
||||||
|
"Cluster: how many spots the list keeps is now a setting (Preferences → Cluster, beside the spot lifetime). It was fixed at a thousand, and on a busy evening that fills in a couple of minutes — so the cap, not the lifetime, decided when a spot disappeared and a 15-minute lifetime never expired anything."
|
||||||
],
|
],
|
||||||
"fr": [
|
"fr": [
|
||||||
"Quand TQSL refuse un envoi, le journal contient désormais l'enregistrement ADIF exact qui lui a été remis et l'emplacement de station demandé pour la signature. « No QSOs processed » recouvre plusieurs causes sans rapport et le fichier temporaire est supprimé dès que TQSL rend la main : la seule pièce à conviction utile était justement invisible.",
|
"Quand TQSL refuse un envoi, le journal contient désormais l'enregistrement ADIF exact qui lui a été remis et l'emplacement de station demandé pour la signature. « No QSOs processed » recouvre plusieurs causes sans rapport et le fichier temporaire est supprimé dès que TQSL rend la main : la seule pièce à conviction utile était justement invisible.",
|
||||||
@@ -22,7 +23,8 @@
|
|||||||
"Console Yaesu : le bouton NAR (filtre FI étroit) n'apparaît que si la radio répond à la commande, et indique ce qu'il fait. Un bouton sans effet passait pour une panne de la radio.",
|
"Console Yaesu : le bouton NAR (filtre FI étroit) n'apparaît que si la radio répond à la commande, et indique ce qu'il fait. Un bouton sans effet passait pour une panne de la radio.",
|
||||||
"L'affichage de fréquence fait maintenant défiler aussi les chiffres des Hz à la molette, pas seulement ceux des kHz — 100, 10 et 1 Hz. Se caler au zéro-beat sur un signal CW se joue à quelques dizaines de Hz, et c'était le seul geste que l'affichage refusait.",
|
"L'affichage de fréquence fait maintenant défiler aussi les chiffres des Hz à la molette, pas seulement ceux des kHz — 100, 10 et 1 Hz. Se caler au zéro-beat sur un signal CW se joue à quelques dizaines de Hz, et c'était le seul geste que l'affichage refusait.",
|
||||||
"Le chasseur de pile-up en split n'apparaît qu'en CW : le marqueur vient d'un skimmer qui décode un report, donc en SSB ou en FT8 il n'a rien à chasser.",
|
"Le chasseur de pile-up en split n'apparaît qu'en CW : le marqueur vient d'un skimmer qui décode un report, donc en SSB ou en FT8 il n'a rien à chasser.",
|
||||||
"Cluster : un clic simple ne remplit plus que l'indicatif, et le DOUBLE clic travaille le spot — QSY, mode et le reste. Parcourir la liste entraînait la radio à chaque ligne regardée."
|
"Cluster : un clic simple ne remplit plus que l'indicatif, et le DOUBLE clic travaille le spot — QSY, mode et le reste. Parcourir la liste entraînait la radio à chaque ligne regardée.",
|
||||||
|
"Cluster : le nombre de spots conservés devient un réglage (Préférences → Cluster, à côté de la durée de vie). Il était fixé à mille, et un soir chargé remplit ça en quelques minutes — c'était donc le plafond, et non la durée de vie, qui décidait de la disparition d'un spot, et une durée de 15 minutes n'expirait jamais rien."
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
+15
-4
@@ -95,7 +95,7 @@ import { ShutdownProgress } from '@/components/ShutdownProgress';
|
|||||||
import { ClusterGrid } from '@/components/ClusterGrid';
|
import { ClusterGrid } from '@/components/ClusterGrid';
|
||||||
import { cleanSpotter, inferSpotMode, spotModeCategory, spotStatusKey } from '@/lib/spot';
|
import { cleanSpotter, inferSpotMode, spotModeCategory, spotStatusKey } from '@/lib/spot';
|
||||||
import { applySpotDisplay, readSpotDisplayOptions, spotIsWorked, SPOT_DISPLAY_OPTIONS_EXPOSED } from '@/lib/spotDisplay';
|
import { applySpotDisplay, readSpotDisplayOptions, spotIsWorked, SPOT_DISPLAY_OPTIONS_EXPOSED } from '@/lib/spotDisplay';
|
||||||
import { AnswerDecode, HaltDecodeTx, LogUIError, FlexTXOnBand, GetMatrixColors, GetRotorPresets, GetRowColors, GetSpotTTLMinutes, IsNewUSCounty } from '../wailsjs/go/main/App';
|
import { AnswerDecode, HaltDecodeTx, LogUIError, FlexTXOnBand, GetMatrixColors, GetRotorPresets, GetRowColors, GetSpotTTLMinutes, GetSpotMax, IsNewUSCounty } from '../wailsjs/go/main/App';
|
||||||
import { applyMatrixColors } from '@/lib/matrixColors';
|
import { applyMatrixColors } from '@/lib/matrixColors';
|
||||||
import { WorkedBeforeGrid } from '@/components/WorkedBeforeGrid';
|
import { WorkedBeforeGrid } from '@/components/WorkedBeforeGrid';
|
||||||
import { NetControlPanel } from '@/components/NetControlPanel';
|
import { NetControlPanel } from '@/components/NetControlPanel';
|
||||||
@@ -1663,7 +1663,14 @@ export default function App() {
|
|||||||
const [clusterServers, setClusterServers] = useState<{ id: number; name: string; enabled: boolean; sort_order: number }[]>([]);
|
const [clusterServers, setClusterServers] = useState<{ id: number; name: string; enabled: boolean; sort_order: number }[]>([]);
|
||||||
// Ring buffer — only keep the last N spots; cluster firehose can be heavy.
|
// Ring buffer — only keep the last N spots; cluster firehose can be heavy.
|
||||||
const [spots, setSpots] = useState<ClusterSpot[]>([]);
|
const [spots, setSpots] = useState<ClusterSpot[]>([]);
|
||||||
const SPOTS_CAP = 1000;
|
// How many spots the list holds. A setting rather than a constant: at a
|
||||||
|
// thousand, a busy evening filled the buffer in a couple of minutes, so the
|
||||||
|
// spot LIFETIME never had anything left to expire — the cap was deciding the
|
||||||
|
// lifetime. Kept in a ref as well: the append path runs inside a state updater
|
||||||
|
// that must not close over a stale value.
|
||||||
|
const [spotsCap, setSpotsCap] = useState(1000);
|
||||||
|
const spotsCapRef = useRef(1000);
|
||||||
|
useEffect(() => { spotsCapRef.current = spotsCap; }, [spotsCap]);
|
||||||
// Cluster filter selections persist across restarts (writeUiPref → localStorage
|
// Cluster filter selections persist across restarts (writeUiPref → localStorage
|
||||||
// + DB, so they also travel with a copied data/ folder). Loaders read the cache
|
// + 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.
|
// synchronously at first render; a single effect below writes them back.
|
||||||
@@ -1977,7 +1984,7 @@ export default function App() {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setSpotStatus((prev) => {
|
setSpotStatus((prev) => {
|
||||||
const keys = Object.keys(prev);
|
const keys = Object.keys(prev);
|
||||||
if (keys.length <= SPOTS_CAP * 2) return prev;
|
if (keys.length <= spotsCapRef.current * 2) return prev;
|
||||||
const live = new Set(spots.map((x) => spotStatusKey(x.dx_call, x.band ?? '', x.comment ?? '', x.freq_hz)));
|
const live = new Set(spots.map((x) => spotStatusKey(x.dx_call, x.band ?? '', x.comment ?? '', x.freq_hz)));
|
||||||
// Decoded stations count as live too. They share this cache, and pruning
|
// Decoded stations count as live too. They share this cache, and pruning
|
||||||
// to the cluster spots alone would evict every one of them — on a busy
|
// to the cluster spots alone would evict every one of them — on a busy
|
||||||
@@ -2488,6 +2495,9 @@ export default function App() {
|
|||||||
// same thing everywhere — and it gives the memory back.
|
// same thing everywhere — and it gives the memory back.
|
||||||
const [spotTTLMin, setSpotTTLMin] = useState(0);
|
const [spotTTLMin, setSpotTTLMin] = useState(0);
|
||||||
useEffect(() => { GetSpotTTLMinutes().then(setSpotTTLMin).catch(() => {}); }, [showSettings]);
|
useEffect(() => { GetSpotTTLMinutes().then(setSpotTTLMin).catch(() => {}); }, [showSettings]);
|
||||||
|
// Same beat as the lifetime: re-read when Preferences closes, since the two
|
||||||
|
// settings decide between them how long a spot survives.
|
||||||
|
useEffect(() => { GetSpotMax().then((n: number) => { if (n > 0) setSpotsCap(n); }).catch(() => {}); }, [showSettings]);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (spotTTLMin <= 0) return; // 0 = keep until the count cap pushes them out
|
if (spotTTLMin <= 0) return; // 0 = keep until the count cap pushes them out
|
||||||
const sweep = () => {
|
const sweep = () => {
|
||||||
@@ -3478,7 +3488,8 @@ export default function App() {
|
|||||||
const filtered = hist(sp) ? next : next.filter((x) => hist(x) || key(x) !== k);
|
const filtered = hist(sp) ? next : next.filter((x) => hist(x) || key(x) !== k);
|
||||||
next = [sp, ...filtered];
|
next = [sp, ...filtered];
|
||||||
}
|
}
|
||||||
return next.length > SPOTS_CAP ? next.slice(0, SPOTS_CAP) : next;
|
const cap = spotsCapRef.current;
|
||||||
|
return next.length > cap ? next.slice(0, cap) : next;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
const unsubSpot = EventsOn('cluster:spot', (sp: ClusterSpot) => {
|
const unsubSpot = EventsOn('cluster:spot', (sp: ClusterSpot) => {
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ import {
|
|||||||
GetFolderSync, SaveFolderSync, PickFolderSyncFolder, GetFolderSyncStatus, SyncFolderNow,
|
GetFolderSync, SaveFolderSync, PickFolderSyncFolder, GetFolderSyncStatus, SyncFolderNow,
|
||||||
GetRelayAuto, SaveRelayAuto, GetStationDevices,
|
GetRelayAuto, SaveRelayAuto, GetStationDevices,
|
||||||
GetAwardDefs, GetTrackedAwards, SaveTrackedAwards,
|
GetAwardDefs, GetTrackedAwards, SaveTrackedAwards,
|
||||||
GetBandOpenSettings, SaveBandOpenSettings, GetGridScopeSettings, SaveGridScopeSettings, GetPSKReporterStatus, GetChaseNewGrids, SetChaseNewGrids, GetChaseNew, SetChaseNew, GetGridCacheStatus, GetLinkedAmps, SetLinkedAmps, GetSpotTTLMinutes, SetSpotTTLMinutes,
|
GetBandOpenSettings, SaveBandOpenSettings, GetGridScopeSettings, SaveGridScopeSettings, GetPSKReporterStatus, GetChaseNewGrids, SetChaseNewGrids, GetChaseNew, SetChaseNew, GetGridCacheStatus, GetLinkedAmps, SetLinkedAmps, GetSpotTTLMinutes, SetSpotTTLMinutes, GetSpotMax, SetSpotMax,
|
||||||
} from '../../wailsjs/go/main/App';
|
} from '../../wailsjs/go/main/App';
|
||||||
import type { profile as profileModels } from '../../wailsjs/go/models';
|
import type { profile as profileModels } from '../../wailsjs/go/models';
|
||||||
import type { LookupSettingsForm, StationSettingsForm, ListsSettingsForm, ModePresetForm } from '@/types';
|
import type { LookupSettingsForm, StationSettingsForm, ListsSettingsForm, ModePresetForm } from '@/types';
|
||||||
@@ -1942,6 +1942,7 @@ export function SettingsModal({ onClose, onSaved, initialSection, onMainPaneChan
|
|||||||
const [chaseNew, setChaseNew] = useState(false);
|
const [chaseNew, setChaseNew] = useState(false);
|
||||||
const [spotTTL, setSpotTTL] = useState(0);
|
const [spotTTL, setSpotTTL] = useState(0);
|
||||||
const [spotTTLText, setSpotTTLText] = useState('0');
|
const [spotTTLText, setSpotTTLText] = useState('0');
|
||||||
|
const [spotMaxText, setSpotMaxText] = useState('1000');
|
||||||
const [gridStat, setGridStat] = useState<any>(null);
|
const [gridStat, setGridStat] = useState<any>(null);
|
||||||
const [pskrStatus, setPskrStatus] = useState<any>(null);
|
const [pskrStatus, setPskrStatus] = useState<any>(null);
|
||||||
const saveBandOpen = async (next: any) => {
|
const saveBandOpen = async (next: any) => {
|
||||||
@@ -1955,6 +1956,7 @@ export function SettingsModal({ onClose, onSaved, initialSection, onMainPaneChan
|
|||||||
try { setChaseNew(await GetChaseNew()); } catch { /* defaults stand */ }
|
try { setChaseNew(await GetChaseNew()); } catch { /* defaults stand */ }
|
||||||
try { setLinkedAmps((await GetLinkedAmps()) ?? []); } catch { /* defaults stand */ }
|
try { setLinkedAmps((await GetLinkedAmps()) ?? []); } catch { /* defaults stand */ }
|
||||||
try { const n = await GetSpotTTLMinutes(); setSpotTTL(n); setSpotTTLText(String(n)); } catch { /* defaults stand */ }
|
try { const n = await GetSpotTTLMinutes(); setSpotTTL(n); setSpotTTLText(String(n)); } catch { /* defaults stand */ }
|
||||||
|
try { const n = await GetSpotMax(); setSpotMaxText(String(n)); } catch { /* defaults stand */ }
|
||||||
})();
|
})();
|
||||||
// Poll the feed while the panel is open: a live count is the only thing that
|
// Poll the feed while the panel is open: a live count is the only thing that
|
||||||
// distinguishes "connected" from "connected and receiving nothing".
|
// distinguishes "connected" from "connected and receiving nothing".
|
||||||
@@ -4973,6 +4975,28 @@ export function SettingsModal({ onClose, onSaved, initialSection, onMainPaneChan
|
|||||||
}} />
|
}} />
|
||||||
<span className="text-xs text-muted-foreground">{t('clu.spotTtlHint')}</span>
|
<span className="text-xs text-muted-foreground">{t('clu.spotTtlHint')}</span>
|
||||||
</div>
|
</div>
|
||||||
|
{/* The list size, beside the lifetime because between them they decide
|
||||||
|
the same thing: whichever runs out first removes the spot. */}
|
||||||
|
<div className="flex items-center gap-3 flex-wrap">
|
||||||
|
<span className="text-sm">{t('clu.spotMax')}</span>
|
||||||
|
<div className="inline-flex rounded-md border border-border overflow-hidden text-xs">
|
||||||
|
{[500, 1000, 2500, 5000].map((v) => (
|
||||||
|
<button key={v} type="button"
|
||||||
|
onClick={() => { setSpotMaxText(String(v)); SetSpotMax(v).catch(() => {}); }}
|
||||||
|
className={cn('px-2.5 py-1.5 font-medium', Number(spotMaxText) === v ? 'bg-primary text-primary-foreground' : 'text-muted-foreground hover:bg-muted')}>
|
||||||
|
{v}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<Input className="h-8 w-24" value={spotMaxText}
|
||||||
|
onChange={(e) => {
|
||||||
|
const raw = e.target.value.replace(/[^0-9]/g, '');
|
||||||
|
setSpotMaxText(raw);
|
||||||
|
const n = parseInt(raw, 10);
|
||||||
|
if (Number.isFinite(n) && n > 0) SetSpotMax(Math.min(10000, n)).catch(() => {});
|
||||||
|
}} />
|
||||||
|
<span className="text-xs text-muted-foreground">{t('clu.spotMaxHint')}</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="border-t border-border/60 pt-3 space-y-2">
|
<div className="border-t border-border/60 pt-3 space-y-2">
|
||||||
|
|||||||
@@ -313,7 +313,7 @@ const en: Dict = {
|
|||||||
'gsc.scope': 'Match a square by', 'gsc.hunt': 'Chase', 'gsc.huntNew': 'New — never worked', 'gsc.huntUnconf': 'New and unconfirmed', 'gsc.scope_band_digi': 'This band + any digital mode', 'gsc.scope_band_mode': 'This band + this exact mode', 'gsc.scope_band_ftx': 'This band + any FT mode (FT8/FT4/FT2)', 'gsc.scope_mix_digi': 'Any band + any digital mode', 'gsc.scope_mix_mode': 'Any band + this exact mode', 'gsc.scope_mix_ftx': 'Any band + any FT mode (FT8/FT4/FT2)', 'gsc.hint': 'Decides when a square stops being NEW. Narrower means more squares to chase: per band and per exact mode is the most demanding, any band and any digital mode the least. Chasing unconfirmed as well keeps a square wanted until a QSL, LoTW or eQSL confirmation arrives — it is still missing from the award until then.',
|
'gsc.scope': 'Match a square by', 'gsc.hunt': 'Chase', 'gsc.huntNew': 'New — never worked', 'gsc.huntUnconf': 'New and unconfirmed', 'gsc.scope_band_digi': 'This band + any digital mode', 'gsc.scope_band_mode': 'This band + this exact mode', 'gsc.scope_band_ftx': 'This band + any FT mode (FT8/FT4/FT2)', 'gsc.scope_mix_digi': 'Any band + any digital mode', 'gsc.scope_mix_mode': 'Any band + this exact mode', 'gsc.scope_mix_ftx': 'Any band + any FT mode (FT8/FT4/FT2)', 'gsc.hint': 'Decides when a square stops being NEW. Narrower means more squares to chase: per band and per exact mode is the most demanding, any band and any digital mode the least. Chasing unconfirmed as well keeps a square wanted until a QSL, LoTW or eQSL confirmation arrives — it is still missing from the award until then.',
|
||||||
'gsm.basemap': 'Basemap', 'gsm.title': 'Grid squares', 'gsm.all': 'All', 'gsm.phone': 'Phone', 'gsm.cw': 'CW', 'gsm.digital': 'Digital', 'gsm.ftx': 'FTx', 'gsm.confirmed': 'confirmed', 'gsm.worked': 'worked', 'gsm.colConfirmed': 'Colour for confirmed squares', 'gsm.colWorked': 'Colour for worked (unconfirmed) squares', 'gsm.colReset': 'Back to the theme colours', 'gsm.refresh': 'Recount from the log', 'gsm.count': '{n} squares · {c} confirmed',
|
'gsm.basemap': 'Basemap', 'gsm.title': 'Grid squares', 'gsm.all': 'All', 'gsm.phone': 'Phone', 'gsm.cw': 'CW', 'gsm.digital': 'Digital', 'gsm.ftx': 'FTx', 'gsm.confirmed': 'confirmed', 'gsm.worked': 'worked', 'gsm.colConfirmed': 'Colour for confirmed squares', 'gsm.colWorked': 'Colour for worked (unconfirmed) squares', 'gsm.colReset': 'Back to the theme colours', 'gsm.refresh': 'Recount from the log', 'gsm.count': '{n} squares · {c} confirmed',
|
||||||
'bo.nearKm': 'Count receivers within', 'bo.nearKmHint': 'A report proves YOUR path only if it was collected near you. Smaller is more local but leaves fewer receivers listening — too small and the watch has nothing to look at. 300 km borrows a whole region; 100 km suits 2 m, where a duct is narrow.',
|
'bo.nearKm': 'Count receivers within', 'bo.nearKmHint': 'A report proves YOUR path only if it was collected near you. Smaller is more local but leaves fewer receivers listening — too small and the watch has nothing to look at. 300 km borrows a whole region; 100 km suits 2 m, where a duct is narrow.',
|
||||||
'bo.open': 'open', 'bo.liveTip': '{band} is open — {n} stations, ~{km} km, {sector}{season}. Click for the band map.', 'bo.enable': 'Watch for band openings', 'bo.enableHint': '(10, 12, 6, 4 and 2 m. Switching this on adds the two RBN nodes and subscribes to the PSK Reporter feed — the detection needs far more ears than a cluster can give it.)', 'bo.feedUp': 'PSK Reporter feed up — {n} decodes seen', 'bo.feedDown': 'PSK Reporter feed down — needs your station grid, and a moment to connect', 'clu.spotTtl': 'Spot lifetime', 'clu.spotTtlNever': 'Keep', 'clu.spotTtlHint': 'minutes — spots older than this are removed from the list and the band maps. 0 keeps them.', 'clu.chaseGrids': 'Chase new grids', 'clu.chaseGridsHint': '(learns locators from your own WSJT-X decodes AND from PSK Reporter, and keeps them in their own database so the cluster shows them from the first second)', 'clu.chaseGridsStat': '{n} locators known — {p} waiting to be written', 'clu.workedSameSlot': 'Already worked only on the same slot',
|
'bo.open': 'open', 'bo.liveTip': '{band} is open — {n} stations, ~{km} km, {sector}{season}. Click for the band map.', 'bo.enable': 'Watch for band openings', 'bo.enableHint': '(10, 12, 6, 4 and 2 m. Switching this on adds the two RBN nodes and subscribes to the PSK Reporter feed — the detection needs far more ears than a cluster can give it.)', 'bo.feedUp': 'PSK Reporter feed up — {n} decodes seen', 'bo.feedDown': 'PSK Reporter feed down — needs your station grid, and a moment to connect', 'clu.spotTtl': 'Spot lifetime', 'clu.spotTtlNever': 'Keep', 'clu.spotMax': 'Spots kept', 'clu.spotMaxHint': 'The list is a ring buffer: past this, the oldest spot drops out. It is usually this, not the lifetime, that decides — a thousand spots arrive in a few minutes on a busy evening.', 'clu.spotTtlHint': 'minutes — spots older than this are removed from the list and the band maps. 0 keeps them.', 'clu.chaseGrids': 'Chase new grids', 'clu.chaseGridsHint': '(learns locators from your own WSJT-X decodes AND from PSK Reporter, and keeps them in their own database so the cluster shows them from the first second)', 'clu.chaseGridsStat': '{n} locators known — {p} waiting to be written', 'clu.workedSameSlot': 'Already worked only on the same slot',
|
||||||
'clu.macros': 'Command buttons', 'clu.macrosHint': 'A named button beside the cluster command box. Leave the command empty and the button is not shown.',
|
'clu.macros': 'Command buttons', 'clu.macrosHint': 'A named button beside the cluster command box. Leave the command empty and the button is not shown.',
|
||||||
'clu.macroLabel': 'Button', 'clu.macroCmd': 'Command',
|
'clu.macroLabel': 'Button', 'clu.macroCmd': 'Command',
|
||||||
'clu.workedSameSlotHint': '— a spot shows "worked" only if you worked that call on the SAME band and mode, not just anywhere. Combines with digital-mode grouping (Settings → General): with it on, a call worked on 20m FT8 also counts as worked for a 20m FT4 spot; with it off, FT8 and FT4 are separate slots.',
|
'clu.workedSameSlotHint': '— a spot shows "worked" only if you worked that call on the SAME band and mode, not just anywhere. Combines with digital-mode grouping (Settings → General): with it on, a call worked on 20m FT8 also counts as worked for a 20m FT4 spot; with it off, FT8 and FT4 are separate slots.',
|
||||||
@@ -795,7 +795,7 @@ const fr: Dict = {
|
|||||||
'bo.nearKm': 'Compter les récepteurs à moins de', 'bo.nearKmHint': 'Un report ne prouve TON chemin que s’il a été collecté près de chez toi. Plus petit est plus local, mais laisse moins de récepteurs à l’écoute — trop petit, la veille n’a plus rien à observer. 300 km emprunte les oreilles de toute une région ; 100 km convient au 2 m, où un conduit est étroit.',
|
'bo.nearKm': 'Compter les récepteurs à moins de', 'bo.nearKmHint': 'Un report ne prouve TON chemin que s’il a été collecté près de chez toi. Plus petit est plus local, mais laisse moins de récepteurs à l’écoute — trop petit, la veille n’a plus rien à observer. 300 km emprunte les oreilles de toute une région ; 100 km convient au 2 m, où un conduit est étroit.',
|
||||||
'bo.open': 'ouvert', 'bo.liveTip': '{band} est ouvert — {n} stations, ~{km} km, {sector}{season}. Cliquer pour le bandmap.', 'bo.enable': 'Surveiller les ouvertures de bande', 'bo.enableHint': "(10, 12, 6, 4 et 2 m. Activer ajoute les deux nœuds RBN et souscrit au flux PSK Reporter — la détection a besoin de bien plus d oreilles qu un cluster ne peut en fournir.)", 'bo.feedUp': 'Flux PSK Reporter actif — {n} décodages vus', 'bo.feedDown': 'Flux PSK Reporter inactif — il faut ton locator, et un instant pour se connecter', 'clu.workedSameSlot': 'Déjà contacté seulement sur le même slot',
|
'bo.open': 'ouvert', 'bo.liveTip': '{band} est ouvert — {n} stations, ~{km} km, {sector}{season}. Cliquer pour le bandmap.', 'bo.enable': 'Surveiller les ouvertures de bande', 'bo.enableHint': "(10, 12, 6, 4 et 2 m. Activer ajoute les deux nœuds RBN et souscrit au flux PSK Reporter — la détection a besoin de bien plus d oreilles qu un cluster ne peut en fournir.)", 'bo.feedUp': 'Flux PSK Reporter actif — {n} décodages vus', 'bo.feedDown': 'Flux PSK Reporter inactif — il faut ton locator, et un instant pour se connecter', 'clu.workedSameSlot': 'Déjà contacté seulement sur le même slot',
|
||||||
'clu.macros': 'Boutons de commande', 'clu.macrosHint': 'Un bouton nommé à côté du champ de commande du cluster. Laisse la commande vide et le bouton n’est pas affiché.',
|
'clu.macros': 'Boutons de commande', 'clu.macrosHint': 'Un bouton nommé à côté du champ de commande du cluster. Laisse la commande vide et le bouton n’est pas affiché.',
|
||||||
'clu.macroLabel': 'Bouton', 'clu.macroCmd': 'Commande', 'clu.spotTtl': 'Durée de vie des spots', 'clu.spotTtlNever': 'Garder', 'clu.spotTtlHint': 'minutes — les spots plus vieux que ça sont retirés de la liste et des bandmaps. 0 les conserve.', 'clu.chaseGrids': 'Chasser les nouveaux locators', 'clu.chaseGridsHint': '(apprend les locators depuis TES propres décodages WSJT-X ET depuis PSK Reporter, et les garde dans leur propre base pour que le cluster les affiche dès la première seconde)', 'clu.chaseGridsStat': '{n} locators connus — {p} en attente d’écriture',
|
'clu.macroLabel': 'Bouton', 'clu.macroCmd': 'Commande', 'clu.spotTtl': 'Durée de vie des spots', 'clu.spotTtlNever': 'Garder', 'clu.spotMax': 'Nombre de spots conservés', 'clu.spotMaxHint': "La liste est un tampon circulaire : au-delà, le plus ancien sort. C'est souvent lui, et non la durée de vie, qui décide — mille spots arrivent en quelques minutes un soir chargé.", 'clu.spotTtlHint': 'minutes — les spots plus vieux que ça sont retirés de la liste et des bandmaps. 0 les conserve.', 'clu.chaseGrids': 'Chasser les nouveaux locators', 'clu.chaseGridsHint': '(apprend les locators depuis TES propres décodages WSJT-X ET depuis PSK Reporter, et les garde dans leur propre base pour que le cluster les affiche dès la première seconde)', 'clu.chaseGridsStat': '{n} locators connus — {p} en attente d’écriture',
|
||||||
'clu.workedSameSlotHint': '— un spot n\'affiche « contacté » que si vous avez contacté cet indicatif sur la MÊME bande et le MÊME mode, pas juste n\'importe où. Se combine avec le groupage des modes numériques (Réglages → Général) : activé, un indicatif contacté en 20m FT8 compte aussi comme contacté pour un spot 20m FT4 ; désactivé, FT8 et FT4 sont des slots distincts.',
|
'clu.workedSameSlotHint': '— un spot n\'affiche « contacté » que si vous avez contacté cet indicatif sur la MÊME bande et le MÊME mode, pas juste n\'importe où. Se combine avec le groupage des modes numériques (Réglages → Général) : activé, un indicatif contacté en 20m FT8 compte aussi comme contacté pour un spot 20m FT4 ; désactivé, FT8 et FT4 sont des slots distincts.',
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Vendored
+4
@@ -563,6 +563,8 @@ export function GetSolarData():Promise<solar.Data>;
|
|||||||
|
|
||||||
export function GetSpotColors():Promise<main.SpotColors>;
|
export function GetSpotColors():Promise<main.SpotColors>;
|
||||||
|
|
||||||
|
export function GetSpotMax():Promise<number>;
|
||||||
|
|
||||||
export function GetSpotTTLMinutes():Promise<number>;
|
export function GetSpotTTLMinutes():Promise<number>;
|
||||||
|
|
||||||
export function GetStartupStatus():Promise<main.StartupStatus>;
|
export function GetStartupStatus():Promise<main.StartupStatus>;
|
||||||
@@ -1149,6 +1151,8 @@ export function SetPassphrase(arg1:string):Promise<void>;
|
|||||||
|
|
||||||
export function SetScpEnabled(arg1:boolean):Promise<void>;
|
export function SetScpEnabled(arg1:boolean):Promise<void>;
|
||||||
|
|
||||||
|
export function SetSpotMax(arg1:number):Promise<void>;
|
||||||
|
|
||||||
export function SetSpotTTLMinutes(arg1:number):Promise<void>;
|
export function SetSpotTTLMinutes(arg1:number):Promise<void>;
|
||||||
|
|
||||||
export function SetTelemetryEnabled(arg1:boolean):Promise<void>;
|
export function SetTelemetryEnabled(arg1:boolean):Promise<void>;
|
||||||
|
|||||||
@@ -1066,6 +1066,10 @@ export function GetSpotColors() {
|
|||||||
return window['go']['main']['App']['GetSpotColors']();
|
return window['go']['main']['App']['GetSpotColors']();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function GetSpotMax() {
|
||||||
|
return window['go']['main']['App']['GetSpotMax']();
|
||||||
|
}
|
||||||
|
|
||||||
export function GetSpotTTLMinutes() {
|
export function GetSpotTTLMinutes() {
|
||||||
return window['go']['main']['App']['GetSpotTTLMinutes']();
|
return window['go']['main']['App']['GetSpotTTLMinutes']();
|
||||||
}
|
}
|
||||||
@@ -2238,6 +2242,10 @@ export function SetScpEnabled(arg1) {
|
|||||||
return window['go']['main']['App']['SetScpEnabled'](arg1);
|
return window['go']['main']['App']['SetScpEnabled'](arg1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function SetSpotMax(arg1) {
|
||||||
|
return window['go']['main']['App']['SetSpotMax'](arg1);
|
||||||
|
}
|
||||||
|
|
||||||
export function SetSpotTTLMinutes(arg1) {
|
export function SetSpotTTLMinutes(arg1) {
|
||||||
return window['go']['main']['App']['SetSpotTTLMinutes'](arg1);
|
return window['go']['main']['App']['SetSpotTTLMinutes'](arg1);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user