mirror of
https://github.com/jhbruhn/respira.git
synced 2026-01-27 10:23:41 +00:00
feature: Update components to use Zustand stores directly
Refactor all child components to consume stores directly instead of receiving props from App.tsx. This eliminates prop drilling and simplifies the component tree. Components updated: - FileUpload: Now uses useMachineStore, usePatternStore, and useUIStore directly instead of receiving 14 props - ProgressMonitor: Now uses useMachineStore and usePatternStore instead of receiving 9 props - PatternCanvas: Now uses useMachineStore and usePatternStore instead of receiving 7 props - PatternSummaryCard: Now uses useMachineStore and usePatternStore instead of receiving 5 props Changes to App.tsx: - Removed all component props that are now accessed via stores - Removed unused callbacks: handlePatternLoaded, handlePatternOffsetChange, handleUpload, handleDeletePattern - Removed unused imports: PesPatternData, canDeletePattern, useCallback - Simplified component tree with zero-prop component calls Benefits: - Eliminated prop drilling across 37 props total - Components can access exactly what they need from stores - Cleaner, more maintainable component interfaces - Better separation of concerns 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
e015c587bd
commit
c22216792a
5 changed files with 160 additions and 199 deletions
99
src/App.tsx
99
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 && (
|
||||
<FileUpload
|
||||
isConnected={isConnected}
|
||||
machineStatus={machineStatus}
|
||||
uploadProgress={uploadProgress}
|
||||
onPatternLoaded={handlePatternLoaded}
|
||||
onUpload={handleUpload}
|
||||
pyodideReady={pyodideReady}
|
||||
patternOffset={patternOffset}
|
||||
patternUploaded={patternUploaded}
|
||||
resumeAvailable={resumeAvailable}
|
||||
resumeFileName={resumeFileName}
|
||||
pesData={pesData}
|
||||
currentFileName={currentFileName}
|
||||
isUploading={isUploading}
|
||||
machineInfo={machineInfo}
|
||||
/>
|
||||
<FileUpload />
|
||||
)}
|
||||
|
||||
{/* Compact Pattern Summary - Show after upload (during sewing stages) */}
|
||||
{isConnected && patternUploaded && pesData && (
|
||||
<PatternSummaryCard
|
||||
pesData={pesData}
|
||||
fileName={currentFileName}
|
||||
onDeletePattern={handleDeletePattern}
|
||||
canDelete={canDeletePattern(machineStatus)}
|
||||
isDeleting={isDeleting}
|
||||
/>
|
||||
<PatternSummaryCard />
|
||||
)}
|
||||
|
||||
{/* Progress Monitor - Show when pattern is uploaded */}
|
||||
{isConnected && patternUploaded && (
|
||||
<div className="lg:flex-1 lg:min-h-0">
|
||||
<ProgressMonitor
|
||||
machineStatus={machineStatus}
|
||||
patternInfo={patternInfo}
|
||||
sewingProgress={sewingProgress}
|
||||
pesData={pesData}
|
||||
onStartMaskTrace={startMaskTrace}
|
||||
onStartSewing={startSewing}
|
||||
onResumeSewing={resumeSewing}
|
||||
onDeletePattern={handleDeletePattern}
|
||||
isDeleting={isDeleting}
|
||||
/>
|
||||
<ProgressMonitor />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
|
@ -460,15 +381,7 @@ function App() {
|
|||
{/* Right Column - Pattern Preview */}
|
||||
<div className="flex flex-col lg:overflow-hidden lg:h-full">
|
||||
{pesData ? (
|
||||
<PatternCanvas
|
||||
pesData={pesData}
|
||||
sewingProgress={sewingProgress}
|
||||
machineInfo={machineInfo}
|
||||
initialPatternOffset={patternOffset}
|
||||
onPatternOffsetChange={handlePatternOffsetChange}
|
||||
patternUploaded={patternUploaded}
|
||||
isUploading={uploadProgress > 0 && uploadProgress < 100}
|
||||
/>
|
||||
<PatternCanvas />
|
||||
) : (
|
||||
<div className="lg:h-full bg-white dark:bg-gray-800 p-6 rounded-lg shadow-md animate-fadeIn flex flex-col">
|
||||
<h2 className="text-base lg:text-lg font-semibold mb-4 pb-2 border-b-2 border-gray-300 dark:border-gray-600 dark:text-white flex-shrink-0">Pattern Preview</h2>
|
||||
|
|
|
|||
|
|
@ -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<PesPatternData | null>(null);
|
||||
const [fileName, setFileName] = useState<string>('');
|
||||
const [fileService] = useState<IFileService>(() => 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(() => {
|
||||
|
|
|
|||
|
|
@ -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<HTMLDivElement>(null);
|
||||
const stageRef = useRef<Konva.Stage | null>(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<number>(1);
|
||||
const prevPesDataRef = useRef<PesPatternData | null>(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
|
|||
<Group
|
||||
name="pattern-group"
|
||||
draggable={!patternUploaded && !isUploading}
|
||||
x={patternOffset.x}
|
||||
y={patternOffset.y}
|
||||
x={localPatternOffset.x}
|
||||
y={localPatternOffset.y}
|
||||
onDragEnd={handlePatternDragEnd}
|
||||
onMouseEnter={(e) => {
|
||||
const stage = e.target.getStage();
|
||||
|
|
@ -278,7 +294,7 @@ export function PatternCanvas({ pesData, sewingProgress, machineInfo, initialPat
|
|||
{/* Current position layer */}
|
||||
<Layer>
|
||||
{pesData && sewingProgress && sewingProgress.currentStitch > 0 && (
|
||||
<Group x={patternOffset.x} y={patternOffset.y}>
|
||||
<Group x={localPatternOffset.x} y={localPatternOffset.y}>
|
||||
<CurrentPosition
|
||||
currentStitchIndex={sewingProgress.currentStitch}
|
||||
stitches={pesData.stitches}
|
||||
|
|
@ -352,7 +368,7 @@ export function PatternCanvas({ pesData, sewingProgress, machineInfo, initialPat
|
|||
)}
|
||||
</div>
|
||||
<div className="text-sm font-semibold text-blue-600 dark:text-blue-400 mb-1">
|
||||
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
|
||||
</div>
|
||||
<div className="text-xs text-gray-600 dark:text-gray-400 italic">
|
||||
{patternUploaded ? 'Pattern locked • Drag background to pan' : 'Drag pattern to move • Drag background to pan'}
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<div className="bg-white dark:bg-gray-800 p-4 rounded-lg shadow-md border-l-4 border-blue-600 dark:border-blue-500">
|
||||
<div className="flex items-start gap-3 mb-3">
|
||||
<DocumentTextIcon className="w-6 h-6 text-blue-600 dark:text-blue-400 flex-shrink-0 mt-0.5" />
|
||||
<div className="flex-1 min-w-0">
|
||||
<h3 className="text-sm font-semibold text-gray-900 dark:text-white mb-1">Active Pattern</h3>
|
||||
<p className="text-xs text-gray-600 dark:text-gray-400 truncate" title={fileName}>
|
||||
{fileName}
|
||||
<p className="text-xs text-gray-600 dark:text-gray-400 truncate" title={currentFileName}>
|
||||
{currentFileName}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -93,7 +109,7 @@ export function PatternSummaryCard({
|
|||
|
||||
{canDelete && (
|
||||
<button
|
||||
onClick={onDeletePattern}
|
||||
onClick={deletePattern}
|
||||
disabled={isDeleting}
|
||||
className="w-full flex items-center justify-center gap-2 px-3 py-2.5 sm:py-2 bg-red-50 dark:bg-red-900/20 text-red-700 dark:text-red-300 rounded border border-red-300 dark:border-red-700 hover:bg-red-100 dark:hover:bg-red-900/30 text-sm font-medium transition-colors disabled:opacity-50 disabled:cursor-not-allowed cursor-pointer"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,7 @@
|
|||
import { useRef, useEffect, useState, useMemo } from "react";
|
||||
import { useShallow } from 'zustand/react/shallow';
|
||||
import { useMachineStore } from '../stores/useMachineStore';
|
||||
import { usePatternStore } from '../stores/usePatternStore';
|
||||
import {
|
||||
CheckCircleIcon,
|
||||
ArrowRightIcon,
|
||||
|
|
@ -11,9 +14,7 @@ import {
|
|||
ChartBarIcon,
|
||||
ArrowPathIcon,
|
||||
} from "@heroicons/react/24/solid";
|
||||
import type { PatternInfo, SewingProgress } from "../types/machine";
|
||||
import { MachineStatus } from "../types/machine";
|
||||
import type { PesPatternData } from "../utils/pystitchConverter";
|
||||
import {
|
||||
canStartSewing,
|
||||
canStartMaskTrace,
|
||||
|
|
@ -21,28 +22,30 @@ import {
|
|||
getStateVisualInfo,
|
||||
} from "../utils/machineStateHelpers";
|
||||
|
||||
interface ProgressMonitorProps {
|
||||
machineStatus: MachineStatus;
|
||||
patternInfo: PatternInfo | null;
|
||||
sewingProgress: SewingProgress | null;
|
||||
pesData: PesPatternData | null;
|
||||
onStartMaskTrace: () => void;
|
||||
onStartSewing: () => void;
|
||||
onResumeSewing: () => void;
|
||||
onDeletePattern: () => void;
|
||||
isDeleting?: boolean;
|
||||
}
|
||||
export function ProgressMonitor() {
|
||||
// Machine store
|
||||
const {
|
||||
machineStatus,
|
||||
patternInfo,
|
||||
sewingProgress,
|
||||
isDeleting,
|
||||
startMaskTrace,
|
||||
startSewing,
|
||||
resumeSewing,
|
||||
} = useMachineStore(
|
||||
useShallow((state) => ({
|
||||
machineStatus: state.machineStatus,
|
||||
patternInfo: state.patternInfo,
|
||||
sewingProgress: state.sewingProgress,
|
||||
isDeleting: state.isDeleting,
|
||||
startMaskTrace: state.startMaskTrace,
|
||||
startSewing: state.startSewing,
|
||||
resumeSewing: state.resumeSewing,
|
||||
}))
|
||||
);
|
||||
|
||||
export function ProgressMonitor({
|
||||
machineStatus,
|
||||
patternInfo,
|
||||
sewingProgress,
|
||||
pesData,
|
||||
onStartMaskTrace,
|
||||
onStartSewing,
|
||||
onResumeSewing,
|
||||
isDeleting = false,
|
||||
}: ProgressMonitorProps) {
|
||||
// Pattern store
|
||||
const pesData = usePatternStore((state) => state.pesData);
|
||||
const currentBlockRef = useRef<HTMLDivElement>(null);
|
||||
const colorBlocksScrollRef = useRef<HTMLDivElement>(null);
|
||||
const [showGradient, setShowGradient] = useState(true);
|
||||
|
|
@ -417,7 +420,7 @@ export function ProgressMonitor({
|
|||
{/* Resume has highest priority when available */}
|
||||
{canResumeSewing(machineStatus) && (
|
||||
<button
|
||||
onClick={onResumeSewing}
|
||||
onClick={resumeSewing}
|
||||
disabled={isDeleting}
|
||||
className="flex-1 flex items-center justify-center gap-1.5 px-3 py-2.5 sm:py-2 bg-blue-600 dark:bg-blue-700 text-white rounded font-semibold text-xs hover:bg-blue-700 dark:hover:bg-blue-600 active:bg-blue-800 dark:active:bg-blue-500 transition-colors cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
aria-label="Resume sewing the current pattern"
|
||||
|
|
@ -430,7 +433,7 @@ export function ProgressMonitor({
|
|||
{/* Start Sewing - primary action, takes more space */}
|
||||
{canStartSewing(machineStatus) && !canResumeSewing(machineStatus) && (
|
||||
<button
|
||||
onClick={onStartSewing}
|
||||
onClick={startSewing}
|
||||
disabled={isDeleting}
|
||||
className="flex-[2] flex items-center justify-center gap-1.5 px-3 py-2.5 sm:py-2 bg-blue-600 dark:bg-blue-700 text-white rounded font-semibold text-xs hover:bg-blue-700 dark:hover:bg-blue-600 active:bg-blue-800 dark:active:bg-blue-500 transition-colors cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
aria-label="Start sewing the pattern"
|
||||
|
|
@ -443,7 +446,7 @@ export function ProgressMonitor({
|
|||
{/* Start Mask Trace - secondary action */}
|
||||
{canStartMaskTrace(machineStatus) && (
|
||||
<button
|
||||
onClick={onStartMaskTrace}
|
||||
onClick={startMaskTrace}
|
||||
disabled={isDeleting}
|
||||
className="flex-1 flex items-center justify-center gap-1.5 px-3 py-2.5 sm:py-2 bg-gray-600 dark:bg-gray-700 text-white rounded font-semibold text-xs hover:bg-gray-700 dark:hover:bg-gray-600 active:bg-gray-800 dark:active:bg-gray-500 transition-colors cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
aria-label={
|
||||
|
|
|
|||
Loading…
Reference in a new issue