Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(extension):【dynamic-group】isRestrict分组缩放限制+transformWidthContainer控制子元素是否需要同时缩放 #1887

Merged
16 changes: 16 additions & 0 deletions packages/core/src/model/BaseModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,22 @@ export namespace Model {
deltaY: number,
) => boolean | IsAllowMove

/**
* 限制节点resize规则
* model: 移动节点的 model
* deltaX: 中心点移动的 X 轴距离
* deltaY: 中心点移动的 Y 轴距离
* width: 中心点新的width
* height: 中心点新的height
*/
export type NodeResizeRule = (
model: BaseNodeModel,
deltaX: number,
deltaY: number,
width: number,
height: number,
) => boolean

export type AdjustEdgeStartAndEndParams = {
startPoint: LogicFlow.Point
endPoint: LogicFlow.Point
Expand Down
15 changes: 13 additions & 2 deletions packages/core/src/model/GraphModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,10 +81,15 @@ export class GraphModel {

/**
* 节点移动规则判断
* 在节点移动的时候,会出发此数组中的所有规则判断
* 在节点移动的时候,会触发此数组中的所有规则判断
*/

nodeMoveRules: Model.NodeMoveRule[] = []
/**
* 节点resize规则判断
* 在节点resize的时候,会触发此数组中的所有规则判断
*/
nodeResizeRules: Model.NodeResizeRule[] = []

/**
* 获取自定义连线轨迹
*/
Expand Down Expand Up @@ -1230,6 +1235,12 @@ export class GraphModel {
}
}

addNodeResizeRules(fn: Model.NodeResizeRule) {
if (!this.nodeResizeRules.includes(fn)) {
this.nodeResizeRules.push(fn)
}
}

/**
* 设置默认的边类型
* 也就是设置在节点直接有用户手动绘制的连线类型。
Expand Down
32 changes: 32 additions & 0 deletions packages/core/src/model/node/BaseNodeModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ export class BaseNodeModel<P extends PropertiesType = PropertiesType>
targetRules: Model.ConnectRule[] = []
sourceRules: Model.ConnectRule[] = []
moveRules: Model.NodeMoveRule[] = [] // 节点移动之前的hook
resizeRules: Model.NodeResizeRule[] = [] // 节点resize之前的hook
hasSetTargetRules = false // 用来限制rules的重复值
hasSetSourceRules = false; // 用来限制rules的重复值
[propName: string]: any // 支持用户自定义属性
Expand Down Expand Up @@ -281,6 +282,13 @@ export class BaseNodeModel<P extends PropertiesType = PropertiesType>
*/
resize(resizeInfo: ResizeInfo): ResizeNodeData {
const { width, height, deltaX, deltaY } = resizeInfo

const isAllowResize = this.isAllowResizeNode(deltaX, deltaY, width, height)

if (!isAllowResize) {
return this.getData()
}

// 移动节点以及文本内容
this.move(deltaX / 2, deltaY / 2)

Expand Down Expand Up @@ -761,6 +769,30 @@ export class BaseNodeModel<P extends PropertiesType = PropertiesType>
}
}

@action addNodeResizeRules(fn: Model.NodeResizeRule) {
if (!this.resizeRules.includes(fn)) {
this.resizeRules.push(fn)
}
}

/**
* 内部方法
* 是否允许resize节点到新的位置
*/
isAllowResizeNode(
deltaX: number,
deltaY: number,
width: number,
height: number,
): boolean {
const rules = this.resizeRules.concat(this.graphModel.nodeResizeRules)
for (const rule of rules) {
const r = rule(this, deltaX, deltaY, width, height)
if (!r) return false
}
return true
}

@action setSelected(flag = true): void {
this.isSelected = flag
}
Expand Down
8 changes: 7 additions & 1 deletion packages/core/src/util/resize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,12 @@ export const handleResize = ({
const preNodeData = nodeModel.getData()
const curNodeData = nodeModel.resize(nextSize)

// 检测preNodeData和curNodeData是否没变化
if (preNodeData.x === curNodeData.x && preNodeData.y === curNodeData.y) {
// 中心点x和y都没有变化,说明无法resize,阻止下面边的更新以及resize事件的emit
return
}

// 更新边
updateEdgePointByAnchors(nodeModel, graphModel)
// 触发 resize 事件
Expand Down Expand Up @@ -417,7 +423,7 @@ export function calculateWidthAndHeight(
y: oldCenter.y - (startRotatedTouchControlPoint.y - oldCenter.y),
}
// 【touchEndPoint】右下角 + freezePoint左上角 计算出新的中心点
let newCenter = getNewCenter(freezePoint, endRotatedTouchControlPoint)
const newCenter = getNewCenter(freezePoint, endRotatedTouchControlPoint)

// 得到【touchEndPoint】右下角-没有transform的坐标
let endZeroTouchControlPoint: SimplePoint = calculatePointAfterRotateAngle(
Expand Down
69 changes: 69 additions & 0 deletions packages/extension/src/dynamic-group/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -451,6 +451,56 @@ export class DynamicGroup {
})
}

/**
* 检测group:resize后的bounds是否会小于children的bounds
* 限制group进行resize时不能小于内部的占地面积
* @param groupModel
* @param deltaX
* @param deltaY
* @param newWidth
* @param newHeight
*/
checkGroupBoundsWithChildren(
groupModel: DynamicGroupNodeModel,
deltaX: number,
deltaY: number,
newWidth: number,
newHeight: number,
) {
if (groupModel.children) {
const { children, x, y } = groupModel
// 根据deltaX和deltaY计算出当前model的bounds
const newX = x + deltaX / 2
const newY = y + deltaY / 2
const groupMinX = newX - newWidth / 2
const groupMinY = newY - newHeight / 2
const groupMaxX = newX + newWidth / 2
const groupMaxY = newY + newHeight / 2

const childrenArray = Array.from(children)
for (let i = 0; i < childrenArray.length; i++) {
const childId = childrenArray[i]
const child = this.lf.getNodeModelById(childId)
if (!child) {
continue
}
const childBounds = child.getBounds()
const { minX, minY, maxX, maxY } = childBounds
// parent:resize后的bounds不能小于child:bounds,否则阻止其resize
const canResize =
groupMinX <= minX &&
groupMinY <= minY &&
groupMaxX >= maxX &&
groupMaxY >= maxY
if (!canResize) {
return false
}
}
}

return true
}

/**
* Group 插件的初始化方法
* TODO:1. 待讨论,可能之前插件分类是有意义的 components, material, tools
Expand Down Expand Up @@ -494,6 +544,25 @@ export class DynamicGroup {

return true
})

// https://github.com/didi/LogicFlow/issues/1442
// https://github.com/didi/LogicFlow/issues/937
// 添加分组节点resize规则
// isRestrict限制模式下,当前model resize时不能小于children的占地面积
// 并且在isRestrict限制模式下,transformWidthContainer即使设置为true,也无效
graphModel.addNodeResizeRules((model, deltaX, deltaY, width, height) => {
if (model.isGroup && model.isRestrict) {
return this.checkGroupBoundsWithChildren(
model as DynamicGroupNodeModel,
deltaX,
deltaY,
width,
height,
)
}
return true
})

graphModel.dynamicGroup = this

lf.on('node:add,node:drop,node:dnd-add', this.addNodeToGroup)
Expand Down
9 changes: 8 additions & 1 deletion packages/extension/src/dynamic-group/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,11 @@ export type IGroupNodeProperties = {
// expandWidth?: number
// expandHeight?: number

/**
* 缩放或旋转容器时,是否缩放或旋转组内节点
*/
transformWithContainer?: boolean

/**
* 当前分组元素的 zIndex
*/
Expand Down Expand Up @@ -94,7 +99,7 @@ export class DynamicGroupNodeModel extends RectNodeModel<IGroupNodeProperties> {
// 当前分组是否在可添加状态 - 实时状态
@observable groupAddable: boolean = false
// 缩放或旋转容器时,是否缩放或旋转组内节点
@observable transformWidthContainer: boolean = true
@observable transformWithContainer: boolean = false
childrenLastCollapseStateDict: Map<string, boolean> = new Map()

constructor(data: NodeConfig<IGroupNodeProperties>, graphModel: GraphModel) {
Expand All @@ -121,6 +126,7 @@ export class DynamicGroupNodeModel extends RectNodeModel<IGroupNodeProperties> {
isRestrict,
autoResize,
autoToFront,
transformWithContainer,
} = data.properties ?? {}

this.children = children ? new Set(children) : new Set()
Expand All @@ -139,6 +145,7 @@ export class DynamicGroupNodeModel extends RectNodeModel<IGroupNodeProperties> {
this.collapsedHeight = collapsedHeight ?? DEFAULT_GROUP_COLLAPSE_HEIGHT

this.isRestrict = isRestrict ?? false
this.transformWithContainer = transformWithContainer ?? false
this.autoResize = autoResize ?? false
this.collapsible = collapsible ?? true
this.autoToFront = autoToFront ?? false
Expand Down
18 changes: 18 additions & 0 deletions packages/extension/src/dynamic-group/node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,16 @@ export class DynamicGroupNode<

// 在 group 旋转时,对组内的所有子节点也进行对应的旋转计算
eventCenter.on('node:rotate', ({ model }) => {
const { transformWithContainer, isRestrict } = this.props.model
if (!transformWithContainer || isRestrict) {
// isRestrict限制模式下,当前model resize时不能小于占地面积
// 由于parent:resize=>child:resize计算复杂,需要根据child:resize的判定结果来递归判断parent能否resize
// 不符合目前 parent:resize成功后emit事件 -> 触发child:resize 的代码交互模式
// 因此isRestrict限制模式下不支持联动(parent:resize=>child:resize)
// 由于transformWidthContainer是控制rotate+resize,为保持transformWidthContainer本来的含义
// parent:resize=>child:resize不支持,那么parent:rotate=>child:rotate也不支持
return
}
// DONE: 目前操作是对分组内节点以节点中心旋转节点本身,而按照正常逻辑,应该是以分组中心,旋转节点(跟 Label 旋转操作逻辑一致)
if (model.id === curGroup.id) {
const center = { x: curGroup.x, y: curGroup.y }
Expand Down Expand Up @@ -60,6 +70,14 @@ export class DynamicGroupNode<
eventCenter.on(
'node:resize',
({ deltaX, deltaY, index, model, preData }) => {
const { transformWithContainer, isRestrict } = this.props.model
if (!transformWithContainer || isRestrict) {
// isRestrict限制模式下,当前model resize时不能小于占地面积
// 由于parent:resize=>child:resize计算复杂,需要根据child:resize的判定结果来递归判断parent能否resize
// 不符合目前 parent:resize成功后emit事件 -> 触发child:resize 的代码交互模式
// 因此isRestrict限制模式下不支持联动(parent:resize=>child:resize)
return
}
if (model.id === curGroup.id) {
// node:resize是group已经改变width和height后的回调
// 因此这里一定得用preData(没resize改变width之前的值),而不是data/model
Expand Down
14 changes: 12 additions & 2 deletions sites/docs/docs/tutorial/extension/dynamic-group.en.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,8 @@ export type IGroupNodeProperties = {
*/
isCollapsed?: boolean
/**
* Whether to restrict the movement range of child nodes
* Whether to restrict the movement range of child nodes +
* Limit resizing to not exceed children's floor area
* Defaults to false, allowing nodes to be dragged out of the group
*/
isRestrict?: boolean
Expand All @@ -139,6 +140,14 @@ export type IGroupNodeProperties = {
collapsedWidth?: number
collapsedHeight?: number

/**
* When scaling or rotating a container,
* do you scale or rotate the nodes within the group
* Default to false, when scaling or rotating the container,
* the nodes within the group are scaled or rotated by default
*/
transformWithContainer?: boolean

/**
* zIndex of the group element
*/
Expand Down Expand Up @@ -168,6 +177,7 @@ const dynamicGroupNodeConfig = {
height: 250,
radius: 5,
isCollapsed: true,
transformWidthContainer: true,
},
}
```
Expand Down Expand Up @@ -249,4 +259,4 @@ Groups are a special type of node, so it is still possible to use [custom connec

Grouping functionality is not the same as swimlanes; developers need to implement swimlanes based on the grouping functionality. Future LogicFlow support for BPMN will include full BPMN swimlane support. Contributions for implementations are welcome.

:::
:::
10 changes: 9 additions & 1 deletion sites/docs/docs/tutorial/extension/dynamic-group.zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,8 @@ export type IGroupNodeProperties = {
*/
isCollapsed?: boolean
/**
* 子节点是否限制移动范围
* 子节点是否限制移动范围 +
* 限制resize不能超过children占地面积
* 默认为 false,允许拖拽移除分组
*/
isRestrict?: boolean
Expand All @@ -143,6 +144,12 @@ export type IGroupNodeProperties = {
collapsedWidth?: number
collapsedHeight?: number

/**
* 缩放或旋转容器时,是否缩放或旋转组内节点
* 默认为 false,缩放或旋转容器时,默认缩放或旋转组内节点
*/
transformWithContainer?: boolean

/**
* 当前分组元素的 zIndex
*/
Expand Down Expand Up @@ -172,6 +179,7 @@ const dynamicGroupNodeConfig = {
height: 250,
radius: 5,
isCollapsed: true,
transformWithContainer: true,
},
}
```
Expand Down