update
This commit is contained in:
@@ -10,6 +10,7 @@ import {
|
||||
GetListsSettings, SaveListsSettings,
|
||||
GetCATSettings, SaveCATSettings,
|
||||
ListProfiles, GetActiveProfile, SaveProfile, DeleteProfile, ActivateProfile, DuplicateProfile,
|
||||
GetRotatorSettings, SaveRotatorSettings, TestRotator, RotatorPark, RotatorStop,
|
||||
} from '../../wailsjs/go/main/App';
|
||||
import type { profile as profileModels } from '../../wailsjs/go/models';
|
||||
import type { LookupSettingsForm, StationSettingsForm, ListsSettingsForm, ModePresetForm } from '@/types';
|
||||
@@ -33,6 +34,7 @@ type StationSettings = StationSettingsForm;
|
||||
type ListsSettings = ListsSettingsForm;
|
||||
type ModePreset = ModePresetForm;
|
||||
type CATSettings = Omit<mainModels.CATSettings, 'convertValues'>;
|
||||
type RotatorSettings = Omit<mainModels.RotatorSettings, 'convertValues'>;
|
||||
type Profile = Omit<profileModels.Profile, 'convertValues'>;
|
||||
|
||||
const emptyProfile = (): Profile => ({
|
||||
@@ -100,7 +102,7 @@ const TREE: TreeNode[] = [
|
||||
{
|
||||
kind: 'group', label: 'Hardware Configuration', icon: Server, children: [
|
||||
{ kind: 'item', label: 'CAT interface (OmniRig)', id: 'cat' },
|
||||
{ kind: 'item', label: 'Rotator (PstRotator)', id: 'rotator', disabled: true },
|
||||
{ kind: 'item', label: 'Rotator (PstRotator)', id: 'rotator' },
|
||||
{ kind: 'item', label: 'Antenna (Ultrabeam)', id: 'antenna', disabled: true },
|
||||
{ kind: 'item', label: 'Audio devices', id: 'audio', disabled: true },
|
||||
],
|
||||
@@ -243,6 +245,11 @@ export function SettingsModal({ onClose, onSaved, initialSection }: Props) {
|
||||
enabled: false, backend: 'omnirig', omnirig_rig: 1, poll_ms: 250, delay_ms: 0,
|
||||
digital_default: 'FT8',
|
||||
});
|
||||
const [rotator, setRotator] = useState<RotatorSettings>({
|
||||
enabled: false, host: '127.0.0.1', port: 12000, has_elevation: false,
|
||||
});
|
||||
const [rotatorTesting, setRotatorTesting] = useState(false);
|
||||
const [rotatorTest, setRotatorTest] = useState<{ ok: boolean; msg: string } | null>(null);
|
||||
const [profiles, setProfiles] = useState<Profile[]>([]);
|
||||
// State for ProfilesPanel — lifted here because PANELS[selected]() calls
|
||||
// the panel as a plain function, not as a JSX element, so any useState
|
||||
@@ -278,8 +285,9 @@ export function SettingsModal({ onClose, onSaved, initialSection }: Props) {
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
try {
|
||||
const [l, ls, c, ap] = await Promise.all([
|
||||
const [l, ls, c, ap, r] = await Promise.all([
|
||||
GetLookupSettings(), GetListsSettings(), GetCATSettings(), GetActiveProfile(),
|
||||
GetRotatorSettings(),
|
||||
]);
|
||||
setLookup(l);
|
||||
setActiveProfile(ap as Profile);
|
||||
@@ -287,6 +295,7 @@ export function SettingsModal({ onClose, onSaved, initialSection }: Props) {
|
||||
await reloadProfiles();
|
||||
setBandsText((ls.bands ?? []).join('\n'));
|
||||
setCatCfg(c);
|
||||
setRotator(r);
|
||||
} catch (e: any) {
|
||||
setErr(String(e?.message ?? e));
|
||||
} finally {
|
||||
@@ -356,6 +365,7 @@ export function SettingsModal({ onClose, onSaved, initialSection }: Props) {
|
||||
}
|
||||
await SaveLookupSettings(lookup as any);
|
||||
await SaveCATSettings(catCfg as any);
|
||||
await SaveRotatorSettings(rotator as any);
|
||||
|
||||
setMsg('Settings saved.');
|
||||
onSaved();
|
||||
@@ -858,6 +868,85 @@ export function SettingsModal({ onClose, onSaved, initialSection }: Props) {
|
||||
);
|
||||
}
|
||||
|
||||
async function testRotator() {
|
||||
setRotatorTesting(true);
|
||||
setRotatorTest(null);
|
||||
try {
|
||||
await TestRotator(rotator as any);
|
||||
setRotatorTest({ ok: true, msg: 'Packet sent — antenna should swing to 0° (north). If it didn\'t, check PstRotator host/port and that PstRotator\'s UDP listener is enabled.' });
|
||||
} catch (e: any) {
|
||||
setRotatorTest({ ok: false, msg: String(e?.message ?? e) });
|
||||
} finally {
|
||||
setRotatorTesting(false);
|
||||
}
|
||||
}
|
||||
|
||||
function RotatorPanel() {
|
||||
return (
|
||||
<>
|
||||
<SectionHeader
|
||||
title="Rotator (PstRotator)"
|
||||
hint="HamLog sends UDP commands to PstRotator. Enable PstRotator's UDP listener (Setup → Communication → UDP) before testing."
|
||||
/>
|
||||
<div className="space-y-4 max-w-xl">
|
||||
<label className="flex items-center gap-2 text-sm cursor-pointer">
|
||||
<Checkbox checked={rotator.enabled} onCheckedChange={(c) => setRotator((s) => ({ ...s, enabled: !!c }))} />
|
||||
Enable PstRotator control
|
||||
</label>
|
||||
<div className="grid grid-cols-3 gap-3">
|
||||
<div className="space-y-1 col-span-2">
|
||||
<Label>Host</Label>
|
||||
<Input
|
||||
value={rotator.host ?? ''}
|
||||
onChange={(e) => setRotator((s) => ({ ...s, host: e.target.value }))}
|
||||
placeholder="127.0.0.1"
|
||||
className="font-mono"
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<Label>UDP port</Label>
|
||||
<Input
|
||||
type="number" min={1} max={65535}
|
||||
value={rotator.port}
|
||||
onChange={(e) => setRotator((s) => ({ ...s, port: parseInt(e.target.value) || 12000 }))}
|
||||
className="font-mono"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<label className="flex items-center gap-2 text-sm cursor-pointer">
|
||||
<Checkbox checked={rotator.has_elevation} onCheckedChange={(c) => setRotator((s) => ({ ...s, has_elevation: !!c }))} />
|
||||
This rotator supports elevation (VHF / satellite)
|
||||
</label>
|
||||
<div className="flex items-center gap-2 pt-2">
|
||||
<Button variant="outline" size="sm" onClick={testRotator} disabled={rotatorTesting}>
|
||||
{rotatorTesting ? 'Sending…' : 'Test (point to 0°)'}
|
||||
</Button>
|
||||
<Button variant="outline" size="sm" onClick={() => RotatorStop().catch((e) => setErr(String(e?.message ?? e)))} disabled={!rotator.enabled}>
|
||||
Stop
|
||||
</Button>
|
||||
<Button variant="outline" size="sm" onClick={() => RotatorPark().catch((e) => setErr(String(e?.message ?? e)))} disabled={!rotator.enabled}>
|
||||
Park
|
||||
</Button>
|
||||
</div>
|
||||
{rotatorTest && (
|
||||
<div className={cn(
|
||||
'text-xs rounded-md p-2.5 border',
|
||||
rotatorTest.ok
|
||||
? 'bg-emerald-50 text-emerald-800 border-emerald-200'
|
||||
: 'bg-destructive/10 text-destructive border-destructive/30',
|
||||
)}>
|
||||
{rotatorTest.msg}
|
||||
</div>
|
||||
)}
|
||||
<p className="text-xs text-muted-foreground">
|
||||
From the main entry strip, click the bearing pill to rotate to the short-path azimuth.
|
||||
Shift+click for long-path, Ctrl+click to stop.
|
||||
</p>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
// Map sections to their content + icon (for placeholder).
|
||||
const PANELS: Record<SectionId, () => JSX.Element> = {
|
||||
station: StationPanel,
|
||||
@@ -869,7 +958,7 @@ export function SettingsModal({ onClose, onSaved, initialSection }: Props) {
|
||||
backup: () => <ComingSoon id="backup" icon={Database} />,
|
||||
awards: () => <ComingSoon id="awards" icon={Award} />,
|
||||
cat: CATPanel,
|
||||
rotator: () => <ComingSoon id="rotator" icon={Compass} />,
|
||||
rotator: RotatorPanel,
|
||||
antenna: () => <ComingSoon id="antenna" icon={AntennaIcon} />,
|
||||
audio: () => <ComingSoon id="audio" icon={Server} />,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user