feat(auto-resize): add ability to automatically expand parent participants
Closes #263
This commit is contained in:
parent
f8d6658c10
commit
39db57987d
|
@ -122,6 +122,7 @@ Modeler.prototype.createModdle = function() {
|
|||
Modeler.prototype._interactionModules = [
|
||||
// non-modeling components
|
||||
require('./features/label-editing'),
|
||||
require('./features/auto-resize'),
|
||||
require('diagram-js/lib/navigation/zoomscroll'),
|
||||
require('diagram-js/lib/navigation/movecanvas'),
|
||||
require('diagram-js/lib/navigation/touch')
|
||||
|
|
|
@ -0,0 +1,101 @@
|
|||
'use strict';
|
||||
|
||||
var inherits = require('inherits');
|
||||
|
||||
var is = require('../../util/ModelUtil').is;
|
||||
|
||||
var pick = require('lodash/object/pick'),
|
||||
assign = require('lodash/object/assign');
|
||||
|
||||
var CommandInterceptor = require('diagram-js/lib/command/CommandInterceptor');
|
||||
|
||||
var PADDING = 15,
|
||||
OFFSET_TOP = 80,
|
||||
OFFSET_BOTTOM = 80,
|
||||
OFFSET_LEFT = 100,
|
||||
OFFSET_RIGHT = 100;
|
||||
|
||||
/**
|
||||
* An auto resize component that takes care of expanding parent participants
|
||||
* and lanes if an element is modeled close to an edge of the parent element.
|
||||
*/
|
||||
function AutoResize(eventBus, canvas, modeling){
|
||||
|
||||
CommandInterceptor.call(this, eventBus);
|
||||
|
||||
this.postExecuted([ 'shape.create', 'shape.move' ], function(event) {
|
||||
var context = event.context,
|
||||
shape = context.shape,
|
||||
parent = context.parent || context.newParent;
|
||||
|
||||
expand(shape, parent);
|
||||
});
|
||||
|
||||
/**
|
||||
* Returns an object which indicates near which bounding edge(s)
|
||||
* of a target an element is located.
|
||||
*
|
||||
* @param {Shape} element
|
||||
* @param {Shape} target
|
||||
* @param {Number} padding
|
||||
*
|
||||
* @return {Object} {top, bottom, left, right}
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* // If an element is near the bottom left corner of a target the return object is:
|
||||
* { top: false, bottom: true, left: true, right: false }
|
||||
*
|
||||
*/
|
||||
function isInbounds(element, target, padding) {
|
||||
return {
|
||||
top: element.y < target.y + padding && element.y + element.height > target.y,
|
||||
bottom: element.y < target.y + target.height && element.y + element.height > target.y + target.height - padding,
|
||||
left: element.x < target.x + padding && element.x + element.width > target.x,
|
||||
right: element.x < target.x + target.width && element.x + element.width > target.x + target.width - padding,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Expand target elements if the moved element is near or on an edge, considering the position
|
||||
* of the element edge in relation to the parent's edge plus padding. The amount to expand
|
||||
* can be defined for each edge in the OFFSET variables.
|
||||
*
|
||||
* @param {Shape} shape
|
||||
* @param {Shape} target
|
||||
*/
|
||||
function expand(shape, target) {
|
||||
if (is(target, 'bpmn:Participant') && target === shape.parent) {
|
||||
|
||||
var inbounds = isInbounds(shape, target, PADDING);
|
||||
|
||||
var newBounds = pick(target, [ 'x', 'y', 'width', 'height' ]);
|
||||
|
||||
if (inbounds.top) {
|
||||
var topPosition = shape.y - OFFSET_TOP;
|
||||
assign(newBounds, { y: topPosition, height: target.height + target.y - topPosition });
|
||||
}
|
||||
|
||||
if (inbounds.bottom) {
|
||||
assign(newBounds, { height: shape.y + shape.height + OFFSET_BOTTOM - target.y });
|
||||
}
|
||||
|
||||
if (inbounds.left) {
|
||||
var leftPosition = shape.x - OFFSET_LEFT;
|
||||
assign(newBounds, { x: leftPosition, width: target.width + target.x - leftPosition });
|
||||
}
|
||||
|
||||
if (inbounds.right) {
|
||||
assign(newBounds, { width: shape.x + shape.width + OFFSET_RIGHT - target.x });
|
||||
}
|
||||
|
||||
modeling.resizeShape(target, newBounds);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AutoResize.$inject = [ 'eventBus', 'canvas', 'modeling' ];
|
||||
|
||||
inherits(AutoResize, CommandInterceptor);
|
||||
|
||||
module.exports = AutoResize;
|
|
@ -0,0 +1,4 @@
|
|||
module.exports = {
|
||||
__init__: [ 'autoResize' ],
|
||||
autoResize: [ 'type', require('./AutoResize') ]
|
||||
};
|
|
@ -0,0 +1,38 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" id="Definitions_1" targetNamespace="http://bpmn.io/schema/bpmn">
|
||||
<bpmn:collaboration id="Collaboration_1">
|
||||
<bpmn:participant id="Participant_1" processRef="Process_1" />
|
||||
</bpmn:collaboration>
|
||||
<bpmn:process id="Process_1" isExecutable="false">
|
||||
<bpmn:startEvent id="StartEvent_1">
|
||||
<bpmn:outgoing>SequenceFlow_1</bpmn:outgoing>
|
||||
</bpmn:startEvent>
|
||||
<bpmn:task id="Task_1">
|
||||
<bpmn:incoming>SequenceFlow_1</bpmn:incoming>
|
||||
</bpmn:task>
|
||||
<bpmn:sequenceFlow id="SequenceFlow_1" sourceRef="StartEvent_1" targetRef="Task_1" />
|
||||
</bpmn:process>
|
||||
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
|
||||
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Collaboration_1">
|
||||
<bpmndi:BPMNShape id="Participant_1_di" bpmnElement="Participant_1">
|
||||
<dc:Bounds x="247" y="160" width="371" height="178" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="StartEvent_1_di" bpmnElement="StartEvent_1">
|
||||
<dc:Bounds x="322" y="231" width="36" height="36" />
|
||||
<bpmndi:BPMNLabel>
|
||||
<dc:Bounds x="295" y="267" width="90" height="20" />
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Task_1_di" bpmnElement="Task_1">
|
||||
<dc:Bounds x="472" y="209" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNEdge id="SequenceFlow_1_di" bpmnElement="SequenceFlow_1">
|
||||
<di:waypoint xsi:type="dc:Point" x="358" y="249" />
|
||||
<di:waypoint xsi:type="dc:Point" x="472" y="249" />
|
||||
<bpmndi:BPMNLabel>
|
||||
<dc:Bounds x="370" y="239" width="90" height="20" />
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNEdge>
|
||||
</bpmndi:BPMNPlane>
|
||||
</bpmndi:BPMNDiagram>
|
||||
</bpmn:definitions>
|
|
@ -0,0 +1,219 @@
|
|||
'use strict';
|
||||
|
||||
var TestHelper = require('../../../TestHelper');
|
||||
|
||||
/* global bootstrapModeler, inject */
|
||||
|
||||
var pick = require('lodash/object/pick'),
|
||||
assign = require('lodash/object/assign');
|
||||
|
||||
var autoResizeModule = require('../../../../lib/features/auto-resize'),
|
||||
modelingModule = require('../../../../lib/features/modeling'),
|
||||
createModule = require('diagram-js/lib/features/create'),
|
||||
coreModule = require('../../../../lib/core');
|
||||
|
||||
function getBounds(shape) {
|
||||
return pick(shape, ['x', 'y', 'width', 'height']);
|
||||
}
|
||||
|
||||
describe('features/auto-resize', function() {
|
||||
|
||||
var testModules = [coreModule, modelingModule, autoResizeModule, createModule ];
|
||||
|
||||
var diagramXML = require('./AutoResize.collaboration.bpmn');
|
||||
|
||||
var task,
|
||||
participant,
|
||||
startEvent,
|
||||
expectedBounds;
|
||||
|
||||
beforeEach(bootstrapModeler(diagramXML, { modules: testModules }));
|
||||
|
||||
beforeEach(inject(function(elementRegistry) {
|
||||
|
||||
task = elementRegistry.get('Task_1');
|
||||
participant = elementRegistry.get('Participant_1');
|
||||
startEvent = elementRegistry.get('StartEvent_1');
|
||||
|
||||
expectedBounds = getBounds(participant);
|
||||
|
||||
expect(expectedBounds).to.eql({ x: 247, y: 160, width: 371, height: 178 });
|
||||
|
||||
}));
|
||||
|
||||
describe('after moving', function() {
|
||||
|
||||
it('should expand the right edge of the parent collaboration',
|
||||
inject(function(modeling) {
|
||||
|
||||
// when
|
||||
modeling.moveElements([task], { x: 100, y: 0 }, participant);
|
||||
|
||||
// then
|
||||
assign(expectedBounds, { width: 525 });
|
||||
|
||||
expect(getBounds(participant)).to.eql(expectedBounds);
|
||||
|
||||
}));
|
||||
|
||||
|
||||
it('should expand the top edge of the parent collaboration',
|
||||
inject(function(modeling) {
|
||||
|
||||
// when
|
||||
modeling.moveElements([task], { x: 0, y: -50 }, participant);
|
||||
|
||||
// then
|
||||
assign(expectedBounds, { y: 79, height: 259 });
|
||||
|
||||
expect(getBounds(participant)).to.eql(expectedBounds);
|
||||
|
||||
}));
|
||||
|
||||
|
||||
it('should expand the bottom edge of the parent collaboration',
|
||||
inject(function(modeling) {
|
||||
|
||||
// when
|
||||
modeling.moveElements([task], { x: 0, y: 50 }, participant);
|
||||
|
||||
// then
|
||||
assign(expectedBounds, { height: 259 });
|
||||
|
||||
expect(getBounds(participant)).to.eql(expectedBounds);
|
||||
|
||||
}));
|
||||
|
||||
|
||||
it('should expand the left edge of the parent collaboration',
|
||||
inject(function(modeling) {
|
||||
|
||||
// when
|
||||
modeling.moveElements([startEvent], { x: -100, y: 0 }, participant);
|
||||
|
||||
// then
|
||||
assign(expectedBounds, { x: 122, width: 496 });
|
||||
|
||||
expect(getBounds(participant)).to.eql(expectedBounds);
|
||||
|
||||
}));
|
||||
|
||||
|
||||
it('should expand the bottom right edges of the parent collaboration',
|
||||
inject(function(modeling) {
|
||||
|
||||
// when
|
||||
modeling.moveElements([task], { x: 50, y: 50 }, participant);
|
||||
|
||||
// then
|
||||
assign(expectedBounds, { width: 475, height: 259 });
|
||||
|
||||
expect(getBounds(participant)).to.eql(expectedBounds);
|
||||
|
||||
}));
|
||||
|
||||
|
||||
it('should expand the top left edges of the parent collaboration',
|
||||
inject(function(modeling) {
|
||||
|
||||
// when
|
||||
modeling.moveElements([startEvent], { x: -100, y: -100 }, participant);
|
||||
|
||||
// then
|
||||
expect(getBounds(participant)).to.eql({ x: 122, y: 51, width: 496, height: 287 });
|
||||
|
||||
}));
|
||||
|
||||
|
||||
it('should not resize the parent collaboration if element is placed too far outside',
|
||||
inject(function(modeling) {
|
||||
|
||||
// when
|
||||
modeling.moveElements([task], { x: 300, y: 0 }, participant);
|
||||
|
||||
// then
|
||||
expect(getBounds(participant)).to.eql(expectedBounds);
|
||||
|
||||
}));
|
||||
|
||||
|
||||
it('should undo resizing', inject(function(modeling, commandStack) {
|
||||
|
||||
// when
|
||||
modeling.moveElements([startEvent], { x: -100, y: -100 }, participant);
|
||||
commandStack.undo();
|
||||
|
||||
// then
|
||||
expect(getBounds(participant)).to.eql(expectedBounds);
|
||||
|
||||
}));
|
||||
|
||||
|
||||
it('should redo resizing', inject(function(modeling, commandStack) {
|
||||
|
||||
// when
|
||||
modeling.moveElements([startEvent], { x: -100, y: -100 }, participant);
|
||||
commandStack.undo();
|
||||
commandStack.redo();
|
||||
|
||||
// then
|
||||
expect(getBounds(participant)).to.eql({ x: 122, y: 51, width: 496, height: 287 });
|
||||
|
||||
}));
|
||||
|
||||
});
|
||||
|
||||
describe('after appending', function(){
|
||||
|
||||
|
||||
it('should expand the bottom right edges of the parent collaboration',
|
||||
inject(function(modeling) {
|
||||
|
||||
// when
|
||||
modeling.appendShape(task, { type: 'bpmn:Task' }, { x: 660, y: 350 }, participant);
|
||||
|
||||
// then
|
||||
assign(expectedBounds, { width: 563, height: 310 });
|
||||
|
||||
expect(getBounds(participant)).to.eql(expectedBounds);
|
||||
|
||||
}));
|
||||
|
||||
|
||||
it('should undo resizing', inject(function(modeling, commandStack) {
|
||||
// given
|
||||
modeling.appendShape(task, { type: 'bpmn:Task' }, { x: 660, y: 250 }, participant);
|
||||
|
||||
// when
|
||||
commandStack.undo();
|
||||
|
||||
// then
|
||||
expect(getBounds(participant)).to.eql(expectedBounds);
|
||||
|
||||
}));
|
||||
|
||||
|
||||
it('should redo resizing and restore shapes and connections',
|
||||
inject(function(modeling, commandStack) {
|
||||
|
||||
// given
|
||||
var task2 = modeling.appendShape(task, { type: 'bpmn:Task' }, { x: 660, y: 250 }, participant);
|
||||
|
||||
// when
|
||||
commandStack.undo();
|
||||
commandStack.redo();
|
||||
|
||||
// then
|
||||
assign(expectedBounds, { width: 563 });
|
||||
|
||||
expect(getBounds(participant)).to.eql(expectedBounds);
|
||||
|
||||
expect(task2).to.be.defined;
|
||||
expect(task.outgoing).not.to.be.empty;
|
||||
expect(task2.incoming).not.to.be.empty;
|
||||
|
||||
}));
|
||||
|
||||
});
|
||||
|
||||
});
|
Loading…
Reference in New Issue