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:
@@ -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')}
|
||||
|
||||
Reference in New Issue
Block a user