import { useState } from 'react'; import { useTranslation } from 'react-i18next'; import { useMutation, useQueryClient } from '@tanstack/react-query'; import { Pause, Play, Square } from 'lucide-react'; import { api, type PrinterStatus } from '../api/client'; import { useAuth } from '../contexts/AuthContext'; import { useToast } from '../contexts/ToastContext'; import { ConfirmModal } from './ConfirmModal'; import { ChamberLight } from './icons/ChamberLight'; interface CameraTileControlsProps { printerId: number; printerName: string; connected: boolean; printerState: string | null; chamberLight: boolean | null; } const BUTTON = 'flex h-7 w-7 items-center justify-center rounded-md bg-black/70 text-white backdrop-blur-sm transition-colors hover:bg-black/90 focus:outline-none focus:ring-2 focus:ring-bambu-green disabled:cursor-not-allowed disabled:opacity-50'; /** * Pause/resume, stop and the chamber light on a camera wall tile (#3479). * * The same actions as the printer card's, behind the same printers:control * permission and the same confirmations: a user without the permission gets * no controls at all. Rendered beside the tile, not inside it, because the * tile itself is a button (it opens the camera). */ export function CameraTileControls({ printerId, printerName, connected, printerState, chamberLight, }: CameraTileControlsProps) { const { t } = useTranslation(); const { hasPermission } = useAuth(); const { showToast } = useToast(); const queryClient = useQueryClient(); const [confirm, setConfirm] = useState<'pause' | 'resume' | 'stop' | null>(null); const refreshStatus = () => queryClient.invalidateQueries({ queryKey: ['printerStatus', printerId] }); const stop = useMutation({ mutationFn: () => api.stopPrint(printerId), onSuccess: () => { showToast(t('printers.toast.printStopped')); refreshStatus(); }, onError: (error: Error) => showToast(error.message || t('printers.toast.failedToStopPrint'), 'error'), }); const pause = useMutation({ mutationFn: () => api.pausePrint(printerId), onSuccess: () => { showToast(t('printers.toast.printPaused')); refreshStatus(); }, onError: (error: Error) => showToast(error.message || t('printers.toast.failedToPausePrint'), 'error'), }); const resume = useMutation({ mutationFn: () => api.resumePrint(printerId), onSuccess: () => { showToast(t('printers.toast.printResumed')); refreshStatus(); }, onError: (error: Error) => showToast(error.message || t('printers.toast.failedToResumePrint'), 'error'), }); // Optimistic, as on the card: the light's state comes back over the // printer's next status push, which can take a few seconds. const light = useMutation({ mutationFn: (on: boolean) => api.setChamberLight(printerId, on), onMutate: async (on) => { await queryClient.cancelQueries({ queryKey: ['printerStatus', printerId] }); const previous = queryClient.getQueryData(['printerStatus', printerId]); if (previous) { queryClient.setQueryData(['printerStatus', printerId], { ...previous, chamber_light: on }); } return { previous }; }, onError: (error: Error, _on, context) => { if (context?.previous) queryClient.setQueryData(['printerStatus', printerId], context.previous); showToast(error.message || t('printers.toast.failedToControlChamberLight'), 'error'); }, }); if (!hasPermission('printers:control') || !connected) return null; const isPaused = printerState === 'PAUSE'; const isPrinting = printerState === 'RUNNING' || isPaused; const busy = stop.isPending || pause.isPending || resume.isPending; const lightOn = chamberLight ?? false; return ( <>
{isPrinting && ( <> )}
{confirm === 'stop' && ( { stop.mutate(); setConfirm(null); }} onCancel={() => setConfirm(null)} /> )} {confirm === 'pause' && ( { pause.mutate(); setConfirm(null); }} onCancel={() => setConfirm(null)} /> )} {confirm === 'resume' && ( { resume.mutate(); setConfirm(null); }} onCancel={() => setConfirm(null)} /> )} ); }