flowgram.ai/apps/docs/src/en/guide/getting-started/create-fixed-layout-simple.mdx
2025-03-03 14:22:10 +08:00

359 lines
9.9 KiB
Plaintext

# Create a Fixed Layout Canvas
This case can be installed by `npx @flowgram.ai/create-app@latest fixed-layout-simple`, the complete code and effect are as follows:
<div className="rs-tip">
<a className="rs-link" href="/en/examples/fixed-layout/fixed-layout-simple.html">
Fixed Layout Basic Usage
</a>
</div>
### 1. Canvas Entry
- `FixedLayoutEditorProvider`: Canvas configurator, internally generates a react-context for consumption by child components
- `EditorRenderer`: The final rendered canvas, can be wrapped in other components to customize the canvas position
```tsx pure title="app.tsx"
import {
FixedLayoutEditorProvider,
EditorRenderer,
} from '@flowgram.ai/fixed-layout-editor';
import { useEditorProps } from './use-editor-props' // Canvas detailed props configuration
import { Tools } from './tools' // Canvas tools
function App() {
const editorProps = useEditorProps()
return (
<FixedLayoutEditorProvider {...editorProps}>
<EditorRenderer className="demo-editor" />
<Tools />
</FixedLayoutEditorProvider>
);
}
```
### 2. Configure Canvas
Canvas configuration uses declarative, providing data, rendering, event, plugin related configurations
```tsx pure title="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 { intialData } from './initial-data' // Initial data
import { nodeRegistries } from './node-registries' // Node declaration configuration
import { BaseNode } from './base-node' // Node rendering
export function useEditorProps(
): FixedLayoutProps {
return useMemo<FixedLayoutProps>(
() => ({
/**
* Initial data
*/
initialData,
/**
* Canvas node definition
*/
nodeRegistries,
/**
* Custom UI components can be defined by key, for example, add a button, here is a semi-component set for quick verification, if you need deep customization, refer to:
* https://github.com/bytedance/flowgram.ai/blob/main/packages/materials/fixed-semi-materials/src/components/index.tsx
*/
materials: {
renderNodes: {
...defaultFixedSemiMaterials,
// [FlowRendererKey.ADDER]: NodeAdder,
// [FlowRendererKey.BRANCH_ADDER]: BranchAdder,
},
renderDefaultNode: BaseNode, // Node rendering component
},
/**
* Node engine, used to render node form
*/
nodeEngine: {
enable: true,
},
/**
* Canvas history, used to control redo/undo
*/
history: {
enable: true,
enableChangeNode: true, // Used to listen to node form data changes
},
/**
* Canvas initialization callback
*/
onInit: ctx => {
// If you want to dynamically load data, you can execute it asynchronously by the following method
// ctx.docuemnt.fromJSON(initialData)
},
/**
* Canvas first rendering completed callback
*/
onAllLayersRendered: (ctx) => {},
/**
* Canvas destruction callback
*/
onDispose: () => { },
plugins: () => [
/**
* Minimap plugin
*/
createMinimapPlugin({}),
],
}),
[],
);
}
```
### 3. Configure Data
Canvas document data uses a tree structure, supports nesting
:::note Document data basic structure:
- nodes `array` Node list, supports nesting
:::
:::note Node data basic structure:
- id: `string` Node unique identifier, must be unique
- meta: `object` Node ui configuration information, such as free layout `position` information
- type: `string | number` Node type, will correspond to `type` in `nodeRegistries`
- data: `object` Node form data
- blocks: `array` Node branch, using `block` is more in line with `Gramming`
:::
```tsx pure title="initial-data.tsx"
import { FlowDocumentJSON } from '@flowgram.ai/fixed-layout-editor';
/**
* Configure workflow data, data is in the format of nested blocks
*/
export const initialData: FlowDocumentJSON = {
nodes: [
// Start node
{
id: 'start_0',
type: 'start',
data: {
title: 'Start',
content: 'start content'
},
blocks: [],
},
// Branch node
{
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: [],
},
],
},
// End node
{
id: 'end_0',
type: 'end',
data: {
title: 'End',
content: 'end content'
},
},
],
};
```
### 4. Declare Node
Declare node can be used to determine the type and rendering method of the node
```tsx pure title="node-registries.tsx"
import { FlowNodeRegistry, ValidateTrigger } from '@flowgram.ai/fixed-layout-editor';
/**
* Custom node registration
*/
export const nodeRegistries: FlowNodeRegistry[] = [
{
/**
* Custom node type
*/
type: 'condition',
/**
* Custom node extension:
* - loop: Extended to loop node
* - start: Extended to start node
* - dynamicSplit: Extended to branch node
* - end: Extended to end node
* - tryCatch: Extended to tryCatch node
* - default: Extended to normal node (default)
*/
extend: 'dynamicSplit',
/**
* Node configuration information
*/
meta: {
// isStart: false, // Whether it is a start node
// isNodeEnd: false, // Whether it is an end node, the node after the end node cannot be added
// draggable: false, // Whether it can be dragged, such as the start node and the end node cannot be dragged
// selectable: false, // The trigger start node cannot be selected
// deleteDisable: true, // Disable deletion
// copyDisable: true, // Disable copy
// addDisable: true, // Disable addition
},
/**
* Configure node form validation and rendering,
* Note: validate uses data and rendering separation, ensuring that even if the node is not rendered, the data can be validated
*/
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>
</>
)
},
},
];
```
### 5. Render Node
The rendering node is used to add styles, events, and form rendering positions
```tsx pure title="base-node.tsx"
import { useNodeRender } from '@flowgram.ai/fixed-layout-editor';
export const BaseNode = () => {
/**
* Provides methods related to node rendering
*/
const nodeRender = useNodeRender();
/**
* Forms can only be used when the node engine is enabled
*/
const form = nodeRender.form;
return (
<div
className="demo-fixed-node"
onMouseEnter={nodeRender.onMouseEnter}
onMouseLeave={nodeRender.onMouseLeave}
onMouseDown={e => {
// Trigger drag
nodeRender.startDrag(e);
e.stopPropagation();
}}
style={{
// BlockOrderIcon represents the first node of a branch, BlockIcon represents the header node of the entire condition
...(nodeRender.isBlockOrderIcon || nodeRender.isBlockIcon ? { width: 260 } : {}),
outline: form?.state.invalid ? '1px solid red' : 'none', // Red border for form validation errors
}}
>
{
// Form rendering is generated through formMeta
form?.render()
}
</div>
);
};
```
### 6. Add Tools
Tools are mainly used to control canvas zooming and other operations. Tools are consolidated in `usePlaygroundTools`, while `useClientContext` is used to get the canvas context, which contains core modules such as `history`
```tsx pure title="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 <div style={{ position: 'absolute', zIndex: 10, bottom: 16, left: 16, 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.changeLayout()}>ChangeLayout</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. Effect
import { FixedLayoutSimple } from '../../../../components';
<div style={{ position: 'relative', width: '100%', height: '600px'}}>
<FixedLayoutSimple />
</div>