@flowgram-vue/core 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/package.json +63 -0
- package/src/common/config-entity.ts +80 -0
- package/src/common/entity-data.ts +171 -0
- package/src/common/entity-manager-contribution.ts +12 -0
- package/src/common/entity-manager.ts +475 -0
- package/src/common/entity.ts +482 -0
- package/src/common/index.ts +22 -0
- package/src/common/playground-context.ts +35 -0
- package/src/common/playground-decorator-helper.ts +113 -0
- package/src/common/playground-decorators.ts +85 -0
- package/src/common/playground-schedule.ts +30 -0
- package/src/common/protect-wheel-area.ts +11 -0
- package/src/common/schema/index.ts +14 -0
- package/src/common/schema/node.ts +15 -0
- package/src/common/schema/opacity-schema.ts +19 -0
- package/src/common/schema/origin-schema.ts +35 -0
- package/src/common/schema/position-schema.ts +35 -0
- package/src/common/schema/rotation-schema.ts +19 -0
- package/src/common/schema/scale-schema.ts +35 -0
- package/src/common/schema/size-schema.ts +42 -0
- package/src/common/schema/skew-schema.ts +35 -0
- package/src/common/schema/transform-schema.ts +613 -0
- package/src/common/utils/bounds.spec.ts +229 -0
- package/src/common/utils/bounds.ts +176 -0
- package/src/common/utils/index.ts +6 -0
- package/src/composables/index.ts +16 -0
- package/src/composables/use-config-entity.ts +31 -0
- package/src/composables/use-entities.ts +26 -0
- package/src/composables/use-entity-data-from-context.ts +34 -0
- package/src/composables/use-entity-from-context.ts +31 -0
- package/src/composables/use-listen-events.ts +22 -0
- package/src/composables/use-playground-container.ts +16 -0
- package/src/composables/use-playground-context.ts +15 -0
- package/src/composables/use-playground-drag.ts +31 -0
- package/src/composables/use-playground.ts +16 -0
- package/src/composables/use-refresh.ts +6 -0
- package/src/composables/use-service.ts +17 -0
- package/src/core/index.ts +8 -0
- package/src/core/layer/config/editor-state-config-entity.ts +180 -0
- package/src/core/layer/config/index.ts +7 -0
- package/src/core/layer/config/playground-config-entity.ts +604 -0
- package/src/core/layer/index.ts +8 -0
- package/src/core/layer/layer.ts +248 -0
- package/src/core/layer/playground-layer.ts +547 -0
- package/src/core/pipeline/index.ts +10 -0
- package/src/core/pipeline/pipeline-entities-selector.ts +200 -0
- package/src/core/pipeline/pipeline-entities.ts +230 -0
- package/src/core/pipeline/pipeline-registry.ts +344 -0
- package/src/core/pipeline/pipeline-renderer.ts +248 -0
- package/src/core/pipeline/pipeline-vue-utils.ts +96 -0
- package/src/core/pipeline/pipeline.ts +28 -0
- package/src/core/utils/index.ts +12 -0
- package/src/core/utils/inject-provider-decorators.ts +27 -0
- package/src/core/utils/lazy-inject-decorators.ts +38 -0
- package/src/core/utils/mouse-touch-event.ts +120 -0
- package/src/core/utils/playground-drag.ts +511 -0
- package/src/core/utils/playground-gesture.spec.ts +135 -0
- package/src/core/utils/playground-gesture.ts +118 -0
- package/src/core/utils/tween.ts +156 -0
- package/src/core/utils/use-gesture/core/Controller.ts +212 -0
- package/src/core/utils/use-gesture/core/EventStore.ts +46 -0
- package/src/core/utils/use-gesture/core/TimeoutStore.ts +28 -0
- package/src/core/utils/use-gesture/core/actions.ts +63 -0
- package/src/core/utils/use-gesture/core/config/commonConfigResolver.ts +86 -0
- package/src/core/utils/use-gesture/core/config/coordinatesConfigResolver.ts +48 -0
- package/src/core/utils/use-gesture/core/config/dragConfigResolver.ts +140 -0
- package/src/core/utils/use-gesture/core/config/hoverConfigResolver.ts +11 -0
- package/src/core/utils/use-gesture/core/config/moveConfigResolver.ts +11 -0
- package/src/core/utils/use-gesture/core/config/pinchConfigResolver.ts +59 -0
- package/src/core/utils/use-gesture/core/config/resolver.ts +70 -0
- package/src/core/utils/use-gesture/core/config/scrollConfigResolver.ts +8 -0
- package/src/core/utils/use-gesture/core/config/sharedConfigResolver.ts +28 -0
- package/src/core/utils/use-gesture/core/config/support.ts +52 -0
- package/src/core/utils/use-gesture/core/config/wheelConfigResolver.ts +8 -0
- package/src/core/utils/use-gesture/core/engines/CoordinatesEngine.ts +78 -0
- package/src/core/utils/use-gesture/core/engines/DragEngine.ts +403 -0
- package/src/core/utils/use-gesture/core/engines/Engine.ts +406 -0
- package/src/core/utils/use-gesture/core/engines/HoverEngine.ts +43 -0
- package/src/core/utils/use-gesture/core/engines/MoveEngine.ts +52 -0
- package/src/core/utils/use-gesture/core/engines/PinchEngine.ts +345 -0
- package/src/core/utils/use-gesture/core/engines/ScrollEngine.ts +42 -0
- package/src/core/utils/use-gesture/core/engines/WheelEngine.ts +48 -0
- package/src/core/utils/use-gesture/core/index.ts +7 -0
- package/src/core/utils/use-gesture/core/parser.ts +92 -0
- package/src/core/utils/use-gesture/core/types/action.ts +19 -0
- package/src/core/utils/use-gesture/core/types/config.ts +255 -0
- package/src/core/utils/use-gesture/core/types/handlers.ts +73 -0
- package/src/core/utils/use-gesture/core/types/index.ts +11 -0
- package/src/core/utils/use-gesture/core/types/internalConfig.ts +80 -0
- package/src/core/utils/use-gesture/core/types/state.ts +275 -0
- package/src/core/utils/use-gesture/core/types/utils.ts +204 -0
- package/src/core/utils/use-gesture/core/types.ts +8 -0
- package/src/core/utils/use-gesture/core/utils/events.ts +158 -0
- package/src/core/utils/use-gesture/core/utils/fn.ts +32 -0
- package/src/core/utils/use-gesture/core/utils/maths.ts +63 -0
- package/src/core/utils/use-gesture/core/utils/state.ts +24 -0
- package/src/core/utils/use-gesture/core/utils.ts +8 -0
- package/src/core/utils/use-gesture/index.ts +6 -0
- package/src/core/utils/use-gesture/vanilla/DragGesture.ts +27 -0
- package/src/core/utils/use-gesture/vanilla/Gesture.ts +45 -0
- package/src/core/utils/use-gesture/vanilla/HoverGesture.ts +27 -0
- package/src/core/utils/use-gesture/vanilla/MoveGesture.ts +27 -0
- package/src/core/utils/use-gesture/vanilla/PinchGesture.ts +27 -0
- package/src/core/utils/use-gesture/vanilla/Recognizer.ts +43 -0
- package/src/core/utils/use-gesture/vanilla/ScrollGesture.ts +27 -0
- package/src/core/utils/use-gesture/vanilla/WheelGesture.ts +27 -0
- package/src/core/utils/use-gesture/vanilla/createGesture.ts +18 -0
- package/src/core/utils/use-gesture/vanilla/index.ts +18 -0
- package/src/env.d.ts +10 -0
- package/src/index.ts +17 -0
- package/src/playground-config.ts +70 -0
- package/src/playground-container.ts +129 -0
- package/src/playground-contribution.ts +81 -0
- package/src/playground-mock-tools.ts +143 -0
- package/src/playground.ts +401 -0
- package/src/plugin/index.ts +6 -0
- package/src/plugin/plugin.ts +226 -0
- package/src/services/clipboard-service.ts +40 -0
- package/src/services/context-menu-service.ts +25 -0
- package/src/services/index.ts +10 -0
- package/src/services/logger-service.ts +49 -0
- package/src/services/selection-service.ts +54 -0
- package/src/services/storage-service.ts +65 -0
- package/src/vue/index.ts +8 -0
- package/src/vue/playground-vue-context.ts +22 -0
- package/src/vue/playground-vue-provider.ts +115 -0
- package/src/vue/playground-vue-renderer.vue +44 -0
|
@@ -0,0 +1,401 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { nanoid } from 'nanoid';
|
|
7
|
+
import type { Component } from 'vue';
|
|
8
|
+
import { inject, injectable, optional, named } from 'inversify';
|
|
9
|
+
import { Disposable, DisposableCollection, domUtils, type Event } from '@flowgram-vue/utils';
|
|
10
|
+
import { CommandService } from '@flowgram-vue/command';
|
|
11
|
+
|
|
12
|
+
import { SelectionService } from './services';
|
|
13
|
+
import { PlaygroundContribution, PlaygroundRegistry } from './playground-contribution';
|
|
14
|
+
import { PlaygroundConfig } from './playground-config';
|
|
15
|
+
import {
|
|
16
|
+
type PipelineDimension,
|
|
17
|
+
// PipelineDispatcher,
|
|
18
|
+
PipelineRegistry,
|
|
19
|
+
PipelineRenderer,
|
|
20
|
+
} from './core/pipeline';
|
|
21
|
+
import {
|
|
22
|
+
EditorStateConfigEntity,
|
|
23
|
+
PlaygroundConfigEntity,
|
|
24
|
+
type PlaygroundConfigRevealOpts,
|
|
25
|
+
} from './core/layer/config';
|
|
26
|
+
import { Layer, LayerRegistry } from './core';
|
|
27
|
+
import {
|
|
28
|
+
// type AbleDispatchEvent,
|
|
29
|
+
// AbleManager,
|
|
30
|
+
type ConfigEntity,
|
|
31
|
+
EntityManager,
|
|
32
|
+
type EntityRegistry,
|
|
33
|
+
PlaygroundContext,
|
|
34
|
+
ContributionProvider,
|
|
35
|
+
PlaygroundContextProvider,
|
|
36
|
+
} from './common';
|
|
37
|
+
// import { PlaygroundCommandRegistry, PlaygroundId, toContextMenuPath } from './playground-registries';
|
|
38
|
+
|
|
39
|
+
@injectable()
|
|
40
|
+
export class Playground<CONTEXT = PlaygroundContext> implements Disposable {
|
|
41
|
+
readonly toDispose = new DisposableCollection();
|
|
42
|
+
|
|
43
|
+
readonly node: HTMLElement;
|
|
44
|
+
|
|
45
|
+
private _focused = false;
|
|
46
|
+
|
|
47
|
+
readonly onBlur: Event<void>;
|
|
48
|
+
|
|
49
|
+
readonly onFocus: Event<void>;
|
|
50
|
+
|
|
51
|
+
readonly onZoom: Event<number>;
|
|
52
|
+
|
|
53
|
+
readonly onScroll: Event<{ scrollX: number; scrollY: number }>;
|
|
54
|
+
|
|
55
|
+
get onResize() {
|
|
56
|
+
return this.pipelineRegistry.onResizeEmitter.event;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// 唯一 className,适配画布多实例场景
|
|
60
|
+
private playgroundClassName = nanoid();
|
|
61
|
+
|
|
62
|
+
constructor(
|
|
63
|
+
// @inject(PlaygroundId) readonly id: PlaygroundId,
|
|
64
|
+
@inject(EntityManager) readonly entityManager: EntityManager,
|
|
65
|
+
@inject(PlaygroundRegistry) readonly registry: PlaygroundRegistry,
|
|
66
|
+
@inject(PlaygroundContextProvider)
|
|
67
|
+
@optional()
|
|
68
|
+
readonly contextProvider: PlaygroundContextProvider,
|
|
69
|
+
@inject(PipelineRenderer)
|
|
70
|
+
readonly pipelineRenderer: PipelineRenderer,
|
|
71
|
+
// @inject(PlaygroundCommandRegistry) protected readonly commands: PlaygroundCommandRegistry,
|
|
72
|
+
@inject(PipelineRegistry)
|
|
73
|
+
readonly pipelineRegistry: PipelineRegistry,
|
|
74
|
+
// @inject(AbleManager) readonly ableManager: AbleManager,
|
|
75
|
+
// @inject(PipelineDispatcher) protected readonly dispatcher: PipelineDispatcher,
|
|
76
|
+
@inject(PlaygroundConfig)
|
|
77
|
+
protected readonly playgroundConfig: PlaygroundConfig,
|
|
78
|
+
@inject(ContributionProvider)
|
|
79
|
+
@named(PlaygroundContribution)
|
|
80
|
+
@optional()
|
|
81
|
+
protected readonly contributionProvider: ContributionProvider<PlaygroundContribution>,
|
|
82
|
+
/**
|
|
83
|
+
* 用于管理画布命令
|
|
84
|
+
*/
|
|
85
|
+
@inject(CommandService) readonly commandService: CommandService,
|
|
86
|
+
/**
|
|
87
|
+
* 用于管理画布选择
|
|
88
|
+
*/
|
|
89
|
+
@inject(SelectionService) readonly selectionService: SelectionService
|
|
90
|
+
) {
|
|
91
|
+
this.toDispose.pushAll([
|
|
92
|
+
this.pipelineRenderer,
|
|
93
|
+
this.pipelineRegistry,
|
|
94
|
+
this.entityManager,
|
|
95
|
+
// this.ableManager,
|
|
96
|
+
this.commandService,
|
|
97
|
+
this.selectionService,
|
|
98
|
+
Disposable.create(() => {
|
|
99
|
+
this.node.remove();
|
|
100
|
+
}),
|
|
101
|
+
pipelineRenderer.onAllLayersRendered(() => {
|
|
102
|
+
this.contributions.forEach((contrib) => contrib.onAllLayersRendered?.(this));
|
|
103
|
+
}),
|
|
104
|
+
]);
|
|
105
|
+
// Deafult entities added
|
|
106
|
+
const editStates =
|
|
107
|
+
this.entityManager.createEntity<EditorStateConfigEntity>(EditorStateConfigEntity);
|
|
108
|
+
this.entityManager.createEntity(PlaygroundConfigEntity);
|
|
109
|
+
this.node = playgroundConfig.node || document.createElement('div');
|
|
110
|
+
this.config.playgroundDomNode = this.node;
|
|
111
|
+
this.toDispose.pushAll([
|
|
112
|
+
// 浏览器原生的 scrollIntoView 会导致页面的滚动
|
|
113
|
+
// 需要禁用这种操作,否则会引发画布 viewport 计算问题
|
|
114
|
+
domUtils.addStandardDisposableListener(this.node, 'scroll', (event: UIEvent) => {
|
|
115
|
+
this.node.scrollTop = 0;
|
|
116
|
+
this.node.scrollLeft = 0;
|
|
117
|
+
event.preventDefault();
|
|
118
|
+
event.stopPropagation();
|
|
119
|
+
}),
|
|
120
|
+
]);
|
|
121
|
+
this.node.classList.add('gedit-playground');
|
|
122
|
+
if (process.env.NODE_ENV !== 'test') {
|
|
123
|
+
this.node.classList.add(this.playgroundClassName);
|
|
124
|
+
}
|
|
125
|
+
this.node.dataset.testid = 'sdk.workflow.canvas';
|
|
126
|
+
if (playgroundConfig.layers)
|
|
127
|
+
playgroundConfig.layers.forEach((layer) => this.registry.registerLayer(layer));
|
|
128
|
+
// if (playgroundConfig.ables)
|
|
129
|
+
// playgroundConfig.ables.forEach(able => this.ableManager.registerAble(able));
|
|
130
|
+
// if (playgroundConfig.entities)
|
|
131
|
+
// playgroundConfig.entities.forEach(entity => this.entityManager.registerEntity(entity));
|
|
132
|
+
if (playgroundConfig.editorStates)
|
|
133
|
+
playgroundConfig.editorStates.forEach((state) => editStates.registerState(state));
|
|
134
|
+
if (playgroundConfig.zoomEnable !== undefined) this.zoomEnable = playgroundConfig.zoomEnable;
|
|
135
|
+
if (playgroundConfig.entityConfigs) {
|
|
136
|
+
for (const [k, v] of playgroundConfig.entityConfigs) {
|
|
137
|
+
const entity = this.entityManager.getEntity<ConfigEntity>(k, true);
|
|
138
|
+
entity?.updateConfig(v);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
this.node.addEventListener('blur', () => {
|
|
142
|
+
this.blur();
|
|
143
|
+
});
|
|
144
|
+
this.node.addEventListener('focus', () => {
|
|
145
|
+
this.focus();
|
|
146
|
+
});
|
|
147
|
+
this.node.tabIndex = 0;
|
|
148
|
+
this.node.appendChild(this.pipelineRenderer.node);
|
|
149
|
+
this.onBlur = this.pipelineRegistry.onBlurEmitter.event;
|
|
150
|
+
this.onFocus = this.pipelineRegistry.onFocusEmitter.event;
|
|
151
|
+
this.onZoom = this.pipelineRegistry.onZoomEmitter.event;
|
|
152
|
+
this.onScroll = this.pipelineRegistry.onScrollEmitter.event;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
get context(): CONTEXT {
|
|
156
|
+
return this.contextProvider?.();
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
protected get contributions(): PlaygroundContribution[] {
|
|
160
|
+
return this.contributionProvider.getContributions();
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
init(): void {
|
|
164
|
+
const { contributions } = this;
|
|
165
|
+
for (const contrib of contributions) {
|
|
166
|
+
if (contrib.registerPlayground) contrib.registerPlayground(this.registry);
|
|
167
|
+
}
|
|
168
|
+
for (const contrib of contributions) {
|
|
169
|
+
if (contrib.onInit) contrib.onInit(this);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
get pipelineNode(): HTMLDivElement {
|
|
174
|
+
return this.pipelineRenderer.node;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
setParent(parent: HTMLElement): void {
|
|
178
|
+
parent.appendChild(this.node);
|
|
179
|
+
this.resize();
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// get onDispatch(): Event<AbleDispatchEvent> {
|
|
183
|
+
// return this.ableManager.onAbleDispatch;
|
|
184
|
+
// }
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* 对应的右键菜单路径
|
|
188
|
+
*/
|
|
189
|
+
// get contextMenuPath(): string[] {
|
|
190
|
+
// return this.playgroundConfig.contextMenuPath ? this.playgroundConfig.contextMenuPath : toContextMenuPath(this.id);
|
|
191
|
+
// }
|
|
192
|
+
get zoomEnable(): boolean {
|
|
193
|
+
return this.config.zoomEnable;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
set zoomEnable(zoomEnable) {
|
|
197
|
+
this.config.zoomEnable = zoomEnable;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* 转换为内部的命令 id
|
|
202
|
+
* @param commandId
|
|
203
|
+
*/
|
|
204
|
+
// toPlaygroundCommandId(commandId: string): string {
|
|
205
|
+
// return this.registry.commands.toCommandId(commandId);
|
|
206
|
+
// }
|
|
207
|
+
/**
|
|
208
|
+
* 通知所有关联 able 的 entity
|
|
209
|
+
*/
|
|
210
|
+
// dispatch<P>(payloadKey: string | symbol, payload: P): string[] {
|
|
211
|
+
// return this.ableManager.dispatch(payloadKey, payload);
|
|
212
|
+
// }
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* 刷新所有 layer
|
|
216
|
+
*/
|
|
217
|
+
flush(): void {
|
|
218
|
+
this.pipelineRenderer.flush();
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* 执行命令
|
|
223
|
+
* @param commandId
|
|
224
|
+
* @param args
|
|
225
|
+
*/
|
|
226
|
+
// execCommand<T>(commandId: string, ...args: any[]): Promise<T | undefined> {
|
|
227
|
+
// return this.commands.executeCommand<T>(commandId, ...args);
|
|
228
|
+
// }
|
|
229
|
+
private isReady = false;
|
|
230
|
+
|
|
231
|
+
ready(): void {
|
|
232
|
+
if (this.isReady) return;
|
|
233
|
+
this.isReady = true;
|
|
234
|
+
if (this.playgroundConfig.autoResize) {
|
|
235
|
+
const resize = () => {
|
|
236
|
+
if (this.disposed) return;
|
|
237
|
+
this.resize();
|
|
238
|
+
};
|
|
239
|
+
if (typeof ResizeObserver !== 'undefined') {
|
|
240
|
+
const resizeObserver = new ResizeObserver(resize);
|
|
241
|
+
resizeObserver.observe(this.node);
|
|
242
|
+
this.toDispose.push(
|
|
243
|
+
Disposable.create(() => {
|
|
244
|
+
resizeObserver.disconnect();
|
|
245
|
+
})
|
|
246
|
+
);
|
|
247
|
+
} else {
|
|
248
|
+
this.toDispose.push(
|
|
249
|
+
domUtils.addStandardDisposableListener(window.document.body, 'resize', resize, {
|
|
250
|
+
passive: true,
|
|
251
|
+
})
|
|
252
|
+
);
|
|
253
|
+
}
|
|
254
|
+
this.resize();
|
|
255
|
+
}
|
|
256
|
+
this.pipelineRegistry.ready();
|
|
257
|
+
this.pipelineRenderer.ready();
|
|
258
|
+
const { contributions } = this;
|
|
259
|
+
for (const contrib of contributions) {
|
|
260
|
+
if (contrib.onReady) contrib.onReady(this);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* 按下边顺序执行
|
|
266
|
+
* 1. 指定的 entity 位置或 pos 位置
|
|
267
|
+
* 2. selection 位置
|
|
268
|
+
* 3. 初始化位置
|
|
269
|
+
*/
|
|
270
|
+
scrollToView(opts?: PlaygroundConfigRevealOpts): Promise<void> {
|
|
271
|
+
const playgroundEntity =
|
|
272
|
+
this.entityManager.getEntity<PlaygroundConfigEntity>(PlaygroundConfigEntity)!;
|
|
273
|
+
return playgroundEntity.scrollToView(opts);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* 这里会由 widget 透传进来
|
|
278
|
+
* @param msg
|
|
279
|
+
*/
|
|
280
|
+
resize(msg?: PipelineDimension, scrollToCenter = true): boolean {
|
|
281
|
+
if (!msg) {
|
|
282
|
+
const boundingRect = this.node.getBoundingClientRect();
|
|
283
|
+
msg = {
|
|
284
|
+
width: boundingRect.width,
|
|
285
|
+
height: boundingRect.height,
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
// 页面宽度变更 触发滚动偏移
|
|
289
|
+
const { width, height } = this.config.config;
|
|
290
|
+
if (msg.width === 0 || msg.height === 0) {
|
|
291
|
+
return false;
|
|
292
|
+
}
|
|
293
|
+
const oldConfig = this.config.config;
|
|
294
|
+
let { scrollX, scrollY } = this.config.config;
|
|
295
|
+
// 这个在处理滚动
|
|
296
|
+
if (scrollToCenter && width && Math.round(msg.width) !== width) {
|
|
297
|
+
scrollX += (width - msg.width) / 2;
|
|
298
|
+
}
|
|
299
|
+
if (scrollToCenter && height && Math.round(msg.height) !== height) {
|
|
300
|
+
scrollY += (height - msg.height) / 2;
|
|
301
|
+
}
|
|
302
|
+
if (
|
|
303
|
+
Math.round(msg.width) !== width ||
|
|
304
|
+
Math.round(msg.height) !== height ||
|
|
305
|
+
oldConfig.scrollX !== scrollX ||
|
|
306
|
+
oldConfig.scrollY !== scrollY
|
|
307
|
+
) {
|
|
308
|
+
this.config.updateConfig({ ...msg, scrollX, scrollY });
|
|
309
|
+
this.pipelineRegistry.onResizeEmitter.fire(msg);
|
|
310
|
+
}
|
|
311
|
+
return true;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* 触发 focus
|
|
316
|
+
*/
|
|
317
|
+
protected focus(): void {
|
|
318
|
+
if (this._focused) return;
|
|
319
|
+
this._focused = true;
|
|
320
|
+
this.pipelineRegistry.onFocusEmitter.fire();
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* 触发 blur
|
|
325
|
+
*/
|
|
326
|
+
protected blur(): void {
|
|
327
|
+
if (!this._focused) return;
|
|
328
|
+
this._focused = false;
|
|
329
|
+
this.pipelineRegistry.onBlurEmitter.fire();
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
get focused(): boolean {
|
|
333
|
+
return this._focused;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* 画布配置数据
|
|
338
|
+
*/
|
|
339
|
+
get config(): PlaygroundConfigEntity {
|
|
340
|
+
return this.entityManager.getEntity<PlaygroundConfigEntity>(PlaygroundConfigEntity)!;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* 画布编辑状态管理
|
|
345
|
+
*/
|
|
346
|
+
get editorState(): EditorStateConfigEntity {
|
|
347
|
+
return this.entityManager.getEntity<EditorStateConfigEntity>(EditorStateConfigEntity)!;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
getConfigEntity<T extends ConfigEntity>(r: EntityRegistry<T>): T {
|
|
351
|
+
return this.entityManager.getEntity<T>(r, true) as T;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
dispose(): void {
|
|
355
|
+
if (this.disposed) return;
|
|
356
|
+
const { contributions } = this;
|
|
357
|
+
for (const contrib of contributions) {
|
|
358
|
+
if (contrib.onDispose) contrib.onDispose(this);
|
|
359
|
+
}
|
|
360
|
+
this.toDispose.dispose();
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
get disposed(): boolean {
|
|
364
|
+
return this.toDispose.disposed;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* 转换成 Vue 组件
|
|
369
|
+
*/
|
|
370
|
+
toVueComponent(): Component {
|
|
371
|
+
return this.pipelineRenderer.toVueComponent();
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* 注册 layer
|
|
376
|
+
*/
|
|
377
|
+
registerLayer<P extends Layer = Layer>(
|
|
378
|
+
layerRegistry: LayerRegistry<P>,
|
|
379
|
+
layerOptions?: P['options']
|
|
380
|
+
): void {
|
|
381
|
+
this.pipelineRegistry.registerLayer<P>(layerRegistry, layerOptions);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* 注册 多个 layer
|
|
386
|
+
*/
|
|
387
|
+
registerLayers(...layerRegistries: LayerRegistry[]): void {
|
|
388
|
+
layerRegistries.forEach((layer) => this.pipelineRegistry.registerLayer(layer));
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/**
|
|
392
|
+
* 获取 layer
|
|
393
|
+
*/
|
|
394
|
+
getLayer<T extends Layer>(layerRegistry: LayerRegistry<T>): T | undefined {
|
|
395
|
+
return this.pipelineRegistry.getLayer<T>(layerRegistry);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
get onAllLayersRendered(): Event<void> {
|
|
399
|
+
return this.pipelineRenderer.onAllLayersRendered;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { ContainerModule, interfaces } from 'inversify';
|
|
7
|
+
|
|
8
|
+
import { PlaygroundContribution } from '../playground-contribution';
|
|
9
|
+
import { type Playground } from '../playground';
|
|
10
|
+
|
|
11
|
+
export interface PluginContext {
|
|
12
|
+
/**
|
|
13
|
+
* 画布实例
|
|
14
|
+
*/
|
|
15
|
+
playground: Playground;
|
|
16
|
+
/**
|
|
17
|
+
* IOC 容器
|
|
18
|
+
*/
|
|
19
|
+
container: interfaces.Container;
|
|
20
|
+
/**
|
|
21
|
+
* 获取 IOC 容器的 单例模块
|
|
22
|
+
* @param identifier
|
|
23
|
+
*/
|
|
24
|
+
get<T>(identifier: interfaces.ServiceIdentifier<T>): T;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* 获取 IOC 容器的 多例模块
|
|
28
|
+
*/
|
|
29
|
+
getAll<T>(identifier: interfaces.ServiceIdentifier<T>): T[];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export const PluginContext = Symbol('PluginContext');
|
|
33
|
+
export interface PluginBindConfig {
|
|
34
|
+
bind: interfaces.Bind;
|
|
35
|
+
unbind: interfaces.Unbind;
|
|
36
|
+
isBound: interfaces.IsBound;
|
|
37
|
+
rebind: interfaces.Rebind;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface PluginConfig<Opts, CTX extends PluginContext = PluginContext> {
|
|
41
|
+
/**
|
|
42
|
+
* 插件 IOC 注册, 等价于 containerModule
|
|
43
|
+
* @param ctx
|
|
44
|
+
*/
|
|
45
|
+
onBind?: (bindConfig: PluginBindConfig, opts: Opts) => void;
|
|
46
|
+
/**
|
|
47
|
+
* 画布注册阶段
|
|
48
|
+
*/
|
|
49
|
+
onInit?: (ctx: CTX, opts: Opts) => void;
|
|
50
|
+
/**
|
|
51
|
+
* 画布准备阶段,一般用于 dom 事件注册等
|
|
52
|
+
*/
|
|
53
|
+
onReady?: (ctx: CTX, opts: Opts) => void;
|
|
54
|
+
/**
|
|
55
|
+
* 画布销毁阶段
|
|
56
|
+
*/
|
|
57
|
+
onDispose?: (ctx: CTX, opts: Opts) => void;
|
|
58
|
+
/**
|
|
59
|
+
* 画布所有 layer 渲染结束
|
|
60
|
+
*/
|
|
61
|
+
onAllLayersRendered?: (ctx: CTX, opts: Opts) => void;
|
|
62
|
+
/**
|
|
63
|
+
* IOC 模块,用于更底层的插件扩展
|
|
64
|
+
*/
|
|
65
|
+
containerModules?: interfaces.ContainerModule[];
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export const Plugin = Symbol('Plugin');
|
|
69
|
+
|
|
70
|
+
export type Plugin<Options = any> = {
|
|
71
|
+
options: Options;
|
|
72
|
+
pluginId: string;
|
|
73
|
+
singleton: boolean;
|
|
74
|
+
initPlugin: () => void;
|
|
75
|
+
contributionKeys?: interfaces.ServiceIdentifier[];
|
|
76
|
+
containerModules?: interfaces.ContainerModule[];
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
export interface PluginsProvider<CTX extends PluginContext = PluginContext> {
|
|
80
|
+
(ctx: CTX): Plugin[];
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export type PluginCreator<Options> = (opts: Options) => Plugin<Options>;
|
|
84
|
+
|
|
85
|
+
export function loadPlugins(plugins: Plugin[], container: interfaces.Container): void {
|
|
86
|
+
const pluginInitSet = new Set<string>();
|
|
87
|
+
const singletonPluginIds = new Set<string>();
|
|
88
|
+
const modules: interfaces.ContainerModule[] = plugins
|
|
89
|
+
.reduceRight((result: Plugin[], plugin: Plugin) => {
|
|
90
|
+
const shouldSkip = plugin.singleton && singletonPluginIds.has(plugin.pluginId);
|
|
91
|
+
if (plugin.singleton) {
|
|
92
|
+
singletonPluginIds.add(plugin.pluginId);
|
|
93
|
+
}
|
|
94
|
+
return shouldSkip ? result : [plugin, ...result];
|
|
95
|
+
}, [])
|
|
96
|
+
.reduce((res, plugin) => {
|
|
97
|
+
if (!pluginInitSet.has(plugin.pluginId)) {
|
|
98
|
+
plugin.initPlugin();
|
|
99
|
+
pluginInitSet.add(plugin.pluginId);
|
|
100
|
+
}
|
|
101
|
+
if (plugin.containerModules && plugin.containerModules.length > 0) {
|
|
102
|
+
for (let module of plugin.containerModules) {
|
|
103
|
+
// 去重
|
|
104
|
+
if (!res.includes(module)) {
|
|
105
|
+
res.push(module);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return res;
|
|
109
|
+
}
|
|
110
|
+
return res;
|
|
111
|
+
}, [] as interfaces.ContainerModule[]);
|
|
112
|
+
modules.forEach((module) => container.load(module));
|
|
113
|
+
plugins.forEach((plugin) => {
|
|
114
|
+
if (plugin.contributionKeys) {
|
|
115
|
+
for (const contribution of plugin.contributionKeys) {
|
|
116
|
+
container.bind(contribution).toConstantValue(plugin.options);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function toPlaygroundContainerModule<Options, CTX extends PluginContext = PluginContext>(
|
|
123
|
+
config: {
|
|
124
|
+
onInit?: (ctx: CTX, opts: Options) => void;
|
|
125
|
+
onDispose?: (ctx: CTX, opts: Options) => void;
|
|
126
|
+
onReady?: (ctx: CTX, opts: Options) => void;
|
|
127
|
+
onAllLayersRendered?: (ctx: CTX, opts: Options) => void;
|
|
128
|
+
},
|
|
129
|
+
opts: Options
|
|
130
|
+
): interfaces.ContainerModule {
|
|
131
|
+
return new ContainerModule((bind) => {
|
|
132
|
+
bind(PlaygroundContribution).toDynamicValue((ctx) => {
|
|
133
|
+
const pluginContext = ctx.container.get<CTX>(PluginContext);
|
|
134
|
+
return {
|
|
135
|
+
onInit: () => {
|
|
136
|
+
config.onInit?.(pluginContext, opts);
|
|
137
|
+
},
|
|
138
|
+
onReady: () => {
|
|
139
|
+
config.onReady?.(pluginContext, opts);
|
|
140
|
+
},
|
|
141
|
+
onDispose: () => {
|
|
142
|
+
config.onDispose?.(pluginContext, opts);
|
|
143
|
+
},
|
|
144
|
+
onAllLayersRendered: () => {
|
|
145
|
+
config.onAllLayersRendered?.(pluginContext, opts);
|
|
146
|
+
},
|
|
147
|
+
};
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
let pluginIndex = 0;
|
|
153
|
+
export function definePluginCreator<Options, CTX extends PluginContext = PluginContext>(
|
|
154
|
+
config: {
|
|
155
|
+
containerModules?: interfaces.ContainerModule[];
|
|
156
|
+
contributionKeys?: interfaces.ServiceIdentifier[];
|
|
157
|
+
singleton?: boolean;
|
|
158
|
+
} & PluginConfig<Options, CTX>
|
|
159
|
+
): PluginCreator<Options> {
|
|
160
|
+
const { contributionKeys, singleton = false } = config;
|
|
161
|
+
pluginIndex += 1;
|
|
162
|
+
const pluginId = `Playground_${pluginIndex}`;
|
|
163
|
+
return (opts: Options) => {
|
|
164
|
+
const containerModules: interfaces.ContainerModule[] = [];
|
|
165
|
+
let isInit = false;
|
|
166
|
+
|
|
167
|
+
return {
|
|
168
|
+
pluginId,
|
|
169
|
+
singleton,
|
|
170
|
+
initPlugin: () => {
|
|
171
|
+
// 防止 plugin 被上层业务多次 init
|
|
172
|
+
if (isInit) {
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
isInit = true;
|
|
176
|
+
|
|
177
|
+
if (config.containerModules) {
|
|
178
|
+
containerModules.push(...config.containerModules);
|
|
179
|
+
}
|
|
180
|
+
if (config.onBind) {
|
|
181
|
+
containerModules.push(
|
|
182
|
+
new ContainerModule((bind, unbind, isBound, rebind) => {
|
|
183
|
+
config.onBind!(
|
|
184
|
+
{
|
|
185
|
+
bind,
|
|
186
|
+
unbind,
|
|
187
|
+
isBound,
|
|
188
|
+
rebind,
|
|
189
|
+
},
|
|
190
|
+
opts
|
|
191
|
+
);
|
|
192
|
+
})
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
if (config.onInit || config.onDispose || config.onReady || config.onAllLayersRendered) {
|
|
196
|
+
containerModules.push(toPlaygroundContainerModule<Options, CTX>(config, opts));
|
|
197
|
+
}
|
|
198
|
+
},
|
|
199
|
+
options: opts,
|
|
200
|
+
contributionKeys,
|
|
201
|
+
containerModules,
|
|
202
|
+
};
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* @example
|
|
208
|
+
* createPlaygroundPlugin({
|
|
209
|
+
* // IOC 注册
|
|
210
|
+
* onBind(bind) {
|
|
211
|
+
* bind('xxx').toSelf().inSingletonScope()
|
|
212
|
+
* },
|
|
213
|
+
* // 画布初始化
|
|
214
|
+
* onInit(ctx) {
|
|
215
|
+
* ctx.playground.registerLayer(MyLayer)
|
|
216
|
+
* },
|
|
217
|
+
* // 画布销毁
|
|
218
|
+
* onDispose(ctx) {
|
|
219
|
+
* },
|
|
220
|
+
* // IOC 模块
|
|
221
|
+
* containerModules: [new ContainerModule(() => {})]
|
|
222
|
+
* })
|
|
223
|
+
*/
|
|
224
|
+
export const createPlaygroundPlugin = <CTX extends PluginContext = PluginContext>(
|
|
225
|
+
options: PluginConfig<undefined, CTX>
|
|
226
|
+
) => definePluginCreator<undefined, CTX>(options)(undefined);
|
|
@@ -0,0 +1,40 @@
|
|
|
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 { Emitter, type Event, type MaybePromise } from '@flowgram-vue/utils';
|
|
8
|
+
|
|
9
|
+
export const ClipboardService = Symbol('ClipboardService');
|
|
10
|
+
|
|
11
|
+
export interface ClipboardService {
|
|
12
|
+
onClipboardChanged: Event<string>;
|
|
13
|
+
readText(): MaybePromise<string>;
|
|
14
|
+
|
|
15
|
+
writeText(value: string): MaybePromise<void>;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* 剪贴板服务,一般用于管理临时的复制黏贴数据
|
|
20
|
+
* TODO: 后续可以支持调用浏览器
|
|
21
|
+
*/
|
|
22
|
+
@injectable()
|
|
23
|
+
export class DefaultClipboardService implements ClipboardService {
|
|
24
|
+
private _currentData: string;
|
|
25
|
+
|
|
26
|
+
protected readonly onClipboardChangedEmitter = new Emitter<string>();
|
|
27
|
+
|
|
28
|
+
readonly onClipboardChanged: Event<string> = this.onClipboardChangedEmitter.event;
|
|
29
|
+
|
|
30
|
+
readText(): string {
|
|
31
|
+
return this._currentData;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
writeText(value: string): void {
|
|
35
|
+
if (this._currentData !== value) {
|
|
36
|
+
this._currentData = value;
|
|
37
|
+
this.onClipboardChangedEmitter.fire(value);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { injectable } from 'inversify';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* 圈选右键菜单相关 service
|
|
10
|
+
*/
|
|
11
|
+
@injectable()
|
|
12
|
+
export class ContextMenuService {
|
|
13
|
+
/**
|
|
14
|
+
* 右键面板是否展示,展示的时候为 true
|
|
15
|
+
*/
|
|
16
|
+
private isRightPanelVisible: boolean;
|
|
17
|
+
|
|
18
|
+
get rightPanelVisible(): boolean {
|
|
19
|
+
return this.isRightPanelVisible;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
set rightPanelVisible(visible: boolean) {
|
|
23
|
+
this.isRightPanelVisible = visible;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export * from './selection-service';
|
|
7
|
+
export * from './storage-service';
|
|
8
|
+
export * from './clipboard-service';
|
|
9
|
+
export * from './context-menu-service';
|
|
10
|
+
export * from './logger-service';
|