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';