Bläddra i källkod

refactor(demo-free-layout): move canDropToNode to canContainNode, fix loop nested loop from paste, node-panel.panelProps add fromPort (#865)

* refactor(demo-free-layout): move canDropToNode to can-contain-node and fix loop nested loop from paste

* fix(core): increase the playground-drag auto-scroll speed

* feat(demo-free-layout): node-panel.panelProps add fromPort

* fix(demo-free-layout): remove @/
xiamidaxia 3 månader sedan
förälder
incheckning
e2a243ff04

+ 1 - 0
apps/demo-free-layout/src/components/line-add-button/index.tsx

@@ -52,6 +52,7 @@ export const LineAddButton = (props: LineRenderProps) => {
       containerNode,
       panelProps: {
         enableScrollClose: true,
+        fromPort,
       },
     });
     if (!result) {

+ 10 - 3
apps/demo-free-layout/src/components/node-panel/index.tsx

@@ -5,16 +5,23 @@
 
 import { FC, useRef } from 'react';
 
-import { NodePanelRenderProps } from '@flowgram.ai/free-node-panel-plugin';
+import { NodePanelRenderProps as NodePanelRenderPropsDefault } from '@flowgram.ai/free-node-panel-plugin';
+import { WorkflowPortEntity } from '@flowgram.ai/free-layout-editor';
 import { Popover } from '@douyinfe/semi-ui';
 
 import { NodePlaceholder } from './node-placeholder';
 import { NodeList } from './node-list';
 import './index.less';
 
+interface NodePanelRenderProps extends NodePanelRenderPropsDefault {
+  panelProps: {
+    fromPort?: WorkflowPortEntity; // 从哪个端口添加 From which port to add
+    enableNodePlaceholder?: boolean;
+  };
+}
 export const NodePanel: FC<NodePanelRenderProps> = (props) => {
   const { onSelect, position, onClose, containerNode, panelProps = {} } = props;
-  const { enableNodePlaceholder } = panelProps;
+  const { enableNodePlaceholder, fromPort } = panelProps;
   const ref = useRef<HTMLDivElement>(null);
 
   return (
@@ -22,7 +29,7 @@ export const NodePanel: FC<NodePanelRenderProps> = (props) => {
       trigger="click"
       visible={true}
       onVisibleChange={(v) => (v ? null : onClose())}
-      content={<NodeList onSelect={onSelect} containerNode={containerNode} />}
+      content={<NodeList onSelect={onSelect} containerNode={containerNode} fromPort={fromPort} />}
       getPopupContainer={containerNode ? () => ref.current || document.body : undefined}
       placement="right"
       popupAlign={{ offset: [30, 0] }}

+ 11 - 8
apps/demo-free-layout/src/components/node-panel/node-list.tsx

@@ -7,10 +7,15 @@ import React, { FC } from 'react';
 
 import styled from 'styled-components';
 import { NodePanelRenderProps } from '@flowgram.ai/free-node-panel-plugin';
-import { useClientContext, WorkflowNodeEntity } from '@flowgram.ai/free-layout-editor';
+import {
+  useClientContext,
+  WorkflowNodeEntity,
+  WorkflowPortEntity,
+} from '@flowgram.ai/free-layout-editor';
 
+import { canContainNode } from '../../utils';
 import { FlowNodeRegistry } from '../../typings';
-import { nodeRegistries, WorkflowNodeType } from '../../nodes';
+import { nodeRegistries } from '../../nodes';
 
 const NodeWrap = styled.div`
   width: 100%;
@@ -62,11 +67,12 @@ const NodesWrap = styled.div`
 
 interface NodeListProps {
   onSelect: NodePanelRenderProps['onSelect'];
+  fromPort?: WorkflowPortEntity; // 从哪个端口添加 From which port to add
   containerNode?: WorkflowNodeEntity;
 }
 
 export const NodeList: FC<NodeListProps> = (props) => {
-  const { onSelect, containerNode } = props;
+  const { onSelect, containerNode, fromPort } = props;
   const context = useClientContext();
   const handleClick = (e: React.MouseEvent, registry: FlowNodeRegistry) => {
     const json = registry.onAdd?.(context);
@@ -76,6 +82,7 @@ export const NodeList: FC<NodeListProps> = (props) => {
       nodeJSON: json,
     });
   };
+  console.log('>>> fromNode', fromPort?.node);
   return (
     <NodesWrap style={{ width: 80 * 2 + 20 }}>
       {nodeRegistries
@@ -88,11 +95,7 @@ export const NodeList: FC<NodeListProps> = (props) => {
            * 循环节点无法嵌套循环节点
            * Loop node cannot nest loop node
            */
-          if (
-            containerNode &&
-            containerNode.flowNodeType === WorkflowNodeType.Loop &&
-            register.type === WorkflowNodeType.Loop
-          ) {
+          if (containerNode && !canContainNode(register.type, containerNode.flowNodeType)) {
             return false;
           }
           return true;

+ 2 - 38
apps/demo-free-layout/src/hooks/use-editor-props.tsx

@@ -20,7 +20,7 @@ import {
 import { createFreeGroupPlugin } from '@flowgram.ai/free-group-plugin';
 import { createContainerNodePlugin } from '@flowgram.ai/free-container-plugin';
 
-import { onDragLineEnd } from '../utils';
+import { canContainNode, onDragLineEnd } from '../utils';
 import { FlowNodeRegistry, FlowDocumentJSON } from '../typings';
 import { shortcuts } from '../shortcuts';
 import { CustomService } from '../services';
@@ -156,43 +156,7 @@ export function useEditorProps(
        * 是否允许拖入子画布 (loop or group)
        * Whether to allow dragging into the sub-canvas (loop or group)
        */
-      canDropToNode: (ctx, params) => {
-        const { dragNodeType, dropNodeType } = params;
-        /**
-         * 开始/结束节点无法更改容器
-         * The start and end nodes cannot change container
-         */
-        if (
-          [
-            WorkflowNodeType.Start,
-            WorkflowNodeType.End,
-            WorkflowNodeType.BlockStart,
-            WorkflowNodeType.BlockEnd,
-          ].includes(dragNodeType as WorkflowNodeType)
-        ) {
-          return false;
-        }
-        /**
-         * 继续循环与终止循环只能在循环节点中
-         * Continue loop and break loop can only be in loop nodes
-         */
-        if (
-          [WorkflowNodeType.Continue, WorkflowNodeType.Break].includes(
-            dragNodeType as WorkflowNodeType
-          ) &&
-          dropNodeType !== WorkflowNodeType.Loop
-        ) {
-          return false;
-        }
-        /**
-         * 循环节点无法嵌套循环节点
-         * Loop node cannot nest loop node
-         */
-        if (dragNodeType === WorkflowNodeType.Loop && dropNodeType === WorkflowNodeType.Loop) {
-          return false;
-        }
-        return true;
-      },
+      canDropToNode: (ctx, params) => canContainNode(params.dragNodeType!, params.dropNodeType!),
       /**
        * Drag the end of the line to create an add panel (feature optional)
        * 拖拽线条结束需要创建一个添加面板 (功能可选)

+ 1 - 0
apps/demo-free-layout/src/hooks/use-port-click.ts

@@ -42,6 +42,7 @@ export const usePortClick = () => {
       containerNode,
       panelProps: {
         enableScrollClose: true,
+        fromPort: port,
       },
     });
 

+ 6 - 1
apps/demo-free-layout/src/shortcuts/paste/index.ts

@@ -25,6 +25,7 @@ import { Toast } from '@douyinfe/semi-ui';
 
 import { WorkflowClipboardData, WorkflowClipboardRect } from '../type';
 import { FlowCommandId, WorkflowClipboardDataID } from '../constants';
+import { canContainNode } from '../../utils';
 import { generateUniqueWorkflow } from './unique-workflow';
 
 export class PasteShortcut implements ShortcutsHandler {
@@ -98,7 +99,11 @@ export class PasteShortcut implements ShortcutsHandler {
     });
 
     const offset = this.calcPasteOffset(data.bounds);
-    const parent = this.getSelectedContainer();
+    let parent = this.getSelectedContainer();
+    // loop 不支持嵌套
+    if (parent && json.nodes.some((n) => !canContainNode(n.type, parent!.flowNodeType))) {
+      parent = undefined;
+    }
     this.applyOffset({ json, offset, parent });
     const { nodes } = this.document.renderJSON(json, {
       parent,

+ 54 - 0
apps/demo-free-layout/src/utils/can-contain-node.ts

@@ -0,0 +1,54 @@
+/**
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
+ * SPDX-License-Identifier: MIT
+ */
+
+import { type FlowNodeType } from '@flowgram.ai/free-layout-editor';
+
+import { WorkflowNodeType } from '../nodes';
+
+/**
+ * 判断父节点是否可以包含对应子节点
+ * Determine whether the parent node can contain the corresponding child node
+ * @param childNodeType
+ * @param parentNodeType
+ */
+export function canContainNode(
+  childNodeType: WorkflowNodeType | FlowNodeType,
+  parentNodeType: WorkflowNodeType | FlowNodeType
+) {
+  /**
+   * 开始/结束节点无法更改容器
+   * The start and end nodes cannot change container
+   */
+  if (
+    [
+      WorkflowNodeType.Start,
+      WorkflowNodeType.End,
+      WorkflowNodeType.BlockStart,
+      WorkflowNodeType.BlockEnd,
+    ].includes(childNodeType as WorkflowNodeType)
+  ) {
+    return false;
+  }
+  /**
+   * 继续循环与终止循环只能在循环节点中
+   * Continue loop and break loop can only be in loop nodes
+   */
+  if (
+    [WorkflowNodeType.Continue, WorkflowNodeType.Break].includes(
+      childNodeType as WorkflowNodeType
+    ) &&
+    parentNodeType !== WorkflowNodeType.Loop
+  ) {
+    return false;
+  }
+  /**
+   * 循环节点无法嵌套循环节点
+   * Loop node cannot nest loop node
+   */
+  if (childNodeType === WorkflowNodeType.Loop && parentNodeType === WorkflowNodeType.Loop) {
+    return false;
+  }
+  return true;
+}

+ 1 - 0
apps/demo-free-layout/src/utils/index.ts

@@ -5,3 +5,4 @@
 
 export { onDragLineEnd } from './on-drag-line-end';
 export { toggleLoopExpanded } from './toggle-loop-expanded';
+export { canContainNode } from './can-contain-node';

+ 1 - 0
apps/demo-free-layout/src/utils/on-drag-line-end.ts

@@ -57,6 +57,7 @@ export const onDragLineEnd = async (ctx: FreeLayoutPluginContext, params: onDrag
     panelProps: {
       enableNodePlaceholder: true,
       enableScrollClose: true,
+      fromPort,
     },
   });
 

+ 1 - 1
packages/canvas-engine/core/src/core/utils/playground-drag.ts

@@ -20,7 +20,7 @@ const SCROLL_DELTA = 4;
 /* istanbul ignore next */
 const SCROLL_AUTO_DISTANCE = 20; // 自动滚动到边缘距离
 /* istanbul ignore next */
-const SCROLL_INTERVAL = 20;
+const SCROLL_INTERVAL = 6;
 
 /* istanbul ignore next */
 function createMouseEvent(type: string, clientX: number, clientY: number): MouseEvent {