@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.
Files changed (133) hide show
  1. package/LICENSE +22 -0
  2. package/dist/index.cjs +0 -0
  3. package/dist/index.cjs.map +1 -0
  4. package/dist/index.d.ts +1 -0
  5. package/dist/index.js +0 -0
  6. package/dist/index.js.map +1 -0
  7. package/package.json +63 -0
  8. package/src/common/config-entity.ts +80 -0
  9. package/src/common/entity-data.ts +171 -0
  10. package/src/common/entity-manager-contribution.ts +12 -0
  11. package/src/common/entity-manager.ts +475 -0
  12. package/src/common/entity.ts +482 -0
  13. package/src/common/index.ts +22 -0
  14. package/src/common/playground-context.ts +35 -0
  15. package/src/common/playground-decorator-helper.ts +113 -0
  16. package/src/common/playground-decorators.ts +85 -0
  17. package/src/common/playground-schedule.ts +30 -0
  18. package/src/common/protect-wheel-area.ts +11 -0
  19. package/src/common/schema/index.ts +14 -0
  20. package/src/common/schema/node.ts +15 -0
  21. package/src/common/schema/opacity-schema.ts +19 -0
  22. package/src/common/schema/origin-schema.ts +35 -0
  23. package/src/common/schema/position-schema.ts +35 -0
  24. package/src/common/schema/rotation-schema.ts +19 -0
  25. package/src/common/schema/scale-schema.ts +35 -0
  26. package/src/common/schema/size-schema.ts +42 -0
  27. package/src/common/schema/skew-schema.ts +35 -0
  28. package/src/common/schema/transform-schema.ts +613 -0
  29. package/src/common/utils/bounds.spec.ts +229 -0
  30. package/src/common/utils/bounds.ts +176 -0
  31. package/src/common/utils/index.ts +6 -0
  32. package/src/composables/index.ts +16 -0
  33. package/src/composables/use-config-entity.ts +31 -0
  34. package/src/composables/use-entities.ts +26 -0
  35. package/src/composables/use-entity-data-from-context.ts +34 -0
  36. package/src/composables/use-entity-from-context.ts +31 -0
  37. package/src/composables/use-listen-events.ts +22 -0
  38. package/src/composables/use-playground-container.ts +16 -0
  39. package/src/composables/use-playground-context.ts +15 -0
  40. package/src/composables/use-playground-drag.ts +31 -0
  41. package/src/composables/use-playground.ts +16 -0
  42. package/src/composables/use-refresh.ts +6 -0
  43. package/src/composables/use-service.ts +17 -0
  44. package/src/core/index.ts +8 -0
  45. package/src/core/layer/config/editor-state-config-entity.ts +180 -0
  46. package/src/core/layer/config/index.ts +7 -0
  47. package/src/core/layer/config/playground-config-entity.ts +604 -0
  48. package/src/core/layer/index.ts +8 -0
  49. package/src/core/layer/layer.ts +248 -0
  50. package/src/core/layer/playground-layer.ts +547 -0
  51. package/src/core/pipeline/index.ts +10 -0
  52. package/src/core/pipeline/pipeline-entities-selector.ts +200 -0
  53. package/src/core/pipeline/pipeline-entities.ts +230 -0
  54. package/src/core/pipeline/pipeline-registry.ts +344 -0
  55. package/src/core/pipeline/pipeline-renderer.ts +248 -0
  56. package/src/core/pipeline/pipeline-vue-utils.ts +96 -0
  57. package/src/core/pipeline/pipeline.ts +28 -0
  58. package/src/core/utils/index.ts +12 -0
  59. package/src/core/utils/inject-provider-decorators.ts +27 -0
  60. package/src/core/utils/lazy-inject-decorators.ts +38 -0
  61. package/src/core/utils/mouse-touch-event.ts +120 -0
  62. package/src/core/utils/playground-drag.ts +511 -0
  63. package/src/core/utils/playground-gesture.spec.ts +135 -0
  64. package/src/core/utils/playground-gesture.ts +118 -0
  65. package/src/core/utils/tween.ts +156 -0
  66. package/src/core/utils/use-gesture/core/Controller.ts +212 -0
  67. package/src/core/utils/use-gesture/core/EventStore.ts +46 -0
  68. package/src/core/utils/use-gesture/core/TimeoutStore.ts +28 -0
  69. package/src/core/utils/use-gesture/core/actions.ts +63 -0
  70. package/src/core/utils/use-gesture/core/config/commonConfigResolver.ts +86 -0
  71. package/src/core/utils/use-gesture/core/config/coordinatesConfigResolver.ts +48 -0
  72. package/src/core/utils/use-gesture/core/config/dragConfigResolver.ts +140 -0
  73. package/src/core/utils/use-gesture/core/config/hoverConfigResolver.ts +11 -0
  74. package/src/core/utils/use-gesture/core/config/moveConfigResolver.ts +11 -0
  75. package/src/core/utils/use-gesture/core/config/pinchConfigResolver.ts +59 -0
  76. package/src/core/utils/use-gesture/core/config/resolver.ts +70 -0
  77. package/src/core/utils/use-gesture/core/config/scrollConfigResolver.ts +8 -0
  78. package/src/core/utils/use-gesture/core/config/sharedConfigResolver.ts +28 -0
  79. package/src/core/utils/use-gesture/core/config/support.ts +52 -0
  80. package/src/core/utils/use-gesture/core/config/wheelConfigResolver.ts +8 -0
  81. package/src/core/utils/use-gesture/core/engines/CoordinatesEngine.ts +78 -0
  82. package/src/core/utils/use-gesture/core/engines/DragEngine.ts +403 -0
  83. package/src/core/utils/use-gesture/core/engines/Engine.ts +406 -0
  84. package/src/core/utils/use-gesture/core/engines/HoverEngine.ts +43 -0
  85. package/src/core/utils/use-gesture/core/engines/MoveEngine.ts +52 -0
  86. package/src/core/utils/use-gesture/core/engines/PinchEngine.ts +345 -0
  87. package/src/core/utils/use-gesture/core/engines/ScrollEngine.ts +42 -0
  88. package/src/core/utils/use-gesture/core/engines/WheelEngine.ts +48 -0
  89. package/src/core/utils/use-gesture/core/index.ts +7 -0
  90. package/src/core/utils/use-gesture/core/parser.ts +92 -0
  91. package/src/core/utils/use-gesture/core/types/action.ts +19 -0
  92. package/src/core/utils/use-gesture/core/types/config.ts +255 -0
  93. package/src/core/utils/use-gesture/core/types/handlers.ts +73 -0
  94. package/src/core/utils/use-gesture/core/types/index.ts +11 -0
  95. package/src/core/utils/use-gesture/core/types/internalConfig.ts +80 -0
  96. package/src/core/utils/use-gesture/core/types/state.ts +275 -0
  97. package/src/core/utils/use-gesture/core/types/utils.ts +204 -0
  98. package/src/core/utils/use-gesture/core/types.ts +8 -0
  99. package/src/core/utils/use-gesture/core/utils/events.ts +158 -0
  100. package/src/core/utils/use-gesture/core/utils/fn.ts +32 -0
  101. package/src/core/utils/use-gesture/core/utils/maths.ts +63 -0
  102. package/src/core/utils/use-gesture/core/utils/state.ts +24 -0
  103. package/src/core/utils/use-gesture/core/utils.ts +8 -0
  104. package/src/core/utils/use-gesture/index.ts +6 -0
  105. package/src/core/utils/use-gesture/vanilla/DragGesture.ts +27 -0
  106. package/src/core/utils/use-gesture/vanilla/Gesture.ts +45 -0
  107. package/src/core/utils/use-gesture/vanilla/HoverGesture.ts +27 -0
  108. package/src/core/utils/use-gesture/vanilla/MoveGesture.ts +27 -0
  109. package/src/core/utils/use-gesture/vanilla/PinchGesture.ts +27 -0
  110. package/src/core/utils/use-gesture/vanilla/Recognizer.ts +43 -0
  111. package/src/core/utils/use-gesture/vanilla/ScrollGesture.ts +27 -0
  112. package/src/core/utils/use-gesture/vanilla/WheelGesture.ts +27 -0
  113. package/src/core/utils/use-gesture/vanilla/createGesture.ts +18 -0
  114. package/src/core/utils/use-gesture/vanilla/index.ts +18 -0
  115. package/src/env.d.ts +10 -0
  116. package/src/index.ts +17 -0
  117. package/src/playground-config.ts +70 -0
  118. package/src/playground-container.ts +129 -0
  119. package/src/playground-contribution.ts +81 -0
  120. package/src/playground-mock-tools.ts +143 -0
  121. package/src/playground.ts +401 -0
  122. package/src/plugin/index.ts +6 -0
  123. package/src/plugin/plugin.ts +226 -0
  124. package/src/services/clipboard-service.ts +40 -0
  125. package/src/services/context-menu-service.ts +25 -0
  126. package/src/services/index.ts +10 -0
  127. package/src/services/logger-service.ts +49 -0
  128. package/src/services/selection-service.ts +54 -0
  129. package/src/services/storage-service.ts +65 -0
  130. package/src/vue/index.ts +8 -0
  131. package/src/vue/playground-vue-context.ts +22 -0
  132. package/src/vue/playground-vue-provider.ts +115 -0
  133. package/src/vue/playground-vue-renderer.vue +44 -0
@@ -0,0 +1,344 @@
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
+ ConflatableMessage,
9
+ type IMessageHandler,
10
+ type Message,
11
+ MessageLoop,
12
+ } from '@phosphor/messaging';
13
+ import { Disposable, DisposableCollection, Emitter } from '@flowgram-vue/utils';
14
+
15
+ import { type Layer, type LayerRegistry, PlaygroundConfigEntity } from '../layer';
16
+ import {
17
+ // AbleManager,
18
+ ConfigEntity,
19
+ Entity,
20
+ EntityManager,
21
+ // getAbleMetadata,
22
+ getEntityDatasMetadata,
23
+ getEntityMetadata,
24
+ injectPlaygroundContext,
25
+ PlaygroundContext,
26
+ } from '../../common';
27
+ import { PipelineRenderer } from './pipeline-renderer';
28
+ import { PipelineEntitiesSelector } from './pipeline-entities-selector';
29
+ import { PipelineEntitiesImpl } from './pipeline-entities';
30
+ // import { PipelineDispatcher } from './pipeline-dispatcher';
31
+ import {
32
+ type PipeEventName,
33
+ type PipelineDimension,
34
+ type PipelineEventHandler,
35
+ type PipelineEventRegsiter,
36
+ PipelineLayerFactory,
37
+ type PipeSupportEvent,
38
+ } from './pipeline';
39
+
40
+ export enum PipelineMessage {
41
+ ZOOM = 'PIPELINE_ZOOM',
42
+ SCROLL = 'PIPELINE_SCROLL',
43
+ }
44
+ const zoomMessage = new ConflatableMessage(PipelineMessage.ZOOM);
45
+ const scrollMessage = new ConflatableMessage(PipelineMessage.SCROLL);
46
+ /**
47
+ * pipeline 注册器,用于注册一些事件
48
+ */
49
+ @injectable()
50
+ export class PipelineRegistry implements Disposable, IMessageHandler {
51
+ private _isFocused = false;
52
+
53
+ protected toDispose = new DisposableCollection();
54
+
55
+ protected allLayersMap = new Map<LayerRegistry, Layer>();
56
+
57
+ readonly onResizeEmitter = new Emitter<PipelineDimension>();
58
+
59
+ readonly onFocusEmitter = new Emitter<void>();
60
+
61
+ readonly onBlurEmitter = new Emitter<void>();
62
+
63
+ readonly onZoomEmitter = new Emitter<number>();
64
+
65
+ readonly onScrollEmitter = new Emitter<{ scrollX: number; scrollY: number }>();
66
+
67
+ readonly onFocus = this.onFocusEmitter.event;
68
+
69
+ readonly onBlur = this.onBlurEmitter.event;
70
+
71
+ readonly onZoom = this.onZoomEmitter.event;
72
+
73
+ readonly onScroll = this.onScrollEmitter.event;
74
+
75
+ constructor() {
76
+ this.toDispose.pushAll([
77
+ this.onResizeEmitter,
78
+ this.onFocusEmitter,
79
+ this.onZoomEmitter,
80
+ this.onBlurEmitter,
81
+ this.onScrollEmitter,
82
+ ]);
83
+ this.onFocusEmitter.event(() => {
84
+ this._isFocused = true;
85
+ });
86
+ this.onBlurEmitter.event(() => {
87
+ this._isFocused = false;
88
+ });
89
+ }
90
+
91
+ // @inject(PipelineDispatcher) dispatcher: PipelineDispatcher;
92
+
93
+ @inject(PipelineRenderer) renderer: PipelineRenderer;
94
+
95
+ @inject(PipelineEntitiesSelector) selector: PipelineEntitiesSelector;
96
+
97
+ @inject(EntityManager) entityManager: EntityManager;
98
+
99
+ // @inject(AbleManager) ableManager: AbleManager;
100
+
101
+ @injectPlaygroundContext() context: PlaygroundContext;
102
+
103
+ @inject(PipelineLayerFactory) layerFactory: PipelineLayerFactory;
104
+
105
+ // @inject(SelectionService) @optional() selectionService?: SelectionService;
106
+ protected playgroundEvents: {
107
+ [key: string]: { handlers: PipelineEventRegsiter[] } & Disposable;
108
+ } = {};
109
+
110
+ protected globalEvents: {
111
+ [key: string]: { handlers: PipelineEventRegsiter[] } & Disposable;
112
+ } = {};
113
+
114
+ _listenEvent(
115
+ name: PipeEventName,
116
+ handle: PipelineEventHandler,
117
+ isGlobal: boolean,
118
+ priority = 0,
119
+ options?: AddEventListenerOptions
120
+ ): Disposable {
121
+ const eventsCache = isGlobal ? this.globalEvents : this.playgroundEvents;
122
+ const domNode = isGlobal ? document : this.renderer.node.parentNode!;
123
+ let eventRegister = eventsCache[name];
124
+ if (!eventRegister) {
125
+ const realHandler = {
126
+ handleEvent: (e: PipeSupportEvent) => {
127
+ const list = eventRegister.handlers;
128
+ for (let i = 0, len = list.length; i < len; i++) {
129
+ const prevent = list[i].handle(e);
130
+ /* v8 ignore next 1 */
131
+ if (prevent) return;
132
+ }
133
+ },
134
+ };
135
+ domNode.addEventListener(name, realHandler, options);
136
+ eventRegister = eventsCache[name] = {
137
+ handlers: [],
138
+ dispose: () => {
139
+ domNode.removeEventListener(name, realHandler);
140
+ delete eventsCache[name];
141
+ },
142
+ };
143
+ }
144
+ const { handlers } = eventRegister;
145
+ const item = { handle, priority };
146
+ /**
147
+ * handlers 排序:
148
+ * 1. 后注册先执行 (符合冒泡规则)
149
+ * 2. 按 priority 排序
150
+ */
151
+ handlers.unshift(item);
152
+ handlers.sort((a, b) => b.priority - a.priority);
153
+ const dispose = Disposable.create(() => {
154
+ const index = eventRegister.handlers.indexOf(item);
155
+ if (index !== -1) eventRegister.handlers.splice(index, 1);
156
+ if (eventRegister.handlers.length === 0) {
157
+ eventRegister.dispose();
158
+ }
159
+ });
160
+ this.toDispose.push(dispose);
161
+ return dispose;
162
+ }
163
+
164
+ /**
165
+ * 监听画布上的浏览器事件
166
+ */
167
+ listenPlaygroundEvent(
168
+ name: PipeEventName,
169
+ handle: (event: PipeSupportEvent) => boolean | undefined,
170
+ priority?: number,
171
+ options?: AddEventListenerOptions
172
+ ): Disposable {
173
+ return this._listenEvent(name, handle, false, priority, options);
174
+ }
175
+
176
+ /**
177
+ * 监听全局的事件
178
+ * @param name
179
+ * @param handle
180
+ */
181
+ listenGlobalEvent(
182
+ name: PipeEventName,
183
+ handle: (event: PipeSupportEvent) => boolean | undefined,
184
+ priority?: number,
185
+ options?: AddEventListenerOptions
186
+ ): Disposable {
187
+ return this._listenEvent(name, handle, true, priority, options);
188
+ }
189
+
190
+ /**
191
+ * 注册 layer
192
+ * @param layerRegistry
193
+ * @param layerOptions 配置
194
+ */
195
+ registerLayer<P extends Layer = Layer>(
196
+ layerRegistry: LayerRegistry<P>,
197
+ layerOptions?: P['options']
198
+ ): void {
199
+ // layer 不允许重复添加
200
+ if (this.allLayersMap.has(layerRegistry)) return;
201
+ const layer = this.layerFactory(layerRegistry, layerOptions);
202
+ this.allLayersMap.set(layerRegistry, layer);
203
+ // const ableRegistries = getAbleMetadata(layerRegistry);
204
+ const entityRegistries = getEntityMetadata(layerRegistry);
205
+ const entityDataRegistries = getEntityDatasMetadata(layerRegistry);
206
+ // ableRegistries.forEach(r => this.ableManager.registerAble(r));
207
+ entityRegistries.forEach((r) => {
208
+ this.entityManager.registerEntity(r);
209
+ if (Entity.isRegistryOf(r, ConfigEntity)) {
210
+ // 自动创建注册的 entity
211
+ this.entityManager.createEntity(r);
212
+ }
213
+ });
214
+ entityDataRegistries.forEach((r) => {
215
+ this.entityManager.registerEntity(r.entity);
216
+ this.entityManager.registerEntityData(r.data);
217
+ });
218
+ // this.selector.subscribeAVbles(layer, ableRegistries);
219
+ this.selector.subscribeEntities(layer, entityRegistries);
220
+ entityDataRegistries.forEach((r) =>
221
+ this.selector.subscribleEntityByData(layer, r.entity, r.data)
222
+ );
223
+ layer.observeManager = new PipelineEntitiesImpl(this.entityManager);
224
+ // layer.commands = this.commands;
225
+ // layer.menus = this.menus;
226
+ // layer.keybindings = this.keybindings;
227
+ // layer.selectionService = this.selectionService;
228
+ layer.reloadEntities = () => {
229
+ const result = this.selector.getLayerData(layer);
230
+ if (result.changed) {
231
+ (layer.observeManager as PipelineEntitiesImpl).load(
232
+ result.observeEntities,
233
+ result.observeDatas
234
+ );
235
+ }
236
+ return result.changed;
237
+ };
238
+ // layer.dispatch = (payloadKey: string | symbol, payloadValue: object, cb?: () => void) => {
239
+ // const changedEntities = this.dispatcher.dispatch(payloadKey, payloadValue);
240
+ // if (cb) {
241
+ // if (changedEntities.length > 0) {
242
+ // const changed = layer.reloadEntities();
243
+ // if (changed && layer.autorun) layer.autorun();
244
+ // }
245
+ // cb();
246
+ // }
247
+ // };
248
+ // @ts-ignore
249
+ layer.listenPlaygroundEvent = this.listenPlaygroundEvent.bind(this);
250
+ // @ts-ignore
251
+ layer.listenGlobalEvent = this.listenGlobalEvent.bind(this);
252
+ layer.config = this.configEntity;
253
+ layer.getOtherLayer = this.getLayer.bind(this);
254
+ Object.defineProperty(layer, 'isFocused', {
255
+ get: () => this._isFocused,
256
+ });
257
+ if (layer.onResize) {
258
+ this.onResize(layer.onResize.bind(layer));
259
+ }
260
+ if (layer.onBlur) {
261
+ this.onBlurEmitter.event(layer.onBlur.bind(layer));
262
+ }
263
+ if (layer.onFocus) {
264
+ this.onFocusEmitter.event(layer.onFocus.bind(layer));
265
+ }
266
+ if (layer.onZoom) {
267
+ this.onZoomEmitter.event(layer.onZoom.bind(layer));
268
+ }
269
+ if (layer.onScroll) {
270
+ this.onScrollEmitter.event(layer.onScroll.bind(layer));
271
+ }
272
+ if (layer.onViewportChange) {
273
+ const viewportChange = layer.onViewportChange.bind(layer);
274
+ this.onResize(viewportChange);
275
+ this.onZoomEmitter.event(viewportChange);
276
+ this.onScrollEmitter.event(viewportChange);
277
+ }
278
+ if (layer.onReadonlyOrDisabledChange) {
279
+ this.configEntity.onReadonlyOrDisabledChange(layer.onReadonlyOrDisabledChange.bind(layer));
280
+ }
281
+ this.renderer.addLayer(layer);
282
+ }
283
+
284
+ /**
285
+ * 获取 layer
286
+ */
287
+ getLayer<T extends Layer>(layerRegistry: LayerRegistry<T>): T | undefined {
288
+ return this.allLayersMap.get(layerRegistry) as T;
289
+ }
290
+
291
+ get configEntity(): PlaygroundConfigEntity {
292
+ return this.entityManager.getEntity<PlaygroundConfigEntity>(PlaygroundConfigEntity, true)!;
293
+ }
294
+
295
+ ready(): void {
296
+ const config = this.configEntity;
297
+ let lastScale = config.finalScale;
298
+ let lastScroll = config.scrollData;
299
+ // 监听 zoom
300
+ config.onConfigChanged(() => {
301
+ /* v8 ignore next 10 */
302
+ const newScale = config.finalScale;
303
+ const newScroll = config.scrollData;
304
+ if (newScale !== lastScale) {
305
+ lastScale = newScale;
306
+ if (process.env.NODE_ENV === 'test') {
307
+ this.processMessage(zoomMessage);
308
+ } else {
309
+ MessageLoop.postMessage(this, zoomMessage);
310
+ }
311
+ }
312
+ if (lastScroll.scrollX !== newScroll.scrollX || lastScroll.scrollY !== newScroll.scrollY) {
313
+ lastScroll = newScroll;
314
+ if (process.env.NODE_ENV === 'test') {
315
+ this.processMessage(scrollMessage);
316
+ } else {
317
+ MessageLoop.postMessage(this, scrollMessage);
318
+ }
319
+ }
320
+ });
321
+ }
322
+
323
+ processMessage(msg: Message): void {
324
+ const config = this.configEntity;
325
+ switch (msg.type) {
326
+ case PipelineMessage.SCROLL:
327
+ this.onScrollEmitter.fire(config.scrollData);
328
+ break;
329
+ case PipelineMessage.ZOOM:
330
+ this.onZoomEmitter.fire(config.finalScale);
331
+ break;
332
+ default:
333
+ }
334
+ }
335
+
336
+ /**
337
+ * pipline 大小变化时候会触发
338
+ */
339
+ readonly onResize = this.onResizeEmitter.event;
340
+
341
+ dispose(): void {
342
+ this.toDispose.dispose();
343
+ }
344
+ }
@@ -0,0 +1,248 @@
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
+ ConflatableMessage,
9
+ type IMessageHandler,
10
+ type Message,
11
+ MessageLoop,
12
+ } from '@phosphor/messaging';
13
+ import { Disposable, DisposableCollection, domUtils, Emitter } from '@flowgram-vue/utils';
14
+ import { defineComponent, type Component } from 'vue';
15
+
16
+ import { type Layer } from '../layer';
17
+ import { LoggerService } from '../../services';
18
+ import { EntityManager } from '../../common';
19
+ import { createLayerVueAutorun } from './pipeline-vue-utils';
20
+ import { PipelineEntitiesSelector } from './pipeline-entities-selector';
21
+ import { type PipelineEntitiesImpl } from './pipeline-entities';
22
+
23
+ export const FLUSH_LAYER_REQUEST = 'flush-layer-request';
24
+
25
+ let id = 0;
26
+
27
+ export class FlushLayerMessage extends ConflatableMessage {
28
+ constructor(readonly layer: Layer) {
29
+ super(`${FLUSH_LAYER_REQUEST}_layer${id++}`);
30
+ }
31
+ }
32
+
33
+ /**
34
+ * pipeline 渲染器
35
+ */
36
+ @injectable()
37
+ export class PipelineRenderer implements Disposable, IMessageHandler {
38
+ public isReady = false;
39
+
40
+ protected onAllLayersRenderedEmitter = new Emitter<void>();
41
+
42
+ protected toDispose = new DisposableCollection();
43
+
44
+ readonly layers: Layer[] = [];
45
+
46
+ protected forceUpdates: Set<Layer> = new Set();
47
+
48
+ readonly layerAutorunMap: Map<Layer, () => void> = new Map();
49
+
50
+ readonly layerRenderedMap: Map<Layer, boolean> = new Map();
51
+
52
+ readonly layerFlushMessages: Map<Layer, FlushLayerMessage> = new Map();
53
+
54
+ readonly node = domUtils.createDivWithClass('gedit-playground-pipeline');
55
+
56
+ /**
57
+ * 所有 Layer 第一次渲染完成后触发
58
+ */
59
+ readonly onAllLayersRendered = this.onAllLayersRenderedEmitter.event;
60
+
61
+ @inject(LoggerService) protected readonly loggerService: LoggerService;
62
+
63
+ constructor(
64
+ @inject(PipelineEntitiesSelector)
65
+ protected readonly selector: PipelineEntitiesSelector,
66
+ @inject(EntityManager) entityManager: EntityManager
67
+ // @inject(AbleManager) ableManager: AbleManager,
68
+ ) {
69
+ /**
70
+ * entity 修改触发 layer 更新
71
+ */
72
+ this.toDispose.push(
73
+ entityManager.onEntityChange((entityType: string) => {
74
+ const layers = this.selector.entityLayerMap.get(entityType);
75
+ if (layers) layers.forEach((layer) => this.updateLayer(layer));
76
+ })
77
+ );
78
+ this.toDispose.push(this.onAllLayersRenderedEmitter);
79
+ }
80
+
81
+ reportLayerRendered(layer: Layer): void {
82
+ this.layerRenderedMap.set(layer, true);
83
+ const allLayersRendered = Array.from(this.layerRenderedMap.values()).every((v) => v);
84
+ if (allLayersRendered) {
85
+ // logger 事件点位上报
86
+ this.loggerService.onAllLayersRendered();
87
+ this.onAllLayersRenderedEmitter.fire();
88
+ // e2e 性能测试时会注入以下全局变量
89
+ if ((window as any).REPORT_TTI_FOR_E2E) {
90
+ (window as any).REPORT_TTI_FOR_E2E(
91
+ // 由于 e2e 环境,performance 耗时即为 tti 的完整耗时。
92
+ performance.now(),
93
+ performance.getEntriesByType('resource')
94
+ );
95
+ }
96
+ }
97
+ }
98
+
99
+ addLayer(layer: Layer): void {
100
+ this.layers.push(layer);
101
+ this.toDispose.push(layer);
102
+ this.layerFlushMessages.set(layer, new FlushLayerMessage(layer));
103
+ layer.pipelineNode = this.node;
104
+ layer.playgroundNode = this.node.parentElement!;
105
+ // Auto create node
106
+ if ((layer.autorun || layer.render) && !layer.node) {
107
+ layer.node = document.createElement('div');
108
+ }
109
+ // 把 layer 加到父亲节点上
110
+ if (layer.node) {
111
+ this.node.appendChild(layer.node);
112
+ layer.node.classList.add('gedit-playground-layer');
113
+ }
114
+ if (layer.autorun) {
115
+ const autorun = layer.autorun.bind(layer);
116
+ this.layerAutorunMap.set(layer, autorun);
117
+ // 重载 layer autorun
118
+ layer.autorun = () => {
119
+ this.updateLayer(layer, true);
120
+ };
121
+ } else if (layer.render) {
122
+ this.layerRenderedMap.set(layer, false);
123
+ const render = layer.render.bind(layer);
124
+ const autorun = createLayerVueAutorun(
125
+ layer,
126
+ render,
127
+ this.reportLayerRendered.bind(this),
128
+ this
129
+ );
130
+ this.layerAutorunMap.set(layer, autorun.autorun);
131
+ this.toDispose.push(Disposable.create(() => autorun.dispose()));
132
+ if (process.env.NODE_ENV === 'test') {
133
+ // 不对外暴露_render 字段
134
+ (layer as any)._render = layer.render;
135
+ }
136
+ // 重载 layer.render:外部调用时走 flush,而不是直接画
137
+ layer.render = () => {
138
+ this.updateLayer(layer, true);
139
+ return null;
140
+ };
141
+ }
142
+ }
143
+
144
+ flush(forceUpdate?: boolean): void {
145
+ this.layers.forEach((layer) => {
146
+ this.updateLayer(layer, forceUpdate);
147
+ });
148
+ }
149
+
150
+ ready(): void {
151
+ this.layers.forEach((layer) => {
152
+ // 先加载一次数据,保证 ready 时候能运行
153
+ this.loadLayerEntities(layer);
154
+ if (layer.onReady) layer.onReady();
155
+ });
156
+ this.isReady = true;
157
+ // 第一次先渲染一次
158
+ this.flush(true);
159
+ }
160
+
161
+ dispose(): void {
162
+ this.toDispose.dispose();
163
+ this.node.remove();
164
+ }
165
+
166
+ processMessage(msg: Message): void {
167
+ if (msg instanceof FlushLayerMessage) {
168
+ this.onFlushRequest(msg.layer);
169
+ }
170
+ }
171
+
172
+ protected loadLayerEntities(layer: Layer): boolean {
173
+ const result = this.selector.getLayerData(layer);
174
+ if (result.changed) {
175
+ // 这里更新 layer 的 entities
176
+ (layer.observeManager as PipelineEntitiesImpl).load(
177
+ result.observeEntities,
178
+ result.observeDatas
179
+ );
180
+ }
181
+ return result.changed;
182
+ }
183
+
184
+ protected onFlushRequest(layer: Layer): boolean {
185
+ // 只有 ready 之后才能执行 autorun
186
+ if (!this.isReady || this.toDispose.disposed) return false;
187
+ const startRenderTime = performance.now();
188
+ const trackRenderPerformance = () => {
189
+ const renderDuration = performance.now() - startRenderTime;
190
+ // 小于 4ms 的渲染时间不需要记录
191
+ if (renderDuration < 4) {
192
+ return;
193
+ }
194
+ this.loggerService.onFlushRequest(renderDuration);
195
+ };
196
+ const autorun = this.layerAutorunMap.get(layer);
197
+ const changed = this.loadLayerEntities(layer);
198
+ // 只有修改或强制刷新才会刷新
199
+ if (autorun && (changed || this.forceUpdates.has(layer))) {
200
+ this.forceUpdates.delete(layer);
201
+ try {
202
+ // console.time(`layer ${layer.constructor.name}`)
203
+ autorun();
204
+ // console.timeEnd(`layer ${layer.constructor.name}`)
205
+ /* v8 ignore next 3 */
206
+ } catch (e) {
207
+ console.error(e);
208
+ }
209
+ trackRenderPerformance();
210
+ return true;
211
+ /* v8 ignore next 2 */
212
+ }
213
+ trackRenderPerformance();
214
+ return false;
215
+ }
216
+
217
+ /**
218
+ * 1. PostMessage: 会将消息在 nextTick 执行
219
+ * 2. ConflatableMessage: 当多个消息进来会在下一个 nextTick 做合并
220
+ * 3. 图层相互隔离,即时一层挂了也不受影响
221
+ */
222
+ updateLayer(layer: Layer, forceUpdate?: boolean): void {
223
+ if (forceUpdate) {
224
+ this.forceUpdates.add(layer);
225
+ }
226
+ if (process.env.NODE_ENV === 'test') {
227
+ this.onFlushRequest(layer);
228
+ } else {
229
+ MessageLoop.postMessage(this, this.layerFlushMessages.get(layer)!);
230
+ }
231
+ }
232
+
233
+ private vueComp?: Component;
234
+
235
+ /**
236
+ * 转成 Vue 组件。Layer 已通过 createApp 挂到 layer.node,这里仅保留 API 兼容。
237
+ */
238
+ toVueComponent(): Component {
239
+ if (this.vueComp) return this.vueComp;
240
+ this.vueComp = defineComponent({
241
+ name: 'PipelineVueLayers',
242
+ setup() {
243
+ return () => null;
244
+ },
245
+ });
246
+ return this.vueComp;
247
+ }
248
+ }
@@ -0,0 +1,96 @@
1
+ /**
2
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
3
+ * SPDX-License-Identifier: MIT
4
+ */
5
+
6
+ import { NOOP } from '@flowgram-vue/utils';
7
+ import {
8
+ createApp,
9
+ defineComponent,
10
+ h,
11
+ isVNode,
12
+ nextTick,
13
+ onMounted,
14
+ shallowRef,
15
+ type App,
16
+ type Component,
17
+ type VNode,
18
+ } from 'vue';
19
+
20
+ import type { Layer } from '../layer';
21
+ import { type PipelineRenderer } from './pipeline-renderer';
22
+
23
+ export type LayerRenderResult = Component | VNode | null | void;
24
+
25
+ export interface LayerVueAutorun {
26
+ autorun: () => void;
27
+ dispose: () => void;
28
+ }
29
+
30
+ export function createLayerVueAutorun(
31
+ layer: Layer,
32
+ originRenderer: () => LayerRenderResult,
33
+ renderedCb: (layer: Layer) => void,
34
+ pipelineRenderer: PipelineRenderer
35
+ ): LayerVueAutorun {
36
+ let app: App | null = null;
37
+ let update = NOOP;
38
+
39
+ const Root = defineComponent({
40
+ name: `PlaygroundVueLayer_${layer.constructor.name}`,
41
+ setup() {
42
+ const version = shallowRef(0);
43
+ update = () => {
44
+ version.value += 1;
45
+ };
46
+ onMounted(() => {
47
+ // 等 ready() 栈(含 contribution onReady)结束后再上报
48
+ nextTick(() => renderedCb(layer));
49
+ });
50
+ return () => {
51
+ void version.value;
52
+ if (!pipelineRenderer.isReady) {
53
+ return null;
54
+ }
55
+ try {
56
+ const result = originRenderer();
57
+ if (result == null) {
58
+ return null;
59
+ }
60
+ if (isVNode(result)) {
61
+ return result;
62
+ }
63
+ return h(result as Component);
64
+ } catch (e) {
65
+ console.error(`Render Layer "${layer.constructor.name}" error `, e);
66
+ return null;
67
+ }
68
+ };
69
+ },
70
+ });
71
+
72
+ function ensureMounted(): void {
73
+ if (app || !layer.node) {
74
+ return;
75
+ }
76
+ app = createApp(Root);
77
+ app.mount(layer.node);
78
+ }
79
+
80
+ return {
81
+ autorun: () => {
82
+ if (!app) {
83
+ ensureMounted();
84
+ return;
85
+ }
86
+ update();
87
+ },
88
+ dispose: () => {
89
+ if (app) {
90
+ app.unmount();
91
+ app = null;
92
+ }
93
+ update = NOOP;
94
+ },
95
+ };
96
+ }
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
3
+ * SPDX-License-Identifier: MIT
4
+ */
5
+
6
+ import { type Layer, type LayerRegistry } from '../layer';
7
+
8
+ export type PipeSupportEvent = MouseEvent | DragEvent | KeyboardEvent | UIEvent | TouchEvent | any;
9
+ export type PipeEventName = string;
10
+ export interface PipelineDimension {
11
+ width: number;
12
+ height: number;
13
+ }
14
+
15
+ export type PipelineEventHandler = (event: PipeSupportEvent) => boolean | undefined;
16
+
17
+ export interface PipelineEventRegsiter {
18
+ handle: PipelineEventHandler;
19
+ priority: number;
20
+ }
21
+
22
+ export enum PipelineLayerPriority {
23
+ BASE_LAYER = -2, // 优先级最低
24
+ TOOL_LAYER = -1, // 工具层
25
+ NORMAL_LAYER, // 使用层
26
+ }
27
+ export const PipelineLayerFactory = Symbol('PipelineLayerFactory');
28
+ export type PipelineLayerFactory = (layerRegistry: LayerRegistry, layerOptions?: any) => Layer;