mirror of
https://gitee.com/ByteDance/flowgram.ai.git
synced 2025-07-07 17:43:29 +08:00
334 lines
8.5 KiB
Plaintext
334 lines
8.5 KiB
Plaintext
|
||
# 创建自由布局画布
|
||
|
||
本案例可通过 `npx @flowgram.ai/create-app@latest free-layout-simple` 安装,完整代码及效果见:
|
||
|
||
<div className="rs-tip">
|
||
<a className="rs-link" href="/flowgram.ai/examples/free-layout/free-layout-simple.html">
|
||
自由布局基础用法
|
||
</a>
|
||
</div>
|
||
|
||
|
||
### 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 (
|
||
<FixedLayoutEditorProvider {...editorProps}>
|
||
<EditorRenderer className="demo-editor" />
|
||
<Tools />
|
||
</FixedLayoutEditorProvider>
|
||
);
|
||
}
|
||
```
|
||
|
||
### 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<FixedLayoutProps>(
|
||
() => ({
|
||
/**
|
||
* 初始化数据
|
||
*/
|
||
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 name="title">
|
||
{({ field }) => <div className="demo-free-node-title">{field.value}</div>}
|
||
</Field>
|
||
<Field name="content">
|
||
{({ field }) => <input onChange={field.onChange} value={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/coze-dev/flowgram.ai/blob/main/packages/client/free-layout-editor/src/components/workflow-node-renderer.tsx
|
||
*/
|
||
return (
|
||
<WorkflowNodeRenderer className="demo-free-node" node={props.node}>
|
||
{
|
||
// 表单渲染通过 formMeta 生成
|
||
form?.render()
|
||
}
|
||
</WorkflowNodeRenderer>
|
||
)
|
||
};
|
||
|
||
```
|
||
|
||
|
||
### 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 <div style={{ position: 'absolute', zIndex: 10, bottom: 16, left: 226, display: 'flex', gap: 8 }}>
|
||
<button onClick={() => tools.zoomin()}>ZoomIn</button>
|
||
<button onClick={() => tools.zoomout()}>ZoomOut</button>
|
||
<button onClick={() => tools.fitView()}>Fitview</button>
|
||
<button onClick={() => tools.autoLayout()}>AutoLayout</button>
|
||
<button onClick={() => history.undo()} disabled={!canUndo}>Undo</button>
|
||
<button onClick={() => history.redo()} disabled={!canRedo}>Redo</button>
|
||
<span>{Math.floor(tools.zoom * 100)}%</span>
|
||
</div>
|
||
}
|
||
```
|
||
### 7. 效果
|
||
|
||
import { FreeLayoutSimple } from '../../../../components';
|
||
|
||
<div style={{ position: 'relative', width: '100%', height: '600px'}}>
|
||
<FreeLayoutSimple />
|
||
</div>
|