feat(canvas): add group node support with drag-and-drop functionality

This commit is contained in:
HouYunFei
2026-07-09 12:54:36 +08:00
parent abe3be58a6
commit ec0e8c4e24
7 changed files with 91 additions and 30 deletions
+11 -21
View File
@@ -235,7 +235,6 @@ function InfiniteCanvasPage() {
const historyPausedRef = useRef(false);
const didInitialCenterRef = useRef(false);
const rafRef = useRef<number | null>(null);
const toolbarHideTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
const nodeDraggingRef = useRef(false);
const dragRef = useRef<{
isDraggingNode: boolean;
@@ -547,25 +546,12 @@ function InfiniteCanvasPage() {
}
}, []);
const keepNodeToolbar = useCallback(
(nodeId: string) => {
if (nodeDraggingRef.current || nodeImageSettingsOpen) return;
if (toolbarHideTimerRef.current) {
clearTimeout(toolbarHideTimerRef.current);
toolbarHideTimerRef.current = null;
}
setToolbarNodeId(nodeId);
},
[nodeImageSettingsOpen],
);
const keepNodeToolbar = useCallback((nodeId: string) => {
if (nodeDraggingRef.current || nodeImageSettingsOpen || !selectedNodeIdsRef.current.has(nodeId)) return;
setToolbarNodeId(nodeId);
}, [nodeImageSettingsOpen]);
const hideNodeToolbar = useCallback(() => {
if (toolbarHideTimerRef.current) clearTimeout(toolbarHideTimerRef.current);
toolbarHideTimerRef.current = setTimeout(() => {
setToolbarNodeId(null);
toolbarHideTimerRef.current = null;
}, 120);
}, []);
const hideNodeToolbar = useCallback(() => {}, []);
const connectNodes = useCallback(
(current: ConnectionHandle, targetNodeId: string) => {
@@ -1106,6 +1092,7 @@ function InfiniteCanvasPage() {
}
setSelectedNodeIds(nextSelected);
setToolbarNodeId(nextSelected.size === 1 && nextSelected.has(nodeId) ? nodeId : null);
const dragIds = new Set(nextSelected);
currentNodes.forEach((node) => {
if (!nextSelected.has(node.id)) return;
@@ -1502,6 +1489,10 @@ function InfiniteCanvasPage() {
setNodes((prev) => prev.map((node) => (node.id === nodeId ? { ...node, metadata: { ...node.metadata, content } } : node)));
}, []);
const handleNodeTitleChange = useCallback((nodeId: string, title: string) => {
setNodes((prev) => prev.map((node) => (node.id === nodeId ? { ...node, title } : node)));
}, []);
const toggleBatchExpanded = useCallback((nodeId: string) => {
const isExpanded = Boolean(nodesRef.current.find((node) => node.id === nodeId)?.metadata?.imageBatchExpanded);
if (isExpanded) {
@@ -2658,15 +2649,14 @@ function InfiniteCanvasPage() {
onHoverStart={(nodeId) => {
if (nodeDraggingRef.current) return;
setHoveredNodeId(nodeId);
keepNodeToolbar(nodeId);
}}
onHoverEnd={(nodeId) => {
setHoveredNodeId((current) => (current === nodeId ? null : current));
hideNodeToolbar();
}}
onConnectStart={handleConnectStart}
onResize={handleNodeResize}
onContentChange={handleNodeContentChange}
onTitleChange={handleNodeTitleChange}
onToggleBatch={toggleBatchExpanded}
onSetBatchPrimary={setBatchPrimary}
onRetry={(node) => void handleRetryNode(node)}