feat(modeling): snap initial participant to diagram contents

Closes #241
This commit is contained in:
Nico Rehwaldt 2015-04-15 09:06:34 +02:00
parent 0381811d03
commit de648520d5
5 changed files with 283 additions and 18 deletions

View File

@ -70,6 +70,7 @@ Modeler.prototype._modelingModules = [
require('diagram-js/lib/features/resize'),
require('diagram-js/lib/features/space-tool'),
require('diagram-js/lib/features/lasso-tool'),
require('./features/snapping'),
require('./features/modeling'),
require('./features/context-pad'),
require('./features/palette')

View File

@ -4,8 +4,6 @@ var inherits = require('inherits');
var CommandInterceptor = require('diagram-js/lib/command/CommandInterceptor');
var getBoundingBox = require('diagram-js/lib/util/Elements').getBBox,
assign = require('lodash/object/assign');
/**
* BPMN specific create behavior
@ -29,22 +27,6 @@ function CreateBehavior(eventBus, modeling) {
if (parent.businessObject.$instanceOf('bpmn:Process') &&
shape.businessObject.$instanceOf('bpmn:Participant')) {
// update shape size + position of new participant to
// encapsulate all existing children
if (parent.children.length) {
var bbox = getBoundingBox(parent.children);
assign(shape, {
width: bbox.width + 60,
height: bbox.height + 40
});
position = {
x: bbox.x - 40 + shape.width / 2,
y: bbox.y - 20 + shape.height / 2
};
}
// this is going to detach the process root
// and set the returned collaboration element
// as the new root element

View File

@ -0,0 +1,110 @@
'use strict';
var getBoundingBox = require('diagram-js/lib/util/Elements').getBBox;
/**
* BPMN specific snapping functionality
*
* * snap on process elements if a pool is created inside a
* process diagram
*
* @param {EventBus} eventBus
* @param {Canvas} canvas
*/
function BpmnSnapping(eventBus, canvas) {
/**
* Drop articipant on process <> process elements snapping
*/
function initParticipantSnapping(context, shape, elements) {
if (!elements.length) {
return;
}
var snapBox = getBoundingBox(elements.filter(function(e) {
return !e.labelTarget && !e.waypoints;
}));
snapBox.x -= 50;
snapBox.y -= 20;
snapBox.width += 70;
snapBox.height += 40;
// adjust shape height to include bounding box
shape.width = Math.max(shape.width, snapBox.width);
shape.height = Math.max(shape.height, snapBox.height);
context.participantSnapBox = snapBox;
}
function snapParticipant(snapBox, shape, event) {
var shapeHalfWidth = shape.width / 2 - 30,
shapeHalfHeight = shape.height / 2;
var currentTopLeft = {
x: event.x - shapeHalfWidth - 30,
y: event.y - shapeHalfHeight
};
var currentBottomRight = {
x: event.x + shapeHalfWidth + 30,
y: event.y + shapeHalfHeight
};
var snapTopLeft = snapBox,
snapBottomRight = {
x: snapBox.x + snapBox.width,
y: snapBox.y + snapBox.height
};
if (currentTopLeft.x >= snapTopLeft.x) {
event.x = snapTopLeft.x + 30 + shapeHalfWidth;
event.snapping = true;
} else
if (currentBottomRight.x <= snapBottomRight.x) {
event.x = snapBottomRight.x - 30 - shapeHalfWidth;
event.snapping = true;
}
if (currentTopLeft.y >= snapTopLeft.y) {
event.y = snapTopLeft.y + shapeHalfHeight;
event.snapping = true;
} else
if (currentBottomRight.y <= snapBottomRight.y) {
event.y = snapBottomRight.y - shapeHalfHeight;
event.snapping = true;
}
}
eventBus.on('create.start', function(event) {
var context = event.context,
shape = context.shape,
rootElement = canvas.getRootElement();
// snap participant around existing elements (if any)
if (shape.businessObject.$instanceOf('bpmn:Participant') &&
rootElement.businessObject.$instanceOf('bpmn:Process')) {
initParticipantSnapping(context, shape, rootElement.children);
}
});
eventBus.on([ 'create.move', 'create.end' ], 1500, function(event) {
var context = event.context,
shape = context.shape,
participantSnapBox = context.participantSnapBox;
if (!event.snapping && participantSnapBox) {
snapParticipant(participantSnapBox, shape, event);
}
});
}
BpmnSnapping.$inject = [ 'eventBus', 'canvas' ];
module.exports = BpmnSnapping;

View File

@ -0,0 +1,4 @@
module.exports = {
__init__: [ 'bpmnSnapping' ],
bpmnSnapping: [ 'type', require('./BpmnSnapping') ]
};

View File

@ -0,0 +1,168 @@
'use strict';
var TestHelper = require('../../../TestHelper');
/* global bootstrapModeler, inject */
var Events = require('diagram-js/test/util/Events');
var coreModule = require('../../../../lib/core'),
snappingModule = require('../../../../lib/features/snapping'),
modelingModule = require('../../../../lib/features/modeling'),
createModule = require('diagram-js/lib/features/create'),
rulesModule = require('../../../../lib/features/modeling/rules');
var pick = require('lodash/object/pick');
function bounds(element) {
return pick(element, [ 'width', 'height', 'x', 'y' ]);
}
describe('features/snapping - BpmnSnapping', function() {
var testModules = [ coreModule, snappingModule, modelingModule, createModule, rulesModule ];
describe('when dropping on non-empty process', function() {
var diagramXML = require('../../../fixtures/bpmn/collaboration/process.bpmn');
beforeEach(bootstrapModeler(diagramXML, { modules: testModules }));
var createEvent;
beforeEach(inject(function(canvas, dragging) {
createEvent = Events.scopedCreate(canvas);
dragging.setOptions({ manual: true });
}));
it('should snap to process children bounds / top left',
inject(function(canvas, create, dragging, elementFactory) {
// given
var participantShape = elementFactory.createParticipantShape(false),
rootElement = canvas.getRootElement(),
rootGfx = canvas.getGraphics(rootElement);
// when
create.start(createEvent({ x: 50, y: 50 }), participantShape);
dragging.hover({ element: rootElement, gfx: rootGfx });
dragging.move(createEvent({ x: 65, y: 65 }));
dragging.end(createEvent({ x: 65, y: 65 }));
// then
expect(bounds(participantShape)).toEqual({
width: 600, height: 300, x: 18, y: -58
});
}));
it('should snap to process children bounds / bottom right',
inject(function(canvas, create, dragging, elementFactory) {
// given
var participantShape = elementFactory.createParticipantShape(false),
rootElement = canvas.getRootElement(),
rootGfx = canvas.getGraphics(rootElement);
// when
create.start(createEvent({ x: 50, y: 50 }), participantShape);
dragging.hover({ element: rootElement, gfx: rootGfx });
dragging.move(createEvent({ x: 400, y: 400 }));
dragging.end(createEvent({ x: 400, y: 400 }));
// then
expect(bounds(participantShape)).toEqual({
width: 600, height: 300, x: 100, y: 52
});
}));
});
describe('when dropping on empty process', function() {
var diagramXML = require('../../../fixtures/bpmn/collaboration/process-empty.bpmn');
beforeEach(bootstrapModeler(diagramXML, { modules: testModules }));
var createEvent;
beforeEach(inject(function(canvas, dragging) {
createEvent = Events.scopedCreate(canvas);
dragging.setOptions({ manual: true });
}));
it('should not snap', inject(function(canvas, create, dragging, elementFactory) {
// given
var participantShape = elementFactory.createParticipantShape(false),
rootElement = canvas.getRootElement(),
rootGfx = canvas.getGraphics(rootElement);
// when
create.start(createEvent({ x: 50, y: 50 }), participantShape);
dragging.hover({ element: rootElement, gfx: rootGfx });
dragging.move(createEvent({ x: 400, y: 400 }));
dragging.end(createEvent({ x: 400, y: 400 }));
// then
expect(bounds(participantShape)).toEqual({
width: 600, height: 300, x: 100, y: 250
});
}));
});
describe('when dropping on collaboration', function() {
var diagramXML = require('../../../fixtures/bpmn/collaboration/collaboration-participant.bpmn');
beforeEach(bootstrapModeler(diagramXML, { modules: testModules }));
var createEvent;
beforeEach(inject(function(canvas, dragging) {
createEvent = Events.scopedCreate(canvas);
dragging.setOptions({ manual: true });
}));
it('should not snap', inject(function(canvas, create, dragging, elementFactory) {
// given
var participantShape = elementFactory.createParticipantShape(false),
rootElement = canvas.getRootElement(),
rootGfx = canvas.getGraphics(rootElement);
// when
create.start(createEvent({ x: 50, y: 50 }), participantShape);
dragging.hover({ element: rootElement, gfx: rootGfx });
dragging.move(createEvent({ x: 400, y: 400 }));
dragging.end(createEvent({ x: 400, y: 400 }));
// then
expect(bounds(participantShape)).toEqual({
width: 600, height: 300, x: 100, y: 250
});
}));
});
});