新增素材拖拽桥接 hook,承接素材拖向画布或文件夹的全局 pointer 监听 恢复认证弹窗 portal 渲染,避免全屏画布遮住账号入口 优化画布背景设置面板,补回当前色、色域、色相、预设、HEX 和恢复默认 补充素材拖拽、认证弹窗和背景面板回归测试并更新文档与 TRACKING
141 lines
4.7 KiB
TypeScript
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]);
|
|
}
|