@logicflow/extension 2.2.0-alpha.6 → 2.2.0-alpha.7

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 (55) hide show
  1. package/.turbo/turbo-build.log +7 -7
  2. package/CHANGELOG.md +6 -0
  3. package/dist/index.min.js +1 -1
  4. package/dist/index.min.js.map +1 -1
  5. package/es/bpmn-elements-adapter/json2xml.d.ts +2 -1
  6. package/es/bpmn-elements-adapter/json2xml.js +18 -4
  7. package/es/bpmn-elements-adapter/xml2json.js +2 -7
  8. package/es/index.d.ts +1 -0
  9. package/es/index.js +2 -0
  10. package/es/pool/LaneModel.d.ts +90 -0
  11. package/es/pool/LaneModel.js +252 -0
  12. package/es/pool/LaneView.d.ts +40 -0
  13. package/es/pool/LaneView.js +202 -0
  14. package/es/pool/PoolModel.d.ts +120 -0
  15. package/es/pool/PoolModel.js +586 -0
  16. package/es/pool/PoolView.d.ts +17 -0
  17. package/es/pool/PoolView.js +76 -0
  18. package/es/pool/constant.d.ts +15 -0
  19. package/es/pool/constant.js +17 -0
  20. package/es/pool/index.d.ts +89 -0
  21. package/es/pool/index.js +524 -0
  22. package/es/pool/utils.d.ts +19 -0
  23. package/es/pool/utils.js +33 -0
  24. package/lib/bpmn-elements-adapter/json2xml.d.ts +2 -1
  25. package/lib/bpmn-elements-adapter/json2xml.js +19 -4
  26. package/lib/bpmn-elements-adapter/xml2json.js +2 -7
  27. package/lib/index.d.ts +1 -0
  28. package/lib/index.js +2 -0
  29. package/lib/pool/LaneModel.d.ts +90 -0
  30. package/lib/pool/LaneModel.js +255 -0
  31. package/lib/pool/LaneView.d.ts +40 -0
  32. package/lib/pool/LaneView.js +205 -0
  33. package/lib/pool/PoolModel.d.ts +120 -0
  34. package/lib/pool/PoolModel.js +589 -0
  35. package/lib/pool/PoolView.d.ts +17 -0
  36. package/lib/pool/PoolView.js +79 -0
  37. package/lib/pool/constant.d.ts +15 -0
  38. package/lib/pool/constant.js +20 -0
  39. package/lib/pool/index.d.ts +89 -0
  40. package/lib/pool/index.js +527 -0
  41. package/lib/pool/utils.d.ts +19 -0
  42. package/lib/pool/utils.js +38 -0
  43. package/package.json +3 -3
  44. package/src/bpmn-elements-adapter/json2xml.ts +18 -4
  45. package/src/bpmn-elements-adapter/xml2json.ts +2 -8
  46. package/src/dynamic-group/index.ts +0 -1
  47. package/src/index.ts +2 -0
  48. package/src/pool/LaneModel.ts +226 -0
  49. package/src/pool/LaneView.ts +220 -0
  50. package/src/pool/PoolModel.ts +631 -0
  51. package/src/pool/PoolView.ts +75 -0
  52. package/src/pool/constant.ts +19 -0
  53. package/src/pool/index.ts +621 -0
  54. package/src/pool/utils.ts +46 -0
  55. package/stats.html +1 -1
@@ -0,0 +1,19 @@
1
+ // 泳池配置常量
2
+ export const poolConfig = {
3
+ // 默认尺寸
4
+ defaultWidth: 120,
5
+ defaultHeight: 120,
6
+ // 标题区域
7
+ titleSize: 60,
8
+ poolMinSize: 20,
9
+ }
10
+
11
+ export const laneConfig = {
12
+ defaultWidth: 120,
13
+ defaultHeight: 120,
14
+ titleSize: 20,
15
+ iconSize: 20,
16
+ iconSpacing: 15,
17
+ }
18
+
19
+ export default null
@@ -0,0 +1,621 @@
1
+ import LogicFlow, {
2
+ CallbackArgs,
3
+ Model,
4
+ BaseNodeModel,
5
+ BaseEdgeModel,
6
+ EventType,
7
+ transformNodeData,
8
+ transformEdgeData,
9
+ } from '@logicflow/core'
10
+ import { assign, filter, forEach, cloneDeep, has, map } from 'lodash-es'
11
+ import { PoolModel } from './PoolModel'
12
+ import { PoolView } from './PoolView'
13
+ import { LaneModel } from './LaneModel'
14
+ import { LaneView } from './LaneView'
15
+ import { isAllowMoveTo, isBoundsInLane } from './utils'
16
+
17
+ import GraphConfigData = LogicFlow.GraphConfigData
18
+ import GraphElements = LogicFlow.GraphElements
19
+ import EdgeConfig = LogicFlow.EdgeConfig
20
+ import EdgeData = LogicFlow.EdgeData
21
+ import NodeData = LogicFlow.NodeData
22
+ import BoxBoundsPoint = Model.BoxBoundsPoint
23
+ type ElementsInfoInGroup = {
24
+ childNodes: BaseNodeModel[] // 分组节点的所有子节点 model
25
+ edgesData: EdgeData[] // 属于分组内的线的 EdgeData (即开始节点和结束节点都在 Group 内)
26
+ }
27
+
28
+ export const PoolNode = {
29
+ type: 'pool',
30
+ view: PoolView,
31
+ model: PoolModel,
32
+ }
33
+
34
+ export const LaneNode = {
35
+ type: 'lane',
36
+ view: LaneView,
37
+ model: LaneModel,
38
+ }
39
+
40
+ export class PoolElements {
41
+ static pluginName = 'PoolElements'
42
+ private lf: LogicFlow
43
+ // 激活态的 group 节点
44
+ activeGroup?: LaneModel
45
+ // 存储节点与 group 的映射关系
46
+ nodeLaneMap: Map<string, string> = new Map()
47
+
48
+ constructor({ lf, options }: LogicFlow.IExtensionProps) {
49
+ lf.register(PoolNode)
50
+ lf.register(LaneNode)
51
+ this.lf = lf
52
+ assign(this, options)
53
+ // 初始化插件,从监听事件开始及设置规则开始
54
+ this.init()
55
+ }
56
+
57
+ /**
58
+ * 获取节点所属的泳道
59
+ * @param nodeId
60
+ */
61
+ getLaneByNodeId(nodeId: string) {
62
+ const laneId = this.nodeLaneMap.get(nodeId)
63
+ if (laneId) {
64
+ return this.lf.getNodeModelById(laneId)
65
+ }
66
+ }
67
+
68
+ /**
69
+ * 获取指定范围内的泳道
70
+ * 当泳道重合时,优先返回最上层的泳道
71
+ * @param bounds
72
+ * @param nodeData
73
+ */
74
+ getLaneByBounds(bounds: BoxBoundsPoint, nodeData: NodeData): any | undefined {
75
+ const { nodes } = this.lf.graphModel
76
+ const lanes = filter(nodes, (node) => {
77
+ return (
78
+ !!node.isGroup &&
79
+ isBoundsInLane(bounds, node) &&
80
+ node.id !== nodeData.id
81
+ )
82
+ })
83
+
84
+ const count = lanes.length
85
+ if (count <= 1) {
86
+ return lanes[0] as LaneModel
87
+ } else {
88
+ let topZIndexLane = lanes[count - 1]
89
+ for (let i = count - 2; i >= 0; i--) {
90
+ if (lanes[i].zIndex > topZIndexLane.zIndex) {
91
+ topZIndexLane = lanes[i]
92
+ }
93
+ }
94
+ return topZIndexLane as LaneModel
95
+ }
96
+ }
97
+
98
+ /**
99
+ * 提高元素的层级,如果是 group,同时提高其子元素的层级
100
+ * @param model
101
+ */
102
+ onSelectionDrop = () => {
103
+ const { nodes: selectedNodes } = this.lf.graphModel.getSelectElements()
104
+ selectedNodes.forEach((node) => {
105
+ this.addNodeToGroup(node)
106
+ })
107
+ }
108
+ onNodeAddOrDrop = ({ data: node }: CallbackArgs<'node:add'>) => {
109
+ this.addNodeToGroup(node)
110
+ }
111
+
112
+ addNodeToGroup = (node: LogicFlow.NodeData) => {
113
+ // 1. 如果该节点之前已经有泳道了,则将其从之前的泳道移除
114
+ const preLaneId = this.nodeLaneMap.get(node.id)
115
+ if (preLaneId) {
116
+ const lane = this.lf.getNodeModelById(preLaneId) as LaneModel
117
+
118
+ lane.removeChild(node.id)
119
+ this.nodeLaneMap.delete(node.id)
120
+ lane.setAllowAppendChild(false)
121
+ }
122
+
123
+ // 2. 然后再判断这个节点是否在某个泳道范围内,如果是,则将其添加到对应的泳道中
124
+ const nodeModel = this.lf.getNodeModelById(node.id)
125
+ const bounds = nodeModel?.getBounds()
126
+
127
+ if (nodeModel && bounds) {
128
+ // TODO: 确认下面的注释内容
129
+ // https://github.com/didi/LogicFlow/issues/1261
130
+ // 当使用 SelectionSelect 框选后触发 lf.addNode(Group)
131
+ // 会触发 appendNodeToGroup() 的执行
132
+ // 由于 this.getGroup() 会判断 node.id !== nodeData.id
133
+ // 因此当 addNode 是 Group 类型时,this.getGroup() 会一直返回空
134
+ // 导致了下面这段代码无法执行,也就是无法将当前添加的 Group 添加到 this.nodeLaneMap 中
135
+ // 这导致了折叠分组时触发的 foldEdge() 无法正确通过 getNodeGroup() 拿到正确的 groupId
136
+ // 从而导致折叠分组时一直都会创建一个虚拟边
137
+ // 而初始化分组时由于正确设置了nodeLaneMap的数据,因此不会产生虚拟边的错误情况
138
+ if (nodeModel.isGroup) {
139
+ const lane = nodeModel as LaneModel
140
+ forEach(Array.from(lane.children), (childId) => {
141
+ this.nodeLaneMap.set(childId, node.id)
142
+ })
143
+ }
144
+
145
+ const lane = this.getLaneByBounds(bounds, node)
146
+ if (lane) {
147
+ const isAllowAppendIn = lane.isAllowAppendIn(node)
148
+ if (isAllowAppendIn) {
149
+ lane.addChild(node.id)
150
+ // 建立节点与 lane 的映射关系放在了 lane.addChild 触发的事件中,与直接调用 addChild 的行为保持一致
151
+ lane.setAllowAppendChild(false)
152
+ const nodeModel = this.lf.getNodeModelById(node.id)
153
+ nodeModel?.setProperties({
154
+ ...nodeModel.properties,
155
+ parent: lane.id,
156
+ // relativeDistanceX: nodeModel.x - lane.x,
157
+ // relativeDistanceY: nodeModel.y - lane.y,
158
+ })
159
+ } else {
160
+ // 抛出不允许插入的事件
161
+ this.lf.emit('lane:not-allowed', {
162
+ lane: lane.getData(),
163
+ node,
164
+ })
165
+ }
166
+ }
167
+ }
168
+ }
169
+
170
+ onGroupAddNode = ({
171
+ data: groupData,
172
+ childId,
173
+ }: CallbackArgs<'group:add-node'>) => {
174
+ this.nodeLaneMap.set(childId, groupData.id)
175
+ }
176
+
177
+ removeNodeFromGroup = ({
178
+ data: node,
179
+ model,
180
+ }: CallbackArgs<'node:delete'>) => {
181
+ if (model.isGroup && node.children) {
182
+ forEach(Array.from((node as LaneModel).children), (childId) => {
183
+ this.nodeLaneMap.delete(childId)
184
+ this.lf.deleteNode(childId)
185
+ })
186
+ }
187
+
188
+ const laneId = this.nodeLaneMap.get(node.id)
189
+ if (laneId) {
190
+ const lane = this.lf.getNodeModelById(laneId)
191
+ lane && (lane as LaneModel).removeChild(node.id)
192
+ this.nodeLaneMap.delete(node.id)
193
+ const nodeModel = this.lf.getNodeModelById(node.id)
194
+ // 移除时删除properties中的parent和relativeDistanceX、relativeDistanceY
195
+ const newProperties = {
196
+ ...nodeModel?.properties,
197
+ parent: undefined,
198
+ relativeDistanceX: undefined,
199
+ relativeDistanceY: undefined,
200
+ }
201
+ nodeModel?.setProperties(newProperties)
202
+ }
203
+ }
204
+
205
+ onSelectionDrag = () => {
206
+ const { nodes: selectedNodes } = this.lf.graphModel.getSelectElements()
207
+ selectedNodes.forEach((node) => {
208
+ this.setActiveGroup(node)
209
+ })
210
+ }
211
+
212
+ onNodeDrag = ({ data: node }: CallbackArgs<'node:drag'>) => {
213
+ this.setActiveGroup(node)
214
+ }
215
+
216
+ setActiveGroup = (node: LogicFlow.NodeData) => {
217
+ const nodeModel = this.lf.getNodeModelById(node.id)
218
+ const bounds = nodeModel?.getBounds()
219
+
220
+ if (nodeModel && bounds) {
221
+ const targetGroup = this.getLaneByBounds(bounds, node)
222
+ if (this.activeGroup) {
223
+ this.activeGroup.setAllowAppendChild(false)
224
+ }
225
+
226
+ if (!targetGroup || (nodeModel.isGroup && targetGroup.id === node.id)) {
227
+ return
228
+ }
229
+
230
+ const isAllowAppendIn = targetGroup.isAllowAppendIn(node)
231
+ if (!isAllowAppendIn) return
232
+
233
+ this.activeGroup = targetGroup
234
+ this.activeGroup?.setAllowAppendChild(true)
235
+ }
236
+ }
237
+ /**
238
+ * @param node
239
+ * @param isMultiple
240
+ * @param isSelected
241
+ */
242
+ onNodeSelect = ({
243
+ data: node,
244
+ isMultiple,
245
+ isSelected,
246
+ }: Omit<CallbackArgs<'node:click'>, 'e' | 'position'>) => {
247
+ const nodeModel = this.lf.getNodeModelById(node.id)
248
+ // FIX #1004
249
+ // 如果节点被多选,
250
+ // 这个节点是分组,则将分组的所有子节点取消选中
251
+ // 这个节点是分组的子节点,且其所属分组节点已选,则取消选中
252
+ if (isMultiple && isSelected) {
253
+ if (nodeModel?.isGroup) {
254
+ const { children } = nodeModel as LaneModel
255
+ forEach(Array.from(children), (childId) => {
256
+ const childModel = this.lf.getNodeModelById(childId)
257
+ childModel?.setSelected(false)
258
+ })
259
+ } else {
260
+ const laneId = this.nodeLaneMap.get(node.id)
261
+ if (laneId) {
262
+ const laneModel = this.lf.getNodeModelById(laneId)
263
+ laneModel?.isSelected && nodeModel?.setSelected(false)
264
+ }
265
+ }
266
+ }
267
+ }
268
+
269
+ onNodeMove = ({
270
+ deltaX,
271
+ deltaY,
272
+ data,
273
+ }: Omit<CallbackArgs<'node:mousemove'>, 'e' | 'position'>) => {
274
+ const { id, x, y, properties } = data
275
+ if (!properties) {
276
+ return
277
+ }
278
+ const { width, height } = properties
279
+ const groupId = this.nodeLaneMap.get(id)
280
+ if (!groupId) {
281
+ return
282
+ }
283
+ const groupModel = this.lf.getNodeModelById(groupId) as LaneModel
284
+
285
+ if (!groupModel || !groupModel.isRestrict || !groupModel.autoResize) {
286
+ return
287
+ }
288
+ // 当父节点isRestrict=true & autoResize=true
289
+ // 子节点在父节点中移动时,父节点会自动调整大小
290
+
291
+ // step1: 计算出当前child的bounds
292
+ const newX = x + deltaX / 2
293
+ const newY = y + deltaY / 2
294
+ const minX = newX - width! / 2
295
+ const minY = newY - height! / 2
296
+ const maxX = newX + width! / 2
297
+ const maxY = newY + height! / 2
298
+ // step2:比较当前child.bounds与parent.bounds的差异,比如child.minX<parent.minX,那么parent.minX=child.minX
299
+ let hasChange = false
300
+ const groupBounds = groupModel.getBounds()
301
+ const newGroupBounds = Object.assign({}, groupBounds)
302
+ if (minX < newGroupBounds.minX) {
303
+ newGroupBounds.minX = minX
304
+ hasChange = true
305
+ }
306
+ if (minY < newGroupBounds.minY) {
307
+ newGroupBounds.minY = minY
308
+ hasChange = true
309
+ }
310
+ if (maxX > newGroupBounds.maxX) {
311
+ newGroupBounds.maxX = maxX
312
+ hasChange = true
313
+ }
314
+ if (maxY > newGroupBounds.maxY) {
315
+ newGroupBounds.maxY = maxY
316
+ hasChange = true
317
+ }
318
+ if (!hasChange) {
319
+ return
320
+ }
321
+ // step3: 根据当前parent.bounds去计算出最新的x、y、width、height
322
+ const newGroupX =
323
+ newGroupBounds.minX + (newGroupBounds.maxX - newGroupBounds.minX) / 2
324
+ const newGroupY =
325
+ newGroupBounds.minY + (newGroupBounds.maxY - newGroupBounds.minY) / 2
326
+ const newGroupWidth = newGroupBounds.maxX - newGroupBounds.minX
327
+ const newGroupHeight = newGroupBounds.maxY - newGroupBounds.minY
328
+ groupModel.moveTo(newGroupX, newGroupY)
329
+ groupModel.width = newGroupWidth
330
+ groupModel.height = newGroupHeight
331
+ }
332
+
333
+ onGraphRendered = ({ data }: CallbackArgs<'graph:rendered'>) => {
334
+ forEach(data.nodes, (node) => {
335
+ if (node.children) {
336
+ forEach(node.children, (childId) => {
337
+ this.nodeLaneMap.set(childId, node.id)
338
+ })
339
+ }
340
+ })
341
+ }
342
+
343
+ removeChildrenInGroupNodeData<
344
+ T extends LogicFlow.NodeData | LogicFlow.NodeConfig,
345
+ >(nodeData: T) {
346
+ const newNodeData = cloneDeep(nodeData)
347
+ delete newNodeData.children
348
+ if (newNodeData.properties?.children) {
349
+ delete newNodeData.properties.children
350
+ }
351
+ return newNodeData
352
+ }
353
+
354
+ /**
355
+ * 创建一个 Group 类型节点内部所有子节点的副本
356
+ * 并且在遍历所有 nodes 的过程中,顺便拿到所有 edges (只在 Group 范围的 edges)
357
+ */
358
+ initGroupChildNodes(
359
+ nodeIdMap: Record<string, string>,
360
+ children: Set<string>,
361
+ curGroup: LaneModel,
362
+ distance: number,
363
+ ): ElementsInfoInGroup {
364
+ // Group 中所有子节点
365
+ const allChildNodes: BaseNodeModel[] = []
366
+ // 属于 Group 内部边的 EdgeData
367
+ const edgesDataArr: EdgeData[] = []
368
+ // 所有有关联的连线
369
+ const allRelatedEdges: BaseEdgeModel[] = []
370
+
371
+ forEach(Array.from(children), (childId: string) => {
372
+ const childNode = this.lf.getNodeModelById(childId)
373
+ if (childNode) {
374
+ const childNodeChildren = childNode.children
375
+ const childNodeData = childNode.getData()
376
+ const eventType = EventType.NODE_GROUP_COPY || 'node:group-copy-add'
377
+
378
+ const newNodeConfig = transformNodeData(
379
+ this.removeChildrenInGroupNodeData(childNodeData),
380
+ distance,
381
+ )
382
+ const tempChildNode = this.lf.addNode(newNodeConfig, eventType)
383
+ curGroup.addChild(tempChildNode.id)
384
+
385
+ nodeIdMap[childId] = tempChildNode.id // id 同 childId,做映射存储
386
+ allChildNodes.push(tempChildNode)
387
+
388
+ // 1. 存储 children 内部节点相关的输入边(incoming)
389
+ allRelatedEdges.push(
390
+ ...[...tempChildNode.incoming.edges, ...tempChildNode.outgoing.edges],
391
+ )
392
+
393
+ if (childNodeChildren instanceof Set) {
394
+ const { childNodes, edgesData } = this.initGroupChildNodes(
395
+ nodeIdMap,
396
+ childNodeChildren,
397
+ tempChildNode as LaneModel,
398
+ distance,
399
+ )
400
+
401
+ allChildNodes.push(...childNodes)
402
+ edgesDataArr.push(...edgesData)
403
+ }
404
+ }
405
+ })
406
+
407
+ // 1. 判断每一条边的开始节点、目标节点是否在 Group 中
408
+ const edgesInnerGroup = filter(allRelatedEdges, (edge) => {
409
+ return (
410
+ has(nodeIdMap, edge.sourceNodeId) && has(nodeIdMap, edge.targetNodeId)
411
+ )
412
+ })
413
+ // 2. 为「每一条 Group 的内部边」构建出 EdgeData 数据,得到 EdgeConfig,生成新的线
414
+ const edgesDataInnerGroup = map(edgesInnerGroup, (edge) => {
415
+ return edge.getData()
416
+ })
417
+
418
+ return {
419
+ childNodes: allChildNodes,
420
+ edgesData: edgesDataArr.concat(edgesDataInnerGroup),
421
+ }
422
+ }
423
+
424
+ /**
425
+ * 根据参数 edge 选择是新建边还是基于已有边,复制一条边出来
426
+ * @param edge
427
+ * @param nodeIdMap
428
+ * @param distance
429
+ */
430
+ createEdge(
431
+ edge: EdgeConfig | EdgeData,
432
+ nodeIdMap: Record<string, string>,
433
+ distance: number,
434
+ ) {
435
+ const { sourceNodeId, targetNodeId } = edge
436
+ const sourceId = nodeIdMap[sourceNodeId] ?? sourceNodeId
437
+ const targetId = nodeIdMap[targetNodeId] ?? targetNodeId
438
+
439
+ // 如果是有 id 且 text 是对象的边,需要重新计算位置,否则直接用 edgeConfig 生成边
440
+ let newEdgeConfig = cloneDeep(edge)
441
+ if (edge.id && typeof edge.text === 'object' && edge.text !== null) {
442
+ newEdgeConfig = transformEdgeData(edge as EdgeData, distance)
443
+ }
444
+
445
+ return this.lf.graphModel.addEdge({
446
+ ...newEdgeConfig,
447
+ sourceNodeId: sourceId,
448
+ targetNodeId: targetId,
449
+ })
450
+ }
451
+
452
+ /**
453
+ * 检测group:resize后的bounds是否会小于children的bounds
454
+ * 限制group进行resize时不能小于内部的占地面积
455
+ * @param groupModel
456
+ * @param deltaX
457
+ * @param deltaY
458
+ * @param newWidth
459
+ * @param newHeight
460
+ */
461
+ checkGroupBoundsWithChildren(
462
+ groupModel: LaneModel,
463
+ deltaX: number,
464
+ deltaY: number,
465
+ newWidth: number,
466
+ newHeight: number,
467
+ ) {
468
+ if (groupModel.children) {
469
+ const { children, x, y } = groupModel
470
+ // 根据deltaX和deltaY计算出当前model的bounds
471
+ const newX = x + deltaX / 2
472
+ const newY = y + deltaY / 2
473
+ const groupMinX = newX - newWidth / 2
474
+ const groupMinY = newY - newHeight / 2
475
+ const groupMaxX = newX + newWidth / 2
476
+ const groupMaxY = newY + newHeight / 2
477
+
478
+ const childrenArray = Array.from(children)
479
+ for (let i = 0; i < childrenArray.length; i++) {
480
+ const childId = childrenArray[i]
481
+ const child = this.lf.getNodeModelById(childId)
482
+ if (!child) {
483
+ continue
484
+ }
485
+ const childBounds = child.getBounds()
486
+ const { minX, minY, maxX, maxY } = childBounds
487
+ // parent:resize后的bounds不能小于child:bounds,否则阻止其resize
488
+ const canResize =
489
+ groupMinX <= minX &&
490
+ groupMinY <= minY &&
491
+ groupMaxX >= maxX &&
492
+ groupMaxY >= maxY
493
+ if (!canResize) {
494
+ return false
495
+ }
496
+ }
497
+ }
498
+
499
+ return true
500
+ }
501
+
502
+ init() {
503
+ const { lf } = this
504
+ const { graphModel } = lf
505
+ // 添加分组节点移动规则
506
+ // 1. 移动分组节点时,同时移动分组内所有节点
507
+ // 2. 移动子节点时,判断是否有限制规则(isRestrict)
508
+ graphModel.addNodeMoveRules((model, deltaX, deltaY) => {
509
+ // 判断如果是 group,移动时需要同时移动组内的所有节点
510
+ if (model.isGroup) {
511
+ return true
512
+ }
513
+
514
+ const groupId = this.nodeLaneMap.get(model.id)!
515
+ const groupModel = this.lf.getNodeModelById(groupId) as LaneModel
516
+
517
+ if (groupModel && groupModel.isRestrict) {
518
+ if (groupModel.autoResize) {
519
+ // 子节点在父节点中移动时,父节点会自动调整大小
520
+ // 在node:mousemove中进行父节点的调整
521
+ return true
522
+ } else {
523
+ // 如果移动的节点存在于某个分组中,且这个分组禁止子节点移出去
524
+ const groupBounds = groupModel.getBounds()
525
+ return isAllowMoveTo(groupBounds, model, deltaX, deltaY)
526
+ }
527
+ }
528
+
529
+ return true
530
+ })
531
+ graphModel.addNodeResizeRules((model, deltaX, deltaY, width, height) => {
532
+ if (model.isGroup && model.isRestrict) {
533
+ return this.checkGroupBoundsWithChildren(
534
+ model as LaneModel,
535
+ deltaX,
536
+ deltaY,
537
+ width,
538
+ height,
539
+ )
540
+ }
541
+ return true
542
+ })
543
+
544
+ graphModel.dynamicGroup = this
545
+ lf.on('node:add,node:drop,node:dnd-add', this.onNodeAddOrDrop)
546
+ lf.on('selection:drop', this.onSelectionDrop)
547
+ lf.on('node:delete', this.removeNodeFromGroup)
548
+ lf.on('node:drag,node:dnd-drag', this.onNodeDrag)
549
+ lf.on('selection:drag', this.onSelectionDrag)
550
+ lf.on('node:click', this.onNodeSelect)
551
+ lf.on('node:mousemove', this.onNodeMove)
552
+ lf.on('graph:rendered', this.onGraphRendered)
553
+
554
+ lf.on('group:add-node', this.onGroupAddNode)
555
+
556
+ lf.addElements = (
557
+ { nodes: selectedNodes, edges: selectedEdges }: GraphConfigData,
558
+ distance = 40,
559
+ ): GraphElements => {
560
+ // oldNodeId -> newNodeId 映射 Map
561
+ const nodeIdMap: Record<string, string> = {}
562
+ // 本次添加的所有节点和边
563
+ const elements: GraphElements = {
564
+ nodes: [],
565
+ edges: [],
566
+ }
567
+ // 所有属于分组内的边 -> sourceNodeId 和 targetNodeId 都在 Group 内
568
+ const edgesInnerGroup: EdgeData[] = []
569
+
570
+ forEach(selectedNodes, (node) => {
571
+ const originId = node.id
572
+ const children = node.properties?.children ?? node.children
573
+
574
+ const model = lf.addNode(this.removeChildrenInGroupNodeData(node))
575
+
576
+ if (originId) nodeIdMap[originId] = model.id
577
+ elements.nodes.push(model) // 此时为 group 的 nodeModel
578
+
579
+ if (model.isGroup) {
580
+ const { edgesData } = this.initGroupChildNodes(
581
+ nodeIdMap,
582
+ children,
583
+ model as LaneModel,
584
+ distance,
585
+ )
586
+ edgesInnerGroup.push(...edgesData)
587
+ }
588
+ })
589
+
590
+ forEach(edgesInnerGroup, (edge) => {
591
+ this.createEdge(edge, nodeIdMap, distance)
592
+ })
593
+ forEach(selectedEdges, (edge) => {
594
+ elements.edges.push(this.createEdge(edge, nodeIdMap, distance))
595
+ })
596
+
597
+ // 返回 elements 进行选中效果,即触发 element.selectElementById()
598
+ // shortcut.ts 也会对最外层的 nodes 和 edges 进行偏移,即 translationNodeData()
599
+ return elements
600
+ }
601
+
602
+ this.render()
603
+ }
604
+
605
+ render() {}
606
+
607
+ destroy() {
608
+ // 销毁监听的事件,并移除渲染的 dom 内容
609
+ this.lf.off('node:add,node:drop,node:dnd-add', this.onNodeAddOrDrop)
610
+ this.lf.off('selection:drop', this.onSelectionDrop)
611
+ this.lf.off('node:delete', this.removeNodeFromGroup)
612
+ this.lf.off('node:drag,node:dnd-drag', this.onNodeDrag)
613
+ this.lf.off('selection:drag', this.onSelectionDrag)
614
+ this.lf.off('node:click', this.onNodeSelect)
615
+ this.lf.off('node:mousemove', this.onNodeMove)
616
+ this.lf.off('graph:rendered', this.onGraphRendered)
617
+ this.lf.off('group:add-node', this.onGroupAddNode)
618
+ }
619
+ }
620
+
621
+ export default PoolElements
@@ -0,0 +1,46 @@
1
+ import { BaseNodeModel, Model } from '@logicflow/core'
2
+ import BoxBoundsPoint = Model.BoxBoundsPoint
3
+
4
+ /**
5
+ *
6
+ * @param bounds
7
+ * @param group
8
+ */
9
+ export function isBoundsInLane(bounds: BoxBoundsPoint, group: BaseNodeModel) {
10
+ const { minX, minY, maxX, maxY } = bounds
11
+ const { x, y, width, height } = group
12
+ return (
13
+ minX >= x - width / 2 &&
14
+ maxX <= x + width / 2 &&
15
+ minY >= y - height / 2 &&
16
+ maxY <= y + height / 2
17
+ )
18
+ }
19
+
20
+ /**
21
+ * 判断 bounds 是否可以移动到下一个范围
22
+ * @param groupBounds
23
+ * @param node
24
+ * @param deltaX
25
+ * @param deltaY
26
+ */
27
+ export function isAllowMoveTo(
28
+ groupBounds: BoxBoundsPoint,
29
+ node: BaseNodeModel,
30
+ deltaX: number,
31
+ deltaY: number,
32
+ ) {
33
+ const { minX, minY, maxX, maxY } = groupBounds
34
+ const { x, y, width, height } = node
35
+
36
+ // DONE: 计算节点坐标 (x, y) 可移动的范围,并判断 x + deltaX, y + deltaY 是否在范围内
37
+ const allowMoveMinX = minX + width / 2
38
+ const allowMoveMinY = minY + height / 2
39
+ const allowMoveMaxX = maxX - width / 2
40
+ const allowMoveMaxY = maxY - height / 2
41
+
42
+ return {
43
+ x: x + deltaX >= allowMoveMinX && x + deltaX <= allowMoveMaxX,
44
+ y: y + deltaY >= allowMoveMinY && y + deltaY <= allowMoveMaxY,
45
+ }
46
+ }