Skip to content

Commit

Permalink
fix(space-tool): move and resize shapes in correct order
Browse files Browse the repository at this point in the history
  • Loading branch information
philippfromme committed Jun 14, 2019
1 parent 9833cd8 commit 045e55a
Show file tree
Hide file tree
Showing 2 changed files with 255 additions and 23 deletions.
147 changes: 124 additions & 23 deletions lib/features/modeling/cmd/SpaceToolHandler.js
Original file line number Diff line number Diff line change
@@ -1,48 +1,149 @@
import { forEach } from 'min-dash';
import {
forEach,
groupBy,
keys
} from 'min-dash';

import {
resizeBounds
} from '../../space-tool/SpaceUtil';


/**
* A handler that implements reversible creating and removing of space.
*
* It executes in two phases:
*
* (1) resize all affected resizeShapes
* (2) move all affected moveElements
* Add or remove space by moving and resizing shapes.
*/
export default function SpaceToolHandler(modeling) {
this._modeling = modeling;
}

SpaceToolHandler.$inject = [ 'modeling' ];

SpaceToolHandler.$inject = [
'modeling'
];

SpaceToolHandler.prototype.preExecute = function(context) {

// resize
var modeling = this._modeling,
var self = this,
movingShapes = context.movingShapes,
resizingShapes = context.resizingShapes,
delta = context.delta,
direction = context.direction;

forEach(resizingShapes, function(shape) {
var newBounds = resizeBounds(shape, direction, delta);
var addingSpace = isAddingSpace(delta, direction);

var steps = getSteps(movingShapes, resizingShapes);

if (!addingSpace) {
steps = steps.reverse();
}

modeling.resizeShape(shape, newBounds);
steps.forEach(function(step) {
var type = step.type,
shapes = step.shapes;

if (type === 'resize') {
self.resizeShapes(shapes, delta, direction);
} else if (type === 'move') {
self.moveShapes(shapes, delta);
}
});
};

SpaceToolHandler.prototype.postExecute = function(context) {
// move
var modeling = this._modeling,
movingShapes = context.movingShapes,
delta = context.delta;
SpaceToolHandler.prototype.execute = function() {};
SpaceToolHandler.prototype.revert = function() {};

modeling.moveElements(movingShapes, delta, undefined, { autoResize: false, attach: false });
SpaceToolHandler.prototype.moveShapes = function(shapes, delta) {
this._modeling.moveElements(shapes, delta, null, {
autoResize: false,
recurse: false
});
};

SpaceToolHandler.prototype.execute = function(context) {};
SpaceToolHandler.prototype.revert = function(context) {};
SpaceToolHandler.prototype.resizeShapes = function(shapes, delta, direction) {
var self = this;

forEach(shapes, function(shape) {
var newBounds = resizeBounds(shape, direction, delta);

self._modeling.resizeShape(shape, newBounds);
});
};



// helpers //////////

function isAddingSpace(delta, direction) {
if (direction === 'n') {
return delta.y < 0;
} else if (direction === 'w') {
return delta.x < 0;
} else if (direction === 's') {
return delta.y >= 0;
} else if (direction === 'e') {
return delta.x >= 0;
}
}

/**
* Get steps for moving and resizing shapes starting with top-level shapes.
*
* @param {Array<djs.model.Shape>} movingShapes
* @param {Array<djs.model.Shape>} resizingShapes
*
* @returns {Array<Object>}
*/
export function getSteps(movingShapes, resizingShapes) {
var steps = [];

var groupedMovingShapes = groupBy(movingShapes, getIndex),
groupedResizingShapes = groupBy(resizingShapes, getIndex);

var maxIndex = max(keys(groupedMovingShapes).concat(keys(groupedResizingShapes)).concat(0));

var index = 1;

while (index <= maxIndex) {
if (groupedMovingShapes[ index ]) {

if (groupedMovingShapes[ index ]) {
steps.push({
type: 'move',
shapes: groupedMovingShapes[ index ]
});
}
}

if (groupedResizingShapes[ index ]) {
steps.push({
type: 'resize',
shapes: groupedResizingShapes[ index ]
});
}

index++;
}

return steps;
}

/**
* Get index of a given shape.
*
* @param {djs.model.Shape} shape
*
* @returns {number}
*/
function getIndex(shape) {
var index = 0;

while (shape.parent) {
index++;

shape = shape.parent;
}

return index;
}

function max(array) {
return Math.max.apply(null, array);
}
131 changes: 131 additions & 0 deletions test/spec/features/space-tool/SpaceToolSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import autoResizeModule from 'lib/features/auto-resize';
import rulesModule from './rules';
import autoResizeProviderModule from './auto-resize';

import { getSteps } from 'lib/features/modeling/cmd/SpaceToolHandler';

import { isMac } from 'lib/util/Platform';

var keyModifier = isMac() ? { metaKey: true } : { ctrlKey: true };
Expand Down Expand Up @@ -629,6 +631,135 @@ describe('features/space-tool', function() {
});


describe('steps', function() {

beforeEach(bootstrapDiagram());

var level1shape1,
level1shape2,
level1shape2label,
level2shape1,
level3shape1,
level3shape1label,
level3shape2,
level3connection,
level3connectionLabel;

beforeEach(inject(function(elementFactory, canvas) {

level1shape1 = elementFactory.createShape({
id: 'level1shape1',
x: 100, y: 100,
width: 450, height: 200
});

canvas.addShape(level1shape1);

level1shape2 = elementFactory.createShape({
id: 'level1shape2',
x: 400, y: 350,
width: 100, height: 50
});

canvas.addShape(level1shape2);

level1shape2label = elementFactory.createLabel({
id: 'level1shape2label',
x: 425, y: 425,
width: 50, height: 20,
labelTarget: level1shape2
});

canvas.addShape(level1shape2label);

level2shape1 = elementFactory.createShape({
id: 'level2shape1',
x: 125, y: 125,
width: 400, height: 150
});

canvas.addShape(level2shape1, level1shape1);

level3shape1 = elementFactory.createShape({
id: 'level3shape1',
x: 150, y: 150,
width: 100, height: 50
});

canvas.addShape(level3shape1, level2shape1);

level3shape1label = elementFactory.createLabel({
id: 'level3shape1label',
x: 175, y: 225,
width: 50, height: 20,
labelTarget: level3shape1
});

canvas.addShape(level3shape1label, level2shape1);

level3shape2 = elementFactory.createShape({
id: 'level3shape2',
x: 400, y: 150,
width: 100, height: 50
});

canvas.addShape(level3shape2, level2shape1);

level3connection = elementFactory.createConnection({
id: 'level3connection',
source: level3shape1,
target: level3shape2,
waypoints: [
{ x: 250, y: 175 },
{ x: 400, y: 175 }
]
});

canvas.addConnection(level3connection, level2shape1);

level3connectionLabel = elementFactory.createLabel({
id: 'level3connectionLabel',
x: 300, y: 200,
width: 50, height: 20,
labelTarget: level3shape1
});

canvas.addShape(level3connectionLabel, level2shape1);
}));


it('should return steps', function() {

// given
var movingShapes = [
level1shape2,
level1shape2label,
level3shape2,
level3connectionLabel
];

var resizingShapes = [
level1shape1,
level2shape1
];

// when
var steps = getSteps(movingShapes, resizingShapes);

// then
expect(steps).to.have.length(4);

expect(steps).to.eql([
{ type: 'move', shapes: [ level1shape2, level1shape2label ] },
{ type: 'resize', shapes: [ level1shape1 ] },
{ type: 'resize', shapes: [ level2shape1 ] },
{ type: 'move', shapes: [ level3shape2, level3connectionLabel ] }
]);
});

});


describe('redo / undo integration', function() {

beforeEach(bootstrapDiagram({
Expand Down

0 comments on commit 045e55a

Please sign in to comment.