Skip to content

Commit

Permalink
feat: add Overlays to navigate collapsed subprocesses
Browse files Browse the repository at this point in the history
This introduces a new css file to bpmn-js. To upgrade, please include
`dist/assets/bpmn-js.css` in your application.

closes #1483
  • Loading branch information
marstamm committed Oct 18, 2021
1 parent 832faf1 commit ceb0940
Show file tree
Hide file tree
Showing 13 changed files with 1,068 additions and 24 deletions.
47 changes: 47 additions & 0 deletions assets/bpmn-js.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
.bpmnjs-breadcrumbs {
position: absolute;
top: 10px;
left: 100px;
font-family: Arial, sans-serif;
}

.bpmnjs-breadcrumbs li {
display: inline-block;
color: var(--blue-base-65);
cursor: pointer;
}

.bpmnjs-breadcrumbs li:last-of-type {
color: inherit;
cursor: default;
}

.bpmnjs-breadcrumbs li:not(:first-child)::before {
content: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="17" height="17" viewBox="0 0 24 24"><path d="M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z" /><path d="M0 0h24v24H0z" fill="none" /></svg>');
padding: 0 8px;
color: black;
}

.bpmnjs-breadcrumbs .bpmnjs-crumb {
display: inline-block;
max-width: 200px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}

.bpmnjs-drilldown {
width: 20px;
height: 20px;

padding: 0px;
margin-left: -20px;

cursor: pointer;
border: none;
border-radius: 2px;
outline: none;

fill: white;
background-color: var(--blue-base-65);
}
4 changes: 3 additions & 1 deletion lib/Viewer.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import CoreModule from './core';
import TranslateModule from 'diagram-js/lib/i18n/translate';
import SelectionModule from 'diagram-js/lib/features/selection';
import OverlaysModule from 'diagram-js/lib/features/overlays';
import SubprocessNavigationModule from './features/subprocess-navigation';

import BaseViewer from './BaseViewer';

Expand Down Expand Up @@ -66,7 +67,8 @@ Viewer.prototype._modules = [
CoreModule,
TranslateModule,
SelectionModule,
OverlaysModule
OverlaysModule,
SubprocessNavigationModule
];

// default moddle extensions the viewer is composed of
Expand Down
35 changes: 35 additions & 0 deletions lib/features/subprocess-navigation/SubprocessCentering.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
export default function CollapsedProcessesNavigation(eventBus, canvas) {
var currentPlane = 'base';
var positionMap = {};

eventBus.on('plane.set', function(event) {

var currentViewbox = canvas.viewbox();
positionMap[currentPlane] = {
x: currentViewbox.x,
y: currentViewbox.y,
zoom: currentViewbox.scale
};

var planeId = event.plane.name;
var storedViewbox = positionMap[planeId] || { x: 0, y: 0, zoom: 1 };

var dx = (currentViewbox.x - storedViewbox.x) * currentViewbox.scale,
dy = (currentViewbox.y - storedViewbox.y) * currentViewbox.scale;

if (dx !== 0 || dy !== 0) {
canvas.scroll({
dx: dx,
dy: dy
});
}

if (storedViewbox.zoom !== currentViewbox.scale) {
canvas.zoom(storedViewbox.zoom, { x: 0, y: 0 });
}

currentPlane = planeId;
});
}

CollapsedProcessesNavigation.$inject = [ 'eventBus', 'canvas' ];
135 changes: 135 additions & 0 deletions lib/features/subprocess-navigation/SubprocessCompatibility.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@

import { selfAndAllChildren } from 'diagram-js/lib/util/Elements';
import { isExpanded } from '../../util/DiUtil';
import { getBusinessObject, getDi, is } from '../../util/ModelUtil';

export default function CollapsedProcessesCompatibility(eventBus, elementRegistry, canvas, moddle, elementFactory, bpmnjs) {
this._eventBus = eventBus;
this._elementRegistry = elementRegistry;
this._canvas = canvas;
this._bpmnjs = bpmnjs;
this._moddle = moddle;
this._elementFactory = elementFactory;

var self = this;

eventBus.on('import.done', 1500, function() {
self._handleImport();
});
}

CollapsedProcessesCompatibility.prototype._handleImport = function() {
var elementRegistry = this._elementRegistry;
var canvas = this._canvas;
var elementFactory = this._elementFactory;
var self = this;
var legacyProcesses = elementRegistry.filter(function(element) {
return is(element, 'bpmn:SubProcess') && !isExpanded(element) && element.children && element.children.length;
});

legacyProcesses.forEach(function(oldParent) {
var bo = getBusinessObject(oldParent);

var newDiagram = self.createDiagram(bo);

var newParent = elementFactory.createRoot(
{
id: bo.id,
type: bo.$type,
businessObject: bo,
di: newDiagram.plane
}
);

newParent.id = newParent.id + '_plane';

canvas.createPlane(bo.id, newParent);

var elementsToChange = selfAndAllChildren(oldParent).filter(function(el) {
return el !== oldParent;
});

self.moveElementsToRoot(elementsToChange);

elementsToChange.forEach(function(el) {
if (el.parent === oldParent) {
el.parent = newParent;
}
el.hidden = el.parent && (el.parent.hidden || el.parent.collapsed);

self.moveToDiPlane(el, newDiagram.plane);

self._eventBus.fire('elements.changed', { elements: [el] });
});
});

};

CollapsedProcessesCompatibility.prototype.moveToDiPlane = function(element, newPlane) {
var di = getDi(element);
var containingDiagram = findRootDiagram(di);

// Remove DI from old Plane and add it to the new one
var parentPlaneElement = containingDiagram.plane.get('planeElement');
parentPlaneElement.splice(parentPlaneElement.indexOf(di), 1);
newPlane.get('planeElement').push(di);
};

CollapsedProcessesCompatibility.prototype.moveElementsToRoot = function(elements) {
var defaultPosition = { x: 180, y: 160 },
minX = Infinity,
minY = Infinity;

elements.forEach(function(el) {
minX = Math.min(minX, el.x || Infinity);
minY = Math.min(minY, el.y || Infinity);
});

var xOffset = defaultPosition.x - minX;
var yOffset = defaultPosition.y - minY;

elements.forEach(function(el) {
if (el.waypoints) {
el.waypoints.forEach(function(waypoint) {
waypoint.x = waypoint.x + xOffset;
waypoint.y = waypoint.y + yOffset;
});
} else {
el.x = el.x + xOffset;
el.y = el.y + yOffset;
}
});
};

CollapsedProcessesCompatibility.prototype.getDefinitions = function() {
return this._bpmnjs._definitions || [];
};

CollapsedProcessesCompatibility.prototype.getDiagrams = function() {
return this.getDefinitions().diagrams || [];
};

CollapsedProcessesCompatibility.prototype.createDiagram = function(bo) {
var plane = this._moddle.create('bpmndi:BPMNPlane', { bpmnElement: bo });
var diagram = this._moddle.create('bpmndi:BPMNDiagram', {
plane: plane
});
plane.$parent = diagram;
plane.bpmnElement = bo;
diagram.$parent = this.getDefinitions();
this.getDiagrams().push(diagram);
return diagram;
};

CollapsedProcessesCompatibility.$inject = [ 'eventBus', 'elementRegistry', 'canvas', 'moddle', 'elementFactory', 'bpmnjs' ];


// Util

var findRootDiagram = function(element) {
if (is(element, 'bpmndi:BPMNDiagram')) {
return element;
} else {
return findRootDiagram(element.$parent);
}
};
100 changes: 100 additions & 0 deletions lib/features/subprocess-navigation/SubprocessOverlays.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import { domify } from 'min-dom';

import { escapeHTML } from 'diagram-js/lib/util/EscapeUtil';
import { getBusinessObject, is } from '../../util/ModelUtil';

var ARROW_DOWN_SVG = '<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 16 16"><path fill-rule="evenodd" d="M4.81801948,3.50735931 L10.4996894,9.1896894 L10.5,4 L12,4 L12,12 L4,12 L4,10.5 L9.6896894,10.4996894 L3.75735931,4.56801948 C3.46446609,4.27512627 3.46446609,3.80025253 3.75735931,3.50735931 C4.05025253,3.21446609 4.52512627,3.21446609 4.81801948,3.50735931 Z"/></svg>';

export default function CollapsedSubprocessOverlays(eventBus, elementRegistry, overlays, canvas) {
var breadcrumbs = domify('<ul class="bpmnjs-breadcrumbs djs-element-hidden"></ul>');
var container = canvas.getContainer();
container.appendChild(breadcrumbs);

function updateBreadcrumbs(plane) {
var subProcess = elementRegistry.get(plane.name);
var parents = getParentChain(subProcess);

var path = parents.map(function(el) {
var title = escapeHTML(el.name) || el.id;
var link = domify('<li><span class="bpmnjs-crumb"><a title="' + title + '">' + title + '</a></span></li>');

link.addEventListener('click', function() {
if (canvas.getPlane(el.id)) {
canvas.setActivePlane(el.id);
} else {
var plane = canvas.findPlane(el.id);
canvas.setActivePlane(plane);
}
});

return link;
});

breadcrumbs.innerHTML = '';

if (path.length > 1) {
breadcrumbs.classList.remove('djs-element-hidden');
} else {
breadcrumbs.classList.add('djs-element-hidden');
}

path.forEach(function(el) {
breadcrumbs.appendChild(el);
});
}

eventBus.on('plane.set', function(event) {
var plane = event.plane;

updateBreadcrumbs(plane);
});

var createOverlay = function(element) {
var html = domify('<button class="bpmnjs-drilldown">' + ARROW_DOWN_SVG + '</button>');

html.addEventListener('click', function() {
canvas.setActivePlane(element.id);
});

overlays.add(element, {
position: {
bottom: -7,
right: -8
},
html: html
});
};

var addOverlays = function(elements) {
elements.forEach(function(element) {
if (is(element, 'bpmn:SubProcess')
&& element.collapsed
&& canvas.getPlane(element.id)) {
createOverlay(element);
}
});
};

eventBus.on('import.done', function() {
addOverlays(elementRegistry.filter(function(el) {
return is(el, 'bpmn:SubProcess');
}));
});
}

CollapsedSubprocessOverlays.$inject = [ 'eventBus', 'elementRegistry', 'overlays', 'canvas' ];


var getParentChain = function(child) {
var bo = getBusinessObject(child);

var parents = [];

for (var element = bo; element; element = element.$parent) {
if (is(element, 'bpmn:SubProcess') || is(element, 'bpmn:Process')) {
parents.push(element);
}
}

return parents.reverse();
};
14 changes: 14 additions & 0 deletions lib/features/subprocess-navigation/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import OverlaysModule from 'diagram-js/lib/features/overlays';
import ChangeSupportModule from 'diagram-js/lib/features/change-support';

import SubprocessCentering from './SubprocessCentering';
import SubprocessCompatibility from './SubprocessCompatibility';
import SubprocessOverlays from './SubprocessOverlays';

export default {
__depends__: [ OverlaysModule, ChangeSupportModule ],
__init__: [ 'subprocessOverlays', 'subprocessCompatibility', 'subprocessCentering' ],
subprocessOverlays: [ 'type', SubprocessOverlays ],
subprocessCompatibility: [ 'type', SubprocessCompatibility ],
subprocessCentering: [ 'type', SubprocessCentering ]
};
3 changes: 3 additions & 0 deletions tasks/build-distro.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ cp(resolve('bpmn-font', '/dist/{font,css}/**'), dest + '/assets/bpmn-font');
console.log('copy diagram-js.css to ' + dest);
cp(resolve('diagram-js', '/assets/**'), dest + '/assets');

console.log('copy bpmn-js.css to ' + dest);
cp('./assets/bpmn-js.css', dest + '/assets');

console.log('building pre-packaged distributions');

var NODE_ENV = process.env.NODE_ENV;
Expand Down
2 changes: 2 additions & 0 deletions test/TestHelper.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import {
insertCSS
} from './helper';

insertCSS('bpmn-js.css', require('../assets/bpmn-js.css'));

insertCSS('diagram-js.css', require('diagram-js/assets/diagram-js.css'));

insertCSS('bpmn-embedded.css', require('bpmn-font/dist/css/bpmn-embedded.css'));
Expand Down
Loading

0 comments on commit ceb0940

Please sign in to comment.