feat(label-editing): improve text annotation editing

Related to camunda/camunda-modeler#564
This commit is contained in:
Philipp Fromme 2017-06-27 09:37:58 +02:00 committed by pedesen
parent 114da17403
commit e797d9c142
10 changed files with 823 additions and 493 deletions

View File

@ -1551,7 +1551,7 @@ function BpmnRenderer(eventBus, styles, pathMap, canvas, priority) {
});
var text = getSemantic(element).text || '';
renderLabel(parentGfx, text, { box: element, align: 'left-middle', padding: 5 });
renderLabel(parentGfx, text, { box: element, align: 'left-top', padding: 5 });
return textElement;
},

View File

@ -0,0 +1,122 @@
var svgAppend = require('tiny-svg/lib/append'),
svgAttr = require('tiny-svg/lib/attr'),
svgCreate = require('tiny-svg/lib/create'),
svgRemove = require('tiny-svg/lib/remove');
var getBusinessObject = require('../../util/ModelUtil').getBusinessObject,
is = require('../../util/ModelUtil').is;
var translate = require('diagram-js/lib/util/SvgTransformUtil').translate;
var MARKER_HIDDEN = 'djs-element-hidden',
MARKER_LABEL_HIDDEN = 'djs-label-hidden';
function getStrokeColor(element, defaultColor) {
var bo = getBusinessObject(element);
return bo.di.get('stroke') || defaultColor || 'black';
}
function LabelEditingPreview(eventBus, canvas, elementRegistry, pathMap) {
var self = this;
var defaultLayer = canvas.getDefaultLayer();
var element, absoluteElementBBox, gfx;
eventBus.on('directEditing.activate', function(context) {
var activeProvider = context.active;
element = activeProvider.element.label || activeProvider.element;
// text annotation
if (is(element, 'bpmn:TextAnnotation')) {
absoluteElementBBox = canvas.getAbsoluteBBox(element);
gfx = svgCreate('g');
var textPathData = pathMap.getScaledPath('TEXT_ANNOTATION', {
xScaleFactor: 1,
yScaleFactor: 1,
containerWidth: element.width,
containerHeight: element.height,
position: {
mx: 0.0,
my: 0.0
}
});
var path = self.path = svgCreate('path');
svgAttr(path, {
d: textPathData,
strokeWidth: 2,
stroke: getStrokeColor(element)
});
svgAppend(gfx, path);
svgAppend(defaultLayer, gfx);
translate(gfx, element.x, element.y);
}
if (is(element, 'bpmn:TextAnnotation') ||
element.labelTarget) {
canvas.addMarker(element, MARKER_HIDDEN);
} else if (is(element, 'bpmn:Task') ||
is(element, 'bpmn:CallActivity') ||
is(element, 'bpmn:SubProcess') ||
is(element, 'bpmn:Participant')) {
canvas.addMarker(element, MARKER_LABEL_HIDDEN);
}
});
eventBus.on('directEditing.resize', function(context) {
// text annotation
if (is(element, 'bpmn:TextAnnotation')) {
var height = context.height,
dy = context.dy;
var newElementHeight = Math.max(element.height / absoluteElementBBox.height * (height + dy), 0);
var textPathData = pathMap.getScaledPath('TEXT_ANNOTATION', {
xScaleFactor: 1,
yScaleFactor: 1,
containerWidth: element.width,
containerHeight: newElementHeight,
position: {
mx: 0.0,
my: 0.0
}
});
svgAttr(self.path, {
d: textPathData
});
}
});
eventBus.on([ 'directEditing.complete', 'directEditing.cancel' ], function(context) {
var activeProvider = context.active;
if (activeProvider) {
canvas.removeMarker(activeProvider.element.label || activeProvider.element, MARKER_HIDDEN);
canvas.removeMarker(element, MARKER_LABEL_HIDDEN);
}
element = undefined;
absoluteElementBBox = undefined;
if (gfx) {
svgRemove(gfx);
gfx = undefined;
}
});
}
LabelEditingPreview.$inject = [ 'eventBus', 'canvas', 'elementRegistry', 'pathMap' ];
module.exports = LabelEditingPreview;

View File

@ -1,5 +1,7 @@
'use strict';
var assign = require('lodash/object/assign');
var UpdateLabelHandler = require('./cmd/UpdateLabelHandler');
var LabelUtil = require('./LabelUtil');
@ -7,10 +9,12 @@ var LabelUtil = require('./LabelUtil');
var is = require('../../util/ModelUtil').is,
isExpanded = require('../../util/DiUtil').isExpanded;
var LINE_HEIGHT = 14,
PADDING = 6;
var SMALL_FONT_SIZE = 11,
SMALL_LINE_HEIGHT = 13,
MEDIUM_FONT_SIZE = 12,
MEDIUM_LINE_HEIGHT = 14;
function LabelEditingProvider(eventBus, canvas, directEditing, commandStack) {
function LabelEditingProvider(eventBus, canvas, directEditing, commandStack, resizeHandles) {
this._canvas = canvas;
this._commandStack = commandStack;
@ -22,19 +26,20 @@ function LabelEditingProvider(eventBus, canvas, directEditing, commandStack) {
// listen to dblclick on non-root elements
eventBus.on('element.dblclick', function(event) {
directEditing.activate(event.element);
resizeHandles.removeResizers();
});
// complete on followup canvas operation
eventBus.on([ 'element.mousedown', 'drag.init', 'canvas.viewbox.changed' ], function(event) {
eventBus.on([ 'element.mousedown', 'drag.init', 'canvas.viewbox.changing' ], function(event) {
directEditing.complete();
});
// cancel on command stack changes
eventBus.on([ 'commandStack.changed' ], function() {
eventBus.on([ 'commandStack.changed' ], function(e) {
directEditing.cancel();
});
if ('ontouchstart' in document.documentElement) {
// we deactivate automatic label editing on mobile devices
// as it breaks the user interaction workflow
@ -42,10 +47,9 @@ function LabelEditingProvider(eventBus, canvas, directEditing, commandStack) {
// TODO(nre): we should temporarily focus the edited element here
// and release the focused viewport after the direct edit operation is finished
} else {
eventBus.on('create.end', 500, function(e) {
var element = e.shape,
canExecute = e.context.canExecute;
eventBus.on('create.end', 500, function(event) {
var element = event.shape,
canExecute = event.context.canExecute;
if (!canExecute) {
return;
@ -53,14 +57,15 @@ function LabelEditingProvider(eventBus, canvas, directEditing, commandStack) {
if (is(element, 'bpmn:Task') || is(element, 'bpmn:TextAnnotation') ||
(is(element, 'bpmn:SubProcess') && !isExpanded(element))) {
directEditing.activate(element);
resizeHandles.removeResizers();
}
});
}
}
LabelEditingProvider.$inject = [ 'eventBus', 'canvas', 'directEditing', 'commandStack' ];
LabelEditingProvider.$inject = [ 'eventBus', 'canvas', 'directEditing', 'commandStack', 'resizeHandles' ];
module.exports = LabelEditingProvider;
@ -70,21 +75,62 @@ module.exports = LabelEditingProvider;
*
* @param {djs.model.Base} element
*
* @return {Object} an object with properties bounds (position and size) and text
* @return {Object} an object with properties bounds (position and size), text and options
*/
LabelEditingProvider.prototype.activate = function(element) {
// text
var text = LabelUtil.getLabel(element);
if (text === undefined) {
return;
}
var properties = this.getEditingBBox(element);
var context = {
text: text
};
properties.text = text;
// bounds
var bounds = this.getEditingBBox(element);
return properties;
assign(context, bounds);
// options
var target = element.label || element;
var options = {};
// tasks
if (is(element, 'bpmn:Task') ||
is(element, 'bpmn:Participant') ||
is(element, 'bpmn:Lane') ||
(is(element, 'bpmn:CallActivity') && !isExpanded(element)) ||
(is(element, 'bpmn:SubProcess') && !isExpanded(element))) {
assign(options, {
centerVertically: true
});
}
// external labels
if (target.labelTarget) {
assign(options, {
autoResize: true
});
}
// text annotations
if (is(element, 'bpmn:TextAnnotation')) {
assign(options, {
resizable: true,
autoResize: true
});
}
assign(context, {
options: options
});
return context;
};
@ -110,17 +156,36 @@ LabelEditingProvider.prototype.getEditingBBox = function(element) {
// default position
var bounds = { x: bbox.x, y: bbox.y };
var style = {},
zoom;
var zoom = canvas.zoom();
// take zoom into account
var smallFontSize = SMALL_FONT_SIZE * zoom,
smallLineHeight = SMALL_LINE_HEIGHT * zoom,
mediumFontSize = MEDIUM_FONT_SIZE * zoom,
mediumLineHeight = MEDIUM_LINE_HEIGHT * zoom;
var style = {};
// adjust for expanded pools AND lanes
if ((is(element, 'bpmn:Participant') && isExpanded(element)) || is(element, 'bpmn:Lane')) {
if ((is(element, 'bpmn:Participant') && isExpanded(element))
|| is(element, 'bpmn:Lane')) {
bounds.width = 150;
bounds.minHeight = LINE_HEIGHT + PADDING;
bounds.maxHeight = LINE_HEIGHT * 2 + PADDING;
bounds.x = bbox.x - bounds.width / 2;
bounds.y = mid.y - bounds.minHeight / 2;
assign(bounds, {
width: bbox.height,
height: 30 * zoom,
x: bbox.x - bbox.height / 2 + (15 * zoom),
y: mid.y - (30 * zoom) / 2
});
assign(style, {
fontSize: mediumFontSize + 'px',
lineHeight: mediumLineHeight + 'px',
paddingTop: (7 * zoom) + 'px',
paddingBottom: (7 * zoom) + 'px',
paddingLeft: (5 * zoom) + 'px',
paddingRight: (5 * zoom) + 'px',
transform: 'rotate(-90deg)'
});
}
@ -131,63 +196,97 @@ LabelEditingProvider.prototype.getEditingBBox = function(element) {
(is(element, 'bpmn:SubProcess') && !isExpanded(element)) ||
(is(element, 'bpmn:Participant') && !isExpanded(element))
) {
assign(bounds, {
width: bbox.width,
height: bbox.height
});
zoom = canvas.zoom();
// fixed size for internal labels:
// on high zoom levels: text box size === bbox size
// on low zoom levels: text box size === bbox size at 100% zoom
// This ensures minimum bounds at low zoom levels
if (zoom > 1) {
bounds.width = bbox.width;
bounds.height = bbox.height;
} else {
bounds.width = bbox.width / zoom;
bounds.height = bbox.height / zoom;
}
// centering overlapping text box size at low zoom levels
if (zoom < 1) {
bounds.x = bbox.x - (bounds.width / 2 - bbox.width / 2);
bounds.y = bbox.y - (bounds.height / 2 - bbox.height / 2);
}
assign(style, {
fontSize: mediumFontSize + 'px',
lineHeight: mediumLineHeight + 'px',
paddingTop: (7 * zoom) + 'px',
paddingBottom: (7 * zoom) + 'px',
paddingLeft: (5 * zoom) + 'px',
paddingRight: (5 * zoom) + 'px'
});
}
// internal labels for expanded sub processes
if (is(element, 'bpmn:SubProcess') && isExpanded(element)) {
assign(bounds, {
width: bbox.width,
x: bbox.x
});
bounds.width = element.width;
bounds.maxHeight = 3 * LINE_HEIGHT + PADDING; // maximum 3 lines
bounds.x = mid.x - element.width / 2;
assign(style, {
fontSize: mediumFontSize + 'px',
lineHeight: mediumLineHeight + 'px',
paddingTop: (7 * zoom) + 'px',
paddingBottom: (7 * zoom) + 'px',
paddingLeft: (5 * zoom) + 'px',
paddingRight: (5 * zoom) + 'px'
});
}
// external labels for events, data elements, gateways and connections
if (target.labelTarget) {
var width = 90 * zoom,
paddingTop = 7 * zoom,
paddingBottom = 4 * zoom;
bounds.width = 150;
bounds.minHeight = LINE_HEIGHT + PADDING; // 1 line
bounds.x = mid.x - bounds.width / 2;
assign(bounds, {
width: width,
height: bbox.height + paddingTop + paddingBottom,
x: mid.x - width / 2,
y: bbox.y - paddingTop
});
assign(style, {
fontSize: smallFontSize + 'px',
lineHeight: smallLineHeight + 'px',
paddingTop: paddingTop + 'px',
paddingBottom: paddingBottom + 'px'
});
}
// text annotations
if (is(element, 'bpmn:TextAnnotation')) {
bounds.minWidth = 100;
bounds.height = element.height;
assign(bounds, {
width: bbox.width,
height: bbox.height,
minWidth: 30 * zoom,
minHeight: 10 * zoom
});
style.textAlign = 'left';
assign(style, {
textAlign: 'left',
paddingTop: (7 * zoom) + 'px',
paddingBottom: (7 * zoom) + 'px',
paddingLeft: (5 * zoom) + 'px',
paddingRight: (5 * zoom) + 'px',
fontSize: mediumFontSize + 'px',
lineHeight: mediumLineHeight + 'px'
});
}
return { bounds: bounds, style: style };
};
LabelEditingProvider.prototype.update = function(element, newLabel) {
LabelEditingProvider.prototype.update = function(element, newLabel, activeContextText, bounds) {
var absoluteElementBBox = this._canvas.getAbsoluteBBox(element);
this._commandStack.execute('element.updateLabel', {
element: element,
newLabel: newLabel
newLabel: newLabel,
bounds: {
x: element.x,
y: element.y,
width: element.width / absoluteElementBBox.width * bounds.width,
height: element.height / absoluteElementBBox.height * bounds.height
}
});
};

View File

@ -6,7 +6,8 @@ var TextUtil = require('diagram-js/lib/util/Text');
var hasExternalLabel = require('../../../util/LabelUtil').hasExternalLabel;
var getBusinessObject = require('../../../util/ModelUtil').getBusinessObject;
var getBusinessObject = require('../../../util/ModelUtil').getBusinessObject,
is = require('../../../util/ModelUtil').is;
var NULL_DIMENSIONS = {
width: 0,
@ -52,14 +53,17 @@ function UpdateLabelHandler(modeling) {
function postExecute(ctx) {
var element = ctx.element,
label = element.label || element;
label = element.label || element,
bounds = ctx.bounds;
// ignore internal labels
if (!hasExternalLabel(element)) {
// ignore internal labels for elements except text annotations
if (!hasExternalLabel(element) && !is(element, 'bpmn:TextAnnotation')) {
return;
}
var text = getBusinessObject(label).name;
var bo = getBusinessObject(label);
var text = bo.name || bo.text;
if (!text) {
return;
@ -67,7 +71,7 @@ function UpdateLabelHandler(modeling) {
// get layouted text bounds and resize external
// external label accordingly
var newBounds = getLayoutedBounds(label, text, textUtil);
var newBounds = is(element, 'bpmn:TextAnnotation') ? bounds : getLayoutedBounds(label, text, textUtil);
modeling.resizeShape(label, newBounds, NULL_DIMENSIONS);
}

View File

@ -2,8 +2,13 @@ module.exports = {
__depends__: [
require('diagram-js/lib/command'),
require('diagram-js/lib/features/change-support'),
require('diagram-js/lib/features/resize'),
require('diagram-js-direct-editing')
],
__init__: [ 'labelEditingProvider' ],
labelEditingProvider: [ 'type', require('./LabelEditingProvider') ]
};
__init__: [
'labelEditingProvider',
'labelEditingPreview'
],
labelEditingProvider: [ 'type', require('./LabelEditingProvider') ],
labelEditingPreview: [ 'type', require('./LabelEditingPreview') ]
};

View File

@ -7,7 +7,7 @@ TestHelper.insertCSS('diagram-js.css', require('diagram-js/assets/diagram-js.css
TestHelper.insertCSS('bpmn-embedded.css', require('../assets/bpmn-font/css/bpmn-embedded.css'));
TestHelper.insertCSS('diagram-js-testing.css',
'.test-container .result { height: 500px; }' + '.test-container > div'
'.test-container .result { height: 500px; }' + '.test-container { height: 1000px !important }'
);

View File

@ -1,248 +1,178 @@
<?xml version="1.0" encoding="UTF-8"?>
<bpmn2:definitions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:bpmn2="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" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" id="_w9Yc0PLSEeOqh_cnoUOA7A" targetNamespace="http://activiti.org/bpmn" exporter="Camunda Modeler" exporterVersion="1.4.0-nightly" xsi:schemaLocation="http://www.omg.org/spec/BPMN/20100524/MODEL BPMN20.xsd">
<bpmn2:collaboration id="_Collaboration_2">
<bpmn2:participant id="expanded-pool" name="A" processRef="Process_1" />
<bpmn2:participant id="collapsed-pool" name="A" />
<bpmn2:messageFlow id="message-flow" name="A" sourceRef="task-nested-embedded" targetRef="collapsed-pool" />
<bpmn2:messageFlow id="message-flow-unlabeled" name="" sourceRef="collapsed-pool" targetRef="expanded-pool" />
<bpmn2:textAnnotation id="text-annotation"> <bpmn2:text>A</bpmn2:text>
</bpmn2:textAnnotation>
</bpmn2:collaboration>
<bpmn2:process id="Process_1" isExecutable="false">
<bpmn2:ioSpecification id="InputOutputSpecification_1">
<bpmn2:dataInput id="data-input" />
<bpmn2:dataOutput id="data-output" />
</bpmn2:ioSpecification>
<bpmn2:laneSet id="LaneSet_1" name="Lane Set 1">
<bpmn2:lane id="lane-1" name="A">
<bpmn2:flowNodeRef>intermediate-throw-event</bpmn2:flowNodeRef>
<bpmn2:flowNodeRef>empty-task</bpmn2:flowNodeRef>
<bpmn2:childLaneSet xsi:type="bpmn2:tLaneSet" id="LaneSet_2">
<bpmn2:lane id="nested-lane-1-1" name="A">
<bpmn2:flowNodeRef>start-event</bpmn2:flowNodeRef>
<bpmn2:flowNodeRef>exclusive-gateway</bpmn2:flowNodeRef>
<bpmn2:flowNodeRef>intermediate-throw-event</bpmn2:flowNodeRef>
<bpmn2:flowNodeRef>empty-task</bpmn2:flowNodeRef>
</bpmn2:lane>
<bpmn2:lane id="nested-lane-1-2" name="A">
<bpmn2:flowNodeRef>call-activity</bpmn2:flowNodeRef>
<bpmn2:flowNodeRef>user-task</bpmn2:flowNodeRef>
</bpmn2:lane>
</bpmn2:childLaneSet>
</bpmn2:lane>
<bpmn2:lane id="lane-2" name="A">
<bpmn2:childLaneSet xsi:type="bpmn2:tLaneSet" id="LaneSet_3">
<bpmn2:lane id="nested-lane-no-label" name="">
<bpmn2:flowNodeRef>subprocess-expanded</bpmn2:flowNodeRef>
<bpmn2:flowNodeRef>subprocess-collapsed</bpmn2:flowNodeRef>
</bpmn2:lane>
</bpmn2:childLaneSet>
</bpmn2:lane>
</bpmn2:laneSet>
<bpmn2:subProcess id="subprocess-expanded" name="A">
<bpmn2:dataInputAssociation id="DataInputAssociation_1">
<bpmn2:sourceRef>data-input</bpmn2:sourceRef>
</bpmn2:dataInputAssociation>
<bpmn2:dataOutputAssociation id="DataOutputAssociation_1">
<bpmn2:targetRef>data-output</bpmn2:targetRef>
</bpmn2:dataOutputAssociation>
<bpmn2:task id="task-nested-embedded" name="A" />
</bpmn2:subProcess>
<bpmn2:subProcess id="subprocess-collapsed" name="A" triggeredByEvent="true">
<bpmn2:dataInputAssociation id="DataInputAssociation_2">
<bpmn2:sourceRef>data-store-reference</bpmn2:sourceRef>
</bpmn2:dataInputAssociation>
<bpmn2:startEvent id="nested-embedded-start-event" name="start event" isInterrupting="false" />
</bpmn2:subProcess>
<bpmn2:callActivity id="call-activity" name="A">
<bpmn2:incoming>sequence-flow-no</bpmn2:incoming>
</bpmn2:callActivity>
<bpmn2:userTask id="user-task" name="A" />
<bpmn2:boundaryEvent id="boundary-event" name="A" attachedToRef="user-task" />
<bpmn2:startEvent id="start-event">
<bpmn2:outgoing>sequenceflow-unlabeled</bpmn2:outgoing>
</bpmn2:startEvent>
<bpmn2:sequenceFlow id="sequenceflow-unlabeled" name="" sourceRef="start-event" targetRef="exclusive-gateway" />
<bpmn2:exclusiveGateway id="exclusive-gateway" name="A">
<bpmn2:incoming>sequenceflow-unlabeled</bpmn2:incoming>
<bpmn2:outgoing>sequence-flow-no</bpmn2:outgoing>
<bpmn2:outgoing>sequence-flow-yes</bpmn2:outgoing>
</bpmn2:exclusiveGateway>
<bpmn2:sequenceFlow id="sequence-flow-no" name="A" sourceRef="exclusive-gateway" targetRef="call-activity" />
<bpmn2:sequenceFlow id="sequence-flow-yes" name="A" sourceRef="exclusive-gateway" targetRef="intermediate-throw-event" />
<bpmn2:dataStoreReference id="data-store-reference" name="A" dataStoreRef="DataStore_1" />
<bpmn2:dataObject id="DataObject_1" name="Data Object 1" />
<bpmn2:dataObjectReference id="data-object-reference" name="A" dataObjectRef="DataObject_1" />
<bpmn2:intermediateThrowEvent id="intermediate-throw-event" name="A">
<bpmn2:incoming>sequence-flow-yes</bpmn2:incoming>
<bpmn2:outgoing>SequenceFlow_1r751z0</bpmn2:outgoing>
</bpmn2:intermediateThrowEvent>
<bpmn2:task id="empty-task">
<bpmn2:incoming>SequenceFlow_1r751z0</bpmn2:incoming>
</bpmn2:task>
<bpmn2:sequenceFlow id="SequenceFlow_1r751z0" sourceRef="intermediate-throw-event" targetRef="empty-task" />
<bpmn2:association id="Association_1" sourceRef="text-annotation" targetRef="subprocess-expanded" />
</bpmn2:process>
<bpmn2:dataStore id="DataStore_1" name="Data Store 1" />
<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" id="Definitions_1" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="1.6.0">
<bpmn:collaboration id="Collaboration_1o0amh9">
<bpmn:participant id="Participant_1" name="FOO BAR" processRef="Process_1" />
<bpmn:participant id="Participant_2" />
<bpmn:messageFlow id="MessageFlow_1" name="FOO" sourceRef="Task_1fo1fvh" targetRef="Participant_2" />
<bpmn:textAnnotation id="TextAnnotation_1"> <bpmn:text>FOO</bpmn:text>
</bpmn:textAnnotation>
</bpmn:collaboration>
<bpmn:process id="Process_1" isExecutable="false">
<bpmn:laneSet>
<bpmn:lane id="Lane_2">
<bpmn:flowNodeRef>StartEvent_1</bpmn:flowNodeRef>
<bpmn:flowNodeRef>SubProcess_1</bpmn:flowNodeRef>
</bpmn:lane>
<bpmn:lane id="Lane_1" name="FOO BAR">
<bpmn:flowNodeRef>Task_1</bpmn:flowNodeRef>
<bpmn:flowNodeRef>StartEvent_08jn2xd</bpmn:flowNodeRef>
<bpmn:flowNodeRef>Task_1fo1fvh</bpmn:flowNodeRef>
<bpmn:flowNodeRef>ExclusiveGateway_1</bpmn:flowNodeRef>
<bpmn:flowNodeRef>EndEvent_1</bpmn:flowNodeRef>
</bpmn:lane>
</bpmn:laneSet>
<bpmn:startEvent id="StartEvent_1" name="FOO">
<bpmn:outgoing>SequenceFlow_2</bpmn:outgoing>
</bpmn:startEvent>
<bpmn:sequenceFlow id="SequenceFlow_2" sourceRef="StartEvent_1" targetRef="SubProcess_1" />
<bpmn:subProcess id="SubProcess_1" name="FOO">
<bpmn:incoming>SequenceFlow_2</bpmn:incoming>
<bpmn:outgoing>SequenceFlow_14kuv1e</bpmn:outgoing>
</bpmn:subProcess>
<bpmn:startEvent id="StartEvent_08jn2xd" name="FOO&#10;BAR">
<bpmn:outgoing>SequenceFlow_1</bpmn:outgoing>
</bpmn:startEvent>
<bpmn:task id="Task_1" name="FOO">
<bpmn:incoming>SequenceFlow_1</bpmn:incoming>
<bpmn:incoming>SequenceFlow_14kuv1e</bpmn:incoming>
<bpmn:outgoing>SequenceFlow_13rjp44</bpmn:outgoing>
<bpmn:outgoing>SequenceFlow_0kmqqm9</bpmn:outgoing>
</bpmn:task>
<bpmn:sequenceFlow id="SequenceFlow_1" name="FOO" sourceRef="StartEvent_08jn2xd" targetRef="Task_1" />
<bpmn:sequenceFlow id="SequenceFlow_14kuv1e" sourceRef="SubProcess_1" targetRef="Task_1" />
<bpmn:task id="Task_1fo1fvh" name="FOO&#10;BAR">
<bpmn:incoming>SequenceFlow_13rjp44</bpmn:incoming>
<bpmn:outgoing>SequenceFlow_1h7vuvi</bpmn:outgoing>
</bpmn:task>
<bpmn:sequenceFlow id="SequenceFlow_13rjp44" sourceRef="Task_1" targetRef="Task_1fo1fvh" />
<bpmn:exclusiveGateway id="ExclusiveGateway_1" name="FOO">
<bpmn:incoming>SequenceFlow_1h7vuvi</bpmn:incoming>
</bpmn:exclusiveGateway>
<bpmn:sequenceFlow id="SequenceFlow_1h7vuvi" sourceRef="Task_1fo1fvh" targetRef="ExclusiveGateway_1" />
<bpmn:endEvent id="EndEvent_1">
<bpmn:incoming>SequenceFlow_0kmqqm9</bpmn:incoming>
</bpmn:endEvent>
<bpmn:sequenceFlow id="SequenceFlow_0kmqqm9" sourceRef="Task_1" targetRef="EndEvent_1" />
<bpmn:dataObjectReference id="DataObjectReference_1" dataObjectRef="DataObject_1rq8hb8" />
<bpmn:dataObject id="DataObject_1rq8hb8" />
<bpmn:dataStoreReference id="DataStoreReference_1" />
<bpmn:association id="Association_0ckvfj2" sourceRef="SubProcess_1" targetRef="TextAnnotation_1" />
</bpmn:process>
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="_Collaboration_2">
<bpmndi:BPMNShape id="_BPMNShape_Participant_2" bpmnElement="expanded-pool" isHorizontal="true">
<dc:Bounds x="60" y="48" width="540" height="613" />
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Collaboration_1o0amh9">
<bpmndi:BPMNShape id="Participant_15tkgjw_di" bpmnElement="Participant_1">
<dc:Bounds x="160" y="122" width="600" height="477" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="_BPMNShape_Lane_2" bpmnElement="lane-1" isHorizontal="true">
<dc:Bounds x="90" y="48" width="510" height="277" />
<bpmndi:BPMNShape id="Lane_00fz5ca_di" bpmnElement="Lane_2">
<dc:Bounds x="190" y="122" width="570" height="250" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="_BPMNShape_Lane_3" bpmnElement="nested-lane-1-1" isHorizontal="true">
<dc:Bounds x="120" y="48" width="480" height="133" />
<bpmndi:BPMNShape id="Lane_1qi2aws_di" bpmnElement="Lane_1">
<dc:Bounds x="190" y="372" width="570" height="227" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="_BPMNShape_Lane_4" bpmnElement="nested-lane-1-2" isHorizontal="true">
<dc:Bounds x="120" y="180" width="480" height="145" />
<bpmndi:BPMNShape id="SubProcess_194zznr_di" bpmnElement="SubProcess_1" isExpanded="true">
<dc:Bounds x="311" y="147" width="350" height="200" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="_BPMNShape_Lane_5" bpmnElement="lane-2" isHorizontal="true">
<dc:Bounds x="90" y="324" width="510" height="337" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="_BPMNShape_Lane_6" bpmnElement="nested-lane-no-label" isHorizontal="true">
<dc:Bounds x="120" y="324" width="480" height="337" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="_BPMNShape_SubProcess_2" bpmnElement="subprocess-expanded" isExpanded="true">
<dc:Bounds x="359" y="364" width="200" height="150" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="_BPMNShape_SubProcess_3" bpmnElement="subprocess-collapsed">
<dc:Bounds x="156" y="364" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="nested-embedded-start-event">
<dc:Bounds x="188" y="386" width="36" height="36" />
<bpmndi:BPMNShape id="StartEvent_0909sti_di" bpmnElement="StartEvent_1">
<dc:Bounds x="223" y="229" width="36" height="36" />
<bpmndi:BPMNLabel>
<dc:Bounds x="173" y="427" width="66" height="22" />
<dc:Bounds x="228" y="265" width="25" height="12" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="_BPMNShape_Task_2" bpmnElement="task-nested-embedded">
<dc:Bounds x="432" y="408" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="_BPMNShape_Participant_3" bpmnElement="collapsed-pool" isHorizontal="true">
<dc:Bounds x="672" y="336" width="385" height="100" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="_BPMNShape_TextAnnotation_2" bpmnElement="text-annotation">
<dc:Bounds x="60" y="669" width="147" height="98" />
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="BPMNEdge_Association_1" bpmnElement="Association_1" sourceElement="_BPMNShape_TextAnnotation_2" targetElement="_BPMNShape_SubProcess_2">
<di:waypoint xsi:type="dc:Point" x="190" y="669" />
<di:waypoint xsi:type="dc:Point" x="371" y="514" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNShape id="_BPMNShape_CallActivity_2" bpmnElement="call-activity">
<dc:Bounds x="422" y="202" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_3" bpmnElement="start-event">
<dc:Bounds x="168" y="96" width="36" height="36" />
<bpmndi:BPMNEdge id="SequenceFlow_140ewlr_di" bpmnElement="SequenceFlow_2">
<di:waypoint xsi:type="dc:Point" x="259" y="247" />
<di:waypoint xsi:type="dc:Point" x="311" y="247" />
<bpmndi:BPMNLabel>
<dc:Bounds x="153" y="137" width="66" height="22" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="_BPMNShape_IntermediateThrowEvent_2" bpmnElement="intermediate-throw-event">
<dc:Bounds x="397" y="96" width="36" height="36" />
<bpmndi:BPMNLabel>
<dc:Bounds x="371" y="137" width="90" height="38" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="_BPMNShape_UserTask_2" bpmnElement="user-task">
<dc:Bounds x="180" y="192" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="_BPMNShape_BoundaryEvent_2" bpmnElement="boundary-event">
<dc:Bounds x="228" y="254" width="36" height="36" />
<bpmndi:BPMNLabel>
<dc:Bounds x="200" y="295" width="93" height="22" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="_BPMNShape_ExclusiveGateway_2" bpmnElement="exclusive-gateway" isMarkerVisible="true">
<dc:Bounds x="312" y="89" width="50" height="50" />
<bpmndi:BPMNLabel>
<dc:Bounds x="306" y="48" width="64" height="38" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="BPMNEdge_SequenceFlow_1" bpmnElement="sequenceflow-unlabeled" sourceElement="_BPMNShape_StartEvent_3" targetElement="_BPMNShape_ExclusiveGateway_2">
<di:waypoint xsi:type="dc:Point" x="204" y="114" />
<di:waypoint xsi:type="dc:Point" x="312" y="114" />
<bpmndi:BPMNLabel>
<dc:Bounds x="243" y="114" width="6" height="6" />
<dc:Bounds x="285" y="232" width="0" height="0" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="BPMNEdge_SequenceFlow_2" bpmnElement="sequence-flow-no" sourceElement="_BPMNShape_ExclusiveGateway_2" targetElement="_BPMNShape_CallActivity_2">
<di:waypoint xsi:type="dc:Point" x="337" y="139" />
<di:waypoint xsi:type="dc:Point" x="337" y="242" />
<di:waypoint xsi:type="dc:Point" x="422" y="242" />
<bpmndi:BPMNLabel>
<dc:Bounds x="342" y="204" width="20" height="22" />
</bpmndi:BPMNLabel>
<bpmndi:BPMNShape id="TextAnnotation_0vwfagl_di" bpmnElement="TextAnnotation_1">
<dc:Bounds x="814" y="122" width="100" height="30" />
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="Association_0ckvfj2_di" bpmnElement="Association_0ckvfj2">
<di:waypoint xsi:type="dc:Point" x="661" y="196" />
<di:waypoint xsi:type="dc:Point" x="814" y="152" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="BPMNEdge_SequenceFlow_3" bpmnElement="sequence-flow-yes" sourceElement="_BPMNShape_ExclusiveGateway_2" targetElement="_BPMNShape_IntermediateThrowEvent_2">
<di:waypoint xsi:type="dc:Point" x="362" y="114" />
<di:waypoint xsi:type="dc:Point" x="397" y="114" />
<bpmndi:BPMNShape id="StartEvent_08jn2xd_di" bpmnElement="StartEvent_08jn2xd">
<dc:Bounds x="223" y="416" width="36" height="36" />
<bpmndi:BPMNLabel>
<dc:Bounds x="335.49989765458423" y="114" width="90" height="22" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="BPMNEdge_MessageFlow_1" bpmnElement="message-flow" sourceElement="_BPMNShape_Task_2" targetElement="_BPMNShape_Participant_3">
<di:waypoint xsi:type="dc:Point" x="532" y="448" />
<di:waypoint xsi:type="dc:Point" x="632" y="448" />
<di:waypoint xsi:type="dc:Point" x="632" y="386" />
<di:waypoint xsi:type="dc:Point" x="672" y="386" />
<bpmndi:BPMNLabel>
<dc:Bounds x="612" y="456" width="91" height="38" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNShape id="_BPMNShape_DataStoreReference_2" bpmnElement="data-store-reference">
<dc:Bounds x="156" y="547" width="50" height="50" />
<bpmndi:BPMNLabel>
<dc:Bounds x="149" y="602" width="64" height="22" />
<dc:Bounds x="228" y="452" width="25" height="24" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="_BPMNShape_DataObjectReference_2" bpmnElement="data-object-reference">
<dc:Bounds x="267" y="547" width="36" height="50" />
<bpmndi:BPMNShape id="Task_1i068gk_di" bpmnElement="Task_1">
<dc:Bounds x="436" y="394" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="SequenceFlow_088kva8_di" bpmnElement="SequenceFlow_1">
<di:waypoint xsi:type="dc:Point" x="259" y="434" />
<di:waypoint xsi:type="dc:Point" x="436" y="434" />
<bpmndi:BPMNLabel>
<dc:Bounds x="245" y="602" width="80" height="38" />
<dc:Bounds x="335" y="409" width="25" height="12" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="SequenceFlow_14kuv1e_di" bpmnElement="SequenceFlow_14kuv1e">
<di:waypoint xsi:type="dc:Point" x="486" y="347" />
<di:waypoint xsi:type="dc:Point" x="486" y="394" />
<bpmndi:BPMNLabel>
<dc:Bounds x="501" y="360.5" width="0" height="0" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNShape id="Task_1fo1fvh_di" bpmnElement="Task_1fo1fvh">
<dc:Bounds x="436" y="500" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="SequenceFlow_13rjp44_di" bpmnElement="SequenceFlow_13rjp44">
<di:waypoint xsi:type="dc:Point" x="486" y="474" />
<di:waypoint xsi:type="dc:Point" x="486" y="500" />
<bpmndi:BPMNLabel>
<dc:Bounds x="501" y="477" width="0" height="0" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNShape id="ExclusiveGateway_1dulwbf_di" bpmnElement="ExclusiveGateway_1" isMarkerVisible="true">
<dc:Bounds x="578" y="515" width="50" height="50" />
<bpmndi:BPMNLabel>
<dc:Bounds x="590" y="565" width="25" height="12" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="_BPMNShape_DataInput_2" bpmnElement="data-input">
<dc:Bounds x="372" y="547" width="36" height="50" />
<bpmndi:BPMNEdge id="SequenceFlow_1h7vuvi_di" bpmnElement="SequenceFlow_1h7vuvi">
<di:waypoint xsi:type="dc:Point" x="536" y="540" />
<di:waypoint xsi:type="dc:Point" x="578" y="540" />
<bpmndi:BPMNLabel>
<dc:Bounds x="390" y="602" width="0" height="0" />
<dc:Bounds x="557" y="515" width="0" height="0" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNShape id="EndEvent_0c4s9zs_di" bpmnElement="EndEvent_1">
<dc:Bounds x="585" y="416" width="36" height="36" />
<bpmndi:BPMNLabel>
<dc:Bounds x="603" y="452" width="0" height="0" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="_BPMNShape_DataOutput_2" bpmnElement="data-output">
<dc:Bounds x="504" y="547" width="36" height="50" />
<bpmndi:BPMNEdge id="SequenceFlow_0kmqqm9_di" bpmnElement="SequenceFlow_0kmqqm9">
<di:waypoint xsi:type="dc:Point" x="536" y="434" />
<di:waypoint xsi:type="dc:Point" x="585" y="434" />
<bpmndi:BPMNLabel>
<dc:Bounds x="522" y="602" width="0" height="0" />
<dc:Bounds x="561" y="409" width="0" height="0" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNShape id="DataObjectReference_024w45b_di" bpmnElement="DataObjectReference_1">
<dc:Bounds x="223" y="515" width="36" height="50" />
<bpmndi:BPMNLabel>
<dc:Bounds x="241" y="565" width="0" height="0" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="BPMNEdge_DataInputAssociation_1" bpmnElement="DataInputAssociation_1" sourceElement="_BPMNShape_DataInput_2" targetElement="_BPMNShape_SubProcess_2">
<di:waypoint xsi:type="dc:Point" x="403" y="547" />
<di:waypoint xsi:type="dc:Point" x="420" y="514" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="BPMNEdge_DataOutputAssociation_1" bpmnElement="DataOutputAssociation_1" sourceElement="_BPMNShape_SubProcess_2" targetElement="_BPMNShape_DataOutput_2">
<di:waypoint xsi:type="dc:Point" x="495" y="514" />
<di:waypoint xsi:type="dc:Point" x="510" y="547" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="BPMNEdge_DataInputAssociation_2" bpmnElement="DataInputAssociation_2" sourceElement="_BPMNShape_DataStoreReference_2" targetElement="_BPMNShape_SubProcess_3">
<di:waypoint xsi:type="dc:Point" x="185" y="547" />
<di:waypoint xsi:type="dc:Point" x="200" y="444" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="BPMNEdge_MessageFlow_2" bpmnElement="message-flow-unlabeled" sourceElement="_BPMNShape_Participant_3" targetElement="_BPMNShape_Participant_2">
<di:waypoint xsi:type="dc:Point" x="672" y="386" />
<di:waypoint xsi:type="dc:Point" x="600" y="386" />
<bpmndi:BPMNShape id="DataStoreReference_0b4jv9b_di" bpmnElement="DataStoreReference_1">
<dc:Bounds x="315.54926829268294" y="515" width="50" height="50" />
<bpmndi:BPMNLabel>
<dc:Bounds x="591" y="368" width="90" height="6" />
<dc:Bounds x="341" y="565" width="0" height="0" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNShape id="Task_0da8ze1_di" bpmnElement="empty-task">
<dc:Bounds x="472" y="74" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="SequenceFlow_1r751z0_di" bpmnElement="SequenceFlow_1r751z0">
<di:waypoint xsi:type="dc:Point" x="433" y="114" />
<di:waypoint xsi:type="dc:Point" x="472" y="114" />
<bpmndi:BPMNEdge id="MessageFlow_0pmpiw7_di" bpmnElement="MessageFlow_1">
<di:waypoint xsi:type="dc:Point" x="486" y="580" />
<di:waypoint xsi:type="dc:Point" x="486" y="646" />
<bpmndi:BPMNLabel>
<dc:Bounds x="407.5" y="89" width="90" height="20" />
<dc:Bounds x="488" y="603" width="25" height="13" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNShape id="Participant_0kzj58d_di" bpmnElement="Participant_2">
<dc:Bounds x="161" y="646" width="600" height="250" />
</bpmndi:BPMNShape>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn2:definitions>
</bpmn:definitions>

View File

@ -0,0 +1,133 @@
'use strict';
/* global bootstrapViewer, inject */
var labelEditingModule = require('../../../../lib/features/label-editing'),
coreModule = require('../../../../lib/core'),
draggingModule = require('diagram-js/lib/features/dragging'),
modelingModule = require('diagram-js/lib/features/modeling');
describe('features - label-editing preview', function() {
var diagramXML = require('../../../fixtures/bpmn/features/label-editing/labels.bpmn');
var testModules = [ labelEditingModule, coreModule, draggingModule, modelingModule ];
beforeEach(bootstrapViewer(diagramXML, { modules: testModules }));
describe('activate', function() {
it('[external labels AND text annotations ] should add marker to hide element on activate', inject(function(directEditing, elementRegistry) {
// given
var textAnnotation = elementRegistry.get('TextAnnotation_1');
// when
directEditing.activate(textAnnotation);
// then
var gfx = elementRegistry.getGraphics(textAnnotation);
expect(gfx.classList.contains('djs-element-hidden')).to.be.true;
}));
it('[internal labels] should add marker to hide label on activate', inject(function(directEditing, elementRegistry) {
// given
var task = elementRegistry.get('Task_1');
// when
directEditing.activate(task);
// then
var gfx = elementRegistry.getGraphics(task);
expect(gfx.classList.contains('djs-label-hidden')).to.be.true;
}));
});
describe('resize', function() {
it('[text annotations] should resize preview on resize', inject(function(directEditing, elementRegistry, eventBus, labelEditingPreview) {
// given
var textAnnotation = elementRegistry.get('TextAnnotation_1');
directEditing.activate(textAnnotation);
// when
eventBus.fire('directEditing.resize', {
width: 200,
height: 200,
dx: 100,
dy: 100
});
// then
var bounds = labelEditingPreview.path.getBBox();
expect(bounds).to.eql({ x: 0, y: 0, width: 10, height: 300 });
}));
it('[text annotations] should resize preview below 0', inject(function(directEditing, elementRegistry, eventBus, labelEditingPreview) {
// given
var textAnnotation = elementRegistry.get('TextAnnotation_1');
directEditing.activate(textAnnotation);
// when
eventBus.fire('directEditing.resize', {
width: 200,
height: 200,
dx: -300,
dy: -300
});
// then
var bounds = labelEditingPreview.path.getBBox();
expect(bounds).to.eql({ x: 0, y: 0, width: 10, height: 0 });
}));
});
describe('complete/cancel', function() {
it('[external labels AND text annotations] should remove marker to hide element on complete', inject(function(directEditing, elementRegistry) {
// given
var textAnnotation = elementRegistry.get('TextAnnotation_1');
directEditing.activate(textAnnotation);
// when
directEditing.complete();
// then
var gfx = elementRegistry.getGraphics(textAnnotation);
expect(gfx.classList.contains('djs-element-hidden')).to.be.false;
}));
it('[internal labels] should remove marker to hide label on complete', inject(function(directEditing, elementRegistry) {
// given
var task = elementRegistry.get('Task_1');
directEditing.activate(task);
// when
directEditing.complete();
// then
var gfx = elementRegistry.getGraphics(task);
expect(gfx.classList.contains('djs-label-hidden')).to.be.false;
}));
});
});

View File

@ -1,14 +1,7 @@
'use strict';
var TestHelper = require('../../../TestHelper');
/* global bootstrapViewer, inject */
TestHelper.insertCSS('diagram-js-label-editing.css',
'div { box-sizing: border-box; }' +
'div[contenteditable=true] { line-height: 14px; font-family: Arial; font-size: 12px }'
);
var labelEditingModule = require('../../../../lib/features/label-editing'),
coreModule = require('../../../../lib/core'),
@ -17,6 +10,10 @@ var labelEditingModule = require('../../../../lib/features/label-editing'),
var LabelUtil = require('../../../../lib/features/label-editing/LabelUtil');
var MEDIUM_LINE_HEIGHT = 14;
var DELTA = 3;
function triggerKeyEvent(element, event, code) {
var e = document.createEvent('Events');
@ -30,6 +27,13 @@ function triggerKeyEvent(element, event, code) {
return element.dispatchEvent(e);
}
function expectBounds(parent, bounds) {
expect(parent.offsetLeft).to.be.closeTo(bounds.x, DELTA);
expect(parent.offsetTop).to.be.closeTo(bounds.y, DELTA);
expect(parent.offsetWidth).to.be.closeTo(bounds.width, DELTA);
expect(parent.offsetHeight).to.be.closeTo(bounds.height, DELTA);
}
describe('features - label-editing', function() {
var diagramXML = require('../../../fixtures/bpmn/features/label-editing/labels.bpmn');
@ -44,7 +48,7 @@ describe('features - label-editing', function() {
it('should register on dblclick', inject(function(elementRegistry, directEditing, eventBus) {
// given
var shape = elementRegistry.get('task-nested-embedded');
var shape = elementRegistry.get('Task_1');
// when
eventBus.fire('element.dblclick', { element: shape });
@ -60,7 +64,7 @@ describe('features - label-editing', function() {
it('should cancel on <ESC>', inject(function(elementRegistry, directEditing, eventBus) {
// given
var shape = elementRegistry.get('task-nested-embedded'),
var shape = elementRegistry.get('Task_1'),
task = shape.businessObject;
var oldName = task.name;
@ -84,12 +88,12 @@ describe('features - label-editing', function() {
it('should complete on drag start', inject(function(elementRegistry, directEditing, dragging) {
// given
var shape = elementRegistry.get('task-nested-embedded'),
var shape = elementRegistry.get('Task_1'),
task = shape.businessObject;
directEditing.activate(shape);
directEditing._textbox.content.innerText = 'FOO BAR';
directEditing._textbox.content.textContent = 'FOO BAR';
// when
dragging.init(null, { x: 0, y: 0 }, 'foo');
@ -102,7 +106,7 @@ describe('features - label-editing', function() {
it('should submit on root element click', inject(function(elementRegistry, directEditing, canvas, eventBus) {
// given
var shape = elementRegistry.get('task-nested-embedded'),
var shape = elementRegistry.get('Task_1'),
task = shape.businessObject;
// activate
@ -174,7 +178,7 @@ describe('features - label-editing', function() {
it('should update via command stack', function() {
// given
var diagramElement = elementRegistry.get('user-task');
var diagramElement = elementRegistry.get('Task_1');
var listenerCalled;
@ -194,7 +198,7 @@ describe('features - label-editing', function() {
it('should undo via command stack', inject(function(commandStack) {
// given
var diagramElement = elementRegistry.get('user-task');
var diagramElement = elementRegistry.get('Task_1');
var oldLabel = LabelUtil.getLabel(diagramElement);
@ -217,7 +221,7 @@ describe('features - label-editing', function() {
it('on shape change', function() {
// given
var diagramElement = elementRegistry.get('user-task');
var diagramElement = elementRegistry.get('Task_1');
var listenerCalled;
@ -239,7 +243,7 @@ describe('features - label-editing', function() {
it('on connection on change', function() {
// given
var diagramElement = elementRegistry.get('sequence-flow-no');
var diagramElement = elementRegistry.get('SequenceFlow_1');
var listenerCalled;
@ -300,297 +304,328 @@ describe('features - label-editing', function() {
}
it('task', directEdit('user-task'));
it('task', directEdit('Task_1'));
it('gateway', directEdit('exclusive-gateway'));
it('gateway', directEdit('ExclusiveGateway_1'));
it('gateway via label', directEdit('exclusive-gateway_label'));
it('gateway via label', directEdit('ExclusiveGateway_1_label'));
it('event', directEdit('intermediate-throw-event'));
it('event', directEdit('StartEvent_1'));
it('event via label', directEdit('intermediate-throw-event_label'));
it('event via label', directEdit('StartEvent_1_label'));
it('event without label', directEdit('start-event'));
it('event without label', directEdit('EndEvent_1'));
it('data store reference', directEdit('data-store-reference'));
it('data store reference', directEdit('DataStoreReference_1'));
it('data object reference', directEdit('data-object-reference'));
it('data object reference', directEdit('DataObjectReference_1'));
it('sequenceflow', directEdit('sequence-flow-yes'));
it('sequenceflow', directEdit('SequenceFlow_1'));
it('sequenceflow without label', directEdit('sequenceflow-unlabeled'));
it('sequenceflow via label', directEdit('SequenceFlow_1_label'));
it('sequenceflow via label', directEdit('sequence-flow-yes_label'));
it('sequenceflow without label', directEdit('SequenceFlow_2'));
it('message flow', directEdit('message-flow'));
it('message flow', directEdit('MessageFlow_1'));
it('message flow via label', directEdit('message-flow_label'));
it('message flow via label', directEdit('MessageFlow_1_label'));
it('pool', directEdit('expanded-pool'));
it('pool', directEdit('Participant_1'));
it('pool, collapsed', directEdit('collapsed-pool'));
it('pool, collapsed', directEdit('Participant_2'));
it('lane with label', directEdit('nested-lane-1-2'));
it('lane with label', directEdit('Lane_1'));
it('lane without label', directEdit('nested-lane-no-label'));
it('lane without label', directEdit('Lane_2'));
});
});
describe('sizing', function() {
describe('sizes', function() {
var testModules = [ labelEditingModule, coreModule, modelingModule ];
function testTextboxSizing(elementId, zoom, width, height, content) {
return inject(function(canvas, elementRegistry, directEditing) {
// zoom in
canvas.zoom(zoom);
// grab one element
var shape = elementRegistry.get(elementId);
// activate label editing
directEditing.activate(shape);
// grab the textarea
var textbox = directEditing._textbox;
// optionally set content text
if (content) {
textbox.content.innerText = content;
}
if (width === 'auto') {
width = shape.width;
}
if (height === 'auto') {
height = shape.height;
}
// then
if (typeof width === 'object' && width.min) {
expect(textbox.content.offsetWidth).to.be.at.least(width.min);
} else {
expect(textbox.content.offsetWidth).to.be.equal(width);
}
if (typeof height === 'object' && height.min) {
expect(textbox.content.offsetHeight).to.be.at.least(height.min);
} else {
expect(textbox.content.offsetHeight).to.be.equal(height);
}
});
}
beforeEach(bootstrapViewer(diagramXML, {
modules: testModules,
canvas: { deferUpdate: false }
}));
describe('height', function() {
var oneLineText = 'One line',
twoLineText = 'Two\nlines',
tenLineText = '1\n2\n3\n4\n5\n6\n7\n8\n9\n0';
describe('bounds', function() {
describe('external labels', function() {
it('[no text] should have min height', testTextboxSizing('start-event', 1, 150, 20));
it('[zoom 1] should have fixed width and element height', inject(function(canvas, directEditing, elementRegistry) {
var zoom = 1;
it('[1 line text] should be 1 line high', testTextboxSizing('start-event', 1, 150, 20, oneLineText));
canvas.zoom(zoom);
it('[2 line text] should be 2 line high', testTextboxSizing('start-event', 1, 150, 34, twoLineText));
var startEvent = elementRegistry.get('StartEvent_1');
it('[10 line text] should be 10 line high', testTextboxSizing('start-event', 1, 150, 146, tenLineText));
var bounds = canvas.getAbsoluteBBox(startEvent.label);
var mid = {
x: bounds.x + bounds.width / 2,
y: bounds.y + bounds.height / 2
};
directEditing.activate(startEvent);
expectBounds(directEditing._textbox.parent, {
x: mid.x - (45 * zoom),
y: bounds.y - (7 * zoom),
width: (90 * zoom),
height: bounds.height + (5 * zoom) + 7
});
}));
it('[zoom 1.5] should have fixed width and element height', inject(function(canvas, directEditing, elementRegistry) {
var zoom = 1.5;
canvas.zoom(zoom);
var startEvent = elementRegistry.get('StartEvent_1');
var bounds = canvas.getAbsoluteBBox(startEvent.label);
var mid = {
x: bounds.x + bounds.width / 2,
y: bounds.y + bounds.height / 2
};
directEditing.activate(startEvent);
expectBounds(directEditing._textbox.parent, {
x: mid.x - (45 * zoom),
y: bounds.y - (7 * zoom),
width: (90 * zoom),
height: bounds.height + (5 * zoom) + (7 * zoom)
});
}));
});
describe('internal labels', function() {
it('[no text] should have fixed dimensions', testTextboxSizing('empty-task', 1, 'auto', 'auto'));
it('[zoom 1] should have element size', inject(function(canvas, directEditing, elementRegistry) {
var zoom = 1;
it('[1 line text] should have fixed dimensions', testTextboxSizing('empty-task', 1, 'auto', 'auto', oneLineText));
canvas.zoom(zoom);
it('[2 line text] should have fixed dimensions', testTextboxSizing('empty-task', 1, 'auto', 'auto', twoLineText));
var task = elementRegistry.get('Task_1');
it('[10 line text] should have fixed dimensions', testTextboxSizing('empty-task', 1, 'auto', 'auto', tenLineText));
var bounds = canvas.getAbsoluteBBox(task);
directEditing.activate(task);
expectBounds(directEditing._textbox.parent, bounds);
}));
it('[zoom 1.5] should have element size', inject(function(canvas, directEditing, elementRegistry) {
var zoom = 1.5;
canvas.zoom(zoom);
var task = elementRegistry.get('Task_1');
var bounds = canvas.getAbsoluteBBox(task);
directEditing.activate(task);
expectBounds(directEditing._textbox.parent, bounds);
}));
});
describe('sequence flows', function() {
it('[no text] should have min height', testTextboxSizing('sequenceflow-unlabeled', 1, 150, 20));
it('[zoom 1] should have fixed width and element height', inject(function(canvas, directEditing, elementRegistry) {
var zoom = 1;
it('[1 line text] should be 1 line high', testTextboxSizing('sequenceflow-unlabeled', 1, 150, 20, oneLineText));
canvas.zoom(zoom);
it('[2 line text] should be 2 line high', testTextboxSizing('sequenceflow-unlabeled', 1, 150, 34, twoLineText));
var sequenceFlow = elementRegistry.get('SequenceFlow_1');
it('[10 line text] should be 10 line high', testTextboxSizing('sequenceflow-unlabeled', 1, 150, 146, tenLineText));
var bounds = canvas.getAbsoluteBBox(sequenceFlow.label);
var mid = {
x: bounds.x + bounds.width / 2,
y: bounds.y + bounds.height / 2
};
directEditing.activate(sequenceFlow);
expectBounds(directEditing._textbox.parent, {
x: mid.x - (45 * zoom),
y: bounds.y - (7 * zoom),
width: (90 * zoom),
height: bounds.height + (5 * zoom) + 7
});
}));
it('[zoom 1.5] should have fixed width and element height', inject(function(canvas, directEditing, elementRegistry) {
var zoom = 1.5;
canvas.zoom(zoom);
var sequenceflow = elementRegistry.get('SequenceFlow_1');
var bounds = canvas.getAbsoluteBBox(sequenceflow.label);
var mid = {
x: bounds.x + bounds.width / 2,
y: bounds.y + bounds.height / 2
};
directEditing.activate(sequenceflow);
expectBounds(directEditing._textbox.parent, {
x: mid.x - (45 * zoom),
y: bounds.y - (7 * zoom),
width: (90 * zoom),
height: bounds.height + (5 * zoom) + (7 * zoom)
});
}));
});
describe('text annotation', function() {
describe('text annotations', function() {
it('[no text] should have element height', testTextboxSizing('text-annotation', 1, 100, 98));
it('[zoom 1] should have element size', inject(function(canvas, directEditing, elementRegistry) {
var zoom = 1;
it('[1 line text] should have element height', testTextboxSizing('text-annotation', 1, 100, 98, oneLineText));
canvas.zoom(zoom);
it('[2 line text] should have element height', testTextboxSizing('text-annotation', 1, 100, 98, twoLineText));
var textAnnotation = elementRegistry.get('TextAnnotation_1');
it('[10 line text] should have element height', testTextboxSizing('text-annotation', 1, { min: 100 }, 98, tenLineText));
var bounds = canvas.getAbsoluteBBox(textAnnotation);
directEditing.activate(textAnnotation);
expectBounds(directEditing._textbox.parent, bounds);
}));
it('[zoom 1.5] should have element size', inject(function(canvas, directEditing, elementRegistry) {
var zoom = 1.5;
canvas.zoom(zoom);
var textAnnotation = elementRegistry.get('TextAnnotation_1');
var bounds = canvas.getAbsoluteBBox(textAnnotation);
directEditing.activate(textAnnotation);
expectBounds(directEditing._textbox.parent, bounds);
}));
});
describe('expanded sub process', function() {
describe('expanded sub processes', function() {
it('[no text] should have min height', testTextboxSizing('subprocess-expanded', 1, 200, 20));
it('[zoom 1] should have element width and height to fit text', inject(function(canvas, directEditing, elementRegistry) {
var zoom = 1;
it('[1 line text] should be 1 line high', testTextboxSizing('subprocess-expanded', 1, 200, 20, oneLineText));
canvas.zoom(zoom);
it('[2 line text] should be 2 line high', testTextboxSizing('subprocess-expanded', 1, 200, 34, twoLineText));
var subProcess = elementRegistry.get('SubProcess_1');
it('[10 line text] should be max 3 line high', testTextboxSizing('subprocess-expanded', 1, 200, 48, tenLineText));
var bounds = canvas.getAbsoluteBBox(subProcess);
directEditing.activate(subProcess);
expectBounds(directEditing._textbox.parent, {
x: bounds.x,
y: bounds.y,
width: bounds.width,
height: (MEDIUM_LINE_HEIGHT * zoom) + (7 * 2 * zoom)
});
}));
it('[zoom 1.5] should have element width and height to fit text', inject(function(canvas, directEditing, elementRegistry) {
var zoom = 1.5;
canvas.zoom(zoom);
var subProcess = elementRegistry.get('SubProcess_1');
var bounds = canvas.getAbsoluteBBox(subProcess);
directEditing.activate(subProcess);
expectBounds(directEditing._textbox.parent, {
x: bounds.x,
y: bounds.y,
width: bounds.width,
height: (MEDIUM_LINE_HEIGHT * zoom) + (7 * 2 * zoom)
});
}));
});
describe('pools/lanes', function() {
it('[no text] should have min height', testTextboxSizing('expanded-pool', 1, 150, 20));
it('[zoom 1] should have width of element height, height of 30', inject(function(canvas, directEditing, elementRegistry) {
var zoom = 1;
it('[1 line text] should be 1 line high', testTextboxSizing('expanded-pool', 1, 150, 20, oneLineText));
canvas.zoom(zoom);
it('[2 line text] should be 2 line high', testTextboxSizing('expanded-pool', 1, 150, 34, twoLineText));
var pool = elementRegistry.get('Participant_1');
it('[10 line text] should be max 2 line high', testTextboxSizing('expanded-pool', 1, 150, 34, tenLineText));
var bounds = canvas.getAbsoluteBBox(pool);
var mid = {
x: bounds.x + bounds.width / 2,
y: bounds.y + bounds.height / 2
};
});
directEditing.activate(pool);
});
describe('width', function() {
var oneWord = 'foobar',
fiveWords = 'lorem ipsum dolor foobar foobar',
longWord = 'loremipsumdolorfoobar';
describe('external labels', function() {
it('[no text] should have fixed width', testTextboxSizing('start-event', 1, 150, 20));
it('[one word] should have fixed width', testTextboxSizing('start-event', 1, 150, 20, oneWord));
it('[five words] should have fixed width, line break', testTextboxSizing('start-event', 1, 150, { min: 34 }, fiveWords));
it('[long word] should have fixed width', testTextboxSizing('start-event', 1, 150, { min: 20 }, longWord));
});
expectBounds(directEditing._textbox.parent, {
x: bounds.x - (bounds.height / 2) + (15 * zoom),
y: mid.y - (30 * zoom) / 2,
width: bounds.height * zoom,
height: 30 * zoom
});
}));
describe('internal labels', function() {
it('[zoom 1.5] should have width of element height, height of 30', inject(function(canvas, directEditing, elementRegistry) {
var zoom = 1.5;
it('[no text] should have fixed dimensions (task)', testTextboxSizing('empty-task', 1, 100, 80));
canvas.zoom(zoom);
it('[no text] should have fixed dimensions (call activity)', testTextboxSizing('call-activity', 1, 100, 80));
var pool = elementRegistry.get('Participant_1');
it('[no text] should have fixed dimensions (collapsed sub process)', testTextboxSizing('subprocess-collapsed', 1, 100, 80));
var bounds = canvas.getAbsoluteBBox(pool);
var mid = {
x: bounds.x + bounds.width / 2,
y: bounds.y + bounds.height / 2
};
it('[one word] should have fixed dimensions', testTextboxSizing('empty-task', 1, 100, 80, oneWord));
directEditing.activate(pool);
it('[five words] should have fixed dimensions', testTextboxSizing('empty-task', 1, 100, 80, fiveWords));
it('[long word] should have fixed dimensions', testTextboxSizing('empty-task', 1, 100, 80, longWord));
describe('zoom', function() {
it('should have fixed dimensions (low zoom)', testTextboxSizing('empty-task', 0.5, 100, 80, oneWord));
it('should have fixed dimensions (high zoom)', testTextboxSizing('empty-task', 1.5, 150, 120, oneWord));
it('should center text box position (low zoom)', inject(function(canvas, elementRegistry, directEditing) {
// given
canvas.zoom(0.5, { x: 0, y: 0 });
var shape = elementRegistry.get('empty-task');
// when
directEditing.activate(shape);
// then
var textbox = directEditing._textbox;
expect(textbox.content.offsetLeft).to.equal(211);
expect(textbox.content.offsetTop).to.equal(17);
}));
});
});
describe('sequence flows', function() {
it('[no text] should have fixed width', testTextboxSizing('sequenceflow-unlabeled', 1, 150, 20));
it('[one word] should have fixed width', testTextboxSizing('sequenceflow-unlabeled', 1, 150, 20, oneWord));
it('[five words] should have fixed width, line break', testTextboxSizing('sequenceflow-unlabeled', 1, 150, { min: 34 }, fiveWords));
it('[long word] should have fixed width', testTextboxSizing('sequenceflow-unlabeled', 1, 150, { min: 20 }, longWord));
});
describe('text annotation', function() {
it('[no text] should have min width', testTextboxSizing('text-annotation', 1, 100, 98));
it('[one word] should have min width', testTextboxSizing('text-annotation', 1, 100, 98, oneWord));
it('[five words] should expand width', testTextboxSizing('text-annotation', 1, { min: 176 }, 98, fiveWords));
it('[long word] should expand width', testTextboxSizing('text-annotation', 1, { min: 129 }, 98, longWord));
});
describe('expanded sub process', function() {
it('[no text] should have fixed width', testTextboxSizing('subprocess-expanded', 1, 200, 20));
it('[one word] should have fixed width', testTextboxSizing('subprocess-expanded', 1, 200, 20, oneWord));
it('[five words] should have fixed width, line break',
testTextboxSizing('subprocess-expanded', 1, 200, 34, 'five yreallyreallyreally loooooong woooords'));
it('[long word] should have fixed width', testTextboxSizing('subprocess-expanded', 1, 200, 20, longWord));
});
describe('pools/lanes', function() {
it('[no text] should have fixed width', testTextboxSizing('expanded-pool', 1, 150, 20));
it('[one word] should have fixed width', testTextboxSizing('expanded-pool', 1, 150, 20, oneWord));
it('[five words] should have fixed width, line break', testTextboxSizing('expanded-pool', 1, 150, 34, fiveWords));
it('[long word] should have fixed width', testTextboxSizing('expanded-pool', 1, 150, { min: 20 }, longWord));
expectBounds(directEditing._textbox.parent, {
x: bounds.x - (bounds.height / 2) + (15 * zoom),
y: mid.y - (30 * zoom) / 2,
width: bounds.height,
height: 30 * zoom
});
}));
});

View File

@ -6,6 +6,8 @@ var Modeler = require('../../../../lib/Modeler');
var TestContainer = require('mocha-test-container-support');
var DELTA = 2;
describe('label bounds', function() {
@ -126,7 +128,7 @@ describe('label bounds', function() {
// then
var expectedX = getExpectedX(shape);
expect(shape.label.x).to.equal(expectedX);
expect(shape.label.x).to.be.closeTo(expectedX, DELTA);
}));