Kaynağa Gözat

feat(document): fromJSON supports incremental update node form & API optimizations (#888)

* feat(document): support update node form data

* feat(document): fromJSON supports diff JSON to perform CRUD on nodes and edges

* revert(document): fromJSON supports diff JSON to perform CRUD on nodes and edges
Louis Young 3 ay önce
ebeveyn
işleme
dd956ed312

+ 1 - 1
apps/demo-free-layout/src/components/comment/hooks/use-model.ts

@@ -42,7 +42,7 @@ export const useModel = () => {
   // 同步表单外部值变化:undo/redo/协同
   // 同步表单外部值变化:undo/redo/协同
   useEffect(() => {
   useEffect(() => {
     const disposer = formModel.onFormValuesChange(({ name }) => {
     const disposer = formModel.onFormValuesChange(({ name }) => {
-      if (name !== CommentEditorFormField.Note) {
+      if (name !== CommentEditorFormField.Note && name !== '') {
         return;
         return;
       }
       }
       const value = formModel.getValueIn<string>(CommentEditorFormField.Note);
       const value = formModel.getValueIn<string>(CommentEditorFormField.Note);

+ 1 - 1
apps/demo-free-layout/src/components/comment/hooks/use-size.ts

@@ -49,7 +49,7 @@ export const useSize = () => {
   // 同步表单外部值变化:初始化/undo/redo/协同
   // 同步表单外部值变化:初始化/undo/redo/协同
   useEffect(() => {
   useEffect(() => {
     const disposer = formModel.onFormValuesChange(({ name }) => {
     const disposer = formModel.onFormValuesChange(({ name }) => {
-      if (name !== CommentEditorFormField.Size) {
+      if (name !== CommentEditorFormField.Size && name !== '') {
         return;
         return;
       }
       }
       const newSize = formModel.getValueIn<{ width: number; height: number }>(
       const newSize = formModel.getValueIn<{ width: number; height: number }>(

+ 3 - 1
apps/demo-free-layout/src/hooks/use-editor-props.tsx

@@ -341,9 +341,11 @@ export function useEditorProps(
         createContextMenuPlugin({}),
         createContextMenuPlugin({}),
         /**
         /**
          * Runtime plugin
          * Runtime plugin
+         * ⚠️ Browser mode is for demo only; for production, please deploy the server-side runtime
+         * https://flowgram.ai/guide/runtime/introduction.html
          */
          */
         createRuntimePlugin({
         createRuntimePlugin({
-          mode: 'browser',
+          mode: 'browser', // browser mode is for demo only!
           // mode: 'server',
           // mode: 'server',
           // serverConfig: {
           // serverConfig: {
           //   domain: 'localhost',
           //   domain: 'localhost',

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

@@ -105,7 +105,7 @@ export class PasteShortcut implements ShortcutsHandler {
       parent = undefined;
       parent = undefined;
     }
     }
     this.applyOffset({ json, offset, parent });
     this.applyOffset({ json, offset, parent });
-    const { nodes } = this.document.renderJSON(json, {
+    const { nodes } = this.document.batchAddFromJSON(json, {
       parent,
       parent,
     });
     });
     this.selectNodes(nodes);
     this.selectNodes(nodes);

+ 1 - 1
apps/demo-nextjs-antd/src/editor/shortcuts/paste/index.ts

@@ -89,7 +89,7 @@ export class PasteShortcut implements ShortcutsHandler {
     const offset = this.calcPasteOffset(data.bounds);
     const offset = this.calcPasteOffset(data.bounds);
     const parent = this.getSelectedContainer();
     const parent = this.getSelectedContainer();
     this.applyOffset({ json, offset, parent });
     this.applyOffset({ json, offset, parent });
-    const { nodes } = this.document.renderJSON(json, {
+    const { nodes } = this.document.batchAddFromJSON(json, {
       parent,
       parent,
     });
     });
     this.selectNodes(nodes);
     this.selectNodes(nodes);

+ 61 - 33
packages/canvas-engine/free-layout-core/src/workflow-document.ts

@@ -122,6 +122,9 @@ export class WorkflowDocument extends FlowDocument {
     this.onLoadedEmitter.fire();
     this.onLoadedEmitter.fire();
   }
   }
 
 
+  /**
+   * @deprecated use `fromJSON` instead
+   */
   async reload(json: WorkflowJSON, delayTime = 0): Promise<void> {
   async reload(json: WorkflowJSON, delayTime = 0): Promise<void> {
     if (this.disposed) return;
     if (this.disposed) return;
     this._loading = true;
     this._loading = true;
@@ -147,7 +150,7 @@ export class WorkflowDocument extends FlowDocument {
     this.entityManager.changeEntityLocked = true;
     this.entityManager.changeEntityLocked = true;
 
 
     // 逐层渲染
     // 逐层渲染
-    this.renderJSON(workflowJSON);
+    this.batchAddFromJSON(workflowJSON);
 
 
     this.entityManager.changeEntityLocked = false;
     this.entityManager.changeEntityLocked = false;
     this.transformer.loading = false;
     this.transformer.loading = false;
@@ -173,12 +176,27 @@ export class WorkflowDocument extends FlowDocument {
    */
    */
   createWorkflowNode(
   createWorkflowNode(
     json: WorkflowNodeJSON,
     json: WorkflowNodeJSON,
+    /** @deprecated */
     isClone: boolean = false,
     isClone: boolean = false,
-    parentId?: string
+    parentID?: string
   ): WorkflowNodeEntity {
   ): WorkflowNodeEntity {
+    return this._createWorkflowNode(json, { parentID });
+  }
+
+  /**
+   * 创建流程节点
+   * @param json
+   */
+  private _createWorkflowNode(
+    json: WorkflowNodeJSON,
+    options?: {
+      parentID?: string;
+    }
+  ): WorkflowNodeEntity {
+    const { parentID } = options ?? {};
     // 是否是一个已经存在的节点
     // 是否是一个已经存在的节点
     const isExistedNode = this.getNode(json.id);
     const isExistedNode = this.getNode(json.id);
-    const parent = this.getNode(parentId ?? this.root.id) ?? this.root;
+    const parent = this.getNode(parentID ?? this.root.id) ?? this.root;
     const node = this.addNode(
     const node = this.addNode(
       {
       {
         ...json,
         ...json,
@@ -213,17 +231,21 @@ export class WorkflowDocument extends FlowDocument {
     });
     });
 
 
     // 初始化表单数据
     // 初始化表单数据
-    if (formMeta && formData && !formData.formModel.initialized) {
-      // 如果表单数据在前置步骤(fromJSON)内已定义,则跳过表单初始化逻辑
-      formData.createForm(formMeta, json.data);
-
-      formData.onDataChange(() => {
-        this.fireContentChange({
-          type: WorkflowContentChangeType.NODE_DATA_CHANGE,
-          toJSON: () => formData.toJSON(),
-          entity: node,
+    if (formMeta && formData) {
+      if (!formData.formModel.initialized) {
+        // 如果表单数据在前置步骤(fromJSON)内已定义,则跳过表单初始化逻辑
+        formData.createForm(formMeta, json.data);
+
+        formData.onDataChange(() => {
+          this.fireContentChange({
+            type: WorkflowContentChangeType.NODE_DATA_CHANGE,
+            toJSON: () => formData.toJSON(),
+            entity: node,
+          });
         });
         });
-      });
+      } else {
+        formData.updateFormValues(json.data);
+      }
     }
     }
     // 位置变更
     // 位置变更
     const positionData = node.getData<PositionData>(PositionData)!;
     const positionData = node.getData<PositionData>(PositionData)!;
@@ -267,11 +289,10 @@ export class WorkflowDocument extends FlowDocument {
 
 
     // 若存在子节点,则创建子节点
     // 若存在子节点,则创建子节点
     if (json.blocks) {
     if (json.blocks) {
-      this.renderJSON(
+      this.batchAddFromJSON(
         { nodes: json.blocks, edges: json.edges ?? [] },
         { nodes: json.blocks, edges: json.edges ?? [] },
         {
         {
           parent: node,
           parent: node,
-          isClone,
         }
         }
       );
       );
     }
     }
@@ -428,7 +449,7 @@ export class WorkflowDocument extends FlowDocument {
         throw new Error(`[WorkflowDocument.createWorkflowNodeByType] Node Id "${id}" duplicated.`);
         throw new Error(`[WorkflowDocument.createWorkflowNodeByType] Node Id "${id}" duplicated.`);
       }
       }
     }
     }
-    return this.createWorkflowNode(
+    return this._createWorkflowNode(
       {
       {
         ...json,
         ...json,
         id,
         id,
@@ -438,8 +459,7 @@ export class WorkflowDocument extends FlowDocument {
         blocks: json?.blocks,
         blocks: json?.blocks,
         edges: json?.edges,
         edges: json?.edges,
       },
       },
-      false,
-      parentID
+      { parentID }
     );
     );
   }
   }
 
 
@@ -449,6 +469,10 @@ export class WorkflowDocument extends FlowDocument {
       .filter((n) => n.id !== FlowNodeBaseType.ROOT);
       .filter((n) => n.id !== FlowNodeBaseType.ROOT);
   }
   }
 
 
+  getAllEdges(): WorkflowLineEntity[] {
+    return this.entityManager.getEntities<WorkflowLineEntity>(WorkflowLineEntity);
+  }
+
   getAllPorts(): WorkflowPortEntity[] {
   getAllPorts(): WorkflowPortEntity[] {
     return this.entityManager
     return this.entityManager
       .getEntities<WorkflowPortEntity>(WorkflowPortEntity)
       .getEntities<WorkflowPortEntity>(WorkflowPortEntity)
@@ -587,7 +611,7 @@ export class WorkflowDocument extends FlowDocument {
       x: json.meta!.position!.x + 30,
       x: json.meta!.position!.x + 30,
       y: json.meta!.position!.y + 30,
       y: json.meta!.position!.y + 30,
     };
     };
-    return this.createWorkflowNode(
+    return this._createWorkflowNode(
       {
       {
         id: newNodeId || `1${nanoid()}`,
         id: newNodeId || `1${nanoid()}`,
         type: node.flowNodeType,
         type: node.flowNodeType,
@@ -599,8 +623,9 @@ export class WorkflowDocument extends FlowDocument {
         blocks: json.blocks,
         blocks: json.blocks,
         edges: json.edges,
         edges: json.edges,
       },
       },
-      true,
-      node.parent?.id
+      {
+        parentID: node.parent?.id,
+      }
     );
     );
   }
   }
 
 
@@ -609,13 +634,13 @@ export class WorkflowDocument extends FlowDocument {
     nodeJSON: WorkflowNodeJSON,
     nodeJSON: WorkflowNodeJSON,
     newNodeId?: string | undefined,
     newNodeId?: string | undefined,
     position?: IPoint,
     position?: IPoint,
-    parentId?: string
+    parentID?: string
   ): WorkflowNodeEntity {
   ): WorkflowNodeEntity {
     position = position || {
     position = position || {
       x: nodeJSON.meta!.position!.x + 30,
       x: nodeJSON.meta!.position!.x + 30,
       y: nodeJSON.meta!.position!.y + 30,
       y: nodeJSON.meta!.position!.y + 30,
     };
     };
-    return this.createWorkflowNode(
+    return this._createWorkflowNode(
       {
       {
         id: newNodeId || `1${nanoid()}`,
         id: newNodeId || `1${nanoid()}`,
         type: flowNodeType,
         type: flowNodeType,
@@ -627,8 +652,9 @@ export class WorkflowDocument extends FlowDocument {
         blocks: nodeJSON.blocks,
         blocks: nodeJSON.blocks,
         edges: nodeJSON.edges,
         edges: nodeJSON.edges,
       },
       },
-      true,
-      parentId
+      {
+        parentID,
+      }
     );
     );
   }
   }
 
 
@@ -686,6 +712,7 @@ export class WorkflowDocument extends FlowDocument {
     json: WorkflowJSON,
     json: WorkflowJSON,
     options?: {
     options?: {
       parent?: WorkflowNodeEntity;
       parent?: WorkflowNodeEntity;
+      /** @deprecated useless api */
       isClone?: boolean;
       isClone?: boolean;
     }
     }
   ): {
   ): {
@@ -702,22 +729,23 @@ export class WorkflowDocument extends FlowDocument {
     json: WorkflowJSON,
     json: WorkflowJSON,
     options?: {
     options?: {
       parent?: WorkflowNodeEntity;
       parent?: WorkflowNodeEntity;
-      isClone?: boolean;
     }
     }
   ): {
   ): {
     nodes: WorkflowNodeEntity[];
     nodes: WorkflowNodeEntity[];
     edges: WorkflowLineEntity[];
     edges: WorkflowLineEntity[];
   } {
   } {
-    const { parent = this.root, isClone = false } = options ?? {};
+    const { parent = this.root } = options ?? {};
     // 创建节点
     // 创建节点
-    const containerID = this.getNodeSubCanvas(parent)?.canvasNode.id ?? parent.id;
+    const parentID = this.getNodeSubCanvas(parent)?.canvasNode.id ?? parent.id;
     const processedJSON = buildGroupJSON(json);
     const processedJSON = buildGroupJSON(json);
     const nodes = processedJSON.nodes.map((nodeJSON: WorkflowNodeJSON) =>
     const nodes = processedJSON.nodes.map((nodeJSON: WorkflowNodeJSON) =>
-      this.createWorkflowNode(nodeJSON, isClone, containerID)
+      this._createWorkflowNode(nodeJSON, {
+        parentID,
+      })
     );
     );
     // 创建线条
     // 创建线条
     const edges = processedJSON.edges
     const edges = processedJSON.edges
-      .map((edge) => this.createWorkflowLine(edge, containerID))
+      .map((edge) => this.createWorkflowLine(edge, parentID))
       .filter(Boolean) as WorkflowLineEntity[];
       .filter(Boolean) as WorkflowLineEntity[];
     return { nodes, edges };
     return { nodes, edges };
   }
   }
@@ -789,7 +817,7 @@ export class WorkflowDocument extends FlowDocument {
 
 
   private createWorkflowLine(
   private createWorkflowLine(
     json: WorkflowEdgeJSON,
     json: WorkflowEdgeJSON,
-    parentId?: string
+    parentID?: string
   ): WorkflowLineEntity | undefined {
   ): WorkflowLineEntity | undefined {
     const fromNode = this.getNode(json.sourceNodeID);
     const fromNode = this.getNode(json.sourceNodeID);
     const toNode = this.getNode(json.targetNodeID);
     const toNode = this.getNode(json.targetNodeID);
@@ -804,11 +832,11 @@ export class WorkflowDocument extends FlowDocument {
       toPort: json.targetPortID,
       toPort: json.targetPortID,
       data: json.data,
       data: json.data,
     };
     };
-    if (!parentId) {
+    if (!parentID) {
       return this.linesManager.createLine(lineInfo);
       return this.linesManager.createLine(lineInfo);
     }
     }
     // 父子节点之间连线,需替换父节点为子画布
     // 父子节点之间连线,需替换父节点为子画布
-    const canvasNode = this.getNode(parentId);
+    const canvasNode = this.getNode(parentID);
     if (!canvasNode) {
     if (!canvasNode) {
       return this.linesManager.createLine(lineInfo);
       return this.linesManager.createLine(lineInfo);
     }
     }