feat(worked): fold portable callsigns into the worked-before history
Typing RK3DWA found nothing while RK3DWA/3 found 21 QSOs, so a station's history was only visible if you happened to type the exact form it had been logged under — and an operator who worked it as /0, /P or /MM saw none of it. The other RDA tools and Log4OM fold these together; this does too. The predicate strips the suffix from what was typed and matches "call = base OR call LIKE base/%", so it works from either end: the base call finds the portable QSOs and a portable call finds the plain ones. Deliberately not a bare prefix LIKE 'RK3DWA%', which would also match RK3DWAB — a different station. The '/' is what makes it the same operator. Settings -> General to turn it off. Default ON, hence the inverted storage: an existing install has no key, and reading that as OFF would leave everyone with the behaviour we were asked to change. Contest dupe checking is untouched — it runs through ContestDupe, a separate binding, and stays an exact match as a contest requires.
This commit is contained in:
@@ -27,6 +27,7 @@ import {
|
||||
AudioStartTX, AudioStopTX, AudioTXActive,
|
||||
ListClusterServers, SaveClusterServer, DeleteClusterServer,
|
||||
GetClusterAutoConnect, SetClusterAutoConnect, GetSelfSpotSettings, SaveSelfSpotSettings,
|
||||
GetWorkedCallVariants, SetWorkedCallVariants,
|
||||
ConnectClusterServer, DisconnectClusterServer,
|
||||
ConnectAllClusters, DisconnectAllClusters, GetClusterStatus,
|
||||
GetBackupSettings, SaveBackupSettings, RunBackupNow, PickBackupFolder,
|
||||
@@ -1524,6 +1525,8 @@ export function SettingsModal({ onClose, onSaved, initialSection, onMainPaneChan
|
||||
|
||||
const [clusterServers, setClusterServers] = useState<ClusterServer[]>([]);
|
||||
const [clusterAutoConnect, setClusterAutoConnectState] = useState(false);
|
||||
// Defaults to true so the checkbox matches the backend before the read lands.
|
||||
const [workedVariants, setWorkedVariants] = useState(true);
|
||||
// Self-spot. SELF_SPOT_MIN_MIN mirrors the backend floor — the input clamps on
|
||||
// blur, not per keystroke, or typing "10" would be rewritten to "5" the moment
|
||||
// the "1" landed and the field would fight the operator.
|
||||
@@ -1632,6 +1635,7 @@ export function SettingsModal({ onClose, onSaved, initialSection, onMainPaneChan
|
||||
try { setAudioCfg(await GetAudioSettings() as any); } catch {}
|
||||
try { setEmailCfg(await GetEmailSettings() as any); } catch {}
|
||||
try { setEqslCfg(await QSLGetEmailTemplates() as any); } catch {}
|
||||
try { setWorkedVariants(await GetWorkedCallVariants()); } catch {}
|
||||
reloadAudioDevices();
|
||||
reloadDvk();
|
||||
} catch (e: any) {
|
||||
@@ -5666,6 +5670,13 @@ export function SettingsModal({ onClose, onSaved, initialSection, onMainPaneChan
|
||||
<Checkbox checked={autofocusWB} onCheckedChange={(c) => { const v = !!c; setAutofocusWB(v); writeUiPref('opslog.autofocusWB', v ? '1' : '0'); }} />
|
||||
{t('gen.autofocusWB')}
|
||||
</label>
|
||||
{/* Backend setting, not a UI pref: the fold happens in the SQL. Saved
|
||||
instantly like the rest of this panel. */}
|
||||
<label className="flex items-center gap-2 text-sm cursor-pointer">
|
||||
<Checkbox checked={workedVariants}
|
||||
onCheckedChange={(c) => { const v = !!c; setWorkedVariants(v); SetWorkedCallVariants(v).catch((e: any) => setErr(String(e?.message ?? e))); }} />
|
||||
{t('gen.workedVariants')} <span className="text-xs text-muted-foreground">{t('gen.workedVariantsHint')}</span>
|
||||
</label>
|
||||
<label className="flex items-center gap-2 text-sm cursor-pointer">
|
||||
<Checkbox checked={showBeamMap} onCheckedChange={(c) => { const v = !!c; setShowBeamMap(v); writeUiPref('opslog.showBeamOnMap', v ? '1' : '0'); }} />
|
||||
{t('gen.showBeam')}
|
||||
|
||||
@@ -186,6 +186,7 @@ const en: Dict = {
|
||||
// General panel
|
||||
'gen.hint': 'App behaviour (saved instantly).',
|
||||
'gen.autofocusWB': 'Auto-focus "Worked before" for known stations',
|
||||
'gen.workedVariants': '"Worked before" folds portable callsigns together', 'gen.workedVariantsHint': '(RK3DWA also finds RK3DWA/3, /P, /MM — and the other way round. Contest dupe checking stays exact.)',
|
||||
'gen.showBeam': 'Show the antenna beam heading on the Main map',
|
||||
'gen.startEqEnd': 'QSO start time = end time', 'gen.startEqEndHint': '(matches LoTW when you call a while)',
|
||||
'gen.showQsoRate': 'Show QSO rate in the header', 'gen.showQsoRateHint': '(QSOs/hour, projected from the last 10 / 60 min)',
|
||||
@@ -607,6 +608,7 @@ const fr: Dict = {
|
||||
'relayauto.from': 'de', 'relayauto.to': 'à',
|
||||
'gen.hint': 'Comportement de l\'application (enregistré immédiatement).',
|
||||
'gen.autofocusWB': 'Focus auto sur « Déjà contacté » pour les stations connues',
|
||||
'gen.workedVariants': '« Déjà contacté » regroupe les indicatifs portables', 'gen.workedVariantsHint': '(RK3DWA trouve aussi RK3DWA/3, /P, /MM — et inversement. Le contrôle de doublon en concours reste strict.)',
|
||||
'gen.showBeam': 'Afficher le cap de l\'antenne sur la carte principale',
|
||||
'gen.startEqEnd': 'Heure de début du QSO = heure de fin', 'gen.startEqEndHint': '(correspond à LoTW quand tu appelles un moment)',
|
||||
'gen.showQsoRate': 'Afficher le rythme QSO dans la barre du haut', 'gen.showQsoRateHint': '(QSO/heure, projeté sur les 10 / 60 dernières min)',
|
||||
|
||||
Vendored
+4
@@ -515,6 +515,8 @@ export function GetWinkeyerSettings():Promise<main.WinkeyerSettings>;
|
||||
|
||||
export function GetWinkeyerStatus():Promise<winkeyer.Status>;
|
||||
|
||||
export function GetWorkedCallVariants():Promise<boolean>;
|
||||
|
||||
export function GetYaesuState():Promise<cat.YaesuTXState>;
|
||||
|
||||
export function HasBuiltinReferences(arg1:string):Promise<boolean>;
|
||||
@@ -967,6 +969,8 @@ export function SetUltrabeamDirection(arg1:number):Promise<void>;
|
||||
|
||||
export function SetWinkeyerTrace(arg1:boolean):Promise<void>;
|
||||
|
||||
export function SetWorkedCallVariants(arg1:boolean):Promise<void>;
|
||||
|
||||
export function SetYaesuAFGain(arg1:number):Promise<void>;
|
||||
|
||||
export function SetYaesuAGC(arg1:string):Promise<void>;
|
||||
|
||||
@@ -978,6 +978,10 @@ export function GetWinkeyerStatus() {
|
||||
return window['go']['main']['App']['GetWinkeyerStatus']();
|
||||
}
|
||||
|
||||
export function GetWorkedCallVariants() {
|
||||
return window['go']['main']['App']['GetWorkedCallVariants']();
|
||||
}
|
||||
|
||||
export function GetYaesuState() {
|
||||
return window['go']['main']['App']['GetYaesuState']();
|
||||
}
|
||||
@@ -1882,6 +1886,10 @@ export function SetWinkeyerTrace(arg1) {
|
||||
return window['go']['main']['App']['SetWinkeyerTrace'](arg1);
|
||||
}
|
||||
|
||||
export function SetWorkedCallVariants(arg1) {
|
||||
return window['go']['main']['App']['SetWorkedCallVariants'](arg1);
|
||||
}
|
||||
|
||||
export function SetYaesuAFGain(arg1) {
|
||||
return window['go']['main']['App']['SetYaesuAFGain'](arg1);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user