@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,401 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { inject, injectable, optional } from 'inversify';
|
|
7
|
+
import { FlowDocument, FlowNodeTransformData } from '@flowgram-vue/document';
|
|
8
|
+
import {
|
|
9
|
+
Layer,
|
|
10
|
+
observeEntity,
|
|
11
|
+
PlaygroundConfigEntity,
|
|
12
|
+
PlaygroundDrag,
|
|
13
|
+
} from '@flowgram-vue/core';
|
|
14
|
+
import { domUtils, Rectangle } from '@flowgram-vue/utils';
|
|
15
|
+
// import {
|
|
16
|
+
// FlowDocument,
|
|
17
|
+
// FlowDocumentTransformerEntity,
|
|
18
|
+
// FlowNodeTransformData,
|
|
19
|
+
// } from '@flowgram-vue/document'
|
|
20
|
+
|
|
21
|
+
import { ScrollBarEvents } from '../utils/scroll-bar-events';
|
|
22
|
+
import { getScrollViewport } from '../utils';
|
|
23
|
+
|
|
24
|
+
// 中间区域边框宽度
|
|
25
|
+
const BORDER_WIDTH = 2;
|
|
26
|
+
|
|
27
|
+
// 右下角预留的 offset
|
|
28
|
+
const BLOCK_OFFSET = 11;
|
|
29
|
+
|
|
30
|
+
// 滚动条样式宽
|
|
31
|
+
const SCROLL_BAR_WIDTH = '7px';
|
|
32
|
+
|
|
33
|
+
// 滚动条显示状态
|
|
34
|
+
enum ScrollBarVisibility {
|
|
35
|
+
Show = 'show',
|
|
36
|
+
Hidden = 'hidden',
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface ScrollBarOptions {
|
|
40
|
+
/**
|
|
41
|
+
* 显示滚动条的时机,可选常驻或滚动时显示
|
|
42
|
+
*/
|
|
43
|
+
showScrollBars: 'whenScrolling' | 'always';
|
|
44
|
+
getBounds(): Rectangle;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* 渲染滚动条 layer
|
|
49
|
+
*/
|
|
50
|
+
@injectable()
|
|
51
|
+
export class FlowScrollBarLayer extends Layer<ScrollBarOptions> {
|
|
52
|
+
@optional()
|
|
53
|
+
@inject(ScrollBarEvents)
|
|
54
|
+
readonly events?: ScrollBarEvents;
|
|
55
|
+
|
|
56
|
+
@inject(FlowDocument) @optional() flowDocument?: FlowDocument;
|
|
57
|
+
|
|
58
|
+
@observeEntity(PlaygroundConfigEntity)
|
|
59
|
+
protected playgroundConfigEntity: PlaygroundConfigEntity;
|
|
60
|
+
|
|
61
|
+
// @observeEntity(FlowDocumentTransformerEntity) readonly documentTransformer: FlowDocumentTransformerEntity
|
|
62
|
+
|
|
63
|
+
// 右滚动区域
|
|
64
|
+
readonly rightScrollBar = domUtils.createDivWithClass('gedit-playground-scroll-right');
|
|
65
|
+
|
|
66
|
+
// 右滚动条
|
|
67
|
+
readonly rightScrollBarBlock = domUtils.createDivWithClass('gedit-playground-scroll-right-block');
|
|
68
|
+
|
|
69
|
+
// 底滚动区域
|
|
70
|
+
readonly bottomScrollBar = domUtils.createDivWithClass('gedit-playground-scroll-bottom');
|
|
71
|
+
|
|
72
|
+
// 底滚动条
|
|
73
|
+
readonly bottomScrollBarBlock = domUtils.createDivWithClass(
|
|
74
|
+
'gedit-playground-scroll-bottom-block',
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
// 最左边的位置
|
|
78
|
+
private mostLeft: number;
|
|
79
|
+
|
|
80
|
+
// 最右边的位置
|
|
81
|
+
private mostRight: number;
|
|
82
|
+
|
|
83
|
+
// 最上面的位置
|
|
84
|
+
private mostTop: number;
|
|
85
|
+
|
|
86
|
+
// 最下面的位置
|
|
87
|
+
private mostBottom: number;
|
|
88
|
+
|
|
89
|
+
// 视区宽度
|
|
90
|
+
private viewportWidth: number;
|
|
91
|
+
|
|
92
|
+
// 视区高度
|
|
93
|
+
private viewportHeight: number;
|
|
94
|
+
|
|
95
|
+
// 元素宽高
|
|
96
|
+
private width: number;
|
|
97
|
+
|
|
98
|
+
private height: number;
|
|
99
|
+
|
|
100
|
+
// 底部滚动条宽度
|
|
101
|
+
private scrollBottomWidth: number;
|
|
102
|
+
|
|
103
|
+
// 右侧滚动条高度
|
|
104
|
+
private scrollRightHeight: number;
|
|
105
|
+
|
|
106
|
+
// 缩放比
|
|
107
|
+
private scale: number;
|
|
108
|
+
|
|
109
|
+
// 总滚动距离
|
|
110
|
+
private sum = 0;
|
|
111
|
+
|
|
112
|
+
// 初始 x 轴滚动距离
|
|
113
|
+
private initialScrollX = 0;
|
|
114
|
+
|
|
115
|
+
// 初始 y 轴滚动距离
|
|
116
|
+
private initialScrollY = 0;
|
|
117
|
+
|
|
118
|
+
// 隐藏滚动条的时延
|
|
119
|
+
private hideTimeout: number | undefined;
|
|
120
|
+
|
|
121
|
+
// 浏览器视图宽度
|
|
122
|
+
get clientViewportWidth(): number {
|
|
123
|
+
return this.viewportWidth * this.scale - BLOCK_OFFSET;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// 浏览器视图高度
|
|
127
|
+
get clientViewportHeight(): number {
|
|
128
|
+
return this.viewportHeight * this.scale - BLOCK_OFFSET;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// 视图的完整宽度
|
|
132
|
+
get viewportFullWidth(): number {
|
|
133
|
+
return this.mostLeft - this.mostRight;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// 视图的完整高度
|
|
137
|
+
get viewportFullHeight(): number {
|
|
138
|
+
return this.mostTop - this.mostBottom;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// 视图的可移动宽度
|
|
142
|
+
get viewportMoveWidth(): number {
|
|
143
|
+
return this.mostLeft - this.mostRight + this.width;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// 视图的可移动高度
|
|
147
|
+
get viewportMoveHeight(): number {
|
|
148
|
+
return this.mostTop - this.mostBottom + this.height;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
getToLeft(scrollX: number): number {
|
|
152
|
+
return ((scrollX - this.mostRight) / this.viewportMoveWidth) * this.clientViewportWidth;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
getToTop(scrollY: number): number {
|
|
156
|
+
return ((scrollY - this.mostBottom) / this.viewportMoveHeight) * this.clientViewportHeight;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
clickRightScrollBar(e: MouseEvent) {
|
|
160
|
+
e.preventDefault();
|
|
161
|
+
e.stopPropagation();
|
|
162
|
+
const ratio = 1 - (e?.y || 0) / this.clientViewportHeight;
|
|
163
|
+
const scrollY = (this.mostTop - this.viewportFullHeight * ratio) * this.scale;
|
|
164
|
+
|
|
165
|
+
// 滚动到指定位置
|
|
166
|
+
this.playgroundConfigEntity.scroll(
|
|
167
|
+
{
|
|
168
|
+
scrollY,
|
|
169
|
+
},
|
|
170
|
+
false,
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
clickBottomScrollBar(e: MouseEvent) {
|
|
175
|
+
e.preventDefault();
|
|
176
|
+
e.stopPropagation();
|
|
177
|
+
const ratio = 1 - (e?.x || 0) / this.clientViewportWidth;
|
|
178
|
+
const scrollX = (this.mostLeft - this.viewportFullWidth * ratio) * this.scale;
|
|
179
|
+
|
|
180
|
+
// 滚动到指定位置
|
|
181
|
+
this.playgroundConfigEntity.scroll(
|
|
182
|
+
{
|
|
183
|
+
scrollX,
|
|
184
|
+
},
|
|
185
|
+
false,
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
onBoardingToast() {
|
|
190
|
+
// onBoarding 逻辑,滚动条指示优化,弹出 toast
|
|
191
|
+
this.events?.dragStart();
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
protected bottomGrabDragger = new PlaygroundDrag({
|
|
195
|
+
onDragStart: e => {
|
|
196
|
+
this.config.updateCursor('grabbing');
|
|
197
|
+
this.sum = 0;
|
|
198
|
+
this.initialScrollX = this.config.getViewport().x;
|
|
199
|
+
this.onBoardingToast();
|
|
200
|
+
},
|
|
201
|
+
onDrag: e => {
|
|
202
|
+
this.sum += e.movingDelta.x;
|
|
203
|
+
this.playgroundConfigEntity.scroll(
|
|
204
|
+
{
|
|
205
|
+
scrollX:
|
|
206
|
+
(this.initialScrollX +
|
|
207
|
+
(this.sum * this.viewportFullWidth) /
|
|
208
|
+
(this.clientViewportWidth - this.scrollBottomWidth)) *
|
|
209
|
+
this.scale,
|
|
210
|
+
},
|
|
211
|
+
false,
|
|
212
|
+
);
|
|
213
|
+
},
|
|
214
|
+
onDragEnd: e => {
|
|
215
|
+
this.config.updateCursor('default');
|
|
216
|
+
},
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
protected rightGrabDragger = new PlaygroundDrag({
|
|
220
|
+
onDragStart: e => {
|
|
221
|
+
this.config.updateCursor('grabbing');
|
|
222
|
+
this.sum = 0;
|
|
223
|
+
this.initialScrollY = this.config.getViewport().y;
|
|
224
|
+
this.onBoardingToast();
|
|
225
|
+
},
|
|
226
|
+
onDrag: e => {
|
|
227
|
+
this.sum += e.movingDelta.y;
|
|
228
|
+
this.playgroundConfigEntity.scroll(
|
|
229
|
+
{
|
|
230
|
+
scrollY:
|
|
231
|
+
(this.initialScrollY +
|
|
232
|
+
(this.sum * this.viewportFullHeight) /
|
|
233
|
+
(this.clientViewportHeight - this.scrollRightHeight)) *
|
|
234
|
+
this.scale,
|
|
235
|
+
},
|
|
236
|
+
false,
|
|
237
|
+
);
|
|
238
|
+
},
|
|
239
|
+
onDragEnd: e => {
|
|
240
|
+
this.config.updateCursor('default');
|
|
241
|
+
},
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
protected changeScrollBarVisibility(scrollBar: HTMLDivElement, status: ScrollBarVisibility) {
|
|
245
|
+
const addClassName =
|
|
246
|
+
status === ScrollBarVisibility.Show
|
|
247
|
+
? 'gedit-playground-scroll-show'
|
|
248
|
+
: 'gedit-playground-scroll-hidden';
|
|
249
|
+
const delClassName =
|
|
250
|
+
status === ScrollBarVisibility.Show
|
|
251
|
+
? 'gedit-playground-scroll-hidden'
|
|
252
|
+
: 'gedit-playground-scroll-show';
|
|
253
|
+
domUtils.addClass(scrollBar, addClassName);
|
|
254
|
+
domUtils.delClass(scrollBar, delClassName);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
onReady() {
|
|
258
|
+
if (!this.options.getBounds) {
|
|
259
|
+
this.options = {
|
|
260
|
+
getBounds: () => {
|
|
261
|
+
const document = this.flowDocument;
|
|
262
|
+
if (!document) return Rectangle.EMPTY;
|
|
263
|
+
document.transformer.refresh();
|
|
264
|
+
return document.root.getData(FlowNodeTransformData)!.bounds;
|
|
265
|
+
},
|
|
266
|
+
showScrollBars: 'whenScrolling',
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
this.pipelineNode.parentNode!.appendChild(this.rightScrollBar);
|
|
270
|
+
this.pipelineNode.parentNode!.appendChild(this.rightScrollBarBlock);
|
|
271
|
+
this.pipelineNode.parentNode!.appendChild(this.bottomScrollBar);
|
|
272
|
+
this.pipelineNode.parentNode!.appendChild(this.bottomScrollBarBlock);
|
|
273
|
+
// 模拟滚动条点击时的滚动
|
|
274
|
+
this.rightScrollBar.onclick = this.clickRightScrollBar.bind(this);
|
|
275
|
+
this.bottomScrollBar.onclick = this.clickBottomScrollBar.bind(this);
|
|
276
|
+
|
|
277
|
+
// 滚动时才显示滚动条 则要监听鼠标事件 hover 的时候也要显示
|
|
278
|
+
if (this.options.showScrollBars === 'whenScrolling') {
|
|
279
|
+
this.rightScrollBar.addEventListener('mouseenter', (e: MouseEvent) => {
|
|
280
|
+
this.changeScrollBarVisibility(this.rightScrollBarBlock, ScrollBarVisibility.Show);
|
|
281
|
+
});
|
|
282
|
+
this.rightScrollBar.addEventListener('mouseleave', (e: MouseEvent) => {
|
|
283
|
+
this.changeScrollBarVisibility(this.rightScrollBarBlock, ScrollBarVisibility.Hidden);
|
|
284
|
+
});
|
|
285
|
+
this.bottomScrollBar.addEventListener('mouseenter', (e: MouseEvent) => {
|
|
286
|
+
this.changeScrollBarVisibility(this.bottomScrollBarBlock, ScrollBarVisibility.Show);
|
|
287
|
+
});
|
|
288
|
+
this.bottomScrollBar.addEventListener('mouseleave', (e: MouseEvent) => {
|
|
289
|
+
this.changeScrollBarVisibility(this.bottomScrollBarBlock, ScrollBarVisibility.Hidden);
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// 监听拖拽滚动
|
|
294
|
+
this.bottomScrollBarBlock.addEventListener('mousedown', (e: MouseEvent) => {
|
|
295
|
+
this.bottomGrabDragger.start(e.clientX, e.clientY);
|
|
296
|
+
e.stopPropagation();
|
|
297
|
+
});
|
|
298
|
+
this.rightScrollBarBlock.addEventListener('mousedown', (e: MouseEvent) => {
|
|
299
|
+
this.rightGrabDragger.start(e.clientX, e.clientY);
|
|
300
|
+
e.stopPropagation();
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
autorun() {
|
|
305
|
+
// if (this.documentTransformer.loading) return null
|
|
306
|
+
// this.documentTransformer.refresh()
|
|
307
|
+
if (this.hideTimeout) {
|
|
308
|
+
clearTimeout(this.hideTimeout);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// 中间活动区域宽高
|
|
312
|
+
const viewportBounds = getScrollViewport(
|
|
313
|
+
{
|
|
314
|
+
scrollX: this.config.config.scrollX,
|
|
315
|
+
scrollY: this.config.config.scrollY,
|
|
316
|
+
},
|
|
317
|
+
this.config,
|
|
318
|
+
);
|
|
319
|
+
|
|
320
|
+
// 画布视区宽高
|
|
321
|
+
const viewport = this.config.getViewport();
|
|
322
|
+
// 计算视区的时候预留 11px,防止右下角滚动条重叠
|
|
323
|
+
this.viewportWidth = viewport.width;
|
|
324
|
+
this.viewportHeight = viewport.height;
|
|
325
|
+
// 中间部分元素的宽高
|
|
326
|
+
const rootBounds = this.options.getBounds(); // this.document.root.getData(FlowNodeTransformData)!.bounds
|
|
327
|
+
this.width = rootBounds?.width || 0;
|
|
328
|
+
this.height = rootBounds?.height || 0;
|
|
329
|
+
// 中间部分元素的左右间距
|
|
330
|
+
const paddingLeftRight = (this.viewportWidth - viewportBounds.width) / 2 - BORDER_WIDTH;
|
|
331
|
+
// 中间部分元素的上下间距
|
|
332
|
+
const paddingTopBottom = (this.viewportHeight - viewportBounds.height) / 2 - BORDER_WIDTH;
|
|
333
|
+
|
|
334
|
+
// 画布可滚动总长度
|
|
335
|
+
const canvasTotalWidth = this.width + viewportBounds.width;
|
|
336
|
+
const canvasTotalHeight = this.height + viewportBounds.height;
|
|
337
|
+
// 根据当前滚动距离计算 滚动条距离边界间距
|
|
338
|
+
// 中间元素初始的偏移位置:
|
|
339
|
+
const initialOffsetX = rootBounds.x;
|
|
340
|
+
const initialOffsetY = rootBounds.y;
|
|
341
|
+
// 最左边的位置
|
|
342
|
+
this.mostLeft = this.width + initialOffsetX - paddingLeftRight;
|
|
343
|
+
// 最右边的位置
|
|
344
|
+
this.mostRight = this.mostLeft - canvasTotalWidth;
|
|
345
|
+
// 最上面的位置
|
|
346
|
+
this.mostTop = this.height + initialOffsetY - paddingTopBottom;
|
|
347
|
+
// 最下面的位置
|
|
348
|
+
this.mostBottom = this.mostTop - canvasTotalHeight;
|
|
349
|
+
|
|
350
|
+
this.scale = this.config.finalScale;
|
|
351
|
+
|
|
352
|
+
const calcViewportWidth = this.clientViewportWidth;
|
|
353
|
+
const calcViewportHeight = this.clientViewportHeight;
|
|
354
|
+
// 计算公式:
|
|
355
|
+
// 可视区域 - 滚动条的长度 / 可视区域 = 可视区域 / 画布可滚动距离
|
|
356
|
+
// 底部滚动条宽度
|
|
357
|
+
this.scrollBottomWidth =
|
|
358
|
+
calcViewportWidth -
|
|
359
|
+
(calcViewportWidth * (this.mostLeft - this.mostRight)) / this.viewportMoveWidth;
|
|
360
|
+
// 右侧滚动条高度
|
|
361
|
+
this.scrollRightHeight =
|
|
362
|
+
calcViewportHeight -
|
|
363
|
+
(calcViewportHeight * (this.mostTop - this.mostBottom)) / this.viewportMoveHeight;
|
|
364
|
+
|
|
365
|
+
// 计算滚动条滚动位移的距离
|
|
366
|
+
// 可滚动区域:canvasTotalWidth - scrollBottomWidth
|
|
367
|
+
const bottomBarToLeft = this.getToLeft(viewport.x);
|
|
368
|
+
const rightBarToTop = this.getToTop(viewport.y);
|
|
369
|
+
|
|
370
|
+
// 设置右侧的滚动条内的 block 样式
|
|
371
|
+
domUtils.setStyle(this.rightScrollBarBlock, {
|
|
372
|
+
right: 2,
|
|
373
|
+
top: rightBarToTop,
|
|
374
|
+
background: '#1F2329',
|
|
375
|
+
zIndex: 10,
|
|
376
|
+
height: this.scrollRightHeight,
|
|
377
|
+
width: SCROLL_BAR_WIDTH,
|
|
378
|
+
});
|
|
379
|
+
// 设置底部的滚动条内的 block 样式
|
|
380
|
+
domUtils.setStyle(this.bottomScrollBarBlock, {
|
|
381
|
+
left: bottomBarToLeft,
|
|
382
|
+
bottom: 2,
|
|
383
|
+
background: '#1F2329',
|
|
384
|
+
zIndex: 10,
|
|
385
|
+
height: SCROLL_BAR_WIDTH,
|
|
386
|
+
width: this.scrollBottomWidth,
|
|
387
|
+
});
|
|
388
|
+
this.changeScrollBarVisibility(this.rightScrollBarBlock, ScrollBarVisibility.Show);
|
|
389
|
+
this.changeScrollBarVisibility(this.bottomScrollBarBlock, ScrollBarVisibility.Show);
|
|
390
|
+
|
|
391
|
+
// 滚动时才显示滚动条
|
|
392
|
+
// 定时器在 1s 后隐藏滚动条
|
|
393
|
+
if (this.options.showScrollBars === 'whenScrolling') {
|
|
394
|
+
this.hideTimeout = window.setTimeout(() => {
|
|
395
|
+
this.changeScrollBarVisibility(this.rightScrollBarBlock, ScrollBarVisibility.Hidden);
|
|
396
|
+
this.changeScrollBarVisibility(this.bottomScrollBarBlock, ScrollBarVisibility.Hidden);
|
|
397
|
+
this.hideTimeout = undefined;
|
|
398
|
+
}, 1000);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
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 { FlowDocument, FlowNodeTransformData } from '@flowgram-vue/document';
|
|
8
|
+
import { Layer } from '@flowgram-vue/core';
|
|
9
|
+
import { ScrollSchema } from '@flowgram-vue/utils';
|
|
10
|
+
|
|
11
|
+
import { scrollLimit } from '../utils';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* 控制滚动边界
|
|
15
|
+
*/
|
|
16
|
+
@injectable()
|
|
17
|
+
export class FlowScrollLimitLayer extends Layer {
|
|
18
|
+
@inject(FlowDocument) readonly document: FlowDocument;
|
|
19
|
+
|
|
20
|
+
getInitScroll(): ScrollSchema {
|
|
21
|
+
return this.document.layout.getInitScroll(this.pipelineNode.getBoundingClientRect());
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
onReady(): void {
|
|
25
|
+
const initScroll = () => this.getInitScroll();
|
|
26
|
+
this.config.updateConfig(initScroll());
|
|
27
|
+
this.config.addScrollLimit(scroll =>
|
|
28
|
+
scrollLimit(
|
|
29
|
+
scroll,
|
|
30
|
+
[this.document.root.getData(FlowNodeTransformData)!.bounds],
|
|
31
|
+
this.config,
|
|
32
|
+
initScroll,
|
|
33
|
+
),
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,191 @@
|
|
|
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 { Rectangle } from '@flowgram-vue/utils';
|
|
10
|
+
import { FlowNodeEntity, FlowNodeRenderData, FlowNodeTransformData } from '@flowgram-vue/document';
|
|
11
|
+
import {
|
|
12
|
+
CommandRegistry,
|
|
13
|
+
EditorState,
|
|
14
|
+
EditorStateConfigEntity,
|
|
15
|
+
Layer,
|
|
16
|
+
LayerOptions,
|
|
17
|
+
PlaygroundConfig,
|
|
18
|
+
PlaygroundContainerFactory,
|
|
19
|
+
observeEntity,
|
|
20
|
+
observeEntityDatas,
|
|
21
|
+
} from '@flowgram-vue/core';
|
|
22
|
+
|
|
23
|
+
import { wrapLayerRender } from '../layer-vue-provide';
|
|
24
|
+
import { FlowRendererKey, FlowRendererRegistry } from '../flow-renderer-registry';
|
|
25
|
+
import { FlowSelectConfigEntity, SelectorBoxConfigEntity } from '../entities';
|
|
26
|
+
|
|
27
|
+
export interface SelectorBoxPopoverProps {
|
|
28
|
+
bounds: Rectangle;
|
|
29
|
+
config: PlaygroundConfig;
|
|
30
|
+
flowSelectConfig: FlowSelectConfigEntity;
|
|
31
|
+
commandRegistry: CommandRegistry;
|
|
32
|
+
children?: VNode | VNode[];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface FlowSelectorBoundsLayerOptions extends LayerOptions {
|
|
36
|
+
ignoreOneSelect?: boolean;
|
|
37
|
+
ignoreChildrenLength?: boolean;
|
|
38
|
+
boundsPadding?: number;
|
|
39
|
+
disableBackground?: boolean;
|
|
40
|
+
backgroundClassName?: string;
|
|
41
|
+
foregroundClassName?: string;
|
|
42
|
+
SelectorBoxPopover?: Component;
|
|
43
|
+
CustomBoundsRenderer?: Component;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* 流程节点被框选后的边界区域渲染
|
|
48
|
+
*/
|
|
49
|
+
@injectable()
|
|
50
|
+
export class FlowSelectorBoundsLayer extends Layer<FlowSelectorBoundsLayerOptions> {
|
|
51
|
+
@inject(FlowRendererRegistry) readonly rendererRegistry: FlowRendererRegistry;
|
|
52
|
+
|
|
53
|
+
@inject(CommandRegistry) readonly commandRegistry: CommandRegistry;
|
|
54
|
+
|
|
55
|
+
@inject(PlaygroundContainerFactory) readonly playgroundContainer: PlaygroundContainerFactory;
|
|
56
|
+
|
|
57
|
+
@observeEntity(FlowSelectConfigEntity)
|
|
58
|
+
protected flowSelectConfigEntity: FlowSelectConfigEntity;
|
|
59
|
+
|
|
60
|
+
@observeEntity(EditorStateConfigEntity)
|
|
61
|
+
protected editorStateConfig: EditorStateConfigEntity;
|
|
62
|
+
|
|
63
|
+
@observeEntity(SelectorBoxConfigEntity)
|
|
64
|
+
protected selectorBoxConfigEntity: SelectorBoxConfigEntity;
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* 需要监听节点的展开和收起状态,重新绘制边框
|
|
68
|
+
*/
|
|
69
|
+
@observeEntityDatas(FlowNodeEntity, FlowNodeRenderData)
|
|
70
|
+
renderStates: FlowNodeRenderData[];
|
|
71
|
+
|
|
72
|
+
@observeEntityDatas(FlowNodeEntity, FlowNodeTransformData)
|
|
73
|
+
_transforms: FlowNodeTransformData[];
|
|
74
|
+
|
|
75
|
+
readonly node = domUtils.createDivWithClass('gedit-selector-bounds-layer');
|
|
76
|
+
|
|
77
|
+
readonly selectBoundsBackground = domUtils.createDivWithClass('gedit-selector-bounds-background');
|
|
78
|
+
|
|
79
|
+
onReady(): void {
|
|
80
|
+
this.node!.style.zIndex = '20';
|
|
81
|
+
const { firstChild } = this.pipelineNode;
|
|
82
|
+
if (this.options.boundsPadding !== undefined) {
|
|
83
|
+
this.flowSelectConfigEntity.boundsPadding = this.options.boundsPadding;
|
|
84
|
+
}
|
|
85
|
+
if (this.options.backgroundClassName) {
|
|
86
|
+
this.selectBoundsBackground.classList.add(this.options.backgroundClassName);
|
|
87
|
+
}
|
|
88
|
+
const selectorBoundsLayer = domUtils.createDivWithClass(
|
|
89
|
+
'gedit-selector-bounds-background-layer gedit-playground-layer'
|
|
90
|
+
);
|
|
91
|
+
selectorBoundsLayer.appendChild(this.selectBoundsBackground);
|
|
92
|
+
this.pipelineNode.insertBefore(selectorBoundsLayer, firstChild);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
onZoom(scale: number) {
|
|
96
|
+
this.node!.style.transform = `scale(${scale})`;
|
|
97
|
+
this.selectBoundsBackground.parentElement!.style.transform = `scale(${scale})`;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
onViewportChange() {
|
|
101
|
+
this.render();
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
isEnabled(): boolean {
|
|
105
|
+
const currentState = this.editorStateConfig.getCurrentState();
|
|
106
|
+
return currentState === EditorState.STATE_SELECT;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
render() {
|
|
110
|
+
const {
|
|
111
|
+
ignoreOneSelect,
|
|
112
|
+
ignoreChildrenLength,
|
|
113
|
+
SelectorBoxPopover: SelectorBoxPopoverFromOpts,
|
|
114
|
+
disableBackground,
|
|
115
|
+
CustomBoundsRenderer,
|
|
116
|
+
} = this.options;
|
|
117
|
+
|
|
118
|
+
const bounds = this.flowSelectConfigEntity.getSelectedBounds();
|
|
119
|
+
const selectedNodes = this.flowSelectConfigEntity.selectedNodes;
|
|
120
|
+
|
|
121
|
+
const bg = this.selectBoundsBackground;
|
|
122
|
+
const isDragging = !this.selectorBoxConfigEntity.isStart;
|
|
123
|
+
|
|
124
|
+
if (
|
|
125
|
+
bounds.width === 0 ||
|
|
126
|
+
bounds.height === 0 ||
|
|
127
|
+
(ignoreOneSelect &&
|
|
128
|
+
selectedNodes.length === 1 &&
|
|
129
|
+
(ignoreChildrenLength || (selectedNodes[0] as FlowNodeEntity).childrenLength <= 1))
|
|
130
|
+
) {
|
|
131
|
+
domUtils.setStyle(bg, {
|
|
132
|
+
display: 'none',
|
|
133
|
+
});
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
if (CustomBoundsRenderer) {
|
|
137
|
+
return wrapLayerRender(
|
|
138
|
+
this.playgroundContainer,
|
|
139
|
+
h(CustomBoundsRenderer, {
|
|
140
|
+
bounds,
|
|
141
|
+
config: this.config,
|
|
142
|
+
flowSelectConfig: this.flowSelectConfigEntity,
|
|
143
|
+
commandRegistry: this.commandRegistry,
|
|
144
|
+
})
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
const style = {
|
|
148
|
+
display: 'block',
|
|
149
|
+
left: `${bounds.left}px`,
|
|
150
|
+
top: `${bounds.top}px`,
|
|
151
|
+
width: `${bounds.width}px`,
|
|
152
|
+
height: `${bounds.height}px`,
|
|
153
|
+
};
|
|
154
|
+
if (!disableBackground) {
|
|
155
|
+
domUtils.setStyle(bg, {
|
|
156
|
+
display: 'block',
|
|
157
|
+
left: bounds.left,
|
|
158
|
+
top: bounds.top,
|
|
159
|
+
width: bounds.width,
|
|
160
|
+
height: bounds.height,
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
let foregroundClassName = 'gedit-selector-bounds-foreground';
|
|
164
|
+
if (this.options.foregroundClassName) {
|
|
165
|
+
foregroundClassName += ` ${this.options.foregroundClassName}`;
|
|
166
|
+
}
|
|
167
|
+
const SelectorBoxPopover =
|
|
168
|
+
SelectorBoxPopoverFromOpts ||
|
|
169
|
+
this.rendererRegistry.tryToGetRendererComponent(FlowRendererKey.SELECTOR_BOX_POPOVER)
|
|
170
|
+
?.renderer;
|
|
171
|
+
if (!isDragging || !SelectorBoxPopover) {
|
|
172
|
+
return wrapLayerRender(
|
|
173
|
+
this.playgroundContainer,
|
|
174
|
+
h('div', { class: foregroundClassName, style })
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
return wrapLayerRender(
|
|
178
|
+
this.playgroundContainer,
|
|
179
|
+
h(
|
|
180
|
+
SelectorBoxPopover as Component,
|
|
181
|
+
{
|
|
182
|
+
bounds,
|
|
183
|
+
config: this.config,
|
|
184
|
+
flowSelectConfig: this.flowSelectConfigEntity,
|
|
185
|
+
commandRegistry: this.commandRegistry,
|
|
186
|
+
},
|
|
187
|
+
() => h('div', { class: foregroundClassName, style })
|
|
188
|
+
)
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
}
|