@@ -4,15 +4,18 @@ import { nanoid } from "nanoid";
|
||||
|
||||
import type { PendingConnectionCreate } from "@/components/canvas/canvas-workspace-overlays";
|
||||
import { getNodeSpec } from "@/constant/canvas";
|
||||
import { canvasConnectionError } from "@/lib/canvas/canvas-connection-policy";
|
||||
import { attachNodeToStoryboardRow, createCanvasNode, getConnectionTargetAnchor, isHiddenBatchChild, normalizeConnection, storyboardHandleAtY, storyboardPromptTemplateMetadata, storyboardRowFromHandle } from "@/lib/canvas/canvas-project-domain";
|
||||
import { createCanvasDrawingFromImage } from "@/lib/canvas/canvas-drawing-storage";
|
||||
import { isDrawingEngineAvailable, type CanvasDrawingEngine } from "@/lib/canvas/canvas-drawing-engine";
|
||||
import { isFrameNode, isNodeHiddenByCollapsedFrame } from "@/lib/canvas/canvas-frame";
|
||||
import type { AiConfig } from "@/stores/use-config-store";
|
||||
import { useUserStore } from "@/stores/use-user-store";
|
||||
import { CanvasNodeType, type CanvasConnection, type CanvasNodeData, type ConnectionHandle, type ContextMenuState, type Position, type ViewportTransform } from "@/types/canvas";
|
||||
|
||||
type UseCanvasConnectionControllerOptions = {
|
||||
projectId: string;
|
||||
config: AiConfig;
|
||||
defaultDrawingEngine: CanvasDrawingEngine;
|
||||
nodesRef: { current: CanvasNodeData[] };
|
||||
connectionsRef: { current: CanvasConnection[] };
|
||||
@@ -41,6 +44,7 @@ const NODE_STATUS_IDLE = "idle" as const;
|
||||
|
||||
export function useCanvasConnectionController({
|
||||
projectId,
|
||||
config,
|
||||
defaultDrawingEngine,
|
||||
nodesRef,
|
||||
connectionsRef,
|
||||
@@ -105,6 +109,11 @@ export function useCanvasConnectionController({
|
||||
const toHandleId = toNodeId === current.nodeId ? current.handleId : targetHandleId;
|
||||
const fromAnchorRatio = fromNodeId === current.nodeId ? current.anchorRatio : targetAnchorRatio;
|
||||
const toAnchorRatio = toNodeId === current.nodeId ? current.anchorRatio : targetAnchorRatio;
|
||||
const policyError = canvasConnectionError(config, nodesRef.current, connectionsRef.current, { fromNodeId, toNodeId });
|
||||
if (policyError) {
|
||||
message.warning(policyError);
|
||||
return;
|
||||
}
|
||||
const exists = connectionsRef.current.find((item) => item.fromNodeId === fromNodeId && item.toNodeId === toNodeId && item.fromHandleId === fromHandleId && item.toHandleId === toHandleId);
|
||||
if (exists) {
|
||||
setConnections((currentConnections) => currentConnections.map((item) => item.id === exists.id ? { ...item, fromAnchorRatio, toAnchorRatio } : item));
|
||||
@@ -113,7 +122,7 @@ export function useCanvasConnectionController({
|
||||
setNodes((currentNodes) => attachNodeToStoryboardRow(currentNodes, { fromNodeId, toNodeId, fromHandleId, toHandleId }));
|
||||
}
|
||||
setContextMenu(null);
|
||||
}, [connectionsRef, message, nodesRef, setConnections, setContextMenu, setNodes]);
|
||||
}, [config, connectionsRef, message, nodesRef, setConnections, setContextMenu, setNodes]);
|
||||
|
||||
const createConnectedNode = useCallback(async (type: CanvasNodeType.Image | CanvasNodeType.Text | CanvasNodeType.Script | CanvasNodeType.Video | CanvasNodeType.Audio | CanvasNodeType.Drawing, pending: PendingConnectionCreate) => {
|
||||
if (type === CanvasNodeType.Drawing && !isDrawingEngineAvailable(defaultDrawingEngine, tldrawLicenseKey)) {
|
||||
@@ -149,6 +158,11 @@ export function useCanvasConnectionController({
|
||||
message.warning("配置节点之间不能连接");
|
||||
return;
|
||||
}
|
||||
const policyError = canvasConnectionError(config, [...nodesRef.current, newNode], connectionsRef.current, connection);
|
||||
if (policyError) {
|
||||
message.warning(policyError);
|
||||
return;
|
||||
}
|
||||
if (type === CanvasNodeType.Drawing) {
|
||||
const drawingSourceNode = nodesRef.current.find((node) => node.id === pending.connection.nodeId);
|
||||
const sourceUrl = drawingSourceNode?.type === CanvasNodeType.Image ? drawingSourceNode.metadata?.content : "";
|
||||
@@ -192,7 +206,16 @@ export function useCanvasConnectionController({
|
||||
else if (type !== CanvasNodeType.Text && type !== CanvasNodeType.Script && type !== CanvasNodeType.Audio) setDialogNodeId(newNode.id);
|
||||
closeConnectionCreateMenu();
|
||||
setConnecting(null);
|
||||
}, [closeConnectionCreateMenu, defaultDrawingEngine, message, nodesRef, projectId, setConnecting, setConnections, setDialogNodeId, setDrawingNodeId, setNodes, setSelectedConnectionId, setSelectedNodeIds, tldrawLicenseKey]);
|
||||
}, [closeConnectionCreateMenu, config, connectionsRef, defaultDrawingEngine, message, nodesRef, projectId, setConnecting, setConnections, setDialogNodeId, setDrawingNodeId, setNodes, setSelectedConnectionId, setSelectedNodeIds, tldrawLicenseKey]);
|
||||
|
||||
const getConnectionCreateDisabledReason = useCallback((type: CanvasNodeType.Image | CanvasNodeType.Text | CanvasNodeType.Script | CanvasNodeType.Video | CanvasNodeType.Audio | CanvasNodeType.Drawing, pending: PendingConnectionCreate) => {
|
||||
const spec = getNodeSpec(type);
|
||||
const pendingNode: CanvasNodeData = { id: "__pending-connection-node__", type, title: "", position: pending.position, width: spec.width, height: spec.height };
|
||||
const pendingNodes = [...nodesRef.current, pendingNode];
|
||||
const connection = normalizeConnection(pending.connection.nodeId, pendingNode.id, pendingNodes, pending.connection.handleType);
|
||||
if (!connection) return "当前节点类型不能这样连接";
|
||||
return canvasConnectionError(config, pendingNodes, connectionsRef.current, connection);
|
||||
}, [config, connectionsRef, nodesRef]);
|
||||
|
||||
const getConnectionDropTarget = useCallback((clientX: number, clientY: number, current: ConnectionHandle): ConnectionDropTarget => {
|
||||
const world = screenToCanvas(clientX, clientY);
|
||||
@@ -221,7 +244,8 @@ export function useCanvasConnectionController({
|
||||
const hitsExpanded = world.x >= node.position.x - padding && world.x <= node.position.x + node.width + padding && world.y >= node.position.y - padding && world.y <= node.position.y + node.height + padding;
|
||||
if (!hitsHandle && !hitsInside && !hitsExpanded) return;
|
||||
isNearNode = true;
|
||||
if (node.id === current.nodeId || !normalizeConnection(current.nodeId, node.id, nodesRef.current, current.handleType)) return;
|
||||
const normalized = node.id === current.nodeId ? null : normalizeConnection(current.nodeId, node.id, nodesRef.current, current.handleType);
|
||||
if (!normalized || canvasConnectionError(config, nodesRef.current, connectionsRef.current, normalized)) return;
|
||||
const priority = hitsInside ? 0 : hitsHandle ? 1 : 2;
|
||||
if (priority < bestPriority) {
|
||||
bestNodeId = node.id;
|
||||
@@ -231,7 +255,7 @@ export function useCanvasConnectionController({
|
||||
}
|
||||
});
|
||||
return { nodeId: bestNodeId, handleId: bestHandleId, anchorRatio: bestAnchorRatio, isNearNode };
|
||||
}, [nodesRef, screenToCanvas, scriptScrollTopById, viewportRef]);
|
||||
}, [config, connectionsRef, nodesRef, screenToCanvas, scriptScrollTopById, viewportRef]);
|
||||
|
||||
const finishConnection = useCallback((clientX: number, clientY: number) => {
|
||||
if (pendingConnectionCreateRef.current) return;
|
||||
@@ -313,6 +337,7 @@ export function useCanvasConnectionController({
|
||||
connectionTargetAnchorRatio,
|
||||
connectingParams,
|
||||
createConnectedNode,
|
||||
getConnectionCreateDisabledReason,
|
||||
handleConnectStart,
|
||||
mouseWorld,
|
||||
pendingConnectionCreate,
|
||||
|
||||
Reference in New Issue
Block a user