@logicflow/extension 2.0.9 → 2.0.11

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.
@@ -339,6 +339,72 @@ export class DynamicGroup {
339
339
  }
340
340
  }
341
341
 
342
+ onNodeMove = ({
343
+ deltaX,
344
+ deltaY,
345
+ data,
346
+ }: Omit<CallbackArgs<'node:mousemove'>, 'e' | 'position'>) => {
347
+ const { id, x, y, properties } = data
348
+ if (!properties) {
349
+ return
350
+ }
351
+ const { width, height } = properties
352
+ const groupId = this.nodeGroupMap.get(id)
353
+ if (!groupId) {
354
+ return
355
+ }
356
+ const groupModel = this.lf.getNodeModelById(
357
+ groupId,
358
+ ) as DynamicGroupNodeModel
359
+
360
+ if (!groupModel || !groupModel.isRestrict || !groupModel.autoResize) {
361
+ return
362
+ }
363
+ // 当父节点isRestrict=true & autoResize=true
364
+ // 子节点在父节点中移动时,父节点会自动调整大小
365
+
366
+ // step1: 计算出当前child的bounds
367
+ const newX = x + deltaX / 2
368
+ const newY = y + deltaY / 2
369
+ const minX = newX - width! / 2
370
+ const minY = newY - height! / 2
371
+ const maxX = newX + width! / 2
372
+ const maxY = newY + height! / 2
373
+ // step2:比较当前child.bounds与parent.bounds的差异,比如child.minX<parent.minX,那么parent.minX=child.minX
374
+ let hasChange = false
375
+ const groupBounds = groupModel.getBounds()
376
+ const newGroupBounds = Object.assign({}, groupBounds)
377
+ if (minX < newGroupBounds.minX) {
378
+ newGroupBounds.minX = minX
379
+ hasChange = true
380
+ }
381
+ if (minY < newGroupBounds.minY) {
382
+ newGroupBounds.minY = minY
383
+ hasChange = true
384
+ }
385
+ if (maxX > newGroupBounds.maxX) {
386
+ newGroupBounds.maxX = maxX
387
+ hasChange = true
388
+ }
389
+ if (maxY > newGroupBounds.maxY) {
390
+ newGroupBounds.maxY = maxY
391
+ hasChange = true
392
+ }
393
+ if (!hasChange) {
394
+ return
395
+ }
396
+ // step3: 根据当前parent.bounds去计算出最新的x、y、width、height
397
+ const newGroupX =
398
+ newGroupBounds.minX + (newGroupBounds.maxX - newGroupBounds.minX) / 2
399
+ const newGroupY =
400
+ newGroupBounds.minY + (newGroupBounds.maxY - newGroupBounds.minY) / 2
401
+ const newGroupWidth = newGroupBounds.maxX - newGroupBounds.minX
402
+ const newGroupHeight = newGroupBounds.maxY - newGroupBounds.minY
403
+ groupModel.moveTo(newGroupX, newGroupY)
404
+ groupModel.width = newGroupWidth
405
+ groupModel.height = newGroupHeight
406
+ }
407
+
342
408
  onGraphRendered = ({ data }: CallbackArgs<'graph:rendered'>) => {
343
409
  console.log('data', data)
344
410
  forEach(data.nodes, (node) => {
@@ -451,6 +517,56 @@ export class DynamicGroup {
451
517
  })
452
518
  }
453
519
 
520
+ /**
521
+ * 检测group:resize后的bounds是否会小于children的bounds
522
+ * 限制group进行resize时不能小于内部的占地面积
523
+ * @param groupModel
524
+ * @param deltaX
525
+ * @param deltaY
526
+ * @param newWidth
527
+ * @param newHeight
528
+ */
529
+ checkGroupBoundsWithChildren(
530
+ groupModel: DynamicGroupNodeModel,
531
+ deltaX: number,
532
+ deltaY: number,
533
+ newWidth: number,
534
+ newHeight: number,
535
+ ) {
536
+ if (groupModel.children) {
537
+ const { children, x, y } = groupModel
538
+ // 根据deltaX和deltaY计算出当前model的bounds
539
+ const newX = x + deltaX / 2
540
+ const newY = y + deltaY / 2
541
+ const groupMinX = newX - newWidth / 2
542
+ const groupMinY = newY - newHeight / 2
543
+ const groupMaxX = newX + newWidth / 2
544
+ const groupMaxY = newY + newHeight / 2
545
+
546
+ const childrenArray = Array.from(children)
547
+ for (let i = 0; i < childrenArray.length; i++) {
548
+ const childId = childrenArray[i]
549
+ const child = this.lf.getNodeModelById(childId)
550
+ if (!child) {
551
+ continue
552
+ }
553
+ const childBounds = child.getBounds()
554
+ const { minX, minY, maxX, maxY } = childBounds
555
+ // parent:resize后的bounds不能小于child:bounds,否则阻止其resize
556
+ const canResize =
557
+ groupMinX <= minX &&
558
+ groupMinY <= minY &&
559
+ groupMaxX >= maxX &&
560
+ groupMaxY >= maxY
561
+ if (!canResize) {
562
+ return false
563
+ }
564
+ }
565
+ }
566
+
567
+ return true
568
+ }
569
+
454
570
  /**
455
571
  * Group 插件的初始化方法
456
572
  * TODO:1. 待讨论,可能之前插件分类是有意义的 components, material, tools
@@ -487,19 +603,45 @@ export class DynamicGroup {
487
603
  ) as DynamicGroupNodeModel
488
604
 
489
605
  if (groupModel && groupModel.isRestrict) {
490
- // 如果移动的节点存在于某个分组中,且这个分组禁止子节点移出去
491
- const groupBounds = groupModel.getBounds()
492
- return isAllowMoveTo(groupBounds, model, deltaX, deltaY)
606
+ if (groupModel.autoResize) {
607
+ // 子节点在父节点中移动时,父节点会自动调整大小
608
+ // 在node:mousemove中进行父节点的调整
609
+ return true
610
+ } else {
611
+ // 如果移动的节点存在于某个分组中,且这个分组禁止子节点移出去
612
+ const groupBounds = groupModel.getBounds()
613
+ return isAllowMoveTo(groupBounds, model, deltaX, deltaY)
614
+ }
493
615
  }
494
616
 
495
617
  return true
496
618
  })
619
+
620
+ // https://github.com/didi/LogicFlow/issues/1442
621
+ // https://github.com/didi/LogicFlow/issues/937
622
+ // 添加分组节点resize规则
623
+ // isRestrict限制模式下,当前model resize时不能小于children的占地面积
624
+ // 并且在isRestrict限制模式下,transformWidthContainer即使设置为true,也无效
625
+ graphModel.addNodeResizeRules((model, deltaX, deltaY, width, height) => {
626
+ if (model.isGroup && model.isRestrict) {
627
+ return this.checkGroupBoundsWithChildren(
628
+ model as DynamicGroupNodeModel,
629
+ deltaX,
630
+ deltaY,
631
+ width,
632
+ height,
633
+ )
634
+ }
635
+ return true
636
+ })
637
+
497
638
  graphModel.dynamicGroup = this
498
639
 
499
640
  lf.on('node:add,node:drop,node:dnd-add', this.addNodeToGroup)
500
641
  lf.on('node:delete', this.removeNodeFromGroup)
501
642
  lf.on('node:drag,node:dnd-drag', this.setActiveGroup)
502
643
  lf.on('node:click', this.onNodeSelect)
644
+ lf.on('node:mousemove', this.onNodeMove)
503
645
  lf.on('graph:rendered', this.onGraphRendered)
504
646
 
505
647
  lf.on('graph:updated', ({ data }) => console.log('data', data))
@@ -568,6 +710,7 @@ export class DynamicGroup {
568
710
  this.lf.off('node:delete', this.removeNodeFromGroup)
569
711
  this.lf.off('node:drag,node:dnd-drag', this.setActiveGroup)
570
712
  this.lf.off('node:click', this.onNodeSelect)
713
+ this.lf.off('node:mousemove', this.onNodeMove)
571
714
  this.lf.off('graph:rendered', this.onGraphRendered)
572
715
 
573
716
  // 还原 lf.addElements 方法?
@@ -49,6 +49,11 @@ export type IGroupNodeProperties = {
49
49
  // expandWidth?: number
50
50
  // expandHeight?: number
51
51
 
52
+ /**
53
+ * 缩放或旋转容器时,是否缩放或旋转组内节点
54
+ */
55
+ transformWithContainer?: boolean
56
+
52
57
  /**
53
58
  * 当前分组元素的 zIndex
54
59
  */
@@ -79,6 +84,8 @@ export class DynamicGroupNodeModel extends RectNodeModel<IGroupNodeProperties> {
79
84
  children!: Set<string>
80
85
  // 是否限制组内节点的移动范围。默认不限制 TODO: 完善该功能
81
86
  isRestrict: boolean = false
87
+ // isRestrict 模式启用时,如果同时设置 autoResize 为 true,那么子节点在父节点中移动时,父节点会自动调整大小
88
+ autoResize: boolean = false
82
89
  // 分组节点是否可以折叠
83
90
  collapsible: boolean = true
84
91
 
@@ -94,7 +101,7 @@ export class DynamicGroupNodeModel extends RectNodeModel<IGroupNodeProperties> {
94
101
  // 当前分组是否在可添加状态 - 实时状态
95
102
  @observable groupAddable: boolean = false
96
103
  // 缩放或旋转容器时,是否缩放或旋转组内节点
97
- @observable transformWidthContainer: boolean = true
104
+ @observable transformWithContainer: boolean = false
98
105
  childrenLastCollapseStateDict: Map<string, boolean> = new Map()
99
106
 
100
107
  constructor(data: NodeConfig<IGroupNodeProperties>, graphModel: GraphModel) {
@@ -121,6 +128,7 @@ export class DynamicGroupNodeModel extends RectNodeModel<IGroupNodeProperties> {
121
128
  isRestrict,
122
129
  autoResize,
123
130
  autoToFront,
131
+ transformWithContainer,
124
132
  } = data.properties ?? {}
125
133
 
126
134
  this.children = children ? new Set(children) : new Set()
@@ -139,6 +147,7 @@ export class DynamicGroupNodeModel extends RectNodeModel<IGroupNodeProperties> {
139
147
  this.collapsedHeight = collapsedHeight ?? DEFAULT_GROUP_COLLAPSE_HEIGHT
140
148
 
141
149
  this.isRestrict = isRestrict ?? false
150
+ this.transformWithContainer = transformWithContainer ?? false
142
151
  this.autoResize = autoResize ?? false
143
152
  this.collapsible = collapsible ?? true
144
153
  this.autoToFront = autoToFront ?? false
@@ -150,11 +159,6 @@ export class DynamicGroupNodeModel extends RectNodeModel<IGroupNodeProperties> {
150
159
 
151
160
  setAttributes() {
152
161
  super.setAttributes()
153
-
154
- // 初始化时,如果 this.isCollapsed 为 true,则主动触发一次折叠操作
155
- if (this.isCollapsed) {
156
- this.toggleCollapse(true)
157
- }
158
162
  }
159
163
 
160
164
  getData(): NodeData {
@@ -195,8 +199,15 @@ export class DynamicGroupNodeModel extends RectNodeModel<IGroupNodeProperties> {
195
199
  isCollapsed,
196
200
  } = this
197
201
  if (isCollapsed) {
202
+ // 如果当前是折叠模式
203
+ // 存入history的时候,将坐标恢复到未折叠前的坐标数据
204
+ // 因为拿出history数据的时候,会触发collapse()进行坐标的折叠计算
198
205
  data.x = x + expandWidth / 2 - collapsedWidth / 2
199
206
  data.y = y + expandHeight / 2 - collapsedHeight / 2
207
+ if (data.text) {
208
+ data.text.x = data.text.x + expandWidth / 2 - collapsedWidth / 2
209
+ data.text.y = data.text.y + expandHeight / 2 - collapsedHeight / 2
210
+ }
200
211
  }
201
212
  return data
202
213
  }
@@ -429,8 +440,8 @@ export class DynamicGroupNodeModel extends RectNodeModel<IGroupNodeProperties> {
429
440
  * TODO: 如何重写该方法呢?
430
441
  * @param _nodeData
431
442
  */
443
+ // eslint-disable-next-line
432
444
  isAllowAppendIn(_nodeData: NodeData) {
433
- console.info('_nodeData', _nodeData)
434
445
  // TODO: 此处使用 this.properties.groupAddable 还是 this.groupAddable
435
446
  // this.groupAddable 是否存在更新不及时的问题
436
447
  return true
@@ -3,6 +3,7 @@ import LogicFlow, {
3
3
  h,
4
4
  RectNode,
5
5
  handleResize,
6
+ CallbackArgs,
6
7
  } from '@logicflow/core'
7
8
  import { forEach } from 'lodash-es'
8
9
  import { DynamicGroupNodeModel } from './model'
@@ -18,83 +19,151 @@ export interface IDynamicGroupNodeProps {
18
19
  export class DynamicGroupNode<
19
20
  P extends IDynamicGroupNodeProps = IDynamicGroupNodeProps,
20
21
  > extends RectNode<P> {
21
- componentDidMount() {
22
- super.componentDidMount()
22
+ childrenPositionMap: Map<string, Position> = new Map()
23
23
 
24
+ onNodeRotate = ({
25
+ model,
26
+ }: Omit<CallbackArgs<'node:rotate'>, 'e' | 'position'>) => {
24
27
  const { model: curGroup, graphModel } = this.props
25
- const { eventCenter } = graphModel
26
-
27
- const childrenPositionMap: Map<string, Position> = new Map()
28
+ const { transformWithContainer, isRestrict } = curGroup
29
+ const childrenPositionMap = this.childrenPositionMap
30
+ if (!transformWithContainer || isRestrict) {
31
+ // isRestrict限制模式下,当前model resize时不能小于占地面积
32
+ // 由于parent:resize=>child:resize计算复杂,需要根据child:resize的判定结果来递归判断parent能否resize
33
+ // 不符合目前 parent:resize成功后emit事件 -> 触发child:resize 的代码交互模式
34
+ // 因此isRestrict限制模式下不支持联动(parent:resize=>child:resize)
35
+ // 由于transformWidthContainer是控制rotate+resize,为保持transformWidthContainer本来的含义
36
+ // parent:resize=>child:resize不支持,那么parent:rotate=>child:rotate也不支持
37
+ return
38
+ }
39
+ // DONE: 目前操作是对分组内节点以节点中心旋转节点本身,而按照正常逻辑,应该是以分组中心,旋转节点(跟 Label 旋转操作逻辑一致)
40
+ if (model.id === curGroup.id) {
41
+ const center = { x: curGroup.x, y: curGroup.y }
42
+ forEach(Array.from(curGroup.children), (childId) => {
43
+ const child = graphModel.getNodeModelById(childId)
28
44
 
29
- // group 旋转时,对组内的所有子节点也进行对应的旋转计算
30
- eventCenter.on('node:rotate', ({ model }) => {
31
- // DONE: 目前操作是对分组内节点以节点中心旋转节点本身,而按照正常逻辑,应该是以分组中心,旋转节点(跟 Label 旋转操作逻辑一致)
32
- if (model.id === curGroup.id) {
33
- const center = { x: curGroup.x, y: curGroup.y }
34
- forEach(Array.from(curGroup.children), (childId) => {
35
- const child = graphModel.getNodeModelById(childId)
36
-
37
- if (child) {
38
- let point: Position = { x: child.x, y: child.y }
39
- if (childrenPositionMap.has(child.id)) {
40
- point = childrenPositionMap.get(child.id)!
41
- } else {
42
- childrenPositionMap.set(child.id, point)
43
- }
44
-
45
- // 弧度转角度
46
- let theta = model.rotate * (180 / Math.PI)
47
- if (theta < 0) theta += 360
48
- const radian = theta * (Math.PI / 180)
49
-
50
- const newPoint = rotatePointAroundCenter(point, center, radian)
51
-
52
- child.moveTo(newPoint.x, newPoint.y)
53
- child.rotate = model.rotate
45
+ if (child) {
46
+ let point: Position = { x: child.x, y: child.y }
47
+ if (childrenPositionMap.has(child.id)) {
48
+ point = childrenPositionMap.get(child.id)!
49
+ } else {
50
+ childrenPositionMap.set(child.id, point)
54
51
  }
55
- })
56
- }
57
- })
58
52
 
59
- // 在 group 缩放时,对组内的所有子节点也进行对应的缩放计算
60
- eventCenter.on(
61
- 'node:resize',
62
- ({ deltaX, deltaY, index, model, preData }) => {
63
- if (model.id === curGroup.id) {
64
- // node:resize是group已经改变width和height后的回调
65
- // 因此这里一定得用preData(没resize改变width之前的值),而不是data/model
66
- const { properties } = preData
67
- const { width: groupWidth, height: groupHeight } = properties || {}
68
- forEach(Array.from(curGroup.children), (childId) => {
69
- const child = graphModel.getNodeModelById(childId)
70
- if (child) {
71
- // 根据比例去控制缩放dx和dy
72
- const childDx = (child.width / groupWidth!) * deltaX
73
- const childDy = (child.height / groupHeight!) * deltaY
74
-
75
- // child.rotate = model.rotate
76
- handleResize({
77
- deltaX: childDx,
78
- deltaY: childDy,
79
- index,
80
- nodeModel: child,
81
- graphModel,
82
- cancelCallback: () => {},
83
- })
84
- }
53
+ // 弧度转角度
54
+ let theta = model.rotate * (180 / Math.PI)
55
+ if (theta < 0) theta += 360
56
+ const radian = theta * (Math.PI / 180)
57
+
58
+ const newPoint = rotatePointAroundCenter(point, center, radian)
59
+
60
+ child.moveTo(newPoint.x, newPoint.y)
61
+ child.rotate = model.rotate
62
+ }
63
+ })
64
+ }
65
+ }
66
+
67
+ onNodeResize = ({
68
+ deltaX,
69
+ deltaY,
70
+ index,
71
+ model,
72
+ preData,
73
+ }: Omit<CallbackArgs<'node:resize'>, 'e' | 'position'>) => {
74
+ const { model: curGroup, graphModel } = this.props
75
+ const { transformWithContainer, isRestrict } = curGroup
76
+ if (!transformWithContainer || isRestrict) {
77
+ // isRestrict限制模式下,当前model resize时不能小于占地面积
78
+ // 由于parent:resize=>child:resize计算复杂,需要根据child:resize的判定结果来递归判断parent能否resize
79
+ // 不符合目前 parent:resize成功后emit事件 -> 触发child:resize 的代码交互模式
80
+ // 因此isRestrict限制模式下不支持联动(parent:resize=>child:resize)
81
+ return
82
+ }
83
+ if (model.id === curGroup.id) {
84
+ // node:resize是group已经改变width和height后的回调
85
+ // 因此这里一定得用preData(没resize改变width之前的值),而不是data/model
86
+ const { properties } = preData
87
+ const { width: groupWidth, height: groupHeight } = properties || {}
88
+ forEach(Array.from(curGroup.children), (childId) => {
89
+ const child = graphModel.getNodeModelById(childId)
90
+ if (child) {
91
+ // 根据比例去控制缩放dx和dy
92
+ const childDx = (child.width / groupWidth!) * deltaX
93
+ const childDy = (child.height / groupHeight!) * deltaY
94
+
95
+ // child.rotate = model.rotate
96
+ handleResize({
97
+ deltaX: childDx,
98
+ deltaY: childDy,
99
+ index,
100
+ nodeModel: child,
101
+ graphModel,
102
+ cancelCallback: () => {},
85
103
  })
86
104
  }
87
- },
88
- )
105
+ })
106
+ }
107
+ }
89
108
 
109
+ onNodeMouseMove = ({
110
+ deltaX,
111
+ deltaY,
112
+ data,
113
+ }: Omit<CallbackArgs<'node:mousemove'>, 'e' | 'position'>) => {
114
+ const { model: curGroup, graphModel } = this.props
115
+ const { transformModel } = graphModel
116
+ const { SCALE_X, SCALE_Y } = transformModel
117
+ if (data.id === curGroup.id) {
118
+ const nodeIds = this.getNodesInGroup(curGroup, graphModel)
119
+ // https://github.com/didi/LogicFlow/issues/1914
120
+ // 当调用lf.fitView()时,会改变整体的SCALE_X和SCALE_Y
121
+ // 由于group的mousemove是在drag.ts的this.onDragging()处理的,在onDragging()里面进行SCALE的处理
122
+ // 而"node:mousemove"emit出来跟onDragging()是同时的,也就是emit出来的数据是没有经过SCALE处理的坐标
123
+ // 因此这里需要增加SCALE的处理
124
+ graphModel.moveNodes(nodeIds, deltaX / SCALE_X, deltaY / SCALE_Y, true)
125
+ }
126
+ }
127
+
128
+ graphRendered = () => {
129
+ const { model } = this.props
130
+ // 初始化时,如果 this.isCollapsed 为 true,则主动触发一次折叠操作
131
+ if (model.isCollapsed) {
132
+ // https://github.com/didi/LogicFlow/issues/1918
133
+ // 当lf.render({nodes:[{分组节点}, {普通节点}]})时,由于是顺序遍历
134
+ // 会先触发分组Group节点的new Model => toggleCollapse()
135
+ // => 此时在graphModel.elementsModelMap找不到它的children,因为还没初始化,因此无法正确折叠子元素
136
+ // --------------------
137
+ // 当lf.render({nodes:[{普通节点}, {分组节点}]})时,
138
+ // 会先触发普通节点的new Model => graphModel.elementsModelMap.set(id, new Model())
139
+ // 然后再触发分组Group节点的new Model => toggleCollapse() =>
140
+ // 此时在graphModel.elementsModelMap能找到它的children了,因此可以正确折叠子元素
141
+ // --------------------
142
+ // 因此将整个初始化判断是否【主动触发一次折叠操作】放在"graph:rendered"全部渲染完成后再执行
143
+ model.toggleCollapse(true)
144
+ }
145
+ }
146
+
147
+ componentDidMount() {
148
+ super.componentDidMount()
149
+ const { eventCenter } = this.props.graphModel
150
+ // 在 group 旋转时,对组内的所有子节点也进行对应的旋转计算
151
+ eventCenter.on('node:rotate', this.onNodeRotate)
152
+ // 在 group 缩放时,对组内的所有子节点也进行对应的缩放计算
153
+ eventCenter.on('node:resize', this.onNodeResize)
90
154
  // 在 group 移动时,对组内的所有子节点也进行对应的移动计算
91
- eventCenter.on('node:mousemove', ({ deltaX, deltaY, data }) => {
92
- if (data.id === curGroup.id) {
93
- const { model: curGroup, graphModel } = this.props
94
- const nodeIds = this.getNodesInGroup(curGroup, graphModel)
95
- graphModel.moveNodes(nodeIds, deltaX, deltaY, true)
96
- }
97
- })
155
+ eventCenter.on('node:mousemove', this.onNodeMouseMove)
156
+ // 全部渲染完成后,判断是否【主动触发一次折叠操作】
157
+ eventCenter.on('graph:rendered', this.graphRendered)
158
+ }
159
+
160
+ componentWillUnmount() {
161
+ super.componentWillUnmount()
162
+ const { eventCenter } = this.props.graphModel
163
+ eventCenter.off('node:rotate', this.onNodeRotate)
164
+ eventCenter.off('node:resize', this.onNodeResize)
165
+ eventCenter.off('node:mousemove', this.onNodeMouseMove)
166
+ eventCenter.off('graph:rendered', this.graphRendered)
98
167
  }
99
168
 
100
169
  /**