From 7cf4a5de17ecc4fb48265fec4e57ddb6100651f7 Mon Sep 17 00:00:00 2001 From: Jan-Henrik Bruhn Date: Sat, 20 Dec 2025 20:12:40 +0100 Subject: [PATCH] feature: Migrate PatternCanvas to shadcn/ui and rename placeholder MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Migrated PatternCanvas component to use shadcn Card components (Card, CardHeader, CardTitle, CardDescription, CardContent) - Replaced custom zoom control buttons with shadcn Button component using outline variant and icon size - Renamed PatternPreviewPlaceholder to PatternCanvasPlaceholder for consistency - Updated all imports and references in App.tsx - Maintained all existing functionality including Konva canvas rendering, zoom controls, and pattern positioning 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 --- src/App.tsx | 4 +- src/components/PatternCanvas.tsx | 488 ++++++++++--------- src/components/PatternCanvasPlaceholder.tsx | 75 +++ src/components/PatternPreviewPlaceholder.tsx | 75 --- 4 files changed, 331 insertions(+), 311 deletions(-) create mode 100644 src/components/PatternCanvasPlaceholder.tsx delete mode 100644 src/components/PatternPreviewPlaceholder.tsx diff --git a/src/App.tsx b/src/App.tsx index 4c94540..a42724f 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -6,7 +6,7 @@ import { useUIStore } from "./stores/useUIStore"; import { AppHeader } from "./components/AppHeader"; import { LeftSidebar } from "./components/LeftSidebar"; import { PatternCanvas } from "./components/PatternCanvas"; -import { PatternPreviewPlaceholder } from "./components/PatternPreviewPlaceholder"; +import { PatternCanvasPlaceholder } from "./components/PatternCanvasPlaceholder"; import { BluetoothDevicePicker } from "./components/BluetoothDevicePicker"; import "./App.css"; @@ -76,7 +76,7 @@ function App() { {/* Right Column - Pattern Preview */}
- {pesData ? : } + {pesData ? : }
diff --git a/src/components/PatternCanvas.tsx b/src/components/PatternCanvas.tsx index b9099b8..249784d 100644 --- a/src/components/PatternCanvas.tsx +++ b/src/components/PatternCanvas.tsx @@ -22,6 +22,14 @@ import { PatternBounds, CurrentPosition, } from "./KonvaComponents"; +import { + Card, + CardHeader, + CardTitle, + CardDescription, + CardContent, +} from "@/components/ui/card"; +import { Button } from "@/components/ui/button"; export function PatternCanvas() { // Machine store @@ -252,126 +260,101 @@ export function PatternCanvas() { : "text-gray-600 dark:text-gray-400"; return ( -
-
- -
-

- Pattern Preview -

- {pesData ? ( -

- {((pesData.bounds.maxX - pesData.bounds.minX) / 10).toFixed(1)} ×{" "} - {((pesData.bounds.maxY - pesData.bounds.minY) / 10).toFixed(1)} mm -

- ) : ( -

- No pattern loaded -

- )} + +
+ +
+ Pattern Preview + {pesData ? ( + + {((pesData.bounds.maxX - pesData.bounds.minX) / 10).toFixed(1)}{" "} + ×{" "} + {((pesData.bounds.maxY - pesData.bounds.minY) / 10).toFixed(1)}{" "} + mm + + ) : ( + + No pattern loaded + + )} +
-
-
- {containerSize.width > 0 && ( - { - if (stageRef.current) { - stageRef.current.container().style.cursor = "grabbing"; - } - }} - onDragEnd={() => { - if (stageRef.current) { - stageRef.current.container().style.cursor = "grab"; - } - }} - ref={(node) => { - stageRef.current = node; - if (node) { - node.container().style.cursor = "grab"; - } - }} - > - {/* Background layer: grid, origin, hoop */} - - {pesData && ( - <> - - - {machineInfo && } - - )} - + + +
+ {containerSize.width > 0 && ( + { + if (stageRef.current) { + stageRef.current.container().style.cursor = "grabbing"; + } + }} + onDragEnd={() => { + if (stageRef.current) { + stageRef.current.container().style.cursor = "grab"; + } + }} + ref={(node) => { + stageRef.current = node; + if (node) { + node.container().style.cursor = "grab"; + } + }} + > + {/* Background layer: grid, origin, hoop */} + + {pesData && ( + <> + + + {machineInfo && } + + )} + - {/* Pattern layer: draggable stitches and bounds */} - - {pesData && ( - { - const stage = e.target.getStage(); - if (stage && !patternUploaded && !isUploading) - stage.container().style.cursor = "move"; - }} - onMouseLeave={(e) => { - const stage = e.target.getStage(); - if (stage && !patternUploaded && !isUploading) - stage.container().style.cursor = "grab"; - }} - > - { - // Convert PEN stitch format {x, y, flags, isJump} to PES format [x, y, cmd, colorIndex] - const cmd = s.isJump ? 0x10 : 0; // MOVE flag if jump - const colorIndex = - pesData.penStitches.colorBlocks.find( - (b) => i >= b.startStitch && i <= b.endStitch, - )?.colorIndex ?? 0; - return [s.x, s.y, cmd, colorIndex]; - }, - )} - pesData={pesData} - currentStitchIndex={sewingProgress?.currentStitch || 0} - showProgress={patternUploaded || isUploading} - /> - - - )} - - - {/* Current position layer */} - - {pesData && - pesData.penStitches && - sewingProgress && - sewingProgress.currentStitch > 0 && ( - - + {pesData && ( + { + const stage = e.target.getStage(); + if (stage && !patternUploaded && !isUploading) + stage.container().style.cursor = "move"; + }} + onMouseLeave={(e) => { + const stage = e.target.getStage(); + if (stage && !patternUploaded && !isUploading) + stage.container().style.cursor = "grab"; + }} + > + { - const cmd = s.isJump ? 0x10 : 0; + // Convert PEN stitch format {x, y, flags, isJump} to PES format [x, y, cmd, colorIndex] + const cmd = s.isJump ? 0x10 : 0; // MOVE flag if jump const colorIndex = pesData.penStitches.colorBlocks.find( (b) => i >= b.startStitch && i <= b.endStitch, @@ -379,138 +362,175 @@ export function PatternCanvas() { return [s.x, s.y, cmd, colorIndex]; }, )} + pesData={pesData} + currentStitchIndex={sewingProgress?.currentStitch || 0} + showProgress={patternUploaded || isUploading} /> + )} - - - )} + - {/* Placeholder overlay when no pattern is loaded */} - {!pesData && ( -
- Load a PES file to preview the pattern -
- )} + {/* Current position layer */} + + {pesData && + pesData.penStitches && + sewingProgress && + sewingProgress.currentStitch > 0 && ( + + { + const cmd = s.isJump ? 0x10 : 0; + const colorIndex = + pesData.penStitches.colorBlocks.find( + (b) => i >= b.startStitch && i <= b.endStitch, + )?.colorIndex ?? 0; + return [s.x, s.y, cmd, colorIndex]; + }, + )} + /> + + )} + + + )} - {/* Pattern info overlays */} - {pesData && ( - <> - {/* Thread Legend Overlay */} -
-

- Colors -

- {pesData.uniqueColors.map((color, idx) => { - // Primary metadata: brand and catalog number - const primaryMetadata = [ - color.brand, - color.catalogNumber ? `#${color.catalogNumber}` : null, - ] - .filter(Boolean) - .join(" "); + {/* Placeholder overlay when no pattern is loaded */} + {!pesData && ( +
+ Load a PES file to preview the pattern +
+ )} - // Secondary metadata: chart and description - const secondaryMetadata = [color.chart, color.description] - .filter(Boolean) - .join(" "); + {/* Pattern info overlays */} + {pesData && ( + <> + {/* Thread Legend Overlay */} +
+

+ Colors +

+ {pesData.uniqueColors.map((color, idx) => { + // Primary metadata: brand and catalog number + const primaryMetadata = [ + color.brand, + color.catalogNumber ? `#${color.catalogNumber}` : null, + ] + .filter(Boolean) + .join(" "); - return ( -
+ // Secondary metadata: chart and description + const secondaryMetadata = [color.chart, color.description] + .filter(Boolean) + .join(" "); + + return (
-
-
- Color {idx + 1} -
- {(primaryMetadata || secondaryMetadata) && ( -
- {primaryMetadata} - {primaryMetadata && secondaryMetadata && ( - • - )} - {secondaryMetadata} + key={idx} + className="flex items-start gap-1.5 sm:gap-2 mb-1 sm:mb-1.5 last:mb-0" + > +
+
+
+ Color {idx + 1}
- )} + {(primaryMetadata || secondaryMetadata) && ( +
+ {primaryMetadata} + {primaryMetadata && secondaryMetadata && ( + • + )} + {secondaryMetadata} +
+ )} +
-
- ); - })} -
+ ); + })} +
- {/* Pattern Offset Indicator */} -
-
-
- Pattern Position: + {/* Pattern Offset Indicator */} +
+
+
+ Pattern Position: +
+ {patternUploaded && ( +
+ + LOCKED +
+ )} +
+
+ X: {(localPatternOffset.x / 10).toFixed(1)}mm, Y:{" "} + {(localPatternOffset.y / 10).toFixed(1)}mm +
+
+ {patternUploaded + ? "Pattern locked • Drag background to pan" + : "Drag pattern to move • Drag background to pan"}
- {patternUploaded && ( -
- - LOCKED -
- )}
-
- X: {(localPatternOffset.x / 10).toFixed(1)}mm, Y:{" "} - {(localPatternOffset.y / 10).toFixed(1)}mm -
-
- {patternUploaded - ? "Pattern locked • Drag background to pan" - : "Drag pattern to move • Drag background to pan"} -
-
- {/* Zoom Controls Overlay */} -
- - - - {Math.round(stageScale * 100)}% - - - -
- - )} -
-
+ {/* Zoom Controls Overlay */} +
+ + + + {Math.round(stageScale * 100)}% + + + +
+ + )} +
+ + ); } diff --git a/src/components/PatternCanvasPlaceholder.tsx b/src/components/PatternCanvasPlaceholder.tsx new file mode 100644 index 0000000..d2ebf1e --- /dev/null +++ b/src/components/PatternCanvasPlaceholder.tsx @@ -0,0 +1,75 @@ +import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card"; + +export function PatternCanvasPlaceholder() { + return ( + + + Pattern Preview + + +
+ {/* Decorative background pattern */} +
+
+
+
+
+ +
+
+ + + +
+ + + +
+
+

+ No Pattern Loaded +

+

+ Connect to your machine and choose a PES embroidery file to see + your design preview +

+
+
+
+ Drag to Position +
+
+
+ Zoom & Pan +
+
+
+ Real-time Preview +
+
+
+
+
+
+ ); +} diff --git a/src/components/PatternPreviewPlaceholder.tsx b/src/components/PatternPreviewPlaceholder.tsx deleted file mode 100644 index 6e4cb01..0000000 --- a/src/components/PatternPreviewPlaceholder.tsx +++ /dev/null @@ -1,75 +0,0 @@ -import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card"; - -export function PatternPreviewPlaceholder() { - return ( - - - Pattern Preview - - -
- {/* Decorative background pattern */} -
-
-
-
-
- -
-
- - - -
- - - -
-
-

- No Pattern Loaded -

-

- Connect to your machine and choose a PES embroidery file to see your - design preview -

-
-
-
- Drag to Position -
-
-
- Zoom & Pan -
-
-
- Real-time Preview -
-
-
-
-
-
- ); -}