mirror of
https://github.com/sartography/bpmn-js.git
synced 2025-01-11 17:44:12 +00:00
feat(grid-snapping): integrate with connection layout
Closes #1010 Related to #973
This commit is contained in:
parent
e4fe8c239b
commit
13f1e05ee7
133
lib/features/grid-snapping/behavior/LayoutConnectionBehavior.js
Normal file
133
lib/features/grid-snapping/behavior/LayoutConnectionBehavior.js
Normal file
@ -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 AutoPlaceBehavior from './AutoPlaceBehavior';
|
||||||
|
import LayoutConnectionBehavior from './LayoutConnectionBehavior';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
__init__: [
|
__init__: [
|
||||||
|
'gridSnappingLayoutConnectionBehavior',
|
||||||
'gridSnappingAutoPlaceBehavior'
|
'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…
x
Reference in New Issue
Block a user