| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358 |
- # 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="/flowgram.ai/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/coze-dev/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>
|