Files
Genarrative/src/components/image-editor/useImageCanvasAssetPointerDragBridge.ts
kdletters cdc823611b 拆分图片画布素材拖拽桥接
新增素材拖拽桥接 hook,承接素材拖向画布或文件夹的全局 pointer 监听

恢复认证弹窗 portal 渲染,避免全屏画布遮住账号入口

优化画布背景设置面板,补回当前色、色域、色相、预设、HEX 和恢复默认

补充素材拖拽、认证弹窗和背景面板回归测试并更新文档与 TRACKING
2026-06-17 12:20:04 +08:00

141 lines
4.7 KiB
TypeScript

import { type RefObject, useEffect, useRef } from 'react';
import type {
AssetPointerDragState,
EditorAsset,
} from './ImageCanvasEditorTypes';
type CanvasPoint = { x: number; y: number };
type UseImageCanvasAssetPointerDragBridgeOptions = {
assetPointerDragRef: RefObject<AssetPointerDragState | null>;
suppressAssetClickRef: RefObject<boolean>;
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]);
}