mirror of
https://github.com/maziggy/bambuddy.git
synced 2026-10-21 10:15:04 +02:00
Wiring up calibration modal
This commit is contained in:
@@ -111,6 +111,10 @@ export interface PrinterStatus {
|
||||
ipcam: boolean; // Live view enabled
|
||||
nozzles: NozzleInfo[]; // Nozzle hardware info (index 0=left/primary, 1=right)
|
||||
print_options: PrintOptions | null; // AI detection and print options
|
||||
// Calibration stage tracking
|
||||
stg_cur: number; // Current stage number (-1 = not calibrating)
|
||||
stg_cur_name: string | null; // Human-readable current stage name
|
||||
stg: number[]; // List of stage numbers in calibration sequence
|
||||
}
|
||||
|
||||
export interface PrinterCreate {
|
||||
@@ -1235,4 +1239,33 @@ export const api = {
|
||||
method: 'POST',
|
||||
});
|
||||
},
|
||||
|
||||
// Calibration
|
||||
startCalibration: (
|
||||
printerId: number,
|
||||
options: {
|
||||
bed_leveling?: boolean;
|
||||
vibration?: boolean;
|
||||
motor_noise?: boolean;
|
||||
nozzle_offset?: boolean;
|
||||
high_temp_heatbed?: boolean;
|
||||
}
|
||||
) => {
|
||||
const params = new URLSearchParams();
|
||||
if (options.bed_leveling) params.append('bed_leveling', 'true');
|
||||
if (options.vibration) params.append('vibration', 'true');
|
||||
if (options.motor_noise) params.append('motor_noise', 'true');
|
||||
if (options.nozzle_offset) params.append('nozzle_offset', 'true');
|
||||
if (options.high_temp_heatbed) params.append('high_temp_heatbed', 'true');
|
||||
return request<{
|
||||
success: boolean;
|
||||
bed_leveling: boolean;
|
||||
vibration: boolean;
|
||||
motor_noise: boolean;
|
||||
nozzle_offset: boolean;
|
||||
high_temp_heatbed: boolean;
|
||||
}>(`/printers/${printerId}/calibration?${params}`, {
|
||||
method: 'POST',
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
@@ -7,10 +7,11 @@ import { ConfirmModal } from '../ConfirmModal';
|
||||
interface BedControlsProps {
|
||||
printerId: number;
|
||||
status: PrinterStatus | null | undefined;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
export function BedControls({ printerId, status }: BedControlsProps) {
|
||||
const isConnected = status?.connected ?? false;
|
||||
export function BedControls({ printerId, status, disabled = false }: BedControlsProps) {
|
||||
const isConnected = (status?.connected ?? false) && !disabled;
|
||||
|
||||
const [confirmModal, setConfirmModal] = useState<{
|
||||
token: string;
|
||||
|
||||
@@ -0,0 +1,424 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useMutation } from '@tanstack/react-query';
|
||||
import { api } from '../../api/client';
|
||||
import type { Printer, PrinterStatus } from '../../api/client';
|
||||
import { X, Loader2, AlertTriangle, Check } from 'lucide-react';
|
||||
import { Card, CardContent } from '../Card';
|
||||
|
||||
interface CalibrationModalProps {
|
||||
printer: Printer;
|
||||
status: PrinterStatus | null | undefined;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
// Calibration stages that indicate active calibration
|
||||
const CALIBRATION_STAGES = new Set([1, 3, 13, 25, 39, 40, 47, 48, 50]);
|
||||
|
||||
// Checkbox component matching Bambu Studio style
|
||||
function Checkbox({
|
||||
checked,
|
||||
onChange,
|
||||
disabled,
|
||||
}: {
|
||||
checked: boolean;
|
||||
onChange: (checked: boolean) => void;
|
||||
disabled?: boolean;
|
||||
}) {
|
||||
return (
|
||||
<button
|
||||
onClick={() => !disabled && onChange(!checked)}
|
||||
disabled={disabled}
|
||||
className={`w-5 h-5 rounded border-2 flex items-center justify-center transition-colors ${
|
||||
checked
|
||||
? 'bg-bambu-green border-bambu-green'
|
||||
: 'bg-transparent border-bambu-gray'
|
||||
} ${disabled ? 'opacity-50 cursor-not-allowed' : 'cursor-pointer'}`}
|
||||
>
|
||||
{checked && (
|
||||
<svg className="w-3 h-3 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={3}>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M5 13l4 4L19 7" />
|
||||
</svg>
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
// Timeline step component - matches Bambu Studio style
|
||||
function TimelineStep({
|
||||
step,
|
||||
name,
|
||||
isActive,
|
||||
isComplete,
|
||||
isLast,
|
||||
}: {
|
||||
step: number;
|
||||
name: string;
|
||||
isActive: boolean;
|
||||
isComplete: boolean;
|
||||
isLast: boolean;
|
||||
}) {
|
||||
return (
|
||||
<div className="flex items-start gap-3">
|
||||
{/* Circle and line container */}
|
||||
<div className="flex flex-col items-center">
|
||||
{/* Number circle */}
|
||||
<div
|
||||
className={`w-6 h-6 rounded-full flex items-center justify-center text-xs font-medium flex-shrink-0 ${
|
||||
isActive || isComplete
|
||||
? 'bg-bambu-green text-white'
|
||||
: 'bg-bambu-green text-white'
|
||||
}`}
|
||||
>
|
||||
{step}
|
||||
</div>
|
||||
{/* Vertical connecting line */}
|
||||
{!isLast && (
|
||||
<div className={`w-0.5 h-6 ${isComplete ? 'bg-bambu-green' : 'bg-bambu-gray/30'}`} />
|
||||
)}
|
||||
</div>
|
||||
{/* Step name */}
|
||||
<span
|
||||
className={`text-sm pt-0.5 ${
|
||||
isActive ? 'text-white font-semibold' : 'text-bambu-gray'
|
||||
}`}
|
||||
>
|
||||
{name}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function CalibrationModal({ printer, status, onClose }: CalibrationModalProps) {
|
||||
const isConnected = status?.connected ?? false;
|
||||
const isDualNozzle = printer.nozzle_count === 2;
|
||||
const currentStage = status?.stg_cur ?? -1;
|
||||
|
||||
// Track if we've started calibration (to switch to progress view)
|
||||
const [calibrationStarted, setCalibrationStarted] = useState(false);
|
||||
// Track if we've seen the printer actually enter calibration mode
|
||||
const [seenCalibrating, setSeenCalibrating] = useState(false);
|
||||
// Track if calibration has completed
|
||||
const [calibrationCompleted, setCalibrationCompleted] = useState(false);
|
||||
|
||||
// Calibration options state - restore from localStorage if calibration is in progress
|
||||
const storageKey = `calibration_options_${printer.id}`;
|
||||
const savedOptions = typeof window !== 'undefined' ? localStorage.getItem(storageKey) : null;
|
||||
const parsedOptions = savedOptions ? JSON.parse(savedOptions) : null;
|
||||
|
||||
const [bedLeveling, setBedLeveling] = useState(parsedOptions?.bedLeveling ?? true);
|
||||
const [vibration, setVibration] = useState(parsedOptions?.vibration ?? true);
|
||||
const [motorNoise, setMotorNoise] = useState(parsedOptions?.motorNoise ?? true);
|
||||
const [nozzleOffset, setNozzleOffset] = useState(parsedOptions?.nozzleOffset ?? isDualNozzle);
|
||||
const [highTempHeatbed, setHighTempHeatbed] = useState(parsedOptions?.highTempHeatbed ?? false);
|
||||
// Track if we've initialized based on calibration state
|
||||
const [initialized, setInitialized] = useState(false);
|
||||
|
||||
// Detect if printer is currently calibrating
|
||||
// Check both stg_cur being a calibration stage AND state being RUNNING
|
||||
// (printer may keep stg_cur at last calibration stage after completion)
|
||||
const printerState = status?.state;
|
||||
const isCalibrating = CALIBRATION_STAGES.has(currentStage) && printerState === 'RUNNING';
|
||||
|
||||
// If calibration is already in progress when modal opens, set tracking state
|
||||
// Checkbox values are preserved from localStorage
|
||||
useEffect(() => {
|
||||
if (!initialized && isCalibrating) {
|
||||
setSeenCalibrating(true);
|
||||
setCalibrationStarted(true);
|
||||
setInitialized(true);
|
||||
} else if (!initialized && !isCalibrating) {
|
||||
setInitialized(true);
|
||||
}
|
||||
}, [initialized, isCalibrating]);
|
||||
|
||||
// Track when printer actually enters calibration mode
|
||||
useEffect(() => {
|
||||
if (isCalibrating && !seenCalibrating) {
|
||||
setSeenCalibrating(true);
|
||||
setCalibrationCompleted(false);
|
||||
}
|
||||
}, [isCalibrating, seenCalibrating]);
|
||||
|
||||
// Auto-detect if calibration was started externally (e.g., from touchscreen)
|
||||
useEffect(() => {
|
||||
if (isCalibrating && !calibrationStarted) {
|
||||
setCalibrationStarted(true);
|
||||
}
|
||||
}, [isCalibrating, calibrationStarted]);
|
||||
|
||||
// Detect when calibration completes:
|
||||
// - Must have seen calibration actually running (seenCalibrating is true)
|
||||
// - Now isCalibrating is false (stg_cur left calibration stages OR state is no longer RUNNING)
|
||||
useEffect(() => {
|
||||
if (seenCalibrating && !isCalibrating && !calibrationCompleted) {
|
||||
setCalibrationCompleted(true);
|
||||
}
|
||||
}, [seenCalibrating, isCalibrating, calibrationCompleted]);
|
||||
|
||||
// Reset function to allow starting a new calibration
|
||||
const resetCalibration = () => {
|
||||
localStorage.removeItem(storageKey);
|
||||
setCalibrationStarted(false);
|
||||
setSeenCalibrating(false);
|
||||
setCalibrationCompleted(false);
|
||||
// Reset to defaults
|
||||
setBedLeveling(true);
|
||||
setVibration(true);
|
||||
setMotorNoise(true);
|
||||
setNozzleOffset(isDualNozzle);
|
||||
setHighTempHeatbed(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]);
|
||||
|
||||
// Start calibration mutation
|
||||
const calibrationMutation = useMutation({
|
||||
mutationFn: () =>
|
||||
api.startCalibration(printer.id, {
|
||||
bed_leveling: bedLeveling,
|
||||
vibration: vibration,
|
||||
motor_noise: motorNoise,
|
||||
nozzle_offset: nozzleOffset,
|
||||
high_temp_heatbed: highTempHeatbed,
|
||||
}),
|
||||
onSuccess: () => {
|
||||
// Save selected options to localStorage so they persist across modal close/open
|
||||
localStorage.setItem(storageKey, JSON.stringify({
|
||||
bedLeveling, vibration, motorNoise, nozzleOffset, highTempHeatbed
|
||||
}));
|
||||
setCalibrationStarted(true);
|
||||
},
|
||||
});
|
||||
|
||||
const hasSelection = bedLeveling || vibration || motorNoise || nozzleOffset || highTempHeatbed;
|
||||
const canStart = isConnected && hasSelection && !calibrationMutation.isPending && !isCalibrating && !calibrationCompleted;
|
||||
|
||||
// Build expected calibration flow based on selections
|
||||
// These are in the typical order the printer performs them
|
||||
const expectedFlow: { name: string; stages: number[] }[] = [];
|
||||
expectedFlow.push({ name: 'Homing toolhead', stages: [13] });
|
||||
if (bedLeveling || highTempHeatbed) {
|
||||
expectedFlow.push({ name: 'Cooling heatbed', stages: [50] });
|
||||
}
|
||||
if (bedLeveling) {
|
||||
expectedFlow.push({ name: 'Auto bed leveling - phase 1', stages: [1, 47] });
|
||||
}
|
||||
if (motorNoise) {
|
||||
expectedFlow.push({ name: 'Motor noise cancellation', stages: [25] });
|
||||
}
|
||||
if (vibration) {
|
||||
expectedFlow.push({ name: 'Vibration compensation', stages: [3] });
|
||||
}
|
||||
if (bedLeveling) {
|
||||
expectedFlow.push({ name: 'Auto bed leveling - phase 2', stages: [48] });
|
||||
}
|
||||
if (isDualNozzle && nozzleOffset) {
|
||||
expectedFlow.push({ name: 'Nozzle offset calibration', stages: [39] });
|
||||
}
|
||||
if (highTempHeatbed) {
|
||||
expectedFlow.push({ name: 'High-temp heatbed calibration', stages: [40] });
|
||||
}
|
||||
|
||||
// Find current step index
|
||||
const currentStepIndex = expectedFlow.findIndex((step) =>
|
||||
step.stages.includes(currentStage)
|
||||
);
|
||||
|
||||
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-3xl max-h-[90vh] flex flex-col" onClick={(e: React.MouseEvent) => e.stopPropagation()}>
|
||||
<CardContent className="p-0 flex flex-col h-full">
|
||||
{/* 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">Calibration</span>
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="p-1 rounded text-bambu-gray hover:bg-bambu-dark-tertiary hover:text-white"
|
||||
>
|
||||
<X className="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<div className="flex-1 overflow-y-auto p-6">
|
||||
{!isConnected && (
|
||||
<div className="flex items-center gap-2 p-3 mb-4 bg-red-500/20 border border-red-500/50 rounded text-red-400">
|
||||
<AlertTriangle className="w-4 h-4" />
|
||||
<span className="text-sm">Printer not connected. Calibration cannot be started.</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="grid grid-cols-2 gap-8">
|
||||
{/* Left column - Calibration step selection */}
|
||||
<div>
|
||||
<h3 className="text-base font-semibold text-white mb-4">Calibration step selection</h3>
|
||||
<div className="space-y-4">
|
||||
{/* Bed leveling */}
|
||||
<div className="flex items-center gap-3">
|
||||
<Checkbox
|
||||
checked={bedLeveling}
|
||||
onChange={setBedLeveling}
|
||||
disabled={!isConnected || isCalibrating}
|
||||
/>
|
||||
<span className="text-sm text-white">Bed leveling</span>
|
||||
</div>
|
||||
|
||||
{/* Vibration compensation */}
|
||||
<div className="flex items-center gap-3">
|
||||
<Checkbox
|
||||
checked={vibration}
|
||||
onChange={setVibration}
|
||||
disabled={!isConnected || isCalibrating}
|
||||
/>
|
||||
<span className="text-sm text-white">Vibration compensation</span>
|
||||
</div>
|
||||
|
||||
{/* Motor noise cancellation */}
|
||||
<div className="flex items-center gap-3">
|
||||
<Checkbox
|
||||
checked={motorNoise}
|
||||
onChange={setMotorNoise}
|
||||
disabled={!isConnected || isCalibrating}
|
||||
/>
|
||||
<span className="text-sm text-white">Motor noise cancellation</span>
|
||||
</div>
|
||||
|
||||
{/* Nozzle offset calibration - only for dual nozzle printers */}
|
||||
{isDualNozzle && (
|
||||
<div className="flex items-center gap-3">
|
||||
<Checkbox
|
||||
checked={nozzleOffset}
|
||||
onChange={setNozzleOffset}
|
||||
disabled={!isConnected || isCalibrating}
|
||||
/>
|
||||
<span className="text-sm text-white">Nozzle offset calibration</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* High-temperature Heatbed Calibration */}
|
||||
<div className="flex items-center gap-3">
|
||||
<Checkbox
|
||||
checked={highTempHeatbed}
|
||||
onChange={setHighTempHeatbed}
|
||||
disabled={!isConnected || isCalibrating}
|
||||
/>
|
||||
<span className="text-sm text-white whitespace-nowrap">High-temperature Heatbed Calibration</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Calibration program description */}
|
||||
<div className="mt-6">
|
||||
<h4 className="text-sm font-semibold text-white mb-2">Calibration program</h4>
|
||||
<p className="text-xs text-bambu-gray">
|
||||
The calibration program detects the status of your device automatically to minimize deviation.
|
||||
It keeps the device performing optimally.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Right column - Calibration Flow & Start button */}
|
||||
<div className="flex flex-col">
|
||||
<h3 className="text-base font-semibold text-bambu-green mb-4 text-center border-b border-bambu-dark-tertiary pb-2">
|
||||
Calibration Flow
|
||||
</h3>
|
||||
|
||||
{/* Timeline progress indicator */}
|
||||
<div className="flex-1 py-4 pl-4">
|
||||
{hasSelection ? (
|
||||
<div className="space-y-0">
|
||||
{expectedFlow.map((step, index) => {
|
||||
const isActive = calibrationStarted && !calibrationCompleted && step.stages.includes(currentStage);
|
||||
const isComplete = calibrationCompleted || (calibrationStarted && currentStepIndex > index);
|
||||
return (
|
||||
<TimelineStep
|
||||
key={step.name}
|
||||
step={index + 1}
|
||||
name={step.name}
|
||||
isActive={isActive}
|
||||
isComplete={isComplete}
|
||||
isLast={index === expectedFlow.length - 1}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
{/* Show current stage name if it's not in expected flow */}
|
||||
{currentStage >= 0 && currentStepIndex === -1 && status?.stg_cur_name && (
|
||||
<div className="mt-4 text-xs text-bambu-gray">
|
||||
Current: {status.stg_cur_name}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex items-center justify-center h-full text-sm text-bambu-gray italic">
|
||||
Select calibration steps
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Start/Calibrating/Completed button */}
|
||||
{calibrationCompleted ? (
|
||||
<div className="space-y-2">
|
||||
<button
|
||||
disabled
|
||||
className="w-full py-2.5 px-4 rounded-lg font-medium text-sm flex items-center justify-center gap-2 bg-bambu-green text-white cursor-default"
|
||||
>
|
||||
<Check className="w-4 h-4" />
|
||||
Completed
|
||||
</button>
|
||||
<button
|
||||
onClick={resetCalibration}
|
||||
className="w-full py-2 px-4 rounded-lg font-medium text-sm text-bambu-gray hover:text-white hover:bg-bambu-dark-tertiary transition-colors"
|
||||
>
|
||||
New Calibration
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<button
|
||||
onClick={() => calibrationMutation.mutate()}
|
||||
disabled={!canStart}
|
||||
className={`w-full py-2.5 px-4 rounded-lg font-medium text-sm flex items-center justify-center gap-2 transition-colors ${
|
||||
isCalibrating
|
||||
? 'bg-bambu-gray/50 text-white cursor-not-allowed'
|
||||
: canStart
|
||||
? 'bg-bambu-green hover:bg-bambu-green/90 text-white'
|
||||
: 'bg-bambu-dark-tertiary text-bambu-gray cursor-not-allowed'
|
||||
}`}
|
||||
>
|
||||
{isCalibrating ? (
|
||||
<>
|
||||
<Loader2 className="w-4 h-4 animate-spin" />
|
||||
Calibrating
|
||||
</>
|
||||
) : calibrationMutation.isPending ? (
|
||||
<>
|
||||
<Loader2 className="w-4 h-4 animate-spin" />
|
||||
Starting...
|
||||
</>
|
||||
) : (
|
||||
'Start Calibration'
|
||||
)}
|
||||
</button>
|
||||
)}
|
||||
|
||||
{calibrationMutation.isError && (
|
||||
<div className="mt-2 text-xs text-red-400 text-center">
|
||||
{calibrationMutation.error?.message || 'Failed to start calibration'}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -9,10 +9,11 @@ interface ExtruderControlsProps {
|
||||
printerId: number;
|
||||
status: PrinterStatus | null | undefined;
|
||||
nozzleCount: number;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
export function ExtruderControls({ printerId, status, nozzleCount }: ExtruderControlsProps) {
|
||||
const isConnected = status?.connected ?? false;
|
||||
export function ExtruderControls({ printerId, status, nozzleCount, disabled = false }: ExtruderControlsProps) {
|
||||
const isConnected = (status?.connected ?? false) && !disabled;
|
||||
const isPrinting = status?.state === 'RUNNING' || status?.state === 'PAUSE';
|
||||
const isDualNozzle = nozzleCount > 1;
|
||||
|
||||
@@ -60,23 +61,25 @@ export function ExtruderControls({ printerId, status, nozzleCount }: ExtruderCon
|
||||
<div className="flex flex-col items-center gap-1.5 justify-center">
|
||||
{/* Left/Right Toggle - only for dual nozzle */}
|
||||
{isDualNozzle && (
|
||||
<div className="flex rounded-md overflow-hidden border border-bambu-dark-tertiary mb-1 flex-shrink-0">
|
||||
<div className={`flex rounded-md overflow-hidden border border-bambu-dark-tertiary mb-1 flex-shrink-0 ${isDisabled ? 'opacity-50' : ''}`}>
|
||||
<button
|
||||
onClick={() => setSelectedNozzle('left')}
|
||||
className={`px-3 py-1.5 text-sm border-r border-bambu-dark-tertiary transition-colors ${
|
||||
disabled={isDisabled}
|
||||
className={`px-3 py-1.5 text-sm border-r border-bambu-dark-tertiary transition-colors disabled:cursor-not-allowed ${
|
||||
selectedNozzle === 'left'
|
||||
? 'bg-bambu-green text-white'
|
||||
: 'bg-bambu-dark-secondary text-bambu-gray hover:bg-bambu-dark-tertiary'
|
||||
: 'bg-bambu-dark-secondary text-bambu-gray hover:bg-bambu-dark-tertiary disabled:hover:bg-bambu-dark-secondary'
|
||||
}`}
|
||||
>
|
||||
Left
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setSelectedNozzle('right')}
|
||||
className={`px-3 py-1.5 text-sm transition-colors ${
|
||||
disabled={isDisabled}
|
||||
className={`px-3 py-1.5 text-sm transition-colors disabled:cursor-not-allowed ${
|
||||
selectedNozzle === 'right'
|
||||
? 'bg-bambu-green text-white'
|
||||
: 'bg-bambu-dark-secondary text-bambu-gray hover:bg-bambu-dark-tertiary'
|
||||
: 'bg-bambu-dark-secondary text-bambu-gray hover:bg-bambu-dark-tertiary disabled:hover:bg-bambu-dark-secondary'
|
||||
}`}
|
||||
>
|
||||
Right
|
||||
|
||||
@@ -7,10 +7,11 @@ import { ConfirmModal } from '../ConfirmModal';
|
||||
interface JogPadProps {
|
||||
printerId: number;
|
||||
status: PrinterStatus | null | undefined;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
export function JogPad({ printerId, status }: JogPadProps) {
|
||||
const isConnected = status?.connected ?? false;
|
||||
export function JogPad({ printerId, status, disabled = false }: JogPadProps) {
|
||||
const isConnected = (status?.connected ?? false) && !disabled;
|
||||
|
||||
const [confirmModal, setConfirmModal] = useState<{
|
||||
action: string;
|
||||
|
||||
@@ -17,14 +17,15 @@ interface TemperatureColumnProps {
|
||||
printerId: number;
|
||||
status: PrinterStatus | null | undefined;
|
||||
nozzleCount: number;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
type EditingField = 'nozzle' | 'nozzle_2' | 'bed' | null;
|
||||
|
||||
export function TemperatureColumn({ printerId, status, nozzleCount }: TemperatureColumnProps) {
|
||||
export function TemperatureColumn({ printerId, status, nozzleCount, disabled = false }: TemperatureColumnProps) {
|
||||
const temps = (status?.temperatures ?? {}) as Temperatures;
|
||||
const isDualNozzle = nozzleCount > 1;
|
||||
const isConnected = status?.connected ?? false;
|
||||
const isConnected = (status?.connected ?? false) && !disabled;
|
||||
|
||||
const [editing, setEditing] = useState<EditingField>(null);
|
||||
const [editValue, setEditValue] = useState('');
|
||||
|
||||
@@ -13,6 +13,7 @@ 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';
|
||||
|
||||
export function ControlPage() {
|
||||
@@ -21,6 +22,7 @@ export function ControlPage() {
|
||||
const [showCameraSettings, setShowCameraSettings] = useState(false);
|
||||
const [showPrinterParts, setShowPrinterParts] = useState(false);
|
||||
const [showPrintOptions, setShowPrintOptions] = useState(false);
|
||||
const [showCalibration, setShowCalibration] = useState(false);
|
||||
|
||||
// Fetch all printers
|
||||
const { data: printers, isLoading: loadingPrinters } = useQuery({
|
||||
@@ -74,6 +76,12 @@ export function ControlPage() {
|
||||
const selectedPrinter = printers?.find((p) => p.id === selectedPrinterId);
|
||||
const selectedStatus = selectedPrinterId ? statuses?.[selectedPrinterId] : null;
|
||||
|
||||
// Calibration stages that indicate active calibration
|
||||
const CALIBRATION_STAGES = new Set([1, 3, 13, 25, 39, 40, 47, 48, 50]);
|
||||
const isCalibrating = selectedStatus
|
||||
? CALIBRATION_STAGES.has(selectedStatus.stg_cur)
|
||||
: false;
|
||||
|
||||
if (loadingPrinters) {
|
||||
return (
|
||||
<div className="flex items-center justify-center h-screen">
|
||||
@@ -186,7 +194,10 @@ export function ControlPage() {
|
||||
>
|
||||
Print Options
|
||||
</button>
|
||||
<button className="px-4 py-1.5 text-xs rounded bg-bambu-green text-white hover:bg-bambu-green-dark">
|
||||
<button
|
||||
onClick={() => setShowCalibration(true)}
|
||||
className="px-4 py-1.5 text-xs rounded bg-bambu-green text-white hover:bg-bambu-green-dark"
|
||||
>
|
||||
Calibration
|
||||
</button>
|
||||
</div>
|
||||
@@ -212,6 +223,7 @@ export function ControlPage() {
|
||||
printerId={selectedPrinter.id}
|
||||
status={selectedStatus}
|
||||
nozzleCount={selectedPrinter.nozzle_count}
|
||||
disabled={isCalibrating}
|
||||
/>
|
||||
|
||||
{/* Movement Column */}
|
||||
@@ -221,10 +233,12 @@ export function ControlPage() {
|
||||
<JogPad
|
||||
printerId={selectedPrinter.id}
|
||||
status={selectedStatus}
|
||||
disabled={isCalibrating}
|
||||
/>
|
||||
<BedControls
|
||||
printerId={selectedPrinter.id}
|
||||
status={selectedStatus}
|
||||
disabled={isCalibrating}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -233,6 +247,7 @@ export function ControlPage() {
|
||||
printerId={selectedPrinter.id}
|
||||
status={selectedStatus}
|
||||
nozzleCount={selectedPrinter.nozzle_count}
|
||||
disabled={isCalibrating}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -275,6 +290,15 @@ export function ControlPage() {
|
||||
onClose={() => setShowPrintOptions(false)}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Calibration Modal */}
|
||||
{showCalibration && selectedPrinter && (
|
||||
<CalibrationModal
|
||||
printer={selectedPrinter}
|
||||
status={selectedStatus}
|
||||
onClose={() => setShowCalibration(false)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user