@flowgram-vue/renderer 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 +2933 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +721 -0
- package/dist/index.js +2918 -0
- package/dist/index.js.map +1 -0
- package/index.module.less +167 -0
- package/package.json +64 -0
- package/src/components/Adder.ts +107 -0
- package/src/components/BranchDraggableRenderer.ts +77 -0
- package/src/components/Collapse.ts +87 -0
- package/src/components/CollapseAdder.ts +94 -0
- package/src/components/CustomLine.ts +35 -0
- package/src/components/LabelsRenderer.ts +162 -0
- package/src/components/LinesRenderer.ts +99 -0
- package/src/components/MarkerActivatedArrow.ts +44 -0
- package/src/components/MarkerArrow.ts +44 -0
- package/src/components/RoundedTurningLine.ts +165 -0
- package/src/components/StraightLine.ts +31 -0
- package/src/components/utils.ts +295 -0
- package/src/entities/README.md +3 -0
- package/src/entities/flow-drag-entity.ts +267 -0
- package/src/entities/flow-select-config-entity.ts +114 -0
- package/src/entities/index.ts +8 -0
- package/src/entities/selector-box-config-entity.ts +88 -0
- package/src/env.d.ts +10 -0
- package/src/flow-renderer-container-module.ts +14 -0
- package/src/flow-renderer-contribution.ts +12 -0
- package/src/flow-renderer-registry.ts +155 -0
- package/src/flow-renderer-resize-observer.ts +56 -0
- package/src/hooks/use-base-color.ts +26 -0
- package/src/index.ts +16 -0
- package/src/layer-vue-provide.ts +44 -0
- package/src/layers/flow-context-menu-layer.ts +153 -0
- package/src/layers/flow-debug-layer.ts +227 -0
- package/src/layers/flow-drag-layer.ts +413 -0
- package/src/layers/flow-labels-layer.ts +100 -0
- package/src/layers/flow-lines-layer.ts +111 -0
- package/src/layers/flow-nodes-content-layer.ts +155 -0
- package/src/layers/flow-nodes-transform-layer.ts +151 -0
- package/src/layers/flow-scroll-bar-layer.ts +401 -0
- package/src/layers/flow-scroll-limit-layer.ts +36 -0
- package/src/layers/flow-selector-bounds-layer.ts +191 -0
- package/src/layers/flow-selector-box-layer.ts +244 -0
- package/src/layers/index.ts +16 -0
- package/src/utils/element.ts +36 -0
- package/src/utils/find-selected-nodes.ts +80 -0
- package/src/utils/index.ts +7 -0
- package/src/utils/scroll-bar-events.ts +13 -0
- package/src/utils/scroll-limit.ts +58 -0
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
PositionSchema,
|
|
8
|
+
SizeSchema,
|
|
9
|
+
ConfigEntity,
|
|
10
|
+
PlaygroundDragEvent,
|
|
11
|
+
} from '@flowgram-vue/core';
|
|
12
|
+
import { Rectangle } from '@flowgram-vue/utils';
|
|
13
|
+
|
|
14
|
+
export interface SelectorBoxConfigData extends PlaygroundDragEvent {
|
|
15
|
+
disabled?: boolean; // 是否禁用选择框
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* 选择框配置
|
|
20
|
+
*/
|
|
21
|
+
export class SelectorBoxConfigEntity extends ConfigEntity<SelectorBoxConfigData> {
|
|
22
|
+
static type = 'SelectorBoxConfigEntity';
|
|
23
|
+
|
|
24
|
+
get dragInfo(): PlaygroundDragEvent {
|
|
25
|
+
return this.config;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
setDragInfo(info: PlaygroundDragEvent): void {
|
|
29
|
+
this.updateConfig(info);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
get disabled(): boolean {
|
|
33
|
+
return this.config && !!this.config.disabled;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
set disabled(disabled: boolean) {
|
|
37
|
+
this.updateConfig({
|
|
38
|
+
disabled,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
get isStart(): boolean {
|
|
43
|
+
return this.dragInfo.isStart;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
get isMoving(): boolean {
|
|
47
|
+
return this.dragInfo.isMoving;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
get position(): PositionSchema {
|
|
51
|
+
const { dragInfo } = this;
|
|
52
|
+
return {
|
|
53
|
+
x: dragInfo.startPos.x < dragInfo.endPos.x ? dragInfo.startPos.x : dragInfo.endPos.x,
|
|
54
|
+
y: dragInfo.startPos.y < dragInfo.endPos.y ? dragInfo.startPos.y : dragInfo.endPos.y,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
get size(): SizeSchema {
|
|
59
|
+
const { dragInfo } = this;
|
|
60
|
+
return {
|
|
61
|
+
width: Math.abs(dragInfo.startPos.x - dragInfo.endPos.x),
|
|
62
|
+
height: Math.abs(dragInfo.startPos.y - dragInfo.endPos.y),
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
get collapsed(): boolean {
|
|
67
|
+
const { size } = this;
|
|
68
|
+
return size.width === 0 && size.height === 0;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
collapse(): void {
|
|
72
|
+
this.setDragInfo({
|
|
73
|
+
...this.dragInfo,
|
|
74
|
+
isMoving: false,
|
|
75
|
+
isStart: false,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
toRectangle(scale: number): Rectangle {
|
|
80
|
+
const { position, size } = this;
|
|
81
|
+
return new Rectangle(
|
|
82
|
+
position.x / scale,
|
|
83
|
+
position.y / scale,
|
|
84
|
+
size.width / scale,
|
|
85
|
+
size.height / scale,
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
}
|
package/src/env.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
declare module '*.vue' {
|
|
7
|
+
import type { DefineComponent } from 'vue';
|
|
8
|
+
const component: DefineComponent<object, object, unknown>;
|
|
9
|
+
export default component;
|
|
10
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { ContainerModule } from 'inversify';
|
|
7
|
+
|
|
8
|
+
import { FlowRendererResizeObserver } from './flow-renderer-resize-observer';
|
|
9
|
+
import { FlowRendererRegistry } from './flow-renderer-registry';
|
|
10
|
+
|
|
11
|
+
export const FlowRendererContainerModule = new ContainerModule(bind => {
|
|
12
|
+
bind(FlowRendererRegistry).toSelf().inSingletonScope();
|
|
13
|
+
bind(FlowRendererResizeObserver).toSelf().inSingletonScope();
|
|
14
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { FlowRendererRegistry } from './flow-renderer-registry';
|
|
7
|
+
|
|
8
|
+
export const FlowRendererContribution = Symbol('FlowRendererContribution');
|
|
9
|
+
|
|
10
|
+
export interface FlowRendererContribution {
|
|
11
|
+
registerRenderer?(registry: FlowRendererRegistry): void;
|
|
12
|
+
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { inject, injectable, multiInject, optional } from 'inversify';
|
|
7
|
+
import type { Component } from 'vue';
|
|
8
|
+
import { I18n } from '@flowgram-vue/i18n';
|
|
9
|
+
import { type Layer, type LayerRegistry, PipelineRegistry } from '@flowgram-vue/core';
|
|
10
|
+
|
|
11
|
+
import { FlowRendererContribution } from './flow-renderer-contribution';
|
|
12
|
+
|
|
13
|
+
export enum FlowRendererComponentType {
|
|
14
|
+
VUE, // vue 组件
|
|
15
|
+
DOM, // dom 组件
|
|
16
|
+
TEXT, // 文案
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export enum FlowRendererKey {
|
|
20
|
+
NODE_RENDER = 'node-render', // 节点渲染
|
|
21
|
+
ADDER = 'adder', // 添加按钮渲染
|
|
22
|
+
COLLAPSE = 'collapse', // 节点展开收起标签(包含展开态和收起态)
|
|
23
|
+
BRANCH_ADDER = 'branch-adder', // 分支添加按钮
|
|
24
|
+
TRY_CATCH_COLLAPSE = 'try-catch-collapse', // 错误处理分支整体收起
|
|
25
|
+
DRAG_NODE = 'drag-node', // 拖拽节点
|
|
26
|
+
DRAGGABLE_ADDER = 'draggable-adder', // 拖拽可被拖入
|
|
27
|
+
DRAG_HIGHLIGHT_ADDER = 'drag-highlight-adder', // 拖拽高亮
|
|
28
|
+
DRAG_BRANCH_HIGHLIGHT_ADDER = 'drag-branch-highlight-adder', // 分支拖拽添加高亮
|
|
29
|
+
SELECTOR_BOX_POPOVER = 'selector-box-popover', // 选择框右上角菜单
|
|
30
|
+
/**
|
|
31
|
+
* @deprecated
|
|
32
|
+
*/
|
|
33
|
+
CONTEXT_MENU_POPOVER = 'context-menu-popover', // 右键菜单
|
|
34
|
+
SUB_CANVAS = 'sub-canvas', // 子画布渲染
|
|
35
|
+
|
|
36
|
+
SLOT_ADDER = 'slot-adder', // 插槽添加按钮
|
|
37
|
+
SLOT_LABEL = 'slot-label', // 插槽标签
|
|
38
|
+
SLOT_COLLAPSE = 'slot-collapse', // 插槽收起按钮渲染
|
|
39
|
+
|
|
40
|
+
// 工作流线条箭头自定义渲染
|
|
41
|
+
ARROW_RENDERER = 'arrow-renderer', // 工作流线条箭头渲染器
|
|
42
|
+
|
|
43
|
+
// 下边两个不一定存在
|
|
44
|
+
MARKER_ARROW = 'marker-arrow', // loop 的默认箭头
|
|
45
|
+
MARKER_ACTIVATE_ARROW = 'marker-active-arrow', // loop 的激活态箭头
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export enum FlowTextKey {
|
|
49
|
+
// 循环节点相关
|
|
50
|
+
LOOP_END_TEXT = 'loop-end-text', // 文案:循环结束
|
|
51
|
+
LOOP_TRAVERSE_TEXT = 'loop-traverse-text', // 文案:循环遍历
|
|
52
|
+
LOOP_WHILE_TEXT = 'loop-while-text', // 文案:满足条件时
|
|
53
|
+
// TryCatch 相关
|
|
54
|
+
TRY_START_TEXT = 'try-start-text', // 文案:监控开始
|
|
55
|
+
TRY_END_TEXT = 'try-end-text', // 文案:监控结束
|
|
56
|
+
CATCH_TEXT = 'catch-text', // 发生错误
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface FlowRendererComponent {
|
|
60
|
+
type: FlowRendererComponentType;
|
|
61
|
+
renderer: Component | ((props?: any) => any);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* 命令分类
|
|
66
|
+
*/
|
|
67
|
+
export enum FlowRendererCommandCategory {
|
|
68
|
+
SELECTOR_BOX = 'SELECTOR_BOX', // 选择框
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
@injectable()
|
|
72
|
+
export class FlowRendererRegistry {
|
|
73
|
+
private componentsMap = new Map<string, FlowRendererComponent>();
|
|
74
|
+
|
|
75
|
+
private textMap = new Map<string, string>();
|
|
76
|
+
|
|
77
|
+
@multiInject(FlowRendererContribution)
|
|
78
|
+
@optional()
|
|
79
|
+
private contribs: FlowRendererContribution[] = [];
|
|
80
|
+
|
|
81
|
+
@inject(PipelineRegistry) readonly pipeline: PipelineRegistry;
|
|
82
|
+
|
|
83
|
+
init() {
|
|
84
|
+
this.contribs.forEach((contrib) => contrib.registerRenderer?.(this));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* 注册 组件数据
|
|
89
|
+
*/
|
|
90
|
+
registerRendererComponents(
|
|
91
|
+
renderKey: FlowRendererKey | string,
|
|
92
|
+
comp: FlowRendererComponent
|
|
93
|
+
): void {
|
|
94
|
+
this.componentsMap.set(renderKey, comp);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
registerVueComponent(
|
|
98
|
+
renderKey: FlowRendererKey | string,
|
|
99
|
+
renderer: Component | ((props: any) => any)
|
|
100
|
+
): void {
|
|
101
|
+
this.componentsMap.set(renderKey, {
|
|
102
|
+
type: FlowRendererComponentType.VUE,
|
|
103
|
+
renderer,
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* 注册文案
|
|
109
|
+
*/
|
|
110
|
+
registerText(configs: Record<FlowTextKey | string, string>): void {
|
|
111
|
+
Object.entries(configs).forEach(([key, value]) => {
|
|
112
|
+
this.textMap.set(key, value);
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
getText(textKey: string) {
|
|
117
|
+
return I18n.t(textKey, { defaultValue: '' }) || this.textMap.get(textKey);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* TODO: support memo
|
|
122
|
+
*/
|
|
123
|
+
public getRendererComponent(renderKey: FlowRendererKey | string): FlowRendererComponent {
|
|
124
|
+
const comp = this.componentsMap.get(renderKey);
|
|
125
|
+
if (!comp) {
|
|
126
|
+
throw new Error(`Unknown render key ${renderKey}`);
|
|
127
|
+
}
|
|
128
|
+
return comp;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
tryToGetRendererComponent(
|
|
132
|
+
renderKey: FlowRendererKey | string
|
|
133
|
+
): FlowRendererComponent | undefined {
|
|
134
|
+
return this.componentsMap.get(renderKey);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* 注册画布层
|
|
139
|
+
*/
|
|
140
|
+
registerLayers(...layerRegistries: LayerRegistry[]): void {
|
|
141
|
+
layerRegistries.forEach((layer) => this.pipeline.registerLayer(layer));
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* 根据配置注册画布
|
|
146
|
+
* @param layerRegistry
|
|
147
|
+
* @param options
|
|
148
|
+
*/
|
|
149
|
+
registerLayer<P extends Layer = Layer>(
|
|
150
|
+
layerRegistry: LayerRegistry<Layer>,
|
|
151
|
+
options?: P['options']
|
|
152
|
+
): void {
|
|
153
|
+
this.pipeline.registerLayer(layerRegistry, options);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { injectable } from 'inversify';
|
|
7
|
+
import { type FlowNodeTransformData } from '@flowgram-vue/document';
|
|
8
|
+
import { Disposable } from '@flowgram-vue/utils';
|
|
9
|
+
|
|
10
|
+
import { isHidden, isRectInit } from './utils/element';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* 监听 dom 元素的 size 变化,用于画布节点的大小变化重新计算
|
|
14
|
+
*/
|
|
15
|
+
@injectable()
|
|
16
|
+
export class FlowRendererResizeObserver {
|
|
17
|
+
/**
|
|
18
|
+
* 监听元素 size,并同步到 transform
|
|
19
|
+
* @param el
|
|
20
|
+
* @param transform
|
|
21
|
+
*/
|
|
22
|
+
observe(el: HTMLElement, transform: FlowNodeTransformData): Disposable {
|
|
23
|
+
const observer = new ResizeObserver(entries => {
|
|
24
|
+
/**
|
|
25
|
+
* NOTICE: 不加 window.requestAnimationFrame
|
|
26
|
+
* 会导致 "ResizeObserver loop completed with undelivered notifications." 报错
|
|
27
|
+
* 这个报错在 chrome 和 firefox 是默认被忽略的,但本地调试会被编译工具弹窗打断
|
|
28
|
+
*/
|
|
29
|
+
window.requestAnimationFrame(() => {
|
|
30
|
+
if (!Array.isArray(entries) || !entries.length) {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
const entry = entries[0];
|
|
34
|
+
const { contentRect, target } = entry;
|
|
35
|
+
// 元素宽高未计算时,不更新节点 size
|
|
36
|
+
const isContentRectInit = isRectInit(contentRect);
|
|
37
|
+
// 目标节点脱离 DOM 树,忽略本次变更
|
|
38
|
+
const isLeaveDOMTree = !target.parentNode;
|
|
39
|
+
// IDE 环境下画布元素可能 display none,这时候会监听到元素宽高 0 导致闪屏
|
|
40
|
+
// 此情况下不作 resize 重渲染
|
|
41
|
+
const isHiddenElement = isHidden(target.parentNode as HTMLElement);
|
|
42
|
+
if (isContentRectInit && !isLeaveDOMTree && !isHiddenElement) {
|
|
43
|
+
// 更新节点 size 数据
|
|
44
|
+
transform.size = {
|
|
45
|
+
width: Math.round(contentRect.width * 10) / 10,
|
|
46
|
+
height: Math.round(contentRect.height * 10) / 10,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
observer.observe(el);
|
|
52
|
+
return Disposable.create(() => {
|
|
53
|
+
observer.unobserve(el);
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { ConstantKeys, FlowDocumentOptions } from '@flowgram-vue/document';
|
|
7
|
+
import { useService } from '@flowgram-vue/core';
|
|
8
|
+
|
|
9
|
+
export const BASE_DEFAULT_COLOR = '#BBBFC4';
|
|
10
|
+
export const BASE_DEFAULT_ACTIVATED_COLOR = '#82A7FC';
|
|
11
|
+
|
|
12
|
+
export function getBaseColor(options?: FlowDocumentOptions | null): {
|
|
13
|
+
baseColor: string;
|
|
14
|
+
baseActivatedColor: string;
|
|
15
|
+
} {
|
|
16
|
+
return {
|
|
17
|
+
baseColor: options?.constants?.[ConstantKeys.BASE_COLOR] || BASE_DEFAULT_COLOR,
|
|
18
|
+
baseActivatedColor:
|
|
19
|
+
options?.constants?.[ConstantKeys.BASE_ACTIVATED_COLOR] || BASE_DEFAULT_ACTIVATED_COLOR,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function useBaseColor(): { baseColor: string; baseActivatedColor: string } {
|
|
24
|
+
const options = useService<FlowDocumentOptions>(FlowDocumentOptions);
|
|
25
|
+
return getBaseColor(options);
|
|
26
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export * from './entities';
|
|
7
|
+
export * from './layers';
|
|
8
|
+
export * from './flow-renderer-contribution';
|
|
9
|
+
export * from './flow-renderer-registry';
|
|
10
|
+
export * from './flow-renderer-container-module';
|
|
11
|
+
|
|
12
|
+
export { ScrollBarEvents } from './utils';
|
|
13
|
+
export { MARK_ARROW_ID } from './components/MarkerArrow';
|
|
14
|
+
export { MARK_ACTIVATED_ARROW_ID } from './components/MarkerActivatedArrow';
|
|
15
|
+
export { useBaseColor } from './hooks/use-base-color';
|
|
16
|
+
export { createLines } from './components/LinesRenderer';
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { defineComponent, h, provide, type VNodeChild } from 'vue';
|
|
7
|
+
import {
|
|
8
|
+
Playground,
|
|
9
|
+
PlaygroundVueContainerKey,
|
|
10
|
+
PlaygroundVueRefKey,
|
|
11
|
+
type PlaygroundContainerFactory,
|
|
12
|
+
} from '@flowgram-vue/core';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* 给 Layer 独立 createApp 注入 playground 容器,使子组件里的 useService / usePlayground 可用。
|
|
16
|
+
*/
|
|
17
|
+
export const LayerVueProvide = defineComponent({
|
|
18
|
+
name: 'LayerVueProvide',
|
|
19
|
+
props: {
|
|
20
|
+
factory: {
|
|
21
|
+
type: Object,
|
|
22
|
+
required: true,
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
setup(props) {
|
|
26
|
+
const factory = props.factory as PlaygroundContainerFactory;
|
|
27
|
+
provide(PlaygroundVueContainerKey, factory as any);
|
|
28
|
+
try {
|
|
29
|
+
provide(PlaygroundVueRefKey, factory.get(Playground));
|
|
30
|
+
} catch {
|
|
31
|
+
// 测试容器可能重复 bind PlaygroundConfig,此时仅 provide container
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
render() {
|
|
35
|
+
return this.$slots.default?.();
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
export function wrapLayerRender(
|
|
40
|
+
factory: PlaygroundContainerFactory,
|
|
41
|
+
children: VNodeChild
|
|
42
|
+
) {
|
|
43
|
+
return h(LayerVueProvide, { factory }, () => children);
|
|
44
|
+
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { h, type Component, type VNode } from 'vue';
|
|
7
|
+
import { inject, injectable } from 'inversify';
|
|
8
|
+
import { domUtils } from '@flowgram-vue/utils';
|
|
9
|
+
import {
|
|
10
|
+
CommandRegistry,
|
|
11
|
+
ContextMenuService,
|
|
12
|
+
EditorState,
|
|
13
|
+
EditorStateConfigEntity,
|
|
14
|
+
Layer,
|
|
15
|
+
observeEntity,
|
|
16
|
+
PipelineLayerPriority,
|
|
17
|
+
PlaygroundConfigEntity,
|
|
18
|
+
PlaygroundContainerFactory,
|
|
19
|
+
SelectionService,
|
|
20
|
+
} from '@flowgram-vue/core';
|
|
21
|
+
|
|
22
|
+
import { wrapLayerRender } from '../layer-vue-provide';
|
|
23
|
+
import {
|
|
24
|
+
FlowRendererCommandCategory,
|
|
25
|
+
FlowRendererKey,
|
|
26
|
+
FlowRendererRegistry,
|
|
27
|
+
} from '../flow-renderer-registry';
|
|
28
|
+
import { SelectorBoxConfigEntity } from '../entities/selector-box-config-entity';
|
|
29
|
+
import { FlowSelectConfigEntity } from '../entities/flow-select-config-entity';
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* 流程右键菜单
|
|
33
|
+
*/
|
|
34
|
+
@injectable()
|
|
35
|
+
export class FlowContextMenuLayer extends Layer {
|
|
36
|
+
@inject(CommandRegistry) readonly commandRegistry: CommandRegistry;
|
|
37
|
+
|
|
38
|
+
@inject(FlowRendererRegistry) readonly rendererRegistry: FlowRendererRegistry;
|
|
39
|
+
|
|
40
|
+
@inject(ContextMenuService) readonly contextMenuService: ContextMenuService;
|
|
41
|
+
|
|
42
|
+
@inject(PlaygroundContainerFactory) readonly playgroundContainer: PlaygroundContainerFactory;
|
|
43
|
+
|
|
44
|
+
@observeEntity(FlowSelectConfigEntity) protected flowSelectConfigEntity: FlowSelectConfigEntity;
|
|
45
|
+
|
|
46
|
+
@inject(SelectionService) readonly selectionService: SelectionService;
|
|
47
|
+
|
|
48
|
+
@observeEntity(PlaygroundConfigEntity)
|
|
49
|
+
protected playgroundConfigEntity: PlaygroundConfigEntity;
|
|
50
|
+
|
|
51
|
+
@observeEntity(EditorStateConfigEntity)
|
|
52
|
+
protected editorStateConfig: EditorStateConfigEntity;
|
|
53
|
+
|
|
54
|
+
@observeEntity(SelectorBoxConfigEntity)
|
|
55
|
+
protected selectorBoxConfigEntity: SelectorBoxConfigEntity;
|
|
56
|
+
|
|
57
|
+
readonly node = domUtils.createDivWithClass('gedit-context-menu-layer');
|
|
58
|
+
|
|
59
|
+
readonly nodeRef: { current: { setVisible: (v: boolean) => void } | null } = { current: null };
|
|
60
|
+
|
|
61
|
+
isEnabled(): boolean {
|
|
62
|
+
const currentState = this.editorStateConfig.getCurrentState();
|
|
63
|
+
return (
|
|
64
|
+
!this.config.disabled &&
|
|
65
|
+
!this.config.readonly &&
|
|
66
|
+
currentState === EditorState.STATE_SELECT &&
|
|
67
|
+
!this.selectorBoxConfigEntity.disabled
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
onReady(): void {
|
|
72
|
+
this.node!.style.zIndex = '30';
|
|
73
|
+
this.node!.style.display = 'block';
|
|
74
|
+
this.toDispose.pushAll([
|
|
75
|
+
this.listenPlaygroundEvent(
|
|
76
|
+
'contextmenu',
|
|
77
|
+
(e: MouseEvent): boolean | undefined => {
|
|
78
|
+
if (!this.isEnabled()) return;
|
|
79
|
+
this.contextMenuService.rightPanelVisible = true;
|
|
80
|
+
const bounds = this.flowSelectConfigEntity.getSelectedBounds();
|
|
81
|
+
if (bounds.width === 0 || bounds.height === 0) {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
e.stopPropagation();
|
|
85
|
+
e.preventDefault();
|
|
86
|
+
|
|
87
|
+
this.nodeRef.current?.setVisible(true);
|
|
88
|
+
const clientBounds = this.playgroundConfigEntity.getClientBounds();
|
|
89
|
+
const dragBlockX = e.clientX - (this.pipelineNode.offsetLeft || 0) - clientBounds.x;
|
|
90
|
+
const dragBlockY = e.clientY - (this.pipelineNode.offsetTop || 0) - clientBounds.y;
|
|
91
|
+
this.node.style.left = `${dragBlockX}px`;
|
|
92
|
+
this.node.style.top = `${dragBlockY}px`;
|
|
93
|
+
},
|
|
94
|
+
PipelineLayerPriority.BASE_LAYER
|
|
95
|
+
),
|
|
96
|
+
this.listenPlaygroundEvent('mousedown', () => {
|
|
97
|
+
this.nodeRef.current?.setVisible(false);
|
|
98
|
+
this.contextMenuService.rightPanelVisible = false;
|
|
99
|
+
}),
|
|
100
|
+
]);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
onScroll() {
|
|
104
|
+
this.nodeRef.current?.setVisible(false);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
onZoom() {
|
|
108
|
+
this.nodeRef.current?.setVisible(false);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Destroy
|
|
113
|
+
*/
|
|
114
|
+
dispose(): void {
|
|
115
|
+
super.dispose();
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* 渲染工具栏
|
|
120
|
+
*/
|
|
121
|
+
renderCommandMenus(): VNode[] {
|
|
122
|
+
return this.commandRegistry.commands
|
|
123
|
+
.filter((cmd) => cmd.category === FlowRendererCommandCategory.SELECTOR_BOX)
|
|
124
|
+
.map((cmd) => {
|
|
125
|
+
const CommandRenderer = this.rendererRegistry.getRendererComponent(
|
|
126
|
+
(cmd.icon as string) || cmd.id
|
|
127
|
+
)?.renderer as Component;
|
|
128
|
+
return h(CommandRenderer, {
|
|
129
|
+
key: cmd.id,
|
|
130
|
+
command: cmd,
|
|
131
|
+
isContextMenu: true,
|
|
132
|
+
disabled: !this.commandRegistry.isEnabled(cmd.id),
|
|
133
|
+
onClick: (e: any) => this.commandRegistry.executeCommand(cmd.id, e),
|
|
134
|
+
});
|
|
135
|
+
})
|
|
136
|
+
.filter((c) => c);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
render() {
|
|
140
|
+
const SelectorBoxPopover = this.rendererRegistry.getRendererComponent(
|
|
141
|
+
FlowRendererKey.CONTEXT_MENU_POPOVER
|
|
142
|
+
).renderer as Component;
|
|
143
|
+
return wrapLayerRender(
|
|
144
|
+
this.playgroundContainer,
|
|
145
|
+
h(SelectorBoxPopover, {
|
|
146
|
+
ref: (inst: any) => {
|
|
147
|
+
this.nodeRef.current = inst;
|
|
148
|
+
},
|
|
149
|
+
content: this.renderCommandMenus(),
|
|
150
|
+
})
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
}
|