Skip to content

Commit

Permalink
feat(modeling): update information requirements on elements move
Browse files Browse the repository at this point in the history
  • Loading branch information
philippfromme committed Apr 3, 2020
1 parent dd79b4c commit 6a8071b
Show file tree
Hide file tree
Showing 2 changed files with 263 additions and 44 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,23 @@ import inherits from 'inherits';

import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';

import { is } from 'dmn-js-shared/lib/util/ModelUtil';
import {
is,
isAny
} from 'dmn-js-shared/lib/util/ModelUtil';

import {
asTRBL,
getMid,
getOrientation
} from 'diagram-js/lib/layout/LayoutUtil';

import { assign } from 'min-dash';
import {
assign,
forEach
} from 'min-dash';

var LOW_PRIORITY = 500;


export default function LayoutConnectionBehavior(injector, layouter, modeling, rules) {
Expand Down Expand Up @@ -47,53 +56,65 @@ export default function LayoutConnectionBehavior(injector, layouter, modeling, r
assign(context.hints, getConnectionHints(source, target, orientation));
}, true);

/**
* Update incoming information requirements.
*
* @param {djs.model.Shape} target
* @param {Array<djs.model.Connection>} [informationRequirements]
* @param {string} [orientation]
*/
function updateInformationRequirements(target, informationRequirements, orientation) {

// (1) sort information requirements
informationRequirements = sortInformationRequirements(
informationRequirements,
orientation
);
// (1) get information requirements
if (!informationRequirements) {
informationRequirements = target.incoming.filter(function(incoming) {
return is(incoming, 'dmn:InformationRequirement');
});
}

// (2) get new docking points
var dockingPoints = informationRequirements.map(function(_, index) {
if (orientation.includes('bottom')) {
return {
x: target.x + target.width / (informationRequirements.length + 1) * (index + 1),
y: target.y + target.height
};
} else if (orientation.includes('top')) {
return {
x: target.x + target.width / (informationRequirements.length + 1) * (index + 1),
y: target.y
};
} else if (orientation.includes('right')) {
return {
x: target.x + target.width,
y: target.y + target.height / (informationRequirements.length + 1) * (index + 1)
};
} else {
return {
x: target.x,
y: target.y + target.height / (informationRequirements.length + 1) * (index + 1)
};
}
});
var incomingInformationRequirementsByOrientation = {};

// (2) get information requirements per orientation
if (orientation) {
incomingInformationRequirementsByOrientation[ orientation ] =
informationRequirements;
} else {
incomingInformationRequirementsByOrientation =
getInformationRequirementsByOrientation(target, informationRequirements);
}

// (4) reconnection information requirements
informationRequirements.forEach((informationRequirement, index) => {
var dockingPoint = dockingPoints[ index ];
// (3) update information requirements per orientation
forEach(incomingInformationRequirementsByOrientation,
function(informationRequirements, orientation) {

var waypoints = layouter.layoutConnection(informationRequirement, {
connectionStart: informationRequirement.waypoints[ 0 ],
connectionEnd: dockingPoint
});
// (3.1) sort information requirements
informationRequirements = sortInformationRequirements(
informationRequirements,
orientation
);

modeling.updateWaypoints(informationRequirement, waypoints);
});
// (3.2) get new connection start and end
var connectionStartEnd =
getConnectionsStartEnd(informationRequirements, target, orientation);

// (3.3) update information requirements
informationRequirements.forEach((informationRequirement, index) => {
var connectionStart = connectionStartEnd[ index ].start,
connectionEnd = connectionStartEnd[ index ].end;

var waypoints = layouter.layoutConnection(informationRequirement, {
connectionStart: connectionStart,
connectionEnd: connectionEnd
});

modeling.updateWaypoints(informationRequirement, waypoints);
});
}
);
}

// lay out information requirements on connection create and delete and reconnect
// update information requirements on connection create and delete
// update information requirements of new target on connection reconnect
this.postExecuted([
'connection.create',
'connection.delete',
Expand All @@ -111,8 +132,10 @@ export default function LayoutConnectionBehavior(injector, layouter, modeling, r

// update all information requirements with same orientation
var informationRequirements = target.incoming.filter(incoming => {
var incomingOrientation = getOrientation(incoming.source, incoming.target);

return is(incoming, 'dmn:InformationRequirement')
&& sameOrientation(getOrientation(incoming.source, incoming.target), orientation);
&& isSameOrientation(incomingOrientation, orientation);
});

if (!informationRequirements.length) {
Expand All @@ -122,6 +145,7 @@ export default function LayoutConnectionBehavior(injector, layouter, modeling, r
updateInformationRequirements(target, informationRequirements, orientation);
}, true);

// update information requirements of old target on connection reconnect
this.preExecute('connection.reconnect', function(context) {
var connection = context.connection,
source = connection.source,
Expand All @@ -135,9 +159,11 @@ export default function LayoutConnectionBehavior(injector, layouter, modeling, r

// update all information requirements with same orientation except reconnected
var informationRequirements = target.incoming.filter(incoming => {
var incomingOrientation = getOrientation(incoming.source, incoming.target);

return incoming !== connection
&& is(incoming, 'dmn:InformationRequirement')
&& sameOrientation(getOrientation(incoming.source, incoming.target), orientation);
&& isSameOrientation(incomingOrientation, orientation);
});

if (!informationRequirements.length) {
Expand All @@ -146,6 +172,39 @@ export default function LayoutConnectionBehavior(injector, layouter, modeling, r

updateInformationRequirements(target, informationRequirements, orientation);
}, true);

// update information requirements on elements move
this.postExecuted('elements.move', LOW_PRIORITY, function(context) {
var shapes = context.shapes,
closure = context.closure,
enclosedConnections = closure.enclosedConnections;

shapes.forEach(function(shape) {
if (!isAny(shape, [ 'dmn:Decision', 'dmn:InputData' ])) {
return;
}

// (1) update incoming information requirements
var incomingInformationRequirements = shape.incoming.filter(function(incoming) {
return is(incoming, 'dmn:InformationRequirement')
&& !enclosedConnections[ incoming.id ];
});

if (incomingInformationRequirements.length) {
updateInformationRequirements(shape, incomingInformationRequirements);
}

// (2) update outgoing information requirements
shape.outgoing.forEach(function(outgoing) {
if (!is(outgoing, 'dmn:InformationRequirement')
|| enclosedConnections[ outgoing.id ]) {
return;
}

updateInformationRequirements(outgoing.target);
});
});
}, true);
}

LayoutConnectionBehavior.$inject = [
Expand Down Expand Up @@ -183,7 +242,100 @@ function getConnectionHints(source, target, orientation) {
};
}

function sameOrientation(orientationA, orientationB) {
/**
* Get connections start and end based on number of information requirements and
* orientation.
*
* @param {Array<djs.model.Connection>} informationRequirements
* @param {djs.model.Shape} target
* @param {string} orientation
*
* @returns {Array<Object>}
*/
function getConnectionsStartEnd(informationRequirements, target, orientation) {
return informationRequirements.map(
function(informationRequirement, index) {
var source = informationRequirement.source,
sourceMid = getMid(source),
sourceTrbl = asTRBL(source),
targetTrbl = asTRBL(target);

var length = informationRequirements.length;

if (orientation.includes('bottom')) {
return {
start: {
x: sourceMid.x,
y: sourceTrbl.top
},
end: {
x: targetTrbl.left + target.width / (length + 1) * (index + 1),
y: targetTrbl.bottom
}
};
} else if (orientation.includes('top')) {
return {
start: {
x: sourceMid.x,
y: sourceTrbl.bottom
},
end: {
x: targetTrbl.left + target.width / (length + 1) * (index + 1),
y: targetTrbl.top
}
};
} else if (orientation.includes('right')) {
return {
start: {
x: sourceTrbl.left,
y: sourceMid.y
},
end: {
x: targetTrbl.right,
y: targetTrbl.top + target.height / (length + 1) * (index + 1)
}
};
} else {
return {
start: {
x: sourceTrbl.right,
y: sourceMid.y
},
end: {
x: targetTrbl.left,
y: targetTrbl.top + target.height / (length + 1) * (index + 1)
}
};
}
}
);
}

/**
* Get information requirements by orientation.
*
* @param {djs.model.shape} target
* @param {Array<djs.model.Connection>} informationRequirements
*
* @returns {Object}
*/
function getInformationRequirementsByOrientation(target, informationRequirements) {
var incomingInformationRequirementsByOrientation = {};

informationRequirements.forEach(function(incoming) {
var orientation = getOrientation(incoming.source, target).split('-').shift();

if (!incomingInformationRequirementsByOrientation[ orientation ]) {
incomingInformationRequirementsByOrientation[ orientation ] = [];
}

incomingInformationRequirementsByOrientation[ orientation ].push(incoming);
});

return incomingInformationRequirementsByOrientation;
}

function isSameOrientation(orientationA, orientationB) {
return orientationA
&& orientationB
&& orientationA.split('-').shift() === orientationB.split('-').shift();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,73 @@ describe('features/modeling - layout connection', function() {

});


describe('elements move', function() {

it('should update information requirements', inject(
function(elementRegistry, modeling) {

// given
var decision2 = elementRegistry.get('Decision_2'),
decision4 = elementRegistry.get('Decision_4'),
informationRequirement = elementRegistry.get('InformationRequirement_1');

var connection = modeling.connect(decision2, decision4);

// when
modeling.moveElements([ decision4 ], { x: -250, y: -150 });

// then
expect(informationRequirement.waypoints).to.eql([
{
original: {
x: 670,
y: 230
},
x: 670,
y: 230
},
{
x: 720,
y: 220
},
{
original: {
x: 740,
y: 220
},
x: 740,
y: 220
}
]);

expect(connection.waypoints).to.eql([
{
original: {
x: 610,
y: 340
},
x: 610,
y: 340
},
{
x: 580,
y: 290
},
{
original: {
x: 580,
y: 270
},
x: 580,
y: 270
}
]);
}
));

});

});

});
Expand Down

0 comments on commit 6a8071b

Please sign in to comment.