-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(grid-snapping): integrate with connection layout
- Loading branch information
1 parent
867b41c
commit 1ac801a
Showing
4 changed files
with
341 additions
and
1 deletion.
There are no files selected for viewing
133 changes: 133 additions & 0 deletions
133
lib/features/grid-snapping/behavior/LayoutConnectionBehavior.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 ] | ||
}; |
29 changes: 29 additions & 0 deletions
29
test/spec/features/grid-snapping/behavior/LayoutConnectionBehavior.bpmn
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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> |
175 changes: 175 additions & 0 deletions
175
test/spec/features/grid-snapping/behavior/LayoutConnectionBehaviorSpec.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 }); | ||
})); | ||
|
||
}); | ||
|
||
}); | ||
|
||
}); |