mirror of
https://github.com/jhbruhn/respira.git
synced 2026-01-27 10:23:41 +00:00
Fix React/Konva DOM conflicts and zoom reset issues
- Separate Konva container from React-managed overlays to prevent removeChild errors - Move wheel zoom handler into stage initialization to maintain stable reference - Remove zoomLevel from background effect dependencies to prevent zoom reset - Add double-initialization guard for stage creation - Clear refs before stage destruction to prevent race conditions 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
0f40cec8ec
commit
3c8c2d49fd
1 changed files with 60 additions and 53 deletions
|
|
@ -36,6 +36,9 @@ export function PatternCanvas({ pesData, sewingProgress, machineInfo, onPatternO
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!containerRef.current) return;
|
if (!containerRef.current) return;
|
||||||
|
|
||||||
|
// Prevent double initialization
|
||||||
|
if (stageRef.current) return;
|
||||||
|
|
||||||
const container = containerRef.current;
|
const container = containerRef.current;
|
||||||
|
|
||||||
// Create stage
|
// Create stage
|
||||||
|
|
@ -78,7 +81,50 @@ export function PatternCanvas({ pesData, sewingProgress, machineInfo, onPatternO
|
||||||
stage.container().style.cursor = 'grab';
|
stage.container().style.cursor = 'grab';
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Mouse wheel zoom handler
|
||||||
|
const handleWheel = (e: Konva.KonvaEventObject<WheelEvent>) => {
|
||||||
|
e.evt.preventDefault();
|
||||||
|
|
||||||
|
const oldScale = stage.scaleX();
|
||||||
|
const pointer = stage.getPointerPosition();
|
||||||
|
if (!pointer) return;
|
||||||
|
|
||||||
|
const scaleBy = 1.1;
|
||||||
|
const direction = e.evt.deltaY > 0 ? -1 : 1;
|
||||||
|
let newScale = direction > 0 ? oldScale * scaleBy : oldScale / scaleBy;
|
||||||
|
|
||||||
|
// Apply constraints
|
||||||
|
newScale = Math.max(0.1, Math.min(10, newScale));
|
||||||
|
|
||||||
|
// Zoom towards pointer
|
||||||
|
const mousePointTo = {
|
||||||
|
x: (pointer.x - stage.x()) / oldScale,
|
||||||
|
y: (pointer.y - stage.y()) / oldScale,
|
||||||
|
};
|
||||||
|
|
||||||
|
const newPos = {
|
||||||
|
x: pointer.x - mousePointTo.x * newScale,
|
||||||
|
y: pointer.y - mousePointTo.y * newScale,
|
||||||
|
};
|
||||||
|
|
||||||
|
stage.scale({ x: newScale, y: newScale });
|
||||||
|
stage.position(newPos);
|
||||||
|
setZoomLevel(newScale);
|
||||||
|
stage.batchDraw();
|
||||||
|
};
|
||||||
|
|
||||||
|
// Attach wheel event
|
||||||
|
stage.on('wheel', handleWheel);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
|
// Clear refs before destroying to prevent race conditions
|
||||||
|
stageRef.current = null;
|
||||||
|
backgroundLayerRef.current = null;
|
||||||
|
patternLayerRef.current = null;
|
||||||
|
currentPosLayerRef.current = null;
|
||||||
|
patternGroupRef.current = null;
|
||||||
|
|
||||||
|
// Destroy the stage (this removes the canvas from DOM)
|
||||||
stage.destroy();
|
stage.destroy();
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
@ -116,53 +162,6 @@ export function PatternCanvas({ pesData, sewingProgress, machineInfo, onPatternO
|
||||||
return () => resizeObserver.disconnect();
|
return () => resizeObserver.disconnect();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// Mouse wheel zoom handler
|
|
||||||
const handleWheel = useCallback((e: Konva.KonvaEventObject<WheelEvent>) => {
|
|
||||||
e.evt.preventDefault();
|
|
||||||
|
|
||||||
const stage = e.target.getStage();
|
|
||||||
if (!stage) return;
|
|
||||||
|
|
||||||
const oldScale = stage.scaleX();
|
|
||||||
const pointer = stage.getPointerPosition();
|
|
||||||
if (!pointer) return;
|
|
||||||
|
|
||||||
const scaleBy = 1.1;
|
|
||||||
const direction = e.evt.deltaY > 0 ? -1 : 1;
|
|
||||||
let newScale = direction > 0 ? oldScale * scaleBy : oldScale / scaleBy;
|
|
||||||
|
|
||||||
// Apply constraints
|
|
||||||
newScale = Math.max(0.1, Math.min(10, newScale));
|
|
||||||
|
|
||||||
// Zoom towards pointer
|
|
||||||
const mousePointTo = {
|
|
||||||
x: (pointer.x - stage.x()) / oldScale,
|
|
||||||
y: (pointer.y - stage.y()) / oldScale,
|
|
||||||
};
|
|
||||||
|
|
||||||
const newPos = {
|
|
||||||
x: pointer.x - mousePointTo.x * newScale,
|
|
||||||
y: pointer.y - mousePointTo.y * newScale,
|
|
||||||
};
|
|
||||||
|
|
||||||
stage.scale({ x: newScale, y: newScale });
|
|
||||||
stage.position(newPos);
|
|
||||||
setZoomLevel(newScale);
|
|
||||||
stage.batchDraw();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// Attach wheel event handler
|
|
||||||
useEffect(() => {
|
|
||||||
const stage = stageRef.current;
|
|
||||||
if (!stage) return;
|
|
||||||
|
|
||||||
stage.on('wheel', handleWheel);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
stage.off('wheel', handleWheel);
|
|
||||||
};
|
|
||||||
}, [handleWheel]);
|
|
||||||
|
|
||||||
// Helper function to zoom to a specific point
|
// Helper function to zoom to a specific point
|
||||||
const zoomToPoint = useCallback(
|
const zoomToPoint = useCallback(
|
||||||
(point: { x: number; y: number }, newScale: number) => {
|
(point: { x: number; y: number }, newScale: number) => {
|
||||||
|
|
@ -246,10 +245,12 @@ export function PatternCanvas({ pesData, sewingProgress, machineInfo, onPatternO
|
||||||
const initialScale = calculateInitialScale(stage.width(), stage.height(), viewWidth, viewHeight);
|
const initialScale = calculateInitialScale(stage.width(), stage.height(), viewWidth, viewHeight);
|
||||||
initialScaleRef.current = initialScale;
|
initialScaleRef.current = initialScale;
|
||||||
|
|
||||||
// Always reset to initial scale when background is re-rendered (e.g., when pattern or hoop changes)
|
// Only set initial scale if this is the first render (zoom level is still 1)
|
||||||
|
if (zoomLevel === 1) {
|
||||||
stage.scale({ x: initialScale, y: initialScale });
|
stage.scale({ x: initialScale, y: initialScale });
|
||||||
stage.position({ x: stage.width() / 2, y: stage.height() / 2 });
|
stage.position({ x: stage.width() / 2, y: stage.height() / 2 });
|
||||||
setZoomLevel(initialScale);
|
setZoomLevel(initialScale);
|
||||||
|
}
|
||||||
|
|
||||||
// Render background elements
|
// Render background elements
|
||||||
const gridSize = 100; // 10mm grid (100 units in 0.1mm)
|
const gridSize = 100; // 10mm grid (100 units in 0.1mm)
|
||||||
|
|
@ -263,7 +264,7 @@ export function PatternCanvas({ pesData, sewingProgress, machineInfo, onPatternO
|
||||||
// Cache the background layer for performance
|
// Cache the background layer for performance
|
||||||
layer.cache();
|
layer.cache();
|
||||||
layer.batchDraw();
|
layer.batchDraw();
|
||||||
}, [machineInfo, pesData, zoomLevel]);
|
}, [machineInfo, pesData]);
|
||||||
|
|
||||||
// Render pattern layer (stitches and bounds in a draggable group)
|
// Render pattern layer (stitches and bounds in a draggable group)
|
||||||
// This effect only runs when the pattern changes, NOT when sewing progress changes
|
// This effect only runs when the pattern changes, NOT when sewing progress changes
|
||||||
|
|
@ -382,12 +383,18 @@ export function PatternCanvas({ pesData, sewingProgress, machineInfo, onPatternO
|
||||||
return (
|
return (
|
||||||
<div className="canvas-panel">
|
<div className="canvas-panel">
|
||||||
<h2>Pattern Preview</h2>
|
<h2>Pattern Preview</h2>
|
||||||
<div className="canvas-container" ref={containerRef}>
|
<div className="canvas-container">
|
||||||
|
{/* Konva container - separate from React-managed overlays */}
|
||||||
|
<div ref={containerRef} style={{ width: '100%', height: '100%', position: 'absolute' }} />
|
||||||
|
|
||||||
|
{/* Placeholder overlay when no pattern is loaded */}
|
||||||
{!pesData && (
|
{!pesData && (
|
||||||
<div className="canvas-placeholder">
|
<div className="canvas-placeholder">
|
||||||
Load a PES file to preview the pattern
|
Load a PES file to preview the pattern
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Pattern info overlays */}
|
||||||
{pesData && (
|
{pesData && (
|
||||||
<>
|
<>
|
||||||
{/* Thread Legend Overlay */}
|
{/* Thread Legend Overlay */}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue