mirror of
https://github.com/maziggy/bambuddy.git
synced 2026-10-18 21:47:39 +02:00
Replaced camera settings with wifi signal in top bar
This commit is contained in:
@@ -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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user