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
+2 -2
View File
@@ -10,7 +10,7 @@
"PowerGenius XL: the amplifier's real state is read at startup. Its status frame carries no 'operate' field — the state is in 'state' — so on the direct GSCP link the flag was never read at all and OpsLog opened claiming STANDBY on an amp that was in line, with the first press of the button then commanding the state it was already in. IDLE means in line, not keyed.",
"Antenna Genius: an option to write the SELECTED antenna into MY_ANTENNA, under the name it carries on the switch, ahead of the band default from Operating conditions. Which of the two ports counts is decided by the antenna jack the radio is transmitting on (ANT1/ANT2 on a Flex), with the jack-to-port wiring set once in Preferences — neither device can report it. When the port cannot be told, the log keeps the band default rather than naming an antenna at random.",
"The awards tab beside the entry form (F3) now offers only the awards this station follows, and drops the ones switched off — the same list the Awards tab reads. It offered every award that existed, so a station chasing three of them picked references out of a list of twenty.",
"CAT settings ask two questions instead of one: WHICH RADIO, then HOW IT IS CONNECTED — USB / serial, RS-232 over Ethernet (a serial bridge carrying the same CAT), or the manufacturer's own network protocol. The old list mixed the two ('Icom (USB)' and 'Icom (network)' were separate entries, while Kenwood and Elecraft hid the same choice in a field further down), and where an option does not exist for a radio it is now named and refused rather than silently absent. The example network address also named port 4532 Hamlib rigctld, a different protocol, and the one OpsLog itself serves under Share CAT."
"CAT settings ask two questions instead of one: WHICH RADIO, then HOW IT IS CONNECTED — and the second only appears where there is a choice to make. OmniRig leads the list, the brands follow alphabetically, and each offers only what it has: USB for a Yaesu or a Xiegu, USB or an RS-232-to-Ethernet bridge for a Kenwood or an Elecraft, USB or its own network protocol for an Icom, and nothing to choose for a FlexRadio or a SunSDR, which are reached one way each. The old list mixed the two questions — 'Icom (USB)' and 'Icom (network)' were separate entries while Kenwood and Elecraft hid the same choice in a field further down — and the example network address named port 4532, which is Hamlib rigctld and the one OpsLog itself serves under Share CAT."
],
"fr": [
"Console Elecraft : les mesures d'émission sont lues dès que la RADIO se déclare en émission, et plus seulement quand OpsLog l'a mise en émission. Passer en émission par le PTT de façade, une pédale ou le bouton du micro laissait le panneau croire à une réception — et comme les barres de puissance et de ROS ne sont lues qu'en émission, elles ne l'étaient jamais pour qui manipule à la main.",
@@ -20,7 +20,7 @@
"Power Genius XL : l'état réel de l'amplificateur est lu au démarrage. Sa trame d'état ne contient pas de champ « operate » — l'état est dans « state » — si bien que sur la liaison GSCP directe l'indicateur n'était jamais lu : OpsLog s'ouvrait en annonçant STANDBY sur un ampli en ligne, et le premier appui commandait l'état dans lequel il se trouvait déjà. IDLE veut dire en ligne, pas en émission.",
"Antenna Genius : une option pour inscrire l'antenne SÉLECTIONNÉE dans MY_ANTENNA, sous le nom qu'elle porte sur le switch, avant l'antenne par défaut des conditions de trafic. C'est la prise d'antenne sur laquelle la radio émet (ANT1/ANT2 sur un Flex) qui décide du port retenu, le câblage prise→port se règlant une fois dans les préférences — aucun des deux appareils ne peut le dire. Quand le port ne peut pas être déterminé, le journal conserve l'antenne par défaut plutôt que d'en nommer une au hasard.",
"L'onglet des diplômes à côté de la saisie (F3) ne propose plus que les diplômes suivis par la station, et écarte ceux qui sont désactivés — la même liste que l'onglet Diplômes. Il proposait tous les diplômes existants : une station qui en chasse trois choisissait ses références dans une liste de vingt.",
"Les réglages CAT posent deux questions au lieu d'une : QUELLE RADIO, puis COMMENT ELLE EST RELIÉE — USB / série, RS-232 sur Ethernet (un pont série qui transporte le même CAT), ou le protocole réseau propre au constructeur. L'ancienne liste mélangeait les deux (« Icom (USB) » et « Icom (réseau) » étaient deux entrées, tandis que Kenwood et Elecraft cachaient le même choix dans un champ plus bas), et là où une option n'existe pas pour une radio, elle est désormais nommée et refusée plutôt qu'absente en silence. L'exemple d'adresse réseau citait par ailleurs le port 4532 Hamlib rigctld, un autre protocole, et celui qu'OpsLog propose lui-même sous « Partager le CAT »."
"Les réglages CAT posent deux questions au lieu d'une : QUELLE RADIO, puis COMMENT ELLE EST RELIÉE — la seconde n'apparaissant que là où il y a un choix. OmniRig ouvre la liste, les marques suivent par ordre alphabétique, et chacune ne propose que ce qu'elle a : USB pour un Yaesu ou un Xiegu, USB ou un pont RS-232 vers Ethernet pour un Kenwood ou un Elecraft, USB ou son protocole réseau propre pour un Icom, et rien à choisir pour un FlexRadio ou un SunSDR, qui n'ont qu'une voie chacun. L'ancienne liste mélangeait les deux questions — « Icom (USB) » et « Icom (réseau) » étaient deux entrées tandis que Kenwood et Elecraft cachaient le même choix dans un champ plus bas — et l'exemple d'adresse réseau citait le port 4532, celui de Hamlib rigctld, que OpsLog propose lui-même sous « Partager le CAT »."
]
},
{
+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')}