mirror of
https://github.com/maziggy/bambuddy.git
synced 2026-10-19 07:33:18 +02:00
Replaced camera settings with wifi signal in top bar
This commit is contained in:
@@ -117,6 +117,7 @@ export interface PrinterStatus {
|
||||
store_to_sdcard: boolean; // Store sent files on SD card
|
||||
timelapse: boolean; // Timelapse recording active
|
||||
ipcam: boolean; // Live view enabled
|
||||
wifi_signal: number | null; // WiFi signal strength in dBm
|
||||
nozzles: NozzleInfo[]; // Nozzle hardware info (index 0=left/primary, 1=right)
|
||||
print_options: PrintOptions | null; // AI detection and print options
|
||||
// Calibration stage tracking
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -10,16 +10,15 @@ import { JogPad } from '../components/control/JogPad';
|
||||
import { BedControls } from '../components/control/BedControls';
|
||||
import { ExtruderControls } from '../components/control/ExtruderControls';
|
||||
import { AMSSectionDual } from '../components/control/AMSSectionDual';
|
||||
import { CameraSettingsModal } from '../components/control/CameraSettingsModal';
|
||||
import { PrinterPartsModal } from '../components/control/PrinterPartsModal';
|
||||
import { PrintOptionsModal } from '../components/control/PrintOptionsModal';
|
||||
import { CalibrationModal } from '../components/control/CalibrationModal';
|
||||
import { Loader2, WifiOff, Video, Webcam, Settings } from 'lucide-react';
|
||||
import { Loader2, WifiOff, Video, Webcam } from 'lucide-react';
|
||||
import { WifiSignal } from '../components/icons/WifiSignal';
|
||||
|
||||
export function ControlPage() {
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
const [selectedPrinterId, setSelectedPrinterId] = useState<number | null>(null);
|
||||
const [showCameraSettings, setShowCameraSettings] = useState(false);
|
||||
const [showPrinterParts, setShowPrinterParts] = useState(false);
|
||||
const [showPrintOptions, setShowPrintOptions] = useState(false);
|
||||
const [showCalibration, setShowCalibration] = useState(false);
|
||||
@@ -148,12 +147,12 @@ export function ControlPage() {
|
||||
<button className={`p-1.5 rounded hover:bg-bambu-dark-tertiary ${selectedStatus?.ipcam ? 'text-bambu-green' : 'text-bambu-gray hover:text-white'}`}>
|
||||
<Webcam className="w-4 h-4" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setShowCameraSettings(true)}
|
||||
className="p-1.5 rounded hover:bg-bambu-dark-tertiary text-bambu-gray hover:text-white"
|
||||
<div
|
||||
className="p-1.5 rounded"
|
||||
title={selectedStatus?.wifi_signal != null ? `WiFi: ${selectedStatus.wifi_signal} dBm` : 'WiFi signal unknown'}
|
||||
>
|
||||
<Settings className="w-4 h-4" />
|
||||
</button>
|
||||
<WifiSignal signal={selectedStatus?.wifi_signal} className="w-4 h-4" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Camera Feed - Embedded directly */}
|
||||
@@ -264,15 +263,6 @@ export function ControlPage() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Camera Settings Modal */}
|
||||
{showCameraSettings && selectedPrinter && (
|
||||
<CameraSettingsModal
|
||||
printerId={selectedPrinter.id}
|
||||
status={selectedStatus}
|
||||
onClose={() => setShowCameraSettings(false)}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Printer Parts Modal */}
|
||||
{showPrinterParts && selectedPrinter && (
|
||||
<PrinterPartsModal
|
||||
|
||||
Reference in New Issue
Block a user