فهرست منبع

feat(demo-fixed-layout): add case-default/break-loop/if nodes,condition -> switch (#336)

* feat(demo-fixed-layout): add case-default/break-loop/if nodes

* feat(demo-fixed-layout): condition -> switch

* chore: e2e fixed
xiamidaxia 7 ماه پیش
والد
کامیت
de863df6fb
28فایلهای تغییر یافته به همراه398 افزوده شده و 95 حذف شده
  1. 1 0
      apps/demo-fixed-layout/src/assets/icon-break.svg
  2. BIN
      apps/demo-fixed-layout/src/assets/icon-case.png
  3. 2 1
      apps/demo-fixed-layout/src/components/base-node/index.tsx
  4. 5 2
      apps/demo-fixed-layout/src/components/branch-adder/index.tsx
  5. 1 1
      apps/demo-fixed-layout/src/components/node-list.tsx
  6. 8 7
      apps/demo-fixed-layout/src/components/sidebar/sidebar-renderer.tsx
  7. 95 43
      apps/demo-fixed-layout/src/initial-data.ts
  8. 13 0
      apps/demo-fixed-layout/src/nodes/break-loop/form-meta.tsx
  9. 42 0
      apps/demo-fixed-layout/src/nodes/break-loop/index.ts
  10. 32 0
      apps/demo-fixed-layout/src/nodes/case-default/form-meta.tsx
  11. 31 0
      apps/demo-fixed-layout/src/nodes/case-default/index.ts
  12. 5 5
      apps/demo-fixed-layout/src/nodes/case/index.ts
  13. 3 2
      apps/demo-fixed-layout/src/nodes/catch-block/index.ts
  14. 28 0
      apps/demo-fixed-layout/src/nodes/if-block/form-meta.tsx
  15. 30 0
      apps/demo-fixed-layout/src/nodes/if-block/index.ts
  16. 57 0
      apps/demo-fixed-layout/src/nodes/if/index.ts
  17. 10 2
      apps/demo-fixed-layout/src/nodes/index.ts
  18. 1 6
      apps/demo-fixed-layout/src/nodes/start/index.ts
  19. 15 7
      apps/demo-fixed-layout/src/nodes/switch/index.ts
  20. 3 2
      apps/demo-fixed-layout/src/typings/node.ts
  21. 8 7
      apps/demo-free-layout/src/components/sidebar/sidebar-renderer.tsx
  22. 1 1
      apps/demo-free-layout/src/hooks/use-editor-props.tsx
  23. 1 1
      apps/demo-free-layout/src/nodes/comment/index.tsx
  24. 0 2
      apps/demo-free-layout/src/nodes/loop/loop-form-render.tsx
  25. 1 1
      apps/demo-free-layout/src/typings/node.ts
  26. 1 1
      e2e/fixed-layout/tests/models/index.ts
  27. 3 3
      e2e/fixed-layout/tests/node.spec.ts
  28. 1 1
      packages/canvas-engine/document/src/entities/flow-node-entity.ts

+ 1 - 0
apps/demo-fixed-layout/src/assets/icon-break.svg

@@ -0,0 +1 @@
+<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" focusable="false" aria-hidden="true"><path fill-rule="evenodd" clip-rule="evenodd" d="M9.56066 2.43934C10.1464 3.02513 10.1464 3.97487 9.56066 4.56066L7.12132 7H14.75C18.8353 7 22 10.5796 22 14.5C22 18.4204 18.8353 22 14.75 22H11.5C10.6716 22 10 21.3284 10 20.5C10 19.6716 10.6716 19 11.5 19H14.75C17.016 19 19 16.9308 19 14.5C19 12.0692 17.016 10 14.75 10H7.12132L9.56066 12.4393C10.1464 13.0251 10.1464 13.9749 9.56066 14.5607C8.97487 15.1464 8.02513 15.1464 7.43934 14.5607L2.43934 9.56066C1.85355 8.97487 1.85355 8.02513 2.43934 7.43934L7.43934 2.43934C8.02513 1.85355 8.97487 1.85355 9.56066 2.43934Z" fill="#54A9FF"></path></svg>

BIN
apps/demo-fixed-layout/src/assets/icon-case.png


+ 2 - 1
apps/demo-fixed-layout/src/components/base-node/index.tsx

@@ -53,7 +53,8 @@ export const BaseNode = ({ node }: { node: FlowNodeEntity }) => {
            * isBlockIcon: 整个 condition 分支的 头部节点
            * isBlockIcon: 整个 condition 分支的 头部节点
            * isBlockOrderIcon: 分支的第一个节点
            * isBlockOrderIcon: 分支的第一个节点
            */
            */
-          ...(nodeRender.isBlockOrderIcon || nodeRender.isBlockIcon ? { width: 260 } : {}),
+          ...(nodeRender.isBlockOrderIcon || nodeRender.isBlockIcon ? {} : {}),
+          ...nodeRender.node.getNodeRegistry().meta.style,
           outline: form?.state.invalid ? '1px solid red' : 'none',
           outline: form?.state.invalid ? '1px solid red' : 'none',
         }}
         }}
       >
       >

+ 5 - 2
apps/demo-fixed-layout/src/components/branch-adder/index.tsx

@@ -20,9 +20,12 @@ export default function BranchAdder(props: PropsType) {
   function addBranch() {
   function addBranch() {
     const block = operation.addBlock(
     const block = operation.addBlock(
       node,
       node,
-      node.flowNodeType === 'condition'
+      node.flowNodeType === 'switch'
         ? CaseNodeRegistry.onAdd!(ctx, node)
         ? CaseNodeRegistry.onAdd!(ctx, node)
-        : CatchBlockNodeRegistry.onAdd!(ctx, node)
+        : CatchBlockNodeRegistry.onAdd!(ctx, node),
+      {
+        index: 0,
+      }
     );
     );
 
 
     setTimeout(() => {
     setTimeout(() => {

+ 1 - 1
apps/demo-fixed-layout/src/components/node-list.tsx

@@ -55,7 +55,7 @@ export function NodeList(props: { onSelect: (meta: any) => void; from: FlowNodeE
   };
   };
   return (
   return (
     <NodesWrap style={{ width: 80 * 2 + 20 }}>
     <NodesWrap style={{ width: 80 * 2 + 20 }}>
-      {FlowNodeRegistries.map((registry) => (
+      {FlowNodeRegistries.filter((registry) => !registry.meta?.addDisable).map((registry) => (
         <Node
         <Node
           key={registry.type}
           key={registry.type}
           disabled={!(registry.canAdd?.(context, props.from) ?? true)}
           disabled={!(registry.canAdd?.(context, props.from) ?? true)}

+ 8 - 7
apps/demo-fixed-layout/src/components/sidebar/sidebar-renderer.tsx

@@ -63,8 +63,8 @@ export const SidebarRenderer = () => {
     if (!node) {
     if (!node) {
       return false;
       return false;
     }
     }
-    const { disableSideBar = false } = node.getNodeMeta<FlowNodeMeta>();
-    return !disableSideBar;
+    const { sidebarDisable = false } = node.getNodeMeta<FlowNodeMeta>();
+    return !sidebarDisable;
   }, [node]);
   }, [node]);
 
 
   if (playground.config.readonly) {
   if (playground.config.readonly) {
@@ -73,11 +73,12 @@ export const SidebarRenderer = () => {
   /**
   /**
    * Add "key" to rerender the sidebar when the node changes
    * Add "key" to rerender the sidebar when the node changes
    */
    */
-  const content = node ? (
-    <PlaygroundEntityContext.Provider key={node.id} value={node}>
-      <SidebarNodeRenderer node={node} />
-    </PlaygroundEntityContext.Provider>
-  ) : null;
+  const content =
+    node && visible ? (
+      <PlaygroundEntityContext.Provider key={node.id} value={node}>
+        <SidebarNodeRenderer node={node} />
+      </PlaygroundEntityContext.Provider>
+    ) : null;
 
 
   return (
   return (
     <SideSheet mask={false} visible={visible} onCancel={handleClose}>
     <SideSheet mask={false} visible={visible} onCancel={handleClose}>

+ 95 - 43
apps/demo-fixed-layout/src/initial-data.ts

@@ -88,63 +88,115 @@ export const initialData: FlowDocumentJSON = {
       },
       },
     },
     },
     {
     {
-      id: 'loop_0',
-      type: 'loop',
+      id: 'if_0',
+      type: 'if',
       data: {
       data: {
-        title: 'Loop',
-        batchFor: {
-          type: 'ref',
-          content: ['start_0', 'array_obj'],
+        title: 'If',
+        inputsValues: {
+          condition: { type: 'constant', content: true },
+        },
+        inputs: {
+          type: 'object',
+          required: ['condition'],
+          properties: {
+            condition: {
+              type: 'boolean',
+            },
+          },
         },
         },
       },
       },
       blocks: [
       blocks: [
         {
         {
-          id: 'condition_0',
-          type: 'condition',
+          id: 'if_true',
+          type: 'ifBlock',
+          data: {
+            title: 'true',
+          },
+          blocks: [],
+        },
+        {
+          id: 'if_false',
+          type: 'ifBlock',
           data: {
           data: {
-            title: 'Condition',
+            title: 'false',
           },
           },
           blocks: [
           blocks: [
             {
             {
-              id: 'case_0',
-              type: 'case',
+              id: 'loop_0',
+              type: 'loop',
               data: {
               data: {
-                title: 'If_0',
-                inputsValues: {
-                  condition: { type: 'constant', content: true },
-                },
-                inputs: {
-                  type: 'object',
-                  required: ['condition'],
-                  properties: {
-                    condition: {
-                      type: 'boolean',
-                    },
-                  },
+                title: 'Loop',
+                batchFor: {
+                  type: 'ref',
+                  content: ['start_0', 'array_obj'],
                 },
                 },
               },
               },
-              blocks: [],
-            },
-            {
-              id: 'case_1',
-              type: 'case',
-              data: {
-                title: 'If_1',
-                inputsValues: {
-                  condition: { type: 'constant', content: true },
-                },
-                inputs: {
-                  type: 'object',
-                  required: ['condition'],
-                  properties: {
-                    condition: {
-                      type: 'boolean',
-                    },
+              blocks: [
+                {
+                  id: 'switch_0',
+                  type: 'switch',
+                  data: {
+                    title: 'Switch',
                   },
                   },
+                  blocks: [
+                    {
+                      id: 'case_0',
+                      type: 'case',
+                      data: {
+                        title: 'Case_0',
+                        inputsValues: {
+                          condition: { type: 'constant', content: true },
+                        },
+                        inputs: {
+                          type: 'object',
+                          required: ['condition'],
+                          properties: {
+                            condition: {
+                              type: 'boolean',
+                            },
+                          },
+                        },
+                      },
+                      blocks: [],
+                    },
+                    {
+                      id: 'case_1',
+                      type: 'case',
+                      data: {
+                        title: 'Case_1',
+                        inputsValues: {
+                          condition: { type: 'constant', content: true },
+                        },
+                        inputs: {
+                          type: 'object',
+                          required: ['condition'],
+                          properties: {
+                            condition: {
+                              type: 'boolean',
+                            },
+                          },
+                        },
+                      },
+                    },
+                    {
+                      id: 'case_default_1',
+                      type: 'caseDefault',
+                      data: {
+                        title: 'Default',
+                      },
+                      blocks: [
+                        {
+                          id: 'break_0',
+                          type: 'breakLoop',
+                          data: {
+                            title: 'BreakLoop',
+                          },
+                        },
+                      ],
+                    },
+                  ],
                 },
                 },
-              },
-              meta: {},
-              blocks: [],
+              ],
             },
             },
           ],
           ],
         },
         },

+ 13 - 0
apps/demo-fixed-layout/src/nodes/break-loop/form-meta.tsx

@@ -0,0 +1,13 @@
+import { FormMeta } from '@flowgram.ai/fixed-layout-editor';
+
+import { FormHeader } from '../../form-components';
+
+export const renderForm = () => (
+  <>
+    <FormHeader />
+  </>
+);
+
+export const formMeta: FormMeta = {
+  render: renderForm,
+};

+ 42 - 0
apps/demo-fixed-layout/src/nodes/break-loop/index.ts

@@ -0,0 +1,42 @@
+import { nanoid } from 'nanoid';
+
+import { FlowNodeRegistry } from '../../typings';
+import iconBreak from '../../assets/icon-break.svg';
+import { formMeta } from './form-meta';
+
+/**
+ * Break 节点用于在 loop 中根据条件终止并跳出
+ */
+export const BreakLoopNodeRegistry: FlowNodeRegistry = {
+  type: 'breakLoop',
+  extend: 'end',
+  info: {
+    icon: iconBreak,
+    description: 'Break in current Loop.',
+  },
+  meta: {
+    style: {
+      width: 240,
+    },
+  },
+  /**
+   * Render node via formMeta
+   */
+  formMeta,
+  canAdd(ctx, from) {
+    while (from.parent) {
+      if (from.parent.flowNodeType === 'loop') return true;
+      from = from.parent;
+    }
+    return false;
+  },
+  onAdd(ctx, from) {
+    return {
+      id: `break_${nanoid()}`,
+      type: 'breakLoop',
+      data: {
+        title: 'BreakLoop',
+      },
+    };
+  },
+};

+ 32 - 0
apps/demo-fixed-layout/src/nodes/case-default/form-meta.tsx

@@ -0,0 +1,32 @@
+import { FormRenderProps, FormMeta, ValidateTrigger } from '@flowgram.ai/fixed-layout-editor';
+
+import { FlowNodeJSON } from '../../typings';
+import { FormHeader, FormContent, FormInputs, FormOutputs } from '../../form-components';
+
+export const renderForm = ({ form }: FormRenderProps<FlowNodeJSON['data']>) => (
+  <>
+    <FormHeader />
+    <FormContent>
+      <FormInputs />
+      <FormOutputs />
+    </FormContent>
+  </>
+);
+
+export const formMeta: FormMeta<FlowNodeJSON['data']> = {
+  render: renderForm,
+  validateTrigger: ValidateTrigger.onChange,
+  validate: {
+    'inputsValues.*': ({ value, context, formValues, name }) => {
+      const valuePropetyKey = name.replace(/^inputsValues\./, '');
+      const required = formValues.inputs?.required || [];
+      if (
+        required.includes(valuePropetyKey) &&
+        (value === '' || value === undefined || value?.content === '')
+      ) {
+        return `${valuePropetyKey} is required`;
+      }
+      return undefined;
+    },
+  },
+};

+ 31 - 0
apps/demo-fixed-layout/src/nodes/case-default/index.ts

@@ -0,0 +1,31 @@
+import { FlowNodeRegistry } from '../../typings';
+import iconCase from '../../assets/icon-case.png';
+import { formMeta } from './form-meta';
+
+export const CaseDefaultNodeRegistry: FlowNodeRegistry = {
+  type: 'caseDefault',
+  /**
+   * 分支节点需要继承自 block
+   * Branch nodes need to inherit from 'block'
+   */
+  extend: 'case',
+  meta: {
+    copyDisable: true,
+    addDisable: true,
+    /**
+     * caseDefault 永远在最后一个分支,所以不允许拖拽排序
+     * "caseDefault" is always in the last branch, so dragging and sorting is not allowed.
+     */
+    draggable: false,
+    deleteDisable: true,
+    style: {
+      width: 240,
+    },
+  },
+  info: {
+    icon: iconCase,
+    description: 'Switch default branch',
+  },
+  canDelete: (ctx, node) => false,
+  formMeta,
+};

+ 5 - 5
apps/demo-fixed-layout/src/nodes/case/index.ts

@@ -1,7 +1,7 @@
 import { nanoid } from 'nanoid';
 import { nanoid } from 'nanoid';
 
 
 import { FlowNodeRegistry } from '../../typings';
 import { FlowNodeRegistry } from '../../typings';
-import iconIf from '../../assets/icon-if.png';
+import iconCase from '../../assets/icon-case.png';
 import { formMeta } from './form-meta';
 import { formMeta } from './form-meta';
 
 
 let id = 2;
 let id = 2;
@@ -14,19 +14,19 @@ export const CaseNodeRegistry: FlowNodeRegistry = {
   extend: 'block',
   extend: 'block',
   meta: {
   meta: {
     copyDisable: true,
     copyDisable: true,
+    addDisable: true,
   },
   },
   info: {
   info: {
-    icon: iconIf,
+    icon: iconCase,
     description: 'Execute the branch when the condition is met.',
     description: 'Execute the branch when the condition is met.',
   },
   },
-  canAdd: () => false,
   canDelete: (ctx, node) => node.parent!.blocks.length >= 3,
   canDelete: (ctx, node) => node.parent!.blocks.length >= 3,
   onAdd(ctx, from) {
   onAdd(ctx, from) {
     return {
     return {
-      id: `if_${nanoid(5)}`,
+      id: `Case_${nanoid(5)}`,
       type: 'case',
       type: 'case',
       data: {
       data: {
-        title: `If_${id++}`,
+        title: `Case_${id++}`,
         inputs: {
         inputs: {
           type: 'object',
           type: 'object',
           required: ['condition'],
           required: ['condition'],

+ 3 - 2
apps/demo-fixed-layout/src/nodes/catch-block/index.ts

@@ -1,7 +1,7 @@
 import { nanoid } from 'nanoid';
 import { nanoid } from 'nanoid';
 
 
 import { FlowNodeRegistry } from '../../typings';
 import { FlowNodeRegistry } from '../../typings';
-import iconIf from '../../assets/icon-if.png';
+import iconCase from '../../assets/icon-case.png';
 import { formMeta } from './form-meta';
 import { formMeta } from './form-meta';
 
 
 let id = 3;
 let id = 3;
@@ -9,9 +9,10 @@ export const CatchBlockNodeRegistry: FlowNodeRegistry = {
   type: 'catchBlock',
   type: 'catchBlock',
   meta: {
   meta: {
     copyDisable: true,
     copyDisable: true,
+    addDisable: true,
   },
   },
   info: {
   info: {
-    icon: iconIf,
+    icon: iconCase,
     description: 'Execute the catch branch when the condition is met.',
     description: 'Execute the catch branch when the condition is met.',
   },
   },
   canAdd: () => false,
   canAdd: () => false,

+ 28 - 0
apps/demo-fixed-layout/src/nodes/if-block/form-meta.tsx

@@ -0,0 +1,28 @@
+import { FormRenderProps, FormMeta, Field } from '@flowgram.ai/fixed-layout-editor';
+
+import { FlowNodeJSON } from '../../typings';
+import { useNodeRenderContext } from '../../hooks';
+
+export const renderForm = (props: FormRenderProps<FlowNodeJSON['data']>) => {
+  const { node } = useNodeRenderContext();
+  return (
+    <div
+      style={{
+        width: '100%',
+        height: '100%',
+        backgroundColor: node.index === 0 ? 'green' : 'red',
+        color: 'white',
+        display: 'flex',
+        pointerEvents: 'none',
+        alignItems: 'center',
+        justifyContent: 'center',
+      }}
+    >
+      <Field name="title">{({ field }) => <>{field.value}</>}</Field>
+    </div>
+  );
+};
+
+export const formMeta: FormMeta<FlowNodeJSON['data']> = {
+  render: renderForm,
+};

+ 30 - 0
apps/demo-fixed-layout/src/nodes/if-block/index.ts

@@ -0,0 +1,30 @@
+import { FlowNodeRegistry } from '../../typings';
+import iconIf from '../../assets/icon-if.png';
+import { formMeta } from './form-meta';
+
+export const IFBlockNodeRegistry: FlowNodeRegistry = {
+  type: 'ifBlock',
+  /**
+   * 分支节点需要继承自 block
+   * Branch nodes need to inherit from 'block'
+   */
+  extend: 'block',
+  meta: {
+    copyDisable: true,
+    addDisable: true,
+    sidebarDisable: true,
+    defaultExpanded: false,
+    style: {
+      width: 66,
+      height: 20,
+      borderRadius: 4,
+    },
+  },
+  info: {
+    icon: iconIf,
+    description: '',
+  },
+  canAdd: () => false,
+  canDelete: (ctx, node) => false,
+  formMeta,
+};

+ 57 - 0
apps/demo-fixed-layout/src/nodes/if/index.ts

@@ -0,0 +1,57 @@
+import { nanoid } from 'nanoid';
+import { FlowNodeSplitType } from '@flowgram.ai/fixed-layout-editor';
+
+import { defaultFormMeta } from '../default-form-meta';
+import { FlowNodeRegistry } from '../../typings';
+import iconIf from '../../assets/icon-if.png';
+
+export const IFNodeRegistry: FlowNodeRegistry = {
+  extend: FlowNodeSplitType.STATIC_SPLIT,
+  type: 'if',
+  info: {
+    icon: iconIf,
+    description: 'Only the corresponding branch will be executed if the set conditions are met.',
+  },
+  meta: {
+    expandable: false, // disable expanded
+  },
+  formMeta: defaultFormMeta,
+  onAdd() {
+    return {
+      id: `if_${nanoid(5)}`,
+      type: 'if',
+      data: {
+        title: 'If',
+        inputsValues: {
+          condition: { type: 'constant', content: true },
+        },
+        inputs: {
+          type: 'object',
+          required: ['condition'],
+          properties: {
+            condition: {
+              type: 'boolean',
+            },
+          },
+        },
+      },
+      blocks: [
+        {
+          id: nanoid(5),
+          type: 'ifBlock',
+          data: {
+            title: 'true',
+          },
+          blocks: [],
+        },
+        {
+          id: nanoid(5),
+          type: 'ifBlock',
+          data: {
+            title: 'false',
+          },
+        },
+      ],
+    };
+  },
+};

+ 10 - 2
apps/demo-fixed-layout/src/nodes/index.ts

@@ -1,20 +1,28 @@
 import { type FlowNodeRegistry } from '../typings';
 import { type FlowNodeRegistry } from '../typings';
 import { TryCatchNodeRegistry } from './trycatch';
 import { TryCatchNodeRegistry } from './trycatch';
+import { SwitchNodeRegistry } from './switch';
 import { StartNodeRegistry } from './start';
 import { StartNodeRegistry } from './start';
 import { LoopNodeRegistry } from './loop';
 import { LoopNodeRegistry } from './loop';
 import { LLMNodeRegistry } from './llm';
 import { LLMNodeRegistry } from './llm';
+import { IFBlockNodeRegistry } from './if-block';
+import { IFNodeRegistry } from './if';
 import { EndNodeRegistry } from './end';
 import { EndNodeRegistry } from './end';
-import { ConditionNodeRegistry } from './condition';
 import { CatchBlockNodeRegistry } from './catch-block';
 import { CatchBlockNodeRegistry } from './catch-block';
+import { CaseDefaultNodeRegistry } from './case-default';
 import { CaseNodeRegistry } from './case';
 import { CaseNodeRegistry } from './case';
+import { BreakLoopNodeRegistry } from './break-loop';
 
 
 export const FlowNodeRegistries: FlowNodeRegistry[] = [
 export const FlowNodeRegistries: FlowNodeRegistry[] = [
   StartNodeRegistry,
   StartNodeRegistry,
   EndNodeRegistry,
   EndNodeRegistry,
-  ConditionNodeRegistry,
+  SwitchNodeRegistry,
   LLMNodeRegistry,
   LLMNodeRegistry,
   LoopNodeRegistry,
   LoopNodeRegistry,
   CaseNodeRegistry,
   CaseNodeRegistry,
   TryCatchNodeRegistry,
   TryCatchNodeRegistry,
   CatchBlockNodeRegistry,
   CatchBlockNodeRegistry,
+  IFNodeRegistry,
+  IFBlockNodeRegistry,
+  BreakLoopNodeRegistry,
+  CaseDefaultNodeRegistry,
 ];
 ];

+ 1 - 6
apps/demo-fixed-layout/src/nodes/start/index.ts

@@ -10,6 +10,7 @@ export const StartNodeRegistry: FlowNodeRegistry = {
     selectable: false, // Start node cannot select
     selectable: false, // Start node cannot select
     copyDisable: true, // Start node cannot copy
     copyDisable: true, // Start node cannot copy
     expandable: false, // disable expanded
     expandable: false, // disable expanded
+    addDisable: true, // Start Node cannot be added
   },
   },
   info: {
   info: {
     icon: iconStart,
     icon: iconStart,
@@ -20,10 +21,4 @@ export const StartNodeRegistry: FlowNodeRegistry = {
    * Render node via formMeta
    * Render node via formMeta
    */
    */
   formMeta,
   formMeta,
-  /**
-   * Start Node cannot be added
-   */
-  canAdd() {
-    return false;
-  },
 };
 };

+ 15 - 7
apps/demo-fixed-layout/src/nodes/condition/index.ts → apps/demo-fixed-layout/src/nodes/switch/index.ts

@@ -5,9 +5,9 @@ import { defaultFormMeta } from '../default-form-meta';
 import { FlowNodeRegistry } from '../../typings';
 import { FlowNodeRegistry } from '../../typings';
 import iconCondition from '../../assets/icon-condition.svg';
 import iconCondition from '../../assets/icon-condition.svg';
 
 
-export const ConditionNodeRegistry: FlowNodeRegistry = {
+export const SwitchNodeRegistry: FlowNodeRegistry = {
   extend: FlowNodeSplitType.DYNAMIC_SPLIT,
   extend: FlowNodeSplitType.DYNAMIC_SPLIT,
-  type: 'condition',
+  type: 'switch',
   info: {
   info: {
     icon: iconCondition,
     icon: iconCondition,
     description:
     description:
@@ -19,17 +19,17 @@ export const ConditionNodeRegistry: FlowNodeRegistry = {
   formMeta: defaultFormMeta,
   formMeta: defaultFormMeta,
   onAdd() {
   onAdd() {
     return {
     return {
-      id: `condition_${nanoid(5)}`,
-      type: 'condition',
+      id: `switch_${nanoid(5)}`,
+      type: 'switch',
       data: {
       data: {
-        title: 'Condition',
+        title: 'Switch',
       },
       },
       blocks: [
       blocks: [
         {
         {
           id: nanoid(5),
           id: nanoid(5),
           type: 'case',
           type: 'case',
           data: {
           data: {
-            title: 'If_0',
+            title: 'Case_0',
             inputsValues: {
             inputsValues: {
               condition: { type: 'constant', content: '' },
               condition: { type: 'constant', content: '' },
             },
             },
@@ -49,7 +49,7 @@ export const ConditionNodeRegistry: FlowNodeRegistry = {
           id: nanoid(5),
           id: nanoid(5),
           type: 'case',
           type: 'case',
           data: {
           data: {
-            title: 'If_1',
+            title: 'Case_1',
             inputsValues: {
             inputsValues: {
               condition: { type: 'constant', content: '' },
               condition: { type: 'constant', content: '' },
             },
             },
@@ -64,6 +64,14 @@ export const ConditionNodeRegistry: FlowNodeRegistry = {
             },
             },
           },
           },
         },
         },
+        {
+          id: nanoid(5),
+          type: 'caseDefault',
+          data: {
+            title: 'Default',
+          },
+          blocks: [],
+        },
       ],
       ],
     };
     };
   },
   },

+ 3 - 2
apps/demo-fixed-layout/src/typings/node.ts

@@ -43,14 +43,15 @@ export interface FlowNodeJSON extends FlowNodeJSONDefault {
  * 你可以自定义节点的meta
  * 你可以自定义节点的meta
  */
  */
 export interface FlowNodeMeta extends FlowNodeMetaDefault {
 export interface FlowNodeMeta extends FlowNodeMetaDefault {
-  disableSideBar?: boolean;
+  sidebarDisable?: boolean;
+  style?: React.CSSProperties;
 }
 }
 /**
 /**
  * You can customize your own node registry
  * You can customize your own node registry
  * 你可以自定义节点的注册器
  * 你可以自定义节点的注册器
  */
  */
 export interface FlowNodeRegistry extends FlowNodeRegistryDefault {
 export interface FlowNodeRegistry extends FlowNodeRegistryDefault {
-  meta: FlowNodeMeta;
+  meta?: FlowNodeMeta;
   info: {
   info: {
     icon: string;
     icon: string;
     description: string;
     description: string;

+ 8 - 7
apps/demo-free-layout/src/components/sidebar/sidebar-renderer.tsx

@@ -63,8 +63,8 @@ export const SidebarRenderer = () => {
     if (!node) {
     if (!node) {
       return false;
       return false;
     }
     }
-    const { disableSideBar = false } = node.getNodeMeta<FlowNodeMeta>();
-    return !disableSideBar;
+    const { sidebarDisable = false } = node.getNodeMeta<FlowNodeMeta>();
+    return !sidebarDisable;
   }, [node]);
   }, [node]);
 
 
   if (playground.config.readonly) {
   if (playground.config.readonly) {
@@ -73,11 +73,12 @@ export const SidebarRenderer = () => {
   /**
   /**
    * Add "key" to rerender the sidebar when the node changes
    * Add "key" to rerender the sidebar when the node changes
    */
    */
-  const content = node ? (
-    <PlaygroundEntityContext.Provider key={node.id} value={node}>
-      <SidebarNodeRenderer node={node} />
-    </PlaygroundEntityContext.Provider>
-  ) : null;
+  const content =
+    node && visible ? (
+      <PlaygroundEntityContext.Provider key={node.id} value={node}>
+        <SidebarNodeRenderer node={node} />
+      </PlaygroundEntityContext.Provider>
+    ) : null;
 
 
   return (
   return (
     <SideSheet mask={false} visible={visible} onCancel={handleClose}>
     <SideSheet mask={false} visible={visible} onCancel={handleClose}>

+ 1 - 1
apps/demo-free-layout/src/hooks/use-editor-props.tsx

@@ -6,7 +6,7 @@ import { createMinimapPlugin } from '@flowgram.ai/minimap-plugin';
 import { createFreeSnapPlugin } from '@flowgram.ai/free-snap-plugin';
 import { createFreeSnapPlugin } from '@flowgram.ai/free-snap-plugin';
 import { createFreeNodePanelPlugin } from '@flowgram.ai/free-node-panel-plugin';
 import { createFreeNodePanelPlugin } from '@flowgram.ai/free-node-panel-plugin';
 import { createFreeLinesPlugin } from '@flowgram.ai/free-lines-plugin';
 import { createFreeLinesPlugin } from '@flowgram.ai/free-lines-plugin';
-import { FreeLayoutProps, WorkflowNodeLinesData } from '@flowgram.ai/free-layout-editor';
+import { FreeLayoutProps, WorkflowNodeLinesData, Form } from '@flowgram.ai/free-layout-editor';
 import { createFreeGroupPlugin } from '@flowgram.ai/free-group-plugin';
 import { createFreeGroupPlugin } from '@flowgram.ai/free-group-plugin';
 import { createContainerNodePlugin } from '@flowgram.ai/free-container-plugin';
 import { createContainerNodePlugin } from '@flowgram.ai/free-container-plugin';
 
 

+ 1 - 1
apps/demo-free-layout/src/nodes/comment/index.tsx

@@ -4,7 +4,7 @@ import { FlowNodeRegistry } from '../../typings';
 export const CommentNodeRegistry: FlowNodeRegistry = {
 export const CommentNodeRegistry: FlowNodeRegistry = {
   type: WorkflowNodeType.Comment,
   type: WorkflowNodeType.Comment,
   meta: {
   meta: {
-    disableSideBar: true,
+    sidebarDisable: true,
     defaultPorts: [],
     defaultPorts: [],
     renderKey: WorkflowNodeType.Comment,
     renderKey: WorkflowNodeType.Comment,
     size: {
     size: {

+ 0 - 2
apps/demo-free-layout/src/nodes/loop/loop-form-render.tsx

@@ -1,5 +1,4 @@
 import { FormRenderProps, FlowNodeJSON, Field } from '@flowgram.ai/free-layout-editor';
 import { FormRenderProps, FlowNodeJSON, Field } from '@flowgram.ai/free-layout-editor';
-import { SubCanvasRender } from '@flowgram.ai/free-container-plugin';
 import { BatchVariableSelector, IFlowRefValue } from '@flowgram.ai/form-materials';
 import { BatchVariableSelector, IFlowRefValue } from '@flowgram.ai/form-materials';
 
 
 import { useIsSidebar, useNodeRenderContext } from '../../hooks';
 import { useIsSidebar, useNodeRenderContext } from '../../hooks';
@@ -48,7 +47,6 @@ export const LoopFormRender = ({ form }: FormRenderProps<LoopNodeJSON>) => {
       <FormHeader />
       <FormHeader />
       <FormContent>
       <FormContent>
         {batchFor}
         {batchFor}
-        <SubCanvasRender />
         <FormOutputs />
         <FormOutputs />
       </FormContent>
       </FormContent>
     </>
     </>

+ 1 - 1
apps/demo-free-layout/src/typings/node.ts

@@ -44,7 +44,7 @@ export interface FlowNodeJSON extends FlowNodeJSONDefault {
  * 你可以自定义节点的meta
  * 你可以自定义节点的meta
  */
  */
 export interface FlowNodeMeta extends WorkflowNodeMeta {
 export interface FlowNodeMeta extends WorkflowNodeMeta {
-  disableSideBar?: boolean;
+  sidebarDisable?: boolean;
 }
 }
 
 
 /**
 /**

+ 1 - 1
e2e/fixed-layout/tests/models/index.ts

@@ -25,7 +25,7 @@ class FixedLayoutModel {
   }
   }
 
 
   public async isConditionNodeExist() {
   public async isConditionNodeExist() {
-    return await this.page.locator('[data-node-id="$blockIcon$condition_0"]').count();
+    return await this.page.locator('[data-node-id="$blockIcon$switch_0"]').count();
   }
   }
 
 
   public async insert(searchText: string, { from, to }: InsertEdgeOptions) {
   public async insert(searchText: string, { from, to }: InsertEdgeOptions) {

+ 3 - 3
e2e/fixed-layout/tests/node.spec.ts

@@ -21,11 +21,11 @@ test.describe('node operations', () => {
 
 
   test('add node', async () => {
   test('add node', async () => {
     const prevCount = await editorPage.getNodeCount();
     const prevCount = await editorPage.getNodeCount();
-    await editorPage.insert('condition', {
+    await editorPage.insert('switch', {
       from: 'llm_0',
       from: 'llm_0',
-      to: 'loop_0',
+      to: 'if_0',
     });
     });
     const defaultNodeCount = await editorPage.getNodeCount();
     const defaultNodeCount = await editorPage.getNodeCount();
-    expect(defaultNodeCount).toEqual(prevCount + 3);
+    expect(defaultNodeCount).toEqual(prevCount + 4);
   });
   });
 });
 });

+ 1 - 1
packages/canvas-engine/document/src/entities/flow-node-entity.ts

@@ -174,7 +174,7 @@ export class FlowNodeEntity extends Entity<FlowNodeEntityConfig> {
     return this.document.renderTree.getParent(this);
     return this.document.renderTree.getParent(this);
   }
   }
 
 
-  getNodeRegistry<M extends FlowNodeRegistry = FlowNodeRegistry>(): M {
+  getNodeRegistry<M extends FlowNodeRegistry = FlowNodeRegistry & { meta: FlowNodeMeta }>(): M {
     if (this._registerCache) return this._registerCache as M;
     if (this._registerCache) return this._registerCache as M;
     this._registerCache = this.document.getNodeRegistry(this.flowNodeType, this.originParent);
     this._registerCache = this.document.getNodeRegistry(this.flowNodeType, this.originParent);
     return this._registerCache as M;
     return this._registerCache as M;