Replaced camera settings with wifi signal in top bar

This commit is contained in:
Martin Ziegler
2025-12-05 13:22:34 +01:00
parent f5e0395375
commit 065f22a0e3
11 changed files with 174 additions and 232 deletions
@@ -1,129 +0,0 @@
import { useEffect } from 'react';
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { api } from '../../api/client';
import type { PrinterStatus } from '../../api/client';
import { X, Loader2 } from 'lucide-react';
import { Card, CardContent } from '../Card';
interface CameraSettingsModalProps {
printerId: number;
status: PrinterStatus | null | undefined;
onClose: () => void;
}
export function CameraSettingsModal({ printerId, status, onClose }: CameraSettingsModalProps) {
const queryClient = useQueryClient();
const isConnected = status?.connected ?? false;
// Close on Escape key
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === 'Escape') onClose();
};
window.addEventListener('keydown', handleKeyDown);
return () => window.removeEventListener('keydown', handleKeyDown);
}, [onClose]);
const timelapseMutation = useMutation({
mutationFn: (enable: boolean) => api.setTimelapse(printerId, enable),
onSuccess: (data) => {
console.log('Timelapse mutation success:', data);
queryClient.invalidateQueries({ queryKey: ['printerStatuses'] });
},
onError: (error) => {
console.error('Timelapse mutation error:', error);
},
});
const liveviewMutation = useMutation({
mutationFn: (enable: boolean) => api.setLiveview(printerId, enable),
onSuccess: (data) => {
console.log('Liveview mutation success:', data);
queryClient.invalidateQueries({ queryKey: ['printerStatuses'] });
},
onError: (error) => {
console.error('Liveview mutation error:', error);
},
});
console.log('CameraSettingsModal render - isConnected:', isConnected, 'status:', status);
const handleTimelapseToggle = () => {
console.log('Timelapse toggle clicked, current:', status?.timelapse, 'setting to:', !status?.timelapse);
timelapseMutation.mutate(!status?.timelapse);
};
const handleLiveviewToggle = () => {
console.log('Liveview toggle clicked, current:', status?.ipcam, 'setting to:', !status?.ipcam);
liveviewMutation.mutate(!status?.ipcam);
};
return (
<div
className="fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4"
onClick={onClose}
>
<Card className="w-full max-w-sm" onClick={(e: React.MouseEvent) => e.stopPropagation()}>
<CardContent className="p-0">
{/* Header */}
<div className="flex items-center justify-between px-4 py-3 border-b border-bambu-dark-tertiary">
<span className="text-sm font-medium text-white">Camera Settings</span>
<button
onClick={onClose}
className="p-1 rounded hover:bg-bambu-dark-tertiary text-bambu-gray hover:text-white"
>
<X className="w-4 h-4" />
</button>
</div>
{/* Settings */}
<div className="p-4 space-y-4">
{/* Auto-record Monitoring (ipcam_record - records to SD during print) */}
<div className="flex items-center justify-between">
<span className="text-sm text-white">Auto-record Monitoring</span>
<button
onClick={handleLiveviewToggle}
disabled={!isConnected || liveviewMutation.isPending}
className={`relative w-12 h-6 rounded-full transition-colors disabled:opacity-50 disabled:cursor-not-allowed ${
status?.ipcam ? 'bg-bambu-green' : 'bg-bambu-dark-tertiary'
}`}
>
{liveviewMutation.isPending ? (
<Loader2 className="absolute inset-0 m-auto w-4 h-4 animate-spin text-white" />
) : (
<span
className={`absolute top-1 w-4 h-4 rounded-full bg-white transition-transform ${
status?.ipcam ? 'left-7' : 'left-1'
}`}
/>
)}
</button>
</div>
{/* Go Live (timelapse - live streaming) */}
<div className="flex items-center justify-between">
<span className="text-sm text-white">Go Live</span>
<button
onClick={handleTimelapseToggle}
disabled={!isConnected || timelapseMutation.isPending}
className={`relative w-12 h-6 rounded-full transition-colors disabled:opacity-50 disabled:cursor-not-allowed ${
status?.timelapse ? 'bg-bambu-green' : 'bg-bambu-dark-tertiary'
}`}
>
{timelapseMutation.isPending ? (
<Loader2 className="absolute inset-0 m-auto w-4 h-4 animate-spin text-white" />
) : (
<span
className={`absolute top-1 w-4 h-4 rounded-full bg-white transition-transform ${
status?.timelapse ? 'left-7' : 'left-1'
}`}
/>
)}
</button>
</div>
</div>
</CardContent>
</Card>
</div>
);
}
@@ -0,0 +1,64 @@
interface WifiSignalProps {
signal: number | null | undefined; // dBm value
className?: string;
}
/**
* WiFi signal icon with 4 bars that fill based on signal strength.
* - 4 bars: >= -50 dBm (excellent)
* - 3 bars: >= -60 dBm (good)
* - 2 bars: >= -70 dBm (fair)
* - 1 bar: < -70 dBm (weak)
* - 0 bars: no signal data
*/
export function WifiSignal({ signal, className = "w-4 h-4" }: WifiSignalProps) {
let bars = 0;
if (signal != null) {
if (signal >= -50) bars = 4;
else if (signal >= -60) bars = 3;
else if (signal >= -70) bars = 2;
else bars = 1;
}
const activeColor = "#00ae42"; // bambu-green
const inactiveColor = "#4a4a4a"; // dark gray
return (
<svg
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className={className}
>
{/* Dot at bottom */}
<circle
cx="12"
cy="20"
r="1"
fill={bars >= 1 ? activeColor : inactiveColor}
stroke={bars >= 1 ? activeColor : inactiveColor}
/>
{/* First arc (smallest) */}
<path
d="M8.5 16.5a5 5 0 0 1 7 0"
stroke={bars >= 2 ? activeColor : inactiveColor}
fill="none"
/>
{/* Second arc */}
<path
d="M5 13a10 10 0 0 1 14 0"
stroke={bars >= 3 ? activeColor : inactiveColor}
fill="none"
/>
{/* Third arc (largest) */}
<path
d="M1.5 9.5a15 15 0 0 1 21 0"
stroke={bars >= 4 ? activeColor : inactiveColor}
fill="none"
/>
</svg>
);
}