From 71d5394a603c9fa07b8e66c5a03916bad8e843ac Mon Sep 17 00:00:00 2001 From: Kylin <1159469891@qq.com> Date: Mon, 17 Mar 2025 11:27:23 +0800 Subject: [PATCH 01/11] docs: Modify the FixedLayoutProps in a free layout --- .../zh/guide/getting-started/create-free-layout-simple.mdx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/docs/src/zh/guide/getting-started/create-free-layout-simple.mdx b/apps/docs/src/zh/guide/getting-started/create-free-layout-simple.mdx index d4a0eb59..d2ffc176 100644 --- a/apps/docs/src/zh/guide/getting-started/create-free-layout-simple.mdx +++ b/apps/docs/src/zh/guide/getting-started/create-free-layout-simple.mdx @@ -42,7 +42,7 @@ function App() { ```tsx pure title="use-editor-props.tsx" import { useMemo } from 'react'; -import { type FixedLayoutProps } from '@flowgram.ai/free-layout-editor'; +import { type FreeLayoutProps } from '@flowgram.ai/free-layout-editor'; import { createMinimapPlugin } from '@flowgram.ai/minimap-plugin'; import { intialData } from './initial-data' // 初始化数据 @@ -50,8 +50,8 @@ import { nodeRegistries } from './node-registries' // 节点声明配置 import { BaseNode } from './base-node' // 节点渲染 export function useEditorProps( -): FixedLayoutProps { - return useMemo( +): FreeLayoutProps { + return useMemo( () => ({ /** * 初始化数据 From dd317316ef64e271cc00bfb8fae7cfd42e2370a8 Mon Sep 17 00:00:00 2001 From: Kylin <1159469891@qq.com> Date: Tue, 18 Mar 2025 13:10:36 +0800 Subject: [PATCH 02/11] dcos: change FreeLayoutProps --- .../guide/getting-started/create-free-layout-simple.mdx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/docs/src/en/guide/getting-started/create-free-layout-simple.mdx b/apps/docs/src/en/guide/getting-started/create-free-layout-simple.mdx index 7d18036c..43025e1e 100644 --- a/apps/docs/src/en/guide/getting-started/create-free-layout-simple.mdx +++ b/apps/docs/src/en/guide/getting-started/create-free-layout-simple.mdx @@ -40,7 +40,7 @@ Canvas configuration is declarative, providing data, rendering, event, and plugi ```tsx pure title="use-editor-props.tsx" import { useMemo } from 'react'; -import { type FixedLayoutProps } from '@flowgram.ai/free-layout-editor'; +import { type FreeLayoutProps } from '@flowgram.ai/free-layout-editor'; import { createMinimapPlugin } from '@flowgram.ai/minimap-plugin'; import { intialData } from './initial-data' // 初始化数据 @@ -48,8 +48,8 @@ import { nodeRegistries } from './node-registries' // 节点声明配置 import { BaseNode } from './base-node' // 节点渲染 export function useEditorProps( -): FixedLayoutProps { - return useMemo( +): FreeLayoutProps { + return useMemo( () => ({ /** * Initialize data @@ -271,7 +271,7 @@ export const BaseNode = () => { */ const { form } = useNodeRender() /** - * WorkflowNodeRenderer will add node drag events and port rendering, for deep customization, + * WorkflowNodeRenderer will add node drag events and port rendering, for deep customization, * you can check the component source code at: * https://github.com/bytedance/flowgram.ai/blob/main/packages/client/free-layout-editor/src/components/workflow-node-renderer.tsx */ From 493d3084f5b8f5e00918571263dd2f2d3a738e4c Mon Sep 17 00:00:00 2001 From: YuanHeDx Date: Fri, 14 Mar 2025 18:07:27 +0800 Subject: [PATCH 03/11] doc(node): init node form demo --- apps/demo-node-form/.eslintrc.js | 15 ++ apps/demo-node-form/index.html | 12 ++ apps/demo-node-form/package.json | 55 ++++++ apps/demo-node-form/rspack.config.js | 46 +++++ apps/demo-node-form/src/app.tsx | 7 + apps/demo-node-form/src/editor.tsx | 27 +++ .../src/hooks/use-editor-props.tsx | 165 ++++++++++++++++++ apps/demo-node-form/src/index.css | 107 ++++++++++++ apps/demo-node-form/src/index.tsx | 1 + apps/demo-node-form/src/initial-data.ts | 18 ++ apps/demo-node-form/src/node-registries.tsx | 23 +++ apps/demo-node-form/tsconfig.json | 23 +++ apps/docs/components/form-basic/index.tsx | 10 ++ apps/docs/components/form-basic/preview.tsx | 42 +++++ apps/docs/components/index.ts | 1 + apps/docs/package.json | 1 + apps/docs/src/zh/examples/_meta.json | 5 + .../docs/src/zh/examples/node-form/_meta.json | 3 + apps/docs/src/zh/examples/node-form/basic.mdx | 10 ++ common/config/rush/pnpm-lock.yaml | 58 ++++++ .../node-engine/node/src/form-model-v2.ts | 4 + packages/node-engine/node/src/types.ts | 10 +- rush.json | 10 ++ 23 files changed, 652 insertions(+), 1 deletion(-) create mode 100644 apps/demo-node-form/.eslintrc.js create mode 100644 apps/demo-node-form/index.html create mode 100644 apps/demo-node-form/package.json create mode 100644 apps/demo-node-form/rspack.config.js create mode 100644 apps/demo-node-form/src/app.tsx create mode 100644 apps/demo-node-form/src/editor.tsx create mode 100644 apps/demo-node-form/src/hooks/use-editor-props.tsx create mode 100644 apps/demo-node-form/src/index.css create mode 100644 apps/demo-node-form/src/index.tsx create mode 100644 apps/demo-node-form/src/initial-data.ts create mode 100644 apps/demo-node-form/src/node-registries.tsx create mode 100644 apps/demo-node-form/tsconfig.json create mode 100644 apps/docs/components/form-basic/index.tsx create mode 100644 apps/docs/components/form-basic/preview.tsx create mode 100644 apps/docs/src/zh/examples/node-form/_meta.json create mode 100644 apps/docs/src/zh/examples/node-form/basic.mdx diff --git a/apps/demo-node-form/.eslintrc.js b/apps/demo-node-form/.eslintrc.js new file mode 100644 index 00000000..9f8bd759 --- /dev/null +++ b/apps/demo-node-form/.eslintrc.js @@ -0,0 +1,15 @@ +const { defineConfig } = require('@flowgram.ai/eslint-config'); + +module.exports = defineConfig({ + preset: 'web', + packageRoot: __dirname, + rules: { + 'no-console': 'off', + 'react/prop-types': 'off', + }, + settings: { + react: { + version: 'detect', // 自动检测 React 版本 + }, + }, +}); diff --git a/apps/demo-node-form/index.html b/apps/demo-node-form/index.html new file mode 100644 index 00000000..7327a045 --- /dev/null +++ b/apps/demo-node-form/index.html @@ -0,0 +1,12 @@ + + + + + + + Flow FreeLayoutEditor Demo + + +
+ + diff --git a/apps/demo-node-form/package.json b/apps/demo-node-form/package.json new file mode 100644 index 00000000..b582fb1b --- /dev/null +++ b/apps/demo-node-form/package.json @@ -0,0 +1,55 @@ +{ + "name": "@flowgram.ai/demo-node-form", + "version": "0.1.0", + "description": "", + "keywords": [], + "license": "MIT", + "main": "./src/index.tsx", + "files": [ + "src/", + ".eslintrc.js", + ".gitignore", + "index.html", + "package.json", + "rspack.config.js", + "tsconfig.json" + ], + "scripts": { + "build": "exit 0", + "build:fast": "exit 0", + "build:watch": "exit 0", + "clean": "rimraf dist", + "dev": "MODE=app NODE_ENV=development rspack serve", + "lint": "eslint ./src --cache", + "lint:fix": "eslint ./src --fix", + "start": "NODE_ENV=development rspack serve", + "test": "exit", + "test:cov": "exit", + "watch": "exit 0" + }, + "dependencies": { + "@douyinfe/semi-icons": "^2.72.3", + "@douyinfe/semi-ui": "^2.72.3", + "@flowgram.ai/free-layout-editor": "workspace:*", + "@flowgram.ai/free-snap-plugin": "workspace:*", + "@flowgram.ai/minimap-plugin": "workspace:*", + "react": "^18", + "react-dom": "^18" + }, + "devDependencies": { + "@flowgram.ai/ts-config": "workspace:*", + "@flowgram.ai/eslint-config": "workspace:*", + "@rspack/cli": "0.2.1", + "@types/lodash-es": "^4.17.12", + "@types/node": "^18", + "@types/react": "^18", + "@types/react-dom": "^18", + "@types/styled-components": "^5", + "@typescript-eslint/parser": "^6.10.0", + "eslint": "^8.54.0" + }, + "publishConfig": { + "access": "public", + "registry": "https://registry.npmjs.org/" + } +} diff --git a/apps/demo-node-form/rspack.config.js b/apps/demo-node-form/rspack.config.js new file mode 100644 index 00000000..e2f05408 --- /dev/null +++ b/apps/demo-node-form/rspack.config.js @@ -0,0 +1,46 @@ +const path = require('path'); + +const isCI = process.env.CI === 'true'; +const isSCM = !!process.env.BUILD_BRANCH; +const isProd = process.env.NODE_ENV === 'production'; +/** + * @type {import('@rspack/cli').Configuration} + */ +module.exports = { + mode: process.env.NODE_ENV, + context: __dirname, + target: ['web'], + entry: { + main: './src/app.tsx', + }, + builtins: { + // https://www.rspack.dev/config/builtins.html#builtinshtml + html: [ + { + template: './index.html', + }, + ], + progress: !isSCM ? {} : false, + treeShaking: isProd, + }, + module: { + // https://www.rspack.dev/config/module.html#rule + rules: [ + { + test: /\.(png|gif|jpg|jpeg|svg|woff2)$/, + type: 'asset', + }, + ], + }, + plugins: [], + /** module is too large now, we may need better way to tackle this in the future */ + stats: isCI + ? { all: false, modules: true, assets: true, chunks: true, warnings: true, errors: true } + : { + modules: false, + all: false, + warnings: false, + errors: true, + timings: true, + }, +}; diff --git a/apps/demo-node-form/src/app.tsx b/apps/demo-node-form/src/app.tsx new file mode 100644 index 00000000..150c56b5 --- /dev/null +++ b/apps/demo-node-form/src/app.tsx @@ -0,0 +1,7 @@ +import { createRoot } from 'react-dom/client'; + +import { Editor } from './editor'; + +const app = createRoot(document.getElementById('root')!); + +app.render(); diff --git a/apps/demo-node-form/src/editor.tsx b/apps/demo-node-form/src/editor.tsx new file mode 100644 index 00000000..16d8e870 --- /dev/null +++ b/apps/demo-node-form/src/editor.tsx @@ -0,0 +1,27 @@ +import { + EditorRenderer, + FlowNodeRegistry, + FreeLayoutEditorProvider, + WorkflowJSON, +} from '@flowgram.ai/free-layout-editor'; + +import { useEditorProps } from './hooks/use-editor-props'; +import '@flowgram.ai/free-layout-editor/index.css'; +import './index.css'; +interface EditorProps { + registry: FlowNodeRegistry; + initialData: WorkflowJSON; +} + +export const Editor = ({ registry, initialData }: EditorProps) => { + const editorProps = useEditorProps({ registry, initialData }); + return ( + +
+
+ +
+
+
+ ); +}; diff --git a/apps/demo-node-form/src/hooks/use-editor-props.tsx b/apps/demo-node-form/src/hooks/use-editor-props.tsx new file mode 100644 index 00000000..5184355b --- /dev/null +++ b/apps/demo-node-form/src/hooks/use-editor-props.tsx @@ -0,0 +1,165 @@ +import { useMemo } from 'react'; + +import { createMinimapPlugin } from '@flowgram.ai/minimap-plugin'; +import { createFreeSnapPlugin } from '@flowgram.ai/free-snap-plugin'; +import { + FreeLayoutProps, + WorkflowNodeProps, + WorkflowNodeRenderer, + Field, + useNodeRender, + FlowNodeRegistry, + WorkflowJSON, +} from '@flowgram.ai/free-layout-editor'; + +import { DEFAULT_DEMO_REGISTRY } from '../node-registries'; +import { DEFAULT_INITIAL_DATA } from '../initial-data'; + +interface EditorProps { + registries: FlowNodeRegistry[]; + initialData: WorkflowJSON; +} + +export const useEditorProps = ({ + registries = [DEFAULT_DEMO_REGISTRY], + initialData = DEFAULT_INITIAL_DATA, +}: EditorProps) => + useMemo( + () => ({ + /** + * Whether to enable the background + */ + background: true, + /** + * Whether it is read-only or not, the node cannot be dragged in read-only mode + */ + readonly: false, + /** + * Initial data + * 初始化数据 + */ + initialData, + /** + * Node registries + * 节点注册 + */ + nodeRegistries: registries, + /** + * Get the default node registry, which will be merged with the 'nodeRegistries' + * 提供默认的节点注册,这个会和 nodeRegistries 做合并 + */ + getNodeDefaultRegistry(type) { + return { + type, + meta: { + defaultExpanded: true, + }, + formMeta: { + /** + * Render form + */ + render: () => ( + <> + name="title"> + {({ field }) =>
{field.value}
} + +
+ name="content"> + + +
+ + ), + }, + }; + }, + materials: { + /** + * Render Node + */ + renderDefaultNode: (props: WorkflowNodeProps) => { + const { form } = useNodeRender(); + return ( + + {form?.render()} + + ); + }, + }, + /** + * Content change + */ + onContentChange(ctx, event) { + // console.log('Auto Save: ', event, ctx.document.toJSON()); + }, + // /** + // * Node engine enable, you can configure formMeta in the FlowNodeRegistry + // */ + nodeEngine: { + enable: true, + }, + /** + * Redo/Undo enable + */ + history: { + enable: true, + enableChangeNode: true, // Listen Node engine data change + }, + /** + * Playground init + */ + onInit: (ctx) => {}, + /** + * Playground render + */ + onAllLayersRendered(ctx) { + // Fitview + ctx.document.fitView(false); + }, + /** + * Playground dispose + */ + onDispose() { + console.log('---- Playground Dispose ----'); + }, + plugins: () => [ + /** + * Minimap plugin + * 缩略图插件 + */ + createMinimapPlugin({ + disableLayer: true, + canvasStyle: { + canvasWidth: 182, + canvasHeight: 102, + canvasPadding: 50, + canvasBackground: 'rgba(245, 245, 245, 1)', + canvasBorderRadius: 10, + viewportBackground: 'rgba(235, 235, 235, 1)', + viewportBorderRadius: 4, + viewportBorderColor: 'rgba(201, 201, 201, 1)', + viewportBorderWidth: 1, + viewportBorderDashLength: 2, + nodeColor: 'rgba(255, 255, 255, 1)', + nodeBorderRadius: 2, + nodeBorderWidth: 0.145, + nodeBorderColor: 'rgba(6, 7, 9, 0.10)', + overlayColor: 'rgba(255, 255, 255, 0)', + }, + inactiveDebounceTime: 1, + }), + /** + * Snap plugin + * 自动对齐及辅助线插件 + */ + createFreeSnapPlugin({ + edgeColor: '#00B2B2', + alignColor: '#00B2B2', + edgeLineWidth: 1, + alignLineWidth: 1, + alignCrossWidth: 8, + }), + ], + }), + [] + ); diff --git a/apps/demo-node-form/src/index.css b/apps/demo-node-form/src/index.css new file mode 100644 index 00000000..c76cebfb --- /dev/null +++ b/apps/demo-node-form/src/index.css @@ -0,0 +1,107 @@ +.demo-free-node { + display: flex; + min-width: 300px; + min-height: 100px; + flex-direction: column; + align-items: flex-start; + box-sizing: border-box; + border-radius: 8px; + border: 1px solid var(--light-usage-border-color-border, rgba(28, 31, 35, 0.08)); + background: #fff; + box-shadow: 0px 2px 4px 0px rgba(0, 0, 0, 0.1); +} + +.demo-free-node-title { + background-color: #93bfe2; + width: 100%; + border-radius: 8px 8px 0 0; + padding: 4px 12px; +} +.demo-free-node-content { + padding: 4px 12px; + flex-grow: 1; + width: 100%; +} +.demo-free-node::before { + content: ''; + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: -1; + background-color: white; + border-radius: 7px; +} + +.demo-free-node:hover:before { + -webkit-filter: drop-shadow(0 0 1px rgba(0, 0, 0, 0.3)) drop-shadow(0 4px 14px rgba(0, 0, 0, 0.1)); + filter: drop-shadow(0 0 1px rgba(0, 0, 0, 0.3)) drop-shadow(0 4px 14px rgba(0, 0, 0, 0.1)); +} + +.demo-free-node.activated:before, +.demo-free-node.selected:before { + outline: 2px solid var(--light-usage-primary-color-primary, #4d53e8); + -webkit-filter: drop-shadow(0 0 1px rgba(0, 0, 0, 0.3)) drop-shadow(0 4px 14px rgba(0, 0, 0, 0.1)); + filter: drop-shadow(0 0 1px rgba(0, 0, 0, 0.3)) drop-shadow(0 4px 14px rgba(0, 0, 0, 0.1)); +} + +.demo-free-sidebar { + height: 100%; + overflow-y: auto; + padding: 12px 16px 0; + box-sizing: border-box; + background: #f7f7fa; + border-right: 1px solid rgba(29, 28, 35, 0.08); +} + +.demo-free-right-top-panel { + position: fixed; + right: 10px; + top: 70px; + width: 300px; + z-index: 999; +} + +.demo-free-card { + width: 140px; + height: 60px; + display: flex; + align-items: center; + justify-content: center; + font-size: 20px; + background: #fff; + border-radius: 8px; + box-shadow: 0 6px 8px 0 rgba(28, 31, 35, 0.03); + cursor: -webkit-grab; + cursor: grab; + line-height: 16px; + margin-bottom: 12px; + overflow: hidden; + padding: 16px; + position: relative; + color: black; +} + +.demo-free-layout { + display: flex; + flex-direction: row; + flex-grow: 1; +} + +.demo-free-editor { + flex-grow: 1; + position: relative; + height: 100%; +} + +.demo-free-container { + position: absolute; + left: 0; + top: 0; + display: flex; + width: 100%; + height: 100%; + flex-direction: column; +} + diff --git a/apps/demo-node-form/src/index.tsx b/apps/demo-node-form/src/index.tsx new file mode 100644 index 00000000..2f6832bf --- /dev/null +++ b/apps/demo-node-form/src/index.tsx @@ -0,0 +1 @@ +export { Editor } from './editor'; diff --git a/apps/demo-node-form/src/initial-data.ts b/apps/demo-node-form/src/initial-data.ts new file mode 100644 index 00000000..b2e76742 --- /dev/null +++ b/apps/demo-node-form/src/initial-data.ts @@ -0,0 +1,18 @@ +import { WorkflowJSON } from '@flowgram.ai/free-layout-editor'; + +export const DEFAULT_INITIAL_DATA: WorkflowJSON = { + nodes: [ + { + id: 'node_0', + type: 'custom', + meta: { + position: { x: 400, y: 0 }, + }, + data: { + title: 'Custom', + content: 'Custom node content', + }, + }, + ], + edges: [], +}; diff --git a/apps/demo-node-form/src/node-registries.tsx b/apps/demo-node-form/src/node-registries.tsx new file mode 100644 index 00000000..21edbcbd --- /dev/null +++ b/apps/demo-node-form/src/node-registries.tsx @@ -0,0 +1,23 @@ +import { WorkflowNodeRegistry, Field } from '@flowgram.ai/free-layout-editor'; +import { Input, TextArea } from '@douyinfe/semi-ui'; + +export const DEFAULT_DEMO_REGISTRY: WorkflowNodeRegistry = { + type: 'custom', + meta: {}, + defaultPorts: [{ type: 'output' }, { type: 'input' }], + formMeta: { + render: () => ( +
+
Basic Node
+

name

+ + + +

city

+ + + +
+ ), + }, +}; diff --git a/apps/demo-node-form/tsconfig.json b/apps/demo-node-form/tsconfig.json new file mode 100644 index 00000000..171a60f5 --- /dev/null +++ b/apps/demo-node-form/tsconfig.json @@ -0,0 +1,23 @@ +{ + "extends": "@flowgram.ai/ts-config/tsconfig.flow.path.json", + "compilerOptions": { + "rootDir": "./src", + "outDir": "./dist", + "experimentalDecorators": true, + "target": "es2020", + "module": "esnext", + "strictPropertyInitialization": false, + "strict": true, + "esModuleInterop": true, + "moduleResolution": "node", + "skipLibCheck": true, + "noUnusedLocals": true, + "noImplicitAny": true, + "allowJs": true, + "resolveJsonModule": true, + "types": ["node"], + "jsx": "react-jsx", + "lib": ["es6", "dom", "es2020", "es2019.Array"] + }, + "include": ["./src"], +} diff --git a/apps/docs/components/form-basic/index.tsx b/apps/docs/components/form-basic/index.tsx new file mode 100644 index 00000000..24153819 --- /dev/null +++ b/apps/docs/components/form-basic/index.tsx @@ -0,0 +1,10 @@ +import React from 'react'; + +// https://github.com/web-infra-dev/rspress/issues/553 +const Editor = React.lazy(() => + import('@flowgram.ai/demo-node-form').then((module) => ({ + default: module.Editor, + })) +); + +export { Editor }; diff --git a/apps/docs/components/form-basic/preview.tsx b/apps/docs/components/form-basic/preview.tsx new file mode 100644 index 00000000..aa21182d --- /dev/null +++ b/apps/docs/components/form-basic/preview.tsx @@ -0,0 +1,42 @@ +import { PreviewEditor } from '../preview-editor'; +import { Editor } from '.'; + +const indexCode = { + code: `import { + EditorRenderer, + FreeLayoutEditorProvider, +} from '@flowgram.ai/free-layout-editor'; + +import { useEditorProps } from './hooks/use-editor-props' +import '@flowgram.ai/free-layout-editor/index.css'; +import './index.css'; + +export const App = () => { + const editorProps = useEditorProps() + return ( + +
+
+ + +
+ + +
+
+ ) +}; + `, + active: true, +}; + +export const NodeFormBasicPreview = () => { + const files = { + 'index.tsx': indexCode, + }; + return ( + + + + ); +}; diff --git a/apps/docs/components/index.ts b/apps/docs/components/index.ts index 1ef1ecbd..e6e21c6a 100644 --- a/apps/docs/components/index.ts +++ b/apps/docs/components/index.ts @@ -5,3 +5,4 @@ export { FreeLayoutSimple } from './free-layout-simple'; export { FreeLayoutSimplePreview } from './free-layout-simple/preview'; export { FixedLayoutSimple } from './fixed-layout-simple'; export { FixedLayoutSimplePreview } from './fixed-layout-simple/preview'; +export { NodeFormBasicPreview } from './form-basic/preview.tsx'; diff --git a/apps/docs/package.json b/apps/docs/package.json index b9a4f66a..cab3a7ef 100644 --- a/apps/docs/package.json +++ b/apps/docs/package.json @@ -19,6 +19,7 @@ "@flowgram.ai/demo-free-layout": "workspace:*", "@flowgram.ai/demo-free-layout-simple": "workspace:*", "@flowgram.ai/demo-fixed-layout-simple": "workspace:*", + "@flowgram.ai/demo-node-form": "workspace:*", "@flowgram.ai/fixed-layout-editor": "workspace:*", "@flowgram.ai/fixed-semi-materials": "workspace:*", "@flowgram.ai/free-layout-editor": "workspace:*", diff --git a/apps/docs/src/zh/examples/_meta.json b/apps/docs/src/zh/examples/_meta.json index 4bed1a45..009f1f8b 100644 --- a/apps/docs/src/zh/examples/_meta.json +++ b/apps/docs/src/zh/examples/_meta.json @@ -13,5 +13,10 @@ "type": "dir", "name": "free-layout", "label": "自由布局" + }, + { + "type": "dir", + "name": "node-form", + "label": "节点表单" } ] diff --git a/apps/docs/src/zh/examples/node-form/_meta.json b/apps/docs/src/zh/examples/node-form/_meta.json new file mode 100644 index 00000000..99cba57d --- /dev/null +++ b/apps/docs/src/zh/examples/node-form/_meta.json @@ -0,0 +1,3 @@ +[ + "basic" +] diff --git a/apps/docs/src/zh/examples/node-form/basic.mdx b/apps/docs/src/zh/examples/node-form/basic.mdx new file mode 100644 index 00000000..07d8cc30 --- /dev/null +++ b/apps/docs/src/zh/examples/node-form/basic.mdx @@ -0,0 +1,10 @@ +--- +outline: false +--- + + +# 基础用法 + +import { NodeFormBasicPreview } from '../../../../components'; + + diff --git a/common/config/rush/pnpm-lock.yaml b/common/config/rush/pnpm-lock.yaml index 10981df2..51f69125 100644 --- a/common/config/rush/pnpm-lock.yaml +++ b/common/config/rush/pnpm-lock.yaml @@ -310,6 +310,61 @@ importers: specifier: ^8.54.0 version: 8.57.1 + ../../apps/demo-node-form: + dependencies: + '@douyinfe/semi-icons': + specifier: ^2.72.3 + version: 2.72.3(react@18.3.1) + '@douyinfe/semi-ui': + specifier: ^2.72.3 + version: 2.72.3(acorn@8.14.0)(react-dom@18.3.1)(react@18.3.1) + '@flowgram.ai/free-layout-editor': + specifier: workspace:* + version: link:../../packages/client/free-layout-editor + '@flowgram.ai/free-snap-plugin': + specifier: workspace:* + version: link:../../packages/plugins/free-snap-plugin + '@flowgram.ai/minimap-plugin': + specifier: workspace:* + version: link:../../packages/plugins/minimap-plugin + react: + specifier: ^18 + version: 18.3.1 + react-dom: + specifier: ^18 + version: 18.3.1(react@18.3.1) + devDependencies: + '@flowgram.ai/eslint-config': + specifier: workspace:* + version: link:../../config/eslint-config + '@flowgram.ai/ts-config': + specifier: workspace:* + version: link:../../config/ts-config + '@rspack/cli': + specifier: 0.2.1 + version: 0.2.1(react-refresh@0.16.0)(webpack@5.76.0) + '@types/lodash-es': + specifier: ^4.17.12 + version: 4.17.12 + '@types/node': + specifier: ^18 + version: 18.19.68 + '@types/react': + specifier: ^18 + version: 18.3.16 + '@types/react-dom': + specifier: ^18 + version: 18.3.5(@types/react@18.3.16) + '@types/styled-components': + specifier: ^5 + version: 5.1.34 + '@typescript-eslint/parser': + specifier: ^6.10.0 + version: 6.21.0(eslint@8.57.1)(typescript@5.0.4) + eslint: + specifier: ^8.54.0 + version: 8.57.1 + ../../apps/docs: dependencies: '@codesandbox/sandpack-react': @@ -333,6 +388,9 @@ importers: '@flowgram.ai/demo-free-layout-simple': specifier: workspace:* version: link:../demo-free-layout-simple + '@flowgram.ai/demo-node-form': + specifier: workspace:* + version: link:../demo-node-form '@flowgram.ai/fixed-layout-editor': specifier: workspace:* version: link:../../packages/client/fixed-layout-editor diff --git a/packages/node-engine/node/src/form-model-v2.ts b/packages/node-engine/node/src/form-model-v2.ts index 7c457131..5f66b32e 100644 --- a/packages/node-engine/node/src/form-model-v2.ts +++ b/packages/node-engine/node/src/form-model-v2.ts @@ -21,6 +21,7 @@ import { Glob, IField, IFieldArray, + toForm, } from '@flowgram.ai/form'; import { FlowNodeEntity } from '@flowgram.ai/document'; import { PlaygroundContext } from '@flowgram.ai/core'; @@ -256,6 +257,7 @@ export class FormModelV2 extends FormModel implements Disposable { value: get(values, currentName), prevValue: get(prevValues, currentName), formValues: values, + form: toForm(this.nativeFormModel!), context: this.nodeContext, }); @@ -298,6 +300,7 @@ export class FormModelV2 extends FormModel implements Disposable { value: get(values, path), formValues: values, prevValue: get(prevValues, path), + form: toForm(this.nativeFormModel!), context: this.nodeContext, }); @@ -332,6 +335,7 @@ export class FormModelV2 extends FormModel implements Disposable { effect({ ...props, formValues: nativeFormModel.values, + form: toForm(this.nativeFormModel!), context: this.nodeContext, }) ); diff --git a/packages/node-engine/node/src/types.ts b/packages/node-engine/node/src/types.ts index e885ac5d..b0e88e75 100644 --- a/packages/node-engine/node/src/types.ts +++ b/packages/node-engine/node/src/types.ts @@ -2,7 +2,12 @@ import * as React from 'react'; import { FormModel, IFormMeta, NodeFormContext } from '@flowgram.ai/form-core'; import { FieldName, FieldValue } from '@flowgram.ai/form/src/types'; -import { FormRenderProps, Validate as FormValidate, ValidateTrigger } from '@flowgram.ai/form'; +import { + FormRenderProps, + IForm, + Validate as FormValidate, + ValidateTrigger, +} from '@flowgram.ai/form'; import { FormPlugin } from './form-plugin'; import { FormModelV2 } from './form-model-v2'; @@ -51,6 +56,7 @@ export type Effect = (props: { value: TFieldValue; prevValue?: TFieldValue; formValues: TFormValues; + form: IForm; context: NodeContext; }) => void | EffectReturn; @@ -59,6 +65,7 @@ export type ArrayAppendEffect = (props: { value: TFieldValue; arrayValues: Array; formValues: TFormValues; + form: IForm; context: NodeContext; }) => void | EffectReturn; @@ -66,6 +73,7 @@ export type ArrayDeleteEffect = (props: { index: number; arrayValue: Array; formValues: TFormValues; + form: IForm; context: NodeContext; }) => void | EffectReturn; diff --git a/rush.json b/rush.json index 866c9a58..531eec28 100644 --- a/rush.json +++ b/rush.json @@ -774,6 +774,16 @@ "team-flow", "demo" ] + }, + { + "packageName": "@flowgram.ai/demo-node-form", + "projectFolder": "apps/demo-node-form", + "versionPolicyName": "appPolicy", + "tags": [ + "level-1", + "team-flow", + "demo" + ] } ] } From 8027436e065fa734a95ce9bfb39d78f13dee7e74 Mon Sep 17 00:00:00 2001 From: YuanHeDx Date: Mon, 17 Mar 2025 15:57:58 +0800 Subject: [PATCH 04/11] doc: complete node form basic example --- apps/demo-free-layout/package.json | 2 +- apps/demo-node-form/package.json | 3 +- .../src/components/field-title.tsx | 5 + .../src/components/field-wrapper.css | 18 +++ .../src/components/field-wrapper.tsx | 21 ++++ apps/demo-node-form/src/components/index.ts | 2 + apps/demo-node-form/src/constant.ts | 41 +++++++ apps/demo-node-form/src/form-meta.tsx | 51 +++++++++ apps/demo-node-form/src/index.css | 14 ++- apps/demo-node-form/src/index.tsx | 5 + apps/demo-node-form/src/initial-data.ts | 4 - apps/demo-node-form/src/node-registries.tsx | 21 +--- apps/docs/components/form-basic/preview.tsx | 108 ++++++++++++++---- common/config/rush/pnpm-lock.yaml | 6 +- 14 files changed, 248 insertions(+), 53 deletions(-) create mode 100644 apps/demo-node-form/src/components/field-title.tsx create mode 100644 apps/demo-node-form/src/components/field-wrapper.css create mode 100644 apps/demo-node-form/src/components/field-wrapper.tsx create mode 100644 apps/demo-node-form/src/components/index.ts create mode 100644 apps/demo-node-form/src/constant.ts create mode 100644 apps/demo-node-form/src/form-meta.tsx diff --git a/apps/demo-free-layout/package.json b/apps/demo-free-layout/package.json index de73eb02..d02ffcfd 100644 --- a/apps/demo-free-layout/package.json +++ b/apps/demo-free-layout/package.json @@ -49,7 +49,7 @@ "@types/node": "^18", "@types/react": "^18", "@types/react-dom": "^18", - "@types/styled-components": "^5", + "styled-components": "^5", "@typescript-eslint/parser": "^6.10.0", "eslint": "^8.54.0", "less": "^4.1.2", diff --git a/apps/demo-node-form/package.json b/apps/demo-node-form/package.json index b582fb1b..f4e53eb2 100644 --- a/apps/demo-node-form/package.json +++ b/apps/demo-node-form/package.json @@ -34,7 +34,8 @@ "@flowgram.ai/free-snap-plugin": "workspace:*", "@flowgram.ai/minimap-plugin": "workspace:*", "react": "^18", - "react-dom": "^18" + "react-dom": "^18", + "styled-components": "^5" }, "devDependencies": { "@flowgram.ai/ts-config": "workspace:*", diff --git a/apps/demo-node-form/src/components/field-title.tsx b/apps/demo-node-form/src/components/field-title.tsx new file mode 100644 index 00000000..3bc680d9 --- /dev/null +++ b/apps/demo-node-form/src/components/field-title.tsx @@ -0,0 +1,5 @@ +import styled from 'styled-components'; + +export const FieldTitle = styled.div` + padding-bottom: 4px; +`; diff --git a/apps/demo-node-form/src/components/field-wrapper.css b/apps/demo-node-form/src/components/field-wrapper.css new file mode 100644 index 00000000..57de555c --- /dev/null +++ b/apps/demo-node-form/src/components/field-wrapper.css @@ -0,0 +1,18 @@ +.error-message { + color: #f5222d !important; +} + +.required { + color: #f5222d !important; + padding-left: 4px +} + +.field-wrapper { + width: 100%; + margin-bottom: 12px; +} + +.field-title { + margin-bottom: 6px; +} + diff --git a/apps/demo-node-form/src/components/field-wrapper.tsx b/apps/demo-node-form/src/components/field-wrapper.tsx new file mode 100644 index 00000000..7e26c9e7 --- /dev/null +++ b/apps/demo-node-form/src/components/field-wrapper.tsx @@ -0,0 +1,21 @@ +import React from 'react'; + +import './field-wrapper.css'; + +interface FieldWrapperProps { + required?: boolean; + title: string; + children?: React.ReactNode; + error?: string; +} + +export const FieldWrapper = ({ required, title, children, error }: FieldWrapperProps) => ( +
+
+ {title} + {required ? * : null} +
+ {children} +

{error}

+
+); diff --git a/apps/demo-node-form/src/components/index.ts b/apps/demo-node-form/src/components/index.ts new file mode 100644 index 00000000..f04829e6 --- /dev/null +++ b/apps/demo-node-form/src/components/index.ts @@ -0,0 +1,2 @@ +export { FieldTitle } from './field-title'; +export { FieldWrapper } from './field-wrapper'; diff --git a/apps/demo-node-form/src/constant.ts b/apps/demo-node-form/src/constant.ts new file mode 100644 index 00000000..7cf14596 --- /dev/null +++ b/apps/demo-node-form/src/constant.ts @@ -0,0 +1,41 @@ +export const fieldWrapperTs = `import React from 'react'; + +import './index.css'; + +interface FieldWrapperProps { + required?: boolean; + title: string; + children?: React.ReactNode; + error?: string; +} + +export const FieldWrapper = ({ required, title, children, error }: FieldWrapperProps) => ( +
+
+ {title} + {required ? * : null} +
+ {children} +

{error}

+
+); +`; + +export const fieldWrapperCss = `.error-message { + color: #f5222d !important; +} + +.required { + color: #f5222d !important; + padding-left: 4px +} + +.field-wrapper { + width: 100%; + margin-bottom: 12px; +} + +.field-title { + margin-bottom: 6px; +} +`; diff --git a/apps/demo-node-form/src/form-meta.tsx b/apps/demo-node-form/src/form-meta.tsx new file mode 100644 index 00000000..8cf213e0 --- /dev/null +++ b/apps/demo-node-form/src/form-meta.tsx @@ -0,0 +1,51 @@ +import { + Field, + FieldRenderProps, + FormMeta, + ValidateTrigger, +} from '@flowgram.ai/free-layout-editor'; +import { Input } from '@douyinfe/semi-ui'; + +// FieldWrapper is not provided by sdk, and can be customized +import { FieldWrapper } from './components'; + +const render = () => ( +
+
Basic Node
+ + {({ field, fieldState }: FieldRenderProps) => ( + + + + )} + + + + {({ field, fieldState }: FieldRenderProps) => ( + + + + )} + +
+); + +const formMeta: FormMeta = { + render, + defaultValues: { name: 'Tina', city: 'Hangzhou' }, + validateTrigger: ValidateTrigger.onChange, + validate: { + name: ({ value }) => { + if (!value) { + return 'Name is required'; + } + }, + city: ({ value }) => { + if (!value) { + return 'City is required'; + } + }, + }, +}; + +export const DEFAULT_FORM_META = formMeta; diff --git a/apps/demo-node-form/src/index.css b/apps/demo-node-form/src/index.css index c76cebfb..15728111 100644 --- a/apps/demo-node-form/src/index.css +++ b/apps/demo-node-form/src/index.css @@ -11,11 +11,17 @@ box-shadow: 0px 2px 4px 0px rgba(0, 0, 0, 0.1); } -.demo-free-node-title { - background-color: #93bfe2; +.demo-node-content { + padding: 8px 12px; + flex-grow: 1; width: 100%; - border-radius: 8px 8px 0 0; - padding: 4px 12px; +} + +.demo-node-title { + font-weight: 500; + font-size: 14px; + width: 100%; + margin: 4px 0px 12px 0px; } .demo-free-node-content { padding: 4px 12px; diff --git a/apps/demo-node-form/src/index.tsx b/apps/demo-node-form/src/index.tsx index 2f6832bf..f99f9f87 100644 --- a/apps/demo-node-form/src/index.tsx +++ b/apps/demo-node-form/src/index.tsx @@ -1 +1,6 @@ export { Editor } from './editor'; +export { FieldTitle, FieldWrapper } from './components'; +export { DEFAULT_FORM_META } from './form-meta'; +export { DEFAULT_DEMO_REGISTRY } from './node-registries'; +export { DEFAULT_INITIAL_DATA } from './initial-data'; +export { fieldWrapperTs, fieldWrapperCss } from './constant'; diff --git a/apps/demo-node-form/src/initial-data.ts b/apps/demo-node-form/src/initial-data.ts index b2e76742..0a0c8cdb 100644 --- a/apps/demo-node-form/src/initial-data.ts +++ b/apps/demo-node-form/src/initial-data.ts @@ -8,10 +8,6 @@ export const DEFAULT_INITIAL_DATA: WorkflowJSON = { meta: { position: { x: 400, y: 0 }, }, - data: { - title: 'Custom', - content: 'Custom node content', - }, }, ], edges: [], diff --git a/apps/demo-node-form/src/node-registries.tsx b/apps/demo-node-form/src/node-registries.tsx index 21edbcbd..1afa4cab 100644 --- a/apps/demo-node-form/src/node-registries.tsx +++ b/apps/demo-node-form/src/node-registries.tsx @@ -1,23 +1,10 @@ -import { WorkflowNodeRegistry, Field } from '@flowgram.ai/free-layout-editor'; -import { Input, TextArea } from '@douyinfe/semi-ui'; +import { WorkflowNodeRegistry } from '@flowgram.ai/free-layout-editor'; + +import { DEFAULT_FORM_META } from './form-meta'; export const DEFAULT_DEMO_REGISTRY: WorkflowNodeRegistry = { type: 'custom', meta: {}, defaultPorts: [{ type: 'output' }, { type: 'input' }], - formMeta: { - render: () => ( -
-
Basic Node
-

name

- - - -

city

- - - -
- ), - }, + formMeta: DEFAULT_FORM_META, }; diff --git a/apps/docs/components/form-basic/preview.tsx b/apps/docs/components/form-basic/preview.tsx index aa21182d..97742096 100644 --- a/apps/docs/components/form-basic/preview.tsx +++ b/apps/docs/components/form-basic/preview.tsx @@ -1,42 +1,104 @@ +import { + DEFAULT_DEMO_REGISTRY, + DEFAULT_INITIAL_DATA, + fieldWrapperCss, + fieldWrapperTs, +} from '@flowgram.ai/demo-node-form'; + import { PreviewEditor } from '../preview-editor'; import { Editor } from '.'; -const indexCode = { +const registryCode = { code: `import { - EditorRenderer, - FreeLayoutEditorProvider, + Field, + FieldRenderProps, + FormMeta, + ValidateTrigger, } from '@flowgram.ai/free-layout-editor'; +import { Input } from '@douyinfe/semi-ui'; -import { useEditorProps } from './hooks/use-editor-props' -import '@flowgram.ai/free-layout-editor/index.css'; -import './index.css'; +// FieldWrapper is not provided by sdk, it can be customized +import { FieldWrapper } from './components'; -export const App = () => { - const editorProps = useEditorProps() - return ( - -
-
- - -
- - -
-
- ) +const render = () => ( +
+
Basic Node
+ + {({ field, fieldState }: FieldRenderProps) => ( + + + + )} + + + + {({ field, fieldState }: FieldRenderProps) => ( + + + + )} + +
+); + +const formMeta: FormMeta = { + render, + defaultValues: { name: 'Tina', city: 'Hangzhou' }, + validateTrigger: ValidateTrigger.onChange, + validate: { + name: ({ value }) => { + if (!value) { + return 'Name is required'; + } + }, + city: ({ value }) => { + if (!value) { + return 'City is required'; + } + } + } }; - `, + + + +export const nodeRegistry: WorkflowNodeRegistry = { + type: 'custom', + meta: {}, + defaultPorts: [{ type: 'output' }, { type: 'input' }], + formMeta +}; +`, + active: true, +}; + +const initialDataCode = { + code: `import { WorkflowJSON } from '@flowgram.ai/free-layout-editor'; + +export const DEFAULT_INITIAL_DATA: WorkflowJSON = { + nodes: [ + { + id: 'node_0', + type: 'custom', + meta: { + position: { x: 400, y: 0 }, + }, + }, + ], + edges: [], +};`, active: true, }; export const NodeFormBasicPreview = () => { const files = { - 'index.tsx': indexCode, + 'node-registry.tsx': registryCode, + 'initial-data.ts': initialDataCode, + 'field-wrapper.tsx': { code: fieldWrapperTs, active: true }, + 'field-wrapper.css': { code: fieldWrapperCss, active: true }, }; return ( - + ); }; diff --git a/common/config/rush/pnpm-lock.yaml b/common/config/rush/pnpm-lock.yaml index 51f69125..b612c91b 100644 --- a/common/config/rush/pnpm-lock.yaml +++ b/common/config/rush/pnpm-lock.yaml @@ -245,9 +245,6 @@ importers: '@types/react-dom': specifier: ^18 version: 18.3.5(@types/react@18.3.16) - '@types/styled-components': - specifier: ^5 - version: 5.1.34 '@typescript-eslint/parser': specifier: ^6.10.0 version: 6.21.0(eslint@8.57.1)(typescript@5.0.4) @@ -333,6 +330,9 @@ importers: react-dom: specifier: ^18 version: 18.3.1(react@18.3.1) + styled-components: + specifier: ^5 + version: 5.3.11(@babel/core@7.26.0)(react-dom@18.3.1)(react-is@18.3.1)(react@18.3.1) devDependencies: '@flowgram.ai/eslint-config': specifier: workspace:* From b4450db944b41c6bd789e95a5ccc5a104492efc6 Mon Sep 17 00:00:00 2001 From: YuanHeDx Date: Mon, 17 Mar 2025 20:35:23 +0800 Subject: [PATCH 05/11] doc: add form effect examples --- .../src/components/field-wrapper.css | 6 + .../src/components/field-wrapper.tsx | 5 +- apps/demo-node-form/src/constant.ts | 28 ++++- apps/demo-node-form/src/editor.tsx | 2 +- apps/demo-node-form/src/index.tsx | 2 +- apps/docs/components/index.ts | 2 +- .../basic-preview.tsx} | 23 +--- .../index.tsx => node-form/editor.tsx} | 0 .../node-form/effect/node-registry.tsx | 82 +++++++++++++ .../components/node-form/effect/preview.tsx | 112 ++++++++++++++++++ apps/docs/components/node-form/index.css | 12 ++ apps/docs/components/node-form/index.ts | 2 + .../docs/src/zh/examples/node-form/_meta.json | 3 +- .../docs/src/zh/examples/node-form/effect.mdx | 10 ++ packages/node-engine/node/src/types.ts | 8 +- 15 files changed, 268 insertions(+), 29 deletions(-) rename apps/docs/components/{form-basic/preview.tsx => node-form/basic-preview.tsx} (84%) rename apps/docs/components/{form-basic/index.tsx => node-form/editor.tsx} (100%) create mode 100644 apps/docs/components/node-form/effect/node-registry.tsx create mode 100644 apps/docs/components/node-form/effect/preview.tsx create mode 100644 apps/docs/components/node-form/index.css create mode 100644 apps/docs/components/node-form/index.ts create mode 100644 apps/docs/src/zh/examples/node-form/effect.mdx diff --git a/apps/demo-node-form/src/components/field-wrapper.css b/apps/demo-node-form/src/components/field-wrapper.css index 57de555c..0963e991 100644 --- a/apps/demo-node-form/src/components/field-wrapper.css +++ b/apps/demo-node-form/src/components/field-wrapper.css @@ -16,3 +16,9 @@ margin-bottom: 6px; } +.field-note{ + color: #a3a0a0 !important; + font-size: 12px; + margin: 6px 0; +} + diff --git a/apps/demo-node-form/src/components/field-wrapper.tsx b/apps/demo-node-form/src/components/field-wrapper.tsx index 7e26c9e7..69f33c58 100644 --- a/apps/demo-node-form/src/components/field-wrapper.tsx +++ b/apps/demo-node-form/src/components/field-wrapper.tsx @@ -7,15 +7,18 @@ interface FieldWrapperProps { title: string; children?: React.ReactNode; error?: string; + note?: string; } -export const FieldWrapper = ({ required, title, children, error }: FieldWrapperProps) => ( +export const FieldWrapper = ({ required, title, children, error, note }: FieldWrapperProps) => (
{title} + {note ?

{note}

: null} {required ? * : null}
{children}

{error}

+ {note ?
: null}
); diff --git a/apps/demo-node-form/src/constant.ts b/apps/demo-node-form/src/constant.ts index 7cf14596..12ed43f2 100644 --- a/apps/demo-node-form/src/constant.ts +++ b/apps/demo-node-form/src/constant.ts @@ -1,22 +1,25 @@ export const fieldWrapperTs = `import React from 'react'; -import './index.css'; +import './field-wrapper.css'; interface FieldWrapperProps { required?: boolean; title: string; children?: React.ReactNode; error?: string; + note?: string; } -export const FieldWrapper = ({ required, title, children, error }: FieldWrapperProps) => ( +export const FieldWrapper = ({ required, title, children, error, note }: FieldWrapperProps) => (
{title} + {note ?

{note}

: null} {required ? * : null}
{children}

{error}

+ {note ?
: null}
); `; @@ -38,4 +41,25 @@ export const fieldWrapperCss = `.error-message { .field-title { margin-bottom: 6px; } + +.field-note{ + color: #a3a0a0 !important; + font-size: 12px; + margin: 6px 0; +} `; + +export const defaultInitialDataTs = `import { WorkflowJSON } from '@flowgram.ai/free-layout-editor'; + +export const DEFAULT_INITIAL_DATA: WorkflowJSON = { + nodes: [ + { + id: 'node_0', + type: 'custom', + meta: { + position: { x: 400, y: 0 }, + }, + }, + ], + edges: [], +};`; diff --git a/apps/demo-node-form/src/editor.tsx b/apps/demo-node-form/src/editor.tsx index 16d8e870..ad395ec6 100644 --- a/apps/demo-node-form/src/editor.tsx +++ b/apps/demo-node-form/src/editor.tsx @@ -14,7 +14,7 @@ interface EditorProps { } export const Editor = ({ registry, initialData }: EditorProps) => { - const editorProps = useEditorProps({ registry, initialData }); + const editorProps = useEditorProps({ registries: [registry], initialData }); return (
diff --git a/apps/demo-node-form/src/index.tsx b/apps/demo-node-form/src/index.tsx index f99f9f87..ecd5a5c2 100644 --- a/apps/demo-node-form/src/index.tsx +++ b/apps/demo-node-form/src/index.tsx @@ -3,4 +3,4 @@ export { FieldTitle, FieldWrapper } from './components'; export { DEFAULT_FORM_META } from './form-meta'; export { DEFAULT_DEMO_REGISTRY } from './node-registries'; export { DEFAULT_INITIAL_DATA } from './initial-data'; -export { fieldWrapperTs, fieldWrapperCss } from './constant'; +export { fieldWrapperTs, fieldWrapperCss, defaultInitialDataTs } from './constant'; diff --git a/apps/docs/components/index.ts b/apps/docs/components/index.ts index e6e21c6a..371c2dd9 100644 --- a/apps/docs/components/index.ts +++ b/apps/docs/components/index.ts @@ -5,4 +5,4 @@ export { FreeLayoutSimple } from './free-layout-simple'; export { FreeLayoutSimplePreview } from './free-layout-simple/preview'; export { FixedLayoutSimple } from './fixed-layout-simple'; export { FixedLayoutSimplePreview } from './fixed-layout-simple/preview'; -export { NodeFormBasicPreview } from './form-basic/preview.tsx'; +export { NodeFormBasicPreview, NodeFormEffectPreview } from './node-form'; diff --git a/apps/docs/components/form-basic/preview.tsx b/apps/docs/components/node-form/basic-preview.tsx similarity index 84% rename from apps/docs/components/form-basic/preview.tsx rename to apps/docs/components/node-form/basic-preview.tsx index 97742096..e31724c4 100644 --- a/apps/docs/components/form-basic/preview.tsx +++ b/apps/docs/components/node-form/basic-preview.tsx @@ -1,12 +1,13 @@ import { DEFAULT_DEMO_REGISTRY, DEFAULT_INITIAL_DATA, + defaultInitialDataTs, fieldWrapperCss, fieldWrapperTs, } from '@flowgram.ai/demo-node-form'; import { PreviewEditor } from '../preview-editor'; -import { Editor } from '.'; +import { Editor } from './editor'; const registryCode = { code: `import { @@ -71,28 +72,10 @@ export const nodeRegistry: WorkflowNodeRegistry = { active: true, }; -const initialDataCode = { - code: `import { WorkflowJSON } from '@flowgram.ai/free-layout-editor'; - -export const DEFAULT_INITIAL_DATA: WorkflowJSON = { - nodes: [ - { - id: 'node_0', - type: 'custom', - meta: { - position: { x: 400, y: 0 }, - }, - }, - ], - edges: [], -};`, - active: true, -}; - export const NodeFormBasicPreview = () => { const files = { 'node-registry.tsx': registryCode, - 'initial-data.ts': initialDataCode, + 'initial-data.ts': { code: defaultInitialDataTs, active: true }, 'field-wrapper.tsx': { code: fieldWrapperTs, active: true }, 'field-wrapper.css': { code: fieldWrapperCss, active: true }, }; diff --git a/apps/docs/components/form-basic/index.tsx b/apps/docs/components/node-form/editor.tsx similarity index 100% rename from apps/docs/components/form-basic/index.tsx rename to apps/docs/components/node-form/editor.tsx diff --git a/apps/docs/components/node-form/effect/node-registry.tsx b/apps/docs/components/node-form/effect/node-registry.tsx new file mode 100644 index 00000000..feabcfd5 --- /dev/null +++ b/apps/docs/components/node-form/effect/node-registry.tsx @@ -0,0 +1,82 @@ +import { + DataEvent, + EffectProps, + Field, + FieldRenderProps, + FormMeta, + ValidateTrigger, + WorkflowNodeRegistry, +} from '@flowgram.ai/free-layout-editor'; +import { FieldWrapper } from '@flowgram.ai/demo-node-form'; +import { Input } from '@douyinfe/semi-ui'; +import '../index.css'; + +const render = () => ( +
+
Effect Examples
+ + {({ field }: FieldRenderProps) => ( + + + + )} + + + + {({ field }: FieldRenderProps) => ( + + + + )} + + + {({ field }: FieldRenderProps) => ( + + + + )} + +
+); + +interface FormData { + field1: string; + field2: string; + field3: string; +} + +const formMeta: FormMeta = { + render, + validateTrigger: ValidateTrigger.onChange, + effect: { + field1: [ + { + event: DataEvent.onValueChange, + effect: ({ value }: EffectProps) => { + console.log('field1 value:', value); + }, + }, + ], + field2: [ + { + event: DataEvent.onValueChange, + effect: ({ value, form }: EffectProps) => { + form.setValueIn('field3', 'field2 value is ' + value); + }, + }, + ], + }, +}; + +export const nodeRegistry: WorkflowNodeRegistry = { + type: 'custom', + meta: {}, + defaultPorts: [{ type: 'output' }, { type: 'input' }], + formMeta, +}; diff --git a/apps/docs/components/node-form/effect/preview.tsx b/apps/docs/components/node-form/effect/preview.tsx new file mode 100644 index 00000000..3f00445f --- /dev/null +++ b/apps/docs/components/node-form/effect/preview.tsx @@ -0,0 +1,112 @@ +import { + DEFAULT_INITIAL_DATA, + defaultInitialDataTs, + fieldWrapperCss, + fieldWrapperTs, +} from '@flowgram.ai/demo-node-form'; + +import { Editor } from '../editor.tsx'; +import { PreviewEditor } from '../../preview-editor.tsx'; +import { nodeRegistry } from './node-registry.tsx'; + +const nodeRegistryFile = { + code: `import { + DataEvent, + EffectProps, + Field, + FieldRenderProps, + FormMeta, + ValidateTrigger, + WorkflowNodeRegistry, +} from '@flowgram.ai/free-layout-editor'; +import { FieldWrapper } from '@flowgram.ai/demo-node-form'; +import { Input } from '@douyinfe/semi-ui'; +import '../index.css'; + +const render = () => ( +
+
Effect Examples
+ + {({ field }: FieldRenderProps) => ( + + + + )} + + + + {({ field }: FieldRenderProps) => ( + + + + )} + + + {({ field }: FieldRenderProps) => ( + + + + )} + +
+); + +interface FormData { + field1: string; + field2: string; + field3: string; +} + +const formMeta: FormMeta = { + render, + validateTrigger: ValidateTrigger.onChange, + effect: { + field1: [ + { + event: DataEvent.onValueChange, + effect: ({ value }: EffectProps) => { + console.log('field1 value:', value); + }, + }, + ], + field2: [ + { + event: DataEvent.onValueChange, + effect: ({ value, form }: EffectProps) => { + form.setValueIn('field3', 'field2 value is ' + value); + }, + }, + ], + }, +}; + +export const nodeRegistry: WorkflowNodeRegistry = { + type: 'custom', + meta: {}, + defaultPorts: [{ type: 'output' }, { type: 'input' }], + formMeta, +}; + +`, + active: true, +}; + +export const NodeFormEffectPreview = () => { + const files = { + 'node-registry.tsx': nodeRegistryFile, + 'initial-data.ts': { code: defaultInitialDataTs, active: true }, + 'field-wrapper.tsx': { code: fieldWrapperTs, active: true }, + 'field-wrapper.css': { code: fieldWrapperCss, active: true }, + }; + return ( + + + + ); +}; diff --git a/apps/docs/components/node-form/index.css b/apps/docs/components/node-form/index.css new file mode 100644 index 00000000..c9f4fd27 --- /dev/null +++ b/apps/docs/components/node-form/index.css @@ -0,0 +1,12 @@ +.demo-node-content { + padding: 8px 12px; + flex-grow: 1; + width: 100%; +} + +.demo-node-title { + font-weight: 500; + font-size: 14px; + width: 100%; + margin: 4px 0px 12px 0px; +} diff --git a/apps/docs/components/node-form/index.ts b/apps/docs/components/node-form/index.ts new file mode 100644 index 00000000..6b593144 --- /dev/null +++ b/apps/docs/components/node-form/index.ts @@ -0,0 +1,2 @@ +export { NodeFormBasicPreview } from './basic-preview'; +export { NodeFormEffectPreview } from './effect/preview'; diff --git a/apps/docs/src/zh/examples/node-form/_meta.json b/apps/docs/src/zh/examples/node-form/_meta.json index 99cba57d..aa824cf5 100644 --- a/apps/docs/src/zh/examples/node-form/_meta.json +++ b/apps/docs/src/zh/examples/node-form/_meta.json @@ -1,3 +1,4 @@ [ - "basic" + "basic", + "effect" ] diff --git a/apps/docs/src/zh/examples/node-form/effect.mdx b/apps/docs/src/zh/examples/node-form/effect.mdx new file mode 100644 index 00000000..f6142722 --- /dev/null +++ b/apps/docs/src/zh/examples/node-form/effect.mdx @@ -0,0 +1,10 @@ +--- +outline: false +--- + + +# 表单项变更副作用 ( effect) + +import { NodeFormEffectPreview } from '../../../../components'; + + diff --git a/packages/node-engine/node/src/types.ts b/packages/node-engine/node/src/types.ts index b0e88e75..971ffd9b 100644 --- a/packages/node-engine/node/src/types.ts +++ b/packages/node-engine/node/src/types.ts @@ -51,14 +51,18 @@ export enum DataEvent { export type EffectReturn = () => void; -export type Effect = (props: { +export interface EffectProps { name: FieldName; value: TFieldValue; prevValue?: TFieldValue; formValues: TFormValues; form: IForm; context: NodeContext; -}) => void | EffectReturn; +} + +export type Effect = ( + props: EffectProps +) => void | EffectReturn; export type ArrayAppendEffect = (props: { index: number; From 40eafff3ec0f9b5a7e6dfde569ee1c390622dfab Mon Sep 17 00:00:00 2001 From: YuanHeDx Date: Mon, 17 Mar 2025 21:14:54 +0800 Subject: [PATCH 06/11] fix(form): fix hasError called in FormModel.validate --- .../form/__tests__/validate.test.ts | 22 +++++++++++++++++++ .../node-engine/form/src/core/form-model.ts | 1 + .../node-engine/form/src/utils/validate.ts | 10 ++------- 3 files changed, 25 insertions(+), 8 deletions(-) create mode 100644 packages/node-engine/form/__tests__/validate.test.ts diff --git a/packages/node-engine/form/__tests__/validate.test.ts b/packages/node-engine/form/__tests__/validate.test.ts new file mode 100644 index 00000000..b1230d33 --- /dev/null +++ b/packages/node-engine/form/__tests__/validate.test.ts @@ -0,0 +1,22 @@ +import { describe, expect, it } from 'vitest'; + +import { hasError } from '../src/utils/validate'; +import { FeedbackLevel, FieldError } from '../src/types'; + +describe('utils/validate', () => { + describe('hasError', () => { + it('should return false when errors is empty', () => { + expect(hasError({ xxx: [] })).toBe(false); + expect(hasError({ xxx: undefined })).toBe(false); + expect(hasError({})).toBe(false); + expect(hasError({ aaa: [], bbb: [] })).toBe(false); + expect(hasError({ aaa: undefined, bbb: [] })).toBe(false); + }); + it('should return true when errors is not empty', () => { + const mockError: FieldError = { name: 'xxx', level: FeedbackLevel.Error, message: 'err' }; + expect(hasError({ xxx: [mockError] })).toBe(true); + expect(hasError({ aaa: [mockError], bbb: [mockError] })).toBe(true); + expect(hasError({ aaa: undefined, bbb: [mockError] })).toBe(true); + }); + }); +}); diff --git a/packages/node-engine/form/src/core/form-model.ts b/packages/node-engine/form/src/core/form-model.ts index 988ef845..6cfa65d2 100644 --- a/packages/node-engine/form/src/core/form-model.ts +++ b/packages/node-engine/form/src/core/form-model.ts @@ -279,6 +279,7 @@ export class FormModel implements Disposable { const warnings = feedbackToFieldErrorsOrWarnings(path, feedback); if (field) { + debugger; field.state.errors = errors; field.state.warnings = warnings; field.state.invalid = hasError(errors); diff --git a/packages/node-engine/form/src/utils/validate.ts b/packages/node-engine/form/src/utils/validate.ts index 117f87c2..dd25a5f2 100644 --- a/packages/node-engine/form/src/utils/validate.ts +++ b/packages/node-engine/form/src/utils/validate.ts @@ -33,11 +33,5 @@ export function feedbackToFieldErrorsOrWarnings(name: string, feedback?: Feed } as T; } -export const hasError = (errors: Errors) => { - for (let fieldErrors in errors) { - if (fieldErrors.length) { - return true; - } - } - return false; -}; +export const hasError = (errors: Errors) => + Object.keys(errors).some((key) => errors[key]?.length > 0); From 26a8ad239006b61e1250dae6234b071e039935bd Mon Sep 17 00:00:00 2001 From: YuanHeDx Date: Tue, 18 Mar 2025 11:43:56 +0800 Subject: [PATCH 07/11] fix: fix demo-node-form versionPolicy --- rush.json | 1 - 1 file changed, 1 deletion(-) diff --git a/rush.json b/rush.json index 531eec28..95dc775c 100644 --- a/rush.json +++ b/rush.json @@ -778,7 +778,6 @@ { "packageName": "@flowgram.ai/demo-node-form", "projectFolder": "apps/demo-node-form", - "versionPolicyName": "appPolicy", "tags": [ "level-1", "team-flow", From 99ecb565cb6ee8b631faccf29059c6d7d0be2c98 Mon Sep 17 00:00:00 2001 From: YuanHeDx Date: Tue, 18 Mar 2025 11:47:13 +0800 Subject: [PATCH 08/11] fix: fix type deps --- apps/demo-free-layout/package.json | 2 +- common/config/rush/pnpm-lock.yaml | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/apps/demo-free-layout/package.json b/apps/demo-free-layout/package.json index d02ffcfd..de73eb02 100644 --- a/apps/demo-free-layout/package.json +++ b/apps/demo-free-layout/package.json @@ -49,7 +49,7 @@ "@types/node": "^18", "@types/react": "^18", "@types/react-dom": "^18", - "styled-components": "^5", + "@types/styled-components": "^5", "@typescript-eslint/parser": "^6.10.0", "eslint": "^8.54.0", "less": "^4.1.2", diff --git a/common/config/rush/pnpm-lock.yaml b/common/config/rush/pnpm-lock.yaml index b612c91b..42c28623 100644 --- a/common/config/rush/pnpm-lock.yaml +++ b/common/config/rush/pnpm-lock.yaml @@ -98,7 +98,7 @@ importers: version: link:../../config/ts-config '@rspack/cli': specifier: 0.2.1 - version: 0.2.1(react-refresh@0.14.0)(webpack@5.76.0) + version: 0.2.1(react-refresh@0.16.0)(webpack@5.76.0) '@types/lodash-es': specifier: ^4.17.12 version: 4.17.12 @@ -232,7 +232,7 @@ importers: version: link:../../config/ts-config '@rspack/cli': specifier: 0.2.1 - version: 0.2.1(react-refresh@0.16.0)(webpack@5.76.0) + version: 0.2.1(react-refresh@0.14.0)(webpack@5.76.0) '@types/lodash-es': specifier: ^4.17.12 version: 4.17.12 @@ -245,6 +245,9 @@ importers: '@types/react-dom': specifier: ^18 version: 18.3.5(@types/react@18.3.16) + '@types/styled-components': + specifier: ^5 + version: 5.1.34 '@typescript-eslint/parser': specifier: ^6.10.0 version: 6.21.0(eslint@8.57.1)(typescript@5.0.4) From 9dfe67a773fdbb36df8729b0b9e55cfd2857f135 Mon Sep 17 00:00:00 2001 From: YuanHeDx Date: Tue, 18 Mar 2025 11:55:14 +0800 Subject: [PATCH 09/11] chore: add cr owners --- .github/CODEOWNERS | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index c6dd2366..b34df23a 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,6 +1,9 @@ # 文件路径与代码负责人分配 # 对整个仓库设置代码负责人 -* @xiamidaxia @luics +* @xiamidaxia @luics @dragooncjw # 对特定目录设置代码负责人 -/docs/ @xiamidaxia @dragooncjw +/apps/docs/ @xiamidaxia @dragooncjw @YuanHeDx +/apps/demo-node-form/ @YuanHeDx +/packages/node-engine/ @YuanHeDx +/packages/plugins/node-core-plugin/ @YuanHeDx From de59756c575bdc81c5bffee7b656306051feff45 Mon Sep 17 00:00:00 2001 From: YuanHeDx Date: Tue, 18 Mar 2025 14:33:35 +0800 Subject: [PATCH 10/11] fix: remove debugger --- packages/node-engine/form/src/core/form-model.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/node-engine/form/src/core/form-model.ts b/packages/node-engine/form/src/core/form-model.ts index 6cfa65d2..988ef845 100644 --- a/packages/node-engine/form/src/core/form-model.ts +++ b/packages/node-engine/form/src/core/form-model.ts @@ -279,7 +279,6 @@ export class FormModel implements Disposable { const warnings = feedbackToFieldErrorsOrWarnings(path, feedback); if (field) { - debugger; field.state.errors = errors; field.state.warnings = warnings; field.state.invalid = hasError(errors); From ff1b637010b2b581e6b8ba14dc433524e4805a42 Mon Sep 17 00:00:00 2001 From: YuanHeDx Date: Tue, 18 Mar 2025 14:46:09 +0800 Subject: [PATCH 11/11] fix: fix type name conflict --- apps/docs/components/node-form/effect/node-registry.tsx | 6 +++--- apps/docs/components/node-form/effect/preview.tsx | 6 +++--- packages/node-engine/node/src/types.ts | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/apps/docs/components/node-form/effect/node-registry.tsx b/apps/docs/components/node-form/effect/node-registry.tsx index feabcfd5..004f0d73 100644 --- a/apps/docs/components/node-form/effect/node-registry.tsx +++ b/apps/docs/components/node-form/effect/node-registry.tsx @@ -1,6 +1,6 @@ import { DataEvent, - EffectProps, + EffectFuncProps, Field, FieldRenderProps, FormMeta, @@ -58,7 +58,7 @@ const formMeta: FormMeta = { field1: [ { event: DataEvent.onValueChange, - effect: ({ value }: EffectProps) => { + effect: ({ value }: EffectFuncProps) => { console.log('field1 value:', value); }, }, @@ -66,7 +66,7 @@ const formMeta: FormMeta = { field2: [ { event: DataEvent.onValueChange, - effect: ({ value, form }: EffectProps) => { + effect: ({ value, form }: EffectFuncProps) => { form.setValueIn('field3', 'field2 value is ' + value); }, }, diff --git a/apps/docs/components/node-form/effect/preview.tsx b/apps/docs/components/node-form/effect/preview.tsx index 3f00445f..45b02e60 100644 --- a/apps/docs/components/node-form/effect/preview.tsx +++ b/apps/docs/components/node-form/effect/preview.tsx @@ -12,7 +12,7 @@ import { nodeRegistry } from './node-registry.tsx'; const nodeRegistryFile = { code: `import { DataEvent, - EffectProps, + EffectFuncProps, Field, FieldRenderProps, FormMeta, @@ -70,7 +70,7 @@ const formMeta: FormMeta = { field1: [ { event: DataEvent.onValueChange, - effect: ({ value }: EffectProps) => { + effect: ({ value }: EffectFuncProps) => { console.log('field1 value:', value); }, }, @@ -78,7 +78,7 @@ const formMeta: FormMeta = { field2: [ { event: DataEvent.onValueChange, - effect: ({ value, form }: EffectProps) => { + effect: ({ value, form }: EffectFuncProps) => { form.setValueIn('field3', 'field2 value is ' + value); }, }, diff --git a/packages/node-engine/node/src/types.ts b/packages/node-engine/node/src/types.ts index 971ffd9b..e7fcf474 100644 --- a/packages/node-engine/node/src/types.ts +++ b/packages/node-engine/node/src/types.ts @@ -51,7 +51,7 @@ export enum DataEvent { export type EffectReturn = () => void; -export interface EffectProps { +export interface EffectFuncProps { name: FieldName; value: TFieldValue; prevValue?: TFieldValue; @@ -61,7 +61,7 @@ export interface EffectProps { } export type Effect = ( - props: EffectProps + props: EffectFuncProps ) => void | EffectReturn; export type ArrayAppendEffect = (props: {