create-fixed-layout-simple.mdx 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358
  1. # Create a Fixed Layout Canvas
  2. This case can be installed by `npx @flowgram.ai/create-app@latest fixed-layout-simple`, the complete code and effect are as follows:
  3. <div className="rs-tip">
  4. <a className="rs-link" href="/flowgram.ai/examples/fixed-layout/fixed-layout-simple.html">
  5. Fixed Layout Basic Usage
  6. </a>
  7. </div>
  8. ### 1. Canvas Entry
  9. - `FixedLayoutEditorProvider`: Canvas configurator, internally generates a react-context for consumption by child components
  10. - `EditorRenderer`: The final rendered canvas, can be wrapped in other components to customize the canvas position
  11. ```tsx pure title="app.tsx"
  12. import {
  13. FixedLayoutEditorProvider,
  14. EditorRenderer,
  15. } from '@flowgram.ai/fixed-layout-editor';
  16. import { useEditorProps } from './use-editor-props' // Canvas detailed props configuration
  17. import { Tools } from './tools' // Canvas tools
  18. function App() {
  19. const editorProps = useEditorProps()
  20. return (
  21. <FixedLayoutEditorProvider {...editorProps}>
  22. <EditorRenderer className="demo-editor" />
  23. <Tools />
  24. </FixedLayoutEditorProvider>
  25. );
  26. }
  27. ```
  28. ### 2. Configure Canvas
  29. Canvas configuration uses declarative, providing data, rendering, event, plugin related configurations
  30. ```tsx pure title="use-editor-props.tsx"
  31. import { useMemo } from 'react';
  32. import { type FixedLayoutProps } from '@flowgram.ai/fixed-layout-editor';
  33. import { defaultFixedSemiMaterials } from '@flowgram.ai/fixed-semi-materials';
  34. import { createMinimapPlugin } from '@flowgram.ai/minimap-plugin';
  35. import { intialData } from './initial-data' // Initial data
  36. import { nodeRegistries } from './node-registries' // Node declaration configuration
  37. import { BaseNode } from './base-node' // Node rendering
  38. export function useEditorProps(
  39. ): FixedLayoutProps {
  40. return useMemo<FixedLayoutProps>(
  41. () => ({
  42. /**
  43. * Initial data
  44. */
  45. initialData,
  46. /**
  47. * Canvas node definition
  48. */
  49. nodeRegistries,
  50. /**
  51. * 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:
  52. * https://github.com/coze-dev/flowgram.ai/blob/main/packages/materials/fixed-semi-materials/src/components/index.tsx
  53. */
  54. materials: {
  55. renderNodes: {
  56. ...defaultFixedSemiMaterials,
  57. // [FlowRendererKey.ADDER]: NodeAdder,
  58. // [FlowRendererKey.BRANCH_ADDER]: BranchAdder,
  59. },
  60. renderDefaultNode: BaseNode, // Node rendering component
  61. },
  62. /**
  63. * Node engine, used to render node form
  64. */
  65. nodeEngine: {
  66. enable: true,
  67. },
  68. /**
  69. * Canvas history, used to control redo/undo
  70. */
  71. history: {
  72. enable: true,
  73. enableChangeNode: true, // Used to listen to node form data changes
  74. },
  75. /**
  76. * Canvas initialization callback
  77. */
  78. onInit: ctx => {
  79. // If you want to dynamically load data, you can execute it asynchronously by the following method
  80. // ctx.docuemnt.fromJSON(initialData)
  81. },
  82. /**
  83. * Canvas first rendering completed callback
  84. */
  85. onAllLayersRendered: (ctx) => {},
  86. /**
  87. * Canvas destruction callback
  88. */
  89. onDispose: () => { },
  90. plugins: () => [
  91. /**
  92. * Minimap plugin
  93. */
  94. createMinimapPlugin({}),
  95. ],
  96. }),
  97. [],
  98. );
  99. }
  100. ```
  101. ### 3. Configure Data
  102. Canvas document data uses a tree structure, supports nesting
  103. :::note Document data basic structure:
  104. - nodes `array` Node list, supports nesting
  105. :::
  106. :::note Node data basic structure:
  107. - id: `string` Node unique identifier, must be unique
  108. - meta: `object` Node ui configuration information, such as free layout `position` information
  109. - type: `string | number` Node type, will correspond to `type` in `nodeRegistries`
  110. - data: `object` Node form data
  111. - blocks: `array` Node branch, using `block` is more in line with `Gramming`
  112. :::
  113. ```tsx pure title="initial-data.tsx"
  114. import { FlowDocumentJSON } from '@flowgram.ai/fixed-layout-editor';
  115. /**
  116. * Configure workflow data, data is in the format of nested blocks
  117. */
  118. export const initialData: FlowDocumentJSON = {
  119. nodes: [
  120. // Start node
  121. {
  122. id: 'start_0',
  123. type: 'start',
  124. data: {
  125. title: 'Start',
  126. content: 'start content'
  127. },
  128. blocks: [],
  129. },
  130. // Branch node
  131. {
  132. id: 'condition_0',
  133. type: 'condition',
  134. data: {
  135. title: 'Condition'
  136. },
  137. blocks: [
  138. {
  139. id: 'branch_0',
  140. type: 'block',
  141. data: {
  142. title: 'Branch 0',
  143. content: 'branch 1 content'
  144. },
  145. blocks: [
  146. {
  147. id: 'custom_0',
  148. type: 'custom',
  149. data: {
  150. title: 'Custom',
  151. content: 'custrom content'
  152. },
  153. },
  154. ],
  155. },
  156. {
  157. id: 'branch_1',
  158. type: 'block',
  159. data: {
  160. title: 'Branch 1',
  161. content: 'branch 1 content'
  162. },
  163. blocks: [],
  164. },
  165. ],
  166. },
  167. // End node
  168. {
  169. id: 'end_0',
  170. type: 'end',
  171. data: {
  172. title: 'End',
  173. content: 'end content'
  174. },
  175. },
  176. ],
  177. };
  178. ```
  179. ### 4. Declare Node
  180. Declare node can be used to determine the type and rendering method of the node
  181. ```tsx pure title="node-registries.tsx"
  182. import { FlowNodeRegistry, ValidateTrigger } from '@flowgram.ai/fixed-layout-editor';
  183. /**
  184. * Custom node registration
  185. */
  186. export const nodeRegistries: FlowNodeRegistry[] = [
  187. {
  188. /**
  189. * Custom node type
  190. */
  191. type: 'condition',
  192. /**
  193. * Custom node extension:
  194. * - loop: Extended to loop node
  195. * - start: Extended to start node
  196. * - dynamicSplit: Extended to branch node
  197. * - end: Extended to end node
  198. * - tryCatch: Extended to tryCatch node
  199. * - default: Extended to normal node (default)
  200. */
  201. extend: 'dynamicSplit',
  202. /**
  203. * Node configuration information
  204. */
  205. meta: {
  206. // isStart: false, // Whether it is a start node
  207. // isNodeEnd: false, // Whether it is an end node, the node after the end node cannot be added
  208. // draggable: false, // Whether it can be dragged, such as the start node and the end node cannot be dragged
  209. // selectable: false, // The trigger start node cannot be selected
  210. // deleteDisable: true, // Disable deletion
  211. // copyDisable: true, // Disable copy
  212. // addDisable: true, // Disable addition
  213. },
  214. /**
  215. * Configure node form validation and rendering,
  216. * Note: validate uses data and rendering separation, ensuring that even if the node is not rendered, the data can be validated
  217. */
  218. formMeta: {
  219. validateTrigger: ValidateTrigger.onChange,
  220. validate: {
  221. title: ({ value }) => (value ? undefined : 'Title is required'),
  222. },
  223. /**
  224. * Render form
  225. */
  226. render: () => (
  227. <>
  228. <Field name="title">
  229. {({ field }) => <div className="demo-free-node-title">{field.value}</div>}
  230. </Field>
  231. <Field name="content">
  232. {({ field }) => <input onChange={field.onChange} value={field.value}/>}
  233. </Field>
  234. </>
  235. )
  236. },
  237. },
  238. ];
  239. ```
  240. ### 5. Render Node
  241. The rendering node is used to add styles, events, and form rendering positions
  242. ```tsx pure title="base-node.tsx"
  243. import { useNodeRender } from '@flowgram.ai/fixed-layout-editor';
  244. export const BaseNode = () => {
  245. /**
  246. * Provides methods related to node rendering
  247. */
  248. const nodeRender = useNodeRender();
  249. /**
  250. * Forms can only be used when the node engine is enabled
  251. */
  252. const form = nodeRender.form;
  253. return (
  254. <div
  255. className="demo-fixed-node"
  256. onMouseEnter={nodeRender.onMouseEnter}
  257. onMouseLeave={nodeRender.onMouseLeave}
  258. onMouseDown={e => {
  259. // Trigger drag
  260. nodeRender.startDrag(e);
  261. e.stopPropagation();
  262. }}
  263. style={{
  264. // BlockOrderIcon represents the first node of a branch, BlockIcon represents the header node of the entire condition
  265. ...(nodeRender.isBlockOrderIcon || nodeRender.isBlockIcon ? { width: 260 } : {}),
  266. outline: form?.state.invalid ? '1px solid red' : 'none', // Red border for form validation errors
  267. }}
  268. >
  269. {
  270. // Form rendering is generated through formMeta
  271. form?.render()
  272. }
  273. </div>
  274. );
  275. };
  276. ```
  277. ### 6. Add Tools
  278. 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`
  279. ```tsx pure title="tools.tsx"
  280. import { useEffect, useState } from 'react'
  281. import { usePlaygroundTools, useClientContext } from '@flowgram.ai/fixed-layout-editor';
  282. export function Tools() {
  283. const { history } = useClientContext();
  284. const tools = usePlaygroundTools();
  285. const [canUndo, setCanUndo] = useState(false);
  286. const [canRedo, setCanRedo] = useState(false);
  287. useEffect(() => {
  288. const disposable = history.undoRedoService.onChange(() => {
  289. setCanUndo(history.canUndo());
  290. setCanRedo(history.canRedo());
  291. });
  292. return () => disposable.dispose();
  293. }, [history]);
  294. return <div style={{ position: 'absolute', zIndex: 10, bottom: 16, left: 16, display: 'flex', gap: 8 }}>
  295. <button onClick={() => tools.zoomin()}>ZoomIn</button>
  296. <button onClick={() => tools.zoomout()}>ZoomOut</button>
  297. <button onClick={() => tools.fitView()}>Fitview</button>
  298. <button onClick={() => tools.changeLayout()}>ChangeLayout</button>
  299. <button onClick={() => history.undo()} disabled={!canUndo}>Undo</button>
  300. <button onClick={() => history.redo()} disabled={!canRedo}>Redo</button>
  301. <span>{Math.floor(tools.zoom * 100)}%</span>
  302. </div>
  303. }
  304. ```
  305. ### 7. Effect
  306. import { FixedLayoutSimple } from '../../../../components';
  307. <div style={{ position: 'relative', width: '100%', height: '600px'}}>
  308. <FixedLayoutSimple />
  309. </div>