style(cat): connection as a dropdown, and only where there is a choice

Three wide buttons for something most radios cannot vary was noise, and
they pushed the fields below into whichever grid column came next — the
panel looked shuffled.

Now: a dropdown, shown only when the brand has more than one way in. A
Yaesu is reached over USB and that is the end of it; a control offering
one entry that cannot be changed is a decision that is not one.

Brands: OmniRig first — it is the one that works with any radio, so it is
where someone who cannot find their rig should land — then alphabetical.
Kenwood and Elecraft drop the proprietary-network entry: neither is
implemented and neither is planned, and naming a road that goes nowhere
is only useful when someone might reasonably look for it.

The standalone checkboxes (protocol log, DTR/RTS) take a full row instead
of half of one, which is what was breaking the alignment.
This commit is contained in:
2026-08-25 17:57:53 +02:00
parent 712d83a012
commit 12b0a861e0
2 changed files with 33 additions and 32 deletions
+31 -30
View File
@@ -1487,15 +1487,18 @@ const ICOM_MODELS: { name: string; addr: number }[] = [
// The backend NAME stored in settings is unchanged — 'icom-net' is still
// 'icom-net' — because a settings file written by an older build has to keep
// working. This table is only how the two questions map onto it.
// OmniRig leads — it is the one that works with any radio, so it is where
// someone who does not find their rig below should land. The rest are
// alphabetical, because a list of brands has no other defensible order.
const CAT_BRANDS: { id: string; label: string; links: string[]; backend: (link: string) => string }[] = [
{ id: 'elecraft', label: 'Elecraft K3 / K4', links: ['usb', 'bridge', 'native'], backend: () => 'elecraft' },
{ id: 'kenwood', label: 'Kenwood', links: ['usb', 'bridge', 'native'], backend: () => 'kenwood' },
{ id: 'yaesu', label: 'Yaesu', links: ['usb'], backend: () => 'yaesu' },
{ id: 'icom', label: 'Icom', links: ['usb', 'native'], backend: (l) => (l === 'native' ? 'icom-net' : 'icom') },
{ id: 'flex', label: 'FlexRadio (SmartSDR)', links: ['native'], backend: () => 'flex' },
{ id: 'tci', label: 'Expert Electronics / SunSDR (TCI)', links: ['native'], backend: () => 'tci' },
{ id: 'xiegu', label: 'Xiegu', links: ['usb'], backend: () => 'xiegu' },
{ id: 'omnirig', label: 'OmniRig (any rig)', links: ['usb'], backend: () => 'omnirig' },
{ id: 'elecraft', label: 'Elecraft K3 / K4', links: ['usb', 'bridge'], backend: () => 'elecraft' },
{ id: 'tci', label: 'Expert Electronics / SunSDR (TCI)', links: ['native'], backend: () => 'tci' },
{ id: 'flex', label: 'FlexRadio (SmartSDR)', links: ['native'], backend: () => 'flex' },
{ id: 'icom', label: 'Icom', links: ['usb', 'native'], backend: (l) => (l === 'native' ? 'icom-net' : 'icom') },
{ id: 'kenwood', label: 'Kenwood', links: ['usb', 'bridge'], backend: () => 'kenwood' },
{ id: 'xiegu', label: 'Xiegu', links: ['usb'], backend: () => 'xiegu' },
{ id: 'yaesu', label: 'Yaesu', links: ['usb'], backend: () => 'yaesu' },
];
// brandOfBackend reads the stored backend back into the two questions.
@@ -3091,26 +3094,24 @@ export function SettingsModal({ onClose, onSaved, initialSection, onMainPaneChan
</SelectContent>
</Select>
</div>
<div className="space-y-1">
<Label>{t('cat.kwLink')}</Label>
{/* One button per connection the brand actually has. A brand with
a single way in still shows it, greyed: "there is no choice
here" is an answer, and an empty space is not. */}
<div className="inline-flex rounded-md border border-border overflow-hidden text-xs w-full">
{(CAT_BRANDS.find((b) => b.id === catBrand)?.links ?? ['usb']).map((l) => (
<button key={l} type="button"
disabled={(CAT_BRANDS.find((b) => b.id === catBrand)?.links ?? []).length < 2}
onClick={() => applyCatLink(l)}
className={cn('flex-1 px-2 py-1.5 font-medium border-l border-border first:border-l-0 disabled:opacity-70',
catLink === l ? 'bg-primary text-primary-foreground' : 'text-muted-foreground hover:bg-muted')}>
{l === 'usb' ? t('cat.kwLinkUsb') : l === 'bridge' ? t('cat.kwLinkBridge') : t('cat.kwLinkNative')}
</button>
))}
{/* Only where there IS a choice. A dropdown showing one entry that
cannot be changed is a control pretending to be a decision a
Yaesu is reached over USB and that is the end of it. */}
{(CAT_BRANDS.find((b) => b.id === catBrand)?.links ?? []).length > 1 ? (
<div className="space-y-1">
<Label>{t('cat.kwLink')}</Label>
<Select value={catLink} onValueChange={applyCatLink}>
<SelectTrigger><SelectValue /></SelectTrigger>
<SelectContent>
{(CAT_BRANDS.find((b) => b.id === catBrand)?.links ?? []).map((l) => (
<SelectItem key={l} value={l}>
{l === 'usb' ? t('cat.kwLinkUsb') : l === 'bridge' ? t('cat.kwLinkBridge') : t('cat.kwLinkNative')}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
{catLink === 'native' && catBrand !== 'flex' && catBrand !== 'tci' && catBrand !== 'icom' && (
<p className="text-xs text-warning">{t('cat.kwLinkNativeHint')}</p>
)}
</div>
) : <div />}
{catCfg.backend === 'omnirig' && (
<div className="space-y-1">
<Label>{t('cat.omnirigRig')}</Label>
@@ -3237,7 +3238,7 @@ export function SettingsModal({ onClose, onSaved, initialSection, onMainPaneChan
</Select>
<span className="text-xs text-muted-foreground">{t('cat.yaesuBaudHint')}</span>
</div>
<div className="space-y-1">
<div className="col-span-2 space-y-1">
<label className="flex items-center gap-2 text-xs cursor-pointer">
<Checkbox checked={!!catCfg.yaesu_low_lines} onCheckedChange={(c) => setCatCfg((s) => ({ ...s, yaesu_low_lines: !!c }))} />
{t('cat.lowerLines')}
@@ -3246,8 +3247,8 @@ export function SettingsModal({ onClose, onSaved, initialSection, onMainPaneChan
</div>
</>
)}
{['icom', 'xiegu', 'kenwood'].includes(catCfg.backend) && (
<div className="border-t border-border/60 pt-3">
{['icom', 'xiegu', 'kenwood', 'elecraft'].includes(catCfg.backend) && (
<div className="col-span-2 border-t border-border/60 pt-3">
<label className="flex items-center gap-2 text-sm cursor-pointer">
<Checkbox checked={civTrace} onCheckedChange={(c) => { setCivTrace(!!c); SetCIVTrace(!!c); }} />
{t('cat.civTrace')}
@@ -3293,7 +3294,7 @@ export function SettingsModal({ onClose, onSaved, initialSection, onMainPaneChan
<p className="text-xs text-muted-foreground">{t('cat.kenwoodHostHint')}</p>
</div>
)}
<div className="space-y-1">
<div className="col-span-2 space-y-1">
<label className="flex items-center gap-2 text-xs cursor-pointer">
<Checkbox checked={!!catCfg.kenwood_low_lines} onCheckedChange={(c) => setCatCfg((s) => ({ ...s, kenwood_low_lines: !!c }))} />
{t('cat.lowerLines')}