@logicflow/extension
Version:
LogicFlow Extensions
776 lines (693 loc) • 26.5 kB
text/typescript
import LogicFlow, {
BaseEdgeModel,
BaseNodeModel,
EventType,
CallbackArgs,
Model,
transformNodeData,
transformEdgeData,
} from '@logicflow/core'
import { assign, cloneDeep, filter, forEach, has, map, sortBy } from 'lodash-es'
import { DynamicGroupNode } from './node'
import { DynamicGroupNodeModel } from './model'
import { isAllowMoveTo, isBoundsInGroup } from './utils'
import GraphConfigData = LogicFlow.GraphConfigData
import GraphElements = LogicFlow.GraphElements
import EdgeConfig = LogicFlow.EdgeConfig
import EdgeData = LogicFlow.EdgeData
import NodeData = LogicFlow.NodeData
import BoxBoundsPoint = Model.BoxBoundsPoint
import ElementsInfoInGroup = DynamicGroup.ElementsInfoInGroup
export * from './node'
export * from './model'
export const dynamicGroup = {
type: 'dynamic-group',
view: DynamicGroupNode,
model: DynamicGroupNodeModel,
}
const DEFAULT_TOP_Z_INDEX = -1000
const DEFAULT_BOTTOM_Z_INDEX = -10000
export class DynamicGroup {
static pluginName = 'dynamicGroup'
private lf: LogicFlow
topGroupZIndex: number = DEFAULT_BOTTOM_Z_INDEX
// 激活态的 group 节点
activeGroup?: DynamicGroupNodeModel
// 存储节点与 group 的映射关系
nodeGroupMap: Map<string, string> = new Map()
constructor({ lf, options }: LogicFlow.IExtensionProps) {
lf.register(dynamicGroup)
this.lf = lf
assign(this, options)
// 初始化插件,从监听事件开始及设置规则开始
this.init()
}
/**
* 获取节点所属的分组
* @param nodeId
*/
getGroupByNodeId(nodeId: string) {
const groupId = this.nodeGroupMap.get(nodeId)
if (groupId) {
return this.lf.getNodeModelById(groupId)
}
}
/**
* 获取自定位置及其所属分组
* 当分组重合时,优先返回最上层的分组
* @param bounds
* @param nodeData
*/
getGroupByBounds(
bounds: BoxBoundsPoint,
nodeData: NodeData,
): DynamicGroupNodeModel | undefined {
const { nodes } = this.lf.graphModel
const groups = filter(nodes, (node) => {
return (
!!node.isGroup &&
isBoundsInGroup(bounds, node) &&
node.id !== nodeData.id
)
})
const count = groups.length
if (count <= 1) {
return groups[0] as DynamicGroupNodeModel
} else {
let topZIndexGroup = groups[count - 1]
for (let i = count - 2; i >= 0; i--) {
if (groups[i].zIndex > topZIndexGroup.zIndex) {
topZIndexGroup = groups[i]
}
}
return topZIndexGroup as DynamicGroupNodeModel
}
}
/**
* 提高元素的层级,如果是 group,同时提高其子元素的层级
* @param model
*/
sendNodeToFront(model?: BaseNodeModel) {
if (!model || !model.isGroup) return
this.topGroupZIndex++
model.setZIndex(this.topGroupZIndex)
if (model.children) {
const { children } = model as DynamicGroupNodeModel
forEach(Array.from(children), (nodeId) => {
const node = this.lf.getNodeModelById(nodeId)
this.sendNodeToFront(node)
})
}
}
/**
* 递归计算某个分组内最高的 zIndex 值
* TODO: 这块儿有点疑问❓如果 node 不是 group,这块儿返回的 maxZIndex 是最小值,但 node 的 zIndex 不一定是这个值
* @param node
*/
getMaxZIndex(node: BaseNodeModel) {
let maxZIndex = DEFAULT_BOTTOM_Z_INDEX
if (node.isGroup) {
maxZIndex = Math.max(maxZIndex, node.zIndex)
}
if (node.children) {
const { children } = node as DynamicGroupNodeModel
forEach(Array.from(children), (childId) => {
const child = this.lf.getNodeModelById(childId)
if (child?.isGroup) {
const childMaxZIndex = this.getMaxZIndex(child)
maxZIndex = Math.max(maxZIndex, childMaxZIndex)
}
})
}
return maxZIndex
}
/**
* 校准当前 topGroupZIndex 的值
* @param nodes
*/
calibrateTopGroupZIndex(nodes: NodeData[]) {
// 初始化时 or 增加新节点时,找出所有 nodes 的最大 zIndex
let maxZIndex = DEFAULT_BOTTOM_Z_INDEX
forEach(nodes, (node) => {
const nodeModel = this.lf.getNodeModelById(node.id)
if (nodeModel) {
const currNodeMaxZIndex = this.getMaxZIndex(nodeModel)
if (currNodeMaxZIndex > maxZIndex) {
maxZIndex = currNodeMaxZIndex
}
}
})
// TODO: 不是很理解这块儿的代码逻辑,需要整理一下
if (this.topGroupZIndex >= maxZIndex) {
// 一般是初始化时/增加新节点时发生,因为外部强行设置了一个很大的 zIndex
// 删除节点不会影响目前最高 zIndex 的赋值
return
}
// 新增 nodes 中如果存在 zIndex 比 this.topGroupZIndex 大
// 说明 this.topGroupZIndex 已经失去意义,代表不了目前最高 zIndex 的 group,需要重新校准
// https://github.com/didi/LogicFlow/issues/1535
// 当外部直接设置多个 BaseNode.zIndex = 1 时
// 当点击某一个 node 时,由于这个 this.topGroupZIndex 是从 -10000 开始计算的,
// this.topGroupZIndex + 1 也就是-9999,这就造成当前点击的 node 的 zIndex 远远
// 比其它 node 的 zIndex 小,因此造成 zIndex 错乱的问题
// TODO: 这儿的 nodes 能否直接用传参进来的 nodes 呢???
const allNodes = this.lf.graphModel.nodes
const allGroups = filter(allNodes, (node) => !!node.isGroup)
let max = this.topGroupZIndex
forEach(allGroups, (group) => {
if (group.zIndex > max) max = group.zIndex
})
this.topGroupZIndex = max
}
onSelectionDrop = () => {
const { nodes: selectedNodes } = this.lf.graphModel.getSelectElements()
selectedNodes.forEach((node) => {
this.addNodeToGroup(node)
})
}
onNodeAddOrDrop = ({ data: node }: CallbackArgs<'node:add'>) => {
this.addNodeToGroup(node)
}
addNodeToGroup = (node: LogicFlow.NodeData) => {
// 1. 如果该节点之前已经在 group 中了,则将其从之前的 group 移除
const preGroupId = this.nodeGroupMap.get(node.id)
if (preGroupId) {
const group = this.lf.getNodeModelById(
preGroupId,
) as DynamicGroupNodeModel
group.removeChild(node.id)
this.nodeGroupMap.delete(node.id)
group.setAllowAppendChild(false)
}
// 2. 然后再判断这个节点是否在某个 group 范围内,如果是,则将其添加到对应的 group 中
const nodeModel = this.lf.getNodeModelById(node.id)
const bounds = nodeModel?.getBounds()
if (nodeModel && bounds) {
// TODO: 确认下面的注释内容
// https://github.com/didi/LogicFlow/issues/1261
// 当使用 SelectionSelect 框选后触发 lf.addNode(Group)
// 会触发 appendNodeToGroup() 的执行
// 由于 this.getGroup() 会判断 node.id !== nodeData.id
// 因此当 addNode 是 Group 类型时,this.getGroup() 会一直返回空
// 导致了下面这段代码无法执行,也就是无法将当前添加的 Group 添加到 this.nodeGroupMap 中
// 这导致了折叠分组时触发的 foldEdge() 无法正确通过 getNodeGroup() 拿到正确的 groupId
// 从而导致折叠分组时一直都会创建一个虚拟边
// 而初始化分组时由于正确设置了nodeGroupMap的数据,因此不会产生虚拟边的错误情况
if (nodeModel.isGroup) {
const group = nodeModel as DynamicGroupNodeModel
forEach(Array.from(group.children), (childId) => {
this.nodeGroupMap.set(childId, node.id)
})
// 新增 node 时进行 this.topGroupZIndex 的校准更新
this.calibrateTopGroupZIndex([node])
this.onNodeSelect({
data: node,
isSelected: false,
isMultiple: false,
})
}
// TODO: 找到这个范围内的 groupModel, 并加 node 添加到该 group
const group = this.getGroupByBounds(bounds, node)
if (group) {
const isAllowAppendIn = group.isAllowAppendIn(node)
if (isAllowAppendIn) {
group.addChild(node.id)
// 建立节点与 group 的映射关系放在了 group.addChild 触发的事件中,与直接调用 addChild 的行为保持一致
group.setAllowAppendChild(false)
} else {
// 抛出不允许插入的事件
this.lf.emit('group:not-allowed', {
group: group.getData(),
node,
})
}
}
}
}
onGroupAddNode = ({
data: groupData,
childId,
}: CallbackArgs<'group:add-node'>) => {
this.nodeGroupMap.set(childId, groupData.id)
}
removeNodeFromGroup = ({
data: node,
model,
}: CallbackArgs<'node:delete'>) => {
if (model.isGroup && node.children) {
forEach(
Array.from((node as DynamicGroupNodeModel).children),
(childId) => {
this.nodeGroupMap.delete(childId)
this.lf.deleteNode(childId)
},
)
}
const groupId = this.nodeGroupMap.get(node.id)
if (groupId) {
const group = this.lf.getNodeModelById(groupId)
group && (group as DynamicGroupNodeModel).removeChild(node.id)
this.nodeGroupMap.delete(node.id)
}
}
onSelectionDrag = () => {
const { nodes: selectedNodes } = this.lf.graphModel.getSelectElements()
selectedNodes.forEach((node) => {
this.setActiveGroup(node)
})
}
onNodeDrag = ({ data: node }: CallbackArgs<'node:drag'>) => {
this.setActiveGroup(node)
}
setActiveGroup = (node: LogicFlow.NodeData) => {
const nodeModel = this.lf.getNodeModelById(node.id)
const bounds = nodeModel?.getBounds()
if (nodeModel && bounds) {
const targetGroup = this.getGroupByBounds(bounds, node)
if (this.activeGroup) {
this.activeGroup.setAllowAppendChild(false)
}
if (!targetGroup || (nodeModel.isGroup && targetGroup.id === node.id)) {
return
}
const isAllowAppendIn = targetGroup.isAllowAppendIn(node)
if (!isAllowAppendIn) return
this.activeGroup = targetGroup
this.activeGroup.setAllowAppendChild(true)
}
}
/**
* 1. 分组节点默认在普通节点下面
* 2. 分组节点被选中后,会将分组节点以及其内部的其它分组节点放到其余分组节点的上面
* 3. 分组节点取消选中后,不会将分组节点重置为原来的高度
* 4. 由于 LogicFlow 核心目标是支持用户手动绘制流程图,所以暂时不支持一张流程图超过 1000 个分组节点的情况
* @param node
* @param isMultiple
* @param isSelected
*/
onNodeSelect = ({
data: node,
isMultiple,
isSelected,
}: Omit<CallbackArgs<'node:click'>, 'e' | 'position'>) => {
const nodeModel = this.lf.getNodeModelById(node.id)
this.sendNodeToFront(nodeModel)
// 重置所有 group 的 zIndex,防止 group 节点 zIndex 增长为正数(目的是保持 group 节点在最底层)
if (this.topGroupZIndex > DEFAULT_TOP_Z_INDEX) {
const { nodes } = this.lf.graphModel
this.topGroupZIndex = DEFAULT_BOTTOM_Z_INDEX
const groups = sortBy(
filter(nodes, (node) => !!node.isGroup),
'zIndex',
)
let preZIndex = 0
forEach(groups, (group) => {
if (group.zIndex !== preZIndex) {
this.topGroupZIndex++
preZIndex = group.zIndex
}
group.setZIndex(this.topGroupZIndex)
})
}
// FIX #1004
// 如果节点被多选,
// 这个节点是分组,则将分组的所有子节点取消选中
// 这个节点是分组的子节点,且其所属分组节点已选,则取消选中
if (isMultiple && isSelected) {
if (nodeModel?.isGroup) {
const { children } = nodeModel as DynamicGroupNodeModel
forEach(Array.from(children), (childId) => {
const childModel = this.lf.getNodeModelById(childId)
childModel?.setSelected(false)
})
} else {
const groupId = this.nodeGroupMap.get(node.id)
if (groupId) {
const graphModel = this.lf.getNodeModelById(groupId)
graphModel?.isSelected && nodeModel?.setSelected(false)
}
}
}
}
onNodeMove = ({
deltaX,
deltaY,
data,
}: Omit<CallbackArgs<'node:mousemove'>, 'e' | 'position'>) => {
const { id, x, y, properties } = data
if (!properties) {
return
}
const { width, height } = properties
const groupId = this.nodeGroupMap.get(id)
if (!groupId) {
return
}
const groupModel = this.lf.getNodeModelById(
groupId,
) as DynamicGroupNodeModel
if (!groupModel || !groupModel.isRestrict || !groupModel.autoResize) {
return
}
// 当父节点isRestrict=true & autoResize=true
// 子节点在父节点中移动时,父节点会自动调整大小
// step1: 计算出当前child的bounds
const newX = x + deltaX / 2
const newY = y + deltaY / 2
const minX = newX - width! / 2
const minY = newY - height! / 2
const maxX = newX + width! / 2
const maxY = newY + height! / 2
// step2:比较当前child.bounds与parent.bounds的差异,比如child.minX<parent.minX,那么parent.minX=child.minX
let hasChange = false
const groupBounds = groupModel.getBounds()
const newGroupBounds = Object.assign({}, groupBounds)
if (minX < newGroupBounds.minX) {
newGroupBounds.minX = minX
hasChange = true
}
if (minY < newGroupBounds.minY) {
newGroupBounds.minY = minY
hasChange = true
}
if (maxX > newGroupBounds.maxX) {
newGroupBounds.maxX = maxX
hasChange = true
}
if (maxY > newGroupBounds.maxY) {
newGroupBounds.maxY = maxY
hasChange = true
}
if (!hasChange) {
return
}
// step3: 根据当前parent.bounds去计算出最新的x、y、width、height
const newGroupX =
newGroupBounds.minX + (newGroupBounds.maxX - newGroupBounds.minX) / 2
const newGroupY =
newGroupBounds.minY + (newGroupBounds.maxY - newGroupBounds.minY) / 2
const newGroupWidth = newGroupBounds.maxX - newGroupBounds.minX
const newGroupHeight = newGroupBounds.maxY - newGroupBounds.minY
groupModel.moveTo(newGroupX, newGroupY)
groupModel.width = newGroupWidth
groupModel.height = newGroupHeight
}
onGraphRendered = ({ data }: CallbackArgs<'graph:rendered'>) => {
forEach(data.nodes, (node) => {
if (node.children) {
forEach(node.children, (childId) => {
this.nodeGroupMap.set(childId, node.id)
})
}
})
// TODO: 确认一下下面方法的必要性及合理性
// 初始化 nodes 时进行 this.topGroupZIndex 的校准更新
this.calibrateTopGroupZIndex(data.nodes)
}
removeChildrenInGroupNodeData<
T extends LogicFlow.NodeData | LogicFlow.NodeConfig,
>(nodeData: T) {
const newNodeData = cloneDeep(nodeData)
delete newNodeData.children
if (newNodeData.properties?.children) {
delete newNodeData.properties.children
}
return newNodeData
}
/**
* 创建一个 Group 类型节点内部所有子节点的副本
* 并且在遍历所有 nodes 的过程中,顺便拿到所有 edges (只在 Group 范围的 edges)
*/
initGroupChildNodes(
nodeIdMap: Record<string, string>,
children: Set<string>,
curGroup: DynamicGroupNodeModel,
distance: number,
): ElementsInfoInGroup {
// Group 中所有子节点
const allChildNodes: BaseNodeModel[] = []
// 属于 Group 内部边的 EdgeData
const edgesDataArr: EdgeData[] = []
// 所有有关联的连线
const allRelatedEdges: BaseEdgeModel[] = []
forEach(Array.from(children), (childId: string) => {
const childNode = this.lf.getNodeModelById(childId)
if (childNode) {
const childNodeChildren = childNode.children
const childNodeData = childNode.getData()
const eventType = EventType.NODE_GROUP_COPY || 'node:group-copy-add'
const newNodeConfig = transformNodeData(
this.removeChildrenInGroupNodeData(childNodeData),
distance,
)
const tempChildNode = this.lf.addNode(newNodeConfig, eventType)
curGroup.addChild(tempChildNode.id)
nodeIdMap[childId] = tempChildNode.id // id 同 childId,做映射存储
allChildNodes.push(tempChildNode)
// 1. 存储 children 内部节点相关的输入边(incoming)
allRelatedEdges.push(
...[...tempChildNode.incoming.edges, ...tempChildNode.outgoing.edges],
)
if (childNodeChildren instanceof Set) {
const { childNodes, edgesData } = this.initGroupChildNodes(
nodeIdMap,
childNodeChildren,
tempChildNode as DynamicGroupNodeModel,
distance,
)
allChildNodes.push(...childNodes)
edgesDataArr.push(...edgesData)
}
}
})
// 1. 判断每一条边的开始节点、目标节点是否在 Group 中
const edgesInnerGroup = filter(allRelatedEdges, (edge) => {
return (
has(nodeIdMap, edge.sourceNodeId) && has(nodeIdMap, edge.targetNodeId)
)
})
// 2. 为「每一条 Group 的内部边」构建出 EdgeData 数据,得到 EdgeConfig,生成新的线
const edgesDataInnerGroup = map(edgesInnerGroup, (edge) => {
return edge.getData()
})
return {
childNodes: allChildNodes,
edgesData: edgesDataArr.concat(edgesDataInnerGroup),
}
}
/**
* 根据参数 edge 选择是新建边还是基于已有边,复制一条边出来
* @param edge
* @param nodeIdMap
* @param distance
*/
createEdge(
edge: EdgeConfig | EdgeData,
nodeIdMap: Record<string, string>,
distance: number,
) {
const { sourceNodeId, targetNodeId } = edge
const sourceId = nodeIdMap[sourceNodeId] ?? sourceNodeId
const targetId = nodeIdMap[targetNodeId] ?? targetNodeId
// 如果是有 id 且 text 是对象的边,需要重新计算位置,否则直接用 edgeConfig 生成边
let newEdgeConfig = cloneDeep(edge)
if (edge.id && typeof edge.text === 'object' && edge.text !== null) {
newEdgeConfig = transformEdgeData(edge as EdgeData, distance)
}
return this.lf.graphModel.addEdge({
...newEdgeConfig,
sourceNodeId: sourceId,
targetNodeId: targetId,
})
}
/**
* 检测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
* 区别是:1. 有些插件就是自定义节点,可能会有初始化方法 init,但不必要有 render (比如 Group)
* 2. 有些插件是渲染一些部件(比如 MiniMap、Control、Menu 等)必须要有 render
* 3. 工具类的,init 、 render
* 该如何分类呢?并如何完善插件的类型
*
* TODO: 2. 插件的 destroy 方法该做些什么,是否应该加 destroy 方法
* TODO: 3. 是否应该定义一个 Extension 的基类,所有插件基于这个基类来开发,这样在初始化的时候就可以确认执行什么方法
*/
init() {
const { lf } = this
const { graphModel } = lf
// 添加分组节点移动规则
// 1. 移动分组节点时,同时移动分组内所有节点
// 2. 移动子节点时,判断是否有限制规则(isRestrict)
graphModel.addNodeMoveRules((model, deltaX, deltaY) => {
// 判断如果是 group,移动时需要同时移动组内的所有节点
if (model.isGroup) {
// https://github.com/didi/LogicFlow/issues/1826
// 这里不应该触发移动子节点的逻辑,这里是判断是否可以移动,而不是触发移动逻辑
// 而且这里触发移动,会导致resize操作的this.x变动也会触发子item的this.x变动
// resize时的deltaX跟正常移动的deltaX是不同的
// const nodeIds = this.getNodesInGroup(model as DynamicGroupNodeModel)
// graphModel.moveNodes(nodeIds, deltaX, deltaY, true)
return true
}
const groupId = this.nodeGroupMap.get(model.id)!
const groupModel = this.lf.getNodeModelById(
groupId,
) as DynamicGroupNodeModel
if (groupModel && groupModel.isRestrict) {
if (groupModel.autoResize) {
// 子节点在父节点中移动时,父节点会自动调整大小
// 在node:mousemove中进行父节点的调整
return true
} else {
// 如果移动的节点存在于某个分组中,且这个分组禁止子节点移出去
const groupBounds = groupModel.getBounds()
return isAllowMoveTo(groupBounds, model, deltaX, deltaY)
}
}
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.onNodeAddOrDrop)
lf.on('selection:drop', this.onSelectionDrop)
lf.on('node:delete', this.removeNodeFromGroup)
lf.on('node:drag,node:dnd-drag', this.onNodeDrag)
lf.on('selection:drag', this.onSelectionDrag)
lf.on('node:click', this.onNodeSelect)
lf.on('node:mousemove', this.onNodeMove)
lf.on('graph:rendered', this.onGraphRendered)
lf.on('group:add-node', this.onGroupAddNode)
// https://github.com/didi/LogicFlow/issues/1346
// 重写 addElements() 方法,在 addElements() 原有基础上增加对 group 内部所有 nodes 和 edges 的复制功能
// 使用场景:addElements api 项目内部目前只在快捷键粘贴时使用(此处解决的也应该是粘贴场景的问题)
lf.addElements = (
{ nodes: selectedNodes, edges: selectedEdges }: GraphConfigData,
distance = 40,
): GraphElements => {
// oldNodeId -> newNodeId 映射 Map
const nodeIdMap: Record<string, string> = {}
// 本次添加的所有节点和边
const elements: GraphElements = {
nodes: [],
edges: [],
}
// 所有属于分组内的边 -> sourceNodeId 和 targetNodeId 都在 Group 内
const edgesInnerGroup: EdgeData[] = []
forEach(selectedNodes, (node) => {
const originId = node.id
const children = node.properties?.children ?? node.children
const model = lf.addNode(this.removeChildrenInGroupNodeData(node))
if (originId) nodeIdMap[originId] = model.id
elements.nodes.push(model) // 此时为 group 的 nodeModel
// TODO: 递归创建 group 的 nodeModel 的 children
if (model.isGroup) {
const { edgesData } = this.initGroupChildNodes(
nodeIdMap,
children,
model as DynamicGroupNodeModel,
distance,
)
edgesInnerGroup.push(...edgesData)
}
})
forEach(edgesInnerGroup, (edge) => {
this.createEdge(edge, nodeIdMap, distance)
})
forEach(selectedEdges, (edge) => {
elements.edges.push(this.createEdge(edge, nodeIdMap, distance))
})
// 返回 elements 进行选中效果,即触发 element.selectElementById()
// shortcut.ts 也会对最外层的 nodes 和 edges 进行偏移,即 translationNodeData()
return elements
}
this.render()
}
render() {}
destroy() {
// 销毁监听的事件,并移除渲染的 dom 内容
this.lf.off('node:add,node:drop,node:dnd-add', this.onNodeAddOrDrop)
this.lf.off('selection:drop', this.onSelectionDrop)
this.lf.off('node:delete', this.removeNodeFromGroup)
this.lf.off('node:drag,node:dnd-drag', this.onNodeDrag)
this.lf.off('selection:drag', this.onSelectionDrag)
this.lf.off('node:click', this.onNodeSelect)
this.lf.off('node:mousemove', this.onNodeMove)
this.lf.off('graph:rendered', this.onGraphRendered)
this.lf.off('group:add-node', this.onGroupAddNode)
// 还原 lf.addElements 方法?
// 移除 graphModel 上重写的 addNodeMoveRules 方法?
// TODO: 讨论一下插件该具体做些什么
}
}
export namespace DynamicGroup {
export type ElementsInfoInGroup = {
childNodes: BaseNodeModel[] // 分组节点的所有子节点 model
edgesData: EdgeData[] // 属于分组内的线的 EdgeData (即开始节点和结束节点都在 Group 内)
}
export type DynamicGroupOptions = Partial<{
isCollapsed: boolean
}>
}
export default DynamicGroup