import { useEffect, useRef, useState } from 'react'; import { Radio, AudioLines, RefreshCw } from 'lucide-react'; import { GetIcomState, IcomRefresh, IcomSetAFGain, IcomSetRFGain, IcomSetNB, IcomSetNBLevel, IcomSetNR, IcomSetNRLevel, IcomSetANF, IcomSetAGC, IcomSetPreamp, IcomSetAtt, IcomSetFilter, } from '../../wailsjs/go/main/App'; import { cn } from '@/lib/utils'; type IcomState = { available: boolean; model?: string; mode?: string; af_gain: number; rf_gain: number; nb: boolean; nb_level: number; nr: boolean; nr_level: number; anf: boolean; agc?: string; preamp: number; att: number; filter: number; }; const ZERO: IcomState = { available: false, af_gain: 0, rf_gain: 0, nb: false, nb_level: 0, nr: false, nr_level: 0, anf: false, preamp: 0, att: 0, filter: 1, }; function Slider({ value, onChange, disabled, accent = '#2563eb' }: { value: number; onChange: (v: number) => void; disabled?: boolean; accent?: string; }) { const v = Math.max(0, Math.min(100, value)); return ( onChange(parseInt(e.target.value, 10))} className={cn('flex-1 h-1.5 rounded-full appearance-none cursor-pointer disabled:opacity-30 disabled:cursor-default', '[&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:size-3.5 [&::-webkit-slider-thumb]:rounded-full', '[&::-webkit-slider-thumb]:bg-white [&::-webkit-slider-thumb]:border-2 [&::-webkit-slider-thumb]:shadow-sm')} style={{ background: `linear-gradient(to right, ${accent} ${v}%, #d8cfb8 ${v}%)`, borderColor: accent }} /> ); } function Segmented({ value, options, onChange }: { value: string; options: { v: string; l: string }[]; onChange: (v: string) => void; }) { return (
{options.map((o) => ( ))}
); } function Chip({ on, onClick, label }: { on: boolean; onClick: () => void; label: string }) { return ( ); } function LevelRow({ label, on, onToggle, value, onLevel }: { label: string; on: boolean; onToggle: () => void; value: number; onLevel: (v: number) => void; }) { return (
{value}
); } function Card({ icon: Icon, title, accent, children }: { icon: any; title: string; accent?: string; children: React.ReactNode }) { return (
{title}
{children}
); } function Row({ label, children }: { label: string; children: React.ReactNode }) { return (
{label} {children}
); } // IcomPanel — receive-DSP control surface for an Icom on the CI-V backend. // Unlike the Flex (which pushes state), the Icom is polled: the cache reflects // the last refresh plus optimistic updates. Front-panel knob changes show after // the next ↻ Refresh. export function IcomPanel() { const [st, setSt] = useState(ZERO); const [busy, setBusy] = useState(false); const load = () => GetIcomState().then((s) => setSt((s ?? ZERO) as IcomState)).catch(() => {}); const refresh = async () => { setBusy(true); try { await IcomRefresh(); } catch {} await load(); setBusy(false); }; useEffect(() => { refresh(); const id = window.setInterval(load, 1500); // cheap cache poll (mode + optimistic state) return () => window.clearInterval(id); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); // Optimistic local update + fire the command; the cache poll reconciles. const set = (patch: Partial, fn: () => Promise) => { setSt((s) => ({ ...s, ...patch })); fn().catch(() => {}); }; if (!st.available) { return (
Icom not connected. Enable the Icom CI-V backend in Settings → CAT and connect the radio's USB port.
); } return (
{st.model || 'Icom'}{st.mode ? {st.mode} : null}
set({ af_gain: v }, () => IcomSetAFGain(v))} /> {st.af_gain} set({ rf_gain: v }, () => IcomSetRFGain(v))} /> {st.rf_gain} set({ agc: v }, () => IcomSetAGC(v))} /> set({ preamp: parseInt(v) }, () => IcomSetPreamp(parseInt(v)))} /> set({ att: parseInt(v) }, () => IcomSetAtt(parseInt(v)))} /> set({ filter: parseInt(v) }, () => IcomSetFilter(parseInt(v)))} /> set({ nb: !st.nb }, () => IcomSetNB(!st.nb))} onLevel={(v) => set({ nb_level: v }, () => IcomSetNBLevel(v))} /> set({ nr: !st.nr }, () => IcomSetNR(!st.nr))} onLevel={(v) => set({ nr_level: v }, () => IcomSetNRLevel(v))} />
set({ anf: !st.anf }, () => IcomSetANF(!st.anf))} /> Auto notch filter
); }