diff --git a/apps/demo-fixed-layout-simple/src/components/flow-select.tsx b/apps/demo-fixed-layout-simple/src/components/flow-select.tsx index e55512cd..834e8bc0 100644 --- a/apps/demo-fixed-layout-simple/src/components/flow-select.tsx +++ b/apps/demo-fixed-layout-simple/src/components/flow-select.tsx @@ -15,7 +15,7 @@ export function FlowSelect() { clientContext.history.stop(); // Stop redo/undo clientContext.history.clear(); // Clear redo/undo clientContext.document.fromJSON(targetDemoJSON); // Reload Data - console.log(clientContext.document.toString()); // Print the document tree + console.log(clientContext.document.toString(true)); // Print the document tree clientContext.history.start(); // Restart redo/undo clientContext.document.setLayout( targetDemoJSON.defaultLayout || FlowLayoutDefault.VERTICAL_FIXED_LAYOUT diff --git a/apps/demo-fixed-layout/src/components/branch-adder/index.tsx b/apps/demo-fixed-layout/src/components/branch-adder/index.tsx index b0ac6db0..ec2abcf1 100644 --- a/apps/demo-fixed-layout/src/components/branch-adder/index.tsx +++ b/apps/demo-fixed-layout/src/components/branch-adder/index.tsx @@ -1,7 +1,8 @@ import { type FlowNodeEntity, useClientContext } from '@flowgram.ai/fixed-layout-editor'; import { IconPlus } from '@douyinfe/semi-icons'; -import { BlockNodeRegistry } from '../../nodes/block'; +import { CatchBlockNodeRegistry } from '../../nodes/catch-block'; +import { CaseNodeRegistry } from '../../nodes/case'; import { Container } from './styles'; interface PropsType { @@ -17,7 +18,12 @@ export default function BranchAdder(props: PropsType) { const { isVertical } = node; function addBranch() { - const block = operation.addBlock(node, BlockNodeRegistry.onAdd!(ctx, node)); + const block = operation.addBlock( + node, + node.flowNodeType === 'condition' + ? CaseNodeRegistry.onAdd!(ctx, node) + : CatchBlockNodeRegistry.onAdd!(ctx, node) + ); setTimeout(() => { playground.scrollToView({ diff --git a/apps/demo-fixed-layout/src/hooks/use-editor-props.ts b/apps/demo-fixed-layout/src/hooks/use-editor-props.ts index f09a8003..1e0b0610 100644 --- a/apps/demo-fixed-layout/src/hooks/use-editor-props.ts +++ b/apps/demo-fixed-layout/src/hooks/use-editor-props.ts @@ -1,5 +1,6 @@ import { useMemo } from 'react'; +import { debounce } from 'lodash-es'; import { createMinimapPlugin } from '@flowgram.ai/minimap-plugin'; import { createGroupPlugin } from '@flowgram.ai/group-plugin'; import { defaultFixedSemiMaterials } from '@flowgram.ai/fixed-semi-materials'; @@ -139,10 +140,10 @@ export function useEditorProps( history: { enable: true, enableChangeNode: true, // Listen Node engine data change - onApply(ctx, opt) { + onApply: debounce((ctx, opt) => { // Listen change to trigger auto save - // console.log('auto save: ', ctx.document.toJSON(), opt); - }, + console.log('auto save: ', ctx.document.toJSON()); + }, 100), }, /** * Node engine enable, you can configure formMeta in the FlowNodeRegistry @@ -201,7 +202,7 @@ export function useEditorProps( setTimeout(() => { ctx.playground.config.fitView(ctx.document.root.bounds.pad(30)); }, 10); - console.log(ctx.document.toString()); // Get the document tree + console.log(ctx.document.toString(true)); // Get the document tree }, /** * Playground dispose diff --git a/apps/demo-fixed-layout/src/initial-data.ts b/apps/demo-fixed-layout/src/initial-data.ts index a3868c14..21d6ce3f 100644 --- a/apps/demo-fixed-layout/src/initial-data.ts +++ b/apps/demo-fixed-layout/src/initial-data.ts @@ -106,8 +106,8 @@ export const initialData: FlowDocumentJSON = { }, blocks: [ { - id: 'branch_0', - type: 'block', + id: 'case_0', + type: 'case', data: { title: 'If_0', inputsValues: { @@ -126,8 +126,8 @@ export const initialData: FlowDocumentJSON = { blocks: [], }, { - id: 'branch_1', - type: 'block', + id: 'case_1', + type: 'case', data: { title: 'If_1', inputsValues: { diff --git a/apps/demo-fixed-layout/src/nodes/block/form-meta.tsx b/apps/demo-fixed-layout/src/nodes/case/form-meta.tsx similarity index 100% rename from apps/demo-fixed-layout/src/nodes/block/form-meta.tsx rename to apps/demo-fixed-layout/src/nodes/case/form-meta.tsx diff --git a/apps/demo-fixed-layout/src/nodes/block/index.ts b/apps/demo-fixed-layout/src/nodes/case/index.ts similarity index 62% rename from apps/demo-fixed-layout/src/nodes/block/index.ts rename to apps/demo-fixed-layout/src/nodes/case/index.ts index ea420384..74367cf8 100644 --- a/apps/demo-fixed-layout/src/nodes/block/index.ts +++ b/apps/demo-fixed-layout/src/nodes/case/index.ts @@ -5,8 +5,13 @@ import iconIf from '../../assets/icon-if.png'; import { formMeta } from './form-meta'; let id = 2; -export const BlockNodeRegistry: FlowNodeRegistry = { - type: 'block', +export const CaseNodeRegistry: FlowNodeRegistry = { + type: 'case', + /** + * 分支节点需要继承自 block + * Branch nodes need to inherit from 'block' + */ + extend: 'block', meta: { copyDisable: true, }, @@ -15,19 +20,13 @@ export const BlockNodeRegistry: FlowNodeRegistry = { description: 'Execute the branch when the condition is met.', }, canAdd: () => false, - canDelete: (ctx, node) => { - if (node.originParent!.flowNodeType === 'tryCatch') { - return node.parent!.blocks.length >= 2; - } - return node.parent!.blocks.length >= 3; - }, + canDelete: (ctx, node) => node.parent!.blocks.length >= 3, onAdd(ctx, from) { - const isTryCatch = from.flowNodeType === 'tryCatch'; return { id: `if_${nanoid(5)}`, - type: isTryCatch ? 'catchBlock' : 'block', + type: 'case', data: { - title: isTryCatch ? `Catch Block ${id++}` : `If_${id++}`, + title: `If_${id++}`, inputs: { type: 'object', required: ['condition'], diff --git a/apps/demo-fixed-layout/src/nodes/catch-block/form-meta.tsx b/apps/demo-fixed-layout/src/nodes/catch-block/form-meta.tsx new file mode 100644 index 00000000..d883231a --- /dev/null +++ b/apps/demo-fixed-layout/src/nodes/catch-block/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) => ( + <> + + + + + + +); + +export const formMeta: FormMeta = { + 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; + }, + }, +}; diff --git a/apps/demo-fixed-layout/src/nodes/catch-block/index.ts b/apps/demo-fixed-layout/src/nodes/catch-block/index.ts new file mode 100644 index 00000000..ff832b5c --- /dev/null +++ b/apps/demo-fixed-layout/src/nodes/catch-block/index.ts @@ -0,0 +1,41 @@ +import { nanoid } from 'nanoid'; + +import { FlowNodeRegistry } from '../../typings'; +import iconIf from '../../assets/icon-if.png'; +import { formMeta } from './form-meta'; + +let id = 3; +export const CatchBlockNodeRegistry: FlowNodeRegistry = { + type: 'catchBlock', + meta: { + copyDisable: true, + }, + info: { + icon: iconIf, + description: 'Execute the catch branch when the condition is met.', + }, + canAdd: () => false, + canDelete: (ctx, node) => node.parent!.blocks.length >= 2, + onAdd(ctx, from) { + return { + id: `Catch_${nanoid(5)}`, + type: 'catchblock', + data: { + title: `Catch Block ${id++}`, + inputs: { + type: 'object', + required: ['condition'], + inputsValues: { + condition: '', + }, + properties: { + condition: { + type: 'string', + }, + }, + }, + }, + }; + }, + formMeta, +}; diff --git a/apps/demo-fixed-layout/src/nodes/condition/index.ts b/apps/demo-fixed-layout/src/nodes/condition/index.ts index db824840..58d36a06 100644 --- a/apps/demo-fixed-layout/src/nodes/condition/index.ts +++ b/apps/demo-fixed-layout/src/nodes/condition/index.ts @@ -27,7 +27,7 @@ export const ConditionNodeRegistry: FlowNodeRegistry = { blocks: [ { id: nanoid(5), - type: 'block', + type: 'case', data: { title: 'If_0', inputsValues: { @@ -47,7 +47,7 @@ export const ConditionNodeRegistry: FlowNodeRegistry = { }, { id: nanoid(5), - type: 'block', + type: 'case', data: { title: 'If_1', inputsValues: { diff --git a/apps/demo-fixed-layout/src/nodes/index.ts b/apps/demo-fixed-layout/src/nodes/index.ts index c50ef395..5a82c9fc 100644 --- a/apps/demo-fixed-layout/src/nodes/index.ts +++ b/apps/demo-fixed-layout/src/nodes/index.ts @@ -5,7 +5,8 @@ import { LoopNodeRegistry } from './loop'; import { LLMNodeRegistry } from './llm'; import { EndNodeRegistry } from './end'; import { ConditionNodeRegistry } from './condition'; -import { BlockNodeRegistry } from './block'; +import { CatchBlockNodeRegistry } from './catch-block'; +import { CaseNodeRegistry } from './case'; export const FlowNodeRegistries: FlowNodeRegistry[] = [ StartNodeRegistry, @@ -13,6 +14,7 @@ export const FlowNodeRegistries: FlowNodeRegistry[] = [ ConditionNodeRegistry, LLMNodeRegistry, LoopNodeRegistry, - BlockNodeRegistry, + CaseNodeRegistry, TryCatchNodeRegistry, + CatchBlockNodeRegistry, ]; diff --git a/apps/docs/src/zh/guide/question.mdx b/apps/docs/src/zh/guide/question.mdx index 98b0cfad..1338853f 100644 --- a/apps/docs/src/zh/guide/question.mdx +++ b/apps/docs/src/zh/guide/question.mdx @@ -1,3 +1,14 @@ # 常见问题 -todo + +## 运行报报错 + + +## 如何修改节点的数据 + + +## 是否支持 vue + + +## + diff --git a/packages/canvas-engine/document/src/flow-document.ts b/packages/canvas-engine/document/src/flow-document.ts index a5b31b0d..1076d153 100644 --- a/packages/canvas-engine/document/src/flow-document.ts +++ b/packages/canvas-engine/document/src/flow-document.ts @@ -581,8 +581,8 @@ export class FlowDocument implements Disposable { return this.entityManager.getEntities(FlowNodeEntity); } - toString(): string { - return this.originTree.toString(); + toString(showType?: boolean): string { + return this.originTree.toString(showType); } /** diff --git a/packages/canvas-engine/document/src/flow-virtual-tree.ts b/packages/canvas-engine/document/src/flow-virtual-tree.ts index ed4d2c8c..63f3949a 100644 --- a/packages/canvas-engine/document/src/flow-virtual-tree.ts +++ b/packages/canvas-engine/document/src/flow-virtual-tree.ts @@ -1,10 +1,14 @@ import { type Disposable, Emitter } from '@flowgram.ai/utils'; +import { type FlowNodeType } from './typings'; + /** * 存储节点的 tree 结构信息 * 策略是 "重修改轻查询",即修改时候做的事情更多,查询都通过指针来操作 */ -export class FlowVirtualTree implements Disposable { +export class FlowVirtualTree + implements Disposable +{ protected onTreeChangeEmitter = new Emitter(); /** @@ -208,13 +212,17 @@ export class FlowVirtualTree implements Disposable { return this.map.size; } - toString(): string { + toString(showType?: boolean): string { const ret: string[] = []; this.traverse((node, depth) => { if (depth === 0) { ret.push(node.id); } else { - ret.push(`|${new Array(depth).fill('--').join('')} ${node.id}`); + ret.push( + `|${new Array(depth).fill('--').join('')} ${ + showType ? `${node.flowNodeType}(${node.id})` : node.id + }` + ); } }); return `${ret.join('\n')}`; diff --git a/packages/canvas-engine/document/src/typings/flow-node-register.ts b/packages/canvas-engine/document/src/typings/flow-node-register.ts index 584de43d..a61071ee 100644 --- a/packages/canvas-engine/document/src/typings/flow-node-register.ts +++ b/packages/canvas-engine/document/src/typings/flow-node-register.ts @@ -261,6 +261,7 @@ export interface FlowNodeRegistry { extendChildRegistries?: FlowNodeRegistry[]; /** + * @deprecated * 自定义子节点添加逻辑 * @param node 节点 * @param json 添加的节点 JSON diff --git a/packages/canvas-engine/fixed-layout-core/src/activities/block.ts b/packages/canvas-engine/fixed-layout-core/src/activities/block.ts index b271cc36..95825007 100644 --- a/packages/canvas-engine/fixed-layout-core/src/activities/block.ts +++ b/packages/canvas-engine/fixed-layout-core/src/activities/block.ts @@ -171,6 +171,9 @@ export const BlockRegistry: FlowNodeRegistry = { return [...draggingLabel]; }, + /** + * @depreacted + */ addChild(node, json, options = {}) { const { index } = options; const document = node.document; diff --git a/packages/canvas-engine/fixed-layout-core/src/activities/dynamic-split.ts b/packages/canvas-engine/fixed-layout-core/src/activities/dynamic-split.ts index 65542482..80d10356 100644 --- a/packages/canvas-engine/fixed-layout-core/src/activities/dynamic-split.ts +++ b/packages/canvas-engine/fixed-layout-core/src/activities/dynamic-split.ts @@ -68,6 +68,9 @@ export const DynamicSplitRegistry: FlowNodeRegistry = { }; }, + /** + * @depreacted + */ addChild(node, json, options = {}) { const { index } = options; const document = node.document; diff --git a/packages/canvas-engine/fixed-layout-core/src/activities/loop.ts b/packages/canvas-engine/fixed-layout-core/src/activities/loop.ts index 3e86804c..3db897b6 100644 --- a/packages/canvas-engine/fixed-layout-core/src/activities/loop.ts +++ b/packages/canvas-engine/fixed-layout-core/src/activities/loop.ts @@ -177,6 +177,9 @@ export const LoopRegistry: FlowNodeRegistry = { LoopInlineBlocksNodeRegistry, ], + /** + * @depreacted + */ addChild(node, json, options = {}) { const { index } = options; const document = node.document; diff --git a/packages/canvas-engine/fixed-layout-core/src/activities/try-catch.ts b/packages/canvas-engine/fixed-layout-core/src/activities/try-catch.ts index 7e0995cc..ecd2d2fe 100644 --- a/packages/canvas-engine/fixed-layout-core/src/activities/try-catch.ts +++ b/packages/canvas-engine/fixed-layout-core/src/activities/try-catch.ts @@ -60,7 +60,7 @@ export const TryCatchRegistry: FlowNodeRegistry = { }); const tryBlockNode = document.addNode({ id: tryBlock.id, - type: TryCatchTypeEnum.TRY_BLOCK, + type: tryBlock.type || TryCatchTypeEnum.TRY_BLOCK, originParent: node, parent: mainBlockNode, data: tryBlock.data, @@ -109,7 +109,7 @@ export const TryCatchRegistry: FlowNodeRegistry = { const parent = node.document.getNode(`$catchInlineBlocks$${node.id}`); const block = node.document.addNode({ id: blockData.id, - type: TryCatchTypeEnum.CATCH_BLOCK, + type: node.type || TryCatchTypeEnum.CATCH_BLOCK, originParent: node, parent, data: blockData.data,