Prechádzať zdrojové kódy

fix(core): remove playground.config.clientX and clientY instead by config.getClientBounds and fix line displacement after page scrolling (#822)

xiamidaxia 4 mesiacov pred
rodič
commit
199b4eb83d

+ 3 - 20
packages/canvas-engine/core/__tests__/playground-mock-tools.spec.ts

@@ -3,6 +3,7 @@
  * SPDX-License-Identifier: MIT
  */
 
+import { describe, it, expect } from 'vitest';
 import { ContainerModule } from 'inversify';
 
 import { EntityManager, PlaygroundLayer, PlaygroundMockTools, Playground, Layer } from '../src';
@@ -14,7 +15,7 @@ describe('playground-mock-tools', () => {
     const container = createContainer();
     expect(container.get(EntityManager)).toBeInstanceOf(EntityManager);
     const container2 = createContainer([
-      new ContainerModule(bind => {
+      new ContainerModule((bind) => {
         bind('abc').toConstantValue('abc');
       }),
     ]);
@@ -85,12 +86,8 @@ describe('playground-mock-tools', () => {
     layer.playground.resize({
       width: 100,
       height: 100,
-      clientX: 0,
-      clientY: 0,
     });
-    expect(layer.onResize.mock.calls).toEqual([
-      [{ clientX: 0, clientY: 0, width: 100, height: 100 }],
-    ]);
+    expect(layer.onResize.mock.calls).toEqual([[{ width: 100, height: 100 }]]);
     expect(layer.onViewportChange.mock.calls.length).toEqual(3);
     layer.playground.config.readonly = true;
     layer.playground.config.disabled = true;
@@ -103,29 +100,15 @@ describe('playground-mock-tools', () => {
     const layerState = createLayerTestState(PlaygroundLayer);
     expect(layerState.onReady.mock.calls.length).toEqual(1);
     expect(layerState.autorun.mock.calls.length).toEqual(1);
-    expect(layerState.onResize.mock.calls.length).toEqual(0);
-    expect(layerState.onResize.mock.lastCall).toEqual(undefined);
     layerState.playground.config.updateConfig({
       scrollX: 100,
     });
-    expect(layerState.onResize.mock.calls.length).toEqual(0);
     expect(layerState.onReady.mock.calls.length).toEqual(1);
     expect(layerState.autorun.mock.calls.length).toEqual(2);
     layerState.playground.resize({
       width: 100,
       height: 100,
-      clientX: 0,
-      clientY: 0,
     });
-    expect(layerState.onResize.mock.calls.length).toEqual(1);
     expect(layerState.autorun.mock.calls.length).toEqual(3);
-    expect(layerState.onResize.mock.lastCall).toEqual([
-      {
-        clientX: 0,
-        clientY: 0,
-        width: 100,
-        height: 100,
-      },
-    ]);
   });
 });

+ 11 - 9
packages/canvas-engine/core/src/core/layer/config/playground-config-entity.ts

@@ -23,8 +23,6 @@ export interface PlaygroundConfigEntityData {
   originY: number // 左上角默认开始原点坐标
   width: number // 编辑区宽,在 onResize 触发后重制
   height: number // 编辑区高,在 onResize 触发后重制
-  clientX: number // 如果有拖拽场景需要传入
-  clientY: number // 如果有拖拽场景需要传入
   reverseScroll: boolean // 支持反方向滚动
   overflowX: 'hidden' | 'scroll'
   overflowY: 'hidden' | 'scroll'
@@ -78,6 +76,7 @@ export class PlaygroundConfigEntity extends ConfigEntity<PlaygroundConfigEntityD
   private _onGrabDisableChangeEmitter = new Emitter<boolean>()
   readonly onGrabDisableChange = this._onGrabDisableChangeEmitter.event;
   readonly onReadonlyOrDisabledChange = this._onReadonlyOrDisabledChangeEmitter.event
+  playgroundDomNode: HTMLElement = document.createElement('div')
 
   cursor = 'default'
   constructor(opts: any) {
@@ -127,8 +126,6 @@ export class PlaygroundConfigEntity extends ConfigEntity<PlaygroundConfigEntityD
       minZoom: 0.25,
       maxZoom: 2,
       zoom: 1,
-      clientX: 0,
-      clientY: 0,
       reverseScroll: true,
       overflowX: 'scroll',
       overflowY: 'scroll',
@@ -259,21 +256,26 @@ export class PlaygroundConfigEntity extends ConfigEntity<PlaygroundConfigEntityD
     const { config } = this
     const scale = withScale ? this.zoom : 1
     const { clientX, clientY } = MouseTouchEvent.getEventCoord(event)
+    const clientRect = this.playgroundDomNode.getBoundingClientRect()
     return {
-      x: (clientX + config.scrollX - config.clientX) / scale,
-      y: (clientY + config.scrollY - config.clientY) / scale,
+      x: (clientX + config.scrollX - clientRect.x) / scale,
+      y: (clientY + config.scrollY - clientRect.y) / scale,
     }
   }
-
+  getClientBounds(): Rectangle {
+    const clientRect = this.playgroundDomNode.getBoundingClientRect()
+    return new Rectangle(clientRect.x, clientRect.y, clientRect.width, clientRect.height)
+  }
   /**
    * 将画布中的位置转成相对 window 的位置
    * @param pos
    */
   toFixedPos(pos: PositionSchema): PositionSchema {
     const { config } = this
+    const clientRect = this.playgroundDomNode.getBoundingClientRect()
     return {
-      x: pos.x - config.scrollX + config.clientX,
-      y: pos.y - config.scrollY + config.clientY,
+      x: pos.x - config.scrollX + clientRect.x,
+      y: pos.y - config.scrollY + clientRect.y,
     }
   }
 

+ 1 - 23
packages/canvas-engine/core/src/core/layer/playground-layer.ts

@@ -9,7 +9,7 @@ import { Disposable, domUtils, PositionSchema } from '@flowgram.ai/utils';
 import { Gesture } from '../utils/use-gesture';
 import { PlaygroundGesture } from '../utils/playground-gesture';
 import { MouseTouchEvent, PlaygroundDrag } from '../utils';
-import { type PipelineDimension, PipelineLayerPriority } from '../pipeline';
+import { PipelineLayerPriority } from '../pipeline';
 import { ProtectWheelArea } from '../../common/protect-wheel-area';
 import { observeEntity } from '../../common';
 import { Layer, LayerOptions } from './layer';
@@ -70,8 +70,6 @@ export class PlaygroundLayer extends Layer<PlaygroundLayerOptions> {
     scrollY: 0,
   };
 
-  private size?: PipelineDimension;
-
   private cursorStyle: HTMLStyleElement = document.createElement('style');
 
   private maskNode: HTMLDivElement = document.createElement('div');
@@ -400,26 +398,6 @@ export class PlaygroundLayer extends Layer<PlaygroundLayerOptions> {
     }
   }
 
-  /**
-   * 监听 resize
-   * @param size
-   */
-  onResize(size: PipelineDimension): void {
-    this.size = { ...size };
-    this.updateSizeWithRulerConfig();
-  }
-
-  updateSizeWithRulerConfig(): void {
-    const { size } = this;
-    if (!size) return;
-    this.config.updateConfig({
-      width: size.width,
-      height: size.height,
-      clientX: size.clientX,
-      clientY: size.clientY,
-    });
-  }
-
   protected handleScrollEvent(event: WheelEvent): void {
     const { playgroundConfigEntity } = this;
     const scrollX = playgroundConfigEntity.config.scrollX + event.deltaX;

+ 0 - 2
packages/canvas-engine/core/src/core/pipeline/pipeline.ts

@@ -10,8 +10,6 @@ export type PipeEventName = string;
 export interface PipelineDimension {
   width: number;
   height: number;
-  clientX: number;
-  clientY: number;
 }
 
 export type PipelineEventHandler = (event: PipeSupportEvent) => boolean | undefined;

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

@@ -112,6 +112,7 @@ export class Playground<CONTEXT = PlaygroundContext> implements Disposable {
       this.entityManager.createEntity<EditorStateConfigEntity>(EditorStateConfigEntity);
     this.entityManager.createEntity(PlaygroundConfigEntity);
     this.node = playgroundConfig.node || document.createElement('div');
+    this.config.playgroundDomNode = this.node;
     this.toDispose.pushAll([
       // 浏览器原生的 scrollIntoView 会导致页面的滚动
       // 需要禁用这种操作,否则会引发画布 viewport 计算问题
@@ -236,10 +237,6 @@ export class Playground<CONTEXT = PlaygroundContext> implements Disposable {
     if (this.isReady) return;
     this.isReady = true;
     if (this.playgroundConfig.autoResize) {
-      const resize = () => {
-        if (this.disposed) return;
-        this.resize();
-      };
       const resizeWithPolling = () => {
         if (this.disposed) return;
         this.resize();
@@ -265,11 +262,6 @@ export class Playground<CONTEXT = PlaygroundContext> implements Disposable {
           )
         );
       }
-      this.toDispose.push(
-        domUtils.addStandardDisposableListener(window.document, 'scroll', resize, {
-          passive: true,
-        })
-      );
       this.resize();
     }
     this.pipelineRegistry.ready();
@@ -300,8 +292,6 @@ export class Playground<CONTEXT = PlaygroundContext> implements Disposable {
     if (!msg) {
       const boundingRect = this.node.getBoundingClientRect();
       msg = {
-        clientX: boundingRect.left,
-        clientY: boundingRect.top,
         width: boundingRect.width,
         height: boundingRect.height,
       };
@@ -321,8 +311,6 @@ export class Playground<CONTEXT = PlaygroundContext> implements Disposable {
       scrollY += (height - msg.height) / 2;
     }
     if (
-      oldConfig.clientY !== msg.clientY ||
-      oldConfig.clientX !== msg.clientX ||
       Math.round(msg.width) !== width ||
       Math.round(msg.height) !== height ||
       oldConfig.scrollX !== scrollX ||

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

@@ -276,7 +276,7 @@ export class WorkflowDragService {
         const targetNode = event.currentTarget as HTMLDivElement;
         domNode = cloneNode ? cloneNode(e) : (targetNode.cloneNode(true) as HTMLDivElement);
         const bounds = targetNode.getBoundingClientRect();
-        startPos = { x: bounds.left, y: bounds.top };
+        startPos = { x: bounds.left + window.scrollX, y: bounds.top + window.scrollY };
         domUtils.setStyle(domNode, {
           zIndex: 1000,
           position: 'absolute',

+ 15 - 14
packages/canvas-engine/renderer/src/entities/flow-drag-entity.tsx

@@ -3,13 +3,13 @@
  * SPDX-License-Identifier: MIT
  */
 
+import { Rectangle } from '@flowgram.ai/utils';
 import {
   type FlowNodeTransitionData,
   FlowTransitionLabelEnum,
   LABEL_SIDE_TYPE,
 } from '@flowgram.ai/document';
 import { ConfigEntity, type EntityOpts, PlaygroundConfigEntity } from '@flowgram.ai/core';
-import { Rectangle } from '@flowgram.ai/utils';
 
 import { DEFAULT_LABEL_ACTIVATE_HEIGHT } from '../components/utils';
 
@@ -60,14 +60,14 @@ export class FlowDragEntity extends ConfigEntity<FlowDragEntityConfig> {
     super(conf);
     this.playgroundConfigEntity = this.entityManager.getEntity<PlaygroundConfigEntity>(
       PlaygroundConfigEntity,
-      true,
+      true
     )!;
   }
 
   isCollision(
     transition: FlowNodeTransitionData,
     rect: Rectangle,
-    isBranch: boolean,
+    isBranch: boolean
   ): CollisionRetType {
     const scale = this.playgroundConfigEntity.finalScale || 0;
     if (isBranch) {
@@ -80,12 +80,12 @@ export class FlowDragEntity extends ConfigEntity<FlowDragEntityConfig> {
   isNodeCollision(
     transition: FlowNodeTransitionData,
     rect: Rectangle,
-    scale: number,
+    scale: number
   ): CollisionRetType {
     const { labels } = transition;
     const { isVertical } = transition.entity;
 
-    const hasCollision = labels.some(label => {
+    const hasCollision = labels.some((label) => {
       if (
         !label ||
         ![
@@ -107,7 +107,7 @@ export class FlowDragEntity extends ConfigEntity<FlowDragEntityConfig> {
         (label.offset.x - hoverWidth / 2) * scale,
         (label.offset.y - hoverHeight / 2) * scale,
         hoverWidth * scale,
-        hoverHeight * scale,
+        hoverHeight * scale
       );
       // 检测两个正方形是否相互碰撞
       return Rectangle.intersects(labelRect, rect);
@@ -124,13 +124,13 @@ export class FlowDragEntity extends ConfigEntity<FlowDragEntityConfig> {
   isBranchCollision(
     transition: FlowNodeTransitionData,
     rect: Rectangle,
-    scale: number,
+    scale: number
   ): CollisionRetType {
     const { labels } = transition;
     const { isVertical } = transition.entity;
 
     let labelOffsetType: LABEL_SIDE_TYPE = LABEL_SIDE_TYPE.NORMAL_BRANCH;
-    const hasCollision = labels.some(label => {
+    const hasCollision = labels.some((label) => {
       if (!label || label.type !== FlowTransitionLabelEnum.BRANCH_DRAGGING_LABEL) {
         return false;
       }
@@ -142,7 +142,7 @@ export class FlowDragEntity extends ConfigEntity<FlowDragEntityConfig> {
         (label.offset.x - hoverWidth / 2) * scale,
         (label.offset.y - hoverHeight / 2) * scale,
         hoverWidth * scale,
-        hoverHeight * scale,
+        hoverHeight * scale
       );
       // 检测两个正方形是否相互碰撞
       const collision = Rectangle.intersects(labelRect, rect);
@@ -240,7 +240,7 @@ export class FlowDragEntity extends ConfigEntity<FlowDragEntityConfig> {
     e: MouseEvent,
     containerDom: HTMLDivElement,
     x: number,
-    y: number,
+    y: number
   ): ScrollDirection | undefined {
     const playgroundConfig = this.playgroundConfigEntity.config;
     const currentScrollX = playgroundConfig.scrollX;
@@ -248,24 +248,25 @@ export class FlowDragEntity extends ConfigEntity<FlowDragEntityConfig> {
     this.containerDom = containerDom;
     this.containerX = x;
     this.containerY = y;
+    const clientRect = this.playgroundConfigEntity.playgroundDomNode.getBoundingClientRect();
 
-    const mouseToBottom = playgroundConfig.height + playgroundConfig.clientY - e.clientY;
+    const mouseToBottom = playgroundConfig.height + clientRect.y - e.clientY;
     if (mouseToBottom < SCROLL_BOUNDING) {
       this._startScrollY(currentScrollY, true);
       return ScrollDirection.BOTTOM;
     }
-    const mouseToTop = e.clientY - playgroundConfig.clientY;
+    const mouseToTop = e.clientY - clientRect.y;
     if (mouseToTop < SCROLL_BOUNDING) {
       this._startScrollY(currentScrollY, false);
       return ScrollDirection.TOP;
     }
     this._stopScrollY();
-    const mouseToRight = playgroundConfig.width + playgroundConfig.clientX - e.clientX;
+    const mouseToRight = playgroundConfig.width + clientRect.x - e.clientX;
     if (mouseToRight < SCROLL_BOUNDING) {
       this._startScrollX(currentScrollX, true);
       return ScrollDirection.RIGHT;
     }
-    const mouseToLeft = e.clientX - playgroundConfig.clientX;
+    const mouseToLeft = e.clientX - clientRect.x;
     if (mouseToLeft < SCROLL_BOUNDING + EDITOR_LEFT_BAR_WIDTH) {
       this._startScrollX(currentScrollX, false);
       return ScrollDirection.LEFT;

+ 10 - 15
packages/canvas-engine/renderer/src/layers/flow-context-menu-layer.tsx

@@ -6,6 +6,7 @@
 import React from 'react';
 
 import { inject, injectable } from 'inversify';
+import { domUtils } from '@flowgram.ai/utils';
 import {
   CommandRegistry,
   ContextMenuService,
@@ -17,7 +18,6 @@ import {
   PlaygroundConfigEntity,
   SelectionService,
 } from '@flowgram.ai/core';
-import { domUtils } from '@flowgram.ai/utils';
 
 import {
   FlowRendererCommandCategory,
@@ -86,18 +86,13 @@ export class FlowContextMenuLayer extends Layer {
           e.preventDefault();
 
           this.nodeRef.current?.setVisible(true);
-          const dragBlockX =
-            e.clientX -
-            (this.pipelineNode.offsetLeft || 0) -
-            this.playgroundConfigEntity.config.clientX;
-          const dragBlockY =
-            e.clientY -
-            (this.pipelineNode.offsetTop || 0) -
-            this.playgroundConfigEntity.config.clientY;
+          const clientBounds = this.playgroundConfigEntity.getClientBounds();
+          const dragBlockX = e.clientX - (this.pipelineNode.offsetLeft || 0) - clientBounds.x;
+          const dragBlockY = e.clientY - (this.pipelineNode.offsetTop || 0) - clientBounds.y;
           this.node.style.left = `${dragBlockX}px`;
           this.node.style.top = `${dragBlockY}px`;
         },
-        PipelineLayerPriority.BASE_LAYER,
+        PipelineLayerPriority.BASE_LAYER
       ),
       this.listenPlaygroundEvent('mousedown', () => {
         this.nodeRef.current?.setVisible(false);
@@ -126,10 +121,10 @@ export class FlowContextMenuLayer extends Layer {
    */
   renderCommandMenus(): JSX.Element[] {
     return this.commandRegistry.commands
-      .filter(cmd => cmd.category === FlowRendererCommandCategory.SELECTOR_BOX)
-      .map(cmd => {
+      .filter((cmd) => cmd.category === FlowRendererCommandCategory.SELECTOR_BOX)
+      .map((cmd) => {
         const CommandRenderer = this.rendererRegistry.getRendererComponent(
-          (cmd.icon as string) || cmd.id,
+          (cmd.icon as string) || cmd.id
         )?.renderer;
         return (
           <CommandRenderer
@@ -141,12 +136,12 @@ export class FlowContextMenuLayer extends Layer {
           />
         );
       })
-      .filter(c => c);
+      .filter((c) => c);
   }
 
   render(): JSX.Element {
     const SelectorBoxPopover = this.rendererRegistry.getRendererComponent(
-      FlowRendererKey.CONTEXT_MENU_POPOVER,
+      FlowRendererKey.CONTEXT_MENU_POPOVER
     ).renderer;
     return <SelectorBoxPopover ref={this.nodeRef} content={this.renderCommandMenus()} />;
   }

+ 3 - 2
packages/canvas-engine/renderer/src/layers/flow-drag-layer.tsx

@@ -150,15 +150,16 @@ export class FlowDragLayer extends Layer<FlowDragOptions> {
 
       if (this.containerRef.current) {
         const dragNode = this.containerRef.current.children?.[0];
+        const clientBounds = this.playgroundConfigEntity.getClientBounds();
         const dragBlockX =
           event.clientX -
           (this.pipelineNode.offsetLeft || 0) -
-          this.playgroundConfigEntity.config.clientX -
+          clientBounds.x -
           (dragNode.clientWidth - this.dragOffset.x) * scale;
         const dragBlockY =
           event.clientY -
           (this.pipelineNode.offsetTop || 0) -
-          this.playgroundConfigEntity.config.clientY -
+          clientBounds.y -
           (dragNode.clientHeight - this.dragOffset.y) * scale;
 
         // 获取节点状态是节点类型还是分支类型