fix(spot): spot the references worth chasing, not the derived ones
The comment came out "SSB WAC EU WAZ 15 WPX HA5" — the QSO's materialised award_refs hold both kinds, and I took them all. Those four are what every reader of the spot derives from the callsign in their head. They spent the whole 30-character comment saying nothing and buried SOTA HA/KM-018, the one reference anyone would have acted on. Computed awards are now filtered out with the same rule the QSO editor uses (isComputedAwardField), which is why they are the ones it lists on the left and not in its read-only panel. The field map moves from a ref to state as well: a ref left the first spot of a session computing before GetAwardDefs had answered. Also opens 0.25.2, since 0.25.1 shipped before the spot work, and sets the default mode list to SSB, CW, FT8, FT4, FT2, RTTY, PSK31, FM. AM and DIGITALVOICE stay in the catalogue but are no longer selected on a fresh install. FT2 joins the digital family everywhere its siblings are listed — report list, RST defaults, Flex power class — so it gets dB reports rather than the 59 an unknown mode falls back to.
This commit is contained in:
+26
-18
@@ -141,7 +141,9 @@ type CATState = Omit<catModels.RigState, 'convertValues'>;
|
||||
// threshold is bypassed while filtering — see buildActiveFilter).
|
||||
const FILTERED_LIMIT_CAP = 10000;
|
||||
const DEFAULT_BANDS = ['160m','80m','60m','40m','30m','20m','17m','15m','12m','10m','6m','2m','70cm','23cm'];
|
||||
const DEFAULT_MODES = ['SSB','CW','FT8','FT4','RTTY','PSK31','AM','FM','DIGITALVOICE'];
|
||||
// Mirrors defaultModes in app.go — the fallback used before the backend answers.
|
||||
// AM and DIGITALVOICE stay in the catalogue but are not offered by default.
|
||||
const DEFAULT_MODES = ['SSB','CW','FT8','FT4','FT2','RTTY','PSK31','FM'];
|
||||
// Modes the QSO recorder captures (phone only). Mirrors recordableMode() in
|
||||
// app.go — digital modes carry no useful audio, and CW has no DAX audio on Flex,
|
||||
// so neither is recorded (no REC badge / timer for them).
|
||||
@@ -311,7 +313,7 @@ function modeAccent(mode?: string): string {
|
||||
type RSTLists = { phone: string[]; cw: string[]; digital: string[] };
|
||||
function rstCategory(mode: string): keyof RSTLists {
|
||||
const m = (mode || '').toUpperCase();
|
||||
const digital = ['FT8', 'FT4', 'JT65', 'JT9', 'JS8', 'Q65', 'MSK144', 'FST4', 'FST4W', 'MFSK', 'OLIVIA', 'JT4', 'WSPR'];
|
||||
const digital = ['FT8', 'FT4', 'FT2', 'JT65', 'JT9', 'JS8', 'Q65', 'MSK144', 'FST4', 'FST4W', 'MFSK', 'OLIVIA', 'JT4', 'WSPR'];
|
||||
if (digital.includes(m)) return 'digital';
|
||||
if (['CW', 'RTTY', 'PSK31', 'PSK63', 'PSK', 'PSK125'].includes(m)) return 'cw';
|
||||
return 'phone';
|
||||
@@ -2086,6 +2088,24 @@ export default function App() {
|
||||
const [showScp, setShowScp] = useState(() => localStorage.getItem('opslog.showScp') !== '0');
|
||||
const [showBeamOnMap, setShowBeamOnMap] = useState(() => localStorage.getItem('opslog.showBeamOnMap') !== '0');
|
||||
|
||||
// Award code → scanned field (e.g. POTA→pota_ref, WWFF→wwff). Used to route
|
||||
// picked award references to the QSO field/extras each award actually reads.
|
||||
const awardFieldRef = useRef<Record<string, string>>({});
|
||||
// The same map as state, because the spot comment has to be recomputed once it
|
||||
// arrives — a ref would leave the first Send Spot of a session with no
|
||||
// references at all, or with the computed ones it is meant to filter out.
|
||||
const [awardFields, setAwardFields] = useState<Record<string, string>>({});
|
||||
useEffect(() => {
|
||||
GetAwardDefs()
|
||||
.then((defs) => {
|
||||
const m: Record<string, string> = {};
|
||||
for (const d of (defs ?? []) as any[]) m[String(d.code).toUpperCase()] = String(d.field || '').toLowerCase();
|
||||
awardFieldRef.current = m;
|
||||
setAwardFields(m);
|
||||
})
|
||||
.catch(() => {});
|
||||
}, []);
|
||||
|
||||
// Award references offered to the Send Spot window, as cluster-ready strings.
|
||||
//
|
||||
// The entry panel holds them as "CODE@REF;CODE@REF" while the QSO is being
|
||||
@@ -2094,23 +2114,11 @@ export default function App() {
|
||||
// the station still in the field — and fall back to the last logged QSO, which
|
||||
// is where the callsign and frequency defaults come from too.
|
||||
const spotEntryRefs = useMemo(() => {
|
||||
const entry = spotRefList(parseManualRefs(details.award_refs ?? ''));
|
||||
const entry = spotRefList(parseManualRefs(details.award_refs ?? ''), awardFields);
|
||||
if (entry.length) return entry;
|
||||
return spotRefList(parseAwardRefs(qsos[0]?.award_refs));
|
||||
}, [details.award_refs, qsos]);
|
||||
return spotRefList(parseAwardRefs(qsos[0]?.award_refs), awardFields);
|
||||
}, [details.award_refs, qsos, awardFields]);
|
||||
|
||||
// Award code → scanned field (e.g. POTA→pota_ref, WWFF→wwff). Used to route
|
||||
// picked award references to the QSO field/extras each award actually reads.
|
||||
const awardFieldRef = useRef<Record<string, string>>({});
|
||||
useEffect(() => {
|
||||
GetAwardDefs()
|
||||
.then((defs) => {
|
||||
const m: Record<string, string> = {};
|
||||
for (const d of (defs ?? []) as any[]) m[String(d.code).toUpperCase()] = String(d.field || '').toLowerCase();
|
||||
awardFieldRef.current = m;
|
||||
})
|
||||
.catch(() => {});
|
||||
}, []);
|
||||
|
||||
// === Clock ===
|
||||
const [utcNow, setUtcNow] = useState('');
|
||||
@@ -7242,7 +7250,7 @@ export default function App() {
|
||||
freqKHz: q.freq_hz ? Math.round((q.freq_hz / 1000) * 10) / 10 : 0,
|
||||
mode: q.mode ?? '',
|
||||
band: q.band,
|
||||
refs: spotRefList(parseAwardRefs(q.award_refs)),
|
||||
refs: spotRefList(parseAwardRefs(q.award_refs), awardFields),
|
||||
}))}
|
||||
onSend={async (call, freqKHz, comment) => {
|
||||
await SendClusterSpot(call, freqKHz, comment);
|
||||
|
||||
@@ -109,6 +109,7 @@ const MODE_CATALOG: Array<{ name: string; sent: string; rcvd: string }> = [
|
||||
{ name: 'DIGITALVOICE', sent: '59', rcvd: '59' },
|
||||
{ name: 'FT8', sent: '-10', rcvd: '-10' },
|
||||
{ name: 'FT4', sent: '-10', rcvd: '-10' },
|
||||
{ name: 'FT2', sent: '-10', rcvd: '-10' },
|
||||
{ name: 'JS8', sent: '-10', rcvd: '-10' },
|
||||
{ name: 'MSK144', sent: '+00', rcvd: '+00' },
|
||||
{ name: 'JT65', sent: '-15', rcvd: '-15' },
|
||||
|
||||
@@ -155,16 +155,37 @@ export function buildAwardRefs(qso: any, pickable: Array<{ code: string; field:
|
||||
return out.join(';');
|
||||
}
|
||||
|
||||
// spotRefList turns a code→ref map into the strings a DX-cluster comment wants:
|
||||
// ["POTA FR-11553", "IOTA EU-064"]. A code carrying several references (a
|
||||
// two-fer park activation) yields one entry per reference, so a spot can carry
|
||||
// the one that fits and drop the rest rather than emitting a truncated pair.
|
||||
// COMPUTED_AWARD_FIELDS mirrors isComputedAwardField in app.go: awards whose
|
||||
// reference is derived from the callsign and cty.dat, never assigned by hand.
|
||||
//
|
||||
// Shortest first: cluster comments are 30 characters, and when they do not all
|
||||
// fit, more references beat fewer.
|
||||
export function spotRefList(byCode: Record<string, string>): string[] {
|
||||
// State and county are deliberately absent, there as here — an operator sets
|
||||
// those themselves and they drive real predefined-list awards (WAS, WAJA, JCC).
|
||||
const COMPUTED_AWARD_FIELDS = new Set([
|
||||
'dxcc', 'cqz', 'ituz', 'prefix', 'callsign', 'cont', 'country', 'grid', 'grid4',
|
||||
]);
|
||||
|
||||
// spotRefList turns a QSO's code→ref map into the strings a DX-cluster comment
|
||||
// wants: ["SOTA HA/KM-018"].
|
||||
//
|
||||
// COMPUTED awards are dropped. A QSO's materialised references hold both kinds,
|
||||
// and the derived ones — DXCC, WAC, WAZ, WPX — are exactly what every reader of
|
||||
// the spot works out from the callsign anyway. Announcing "WAC EU WAZ 15 WPX HA5"
|
||||
// spends the whole 30-character comment saying nothing, and buries the one
|
||||
// reference worth chasing. Same rule as the QSO editor, which is why these are
|
||||
// the references it lists on the LEFT and not in its read-only panel.
|
||||
//
|
||||
// fieldOf is award code → scanned field (from GetAwardDefs). A code missing from
|
||||
// it is dropped rather than guessed: showing a reference on the air matters more
|
||||
// than showing all of them.
|
||||
//
|
||||
// A code carrying several references (a two-fer park) yields one entry each, so
|
||||
// a spot can take the one that fits instead of emitting a truncated pair.
|
||||
// Shortest first: when they cannot all fit, more references beat fewer.
|
||||
export function spotRefList(byCode: Record<string, string>, fieldOf: Record<string, string>): string[] {
|
||||
const out: string[] = [];
|
||||
for (const [code, refs] of Object.entries(byCode ?? {})) {
|
||||
const field = fieldOf?.[code.toUpperCase()];
|
||||
if (!field || COMPUTED_AWARD_FIELDS.has(field)) continue;
|
||||
for (const ref of String(refs).split(',').map((s) => s.trim()).filter(Boolean)) {
|
||||
out.push(`${code.toUpperCase()} ${ref.toUpperCase()}`);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user