feat: Implemented Icom Ethernet CAT control

This commit is contained in:
2026-07-06 17:37:25 +02:00
parent c4ab935d5f
commit 701e8a2c25
7 changed files with 1122 additions and 196 deletions
+18 -2
View File
@@ -4,7 +4,7 @@ import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { cn } from '@/lib/utils';
import { useI18n } from '@/lib/i18n';
import { useI18n, FlagGB, FlagFR, type Lang } from '@/lib/i18n';
import { GetActiveProfile, SaveProfile, DownloadAllReferenceLists } from '../../wailsjs/go/main/App';
import type { profile as profileModels } from '../../wailsjs/go/models';
@@ -14,7 +14,7 @@ type Profile = Omit<profileModels.Profile, 'convertValues'>;
// (no callsign configured yet). It writes straight into the active profile, so
// OpsLog has a valid station before any QSO is logged. Not dismissable.
export function FirstRunModal({ onDone }: { onDone: () => void }) {
const { t } = useI18n();
const { t, lang, setLang } = useI18n();
const [p, setP] = useState<Profile | null>(null);
const [saving, setSaving] = useState(false);
const [err, setErr] = useState('');
@@ -68,6 +68,22 @@ export function FirstRunModal({ onDone }: { onDone: () => void }) {
return (
<div className="fixed inset-0 z-[200] flex items-center justify-center bg-black/40 backdrop-blur-sm">
<div className="w-full max-w-md rounded-xl border border-border bg-card shadow-2xl p-6 animate-in fade-in zoom-in-95">
{/* Language chooser — lives here (and not only in the localStorage-backed
first-launch flag gate) so a fresh setup always offers EN/FR, like the
station identity below. */}
<div className="flex justify-center mb-4">
<div className="inline-flex rounded-md border border-border overflow-hidden">
{([['en', FlagGB, 'English'], ['fr', FlagFR, 'Français']] as [Lang, typeof FlagGB, string][]).map(([code, Flag, label]) => (
<button key={code} type="button" onClick={() => setLang(code)}
className={cn('flex items-center gap-2 px-3 py-1.5 text-sm font-medium border-l border-border first:border-l-0 transition-colors',
lang === code ? 'bg-primary text-primary-foreground' : 'bg-card text-muted-foreground hover:bg-muted')}>
<Flag className="w-5 rounded-[2px] border border-border/30" />
{label}
</button>
))}
</div>
</div>
<div className="flex items-center gap-2 mb-1">
<Radio className="size-5 text-primary" />
<h2 className="text-lg font-semibold">{t('frm.welcome')}</h2>
+29 -2
View File
@@ -793,7 +793,8 @@ export function SettingsModal({ onClose, onSaved, initialSection, onMainPaneChan
const [modeDraft, setModeDraft] = useState('');
const [catCfg, setCatCfg] = useState<CATSettings>({
enabled: false, backend: 'omnirig', omnirig_rig: 1, flex_host: '', flex_port: 4992, flex_spots: false,
icom_port: '', icom_baud: 115200, icom_addr: 0x98, tci_host: '', tci_port: 40001, tci_spots: false, poll_ms: 250, delay_ms: 0,
icom_port: '', icom_baud: 115200, icom_addr: 0x98, icom_net_host: '', icom_net_user: '', icom_net_pass: '',
tci_host: '', tci_port: 40001, tci_spots: false, poll_ms: 250, delay_ms: 0,
digital_default: 'FT8',
});
const [rotator, setRotator] = useState<RotatorSettings>({
@@ -1967,6 +1968,7 @@ export function SettingsModal({ onClose, onSaved, initialSection, onMainPaneChan
<SelectItem value="omnirig">{t('cat.optOmnirig')}</SelectItem>
<SelectItem value="flex">{t('cat.optFlex')}</SelectItem>
<SelectItem value="icom">{t('cat.optIcom')}</SelectItem>
<SelectItem value="icom-net">{t('cat.optIcomNet')}</SelectItem>
<SelectItem value="tci">{t('cat.optTci')}</SelectItem>
</SelectContent>
</Select>
@@ -2037,6 +2039,31 @@ export function SettingsModal({ onClose, onSaved, initialSection, onMainPaneChan
</div>
</>
)}
{catCfg.backend === 'icom-net' && (
<>
<div className="space-y-1">
<Label>{t('cat.icomNetHost')}</Label>
<Input placeholder="192.168.1.60" value={catCfg.icom_net_host ?? ''}
onChange={(e) => setCatCfg((s) => ({ ...s, icom_net_host: e.target.value }))} />
</div>
<div className="space-y-1">
<Label>{t('cat.civAddr')}</Label>
<Input value={(catCfg.icom_addr ?? 0x98).toString(16).toUpperCase().padStart(2, '0')}
onChange={(e) => { const n = parseInt(e.target.value.replace(/[^0-9a-fA-F]/g, ''), 16); setCatCfg((s) => ({ ...s, icom_addr: (n >= 0 && n <= 0xFF) ? n : s.icom_addr })); }} />
</div>
<div className="space-y-1">
<Label>{t('cat.icomNetUser')}</Label>
<Input value={catCfg.icom_net_user ?? ''}
onChange={(e) => setCatCfg((s) => ({ ...s, icom_net_user: e.target.value }))} />
</div>
<div className="space-y-1">
<Label>{t('cat.icomNetPass')}</Label>
<Input type="password" value={catCfg.icom_net_pass ?? ''}
onChange={(e) => setCatCfg((s) => ({ ...s, icom_net_pass: e.target.value }))} />
</div>
<p className="col-span-2 text-xs text-muted-foreground">{t('cat.icomNetHint')}</p>
</>
)}
{catCfg.backend === 'tci' && (
<>
<div className="space-y-1">
@@ -2058,7 +2085,7 @@ export function SettingsModal({ onClose, onSaved, initialSection, onMainPaneChan
</label>
</>
)}
{(catCfg.backend === 'omnirig' || catCfg.backend === 'icom') && (
{(catCfg.backend === 'omnirig' || catCfg.backend === 'icom' || catCfg.backend === 'icom-net') && (
<>
<div className="space-y-1">
<Label>{t('cat.pollMs')}</Label>