mirror of
https://github.com/jhbruhn/respira.git
synced 2026-01-27 02:13:41 +00:00
Merge pull request #18 from jhbruhn/fix/progress-monitor-use-pen-stitches
Some checks are pending
Build, Test, and Lint / Build, Test, and Lint (push) Waiting to run
Draft Release / Draft Release (push) Waiting to run
Draft Release / Build Web App (push) Blocked by required conditions
Draft Release / Build Release - macos-latest (push) Blocked by required conditions
Draft Release / Build Release - ubuntu-latest (push) Blocked by required conditions
Draft Release / Build Release - windows-latest (push) Blocked by required conditions
Draft Release / Upload to GitHub Release (push) Blocked by required conditions
Some checks are pending
Build, Test, and Lint / Build, Test, and Lint (push) Waiting to run
Draft Release / Draft Release (push) Waiting to run
Draft Release / Build Web App (push) Blocked by required conditions
Draft Release / Build Release - macos-latest (push) Blocked by required conditions
Draft Release / Build Release - ubuntu-latest (push) Blocked by required conditions
Draft Release / Build Release - windows-latest (push) Blocked by required conditions
Draft Release / Upload to GitHub Release (push) Blocked by required conditions
fix: Use decoded penStitches for progress monitor color blocks
This commit is contained in:
commit
60762d1526
5 changed files with 117 additions and 153 deletions
6
.github/workflows/autolabel.yaml
vendored
6
.github/workflows/autolabel.yaml
vendored
|
|
@ -4,12 +4,16 @@ on:
|
|||
pull_request:
|
||||
types: [opened, reopened, synchronize]
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
autolabel:
|
||||
name: Autolabel PR
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Draft release
|
||||
- name: Label PR
|
||||
id: drafter
|
||||
uses: release-drafter/release-drafter@v6
|
||||
with:
|
||||
|
|
|
|||
|
|
@ -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,66 +201,7 @@ export function FileUpload() {
|
|||
|
||||
{!isLoading && pesData && (
|
||||
<div className="mb-3">
|
||||
<div className="grid grid-cols-3 gap-2 text-xs mb-2">
|
||||
<div className="bg-gray-50 dark:bg-gray-700/50 p-2 rounded">
|
||||
<span className="text-gray-600 dark:text-gray-400 block">Size</span>
|
||||
<span className="font-semibold text-gray-900 dark:text-gray-100">
|
||||
{((pesData.bounds.maxX - pesData.bounds.minX) / 10).toFixed(1)} x{' '}
|
||||
{((pesData.bounds.maxY - pesData.bounds.minY) / 10).toFixed(1)} mm
|
||||
</span>
|
||||
</div>
|
||||
<div className="bg-gray-50 dark:bg-gray-700/50 p-2 rounded">
|
||||
<span className="text-gray-600 dark:text-gray-400 block">Stitches</span>
|
||||
<span className="font-semibold text-gray-900 dark:text-gray-100">
|
||||
{pesData.stitchCount.toLocaleString()}
|
||||
</span>
|
||||
</div>
|
||||
<div className="bg-gray-50 dark:bg-gray-700/50 p-2 rounded">
|
||||
<span className="text-gray-600 dark:text-gray-400 block">Colors / Blocks</span>
|
||||
<span className="font-semibold text-gray-900 dark:text-gray-100">
|
||||
{pesData.uniqueColors.length} / {pesData.threads.length}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<span className="text-xs text-gray-600 dark:text-gray-400">Colors:</span>
|
||||
<div className="flex gap-1">
|
||||
{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 (
|
||||
<div
|
||||
key={idx}
|
||||
className="w-3 h-3 rounded-full border border-gray-300 dark:border-gray-600"
|
||||
style={{ backgroundColor: color.hex }}
|
||||
title={tooltipText}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
{pesData.uniqueColors.length > 8 && (
|
||||
<div className="w-3 h-3 rounded-full bg-gray-300 dark:bg-gray-600 border border-gray-400 dark:border-gray-500 flex items-center justify-center text-xs font-bold text-gray-600 dark:text-gray-300 leading-none">
|
||||
+{pesData.uniqueColors.length - 8}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<PatternInfo pesData={pesData} showThreadBlocks />
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
|
|
|||
92
src/components/PatternInfo.tsx
Normal file
92
src/components/PatternInfo.tsx
Normal file
|
|
@ -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 (
|
||||
<>
|
||||
<div className="grid grid-cols-3 gap-2 text-xs mb-2">
|
||||
<div className="bg-gray-50 dark:bg-gray-700/50 p-2 rounded">
|
||||
<span className="text-gray-600 dark:text-gray-400 block">Size</span>
|
||||
<span className="font-semibold text-gray-900 dark:text-gray-100">
|
||||
{((pesData.bounds.maxX - pesData.bounds.minX) / 10).toFixed(1)} x{' '}
|
||||
{((pesData.bounds.maxY - pesData.bounds.minY) / 10).toFixed(1)} mm
|
||||
</span>
|
||||
</div>
|
||||
<div className="bg-gray-50 dark:bg-gray-700/50 p-2 rounded">
|
||||
<span className="text-gray-600 dark:text-gray-400 block">Stitches</span>
|
||||
<span className="font-semibold text-gray-900 dark:text-gray-100">
|
||||
{pesData.penStitches?.stitches.length.toLocaleString() || pesData.stitchCount.toLocaleString()}
|
||||
{pesData.penStitches && pesData.penStitches.stitches.length !== pesData.stitchCount && (
|
||||
<span
|
||||
className="text-gray-500 dark:text-gray-500 font-normal ml-1"
|
||||
title="Input stitch count from PES file (lock stitches were added for machine compatibility)"
|
||||
>
|
||||
({pesData.stitchCount.toLocaleString()})
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="bg-gray-50 dark:bg-gray-700/50 p-2 rounded">
|
||||
<span className="text-gray-600 dark:text-gray-400 block">
|
||||
{showThreadBlocks ? 'Colors / Blocks' : 'Colors'}
|
||||
</span>
|
||||
<span className="font-semibold text-gray-900 dark:text-gray-100">
|
||||
{showThreadBlocks
|
||||
? `${pesData.uniqueColors.length} / ${pesData.threads.length}`
|
||||
: pesData.uniqueColors.length
|
||||
}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<span className="text-xs text-gray-600 dark:text-gray-400">Colors:</span>
|
||||
<div className="flex gap-1">
|
||||
{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 (
|
||||
<div
|
||||
key={idx}
|
||||
className="w-3 h-3 rounded-full border border-gray-300 dark:border-gray-600"
|
||||
style={{ backgroundColor: color.hex }}
|
||||
title={tooltipText}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
{pesData.uniqueColors.length > 8 && (
|
||||
<div className="w-3 h-3 rounded-full bg-gray-300 dark:bg-gray-600 border border-gray-400 dark:border-gray-500 flex items-center justify-center text-xs font-bold text-gray-600 dark:text-gray-300 leading-none">
|
||||
+{pesData.uniqueColors.length - 8}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
@ -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,68 +45,7 @@ export function PatternSummaryCard() {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-3 gap-2 text-xs mb-3">
|
||||
<div className="bg-gray-50 dark:bg-gray-700/50 p-2 rounded">
|
||||
<span className="text-gray-600 dark:text-gray-400 block">Size</span>
|
||||
<span className="font-semibold text-gray-900 dark:text-gray-100">
|
||||
{((pesData.bounds.maxX - pesData.bounds.minX) / 10).toFixed(1)} x{' '}
|
||||
{((pesData.bounds.maxY - pesData.bounds.minY) / 10).toFixed(1)} mm
|
||||
</span>
|
||||
</div>
|
||||
<div className="bg-gray-50 dark:bg-gray-700/50 p-2 rounded">
|
||||
<span className="text-gray-600 dark:text-gray-400 block">Stitches</span>
|
||||
<span className="font-semibold text-gray-900 dark:text-gray-100">
|
||||
{pesData.stitchCount.toLocaleString()}
|
||||
</span>
|
||||
</div>
|
||||
<div className="bg-gray-50 dark:bg-gray-700/50 p-2 rounded">
|
||||
<span className="text-gray-600 dark:text-gray-400 block">Colors</span>
|
||||
<span className="font-semibold text-gray-900 dark:text-gray-100">
|
||||
{pesData.uniqueColors.length}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<span className="text-xs text-gray-600 dark:text-gray-400">Colors:</span>
|
||||
<div className="flex gap-1">
|
||||
{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 (
|
||||
<div
|
||||
key={idx}
|
||||
className="w-3 h-3 rounded-full border border-gray-300 dark:border-gray-600"
|
||||
style={{ backgroundColor: color.hex }}
|
||||
title={tooltipText}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
{pesData.uniqueColors.length > 8 && (
|
||||
<div className="w-3 h-3 rounded-full bg-gray-300 dark:bg-gray-600 border border-gray-400 dark:border-gray-500 flex items-center justify-center text-xs font-bold text-gray-600 dark:text-gray-300 leading-none">
|
||||
+{pesData.uniqueColors.length - 8}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<PatternInfo pesData={pesData} />
|
||||
|
||||
{canDelete && (
|
||||
<button
|
||||
|
|
|
|||
|
|
@ -60,9 +60,9 @@ export function ProgressMonitor() {
|
|||
? ((sewingProgress?.currentStitch || 0) / patternInfo.totalStitches) * 100
|
||||
: 0;
|
||||
|
||||
// Calculate color block information from pesData
|
||||
// Calculate color block information from decoded penStitches
|
||||
const colorBlocks = useMemo(() => {
|
||||
if (!pesData) return [];
|
||||
if (!pesData || !pesData.penStitches) return [];
|
||||
|
||||
const blocks: Array<{
|
||||
colorIndex: number;
|
||||
|
|
@ -76,34 +76,20 @@ export function ProgressMonitor() {
|
|||
threadChart: string | null;
|
||||
}> = [];
|
||||
|
||||
let currentColorIndex = pesData.stitches[0]?.[3] ?? 0;
|
||||
let blockStartStitch = 0;
|
||||
|
||||
for (let i = 0; i < pesData.stitches.length; i++) {
|
||||
const stitchColorIndex = pesData.stitches[i][3];
|
||||
|
||||
// When color changes, save the previous block
|
||||
if (
|
||||
stitchColorIndex !== currentColorIndex ||
|
||||
i === pesData.stitches.length - 1
|
||||
) {
|
||||
const endStitch = i === pesData.stitches.length - 1 ? i + 1 : i;
|
||||
const thread = pesData.threads[currentColorIndex];
|
||||
// Use the pre-computed color blocks from decoded PEN data
|
||||
for (const penBlock of pesData.penStitches.colorBlocks) {
|
||||
const thread = pesData.threads[penBlock.colorIndex];
|
||||
blocks.push({
|
||||
colorIndex: currentColorIndex,
|
||||
colorIndex: penBlock.colorIndex,
|
||||
threadHex: thread?.hex || "#000000",
|
||||
threadCatalogNumber: thread?.catalogNumber ?? null,
|
||||
threadBrand: thread?.brand ?? null,
|
||||
threadDescription: thread?.description ?? null,
|
||||
threadChart: thread?.chart ?? null,
|
||||
startStitch: blockStartStitch,
|
||||
endStitch: endStitch,
|
||||
stitchCount: endStitch - blockStartStitch,
|
||||
startStitch: penBlock.startStitchIndex,
|
||||
endStitch: penBlock.endStitchIndex,
|
||||
stitchCount: penBlock.endStitchIndex - penBlock.startStitchIndex,
|
||||
});
|
||||
|
||||
currentColorIndex = stitchColorIndex;
|
||||
blockStartStitch = i;
|
||||
}
|
||||
}
|
||||
|
||||
return blocks;
|
||||
|
|
|
|||
Loading…
Reference in a new issue