mirror of
https://github.com/jhbruhn/respira.git
synced 2026-01-27 02:13:41 +00:00
fix: Store original and uploaded pattern data to prevent rotation inconsistencies
Store both original unrotated pesData and uploaded rotated pesData in cache to ensure exact consistency on resume and prevent issues from algorithm changes between versions. This fixes rotation/position reset issues after page reload. - Cache original unrotated pattern + rotation angle for editing - Cache exact uploaded pattern data sent to machine - Restore original offset after loading cached pattern - Use cached uploaded data on resume instead of recalculating 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
3ec9dda235
commit
2b5e1d763b
12 changed files with 287 additions and 36 deletions
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,10 @@ 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,11 @@ 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 +83,11 @@ 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,22 @@ 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 +77,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 +119,18 @@ 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 +156,11 @@ 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