fix(amp): couple a chosen GROUP, not every amplifier
The first version was a single switch meaning "command them all", and that is wrong the moment a station has three: two SPE on a combiner and a PowerGenius on another antenna would all go into OPERATE together, keying an amplifier that has nothing to do with the pair. It is now a set. Each amplifier is ticked into the group or not, the group is stored as a list of ids, and an amplifier outside it keeps its own buttons — which is the entire point of it being a set. A group of fewer than two members is stored as none: one amplifier coupled to itself would make every command fan out to a single member for ever. A remembered member that is no longer running is skipped rather than failing the command — deleting one amplifier must not break the button on the other. An amplifier saved without an id cannot join, and the panel says so instead of quietly omitting it from the list.
This commit is contained in:
@@ -52,7 +52,7 @@ import {
|
||||
GetADIFMonitor, SaveADIFMonitor, PickADIFMonitorFile,
|
||||
GetRelayAuto, SaveRelayAuto, GetStationDevices,
|
||||
GetAwardDefs, GetTrackedAwards, SaveTrackedAwards,
|
||||
GetBandOpenSettings, SaveBandOpenSettings, GetPSKReporterStatus, GetChaseNewGrids, SetChaseNewGrids, GetGridCacheStatus, GetAmpsLinked, SetAmpsLinked, GetSpotTTLMinutes, SetSpotTTLMinutes,
|
||||
GetBandOpenSettings, SaveBandOpenSettings, GetPSKReporterStatus, GetChaseNewGrids, SetChaseNewGrids, GetGridCacheStatus, GetLinkedAmps, SetLinkedAmps, GetSpotTTLMinutes, SetSpotTTLMinutes,
|
||||
} from '../../wailsjs/go/main/App';
|
||||
import type { profile as profileModels } from '../../wailsjs/go/models';
|
||||
import type { LookupSettingsForm, StationSettingsForm, ListsSettingsForm, ModePresetForm } from '@/types';
|
||||
@@ -1274,7 +1274,7 @@ export function SettingsModal({ onClose, onSaved, initialSection, onMainPaneChan
|
||||
// Amplifier list — operators can run SEVERAL amps (even two SPEs combined),
|
||||
// each with its own connection. Saved as a whole via SaveAmplifiers.
|
||||
const [amps, setAmps] = useState<AmpUI[]>([]);
|
||||
const [ampsLinked, setAmpsLinked] = useState(false);
|
||||
const [linkedAmps, setLinkedAmps] = useState<string[]>([]);
|
||||
|
||||
// WinKeyer CW keyer settings + macro editor.
|
||||
type WKMac = { label: string; text: string };
|
||||
@@ -1572,7 +1572,7 @@ export function SettingsModal({ onClose, onSaved, initialSection, onMainPaneChan
|
||||
(async () => {
|
||||
try { setBandOpen(await GetBandOpenSettings()); } catch { /* defaults stand */ }
|
||||
try { setChaseGrids(await GetChaseNewGrids()); } catch { /* defaults stand */ }
|
||||
try { setAmpsLinked(await GetAmpsLinked()); } catch { /* defaults stand */ }
|
||||
try { setLinkedAmps((await GetLinkedAmps()) ?? []); } catch { /* defaults stand */ }
|
||||
try { const n = await GetSpotTTLMinutes(); setSpotTTL(n); setSpotTTLText(String(n)); } catch { /* defaults stand */ }
|
||||
})();
|
||||
// Poll the feed while the panel is open: a live count is the only thing that
|
||||
@@ -3604,20 +3604,36 @@ export function SettingsModal({ onClose, onSaved, initialSection, onMainPaneChan
|
||||
<Plus className="size-3.5 mr-1" /> {t('amp.add')}
|
||||
</Button>
|
||||
|
||||
{/* Coupling belongs to the SET of amplifiers, not to any one of them,
|
||||
so it sits with the other set-level control rather than above the
|
||||
first card — where three tall cards push it out of view and it has
|
||||
to be hunted for. Shown from two amplifiers up: coupling one to
|
||||
itself is a switch that cannot do anything. */}
|
||||
{/* WHICH amplifiers are coupled, not whether coupling is on.
|
||||
A station can run a combiner pair AND a third amplifier that has
|
||||
nothing to do with it — two SPE on the combiner, a PowerGenius on
|
||||
another antenna — so a single switch would send that third one into
|
||||
OPERATE alongside them. Shown from two amplifiers up. */}
|
||||
{amps.length > 1 && (
|
||||
<label className="flex items-start gap-2 text-sm cursor-pointer border-t border-border/60 pt-3">
|
||||
<Checkbox checked={ampsLinked} className="mt-0.5"
|
||||
onCheckedChange={(c) => { setAmpsLinked(!!c); SetAmpsLinked(!!c).catch(() => {}); }} />
|
||||
<span>
|
||||
{t('amp.linked')}
|
||||
<span className="block text-xs text-muted-foreground mt-0.5">{t('amp.linkedHint')}</span>
|
||||
</span>
|
||||
</label>
|
||||
<div className="border-t border-border/60 pt-3 space-y-2">
|
||||
<div className="text-sm">{t('amp.linked')}</div>
|
||||
<p className="text-xs text-muted-foreground">{t('amp.linkedHint')}</p>
|
||||
<div className="flex flex-wrap gap-x-4 gap-y-1.5">
|
||||
{amps.filter((x) => (x.id ?? '') !== '').map((x) => {
|
||||
const on = linkedAmps.includes(x.id!);
|
||||
return (
|
||||
<label key={x.id} className="flex items-center gap-1.5 text-sm cursor-pointer">
|
||||
<Checkbox checked={on} onCheckedChange={(c) => {
|
||||
const next = c ? [...linkedAmps, x.id!] : linkedAmps.filter((v) => v !== x.id);
|
||||
setLinkedAmps(next);
|
||||
SetLinkedAmps(next).catch(() => {});
|
||||
}} />
|
||||
{x.name?.trim() || x.type}
|
||||
</label>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
{/* An amplifier saved without an id cannot be a member — say so
|
||||
rather than silently leaving it out of the list. */}
|
||||
{amps.some((x) => (x.id ?? '') === '') && (
|
||||
<p className="text-xs text-muted-foreground">{t('amp.linkedSaveFirst')}</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user