mirror of
https://github.com/johndoe6345789/metabuilder.git
synced 2026-04-24 13:54:57 +00:00
168 lines
3.9 KiB
TypeScript
168 lines
3.9 KiB
TypeScript
/**
|
|
* useWorkflowCanvas Hook
|
|
* Manages canvas transform state: zoom, pan, viewport
|
|
*/
|
|
|
|
import { useState, useCallback, RefObject } from 'react';
|
|
|
|
export interface CanvasTransform {
|
|
zoom: number;
|
|
pan: { x: number; y: number };
|
|
}
|
|
|
|
export interface UseWorkflowCanvasReturn {
|
|
// State
|
|
zoom: number;
|
|
pan: { x: number; y: number };
|
|
isPanning: boolean;
|
|
|
|
// Zoom controls
|
|
zoomIn: () => void;
|
|
zoomOut: () => void;
|
|
resetZoom: () => void;
|
|
setZoom: (zoom: number) => void;
|
|
|
|
// Pan controls
|
|
setPan: (x: number, y: number) => void;
|
|
panBy: (dx: number, dy: number) => void;
|
|
resetPan: () => void;
|
|
|
|
// Pan interaction handlers
|
|
startPan: (clientX: number, clientY: number) => void;
|
|
updatePan: (clientX: number, clientY: number) => void;
|
|
endPan: () => void;
|
|
|
|
// Combined reset
|
|
resetView: () => void;
|
|
|
|
// Screen/canvas coordinate conversion
|
|
screenToCanvas: (screenX: number, screenY: number, canvasRect: DOMRect) => { x: number; y: number };
|
|
canvasToScreen: (canvasX: number, canvasY: number, canvasRect: DOMRect) => { x: number; y: number };
|
|
}
|
|
|
|
export interface UseWorkflowCanvasOptions {
|
|
initialZoom?: number;
|
|
initialPan?: { x: number; y: number };
|
|
minZoom?: number;
|
|
maxZoom?: number;
|
|
zoomStep?: number;
|
|
}
|
|
|
|
export function useWorkflowCanvas(options: UseWorkflowCanvasOptions = {}): UseWorkflowCanvasReturn {
|
|
const {
|
|
initialZoom = 1,
|
|
initialPan = { x: 0, y: 0 },
|
|
minZoom = 0.25,
|
|
maxZoom = 2,
|
|
zoomStep = 0.25,
|
|
} = options;
|
|
|
|
const [zoom, setZoomState] = useState(initialZoom);
|
|
const [pan, setPanState] = useState(initialPan);
|
|
const [isPanning, setIsPanning] = useState(false);
|
|
const [panStart, setPanStart] = useState({ x: 0, y: 0 });
|
|
|
|
// Clamp zoom to valid range
|
|
const clampZoom = useCallback(
|
|
(value: number) => Math.min(maxZoom, Math.max(minZoom, value)),
|
|
[minZoom, maxZoom]
|
|
);
|
|
|
|
// Zoom controls
|
|
const zoomIn = useCallback(() => {
|
|
setZoomState((prev) => clampZoom(prev + zoomStep));
|
|
}, [clampZoom, zoomStep]);
|
|
|
|
const zoomOut = useCallback(() => {
|
|
setZoomState((prev) => clampZoom(prev - zoomStep));
|
|
}, [clampZoom, zoomStep]);
|
|
|
|
const resetZoom = useCallback(() => {
|
|
setZoomState(1);
|
|
}, []);
|
|
|
|
const setZoom = useCallback(
|
|
(value: number) => {
|
|
setZoomState(clampZoom(value));
|
|
},
|
|
[clampZoom]
|
|
);
|
|
|
|
// Pan controls
|
|
const setPan = useCallback((x: number, y: number) => {
|
|
setPanState({ x, y });
|
|
}, []);
|
|
|
|
const panBy = useCallback((dx: number, dy: number) => {
|
|
setPanState((prev) => ({ x: prev.x + dx, y: prev.y + dy }));
|
|
}, []);
|
|
|
|
const resetPan = useCallback(() => {
|
|
setPanState({ x: 0, y: 0 });
|
|
}, []);
|
|
|
|
// Pan interaction handlers
|
|
const startPan = useCallback(
|
|
(clientX: number, clientY: number) => {
|
|
setIsPanning(true);
|
|
setPanStart({ x: clientX - pan.x, y: clientY - pan.y });
|
|
},
|
|
[pan]
|
|
);
|
|
|
|
const updatePan = useCallback(
|
|
(clientX: number, clientY: number) => {
|
|
if (isPanning) {
|
|
setPanState({ x: clientX - panStart.x, y: clientY - panStart.y });
|
|
}
|
|
},
|
|
[isPanning, panStart]
|
|
);
|
|
|
|
const endPan = useCallback(() => {
|
|
setIsPanning(false);
|
|
}, []);
|
|
|
|
// Combined reset
|
|
const resetView = useCallback(() => {
|
|
setZoomState(1);
|
|
setPanState({ x: 0, y: 0 });
|
|
}, []);
|
|
|
|
// Coordinate conversion
|
|
const screenToCanvas = useCallback(
|
|
(screenX: number, screenY: number, canvasRect: DOMRect) => ({
|
|
x: (screenX - canvasRect.left - pan.x) / zoom,
|
|
y: (screenY - canvasRect.top - pan.y) / zoom,
|
|
}),
|
|
[pan, zoom]
|
|
);
|
|
|
|
const canvasToScreen = useCallback(
|
|
(canvasX: number, canvasY: number, canvasRect: DOMRect) => ({
|
|
x: canvasX * zoom + pan.x + canvasRect.left,
|
|
y: canvasY * zoom + pan.y + canvasRect.top,
|
|
}),
|
|
[pan, zoom]
|
|
);
|
|
|
|
return {
|
|
zoom,
|
|
pan,
|
|
isPanning,
|
|
zoomIn,
|
|
zoomOut,
|
|
resetZoom,
|
|
setZoom,
|
|
setPan,
|
|
panBy,
|
|
resetPan,
|
|
startPan,
|
|
updatePan,
|
|
endPan,
|
|
resetView,
|
|
screenToCanvas,
|
|
canvasToScreen,
|
|
};
|
|
}
|