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:
|
pull_request:
|
||||||
types: [opened, reopened, synchronize]
|
types: [opened, reopened, synchronize]
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
pull-requests: write
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
autolabel:
|
autolabel:
|
||||||
name: Autolabel PR
|
name: Autolabel PR
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Draft release
|
- name: Label PR
|
||||||
id: drafter
|
id: drafter
|
||||||
uses: release-drafter/release-drafter@v6
|
uses: release-drafter/release-drafter@v6
|
||||||
with:
|
with:
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import { useUIStore } from '../stores/useUIStore';
|
||||||
import { convertPesToPen, type PesPatternData } from '../formats/import/pesImporter';
|
import { convertPesToPen, type PesPatternData } from '../formats/import/pesImporter';
|
||||||
import { canUploadPattern, getMachineStateCategory } from '../utils/machineStateHelpers';
|
import { canUploadPattern, getMachineStateCategory } from '../utils/machineStateHelpers';
|
||||||
import { PatternInfoSkeleton } from './SkeletonLoader';
|
import { PatternInfoSkeleton } from './SkeletonLoader';
|
||||||
|
import { PatternInfo } from './PatternInfo';
|
||||||
import { ArrowUpTrayIcon, CheckCircleIcon, DocumentTextIcon, FolderOpenIcon } from '@heroicons/react/24/solid';
|
import { ArrowUpTrayIcon, CheckCircleIcon, DocumentTextIcon, FolderOpenIcon } from '@heroicons/react/24/solid';
|
||||||
import { createFileService } from '../platform';
|
import { createFileService } from '../platform';
|
||||||
import type { IFileService } from '../platform/interfaces/IFileService';
|
import type { IFileService } from '../platform/interfaces/IFileService';
|
||||||
|
|
@ -200,66 +201,7 @@ export function FileUpload() {
|
||||||
|
|
||||||
{!isLoading && pesData && (
|
{!isLoading && pesData && (
|
||||||
<div className="mb-3">
|
<div className="mb-3">
|
||||||
<div className="grid grid-cols-3 gap-2 text-xs mb-2">
|
<PatternInfo pesData={pesData} showThreadBlocks />
|
||||||
<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>
|
|
||||||
</div>
|
</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 { useMachineStore } from '../stores/useMachineStore';
|
||||||
import { usePatternStore } from '../stores/usePatternStore';
|
import { usePatternStore } from '../stores/usePatternStore';
|
||||||
import { canDeletePattern } from '../utils/machineStateHelpers';
|
import { canDeletePattern } from '../utils/machineStateHelpers';
|
||||||
|
import { PatternInfo } from './PatternInfo';
|
||||||
import { DocumentTextIcon, TrashIcon } from '@heroicons/react/24/solid';
|
import { DocumentTextIcon, TrashIcon } from '@heroicons/react/24/solid';
|
||||||
|
|
||||||
export function PatternSummaryCard() {
|
export function PatternSummaryCard() {
|
||||||
|
|
@ -44,68 +45,7 @@ export function PatternSummaryCard() {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-3 gap-2 text-xs mb-3">
|
<PatternInfo pesData={pesData} />
|
||||||
<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>
|
|
||||||
|
|
||||||
{canDelete && (
|
{canDelete && (
|
||||||
<button
|
<button
|
||||||
|
|
|
||||||
|
|
@ -60,9 +60,9 @@ export function ProgressMonitor() {
|
||||||
? ((sewingProgress?.currentStitch || 0) / patternInfo.totalStitches) * 100
|
? ((sewingProgress?.currentStitch || 0) / patternInfo.totalStitches) * 100
|
||||||
: 0;
|
: 0;
|
||||||
|
|
||||||
// Calculate color block information from pesData
|
// Calculate color block information from decoded penStitches
|
||||||
const colorBlocks = useMemo(() => {
|
const colorBlocks = useMemo(() => {
|
||||||
if (!pesData) return [];
|
if (!pesData || !pesData.penStitches) return [];
|
||||||
|
|
||||||
const blocks: Array<{
|
const blocks: Array<{
|
||||||
colorIndex: number;
|
colorIndex: number;
|
||||||
|
|
@ -76,34 +76,20 @@ export function ProgressMonitor() {
|
||||||
threadChart: string | null;
|
threadChart: string | null;
|
||||||
}> = [];
|
}> = [];
|
||||||
|
|
||||||
let currentColorIndex = pesData.stitches[0]?.[3] ?? 0;
|
// Use the pre-computed color blocks from decoded PEN data
|
||||||
let blockStartStitch = 0;
|
for (const penBlock of pesData.penStitches.colorBlocks) {
|
||||||
|
const thread = pesData.threads[penBlock.colorIndex];
|
||||||
for (let i = 0; i < pesData.stitches.length; i++) {
|
blocks.push({
|
||||||
const stitchColorIndex = pesData.stitches[i][3];
|
colorIndex: penBlock.colorIndex,
|
||||||
|
threadHex: thread?.hex || "#000000",
|
||||||
// When color changes, save the previous block
|
threadCatalogNumber: thread?.catalogNumber ?? null,
|
||||||
if (
|
threadBrand: thread?.brand ?? null,
|
||||||
stitchColorIndex !== currentColorIndex ||
|
threadDescription: thread?.description ?? null,
|
||||||
i === pesData.stitches.length - 1
|
threadChart: thread?.chart ?? null,
|
||||||
) {
|
startStitch: penBlock.startStitchIndex,
|
||||||
const endStitch = i === pesData.stitches.length - 1 ? i + 1 : i;
|
endStitch: penBlock.endStitchIndex,
|
||||||
const thread = pesData.threads[currentColorIndex];
|
stitchCount: penBlock.endStitchIndex - penBlock.startStitchIndex,
|
||||||
blocks.push({
|
});
|
||||||
colorIndex: currentColorIndex,
|
|
||||||
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,
|
|
||||||
});
|
|
||||||
|
|
||||||
currentColorIndex = stitchColorIndex;
|
|
||||||
blockStartStitch = i;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return blocks;
|
return blocks;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue