@flowgram-vue/fixed-layout-editor 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +22 -0
- package/dist/index.cjs +0 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +0 -0
- package/dist/index.js.map +1 -0
- package/index.css +176 -0
- package/package.json +71 -0
- package/src/components/fixed-layout-editor-provider.vue +102 -0
- package/src/components/fixed-layout-editor.vue +54 -0
- package/src/components/index.ts +7 -0
- package/src/env.d.ts +10 -0
- package/src/hooks/use-client-context.ts +12 -0
- package/src/hooks/use-node-render.ts +222 -0
- package/src/hooks/use-playground-tools.ts +192 -0
- package/src/index.ts +23 -0
- package/src/plugins/create-operation-plugin.ts +23 -0
- package/src/preset/fixed-layout-preset.ts +184 -0
- package/src/preset/fixed-layout-props.ts +84 -0
- package/src/preset/index.ts +7 -0
- package/src/preset/node-serialize.ts +72 -0
- package/src/services/flow-operation-service.ts +59 -0
- package/src/services/history-operation-service.ts +125 -0
- package/src/types.ts +40 -0
- package/src/utils/compose.ts +18 -0
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { createSelectBoxPlugin } from '@flowgram-vue/select-box-plugin';
|
|
7
|
+
import { FixedLayoutContainerModule } from '@flowgram-vue/fixed-layout-core';
|
|
8
|
+
import { FixedHistoryService, createFixedHistoryPlugin } from '@flowgram-vue/fixed-history-plugin';
|
|
9
|
+
import { createFixedDragPlugin } from '@flowgram-vue/fixed-drag-plugin';
|
|
10
|
+
import {
|
|
11
|
+
PluginsProvider,
|
|
12
|
+
createDefaultPreset,
|
|
13
|
+
createVariablePlugin,
|
|
14
|
+
createPlaygroundPlugin,
|
|
15
|
+
createShortcutsPlugin,
|
|
16
|
+
SelectionService,
|
|
17
|
+
Command,
|
|
18
|
+
Plugin,
|
|
19
|
+
FlowDocument,
|
|
20
|
+
FlowNodeEntity,
|
|
21
|
+
FlowDocumentOptionsDefault,
|
|
22
|
+
FlowDocumentOptions,
|
|
23
|
+
FlowNodesContentLayer,
|
|
24
|
+
FlowNodesTransformLayer,
|
|
25
|
+
FlowScrollBarLayer,
|
|
26
|
+
FlowScrollLimitLayer,
|
|
27
|
+
createPlaygroundVuePreset,
|
|
28
|
+
} from '@flowgram-vue/editor';
|
|
29
|
+
|
|
30
|
+
import { compose } from '../utils/compose';
|
|
31
|
+
import { FlowOperationService } from '../types';
|
|
32
|
+
import { createOperationPlugin } from '../plugins/create-operation-plugin';
|
|
33
|
+
import { fromNodeJSON, toNodeJSON } from './node-serialize';
|
|
34
|
+
import { FixedLayoutPluginContext, FixedLayoutProps } from './fixed-layout-props';
|
|
35
|
+
|
|
36
|
+
export function createFixedLayoutPreset(
|
|
37
|
+
opts: FixedLayoutProps
|
|
38
|
+
): PluginsProvider<FixedLayoutPluginContext> {
|
|
39
|
+
return (ctx: FixedLayoutPluginContext) => {
|
|
40
|
+
opts = { ...FixedLayoutProps.DEFAULT, ...opts };
|
|
41
|
+
let plugins: Plugin[] = [createOperationPlugin(opts)];
|
|
42
|
+
/**
|
|
43
|
+
* 注册默认的快捷键
|
|
44
|
+
*/
|
|
45
|
+
plugins.push(
|
|
46
|
+
createShortcutsPlugin({
|
|
47
|
+
registerShortcuts(registry) {
|
|
48
|
+
const selection = ctx.get<SelectionService>(SelectionService);
|
|
49
|
+
registry.addHandlers({
|
|
50
|
+
commandId: Command.Default.DELETE,
|
|
51
|
+
shortcuts: ['backspace', 'delete'],
|
|
52
|
+
isEnabled: () =>
|
|
53
|
+
selection.selection.length > 0 && !ctx.playground.config.readonlyOrDisabled,
|
|
54
|
+
execute: () => {
|
|
55
|
+
// TODO 这里要判断 CurrentEditor
|
|
56
|
+
const nodes = selection.selection.filter(
|
|
57
|
+
(entity) => entity instanceof FlowNodeEntity
|
|
58
|
+
) as FlowNodeEntity[];
|
|
59
|
+
|
|
60
|
+
const flowOperationService = ctx.get<FlowOperationService>(FlowOperationService);
|
|
61
|
+
flowOperationService.deleteNodes(nodes);
|
|
62
|
+
selection.selection = selection.selection.filter((s) => !s.disposed);
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
if (opts?.history?.enable) {
|
|
67
|
+
const fixedHistoryService = ctx.get<FixedHistoryService>(FixedHistoryService);
|
|
68
|
+
|
|
69
|
+
if (!opts.history.disableShortcuts) {
|
|
70
|
+
registry.addHandlers({
|
|
71
|
+
commandId: Command.Default.UNDO,
|
|
72
|
+
shortcuts: ['meta z', 'ctrl z'],
|
|
73
|
+
isEnabled: () => true,
|
|
74
|
+
execute: () => {
|
|
75
|
+
fixedHistoryService.undo();
|
|
76
|
+
},
|
|
77
|
+
});
|
|
78
|
+
registry.addHandlers({
|
|
79
|
+
commandId: Command.Default.REDO,
|
|
80
|
+
shortcuts: ['meta shift z', 'ctrl shift z'],
|
|
81
|
+
isEnabled: () => true,
|
|
82
|
+
execute: () => {
|
|
83
|
+
fixedHistoryService.redo();
|
|
84
|
+
},
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
}),
|
|
90
|
+
/**
|
|
91
|
+
* 圈选逻辑实现
|
|
92
|
+
*/
|
|
93
|
+
createSelectBoxPlugin({
|
|
94
|
+
canSelect: (e) =>
|
|
95
|
+
// 需满足以下条件:
|
|
96
|
+
// - 鼠标左键
|
|
97
|
+
e.button === 0 &&
|
|
98
|
+
!(ctx.get(FlowDocument) as FlowDocument).renderState.config.nodeHoveredId,
|
|
99
|
+
...(opts.selectBox || {}),
|
|
100
|
+
}),
|
|
101
|
+
/**
|
|
102
|
+
* 固定布局拖拽逻辑实现
|
|
103
|
+
*/
|
|
104
|
+
createFixedDragPlugin((opts.dragdrop || {}) as Parameters<typeof createFixedDragPlugin>[0])
|
|
105
|
+
);
|
|
106
|
+
/**
|
|
107
|
+
* 加载默认编辑器配置
|
|
108
|
+
*/
|
|
109
|
+
plugins = createDefaultPreset(opts, plugins)(ctx);
|
|
110
|
+
/**
|
|
111
|
+
* 注册 变量系统
|
|
112
|
+
*/
|
|
113
|
+
if (opts.variableEngine?.enable) {
|
|
114
|
+
plugins.push(
|
|
115
|
+
createVariablePlugin({
|
|
116
|
+
...opts.variableEngine,
|
|
117
|
+
layout: 'fixed',
|
|
118
|
+
})
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* 注册 历史记录
|
|
123
|
+
*/
|
|
124
|
+
if (opts.history?.enable) {
|
|
125
|
+
plugins.push(createFixedHistoryPlugin(opts.history as Parameters<typeof createFixedHistoryPlugin>[0]));
|
|
126
|
+
}
|
|
127
|
+
/*
|
|
128
|
+
* 加载固定布局画布模块
|
|
129
|
+
* */
|
|
130
|
+
plugins.push(
|
|
131
|
+
createPlaygroundPlugin<FixedLayoutPluginContext>({
|
|
132
|
+
containerModules: [FixedLayoutContainerModule],
|
|
133
|
+
onBind(bindConfig) {
|
|
134
|
+
if (!bindConfig.isBound(FlowDocumentOptions)) {
|
|
135
|
+
bindConfig.bind(FlowDocumentOptions).toConstantValue({
|
|
136
|
+
...FlowDocumentOptionsDefault,
|
|
137
|
+
defaultLayout: opts.defaultLayout,
|
|
138
|
+
toNodeJSON: (node) => toNodeJSON(opts, node),
|
|
139
|
+
fromNodeJSON: (node, json, isFirstCreate) =>
|
|
140
|
+
fromNodeJSON(opts, node, json, isFirstCreate),
|
|
141
|
+
allNodesDefaultExpanded: opts.allNodesDefaultExpanded,
|
|
142
|
+
} as FlowDocumentOptions);
|
|
143
|
+
}
|
|
144
|
+
},
|
|
145
|
+
onInit: (ctx) => {
|
|
146
|
+
ctx.playground.registerLayers(
|
|
147
|
+
FlowNodesContentLayer, // 节点内容渲染
|
|
148
|
+
FlowNodesTransformLayer // 节点位置偏移计算
|
|
149
|
+
);
|
|
150
|
+
// 劫持节点线条
|
|
151
|
+
if (opts.formatNodeLines) {
|
|
152
|
+
ctx.document.options.formatNodeLines = compose([
|
|
153
|
+
ctx.document.options.formatNodeLines,
|
|
154
|
+
opts.formatNodeLines,
|
|
155
|
+
]);
|
|
156
|
+
}
|
|
157
|
+
// 劫持节点 label
|
|
158
|
+
if (opts.formatNodeLabels) {
|
|
159
|
+
ctx.document.options.formatNodeLabels = compose([
|
|
160
|
+
ctx.document.options.formatNodeLabels,
|
|
161
|
+
opts.formatNodeLabels,
|
|
162
|
+
]);
|
|
163
|
+
}
|
|
164
|
+
if (opts.scroll?.enableScrollLimit) {
|
|
165
|
+
// 控制滚动范围
|
|
166
|
+
ctx.playground.registerLayer(FlowScrollLimitLayer);
|
|
167
|
+
}
|
|
168
|
+
if (!opts.scroll?.disableScrollBar) {
|
|
169
|
+
// 控制条
|
|
170
|
+
ctx.playground.registerLayer(FlowScrollBarLayer);
|
|
171
|
+
}
|
|
172
|
+
if (opts.scroll?.disableScroll) {
|
|
173
|
+
ctx.playground.config.scrollDisable = true;
|
|
174
|
+
}
|
|
175
|
+
if (opts.nodeRegistries) {
|
|
176
|
+
ctx.document.registerFlowNodes(...opts.nodeRegistries);
|
|
177
|
+
}
|
|
178
|
+
},
|
|
179
|
+
})
|
|
180
|
+
);
|
|
181
|
+
|
|
182
|
+
return createPlaygroundVuePreset(opts, plugins)(ctx);
|
|
183
|
+
};
|
|
184
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { SelectBoxPluginOptions } from '@flowgram-vue/select-box-plugin';
|
|
7
|
+
import { FixedHistoryPluginOptions, HistoryService } from '@flowgram-vue/fixed-history-plugin';
|
|
8
|
+
import { type FixDragPluginOptions } from '@flowgram-vue/fixed-drag-plugin';
|
|
9
|
+
import {
|
|
10
|
+
ClipboardService,
|
|
11
|
+
EditorPluginContext,
|
|
12
|
+
EditorProps,
|
|
13
|
+
FlowDocument,
|
|
14
|
+
FlowDocumentJSON,
|
|
15
|
+
FlowLayoutDefault,
|
|
16
|
+
SelectionService,
|
|
17
|
+
PluginContext,
|
|
18
|
+
FlowNodeEntity,
|
|
19
|
+
FlowTransitionLine,
|
|
20
|
+
FlowTransitionLabel,
|
|
21
|
+
} from '@flowgram-vue/editor';
|
|
22
|
+
|
|
23
|
+
import { FlowOperationService } from '../types';
|
|
24
|
+
|
|
25
|
+
export const FixedLayoutPluginContext = PluginContext;
|
|
26
|
+
|
|
27
|
+
export interface FixedLayoutPluginTools {
|
|
28
|
+
fitView: (easing?: boolean) => Promise<void>;
|
|
29
|
+
}
|
|
30
|
+
export interface FixedLayoutPluginContext extends EditorPluginContext {
|
|
31
|
+
document: FlowDocument;
|
|
32
|
+
/**
|
|
33
|
+
* 提供对画布节点相关操作方法, 并 支持 redo/undo
|
|
34
|
+
*/
|
|
35
|
+
operation: FlowOperationService;
|
|
36
|
+
clipboard: ClipboardService;
|
|
37
|
+
selection: SelectionService;
|
|
38
|
+
history: HistoryService;
|
|
39
|
+
tools: FixedLayoutPluginTools;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* 固定布局配置
|
|
44
|
+
*/
|
|
45
|
+
export interface FixedLayoutProps extends EditorProps<FixedLayoutPluginContext, FlowDocumentJSON> {
|
|
46
|
+
/**
|
|
47
|
+
* SelectBox config
|
|
48
|
+
*/
|
|
49
|
+
selectBox?: SelectBoxPluginOptions;
|
|
50
|
+
/**
|
|
51
|
+
* Drag/Drop config
|
|
52
|
+
*/
|
|
53
|
+
dragdrop?: FixDragPluginOptions<FixedLayoutPluginContext>;
|
|
54
|
+
/**
|
|
55
|
+
* Redo/Undo enable
|
|
56
|
+
*/
|
|
57
|
+
history?: FixedHistoryPluginOptions<FixedLayoutPluginContext> & { disableShortcuts?: boolean };
|
|
58
|
+
/**
|
|
59
|
+
* vertical or horizontal layout
|
|
60
|
+
*/
|
|
61
|
+
defaultLayout?: FlowLayoutDefault | string; // 默认布局
|
|
62
|
+
/**
|
|
63
|
+
* Customize the node line
|
|
64
|
+
* 自定义节点线条
|
|
65
|
+
*/
|
|
66
|
+
formatNodeLines?: (node: FlowNodeEntity, lines: FlowTransitionLine[]) => FlowTransitionLine[];
|
|
67
|
+
/**
|
|
68
|
+
* Custom node label
|
|
69
|
+
* 自定义节点 label
|
|
70
|
+
*/
|
|
71
|
+
formatNodeLabels?: (node: FlowNodeEntity, lines: FlowTransitionLabel[]) => FlowTransitionLabel[];
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export namespace FixedLayoutProps {
|
|
75
|
+
/**
|
|
76
|
+
* 默认配置
|
|
77
|
+
*/
|
|
78
|
+
export const DEFAULT: FixedLayoutProps = {
|
|
79
|
+
...EditorProps.DEFAULT,
|
|
80
|
+
scroll: {
|
|
81
|
+
enableScrollLimit: true,
|
|
82
|
+
},
|
|
83
|
+
} as FixedLayoutProps;
|
|
84
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { FlowNodeEntity, FlowNodeJSON, FlowNodeFormData } from '@flowgram-vue/editor';
|
|
7
|
+
|
|
8
|
+
import { FixedLayoutProps } from './fixed-layout-props';
|
|
9
|
+
|
|
10
|
+
export function fromNodeJSON(
|
|
11
|
+
opts: FixedLayoutProps,
|
|
12
|
+
node: FlowNodeEntity,
|
|
13
|
+
json: FlowNodeJSON,
|
|
14
|
+
isFirstCreate: boolean
|
|
15
|
+
) {
|
|
16
|
+
json = opts.fromNodeJSON ? opts.fromNodeJSON(node, json, isFirstCreate) : json;
|
|
17
|
+
const formData = node.getData(FlowNodeFormData)!;
|
|
18
|
+
// 如果没有使用表单引擎,将 data 数据填入 extInfo
|
|
19
|
+
if (!formData) {
|
|
20
|
+
if (json.data) {
|
|
21
|
+
node.updateExtInfo(json.data, true);
|
|
22
|
+
}
|
|
23
|
+
} else {
|
|
24
|
+
const defaultFormMeta = opts.nodeEngine?.createDefaultFormMeta?.(node);
|
|
25
|
+
|
|
26
|
+
const formMeta = node.getNodeRegistry()?.formMeta || defaultFormMeta;
|
|
27
|
+
|
|
28
|
+
if (formMeta) {
|
|
29
|
+
if (isFirstCreate) {
|
|
30
|
+
formData.createForm(formMeta, json.data);
|
|
31
|
+
} else {
|
|
32
|
+
formData.updateFormValues(json.data);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function toNodeJSON(opts: FixedLayoutProps, node: FlowNodeEntity): FlowNodeJSON {
|
|
39
|
+
const nodesMap: Record<string, FlowNodeJSON> = {};
|
|
40
|
+
let startNodeJSON: FlowNodeJSON;
|
|
41
|
+
node.document.traverse((node) => {
|
|
42
|
+
const isSystemNode = node.id.startsWith('$');
|
|
43
|
+
if (isSystemNode) return;
|
|
44
|
+
const formData = node.getData(FlowNodeFormData);
|
|
45
|
+
let formJSON =
|
|
46
|
+
formData && formData.formModel && formData.formModel.initialized
|
|
47
|
+
? formData.toJSON()
|
|
48
|
+
: undefined;
|
|
49
|
+
let nodeJSON: FlowNodeJSON = {
|
|
50
|
+
id: node.id,
|
|
51
|
+
type: node.flowNodeType,
|
|
52
|
+
data: formData ? formJSON : node.getExtInfo(),
|
|
53
|
+
blocks: [],
|
|
54
|
+
};
|
|
55
|
+
if (opts.toNodeJSON) {
|
|
56
|
+
nodeJSON = opts.toNodeJSON(node, nodeJSON);
|
|
57
|
+
}
|
|
58
|
+
if (!startNodeJSON) startNodeJSON = nodeJSON;
|
|
59
|
+
let { parent } = node;
|
|
60
|
+
if (parent && parent.id.startsWith('$')) {
|
|
61
|
+
parent = parent.originParent;
|
|
62
|
+
}
|
|
63
|
+
const parentJSON = parent ? nodesMap[parent.id] : undefined;
|
|
64
|
+
if (parentJSON) {
|
|
65
|
+
parentJSON.blocks?.push(nodeJSON);
|
|
66
|
+
}
|
|
67
|
+
nodesMap[node.id] = nodeJSON;
|
|
68
|
+
}, node);
|
|
69
|
+
|
|
70
|
+
// @ts-ignore
|
|
71
|
+
return startNodeJSON;
|
|
72
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { inject, injectable } from 'inversify';
|
|
7
|
+
import {
|
|
8
|
+
FlowGroupService,
|
|
9
|
+
FlowNodeEntity,
|
|
10
|
+
FlowNodeEntityOrId,
|
|
11
|
+
FlowNodeFormData,
|
|
12
|
+
FlowOperationBaseServiceImpl,
|
|
13
|
+
FormModel,
|
|
14
|
+
FormModelV2,
|
|
15
|
+
isFormModelV2,
|
|
16
|
+
} from '@flowgram-vue/editor';
|
|
17
|
+
|
|
18
|
+
import { FlowOperationService } from '../types';
|
|
19
|
+
|
|
20
|
+
@injectable()
|
|
21
|
+
export class FlowOperationServiceImpl
|
|
22
|
+
extends FlowOperationBaseServiceImpl
|
|
23
|
+
implements FlowOperationService
|
|
24
|
+
{
|
|
25
|
+
@inject(FlowGroupService)
|
|
26
|
+
protected groupService: FlowGroupService;
|
|
27
|
+
|
|
28
|
+
createGroup(nodes: FlowNodeEntity[]): FlowNodeEntity | undefined {
|
|
29
|
+
return this.groupService.createGroup(nodes);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
ungroup(groupNode: FlowNodeEntity): void {
|
|
33
|
+
return this.groupService.ungroup(groupNode);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
setFormValue(nodeOrId: FlowNodeEntityOrId, path: string, value: unknown): void {
|
|
37
|
+
const node = this.toNodeEntity(nodeOrId);
|
|
38
|
+
const formModel = node?.getData(FlowNodeFormData)?.getFormModel<FormModel | FormModelV2>();
|
|
39
|
+
|
|
40
|
+
if (!formModel) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (isFormModelV2(formModel)) {
|
|
45
|
+
(formModel as FormModelV2).setValueIn(path, value);
|
|
46
|
+
} else {
|
|
47
|
+
const formItem = (formModel as FormModel).getFormItemByPath(path);
|
|
48
|
+
|
|
49
|
+
if (!formItem) {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
formItem.value = value;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
startTransaction(): void {}
|
|
57
|
+
|
|
58
|
+
endTransaction(): void {}
|
|
59
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { inject, injectable, postConstruct } from 'inversify';
|
|
7
|
+
import { HistoryService } from '@flowgram-vue/history';
|
|
8
|
+
import { FixedHistoryService } from '@flowgram-vue/fixed-history-plugin';
|
|
9
|
+
import {
|
|
10
|
+
AddBlockConfig,
|
|
11
|
+
AddOrDeleteNodeValue,
|
|
12
|
+
FlowDocument,
|
|
13
|
+
FlowNodeEntity,
|
|
14
|
+
FlowNodeEntityOrId,
|
|
15
|
+
FlowNodeJSON,
|
|
16
|
+
FlowOperation,
|
|
17
|
+
MoveChildNodesOperationValue,
|
|
18
|
+
OnNodeAddEvent,
|
|
19
|
+
OperationType,
|
|
20
|
+
} from '@flowgram-vue/editor';
|
|
21
|
+
|
|
22
|
+
import { FlowOperationService } from '../types';
|
|
23
|
+
import { FlowOperationServiceImpl } from './flow-operation-service';
|
|
24
|
+
|
|
25
|
+
@injectable()
|
|
26
|
+
export class HistoryOperationServiceImpl
|
|
27
|
+
extends FlowOperationServiceImpl
|
|
28
|
+
implements FlowOperationService
|
|
29
|
+
{
|
|
30
|
+
@inject(FixedHistoryService)
|
|
31
|
+
protected fixedHistoryService: FixedHistoryService;
|
|
32
|
+
|
|
33
|
+
@inject(HistoryService)
|
|
34
|
+
protected historyService: HistoryService;
|
|
35
|
+
|
|
36
|
+
@inject(FlowDocument)
|
|
37
|
+
protected document: FlowDocument;
|
|
38
|
+
|
|
39
|
+
@postConstruct()
|
|
40
|
+
protected init() {
|
|
41
|
+
this.toDispose.push(this.onNodeAdd(this.handleNodeAdd.bind(this)));
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
addFromNode(fromNode: FlowNodeEntityOrId, nodeJSON: FlowNodeJSON): FlowNodeEntity {
|
|
45
|
+
return this.fixedHistoryService.addFromNode(fromNode, nodeJSON);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
addBlock(
|
|
49
|
+
target: FlowNodeEntityOrId,
|
|
50
|
+
blockJSON: FlowNodeJSON,
|
|
51
|
+
config: AddBlockConfig = {}
|
|
52
|
+
): FlowNodeEntity {
|
|
53
|
+
const { parent, index } = config;
|
|
54
|
+
return this.fixedHistoryService.addBlock(target, blockJSON, parent, index);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
deleteNode(nodeOrId: FlowNodeEntityOrId): void {
|
|
58
|
+
const node = this.toNodeEntity(nodeOrId);
|
|
59
|
+
if (!node) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
this.fixedHistoryService.deleteNode(node);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
deleteNodes(nodes: FlowNodeEntityOrId[]): void {
|
|
66
|
+
const nodesEntities = nodes.map((node) =>
|
|
67
|
+
typeof node === 'string' ? this.document.getNode(node) : node
|
|
68
|
+
) as FlowNodeEntity[];
|
|
69
|
+
return this.fixedHistoryService.deleteNodes(nodesEntities);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
startTransaction(): void {
|
|
73
|
+
this.historyService.startTransaction();
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
endTransaction(): void {
|
|
77
|
+
this.historyService.endTransaction();
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
apply(operation: FlowOperation) {
|
|
81
|
+
this.historyService.pushOperation(operation);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
protected doMoveNode(node: FlowNodeEntity, newParent: FlowNodeEntity, index: number) {
|
|
85
|
+
const fromParentId = node.parent?.id;
|
|
86
|
+
|
|
87
|
+
if (!fromParentId) {
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const value: MoveChildNodesOperationValue = {
|
|
92
|
+
nodeIds: [this.toId(node)],
|
|
93
|
+
fromParentId: node.parent.id,
|
|
94
|
+
toParentId: this.toId(newParent),
|
|
95
|
+
fromIndex: this.getNodeIndex(node),
|
|
96
|
+
toIndex: index,
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
return this.historyService.pushOperation({
|
|
100
|
+
type: OperationType.moveChildNodes,
|
|
101
|
+
value,
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
protected handleNodeAdd({ data: addNodeData }: OnNodeAddEvent): FlowNodeEntity {
|
|
106
|
+
const { parent, index, hidden, originParent, ...nodeJSON } = addNodeData;
|
|
107
|
+
const value: AddOrDeleteNodeValue = {
|
|
108
|
+
data: nodeJSON,
|
|
109
|
+
parentId: parent?.id,
|
|
110
|
+
index,
|
|
111
|
+
hidden,
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
return this.historyService.pushOperation(
|
|
115
|
+
{
|
|
116
|
+
type: OperationType.addNode,
|
|
117
|
+
value: value,
|
|
118
|
+
uri: this.fixedHistoryService.config.getNodeURI(nodeJSON.id),
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
noApply: true,
|
|
122
|
+
}
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
FlowNodeEntity,
|
|
8
|
+
FlowNodeEntityOrId,
|
|
9
|
+
FlowOperationBaseService,
|
|
10
|
+
} from '@flowgram-vue/editor';
|
|
11
|
+
|
|
12
|
+
export interface FlowOperationService extends FlowOperationBaseService {
|
|
13
|
+
/**
|
|
14
|
+
* 创建分组
|
|
15
|
+
* @param nodes 节点列表
|
|
16
|
+
*/
|
|
17
|
+
createGroup(nodes: FlowNodeEntity[]): FlowNodeEntity | undefined;
|
|
18
|
+
/**
|
|
19
|
+
* 取消分组
|
|
20
|
+
* @param groupNode
|
|
21
|
+
*/
|
|
22
|
+
ungroup(groupNode: FlowNodeEntity): void;
|
|
23
|
+
/**
|
|
24
|
+
* 开始事务
|
|
25
|
+
*/
|
|
26
|
+
startTransaction(): void;
|
|
27
|
+
/**
|
|
28
|
+
* 结束事务
|
|
29
|
+
*/
|
|
30
|
+
endTransaction(): void;
|
|
31
|
+
/**
|
|
32
|
+
* 修改表单数据
|
|
33
|
+
* @param node 节点
|
|
34
|
+
* @param path 属性路径
|
|
35
|
+
* @param value 值
|
|
36
|
+
*/
|
|
37
|
+
setFormValue(node: FlowNodeEntityOrId, path: string, value: unknown): void;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export const FlowOperationService = Symbol('FlowOperationService');
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { FlowNodeEntity } from '@flowgram-vue/editor';
|
|
7
|
+
|
|
8
|
+
export type ComposeListItem<T> = (node: FlowNodeEntity, data: T[]) => T[];
|
|
9
|
+
|
|
10
|
+
export const compose =
|
|
11
|
+
<T>(fnList: (ComposeListItem<T> | undefined)[]) =>
|
|
12
|
+
(node: FlowNodeEntity, data: T[]): T[] => {
|
|
13
|
+
const list = fnList.filter(Boolean) as ComposeListItem<T>[];
|
|
14
|
+
if (!list.length) {
|
|
15
|
+
return data;
|
|
16
|
+
}
|
|
17
|
+
return list.reduce((acc: T[], fn) => fn(node, acc), data);
|
|
18
|
+
};
|