fix: recording not being sent if mail enable was not checked

This commit is contained in:
2026-06-28 19:08:32 +02:00
parent 464a1c702c
commit 165f33caa5
9 changed files with 166 additions and 114 deletions
+33 -11
View File
@@ -117,7 +117,7 @@ const emptyDetails: DetailsState = {
prop_mode: '', my_rig: '', my_antenna: '',
tx_pwr: undefined,
sat_name: '', sat_mode: '',
contest_id: '', srx: undefined, stx: undefined,
contest_id: '', srx_string: '', stx_string: '',
email: '',
award_refs: '',
};
@@ -216,6 +216,14 @@ function estimateCwMs(resolved: string, wpm: number): number {
const spaces = (resolved.match(/\s/g) || []).length;
return ((chars * 10 + spaces * 4) * 1200) / w;
}
// exchangeFields splits a contest exchange the operator typed as free text into
// the ADIF numeric field (SRX/STX, when it's all digits) or the string field
// (SRX_STRING/STX_STRING, when it's alphanumeric like a section/zone).
function exchangeFields(raw?: string): { num?: number; str: string } {
const t = (raw || '').trim();
if (t === '') return { str: '' };
return /^\d+$/.test(t) ? { num: parseInt(t, 10), str: '' } : { str: t };
}
// rstOptions returns the valid report choices for a mode from the user's
// editable lists (Settings → Modes), with a tiny fallback before they load.
function rstOptions(mode: string, lists: RSTLists): string[] {
@@ -413,10 +421,12 @@ export default function App() {
// User changed band/mode in the entry strip → push to the rig if CAT is up.
// Both calls are fire-and-forget; CAT will reflect back via cat:state.
// When the field is LOCKED (🔒, e.g. logging an old QSO off-frequency), we do
// NOT drive the radio — the lock means "this value is decoupled from the rig".
function onBandUserChange(v: string) {
setBand(v);
noteManualEdit();
if (catState.enabled && catState.connected) {
if (catState.enabled && catState.connected && !locks.band && !locks.freq) {
const hz = qsyFreqHz(v, mode);
if (hz > 0) SetCATFrequency(hz).catch(() => {});
}
@@ -425,7 +435,7 @@ export default function App() {
setMode(v);
applyModePreset(v);
noteManualEdit();
if (catState.enabled && catState.connected) {
if (catState.enabled && catState.connected && !locks.mode) {
SetCATMode(v).catch(() => {});
}
}
@@ -1465,30 +1475,38 @@ export default function App() {
// typing: only update when the field is empty, already shows this call, or
// still shows the previous broadcast (i.e. the field content is ours, not
// a different call the user typed). Returns true if it actually changed.
const applyUdpCall = (raw: string): boolean => {
// force = true means an EXPLICIT action (a panadapter/spot click, a remote
// "set call" command) that should always win, even over a call the operator
// already typed. Only WSJT-X's CONTINUOUS DX-call stream keeps the no-clobber
// guard (it re-broadcasts constantly and must not fight what you typed).
const applyUdpCall = (raw: string, force = false): boolean => {
const call = String(raw ?? '').trim();
if (!call) return false;
const upper = call.toUpperCase();
const current = callsignValRef.current.trim().toUpperCase();
const prev = lastUdpCallRef.current;
lastUdpCallRef.current = upper; // remember this broadcast either way
if (current === upper) return false; // already shown → no-op
if (current !== '' && current !== prev) return false; // user typed a different call → leave it
if (current === upper) return false; // already shown → no-op
if (!force && current !== '' && current !== prev) return false; // user typed a different call → leave it
onCallsignInput(call, { force: true }); // programmatic → always look up
return true;
};
const unsubDX = EventsOn('udp:dx_call', (p: any) => {
// Anything that isn't WSJT-X (N1MM, ADIF, a panadapter/cluster click relayed
// over UDP…) is an explicit pick → force it over an existing call.
const force = String(p?.service ?? '').toLowerCase() !== 'wsjt';
// External app moved to a new station → fresh recording for the new target.
if (applyUdpCall(p?.call)) restartRecordingForNewTarget(String(p?.call ?? ''));
if (applyUdpCall(p?.call, force)) restartRecordingForNewTarget(String(p?.call ?? ''));
});
const unsubRC = EventsOn('udp:remote_call', (raw: string) => {
if (applyUdpCall(raw)) restartRecordingForNewTarget(String(raw ?? ''));
if (applyUdpCall(raw, true)) restartRecordingForNewTarget(String(raw ?? '')); // explicit remote pick
});
// Clicked one of OpsLog's spots on the FlexRadio panadapter → fill the call
// (the radio already tuned via trigger_action=Tune, and CAT reads the freq).
// An explicit click always wins over whatever call is currently in the field.
const unsubFlexSpot = EventsOn('flex:spot_clicked', (p: any) => {
const call = String(p?.call ?? '');
if (applyUdpCall(call)) restartRecordingForNewTarget(call);
if (applyUdpCall(call, true)) restartRecordingForNewTarget(call);
});
const unsubProg = EventsOn('import:progress', (p: any) => {
setImportProgress({ processed: Number(p?.processed ?? 0), total: Number(p?.total ?? 0) });
@@ -1585,7 +1603,7 @@ export default function App() {
GRID: station.my_grid || '', COUNTRY: (station as any).my_country || '',
MY_QTH: (station as any).my_city || '', MY_RIG: details.my_rig || '', MY_ANTENNA: details.my_antenna || '',
MY_IOTA: (station as any).my_iota || '', MY_SOTA: (station as any).my_sota_ref || '',
CONT_RX: details.srx != null ? String(details.srx) : '', CONT_TX: details.stx != null ? String(details.stx) : '',
CONT_RX: details.srx_string || '', CONT_TX: details.stx_string || '',
};
let out = text.replace(/<([A-Z_]+)>/g, (_m, k) => vars[k] ?? '');
out = out.replace(/\*/g, myCall).replace(/!/g, his);
@@ -1728,6 +1746,10 @@ export default function App() {
// when you first entered the call (minutes early) and won't match LoTW.
const startEqualsEnd = localStorage.getItem('opslog.startEqualsEnd') === '1';
const start = (startEqualsEnd && !locks.start) ? end : baseStart;
// Contest exchanges: a purely-numeric exchange → ADIF SRX/STX (int); an
// alphanumeric one (e.g. a section/zone) → SRX_STRING/STX_STRING.
const srxE = exchangeFields(details.srx_string);
const stxE = exchangeFields(details.stx_string);
const payload: any = {
callsign: callsign.trim().toUpperCase(),
qso_date: start.toISOString(),
@@ -1746,7 +1768,7 @@ export default function App() {
tx_pwr: details.tx_pwr,
sat_name: details.sat_name, sat_mode: details.sat_mode,
contest_id: details.contest_id,
srx: details.srx, stx: details.stx,
srx: srxE.num, stx: stxE.num, srx_string: srxE.str, stx_string: stxE.str,
email: details.email,
};
applyAwardRefs(payload, details.award_refs ?? '', awardFieldRef.current);