# 创建固定布局画布
本案例可通过 `npx @flowgram.ai/create-app@latest fixed-layout-simple` 安装,完整代码及效果见:
文件结构:
```
- hooks
- use-editor-props.ts # 画布配置
- components
- base-node.tsx # 节点渲染
- tools.tsx # 画布工具栏
- initial-data.ts # 初始化数据
- node-registries.ts # 节点配置
- app.tsx # 画布入口
```
### 1. 画布入口
- `FixedLayoutEditorProvider`: 画布配置器, 内部会生成 react-context 供子组件消费
- `EditorRenderer`: 为最终渲染的画布,可以包装在其他组件下边方便定制画布位置
```tsx pure title="app.tsx"
import {
FixedLayoutEditorProvider,
EditorRenderer,
} from '@flowgram.ai/fixed-layout-editor';
import '@flowgram.ai/fixed-layout-editor/index.css'; // 加载样式
import { useEditorProps } from './hooks/use-editor-props' // 画布详细的 props 配置
import { Tools } from './components/tools' // 画布工具
function App() {
const editorProps = useEditorProps()
return (
);
}
```
### 2. 配置画布
画布配置采用声明式,提供 数据、渲染、事件、插件相关配置
```tsx pure title="hooks/use-editor-props.tsx"
import { useMemo } from 'react';
import { type FixedLayoutProps } from '@flowgram.ai/fixed-layout-editor';
import { defaultFixedSemiMaterials } from '@flowgram.ai/fixed-semi-materials';
import { createMinimapPlugin } from '@flowgram.ai/minimap-plugin';
import { initialData } from './initial-data' // 初始化数据
import { nodeRegistries } from './node-registries' // 节点声明配置
import { BaseNode } from './base-node' // 节点渲染
export function useEditorProps(
): FixedLayoutProps {
return useMemo(
() => ({
/**
* 初始化数据
*/
initialData,
/**
* 画布节点定义
*/
nodeRegistries,
/**
* 可以通过 key 自定义 UI 组件, 比如添加按钮,这里提供了一套 semi 组件方便快速验证, 如果需要深度定制,参考:
* https://github.com/bytedance/flowgram.ai/blob/main/packages/materials/fixed-semi-materials/src/components/index.tsx
*/
materials: {
components: {
...defaultFixedSemiMaterials,
// [FlowRendererKey.ADDER]: NodeAdder,
// [FlowRendererKey.BRANCH_ADDER]: BranchAdder,
},
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` 节点列表, 支持嵌套
:::
:::note 节点数据基本结构:
- id: `string` 节点唯一标识, 必须保证唯一
- meta: `object` 节点的 ui 配置信息,如自由布局的 `position` 信息放这里
- type: `string | number` 节点类型,会和 `nodeRegistries` 中的 `type` 对应
- data: `object` 节点表单数据
- blocks: `array` 节点的分支, 采用 `block` 更贴近 `Gramming`
:::
```tsx pure title="initial-data.tsx"
import { FlowDocumentJSON } from '@flowgram.ai/fixed-layout-editor';
/**
* 配置流程数据,数据为 blocks 嵌套的格式
*/
export const initialData: FlowDocumentJSON = {
nodes: [
// 开始节点
{
id: 'start_0',
type: 'start',
data: {
title: 'Start',
content: 'start content'
},
blocks: [],
},
// 分支节点
{
id: 'condition_0',
type: 'condition',
data: {
title: 'Condition'
},
blocks: [
{
id: 'branch_0',
type: 'block',
data: {
title: 'Branch 0',
content: 'branch 1 content'
},
blocks: [
{
id: 'custom_0',
type: 'custom',
data: {
title: 'Custom',
content: 'custrom content'
},
},
],
},
{
id: 'branch_1',
type: 'block',
data: {
title: 'Branch 1',
content: 'branch 1 content'
},
blocks: [],
},
],
},
// 结束节点
{
id: 'end_0',
type: 'end',
data: {
title: 'End',
content: 'end content'
},
},
],
};
```
### 4. 声明节点
声明节点可以用于确定节点的类型及渲染方式
```tsx pure title="node-registries.tsx"
import { FlowNodeRegistry, ValidateTrigger } from '@flowgram.ai/fixed-layout-editor';
/**
* 自定义节点注册
*/
export const nodeRegistries: FlowNodeRegistry[] = [
{
/**
* 自定义节点类型
*/
type: 'condition',
/**
* 自定义节点扩展:
* - loop: 扩展为循环节点
* - start: 扩展为开始节点
* - dynamicSplit: 扩展为分支节点
* - end: 扩展为结束节点
* - tryCatch: 扩展为 tryCatch 节点
* - default: 扩展为普通节点 (默认)
*/
extend: 'dynamicSplit',
/**
* 节点配置信息
*/
meta: {
// isStart: false, // 是否为开始节点
// isNodeEnd: false, // 是否为结束节点,结束节点后边无法再添加节点
// draggable: false, // 是否可拖拽,如开始节点和结束节点无法拖拽
// selectable: false, // 触发器等开始节点不能被框选
// deleteDisable: true, // 禁止删除
// copyDisable: true, // 禁止copy
// addDisable: true, // 禁止添加
},
/**
* 配置节点表单的校验及渲染,
* 注:validate 采用数据和渲染分离,保证节点即使不渲染也能对数据做校验
*/
formMeta: {
validateTrigger: ValidateTrigger.onChange,
validate: {
title: ({ value }) => (value ? undefined : 'Title is required'),
},
/**
* Render form
*/
render: () => (
<>
{({ field }) => {field.value}
}
{({ field }) => }
>
)
},
},
];
```
### 5. 渲染节点
渲染节点用于添加样式、事件及表单渲染的位置
```tsx pure title="components/base-node.tsx"
import { useNodeRender } from '@flowgram.ai/fixed-layout-editor';
export const BaseNode = () => {
/**
* 提供节点渲染相关的方法
*/
const nodeRender = useNodeRender();
/**
* 只有在节点引擎开启时候才能使用表单
*/
const form = nodeRender.form;
return (
{
// 触发拖拽
nodeRender.startDrag(e);
e.stopPropagation();
}}
style={{
// BlockOrderIcon 表示为分支的第一个节点,BlockIcon 则表示整个 condition 的头部节点
...(nodeRender.isBlockOrderIcon || nodeRender.isBlockIcon ? { width: 260 } : {}),
outline: form?.state.invalid ? '1px solid red' : 'none', // 表单校验错误让边框标红
}}
>
{
// 表单渲染通过 formMeta 生成
form?.render()
}
);
};
```
### 6. 添加工具
工具主要用于控制画布缩放等操作, 工具汇总在 `usePlaygroundTools` 中, 而 `useClientContext` 用于获取画布的上下文, 里边包含画布的核心模块如 `history`
```tsx pure title="components/tools.tsx"
import { useEffect, useState } from 'react'
import { usePlaygroundTools, useClientContext } from '@flowgram.ai/fixed-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 { FixedLayoutSimple } from '../../../../components';