@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,244 @@
|
|
|
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 { domUtils, PositionSchema } from '@flowgram-vue/utils';
|
|
8
|
+
import { FlowDocument, FlowNodeEntity, FlowNodeTransformData } from '@flowgram-vue/document';
|
|
9
|
+
import {
|
|
10
|
+
ContextMenuService,
|
|
11
|
+
EditorState,
|
|
12
|
+
EditorStateConfigEntity,
|
|
13
|
+
Layer,
|
|
14
|
+
LayerOptions,
|
|
15
|
+
observeEntity,
|
|
16
|
+
PipelineLayerPriority,
|
|
17
|
+
PlaygroundConfigEntity,
|
|
18
|
+
PlaygroundDrag,
|
|
19
|
+
SelectionService,
|
|
20
|
+
} from '@flowgram-vue/core';
|
|
21
|
+
|
|
22
|
+
import { FlowSelectConfigEntity, SelectorBoxConfigEntity } from '../entities';
|
|
23
|
+
|
|
24
|
+
export interface FlowSelectorBoxOptions extends LayerOptions {
|
|
25
|
+
/**
|
|
26
|
+
* 默认不提供则为点击空白地方可以框选
|
|
27
|
+
* @param e
|
|
28
|
+
* @param entity
|
|
29
|
+
*/
|
|
30
|
+
canSelect?: (e: MouseEvent, entity: SelectorBoxConfigEntity) => boolean;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* 流程选择框
|
|
34
|
+
*/
|
|
35
|
+
@injectable()
|
|
36
|
+
export class FlowSelectorBoxLayer extends Layer<FlowSelectorBoxOptions> {
|
|
37
|
+
@inject(FlowDocument)
|
|
38
|
+
protected flowDocument: FlowDocument;
|
|
39
|
+
|
|
40
|
+
@inject(ContextMenuService)
|
|
41
|
+
readonly contextMenuService: ContextMenuService;
|
|
42
|
+
|
|
43
|
+
@observeEntity(PlaygroundConfigEntity)
|
|
44
|
+
protected playgroundConfigEntity: PlaygroundConfigEntity;
|
|
45
|
+
|
|
46
|
+
@inject(SelectionService) readonly selectionService: SelectionService;
|
|
47
|
+
|
|
48
|
+
@observeEntity(SelectorBoxConfigEntity)
|
|
49
|
+
protected selectorBoxConfigEntity: SelectorBoxConfigEntity;
|
|
50
|
+
|
|
51
|
+
@observeEntity(FlowSelectConfigEntity)
|
|
52
|
+
protected selectConfigEntity: FlowSelectConfigEntity;
|
|
53
|
+
|
|
54
|
+
@observeEntity(EditorStateConfigEntity)
|
|
55
|
+
protected editorStateConfig: EditorStateConfigEntity;
|
|
56
|
+
|
|
57
|
+
readonly node = domUtils.createDivWithClass('gedit-selector-box-layer');
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* 选择框
|
|
61
|
+
*/
|
|
62
|
+
protected selectorBox = this.createDOMCache('gedit-selector-box');
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* 用于遮挡鼠标,避免触发 hover
|
|
66
|
+
*/
|
|
67
|
+
protected selectorBoxBlock = this.createDOMCache('gedit-selector-box-block');
|
|
68
|
+
|
|
69
|
+
protected transformVisibles: FlowNodeTransformData[];
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* 拖动选择框
|
|
73
|
+
*/
|
|
74
|
+
protected selectboxDragger = new PlaygroundDrag({
|
|
75
|
+
onDragStart: (e) => {
|
|
76
|
+
this.selectConfigEntity.clearSelectedNodes();
|
|
77
|
+
const mousePos = this.playgroundConfigEntity.getPosFromMouseEvent(e);
|
|
78
|
+
this.transformVisibles = this.flowDocument
|
|
79
|
+
.getRenderDatas(FlowNodeTransformData, false)
|
|
80
|
+
.filter((transform) => {
|
|
81
|
+
const { entity } = transform;
|
|
82
|
+
if (entity.originParent) {
|
|
83
|
+
return (
|
|
84
|
+
this.nodeSelectable(entity, mousePos) &&
|
|
85
|
+
this.nodeSelectable(entity.originParent, mousePos)
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
return this.nodeSelectable(entity, mousePos);
|
|
89
|
+
});
|
|
90
|
+
this.selectorBoxConfigEntity.setDragInfo(e);
|
|
91
|
+
this.updateSelectorBox(this.selectorBoxConfigEntity);
|
|
92
|
+
},
|
|
93
|
+
onDrag: (e) => {
|
|
94
|
+
this.selectorBoxConfigEntity.setDragInfo(e);
|
|
95
|
+
// 更新选择框
|
|
96
|
+
this.selectConfigEntity.selectFromBounds(
|
|
97
|
+
this.selectorBoxConfigEntity.toRectangle(this.playgroundConfigEntity.finalScale),
|
|
98
|
+
this.transformVisibles
|
|
99
|
+
);
|
|
100
|
+
this.updateSelectorBox(this.selectorBoxConfigEntity);
|
|
101
|
+
},
|
|
102
|
+
onDragEnd: (e) => {
|
|
103
|
+
this.selectorBoxConfigEntity.setDragInfo(e);
|
|
104
|
+
this.transformVisibles.length = 0;
|
|
105
|
+
this.updateSelectorBox(this.selectorBoxConfigEntity);
|
|
106
|
+
},
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
onReady(): void {
|
|
110
|
+
if (!this.options.canSelect) {
|
|
111
|
+
this.options.canSelect = (e: MouseEvent) => {
|
|
112
|
+
const target = e.target as HTMLElement | undefined;
|
|
113
|
+
// 默认点击空白地方可以框选
|
|
114
|
+
return target === this.pipelineNode || target === this.playgroundNode;
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
// 将选中的节点同步到全局
|
|
118
|
+
// TODO 后续要统一到 selection service
|
|
119
|
+
this.toDispose.pushAll([
|
|
120
|
+
this.selectConfigEntity.onConfigChanged(() => {
|
|
121
|
+
this.selectionService.selection = this.selectConfigEntity.selectedNodes;
|
|
122
|
+
}),
|
|
123
|
+
this.selectionService.onSelectionChanged(() => {
|
|
124
|
+
const selectedNodes = this.selectionService.selection.filter(
|
|
125
|
+
(entity) => entity instanceof FlowNodeEntity
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
this.selectConfigEntity.selectedNodes = selectedNodes as FlowNodeEntity[];
|
|
129
|
+
}),
|
|
130
|
+
]);
|
|
131
|
+
this.listenPlaygroundEvent(
|
|
132
|
+
'mousedown',
|
|
133
|
+
(e: MouseEvent): boolean | undefined => {
|
|
134
|
+
if (!this.isEnabled()) return;
|
|
135
|
+
// 自定义拦截选择框事件
|
|
136
|
+
if (this.options.canSelect && !this.options.canSelect(e, this.selectorBoxConfigEntity)) {
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const currentState = this.editorStateConfig.getCurrentState();
|
|
141
|
+
|
|
142
|
+
// 鼠标友好模式,框选后,再次点击其他地方或者框选其他地方,需要清空已有选择的节点
|
|
143
|
+
if (currentState === EditorState.STATE_MOUSE_FRIENDLY_SELECT) {
|
|
144
|
+
this.selectConfigEntity.clearSelectedNodes();
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// const target = e.target as HTMLElement | undefined;
|
|
148
|
+
// TODO 下边这些特化逻辑迁移到固定布局逻辑
|
|
149
|
+
// const linesLayer = document.querySelector('.gedit-flow-lines-layer');
|
|
150
|
+
// const toolsTarget = document.querySelector('.flow-canvas-selector-box-tools');
|
|
151
|
+
// const isInTools = toolsTarget && (toolsTarget === target || toolsTarget.contains(target!));
|
|
152
|
+
// 保证 service 更新后进行是否清除的计算
|
|
153
|
+
// setTimeout(() => {
|
|
154
|
+
// // 如果点击到选中区域的菜单栏
|
|
155
|
+
// if (!isInTools && !this.contextMenuService.rightPanelVisible) {
|
|
156
|
+
// // 取消之前的选择状态
|
|
157
|
+
// this.selectConfigEntity.clearSelectedNodes();
|
|
158
|
+
// }
|
|
159
|
+
// }, 0);
|
|
160
|
+
// if (
|
|
161
|
+
// target === this.pipelineNode ||
|
|
162
|
+
// target === this.playgroundNode // 点击空白区域
|
|
163
|
+
// linesLayer?.contains(target!) // 点击 svg 线条
|
|
164
|
+
// target?.classList.contains('flow-canvas-adder') || // 点击添加按钮的留白区域
|
|
165
|
+
// target?.classList.contains('flow-canvas-block-icon') // 点击添加按钮的留白区域
|
|
166
|
+
// ) {
|
|
167
|
+
// return true;
|
|
168
|
+
// }
|
|
169
|
+
this.selectboxDragger.start(e.clientX, e.clientY, this.config);
|
|
170
|
+
return true;
|
|
171
|
+
},
|
|
172
|
+
PipelineLayerPriority.BASE_LAYER
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
isEnabled(): boolean {
|
|
177
|
+
const currentState = this.editorStateConfig.getCurrentState();
|
|
178
|
+
const isMouseFriendly = currentState === EditorState.STATE_MOUSE_FRIENDLY_SELECT;
|
|
179
|
+
|
|
180
|
+
return (
|
|
181
|
+
!this.config.disabled &&
|
|
182
|
+
!this.config.readonly &&
|
|
183
|
+
// 鼠标友好模式下,需要按下 shift 启动框选
|
|
184
|
+
((isMouseFriendly && this.editorStateConfig.isPressingShift) ||
|
|
185
|
+
currentState === EditorState.STATE_SELECT) &&
|
|
186
|
+
!this.selectorBoxConfigEntity.disabled
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Destroy
|
|
192
|
+
*/
|
|
193
|
+
dispose(): void {
|
|
194
|
+
this.selectorBox.dispose();
|
|
195
|
+
this.selectorBoxBlock.dispose();
|
|
196
|
+
super.dispose();
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
protected updateSelectorBox(selector: SelectorBoxConfigEntity): void {
|
|
200
|
+
const node = this.selectorBox.get();
|
|
201
|
+
const block = this.selectorBoxBlock.get();
|
|
202
|
+
// 非可用状态且在 moving 则关闭选择框
|
|
203
|
+
if (!this.isEnabled() && selector.isMoving) {
|
|
204
|
+
this.selectorBoxConfigEntity.collapse();
|
|
205
|
+
}
|
|
206
|
+
if (!this.isEnabled() || !selector.isMoving) {
|
|
207
|
+
node.setStyle({
|
|
208
|
+
display: 'none',
|
|
209
|
+
});
|
|
210
|
+
block.setStyle({
|
|
211
|
+
display: 'none',
|
|
212
|
+
});
|
|
213
|
+
} else {
|
|
214
|
+
node.setStyle({
|
|
215
|
+
display: 'block',
|
|
216
|
+
left: selector.position.x,
|
|
217
|
+
top: selector.position.y,
|
|
218
|
+
width: selector.size.width,
|
|
219
|
+
height: selector.size.height,
|
|
220
|
+
});
|
|
221
|
+
// 这是遮挡滑块,防止触发节点 hover
|
|
222
|
+
block.setStyle({
|
|
223
|
+
display: 'block',
|
|
224
|
+
left: selector.position.x - 10,
|
|
225
|
+
top: selector.position.y - 10,
|
|
226
|
+
width: selector.size.width + 20,
|
|
227
|
+
height: selector.size.height + 20,
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
private nodeSelectable(node: FlowNodeEntity, mousePos: PositionSchema) {
|
|
233
|
+
const selectable = node.getNodeMeta().selectable;
|
|
234
|
+
if (typeof selectable === 'function') {
|
|
235
|
+
return selectable(node, mousePos);
|
|
236
|
+
} else {
|
|
237
|
+
return selectable;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// autorun(): void {
|
|
242
|
+
// this.updateSelectorBox(this.selectorBoxConfigEntity);
|
|
243
|
+
// }
|
|
244
|
+
}
|
|
@@ -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 './flow-nodes-transform-layer';
|
|
7
|
+
export * from './flow-nodes-content-layer';
|
|
8
|
+
export * from './flow-lines-layer';
|
|
9
|
+
export * from './flow-labels-layer';
|
|
10
|
+
export * from './flow-debug-layer';
|
|
11
|
+
export * from './flow-scroll-bar-layer';
|
|
12
|
+
export * from './flow-drag-layer';
|
|
13
|
+
export * from './flow-selector-box-layer';
|
|
14
|
+
export * from './flow-selector-bounds-layer';
|
|
15
|
+
export * from './flow-context-menu-layer';
|
|
16
|
+
export * from './flow-scroll-limit-layer';
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { isNil } from 'lodash-es';
|
|
7
|
+
export const isHidden = (dom?: HTMLElement) => {
|
|
8
|
+
if (!dom || isNil(dom?.offsetParent)) {
|
|
9
|
+
return true;
|
|
10
|
+
}
|
|
11
|
+
const style = window.getComputedStyle(dom);
|
|
12
|
+
if (style?.display === 'none') {
|
|
13
|
+
return true;
|
|
14
|
+
}
|
|
15
|
+
return false;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export const isRectInit = (rect?: DOMRect): boolean => {
|
|
19
|
+
if (!rect) {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
// 检查所有属性是否都为0,表示DOMRect未初始化
|
|
23
|
+
if (
|
|
24
|
+
rect.bottom === 0 &&
|
|
25
|
+
rect.height === 0 &&
|
|
26
|
+
rect.left === 0 &&
|
|
27
|
+
rect.right === 0 &&
|
|
28
|
+
rect.top === 0 &&
|
|
29
|
+
rect.width === 0 &&
|
|
30
|
+
rect.x === 0 &&
|
|
31
|
+
rect.y === 0
|
|
32
|
+
) {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
return true;
|
|
36
|
+
};
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { uniq } from 'lodash-es';
|
|
7
|
+
import { type FlowNodeEntity } from '@flowgram-vue/document';
|
|
8
|
+
|
|
9
|
+
function getNodePath(node: FlowNodeEntity): FlowNodeEntity[] {
|
|
10
|
+
const path: FlowNodeEntity[] = [node];
|
|
11
|
+
node = node.parent as FlowNodeEntity;
|
|
12
|
+
while (node) {
|
|
13
|
+
path.push(node);
|
|
14
|
+
node = node.parent as FlowNodeEntity;
|
|
15
|
+
}
|
|
16
|
+
return path.reverse();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* 过滤掉画布节点, 有 originParent,都是非独立节点
|
|
21
|
+
* @param entity
|
|
22
|
+
*/
|
|
23
|
+
function findRealEntity(entity: FlowNodeEntity): FlowNodeEntity {
|
|
24
|
+
while (entity.originParent) {
|
|
25
|
+
entity = entity.originParent;
|
|
26
|
+
}
|
|
27
|
+
return entity;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* 生成选中节点的路径
|
|
31
|
+
* 如
|
|
32
|
+
* [
|
|
33
|
+
* 'root',
|
|
34
|
+
* 'exclusiveSplit_30baf8b1da0',
|
|
35
|
+
* 'exclusiveSplit_d0070ce5d04',
|
|
36
|
+
* 'createRecord_47e8fe1dfc3'
|
|
37
|
+
* ],
|
|
38
|
+
* [
|
|
39
|
+
* 'root',
|
|
40
|
+
* 'exclusiveSplit_30baf8b1da0',
|
|
41
|
+
* 'exclusiveSplit_d0070ce5d04',
|
|
42
|
+
* 'createRecord_32dcdd10274'
|
|
43
|
+
* ],
|
|
44
|
+
* [
|
|
45
|
+
* 'root',
|
|
46
|
+
* 'exclusiveSplit_30baf8b1da0',
|
|
47
|
+
* 'exclusiveSplit_d0070ce5d04',
|
|
48
|
+
* 'exclusiveSplit_a5579b3997d', // 这里产生分叉
|
|
49
|
+
* 'createRecord_b57b00eee94' // 父亲节点分叉了,这里就忽略了
|
|
50
|
+
* ]
|
|
51
|
+
* ]
|
|
52
|
+
* 1. 相同分支的节点,选择每个节点
|
|
53
|
+
* 2. 跨分支的节点选择共同的父节点
|
|
54
|
+
*/
|
|
55
|
+
export function findSelectedNodes(nodes: FlowNodeEntity[]): FlowNodeEntity[] {
|
|
56
|
+
if (nodes.length === 0) return [];
|
|
57
|
+
/**
|
|
58
|
+
* 生成节点的路径
|
|
59
|
+
*/
|
|
60
|
+
const nodePathList: FlowNodeEntity[][] = nodes.map((n) => getNodePath(n));
|
|
61
|
+
/**
|
|
62
|
+
* 只需要比较最小的路径
|
|
63
|
+
*/
|
|
64
|
+
const minLength = Math.min(...nodePathList.map((n) => n.length));
|
|
65
|
+
let index = 0;
|
|
66
|
+
let selectedItems: FlowNodeEntity[] = [];
|
|
67
|
+
/**
|
|
68
|
+
* 从二维数组的每一层打平去看,看看有没有分叉,如果有分叉就在当前层停止并作为选中的节点
|
|
69
|
+
*/
|
|
70
|
+
while (index < minLength) {
|
|
71
|
+
// eslint-disable-next-line no-loop-func
|
|
72
|
+
selectedItems = uniq(nodePathList.map((p) => p[index]));
|
|
73
|
+
// 存在分叉
|
|
74
|
+
if (selectedItems.length > 1) {
|
|
75
|
+
break;
|
|
76
|
+
}
|
|
77
|
+
index += 1;
|
|
78
|
+
}
|
|
79
|
+
return uniq(selectedItems.map((item) => findRealEntity(item)));
|
|
80
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* 滚动条点击事件监听
|
|
8
|
+
*/
|
|
9
|
+
export const ScrollBarEvents = Symbol('ScrollBarEvents');
|
|
10
|
+
|
|
11
|
+
export interface ScrollBarEvents {
|
|
12
|
+
dragStart: () => void;
|
|
13
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { Rectangle } from '@flowgram-vue/utils';
|
|
7
|
+
import { type PlaygroundConfigEntity } from '@flowgram-vue/core';
|
|
8
|
+
|
|
9
|
+
export interface ScrollData {
|
|
10
|
+
scrollX: number;
|
|
11
|
+
scrollY: number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// viewport 缩小 30 像素
|
|
15
|
+
const SCROLL_LIMIT_PADDING = -120;
|
|
16
|
+
|
|
17
|
+
export function getScrollViewport(
|
|
18
|
+
scrollData: ScrollData,
|
|
19
|
+
config: PlaygroundConfigEntity
|
|
20
|
+
): Rectangle {
|
|
21
|
+
const scale = config.finalScale;
|
|
22
|
+
return new Rectangle(
|
|
23
|
+
scrollData.scrollX / scale,
|
|
24
|
+
scrollData.scrollY / scale,
|
|
25
|
+
config.config.width / scale,
|
|
26
|
+
config.config.height / scale
|
|
27
|
+
).pad(SCROLL_LIMIT_PADDING / scale, SCROLL_LIMIT_PADDING / scale);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* 限制滚动
|
|
32
|
+
*/
|
|
33
|
+
export function scrollLimit(
|
|
34
|
+
scroll: ScrollData,
|
|
35
|
+
boundsList: Rectangle[],
|
|
36
|
+
config: PlaygroundConfigEntity,
|
|
37
|
+
initScroll: () => ScrollData
|
|
38
|
+
): ScrollData {
|
|
39
|
+
scroll = { ...scroll };
|
|
40
|
+
const configData = config.config;
|
|
41
|
+
const oldScroll = { scrollX: configData.scrollX, scrollY: configData.scrollY };
|
|
42
|
+
// 画布 size 还没初始化滚动不限制
|
|
43
|
+
if (boundsList.length === 0 || configData.width === 0 || configData.height === 0) return scroll;
|
|
44
|
+
const viewport = getScrollViewport(scroll, config);
|
|
45
|
+
const isVisible = boundsList.find((bounds) => Rectangle.isViewportVisible(bounds, viewport));
|
|
46
|
+
if (!isVisible) {
|
|
47
|
+
const oldViewport = getScrollViewport(oldScroll, config);
|
|
48
|
+
const isOldVisible = boundsList.find((bounds) =>
|
|
49
|
+
Rectangle.isViewportVisible(bounds, oldViewport)
|
|
50
|
+
);
|
|
51
|
+
// 如果之前也是不可见就不阻止
|
|
52
|
+
if (!isOldVisible) {
|
|
53
|
+
return initScroll();
|
|
54
|
+
}
|
|
55
|
+
return oldScroll;
|
|
56
|
+
}
|
|
57
|
+
return scroll;
|
|
58
|
+
}
|