This commit is contained in:
2026-05-26 01:14:43 +02:00
parent 7e518ddba3
commit 28da6f6165
9 changed files with 136 additions and 24 deletions
+49 -3
View File
@@ -1,6 +1,6 @@
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import {
AlertCircle, Antenna, CheckCircle2, Clock, Compass, Hash, Loader2, Lock,
AlertCircle, Antenna, CheckCircle2, Clock, Compass, ExternalLink, Hash, Loader2, Lock,
Maximize2, Minimize2, Pencil, RadioTower, RefreshCw, Send, Settings, Square, Trash2, Unlock, X,
} from 'lucide-react';
@@ -15,6 +15,7 @@ import {
GetCATState, SetCATFrequency, SetCATMode, SwitchCATRig,
RefreshCtyDat,
RotatorGoTo, RotatorStop,
OpenExternalURL,
GetCATSettings,
} from '../wailsjs/go/main/App';
import { EventsOn, EventsOff } from '../wailsjs/runtime/runtime';
@@ -555,6 +556,8 @@ export default function App() {
function resetAutoFill() {
setName(''); setQth(''); setCountry(''); setGrid('');
setWb(null);
setLookupResult(null);
setDetails((d) => ({
...d,
state: '', cnty: '', address: '',
@@ -1010,6 +1013,21 @@ export default function App() {
<div className="flex flex-col w-40">
<Label className="mb-1 flex items-center gap-2 h-3.5">
Callsign
{callsign.trim() && (
<button
type="button"
tabIndex={-1}
onClick={() => {
const c = callsign.trim().toUpperCase();
OpenExternalURL(`https://www.qrz.com/db/${encodeURIComponent(c)}`)
.catch((err) => setError(String(err?.message ?? err)));
}}
title="Open this callsign on QRZ.com"
className="inline-flex items-center justify-center size-3.5 rounded text-muted-foreground/60 hover:text-primary transition-colors"
>
<ExternalLink className="size-3" />
</button>
)}
{lookupBusy && <Badge variant="secondary" className="text-[9px] py-0 px-1.5 normal-case font-medium tracking-wider"><Loader2 className="size-2.5 mr-1 animate-spin" />Looking up</Badge>}
{!lookupBusy && lookupResult && (
<Badge className="bg-emerald-100 text-emerald-700 hover:bg-emerald-100 text-[9px] py-0 px-1.5 normal-case font-medium tracking-wider" variant="outline">
@@ -1205,7 +1223,35 @@ export default function App() {
else and let the user re-expand with the topbar toggle. */}
{compact ? null : <>
{/* ===== BAND/SLOT GRID ===== */}
<BandSlotGrid wb={wb} busy={wbBusy} currentBand={band} currentMode={mode} />
{/* QRZ profile picture sits next to the matrix when the user has
opted in (Settings → Lookup → Show QRZ profile pictures). The
backend returns image_url="" when the toggle is off, so we
don't need to re-check the setting here. */}
<div className="flex items-start gap-3">
<div className="flex-1 min-w-0">
<BandSlotGrid wb={wb} busy={wbBusy} currentBand={band} currentMode={mode} />
</div>
{lookupResult?.image_url && (
<a
href={lookupResult.image_url}
onClick={(e) => {
e.preventDefault();
OpenExternalURL(lookupResult.image_url!).catch((err) => setError(String(err?.message ?? err)));
}}
title="Open full-size on QRZ.com"
className="block shrink-0 rounded border border-border overflow-hidden hover:border-primary/60 transition-colors"
>
<img
src={lookupResult.image_url}
alt={`${callsign} profile`}
className="block w-[160px] h-[120px] object-cover bg-muted/30"
loading="lazy"
referrerPolicy="no-referrer"
onError={(e) => { (e.currentTarget as HTMLImageElement).style.display = 'none'; }}
/>
</a>
)}
</div>
{/* ===== F2-F5 DETAILS ===== */}
<DetailsPanel
@@ -1340,7 +1386,7 @@ export default function App() {
<td className="px-2.5 py-1.5 whitespace-nowrap border-b border-border/40">
<span className="inline-block px-2 py-0.5 rounded font-mono text-[11px] font-semibold bg-emerald-100 text-emerald-700">{q.mode}</span>
</td>
<td className="px-2.5 py-1.5 font-mono text-right whitespace-nowrap border-b border-border/40">{fmtFreq(q.freq_hz)}</td>
<td className="px-2.5 py-1.5 font-mono text-right whitespace-nowrap border-b border-border/40">{q.freq_hz ? fmtFreqDots(fmtFreq(q.freq_hz)) : ''}</td>
<td className="px-2.5 py-1.5 font-mono whitespace-nowrap border-b border-border/40">{q.rst_sent ?? ''}</td>
<td className="px-2.5 py-1.5 font-mono whitespace-nowrap border-b border-border/40">{q.rst_rcvd ?? ''}</td>
<td className="px-2.5 py-1.5 whitespace-nowrap border-b border-border/40">{q.name ?? ''}</td>
+20 -1
View File
@@ -100,7 +100,7 @@ const TREE: TreeNode[] = [
],
},
{
kind: 'group', label: 'Hardware Configuration', icon: Server, children: [
kind: 'group', label: 'Hardware Configuration', icon: Server, defaultOpen: true, children: [
{ kind: 'item', label: 'CAT interface (OmniRig)', id: 'cat' },
{ kind: 'item', label: 'Rotator (PstRotator)', id: 'rotator' },
{ kind: 'item', label: 'Antenna (Ultrabeam)', id: 'antenna', disabled: true },
@@ -225,6 +225,7 @@ export function SettingsModal({ onClose, onSaved, initialSection }: Props) {
qrz_user: '', qrz_password: '',
hamqth_user: '', hamqth_password: '',
primary: '', failsafe: '',
download_images: false,
cache_ttl_days: 30,
});
// Per-provider Test state — keeps the success/error feedback adjacent
@@ -712,6 +713,24 @@ export function SettingsModal({ onClose, onSaved, initialSection }: Props) {
Failsafe is consulted only when the Primary returns no match or errors. Set both to none (uncheck) during contests to skip the network entirely.
</p>
<div className="mt-6 pt-4 border-t border-border">
<h3 className="text-sm font-semibold mb-2">Display</h3>
<label className="flex items-start gap-2 text-sm cursor-pointer">
<Checkbox
checked={lookup.download_images}
onCheckedChange={(c) => setLookup((s) => ({ ...s, download_images: !!c }))}
className="mt-0.5"
/>
<span>
Show QRZ profile pictures
<span className="block text-xs text-muted-foreground mt-0.5">
Display the photo from QRZ.com next to the worked-before matrix.
May noticeably slow lookups during busy contest days; turn off if you operate fast.
</span>
</span>
</label>
</div>
<div className="mt-6 pt-4 border-t border-border">
<h3 className="text-sm font-semibold mb-2">Cache</h3>
<p className="text-xs text-muted-foreground mb-3">
+2
View File
@@ -53,6 +53,8 @@ export function LookupCallsign(arg1:string):Promise<lookup.Result>;
export function OpenADIFFile():Promise<string>;
export function OpenExternalURL(arg1:string):Promise<void>;
export function RefreshCtyDat():Promise<main.CtyDatInfo>;
export function RotatorGoTo(arg1:number,arg2:number):Promise<void>;
+4
View File
@@ -94,6 +94,10 @@ export function OpenADIFFile() {
return window['go']['main']['App']['OpenADIFFile']();
}
export function OpenExternalURL(arg1) {
return window['go']['main']['App']['OpenExternalURL'](arg1);
}
export function RefreshCtyDat() {
return window['go']['main']['App']['RefreshCtyDat']();
}
+4
View File
@@ -104,6 +104,7 @@ export namespace lookup {
cont?: string;
email?: string;
qsl_via?: string;
image_url?: string;
source: string;
// Go type: time
fetched_at: any;
@@ -130,6 +131,7 @@ export namespace lookup {
this.cont = source["cont"];
this.email = source["email"];
this.qsl_via = source["qsl_via"];
this.image_url = source["image_url"];
this.source = source["source"];
this.fetched_at = this.convertValues(source["fetched_at"], null);
}
@@ -252,6 +254,7 @@ export namespace main {
hamqth_password: string;
primary: string;
failsafe: string;
download_images: boolean;
cache_ttl_days: number;
static createFrom(source: any = {}) {
@@ -266,6 +269,7 @@ export namespace main {
this.hamqth_password = source["hamqth_password"];
this.primary = source["primary"];
this.failsafe = source["failsafe"];
this.download_images = source["download_images"];
this.cache_ttl_days = source["cache_ttl_days"];
}
}