# 创建自由布局画布 本案例可通过 `npx @flowgram.ai/create-app@latest free-layout-simple` 安装,完整代码及效果见:
自由布局基础用法
### 1. 画布入口 - `FreeLayoutEditorProvider`: 画布配置器, 内部会生成 react-context 供子组件消费 - `EditorRenderer`: 为最终渲染的画布,可以包装在其他组件下边方便定制画布位置 ```tsx pure title="app.tsx" import { FreeLayoutEditorProvider, EditorRenderer, } from '@flowgram.ai/free-layout-editor'; import { useEditorProps } from './use-editor-props' // 画布详细的 props 配置 import { Tools } from './tools' // 画布工具 function App() { const editorProps = useEditorProps() return ( ); } ``` ### 2. 配置画布 画布配置采用声明式,提供 数据、渲染、事件、插件相关配置 ```tsx pure title="use-editor-props.tsx" import { useMemo } from 'react'; import { type FixedLayoutProps } from '@flowgram.ai/free-layout-editor'; import { createMinimapPlugin } from '@flowgram.ai/minimap-plugin'; import { intialData } from './initial-data' // 初始化数据 import { nodeRegistries } from './node-registries' // 节点声明配置 import { BaseNode } from './base-node' // 节点渲染 export function useEditorProps( ): FixedLayoutProps { return useMemo( () => ({ /** * 初始化数据 */ initialData, /** * 画布节点定义 */ nodeRegistries, /** * 物料 */ materials: { renderDefaultNode: BaseNode, // 节点渲染组件 }, /** * 节点引擎, 用于渲染节点表单 */ nodeEngine: { enable: true, }, /** * 画布历史记录, 用于控制 redo/undo */ history: { enable: true, enableChangeNode: true, // 用于监听节点表单数据变化 }, /** * 画布初始化回调 */ onInit: ctx => { // 如果要动态加载数据,可以通过如下方法异步执行 // ctx.docuemnt.fromJSON(initialData) }, /** * 画布第一次渲染完整回调 */ onAllLayersRendered: (ctx) => {}, /** * 画布销毁回调 */ onDispose: () => { }, plugins: () => [ /** * 缩略图插件 */ createMinimapPlugin({}), ], }), [], ); } ``` ### 3. 配置数据 画布文档数据采用树形结构,支持嵌套 :::note 文档数据基本结构: - nodes `array` 节点列表, 支持嵌套 - edges `array` 边列表 ::: :::note 节点数据基本结构: - id: `string` 节点唯一标识, 必须保证唯一 - meta: `object` 节点的 ui 配置信息,如自由布局的 `position` 信息放这里 - type: `string | number` 节点类型,会和 `nodeRegistries` 中的 `type` 对应 - data: `object` 节点表单数据, 业务可自定义 - blocks: `array` 节点的分支, 采用 `block` 更贴近 `Gramming` ::: :::note 边数据基本结构: - sourceNodeID: `string` 开始节点 id - targetNodeID: `string` 目标节点 id - sourcePortID?: `string | number` 开始端口 id, 缺省则采用开始节点的默认端口 - targetPortID?: `string | number` 目标端口 id, 缺省则采用目标节点的默认端口 ::: ```tsx pure title="initial-data.tsx" import { WorkflowJSON } from '@flowgram.ai/free-layout-editor'; export const initialData: WorkflowJSON = { nodes: [ { id: 'start_0', type: 'start', meta: { position: { x: 0, y: 0 }, }, data: { title: 'Start', content: 'Start content' }, }, { id: 'node_0', type: 'custom', meta: { position: { x: 400, y: 0 }, }, data: { title: 'Custom', content: 'Custom node content' }, }, { id: 'end_0', type: 'end', meta: { position: { x: 800, y: 0 }, }, data: { title: 'End', content: 'End content' }, }, ], edges: [ { sourceNodeID: 'start_0', targetNodeID: 'node_0', }, { sourceNodeID: 'node_0', targetNodeID: 'end_0', }, ], }; ``` ### 4. 声明节点 声明节点可以用于确定节点的类型及渲染方式 ```tsx pure title="node-registries.tsx" import { WorkflowNodeRegistry, ValidateTrigger } from '@flowgram.ai/free-layout-editor'; /** * You can customize your own node registry * 你可以自定义节点的注册器 */ export const nodeRegistries: WorkflowNodeRegistry[] = [ { type: 'start', meta: { isStart: true, // 标记为开始节点 deleteDisable: true, // 开始节点不能删除 copyDisable: true, // 开始节点不能复制 defaultPorts: [{ type: 'output' }], // 用于定义节点的输入和输出端口, 开始节点只有输出端口 // dynamicPort: true, // 用于动态端口,会寻找 data-port-id 和 data-port-type 属性的 dom 作为端口 }, /** * 配置节点表单的校验及渲染, * 注:validate 采用数据和渲染分离,保证节点即使不渲染也能对数据做校验 */ formMeta: { validateTrigger: ValidateTrigger.onChange, validate: { title: ({ value }) => (value ? undefined : 'Title is required'), }, /** * Render form */ render: () => ( <> {({ field }) =>
{field.value}
}
{({ field }) => } ) }, }, { type: 'end', meta: { deleteDisable: true, copyDisable: true, defaultPorts: [{ type: 'input' }], }, formMeta: { // ... } }, { type: 'custom', meta: { }, formMeta: { // ... }, defaultPorts: [{ type: 'output' }, { type: 'input' }], // 普通节点有两个端口 }, ]; ``` ### 5. 渲染节点 渲染节点用于添加样式、事件及表单渲染的位置 ```tsx pure title="base-node.tsx" import { useNodeRender, WorkflowNodeRenderer } from '@flowgram.ai/free-layout-editor'; export const BaseNode = () => { /** * 提供节点渲染相关的方法 */ const { form } = useNodeRender() /** * WorkflowNodeRenderer 会添加节点拖拽事件及 端口渲染,如果要深度定制,可以看该组件源代码: * https://github.com/bytedance/flowgram.ai/blob/main/packages/client/free-layout-editor/src/components/workflow-node-renderer.tsx */ return ( { // 表单渲染通过 formMeta 生成 form?.render() } ) }; ``` ### 6. 添加工具 工具主要用于控制画布缩放等操作, 工具汇总在 `usePlaygroundTools` 中, 而 `useClientContext` 用于获取画布的上下文, 里边包含画布的核心模块如 `history` ```tsx pure title="tools.tsx" import { useEffect, useState } from 'react' import { usePlaygroundTools, useClientContext } from '@flowgram.ai/free-layout-editor'; export function Tools() { const { history } = useClientContext(); const tools = usePlaygroundTools(); const [canUndo, setCanUndo] = useState(false); const [canRedo, setCanRedo] = useState(false); useEffect(() => { const disposable = history.undoRedoService.onChange(() => { setCanUndo(history.canUndo()); setCanRedo(history.canRedo()); }); return () => disposable.dispose(); }, [history]); return
{Math.floor(tools.zoom * 100)}%
} ``` ### 7. 效果 import { FreeLayoutSimple } from '../../../../components';