Skip to content

Commit

Permalink
feat(replace): update incoming and outgoing anchors
Browse files Browse the repository at this point in the history
* re-compute correct incoming and outgoing anchors when replacing a
shape with different bounds
* make it possible for Replace to take bounds

Related to bpmn-io/bpmn-js#1265
  • Loading branch information
nikku authored and philippfromme committed Jan 15, 2020
1 parent 553b617 commit ba5a5fb
Show file tree
Hide file tree
Showing 3 changed files with 99 additions and 27 deletions.
31 changes: 23 additions & 8 deletions lib/features/modeling/cmd/ReplaceShapeHandler.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import { forEach } from 'min-dash';

import {
getResizedSourceAnchor,
getResizedTargetAnchor
} from './helper/AnchorsHelper';

/**
* Replace shape by adding new shape and removing old shape. Incoming and outgoing connections will
Expand Down Expand Up @@ -54,6 +58,13 @@ ReplaceShapeHandler.prototype.preExecute = function(context) {
y: newData.y
};

var oldBounds = {
x: oldShape.x,
y: oldShape.y,
width: oldShape.width,
height: oldShape.height
};

newShape = context.newShape =
context.newShape ||
self.createShape(newData, position, oldShape.parent, hints);
Expand All @@ -77,24 +88,28 @@ ReplaceShapeHandler.prototype.preExecute = function(context) {
outgoing = oldShape.outgoing.slice();

forEach(incoming, function(connection) {
var waypoints = connection.waypoints,
docking = waypoints[ waypoints.length - 1 ],
source = connection.source,
var source = connection.source,
allowed = canReconnect(source, newShape, connection);

if (allowed) {
self.reconnectEnd(connection, newShape, docking, hints);
self.reconnectEnd(
connection, newShape,
getResizedTargetAnchor(connection, newShape, oldBounds),
hints
);
}
});

forEach(outgoing, function(connection) {
var waypoints = connection.waypoints,
docking = waypoints[ 0 ],
target = connection.target,
var target = connection.target,
allowed = canReconnect(newShape, target, connection);

if (allowed) {
self.reconnectStart(connection, newShape, docking, hints);
self.reconnectStart(
connection, newShape,
getResizedSourceAnchor(connection, newShape, oldBounds),
hints
);
}
});
};
Expand Down
50 changes: 34 additions & 16 deletions lib/features/replace/Replace.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
import {
assign
} from 'min-dash';

var round = Math.round;

/**
Expand All @@ -12,31 +16,45 @@ Replace.$inject = [ 'modeling' ];

/**
* @param {Element} oldElement - Element to be replaced
* @param {Object} newElementData - Containing information about the new Element, for example height, width, type.
* @param {Object} newElementData - Containing information about the new element,
* for example the new bounds and type.
* @param {Object} options - Custom options that will be attached to the context. It can be used to inject data
* that is needed in the command chain. For example it could be used in
* eventbus.on('commandStack.shape.replace.postExecute') to change shape attributes after
* shape creation.
*/
Replace.prototype.replaceElement = function(oldElement, newElementData, options) {

var modeling = this._modeling;

var newElement = null;

if (oldElement.waypoints) {

// TODO
// modeling.replaceConnection
} else {

// set center of element for modeling API
// if no new width / height is given use old elements size
newElementData.x = round(oldElement.x + (newElementData.width || oldElement.width) / 2);
newElementData.y = round(oldElement.y + (newElementData.height || oldElement.height) / 2);

newElement = modeling.replaceShape(oldElement, newElementData, options);
// TODO(nikku): we do not replace connections, yet
return null;
}

return newElement;
var modeling = this._modeling;

var width = newElementData.width || oldElement.width,
height = newElementData.height || oldElement.height,
x = newElementData.x || oldElement.x,
y = newElementData.y || oldElement.y,
centerX = round(x + width / 2),
centerY = round(y + height / 2);

// modeling API requires center coordinates,
// account for that when handling shape bounds

return modeling.replaceShape(
oldElement,
assign(
{},
newElementData,
{
x: centerX,
y: centerY,
width: width,
height: height
}
),
options
);
};
45 changes: 42 additions & 3 deletions test/spec/features/replace/ReplaceSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,30 @@ describe('features/replace', function() {
expect(replacementShape.height).to.equal(201);
}));


it('should replace with new bounds', inject(function(elementFactory, replace, elementRegistry) {

// given
var replacement = {
id: 'replacement',
width: 200,
height: 200,
x: 10,
y: 50
};

// shape replacement
var replacedShape = replace.replaceElement(originalShape, replacement);

// then
expect(replacedShape).to.have.bounds({
width: 200,
height: 200,
x: 10,
y: 50
});
}));

});


Expand Down Expand Up @@ -234,20 +258,28 @@ describe('features/replace', function() {
// canvas.addConnection(connection);
}));


it('should reconnect start', inject(function(elementFactory, replace, elementRegistry) {

// given
var replacement = {
id: 'replacement',
width: 120,
height: 120
height: 120,
x: 80,
y: 80
};

// when
var replacedShape = replace.replaceElement(sourceShape, replacement);

// then
expect(replacedShape.outgoing[0]).to.exist;
expect(replacedShape.outgoing[0]).to.eql(connection);

expect(connection).to.have.waypoints([
{ x: 140, y: 140 },
{ x: 340, y: 160 }
]);
}));


Expand All @@ -256,6 +288,8 @@ describe('features/replace', function() {
// given
var replacement = {
id: 'replacement',
x: 280,
y: 120,
width: 80,
height: 80
};
Expand All @@ -264,7 +298,12 @@ describe('features/replace', function() {
var replacedShape = replace.replaceElement(targetShape, replacement);

// then
expect(replacedShape.incoming[0]).to.exist;
expect(replacedShape.incoming[0]).to.eql(connection);

expect(connection).to.have.waypoints([
{ x: 160, y: 160 },
{ x: 320, y: 160 }
]);
}));


Expand Down

0 comments on commit ba5a5fb

Please sign in to comment.