@logicflow/extension 2.0.0-beta.6 → 2.0.0-beta.8

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 (73) hide show
  1. package/.turbo/turbo-build.log +739 -870
  2. package/dist/index.min.js +20 -6
  3. package/es/NodeResize/control/Control.d.ts +3 -3
  4. package/es/NodeResize/control/Control.js +10 -1
  5. package/es/NodeResize/control/Control.js.map +1 -1
  6. package/es/NodeResize/index.d.ts +4 -0
  7. package/es/NodeResize/index.js.map +1 -1
  8. package/es/bpmn-elements/presets/Pool/Pool.d.ts +21 -1
  9. package/es/dynamic-group/index.d.ts +106 -0
  10. package/es/dynamic-group/index.js +536 -0
  11. package/es/dynamic-group/index.js.map +1 -0
  12. package/es/dynamic-group/model.d.ts +135 -0
  13. package/es/dynamic-group/model.js +413 -0
  14. package/es/dynamic-group/model.js.map +1 -0
  15. package/es/dynamic-group/node.d.ts +16 -0
  16. package/es/dynamic-group/node.js +143 -0
  17. package/es/dynamic-group/node.js.map +1 -0
  18. package/es/dynamic-group/utils.d.ts +17 -0
  19. package/es/dynamic-group/utils.js +27 -0
  20. package/es/dynamic-group/utils.js.map +1 -0
  21. package/es/index.d.ts +14 -9
  22. package/es/index.js +21 -9
  23. package/es/index.js.map +1 -1
  24. package/es/materials/group/GroupNode.d.ts +0 -4
  25. package/es/materials/group/GroupNode.js +1 -0
  26. package/es/materials/group/GroupNode.js.map +1 -1
  27. package/es/materials/group/index.d.ts +4 -4
  28. package/es/materials/group/index.js +5 -1
  29. package/es/materials/group/index.js.map +1 -1
  30. package/es/materials/node-selection/index.d.ts +4 -0
  31. package/es/materials/node-selection/index.js.map +1 -1
  32. package/es/mindmap/index.d.ts +2 -2
  33. package/lib/NodeResize/control/Control.d.ts +3 -3
  34. package/lib/NodeResize/control/Control.js +10 -1
  35. package/lib/NodeResize/control/Control.js.map +1 -1
  36. package/lib/NodeResize/index.d.ts +4 -0
  37. package/lib/NodeResize/index.js.map +1 -1
  38. package/lib/bpmn-elements/presets/Pool/Pool.d.ts +21 -1
  39. package/lib/dynamic-group/index.d.ts +106 -0
  40. package/lib/dynamic-group/index.js +553 -0
  41. package/lib/dynamic-group/index.js.map +1 -0
  42. package/lib/dynamic-group/model.d.ts +135 -0
  43. package/lib/dynamic-group/model.js +416 -0
  44. package/lib/dynamic-group/model.js.map +1 -0
  45. package/lib/dynamic-group/node.d.ts +16 -0
  46. package/lib/dynamic-group/node.js +146 -0
  47. package/lib/dynamic-group/node.js.map +1 -0
  48. package/lib/dynamic-group/utils.d.ts +17 -0
  49. package/lib/dynamic-group/utils.js +32 -0
  50. package/lib/dynamic-group/utils.js.map +1 -0
  51. package/lib/index.d.ts +14 -9
  52. package/lib/index.js +21 -9
  53. package/lib/index.js.map +1 -1
  54. package/lib/materials/group/GroupNode.d.ts +0 -4
  55. package/lib/materials/group/GroupNode.js +1 -0
  56. package/lib/materials/group/GroupNode.js.map +1 -1
  57. package/lib/materials/group/index.d.ts +4 -4
  58. package/lib/materials/group/index.js +5 -1
  59. package/lib/materials/group/index.js.map +1 -1
  60. package/lib/materials/node-selection/index.d.ts +4 -0
  61. package/lib/materials/node-selection/index.js.map +1 -1
  62. package/lib/mindmap/index.d.ts +2 -2
  63. package/package.json +3 -5
  64. package/src/NodeResize/control/Control.tsx +10 -1
  65. package/src/NodeResize/index.ts +4 -0
  66. package/src/dynamic-group/index.ts +609 -0
  67. package/src/dynamic-group/model.ts +503 -0
  68. package/src/dynamic-group/node.ts +140 -0
  69. package/src/dynamic-group/utils.ts +33 -0
  70. package/src/index.ts +26 -9
  71. package/src/materials/group/GroupNode.ts +1 -4
  72. package/src/materials/group/index.ts +6 -1
  73. package/src/materials/node-selection/index.ts +6 -1
@@ -0,0 +1,503 @@
1
+ import LogicFlow, {
2
+ observable,
3
+ BaseEdgeModel,
4
+ ElementType,
5
+ GraphModel,
6
+ IRectNodeProperties,
7
+ RectNodeModel,
8
+ } from '@logicflow/core'
9
+ import { forEach } from 'lodash-es'
10
+
11
+ import NodeData = LogicFlow.NodeData
12
+ import NodeConfig = LogicFlow.NodeConfig
13
+ import EdgeConfig = LogicFlow.EdgeConfig
14
+
15
+ export type IGroupNodeProperties = {
16
+ /**
17
+ * 当前分组中的节点 id
18
+ */
19
+ children?: string[]
20
+ /**
21
+ * 分组节点是否可以折叠
22
+ */
23
+ collapsible?: boolean
24
+ /**
25
+ * 分组节点折叠状态
26
+ */
27
+ isCollapsed?: boolean
28
+ /**
29
+ * 子节点是否限制移动范围
30
+ * 默认为 false,允许拖拽移除分组
31
+ */
32
+ isRestrict?: boolean
33
+ /**
34
+ * isRestrict 模式启用时,
35
+ * 如果同时设置 autoResize 为 true,
36
+ * 那么子节点在父节点中移动时,父节点会自动调整大小
37
+ */
38
+ autoResize?: boolean
39
+
40
+ /**
41
+ * 分组节点的收起状态宽高
42
+ */
43
+ collapsedWidth?: number
44
+ collapsedHeight?: number
45
+ // 默认宽高作为 group 的展开宽高
46
+ // /**
47
+ // * 分组节点的展开状态宽高
48
+ // */
49
+ // expandWidth?: number
50
+ // expandHeight?: number
51
+
52
+ /**
53
+ * 当前分组元素的 zIndex
54
+ */
55
+ zIndex?: number
56
+ /**
57
+ * 分组节点是否自动置顶
58
+ */
59
+ autoToFront?: boolean
60
+
61
+ // 节点是否允许添加到分组中,是否可以通过 properties 的方式传入
62
+ // TODO: 函数类型的 properties 该如何传入
63
+ isAllowAppendIn?: (_nodeData) => boolean
64
+ } & IRectNodeProperties
65
+
66
+ // 分组节点默认展开时的大小
67
+ const DEFAULT_GROUP_EXPAND_WIDTH = 400
68
+ const DEFAULT_GROUP_EXPAND_HEIGHT = 230
69
+ // 分组节点默认收起时的大小
70
+ const DEFAULT_GROUP_COLLAPSE_WIDTH = 80
71
+ const DEFAULT_GROUP_COLLAPSE_HEIGHT = 60
72
+
73
+ const DEFAULT_BOTTOM_Z_INDEX = -10000
74
+
75
+ export class DynamicGroupNodeModel extends RectNodeModel<IGroupNodeProperties> {
76
+ readonly isGroup = true
77
+
78
+ // 保存组内的节点
79
+ children!: Set<string>
80
+ // 是否限制组内节点的移动范围。默认不限制 TODO: 完善该功能
81
+ isRestrict: boolean = false
82
+ // 分组节点是否可以折叠
83
+ collapsible: boolean = true
84
+
85
+ // 分组节点 初始化尺寸(默认展开),后续支持从 properties 中传入 width 和 height 设置
86
+ expandWidth!: number
87
+ expandHeight!: number
88
+ // 折叠后
89
+ collapsedWidth!: number
90
+ collapsedHeight!: number
91
+
92
+ // 当前组是否收起状态
93
+ @observable isCollapsed: boolean = false
94
+ // 当前分组是否在可添加状态 - 实时状态
95
+ @observable groupAddable: boolean = false
96
+ // 缩放或旋转容器时,是否缩放或旋转组内节点
97
+ @observable transformWidthContainer: boolean = true
98
+ childrenLastCollapseStateDict: Record<string, boolean> = {}
99
+
100
+ constructor(data: NodeConfig<IGroupNodeProperties>, graphModel: GraphModel) {
101
+ super(data, graphModel)
102
+ this.initNodeData(data)
103
+
104
+ this.setAttributes()
105
+ }
106
+
107
+ initNodeData(data: LogicFlow.NodeConfig<IGroupNodeProperties>) {
108
+ super.initNodeData(data)
109
+
110
+ const {
111
+ children,
112
+ width,
113
+ height,
114
+ collapsedWidth,
115
+ collapsedHeight,
116
+
117
+ collapsible,
118
+ isCollapsed,
119
+ zIndex,
120
+ isRestrict,
121
+ autoToFront,
122
+ } = data.properties ?? {}
123
+
124
+ this.children = children ? new Set(children) : new Set()
125
+ this.zIndex = zIndex ?? DEFAULT_BOTTOM_Z_INDEX
126
+ this.isCollapsed = isCollapsed ?? false
127
+
128
+ const expandWidth = width ?? DEFAULT_GROUP_EXPAND_WIDTH
129
+ const expandHeight = height ?? DEFAULT_GROUP_EXPAND_HEIGHT
130
+
131
+ // 初始化分组节点的宽高数据
132
+ this.width = expandWidth
133
+ this.height = expandHeight
134
+ this.expandWidth = expandWidth
135
+ this.expandHeight = expandHeight
136
+ this.collapsedWidth = collapsedWidth ?? DEFAULT_GROUP_COLLAPSE_WIDTH
137
+ this.collapsedHeight = collapsedHeight ?? DEFAULT_GROUP_COLLAPSE_HEIGHT
138
+
139
+ this.isRestrict = isRestrict ?? false
140
+ this.collapsible = collapsible ?? true
141
+ this.autoToFront = autoToFront ?? false
142
+
143
+ // 禁用掉 Group 节点的文本编辑能力
144
+ this.text.editable = false
145
+ this.text.draggable = false
146
+ }
147
+
148
+ setAttributes() {
149
+ super.setAttributes()
150
+
151
+ // 初始化时,如果 this.isCollapsed 为 true,则主动触发一次折叠操作
152
+ if (this.isCollapsed) {
153
+ this.toggleCollapse(true)
154
+ }
155
+ }
156
+
157
+ getData(): NodeData {
158
+ const data = super.getData()
159
+ const children: string[] = []
160
+
161
+ forEach(Array.from(this.children), (childId) => {
162
+ const model = this.graphModel.getNodeModelById(childId)
163
+ if (model && !model.virtual) {
164
+ children.push(childId)
165
+ }
166
+ })
167
+ data.children = children
168
+
169
+ if (data.properties) {
170
+ data.properties.children = children
171
+ data.properties.isCollapsed = this.isCollapsed
172
+ }
173
+
174
+ return data
175
+ }
176
+
177
+ /**
178
+ * 重写 getHistoryData 方法
179
+ */
180
+ getHistoryData(): NodeData {
181
+ const data = super.getHistoryData()
182
+ data.children = Array.from(this.children)
183
+ data.isGroup = true
184
+
185
+ const {
186
+ x,
187
+ y,
188
+ collapsedWidth,
189
+ collapsedHeight,
190
+ expandWidth,
191
+ expandHeight,
192
+ isCollapsed,
193
+ } = this
194
+ if (isCollapsed) {
195
+ data.x = x + expandWidth / 2 - collapsedWidth / 2
196
+ data.y = y + expandHeight / 2 - collapsedHeight / 2
197
+ }
198
+ return data
199
+ }
200
+
201
+ /**
202
+ * 触发分组节点的「折叠 or 展开」动作
203
+ * 1. 折叠分组的宽高
204
+ * 2. 处理分组子节点
205
+ * 3. 处理连线
206
+ * @param collapse {boolean} true -> 折叠;false -> 展开
207
+ */
208
+ toggleCollapse(collapse?: boolean) {
209
+ const nextCollapseState = !!collapse
210
+ this.isCollapsed = nextCollapseState
211
+ // step 1
212
+ if (nextCollapseState) {
213
+ this.collapse()
214
+ } else {
215
+ this.expand()
216
+ }
217
+
218
+ // step 2
219
+ let allRelatedEdges = [...this.incoming.edges, ...this.outgoing.edges]
220
+ console.log('this -->>', this)
221
+ console.log('this.children -->>', this.children)
222
+ const childrenArr = Array.from(this.children)
223
+ forEach(childrenArr, (elementId) => {
224
+ const model = this.graphModel.getElement(elementId)
225
+
226
+ if (model) {
227
+ // TODO: ??? 普通节点有这个属性吗?确认这个代码的意义
228
+ const collapseStatus = model.isCollapsed
229
+ // FIX: https://github.com/didi/LogicFlow/issues/1007
230
+ // 下面代码片段,针对 Group 节点执行
231
+ if (model.isGroup) {
232
+ const groupModel = model as DynamicGroupNodeModel
233
+
234
+ if (!groupModel.isCollapsed) {
235
+ // 正常情况下,parent 折叠后,children 也应该折叠
236
+ // 因此当前 parent 准备展开时,children 的目前状态肯定是折叠状态,也就是 model.isCollapsed 为 true,这个代码块不会触发
237
+ // 只有当 parent 准备折叠时,children 目前状态才有可能是展开
238
+ // 即 model.isCollapsed 为 false,这个代码块触发, 此时 isCollapse 为 true,触发 children 也进行折叠
239
+ groupModel.toggleCollapse(collapse)
240
+ }
241
+
242
+ if (!collapse) {
243
+ // 当 parent 准备展开时,children 的值应该恢复到折叠前的状态
244
+ const lastCollapseStatus =
245
+ this.childrenLastCollapseStateDict[elementId]
246
+ if (
247
+ lastCollapseStatus !== undefined &&
248
+ lastCollapseStatus !== model.isCollapsed
249
+ ) {
250
+ // https://github.com/didi/LogicFlow/issues/1145
251
+ // 当parent准备展开时,children的值肯定是折叠,也就是nodeModel.isCollapsed=true
252
+ // 当parent准备展开时,如果children之前的状态是展开,则恢复展开状态
253
+ groupModel.toggleCollapse(lastCollapseStatus)
254
+ }
255
+ }
256
+ }
257
+
258
+ this.childrenLastCollapseStateDict[elementId] = !!collapseStatus
259
+ model.visible = !collapse
260
+
261
+ // 判断,如果是节点时,才去读取节点的 incoming 和 outgoing
262
+ if (model.BaseType === ElementType.NODE) {
263
+ const incomingEdges = model.incoming.edges
264
+ const outgoingEdges = model.outgoing.edges
265
+
266
+ allRelatedEdges = [
267
+ ...allRelatedEdges,
268
+ ...incomingEdges,
269
+ ...outgoingEdges,
270
+ ]
271
+ }
272
+ }
273
+ })
274
+ // step 3
275
+ this.collapseEdge(nextCollapseState, allRelatedEdges)
276
+ }
277
+
278
+ // 折叠操作
279
+ collapse() {
280
+ const { x, y, text, width, height, collapsedWidth, collapsedHeight } = this
281
+ this.x = x - width / 2 + collapsedWidth / 2
282
+ this.y = y - height / 2 + collapsedHeight / 2
283
+
284
+ this.text.x = text.x - width / 2 + collapsedWidth / 2
285
+ this.text.y = text.y - height / 2 + collapsedHeight / 2
286
+
287
+ // 记录折叠前的节点大小,并将其记录到 expandWidth 中
288
+ this.expandWidth = width
289
+ this.expandHeight = height
290
+
291
+ this.width = collapsedWidth
292
+ this.height = collapsedHeight
293
+ }
294
+
295
+ // 展开操作
296
+ expand() {
297
+ const {
298
+ x,
299
+ y,
300
+ text,
301
+ expandWidth,
302
+ expandHeight,
303
+ collapsedWidth,
304
+ collapsedHeight,
305
+ } = this
306
+ this.width = expandWidth
307
+ this.height = expandHeight
308
+
309
+ // 重新计算节点及文本的坐标
310
+ this.x = x + this.width / 2 - collapsedWidth / 2
311
+ this.y = y + this.height / 2 - collapsedHeight / 2
312
+
313
+ this.text.x = text.x + this.width / 2 - collapsedWidth / 2
314
+ this.text.y = text.y + this.height / 2 - collapsedHeight / 2
315
+ }
316
+
317
+ createVirtualEdge(edgeConfig: EdgeConfig) {
318
+ edgeConfig.pointsList = undefined
319
+
320
+ const virtualEdge = this.graphModel.addEdge(edgeConfig)
321
+ virtualEdge.virtual = true
322
+
323
+ // TODO: 强制不保存 group 连线数据???-> 为什么注释掉?是不是不能强制设置为 null,会导致无法回填
324
+ // virtualEdge.getData = () => null
325
+ virtualEdge.text.editable = false
326
+ virtualEdge.isCollapsedEdge = true // 这一行干啥的,TODO: 项目中没搜到应用的地方,是否应该移除
327
+ }
328
+
329
+ /**
330
+ * 折叠分组的时候,需要处理分组自身的连线和分组内部子节点上的连线
331
+ * 边的分类:
332
+ * - 虚拟边:分组被收起时,标识分组本身与外部节点关系的边
333
+ * - 真实边:分组本身或者分组内部节点与外部节点(非收起分组)关系的边
334
+ * 如果一个分组,本身与外部节点有 M 条连线,且内部 N 个子节点与外部节点有连线,那么这个分组收起时会生成 M+N 条连线
335
+ * 折叠分组时:
336
+ * - 原有的虚拟边删除
337
+ * - 创建一个虚拟边
338
+ * - 真实边则隐藏
339
+ * 展开分组时:
340
+ * - 当前的虚拟边删除
341
+ * - 如果外部节点是收起的分组,则创建虚拟边
342
+ * - 如果外部节点是普通节点,则显示真实边
343
+ *
344
+ * @param collapse
345
+ * @param edges
346
+ */
347
+ collapseEdge(collapse: boolean, edges: BaseEdgeModel[]) {
348
+ const { graphModel } = this
349
+ forEach(edges, (edge, idx) => {
350
+ const edgeData = edge.getData()
351
+ const { targetNodeId, sourceNodeId } = edgeData
352
+
353
+ const edgeConfig: EdgeConfig = {
354
+ ...edgeData,
355
+ id: `${edgeData.id}__${idx}`,
356
+ text: edgeData.text?.value,
357
+ }
358
+
359
+ if (edge.virtual) {
360
+ graphModel.deleteEdgeById(edge.id)
361
+ }
362
+ // 考虑目标节点也属于分组的情况
363
+ let targetNodeGroup = graphModel.group.getGroupByNodeId(targetNodeId)
364
+ if (!targetNodeGroup) {
365
+ targetNodeGroup = graphModel.getNodeModelById(targetNodeId)
366
+ }
367
+
368
+ // 考虑源节点也属于分组的情况
369
+ let sourceNodeGroup = graphModel.group.getGroupByNodeId(sourceNodeId)
370
+ if (!sourceNodeGroup) {
371
+ sourceNodeGroup = graphModel.getNodeModelById(sourceNodeId)
372
+ }
373
+
374
+ // 折叠时,处理未被隐藏的边的逻辑 -> collapse
375
+ if (collapse && edge.visible) {
376
+ // 需要确认此分组节点是新连线的起点还是终点
377
+ // 创建一个虚拟边,虚拟边相对于真实边,起点或者终点从一起分组节点的中心点开始 TODO:??? 确认什么意思
378
+ // 如果需要被隐藏的边的起点在需要折叠的分组中,那么设置虚拟边的开始节点为此分组
379
+ if (this.children.has(sourceNodeId) || this.id === sourceNodeId) {
380
+ edgeConfig.startPoint = undefined
381
+ edgeConfig.sourceNodeId = this.id
382
+ } else {
383
+ edgeConfig.endPoint = undefined
384
+ edgeConfig.targetNodeId = this.id
385
+ }
386
+
387
+ // 如果边的起点和终点都在分组内部,则不创建新的虚拟边
388
+ if (targetNodeGroup.id !== this.id || sourceNodeGroup.id !== this.id) {
389
+ this.createVirtualEdge(edgeConfig)
390
+ }
391
+ edge.visible = false
392
+ }
393
+
394
+ // 展开时,处理被隐藏的边的逻辑 -> expand
395
+ if (!collapse && !edge.visible) {
396
+ // 展开分组时:判断真实边的起点和中带你是否有任一节点在已折叠分组中,如果不是,则显示真实边
397
+ // 如果是,则修改这个边的对应目标节点 id 来创建虚拟边
398
+ if (
399
+ targetNodeGroup &&
400
+ targetNodeGroup.isGroup &&
401
+ targetNodeGroup.isCollapsed
402
+ ) {
403
+ edgeConfig.targetNodeId = targetNodeGroup.id
404
+ edgeConfig.endPoint = undefined
405
+ this.createVirtualEdge(edgeConfig)
406
+ } else if (
407
+ sourceNodeGroup &&
408
+ sourceNodeGroup.isGroup &&
409
+ sourceNodeGroup.isCollapsed
410
+ ) {
411
+ edgeConfig.sourceNodeId = sourceNodeGroup.id
412
+ edgeConfig.startPoint = undefined
413
+ this.createVirtualEdge(edgeConfig)
414
+ } else {
415
+ edge.visible = true
416
+ }
417
+ }
418
+ })
419
+ }
420
+
421
+ /**
422
+ * 是否允许此节点添加到该分组
423
+ * TODO: 如何重写该方法呢?
424
+ * @param _nodeData
425
+ */
426
+ isAllowAppendIn(_nodeData: NodeData) {
427
+ console.info('_nodeData', _nodeData)
428
+ // TODO: 此处使用 this.properties.groupAddable 还是 this.groupAddable
429
+ // this.groupAddable 是否存在更新不及时的问题
430
+ return true
431
+ }
432
+
433
+ /**
434
+ * 更新分组节点是否允许添加节点的属性
435
+ * @param isAllow
436
+ */
437
+ setAllowAppendChild(isAllow: boolean) {
438
+ // this.setProperty('groupAddable', isAllow)
439
+ this.groupAddable = isAllow
440
+ }
441
+
442
+ /**
443
+ * 添加节点至分组中
444
+ * @param id
445
+ */
446
+ addChild(id: string) {
447
+ this.children.add(id)
448
+ const groupData = this.getData()
449
+ this.graphModel.eventCenter.emit('group:add-node', { data: groupData })
450
+ }
451
+
452
+ /**
453
+ * 从分组中移除某节点
454
+ * @param id
455
+ */
456
+ removeChild(id: string) {
457
+ this.children.delete(id)
458
+ const groupData = this.getData()
459
+ this.graphModel.eventCenter.emit('group:remove-node', { data: groupData })
460
+ }
461
+
462
+ /**
463
+ * 当 groupA 被添加到 groupB 中时,将 groupB 及 groupB 所属的 group zIndex 减 1
464
+ */
465
+ toBack() {
466
+ this.zIndex--
467
+ }
468
+
469
+ /**
470
+ * 重写 Group 节点的 Resize Outline
471
+ */
472
+ getResizeOutlineStyle(): LogicFlow.CommonTheme {
473
+ const style = super.getResizeOutlineStyle()
474
+ style.stroke = 'none'
475
+ return style
476
+ }
477
+
478
+ // TODO: 是否是设置 group 节点没有锚点,而不是设置成透明???
479
+ getAnchorStyle() {
480
+ const style = super.getAnchorStyle()
481
+ style.stroke = 'transparent'
482
+ style.fill = 'transparent'
483
+ if (style.hover) {
484
+ style.hover.fill = 'transparent'
485
+ style.hover.stroke = 'transparent'
486
+ }
487
+ return style
488
+ }
489
+
490
+ /**
491
+ * 设置分组节点 drop 区域的样式
492
+ */
493
+ getAddableOutlineStyle() {
494
+ return {
495
+ stroke: '#feb663',
496
+ strokeWidth: 2,
497
+ strokeDasharray: '4 4',
498
+ fill: 'transparent',
499
+ }
500
+ }
501
+ }
502
+
503
+ export default DynamicGroupNodeModel
@@ -0,0 +1,140 @@
1
+ import { GraphModel, h, RectNode } from '@logicflow/core'
2
+ import { forEach } from 'lodash-es'
3
+ import { DynamicGroupNodeModel } from './model'
4
+ import { handleResize } from '@logicflow/core/es/util/resize'
5
+
6
+ export interface IDynamicGroupNodeProps {
7
+ model: DynamicGroupNodeModel
8
+ graphModel: GraphModel
9
+ }
10
+
11
+ export class DynamicGroupNode<
12
+ P extends IDynamicGroupNodeProps = IDynamicGroupNodeProps,
13
+ > extends RectNode<P> {
14
+ componentDidMount() {
15
+ super.componentDidMount()
16
+
17
+ const { model: curGroup, graphModel } = this.props
18
+ const { eventCenter } = graphModel
19
+
20
+ // 在 group 旋转时,对组内的所有子节点也进行对应的旋转计算
21
+ eventCenter.on('node:rotate', ({ model }) => {
22
+ if (model.id === curGroup.id) {
23
+ forEach(Array.from(curGroup.children), (childId) => {
24
+ const child = graphModel.getNodeModelById(childId)
25
+ if (child) {
26
+ child.rotate = model.rotate
27
+ }
28
+ })
29
+ }
30
+ })
31
+
32
+ // 在 group 缩放时,对组内的所有子节点也进行对应的缩放计算
33
+ eventCenter.on('node:resize', ({ deltaX, deltaY, index, model }) => {
34
+ if (model.id === curGroup.id) {
35
+ forEach(Array.from(curGroup.children), (childId) => {
36
+ const child = graphModel.getNodeModelById(childId)
37
+ if (child) {
38
+ // child.rotate = model.rotate
39
+ handleResize({
40
+ deltaX,
41
+ deltaY,
42
+ index,
43
+ nodeModel: child,
44
+ graphModel,
45
+ cancelCallback: () => {},
46
+ })
47
+ }
48
+ })
49
+ }
50
+ })
51
+ }
52
+
53
+ getResizeControl(): h.JSX.Element | null {
54
+ const { resizable, isCollapsed } = this.props.model
55
+ const showResizeControl = resizable && !isCollapsed
56
+ return showResizeControl ? super.getResizeControl() : null
57
+ }
58
+
59
+ getAppendAreaShape(): h.JSX.Element | null {
60
+ // DONE: 此区域用于初始化 group container, 即元素拖拽进入感应区域
61
+ const { model } = this.props
62
+ const { width, height, x, y, radius, groupAddable } = model
63
+ if (!groupAddable) return null
64
+
65
+ const { strokeWidth = 0 } = model.getNodeStyle()
66
+ const style = model.getAddableOutlineStyle()
67
+
68
+ const newWidth = width + strokeWidth + 8
69
+ const newHeight = height + strokeWidth + 8
70
+ return h('rect', {
71
+ ...style,
72
+ width: newWidth,
73
+ height: newHeight,
74
+ x: x - newWidth / 2,
75
+ y: y - newHeight / 2,
76
+ rx: radius,
77
+ ry: radius,
78
+ })
79
+ }
80
+
81
+ getCollapseIcon(sx: number, sy: number): string {
82
+ return `M ${sx + 3},${sy + 6} ${sx + 11},${sy + 6} M${sx + 7},${sy + 2} ${
83
+ sx + 7
84
+ },${sy + 10}`
85
+ }
86
+
87
+ getExpandIcon(sx: number, sy: number): string {
88
+ return `M ${sx + 3},${sy + 6} ${sx + 11},${sy + 6} `
89
+ }
90
+
91
+ // 获取操作图标(收起或展开)
92
+ getOperateIcon(): h.JSX.Element | null {
93
+ const { model } = this.props
94
+ const { x, y, width, height } = model
95
+ const sx = x - width / 2 + 10
96
+ const sy = y - height / 2 + 10
97
+
98
+ if (!model.collapsible) return null
99
+ const iconPath = model?.isCollapsed
100
+ ? this.getCollapseIcon(sx, sy)
101
+ : this.getExpandIcon(sx, sy)
102
+
103
+ const operateIcon = h('path', {
104
+ fill: 'none',
105
+ stroke: '#818281',
106
+ strokeWidth: 2,
107
+ 'pointer-events': 'none',
108
+ d: iconPath,
109
+ })
110
+
111
+ return h('g', {}, [
112
+ h('rect', {
113
+ height: 12,
114
+ width: 14,
115
+ rx: 2,
116
+ ry: 2,
117
+ strokeWidth: 1,
118
+ fill: '#f4f5f6',
119
+ stroke: '#cecece',
120
+ cursor: 'pointer',
121
+ x: sx,
122
+ y: sy,
123
+ onClick: () => {
124
+ model.toggleCollapse(!model.isCollapsed)
125
+ },
126
+ }),
127
+ operateIcon,
128
+ ])
129
+ }
130
+
131
+ getShape(): h.JSX.Element | null {
132
+ return h('g', {}, [
133
+ this.getAppendAreaShape(),
134
+ super.getShape(),
135
+ this.getOperateIcon(),
136
+ ])
137
+ }
138
+ }
139
+
140
+ export default DynamicGroupNode
@@ -0,0 +1,33 @@
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 isBoundsInGroup(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 bounds
23
+ * @param group
24
+ */
25
+ export function isAllowMoveTo(bounds: BoxBoundsPoint, group: BaseNodeModel) {
26
+ const { minX, minY, maxX, maxY } = bounds
27
+ const { x, y, width, height } = group
28
+
29
+ return {
30
+ x: minX >= x - width / 2 && maxX <= x + width / 2,
31
+ y: minY >= y - height / 2 && maxY <= y + height / 2,
32
+ }
33
+ }