UNPKG

relu-bpmn

Version:

RELU快速开发平台bpmn组件

649 lines (648 loc) 28.8 kB
import { bpmnStart, bpmnTask, typeConfluence, typeTrigger } from '../config/variableName'; import { NodeUtils } from './nodeUtil'; import { changeTypeByTaskShape, hasGatewayType, typeConfig } from '../config'; import { DEFAULT_CONNECT, DEFAULT_DISTANCE } from '../config/constants'; export class BPMNTreeBuilder { _allElement; _connectMap; constructor(allElement) { this._allElement = allElement; this._connectMap = new Map(); } addUniqueChild(parent, child) { if (!parent.children.some(c => c.id === child.id)) parent.children.push(child); } findStartElement(obj) { for (const key in obj) { if (obj.hasOwnProperty(key)) { const item = obj[key]; if (item.type === bpmnStart) return item; } } return null; } constructTree(treeType) { let startNode = this.findStartElement(this._allElement); if (!startNode) throw new Error('开始节点未找到'); const rootNode = { id: startNode.id, name: startNode.businessObject.name, type: startNode.type, wnType: startNode.wnType, children: [], virtualWidth: 320, virtualHeight: DEFAULT_DISTANCE, level: 0, x: startNode.x, y: startNode.y, width: startNode.width, height: startNode.height, }; let connections = {}; let newConnectsMap = new Map(); for (let key in this._allElement) { let element = this._allElement[key]; if (element.incoming?.length > 1) { let connectMap = new Map(); element.incoming.map((item) => { if (!connectMap.has(item.id)) { let connectId = item.id; let parentELement = item.source; if (parentELement.outgoing?.length > 1) { parentELement.outgoing.map((connect) => { if (!connectMap.has(connect.id)) { connectMap.set(connectId, parentELement.outgoing.length || 0); } }); } } }); if (treeType != 1) { if (connectMap.size > 0) { if (connectMap.size === element.incoming?.length) { let minKey = null; let minValue = Infinity; for (let [key, value] of connectMap) { if (value < minValue) { minValue = value; minKey = key; } } if (minKey !== null) { connectMap.delete(minKey); } } } connectMap.forEach((value, key) => { newConnectsMap.set(key, value); }); } } } for (let key in this._allElement) { let element = this._allElement[key]; if (element.type === 'bpmn:SequenceFlow' && !newConnectsMap.has(element.id)) { let sourceId = element.source.id; let targetId = element.target.id; if (!connections[sourceId]) connections[sourceId] = []; let targetElement = element.target; let childNode = { id: targetId, name: targetElement.name, type: targetElement.type, wnType: targetElement.wnType, children: [], virtualWidth: 320, virtualHeight: DEFAULT_DISTANCE, x: targetElement.x, y: targetElement.y, width: targetElement.width, height: targetElement.height, }; if (!connections[sourceId].some(child => child.id === childNode.id)) connections[sourceId].push(childNode); } } let queue = [rootNode]; let processedNodes = new Map(); while (queue.length > 0) { let current = queue.shift(); if (current && connections[current.id]) { connections[current.id].forEach(child => { child.parentData = current; if (!processedNodes.has(child.id)) { this.addUniqueChild(current, child); queue.push(child); processedNodes.set(child.id, child); } else this.addUniqueChild(current, processedNodes.get(child.id)); }); } } this._connectMap = newConnectsMap; return rootNode; } calculateVirtualWidth(root, elementRegistry) { let stack = []; stack.push({ node: root, totalWidth: 0 }); let parentChildMapping = new Map(); while (stack.length > 0) { let current = stack[stack.length - 1]; let { node, totalWidth } = current; if (node.children.length === 0) { node.virtualWidth = 320; stack.pop(); this.updateParent(stack, node.virtualWidth, 'horizontal'); continue; } let children = node.children; let unprocessedChildren = parentChildMapping.get(node); if (!unprocessedChildren) { unprocessedChildren = [...children]; parentChildMapping.set(node, unprocessedChildren); } if (unprocessedChildren.length > 0) { let child = unprocessedChildren.pop(); stack.push({ node: child, totalWidth: 0 }); } else { let finalWidth = totalWidth; let hasMergeChild = children.some(child => child.wnType === typeConfluence); let hasTrigger = children.some(child => child.wnType === typeTrigger); if (hasMergeChild && !hasTrigger) finalWidth = 320; else { let newElement = elementRegistry?.get(node.id); finalWidth = children.reduce((sum, child) => { let virtualWidth = child.virtualWidth ?? 0; if (child.isGateway) virtualWidth = 320; return sum + virtualWidth; }, 0); let hasTrigger = newElement.outgoing?.some(o => o.target.wnType === typeTrigger); let isNotTriggerChildren = children?.some(o => o.wnType != typeTrigger); if (hasTrigger && !isNotTriggerChildren) finalWidth += 320; } if (hasGatewayType.has(node.parentData?.wnType) && !hasTrigger) { let elements = new Map(); let number = 0; function getChildrenMaxWidth(targetElement) { elements.set(targetElement.id, targetElement.virtualWidth); if (number < targetElement.virtualWidth && targetElement.wnType != typeConfluence) number = targetElement.virtualWidth; if (targetElement.id !== node.parentData?.id + '_confluence') findPath(targetElement, false); } function findPath(currentElement, isRoot, id) { currentElement?.children.forEach(targetElement => { if (isRoot) { if (targetElement.id === id) getChildrenMaxWidth(targetElement); } else getChildrenMaxWidth(targetElement); }); } findPath(node.parentData, true, node.id); finalWidth = number; } if (elementRegistry) { let currentElement = elementRegistry.get(node.id); if (currentElement.incoming?.length > 1) node.isGateway = true; } node.virtualWidth = finalWidth; stack.pop(); this.updateParent(stack, finalWidth, 'horizontal'); } } return root.virtualWidth; } calculateVirtualHeight(root, elementRegistry) { let stack = []; stack.push({ node: root, totalHeight: 0 }); let parentChildMapping = new Map(); while (stack.length > 0) { let current = stack[stack.length - 1]; let { node, totalHeight } = current; if (node.children.length === 0) { node.virtualHeight = 118; stack.pop(); this.updateParent(stack, node.virtualHeight, 'vertical'); continue; } let children = node.children; let unprocessedChildren = parentChildMapping.get(node); if (!unprocessedChildren) { unprocessedChildren = [...children]; parentChildMapping.set(node, unprocessedChildren); } if (unprocessedChildren.length > 0) { let child = unprocessedChildren.pop(); stack.push({ node: child, totalHeight: 0 }); } else { let finalHeight = totalHeight; let hasMergeChild = children.some(child => child.wnType === typeConfluence); if (hasMergeChild) finalHeight = 208; else finalHeight = children.reduce((sum, child) => { let virtualHeight = child.virtualHeight ?? 0; if (child.isGateway) virtualHeight = 208; return sum + virtualHeight; }, 0); if (hasGatewayType.has(node.parentData?.wnType)) { let elements = new Map(); let number = 0; function getChildrenMaxHeight(targetElement) { elements.set(targetElement.id, targetElement.virtualHeight); if (number < targetElement.virtualHeight && targetElement.wnType != typeConfluence) number = targetElement.virtualHeight; if (targetElement.id !== node.parentData?.id + '_confluence') findPath(targetElement, false); } function findPath(currentElement, isRoot, id) { currentElement?.children.forEach(targetElement => { if (isRoot) { if (targetElement.id === id) getChildrenMaxHeight(targetElement); } else getChildrenMaxHeight(targetElement); }); } findPath(node.parentData, true, node.id); finalHeight = number; } if (elementRegistry) { let currentElement = elementRegistry.get(node.id); if (currentElement.incoming?.length > 1) node.isGateway = true; } node.virtualHeight = finalHeight; stack.pop(); this.updateParent(stack, finalHeight, 'vertical'); } } return root.virtualHeight; } updateParent(stack, number, type) { if (stack.length > 0) { const parent = stack[stack.length - 1]; type === 'horizontal' ? (parent['totalWidth'] += number) : (parent['totalHeight'] += number); } } findNodeById(root, id) { if (root.id === id) return root; for (const child of root.children) { const foundNode = this.findNodeById(child, id); if (foundNode) return foundNode; } return undefined; } isGateway(element) { const gatewayTypes = ['bpmn:ExclusiveGateway', 'bpmn:ParallelGateway', 'bpmn:InclusiveGateway', 'bpmn:EventBasedGateway']; if (element.wnType === typeConfluence) return false; return gatewayTypes.includes(element.type); } formatCanvas(visited, modeling, elementRegistry) { let obj = visited.reduce((acc, item) => { let x = item.offset?.x - item.x || 0; if (!acc[x]) acc[x] = []; acc[x].push(elementRegistry.get(item.id)); return acc; }, {}); for (const key in obj) { if (obj.hasOwnProperty(key)) { let list = obj[key]; modeling.moveElements(list, { x: Number(key), y: 0 }); } } } formatCanvasHorizontal(visited, modeling, elementRegistry) { let obj = visited.reduce((acc, item) => { let y = item.offset?.y - item.y || 0; if (!acc[y]) acc[y] = []; acc[y].push(elementRegistry.get(item.id)); return acc; }, {}); for (const key in obj) obj.hasOwnProperty(key) && modeling.moveElements(obj[key], { x: 0, y: Number(key) }); } getParentOffsetById(data, id) { if (data.parentData) { if (data.parentData.id === id) { let offset = { x: data.parentData.offset.x + data.parentData.width / 2, y: data.parentData.offset.y, }; return offset; } return this.getParentOffsetById(data.parentData, id); } } traverseTreeBFS(root, callback) { let queue = [root]; const processedNodes = new Set(); const AUTO_HEIGHT = 150; while (queue.length > 0) { let current = queue.shift(); if (current && !processedNodes.has(current.id)) { processedNodes.add(current.id); if (current.id != root.id) { let parentData = current.parentData; let n = 0; if (parentData) { for (let i = 0; i < parentData.children.length; i++) { if (parentData.children[i].id === current.id) break; if (parentData.children[i].wnType === typeConfluence) { parentData.children[i].virtualWidth = 320; } n += parentData.children[i].virtualWidth; } } let parentX = parentData.offset ? parentData.offset.x : parentData.x; let parentY = parentData.offset ? parentData.offset.y : parentData.y; let minX = parentX - parentData.virtualWidth / 2; let currentVirtualWidth = current.virtualWidth / 2; if (current.children) if (parentData.virtualWidth > current.virtualWidth && parentData.children?.length === 1) currentVirtualWidth = parentData.virtualWidth / 2; if (parentData.children?.length === 1 && !parentData.children.some(o => o.wnType != typeTrigger)) { minX = parentX - (parentData.virtualWidth + 320) / 2; } let isTrigger = parentData.children.every(s => s.wnType === typeTrigger); if (isTrigger) { n += 320; } let offset = { x: minX + currentVirtualWidth - (current.width - parentData.width) / 2 + n, y: parentY + AUTO_HEIGHT + current.height, }; if (current.id.includes('_confluence')) { let id = current.id.replace('_confluence', ''); let gatewayOffset = this.getParentOffsetById(current, id); offset = { x: gatewayOffset.x, y: parentY + AUTO_HEIGHT + current.height, }; } current.offset = offset; } callback(current); current.children = current.children.map((children) => { return { ...children, parentData: { ...current } }; }); queue.push(...current.children); } } } bpmnTraverseTreeBFS(root, callback, type) { let queue = [root]; const AUTO_HEIGHT = 150; while (queue.length > 0) { let current = queue.shift(); if (current) { if (current.id != root.id) { let parentData = current.parentData; let n = 0; if (parentData) { for (let i = 0; i < parentData.children.length; i++) { if (parentData.children[i].id === current.id) break; type === 'horizontal' ? (n += parentData.children[i].virtualHeight || 208) : (n += parentData.children[i].virtualWidth || 320); } } let parentX = parentData.offset ? parentData.offset.x : parentData.x; let parentY = parentData.offset ? parentData.offset.y : parentData.y; if (type === 'horizontal') { let minY = parentY - parentData.virtualHeight / 2; let currentVirtualHeight = current.virtualHeight / 2; if (parentData.virtualHeight > current.virtualHeight && parentData.children?.length === 1) currentVirtualHeight = parentData.virtualHeight / 2; let offset = { x: parentX + AUTO_HEIGHT + current.width, y: minY + n + currentVirtualHeight + (parentData.height - current.height) / 2, }; let level = current.level; current.offset = offset; current.level = level; } else { let minX = parentX - parentData.virtualWidth / 2; let currentVirtualWidth = current.virtualWidth / 2; if (parentData.virtualWidth > current.virtualWidth && parentData.children?.length === 1) currentVirtualWidth = parentData.virtualWidth / 2; let offset = { x: minX + n + currentVirtualWidth + (parentData.width - current.width) / 2, y: parentY + AUTO_HEIGHT + current.height, }; let level = current.level; current.offset = offset; current.level = level; } } callback(current); let level = current?.level ?? 0; current.children = current.children.map((children) => { return { ...children, parentData: { ...current }, level: level + 1 }; }); queue.push(...current.children); } } } updateConnectionWaypoints(connect, modeling, type) { let source = connect.source; let target = connect.target; let newWaypoints = []; if (type === 'vertical') { if (source.x < target.x) { newWaypoints = [ { x: source.x + source.width / 2, y: source.y + source.height }, { x: source.x + source.width / 2, y: target.y - 60 }, { x: target.x + target.width / 2, y: target.y - 60 }, { x: target.x + target.width / 2, y: target.y }, ]; } else if (source.x > target.x) { newWaypoints = [ { x: source.x + source.width / 2, y: source.y + source.height }, { x: source.x + source.width / 2, y: target.y - 60 }, { x: target.x + target.width / 2, y: target.y - 60 }, { x: target.x + target.width / 2, y: target.y }, ]; } else { newWaypoints = [ { x: source.x + source.width / 2, y: source.y + source.height }, { x: target.x + target.width / 2, y: target.y }, ]; } } else { if (source.y < target.y) { newWaypoints = [ { x: source.x + source.width, y: source.y + source.height / 2 }, { x: target.x - DEFAULT_CONNECT / 2, y: source.y + source.height / 2 }, { x: target.x - DEFAULT_CONNECT / 2, y: target.y + target.height / 2 }, { x: target.x, y: target.y + target.height / 2 }, ]; } else if (source.y > target.y) { newWaypoints = [ { x: source.x + source.width, y: source.y + source.height / 2 }, { x: target.x - DEFAULT_CONNECT / 2, y: source.y + source.height / 2 }, { x: target.x - DEFAULT_CONNECT / 2, y: target.y + target.height / 2 }, { x: target.x, y: target.y + target.height / 2 }, ]; } else { newWaypoints = [ { x: source.x + source.width, y: source.y + source.height / 2 }, { x: target.x, y: target.y + target.height / 2 }, ]; } } modeling.updateWaypoints(connect, newWaypoints); } isWithinThresholdDel(target, source, threshold) { let gatewayY = target.y; let sourceElementY = source.y; if (target.wnType === typeConfluence) { if (target.incoming?.length > 1) { let y = -Infinity; target.incoming.map((item) => { if (item?.source?.y > y) y = item.source.y; }); return gatewayY - y <= threshold; } } return gatewayY - sourceElementY < threshold && gatewayY > sourceElementY; } moveConnectedElements(connection, height) { const stack = []; const processedElements = new Set(); stack.push(connection); while (stack.length > 0) { const currentConnection = stack.pop(); const target = currentConnection.target; if (!target) continue; if (processedElements.has(target)) continue; if (this.isWithinThresholdDel(target, currentConnection.source, height)) continue; processedElements.add(target); const outgoingConnections = target.outgoing || []; for (const outgoingConnection of outgoingConnections) { stack.push(outgoingConnection); } } return Array.from(processedElements); } getElementsByGateway(gateway) { const elementsMap = new Map(); let allElements = this._allElement; function getList(list) { list.map((element) => { if (element.id === gateway.id + '_confluence') return; if (!elementsMap.has(element.id)) { elementsMap.set(element.id, element); let childrenList = NodeUtils.getNextElementList(element, allElements); getList(childrenList); } return; }); } let list = NodeUtils.getNextElementList(gateway, allElements); getList(list); return Array.from(elementsMap.values()); } resizeGroupShape(shapes, bpmn) { let elementRegistry = bpmn.get('elementRegistry'); let modeling = bpmn.get('modeling'); let groupSet = new Set(); shapes.length > 0 && shapes.map((shape) => { if (changeTypeByTaskShape[shape.wnType] || shape.wnType === typeTrigger) { groupSet.add(shape.businessObject.$attrs.customGroupId); } }); for (let groupId of groupSet) { let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity; let groupShape = elementRegistry.get(groupId); if (groupShape) { bpmn.get('elementRegistry').forEach(element => { if (element.businessObject.$attrs.customGroupId === groupId) { minX = Math.min(minX, element.x); minY = Math.min(minY, element.y); maxX = Math.max(maxX, element.x + element.width); maxY = Math.max(maxY, element.y + element.height); } }); modeling.resizeShape(groupShape, { x: minX - 25, y: minY - 15, width: maxX - minX + 50, height: maxY - minY + 30, }); } } } getGroupElementById(groupId, bpmn) { let groupList = []; let groupShape = bpmn.get('elementRegistry').get(groupId); if (groupShape) { bpmn.get('elementRegistry').forEach(element => { if (element.businessObject.$attrs?.customGroupId === groupId || element.id === groupId) groupList.push(element); }); } return groupList; } getOutgoingConnections(element) { return element.outgoing || []; } findUniqueElementsBetween(currentElement, targetElement, visitedElements = new Set()) { visitedElements.add(currentElement); if (currentElement === targetElement) { return visitedElements; } const outgoingConnections = this.getOutgoingConnections(currentElement); outgoingConnections.forEach(connection => { const nextElement = connection.target; if (!visitedElements.has(nextElement)) { this.findUniqueElementsBetween(nextElement, targetElement, visitedElements); } }); return visitedElements; } onComputerMaxElementH(bpmn, current, gatewayElement, groupList, processedElements, threshold) { let elementRegistry = bpmn.get('elementRegistry'); let confluenceElement = elementRegistry.get(current.id); let treeBuilder = new BPMNTreeBuilder(elementRegistry.getAll()); let list = []; let groupH = 0; const uniqueElementsSet = treeBuilder.findUniqueElementsBetween(gatewayElement, confluenceElement); let maxElement = null; let maxValue = -Infinity; uniqueElementsSet.forEach((element) => { let y = element?.y; if (processedElements?.has(element.id)) y += threshold; if (y > maxValue && current.id != element.id) { maxValue = y; maxElement = element; } }); if (maxValue <= current.y - 220) { list.push(current); groupH = current.y - (maxValue + DEFAULT_DISTANCE + typeConfig[bpmnTask].renderer.attr.height); } else if (maxElement.wnType === current.wnType && current.wnType === typeConfluence) { list.push(current); } if (groupList?.length) { if (current.y > maxValue) { list.push(current); } } return { list: list, h: groupH }; } }