feat(demo-fixed-layout): add case node for condition branches (#302)

* refactor: FlowNodeRegistry.addChild depreacted

* feat: FlowDocument.toString(true) support showType

* feat(demo-fixed-layout): add case node and catch-block node
This commit is contained in:
xiamidaxia 2025-06-03 15:54:45 +08:00 committed by GitHub
parent b477181502
commit f0d9c5062c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 144 additions and 34 deletions

View File

@ -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

View File

@ -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({

View File

@ -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

View File

@ -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: {

View File

@ -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'],

View File

@ -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;
},
},
};

View File

@ -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,
};

View File

@ -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: {

View File

@ -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,
];

View File

@ -1,3 +1,14 @@
# 常见问题
todo
## 运行报报错
## 如何修改节点的数据
## 是否支持 vue
##

View File

@ -581,8 +581,8 @@ export class FlowDocument<T = FlowDocumentJSON> implements Disposable {
return this.entityManager.getEntities(FlowNodeEntity);
}
toString(): string {
return this.originTree.toString();
toString(showType?: boolean): string {
return this.originTree.toString(showType);
}
/**

View File

@ -1,10 +1,14 @@
import { type Disposable, Emitter } from '@flowgram.ai/utils';
import { type FlowNodeType } from './typings';
/**
* tree
* "重修改轻查询"
*/
export class FlowVirtualTree<T extends { id: string }> implements Disposable {
export class FlowVirtualTree<T extends { id: string; flowNodeType?: FlowNodeType }>
implements Disposable
{
protected onTreeChangeEmitter = new Emitter<void>();
/**
@ -208,13 +212,17 @@ export class FlowVirtualTree<T extends { id: string }> 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')}`;

View File

@ -261,6 +261,7 @@ export interface FlowNodeRegistry<M extends FlowNodeMeta = FlowNodeMeta> {
extendChildRegistries?: FlowNodeRegistry[];
/**
* @deprecated
*
* @param node
* @param json JSON

View File

@ -171,6 +171,9 @@ export const BlockRegistry: FlowNodeRegistry = {
return [...draggingLabel];
},
/**
* @depreacted
*/
addChild(node, json, options = {}) {
const { index } = options;
const document = node.document;

View File

@ -68,6 +68,9 @@ export const DynamicSplitRegistry: FlowNodeRegistry = {
};
},
/**
* @depreacted
*/
addChild(node, json, options = {}) {
const { index } = options;
const document = node.document;

View File

@ -177,6 +177,9 @@ export const LoopRegistry: FlowNodeRegistry = {
LoopInlineBlocksNodeRegistry,
],
/**
* @depreacted
*/
addChild(node, json, options = {}) {
const { index } = options;
const document = node.document;

View File

@ -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,