mirror of
https://github.com/jhbruhn/respira.git
synced 2026-01-27 02:13:41 +00:00
Migrate to declarative react-konva and add pattern offset caching
React-Konva Migration: - Create KonvaComponents.tsx with declarative components - Convert Grid, Origin, Hoop, Stitches, PatternBounds, and CurrentPosition to React components - Add React.memo and useMemo for performance optimization - Remove imperative layer manipulation (destroyChildren, add, batchDraw) - Remove backgroundLayerRef and patternLayerRef - Let React handle component lifecycle and updates - Improve performance through React's diffing algorithm Pattern Offset Persistence: - Add patternOffset field to CachedPattern interface - Update PatternCacheService.savePattern to accept and store offset - Modify useBrotherMachine to save offset when uploading pattern - Update resumedPattern state to include offset information - Restore cached pattern offset in App.tsx on resume - Add initialPatternOffset prop to PatternCanvas component - Pattern position now persists across page reloads and reconnections Benefits: - More maintainable and React-idiomatic code - Better performance with large patterns - Automatic cleanup and no memory leaks - Pattern positioning workflow preserved across sessions - Improved developer experience 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
30d87f82bc
commit
cd43a64bc4
5 changed files with 291 additions and 85 deletions
|
|
@ -32,8 +32,12 @@ function App() {
|
||||||
// Auto-load cached pattern when available
|
// Auto-load cached pattern when available
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (machine.resumedPattern && !pesData) {
|
if (machine.resumedPattern && !pesData) {
|
||||||
console.log('[App] Loading resumed pattern:', machine.resumeFileName);
|
console.log('[App] Loading resumed pattern:', machine.resumeFileName, 'Offset:', machine.resumedPattern.patternOffset);
|
||||||
setPesData(machine.resumedPattern);
|
setPesData(machine.resumedPattern.pesData);
|
||||||
|
// Restore the cached pattern offset
|
||||||
|
if (machine.resumedPattern.patternOffset) {
|
||||||
|
setPatternOffset(machine.resumedPattern.patternOffset);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, [machine.resumedPattern, pesData, machine.resumeFileName]);
|
}, [machine.resumedPattern, pesData, machine.resumeFileName]);
|
||||||
|
|
||||||
|
|
@ -106,6 +110,7 @@ function App() {
|
||||||
pesData={pesData}
|
pesData={pesData}
|
||||||
sewingProgress={machine.sewingProgress}
|
sewingProgress={machine.sewingProgress}
|
||||||
machineInfo={machine.machineInfo}
|
machineInfo={machine.machineInfo}
|
||||||
|
initialPatternOffset={patternOffset}
|
||||||
onPatternOffsetChange={handlePatternOffsetChange}
|
onPatternOffsetChange={handlePatternOffsetChange}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
229
src/components/KonvaComponents.tsx
Normal file
229
src/components/KonvaComponents.tsx
Normal file
|
|
@ -0,0 +1,229 @@
|
||||||
|
import { memo, useMemo } from 'react';
|
||||||
|
import { Group, Line, Rect, Text, Circle } from 'react-konva';
|
||||||
|
import type { PesPatternData } from '../utils/pystitchConverter';
|
||||||
|
import { getThreadColor } from '../utils/pystitchConverter';
|
||||||
|
import type { MachineInfo } from '../types/machine';
|
||||||
|
|
||||||
|
const MOVE = 0x10;
|
||||||
|
|
||||||
|
interface GridProps {
|
||||||
|
gridSize: number;
|
||||||
|
bounds: { minX: number; maxX: number; minY: number; maxY: number };
|
||||||
|
machineInfo: MachineInfo | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Grid = memo(({ gridSize, bounds, machineInfo }: GridProps) => {
|
||||||
|
const lines = useMemo(() => {
|
||||||
|
const gridMinX = machineInfo ? -machineInfo.maxWidth / 2 : bounds.minX;
|
||||||
|
const gridMaxX = machineInfo ? machineInfo.maxWidth / 2 : bounds.maxX;
|
||||||
|
const gridMinY = machineInfo ? -machineInfo.maxHeight / 2 : bounds.minY;
|
||||||
|
const gridMaxY = machineInfo ? machineInfo.maxHeight / 2 : bounds.maxY;
|
||||||
|
|
||||||
|
const verticalLines: number[][] = [];
|
||||||
|
const horizontalLines: number[][] = [];
|
||||||
|
|
||||||
|
// Vertical lines
|
||||||
|
for (let x = Math.floor(gridMinX / gridSize) * gridSize; x <= gridMaxX; x += gridSize) {
|
||||||
|
verticalLines.push([x, gridMinY, x, gridMaxY]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Horizontal lines
|
||||||
|
for (let y = Math.floor(gridMinY / gridSize) * gridSize; y <= gridMaxY; y += gridSize) {
|
||||||
|
horizontalLines.push([gridMinX, y, gridMaxX, y]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return { verticalLines, horizontalLines };
|
||||||
|
}, [gridSize, bounds, machineInfo]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Group name="grid">
|
||||||
|
{lines.verticalLines.map((points, i) => (
|
||||||
|
<Line
|
||||||
|
key={`v-${i}`}
|
||||||
|
points={points}
|
||||||
|
stroke="#e0e0e0"
|
||||||
|
strokeWidth={1}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
{lines.horizontalLines.map((points, i) => (
|
||||||
|
<Line
|
||||||
|
key={`h-${i}`}
|
||||||
|
points={points}
|
||||||
|
stroke="#e0e0e0"
|
||||||
|
strokeWidth={1}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Group>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
Grid.displayName = 'Grid';
|
||||||
|
|
||||||
|
export const Origin = memo(() => {
|
||||||
|
return (
|
||||||
|
<Group name="origin">
|
||||||
|
<Line points={[-10, 0, 10, 0]} stroke="#888" strokeWidth={2} />
|
||||||
|
<Line points={[0, -10, 0, 10]} stroke="#888" strokeWidth={2} />
|
||||||
|
</Group>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
Origin.displayName = 'Origin';
|
||||||
|
|
||||||
|
interface HoopProps {
|
||||||
|
machineInfo: MachineInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Hoop = memo(({ machineInfo }: HoopProps) => {
|
||||||
|
const { maxWidth, maxHeight } = machineInfo;
|
||||||
|
const hoopLeft = -maxWidth / 2;
|
||||||
|
const hoopTop = -maxHeight / 2;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Group name="hoop">
|
||||||
|
<Rect
|
||||||
|
x={hoopLeft}
|
||||||
|
y={hoopTop}
|
||||||
|
width={maxWidth}
|
||||||
|
height={maxHeight}
|
||||||
|
stroke="#2196F3"
|
||||||
|
strokeWidth={3}
|
||||||
|
dash={[10, 5]}
|
||||||
|
/>
|
||||||
|
<Text
|
||||||
|
x={hoopLeft + 10}
|
||||||
|
y={hoopTop + 10}
|
||||||
|
text={`Hoop: ${(maxWidth / 10).toFixed(0)} x ${(maxHeight / 10).toFixed(0)} mm`}
|
||||||
|
fontSize={14}
|
||||||
|
fontFamily="sans-serif"
|
||||||
|
fontStyle="bold"
|
||||||
|
fill="#2196F3"
|
||||||
|
/>
|
||||||
|
</Group>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
Hoop.displayName = 'Hoop';
|
||||||
|
|
||||||
|
interface PatternBoundsProps {
|
||||||
|
bounds: { minX: number; maxX: number; minY: number; maxY: number };
|
||||||
|
}
|
||||||
|
|
||||||
|
export const PatternBounds = memo(({ bounds }: PatternBoundsProps) => {
|
||||||
|
const { minX, maxX, minY, maxY } = bounds;
|
||||||
|
const width = maxX - minX;
|
||||||
|
const height = maxY - minY;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Rect
|
||||||
|
x={minX}
|
||||||
|
y={minY}
|
||||||
|
width={width}
|
||||||
|
height={height}
|
||||||
|
stroke="#ff0000"
|
||||||
|
strokeWidth={2}
|
||||||
|
dash={[5, 5]}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
PatternBounds.displayName = 'PatternBounds';
|
||||||
|
|
||||||
|
interface StitchesProps {
|
||||||
|
stitches: number[][];
|
||||||
|
pesData: PesPatternData;
|
||||||
|
currentStitchIndex: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Stitches = memo(({ stitches, pesData, currentStitchIndex }: StitchesProps) => {
|
||||||
|
const stitchGroups = useMemo(() => {
|
||||||
|
interface StitchGroup {
|
||||||
|
color: string;
|
||||||
|
points: number[];
|
||||||
|
completed: boolean;
|
||||||
|
isJump: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const groups: StitchGroup[] = [];
|
||||||
|
let currentGroup: StitchGroup | null = null;
|
||||||
|
|
||||||
|
for (let i = 0; i < stitches.length; i++) {
|
||||||
|
const stitch = stitches[i];
|
||||||
|
const [x, y, cmd, colorIndex] = stitch;
|
||||||
|
const isCompleted = i < currentStitchIndex;
|
||||||
|
const isJump = (cmd & MOVE) !== 0;
|
||||||
|
const color = getThreadColor(pesData, colorIndex);
|
||||||
|
|
||||||
|
// Start new group if color/status/type changes
|
||||||
|
if (
|
||||||
|
!currentGroup ||
|
||||||
|
currentGroup.color !== color ||
|
||||||
|
currentGroup.completed !== isCompleted ||
|
||||||
|
currentGroup.isJump !== isJump
|
||||||
|
) {
|
||||||
|
currentGroup = {
|
||||||
|
color,
|
||||||
|
points: [x, y],
|
||||||
|
completed: isCompleted,
|
||||||
|
isJump,
|
||||||
|
};
|
||||||
|
groups.push(currentGroup);
|
||||||
|
} else {
|
||||||
|
currentGroup.points.push(x, y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return groups;
|
||||||
|
}, [stitches, pesData, currentStitchIndex]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Group name="stitches">
|
||||||
|
{stitchGroups.map((group, i) => (
|
||||||
|
<Line
|
||||||
|
key={i}
|
||||||
|
points={group.points}
|
||||||
|
stroke={group.isJump ? (group.completed ? '#cccccc' : '#e8e8e8') : group.color}
|
||||||
|
strokeWidth={1.5}
|
||||||
|
lineCap="round"
|
||||||
|
lineJoin="round"
|
||||||
|
dash={group.isJump ? [3, 3] : undefined}
|
||||||
|
opacity={group.isJump ? 1 : (group.completed ? 1.0 : 0.3)}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Group>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
Stitches.displayName = 'Stitches';
|
||||||
|
|
||||||
|
interface CurrentPositionProps {
|
||||||
|
currentStitchIndex: number;
|
||||||
|
stitches: number[][];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const CurrentPosition = memo(({ currentStitchIndex, stitches }: CurrentPositionProps) => {
|
||||||
|
if (currentStitchIndex <= 0 || currentStitchIndex >= stitches.length) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const [x, y] = stitches[currentStitchIndex];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Group name="currentPosition">
|
||||||
|
<Circle
|
||||||
|
x={x}
|
||||||
|
y={y}
|
||||||
|
radius={8}
|
||||||
|
fill="rgba(255, 0, 0, 0.3)"
|
||||||
|
stroke="#ff0000"
|
||||||
|
strokeWidth={3}
|
||||||
|
/>
|
||||||
|
<Line points={[x - 12, y, x - 3, y]} stroke="#ff0000" strokeWidth={2} />
|
||||||
|
<Line points={[x + 12, y, x + 3, y]} stroke="#ff0000" strokeWidth={2} />
|
||||||
|
<Line points={[x, y - 12, x, y - 3]} stroke="#ff0000" strokeWidth={2} />
|
||||||
|
<Line points={[x, y + 12, x, y + 3]} stroke="#ff0000" strokeWidth={2} />
|
||||||
|
</Group>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
CurrentPosition.displayName = 'CurrentPosition';
|
||||||
|
|
@ -3,34 +3,35 @@ import { Stage, Layer, Group } from 'react-konva';
|
||||||
import Konva from 'konva';
|
import Konva from 'konva';
|
||||||
import type { PesPatternData } from '../utils/pystitchConverter';
|
import type { PesPatternData } from '../utils/pystitchConverter';
|
||||||
import type { SewingProgress, MachineInfo } from '../types/machine';
|
import type { SewingProgress, MachineInfo } from '../types/machine';
|
||||||
import {
|
import { calculateInitialScale } from '../utils/konvaRenderers';
|
||||||
renderGrid,
|
import { Grid, Origin, Hoop, Stitches, PatternBounds, CurrentPosition } from './KonvaComponents';
|
||||||
renderOrigin,
|
|
||||||
renderHoop,
|
|
||||||
renderStitches,
|
|
||||||
renderPatternBounds,
|
|
||||||
calculateInitialScale,
|
|
||||||
} from '../utils/konvaRenderers';
|
|
||||||
|
|
||||||
interface PatternCanvasProps {
|
interface PatternCanvasProps {
|
||||||
pesData: PesPatternData | null;
|
pesData: PesPatternData | null;
|
||||||
sewingProgress: SewingProgress | null;
|
sewingProgress: SewingProgress | null;
|
||||||
machineInfo: MachineInfo | null;
|
machineInfo: MachineInfo | null;
|
||||||
|
initialPatternOffset?: { x: number; y: number };
|
||||||
onPatternOffsetChange?: (offsetX: number, offsetY: number) => void;
|
onPatternOffsetChange?: (offsetX: number, offsetY: number) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function PatternCanvas({ pesData, sewingProgress, machineInfo, onPatternOffsetChange }: PatternCanvasProps) {
|
export function PatternCanvas({ pesData, sewingProgress, machineInfo, initialPatternOffset, onPatternOffsetChange }: PatternCanvasProps) {
|
||||||
const containerRef = useRef<HTMLDivElement>(null);
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
const stageRef = useRef<Konva.Stage | null>(null);
|
const stageRef = useRef<Konva.Stage | null>(null);
|
||||||
const backgroundLayerRef = useRef<Konva.Layer | null>(null);
|
|
||||||
const patternLayerRef = useRef<Konva.Layer | null>(null);
|
|
||||||
|
|
||||||
const [stagePos, setStagePos] = useState({ x: 0, y: 0 });
|
const [stagePos, setStagePos] = useState({ x: 0, y: 0 });
|
||||||
const [stageScale, setStageScale] = useState(1);
|
const [stageScale, setStageScale] = useState(1);
|
||||||
const [patternOffset, setPatternOffset] = useState({ x: 0, y: 0 });
|
const [patternOffset, setPatternOffset] = useState(initialPatternOffset || { x: 0, y: 0 });
|
||||||
const [containerSize, setContainerSize] = useState({ width: 0, height: 0 });
|
const [containerSize, setContainerSize] = useState({ width: 0, height: 0 });
|
||||||
const initialScaleRef = useRef<number>(1);
|
const initialScaleRef = useRef<number>(1);
|
||||||
|
|
||||||
|
// Update pattern offset when initialPatternOffset changes
|
||||||
|
useEffect(() => {
|
||||||
|
if (initialPatternOffset) {
|
||||||
|
setPatternOffset(initialPatternOffset);
|
||||||
|
console.log('[PatternCanvas] Restored pattern offset:', initialPatternOffset);
|
||||||
|
}
|
||||||
|
}, [initialPatternOffset]);
|
||||||
|
|
||||||
// Track container size
|
// Track container size
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!containerRef.current) return;
|
if (!containerRef.current) return;
|
||||||
|
|
@ -166,61 +167,6 @@ export function PatternCanvas({ pesData, sewingProgress, machineInfo, onPatternO
|
||||||
}
|
}
|
||||||
}, [onPatternOffsetChange]);
|
}, [onPatternOffsetChange]);
|
||||||
|
|
||||||
const handlePatternDragMove = useCallback(() => {
|
|
||||||
// Just for visual feedback during drag
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// Render background layer content
|
|
||||||
const renderBackgroundLayer = useCallback((layer: Konva.Layer) => {
|
|
||||||
if (!pesData) return;
|
|
||||||
|
|
||||||
layer.destroyChildren();
|
|
||||||
|
|
||||||
const { bounds } = pesData;
|
|
||||||
const gridSize = 100; // 10mm grid (100 units in 0.1mm)
|
|
||||||
|
|
||||||
renderGrid(layer, gridSize, bounds, machineInfo);
|
|
||||||
renderOrigin(layer);
|
|
||||||
|
|
||||||
if (machineInfo) {
|
|
||||||
renderHoop(layer, machineInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
layer.batchDraw();
|
|
||||||
}, [pesData, machineInfo]);
|
|
||||||
|
|
||||||
// Render pattern layer content
|
|
||||||
const renderPatternLayer = useCallback((layer: Konva.Layer, group: Konva.Group) => {
|
|
||||||
if (!pesData) return;
|
|
||||||
|
|
||||||
group.destroyChildren();
|
|
||||||
|
|
||||||
const currentStitch = sewingProgress?.currentStitch || 0;
|
|
||||||
const { stitches, bounds } = pesData;
|
|
||||||
|
|
||||||
renderStitches(group, stitches, pesData, currentStitch);
|
|
||||||
renderPatternBounds(group, bounds);
|
|
||||||
|
|
||||||
layer.batchDraw();
|
|
||||||
}, [pesData, sewingProgress]);
|
|
||||||
|
|
||||||
// Update background layer when deps change
|
|
||||||
useEffect(() => {
|
|
||||||
if (backgroundLayerRef.current) {
|
|
||||||
renderBackgroundLayer(backgroundLayerRef.current);
|
|
||||||
}
|
|
||||||
}, [renderBackgroundLayer]);
|
|
||||||
|
|
||||||
// Update pattern layer when deps change
|
|
||||||
useEffect(() => {
|
|
||||||
if (patternLayerRef.current) {
|
|
||||||
const patternGroup = patternLayerRef.current.findOne('.pattern-group') as Konva.Group;
|
|
||||||
if (patternGroup) {
|
|
||||||
renderPatternLayer(patternLayerRef.current, patternGroup);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [renderPatternLayer]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="bg-white p-6 rounded-lg shadow-md">
|
<div className="bg-white p-6 rounded-lg shadow-md">
|
||||||
<h2 className="text-xl font-semibold mb-4 pb-2 border-b-2 border-gray-300">Pattern Preview</h2>
|
<h2 className="text-xl font-semibold mb-4 pb-2 border-b-2 border-gray-300">Pattern Preview</h2>
|
||||||
|
|
@ -253,10 +199,22 @@ export function PatternCanvas({ pesData, sewingProgress, machineInfo, onPatternO
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{/* Background layer: grid, origin, hoop */}
|
{/* Background layer: grid, origin, hoop */}
|
||||||
<Layer ref={backgroundLayerRef} />
|
<Layer>
|
||||||
|
{pesData && (
|
||||||
|
<>
|
||||||
|
<Grid
|
||||||
|
gridSize={100}
|
||||||
|
bounds={pesData.bounds}
|
||||||
|
machineInfo={machineInfo}
|
||||||
|
/>
|
||||||
|
<Origin />
|
||||||
|
{machineInfo && <Hoop machineInfo={machineInfo} />}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Layer>
|
||||||
|
|
||||||
{/* Pattern layer: draggable stitches and bounds */}
|
{/* Pattern layer: draggable stitches and bounds */}
|
||||||
<Layer ref={patternLayerRef}>
|
<Layer>
|
||||||
{pesData && (
|
{pesData && (
|
||||||
<Group
|
<Group
|
||||||
name="pattern-group"
|
name="pattern-group"
|
||||||
|
|
@ -264,7 +222,6 @@ export function PatternCanvas({ pesData, sewingProgress, machineInfo, onPatternO
|
||||||
x={patternOffset.x}
|
x={patternOffset.x}
|
||||||
y={patternOffset.y}
|
y={patternOffset.y}
|
||||||
onDragEnd={handlePatternDragEnd}
|
onDragEnd={handlePatternDragEnd}
|
||||||
onDragMove={handlePatternDragMove}
|
|
||||||
onMouseEnter={(e) => {
|
onMouseEnter={(e) => {
|
||||||
const stage = e.target.getStage();
|
const stage = e.target.getStage();
|
||||||
if (stage) stage.container().style.cursor = 'move';
|
if (stage) stage.container().style.cursor = 'move';
|
||||||
|
|
@ -273,14 +230,26 @@ export function PatternCanvas({ pesData, sewingProgress, machineInfo, onPatternO
|
||||||
const stage = e.target.getStage();
|
const stage = e.target.getStage();
|
||||||
if (stage) stage.container().style.cursor = 'grab';
|
if (stage) stage.container().style.cursor = 'grab';
|
||||||
}}
|
}}
|
||||||
/>
|
>
|
||||||
|
<Stitches
|
||||||
|
stitches={pesData.stitches}
|
||||||
|
pesData={pesData}
|
||||||
|
currentStitchIndex={sewingProgress?.currentStitch || 0}
|
||||||
|
/>
|
||||||
|
<PatternBounds bounds={pesData.bounds} />
|
||||||
|
</Group>
|
||||||
)}
|
)}
|
||||||
</Layer>
|
</Layer>
|
||||||
|
|
||||||
{/* Current position layer */}
|
{/* Current position layer */}
|
||||||
<Layer>
|
<Layer>
|
||||||
{pesData && sewingProgress && sewingProgress.currentStitch > 0 && (
|
{pesData && sewingProgress && sewingProgress.currentStitch > 0 && (
|
||||||
<Group x={patternOffset.x} y={patternOffset.y} />
|
<Group x={patternOffset.x} y={patternOffset.y}>
|
||||||
|
<CurrentPosition
|
||||||
|
currentStitchIndex={sewingProgress.currentStitch}
|
||||||
|
stitches={pesData.stitches}
|
||||||
|
/>
|
||||||
|
</Group>
|
||||||
)}
|
)}
|
||||||
</Layer>
|
</Layer>
|
||||||
</Stage>
|
</Stage>
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ export function useBrotherMachine() {
|
||||||
const [isPolling, setIsPolling] = useState(false);
|
const [isPolling, setIsPolling] = useState(false);
|
||||||
const [resumeAvailable, setResumeAvailable] = useState(false);
|
const [resumeAvailable, setResumeAvailable] = useState(false);
|
||||||
const [resumeFileName, setResumeFileName] = useState<string | null>(null);
|
const [resumeFileName, setResumeFileName] = useState<string | null>(null);
|
||||||
const [resumedPattern, setResumedPattern] = useState<PesPatternData | null>(
|
const [resumedPattern, setResumedPattern] = useState<{ pesData: PesPatternData; patternOffset?: { x: number; y: number } } | null>(
|
||||||
null,
|
null,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -58,11 +58,11 @@ export function useBrotherMachine() {
|
||||||
const cached = PatternCacheService.getPatternByUUID(uuidStr);
|
const cached = PatternCacheService.getPatternByUUID(uuidStr);
|
||||||
|
|
||||||
if (cached) {
|
if (cached) {
|
||||||
console.log("[Resume] Pattern found in cache:", cached.fileName);
|
console.log("[Resume] Pattern found in cache:", cached.fileName, "Offset:", cached.patternOffset);
|
||||||
console.log("[Resume] Auto-loading cached pattern...");
|
console.log("[Resume] Auto-loading cached pattern...");
|
||||||
setResumeAvailable(true);
|
setResumeAvailable(true);
|
||||||
setResumeFileName(cached.fileName);
|
setResumeFileName(cached.fileName);
|
||||||
setResumedPattern(cached.pesData);
|
setResumedPattern({ pesData: cached.pesData, patternOffset: cached.patternOffset });
|
||||||
|
|
||||||
// Fetch pattern info from machine
|
// Fetch pattern info from machine
|
||||||
try {
|
try {
|
||||||
|
|
@ -166,7 +166,7 @@ export function useBrotherMachine() {
|
||||||
}, [service, isConnected]);
|
}, [service, isConnected]);
|
||||||
|
|
||||||
const loadCachedPattern =
|
const loadCachedPattern =
|
||||||
useCallback(async (): Promise<PesPatternData | null> => {
|
useCallback(async (): Promise<{ pesData: PesPatternData; patternOffset?: { x: number; y: number } } | null> => {
|
||||||
if (!resumeAvailable) return null;
|
if (!resumeAvailable) return null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
@ -177,10 +177,10 @@ export function useBrotherMachine() {
|
||||||
const cached = PatternCacheService.getPatternByUUID(uuidStr);
|
const cached = PatternCacheService.getPatternByUUID(uuidStr);
|
||||||
|
|
||||||
if (cached) {
|
if (cached) {
|
||||||
console.log("[Resume] Loading cached pattern:", cached.fileName);
|
console.log("[Resume] Loading cached pattern:", cached.fileName, "Offset:", cached.patternOffset);
|
||||||
// Refresh pattern info from machine
|
// Refresh pattern info from machine
|
||||||
await refreshPatternInfo();
|
await refreshPatternInfo();
|
||||||
return cached.pesData;
|
return { pesData: cached.pesData, patternOffset: cached.patternOffset };
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|
@ -212,10 +212,10 @@ export function useBrotherMachine() {
|
||||||
);
|
);
|
||||||
setUploadProgress(100);
|
setUploadProgress(100);
|
||||||
|
|
||||||
// Cache the pattern with its UUID
|
// Cache the pattern with its UUID and offset
|
||||||
const uuidStr = uuidToString(uuid);
|
const uuidStr = uuidToString(uuid);
|
||||||
PatternCacheService.savePattern(uuidStr, pesData, fileName);
|
PatternCacheService.savePattern(uuidStr, pesData, fileName, patternOffset);
|
||||||
console.log("[Cache] Saved pattern:", fileName, "with UUID:", uuidStr);
|
console.log("[Cache] Saved pattern:", fileName, "with UUID:", uuidStr, "Offset:", patternOffset);
|
||||||
|
|
||||||
// Clear resume state since we just uploaded
|
// Clear resume state since we just uploaded
|
||||||
setResumeAvailable(false);
|
setResumeAvailable(false);
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ interface CachedPattern {
|
||||||
pesData: PesPatternData;
|
pesData: PesPatternData;
|
||||||
fileName: string;
|
fileName: string;
|
||||||
timestamp: number;
|
timestamp: number;
|
||||||
|
patternOffset?: { x: number; y: number };
|
||||||
}
|
}
|
||||||
|
|
||||||
const CACHE_KEY = 'brother_pattern_cache';
|
const CACHE_KEY = 'brother_pattern_cache';
|
||||||
|
|
@ -34,7 +35,8 @@ export class PatternCacheService {
|
||||||
static savePattern(
|
static savePattern(
|
||||||
uuid: string,
|
uuid: string,
|
||||||
pesData: PesPatternData,
|
pesData: PesPatternData,
|
||||||
fileName: string
|
fileName: string,
|
||||||
|
patternOffset?: { x: number; y: number }
|
||||||
): void {
|
): void {
|
||||||
try {
|
try {
|
||||||
// Convert penData Uint8Array to array for JSON serialization
|
// Convert penData Uint8Array to array for JSON serialization
|
||||||
|
|
@ -48,10 +50,11 @@ export class PatternCacheService {
|
||||||
pesData: pesDataWithArrayPenData,
|
pesData: pesDataWithArrayPenData,
|
||||||
fileName,
|
fileName,
|
||||||
timestamp: Date.now(),
|
timestamp: Date.now(),
|
||||||
|
patternOffset,
|
||||||
};
|
};
|
||||||
|
|
||||||
localStorage.setItem(CACHE_KEY, JSON.stringify(cached));
|
localStorage.setItem(CACHE_KEY, JSON.stringify(cached));
|
||||||
console.log('[PatternCache] Saved pattern:', fileName, 'UUID:', uuid);
|
console.log('[PatternCache] Saved pattern:', fileName, 'UUID:', uuid, 'Offset:', patternOffset);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('[PatternCache] Failed to save pattern:', err);
|
console.error('[PatternCache] Failed to save pattern:', err);
|
||||||
// If quota exceeded, clear and try again
|
// If quota exceeded, clear and try again
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue