Просмотр исходного кода

feat(canvas-core): drag service supports detailed event params

liuyangxing 10 месяцев назад
Родитель
Сommit
2607ec93d3

+ 76 - 48
packages/canvas-engine/free-layout-core/src/service/workflow-drag-service.ts

@@ -7,14 +7,15 @@ import {
   type IPoint,
   PromiseDeferred,
   Emitter,
+  type PositionSchema,
   DisposableCollection,
   Rectangle,
   delay,
   Disposable,
 } from '@flowgram.ai/utils';
-import type { PositionSchema } from '@flowgram.ai/utils';
 import {
   FlowNodeTransformData,
+  FlowNodeType,
   FlowOperationBaseService,
   type FlowNodeEntity,
 } from '@flowgram.ai/document';
@@ -31,6 +32,7 @@ import { WorkflowLinesManager } from '../workflow-lines-manager';
 import { WorkflowDocumentOptions } from '../workflow-document-option';
 import { WorkflowDocument } from '../workflow-document';
 import { WorkflowCommands } from '../workflow-commands';
+import { LineEventProps, NodesDragEvent, OnDragLineEnd } from '../typings/workflow-drag';
 import { type WorkflowNodeJSON, type WorkflowNodeMeta } from '../typings';
 import { WorkflowNodePortsData } from '../entity-datas';
 import {
@@ -60,36 +62,6 @@ function checkDragSuccess(
   return false;
 }
 
-interface LineEventProps {
-  type: 'onDrag' | 'onDragEnd';
-  onDragNodeId?: string;
-  event?: MouseEvent;
-}
-
-interface INodesDragEvent {
-  type: string;
-  nodes: FlowNodeEntity[];
-  startPositions: IPoint[];
-  altKey: boolean;
-}
-
-export interface NodesDragEndEvent extends INodesDragEvent {
-  type: 'onDragEnd';
-}
-
-export type NodesDragEvent = NodesDragEndEvent;
-
-export type onDragLineEndParams = {
-  fromPort: WorkflowPortEntity;
-  toPort?: WorkflowPortEntity;
-  mousePos: PositionSchema;
-  line?: WorkflowLineEntity;
-  originLine?: WorkflowLineEntity;
-  event: PlaygroundDragEvent;
-};
-
-export type OnDragLineEnd = (params: onDragLineEndParams) => Promise<void>;
-
 @injectable()
 export class WorkflowDragService {
   @inject(PlaygroundConfigEntity)
@@ -147,9 +119,9 @@ export class WorkflowDragService {
 
   /**
    * 拖拽选中节点
-   * @param event
+   * @param triggerEvent
    */
-  startDragSelectedNodes(event: MouseEvent | React.MouseEvent): Promise<boolean> {
+  startDragSelectedNodes(triggerEvent: MouseEvent | React.MouseEvent): Promise<boolean> {
     let { selectedNodes } = this.selectService;
     if (
       selectedNodes.length === 0 ||
@@ -162,7 +134,7 @@ export class WorkflowDragService {
     if (sameParent && sameParent.flowNodeType !== FlowNodeBaseType.ROOT) {
       selectedNodes = [sameParent];
     }
-    const { altKey } = event;
+    const { altKey } = triggerEvent;
     // 节点整体开始位置
     let startPosition = this.getNodesPosition(selectedNodes);
     // 单个节点开始位置
@@ -173,11 +145,20 @@ export class WorkflowDragService {
     let dragSuccess = false;
     const startTime = Date.now();
     const dragger = new PlaygroundDrag({
-      onDragStart: () => {
+      onDragStart: (dragEvent) => {
         this.isDragging = true;
+        this._nodesDragEmitter.fire({
+          type: 'onDragStart',
+          nodes: selectedNodes,
+          startPositions,
+          altKey,
+          dragEvent,
+          triggerEvent,
+          dragger,
+        });
       },
-      onDrag: (e) => {
-        if (!dragSuccess && checkDragSuccess(Date.now() - startTime, e)) {
+      onDrag: (dragEvent) => {
+        if (!dragSuccess && checkDragSuccess(Date.now() - startTime, dragEvent)) {
           dragSuccess = true;
           if (altKey) {
             // 按住 alt 为复制
@@ -207,11 +188,13 @@ export class WorkflowDragService {
 
         // 计算拖拽偏移量
         const offset: IPoint = this.getDragPosOffset({
-          event: e,
+          event: dragEvent,
           selectedNodes,
           startPosition,
         });
 
+        const positions: PositionSchema[] = [];
+
         selectedNodes.forEach((node, index) => {
           const transform = node.getData(TransformData);
           const nodeStartPosition = startPositions[index];
@@ -230,21 +213,36 @@ export class WorkflowDragService {
           transform.update({
             position: newPosition,
           });
+          positions.push(newPosition);
+        });
+
+        this._nodesDragEmitter.fire({
+          type: 'onDragging',
+          nodes: selectedNodes,
+          startPositions,
+          positions,
+          altKey,
+          dragEvent,
+          triggerEvent,
+          dragger,
         });
       },
-      onDragEnd: () => {
+      onDragEnd: (dragEvent) => {
         this.isDragging = false;
         this._nodesDragEmitter.fire({
           type: 'onDragEnd',
           nodes: selectedNodes,
           startPositions,
           altKey,
+          dragEvent,
+          triggerEvent,
+          dragger,
         });
       },
     });
     return dragger
-      .start(event.clientX, event.clientY, this.playgroundConfig)
-      .then(() => dragSuccess);
+      .start(triggerEvent.clientX, triggerEvent.clientY, this.playgroundConfig)
+      ?.then(() => dragSuccess);
   }
 
   /**
@@ -332,6 +330,13 @@ export class WorkflowDragService {
       },
       onDragEnd: async (e) => {
         const dropNode = this._dropNode;
+        const { allowDrop } = this.canDropToNode({
+          dragNodeType: type,
+          dropNode,
+        });
+        if (!allowDrop) {
+          return this.clearDrop();
+        }
         const dragNode = await this.dropCard(type, e, data, dropNode);
         this.clearDrop();
         if (dragNode) {
@@ -396,6 +401,27 @@ export class WorkflowDragService {
     };
   }
 
+  /**
+   * 判断是否可以放置节点
+   */
+  public canDropToNode(params: { dragNodeType?: FlowNodeType; dropNode?: WorkflowNodeEntity }): {
+    allowDrop: boolean;
+    message?: string;
+    dropNode?: WorkflowNodeEntity;
+  } {
+    const { dragNodeType, dropNode } = params;
+    if (!dragNodeType) {
+      return {
+        allowDrop: false,
+        message: 'Please select a node to drop',
+      };
+    }
+    return {
+      allowDrop: true,
+      dropNode,
+    };
+  }
+
   /**
    * 获取拖拽偏移
    */
@@ -440,10 +466,12 @@ export class WorkflowDragService {
         }
         return this.nodeSelectable(entity);
       })
-      .filter((transform) => {
-        const { entity } = transform;
-        return entity.flowNodeType === FlowNodeBaseType.SUB_CANVAS;
-      });
+      .filter((transform) => this.isContainer(transform.entity));
+  }
+
+  /** 是否容器节点 */
+  private isContainer(node?: WorkflowNodeEntity): boolean {
+    return node?.getNodeMeta<WorkflowNodeMeta>().isContainer ?? false;
   }
 
   /**
@@ -519,8 +547,8 @@ export class WorkflowDragService {
       return {
         hasError: false,
       };
-    } else if (toNode.flowNodeType === FlowNodeBaseType.SUB_CANVAS) {
-      // 在子画布内进行连线的情况,需忽略
+    } else if (this.isContainer(toNode)) {
+      // 在容器内进行连线的情况,需忽略
       return {
         hasError: false,
       };
@@ -631,7 +659,7 @@ export class WorkflowDragService {
         });
 
         this.setLineColor(line, this.linesManager.lineColor.drawing);
-        if (toNode && toNode.flowNodeType !== FlowNodeBaseType.SUB_CANVAS) {
+        if (toNode && !this.isContainer(toNode)) {
           // 如果鼠标 hover 在 node 中的时候,默认连线到这个 node 的初始位置
           const portsData = toNode.getData(WorkflowNodePortsData)!;
           toPort = portsData.inputPorts[0];

+ 1 - 0
packages/canvas-engine/free-layout-core/src/typings/index.ts

@@ -5,6 +5,7 @@ export * from './workflow-registry';
 export * from './workflow-line';
 export * from './workflow-sub-canvas';
 export * from './workflow-operation';
+export * from './workflow-drag';
 
 export const URLParams = Symbol('');
 

+ 49 - 0
packages/canvas-engine/free-layout-core/src/typings/workflow-drag.ts

@@ -0,0 +1,49 @@
+import type React from 'react';
+
+import { type PositionSchema } from '@flowgram.ai/utils';
+import { type FlowNodeEntity } from '@flowgram.ai/document';
+import { PlaygroundDrag, type PlaygroundDragEvent } from '@flowgram.ai/core';
+
+import { type WorkflowLineEntity, type WorkflowPortEntity } from '../entities';
+
+export interface LineEventProps {
+  type: 'onDrag' | 'onDragEnd';
+  onDragNodeId?: string;
+  event?: MouseEvent;
+}
+
+interface INodesDragEvent {
+  type: string;
+  nodes: FlowNodeEntity[];
+  startPositions: PositionSchema[];
+  altKey: boolean;
+  dragEvent: PlaygroundDragEvent;
+  triggerEvent: MouseEvent | React.MouseEvent;
+  dragger: PlaygroundDrag;
+}
+
+export interface NodesDragStartEvent extends INodesDragEvent {
+  type: 'onDragStart';
+}
+
+export interface NodesDragEndEvent extends INodesDragEvent {
+  type: 'onDragEnd';
+}
+
+export interface NodesDraggingEvent extends INodesDragEvent {
+  type: 'onDragging';
+  positions: PositionSchema[];
+}
+
+export type NodesDragEvent = NodesDragStartEvent | NodesDraggingEvent | NodesDragEndEvent;
+
+export type onDragLineEndParams = {
+  fromPort: WorkflowPortEntity;
+  toPort?: WorkflowPortEntity;
+  mousePos: PositionSchema;
+  line?: WorkflowLineEntity;
+  originLine?: WorkflowLineEntity;
+  event: PlaygroundDragEvent;
+};
+
+export type OnDragLineEnd = (params: onDragLineEndParams) => Promise<void>;