From 867b41c04f8b8d7ca19d4beb27b711f658b0071f Mon Sep 17 00:00:00 2001 From: Philipp Fromme Date: Mon, 29 Apr 2019 16:03:34 +0200 Subject: [PATCH] feat(grid-snapping): integrate grid snapping with auto place feature Closes #1003 --- .../behavior/AutoPlaceBehavior.js | 57 +++++ lib/features/grid-snapping/behavior/index.js | 8 + lib/features/grid-snapping/index.js | 7 +- .../behavior/AutoPlaceBehavior.bpmn | 17 ++ .../behavior/AutoPlaceBehaviorSpec.js | 198 ++++++++++++++++++ 5 files changed, 286 insertions(+), 1 deletion(-) create mode 100644 lib/features/grid-snapping/behavior/AutoPlaceBehavior.js create mode 100644 lib/features/grid-snapping/behavior/index.js create mode 100644 test/spec/features/grid-snapping/behavior/AutoPlaceBehavior.bpmn create mode 100644 test/spec/features/grid-snapping/behavior/AutoPlaceBehaviorSpec.js diff --git a/lib/features/grid-snapping/behavior/AutoPlaceBehavior.js b/lib/features/grid-snapping/behavior/AutoPlaceBehavior.js new file mode 100644 index 00000000..820b1a01 --- /dev/null +++ b/lib/features/grid-snapping/behavior/AutoPlaceBehavior.js @@ -0,0 +1,57 @@ +import { getNewShapePosition } from '../../auto-place/AutoPlaceUtil'; + +import { getMid } from 'diagram-js/lib/layout/LayoutUtil'; +import { is } from '../../../util/ModelUtil'; + + +export default function AutoPlaceBehavior(eventBus, gridSnapping) { + eventBus.on('autoPlace', function(context) { + var source = context.source, + sourceMid = getMid(source), + shape = context.shape; + + var position = getNewShapePosition(source, shape); + + [ 'x', 'y' ].forEach(function(axis) { + var options = {}; + + // do not snap if x/y equal + if (position[ axis ] === sourceMid[ axis ]) { + return; + } + + if (position[ axis ] > sourceMid[ axis ]) { + options.min = position[ axis ]; + } else { + options.max = position[ axis ]; + } + + if (is(shape, 'bpmn:TextAnnotation')) { + + if (isHorizontal(axis)) { + options.offset = -shape.width / 2; + } else { + options.offset = -shape.height / 2; + } + + } + + position[ axis ] = gridSnapping.snapValue(position[ axis ], options); + + }); + + // must be returned to be considered by auto place + return position; + }); +} + +AutoPlaceBehavior.$inject = [ + 'eventBus', + 'gridSnapping' +]; + +// helpers ////////// + +function isHorizontal(axis) { + return axis === 'x'; +} \ No newline at end of file diff --git a/lib/features/grid-snapping/behavior/index.js b/lib/features/grid-snapping/behavior/index.js new file mode 100644 index 00000000..e26f92a4 --- /dev/null +++ b/lib/features/grid-snapping/behavior/index.js @@ -0,0 +1,8 @@ +import AutoPlaceBehavior from './AutoPlaceBehavior'; + +export default { + __init__: [ + 'gridSnappingAutoPlaceBehavior' + ], + gridSnappingAutoPlaceBehavior: [ 'type', AutoPlaceBehavior ] +}; \ No newline at end of file diff --git a/lib/features/grid-snapping/index.js b/lib/features/grid-snapping/index.js index edde8a34..b80f7d1e 100644 --- a/lib/features/grid-snapping/index.js +++ b/lib/features/grid-snapping/index.js @@ -1,8 +1,13 @@ import BpmnGridSnapping from './BpmnGridSnapping'; import GridSnappingModule from 'diagram-js/lib/features/grid-snapping'; +import GridSnappingBehaviorModule from './behavior'; + export default { - __depends__: [ GridSnappingModule ], + __depends__: [ + GridSnappingModule, + GridSnappingBehaviorModule + ], __init__: [ 'bpmnGridSnapping' ], bpmnGridSnapping: [ 'type', BpmnGridSnapping ] }; \ No newline at end of file diff --git a/test/spec/features/grid-snapping/behavior/AutoPlaceBehavior.bpmn b/test/spec/features/grid-snapping/behavior/AutoPlaceBehavior.bpmn new file mode 100644 index 00000000..2a29937e --- /dev/null +++ b/test/spec/features/grid-snapping/behavior/AutoPlaceBehavior.bpmn @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/test/spec/features/grid-snapping/behavior/AutoPlaceBehaviorSpec.js b/test/spec/features/grid-snapping/behavior/AutoPlaceBehaviorSpec.js new file mode 100644 index 00000000..c4a0b570 --- /dev/null +++ b/test/spec/features/grid-snapping/behavior/AutoPlaceBehaviorSpec.js @@ -0,0 +1,198 @@ +import { + bootstrapModeler, + inject +} from 'test/TestHelper'; + +import { getMid } from 'diagram-js/lib/layout/LayoutUtil'; + +import autoPlaceModule from 'lib/features/auto-place'; +import coreModule from 'lib/core'; +import gridSnappingModule from 'lib/features/grid-snapping'; +import modelingModule from 'lib/features/modeling'; + + +describe('features/grid-snapping - auto-place', function() { + + var diagramXML = require('./AutoPlaceBehavior.bpmn'); + + beforeEach(bootstrapModeler(diagramXML, { + modules: [ + autoPlaceModule, + coreModule, + gridSnappingModule, + modelingModule + ] + })); + + + describe('flow node', function() { + + it('without existing elements', inject(function(autoPlace, elementFactory, elementRegistry) { + + // given + var shape1 = elementFactory.createShape({ + id: 'Task_2', + type: 'bpmn:Task' + }); + + var source = elementRegistry.get('StartEvent_1'); + + // when + autoPlace.append(source, shape1); + + // then + shape1 = elementRegistry.get('Task_2'); + + expect(getMid(shape1)).to.eql({ + x: 220, // 218 snapped to 220 + y: 105 // not snapped + }); + })); + + + it('with existing elements', inject(function(autoPlace, elementFactory, elementRegistry) { + + // given + var shape1 = elementFactory.createShape({ + id: 'Task_2', + type: 'bpmn:Task' + }); + + var source = elementRegistry.get('StartEvent_1'); + + autoPlace.append(source, shape1); + + var shape2 = elementFactory.createShape({ + id: 'Task_3', + type: 'bpmn:Task' + }); + + // when + autoPlace.append(source, shape2); + + // then + shape2 = elementRegistry.get('Task_3'); + + expect(getMid(shape2)).to.eql({ + x: 220, // 220 snapped to 220 + y: 220 // 215 snapped to 220 + }); + })); + + }); + + + describe('text annotation', function() { + + it('without existing elements', inject(function(autoPlace, elementFactory, elementRegistry) { + + // given + var shape1 = elementFactory.createShape({ + id: 'TextAnnotation_1', + type: 'bpmn:TextAnnotation' + }); + + var source = elementRegistry.get('StartEvent_1'); + + // when + autoPlace.append(source, shape1); + + // then + shape1 = elementRegistry.get('TextAnnotation_1'); + + expect(getMid(shape1)).to.eql({ + x: 170, // 168 snapped to 170 + y: 15 // 22 snapped to 15 + }); + })); + + + it('with existing elements', inject(function(autoPlace, elementFactory, elementRegistry) { + + // given + var shape1 = elementFactory.createShape({ + id: 'TextAnnotation_1', + type: 'bpmn:TextAnnotation' + }); + + var source = elementRegistry.get('StartEvent_1'); + + autoPlace.append(source, shape1); + + var shape2 = elementFactory.createShape({ + id: 'TextAnnotation_2', + type: 'bpmn:TextAnnotation' + }); + + // when + autoPlace.append(source, shape2); + + // then + shape2 = elementRegistry.get('TextAnnotation_2'); + + expect(getMid(shape2)).to.eql({ + x: 170, // 168 snapped to 170 + y: -45 // -45 snapped to -45 + }); + })); + + }); + + + describe('data object/store reference', function() { + + it('without existing elements', inject(function(autoPlace, elementFactory, elementRegistry) { + + // given + var shape1 = elementFactory.createShape({ + id: 'DataObjectReference_1', + type: 'bpmn:DataObjectReference' + }); + + var source = elementRegistry.get('Task_1'); + + // when + autoPlace.append(source, shape1); + + // then + shape1 = elementRegistry.get('DataObjectReference_1'); + + expect(getMid(shape1)).to.eql({ + x: 160, // 158 snapped to 160 + y: 400 // 398 snapped to 400 + }); + })); + + + it('with existing elements', inject(function(autoPlace, elementFactory, elementRegistry) { + + // given + var shape1 = elementFactory.createShape({ + id: 'DataObjectReference_1', + type: 'bpmn:DataObjectReference' + }); + + var source = elementRegistry.get('Task_1'); + + autoPlace.append(source, shape1); + + var shape2 = elementFactory.createShape({ + id: 'DataObjectReference_2', + type: 'bpmn:DataObjectReference' + }); + + // when + autoPlace.append(source, shape2); + + // then + shape2 = elementRegistry.get('DataObjectReference_2'); + + expect(getMid(shape2)).to.eql({ + x: 230, // 226 snapped to 230 + y: 400 // 398 snapped to 400 + }); + })); + + }); + +}); \ No newline at end of file