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'; import { MOVE } from '../utils/embroideryConstants'; 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]); // Detect dark mode const isDarkMode = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches; const gridColor = isDarkMode ? '#404040' : '#e0e0e0'; return ( {lines.verticalLines.map((points, i) => ( ))} {lines.horizontalLines.map((points, i) => ( ))} ); }); Grid.displayName = 'Grid'; export const Origin = memo(() => { const isDarkMode = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches; const originColor = isDarkMode ? '#999' : '#888'; return ( ); }); 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 ( ); }); 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 ( ); }); PatternBounds.displayName = 'PatternBounds'; interface StitchesProps { stitches: number[][]; pesData: PesPatternData; currentStitchIndex: number; showProgress?: boolean; } export const Stitches = memo(({ stitches, pesData, currentStitchIndex, showProgress = false }: 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 ( {stitchGroups.map((group, i) => ( ))} ); }); 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 ( ); }); CurrentPosition.displayName = 'CurrentPosition';