mirror of
https://github.com/jhbruhn/respira.git
synced 2026-01-27 02:13:41 +00:00
Merge pull request #49 from jhbruhn/fix/48-cached-pattern-disappears-on-reconnect
fix: Store original and uploaded pattern data to prevent rotation inconsistencies
This commit is contained in:
commit
a3cb6a4e5c
13 changed files with 324 additions and 37 deletions
|
|
@ -4,7 +4,12 @@
|
||||||
"Bash(npm run build:*)",
|
"Bash(npm run build:*)",
|
||||||
"Bash(npm run lint)",
|
"Bash(npm run lint)",
|
||||||
"Bash(cat:*)",
|
"Bash(cat:*)",
|
||||||
"Bash(npm run dev:electron:*)"
|
"Bash(npm run dev:electron:*)",
|
||||||
|
"Bash(npm run lint:*)",
|
||||||
|
"Bash(npm test:*)",
|
||||||
|
"Bash(npm run:*)",
|
||||||
|
"Bash(gh issue create:*)",
|
||||||
|
"Bash(gh label create:*)"
|
||||||
],
|
],
|
||||||
"deny": [],
|
"deny": [],
|
||||||
"ask": []
|
"ask": []
|
||||||
|
|
|
||||||
148
src/App.tsx
148
src/App.tsx
|
|
@ -8,6 +8,13 @@ import { LeftSidebar } from "./components/LeftSidebar";
|
||||||
import { PatternCanvas } from "./components/PatternCanvas";
|
import { PatternCanvas } from "./components/PatternCanvas";
|
||||||
import { PatternCanvasPlaceholder } from "./components/PatternCanvasPlaceholder";
|
import { PatternCanvasPlaceholder } from "./components/PatternCanvasPlaceholder";
|
||||||
import { BluetoothDevicePicker } from "./components/BluetoothDevicePicker";
|
import { BluetoothDevicePicker } from "./components/BluetoothDevicePicker";
|
||||||
|
import { transformStitchesRotation } from "./utils/rotationUtils";
|
||||||
|
import { encodeStitchesToPen } from "./formats/pen/encoder";
|
||||||
|
import { decodePenData } from "./formats/pen/decoder";
|
||||||
|
import {
|
||||||
|
calculatePatternCenter,
|
||||||
|
calculateBoundsFromDecodedStitches,
|
||||||
|
} from "./components/PatternCanvas/patternCanvasHelpers";
|
||||||
import "./App.css";
|
import "./App.css";
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
|
|
@ -25,10 +32,20 @@ function App() {
|
||||||
);
|
);
|
||||||
|
|
||||||
// Pattern store - for auto-loading cached pattern
|
// Pattern store - for auto-loading cached pattern
|
||||||
const { pesData, setPattern, setPatternOffset } = usePatternStore(
|
const {
|
||||||
|
pesData,
|
||||||
|
uploadedPesData,
|
||||||
|
setPattern,
|
||||||
|
setUploadedPattern,
|
||||||
|
setPatternRotation,
|
||||||
|
setPatternOffset,
|
||||||
|
} = usePatternStore(
|
||||||
useShallow((state) => ({
|
useShallow((state) => ({
|
||||||
pesData: state.pesData,
|
pesData: state.pesData,
|
||||||
|
uploadedPesData: state.uploadedPesData,
|
||||||
setPattern: state.setPattern,
|
setPattern: state.setPattern,
|
||||||
|
setUploadedPattern: state.setUploadedPattern,
|
||||||
|
setPatternRotation: state.setPatternRotation,
|
||||||
setPatternOffset: state.setPatternOffset,
|
setPatternOffset: state.setPatternOffset,
|
||||||
})),
|
})),
|
||||||
);
|
);
|
||||||
|
|
@ -47,23 +64,130 @@ function App() {
|
||||||
|
|
||||||
// Auto-load cached pattern when available
|
// Auto-load cached pattern when available
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (resumedPattern && !pesData) {
|
// Only auto-load if we have a resumed pattern and haven't already loaded it
|
||||||
|
if (resumedPattern && !uploadedPesData && !pesData) {
|
||||||
|
if (!resumedPattern.pesData) {
|
||||||
|
console.error(
|
||||||
|
"[App] ERROR: resumedPattern has no pesData!",
|
||||||
|
resumedPattern,
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
console.log(
|
console.log(
|
||||||
"[App] Loading resumed pattern:",
|
"[App] Loading resumed pattern:",
|
||||||
resumeFileName,
|
resumeFileName,
|
||||||
"Offset:",
|
"Offset:",
|
||||||
resumedPattern.patternOffset,
|
resumedPattern.patternOffset,
|
||||||
|
"Rotation:",
|
||||||
|
resumedPattern.patternRotation,
|
||||||
|
"Has stitches:",
|
||||||
|
resumedPattern.pesData.stitches?.length || 0,
|
||||||
|
"Has cached uploaded data:",
|
||||||
|
!!resumedPattern.uploadedPesData,
|
||||||
);
|
);
|
||||||
setPattern(resumedPattern.pesData, resumeFileName || "");
|
|
||||||
// Restore the cached pattern offset
|
const originalPesData = resumedPattern.pesData;
|
||||||
if (resumedPattern.patternOffset) {
|
const cachedUploadedPesData = resumedPattern.uploadedPesData;
|
||||||
setPatternOffset(
|
const rotation = resumedPattern.patternRotation || 0;
|
||||||
resumedPattern.patternOffset.x,
|
const originalOffset = resumedPattern.patternOffset || { x: 0, y: 0 };
|
||||||
resumedPattern.patternOffset.y,
|
|
||||||
|
// Set the original pattern data for editing
|
||||||
|
setPattern(originalPesData, resumeFileName || "");
|
||||||
|
|
||||||
|
// Restore the original offset (setPattern resets it to 0,0)
|
||||||
|
setPatternOffset(originalOffset.x, originalOffset.y);
|
||||||
|
|
||||||
|
// Set rotation if present
|
||||||
|
if (rotation !== 0) {
|
||||||
|
setPatternRotation(rotation);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use cached uploadedPesData if available, otherwise recalculate
|
||||||
|
if (cachedUploadedPesData) {
|
||||||
|
// Use the exact uploaded data from cache
|
||||||
|
// Calculate the adjusted offset (same logic as upload)
|
||||||
|
if (rotation !== 0) {
|
||||||
|
const originalCenter = calculatePatternCenter(originalPesData.bounds);
|
||||||
|
const rotatedCenter = calculatePatternCenter(
|
||||||
|
cachedUploadedPesData.bounds,
|
||||||
|
);
|
||||||
|
const centerShiftX = rotatedCenter.x - originalCenter.x;
|
||||||
|
const centerShiftY = rotatedCenter.y - originalCenter.y;
|
||||||
|
|
||||||
|
const adjustedOffset = {
|
||||||
|
x: originalOffset.x + centerShiftX,
|
||||||
|
y: originalOffset.y + centerShiftY,
|
||||||
|
};
|
||||||
|
|
||||||
|
setUploadedPattern(
|
||||||
|
cachedUploadedPesData,
|
||||||
|
adjustedOffset,
|
||||||
|
resumeFileName || undefined,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
setUploadedPattern(
|
||||||
|
cachedUploadedPesData,
|
||||||
|
originalOffset,
|
||||||
|
resumeFileName || undefined,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else if (rotation !== 0) {
|
||||||
|
// Fallback: recalculate if no cached uploaded data (shouldn't happen for new uploads)
|
||||||
|
console.warn("[App] No cached uploaded data, recalculating rotation");
|
||||||
|
const rotatedStitches = transformStitchesRotation(
|
||||||
|
originalPesData.stitches,
|
||||||
|
rotation,
|
||||||
|
originalPesData.bounds,
|
||||||
|
);
|
||||||
|
|
||||||
|
const penResult = encodeStitchesToPen(rotatedStitches);
|
||||||
|
const penData = new Uint8Array(penResult.penBytes);
|
||||||
|
const decoded = decodePenData(penData);
|
||||||
|
const rotatedBounds = calculateBoundsFromDecodedStitches(decoded);
|
||||||
|
|
||||||
|
const originalCenter = calculatePatternCenter(originalPesData.bounds);
|
||||||
|
const rotatedCenter = calculatePatternCenter(rotatedBounds);
|
||||||
|
const centerShiftX = rotatedCenter.x - originalCenter.x;
|
||||||
|
const centerShiftY = rotatedCenter.y - originalCenter.y;
|
||||||
|
|
||||||
|
const adjustedOffset = {
|
||||||
|
x: originalOffset.x + centerShiftX,
|
||||||
|
y: originalOffset.y + centerShiftY,
|
||||||
|
};
|
||||||
|
|
||||||
|
const rotatedPesData = {
|
||||||
|
...originalPesData,
|
||||||
|
stitches: rotatedStitches,
|
||||||
|
penData,
|
||||||
|
penStitches: decoded,
|
||||||
|
bounds: rotatedBounds,
|
||||||
|
};
|
||||||
|
|
||||||
|
setUploadedPattern(
|
||||||
|
rotatedPesData,
|
||||||
|
adjustedOffset,
|
||||||
|
resumeFileName || undefined,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// No rotation - uploaded pattern is same as original
|
||||||
|
setUploadedPattern(
|
||||||
|
originalPesData,
|
||||||
|
originalOffset,
|
||||||
|
resumeFileName || undefined,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [resumedPattern, resumeFileName, pesData, setPattern, setPatternOffset]);
|
}, [
|
||||||
|
resumedPattern,
|
||||||
|
resumeFileName,
|
||||||
|
uploadedPesData,
|
||||||
|
pesData,
|
||||||
|
setPattern,
|
||||||
|
setUploadedPattern,
|
||||||
|
setPatternRotation,
|
||||||
|
setPatternOffset,
|
||||||
|
]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="h-screen flex flex-col bg-gray-100 dark:bg-gray-900 overflow-hidden">
|
<div className="h-screen flex flex-col bg-gray-100 dark:bg-gray-900 overflow-hidden">
|
||||||
|
|
@ -76,7 +200,11 @@ function App() {
|
||||||
|
|
||||||
{/* Right Column - Pattern Preview */}
|
{/* Right Column - Pattern Preview */}
|
||||||
<div className="flex flex-col lg:overflow-hidden lg:h-full">
|
<div className="flex flex-col lg:overflow-hidden lg:h-full">
|
||||||
{pesData ? <PatternCanvas /> : <PatternCanvasPlaceholder />}
|
{pesData || uploadedPesData ? (
|
||||||
|
<PatternCanvas />
|
||||||
|
) : (
|
||||||
|
<PatternCanvasPlaceholder />
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -206,11 +206,14 @@ export function FileUpload() {
|
||||||
setUploadedPattern(pesDataForUpload, adjustedOffset);
|
setUploadedPattern(pesDataForUpload, adjustedOffset);
|
||||||
|
|
||||||
// Upload the pattern with offset
|
// Upload the pattern with offset
|
||||||
|
// IMPORTANT: Pass original unrotated pesData for caching, rotated pesData for upload
|
||||||
uploadPattern(
|
uploadPattern(
|
||||||
penDataToUpload,
|
penDataToUpload,
|
||||||
pesDataForUpload,
|
pesDataForUpload,
|
||||||
displayFileName,
|
displayFileName,
|
||||||
adjustedOffset,
|
adjustedOffset,
|
||||||
|
patternRotation,
|
||||||
|
pesData, // Original unrotated pattern for caching
|
||||||
);
|
);
|
||||||
|
|
||||||
return; // Early return to skip the upload below
|
return; // Early return to skip the upload below
|
||||||
|
|
@ -226,6 +229,8 @@ export function FileUpload() {
|
||||||
pesDataForUpload,
|
pesDataForUpload,
|
||||||
displayFileName,
|
displayFileName,
|
||||||
patternOffset,
|
patternOffset,
|
||||||
|
0, // No rotation
|
||||||
|
// No need to pass originalPesData since it's the same as pesDataForUpload
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}, [
|
}, [
|
||||||
|
|
|
||||||
|
|
@ -13,14 +13,16 @@ export function LeftSidebar() {
|
||||||
})),
|
})),
|
||||||
);
|
);
|
||||||
|
|
||||||
const { pesData } = usePatternStore(
|
const { pesData, uploadedPesData } = usePatternStore(
|
||||||
useShallow((state) => ({
|
useShallow((state) => ({
|
||||||
pesData: state.pesData,
|
pesData: state.pesData,
|
||||||
|
uploadedPesData: state.uploadedPesData,
|
||||||
})),
|
})),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Derived state: pattern is uploaded if machine has pattern info
|
// Derived state: pattern is uploaded if machine has pattern info
|
||||||
const patternUploaded = usePatternUploaded();
|
const patternUploaded = usePatternUploaded();
|
||||||
|
const hasPattern = pesData || uploadedPesData;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-4 md:gap-5 lg:gap-6 lg:overflow-hidden">
|
<div className="flex flex-col gap-4 md:gap-5 lg:gap-6 lg:overflow-hidden">
|
||||||
|
|
@ -31,7 +33,7 @@ export function LeftSidebar() {
|
||||||
{isConnected && !patternUploaded && <FileUpload />}
|
{isConnected && !patternUploaded && <FileUpload />}
|
||||||
|
|
||||||
{/* Compact Pattern Summary - Show after upload (during sewing stages) */}
|
{/* Compact Pattern Summary - Show after upload (during sewing stages) */}
|
||||||
{isConnected && patternUploaded && pesData && <PatternSummaryCard />}
|
{isConnected && patternUploaded && hasPattern && <PatternSummaryCard />}
|
||||||
|
|
||||||
{/* Progress Monitor - Show when pattern is uploaded */}
|
{/* Progress Monitor - Show when pattern is uploaded */}
|
||||||
{isConnected && patternUploaded && (
|
{isConnected && patternUploaded && (
|
||||||
|
|
|
||||||
|
|
@ -190,7 +190,9 @@ export function PatternCanvas() {
|
||||||
</Layer>
|
</Layer>
|
||||||
|
|
||||||
{/* Original pattern layer: draggable with transformer (shown before upload starts) */}
|
{/* Original pattern layer: draggable with transformer (shown before upload starts) */}
|
||||||
<Layer visible={!isUploading && !patternUploaded}>
|
<Layer
|
||||||
|
visible={!isUploading && !patternUploaded && !uploadedPesData}
|
||||||
|
>
|
||||||
{pesData && (
|
{pesData && (
|
||||||
<PatternLayer
|
<PatternLayer
|
||||||
pesData={pesData}
|
pesData={pesData}
|
||||||
|
|
@ -209,7 +211,9 @@ export function PatternCanvas() {
|
||||||
</Layer>
|
</Layer>
|
||||||
|
|
||||||
{/* Uploaded pattern layer: locked, rotation baked in (shown during and after upload) */}
|
{/* Uploaded pattern layer: locked, rotation baked in (shown during and after upload) */}
|
||||||
<Layer visible={isUploading || patternUploaded}>
|
<Layer
|
||||||
|
visible={isUploading || patternUploaded || !!uploadedPesData}
|
||||||
|
>
|
||||||
{uploadedPesData && (
|
{uploadedPesData && (
|
||||||
<PatternLayer
|
<PatternLayer
|
||||||
pesData={uploadedPesData}
|
pesData={uploadedPesData}
|
||||||
|
|
@ -241,12 +245,12 @@ export function PatternCanvas() {
|
||||||
|
|
||||||
<PatternPositionIndicator
|
<PatternPositionIndicator
|
||||||
offset={
|
offset={
|
||||||
isUploading || patternUploaded
|
isUploading || patternUploaded || uploadedPesData
|
||||||
? initialUploadedPatternOffset
|
? initialUploadedPatternOffset
|
||||||
: localPatternOffset
|
: localPatternOffset
|
||||||
}
|
}
|
||||||
rotation={localPatternRotation}
|
rotation={localPatternRotation}
|
||||||
isLocked={patternUploaded}
|
isLocked={patternUploaded || !!uploadedPesData}
|
||||||
isUploading={isUploading}
|
isUploading={isUploading}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|
@ -257,7 +261,10 @@ export function PatternCanvas() {
|
||||||
onZoomReset={handleZoomReset}
|
onZoomReset={handleZoomReset}
|
||||||
onCenterPattern={handleCenterPattern}
|
onCenterPattern={handleCenterPattern}
|
||||||
canCenterPattern={
|
canCenterPattern={
|
||||||
!!pesData && !patternUploaded && !isUploading
|
!!pesData &&
|
||||||
|
!patternUploaded &&
|
||||||
|
!isUploading &&
|
||||||
|
!uploadedPesData
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
|
|
|
||||||
|
|
@ -25,14 +25,16 @@ export function PatternSummaryCard() {
|
||||||
);
|
);
|
||||||
|
|
||||||
// Pattern store
|
// Pattern store
|
||||||
const { pesData, currentFileName } = usePatternStore(
|
const { pesData, uploadedPesData, currentFileName } = usePatternStore(
|
||||||
useShallow((state) => ({
|
useShallow((state) => ({
|
||||||
pesData: state.pesData,
|
pesData: state.pesData,
|
||||||
|
uploadedPesData: state.uploadedPesData,
|
||||||
currentFileName: state.currentFileName,
|
currentFileName: state.currentFileName,
|
||||||
})),
|
})),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!pesData) return null;
|
const displayPattern = uploadedPesData || pesData;
|
||||||
|
if (!displayPattern) return null;
|
||||||
|
|
||||||
const canDelete = canDeletePattern(machineStatus);
|
const canDelete = canDeletePattern(machineStatus);
|
||||||
return (
|
return (
|
||||||
|
|
@ -52,7 +54,7 @@ export function PatternSummaryCard() {
|
||||||
</div>
|
</div>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="px-4 pt-0 pb-4">
|
<CardContent className="px-4 pt-0 pb-4">
|
||||||
<PatternInfo pesData={pesData} />
|
<PatternInfo pesData={displayPattern} />
|
||||||
|
|
||||||
{canDelete && (
|
{canDelete && (
|
||||||
<Button
|
<Button
|
||||||
|
|
|
||||||
|
|
@ -52,6 +52,8 @@ export function ProgressMonitor() {
|
||||||
|
|
||||||
// Pattern store
|
// Pattern store
|
||||||
const pesData = usePatternStore((state) => state.pesData);
|
const pesData = usePatternStore((state) => state.pesData);
|
||||||
|
const uploadedPesData = usePatternStore((state) => state.uploadedPesData);
|
||||||
|
const displayPattern = uploadedPesData || pesData;
|
||||||
const currentBlockRef = useRef<HTMLDivElement>(null);
|
const currentBlockRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
// State indicators
|
// State indicators
|
||||||
|
|
@ -60,8 +62,8 @@ export function ProgressMonitor() {
|
||||||
|
|
||||||
// Use PEN stitch count as fallback when machine reports 0 total stitches
|
// Use PEN stitch count as fallback when machine reports 0 total stitches
|
||||||
const totalStitches = patternInfo
|
const totalStitches = patternInfo
|
||||||
? patternInfo.totalStitches === 0 && pesData?.penStitches
|
? patternInfo.totalStitches === 0 && displayPattern?.penStitches
|
||||||
? pesData.penStitches.stitches.length
|
? displayPattern.penStitches.stitches.length
|
||||||
: patternInfo.totalStitches
|
: patternInfo.totalStitches
|
||||||
: 0;
|
: 0;
|
||||||
|
|
||||||
|
|
@ -72,7 +74,7 @@ export function ProgressMonitor() {
|
||||||
|
|
||||||
// Calculate color block information from decoded penStitches
|
// Calculate color block information from decoded penStitches
|
||||||
const colorBlocks = useMemo(() => {
|
const colorBlocks = useMemo(() => {
|
||||||
if (!pesData || !pesData.penStitches) return [];
|
if (!displayPattern || !displayPattern.penStitches) return [];
|
||||||
|
|
||||||
const blocks: Array<{
|
const blocks: Array<{
|
||||||
colorIndex: number;
|
colorIndex: number;
|
||||||
|
|
@ -87,8 +89,8 @@ export function ProgressMonitor() {
|
||||||
}> = [];
|
}> = [];
|
||||||
|
|
||||||
// Use the pre-computed color blocks from decoded PEN data
|
// Use the pre-computed color blocks from decoded PEN data
|
||||||
for (const penBlock of pesData.penStitches.colorBlocks) {
|
for (const penBlock of displayPattern.penStitches.colorBlocks) {
|
||||||
const thread = pesData.threads[penBlock.colorIndex];
|
const thread = displayPattern.threads[penBlock.colorIndex];
|
||||||
blocks.push({
|
blocks.push({
|
||||||
colorIndex: penBlock.colorIndex,
|
colorIndex: penBlock.colorIndex,
|
||||||
threadHex: thread?.hex || "#000000",
|
threadHex: thread?.hex || "#000000",
|
||||||
|
|
@ -103,7 +105,7 @@ export function ProgressMonitor() {
|
||||||
}
|
}
|
||||||
|
|
||||||
return blocks;
|
return blocks;
|
||||||
}, [pesData]);
|
}, [displayPattern]);
|
||||||
|
|
||||||
// Determine current color block based on current stitch
|
// Determine current color block based on current stitch
|
||||||
const currentStitch = sewingProgress?.currentStitch || 0;
|
const currentStitch = sewingProgress?.currentStitch || 0;
|
||||||
|
|
|
||||||
|
|
@ -15,8 +15,17 @@ export class BrowserStorageService implements IStorageService {
|
||||||
pesData: PesPatternData,
|
pesData: PesPatternData,
|
||||||
fileName: string,
|
fileName: string,
|
||||||
patternOffset?: { x: number; y: number },
|
patternOffset?: { x: number; y: number },
|
||||||
|
patternRotation?: number,
|
||||||
|
uploadedPesData?: PesPatternData,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
PatternCacheService.savePattern(uuid, pesData, fileName, patternOffset);
|
PatternCacheService.savePattern(
|
||||||
|
uuid,
|
||||||
|
pesData,
|
||||||
|
fileName,
|
||||||
|
patternOffset,
|
||||||
|
patternRotation,
|
||||||
|
uploadedPesData,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getPatternByUUID(uuid: string): Promise<ICachedPattern | null> {
|
async getPatternByUUID(uuid: string): Promise<ICachedPattern | null> {
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,8 @@ export class ElectronStorageService implements IStorageService {
|
||||||
pesData: PesPatternData,
|
pesData: PesPatternData,
|
||||||
fileName: string,
|
fileName: string,
|
||||||
patternOffset?: { x: number; y: number },
|
patternOffset?: { x: number; y: number },
|
||||||
|
patternRotation?: number,
|
||||||
|
uploadedPesData?: PesPatternData,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
// Convert Uint8Array to array for JSON serialization over IPC
|
// Convert Uint8Array to array for JSON serialization over IPC
|
||||||
const serializable = {
|
const serializable = {
|
||||||
|
|
@ -28,9 +30,16 @@ export class ElectronStorageService implements IStorageService {
|
||||||
...pesData,
|
...pesData,
|
||||||
penData: Array.from(pesData.penData),
|
penData: Array.from(pesData.penData),
|
||||||
},
|
},
|
||||||
|
uploadedPesData: uploadedPesData
|
||||||
|
? {
|
||||||
|
...uploadedPesData,
|
||||||
|
penData: Array.from(uploadedPesData.penData),
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
fileName,
|
fileName,
|
||||||
timestamp: Date.now(),
|
timestamp: Date.now(),
|
||||||
patternOffset,
|
patternOffset,
|
||||||
|
patternRotation,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Fire and forget (sync-like behavior to match interface)
|
// Fire and forget (sync-like behavior to match interface)
|
||||||
|
|
@ -51,6 +60,17 @@ export class ElectronStorageService implements IStorageService {
|
||||||
pattern.pesData.penData = new Uint8Array(pattern.pesData.penData);
|
pattern.pesData.penData = new Uint8Array(pattern.pesData.penData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
pattern &&
|
||||||
|
pattern.uploadedPesData &&
|
||||||
|
Array.isArray(pattern.uploadedPesData.penData)
|
||||||
|
) {
|
||||||
|
// Restore Uint8Array from array for uploadedPesData
|
||||||
|
pattern.uploadedPesData.penData = new Uint8Array(
|
||||||
|
pattern.uploadedPesData.penData,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return pattern;
|
return pattern;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("[ElectronStorage] Failed to get pattern:", err);
|
console.error("[ElectronStorage] Failed to get pattern:", err);
|
||||||
|
|
@ -69,6 +89,17 @@ export class ElectronStorageService implements IStorageService {
|
||||||
pattern.pesData.penData = new Uint8Array(pattern.pesData.penData);
|
pattern.pesData.penData = new Uint8Array(pattern.pesData.penData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
pattern &&
|
||||||
|
pattern.uploadedPesData &&
|
||||||
|
Array.isArray(pattern.uploadedPesData.penData)
|
||||||
|
) {
|
||||||
|
// Restore Uint8Array from array for uploadedPesData
|
||||||
|
pattern.uploadedPesData.penData = new Uint8Array(
|
||||||
|
pattern.uploadedPesData.penData,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return pattern;
|
return pattern;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("[ElectronStorage] Failed to get latest pattern:", err);
|
console.error("[ElectronStorage] Failed to get latest pattern:", err);
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,12 @@ import type { PesPatternData } from "../../formats/import/pesImporter";
|
||||||
|
|
||||||
export interface ICachedPattern {
|
export interface ICachedPattern {
|
||||||
uuid: string;
|
uuid: string;
|
||||||
pesData: PesPatternData;
|
pesData: PesPatternData; // Original unrotated pattern data
|
||||||
|
uploadedPesData?: PesPatternData; // Pattern with rotation applied (what was uploaded to machine)
|
||||||
fileName: string;
|
fileName: string;
|
||||||
timestamp: number;
|
timestamp: number;
|
||||||
patternOffset?: { x: number; y: number };
|
patternOffset?: { x: number; y: number };
|
||||||
|
patternRotation?: number; // Rotation angle in degrees
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IStorageService {
|
export interface IStorageService {
|
||||||
|
|
@ -14,6 +16,8 @@ export interface IStorageService {
|
||||||
pesData: PesPatternData,
|
pesData: PesPatternData,
|
||||||
fileName: string,
|
fileName: string,
|
||||||
patternOffset?: { x: number; y: number },
|
patternOffset?: { x: number; y: number },
|
||||||
|
patternRotation?: number,
|
||||||
|
uploadedPesData?: PesPatternData,
|
||||||
): Promise<void>;
|
): Promise<void>;
|
||||||
|
|
||||||
getPatternByUUID(uuid: string): Promise<ICachedPattern | null>;
|
getPatternByUUID(uuid: string): Promise<ICachedPattern | null>;
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,12 @@ import type { PesPatternData } from "../formats/import/pesImporter";
|
||||||
|
|
||||||
interface CachedPattern {
|
interface CachedPattern {
|
||||||
uuid: string;
|
uuid: string;
|
||||||
pesData: PesPatternData;
|
pesData: PesPatternData; // Original unrotated pattern data
|
||||||
|
uploadedPesData?: PesPatternData; // Pattern with rotation applied (what was uploaded to machine)
|
||||||
fileName: string;
|
fileName: string;
|
||||||
timestamp: number;
|
timestamp: number;
|
||||||
patternOffset?: { x: number; y: number };
|
patternOffset?: { x: number; y: number };
|
||||||
|
patternRotation?: number; // Rotation angle in degrees
|
||||||
}
|
}
|
||||||
|
|
||||||
const CACHE_KEY = "brother_pattern_cache";
|
const CACHE_KEY = "brother_pattern_cache";
|
||||||
|
|
@ -39,6 +41,8 @@ export class PatternCacheService {
|
||||||
pesData: PesPatternData,
|
pesData: PesPatternData,
|
||||||
fileName: string,
|
fileName: string,
|
||||||
patternOffset?: { x: number; y: number },
|
patternOffset?: { x: number; y: number },
|
||||||
|
patternRotation?: number,
|
||||||
|
uploadedPesData?: PesPatternData,
|
||||||
): void {
|
): void {
|
||||||
try {
|
try {
|
||||||
// Convert penData Uint8Array to array for JSON serialization
|
// Convert penData Uint8Array to array for JSON serialization
|
||||||
|
|
@ -47,12 +51,24 @@ export class PatternCacheService {
|
||||||
penData: Array.from(pesData.penData) as unknown as Uint8Array,
|
penData: Array.from(pesData.penData) as unknown as Uint8Array,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Also convert uploadedPesData if present
|
||||||
|
const uploadedPesDataWithArrayPenData = uploadedPesData
|
||||||
|
? {
|
||||||
|
...uploadedPesData,
|
||||||
|
penData: Array.from(
|
||||||
|
uploadedPesData.penData,
|
||||||
|
) as unknown as Uint8Array,
|
||||||
|
}
|
||||||
|
: undefined;
|
||||||
|
|
||||||
const cached: CachedPattern = {
|
const cached: CachedPattern = {
|
||||||
uuid,
|
uuid,
|
||||||
pesData: pesDataWithArrayPenData,
|
pesData: pesDataWithArrayPenData,
|
||||||
|
uploadedPesData: uploadedPesDataWithArrayPenData,
|
||||||
fileName,
|
fileName,
|
||||||
timestamp: Date.now(),
|
timestamp: Date.now(),
|
||||||
patternOffset,
|
patternOffset,
|
||||||
|
patternRotation,
|
||||||
};
|
};
|
||||||
|
|
||||||
localStorage.setItem(CACHE_KEY, JSON.stringify(cached));
|
localStorage.setItem(CACHE_KEY, JSON.stringify(cached));
|
||||||
|
|
@ -63,6 +79,10 @@ export class PatternCacheService {
|
||||||
uuid,
|
uuid,
|
||||||
"Offset:",
|
"Offset:",
|
||||||
patternOffset,
|
patternOffset,
|
||||||
|
"Rotation:",
|
||||||
|
patternRotation,
|
||||||
|
"Has uploaded data:",
|
||||||
|
!!uploadedPesData,
|
||||||
);
|
);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("[PatternCache] Failed to save pattern:", err);
|
console.error("[PatternCache] Failed to save pattern:", err);
|
||||||
|
|
@ -101,11 +121,23 @@ export class PatternCacheService {
|
||||||
pattern.pesData.penData = new Uint8Array(pattern.pesData.penData);
|
pattern.pesData.penData = new Uint8Array(pattern.pesData.penData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Restore Uint8Array from array inside uploadedPesData if present
|
||||||
|
if (
|
||||||
|
pattern.uploadedPesData &&
|
||||||
|
Array.isArray(pattern.uploadedPesData.penData)
|
||||||
|
) {
|
||||||
|
pattern.uploadedPesData.penData = new Uint8Array(
|
||||||
|
pattern.uploadedPesData.penData,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
console.log(
|
console.log(
|
||||||
"[PatternCache] Found cached pattern:",
|
"[PatternCache] Found cached pattern:",
|
||||||
pattern.fileName,
|
pattern.fileName,
|
||||||
"UUID:",
|
"UUID:",
|
||||||
uuid,
|
uuid,
|
||||||
|
"Has uploaded data:",
|
||||||
|
!!pattern.uploadedPesData,
|
||||||
);
|
);
|
||||||
return pattern;
|
return pattern;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|
@ -131,6 +163,16 @@ export class PatternCacheService {
|
||||||
pattern.pesData.penData = new Uint8Array(pattern.pesData.penData);
|
pattern.pesData.penData = new Uint8Array(pattern.pesData.penData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Restore Uint8Array from array inside uploadedPesData if present
|
||||||
|
if (
|
||||||
|
pattern.uploadedPesData &&
|
||||||
|
Array.isArray(pattern.uploadedPesData.penData)
|
||||||
|
) {
|
||||||
|
pattern.uploadedPesData.penData = new Uint8Array(
|
||||||
|
pattern.uploadedPesData.penData,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return pattern;
|
return pattern;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("[PatternCache] Failed to retrieve pattern:", err);
|
console.error("[PatternCache] Failed to retrieve pattern:", err);
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,9 @@ interface MachineState {
|
||||||
resumeFileName: string | null;
|
resumeFileName: string | null;
|
||||||
resumedPattern: {
|
resumedPattern: {
|
||||||
pesData: PesPatternData;
|
pesData: PesPatternData;
|
||||||
|
uploadedPesData?: PesPatternData;
|
||||||
patternOffset?: { x: number; y: number };
|
patternOffset?: { x: number; y: number };
|
||||||
|
patternRotation?: number;
|
||||||
} | null;
|
} | null;
|
||||||
|
|
||||||
// Error state
|
// Error state
|
||||||
|
|
@ -67,9 +69,11 @@ interface MachineState {
|
||||||
refreshServiceCount: () => Promise<void>;
|
refreshServiceCount: () => Promise<void>;
|
||||||
uploadPattern: (
|
uploadPattern: (
|
||||||
penData: Uint8Array,
|
penData: Uint8Array,
|
||||||
pesData: PesPatternData,
|
uploadedPesData: PesPatternData, // Pattern with rotation applied (for machine upload)
|
||||||
fileName: string,
|
fileName: string,
|
||||||
patternOffset?: { x: number; y: number },
|
patternOffset?: { x: number; y: number },
|
||||||
|
patternRotation?: number,
|
||||||
|
originalPesData?: PesPatternData, // Original unrotated pattern (for caching)
|
||||||
) => Promise<void>;
|
) => Promise<void>;
|
||||||
startMaskTrace: () => Promise<void>;
|
startMaskTrace: () => Promise<void>;
|
||||||
startSewing: () => Promise<void>;
|
startSewing: () => Promise<void>;
|
||||||
|
|
@ -78,7 +82,9 @@ interface MachineState {
|
||||||
checkResume: () => Promise<PesPatternData | null>;
|
checkResume: () => Promise<PesPatternData | null>;
|
||||||
loadCachedPattern: () => Promise<{
|
loadCachedPattern: () => Promise<{
|
||||||
pesData: PesPatternData;
|
pesData: PesPatternData;
|
||||||
|
uploadedPesData?: PesPatternData;
|
||||||
patternOffset?: { x: number; y: number };
|
patternOffset?: { x: number; y: number };
|
||||||
|
patternRotation?: number;
|
||||||
} | null>;
|
} | null>;
|
||||||
|
|
||||||
// Internal methods
|
// Internal methods
|
||||||
|
|
@ -137,6 +143,10 @@ export const useMachineStore = create<MachineState>((set, get) => ({
|
||||||
cached.fileName,
|
cached.fileName,
|
||||||
"Offset:",
|
"Offset:",
|
||||||
cached.patternOffset,
|
cached.patternOffset,
|
||||||
|
"Rotation:",
|
||||||
|
cached.patternRotation,
|
||||||
|
"Has uploaded data:",
|
||||||
|
!!cached.uploadedPesData,
|
||||||
);
|
);
|
||||||
console.log("[Resume] Auto-loading cached pattern...");
|
console.log("[Resume] Auto-loading cached pattern...");
|
||||||
set({
|
set({
|
||||||
|
|
@ -144,7 +154,9 @@ export const useMachineStore = create<MachineState>((set, get) => ({
|
||||||
resumeFileName: cached.fileName,
|
resumeFileName: cached.fileName,
|
||||||
resumedPattern: {
|
resumedPattern: {
|
||||||
pesData: cached.pesData,
|
pesData: cached.pesData,
|
||||||
|
uploadedPesData: cached.uploadedPesData,
|
||||||
patternOffset: cached.patternOffset,
|
patternOffset: cached.patternOffset,
|
||||||
|
patternRotation: cached.patternRotation,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -302,9 +314,11 @@ export const useMachineStore = create<MachineState>((set, get) => ({
|
||||||
// Upload pattern to machine
|
// Upload pattern to machine
|
||||||
uploadPattern: async (
|
uploadPattern: async (
|
||||||
penData: Uint8Array,
|
penData: Uint8Array,
|
||||||
pesData: PesPatternData,
|
uploadedPesData: PesPatternData, // Pattern with rotation applied (for machine upload)
|
||||||
fileName: string,
|
fileName: string,
|
||||||
patternOffset?: { x: number; y: number },
|
patternOffset?: { x: number; y: number },
|
||||||
|
patternRotation?: number,
|
||||||
|
originalPesData?: PesPatternData, // Original unrotated pattern (for caching)
|
||||||
) => {
|
) => {
|
||||||
const {
|
const {
|
||||||
isConnected,
|
isConnected,
|
||||||
|
|
@ -321,20 +335,31 @@ export const useMachineStore = create<MachineState>((set, get) => ({
|
||||||
try {
|
try {
|
||||||
set({ error: null, uploadProgress: 0, isUploading: true });
|
set({ error: null, uploadProgress: 0, isUploading: true });
|
||||||
|
|
||||||
|
// Upload to machine using the rotated bounds
|
||||||
const uuid = await service.uploadPattern(
|
const uuid = await service.uploadPattern(
|
||||||
penData,
|
penData,
|
||||||
(progress) => {
|
(progress) => {
|
||||||
set({ uploadProgress: progress });
|
set({ uploadProgress: progress });
|
||||||
},
|
},
|
||||||
pesData.bounds,
|
uploadedPesData.bounds,
|
||||||
patternOffset,
|
patternOffset,
|
||||||
);
|
);
|
||||||
|
|
||||||
set({ uploadProgress: 100 });
|
set({ uploadProgress: 100 });
|
||||||
|
|
||||||
// Cache the pattern with its UUID and offset
|
// Cache the ORIGINAL unrotated pattern with rotation angle AND the uploaded data
|
||||||
|
// This allows us to restore the editable state correctly and ensures the exact
|
||||||
|
// uploaded data is used on resume (prevents inconsistencies from version updates)
|
||||||
|
const pesDataToCache = originalPesData || uploadedPesData;
|
||||||
const uuidStr = uuidToString(uuid);
|
const uuidStr = uuidToString(uuid);
|
||||||
storageService.savePattern(uuidStr, pesData, fileName, patternOffset);
|
storageService.savePattern(
|
||||||
|
uuidStr,
|
||||||
|
pesDataToCache,
|
||||||
|
fileName,
|
||||||
|
patternOffset,
|
||||||
|
patternRotation,
|
||||||
|
uploadedPesData, // Cache the exact uploaded data
|
||||||
|
);
|
||||||
console.log(
|
console.log(
|
||||||
"[Cache] Saved pattern:",
|
"[Cache] Saved pattern:",
|
||||||
fileName,
|
fileName,
|
||||||
|
|
@ -342,6 +367,9 @@ export const useMachineStore = create<MachineState>((set, get) => ({
|
||||||
uuidStr,
|
uuidStr,
|
||||||
"Offset:",
|
"Offset:",
|
||||||
patternOffset,
|
patternOffset,
|
||||||
|
"Rotation:",
|
||||||
|
patternRotation,
|
||||||
|
"(cached original unrotated data + uploaded data)",
|
||||||
);
|
);
|
||||||
|
|
||||||
// Clear resume state since we just uploaded
|
// Clear resume state since we just uploaded
|
||||||
|
|
@ -440,6 +468,7 @@ export const useMachineStore = create<MachineState>((set, get) => ({
|
||||||
uploadProgress: 0,
|
uploadProgress: 0,
|
||||||
resumeAvailable: false,
|
resumeAvailable: false,
|
||||||
resumeFileName: null,
|
resumeFileName: null,
|
||||||
|
resumedPattern: null, // Clear this to prevent auto-reload
|
||||||
});
|
});
|
||||||
|
|
||||||
// Clear uploaded pattern data in pattern store
|
// Clear uploaded pattern data in pattern store
|
||||||
|
|
@ -458,7 +487,9 @@ export const useMachineStore = create<MachineState>((set, get) => ({
|
||||||
// Load cached pattern
|
// Load cached pattern
|
||||||
loadCachedPattern: async (): Promise<{
|
loadCachedPattern: async (): Promise<{
|
||||||
pesData: PesPatternData;
|
pesData: PesPatternData;
|
||||||
|
uploadedPesData?: PesPatternData;
|
||||||
patternOffset?: { x: number; y: number };
|
patternOffset?: { x: number; y: number };
|
||||||
|
patternRotation?: number;
|
||||||
} | null> => {
|
} | null> => {
|
||||||
const { resumeAvailable, service, storageService, refreshPatternInfo } =
|
const { resumeAvailable, service, storageService, refreshPatternInfo } =
|
||||||
get();
|
get();
|
||||||
|
|
@ -477,9 +508,18 @@ export const useMachineStore = create<MachineState>((set, get) => ({
|
||||||
cached.fileName,
|
cached.fileName,
|
||||||
"Offset:",
|
"Offset:",
|
||||||
cached.patternOffset,
|
cached.patternOffset,
|
||||||
|
"Rotation:",
|
||||||
|
cached.patternRotation,
|
||||||
|
"Has uploaded data:",
|
||||||
|
!!cached.uploadedPesData,
|
||||||
);
|
);
|
||||||
await refreshPatternInfo();
|
await refreshPatternInfo();
|
||||||
return { pesData: cached.pesData, patternOffset: cached.patternOffset };
|
return {
|
||||||
|
pesData: cached.pesData,
|
||||||
|
uploadedPesData: cached.uploadedPesData,
|
||||||
|
patternOffset: cached.patternOffset,
|
||||||
|
patternRotation: cached.patternRotation,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ interface PatternState {
|
||||||
setUploadedPattern: (
|
setUploadedPattern: (
|
||||||
uploadedData: PesPatternData,
|
uploadedData: PesPatternData,
|
||||||
uploadedOffset: { x: number; y: number },
|
uploadedOffset: { x: number; y: number },
|
||||||
|
fileName?: string,
|
||||||
) => void;
|
) => void;
|
||||||
clearUploadedPattern: () => void;
|
clearUploadedPattern: () => void;
|
||||||
resetPatternOffset: () => void;
|
resetPatternOffset: () => void;
|
||||||
|
|
@ -69,23 +70,32 @@ export const usePatternStore = create<PatternState>((set) => ({
|
||||||
setUploadedPattern: (
|
setUploadedPattern: (
|
||||||
uploadedData: PesPatternData,
|
uploadedData: PesPatternData,
|
||||||
uploadedOffset: { x: number; y: number },
|
uploadedOffset: { x: number; y: number },
|
||||||
|
fileName?: string,
|
||||||
) => {
|
) => {
|
||||||
set({
|
set({
|
||||||
uploadedPesData: uploadedData,
|
uploadedPesData: uploadedData,
|
||||||
uploadedPatternOffset: uploadedOffset,
|
uploadedPatternOffset: uploadedOffset,
|
||||||
patternUploaded: true,
|
patternUploaded: true,
|
||||||
|
// Optionally set filename if provided (for resume/reconnect scenarios)
|
||||||
|
...(fileName && { currentFileName: fileName }),
|
||||||
});
|
});
|
||||||
console.log("[PatternStore] Uploaded pattern set");
|
console.log("[PatternStore] Uploaded pattern set");
|
||||||
},
|
},
|
||||||
|
|
||||||
// Clear uploaded pattern (called when deleting from machine)
|
// Clear uploaded pattern (called when deleting from machine)
|
||||||
|
// This reverts to pre-upload state, keeping pesData so user can re-adjust and re-upload
|
||||||
clearUploadedPattern: () => {
|
clearUploadedPattern: () => {
|
||||||
|
console.log("[PatternStore] CLEARING uploaded pattern...");
|
||||||
set({
|
set({
|
||||||
uploadedPesData: null,
|
uploadedPesData: null,
|
||||||
uploadedPatternOffset: { x: 0, y: 0 },
|
uploadedPatternOffset: { x: 0, y: 0 },
|
||||||
patternUploaded: false,
|
patternUploaded: false,
|
||||||
|
// Keep pesData, currentFileName, patternOffset, patternRotation
|
||||||
|
// so user can adjust and re-upload
|
||||||
});
|
});
|
||||||
console.log("[PatternStore] Uploaded pattern cleared");
|
console.log(
|
||||||
|
"[PatternStore] Uploaded pattern cleared - back to editable mode",
|
||||||
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
// Reset pattern offset to default
|
// Reset pattern offset to default
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue