Explorar o código

feat(docs): add custom layer docs (#383)

xiamidaxia hai 6 meses
pai
achega
77d8a893cb

+ 2 - 2
apps/demo-free-layout-simple/src/components/node-add-panel.tsx

@@ -2,7 +2,7 @@ import React from 'react';
 
 import { WorkflowDragService, useService } from '@flowgram.ai/free-layout-editor';
 
-const cardkeys = ['Node1', 'Node2'];
+const cardkeys = ['Node1', 'Node2', 'Condition'];
 
 export const NodeAddPanel: React.FC = (props) => {
   const startDragSerivce = useService<WorkflowDragService>(WorkflowDragService);
@@ -14,7 +14,7 @@ export const NodeAddPanel: React.FC = (props) => {
           key={nodeType}
           className="demo-free-card"
           onMouseDown={(e) =>
-            startDragSerivce.startDragCard(nodeType, e, {
+            startDragSerivce.startDragCard(nodeType.toLowerCase(), e, {
               data: {
                 title: `New ${nodeType}`,
                 content: 'xxxx',

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

@@ -86,10 +86,24 @@ export const useEditorProps = () =>
          * Render Node
          */
         renderDefaultNode: (props: WorkflowNodeProps) => {
-          const { form } = useNodeRender();
+          const { form, node } = useNodeRender();
           return (
             <WorkflowNodeRenderer className="demo-free-node" node={props.node}>
               {form?.render()}
+              {node.flowNodeType === 'condition' && (
+                <div
+                  data-port-id="if"
+                  data-port-type="output"
+                  style={{ position: 'absolute', right: 0, top: '33%' }}
+                />
+              )}
+              {node.flowNodeType === 'condition' && (
+                <div
+                  data-port-id="else"
+                  data-port-type="output"
+                  style={{ position: 'absolute', right: 0, top: '66%' }}
+                />
+              )}
             </WorkflowNodeRenderer>
           );
         },
@@ -98,7 +112,7 @@ export const useEditorProps = () =>
        * Content change
        */
       onContentChange(ctx, event) {
-        // console.log('Auto Save: ', event, ctx.document.toJSON());
+        console.log('Auto Save: ', event, ctx.document.toJSON());
       },
       // /**
       //  * Node engine enable, you can configure formMeta in the FlowNodeRegistry

+ 57 - 6
apps/demo-free-layout-simple/src/initial-data.ts

@@ -6,7 +6,10 @@ export const initialData: WorkflowJSON = {
       id: 'start_0',
       type: 'start',
       meta: {
-        position: { x: 0, y: 0 },
+        position: {
+          x: 150,
+          y: 100,
+        },
       },
       data: {
         title: 'Start',
@@ -15,26 +18,60 @@ export const initialData: WorkflowJSON = {
     },
     {
       id: 'node_0',
-      type: 'custom',
+      type: 'condition',
       meta: {
-        position: { x: 400, y: 0 },
+        position: {
+          x: 550,
+          y: 100,
+        },
       },
       data: {
-        title: 'Custom',
-        content: 'Custom node content',
+        title: 'Condition',
+        content: 'Condition node content',
       },
     },
     {
       id: 'end_0',
       type: 'end',
       meta: {
-        position: { x: 800, y: 0 },
+        position: {
+          x: 1350,
+          y: 100,
+        },
       },
       data: {
         title: 'End',
         content: 'End content',
       },
     },
+    {
+      id: '144150',
+      type: 'node1',
+      meta: {
+        position: {
+          x: 950,
+          y: 0,
+        },
+      },
+      data: {
+        title: 'New Node1',
+        content: 'xxxx',
+      },
+    },
+    {
+      id: '118937',
+      type: 'node2',
+      meta: {
+        position: {
+          x: 950,
+          y: 200,
+        },
+      },
+      data: {
+        title: 'New Node2',
+        content: 'xxxx',
+      },
+    },
   ],
   edges: [
     {
@@ -43,6 +80,20 @@ export const initialData: WorkflowJSON = {
     },
     {
       sourceNodeID: 'node_0',
+      targetNodeID: '144150',
+      sourcePortID: 'if',
+    },
+    {
+      sourceNodeID: 'node_0',
+      targetNodeID: '118937',
+      sourcePortID: 'else',
+    },
+    {
+      sourceNodeID: '118937',
+      targetNodeID: 'end_0',
+    },
+    {
+      sourceNodeID: '144150',
       targetNodeID: 'end_0',
     },
   ],

+ 7 - 0
apps/demo-free-layout-simple/src/node-registries.ts

@@ -14,6 +14,13 @@ export const nodeRegistries: WorkflowNodeRegistry[] = [
       defaultPorts: [{ type: 'output' }], // Used to define the input and output ports, the start node only has the output port
     },
   },
+  {
+    type: 'condition',
+    meta: {
+      defaultPorts: [{ type: 'input' }],
+      useDynamicPort: true,
+    },
+  },
   {
     type: 'end',
     meta: {

+ 4 - 4
apps/docs/llms/llms-full.txt

@@ -6286,14 +6286,14 @@ url: /guide/advanced/free-layout/port.md
 
 * 动态端口
 
-节点声明添加 `dynamicPorts` , 当设置为 true 则会到节点dom 上寻找 data-port-id 和 data-port-type 属性的 dom 作为端口
+节点声明添加 `useDynamicPort` , 当设置为 true 则会到节点dom 上寻找 data-port-id 和 data-port-type 属性的 dom 作为端口
 
 ```ts pure title="node-registries.ts"
 {
   type: 'condition',
   meta: {
     defaultPorts: [{ type: 'input'}]
-    dynamicPort: true
+    useDynamicPort: true
   },
 }
 
@@ -7528,7 +7528,7 @@ export const useEditorProps = () => {
   return {
     // ...
     nodeEngine: {
-      enable: false, // 不开启节点引擎,则无法使用 from
+      enable: false, // 不开启节点引擎,则无法使用 form
     },
     history: {
       enable: true,
@@ -8745,7 +8745,7 @@ export const nodeRegistries: WorkflowNodeRegistry[] = [
       deleteDisable: true, // 开始节点不能删除
       copyDisable: true, // 开始节点不能复制
       defaultPorts: [{ type: 'output' }], // 用于定义节点的输入和输出端口, 开始节点只有输出端口
-      // dynamicPort: true, // 用于动态端口,会寻找 data-port-id 和 data-port-type 属性的 dom 作为端口
+      // useDynamicPort: true, // 用于动态端口,会寻找 data-port-id 和 data-port-type 属性的 dom 作为端口
     },
     /**
      * 配置节点表单的校验及渲染,

+ 1 - 0
apps/docs/src/en/guide/advanced/_meta.json

@@ -21,5 +21,6 @@
   "minimap",
   "custom-plugin",
   "custom-service",
+  "custom-layer",
   "form-materials"
 ]

+ 113 - 0
apps/docs/src/en/guide/advanced/custom-layer.mdx

@@ -0,0 +1,113 @@
+# Custom Layer
+
+We split the canvas into multiple Layers, implementing the concept of interaction layering for better plugin management. For more details, see [Canvas Engine](guide/concepts/canvas-engine.html)
+
+1. Use `observeEntityDatas`, `observeEntities`, and `observeEntity` to monitor updates to any data module of canvas nodes
+2. Use `onZoom`, `onScroll`, `onViewportChange`, etc. to monitor canvas zooming or scrolling
+3. Use `render` to insert React elements into the canvas, such as drawing SVG lines
+
+![Aspect-oriented programming](@/public/en-layer-uml.jpg)
+
+## Creating a Layer
+
+```tsx pure
+import { FreeLayoutPluginContext, inject, injectable, FlowNodeEntity, FlowNodeTransformData, FlowNodeFormData } from '@flowgram.ai/free-layout-editor'
+
+@injectable()
+export class MyLayer extends Layer {
+  @inject(FreeLayoutPluginContext) ctx: FreeLayoutPluginContext
+  // Can monitor node width, height, and position changes
+  @observeEntityDatas(FlowNodeEntity, FlowNodeTransformData) transformDatas: FlowNodeTransformData[];
+  // Can monitor form data changes, connection data can be stored in forms
+  @observeEntityDatas(FlowNodeEntity, FlowNodeFormData) formDatas: FlowNodeFormData[];
+  onReady() {
+    // Can also add styles
+    // zIndex controls whether to overlay nodes, nodes default to 10, greater than 10 will be above nodes
+    this.node.style.zIndex = 11;
+  }
+  onZoom(scale) {
+    // Scale with canvas
+    this.node.style.transform = `scale(${scale})`;
+  }
+  render() {
+    return <div>{...}</div>
+  }
+}
+
+```
+
+## Adding to Canvas
+
+- Through use-editor-props
+
+```ts pure
+{
+  onInit: (ctx) => {
+    ctx.playground.registerLayer(MyLayer)
+  }
+}
+```
+
+- Through plugin
+
+```tsx pure
+import { FreeLayoutPluginContext } from '@flowgram.ai/free-layout-editor'
+
+export const createMyPlugin = definePluginCreator<{}, FreeLayoutPluginContext>({
+  onInit: (ctx, opts) => {
+    ctx.playground.registerLayer(MyLayer)
+  },
+});
+```
+
+## Layer Lifecycle Description
+
+```ts
+interface Layer {
+    /**
+     * Triggered during initialization
+     */
+    onReady?(): void;
+
+    /**
+     * Triggered when playground size changes
+     */
+    onResize?(size: PipelineDimension): void;
+
+    /**
+     * Triggered when playground is focused
+     */
+    onFocus?(): void;
+
+    /**
+     * Triggered when playground loses focus
+     */
+    onBlur?(): void;
+
+    /**
+     * Monitor zoom
+     */
+    onZoom?(scale: number): void;
+
+    /**
+     * Monitor scroll
+     */
+    onScroll?(scroll: { scrollX: number; scrollY: number }): void;
+
+    /**
+     * Triggered when viewport updates
+     */
+    onViewportChange?(): void;
+
+    /**
+     * Triggered when readonly or disabled state changes
+     * @param state
+     */
+    onReadonlyOrDisabledChange?(state: { disabled: boolean; readonly: boolean }): void;
+
+    /**
+     * Data updates automatically trigger React render, if not provided React rendering won't be called
+     */
+    render?(): JSX.Element
+ }
+```

+ 2 - 2
apps/docs/src/en/guide/advanced/free-layout/port.mdx

@@ -22,7 +22,7 @@ Add `defaultPorts` to node declaration, such as `{ type: 'input' }`, which will
 
 - Dynamic Ports
 
-Add `dynamicPorts` to node declaration, when set to true it will look for DOM elements with `data-port-id` and `data-port-type` attributes as ports
+Add `useDynamicPort` to node declaration, when set to true it will look for DOM elements with `data-port-id` and `data-port-type` attributes as ports
 
 
 ```ts pure title="node-registries.ts"
@@ -30,7 +30,7 @@ Add `dynamicPorts` to node declaration, when set to true it will look for DOM el
   type: 'condition',
   meta: {
     defaultPorts: [{ type: 'input'}]
-    dynamicPort: true
+    useDynamicPort: true
   },
 }
 

+ 1 - 1
apps/docs/src/en/guide/getting-started/create-free-layout-simple.mdx

@@ -220,7 +220,7 @@ export const nodeRegistries: WorkflowNodeRegistry[] = [
       deleteDisable: true, // Start node cannot be deleted
       copyDisable: true, // Start node cannot be copied
       defaultPorts: [{ type: 'output' }], // Define node input and output ports, start node only has output port
-      // dynamicPort: true, // For dynamic ports, will look for DOM with data-port-id and data-port-type attributes as ports
+      // useDynamicPort: true, // For dynamic ports, will look for DOM with data-port-id and data-port-type attributes as ports
     },
     /**
      * Configure node form validation and rendering

+ 1 - 0
apps/docs/src/zh/guide/advanced/_meta.json

@@ -21,5 +21,6 @@
   "minimap",
   "custom-plugin",
   "custom-service",
+  "custom-layer",
   "form-materials"
 ]

+ 113 - 0
apps/docs/src/zh/guide/advanced/custom-layer.mdx

@@ -0,0 +1,113 @@
+#  自定义 Layer
+
+我们将画布拆分成多个 Layer,实现交互分层的思想,便于插件化管理,详细见 [画布引擎](guide/concepts/canvas-engine.html)
+
+1. 通过 `observeEntityDatas` `observeEntities` `observeEntity` 监听画布节点任意数据模块的更新
+2. 通过 `onZoom` `onScroll` `onViewportChange` 等监听画布的缩放或者滚动
+3. 通过 `render` 往画布中插入 react 元素, 如绘制 svg 线条
+
+![Layer](@/public/layer-uml.jpg)
+
+## 创建 Layer
+
+```tsx pure
+import { FreeLayoutPluginContext, inject, injectable, FlowNodeEntity, FlowNodeTransformData, FlowNodeFormData } from '@flowgram.ai/free-layout-editor'
+
+@injectable()
+export class MyLayer extends Layer {
+  @inject(FreeLayoutPluginContext) ctx: FreeLayoutPluginContext
+  // 可以监听节点的宽高位置变化
+  @observeEntityDatas(FlowNodeEntity, FlowNodeTransformData) transformDatas: FlowNodeTransformData[];
+  // 可以监听表单数据变化,连线数据可以存在表单里
+  @observeEntityDatas(FlowNodeEntity, FlowNodeFormData) formDatas: FlowNodeFormData[];
+  onReady() {
+    // 也可以添加样式
+    // zIndex可以控制是否要盖在节点, 节点默认是 10,大于 10 则在节点上边
+    this.node.style.zIndex = 11;
+  }
+  onZoom(scale) {
+    // 跟着画布缩放
+    this.node.style.transform = `scale(${scale})`;
+  }
+  render() {
+    return <div>{...}</div>
+  }
+}
+
+```
+
+## 加入到画布
+
+- 通过 use-editor-props
+
+```ts pure
+{
+  onInit: (ctx) => {
+    ctx.playground.registerLayer(MyLayer)
+  }
+}
+```
+
+- 通过插件添加
+
+```tsx pure
+import { FreeLayoutPluginContext } from '@flowgram.ai/free-layout-editor'
+
+export const createMyPlugin = definePluginCreator<{}, FreeLayoutPluginContext>({
+  onInit: (ctx, opts) => {
+    ctx.playground.registerLayer(MyLayer)
+  },
+});
+```
+
+## Layer 生命周期说明
+
+```ts
+interface Layer {
+    /**
+     * 初始化时候触发
+     */
+    onReady?(): void;
+
+    /**
+     * playground 大小变化时候会触发
+     */
+    onResize?(size: PipelineDimension): void;
+
+    /**
+     * playground focus 时候触发
+     */
+    onFocus?(): void;
+
+    /**
+     * playground blur 时候触发
+     */
+    onBlur?(): void;
+
+    /**
+     * 监听缩放
+     */
+    onZoom?(scale: number): void;
+
+    /**
+     * 监听滚动
+     */
+    onScroll?(scroll: { scrollX: number; scrollY: number }): void;
+
+    /**
+     * viewport 更新触发
+     */
+    onViewportChange?(): void;
+
+    /**
+     * readonly 或 disable 状态变化
+     * @param state
+     */
+    onReadonlyOrDisabledChange?(state: { disabled: boolean; readonly: boolean }): void;
+
+    /**
+   * 数据更新自动触发react render,如果不提供则不会调用react渲染
+   */
+    render?(): JSX.Element
+ }
+```

+ 2 - 2
apps/docs/src/zh/guide/advanced/free-layout/port.mdx

@@ -22,7 +22,7 @@
 
 - 动态端口
 
-节点声明添加 `dynamicPorts` , 当设置为 true 则会到节点dom 上寻找 data-port-id 和 data-port-type 属性的 dom 作为端口
+节点声明添加 `useDynamicPort` , 当设置为 true 则会到节点dom 上寻找 data-port-id 和 data-port-type 属性的 dom 作为端口
 
 
 ```ts pure title="node-registries.ts"
@@ -30,7 +30,7 @@
   type: 'condition',
   meta: {
     defaultPorts: [{ type: 'input'}]
-    dynamicPort: true
+    useDynamicPort: true
   },
 }
 

+ 1 - 1
apps/docs/src/zh/guide/advanced/without-form.mdx

@@ -8,7 +8,7 @@ export const useEditorProps = () => {
   return {
     // ...
     nodeEngine: {
-      enable: false, // 不开启节点引擎,则无法使用 from
+      enable: false, // 不开启节点引擎,则无法使用 form
     },
     history: {
       enable: true,

+ 1 - 1
apps/docs/src/zh/guide/getting-started/create-free-layout-simple.mdx

@@ -229,7 +229,7 @@ export const nodeRegistries: WorkflowNodeRegistry[] = [
       deleteDisable: true, // 开始节点不能删除
       copyDisable: true, // 开始节点不能复制
       defaultPorts: [{ type: 'output' }], // 用于定义节点的输入和输出端口, 开始节点只有输出端口
-      // dynamicPort: true, // 用于动态端口,会寻找 data-port-id 和 data-port-type 属性的 dom 作为端口
+      // useDynamicPort: true, // 用于动态端口,会寻找 data-port-id 和 data-port-type 属性的 dom 作为端口
     },
     /**
      * 配置节点表单的校验及渲染,

+ 22 - 0
apps/docs/src/zh/guide/question.mdx

@@ -1,3 +1,25 @@
+# 注意事项及常见问题
+
+# 注意事项
+
+1. 画布的 editor 版本及导入的插件版本必须一致,如果出现以下错误都是这个问题造成
+
+
+2. 画布的事件监听,在 react 中使用都要配套写销毁逻辑,防止无限注册导致内存泄漏
+
+```tsx pure
+function SomeReactComp() {
+  useEffect(() => {
+    const toDispose = ctx.document.onContentChange(() => {
+      // DO Something
+    })
+    return () => toDispose.dispose() // Destroy Event
+  }, [])
+}
+```
+
+
+
 # 常见问题