diff --git a/src/components/PatternCanvas/PatternCanvas.tsx b/src/components/PatternCanvas/PatternCanvas.tsx index 8b0d213..948332b 100644 --- a/src/components/PatternCanvas/PatternCanvas.tsx +++ b/src/components/PatternCanvas/PatternCanvas.tsx @@ -8,14 +8,7 @@ import { usePatternStore } from "../../stores/usePatternStore"; import { Stage, Layer, Group, Transformer } from "react-konva"; import Konva from "konva"; import type { KonvaEventObject } from "konva/lib/Node"; -import { - PlusIcon, - MinusIcon, - ArrowPathIcon, - LockClosedIcon, - PhotoIcon, - ArrowsPointingInIcon, -} from "@heroicons/react/24/solid"; +import { PhotoIcon } from "@heroicons/react/24/solid"; import type { PesPatternData } from "../../formats/import/pesImporter"; import { calculateInitialScale } from "../../utils/konvaRenderers"; import { @@ -33,12 +26,14 @@ import { CardDescription, CardContent, } from "@/components/ui/card"; -import { Button } from "@/components/ui/button"; import { calculatePatternCenter, convertPenStitchesToPesFormat, calculateZoomToPoint, } from "./patternCanvasHelpers"; +import { ThreadLegend } from "./ThreadLegend"; +import { PatternPositionIndicator } from "./PatternPositionIndicator"; +import { ZoomControls } from "./ZoomControls"; export function PatternCanvas() { // Machine store @@ -579,156 +574,29 @@ export function PatternCanvas() { return ( displayPattern && ( <> - {/* Thread Legend Overlay */} -
-

- Colors -

- {displayPattern.uniqueColors.map((color, idx) => { - // Primary metadata: brand and catalog number - const primaryMetadata = [ - color.brand, - color.catalogNumber - ? `#${color.catalogNumber}` - : null, - ] - .filter(Boolean) - .join(" "); + - // Secondary metadata: chart and description - // Only show chart if it's different from catalogNumber - const secondaryMetadata = [ - color.chart && color.chart !== color.catalogNumber - ? color.chart - : null, - color.description, - ] - .filter(Boolean) - .join(" "); - - return ( -
-
-
-
- Color {idx + 1} -
- {(primaryMetadata || secondaryMetadata) && ( -
- {primaryMetadata} - {primaryMetadata && secondaryMetadata && ( - - )} - {secondaryMetadata} -
- )} -
-
- ); - })} -
- - {/* Pattern Offset Indicator */} -
-
-
- Pattern Position: -
- {(isUploading || patternUploaded) && ( -
- - - {isUploading ? "UPLOADING" : "LOCKED"} - -
- )} -
-
- {isUploading || patternUploaded ? ( - <> - X:{" "} - {(initialUploadedPatternOffset.x / 10).toFixed(1)} - mm, Y:{" "} - {(initialUploadedPatternOffset.y / 10).toFixed(1)}mm - - ) : ( - <> - X: {(localPatternOffset.x / 10).toFixed(1)}mm, Y:{" "} - {(localPatternOffset.y / 10).toFixed(1)}mm - - )} -
- {!isUploading && - !patternUploaded && - localPatternRotation !== 0 && ( -
- Rotation: {localPatternRotation.toFixed(1)}° -
- )} -
- {isUploading - ? "Uploading pattern..." - : patternUploaded - ? "Pattern locked • Drag background to pan" - : "Drag pattern to move • Drag background to pan"} -
-
+ ? initialUploadedPatternOffset + : localPatternOffset + } + rotation={localPatternRotation} + isLocked={patternUploaded} + isUploading={isUploading} + /> - {/* Zoom Controls Overlay */} -
- - - - {Math.round(stageScale * 100)}% - - - -
+ ) ); diff --git a/src/components/PatternCanvas/PatternPositionIndicator.tsx b/src/components/PatternCanvas/PatternPositionIndicator.tsx new file mode 100644 index 0000000..6e7474c --- /dev/null +++ b/src/components/PatternCanvas/PatternPositionIndicator.tsx @@ -0,0 +1,61 @@ +/** + * PatternPositionIndicator Component + * + * Displays the current pattern position and rotation + * Shows locked state when pattern is uploaded or being uploaded + */ + +import { LockClosedIcon } from "@heroicons/react/24/solid"; + +interface PatternPositionIndicatorProps { + offset: { x: number; y: number }; + rotation?: number; + isLocked: boolean; + isUploading: boolean; +} + +export function PatternPositionIndicator({ + offset, + rotation = 0, + isLocked, + isUploading, +}: PatternPositionIndicatorProps) { + return ( +
+
+
+ Pattern Position: +
+ {(isUploading || isLocked) && ( +
+ + + {isUploading ? "UPLOADING" : "LOCKED"} + +
+ )} +
+
+ X: {(offset.x / 10).toFixed(1)}mm, Y: {(offset.y / 10).toFixed(1)}mm +
+ {!isUploading && !isLocked && rotation !== 0 && ( +
+ Rotation: {rotation.toFixed(1)}° +
+ )} +
+ {isUploading + ? "Uploading pattern..." + : isLocked + ? "Pattern locked • Drag background to pan" + : "Drag pattern to move • Drag background to pan"} +
+
+ ); +} diff --git a/src/components/PatternCanvas/ThreadLegend.tsx b/src/components/PatternCanvas/ThreadLegend.tsx new file mode 100644 index 0000000..4f07738 --- /dev/null +++ b/src/components/PatternCanvas/ThreadLegend.tsx @@ -0,0 +1,74 @@ +/** + * ThreadLegend Component + * + * Displays a legend of thread colors used in the embroidery pattern + * Shows color swatches with brand, catalog number, and description metadata + */ + +interface ThreadColor { + hex: string; + brand?: string | null; + catalogNumber?: string | null; + chart?: string | null; + description?: string | null; +} + +interface ThreadLegendProps { + colors: ThreadColor[]; +} + +export function ThreadLegend({ colors }: ThreadLegendProps) { + return ( +
+

+ Colors +

+ {colors.map((color, idx) => { + // Primary metadata: brand and catalog number + const primaryMetadata = [ + color.brand, + color.catalogNumber ? `#${color.catalogNumber}` : null, + ] + .filter(Boolean) + .join(" "); + + // Secondary metadata: chart and description + // Only show chart if it's different from catalogNumber + const secondaryMetadata = [ + color.chart && color.chart !== color.catalogNumber + ? color.chart + : null, + color.description, + ] + .filter(Boolean) + .join(" "); + + return ( +
+
+
+
+ Color {idx + 1} +
+ {(primaryMetadata || secondaryMetadata) && ( +
+ {primaryMetadata} + {primaryMetadata && secondaryMetadata && ( + + )} + {secondaryMetadata} +
+ )} +
+
+ ); + })} +
+ ); +} diff --git a/src/components/PatternCanvas/ZoomControls.tsx b/src/components/PatternCanvas/ZoomControls.tsx new file mode 100644 index 0000000..9d5d59f --- /dev/null +++ b/src/components/PatternCanvas/ZoomControls.tsx @@ -0,0 +1,77 @@ +/** + * ZoomControls Component + * + * Provides zoom and pan controls for the pattern canvas + * Includes zoom in/out, reset zoom, and center pattern buttons + */ + +import { + PlusIcon, + MinusIcon, + ArrowPathIcon, + ArrowsPointingInIcon, +} from "@heroicons/react/24/solid"; +import { Button } from "@/components/ui/button"; + +interface ZoomControlsProps { + scale: number; + onZoomIn: () => void; + onZoomOut: () => void; + onZoomReset: () => void; + onCenterPattern: () => void; + canCenterPattern: boolean; +} + +export function ZoomControls({ + scale, + onZoomIn, + onZoomOut, + onZoomReset, + onCenterPattern, + canCenterPattern, +}: ZoomControlsProps) { + return ( +
+ + + + {Math.round(scale * 100)}% + + + +
+ ); +}