Add ability to pause radio connection (closes #51)

This commit is contained in:
Jack Kingsman
2026-03-11 17:17:03 -07:00
parent e993009782
commit 4e0b6a49b0
18 changed files with 371 additions and 34 deletions
@@ -31,6 +31,8 @@ interface SettingsModalBaseProps {
onSaveAppSettings: (update: AppSettingsUpdate) => Promise<void>;
onSetPrivateKey: (key: string) => Promise<void>;
onReboot: () => Promise<void>;
onDisconnect: () => Promise<void>;
onReconnect: () => Promise<void>;
onAdvertise: () => Promise<void>;
onHealthRefresh: () => Promise<void>;
onRefreshAppSettings: () => Promise<void>;
@@ -59,6 +61,8 @@ export function SettingsModal(props: SettingsModalProps) {
onSaveAppSettings,
onSetPrivateKey,
onReboot,
onDisconnect,
onReconnect,
onAdvertise,
onHealthRefresh,
onRefreshAppSettings,
@@ -182,6 +186,8 @@ export function SettingsModal(props: SettingsModalProps) {
onSaveAppSettings={onSaveAppSettings}
onSetPrivateKey={onSetPrivateKey}
onReboot={onReboot}
onDisconnect={onDisconnect}
onReconnect={onReconnect}
onAdvertise={onAdvertise}
onClose={onClose}
className={sectionContentClass}
+20 -9
View File
@@ -22,13 +22,24 @@ export function StatusBar({
onSettingsClick,
onMenuClick,
}: StatusBarProps) {
const radioState =
health?.radio_state ??
(health?.radio_initializing
? 'initializing'
: health?.radio_connected
? 'connected'
: 'disconnected');
const connected = health?.radio_connected ?? false;
const initializing = health?.radio_initializing ?? false;
const statusLabel = initializing
? 'Radio Initializing'
: connected
? 'Radio OK'
: 'Radio Disconnected';
const statusLabel =
radioState === 'paused'
? 'Radio Paused'
: radioState === 'connecting'
? 'Radio Connecting'
: radioState === 'initializing'
? 'Radio Initializing'
: connected
? 'Radio OK'
: 'Radio Disconnected';
const [reconnecting, setReconnecting] = useState(false);
const [currentTheme, setCurrentTheme] = useState(getSavedTheme);
@@ -97,7 +108,7 @@ export function StatusBar({
<div
className={cn(
'w-2 h-2 rounded-full transition-colors',
initializing
radioState === 'initializing' || radioState === 'connecting'
? 'bg-warning'
: connected
? 'bg-status-connected shadow-[0_0_6px_hsl(var(--status-connected)/0.5)]'
@@ -128,13 +139,13 @@ export function StatusBar({
</div>
)}
{!connected && !initializing && (
{(radioState === 'disconnected' || radioState === 'paused') && (
<button
onClick={handleReconnect}
disabled={reconnecting}
className="px-3 py-1 bg-warning/10 border border-warning/20 text-warning rounded-md text-xs cursor-pointer hover:bg-warning/15 transition-colors disabled:opacity-50 disabled:cursor-not-allowed focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
>
{reconnecting ? 'Reconnecting...' : 'Reconnect'}
{reconnecting ? 'Reconnecting...' : radioState === 'paused' ? 'Connect' : 'Reconnect'}
</button>
)}
<button
@@ -24,6 +24,8 @@ export function SettingsRadioSection({
onSaveAppSettings,
onSetPrivateKey,
onReboot,
onDisconnect,
onReconnect,
onAdvertise,
onClose,
className,
@@ -36,6 +38,8 @@ export function SettingsRadioSection({
onSaveAppSettings: (update: AppSettingsUpdate) => Promise<void>;
onSetPrivateKey: (key: string) => Promise<void>;
onReboot: () => Promise<void>;
onDisconnect: () => Promise<void>;
onReconnect: () => Promise<void>;
onAdvertise: () => Promise<void>;
onClose: () => void;
className?: string;
@@ -70,6 +74,7 @@ export function SettingsRadioSection({
// Advertise state
const [advertising, setAdvertising] = useState(false);
const [connectionBusy, setConnectionBusy] = useState(false);
useEffect(() => {
setName(config.name);
@@ -285,24 +290,82 @@ export function SettingsRadioSection({
}
};
const radioState =
health?.radio_state ?? (health?.radio_initializing ? 'initializing' : 'disconnected');
const connectionActionLabel =
radioState === 'paused'
? 'Reconnect'
: radioState === 'connected' || radioState === 'initializing'
? 'Disconnect'
: 'Stop Trying';
const connectionStatusLabel =
radioState === 'connected'
? health?.connection_info || 'Connected'
: radioState === 'initializing'
? `Initializing ${health?.connection_info || 'radio'}`
: radioState === 'connecting'
? `Attempting to connect${health?.connection_info ? ` to ${health.connection_info}` : ''}`
: radioState === 'paused'
? `Connection paused${health?.connection_info ? ` (${health.connection_info})` : ''}`
: 'Not connected';
const handleConnectionAction = async () => {
setConnectionBusy(true);
try {
if (radioState === 'paused') {
await onReconnect();
toast.success('Reconnect requested');
} else {
await onDisconnect();
toast.success('Radio connection paused');
}
} catch (err) {
toast.error('Failed to change radio connection state', {
description: err instanceof Error ? err.message : 'Check radio connection and try again',
});
} finally {
setConnectionBusy(false);
}
};
return (
<div className={className}>
{/* Connection display */}
<div className="space-y-2">
<div className="space-y-3">
<Label>Connection</Label>
{health?.connection_info ? (
<div className="flex items-center gap-2">
<div className="w-2 h-2 rounded-full bg-status-connected" />
<code className="px-2 py-1 bg-muted rounded text-foreground text-sm">
{health.connection_info}
</code>
</div>
) : (
<div className="flex items-center gap-2 text-muted-foreground">
<div className="w-2 h-2 rounded-full bg-status-disconnected" />
<span>Not connected</span>
</div>
)}
<div className="flex items-center gap-2">
<div
className={`w-2 h-2 rounded-full ${
radioState === 'connected'
? 'bg-status-connected'
: radioState === 'initializing' || radioState === 'connecting'
? 'bg-warning'
: 'bg-status-disconnected'
}`}
/>
<span
className={
radioState === 'paused' || radioState === 'disconnected'
? 'text-muted-foreground'
: ''
}
>
{connectionStatusLabel}
</span>
</div>
<Button
type="button"
variant="outline"
onClick={handleConnectionAction}
disabled={connectionBusy}
className="w-full"
>
{connectionBusy ? `${connectionActionLabel}...` : connectionActionLabel}
</Button>
<p className="text-xs text-muted-foreground">
Disconnect pauses automatic reconnect attempts so another device can use the radio.
</p>
</div>
{/* Radio Name */}