feat(grid-snapping): integrate with connection layout
Closes #1010 Related to #973
This commit is contained in:
parent
e4fe8c239b
commit
13f1e05ee7
|
@ -0,0 +1,133 @@
|
|||
import inherits from 'inherits';
|
||||
|
||||
import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';
|
||||
|
||||
import { pointsAligned } from 'diagram-js/lib/util/Geometry';
|
||||
|
||||
var HIGH_PRIORITY = 3000;
|
||||
|
||||
|
||||
/**
|
||||
* Snaps connections with Manhattan layout.
|
||||
*/
|
||||
export default function LayoutConnectionBehavior(eventBus, gridSnapping, modeling) {
|
||||
CommandInterceptor.call(this, eventBus);
|
||||
|
||||
this._gridSnapping = gridSnapping;
|
||||
|
||||
var self = this;
|
||||
|
||||
this.postExecuted([
|
||||
'connection.create',
|
||||
'connection.layout'
|
||||
], HIGH_PRIORITY, function(event) {
|
||||
var context = event.context,
|
||||
connection = context.connection,
|
||||
waypoints = connection.waypoints;
|
||||
|
||||
if (hasMiddleSegments(waypoints)) {
|
||||
modeling.updateProperties(connection, {
|
||||
waypoints: self.snapMiddleSegments(waypoints)
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
LayoutConnectionBehavior.$inject = [
|
||||
'eventBus',
|
||||
'gridSnapping',
|
||||
'modeling'
|
||||
];
|
||||
|
||||
inherits(LayoutConnectionBehavior, CommandInterceptor);
|
||||
|
||||
/**
|
||||
* Snap middle segments of a given connection.
|
||||
*
|
||||
* @param {Array<Point>} waypoints
|
||||
*
|
||||
* @returns {Array<Point>}
|
||||
*/
|
||||
LayoutConnectionBehavior.prototype.snapMiddleSegments = function(waypoints) {
|
||||
var gridSnapping = this._gridSnapping;
|
||||
|
||||
var middleSegments = getMiddleSegments(waypoints);
|
||||
|
||||
middleSegments.forEach(function(middleSegment) {
|
||||
var segmentStart = middleSegment.start,
|
||||
segmentEnd = middleSegment.end;
|
||||
|
||||
var aligned = pointsAligned(segmentStart, segmentEnd);
|
||||
|
||||
if (horizontallyAligned(aligned)) {
|
||||
|
||||
// snap horizontally
|
||||
segmentStart.x = segmentEnd.x = gridSnapping.snapValue(segmentStart.x);
|
||||
}
|
||||
|
||||
if (verticallyAligned(aligned)) {
|
||||
|
||||
// snap vertically
|
||||
segmentStart.y = segmentEnd.y = gridSnapping.snapValue(segmentStart.y);
|
||||
}
|
||||
});
|
||||
|
||||
return waypoints;
|
||||
};
|
||||
|
||||
|
||||
|
||||
// helpers //////////
|
||||
|
||||
/**
|
||||
* Check wether a connection has a middle segments.
|
||||
*
|
||||
* @param {Array} waypoints
|
||||
*
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function hasMiddleSegments(waypoints) {
|
||||
return waypoints.length > 3;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check wether an alignment is horizontal.
|
||||
*
|
||||
* @param {string} aligned
|
||||
*
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function horizontallyAligned(aligned) {
|
||||
return aligned === 'h';
|
||||
}
|
||||
|
||||
/**
|
||||
* Check wether an alignment is vertical.
|
||||
*
|
||||
* @param {string} aligned
|
||||
*
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function verticallyAligned(aligned) {
|
||||
return aligned === 'v';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get middle segments from a given connection.
|
||||
*
|
||||
* @param {Array} waypoints
|
||||
*
|
||||
* @returns {Array}
|
||||
*/
|
||||
function getMiddleSegments(waypoints) {
|
||||
var middleSegments = [];
|
||||
|
||||
for (var i = 1; i < waypoints.length - 2; i++) {
|
||||
middleSegments.push({
|
||||
start: waypoints[i],
|
||||
end: waypoints[i + 1]
|
||||
});
|
||||
}
|
||||
|
||||
return middleSegments;
|
||||
}
|
|
@ -1,8 +1,11 @@
|
|||
import AutoPlaceBehavior from './AutoPlaceBehavior';
|
||||
import LayoutConnectionBehavior from './LayoutConnectionBehavior';
|
||||
|
||||
export default {
|
||||
__init__: [
|
||||
'gridSnappingLayoutConnectionBehavior',
|
||||
'gridSnappingAutoPlaceBehavior'
|
||||
],
|
||||
gridSnappingAutoPlaceBehavior: [ 'type', AutoPlaceBehavior ]
|
||||
gridSnappingAutoPlaceBehavior: [ 'type', AutoPlaceBehavior ],
|
||||
gridSnappingLayoutConnectionBehavior: [ 'type', LayoutConnectionBehavior ]
|
||||
};
|
|
@ -0,0 +1,29 @@
|
|||
<?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:dc="http://www.omg.org/spec/DD/20100524/DC" id="Definitions_1" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="3.0.0">
|
||||
<bpmn:process id="Process_1" isExecutable="true">
|
||||
<bpmn:task id="Task_1" />
|
||||
<bpmn:task id="Task_2" />
|
||||
<bpmn:task id="Task_3" />
|
||||
<bpmn:task id="Task_4" />
|
||||
<bpmn:boundaryEvent id="BoundaryEvent_1" attachedToRef="Task_3" />
|
||||
</bpmn:process>
|
||||
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
|
||||
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_1">
|
||||
<bpmndi:BPMNShape id="Task_1_di" bpmnElement="Task_1">
|
||||
<dc:Bounds x="100" y="100" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Task_2_di" bpmnElement="Task_2">
|
||||
<dc:Bounds x="300" y="200" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Task_3_di" bpmnElement="Task_3">
|
||||
<dc:Bounds x="100" y="400" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Task_4_di" bpmnElement="Task_4">
|
||||
<dc:Bounds x="300" y="400" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="BoundaryEvent_1_di" bpmnElement="BoundaryEvent_1">
|
||||
<dc:Bounds x="132" y="462" width="36" height="36" />
|
||||
</bpmndi:BPMNShape>
|
||||
</bpmndi:BPMNPlane>
|
||||
</bpmndi:BPMNDiagram>
|
||||
</bpmn:definitions>
|
|
@ -0,0 +1,175 @@
|
|||
import {
|
||||
bootstrapModeler,
|
||||
inject
|
||||
} from 'test/TestHelper';
|
||||
|
||||
import coreModule from 'lib/core';
|
||||
import gridSnappingModule from 'lib/features/grid-snapping';
|
||||
import modelingModule from 'lib/features/modeling';
|
||||
import moveModule from 'diagram-js/lib/features/move';
|
||||
|
||||
|
||||
describe('features/grid-snapping - layout connection', function() {
|
||||
|
||||
var diagramXML = require('./LayoutConnectionBehavior.bpmn');
|
||||
|
||||
beforeEach(bootstrapModeler(diagramXML, {
|
||||
modules: [
|
||||
coreModule,
|
||||
gridSnappingModule,
|
||||
modelingModule,
|
||||
moveModule
|
||||
]
|
||||
}));
|
||||
|
||||
|
||||
describe('on connection create', function() {
|
||||
|
||||
it('should snap 3 segment connection (1 middle segment)', inject(
|
||||
function(elementRegistry, modeling) {
|
||||
|
||||
// given
|
||||
var task1 = elementRegistry.get('Task_1'),
|
||||
task2 = elementRegistry.get('Task_2');
|
||||
|
||||
// when
|
||||
var connection = modeling.connect(task1, task2);
|
||||
|
||||
// then
|
||||
expect(connection.waypoints[1]).to.eql({ x: 250, y: 140 });
|
||||
expect(connection.waypoints[2]).to.eql({ x: 250, y: 240 });
|
||||
})
|
||||
);
|
||||
|
||||
|
||||
it('should snap 4 segment connection (2 middle segments)', inject(
|
||||
function(elementRegistry, modeling) {
|
||||
|
||||
// given
|
||||
var boundaryEvent1 = elementRegistry.get('BoundaryEvent_1'),
|
||||
task4 = elementRegistry.get('Task_4');
|
||||
|
||||
// when
|
||||
var connection = modeling.connect(boundaryEvent1, task4);
|
||||
|
||||
// then
|
||||
expect(connection.waypoints[1]).to.eql({ x: 150, y: 520 });
|
||||
expect(connection.waypoints[2]).to.eql({ x: 230, y: 520 });
|
||||
expect(connection.waypoints[3]).to.eql({ x: 230, y: 440 });
|
||||
}
|
||||
));
|
||||
|
||||
});
|
||||
|
||||
|
||||
describe('on connection layout', function() {
|
||||
|
||||
describe('should snap 3 segment connection (1 middle segment)', function() {
|
||||
|
||||
var connection;
|
||||
|
||||
beforeEach(inject(function(elementRegistry, modeling) {
|
||||
|
||||
// given
|
||||
var task1 = elementRegistry.get('Task_1'),
|
||||
task2 = elementRegistry.get('Task_2');
|
||||
|
||||
connection = modeling.connect(task1, task2);
|
||||
|
||||
// when
|
||||
modeling.moveElements([ task2 ], { x: 50, y: 50 });
|
||||
}));
|
||||
|
||||
|
||||
it('should do', function() {
|
||||
|
||||
// then
|
||||
expect(connection.waypoints[1]).to.eql({ x: 250, y: 140 });
|
||||
expect(connection.waypoints[2]).to.eql({ x: 250, y: 290 });
|
||||
});
|
||||
|
||||
|
||||
it('should undo', inject(function(commandStack) {
|
||||
|
||||
// when
|
||||
commandStack.undo();
|
||||
|
||||
// then
|
||||
expect(connection.waypoints[1]).to.eql({ x: 250, y: 140 });
|
||||
expect(connection.waypoints[2]).to.eql({ x: 250, y: 240 });
|
||||
}));
|
||||
|
||||
|
||||
it('should redo', inject(function(commandStack) {
|
||||
|
||||
// given
|
||||
commandStack.undo();
|
||||
|
||||
// when
|
||||
commandStack.redo();
|
||||
|
||||
// then
|
||||
expect(connection.waypoints[1]).to.eql({ x: 250, y: 140 });
|
||||
expect(connection.waypoints[2]).to.eql({ x: 250, y: 290 });
|
||||
}));
|
||||
|
||||
});
|
||||
|
||||
|
||||
describe('should snap 4 segment connection (2 middle segments)', function() {
|
||||
|
||||
var connection;
|
||||
|
||||
beforeEach(inject(function(elementRegistry, modeling) {
|
||||
|
||||
// given
|
||||
var boundaryEvent1 = elementRegistry.get('BoundaryEvent_1'),
|
||||
task4 = elementRegistry.get('Task_4');
|
||||
|
||||
connection = modeling.connect(boundaryEvent1, task4);
|
||||
|
||||
// when
|
||||
modeling.moveElements([ task4 ], { x: 50, y: 50 });
|
||||
}));
|
||||
|
||||
|
||||
it('should do', function() {
|
||||
|
||||
// then
|
||||
expect(connection.waypoints[1]).to.eql({ x: 150, y: 520 });
|
||||
expect(connection.waypoints[2]).to.eql({ x: 230, y: 520 });
|
||||
expect(connection.waypoints[3]).to.eql({ x: 230, y: 490 });
|
||||
});
|
||||
|
||||
|
||||
it('should undo', inject(function(commandStack) {
|
||||
|
||||
// when
|
||||
commandStack.undo();
|
||||
|
||||
// then
|
||||
expect(connection.waypoints[1]).to.eql({ x: 150, y: 520 });
|
||||
expect(connection.waypoints[2]).to.eql({ x: 230, y: 520 });
|
||||
expect(connection.waypoints[3]).to.eql({ x: 230, y: 440 });
|
||||
}));
|
||||
|
||||
|
||||
it('should redo', inject(function(commandStack) {
|
||||
|
||||
// given
|
||||
commandStack.undo();
|
||||
|
||||
// when
|
||||
commandStack.redo();
|
||||
|
||||
// then
|
||||
expect(connection.waypoints[1]).to.eql({ x: 150, y: 520 });
|
||||
expect(connection.waypoints[2]).to.eql({ x: 230, y: 520 });
|
||||
expect(connection.waypoints[3]).to.eql({ x: 230, y: 490 });
|
||||
}));
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
Loading…
Reference in New Issue