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: add Overlays to navigate collapsed subprocesses #1487

Merged
merged 2 commits into from
Oct 21, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions assets/bpmn-js.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
.bjs-container {
--bjs-font-family: Arial, sans-serif;
}

.bjs-breadcrumbs {
position: absolute;
top: 10px;
left: 100px;
font-family: var(--bjs-font-family);
}

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

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

.bjs-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;
}

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

.bjs-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 SubprocessCentering(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;
});
}

SubprocessCentering.$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 SubprocessCompatibility(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();
});
}

SubprocessCompatibility.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] });
});
});

};

SubprocessCompatibility.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);
};

SubprocessCompatibility.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;
}
});
};

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

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

SubprocessCompatibility.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;
};

SubprocessCompatibility.$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 SubprocessOverlays(eventBus, elementRegistry, overlays, canvas) {
var breadcrumbs = domify('<ul class="bjs-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="bjs-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');
}));
});
}

SubprocessOverlays.$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