import { type RefObject, useEffect, useRef } from 'react'; import type { AssetPointerDragState, EditorAsset, } from './ImageCanvasEditorTypes'; type CanvasPoint = { x: number; y: number }; type UseImageCanvasAssetPointerDragBridgeOptions = { assetPointerDragRef: RefObject; suppressAssetClickRef: RefObject; assets: EditorAsset[]; resolveAssetFolderId: (clientX: number, clientY: number) => string | null; resolveCanvasPoint: (clientX: number, clientY: number) => CanvasPoint | null; setAssetPointerDrag: (dragState: AssetPointerDragState | null) => void; setUploadDropTarget: (target: 'canvas' | 'assets' | null) => void; updateAssetMoveDropFolder: (folderId: string | null) => void; moveAssetToFolder: (assetId: string, folderId: string) => void; addAssetLayer: (asset: EditorAsset, position?: CanvasPoint) => void; }; export function useImageCanvasAssetPointerDragBridge({ assetPointerDragRef, suppressAssetClickRef, assets, resolveAssetFolderId, resolveCanvasPoint, setAssetPointerDrag, setUploadDropTarget, updateAssetMoveDropFolder, moveAssetToFolder, addAssetLayer, }: UseImageCanvasAssetPointerDragBridgeOptions) { const assetsRef = useRef(assets); const callbacksRef = useRef({ resolveAssetFolderId, resolveCanvasPoint, setAssetPointerDrag, setUploadDropTarget, updateAssetMoveDropFolder, moveAssetToFolder, addAssetLayer, }); useEffect(() => { assetsRef.current = assets; }, [assets]); callbacksRef.current = { resolveAssetFolderId, resolveCanvasPoint, setAssetPointerDrag, setUploadDropTarget, updateAssetMoveDropFolder, moveAssetToFolder, addAssetLayer, }; useEffect(() => { const updatePointerDrag = (event: PointerEvent) => { const currentDrag = assetPointerDragRef.current; if (!currentDrag || currentDrag.pointerId !== event.pointerId) { return; } const { resolveAssetFolderId: resolveFolder, resolveCanvasPoint: resolvePoint, setAssetPointerDrag: setPointerDrag, setUploadDropTarget: setDropTarget, updateAssetMoveDropFolder: updateDropFolder, } = callbacksRef.current; const distance = Math.hypot( event.clientX - currentDrag.startClientX, event.clientY - currentDrag.startClientY, ); const dropFolderId = resolveFolder(event.clientX, event.clientY); const nextDrag: AssetPointerDragState = { ...currentDrag, currentClientX: event.clientX, currentClientY: event.clientY, active: currentDrag.active || distance > 4, dropFolderId, }; assetPointerDragRef.current = nextDrag; setPointerDrag(nextDrag); setDropTarget(resolvePoint(event.clientX, event.clientY) ? 'canvas' : null); updateDropFolder(dropFolderId); }; const finishPointerDrag = (event: PointerEvent) => { const currentDrag = assetPointerDragRef.current; if (!currentDrag || currentDrag.pointerId !== event.pointerId) { return; } const { resolveAssetFolderId: resolveFolder, resolveCanvasPoint: resolvePoint, setAssetPointerDrag: setPointerDrag, setUploadDropTarget: setDropTarget, updateAssetMoveDropFolder: updateDropFolder, moveAssetToFolder: moveToFolder, addAssetLayer: addLayer, } = callbacksRef.current; const canvasPoint = resolvePoint(event.clientX, event.clientY); const dropFolderId = resolveFolder(event.clientX, event.clientY) ?? currentDrag.dropFolderId; const draggedAsset = assetsRef.current.find( (asset) => asset.id === currentDrag.assetId, ); assetPointerDragRef.current = null; setPointerDrag(null); setDropTarget(null); updateDropFolder(null); if (!currentDrag.active || !draggedAsset) { return; } suppressAssetClickRef.current = true; window.setTimeout(() => { suppressAssetClickRef.current = false; }, 0); if (dropFolderId && dropFolderId !== draggedAsset.folderId) { moveToFolder(draggedAsset.id, dropFolderId); return; } if (canvasPoint) { addLayer(draggedAsset, canvasPoint); } }; window.addEventListener('pointermove', updatePointerDrag); window.addEventListener('pointerup', finishPointerDrag); window.addEventListener('pointercancel', finishPointerDrag); return () => { window.removeEventListener('pointermove', updatePointerDrag); window.removeEventListener('pointerup', finishPointerDrag); window.removeEventListener('pointercancel', finishPointerDrag); }; }, [assetPointerDragRef, suppressAssetClickRef]); }