relu-bpmn
Version:
RELU快速开发平台bpmn组件
649 lines (648 loc) • 28.8 kB
JavaScript
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 };
}
}