diff --git a/src/components/FileUpload.tsx b/src/components/FileUpload.tsx
index f83a912..201166a 100644
--- a/src/components/FileUpload.tsx
+++ b/src/components/FileUpload.tsx
@@ -6,6 +6,7 @@ import { useUIStore } from '../stores/useUIStore';
import { convertPesToPen, type PesPatternData } from '../formats/import/pesImporter';
import { canUploadPattern, getMachineStateCategory } from '../utils/machineStateHelpers';
import { PatternInfoSkeleton } from './SkeletonLoader';
+import { PatternInfo } from './PatternInfo';
import { ArrowUpTrayIcon, CheckCircleIcon, DocumentTextIcon, FolderOpenIcon } from '@heroicons/react/24/solid';
import { createFileService } from '../platform';
import type { IFileService } from '../platform/interfaces/IFileService';
@@ -200,74 +201,7 @@ export function FileUpload() {
{!isLoading && pesData && (
-
-
- Size
-
- {((pesData.bounds.maxX - pesData.bounds.minX) / 10).toFixed(1)} x{' '}
- {((pesData.bounds.maxY - pesData.bounds.minY) / 10).toFixed(1)} mm
-
-
-
- Stitches
-
- {pesData.penStitches?.stitches.length.toLocaleString() || pesData.stitchCount.toLocaleString()}
- {pesData.penStitches && pesData.penStitches.stitches.length !== pesData.stitchCount && (
-
- ({pesData.stitchCount.toLocaleString()})
-
- )}
-
-
-
- Colors / Blocks
-
- {pesData.uniqueColors.length} / {pesData.threads.length}
-
-
-
-
-
-
Colors:
-
- {pesData.uniqueColors.slice(0, 8).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
- const secondaryMetadata = [
- color.chart,
- color.description
- ].filter(Boolean).join(" ");
-
- const metadata = [primaryMetadata, secondaryMetadata].filter(Boolean).join(" • ");
-
- const tooltipText = metadata
- ? `Color ${idx + 1}: ${color.hex} - ${metadata}`
- : `Color ${idx + 1}: ${color.hex}`;
-
- return (
-
- );
- })}
- {pesData.uniqueColors.length > 8 && (
-
- +{pesData.uniqueColors.length - 8}
-
- )}
-
-
+
)}
diff --git a/src/components/PatternInfo.tsx b/src/components/PatternInfo.tsx
new file mode 100644
index 0000000..efff798
--- /dev/null
+++ b/src/components/PatternInfo.tsx
@@ -0,0 +1,92 @@
+import type { PesPatternData } from '../formats/import/pesImporter';
+
+interface PatternInfoProps {
+ pesData: PesPatternData;
+ showThreadBlocks?: boolean;
+}
+
+export function PatternInfo({ pesData, showThreadBlocks = false }: PatternInfoProps) {
+ return (
+ <>
+
+
+ Size
+
+ {((pesData.bounds.maxX - pesData.bounds.minX) / 10).toFixed(1)} x{' '}
+ {((pesData.bounds.maxY - pesData.bounds.minY) / 10).toFixed(1)} mm
+
+
+
+ Stitches
+
+ {pesData.penStitches?.stitches.length.toLocaleString() || pesData.stitchCount.toLocaleString()}
+ {pesData.penStitches && pesData.penStitches.stitches.length !== pesData.stitchCount && (
+
+ ({pesData.stitchCount.toLocaleString()})
+
+ )}
+
+
+
+
+ {showThreadBlocks ? 'Colors / Blocks' : 'Colors'}
+
+
+ {showThreadBlocks
+ ? `${pesData.uniqueColors.length} / ${pesData.threads.length}`
+ : pesData.uniqueColors.length
+ }
+
+
+
+
+
+
Colors:
+
+ {pesData.uniqueColors.slice(0, 8).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
+ const secondaryMetadata = [
+ color.chart,
+ color.description
+ ].filter(Boolean).join(" ");
+
+ const metadata = [primaryMetadata, secondaryMetadata].filter(Boolean).join(" • ");
+
+ // Show which thread blocks use this color in PatternSummaryCard
+ const threadNumbers = color.threadIndices.map(i => i + 1).join(", ");
+ const tooltipText = showThreadBlocks
+ ? (metadata
+ ? `Color ${idx + 1}: ${color.hex} - ${metadata}`
+ : `Color ${idx + 1}: ${color.hex}`)
+ : (metadata
+ ? `Color ${idx + 1}: ${color.hex}\n${metadata}\nUsed in thread blocks: ${threadNumbers}`
+ : `Color ${idx + 1}: ${color.hex}\nUsed in thread blocks: ${threadNumbers}`);
+
+ return (
+
+ );
+ })}
+ {pesData.uniqueColors.length > 8 && (
+
+ +{pesData.uniqueColors.length - 8}
+
+ )}
+
+
+ >
+ );
+}
diff --git a/src/components/PatternSummaryCard.tsx b/src/components/PatternSummaryCard.tsx
index 8a62357..a094f35 100644
--- a/src/components/PatternSummaryCard.tsx
+++ b/src/components/PatternSummaryCard.tsx
@@ -2,6 +2,7 @@ import { useShallow } from 'zustand/react/shallow';
import { useMachineStore } from '../stores/useMachineStore';
import { usePatternStore } from '../stores/usePatternStore';
import { canDeletePattern } from '../utils/machineStateHelpers';
+import { PatternInfo } from './PatternInfo';
import { DocumentTextIcon, TrashIcon } from '@heroicons/react/24/solid';
export function PatternSummaryCard() {
@@ -44,76 +45,7 @@ export function PatternSummaryCard() {
-
-
- Size
-
- {((pesData.bounds.maxX - pesData.bounds.minX) / 10).toFixed(1)} x{' '}
- {((pesData.bounds.maxY - pesData.bounds.minY) / 10).toFixed(1)} mm
-
-
-
- Stitches
-
- {pesData.penStitches?.stitches.length.toLocaleString() || pesData.stitchCount.toLocaleString()}
- {pesData.penStitches && pesData.penStitches.stitches.length !== pesData.stitchCount && (
-
- ({pesData.stitchCount.toLocaleString()})
-
- )}
-
-
-
- Colors
-
- {pesData.uniqueColors.length}
-
-
-
-
-
-
Colors:
-
- {pesData.uniqueColors.slice(0, 8).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
- const secondaryMetadata = [
- color.chart,
- color.description
- ].filter(Boolean).join(" ");
-
- const metadata = [primaryMetadata, secondaryMetadata].filter(Boolean).join(" • ");
-
- // Show which thread blocks use this color
- const threadNumbers = color.threadIndices.map(i => i + 1).join(", ");
- const tooltipText = metadata
- ? `Color ${idx + 1}: ${color.hex}\n${metadata}\nUsed in thread blocks: ${threadNumbers}`
- : `Color ${idx + 1}: ${color.hex}\nUsed in thread blocks: ${threadNumbers}`;
-
- return (
-
- );
- })}
- {pesData.uniqueColors.length > 8 && (
-
- +{pesData.uniqueColors.length - 8}
-
- )}
-
-
+
{canDelete && (