feat(modeling): prevent accidential dragging of container elements
This implements custom hit areas for participants, lanes and expanded subprocesses. Given these changes, users need to grab container elements on the boarder or the label area to move them. Closes https://github.com/bpmn-io/bpmn-js/issues/957
This commit is contained in:
parent
1a6b6dc46a
commit
ab56fc21ad
|
@ -24,6 +24,7 @@ import CreateModule from 'diagram-js/lib/features/create';
|
|||
import DistributeElementsModule from './features/distribute-elements';
|
||||
import EditorActionsModule from './features/editor-actions';
|
||||
import GridSnappingModule from './features/grid-snapping';
|
||||
import InteractionEventsModule from './features/interaction-events';
|
||||
import KeyboardModule from './features/keyboard';
|
||||
import KeyboardMoveSelectionModule from 'diagram-js/lib/features/keyboard-move-selection';
|
||||
import LabelEditingModule from './features/label-editing';
|
||||
|
@ -218,6 +219,7 @@ Modeler.prototype._modelingModules = [
|
|||
DistributeElementsModule,
|
||||
EditorActionsModule,
|
||||
GridSnappingModule,
|
||||
InteractionEventsModule,
|
||||
KeyboardModule,
|
||||
KeyboardMoveSelectionModule,
|
||||
LabelEditingModule,
|
||||
|
|
|
@ -0,0 +1,106 @@
|
|||
import { is } from '../../util/ModelUtil';
|
||||
|
||||
import { isExpanded } from '../../util/DiUtil';
|
||||
|
||||
var LABEL_WIDTH = 30,
|
||||
LABEL_HEIGHT = 30;
|
||||
|
||||
|
||||
/**
|
||||
* BPMN-specific hit zones and interaction fixes.
|
||||
*
|
||||
* @param {EventBus} eventBus
|
||||
* @param {InteractionEvents} interactionEvents
|
||||
*/
|
||||
export default function BpmnInteractionEvents(eventBus, interactionEvents) {
|
||||
|
||||
this._interactionEvents = interactionEvents;
|
||||
|
||||
var self = this;
|
||||
|
||||
eventBus.on([
|
||||
'interactionEvents.createHit',
|
||||
'interactionEvents.updateHit'
|
||||
], function(context) {
|
||||
var element = context.element,
|
||||
gfx = context.gfx;
|
||||
|
||||
if (is(element, 'bpmn:Lane')) {
|
||||
return self.createParticipantHit(element, gfx);
|
||||
} else
|
||||
|
||||
if (is(element, 'bpmn:Participant')) {
|
||||
if (isExpanded(element)) {
|
||||
return self.createParticipantHit(element, gfx);
|
||||
} else {
|
||||
return self.createDefaultHit(element, gfx);
|
||||
}
|
||||
} else
|
||||
|
||||
if (is(element, 'bpmn:SubProcess')) {
|
||||
if (isExpanded(element)) {
|
||||
return self.createSubProcessHit(element, gfx);
|
||||
} else {
|
||||
return self.createDefaultHit(element, gfx);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
BpmnInteractionEvents.$inject = [
|
||||
'eventBus',
|
||||
'interactionEvents'
|
||||
];
|
||||
|
||||
|
||||
BpmnInteractionEvents.prototype.createDefaultHit = function(element, gfx) {
|
||||
this._interactionEvents.removeHits(gfx);
|
||||
|
||||
this._interactionEvents.createDefaultHit(element, gfx);
|
||||
|
||||
// indicate that we created a hit
|
||||
return true;
|
||||
};
|
||||
|
||||
BpmnInteractionEvents.prototype.createParticipantHit = function(element, gfx) {
|
||||
|
||||
// remove existing hits
|
||||
this._interactionEvents.removeHits(gfx);
|
||||
|
||||
// add outline hit
|
||||
this._interactionEvents.createBoxHit(gfx, 'click-stroke', {
|
||||
width: element.width,
|
||||
height: element.height
|
||||
});
|
||||
|
||||
// add label hit
|
||||
this._interactionEvents.createBoxHit(gfx, 'all', {
|
||||
width: LABEL_WIDTH,
|
||||
height: element.height
|
||||
});
|
||||
|
||||
// indicate that we created a hit
|
||||
return true;
|
||||
};
|
||||
|
||||
BpmnInteractionEvents.prototype.createSubProcessHit = function(element, gfx) {
|
||||
|
||||
// remove existing hits
|
||||
this._interactionEvents.removeHits(gfx);
|
||||
|
||||
// add outline hit
|
||||
this._interactionEvents.createBoxHit(gfx, 'click-stroke', {
|
||||
width: element.width,
|
||||
height: element.height
|
||||
});
|
||||
|
||||
// add label hit
|
||||
this._interactionEvents.createBoxHit(gfx, 'all', {
|
||||
width: element.width,
|
||||
height: LABEL_HEIGHT
|
||||
});
|
||||
|
||||
// indicate that we created a hit
|
||||
return true;
|
||||
};
|
|
@ -0,0 +1,6 @@
|
|||
import BpmnInteractionEvents from './BpmnInteractionEvents';
|
||||
|
||||
export default {
|
||||
__init__: [ 'bpmnInteractionEvents' ],
|
||||
bpmnInteractionEvents: [ 'type', BpmnInteractionEvents ]
|
||||
};
|
|
@ -0,0 +1,150 @@
|
|||
import {
|
||||
queryAll as domQueryAll
|
||||
} from 'min-dom';
|
||||
|
||||
import {
|
||||
getBpmnJS,
|
||||
bootstrapModeler,
|
||||
inject
|
||||
} from 'test/TestHelper';
|
||||
|
||||
import modelingModule from 'lib/features/modeling';
|
||||
import coreModule from 'lib/core';
|
||||
import interactionEventsModule from 'lib/features/interaction-events';
|
||||
|
||||
import createModule from 'diagram-js/lib/features/create';
|
||||
import moveModule from 'diagram-js/lib/features/move';
|
||||
|
||||
var testModules = [
|
||||
coreModule,
|
||||
modelingModule,
|
||||
interactionEventsModule,
|
||||
createModule,
|
||||
moveModule
|
||||
];
|
||||
|
||||
var HIT_ALL_CLS = 'djs-hit-all';
|
||||
var HIT_CLICK_STROKE_CLS = 'djs-hit-click-stroke';
|
||||
|
||||
|
||||
describe('features/interaction-events', function() {
|
||||
|
||||
describe('participant hits', function() {
|
||||
|
||||
var diagramXML = require('test/fixtures/bpmn/collaboration.bpmn');
|
||||
|
||||
beforeEach(bootstrapModeler(diagramXML, {
|
||||
modules: testModules
|
||||
}));
|
||||
|
||||
beforeEach(inject(function(dragging) {
|
||||
dragging.setOptions({ manual: true });
|
||||
}));
|
||||
|
||||
|
||||
it('should create two hit zones per participant', inject(function(elementRegistry) {
|
||||
|
||||
// given
|
||||
var participant = elementRegistry.get('Participant_1');
|
||||
|
||||
// then
|
||||
expectToHaveChildren(HIT_ALL_CLS, 1, participant);
|
||||
expectToHaveChildren(HIT_CLICK_STROKE_CLS, 1, participant);
|
||||
}));
|
||||
|
||||
|
||||
it('should create two hit zones per lane', inject(function(elementRegistry) {
|
||||
|
||||
// given
|
||||
var lane = elementRegistry.get('Lane_1');
|
||||
|
||||
// then
|
||||
expectToHaveChildren(HIT_ALL_CLS, 1, lane);
|
||||
expectToHaveChildren(HIT_CLICK_STROKE_CLS, 1, lane);
|
||||
}));
|
||||
|
||||
|
||||
it('should create one hit zone per collapsed participant',
|
||||
inject(function(elementRegistry, bpmnReplace) {
|
||||
|
||||
// given
|
||||
var participant = elementRegistry.get('Participant_1');
|
||||
|
||||
// when
|
||||
var collapsedParticipant = bpmnReplace.replaceElement(participant, {
|
||||
type: 'bpmn:Participant',
|
||||
isExpanded: false
|
||||
});
|
||||
|
||||
|
||||
// then
|
||||
expectToHaveChildren(HIT_ALL_CLS, 1, collapsedParticipant);
|
||||
})
|
||||
);
|
||||
|
||||
});
|
||||
|
||||
|
||||
describe('sub process hits', function() {
|
||||
|
||||
var diagramXML = require('test/fixtures/bpmn/containers.bpmn');
|
||||
|
||||
beforeEach(bootstrapModeler(diagramXML, {
|
||||
modules: testModules
|
||||
}));
|
||||
|
||||
beforeEach(inject(function(dragging) {
|
||||
dragging.setOptions({ manual: true });
|
||||
}));
|
||||
|
||||
|
||||
it('should create two hit zones per sub process', inject(function(elementRegistry) {
|
||||
|
||||
// given
|
||||
var subProcess = elementRegistry.get('SubProcess_1');
|
||||
|
||||
// then
|
||||
expectToHaveChildren(HIT_ALL_CLS, 1, subProcess);
|
||||
expectToHaveChildren(HIT_CLICK_STROKE_CLS, 1, subProcess);
|
||||
}));
|
||||
|
||||
|
||||
it('should create one hit zone per collapsed sub process',
|
||||
inject(function(elementRegistry, bpmnReplace) {
|
||||
|
||||
// given
|
||||
var subProcess = elementRegistry.get('SubProcess_1');
|
||||
|
||||
// when
|
||||
var collapsedSubProcess = bpmnReplace.replaceElement(subProcess, {
|
||||
type: 'bpmn:SubProcess',
|
||||
isExpanded: false
|
||||
});
|
||||
|
||||
// then
|
||||
expectToHaveChildren(HIT_ALL_CLS, 1, collapsedSubProcess);
|
||||
expectToHaveChildren(HIT_CLICK_STROKE_CLS, 0, collapsedSubProcess);
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
||||
// helper ///////////
|
||||
|
||||
function expectToHaveChildren(className, expectedCount, element) {
|
||||
|
||||
var selector = '.' + className;
|
||||
|
||||
var elementRegistry = getBpmnJS().get('elementRegistry'),
|
||||
gfx = elementRegistry.getGraphics(element),
|
||||
realCount = domQueryAll(selector, gfx).length;
|
||||
|
||||
expect(
|
||||
realCount,
|
||||
'expected ' + element.id + ' to have ' + expectedCount +
|
||||
' children mat ' + selector + ' but got ' + realCount
|
||||
).to.eql(expectedCount);
|
||||
}
|
Loading…
Reference in New Issue