feat(cluster): LoTW badge and filter, spotter-continent filter
The L badge is a single letter, not a word: the call column is 120 px and holds a callsign. It keeps the muted blue of a confirmation and never a status colour, because whether a station uploads to LoTW says nothing about whether the spot is worth chasing - those are different questions and must not share a palette. The spotter's continent is not the DX's. It asks whether anyone near you is hearing the band at all, which is why it earns its own control rather than reusing the existing Continent column. The spotter callsign now travels with the status query so the backend can resolve it against the one DXCC prefix table, instead of a second continent rule appearing in the frontend. Both AND with the status chips rather than joining their OR: "a new band, and from Europe" is the question being asked. An unresolved spot is never dropped by them - the status arrives a moment after the row, and filtering meanwhile made the list flicker. New counties only is the SAME state as the NEW COUNTY chip, reached a second way, not a second filter. County chasing is a mode you switch into, and hunting for one chip among eight is not how you switch into it.
This commit is contained in:
+6
-2
@@ -2,8 +2,12 @@
|
|||||||
{
|
{
|
||||||
"version": "0.24.4",
|
"version": "0.24.4",
|
||||||
"date": "",
|
"date": "",
|
||||||
"en": [],
|
"en": [
|
||||||
"fr": []
|
"DX cluster: an L badge next to a callsign marks a station that uploads to LoTW, with a matching LoTW users only filter. A Spotter continent dropdown narrows the list by where the spot came FROM — a JA report on 20 m tells a European little about their own path. New counties only joins the filter panel as a second way to the NEW COUNTY chip. All of them apply to the band map too."
|
||||||
|
],
|
||||||
|
"fr": [
|
||||||
|
"Cluster DX : un badge L à côté de l indicatif signale une station qui utilise LoTW, avec le filtre Utilisateurs LoTW seulement qui va avec. Une liste déroulante Continent du spotter restreint selon l origine du spot — un report JA sur 20 m dit peu de chose à un Européen sur son propre chemin. Nouveaux comtés seulement rejoint le panneau de filtres, en second accès à la puce NOUV CTY. Le tout s applique aussi au bandmap."
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"version": "0.24.3",
|
"version": "0.24.3",
|
||||||
|
|||||||
+61
-9
@@ -1491,6 +1491,11 @@ export default function App() {
|
|||||||
// Display options, shared with the band map through lib/spotDisplay. Kept in
|
// Display options, shared with the band map through lib/spotDisplay. Kept in
|
||||||
// localStorage (mirrored to settings by writeUiPref) so both panels and the
|
// localStorage (mirrored to settings by writeUiPref) so both panels and the
|
||||||
// filter predicate read one source without prop-drilling through three levels.
|
// filter predicate read one source without prop-drilling through three levels.
|
||||||
|
// 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(() => localStorage.getItem('opslog.clusterLotwOnly') === '1');
|
||||||
|
const [clusterSpotterCont, setClusterSpotterCont] = useState(() => localStorage.getItem('opslog.clusterSpotterCont') ?? '');
|
||||||
const [clusterMuteWorked, setClusterMuteWorked] = useState(() => localStorage.getItem('opslog.clusterMuteWorked') === '1');
|
const [clusterMuteWorked, setClusterMuteWorked] = useState(() => localStorage.getItem('opslog.clusterMuteWorked') === '1');
|
||||||
const [clusterSlotHighlight, setClusterSlotHighlight] = useState(() => localStorage.getItem('opslog.clusterSlotHighlight') === '1');
|
const [clusterSlotHighlight, setClusterSlotHighlight] = useState(() => localStorage.getItem('opslog.clusterSlotHighlight') === '1');
|
||||||
const [clusterStatusFilter, setClusterStatusFilter] = useState<Set<SpotFilterKey>>(() => lsSet<SpotFilterKey>('opslog.clusterStatusFilter'));
|
const [clusterStatusFilter, setClusterStatusFilter] = useState<Set<SpotFilterKey>>(() => lsSet<SpotFilterKey>('opslog.clusterStatusFilter'));
|
||||||
@@ -1591,7 +1596,7 @@ export default function App() {
|
|||||||
// worked_slot must be carried explicitly like every other field: this map is
|
// worked_slot must be carried explicitly like every other field: this map is
|
||||||
// assembled field by field, so a backend flag that nobody copies here simply
|
// assembled field by field, so a backend flag that nobody copies here simply
|
||||||
// never reaches the panels — silently, since the extra key is just dropped.
|
// never reaches the panels — silently, since the extra key is just dropped.
|
||||||
const [spotStatus, setSpotStatus] = useState<Record<string, { status: string; country?: string; continent?: string; worked_call?: boolean; worked_slot?: boolean; new_county?: boolean; county?: string; state?: string; new_pota?: boolean; new_pfx?: boolean; pfx?: string }>>({});
|
const [spotStatus, setSpotStatus] = useState<Record<string, { status: string; country?: string; continent?: string; worked_call?: boolean; worked_slot?: boolean; new_county?: boolean; county?: string; state?: string; lotw?: boolean; spotter_continent?: string; grid?: string; new_grid?: boolean; new_pota?: boolean; new_pfx?: boolean; pfx?: string }>>({});
|
||||||
// Live mirror of spotStatus so the incoming-spot buffer can tell which slots
|
// Live mirror of spotStatus so the incoming-spot buffer can tell which slots
|
||||||
// still need resolving without re-subscribing the cluster:spot listener.
|
// still need resolving without re-subscribing the cluster:spot listener.
|
||||||
const spotStatusRef = useRef(spotStatus);
|
const spotStatusRef = useRef(spotStatus);
|
||||||
@@ -1624,13 +1629,13 @@ export default function App() {
|
|||||||
const refreshSpotStatuses = useCallback(async () => {
|
const refreshSpotStatuses = useCallback(async () => {
|
||||||
const cur = spotsRef.current;
|
const cur = spotsRef.current;
|
||||||
if (!cur.length) return;
|
if (!cur.length) return;
|
||||||
const queries: { call: string; band: string; mode: string; pota_ref: string }[] = [];
|
const queries: { call: string; band: string; mode: string; pota_ref: string; spotter: string }[] = [];
|
||||||
const seen = new Set<string>();
|
const seen = new Set<string>();
|
||||||
for (const s of cur) {
|
for (const s of cur) {
|
||||||
const k = spotStatusKey(s.dx_call, s.band ?? '', s.comment ?? '', s.freq_hz);
|
const k = spotStatusKey(s.dx_call, s.band ?? '', s.comment ?? '', s.freq_hz);
|
||||||
if (seen.has(k)) continue;
|
if (seen.has(k)) continue;
|
||||||
seen.add(k);
|
seen.add(k);
|
||||||
queries.push({ call: s.dx_call, band: s.band ?? '', mode: inferSpotMode(s.comment ?? '', s.freq_hz), pota_ref: (s as any).pota_ref ?? '' });
|
queries.push({ call: s.dx_call, band: s.band ?? '', mode: inferSpotMode(s.comment ?? '', s.freq_hz), pota_ref: (s as any).pota_ref ?? '', spotter: s.spotter ?? '' });
|
||||||
}
|
}
|
||||||
if (!queries.length) return;
|
if (!queries.length) return;
|
||||||
try {
|
try {
|
||||||
@@ -1641,7 +1646,7 @@ export default function App() {
|
|||||||
const k = `${r.call}|${r.band ?? ''}|${(r.mode ?? '').toUpperCase()}`;
|
const k = `${r.call}|${r.band ?? ''}|${(r.mode ?? '').toUpperCase()}`;
|
||||||
next[k] = {
|
next[k] = {
|
||||||
status: r.status ?? '', country: r.country, continent: (r as any).continent,
|
status: r.status ?? '', country: r.country, continent: (r as any).continent,
|
||||||
worked_call: !!(r as any).worked_call, worked_slot: !!(r as any).worked_slot, new_county: !!(r as any).new_county, county: (r as any).county, state: (r as any).state,
|
worked_call: !!(r as any).worked_call, worked_slot: !!(r as any).worked_slot, new_county: !!(r as any).new_county, lotw: !!(r as any).lotw, spotter_continent: (r as any).spotter_continent, grid: (r as any).grid, new_grid: !!(r as any).new_grid, county: (r as any).county, state: (r as any).state,
|
||||||
new_pota: !!(r as any).new_pota, new_pfx: !!(r as any).new_pfx, pfx: (r as any).pfx,
|
new_pota: !!(r as any).new_pota, new_pfx: !!(r as any).new_pfx, pfx: (r as any).pfx,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -2717,7 +2722,7 @@ export default function App() {
|
|||||||
// Resolve unknown statuses before the rows go in.
|
// Resolve unknown statuses before the rows go in.
|
||||||
try {
|
try {
|
||||||
const known = spotStatusRef.current;
|
const known = spotStatusRef.current;
|
||||||
const unknown: { call: string; band: string; mode: string; pota_ref: string }[] = [];
|
const unknown: { call: string; band: string; mode: string; pota_ref: string; spotter: string }[] = [];
|
||||||
const seen = new Set<string>();
|
const seen = new Set<string>();
|
||||||
for (const s of batch) {
|
for (const s of batch) {
|
||||||
const k = spotStatusKey(s.dx_call, s.band ?? '', s.comment ?? '', s.freq_hz);
|
const k = spotStatusKey(s.dx_call, s.band ?? '', s.comment ?? '', s.freq_hz);
|
||||||
@@ -2727,6 +2732,7 @@ export default function App() {
|
|||||||
call: s.dx_call, band: s.band ?? '',
|
call: s.dx_call, band: s.band ?? '',
|
||||||
mode: inferSpotMode(s.comment ?? '', s.freq_hz),
|
mode: inferSpotMode(s.comment ?? '', s.freq_hz),
|
||||||
pota_ref: (s as any).pota_ref ?? '',
|
pota_ref: (s as any).pota_ref ?? '',
|
||||||
|
spotter: s.spotter ?? '',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (unknown.length > 0) {
|
if (unknown.length > 0) {
|
||||||
@@ -2741,7 +2747,7 @@ export default function App() {
|
|||||||
continent: (r as any).continent,
|
continent: (r as any).continent,
|
||||||
worked_call: !!(r as any).worked_call,
|
worked_call: !!(r as any).worked_call,
|
||||||
worked_slot: !!(r as any).worked_slot,
|
worked_slot: !!(r as any).worked_slot,
|
||||||
new_county: !!(r as any).new_county, county: (r as any).county, state: (r as any).state,
|
new_county: !!(r as any).new_county, lotw: !!(r as any).lotw, spotter_continent: (r as any).spotter_continent, grid: (r as any).grid, new_grid: !!(r as any).new_grid, county: (r as any).county, state: (r as any).state,
|
||||||
new_pota: !!(r as any).new_pota,
|
new_pota: !!(r as any).new_pota,
|
||||||
new_pfx: !!(r as any).new_pfx,
|
new_pfx: !!(r as any).new_pfx,
|
||||||
pfx: (r as any).pfx,
|
pfx: (r as any).pfx,
|
||||||
@@ -3187,14 +3193,14 @@ export default function App() {
|
|||||||
// "new-slot" because the lookup key carried mode="".
|
// "new-slot" because the lookup key carried mode="".
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const t = window.setTimeout(async () => {
|
const t = window.setTimeout(async () => {
|
||||||
const unknown: { call: string; band: string; mode: string; pota_ref: string }[] = [];
|
const unknown: { call: string; band: string; mode: string; pota_ref: string; spotter: string }[] = [];
|
||||||
const seen = new Set<string>();
|
const seen = new Set<string>();
|
||||||
for (const s of spots) {
|
for (const s of spots) {
|
||||||
const mode = inferSpotMode(s.comment ?? '', s.freq_hz);
|
const mode = inferSpotMode(s.comment ?? '', s.freq_hz);
|
||||||
const k = spotStatusKey(s.dx_call, s.band ?? '', s.comment ?? '', s.freq_hz);
|
const k = spotStatusKey(s.dx_call, s.band ?? '', s.comment ?? '', s.freq_hz);
|
||||||
if (seen.has(k) || spotStatus[k]) continue;
|
if (seen.has(k) || spotStatus[k]) continue;
|
||||||
seen.add(k);
|
seen.add(k);
|
||||||
unknown.push({ call: s.dx_call, band: s.band ?? '', mode, pota_ref: (s as any).pota_ref ?? '' });
|
unknown.push({ call: s.dx_call, band: s.band ?? '', mode, pota_ref: (s as any).pota_ref ?? '', spotter: s.spotter ?? '' });
|
||||||
}
|
}
|
||||||
if (unknown.length === 0) return;
|
if (unknown.length === 0) return;
|
||||||
try {
|
try {
|
||||||
@@ -3209,7 +3215,7 @@ export default function App() {
|
|||||||
continent: (r as any).continent,
|
continent: (r as any).continent,
|
||||||
worked_call: !!(r as any).worked_call,
|
worked_call: !!(r as any).worked_call,
|
||||||
worked_slot: !!(r as any).worked_slot,
|
worked_slot: !!(r as any).worked_slot,
|
||||||
new_county: !!(r as any).new_county, county: (r as any).county, state: (r as any).state,
|
new_county: !!(r as any).new_county, lotw: !!(r as any).lotw, spotter_continent: (r as any).spotter_continent, grid: (r as any).grid, new_grid: !!(r as any).new_grid, county: (r as any).county, state: (r as any).state,
|
||||||
new_pota: !!(r as any).new_pota,
|
new_pota: !!(r as any).new_pota,
|
||||||
new_pfx: !!(r as any).new_pfx,
|
new_pfx: !!(r as any).new_pfx,
|
||||||
pfx: (r as any).pfx,
|
pfx: (r as any).pfx,
|
||||||
@@ -4708,6 +4714,19 @@ export default function App() {
|
|||||||
|| (!!e?.new_pfx && clusterStatusFilter.has('new-pfx'));
|
|| (!!e?.new_pfx && clusterStatusFilter.has('new-pfx'));
|
||||||
if (!matches) return false;
|
if (!matches) return false;
|
||||||
}
|
}
|
||||||
|
// LoTW only, and the spotter's continent. Both are properties of the
|
||||||
|
// station rather than judgements about the spot, so they AND with the
|
||||||
|
// status chips instead of joining that OR: "a new band, and from Europe".
|
||||||
|
if (clusterLotwOnly || clusterSpotterCont) {
|
||||||
|
const k = spotStatusKey(s.dx_call, s.band ?? '', s.comment ?? '', s.freq_hz);
|
||||||
|
const e = spotStatus[k];
|
||||||
|
// An unresolved spot is not filtered out. The status arrives a moment
|
||||||
|
// after the row does, and dropping it meanwhile made the list flicker.
|
||||||
|
if (e) {
|
||||||
|
if (clusterLotwOnly && !e.lotw) return false;
|
||||||
|
if (clusterSpotterCont && e.spotter_continent && e.spotter_continent !== clusterSpotterCont) return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
if (clusterHideWorked) {
|
if (clusterHideWorked) {
|
||||||
const k = spotStatusKey(s.dx_call, s.band ?? '', s.comment ?? '', s.freq_hz);
|
const k = spotStatusKey(s.dx_call, s.band ?? '', s.comment ?? '', s.freq_hz);
|
||||||
const e = spotStatus[k];
|
const e = spotStatus[k];
|
||||||
@@ -4776,6 +4795,39 @@ export default function App() {
|
|||||||
onCheckedChange={(c) => { const v = !!c; setClusterSlotHighlight(v); writeUiPref('opslog.clusterSlotHighlight', v ? '1' : '0'); }} />
|
onCheckedChange={(c) => { const v = !!c; setClusterSlotHighlight(v); writeUiPref('opslog.clusterSlotHighlight', v ? '1' : '0'); }} />
|
||||||
{t('clu.slotHighlightShort')}
|
{t('clu.slotHighlightShort')}
|
||||||
</label>
|
</label>
|
||||||
|
<label className="flex items-center gap-1.5 cursor-pointer">
|
||||||
|
<Checkbox checked={clusterLotwOnly}
|
||||||
|
onCheckedChange={(c) => { const v = !!c; setClusterLotwOnly(v); writeUiPref('opslog.clusterLotwOnly', v ? '1' : '0'); }} />
|
||||||
|
{t('clu.lotwOnly')}
|
||||||
|
</label>
|
||||||
|
{/* The same state as the NEW COUNTY status chip, reached a second way.
|
||||||
|
Not a second filter: county chasing is a mode you switch into, and
|
||||||
|
hunting for one chip among eight is not how you switch into it. */}
|
||||||
|
<label className="flex items-center gap-1.5 cursor-pointer">
|
||||||
|
<Checkbox checked={clusterStatusFilter.has('new-county')}
|
||||||
|
onCheckedChange={(c) => setClusterStatusFilter((cur) => {
|
||||||
|
const n = new Set(cur);
|
||||||
|
if (c) n.add('new-county'); else n.delete('new-county');
|
||||||
|
return n;
|
||||||
|
})} />
|
||||||
|
{t('clu.newCountyOnly')}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Spotter's continent. Not the DX's: this asks whether anyone near you
|
||||||
|
is hearing the band at all, which is a different question. */}
|
||||||
|
<div>
|
||||||
|
<div className="text-[10px] uppercase tracking-wider text-muted-foreground mb-1">{t('clu.spotterCont')}</div>
|
||||||
|
<select
|
||||||
|
className="w-full h-7 rounded border border-border bg-background px-1 text-xs"
|
||||||
|
value={clusterSpotterCont}
|
||||||
|
onChange={(e) => { const v = e.target.value; setClusterSpotterCont(v); writeUiPref('opslog.clusterSpotterCont', v); }}
|
||||||
|
>
|
||||||
|
<option value="">{t('clu.allContinents')}</option>
|
||||||
|
{['AF', 'AN', 'AS', 'EU', 'NA', 'OC', 'SA'].map((c) => (
|
||||||
|
<option key={c} value={c}>{c}</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Band filter — multi-select listbox */}
|
{/* Band filter — multi-select listbox */}
|
||||||
|
|||||||
@@ -64,6 +64,11 @@ export type SpotStatusEntry = {
|
|||||||
new_pota?: boolean;
|
new_pota?: boolean;
|
||||||
new_pfx?: boolean;
|
new_pfx?: boolean;
|
||||||
pfx?: string;
|
pfx?: string;
|
||||||
|
// lotw: the DX uploads to LoTW, per ARRL user list. Inert until downloaded.
|
||||||
|
lotw?: boolean;
|
||||||
|
spotter_continent?: string;
|
||||||
|
grid?: string;
|
||||||
|
new_grid?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@@ -220,7 +225,21 @@ const makeColCatalog = (t: TFn): ColEntry[] => [
|
|||||||
cellRenderer: (p: any) => {
|
cellRenderer: (p: any) => {
|
||||||
const s = statusFor(p);
|
const s = statusFor(p);
|
||||||
const color = s?.status === 'new' ? null : s?.worked_call ? WKD : null;
|
const color = s?.status === 'new' ? null : s?.worked_call ? WKD : null;
|
||||||
return <span style={{ color: color ?? undefined, fontWeight: 700 }}>{p.value ?? ''}</span>;
|
return (
|
||||||
|
<span style={{ color: color ?? undefined, fontWeight: 700 }}>
|
||||||
|
{p.value ?? ''}
|
||||||
|
{/* A single letter rather than a word: the column is 120 px and holds a
|
||||||
|
callsign. It stays the muted-blue of a confirmation, never a status
|
||||||
|
colour — whether a station uploads to LoTW says nothing about
|
||||||
|
whether the spot is worth chasing. */}
|
||||||
|
{s?.lotw ? (
|
||||||
|
<span title={t('clu.lotwBadge')} style={{
|
||||||
|
marginLeft: 4, padding: '0 3px', borderRadius: 3, fontSize: 9, verticalAlign: 'middle',
|
||||||
|
background: 'color-mix(in srgb, var(--info) 22%, transparent)', color: 'var(--info)',
|
||||||
|
}}>L</span>
|
||||||
|
) : null}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
},
|
},
|
||||||
tooltipValueGetter: (p: any) => {
|
tooltipValueGetter: (p: any) => {
|
||||||
const s = statusFor(p);
|
const s = statusFor(p);
|
||||||
|
|||||||
@@ -266,7 +266,7 @@ const en: Dict = {
|
|||||||
'clu.selfSpot': 'Self-spot while I log', 'clu.selfSpotEvery': 'at most every', 'clu.selfSpotMinutes': 'min',
|
'clu.selfSpot': 'Self-spot while I log', 'clu.selfSpotEvery': 'at most every', 'clu.selfSpotMinutes': 'min',
|
||||||
'clu.selfSpotHint': 'Announces YOU on the master cluster when you log a QSO — the spot carries your station callsign and the frequency you just worked on, so callers find you without waiting for someone else to spot you. Sent on the first QSO of a frequency, then no more often than the gap below. Five minutes is the floor: a self-spot is traffic every user of the node sees.',
|
'clu.selfSpotHint': 'Announces YOU on the master cluster when you log a QSO — the spot carries your station callsign and the frequency you just worked on, so callers find you without waiting for someone else to spot you. Sent on the first QSO of a frequency, then no more often than the gap below. Five minutes is the floor: a self-spot is traffic every user of the node sees.',
|
||||||
'clu.freeNodes': 'Free public nodes:',
|
'clu.freeNodes': 'Free public nodes:',
|
||||||
'clu.muteWorkedShort': 'No colour on worked', 'clu.slotHighlightShort': 'Colour unworked here', 'clu.muteWorked': 'No colour or badge on stations already worked',
|
'clu.lotwOnly': 'LoTW users only', 'clu.newCountyOnly': 'New counties only', 'clu.spotterCont': 'Spotter continent', 'clu.allContinents': 'All continents', 'clu.lotwBadge': 'Uploads to LoTW', 'clu.muteWorkedShort': 'No colour on worked', 'clu.slotHighlightShort': 'Colour unworked here', 'clu.muteWorked': 'No colour or badge on stations already worked',
|
||||||
'clu.muteWorkedHint': '(they stay in the list, just quiet — leaves the colour for what is left to do)',
|
'clu.muteWorkedHint': '(they stay in the list, just quiet — leaves the colour for what is left to do)',
|
||||||
'clu.slotHighlight': 'Colour the stations not worked on this band and mode',
|
'clu.slotHighlight': 'Colour the stations not worked on this band and mode',
|
||||||
'clu.slotHighlightHint': '(by callsign, whatever the entity status says)',
|
'clu.slotHighlightHint': '(by callsign, whatever the entity status says)',
|
||||||
@@ -686,7 +686,7 @@ const fr: Dict = {
|
|||||||
'clu.selfSpot': "M'auto-spotter quand j'enregistre", 'clu.selfSpotEvery': 'au plus toutes les', 'clu.selfSpotMinutes': 'min',
|
'clu.selfSpot': "M'auto-spotter quand j'enregistre", 'clu.selfSpotEvery': 'au plus toutes les', 'clu.selfSpotMinutes': 'min',
|
||||||
'clu.selfSpotHint': "Annonce TON indicatif sur le cluster maître quand tu enregistres un QSO — le spot porte l'indicatif de station et la fréquence que tu viens de travailler, pour qu'on te trouve sans attendre que quelqu'un te spotte. Envoyé au premier QSO d'une fréquence, puis pas plus souvent que l'intervalle ci-dessous. Cinq minutes est le plancher : un auto-spot est du trafic que voient tous les utilisateurs du nœud.",
|
'clu.selfSpotHint': "Annonce TON indicatif sur le cluster maître quand tu enregistres un QSO — le spot porte l'indicatif de station et la fréquence que tu viens de travailler, pour qu'on te trouve sans attendre que quelqu'un te spotte. Envoyé au premier QSO d'une fréquence, puis pas plus souvent que l'intervalle ci-dessous. Cinq minutes est le plancher : un auto-spot est du trafic que voient tous les utilisateurs du nœud.",
|
||||||
'clu.freeNodes': 'Nœuds publics gratuits :',
|
'clu.freeNodes': 'Nœuds publics gratuits :',
|
||||||
'clu.muteWorkedShort': 'Pas de couleur sur les faits', 'clu.slotHighlightShort': 'Colorer les non faits ici', 'clu.muteWorked': 'Aucune couleur ni badge sur les stations déjà contactées',
|
'clu.lotwOnly': 'Utilisateurs LoTW seulement', 'clu.newCountyOnly': 'Nouveaux comtés seulement', 'clu.spotterCont': 'Continent du spotter', 'clu.allContinents': 'Tous les continents', 'clu.lotwBadge': 'Utilise LoTW', 'clu.muteWorkedShort': 'Pas de couleur sur les faits', 'clu.slotHighlightShort': 'Colorer les non faits ici', 'clu.muteWorked': 'Aucune couleur ni badge sur les stations déjà contactées',
|
||||||
'clu.muteWorkedHint': '(elles restent dans la liste, simplement discrètes — la couleur reste pour ce qui est à faire)',
|
'clu.muteWorkedHint': '(elles restent dans la liste, simplement discrètes — la couleur reste pour ce qui est à faire)',
|
||||||
'clu.slotHighlight': 'Colorer les stations non contactées sur cette bande et ce mode',
|
'clu.slotHighlight': 'Colorer les stations non contactées sur cette bande et ce mode',
|
||||||
'clu.slotHighlightHint': "(par indicatif, quel que soit le statut de l'entité)",
|
'clu.slotHighlightHint': "(par indicatif, quel que soit le statut de l'entité)",
|
||||||
|
|||||||
Reference in New Issue
Block a user