mirror of
https://github.com/jhbruhn/respira.git
synced 2026-01-27 10:23:41 +00:00
Compare commits
No commits in common. "3ba87ba1921c9ca37d7fbdb496584a33a137377e" and "212d21e0656ad5d4d672b7ddb7ad46cf1d230a29" have entirely different histories.
3ba87ba192
...
212d21e065
5 changed files with 38 additions and 116 deletions
|
|
@ -46,7 +46,7 @@ export const Grid = memo(({ gridSize, bounds, machineInfo }: GridProps) => {
|
|||
const gridColor = canvasColors.grid();
|
||||
|
||||
return (
|
||||
<Group name="grid" listening={false}>
|
||||
<Group name="grid">
|
||||
{lines.verticalLines.map((points, i) => (
|
||||
<Line
|
||||
key={`v-${i}`}
|
||||
|
|
@ -73,7 +73,7 @@ export const Origin = memo(() => {
|
|||
const originColor = canvasColors.origin();
|
||||
|
||||
return (
|
||||
<Group name="origin" listening={false}>
|
||||
<Group name="origin">
|
||||
<Line points={[-10, 0, 10, 0]} stroke={originColor} strokeWidth={2} />
|
||||
<Line points={[0, -10, 0, 10]} stroke={originColor} strokeWidth={2} />
|
||||
</Group>
|
||||
|
|
@ -93,7 +93,7 @@ export const Hoop = memo(({ machineInfo }: HoopProps) => {
|
|||
const hoopColor = canvasColors.hoop();
|
||||
|
||||
return (
|
||||
<Group name="hoop" listening={false}>
|
||||
<Group name="hoop">
|
||||
<Rect
|
||||
x={hoopLeft}
|
||||
y={hoopTop}
|
||||
|
|
|
|||
|
|
@ -74,8 +74,6 @@ export function PatternCanvas() {
|
|||
handleZoomIn,
|
||||
handleZoomOut,
|
||||
handleZoomReset,
|
||||
handleStageDragStart,
|
||||
handleStageDragEnd,
|
||||
} = useCanvasViewport({
|
||||
containerRef,
|
||||
pesData,
|
||||
|
|
@ -167,8 +165,16 @@ export function PatternCanvas() {
|
|||
scaleY={stageScale}
|
||||
draggable
|
||||
onWheel={handleWheel}
|
||||
onDragStart={handleStageDragStart}
|
||||
onDragEnd={handleStageDragEnd}
|
||||
onDragStart={() => {
|
||||
if (stageRef.current) {
|
||||
stageRef.current.container().style.cursor = "grabbing";
|
||||
}
|
||||
}}
|
||||
onDragEnd={() => {
|
||||
if (stageRef.current) {
|
||||
stageRef.current.container().style.cursor = "grab";
|
||||
}
|
||||
}}
|
||||
ref={(node) => {
|
||||
stageRef.current = node;
|
||||
if (node) {
|
||||
|
|
@ -176,8 +182,8 @@ export function PatternCanvas() {
|
|||
}
|
||||
}}
|
||||
>
|
||||
{/* Background layer: grid, origin, hoop - static, no event listening */}
|
||||
<Layer listening={false}>
|
||||
{/* Background layer: grid, origin, hoop */}
|
||||
<Layer>
|
||||
{displayPattern && (
|
||||
<>
|
||||
<Grid
|
||||
|
|
|
|||
|
|
@ -2,16 +2,10 @@
|
|||
* useCanvasViewport Hook
|
||||
*
|
||||
* Manages canvas viewport state including zoom, pan, and container size
|
||||
* Handles wheel zoom, button zoom operations, and stage drag cursor updates
|
||||
* Handles wheel zoom and button zoom operations
|
||||
*/
|
||||
|
||||
import {
|
||||
useState,
|
||||
useEffect,
|
||||
useCallback,
|
||||
useRef,
|
||||
type RefObject,
|
||||
} from "react";
|
||||
import { useState, useEffect, useCallback, type RefObject } from "react";
|
||||
import type Konva from "konva";
|
||||
import type { PesPatternData } from "../../formats/import/pesImporter";
|
||||
import type { MachineInfo } from "../../types/machine";
|
||||
|
|
@ -93,12 +87,7 @@ export function useCanvasViewport({
|
|||
setStagePos({ x: containerSize.width / 2, y: containerSize.height / 2 });
|
||||
}
|
||||
|
||||
// Wheel zoom handler with RAF throttling and delta accumulation
|
||||
const wheelThrottleRef = useRef<number | null>(null);
|
||||
const accumulatedDeltaRef = useRef<number>(0);
|
||||
const lastPointerRef = useRef<{ x: number; y: number } | null>(null);
|
||||
const lastStageRef = useRef<Konva.Stage | null>(null);
|
||||
|
||||
// Wheel zoom handler
|
||||
const handleWheel = useCallback((e: Konva.KonvaEventObject<WheelEvent>) => {
|
||||
e.evt.preventDefault();
|
||||
|
||||
|
|
@ -108,60 +97,24 @@ export function useCanvasViewport({
|
|||
const pointer = stage.getPointerPosition();
|
||||
if (!pointer) return;
|
||||
|
||||
// Accumulate deltaY from all events during throttle period
|
||||
accumulatedDeltaRef.current += e.evt.deltaY;
|
||||
lastPointerRef.current = pointer;
|
||||
lastStageRef.current = stage;
|
||||
const scaleBy = 1.1;
|
||||
const direction = e.evt.deltaY > 0 ? -1 : 1;
|
||||
|
||||
// Skip if throttle already in progress
|
||||
if (wheelThrottleRef.current !== null) {
|
||||
return;
|
||||
}
|
||||
setStageScale((oldScale) => {
|
||||
const newScale = Math.max(
|
||||
0.1,
|
||||
Math.min(direction > 0 ? oldScale * scaleBy : oldScale / scaleBy, 2),
|
||||
);
|
||||
|
||||
// Schedule update on next animation frame (~16ms)
|
||||
wheelThrottleRef.current = requestAnimationFrame(() => {
|
||||
const accumulatedDelta = accumulatedDeltaRef.current;
|
||||
const pointer = lastPointerRef.current;
|
||||
const stage = lastStageRef.current;
|
||||
// Zoom towards pointer
|
||||
setStagePos((prevPos) =>
|
||||
calculateZoomToPoint(oldScale, newScale, pointer, prevPos),
|
||||
);
|
||||
|
||||
if (!pointer || !stage || accumulatedDelta === 0) {
|
||||
wheelThrottleRef.current = null;
|
||||
accumulatedDeltaRef.current = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
const scaleBy = 1.1;
|
||||
const direction = accumulatedDelta > 0 ? -1 : 1;
|
||||
|
||||
setStageScale((oldScale) => {
|
||||
const newScale = Math.max(
|
||||
0.1,
|
||||
Math.min(direction > 0 ? oldScale * scaleBy : oldScale / scaleBy, 2),
|
||||
);
|
||||
|
||||
// Zoom towards pointer
|
||||
setStagePos((prevPos) =>
|
||||
calculateZoomToPoint(oldScale, newScale, pointer, prevPos),
|
||||
);
|
||||
|
||||
return newScale;
|
||||
});
|
||||
|
||||
// Reset accumulator and throttle
|
||||
wheelThrottleRef.current = null;
|
||||
accumulatedDeltaRef.current = 0;
|
||||
return newScale;
|
||||
});
|
||||
}, []);
|
||||
|
||||
// Cleanup wheel throttle on unmount
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (wheelThrottleRef.current !== null) {
|
||||
cancelAnimationFrame(wheelThrottleRef.current);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
// Zoom control handlers
|
||||
const handleZoomIn = useCallback(() => {
|
||||
setStageScale((oldScale) => {
|
||||
|
|
@ -202,27 +155,6 @@ export function useCanvasViewport({
|
|||
setStagePos({ x: containerSize.width / 2, y: containerSize.height / 2 });
|
||||
}, [initialScale, containerSize]);
|
||||
|
||||
// Stage drag handlers - cursor updates immediately for better UX
|
||||
const handleStageDragStart = useCallback(
|
||||
(e: Konva.KonvaEventObject<DragEvent>) => {
|
||||
const stage = e.target.getStage();
|
||||
if (stage) {
|
||||
stage.container().style.cursor = "grabbing";
|
||||
}
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const handleStageDragEnd = useCallback(
|
||||
(e: Konva.KonvaEventObject<DragEvent>) => {
|
||||
const stage = e.target.getStage();
|
||||
if (stage) {
|
||||
stage.container().style.cursor = "grab";
|
||||
}
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
return {
|
||||
// State
|
||||
stagePos,
|
||||
|
|
@ -234,7 +166,5 @@ export function useCanvasViewport({
|
|||
handleZoomIn,
|
||||
handleZoomOut,
|
||||
handleZoomReset,
|
||||
handleStageDragStart,
|
||||
handleStageDragEnd,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -113,8 +113,7 @@ export function usePatternTransform({
|
|||
setPatternOffset(centerOffset.x, centerOffset.y);
|
||||
}, [pesData, setPatternOffset]);
|
||||
|
||||
// Pattern drag handler - only updates state when drag is complete
|
||||
// Konva handles the visual drag internally, no need to update React state during drag
|
||||
// Pattern drag handlers
|
||||
const handlePatternDragEnd = useCallback(
|
||||
(e: Konva.KonvaEventObject<DragEvent>) => {
|
||||
const newOffset = {
|
||||
|
|
|
|||
|
|
@ -5,9 +5,6 @@ import { onPatternDeleted } from "./storeEvents";
|
|||
import { calculatePatternCenter } from "../components/PatternCanvas/patternCanvasHelpers";
|
||||
import { calculateRotatedBounds } from "../utils/rotationUtils";
|
||||
|
||||
// Conditional logging for development only
|
||||
const isDev = import.meta.env.DEV;
|
||||
|
||||
interface PatternState {
|
||||
// Original pattern (pre-upload)
|
||||
pesData: PesPatternData | null;
|
||||
|
|
@ -82,17 +79,13 @@ export const usePatternStore = create<PatternState>((set) => ({
|
|||
// Update pattern offset (for original pattern only)
|
||||
setPatternOffset: (x: number, y: number) => {
|
||||
set({ patternOffset: { x, y } });
|
||||
if (isDev) {
|
||||
console.log("[PatternStore] Pattern offset changed:", { x, y });
|
||||
}
|
||||
console.log("[PatternStore] Pattern offset changed:", { x, y });
|
||||
},
|
||||
|
||||
// Set pattern rotation (for original pattern only)
|
||||
setPatternRotation: (rotation: number) => {
|
||||
set({ patternRotation: rotation % 360 });
|
||||
if (isDev) {
|
||||
console.log("[PatternStore] Pattern rotation changed:", rotation);
|
||||
}
|
||||
console.log("[PatternStore] Pattern rotation changed:", rotation);
|
||||
},
|
||||
|
||||
// Set uploaded pattern data (called after upload completes)
|
||||
|
|
@ -108,17 +101,13 @@ export const usePatternStore = create<PatternState>((set) => ({
|
|||
// Optionally set filename if provided (for resume/reconnect scenarios)
|
||||
...(fileName && { currentFileName: fileName }),
|
||||
});
|
||||
if (isDev) {
|
||||
console.log("[PatternStore] Uploaded pattern set");
|
||||
}
|
||||
console.log("[PatternStore] Uploaded pattern set");
|
||||
},
|
||||
|
||||
// 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: () => {
|
||||
if (isDev) {
|
||||
console.log("[PatternStore] CLEARING uploaded pattern...");
|
||||
}
|
||||
console.log("[PatternStore] CLEARING uploaded pattern...");
|
||||
set({
|
||||
uploadedPesData: null,
|
||||
uploadedPatternOffset: { x: 0, y: 0 },
|
||||
|
|
@ -126,11 +115,9 @@ export const usePatternStore = create<PatternState>((set) => ({
|
|||
// Keep pesData, currentFileName, patternOffset, patternRotation
|
||||
// so user can adjust and re-upload
|
||||
});
|
||||
if (isDev) {
|
||||
console.log(
|
||||
"[PatternStore] Uploaded pattern cleared - back to editable mode",
|
||||
);
|
||||
}
|
||||
console.log(
|
||||
"[PatternStore] Uploaded pattern cleared - back to editable mode",
|
||||
);
|
||||
},
|
||||
|
||||
// Reset pattern offset to default
|
||||
|
|
|
|||
Loading…
Reference in a new issue