feat(canvas): 画布节点 - 恢复批量创作表与转换能力
This commit is contained in:
1 parent
f59b59b3e6
commit
c57f1b36ae
36 files changed
+3341
-34
No files matched your search
@@ -6,6 +6,7 @@ import { latchCanvasConnectionApproach, type CanvasConnectionApproach } from "@/
|
||||
import type { PendingConnectionCreate } from "@/components/canvas/canvas-workspace-overlays";
|
||||
import { getNodeSpec } from "@/constant/canvas";
|
||||
import { batchSourceRestriction, buildBatchConnectionCreateRequest, hasBatchConnectionCandidate, planBatchConnections, type CanvasBatchConnectionPreview } from "@/lib/canvas/canvas-batch-connection";
|
||||
import { batchReferenceHandleAtY } from "@/lib/canvas/canvas-batch-table";
|
||||
import { connectedNodeCenterFromEdgeDrop } from "@/lib/canvas/canvas-connected-node-placement";
|
||||
import { canvasConnectionError } from "@/lib/canvas/canvas-connection-policy";
|
||||
import { attachNodeToStoryboardRow, createCanvasNode, getConnectionTargetAnchor, isHiddenBatchChild, normalizeConnection, storyboardHandleAtY, storyboardPromptTemplateMetadata, storyboardRowFromHandle } from "@/lib/canvas/canvas-project-domain";
|
||||
@@ -227,7 +228,7 @@ export function useCanvasConnectionController({
|
||||
setContextMenu(null);
|
||||
}, [config, connectionsRef, message, nodesRef, setConnections, setContextMenu, setNodes]);
|
||||
|
||||
const createConnectedNode = useCallback(async (type: CanvasNodeType.Image | CanvasNodeType.Text | CanvasNodeType.Script | CanvasNodeType.Video | CanvasNodeType.Audio | CanvasNodeType.Drawing | CanvasNodeType.Config, pending: PendingConnectionCreate, workflowProvider?: "runninghub") => {
|
||||
const createConnectedNode = useCallback(async (type: CanvasNodeType.Image | CanvasNodeType.Text | CanvasNodeType.Script | CanvasNodeType.BatchTable | CanvasNodeType.Video | CanvasNodeType.Audio | CanvasNodeType.Drawing | CanvasNodeType.Config | CanvasNodeType.MediaConversion, pending: PendingConnectionCreate, workflowProvider?: "runninghub") => {
|
||||
const nodeType = type;
|
||||
if (nodeType === CanvasNodeType.Drawing && !isDrawingEngineAvailable(defaultDrawingEngine, tldrawLicenseKey)) {
|
||||
message.error("当前生产构建未配置 tldraw License Key,不能创建 tldraw 绘图");
|
||||
@@ -319,8 +320,7 @@ export function useCanvasConnectionController({
|
||||
setConnections(nextConnections);
|
||||
setSelectedNodeIds(new Set([newNode.id]));
|
||||
setSelectedConnectionId(null);
|
||||
if (nodeType === CanvasNodeType.Script) setDialogNodeId(null);
|
||||
else if (nodeType !== CanvasNodeType.Text && nodeType !== CanvasNodeType.Audio) setDialogNodeId(newNode.id);
|
||||
if (nodeType !== CanvasNodeType.Text && nodeType !== CanvasNodeType.Script && nodeType !== CanvasNodeType.BatchTable && nodeType !== CanvasNodeType.Audio && nodeType !== CanvasNodeType.MediaConversion) setDialogNodeId(newNode.id);
|
||||
const skippedCount = batchPlan.skipped.length;
|
||||
const duplicateCount = batchPlan.duplicates.length;
|
||||
const suffix = skippedCount || duplicateCount ? `,跳过 ${skippedCount + duplicateCount} 个` : "";
|
||||
@@ -386,13 +386,12 @@ export function useCanvasConnectionController({
|
||||
setSelectedNodeIds(new Set([newNode.id]));
|
||||
setSelectedConnectionId(null);
|
||||
if (nodeType === CanvasNodeType.Drawing) setDrawingNodeId(newNode.id);
|
||||
else if (nodeType === CanvasNodeType.Script) setDialogNodeId(null);
|
||||
else if (nodeType !== CanvasNodeType.Text && nodeType !== CanvasNodeType.Audio) setDialogNodeId(newNode.id);
|
||||
else if (nodeType !== CanvasNodeType.Text && nodeType !== CanvasNodeType.Script && nodeType !== CanvasNodeType.BatchTable && nodeType !== CanvasNodeType.Audio && nodeType !== CanvasNodeType.MediaConversion) setDialogNodeId(newNode.id);
|
||||
closeConnectionCreateMenu();
|
||||
setConnecting(null);
|
||||
}, [closeConnectionCreateMenu, config, connectionsRef, defaultDrawingEngine, message, nodesRef, projectId, runtimeStatuses, setConnecting, setConnections, setDialogNodeId, setDrawingNodeId, setNodes, setSelectedConnectionId, setSelectedNodeIds, tldrawLicenseKey]);
|
||||
|
||||
const getConnectionCreateDisabledReason = useCallback((type: CanvasNodeType.Image | CanvasNodeType.Text | CanvasNodeType.Script | CanvasNodeType.Video | CanvasNodeType.Audio | CanvasNodeType.Drawing | CanvasNodeType.Config, pending: PendingConnectionCreate, workflowProvider?: "runninghub") => {
|
||||
const getConnectionCreateDisabledReason = useCallback((type: CanvasNodeType.Image | CanvasNodeType.Text | CanvasNodeType.Script | CanvasNodeType.BatchTable | CanvasNodeType.Video | CanvasNodeType.Audio | CanvasNodeType.Drawing | CanvasNodeType.Config | CanvasNodeType.MediaConversion, pending: PendingConnectionCreate, workflowProvider?: "runninghub") => {
|
||||
const nodeType = type;
|
||||
if (nodeType === CanvasNodeType.Config) {
|
||||
if (workflowProvider && !workflowProviderPluginEnabled(runtimeStatuses, workflowProvider)) return "RunningHub 工作流插件未启用";
|
||||
@@ -426,8 +425,8 @@ export function useCanvasConnectionController({
|
||||
.reverse()
|
||||
.forEach((node) => {
|
||||
const scrollTop = scriptScrollTopById[node.id] || 0;
|
||||
const targetHandleId = node.type === CanvasNodeType.Script ? storyboardHandleAtY(node, world.y, scrollTop) : undefined;
|
||||
if (node.type === CanvasNodeType.Script && !targetHandleId) return;
|
||||
const targetHandleId = node.type === CanvasNodeType.Script ? storyboardHandleAtY(node, world.y, scrollTop) : node.type === CanvasNodeType.BatchTable ? batchReferenceHandleAtY(node, world.y, handleRadius) : undefined;
|
||||
if ((node.type === CanvasNodeType.Script || node.type === CanvasNodeType.BatchTable) && !targetHandleId) return;
|
||||
// Ordinary nodes expose one centered input/output port. Only
|
||||
// storyboard rows have a meaningful vertical target position.
|
||||
const targetAnchorRatio = undefined;
|
||||
@@ -473,8 +472,8 @@ export function useCanvasConnectionController({
|
||||
.reverse()
|
||||
.forEach((node) => {
|
||||
const scrollTop = scriptScrollTopById[node.id] || 0;
|
||||
const targetHandleId = node.type === CanvasNodeType.Script ? storyboardHandleAtY(node, world.y, scrollTop) : undefined;
|
||||
if (node.type === CanvasNodeType.Script && !targetHandleId) return;
|
||||
const targetHandleId = node.type === CanvasNodeType.Script ? storyboardHandleAtY(node, world.y, scrollTop) : node.type === CanvasNodeType.BatchTable ? batchReferenceHandleAtY(node, world.y, handleRadius) : undefined;
|
||||
if ((node.type === CanvasNodeType.Script || node.type === CanvasNodeType.BatchTable) && !targetHandleId) return;
|
||||
const targetAnchorRatio = undefined;
|
||||
const anchor = getConnectionTargetAnchor(node, current, targetHandleId, scrollTop);
|
||||
const dx = world.x - anchor.x;
|
||||
@@ -646,12 +645,21 @@ export function useCanvasConnectionController({
|
||||
// reset, an old quick-create menu can coexist with the new draft line;
|
||||
// pointerup then sees the stale pending menu and silently aborts.
|
||||
if (pendingConnectionCreateRef.current) closeConnectionCreateMenu();
|
||||
if (connectingParamsRef.current) setConnecting(null);
|
||||
if (batchConnectionPreviewRef.current && handleType === "target") {
|
||||
commitBatchConnection(batchConnectionPreviewRef.current.sourceNodeIds, nodeId, handleId, anchorRatio);
|
||||
clearBatchConnection();
|
||||
return;
|
||||
}
|
||||
// A batch table exposes real target pins inside its content. When a normal
|
||||
// image output is dragged onto one, commit that active connection instead
|
||||
// of starting a new connection session from the target pin.
|
||||
const activeConnection = connectingParamsRef.current;
|
||||
if (activeConnection && handleType === "target" && activeConnection.nodeId !== nodeId) {
|
||||
connectNodes(activeConnection, nodeId, handleId, anchorRatio);
|
||||
setConnecting(null);
|
||||
return;
|
||||
}
|
||||
if (activeConnection) setConnecting(null);
|
||||
if (batchConnectionPreviewRef.current) clearBatchConnection();
|
||||
connectingPointerIdRef.current = event.pointerId;
|
||||
connectingPointerStartRef.current = { x: event.clientX, y: event.clientY };
|
||||
@@ -660,7 +668,21 @@ export function useCanvasConnectionController({
|
||||
setConnectionTargetNodeId(null);
|
||||
setConnectionTargetAnchorRatio(undefined);
|
||||
setSelectedConnectionId(null);
|
||||
}, [clearBatchConnection, closeConnectionCreateMenu, commitBatchConnection, screenToCanvas, setConnecting, setSelectedConnectionId]);
|
||||
}, [clearBatchConnection, closeConnectionCreateMenu, commitBatchConnection, connectNodes, screenToCanvas, setConnecting, setSelectedConnectionId]);
|
||||
|
||||
const handleConnectDrop = useCallback((event: ReactPointerEvent, nodeId: string, handleId?: string) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
if (batchConnectionPreviewRef.current) {
|
||||
commitBatchConnection(batchConnectionPreviewRef.current.sourceNodeIds, nodeId, handleId);
|
||||
clearBatchConnection();
|
||||
return;
|
||||
}
|
||||
const current = connectingParamsRef.current;
|
||||
if (!current || current.nodeId === nodeId) return;
|
||||
connectNodes(current, nodeId, handleId);
|
||||
setConnecting(null);
|
||||
}, [clearBatchConnection, commitBatchConnection, connectNodes, setConnecting]);
|
||||
|
||||
useEffect(() => {
|
||||
const cancelPendingPointerMove = () => {
|
||||
@@ -799,6 +821,7 @@ export function useCanvasConnectionController({
|
||||
createConnectedNode,
|
||||
getConnectionCreateDisabledReason,
|
||||
handleConnectStart,
|
||||
handleConnectDrop,
|
||||
handleBatchConnectionTargetClick,
|
||||
batchConnectionPreview,
|
||||
beginBatchConnectionMode,
|
||||
|
||||
Reference in new issue
Block a user