mirror of
https://github.com/jhbruhn/respira.git
synced 2026-01-27 02:13:41 +00:00
- Add selectPatternCenter for pattern center calculation - Add selectRotatedBounds for rotated bounds with memoization - Add selectRotationCenterShift for center shift calculation - Add selectPatternValidation for bounds checking against hoop - Add comprehensive tests for all selectors - Update usePatternValidation to use store selectors - All tests passing and linter clean Co-authored-by: jhbruhn <1036566+jhbruhn@users.noreply.github.com>
149 lines
4.5 KiB
TypeScript
149 lines
4.5 KiB
TypeScript
/**
|
|
* PatternLayer Component
|
|
*
|
|
* Unified component for rendering pattern layers (both original and uploaded)
|
|
* Handles both interactive (draggable/rotatable) and locked states
|
|
*/
|
|
|
|
import { useMemo, memo, type RefObject } from "react";
|
|
import { Group, Transformer } from "react-konva";
|
|
import type Konva from "konva";
|
|
import type { KonvaEventObject } from "konva/lib/Node";
|
|
import type { PesPatternData } from "../../formats/import/pesImporter";
|
|
import {
|
|
calculatePatternCenter,
|
|
convertPenStitchesToPesFormat,
|
|
} from "./patternCanvasHelpers";
|
|
import { Stitches, PatternBounds, CurrentPosition } from "./KonvaComponents";
|
|
|
|
interface PatternLayerProps {
|
|
pesData: PesPatternData;
|
|
offset: { x: number; y: number };
|
|
rotation?: number;
|
|
isInteractive: boolean;
|
|
showProgress?: boolean;
|
|
currentStitchIndex?: number;
|
|
patternGroupRef?: RefObject<Konva.Group | null>;
|
|
transformerRef?: RefObject<Konva.Transformer | null>;
|
|
onDragEnd?: (e: Konva.KonvaEventObject<DragEvent>) => void;
|
|
onTransformEnd?: (e: KonvaEventObject<Event>) => void;
|
|
attachTransformer?: () => void;
|
|
}
|
|
|
|
export const PatternLayer = memo(function PatternLayer({
|
|
pesData,
|
|
offset,
|
|
rotation = 0,
|
|
isInteractive,
|
|
showProgress = false,
|
|
currentStitchIndex = 0,
|
|
patternGroupRef,
|
|
transformerRef,
|
|
onDragEnd,
|
|
onTransformEnd,
|
|
attachTransformer,
|
|
}: PatternLayerProps) {
|
|
// Memoize center calculation - this is the pattern center calculation
|
|
// Note: We keep this local calculation here since the component receives
|
|
// pesData as a prop which could be either original or uploaded pattern
|
|
const center = useMemo(
|
|
() => calculatePatternCenter(pesData.bounds),
|
|
[pesData.bounds],
|
|
);
|
|
|
|
const stitches = useMemo(
|
|
() => convertPenStitchesToPesFormat(pesData.penStitches),
|
|
[pesData.penStitches],
|
|
);
|
|
|
|
const groupName = isInteractive ? "pattern-group" : "uploaded-pattern-group";
|
|
|
|
return (
|
|
<>
|
|
<Group
|
|
name={groupName}
|
|
ref={
|
|
isInteractive
|
|
? (node) => {
|
|
if (patternGroupRef) {
|
|
patternGroupRef.current = node;
|
|
}
|
|
// Set initial rotation from state
|
|
if (node && isInteractive) {
|
|
node.rotation(rotation);
|
|
// Try to attach transformer when group is mounted
|
|
if (attachTransformer) {
|
|
attachTransformer();
|
|
}
|
|
}
|
|
}
|
|
: undefined
|
|
}
|
|
draggable={isInteractive}
|
|
x={offset.x}
|
|
y={offset.y}
|
|
offsetX={center.x}
|
|
offsetY={center.y}
|
|
onDragEnd={isInteractive ? onDragEnd : undefined}
|
|
onTransformEnd={isInteractive ? onTransformEnd : undefined}
|
|
onMouseEnter={
|
|
isInteractive
|
|
? (e) => {
|
|
const stage = e.target.getStage();
|
|
if (stage) stage.container().style.cursor = "move";
|
|
}
|
|
: undefined
|
|
}
|
|
onMouseLeave={
|
|
isInteractive
|
|
? (e) => {
|
|
const stage = e.target.getStage();
|
|
if (stage) stage.container().style.cursor = "grab";
|
|
}
|
|
: undefined
|
|
}
|
|
>
|
|
<Stitches
|
|
stitches={stitches}
|
|
pesData={pesData}
|
|
currentStitchIndex={currentStitchIndex}
|
|
showProgress={showProgress}
|
|
/>
|
|
<PatternBounds bounds={pesData.bounds} />
|
|
</Group>
|
|
|
|
{/* Transformer only for interactive layer */}
|
|
{isInteractive && transformerRef && (
|
|
<Transformer
|
|
ref={(node) => {
|
|
if (transformerRef) {
|
|
transformerRef.current = node;
|
|
}
|
|
// Try to attach transformer when transformer is mounted
|
|
if (node && attachTransformer) {
|
|
attachTransformer();
|
|
}
|
|
}}
|
|
enabledAnchors={[]}
|
|
rotateEnabled={true}
|
|
borderEnabled={true}
|
|
borderStroke="#FF6B6B"
|
|
borderStrokeWidth={2}
|
|
rotationSnaps={[0, 45, 90, 135, 180, 225, 270, 315]}
|
|
ignoreStroke={true}
|
|
rotateAnchorOffset={20}
|
|
/>
|
|
)}
|
|
|
|
{/* Current position indicator (only for uploaded pattern with progress) */}
|
|
{!isInteractive && showProgress && currentStitchIndex > 0 && (
|
|
<Group x={offset.x} y={offset.y} offsetX={center.x} offsetY={center.y}>
|
|
<CurrentPosition
|
|
currentStitchIndex={currentStitchIndex}
|
|
stitches={stitches}
|
|
/>
|
|
</Group>
|
|
)}
|
|
</>
|
|
);
|
|
});
|