feat: added versionning & About window

This commit is contained in:
2026-06-16 19:36:56 +02:00
parent 33af122964
commit 69d0780bac
16 changed files with 1398 additions and 56 deletions
+83 -6
View File
@@ -8,7 +8,7 @@ import {
import {
GetLookupSettings, SaveLookupSettings, ClearLookupCache, TestLookupProvider,
GetListsSettings, SaveListsSettings,
GetCATSettings, SaveCATSettings,
GetCATSettings, SaveCATSettings, DiscoverFlexRadios,
ListProfiles, GetActiveProfile, SaveProfile, DeleteProfile, ActivateProfile, DuplicateProfile,
GetRotatorSettings, SaveRotatorSettings, TestRotator, RotatorPark, RotatorStop,
GetUltrabeamSettings, SaveUltrabeamSettings, TestUltrabeam,
@@ -445,6 +445,44 @@ function TelemetryToggle() {
);
}
// FlexDiscover scans the LAN for FlexRadio broadcasts and lets the user pick one
// (fills the IP/port). Self-contained so it can own its state (rendered inside
// the hook-less CATPanel).
function FlexDiscover({ onPick }: { onPick: (ip: string, port: number) => void }) {
const [busy, setBusy] = useState(false);
const [found, setFound] = useState<Array<{ ip: string; port: number; model?: string; nickname?: string }>>([]);
const [msg, setMsg] = useState('');
async function scan() {
setBusy(true); setMsg('');
try {
const r = ((await DiscoverFlexRadios()) ?? []) as any[];
setFound(r as any);
if (r.length === 0) setMsg('No radio found — check it\'s on the same network, or enter the IP manually.');
} catch (e: any) {
setMsg(String(e?.message ?? e));
} finally { setBusy(false); }
}
return (
<div className="rounded-md border border-border bg-muted/20 p-2 space-y-1.5">
<div className="flex items-center gap-2">
<Button size="sm" variant="outline" onClick={scan} disabled={busy}>
{busy ? <Loader2 className="size-3.5 animate-spin" /> : <Wifi className="size-3.5" />} Detect radios
</Button>
<span className="text-[11px] text-muted-foreground">listens for FlexRadio broadcast on the LAN</span>
</div>
{found.map((r) => (
<button key={r.ip} type="button" onClick={() => onPick(r.ip, r.port || 4992)}
className="w-full text-left text-xs rounded border border-border px-2 py-1 hover:bg-accent/50">
<span className="font-mono font-semibold">{r.ip}</span>
{r.model ? <span className="text-muted-foreground"> · {r.model}</span> : ''}
{r.nickname ? <span className="text-muted-foreground"> ({r.nickname})</span> : ''}
</button>
))}
{msg && <div className="text-[11px] text-muted-foreground">{msg}</div>}
</div>
);
}
function ComingSoon({ id, icon: Icon }: { id: SectionId; icon?: any }) {
const label = SECTION_LABELS[id] ?? id;
const IconCmp = Icon ?? Construction;
@@ -492,7 +530,7 @@ export function SettingsModal({ onClose, onSaved, initialSection }: Props) {
const [bandDraft, setBandDraft] = useState('');
const [modeDraft, setModeDraft] = useState('');
const [catCfg, setCatCfg] = useState<CATSettings>({
enabled: false, backend: 'omnirig', omnirig_rig: 1, poll_ms: 250, delay_ms: 0,
enabled: false, backend: 'omnirig', omnirig_rig: 1, flex_host: '', flex_port: 4992, flex_spots: false, poll_ms: 250, delay_ms: 0,
digital_default: 'FT8',
});
const [rotator, setRotator] = useState<RotatorSettings>({
@@ -1634,9 +1672,9 @@ export function SettingsModal({ onClose, onSaved, initialSection }: Props) {
<>
<SectionHeader
title="CAT interface"
hint="Reads the rig's current frequency / band / mode and pushes them into the entry strip in real time. Requires OmniRig (free) installed and configured separately for your rig — OpsLog just talks to it."
hint="Reads the rig's frequency / band / mode and pushes them into the entry strip in real time. Use OmniRig (free, any rig) or — for FlexRadio — the native SmartSDR API (no OmniRig needed, real-time, no second-click mode bug)."
/>
<div className="space-y-4 max-w-lg">
<div className="space-y-4 max-w-3xl">
<label className="flex items-center gap-2 text-sm cursor-pointer">
<Checkbox checked={catCfg.enabled} onCheckedChange={(c) => setCatCfg((s) => ({ ...s, enabled: !!c }))} />
Enable CAT
@@ -1648,11 +1686,12 @@ export function SettingsModal({ onClose, onSaved, initialSection }: Props) {
<Select value={catCfg.backend} onValueChange={(v) => setCatCfg((s) => ({ ...s, backend: v }))}>
<SelectTrigger><SelectValue /></SelectTrigger>
<SelectContent>
<SelectItem value="omnirig">OmniRig (Windows COM)</SelectItem>
<SelectItem value="flex" disabled>Flex SmartSDR (coming soon)</SelectItem>
<SelectItem value="omnirig">OmniRig (any rig, Windows COM)</SelectItem>
<SelectItem value="flex">FlexRadio / SmartSDR (native)</SelectItem>
</SelectContent>
</Select>
</div>
{catCfg.backend === 'omnirig' && (
<div className="space-y-1">
<Label>OmniRig rig slot</Label>
<Select value={String(catCfg.omnirig_rig)} onValueChange={(v) => setCatCfg((s) => ({ ...s, omnirig_rig: parseInt(v) as 1 | 2 }))}>
@@ -1663,6 +1702,30 @@ export function SettingsModal({ onClose, onSaved, initialSection }: Props) {
</SelectContent>
</Select>
</div>
)}
{catCfg.backend === 'flex' && (
<>
<div className="space-y-1">
<Label>FlexRadio IP</Label>
<Input placeholder="192.168.1.50" value={catCfg.flex_host ?? ''}
onChange={(e) => setCatCfg((s) => ({ ...s, flex_host: e.target.value }))} />
</div>
<div className="space-y-1">
<Label>Port</Label>
<Input type="number" value={catCfg.flex_port || 4992}
onChange={(e) => setCatCfg((s) => ({ ...s, flex_port: parseInt(e.target.value) || 4992 }))} />
</div>
<div className="col-span-2">
<FlexDiscover onPick={(ip, port) => setCatCfg((s) => ({ ...s, flex_host: ip, flex_port: port }))} />
</div>
<label className="col-span-2 flex items-center gap-2 text-sm cursor-pointer">
<Checkbox checked={!!catCfg.flex_spots} onCheckedChange={(c) => setCatCfg((s) => ({ ...s, flex_spots: !!c }))} />
Show cluster spots on the panadapter <span className="text-xs text-muted-foreground">(spots from OpsLog's DX cluster appear on the radio, auto-expire after 30 min)</span>
</label>
</>
)}
{catCfg.backend === 'omnirig' && (
<>
<div className="space-y-1">
<Label>Poll interval (ms)</Label>
<Input
@@ -1679,6 +1742,8 @@ export function SettingsModal({ onClose, onSaved, initialSection }: Props) {
onChange={(e) => setCatCfg((s) => ({ ...s, delay_ms: parseInt(e.target.value) || 0 }))}
/>
</div>
</>
)}
<div className="space-y-1 col-span-2">
<Label>Default digital mode (when rig reports DIG)</Label>
<Select
@@ -1694,6 +1759,8 @@ export function SettingsModal({ onClose, onSaved, initialSection }: Props) {
</Select>
</div>
</div>
{catCfg.backend === 'omnirig' && (
<>
<label className="flex items-center gap-2 text-sm cursor-pointer">
<Checkbox
checked={catModeBeforeFreq}
@@ -1708,6 +1775,16 @@ export function SettingsModal({ onClose, onSaved, initialSection }: Props) {
OmniRig only reports generic "DIG" for digital modes <strong>Default digital mode</strong>
{' '}is the specific mode OpsLog will surface (and log).
</p>
</>
)}
{catCfg.backend === 'flex' && (
<p className="text-xs text-muted-foreground">
Native SmartSDR API no OmniRig needed. Frequency, mode and split are read in
real time from the radio (no polling, no second-click mode bug). Use <strong>Detect
radios</strong> or enter the IP. <strong>Default digital mode</strong> is what OpsLog
logs when the slice is in a digital mode (DIGU/DIGL).
</p>
)}
</div>
</>
);