diff --git a/src/App.tsx b/src/App.tsx
index 7baa6d0..be7e856 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -1,4 +1,4 @@
-import { useEffect, useCallback, useRef } from 'react';
+import { useEffect, useRef } from 'react';
import { useShallow } from 'zustand/react/shallow';
import { useMachineStore } from './stores/useMachineStore';
import { usePatternStore } from './stores/usePatternStore';
@@ -9,9 +9,8 @@ import { ProgressMonitor } from './components/ProgressMonitor';
import { WorkflowStepper } from './components/WorkflowStepper';
import { PatternSummaryCard } from './components/PatternSummaryCard';
import { BluetoothDevicePicker } from './components/BluetoothDevicePicker';
-import type { PesPatternData } from './utils/pystitchConverter';
import { hasError, getErrorDetails } from './utils/errorCodeHelpers';
-import { canDeletePattern, getStateVisualInfo } from './utils/machineStateHelpers';
+import { getStateVisualInfo } from './utils/machineStateHelpers';
import { CheckCircleIcon, BoltIcon, PauseCircleIcon, ExclamationTriangleIcon, ArrowPathIcon, XMarkIcon, InformationCircleIcon } from '@heroicons/react/24/solid';
import './App.css';
@@ -24,23 +23,13 @@ function App() {
machineStatusName,
machineError,
patternInfo,
- sewingProgress,
- uploadProgress,
error: machineErrorMessage,
isPairingError,
isCommunicating: isPolling,
- isUploading,
- isDeleting,
- resumeAvailable,
resumeFileName,
resumedPattern,
connect,
disconnect,
- uploadPattern,
- startMaskTrace,
- startSewing,
- resumeSewing,
- deletePattern,
} = useMachineStore(
useShallow((state) => ({
isConnected: state.isConnected,
@@ -49,59 +38,41 @@ function App() {
machineStatusName: state.machineStatusName,
machineError: state.machineError,
patternInfo: state.patternInfo,
- sewingProgress: state.sewingProgress,
- uploadProgress: state.uploadProgress,
error: state.error,
isPairingError: state.isPairingError,
isCommunicating: state.isCommunicating,
- isUploading: state.isUploading,
- isDeleting: state.isDeleting,
- resumeAvailable: state.resumeAvailable,
resumeFileName: state.resumeFileName,
resumedPattern: state.resumedPattern,
connect: state.connect,
disconnect: state.disconnect,
- uploadPattern: state.uploadPattern,
- startMaskTrace: state.startMaskTrace,
- startSewing: state.startSewing,
- resumeSewing: state.resumeSewing,
- deletePattern: state.deletePattern,
}))
);
// Pattern store
const {
pesData,
- currentFileName,
- patternOffset,
patternUploaded,
setPattern,
setPatternOffset,
setPatternUploaded,
- clearPattern,
} = usePatternStore(
useShallow((state) => ({
pesData: state.pesData,
- currentFileName: state.currentFileName,
- patternOffset: state.patternOffset,
patternUploaded: state.patternUploaded,
setPattern: state.setPattern,
setPatternOffset: state.setPatternOffset,
setPatternUploaded: state.setPatternUploaded,
- clearPattern: state.clearPattern,
}))
);
// UI store
const {
- pyodideReady,
pyodideError,
showErrorPopover,
initializePyodide,
setErrorPopover,
} = useUIStore(
useShallow((state) => ({
- pyodideReady: state.pyodideReady,
pyodideError: state.pyodideError,
showErrorPopover: state.showErrorPopover,
initializePyodide: state.initializePyodide,
@@ -146,25 +117,6 @@ function App() {
}
}
- const handlePatternLoaded = useCallback((data: PesPatternData, fileName: string) => {
- setPattern(data, fileName);
- }, [setPattern]);
-
- const handlePatternOffsetChange = useCallback((offsetX: number, offsetY: number) => {
- setPatternOffset(offsetX, offsetY);
- }, [setPatternOffset]);
-
- const handleUpload = useCallback(async (penData: Uint8Array, pesData: PesPatternData, fileName: string, patternOffset?: { x: number; y: number }) => {
- await uploadPattern(penData, pesData, fileName, patternOffset);
- setPatternUploaded(true);
- }, [uploadPattern, setPatternUploaded]);
-
- const handleDeletePattern = useCallback(async () => {
- await deletePattern();
- clearPattern();
- // NOTE: We intentionally DON'T clear pesData in the pattern store
- // so the pattern remains visible in the canvas for re-editing and re-uploading
- }, [deletePattern, clearPattern]);
// Track pattern uploaded state based on machine status
if (!isConnected) {
@@ -410,49 +362,18 @@ function App() {
{/* Pattern File - Show during upload stage (before pattern is uploaded) */}
{isConnected && !patternUploaded && (
-
+
)}
{/* Compact Pattern Summary - Show after upload (during sewing stages) */}
{isConnected && patternUploaded && pesData && (
-
+
)}
{/* Progress Monitor - Show when pattern is uploaded */}
{isConnected && patternUploaded && (
)}
@@ -460,15 +381,7 @@ function App() {
{/* Right Column - Pattern Preview */}
{pesData ? (
-
0 && uploadProgress < 100}
- />
+
) : (
Pattern Preview
diff --git a/src/components/FileUpload.tsx b/src/components/FileUpload.tsx
index 505366f..36fcf84 100644
--- a/src/components/FileUpload.tsx
+++ b/src/components/FileUpload.tsx
@@ -1,45 +1,58 @@
import { useState, useCallback } from 'react';
+import { useShallow } from 'zustand/react/shallow';
+import { useMachineStore } from '../stores/useMachineStore';
+import { usePatternStore } from '../stores/usePatternStore';
+import { useUIStore } from '../stores/useUIStore';
import { convertPesToPen, type PesPatternData } from '../utils/pystitchConverter';
-import { MachineStatus, type MachineInfo } from '../types/machine';
import { canUploadPattern, getMachineStateCategory } from '../utils/machineStateHelpers';
import { PatternInfoSkeleton } from './SkeletonLoader';
import { ArrowUpTrayIcon, CheckCircleIcon, DocumentTextIcon, FolderOpenIcon } from '@heroicons/react/24/solid';
import { createFileService } from '../platform';
import type { IFileService } from '../platform/interfaces/IFileService';
-interface FileUploadProps {
- isConnected: boolean;
- machineStatus: MachineStatus;
- uploadProgress: number;
- onPatternLoaded: (pesData: PesPatternData, fileName: string) => void;
- onUpload: (penData: Uint8Array, pesData: PesPatternData, fileName: string, patternOffset?: { x: number; y: number }) => void;
- pyodideReady: boolean;
- patternOffset: { x: number; y: number };
- patternUploaded: boolean;
- resumeAvailable: boolean;
- resumeFileName: string | null;
- pesData: PesPatternData | null;
- currentFileName: string;
- isUploading?: boolean;
- machineInfo: MachineInfo | null;
-}
+export function FileUpload() {
+ // Machine store
+ const {
+ isConnected,
+ machineStatus,
+ uploadProgress,
+ isUploading,
+ machineInfo,
+ resumeAvailable,
+ resumeFileName,
+ uploadPattern,
+ } = useMachineStore(
+ useShallow((state) => ({
+ isConnected: state.isConnected,
+ machineStatus: state.machineStatus,
+ uploadProgress: state.uploadProgress,
+ isUploading: state.isUploading,
+ machineInfo: state.machineInfo,
+ resumeAvailable: state.resumeAvailable,
+ resumeFileName: state.resumeFileName,
+ uploadPattern: state.uploadPattern,
+ }))
+ );
-export function FileUpload({
- isConnected,
- machineStatus,
- uploadProgress,
- onPatternLoaded,
- onUpload,
- pyodideReady,
- patternOffset,
- patternUploaded,
- resumeAvailable,
- resumeFileName,
- pesData: pesDataProp,
- currentFileName,
- isUploading = false,
- machineInfo,
-}: FileUploadProps) {
+ // Pattern store
+ const {
+ pesData: pesDataProp,
+ currentFileName,
+ patternOffset,
+ patternUploaded,
+ setPattern,
+ } = usePatternStore(
+ useShallow((state) => ({
+ pesData: state.pesData,
+ currentFileName: state.currentFileName,
+ patternOffset: state.patternOffset,
+ patternUploaded: state.patternUploaded,
+ setPattern: state.setPattern,
+ }))
+ );
+
+ // UI store
+ const pyodideReady = useUIStore((state) => state.pyodideReady);
const [localPesData, setLocalPesData] = useState
(null);
const [fileName, setFileName] = useState('');
const [fileService] = useState(() => createFileService());
@@ -77,7 +90,7 @@ export function FileUpload({
const data = await convertPesToPen(file);
setLocalPesData(data);
setFileName(file.name);
- onPatternLoaded(data, file.name);
+ setPattern(data, file.name);
} catch (err) {
alert(
`Failed to load PES file: ${
@@ -88,14 +101,14 @@ export function FileUpload({
setIsLoading(false);
}
},
- [fileService, onPatternLoaded, pyodideReady]
+ [fileService, setPattern, pyodideReady]
);
const handleUpload = useCallback(() => {
if (pesData && displayFileName) {
- onUpload(pesData.penData, pesData, displayFileName, patternOffset);
+ uploadPattern(pesData.penData, pesData, displayFileName, patternOffset);
}
- }, [pesData, displayFileName, onUpload, patternOffset]);
+ }, [pesData, displayFileName, uploadPattern, patternOffset]);
// Check if pattern (with offset) fits within hoop bounds
const checkPatternFitsInHoop = useCallback(() => {
diff --git a/src/components/PatternCanvas.tsx b/src/components/PatternCanvas.tsx
index 86eaf57..e7367ed 100644
--- a/src/components/PatternCanvas.tsx
+++ b/src/components/PatternCanvas.tsx
@@ -1,39 +1,58 @@
import { useEffect, useRef, useState, useCallback } from 'react';
+import { useShallow } from 'zustand/react/shallow';
+import { useMachineStore } from '../stores/useMachineStore';
+import { usePatternStore } from '../stores/usePatternStore';
import { Stage, Layer, Group } from 'react-konva';
import Konva from 'konva';
import { PlusIcon, MinusIcon, ArrowPathIcon, LockClosedIcon, PhotoIcon } from '@heroicons/react/24/solid';
import type { PesPatternData } from '../utils/pystitchConverter';
-import type { SewingProgress, MachineInfo } from '../types/machine';
import { calculateInitialScale } from '../utils/konvaRenderers';
import { Grid, Origin, Hoop, Stitches, PatternBounds, CurrentPosition } from './KonvaComponents';
-interface PatternCanvasProps {
- pesData: PesPatternData | null;
- sewingProgress: SewingProgress | null;
- machineInfo: MachineInfo | null;
- initialPatternOffset?: { x: number; y: number };
- onPatternOffsetChange?: (offsetX: number, offsetY: number) => void;
- patternUploaded?: boolean;
- isUploading?: boolean;
-}
+export function PatternCanvas() {
+ // Machine store
+ const {
+ sewingProgress,
+ machineInfo,
+ isUploading,
+ } = useMachineStore(
+ useShallow((state) => ({
+ sewingProgress: state.sewingProgress,
+ machineInfo: state.machineInfo,
+ isUploading: state.isUploading,
+ }))
+ );
-export function PatternCanvas({ pesData, sewingProgress, machineInfo, initialPatternOffset, onPatternOffsetChange, patternUploaded = false, isUploading = false }: PatternCanvasProps) {
+ // Pattern store
+ const {
+ pesData,
+ patternOffset: initialPatternOffset,
+ patternUploaded,
+ setPatternOffset,
+ } = usePatternStore(
+ useShallow((state) => ({
+ pesData: state.pesData,
+ patternOffset: state.patternOffset,
+ patternUploaded: state.patternUploaded,
+ setPatternOffset: state.setPatternOffset,
+ }))
+ );
const containerRef = useRef(null);
const stageRef = useRef(null);
const [stagePos, setStagePos] = useState({ x: 0, y: 0 });
const [stageScale, setStageScale] = useState(1);
- const [patternOffset, setPatternOffset] = useState(initialPatternOffset || { x: 0, y: 0 });
+ const [localPatternOffset, setLocalPatternOffset] = useState(initialPatternOffset || { x: 0, y: 0 });
const [containerSize, setContainerSize] = useState({ width: 0, height: 0 });
const initialScaleRef = useRef(1);
const prevPesDataRef = useRef(null);
// Update pattern offset when initialPatternOffset changes
if (initialPatternOffset && (
- patternOffset.x !== initialPatternOffset.x ||
- patternOffset.y !== initialPatternOffset.y
+ localPatternOffset.x !== initialPatternOffset.x ||
+ localPatternOffset.y !== initialPatternOffset.y
)) {
- setPatternOffset(initialPatternOffset);
+ setLocalPatternOffset(initialPatternOffset);
console.log('[PatternCanvas] Restored pattern offset:', initialPatternOffset);
}
@@ -178,12 +197,9 @@ export function PatternCanvas({ pesData, sewingProgress, machineInfo, initialPat
x: e.target.x(),
y: e.target.y(),
};
- setPatternOffset(newOffset);
-
- if (onPatternOffsetChange) {
- onPatternOffsetChange(newOffset.x, newOffset.y);
- }
- }, [onPatternOffsetChange]);
+ setLocalPatternOffset(newOffset);
+ setPatternOffset(newOffset.x, newOffset.y);
+ }, [setPatternOffset]);
const borderColor = pesData ? 'border-teal-600 dark:border-teal-500' : 'border-gray-400 dark:border-gray-600';
const iconColor = pesData ? 'text-teal-600 dark:text-teal-400' : 'text-gray-600 dark:text-gray-400';
@@ -252,8 +268,8 @@ export function PatternCanvas({ pesData, sewingProgress, machineInfo, initialPat
{
const stage = e.target.getStage();
@@ -278,7 +294,7 @@ export function PatternCanvas({ pesData, sewingProgress, machineInfo, initialPat
{/* Current position layer */}
{pesData && sewingProgress && sewingProgress.currentStitch > 0 && (
-
+
- X: {(patternOffset.x / 10).toFixed(1)}mm, Y: {(patternOffset.y / 10).toFixed(1)}mm
+ X: {(localPatternOffset.x / 10).toFixed(1)}mm, Y: {(localPatternOffset.y / 10).toFixed(1)}mm
{patternUploaded ? 'Pattern locked • Drag background to pan' : 'Drag pattern to move • Drag background to pan'}
diff --git a/src/components/PatternSummaryCard.tsx b/src/components/PatternSummaryCard.tsx
index e42e5e7..9abc0ca 100644
--- a/src/components/PatternSummaryCard.tsx
+++ b/src/components/PatternSummaryCard.tsx
@@ -1,29 +1,45 @@
+import { useShallow } from 'zustand/react/shallow';
+import { useMachineStore } from '../stores/useMachineStore';
+import { usePatternStore } from '../stores/usePatternStore';
+import { canDeletePattern } from '../utils/machineStateHelpers';
import { DocumentTextIcon, TrashIcon } from '@heroicons/react/24/solid';
-import type { PesPatternData } from '../utils/pystitchConverter';
-interface PatternSummaryCardProps {
- pesData: PesPatternData;
- fileName: string;
- onDeletePattern: () => void;
- canDelete: boolean;
- isDeleting: boolean;
-}
+export function PatternSummaryCard() {
+ // Machine store
+ const {
+ machineStatus,
+ isDeleting,
+ deletePattern,
+ } = useMachineStore(
+ useShallow((state) => ({
+ machineStatus: state.machineStatus,
+ isDeleting: state.isDeleting,
+ deletePattern: state.deletePattern,
+ }))
+ );
-export function PatternSummaryCard({
- pesData,
- fileName,
- onDeletePattern,
- canDelete,
- isDeleting
-}: PatternSummaryCardProps) {
+ // Pattern store
+ const {
+ pesData,
+ currentFileName,
+ } = usePatternStore(
+ useShallow((state) => ({
+ pesData: state.pesData,
+ currentFileName: state.currentFileName,
+ }))
+ );
+
+ if (!pesData) return null;
+
+ const canDelete = canDeletePattern(machineStatus);
return (
Active Pattern
-
- {fileName}
+
+ {currentFileName}
@@ -93,7 +109,7 @@ export function PatternSummaryCard({
{canDelete && (