mirror of
https://gitee.com/ByteDance/flowgram.ai.git
synced 2025-07-07 17:43:29 +08:00
fix: flowDocument fromJSON will reload the node json data (#240)
* fix: flowDocument fromJSON update node data * feat: getNodeForm add updateFormValues
This commit is contained in:
parent
d9e805b167
commit
766fdc1597
@ -13,7 +13,7 @@ doc.toJSON() // TODO This is the old version data, not yet optimized. Business l
|
||||
|
||||
doc.addFromNode(targetNode, json) // Insert after the specified node
|
||||
|
||||
doc.onNodeCreate(({ node, data}) => {}) // Listen to node creation, data is the JSON data at creation time
|
||||
doc.onNodeCreate(({ node, json }) => {}) // Listen to node creation, data is the JSON data at creation time
|
||||
doc.onNodeDispose(({ node }) => {}) // Listen to node deletion
|
||||
```
|
||||
|
||||
|
||||
@ -13,7 +13,7 @@ doc.toJSON() // TODO 这里老版本的数据,还没优化,业务最好自
|
||||
|
||||
doc.addFromNode(targetNode, json) // 插入到指定节点的后边
|
||||
|
||||
doc.onNodeCreate(({ node, data}) => {}) // 监听节点创建,data 为创建时候的json数据
|
||||
doc.onNodeCreate(({ node, json }) => {}) // 监听节点创建,data 为创建时候的json数据
|
||||
doc.onNodeDispose(({ node }) => {}) // 监听节点删除
|
||||
```
|
||||
|
||||
|
||||
@ -23,7 +23,7 @@ export interface FlowDocumentOptions {
|
||||
*/
|
||||
allNodesDefaultExpanded?: boolean;
|
||||
toNodeJSON?(node: FlowNodeEntity): FlowNodeJSON;
|
||||
fromNodeJSON?(node: FlowNodeEntity, json: FlowNodeJSON): void;
|
||||
fromNodeJSON?(node: FlowNodeEntity, json: FlowNodeJSON, isFirstCreate: boolean): void;
|
||||
constants?: Record<string, any>;
|
||||
formatNodeLines?: (node: FlowNodeEntity, lines: FlowTransitionLine[]) => FlowTransitionLine[];
|
||||
formatNodeLabels?: (node: FlowNodeEntity, lines: FlowTransitionLabel[]) => FlowTransitionLabel[];
|
||||
|
||||
@ -57,12 +57,22 @@ export class FlowDocument<T = FlowDocumentJSON> implements Disposable {
|
||||
|
||||
protected onNodeUpdateEmitter = new Emitter<{
|
||||
node: FlowNodeEntity;
|
||||
/**
|
||||
* use 'json' instead
|
||||
* @deprecated
|
||||
*/
|
||||
data: FlowNodeJSON;
|
||||
json: FlowNodeJSON;
|
||||
}>();
|
||||
|
||||
protected onNodeCreateEmitter = new Emitter<{
|
||||
node: FlowNodeEntity;
|
||||
/**
|
||||
* use 'json' instead
|
||||
* @deprecated
|
||||
*/
|
||||
data: FlowNodeJSON;
|
||||
json: FlowNodeJSON;
|
||||
}>();
|
||||
|
||||
protected onNodeDisposeEmitter = new Emitter<{
|
||||
@ -219,7 +229,7 @@ export class FlowDocument<T = FlowDocumentJSON> implements Disposable {
|
||||
addNode(
|
||||
data: AddNodeData,
|
||||
addedNodes?: FlowNodeEntity[],
|
||||
ignoreCreateEvent?: boolean
|
||||
ignoreCreateAndUpdateEvent?: boolean
|
||||
): FlowNodeEntity {
|
||||
const { id, type = 'block', originParent, parent, meta, hidden, index } = data;
|
||||
let node = this.getNode(id);
|
||||
@ -244,10 +254,10 @@ export class FlowDocument<T = FlowDocumentJSON> implements Disposable {
|
||||
: this.nodeDataRegistries;
|
||||
node.addInitializeData(datas);
|
||||
node.onDispose(() => this.onNodeDisposeEmitter.fire({ node: node! }));
|
||||
if (this.options.fromNodeJSON) {
|
||||
this.options.fromNodeJSON(node, data);
|
||||
}
|
||||
this.options.fromNodeJSON?.(node, data, true);
|
||||
isNew = true;
|
||||
} else {
|
||||
this.options.fromNodeJSON?.(node, data, false);
|
||||
}
|
||||
// 初始化数据重制
|
||||
node.initData({
|
||||
@ -261,7 +271,6 @@ export class FlowDocument<T = FlowDocumentJSON> implements Disposable {
|
||||
if (node.isStart) {
|
||||
this.root.addChild(node);
|
||||
}
|
||||
this.onNodeUpdateEmitter.fire({ node, data });
|
||||
addedNodes?.push(node);
|
||||
// 自定义创建逻辑
|
||||
if (register.onCreate) {
|
||||
@ -278,11 +287,16 @@ export class FlowDocument<T = FlowDocumentJSON> implements Disposable {
|
||||
}
|
||||
}
|
||||
|
||||
if (isNew && !ignoreCreateEvent) {
|
||||
this.onNodeCreateEmitter.fire({
|
||||
node,
|
||||
data,
|
||||
});
|
||||
if (!ignoreCreateAndUpdateEvent) {
|
||||
if (isNew) {
|
||||
this.onNodeCreateEmitter.fire({
|
||||
node,
|
||||
data,
|
||||
json: data,
|
||||
});
|
||||
} else {
|
||||
this.onNodeUpdateEmitter.fire({ node, data, json: data });
|
||||
}
|
||||
}
|
||||
|
||||
return node;
|
||||
|
||||
@ -14,19 +14,27 @@ export function toFormJSON(node: FlowNodeEntity) {
|
||||
return formData.toJSON();
|
||||
}
|
||||
|
||||
export function initFormDataFromJSON(node: FlowNodeEntity, json: FlowNodeJSON) {
|
||||
export function initFormDataFromJSON(
|
||||
node: FlowNodeEntity,
|
||||
json: FlowNodeJSON,
|
||||
isFirstCreate: boolean
|
||||
) {
|
||||
const formData = node.getData(FlowNodeFormData)!;
|
||||
const registry = node.getNodeRegistry();
|
||||
const { formMeta } = registry;
|
||||
|
||||
if (formData && formMeta) {
|
||||
formData.createForm(formMeta, json.data);
|
||||
formData.onDataChange(() => {
|
||||
(node.document as WorkflowDocument).fireContentChange({
|
||||
type: WorkflowContentChangeType.NODE_DATA_CHANGE,
|
||||
toJSON: () => formData.toJSON(),
|
||||
entity: node,
|
||||
if (isFirstCreate) {
|
||||
formData.createForm(formMeta, json.data);
|
||||
formData.onDataChange(() => {
|
||||
(node.document as WorkflowDocument).fireContentChange({
|
||||
type: WorkflowContentChangeType.NODE_DATA_CHANGE,
|
||||
toJSON: () => formData.toJSON(),
|
||||
entity: node,
|
||||
});
|
||||
});
|
||||
});
|
||||
} else {
|
||||
formData.updateFormValues(json.data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -92,8 +92,8 @@ export const WorkflowDocumentOptionsDefault: WorkflowDocumentOptions = {
|
||||
'url("data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAiIGhlaWdodD0iMjEiIHZpZXdCb3g9IjAgMCAyMCAyMSIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik02LjYxODE3IDUuNTk4NzVDNi42MTgxNyA0LjgxOTIgNy4yNTAxMiA0LjE4NzI2IDguMDI5NjcgNC4xODcyNkM4Ljc3ODczIDQuMTg3MjYgOS4zOTE1MiA0Ljc3MDc1IDkuNDM4MjkgNS41MDgwMUM5LjQ1OTkyIDQuOTQ3MSA5LjkyMTQ3IDQuNDk5MDIgMTAuNDg3NyA0LjQ5OTAyQzExLjA2NzcgNC40OTkwMiAxMS41Mzc4IDQuOTY5MiAxMS41Mzc4IDUuNTQ5MTlWOC43NjI0NkwxMS41Mzc5IDYuNzExNUMxMS41Mzc5IDYuMDMwOTUgMTIuMDg5NiA1LjQ3OTI2IDEyLjc3MDIgNS40NzkyNkMxMy40NTA3IDUuNDc5MjYgMTQuMDAyNCA2LjAzMDk1IDE0LjAwMjQgNi43MTE1TDE0LjAwMjQgOC43NjI0NkwxNC4wMDI5IDguMDE5ODNDMTQuMDAyOSA3LjMzOTI5IDE0LjU1NDYgNi43ODc1OSAxNS4yMzUyIDYuNzg3NTlDMTUuOTE1NyA2Ljc4NzU5IDE2LjQ2NzQgNy4zMzkyOCAxNi40Njc0IDguMDE5ODNWMTEuNDk3TDE2LjQ2NzUgMTIuNDQ2N0MxNi40Njc1IDE0LjA4MjEgMTUuODA5NiAxNS42NDg3IDE0LjY0MiAxNi43OTM4VjE3LjMyNjJDMTQuNjQyIDE3Ljc4NjQgMTQuMjY4OSAxOC4xNTk1IDEzLjgwODcgMTguMTU5NUg4LjE3MzE3QzcuNzEyOTMgMTguMTU5NSA3LjMzOTg0IDE3Ljc4NjQgNy4zMzk4NCAxNy4zMjYyVjE1Ljk0MjRMNS4zNDU2MiAxNC43NTM0QzQuNTg5MjQgMTQuMzAyNCA0LjEyNTkxIDEzLjQ4NjcgNC4xMjU4OSAxMi42MDYxTDQuMTI1ODMgOS4yODM4M0M0LjEyNTgyIDguOTU0MjcgNC4zMjAwMyA4LjY1NTY2IDQuNjIxMjkgOC41MjIwNUw2LjYxODE3IDcuNjM2MzRWNS41OTg3NVoiIGZpbGw9IndoaXRlIi8+CjxwYXRoIGZpbGwtcnVsZT0iZXZlbm9kZCIgY2xpcC1ydWxlPSJldmVub2RkIiBkPSJNMTAuNDg3OCAzLjI0OTAyQzExLjI3OTYgMy4yNDkwMiAxMS45Nzc4IDMuNjQ5MDIgMTIuMzkxNyA0LjI1Nzk2QzEyLjUxNTEgNC4yMzkwNiAxMi42NDE2IDQuMjI5MjYgMTIuNzcwMyA0LjIyOTI2QzEzLjcyMjQgNC4yMjkyNiAxNC41NDkzIDQuNzY1MzEgMTQuOTY1NyA1LjU1MjA3QzE1LjA1NDMgNS41NDI1IDE1LjE0NDIgNS41Mzc1OSAxNS4yMzUzIDUuNTM3NTlDMTYuNjA2MiA1LjUzNzU5IDE3LjcxNzYgNi42NDg5MyAxNy43MTc2IDguMDE5ODNMMTcuNzE3NyAxMi40NDY3QzE3LjcxNzcgMTQuMjM1NSAxNy4wNjQ3IDE1Ljk1NTkgMTUuODkyMSAxNy4yOTA0VjE3LjMyNjJDMTUuODkyMSAxOC40NzY4IDE0Ljk1OTQgMTkuNDA5NSAxMy44MDg4IDE5LjQwOTVIOC4xNzMzMkM3LjAyMjczIDE5LjQwOTUgNi4wODk5OCAxOC40NzY4IDYuMDg5OTggMTcuMzI2MlYxNi42NTI0TDQuNzA1NjMgMTUuODI3QzMuNTcxMDYgMTUuMTUwNSAyLjg3NjA3IDEzLjkyNzEgMi44NzYwNCAxMi42MDYxTDIuODc1OTggOS4yODM4NUMyLjg3NTk2IDguNDU5OTYgMy4zNjE0OSA3LjcxMzQ1IDQuMTE0NjIgNy4zNzk0TDUuMzY4MzIgNi44MjMzM1Y1LjU5ODc1QzUuMzY4MzIgNC4xMjg4NSA2LjU1OTkxIDIuOTM3MjYgOC4wMjk4MiAyLjkzNzI2QzguNjA4MzEgMi45MzcyNiA5LjE0MzU1IDMuMTIxNyA5LjU4MDA1IDMuNDM1MDVDOS44NTg1MyAzLjMxNTMyIDEwLjE2NTQgMy4yNDkwMiAxMC40ODc4IDMuMjQ5MDJaTTEyLjQ0NzkgNS41MjE4NlY5LjQ3NTU3QzEyLjQ0NzkgOS43Mjc2MiAxMi4yNDM2IDkuOTMxOTUgMTEuOTkxNiA5LjkzMTk1QzExLjc1NjggOS45MzE5NSAxMS41NjM0IDkuNzU0NjUgMTEuNTM4IDkuNTI2NjNDMTEuNTM2MSA5LjUwOTg3IDExLjUzNTIgOS40OTI4MyAxMS41MzUyIDkuNDc1NTdWNS40NzE1OEMxMS41MTU0IDUuMjAwODMgMTEuMzkzIDQuOTU4NTggMTEuMjA2NiA0Ljc4MzU4QzExLjAxODggNC42MDcxMSAxMC43NjU5IDQuNDk5MDIgMTAuNDg3OCA0LjQ5OTAyQzEwLjQ3NjYgNC40OTkwMiAxMC40NjU0IDQuNDk5MTkgMTAuNDU0MiA0LjQ5OTU0QzkuOTAzNDcgNC41MTY4NCA5LjQ1OTY0IDQuOTU4MjQgOS40Mzg0NCA1LjUwODAxQzkuNDM4MiA1LjUwNDMgOS40Mzc5NSA1LjUwMDU4IDkuNDM3NjkgNS40OTY4OFY5LjkwMjQzQzkuNDM3NjkgMTAuMTU0NSA5LjIzMzM2IDEwLjM1ODggOC45ODEzMSAxMC4zNTg4QzguNzI5MjUgMTAuMzU4OCA4LjUyNDkyIDEwLjE1NDUgOC41MjQ5MiA5LjkwMjQzVjQuMjc2NTNDOC4zNzA4NiA0LjIxODgyIDguMjA0MDIgNC4xODcyNiA4LjAyOTgyIDQuMTg3MjZDNy4yNTAyNyA0LjE4NzI2IDYuNjE4MzIgNC44MTkyIDYuNjE4MzIgNS41OTg3NUw2LjYxODI3IDkuOTc1OTlDNi42MTgyNyAxMC4yMjggNi40MTM5NCAxMC40MzI0IDYuMTYxODkgMTAuNDMyNEM1LjkwOTgzIDEwLjQzMjQgNS43MDU1IDEwLjIyOCA1LjcwNTUgOS45NzU5OVY4LjA0MTIyTDQuNjIxNDQgOC41MjIwNUM0LjMyMDE4IDguNjU1NjYgNC4xMjU5NyA4Ljk1NDI3IDQuMTI1OTggOS4yODM4M0w0LjEyNjA0IDEyLjYwNjFDNC4xMjYwNiAxMy40ODY3IDQuNTg5MzkgMTQuMzAyNCA1LjM0NTc2IDE0Ljc1MzRMNy4zMzk5OCAxNS45NDI0VjE3LjMyNjJDNy4zMzk5OCAxNy43ODY0IDcuNzEzMDggMTguMTU5NSA4LjE3MzMyIDE4LjE1OTVIMTMuODA4OEMxNC4yNjkgMTguMTU5NSAxNC42NDIxIDE3Ljc4NjQgMTQuNjQyMSAxNy4zMjYyVjE2Ljc5MzhDMTUuNzM4MSAxNS43MTkgMTYuMzg1IDE0LjI3MjggMTYuNDYwMyAxMi43NDdDMTYuNDY0NiAxMi42NiAxNi40NjcgMTIuNTcyOCAxNi40Njc2IDEyLjQ4NTRMMTYuNDY3NyAxMi40NDY3TDE2LjQ2NzYgOC4wMTk4M0MxNi40Njc2IDcuMzQ1MDQgMTUuOTI1MiA2Ljc5NjkzIDE1LjI1MjUgNi43ODc3MUwxNS4yMzUzIDYuNzg3NTlDMTUuMTI1IDYuNzg3NTkgMTUuMDE4IDYuODAyMSAxNC45MTYyIDYuODI5MzFWOS42MDEzNkMxNC45MTYyIDkuODUzNDIgMTQuNzExOSAxMC4wNTc3IDE0LjQ1OTggMTAuMDU3N0MxNC4yMDc4IDEwLjA1NzcgMTQuMDAzNCA5Ljg1MzQxIDE0LjAwMzQgOS42MDEzNlY3Ljk4OTg1QzE0LjAwMzIgNy45OTk4MiAxNC4wMDMxIDguMDA5ODEgMTQuMDAzMSA4LjAxOTgzTDE0LjAwMzQgOS42MDEzNkwxNC4wMDI1IDYuNzExNUMxNC4wMDI1IDYuNDQ5NzQgMTMuOTIwOSA2LjIwNzA1IDEzLjc4MTggNi4wMDc0OEMxMy41NjIgNS42OTI0MiAxMy4xOTg5IDUuNDg0ODMgMTIuNzg3IDUuNDc5MzdMMTIuNzcwMyA1LjQ3OTI2QzEyLjY1ODggNS40NzkyNiAxMi41NTA3IDUuNDk0MDggMTIuNDQ3OSA1LjUyMTg2WiIgZmlsbD0iIzFEMUMyMyIvPgo8L3N2Zz4="), auto',
|
||||
},
|
||||
|
||||
fromNodeJSON(node, json) {
|
||||
initFormDataFromJSON(node, json);
|
||||
fromNodeJSON(node, json, isFirstCreate) {
|
||||
initFormDataFromJSON(node, json, isFirstCreate);
|
||||
return;
|
||||
},
|
||||
toNodeJSON(node: WorkflowNodeEntity): WorkflowNodeJSON {
|
||||
|
||||
@ -180,10 +180,12 @@ export class WorkflowDocument extends FlowDocument {
|
||||
|
||||
const transform = node.getData<FlowNodeTransformData>(FlowNodeTransformData)!;
|
||||
const freeLayout = this.layout as FreeLayout;
|
||||
transform.onDataChange(() => {
|
||||
// TODO 这个有点难以理解,其实是为了同步size 数据
|
||||
freeLayout.syncTransform(node);
|
||||
});
|
||||
if (!isExistedNode) {
|
||||
transform.onDataChange(() => {
|
||||
// TODO 这个有点难以理解,其实是为了同步size 数据
|
||||
freeLayout.syncTransform(node);
|
||||
});
|
||||
}
|
||||
let { position } = meta;
|
||||
if (!position) {
|
||||
// 获取默认的位置
|
||||
@ -210,13 +212,15 @@ export class WorkflowDocument extends FlowDocument {
|
||||
}
|
||||
// 位置变更
|
||||
const positionData = node.getData<PositionData>(PositionData)!;
|
||||
positionData.onDataChange(() => {
|
||||
this.fireContentChange({
|
||||
type: WorkflowContentChangeType.MOVE_NODE,
|
||||
toJSON: () => positionData.toJSON(),
|
||||
entity: node,
|
||||
if (!isExistedNode) {
|
||||
positionData.onDataChange(() => {
|
||||
this.fireContentChange({
|
||||
type: WorkflowContentChangeType.MOVE_NODE,
|
||||
toJSON: () => positionData.toJSON(),
|
||||
entity: node,
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const subCanvas = this.getNodeSubCanvas(node);
|
||||
|
||||
@ -265,19 +269,29 @@ export class WorkflowDocument extends FlowDocument {
|
||||
canvasTransform.update({
|
||||
position: subCanvas.parentNode.getNodeMeta()?.canvasPosition,
|
||||
});
|
||||
subCanvas.parentNode.onDispose(() => {
|
||||
subCanvas.canvasNode.dispose();
|
||||
if (!isExistedNode) {
|
||||
subCanvas.parentNode.onDispose(() => {
|
||||
subCanvas.canvasNode.dispose();
|
||||
});
|
||||
subCanvas.canvasNode.onDispose(() => {
|
||||
subCanvas.parentNode.dispose();
|
||||
});
|
||||
}
|
||||
}
|
||||
if (!isExistedNode) {
|
||||
this.onNodeCreateEmitter.fire({
|
||||
node,
|
||||
data: json,
|
||||
json,
|
||||
});
|
||||
subCanvas.canvasNode.onDispose(() => {
|
||||
subCanvas.parentNode.dispose();
|
||||
} else {
|
||||
this.onNodeUpdateEmitter.fire({
|
||||
node,
|
||||
data: json,
|
||||
json,
|
||||
});
|
||||
}
|
||||
|
||||
this.onNodeCreateEmitter.fire({
|
||||
node,
|
||||
data: json,
|
||||
});
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
@ -554,8 +568,6 @@ export class WorkflowDocument extends FlowDocument {
|
||||
* 导出数据
|
||||
*/
|
||||
toJSON(): WorkflowJSON {
|
||||
// 要等 一些节点的 dispose 触发结束
|
||||
// await delay(10);
|
||||
const rootJSON = this.toNodeJSON(this.root);
|
||||
return {
|
||||
nodes: rootJSON.blocks ?? [],
|
||||
|
||||
@ -1 +0,0 @@
|
||||
export const EditorOptions = Symbol('EditorOptions');
|
||||
@ -10,7 +10,6 @@ import { FlowDocumentContainerModule } from '@flowgram.ai/document';
|
||||
import { createPlaygroundPlugin, Plugin, PluginsProvider } from '@flowgram.ai/core';
|
||||
|
||||
import { compose } from '../utils/compose';
|
||||
import { EditorOptions } from '../constants';
|
||||
import { createFlowEditorClientPlugins } from '../clients/flow-editor-client-plugins';
|
||||
import { EditorPluginContext, EditorProps } from './editor-props';
|
||||
|
||||
@ -20,7 +19,6 @@ export function createDefaultPreset<CTX extends EditorPluginContext = EditorPlug
|
||||
): PluginsProvider<CTX> {
|
||||
return (ctx: CTX) => {
|
||||
opts = { ...EditorProps.DEFAULT, ...opts };
|
||||
ctx.container.bind(EditorOptions).toConstantValue(opts);
|
||||
/**
|
||||
* i18n support
|
||||
*/
|
||||
|
||||
@ -5,7 +5,7 @@ import { NodeCorePluginOptions } from '@flowgram.ai/node-core-plugin';
|
||||
import { MaterialsPluginOptions } from '@flowgram.ai/materials-plugin';
|
||||
import { I18nPluginOptions } from '@flowgram.ai/i18n-plugin';
|
||||
import { HistoryPluginOptions } from '@flowgram.ai/history';
|
||||
import { FlowNodeFormData, FormMetaOrFormMetaGenerator } from '@flowgram.ai/form-core';
|
||||
import { FormMetaOrFormMetaGenerator } from '@flowgram.ai/form-core';
|
||||
import {
|
||||
FlowDocument,
|
||||
FlowDocumentJSON,
|
||||
@ -18,8 +18,6 @@ import {
|
||||
} from '@flowgram.ai/document';
|
||||
import { PluginContext } from '@flowgram.ai/core';
|
||||
|
||||
import { EditorOptions } from '../constants';
|
||||
|
||||
export interface EditorPluginContext extends PluginContext {
|
||||
document: FlowDocument;
|
||||
selection: SelectionService;
|
||||
@ -94,12 +92,18 @@ export interface EditorProps<
|
||||
};
|
||||
|
||||
/**
|
||||
* 节点转
|
||||
* @param node
|
||||
* 节点数据导出
|
||||
* - node 当前节点
|
||||
* - json 当前节点数据
|
||||
*/
|
||||
toNodeJSON?(node: FlowNodeEntity): FlowNodeJSON;
|
||||
fromNodeJSON?(node: FlowNodeEntity, json: FlowNodeJSON): void;
|
||||
|
||||
toNodeJSON?(node: FlowNodeEntity, json: FlowNodeJSON): FlowNodeJSON;
|
||||
/**
|
||||
* 节点数据导入
|
||||
* - node 当前节点
|
||||
* - json 当前节点数据
|
||||
* - isFirstCreate 是否是第一次创建
|
||||
*/
|
||||
fromNodeJSON?(node: FlowNodeEntity, json: FlowNodeJSON, isFirstCreate: boolean): FlowNodeJSON;
|
||||
/**
|
||||
* 画布内部常量自定义
|
||||
*/
|
||||
@ -124,54 +128,5 @@ export namespace EditorProps {
|
||||
*/
|
||||
export const DEFAULT: EditorProps = {
|
||||
background: {},
|
||||
fromNodeJSON(node: FlowNodeEntity, json: FlowNodeJSON) {
|
||||
const formData = node.getData(FlowNodeFormData)!;
|
||||
// 如果没有使用表单引擎,将 data 数据填入 extInfo
|
||||
if (!formData) {
|
||||
if (json.data) {
|
||||
node.updateExtInfo(json.data);
|
||||
}
|
||||
} else {
|
||||
const defaultFormMeta = node
|
||||
.getService<EditorProps>(EditorOptions)
|
||||
.nodeEngine?.createDefaultFormMeta?.(node);
|
||||
|
||||
const formMeta = node.getNodeRegistry()?.formMeta || defaultFormMeta;
|
||||
|
||||
if (formMeta) {
|
||||
formData.createForm(formMeta, json.data);
|
||||
}
|
||||
}
|
||||
},
|
||||
toNodeJSON(node: FlowNodeEntity): FlowNodeJSON {
|
||||
const nodesMap: Record<string, FlowNodeJSON> = {};
|
||||
let startNodeJSON: FlowNodeJSON;
|
||||
node.document.traverse((node) => {
|
||||
const isSystemNode = node.id.startsWith('$');
|
||||
if (isSystemNode) return;
|
||||
const formData = node.getData(FlowNodeFormData);
|
||||
let formJSON =
|
||||
formData && formData.formModel && formData.formModel.initialized
|
||||
? formData.toJSON()
|
||||
: undefined;
|
||||
const nodeJSON = {
|
||||
id: node.id,
|
||||
type: node.flowNodeType,
|
||||
data: formData ? formJSON : node.getExtInfo(),
|
||||
blocks: [],
|
||||
};
|
||||
if (!startNodeJSON) startNodeJSON = nodeJSON;
|
||||
let { parent } = node;
|
||||
if (parent && parent.id.startsWith('$')) {
|
||||
parent = parent.originParent;
|
||||
}
|
||||
const parentJSON = parent ? nodesMap[parent.id] : undefined;
|
||||
if (parentJSON) {
|
||||
parentJSON.blocks?.push(nodeJSON);
|
||||
}
|
||||
nodesMap[node.id] = nodeJSON;
|
||||
}, node);
|
||||
return startNodeJSON!;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@ -26,6 +26,17 @@ export const formMock: FlowDocumentJSON = {
|
||||
}]
|
||||
}
|
||||
|
||||
export const formMock2: FlowDocumentJSON = {
|
||||
nodes: [{
|
||||
id: 'noop_0',
|
||||
type: 'noop',
|
||||
data: {
|
||||
title: 'noop title changed',
|
||||
},
|
||||
blocks: [],
|
||||
}]
|
||||
}
|
||||
|
||||
export const baseWithDataMock: FlowDocumentJSON = {
|
||||
nodes: [ {
|
||||
id: 'start_0',
|
||||
@ -58,6 +69,39 @@ export const baseWithDataMock: FlowDocumentJSON = {
|
||||
]
|
||||
}
|
||||
|
||||
export const baseWithDataMock2: FlowDocumentJSON = {
|
||||
nodes: [ {
|
||||
id: 'start_0',
|
||||
type: 'start',
|
||||
data: {
|
||||
title: 'start title changed',
|
||||
},
|
||||
blocks: [],
|
||||
},
|
||||
{
|
||||
id: 'dynamicSplit_0',
|
||||
type: 'dynamicSplit',
|
||||
data: {
|
||||
title: 'dynamic title changed',
|
||||
},
|
||||
blocks: [
|
||||
{ id: 'block_3', data: { title: '' }, blocks: [], type: 'block' },
|
||||
{ id: 'block_4',data: { title: '' }, blocks: [], type: 'block'},
|
||||
{ id: 'block_2',data: { title: 'title changed' },blocks: [], type: 'block' }
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'end_0',
|
||||
type: 'end',
|
||||
data: {
|
||||
title: 'end title changed',
|
||||
},
|
||||
blocks: [],
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
export const baseMock: FlowDocumentJSON = {
|
||||
nodes: [
|
||||
{
|
||||
|
||||
@ -1,5 +1,141 @@
|
||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`fixed-layout-preset > custom fromNodeJSON and toNodeJSON 1`] = `
|
||||
{
|
||||
"nodes": [
|
||||
{
|
||||
"blocks": [],
|
||||
"data": {
|
||||
"isFirstCreate": true,
|
||||
"runningTimes": 1,
|
||||
"title": "start title",
|
||||
},
|
||||
"id": "start_0",
|
||||
"type": "start",
|
||||
},
|
||||
{
|
||||
"blocks": [
|
||||
{
|
||||
"blocks": [],
|
||||
"data": {
|
||||
"isFirstCreate": true,
|
||||
"runningTimes": 1,
|
||||
"title": "",
|
||||
},
|
||||
"id": "block_0",
|
||||
"type": "block",
|
||||
},
|
||||
{
|
||||
"blocks": [],
|
||||
"data": {
|
||||
"isFirstCreate": true,
|
||||
"runningTimes": 1,
|
||||
"title": "",
|
||||
},
|
||||
"id": "block_1",
|
||||
"type": "block",
|
||||
},
|
||||
{
|
||||
"blocks": [],
|
||||
"data": {
|
||||
"isFirstCreate": true,
|
||||
"runningTimes": 1,
|
||||
"title": "",
|
||||
},
|
||||
"id": "block_2",
|
||||
"type": "block",
|
||||
},
|
||||
],
|
||||
"data": {
|
||||
"isFirstCreate": true,
|
||||
"runningTimes": 1,
|
||||
"title": "dynamic title",
|
||||
},
|
||||
"id": "dynamicSplit_0",
|
||||
"type": "dynamicSplit",
|
||||
},
|
||||
{
|
||||
"blocks": [],
|
||||
"data": {
|
||||
"isFirstCreate": true,
|
||||
"runningTimes": 1,
|
||||
"title": "end title",
|
||||
},
|
||||
"id": "end_0",
|
||||
"type": "end",
|
||||
},
|
||||
],
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`fixed-layout-preset > custom fromNodeJSON and toNodeJSON 2`] = `
|
||||
{
|
||||
"nodes": [
|
||||
{
|
||||
"blocks": [],
|
||||
"data": {
|
||||
"isFirstCreate": false,
|
||||
"runningTimes": 1,
|
||||
"title": "start title changed",
|
||||
},
|
||||
"id": "start_0",
|
||||
"type": "start",
|
||||
},
|
||||
{
|
||||
"blocks": [
|
||||
{
|
||||
"blocks": [],
|
||||
"data": {
|
||||
"isFirstCreate": true,
|
||||
"runningTimes": 1,
|
||||
"title": "",
|
||||
},
|
||||
"id": "block_3",
|
||||
"type": "block",
|
||||
},
|
||||
{
|
||||
"blocks": [],
|
||||
"data": {
|
||||
"isFirstCreate": true,
|
||||
"runningTimes": 1,
|
||||
"title": "",
|
||||
},
|
||||
"id": "block_4",
|
||||
"type": "block",
|
||||
},
|
||||
{
|
||||
"blocks": [],
|
||||
"data": {
|
||||
"isFirstCreate": false,
|
||||
"runningTimes": 1,
|
||||
"title": "title changed",
|
||||
},
|
||||
"id": "block_2",
|
||||
"type": "block",
|
||||
},
|
||||
],
|
||||
"data": {
|
||||
"isFirstCreate": false,
|
||||
"runningTimes": 1,
|
||||
"title": "dynamic title changed",
|
||||
},
|
||||
"id": "dynamicSplit_0",
|
||||
"type": "dynamicSplit",
|
||||
},
|
||||
{
|
||||
"blocks": [],
|
||||
"data": {
|
||||
"isFirstCreate": false,
|
||||
"runningTimes": 1,
|
||||
"title": "end title changed",
|
||||
},
|
||||
"id": "end_0",
|
||||
"type": "end",
|
||||
},
|
||||
],
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`fixed-layout-preset > nodeEngine(v2) toJSON 1`] = `
|
||||
{
|
||||
"nodes": [
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { describe, it, expect, beforeEach } from 'vitest';
|
||||
import { FlowDocument, FlowNodeFormData } from '@flowgram.ai/editor';
|
||||
|
||||
import { baseWithDataMock, formMock } from '../__mocks__/flow.mock';
|
||||
import { baseWithDataMock, baseWithDataMock2, formMock, formMock2 } from '../__mocks__/flow.mock';
|
||||
import { createContainer } from './create-container';
|
||||
|
||||
describe('fixed-layout-preset', () => {
|
||||
@ -13,6 +13,28 @@ describe('fixed-layout-preset', () => {
|
||||
it('fromJSON and toJSON', () => {
|
||||
flowDocument.fromJSON(baseWithDataMock);
|
||||
expect(flowDocument.toJSON()).toEqual(baseWithDataMock);
|
||||
// reload data
|
||||
flowDocument.fromJSON(baseWithDataMock2);
|
||||
expect(flowDocument.toJSON()).toEqual(baseWithDataMock2);
|
||||
});
|
||||
it('custom fromNodeJSON and toNodeJSON', () => {
|
||||
const container = createContainer({
|
||||
fromNodeJSON: (node, json, isFirstCreate) => {
|
||||
if (!json.data) {
|
||||
json.data = {};
|
||||
}
|
||||
json.data = { ...json.data, isFirstCreate };
|
||||
return json;
|
||||
},
|
||||
toNodeJSON(node, json) {
|
||||
json.data.runningTimes = (json.data.runningTimes || 0) + 1;
|
||||
return json;
|
||||
},
|
||||
});
|
||||
container.get(FlowDocument).fromJSON(baseWithDataMock);
|
||||
expect(container.get(FlowDocument).toJSON()).toMatchSnapshot();
|
||||
container.get(FlowDocument).fromJSON(baseWithDataMock2);
|
||||
expect(container.get(FlowDocument).toJSON()).toMatchSnapshot();
|
||||
});
|
||||
it('nodeEngine(v2) toJSON', async () => {
|
||||
const container = createContainer({
|
||||
@ -33,5 +55,7 @@ describe('fixed-layout-preset', () => {
|
||||
expect(formModel.getFormItemByPath('title').value).toEqual('noop title');
|
||||
formModel.getFormItemByPath('title').value = 'noop title2';
|
||||
expect(flowDocument.toJSON()).toMatchSnapshot();
|
||||
flowDocument.fromJSON(formMock2);
|
||||
expect(flowDocument.toJSON()).toEqual(formMock2);
|
||||
});
|
||||
});
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { beforeEach, describe, it, expect } from 'vitest';
|
||||
import { FlowNodeEntity, FlowNodeFormData, FlowNodeJSON, FormModelV2 } from '@flowgram.ai/editor';
|
||||
import { FlowNodeFormData, FormModelV2 } from '@flowgram.ai/editor';
|
||||
|
||||
import { createHistoryContainer } from '../../create-container';
|
||||
import { formMock } from '../../../__mocks__/form.mock';
|
||||
@ -7,14 +7,6 @@ import { emptyMock } from '../../../__mocks__/flow.mock';
|
||||
|
||||
describe('history-operation-service changeFormData', () => {
|
||||
const { flowDocument, flowOperationService, historyService } = createHistoryContainer({
|
||||
fromNodeJSON(node: FlowNodeEntity, json: FlowNodeJSON) {
|
||||
const formData = node.getData(FlowNodeFormData)!;
|
||||
const formMeta = node.getNodeRegistry()?.formMeta;
|
||||
|
||||
if (formMeta) {
|
||||
formData.createForm(formMeta, json.data);
|
||||
}
|
||||
},
|
||||
nodeEngine: {},
|
||||
nodeRegistries: [
|
||||
{
|
||||
|
||||
@ -24,6 +24,7 @@ import {
|
||||
|
||||
import { FlowOperationService } from '../types';
|
||||
import { createOperationPlugin } from '../plugins/create-operation-plugin';
|
||||
import { fromNodeJSON, toNodeJSON } from './node-serialize';
|
||||
import { FixedLayoutPluginContext, FixedLayoutProps } from './fixed-layout-props';
|
||||
|
||||
export function createFixedLayoutPreset(
|
||||
@ -128,8 +129,9 @@ export function createFixedLayoutPreset(
|
||||
bindConfig.bind(FlowDocumentOptions).toConstantValue({
|
||||
...FlowDocumentOptionsDefault,
|
||||
defaultLayout: opts.defaultLayout,
|
||||
toNodeJSON: opts.toNodeJSON,
|
||||
fromNodeJSON: opts.fromNodeJSON,
|
||||
toNodeJSON: (node) => toNodeJSON(opts, node),
|
||||
fromNodeJSON: (node, json, isFirstCreate) =>
|
||||
fromNodeJSON(opts, node, json, isFirstCreate),
|
||||
allNodesDefaultExpanded: opts.allNodesDefaultExpanded,
|
||||
} as FlowDocumentOptions);
|
||||
}
|
||||
|
||||
@ -0,0 +1,67 @@
|
||||
import { FlowNodeEntity, FlowNodeJSON, FlowNodeFormData } from '@flowgram.ai/editor';
|
||||
|
||||
import { FixedLayoutProps } from './fixed-layout-props';
|
||||
|
||||
export function fromNodeJSON(
|
||||
opts: FixedLayoutProps,
|
||||
node: FlowNodeEntity,
|
||||
json: FlowNodeJSON,
|
||||
isFirstCreate: boolean
|
||||
) {
|
||||
json = opts.fromNodeJSON ? opts.fromNodeJSON(node, json, isFirstCreate) : json;
|
||||
const formData = node.getData(FlowNodeFormData)!;
|
||||
// 如果没有使用表单引擎,将 data 数据填入 extInfo
|
||||
if (!formData) {
|
||||
if (json.data) {
|
||||
node.updateExtInfo(json.data);
|
||||
}
|
||||
} else {
|
||||
const defaultFormMeta = opts.nodeEngine?.createDefaultFormMeta?.(node);
|
||||
|
||||
const formMeta = node.getNodeRegistry()?.formMeta || defaultFormMeta;
|
||||
|
||||
if (formMeta) {
|
||||
if (isFirstCreate) {
|
||||
formData.createForm(formMeta, json.data);
|
||||
} else {
|
||||
formData.updateFormValues(json.data);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function toNodeJSON(opts: FixedLayoutProps, node: FlowNodeEntity): FlowNodeJSON {
|
||||
const nodesMap: Record<string, FlowNodeJSON> = {};
|
||||
let startNodeJSON: FlowNodeJSON;
|
||||
node.document.traverse((node) => {
|
||||
const isSystemNode = node.id.startsWith('$');
|
||||
if (isSystemNode) return;
|
||||
const formData = node.getData(FlowNodeFormData);
|
||||
let formJSON =
|
||||
formData && formData.formModel && formData.formModel.initialized
|
||||
? formData.toJSON()
|
||||
: undefined;
|
||||
let nodeJSON: FlowNodeJSON = {
|
||||
id: node.id,
|
||||
type: node.flowNodeType,
|
||||
data: formData ? formJSON : node.getExtInfo(),
|
||||
blocks: [],
|
||||
};
|
||||
if (opts.toNodeJSON) {
|
||||
nodeJSON = opts.toNodeJSON(node, nodeJSON);
|
||||
}
|
||||
if (!startNodeJSON) startNodeJSON = nodeJSON;
|
||||
let { parent } = node;
|
||||
if (parent && parent.id.startsWith('$')) {
|
||||
parent = parent.originParent;
|
||||
}
|
||||
const parentJSON = parent ? nodesMap[parent.id] : undefined;
|
||||
if (parentJSON) {
|
||||
parentJSON.blocks?.push(nodeJSON);
|
||||
}
|
||||
nodesMap[node.id] = nodeJSON;
|
||||
}, node);
|
||||
|
||||
// @ts-ignore
|
||||
return startNodeJSON;
|
||||
}
|
||||
440
packages/client/free-layout-editor/__mocks__/flow.mocks.ts
Normal file
440
packages/client/free-layout-editor/__mocks__/flow.mocks.ts
Normal file
@ -0,0 +1,440 @@
|
||||
import { WorkflowJSON } from '@flowgram.ai/free-layout-core';
|
||||
|
||||
export const mockJSON: WorkflowJSON = {
|
||||
nodes: [
|
||||
{
|
||||
id: 'start_0',
|
||||
type: 'start',
|
||||
meta: {
|
||||
position: {
|
||||
x: 180,
|
||||
y: 381.75,
|
||||
},
|
||||
},
|
||||
data: {
|
||||
title: 'Start',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'condition_0',
|
||||
type: 'condition',
|
||||
meta: {
|
||||
position: {
|
||||
x: 640,
|
||||
y: 363.25,
|
||||
},
|
||||
},
|
||||
data: {
|
||||
title: 'Condition',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'end_0',
|
||||
type: 'end',
|
||||
meta: {
|
||||
position: {
|
||||
x: 2220,
|
||||
y: 381.75,
|
||||
},
|
||||
},
|
||||
data: {
|
||||
title: 'End',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'loop_H8M3U',
|
||||
type: 'loop',
|
||||
meta: {
|
||||
position: {
|
||||
x: 1020,
|
||||
y: 547.96875,
|
||||
},
|
||||
},
|
||||
data: {
|
||||
title: 'Loop_2',
|
||||
},
|
||||
blocks: [
|
||||
{
|
||||
id: 'llm_CBdCg',
|
||||
type: 'llm',
|
||||
meta: {
|
||||
position: {
|
||||
x: 180,
|
||||
y: 0,
|
||||
},
|
||||
},
|
||||
data: {
|
||||
title: 'LLM_4',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'llm_gZafu',
|
||||
type: 'llm',
|
||||
meta: {
|
||||
position: {
|
||||
x: 640,
|
||||
y: 0,
|
||||
},
|
||||
},
|
||||
data: {
|
||||
title: 'LLM_5',
|
||||
},
|
||||
},
|
||||
],
|
||||
edges: [
|
||||
{
|
||||
sourceNodeID: 'llm_CBdCg',
|
||||
targetNodeID: 'llm_gZafu',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: '159623',
|
||||
type: 'comment',
|
||||
meta: {
|
||||
position: {
|
||||
x: 640,
|
||||
y: 522.46875,
|
||||
},
|
||||
},
|
||||
data: {
|
||||
size: {
|
||||
width: 240,
|
||||
height: 150,
|
||||
},
|
||||
note: 'hi ~\n\nthis is a comment node\n\n- flowgram.ai',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'group_V-_st',
|
||||
type: 'group',
|
||||
meta: {
|
||||
position: {
|
||||
x: 1020,
|
||||
y: 96.25,
|
||||
},
|
||||
},
|
||||
data: {
|
||||
title: 'LLM_Group',
|
||||
color: 'Violet',
|
||||
},
|
||||
blocks: [
|
||||
{
|
||||
id: 'llm_0',
|
||||
type: 'llm',
|
||||
meta: {
|
||||
position: {
|
||||
x: 640,
|
||||
y: 0,
|
||||
},
|
||||
},
|
||||
data: {
|
||||
title: 'LLM_0',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'llm_l_TcE',
|
||||
type: 'llm',
|
||||
meta: {
|
||||
position: {
|
||||
x: 180,
|
||||
y: 0,
|
||||
},
|
||||
},
|
||||
data: {
|
||||
title: 'LLM_1',
|
||||
},
|
||||
},
|
||||
],
|
||||
edges: [
|
||||
{
|
||||
sourceNodeID: 'llm_l_TcE',
|
||||
targetNodeID: 'llm_0',
|
||||
},
|
||||
{
|
||||
sourceNodeID: 'llm_0',
|
||||
targetNodeID: 'end_0',
|
||||
},
|
||||
{
|
||||
sourceNodeID: 'condition_0',
|
||||
targetNodeID: 'llm_l_TcE',
|
||||
sourcePortID: 'if_0',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
edges: [
|
||||
{
|
||||
sourceNodeID: 'start_0',
|
||||
targetNodeID: 'condition_0',
|
||||
},
|
||||
{
|
||||
sourceNodeID: 'condition_0',
|
||||
targetNodeID: 'llm_l_TcE',
|
||||
sourcePortID: 'if_0',
|
||||
},
|
||||
{
|
||||
sourceNodeID: 'condition_0',
|
||||
targetNodeID: 'loop_H8M3U',
|
||||
sourcePortID: 'if_f0rOAt',
|
||||
},
|
||||
{
|
||||
sourceNodeID: 'llm_0',
|
||||
targetNodeID: 'end_0',
|
||||
},
|
||||
{
|
||||
sourceNodeID: 'loop_H8M3U',
|
||||
targetNodeID: 'end_0',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const mockJSON2: WorkflowJSON = {
|
||||
nodes: [
|
||||
{
|
||||
id: 'start_0',
|
||||
type: 'start',
|
||||
meta: {
|
||||
position: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
},
|
||||
},
|
||||
data: {
|
||||
title: 'Start changed',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'condition_0',
|
||||
type: 'condition',
|
||||
meta: {
|
||||
position: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
},
|
||||
},
|
||||
data: {
|
||||
title: 'Condition changed',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'end_0',
|
||||
type: 'end',
|
||||
meta: {
|
||||
position: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
},
|
||||
},
|
||||
data: {
|
||||
title: 'End',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'loop_H8M3U',
|
||||
type: 'loop',
|
||||
meta: {
|
||||
position: {
|
||||
x: 1020,
|
||||
y: 547.96875,
|
||||
},
|
||||
},
|
||||
data: {
|
||||
title: 'Loop_2 changed',
|
||||
},
|
||||
blocks: [
|
||||
{
|
||||
id: 'llm_CBdCg',
|
||||
type: 'llm',
|
||||
meta: {
|
||||
position: {
|
||||
x: 180,
|
||||
y: 0,
|
||||
},
|
||||
},
|
||||
data: {
|
||||
title: 'LLM_4 chnaged',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'llm_gZafu',
|
||||
type: 'llm changed',
|
||||
meta: {
|
||||
position: {
|
||||
x: 6,
|
||||
y: 0,
|
||||
},
|
||||
},
|
||||
data: {
|
||||
title: 'LLM_5',
|
||||
},
|
||||
},
|
||||
],
|
||||
edges: [
|
||||
{
|
||||
sourceNodeID: 'llm_CBdCg',
|
||||
targetNodeID: 'llm_gZafu',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: '159623',
|
||||
type: 'comment',
|
||||
meta: {
|
||||
position: {
|
||||
x: 640,
|
||||
y: 522.46875,
|
||||
},
|
||||
},
|
||||
data: {
|
||||
size: {
|
||||
width: 240,
|
||||
height: 150,
|
||||
},
|
||||
note: 'hi ~\n\nthis is a comment node changed\n\n- flowgram.ai',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'group_V-_st',
|
||||
type: 'group',
|
||||
meta: {
|
||||
position: {
|
||||
x: 1020,
|
||||
y: 96.25,
|
||||
},
|
||||
},
|
||||
data: {
|
||||
title: 'LLM_Group changed',
|
||||
color: 'Violet',
|
||||
},
|
||||
blocks: [
|
||||
{
|
||||
id: 'llm_0',
|
||||
type: 'llm',
|
||||
meta: {
|
||||
position: {
|
||||
x: 640,
|
||||
y: 0,
|
||||
},
|
||||
},
|
||||
data: {
|
||||
title: 'LLM_0 changed',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'llm_l_TcE',
|
||||
type: 'llm',
|
||||
meta: {
|
||||
position: {
|
||||
x: 180,
|
||||
y: 0,
|
||||
},
|
||||
},
|
||||
data: {
|
||||
title: 'LLM_1',
|
||||
},
|
||||
},
|
||||
],
|
||||
edges: [
|
||||
{
|
||||
sourceNodeID: 'llm_l_TcE',
|
||||
targetNodeID: 'llm_0',
|
||||
},
|
||||
{
|
||||
sourceNodeID: 'llm_0',
|
||||
targetNodeID: 'end_0',
|
||||
},
|
||||
{
|
||||
sourceNodeID: 'condition_0',
|
||||
targetNodeID: 'llm_l_TcE',
|
||||
sourcePortID: 'if_0',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
edges: [
|
||||
{
|
||||
sourceNodeID: 'start_0',
|
||||
targetNodeID: 'condition_0',
|
||||
},
|
||||
{
|
||||
sourceNodeID: 'condition_0',
|
||||
targetNodeID: 'llm_l_TcE',
|
||||
sourcePortID: 'if_0',
|
||||
},
|
||||
{
|
||||
sourceNodeID: 'condition_0',
|
||||
targetNodeID: 'loop_H8M3U',
|
||||
sourcePortID: 'if_f0rOAt',
|
||||
},
|
||||
{
|
||||
sourceNodeID: 'llm_0',
|
||||
targetNodeID: 'end_0',
|
||||
},
|
||||
{
|
||||
sourceNodeID: 'loop_H8M3U',
|
||||
targetNodeID: 'end_0',
|
||||
},
|
||||
],
|
||||
};
|
||||
export const mockSimpleJSON: WorkflowJSON = {
|
||||
nodes: [
|
||||
{
|
||||
id: 'start_0',
|
||||
type: 'start',
|
||||
meta: {
|
||||
position: { x: 0, y: 0 },
|
||||
},
|
||||
data: {
|
||||
title: 'start'
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'end_0',
|
||||
type: 'end',
|
||||
meta: {
|
||||
position: { x: 800, y: 0 },
|
||||
},
|
||||
data: {
|
||||
title: 'end'
|
||||
},
|
||||
},
|
||||
],
|
||||
edges: [
|
||||
{
|
||||
sourceNodeID: 'start_0',
|
||||
targetNodeID: 'end_0',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const mockSimpleJSON2: WorkflowJSON = {
|
||||
nodes: [
|
||||
{
|
||||
id: 'start_0',
|
||||
type: 'start',
|
||||
meta: {
|
||||
position: { x: 1, y: 1 },
|
||||
},
|
||||
data: {
|
||||
title: 'start changed'
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'end_0',
|
||||
type: 'end',
|
||||
meta: {
|
||||
position: { x: 801, y: 1 },
|
||||
},
|
||||
data: {
|
||||
title: 'end changed'
|
||||
},
|
||||
},
|
||||
],
|
||||
edges: [
|
||||
{
|
||||
sourceNodeID: 'start_0',
|
||||
targetNodeID: 'end_0',
|
||||
},
|
||||
],
|
||||
};
|
||||
@ -0,0 +1,87 @@
|
||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`free-layout-preset > custom fromNodeJSON and toNodeJSON 1`] = `
|
||||
{
|
||||
"edges": [
|
||||
{
|
||||
"sourceNodeID": "start_0",
|
||||
"targetNodeID": "end_0",
|
||||
},
|
||||
],
|
||||
"nodes": [
|
||||
{
|
||||
"data": {
|
||||
"isFirstCreate": true,
|
||||
"runningTimes": 1,
|
||||
"title": "start",
|
||||
},
|
||||
"id": "start_0",
|
||||
"meta": {
|
||||
"position": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
},
|
||||
},
|
||||
"type": "start",
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"isFirstCreate": true,
|
||||
"runningTimes": 1,
|
||||
"title": "end",
|
||||
},
|
||||
"id": "end_0",
|
||||
"meta": {
|
||||
"position": {
|
||||
"x": 800,
|
||||
"y": 0,
|
||||
},
|
||||
},
|
||||
"type": "end",
|
||||
},
|
||||
],
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`free-layout-preset > custom fromNodeJSON and toNodeJSON 2`] = `
|
||||
{
|
||||
"edges": [
|
||||
{
|
||||
"sourceNodeID": "start_0",
|
||||
"targetNodeID": "end_0",
|
||||
},
|
||||
],
|
||||
"nodes": [
|
||||
{
|
||||
"data": {
|
||||
"isFirstCreate": false,
|
||||
"runningTimes": 1,
|
||||
"title": "start changed",
|
||||
},
|
||||
"id": "start_0",
|
||||
"meta": {
|
||||
"position": {
|
||||
"x": 1,
|
||||
"y": 1,
|
||||
},
|
||||
},
|
||||
"type": "start",
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"isFirstCreate": false,
|
||||
"runningTimes": 1,
|
||||
"title": "end changed",
|
||||
},
|
||||
"id": "end_0",
|
||||
"meta": {
|
||||
"position": {
|
||||
"x": 801,
|
||||
"y": 1,
|
||||
},
|
||||
},
|
||||
"type": "end",
|
||||
},
|
||||
],
|
||||
}
|
||||
`;
|
||||
@ -0,0 +1,31 @@
|
||||
import { interfaces } from 'inversify';
|
||||
import {
|
||||
createPlaygroundContainer,
|
||||
Playground,
|
||||
loadPlugins,
|
||||
PluginContext,
|
||||
createPluginContextDefault,
|
||||
FlowDocument,
|
||||
} from '@flowgram.ai/editor';
|
||||
|
||||
import { FreeLayoutPluginContext, FreeLayoutProps, createFreeLayoutPreset } from '../src';
|
||||
|
||||
export function createEditor(opts: FreeLayoutProps): interfaces.Container {
|
||||
const container = createPlaygroundContainer();
|
||||
|
||||
const playground = container.get(Playground);
|
||||
const preset = createFreeLayoutPreset(opts);
|
||||
const customPluginContext = (container: interfaces.Container) =>
|
||||
({
|
||||
...createPluginContextDefault(container),
|
||||
get document(): FlowDocument {
|
||||
return container.get<FlowDocument>(FlowDocument);
|
||||
},
|
||||
} as FreeLayoutPluginContext);
|
||||
|
||||
const ctx = customPluginContext(container);
|
||||
container.rebind(PluginContext).toConstantValue(ctx);
|
||||
loadPlugins(preset(ctx), container);
|
||||
playground.init();
|
||||
return container;
|
||||
}
|
||||
@ -0,0 +1,65 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { FlowDocument, FlowNodeFormData } from '@flowgram.ai/editor';
|
||||
|
||||
import { mockJSON, mockJSON2, mockSimpleJSON, mockSimpleJSON2 } from '../__mocks__/flow.mocks';
|
||||
import { createEditor } from './create-editor';
|
||||
|
||||
describe('free-layout-preset', () => {
|
||||
it('fromJSON and toJSON', () => {
|
||||
const editor = createEditor({});
|
||||
const document = editor.get(FlowDocument);
|
||||
document.fromJSON(mockJSON);
|
||||
expect(document.toJSON()).toEqual(mockJSON);
|
||||
document.fromJSON(mockJSON2);
|
||||
expect(document.toJSON()).toEqual(mockJSON2);
|
||||
});
|
||||
it('custom fromNodeJSON and toNodeJSON', () => {
|
||||
const container = createEditor({
|
||||
fromNodeJSON: (node, json, isFirstCreate) => {
|
||||
if (!json.data) {
|
||||
json.data = {};
|
||||
}
|
||||
json.data = { ...json.data, isFirstCreate };
|
||||
return json;
|
||||
},
|
||||
toNodeJSON(node, json) {
|
||||
json.data.runningTimes = (json.data.runningTimes || 0) + 1;
|
||||
return json;
|
||||
},
|
||||
});
|
||||
container.get(FlowDocument).fromJSON(mockSimpleJSON);
|
||||
expect(container.get(FlowDocument).toJSON()).toMatchSnapshot();
|
||||
container.get(FlowDocument).fromJSON(mockSimpleJSON2);
|
||||
expect(container.get(FlowDocument).toJSON()).toMatchSnapshot();
|
||||
});
|
||||
it('nodeEngine(v2) toJSON', async () => {
|
||||
const container = createEditor({
|
||||
nodeEngine: {},
|
||||
nodeRegistries: [
|
||||
{
|
||||
type: 'start',
|
||||
formMeta: {
|
||||
render: () => undefined,
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'end',
|
||||
formMeta: {
|
||||
render: () => undefined,
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
const flowDocument = container.get(FlowDocument);
|
||||
flowDocument.fromJSON(mockSimpleJSON);
|
||||
expect(flowDocument.toJSON()).toEqual(mockSimpleJSON);
|
||||
flowDocument.fromJSON(mockSimpleJSON2);
|
||||
expect(flowDocument.toJSON()).toEqual(mockSimpleJSON2);
|
||||
const { formModel } = flowDocument.getNode('start_0').getData(FlowNodeFormData);
|
||||
expect(formModel.getFormItemByPath('title').value).toEqual('start changed');
|
||||
formModel.getFormItemByPath('title').value = 'start changed 2';
|
||||
expect(formModel.toJSON()).toEqual({
|
||||
title: 'start changed 2',
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -32,6 +32,7 @@ import {
|
||||
createPlaygroundReactPreset,
|
||||
} from '@flowgram.ai/editor';
|
||||
|
||||
import { fromNodeJSON, toNodeJSON } from './node-serialize';
|
||||
import { FreeLayoutProps, FreeLayoutPluginContext } from './free-layout-props';
|
||||
|
||||
const renderElement = (ctx: PluginContext) => {
|
||||
@ -162,8 +163,9 @@ export function createFreeLayoutPreset(
|
||||
cursors: opts.cursors ?? WorkflowDocumentOptionsDefault.cursors,
|
||||
lineColor: opts.lineColor ?? WorkflowDocumentOptionsDefault.lineColor,
|
||||
allNodesDefaultExpanded: opts.allNodesDefaultExpanded,
|
||||
toNodeJSON: opts.toNodeJSON,
|
||||
fromNodeJSON: opts.fromNodeJSON,
|
||||
toNodeJSON: (node) => toNodeJSON(opts, node),
|
||||
fromNodeJSON: (node, json, isFirstCreate) =>
|
||||
fromNodeJSON(opts, node, json, isFirstCreate),
|
||||
} as WorkflowDocumentOptions);
|
||||
},
|
||||
onInit: (ctx) => {
|
||||
|
||||
@ -5,9 +5,7 @@ import {
|
||||
LineRenderType,
|
||||
onDragLineEndParams,
|
||||
WorkflowContentChangeEvent,
|
||||
WorkflowContentChangeType,
|
||||
WorkflowDocument,
|
||||
WorkflowDocumentOptionsDefault,
|
||||
WorkflowJSON,
|
||||
WorkflowLineEntity,
|
||||
WorkflowLinePortInfo,
|
||||
@ -20,9 +18,6 @@ import {
|
||||
ClipboardService,
|
||||
EditorPluginContext,
|
||||
EditorProps,
|
||||
FlowNodeEntity,
|
||||
FlowNodeFormData,
|
||||
type FlowNodeJSON,
|
||||
SelectionService,
|
||||
PluginContext,
|
||||
} from '@flowgram.ai/editor';
|
||||
@ -203,41 +198,5 @@ export namespace FreeLayoutProps {
|
||||
*/
|
||||
export const DEFAULT: FreeLayoutProps = {
|
||||
...EditorProps.DEFAULT,
|
||||
fromNodeJSON(node: FlowNodeEntity, json: FlowNodeJSON) {
|
||||
const formData = node.getData(FlowNodeFormData)!;
|
||||
// 如果没有使用表单引擎,将 data 数据填入 extInfo
|
||||
if (!formData) {
|
||||
if (json.data) {
|
||||
node.updateExtInfo(json.data);
|
||||
}
|
||||
// extInfo 数据更新则触发内容更新
|
||||
node.onExtInfoChange(() => {
|
||||
(node.document as WorkflowDocument).fireContentChange({
|
||||
type: WorkflowContentChangeType.NODE_DATA_CHANGE,
|
||||
toJSON: () => node.getExtInfo(),
|
||||
entity: node,
|
||||
});
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
return WorkflowDocumentOptionsDefault.fromNodeJSON?.(node, json);
|
||||
},
|
||||
toNodeJSON(node: FlowNodeEntity): FlowNodeJSON {
|
||||
const formData = node.getData(FlowNodeFormData)!;
|
||||
const position = node.transform.position;
|
||||
// 不使用节点引擎则采用 extInfo
|
||||
if (!formData) {
|
||||
return {
|
||||
id: node.id,
|
||||
type: node.flowNodeType,
|
||||
meta: {
|
||||
position: { x: position.x, y: position.y },
|
||||
},
|
||||
data: node.getExtInfo(),
|
||||
};
|
||||
}
|
||||
return WorkflowDocumentOptionsDefault.toNodeJSON!(node);
|
||||
},
|
||||
} as FreeLayoutProps;
|
||||
}
|
||||
|
||||
@ -0,0 +1,57 @@
|
||||
import {
|
||||
WorkflowContentChangeType,
|
||||
WorkflowDocument,
|
||||
WorkflowDocumentOptionsDefault,
|
||||
} from '@flowgram.ai/free-layout-core';
|
||||
import { FlowNodeEntity, FlowNodeFormData, type FlowNodeJSON } from '@flowgram.ai/editor';
|
||||
|
||||
import { FreeLayoutProps } from './free-layout-props';
|
||||
|
||||
export function fromNodeJSON(
|
||||
opts: FreeLayoutProps,
|
||||
node: FlowNodeEntity,
|
||||
json: FlowNodeJSON,
|
||||
isFirstCreate: boolean
|
||||
) {
|
||||
json = opts.fromNodeJSON ? opts.fromNodeJSON(node, json, isFirstCreate) : json;
|
||||
const formData = node.getData(FlowNodeFormData)!;
|
||||
// 如果没有使用表单引擎,将 data 数据填入 extInfo
|
||||
if (!formData) {
|
||||
if (json.data) {
|
||||
node.updateExtInfo(json.data);
|
||||
}
|
||||
// extInfo 数据更新则触发内容更新
|
||||
if (isFirstCreate) {
|
||||
node.onExtInfoChange(() => {
|
||||
(node.document as WorkflowDocument).fireContentChange({
|
||||
type: WorkflowContentChangeType.NODE_DATA_CHANGE,
|
||||
toJSON: () => node.getExtInfo(),
|
||||
entity: node,
|
||||
});
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
return WorkflowDocumentOptionsDefault.fromNodeJSON?.(node, json, isFirstCreate);
|
||||
}
|
||||
|
||||
export function toNodeJSON(opts: FreeLayoutProps, node: FlowNodeEntity): FlowNodeJSON {
|
||||
const formData = node.getData(FlowNodeFormData)!;
|
||||
const position = node.transform.position;
|
||||
let json: FlowNodeJSON;
|
||||
// 不使用节点引擎则采用 extInfo
|
||||
if (!formData) {
|
||||
json = {
|
||||
id: node.id,
|
||||
type: node.flowNodeType,
|
||||
meta: {
|
||||
position: { x: position.x, y: position.y },
|
||||
},
|
||||
data: node.getExtInfo(),
|
||||
};
|
||||
} else {
|
||||
json = WorkflowDocumentOptionsDefault.toNodeJSON!(node);
|
||||
}
|
||||
return opts.toNodeJSON ? opts.toNodeJSON(node, json) : json;
|
||||
}
|
||||
@ -78,6 +78,10 @@ export class FlowNodeFormData extends EntityData {
|
||||
}
|
||||
}
|
||||
|
||||
updateFormValues(value: any) {
|
||||
this.formModel.updateFormValues(value);
|
||||
}
|
||||
|
||||
recreateForm(formMetaOrFormMetaGenerator: FormMetaOrFormMetaGenerator, initialValue?: any): void {
|
||||
this.createForm(formMetaOrFormMetaGenerator, initialValue);
|
||||
}
|
||||
|
||||
@ -39,6 +39,8 @@ export abstract class FormModel {
|
||||
|
||||
abstract get valid(): FormModelValid;
|
||||
|
||||
abstract updateFormValues(value: any): void;
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* use `formModel.getFieldIn` instead in FormModelV2 to get the model of a form field
|
||||
|
||||
@ -145,6 +145,12 @@ export class FormModelV2 extends FormModel implements Disposable {
|
||||
return this._feedbacks;
|
||||
}
|
||||
|
||||
updateFormValues(value: any) {
|
||||
if (this.nativeFormModel) {
|
||||
this.nativeFormModel.values = value;
|
||||
}
|
||||
}
|
||||
|
||||
private set feedbacks(feedbacks: FormFeedback[]) {
|
||||
this._feedbacks = feedbacks;
|
||||
this.onFeedbacksChangeEmitter.fire(feedbacks);
|
||||
|
||||
@ -33,6 +33,10 @@ export interface NodeFormProps<TValues> {
|
||||
* @param name path
|
||||
*/
|
||||
setValueIn<TValue>(name: FieldName, value: TValue): void;
|
||||
/**
|
||||
* set form values
|
||||
*/
|
||||
updateFormValues(values: any): void;
|
||||
/**
|
||||
* Render form
|
||||
*/
|
||||
@ -75,9 +79,13 @@ export function getNodeForm<TValues = FieldValue>(
|
||||
get values() {
|
||||
return nativeFormModel.values;
|
||||
},
|
||||
|
||||
state: nativeFormModel.state,
|
||||
getValueIn: (name: FieldName) => nativeFormModel.getValueIn(name),
|
||||
setValueIn: (name: FieldName, value: any) => nativeFormModel.setValueIn(name, value),
|
||||
updateFormValues: (values: any) => {
|
||||
formModel.updateFormValues(values);
|
||||
},
|
||||
render: () => <NodeRender node={node} />,
|
||||
onFormValuesChange: formModel.onFormValuesChange.bind(formModel),
|
||||
onFormValueChangeIn: formModel.onFormValueChangeIn.bind(formModel),
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user