import { useState, useEffect, useCallback } from 'react'; import { useBrotherMachine } from './hooks/useBrotherMachine'; import { FileUpload } from './components/FileUpload'; import { PatternCanvas } from './components/PatternCanvas'; import { ProgressMonitor } from './components/ProgressMonitor'; import { WorkflowStepper } from './components/WorkflowStepper'; import { NextStepGuide } from './components/NextStepGuide'; import { PatternSummaryCard } from './components/PatternSummaryCard'; import type { PesPatternData } from './utils/pystitchConverter'; import { pyodideLoader } from './utils/pyodideLoader'; import { hasError } from './utils/errorCodeHelpers'; import { canDeletePattern, getStateVisualInfo } from './utils/machineStateHelpers'; import { CheckCircleIcon, BoltIcon, PauseCircleIcon, ExclamationTriangleIcon, ArrowPathIcon, XMarkIcon } from '@heroicons/react/24/solid'; import './App.css'; function App() { const machine = useBrotherMachine(); const [pesData, setPesData] = useState(null); const [pyodideReady, setPyodideReady] = useState(false); const [pyodideError, setPyodideError] = useState(null); const [patternOffset, setPatternOffset] = useState<{ x: number; y: number }>({ x: 0, y: 0 }); const [patternUploaded, setPatternUploaded] = useState(false); const [currentFileName, setCurrentFileName] = useState(''); // Track current pattern filename // Initialize Pyodide on mount useEffect(() => { pyodideLoader .initialize() .then(() => { setPyodideReady(true); console.log('[App] Pyodide initialized successfully'); }) .catch((err) => { setPyodideError(err instanceof Error ? err.message : 'Failed to initialize Python environment'); console.error('[App] Failed to initialize Pyodide:', err); }); }, []); // Auto-load cached pattern when available const resumedPattern = machine.resumedPattern; const resumeFileName = machine.resumeFileName; if (resumedPattern && !pesData) { console.log('[App] Loading resumed pattern:', resumeFileName, 'Offset:', resumedPattern.patternOffset); setPesData(resumedPattern.pesData); // Restore the cached pattern offset if (resumedPattern.patternOffset) { setPatternOffset(resumedPattern.patternOffset); } // Preserve the filename from cache if (resumeFileName) { setCurrentFileName(resumeFileName); } } const handlePatternLoaded = useCallback((data: PesPatternData, fileName: string) => { setPesData(data); setCurrentFileName(fileName); // Reset pattern offset when new pattern is loaded setPatternOffset({ x: 0, y: 0 }); setPatternUploaded(false); }, []); const handlePatternOffsetChange = useCallback((offsetX: number, offsetY: number) => { setPatternOffset({ x: offsetX, y: offsetY }); console.log('[App] Pattern offset changed:', { x: offsetX, y: offsetY }); }, []); const handleUpload = useCallback(async (penData: Uint8Array, pesData: PesPatternData, fileName: string, patternOffset?: { x: number; y: number }) => { await machine.uploadPattern(penData, pesData, fileName, patternOffset); setPatternUploaded(true); }, [machine]); const handleDeletePattern = useCallback(async () => { await machine.deletePattern(); setPatternUploaded(false); // NOTE: We intentionally DON'T clear setPesData(null) here // so the pattern remains visible in the canvas for re-editing and re-uploading }, [machine]); // Track pattern uploaded state based on machine status const isConnected = machine.isConnected; const patternInfo = machine.patternInfo; if (!isConnected) { if (patternUploaded) { setPatternUploaded(false); } } else { // Pattern is uploaded if machine has pattern info const shouldBeUploaded = patternInfo !== null; if (patternUploaded !== shouldBeUploaded) { setPatternUploaded(shouldBeUploaded); } } // Get state visual info for header status badge const stateVisual = getStateVisualInfo(machine.machineStatus); const stateIcons = { ready: CheckCircleIcon, active: BoltIcon, waiting: PauseCircleIcon, complete: CheckCircleIcon, interrupted: PauseCircleIcon, error: ExclamationTriangleIcon, }; const StatusIcon = stateIcons[stateVisual.iconName]; return (
{/* Machine Connection Status - Fixed width column */}

SKiTCH Controller

{machine.isConnected && machine.machineInfo?.serialNumber && ( • {machine.machineInfo.serialNumber} )} {machine.isPolling && ( )}
{machine.isConnected ? ( <> {machine.machineStatusName} ) : (

Not Connected

)}
{/* Workflow Stepper - Flexible width column */}
{/* Global errors */} {machine.error && (
Error: {machine.error}
)} {pyodideError && (
Python Error: {pyodideError}
)} {!pyodideReady && !pyodideError && (
Initializing Python environment...
)}
{/* Left Column - Controls */}
{/* Connect Button - Show when disconnected */} {!machine.isConnected && (

Get Started

Connect to your embroidery machine

)} {/* Pattern File - Show during upload stage (before pattern is uploaded) */} {machine.isConnected && !patternUploaded && ( )} {/* Compact Pattern Summary - Show after upload (during sewing stages) */} {machine.isConnected && patternUploaded && pesData && ( )} {/* Progress Monitor - Show when pattern is uploaded */} {machine.isConnected && patternUploaded && ( )}
{/* Right Column - Pattern Preview */}
{pesData ? ( 0 && machine.uploadProgress < 100} /> ) : (

Pattern Preview

{/* Decorative background pattern */}

No Pattern Loaded

Connect to your machine and choose a PES embroidery file to see your design preview

Drag to Position
Zoom & Pan
Real-time Preview
)} {/* Next Step Guide - Below pattern preview */}
); } export default App;