chore: release v0.23.1
This commit is contained in:
@@ -15476,6 +15476,12 @@ type SpotStatus struct {
|
||||
// when that database has been downloaded); POTA from the spot's tagged park.
|
||||
NewCounty bool `json:"new_county"`
|
||||
NewPOTA bool `json:"new_pota"`
|
||||
// NewPfx flags a CQ WPX prefix never worked before, and Pfx is that prefix.
|
||||
// Also orthogonal: a common entity on a worked band can still carry a prefix
|
||||
// that has never been in the log, which is exactly what a WPX chaser is
|
||||
// scanning the cluster for.
|
||||
NewPfx bool `json:"new_pfx"`
|
||||
Pfx string `json:"pfx,omitempty"`
|
||||
}
|
||||
|
||||
// ClusterSpotStatuses takes a batch of spots and returns slot status for
|
||||
@@ -15531,6 +15537,16 @@ func (a *App) ClusterSpotStatuses(spots []SpotQuery) []SpotStatus {
|
||||
// lookup) and worked POTA parks. Both built once per batch.
|
||||
workedCounties, _ := a.qso.WorkedCountyKeys(a.ctx, award.USCountyKey)
|
||||
workedPOTA, _ := a.qso.WorkedPOTARefs(a.ctx)
|
||||
// Worked WPX prefixes, derived from the callsigns we already loaded — no
|
||||
// extra query. Derived rather than read from the stored PFX column: that
|
||||
// column is only filled when an import supplied it, and deriving keeps this
|
||||
// in step with the WPX award, which does the same thing.
|
||||
workedPfx := make(map[string]struct{}, len(workedCalls))
|
||||
for c := range workedCalls {
|
||||
if p := award.WPXPrefix(c); p != "" {
|
||||
workedPfx[p] = struct{}{}
|
||||
}
|
||||
}
|
||||
for i, q := range spots {
|
||||
out[i] = SpotStatus{
|
||||
Call: q.Call,
|
||||
@@ -15540,6 +15556,13 @@ func (a *App) ClusterSpotStatuses(spots []SpotQuery) []SpotStatus {
|
||||
if _, ok := workedCalls[strings.ToUpper(q.Call)]; ok {
|
||||
out[i].WorkedCall = true
|
||||
}
|
||||
// NEW PFX: the spot's CQ WPX prefix, never worked before.
|
||||
if p := award.WPXPrefix(q.Call); p != "" {
|
||||
out[i].Pfx = p
|
||||
if _, done := workedPfx[p]; !done {
|
||||
out[i].NewPfx = true
|
||||
}
|
||||
}
|
||||
// NEW POTA: the spot's tagged park, never worked before.
|
||||
if ref := strings.ToUpper(strings.TrimSpace(q.POTARef)); ref != "" {
|
||||
if _, done := workedPOTA[ref]; !done {
|
||||
|
||||
+16
-2
@@ -1,4 +1,16 @@
|
||||
[
|
||||
{
|
||||
"version": "0.23.1",
|
||||
"date": "",
|
||||
"en": [
|
||||
"DX cluster: NEW COUNTY is green, like NEW POTA.",
|
||||
"CW: pressing ESC during a macro no longer turns the report from 599 into 59 when \"ESC clears the callsign too\" is on."
|
||||
],
|
||||
"fr": [
|
||||
"Cluster DX : NOUVEAU COMTÉ passe en vert, comme NOUVEAU POTA.",
|
||||
"CW : appuyer sur Échap pendant une macro ne fait plus passer le report de 599 à 59 quand l'option « Échap efface aussi l'indicatif » est cochée."
|
||||
]
|
||||
},
|
||||
{
|
||||
"version": "0.23.0",
|
||||
"date": "",
|
||||
@@ -6,13 +18,15 @@
|
||||
"Station Control: Dingtian IOT relay boards (2 to 32 relays, LAN or WiFi) can now be added.",
|
||||
"ACOM amplifiers: the card now shows TUNE while the tuner is running, instead of an unknown state.",
|
||||
"DX cluster: the status pills are gone. The call, band and mode turn yellow when that one is new, and a callsign already in the log turns blue.",
|
||||
"Reports: starting OpsLog with the rig already in CW no longer leaves the RST fields on 59 — they now follow the mode you are actually on."
|
||||
"Reports: starting OpsLog with the rig already in CW no longer leaves the RST fields on 59 — they now follow the mode you are actually on. (Thanks HB9HBY)",
|
||||
"DX cluster: NEW PFX marks a spot whose CQ WPX prefix has never been worked, in the status column and as a filter."
|
||||
],
|
||||
"fr": [
|
||||
"Contrôle station : les cartes relais Dingtian IOT (2 à 32 relais, LAN ou WiFi) peuvent désormais être ajoutées.",
|
||||
"Amplificateurs ACOM : la carte affiche TUNE pendant l'accord, au lieu d'un état inconnu.",
|
||||
"Cluster DX : les pastilles de statut disparaissent. L'indicatif, la bande et le mode passent en jaune quand celui-ci est nouveau, et un indicatif déjà dans le log passe en bleu.",
|
||||
"Reports : lancer OpsLog avec le poste déjà en CW ne laisse plus les RST à 59 — ils suivent maintenant le mode réellement en cours."
|
||||
"Reports : lancer OpsLog avec le poste déjà en CW ne laisse plus les RST à 59 — ils suivent maintenant le mode réellement en cours. (Merci HB9HBY)",
|
||||
"Cluster DX : NOUVEAU PFX signale un spot dont le préfixe CQ WPX n'a jamais été contacté, dans la colonne de statut et en filtre."
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
+26
-7
@@ -492,6 +492,14 @@ export default function App() {
|
||||
const [bandRx, setBandRx] = useState('20m');
|
||||
const [countries, setCountries] = useState<string[]>([]);
|
||||
const [rstLists, setRstLists] = useState<RSTLists>({ phone: [], cw: [], digital: [] });
|
||||
// Refs for the report defaults. applyModePreset is called from long-lived
|
||||
// closures — the global key handler is the one that bit us — whose dependency
|
||||
// lists deliberately exclude the mode, so reading the state variables there
|
||||
// gives whatever they were when that closure was built.
|
||||
const modePresetsRef = useRef(modePresets);
|
||||
useEffect(() => { modePresetsRef.current = modePresets; }, [modePresets]);
|
||||
const rstListsRef = useRef(rstLists);
|
||||
useEffect(() => { rstListsRef.current = rstLists; }, [rstLists]);
|
||||
const [rstSent, setRstSent] = useState('59');
|
||||
const [rstRcvd, setRstRcvd] = useState('59');
|
||||
const [grid, setGrid] = useState('');
|
||||
@@ -1382,7 +1390,7 @@ export default function App() {
|
||||
// county or a new park is not a DXCC state, it is another dimension of the
|
||||
// same spot — which is why the grid shows them as separate badges. Filtering
|
||||
// is an OR across all of them, as it already was for a worked callsign.
|
||||
type SpotFilterKey = SpotStatusKey | 'new-pota' | 'new-county';
|
||||
type SpotFilterKey = SpotStatusKey | 'new-pota' | 'new-county' | 'new-pfx';
|
||||
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).
|
||||
@@ -1478,7 +1486,7 @@ export default function App() {
|
||||
// Cached per-call slot status: "new" | "new-band" | "new-slot" | "worked".
|
||||
// Keyed by `${call}|${band}|${mode}` so two spots of the same call on
|
||||
// different slots don't share the same colour.
|
||||
const [spotStatus, setSpotStatus] = useState<Record<string, { status: string; country?: string; continent?: string; worked_call?: boolean; new_county?: boolean; new_pota?: boolean }>>({});
|
||||
const [spotStatus, setSpotStatus] = useState<Record<string, { status: string; country?: string; continent?: string; worked_call?: boolean; new_county?: boolean; new_pota?: boolean; new_pfx?: boolean; pfx?: string }>>({});
|
||||
// Live mirror of spotStatus so the incoming-spot buffer can tell which slots
|
||||
// still need resolving without re-subscribing the cluster:spot listener.
|
||||
const spotStatusRef = useRef(spotStatus);
|
||||
@@ -2187,8 +2195,9 @@ export default function App() {
|
||||
// Prefer the user's configured preset RST; otherwise fall back to the mode
|
||||
// category default (CW/RTTY/PSK → 599, phone → 59, digital → first option)
|
||||
// so switching SSB→CW flips 59→599 even without a configured preset.
|
||||
const p = modePresets.find((x) => x.name === m);
|
||||
const fallback = rstOptions(m, rstLists)[0] || '';
|
||||
// Read through the refs, never the state: see their declaration.
|
||||
const p = modePresetsRef.current.find((x) => x.name === m);
|
||||
const fallback = rstOptions(m, rstListsRef.current)[0] || '';
|
||||
setRstSent(p?.default_rst_sent || fallback);
|
||||
setRstRcvd(p?.default_rst_rcvd || fallback);
|
||||
}
|
||||
@@ -2461,6 +2470,8 @@ export default function App() {
|
||||
worked_call: !!(r as any).worked_call,
|
||||
new_county: !!(r as any).new_county,
|
||||
new_pota: !!(r as any).new_pota,
|
||||
new_pfx: !!(r as any).new_pfx,
|
||||
pfx: (r as any).pfx,
|
||||
};
|
||||
}
|
||||
return next;
|
||||
@@ -2916,6 +2927,8 @@ export default function App() {
|
||||
worked_call: !!(r as any).worked_call,
|
||||
new_county: !!(r as any).new_county,
|
||||
new_pota: !!(r as any).new_pota,
|
||||
new_pfx: !!(r as any).new_pfx,
|
||||
pfx: (r as any).pfx,
|
||||
};
|
||||
}
|
||||
return next;
|
||||
@@ -3024,7 +3037,11 @@ export default function App() {
|
||||
setWb(null); // clear the Worked-before grid for the just-cleared callsign
|
||||
setLookupError('');
|
||||
rstUserEditedRef.current = false;
|
||||
applyModePreset(mode);
|
||||
// modeRef, not `mode`: resetEntry is reached from the global key handler,
|
||||
// whose effect does not re-run on a mode change. Pressing ESC during a CW
|
||||
// macro therefore reset the report using the mode as it was when that
|
||||
// handler was built — 'SSB' — and turned 599 into 59.
|
||||
applyModePreset(modeRef.current);
|
||||
setDetails((d) => ({
|
||||
...d,
|
||||
state: '', cnty: '', address: '', lat: undefined, lon: undefined,
|
||||
@@ -4351,7 +4368,8 @@ export default function App() {
|
||||
const matches = clusterStatusFilter.has(st)
|
||||
|| (!!e?.worked_call && clusterStatusFilter.has('worked'))
|
||||
|| (!!e?.new_pota && clusterStatusFilter.has('new-pota'))
|
||||
|| (!!e?.new_county && clusterStatusFilter.has('new-county'));
|
||||
|| (!!e?.new_county && clusterStatusFilter.has('new-county'))
|
||||
|| (!!e?.new_pfx && clusterStatusFilter.has('new-pfx'));
|
||||
if (!matches) return false;
|
||||
}
|
||||
if (clusterHideWorked) {
|
||||
@@ -4470,7 +4488,8 @@ export default function App() {
|
||||
// Same colours as the badges in the grid — a filter that does not
|
||||
// look like what it selects has to be learned twice.
|
||||
{ k: 'new-pota' as SpotFilterKey, label: 'NEW POTA', cls: 'bg-success-muted text-success-muted-foreground border-success-border' },
|
||||
{ k: 'new-county' as SpotFilterKey, label: 'NEW COUNTY', cls: 'bg-info-muted text-info-muted-foreground border-info-border' },
|
||||
{ k: 'new-county' as SpotFilterKey, label: 'NEW COUNTY', cls: 'bg-success-muted text-success-muted-foreground border-success-border' },
|
||||
{ k: 'new-pfx' as SpotFilterKey, label: 'NEW PFX', cls: 'bg-caution-muted text-caution-muted-foreground border-caution-border' },
|
||||
// (no WORKED chip — use the "Hide worked" checkbox to drop dupes.)
|
||||
]).map((s) => {
|
||||
const on = clusterStatusFilter.has(s.k);
|
||||
|
||||
@@ -53,6 +53,8 @@ export type SpotStatusEntry = {
|
||||
worked_call?: boolean;
|
||||
new_county?: boolean;
|
||||
new_pota?: boolean;
|
||||
new_pfx?: boolean;
|
||||
pfx?: string;
|
||||
};
|
||||
|
||||
type Props = {
|
||||
@@ -123,9 +125,9 @@ function cellText(value: any, color: string | null): any {
|
||||
return <span style={color ? { color, fontWeight: 700 } : undefined}>{txt}</span>;
|
||||
}
|
||||
|
||||
// statusColor is the colour for a resolved status in the Status column. County
|
||||
// and POTA keep their own tokens: they are orthogonal to the band/mode/DXCC
|
||||
// story and an operator filters on them separately.
|
||||
// statusColor is the colour for a resolved status in the Status column. County,
|
||||
// POTA and prefix keep their own tokens: they are orthogonal to the band/mode/
|
||||
// DXCC story and an operator filters on them separately.
|
||||
function statusColor(s: SpotStatusEntry | undefined): string | null {
|
||||
switch (s?.status) {
|
||||
case 'new':
|
||||
@@ -191,6 +193,7 @@ const makeColCatalog = (t: TFn): ColEntry[] => [
|
||||
else if (s?.worked_call) parts.push(t('clg2.wkdCall'));
|
||||
if (s?.new_county) parts.push(t('clg2.newCounty'));
|
||||
if (s?.new_pota) parts.push(t('clg2.newPota'));
|
||||
if (s?.new_pfx) parts.push(t('clg2.newPfx'));
|
||||
return parts.join(' ');
|
||||
},
|
||||
cellRenderer: (p: any) => {
|
||||
@@ -205,8 +208,9 @@ const makeColCatalog = (t: TFn): ColEntry[] => [
|
||||
: t('clg2.wkdCall');
|
||||
parts.push({ text: label, color: main });
|
||||
}
|
||||
if (s?.new_county) parts.push({ text: t('clg2.newCounty'), color: 'var(--info)' });
|
||||
if (s?.new_county) parts.push({ text: t('clg2.newCounty'), color: 'var(--success)' });
|
||||
if (s?.new_pota) parts.push({ text: t('clg2.newPota'), color: 'var(--success)' });
|
||||
if (s?.new_pfx) parts.push({ text: t('clg2.newPfx'), color: 'var(--caution)' });
|
||||
if (parts.length === 0) return <span style={{ color: 'var(--muted-foreground)', fontSize: 10 }}>—</span>;
|
||||
return (
|
||||
<span style={{ whiteSpace: 'nowrap' }}>
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,6 +1,6 @@
|
||||
// Single source of truth for the app version shown in the UI (header + About).
|
||||
// Bump this on a release (the release script updates it alongside telemetry.go).
|
||||
export const APP_VERSION = '0.23.0';
|
||||
export const APP_VERSION = '0.23.1';
|
||||
|
||||
// Author / credits, shown in Help -> About.
|
||||
export const APP_AUTHOR = 'F4BPO';
|
||||
|
||||
@@ -2904,6 +2904,8 @@ export namespace main {
|
||||
worked_call: boolean;
|
||||
new_county: boolean;
|
||||
new_pota: boolean;
|
||||
new_pfx: boolean;
|
||||
pfx?: string;
|
||||
|
||||
static createFrom(source: any = {}) {
|
||||
return new SpotStatus(source);
|
||||
@@ -2920,6 +2922,8 @@ export namespace main {
|
||||
this.worked_call = source["worked_call"];
|
||||
this.new_county = source["new_county"];
|
||||
this.new_pota = source["new_pota"];
|
||||
this.new_pfx = source["new_pfx"];
|
||||
this.pfx = source["pfx"];
|
||||
}
|
||||
}
|
||||
export class StartupStatus {
|
||||
|
||||
+1
-1
@@ -21,7 +21,7 @@ import (
|
||||
|
||||
const (
|
||||
// appVersion is stamped on every heartbeat (and could feed the About box).
|
||||
appVersion = "0.23.0"
|
||||
appVersion = "0.23.1"
|
||||
|
||||
// posthogHost is the PostHog ingestion endpoint. EU cloud by default; change
|
||||
// to https://us.i.posthog.com for a US project.
|
||||
|
||||
Reference in New Issue
Block a user