Browse Source

doc(node): init node form demo

YuanHeDx 10 months ago
parent
commit
493d3084f5

+ 15 - 0
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 版本
+    },
+  },
+});

+ 12 - 0
apps/demo-node-form/index.html

@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html lang="en" data-bundler="rspack">
+  <head>
+    <meta charset="UTF-8" />
+    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <title>Flow FreeLayoutEditor Demo</title>
+  </head>
+  <body>
+    <div id="root"></div>
+  </body>
+</html>

+ 55 - 0
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/"
+  }
+}

+ 46 - 0
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,
+      },
+};

+ 7 - 0
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(<Editor />);

+ 27 - 0
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 (
+    <FreeLayoutEditorProvider {...editorProps}>
+      <div className="demo-free-container">
+        <div className="demo-free-layout">
+          <EditorRenderer className="demo-free-editor" />
+        </div>
+      </div>
+    </FreeLayoutEditorProvider>
+  );
+};

+ 165 - 0
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<FreeLayoutProps>(
+    () => ({
+      /**
+       * 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: () => (
+              <>
+                <Field<string> name="title">
+                  {({ field }) => <div className="demo-free-node-title">{field.value}</div>}
+                </Field>
+                <div className="demo-free-node-content">
+                  <Field<string> name="content">
+                    <input />
+                  </Field>
+                </div>
+              </>
+            ),
+          },
+        };
+      },
+      materials: {
+        /**
+         * Render Node
+         */
+        renderDefaultNode: (props: WorkflowNodeProps) => {
+          const { form } = useNodeRender();
+          return (
+            <WorkflowNodeRenderer className="demo-free-node" node={props.node}>
+              {form?.render()}
+            </WorkflowNodeRenderer>
+          );
+        },
+      },
+      /**
+       * 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,
+        }),
+      ],
+    }),
+    []
+  );

+ 107 - 0
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;
+}
+

+ 1 - 0
apps/demo-node-form/src/index.tsx

@@ -0,0 +1 @@
+export { Editor } from './editor';

+ 18 - 0
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: [],
+};

+ 23 - 0
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: () => (
+      <div>
+        <div>Basic Node</div>
+        <p>name</p>
+        <Field name="name">
+          <Input />
+        </Field>
+        <p>city</p>
+        <Field name="city">
+          <Input />
+        </Field>
+      </div>
+    ),
+  },
+};

+ 23 - 0
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"],
+}

+ 10 - 0
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 };

+ 42 - 0
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 (
+    <FreeLayoutEditorProvider {...editorProps}>
+      <div className="demo-free-container">
+        <div className="demo-free-layout">
+          <NodeAddPanel />
+          <EditorRenderer className="demo-free-editor" />
+        </div>
+        <Tools />
+        <Minimap />
+      </div>
+    </FreeLayoutEditorProvider>
+  )
+};
+  `,
+  active: true,
+};
+
+export const NodeFormBasicPreview = () => {
+  const files = {
+    'index.tsx': indexCode,
+  };
+  return (
+    <PreviewEditor files={files} previewStyle={{ height: 500 }} editorStyle={{ height: 500 }}>
+      <Editor />
+    </PreviewEditor>
+  );
+};

+ 1 - 0
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';

+ 1 - 0
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:*",

+ 5 - 0
apps/docs/src/zh/examples/_meta.json

@@ -13,5 +13,10 @@
     "type": "dir",
     "name": "free-layout",
     "label": "自由布局"
+  },
+  {
+    "type": "dir",
+    "name": "node-form",
+    "label": "节点表单"
   }
 ]

+ 3 - 0
apps/docs/src/zh/examples/node-form/_meta.json

@@ -0,0 +1,3 @@
+[
+  "basic"
+]

+ 10 - 0
apps/docs/src/zh/examples/node-form/basic.mdx

@@ -0,0 +1,10 @@
+---
+outline: false
+---
+
+
+# 基础用法
+
+import { NodeFormBasicPreview } from '../../../../components';
+
+<NodeFormBasicPreview />

+ 58 - 0
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

+ 4 - 0
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,
               })
             );

+ 9 - 1
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<TFieldValue = any, TFormValues = any> = (props: {
   value: TFieldValue;
   prevValue?: TFieldValue;
   formValues: TFormValues;
+  form: IForm;
   context: NodeContext;
 }) => void | EffectReturn;
 
@@ -59,6 +65,7 @@ export type ArrayAppendEffect<TFieldValue = any, TFormValues = any> = (props: {
   value: TFieldValue;
   arrayValues: Array<TFieldValue>;
   formValues: TFormValues;
+  form: IForm;
   context: NodeContext;
 }) => void | EffectReturn;
 
@@ -66,6 +73,7 @@ export type ArrayDeleteEffect<TFieldValue = any, TFormValues = any> = (props: {
   index: number;
   arrayValue: Array<TFieldValue>;
   formValues: TFormValues;
+  form: IForm;
   context: NodeContext;
 }) => void | EffectReturn;
 

+ 10 - 0
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"
+      ]
     }
   ]
 }