feat(subProcesses): add expanded view

closes #1484
This commit is contained in:
Martin Stamm 2021-10-25 11:58:11 +02:00
parent 4a1cc29a4d
commit d30574f5dd
No known key found for this signature in database
GPG Key ID: B3A641060A8CBCF4
9 changed files with 169 additions and 66 deletions

View File

@ -3,30 +3,37 @@
}
.bjs-breadcrumbs {
position: absolute;
top: 10px;
left: 100px;
width: 100%;
display: flex;
align-items: center;
margin: 0;
font-family: var(--bjs-font-family);
font-size: 16px;
}
.bjs-breadcrumbs li {
display: inline-block;
display: inline-flex;
color: var(--blue-base-65);
cursor: pointer;
}
.bjs-breadcrumbs .bjs-drilldown {
margin-right: 5px;
}
.bjs-breadcrumbs li:last-of-type {
color: inherit;
cursor: default;
}
.bjs-breadcrumbs li:not(:first-child)::before {
.bjs-breadcrumbs li:not(:first-of-type)::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>');
height: 16px;
padding: 0 8px;
color: black;
}
.bjs-breadcrumbs .bpmnjs-crumb {
.bjs-breadcrumbs .bjs-crumb {
display: inline-block;
max-width: 200px;
overflow: hidden;

View File

@ -1,3 +1,5 @@
var defaultPosition = { x: 100, y: 50 };
export default function SubprocessCentering(eventBus, canvas) {
var currentPlane = 'base';
var positionMap = {};
@ -12,7 +14,25 @@ export default function SubprocessCentering(eventBus, canvas) {
};
var planeId = event.plane.name;
var storedViewbox = positionMap[planeId] || { x: 0, y: 0, zoom: 1 };
var storedViewbox = positionMap[planeId];
if (!storedViewbox) {
if (!event.plane.rootElement.isImplicit) {
storedViewbox = {
x: 0,
y: 0,
zoom: 1
};
}
else {
var currentBoundingBox = canvas.getActiveLayer().getBBox();
storedViewbox = {
x: currentBoundingBox.x - defaultPosition.x,
y: currentBoundingBox.y - defaultPosition.y,
zoom: 1
};
}
}
var dx = (currentViewbox.x - storedViewbox.x) * currentViewbox.scale,
dy = (currentViewbox.y - storedViewbox.y) * currentViewbox.scale;

View File

@ -4,19 +4,25 @@ 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>';
var ARROW_UP_SVG = '<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 16 16"><path fill-rule="evenodd" d="M11.1819805,12.4926407 L5.5003106,6.8103106 L5.5,12 L4,12 L4,4 L12,4 L12,5.5 L6.3103106,5.5003106 L12.2426407,11.4319805 C12.5355339,11.7248737 12.5355339,12.1997475 12.2426407,12.4926407 C11.9497475,12.7855339 11.4748737,12.7855339 11.1819805,12.4926407 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);
function createBreadcrumbs(subProcess) {
var breadcrumbs = domify('<ul class="bjs-breadcrumbs"></ul>');
var drillUp = domify('<button class="bjs-drilldown">' + ARROW_UP_SVG + '</button>');
drillUp.addEventListener('click', function() {
var plane = canvas.findPlane(getBusinessObject(subProcess).id);
canvas.setActivePlane(plane);
});
breadcrumbs.appendChild(drillUp);
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>');
var link = domify('<li><span class="bjs-crumb"><a title="' + title + '">' + title + '</a></span></li>');
link.addEventListener('click', function() {
if (canvas.getPlane(el.id)) {
@ -30,26 +36,20 @@ export default function SubprocessOverlays(eventBus, elementRegistry, overlays,
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);
});
overlays.add(subProcess, {
position: {
top: -30,
left: -10
},
html: breadcrumbs
});
}
eventBus.on('plane.set', function(event) {
var plane = event.plane;
updateBreadcrumbs(plane);
});
var createOverlay = function(element) {
var createDrilldown = function(element) {
var html = domify('<button class="bjs-drilldown">' + ARROW_DOWN_SVG + '</button>');
html.addEventListener('click', function() {
@ -70,7 +70,11 @@ export default function SubprocessOverlays(eventBus, elementRegistry, overlays,
if (is(element, 'bpmn:SubProcess')
&& element.collapsed
&& canvas.getPlane(element.id)) {
createOverlay(element);
createDrilldown(element);
}
if (is(element, 'bpmn:SubProcess') && element.isSecondary) {
createBreadcrumbs(element);
}
});
};

View File

@ -10,6 +10,8 @@ import {
} from '../util/LabelUtil';
import {
asBounds,
asTRBL,
getMid
} from 'diagram-js/lib/layout/LayoutUtil';
@ -48,6 +50,32 @@ function getWaypoints(di, source, target) {
});
}
var PLANE_PADDING = 50;
function getPlaneBounds(plane) {
var planeTrbl = {
top: Infinity,
right: -Infinity,
bottom: -Infinity,
left: Infinity
};
plane.planeElement.forEach(function(element) {
if (!element.bounds) {
return;
}
var trbl = asTRBL(element.bounds);
planeTrbl.top = Math.min(trbl.top, planeTrbl.top);
planeTrbl.left = Math.min(trbl.left, planeTrbl.left);
planeTrbl.right = Math.max(trbl.right, planeTrbl.right);
planeTrbl.bottom = Math.max(trbl.bottom, planeTrbl.bottom);
});
return asBounds(planeTrbl);
}
function notYetDrawn(translate, semantic, refSemantic, property) {
return new Error(translate('element {element} referenced by {referenced}#{property} not yet drawn', {
element: elementToString(refSemantic),
@ -105,14 +133,36 @@ BpmnImporter.prototype.add = function(semantic, di, parentElement) {
// invisible root element (process or collaboration)
if (is(di, 'bpmndi:BPMNPlane')) {
// add a virtual element (not being drawn)
element = this._elementFactory.createRoot(elementData(semantic, di));
if (is(semantic, 'bpmn:SubProcess')) {
element.id = element.id + '_plane';
}
var plane = this._canvas.createPlane(semantic.id, element);
this._canvas.createPlane(semantic.id, element);
// Get size of expanded element
var bounds = getPlaneBounds(di);
var primary = this._elementRegistry.get(semantic.id);
element = this._elementFactory.createShape(elementData(semantic, di, {
collapsed: false,
hidden: false,
x: bounds.x - PLANE_PADDING,
y: bounds.y - PLANE_PADDING,
width: bounds.width + PLANE_PADDING * 2,
height: bounds.height + PLANE_PADDING * 2,
isFrame: true
}));
element.id = element.id + '_secondary';
element.isSecondary = true;
element.primaryShape = primary;
this._canvas.addShape(element, plane.rootElement);
}
else {
// add a virtual element (not being drawn)
element = this._elementFactory.createRoot(elementData(semantic, di));
this._canvas.createPlane(semantic.id, element);
}
}
// SHAPE
@ -123,7 +173,7 @@ BpmnImporter.prototype.add = function(semantic, di, parentElement) {
hidden = parentElement && (parentElement.hidden || parentElement.collapsed);
var bounds = di.bounds;
bounds = di.bounds;
element = this._elementFactory.createShape(elementData(semantic, di, {
collapsed: collapsed,

View File

@ -256,7 +256,12 @@ export function createViewer(container, viewerInstance, xml, diagramId) {
clearBpmnJS();
var viewer = new viewerInstance({ container: container });
var viewer = new viewerInstance({
container: container,
canvas: {
deferUpdate: false
}
});
setBpmnJS(viewer);

View File

@ -40,6 +40,9 @@ describe('Modeler', function() {
container: container,
keyboard: {
bindTo: document
},
canvas: {
deferUpdate: false
}
});
@ -715,18 +718,18 @@ describe('Modeler', function() {
return createModeler(xml).then(function() {
var drilldown = container.querySelector('.bjs-drilldown');
var breadcrumbs = container.querySelector('.bjs-breadcrumbs');
var visibleBreadcrumbs = container.querySelector('.djs-overlay:not([style*="display: none"]) .bjs-breadcrumbs');
// assume
expect(drilldown).to.exist;
expect(breadcrumbs).to.exist;
expect(breadcrumbs.classList.contains('djs-element-hidden')).to.be.true;
expect(visibleBreadcrumbs).to.not.exist;
// when
drilldown.click();
// then
expect(breadcrumbs.classList.contains('djs-element-hidden')).to.be.false;
visibleBreadcrumbs = container.querySelector('.djs-overlay:not([style*="display: none"]) .bjs-breadcrumbs');
expect(visibleBreadcrumbs).to.exist;
});
});

View File

@ -378,18 +378,18 @@ describe('Viewer', function() {
return createViewer(container, Viewer, xml).then(function() {
var drilldown = container.querySelector('.bjs-drilldown');
var breadcrumbs = container.querySelector('.bjs-breadcrumbs');
var visibleBreadcrumbs = container.querySelector('.djs-overlay:not([style*="display: none"]) .bjs-breadcrumbs');
// assume
expect(drilldown).to.exist;
expect(breadcrumbs).to.exist;
expect(breadcrumbs.classList.contains('djs-element-hidden')).to.be.true;
expect(visibleBreadcrumbs).to.not.exist;
// when
drilldown.click();
// then
expect(breadcrumbs.classList.contains('djs-element-hidden')).to.be.false;
visibleBreadcrumbs = container.querySelector('.djs-overlay:not([style*="display: none"]) .bjs-breadcrumbs');
expect(visibleBreadcrumbs).to.exist;
});
});

View File

@ -64,64 +64,69 @@ describe('features - subprocess-navigation', function() {
it('should not show breadcrumbs in root view', inject(function(canvas) {
// given
var breadcrumbs = canvas.getContainer().querySelector('.bjs-breadcrumbs');
var breadcrumbs = getBreadcrumbs(canvas);
// then
expect(breadcrumbs.classList.contains('djs-element-hidden')).to.be.true;
expect(breadcrumbs).to.not.exist;
}));
it('should show breadcrumbs in subprocess view', inject(function(canvas) {
// given
var breadcrumbs = canvas.getContainer().querySelector('.bjs-breadcrumbs');
// when
canvas.setActivePlane('collapsedProcess');
// then
expect(breadcrumbs.classList.contains('djs-element-hidden')).to.be.false;
expect(getBreadcrumbs(canvas)).to.exist;
}));
it('should show execution tree', inject(function(canvas) {
// given
var breadcrumbs = canvas.getContainer().querySelector('.bjs-breadcrumbs');
// when
canvas.setActivePlane('collapsedProcess_2');
// then
expectBreadcrumbs(breadcrumbs, ['Root', 'Collapsed Process', 'Expanded Process', 'Collapsed Process 2']);
expectBreadcrumbs(getBreadcrumbs(canvas), ['Root', 'Collapsed Process', 'Expanded Process', 'Collapsed Process 2']);
}));
it('should switch to process plane on click', inject(function(canvas) {
// given
var breadcrumbs = canvas.getContainer().querySelector('.bjs-breadcrumbs');
canvas.setActivePlane('collapsedProcess_2');
// when
breadcrumbs.children[1].click();
getBreadcrumbs(canvas).children[2].click();
// then
expectBreadcrumbs(breadcrumbs, ['Root', 'Collapsed Process']);
expectBreadcrumbs(getBreadcrumbs(canvas), ['Root', 'Collapsed Process']);
}));
it('should switch to containing process plane on embedded click', inject(function(canvas) {
// given
var breadcrumbs = canvas.getContainer().querySelector('.bjs-breadcrumbs');
canvas.setActivePlane('collapsedProcess_2');
// when
breadcrumbs.children[2].click();
getBreadcrumbs(canvas).children[3].click();
// then
expectBreadcrumbs(breadcrumbs, ['Root', 'Collapsed Process']);
expectBreadcrumbs(getBreadcrumbs(canvas), ['Root', 'Collapsed Process']);
}));
it('should switch to containing process plane on drillup icon', inject(function(canvas) {
// given
canvas.setActivePlane('collapsedProcess_2');
// when
getBreadcrumbs(canvas).querySelector('.bjs-drilldown').click();
// then
expectBreadcrumbs(getBreadcrumbs(canvas), ['Root', 'Collapsed Process']);
}));
});
@ -201,10 +206,19 @@ describe('features - subprocess-navigation', function() {
// helpers
function getBreadcrumbs(canvas) {
return canvas.getContainer().querySelector('.djs-overlay:not([style*="display: none"]) .bjs-breadcrumbs');
}
function expectBreadcrumbs(breadcrumbs, expected) {
var crumbs = Array.from(breadcrumbs.children).map(function(element) {
return element.innerText;
});
var crumbs = Array.from(breadcrumbs.children)
.filter(function(child) {
return !!child.querySelector('.bjs-crumb');
})
.map(function(element) {
return element.innerText;
});
expect(crumbs).to.eql(expected);
}
}

View File

@ -724,7 +724,7 @@ describe('import - Importer', function() {
expect(warnings).to.have.length(0);
expect(diagram.get('elementRegistry').get('Subprocess')).to.exist;
expect(diagram.get('elementRegistry').get('Subprocess_plane')).to.exist;
expect(diagram.get('elementRegistry').get('Subprocess_secondary')).to.exist;
});
});