You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
426 lines
9.3 KiB
426 lines
9.3 KiB
import { |
|
assign |
|
} from 'min-dash'; |
|
|
|
import { |
|
getLabel |
|
} from './LabelUtil'; |
|
|
|
import { |
|
is |
|
} from '../../util/ModelUtil'; |
|
|
|
import { isAny } from '../modeling/util/ModelingUtil'; |
|
import { isExpanded } from '../../util/DiUtil'; |
|
|
|
import { |
|
getExternalLabelMid, |
|
isLabelExternal, |
|
hasExternalLabel, |
|
isLabel |
|
} from '../../util/LabelUtil'; |
|
|
|
var HIGH_PRIORITY = 2000; |
|
|
|
|
|
export default function LabelEditingProvider( |
|
eventBus, bpmnFactory, canvas, directEditing, |
|
modeling, resizeHandles, textRenderer) { |
|
|
|
this._bpmnFactory = bpmnFactory; |
|
this._canvas = canvas; |
|
this._modeling = modeling; |
|
this._textRenderer = textRenderer; |
|
|
|
directEditing.registerProvider(this); |
|
|
|
// listen to dblclick on non-root elements |
|
eventBus.on('element.dblclick', function(event) { |
|
activateDirectEdit(event.element, true); |
|
}); |
|
|
|
// complete on followup canvas operation |
|
eventBus.on([ |
|
'autoPlace.start', |
|
'canvas.viewbox.changing', |
|
'drag.init', |
|
'element.mousedown', |
|
'popupMenu.open', |
|
'root.set', |
|
'selection.changed' |
|
], function(event) { |
|
|
|
if (directEditing.isActive()) { |
|
directEditing.complete(); |
|
} |
|
}); |
|
|
|
eventBus.on([ |
|
'shape.remove', |
|
'connection.remove' |
|
], HIGH_PRIORITY, function(event) { |
|
|
|
if (directEditing.isActive(event.element)) { |
|
directEditing.cancel(); |
|
} |
|
}); |
|
|
|
// cancel on command stack changes |
|
eventBus.on([ 'commandStack.changed' ], function(e) { |
|
if (directEditing.isActive()) { |
|
directEditing.cancel(); |
|
} |
|
}); |
|
|
|
|
|
eventBus.on('directEditing.activate', function(event) { |
|
resizeHandles.removeResizers(); |
|
}); |
|
|
|
eventBus.on('create.end', 500, function(event) { |
|
|
|
var context = event.context, |
|
element = context.shape, |
|
canExecute = event.context.canExecute, |
|
isTouch = event.isTouch; |
|
|
|
// TODO(nikku): we need to find a way to support the |
|
// direct editing on mobile devices; right now this will |
|
// break for desworkflowediting on mobile devices |
|
// as it breaks the user interaction workflow |
|
|
|
// TODO(nre): we should temporarily focus the edited element |
|
// here and release the focused viewport after the direct edit |
|
// operation is finished |
|
if (isTouch) { |
|
return; |
|
} |
|
|
|
if (!canExecute) { |
|
return; |
|
} |
|
|
|
if (context.hints && context.hints.createElementsBehavior === false) { |
|
return; |
|
} |
|
|
|
activateDirectEdit(element); |
|
}); |
|
|
|
eventBus.on('autoPlace.end', 500, function(event) { |
|
activateDirectEdit(event.shape); |
|
}); |
|
|
|
|
|
function activateDirectEdit(element, force) { |
|
if (force || |
|
isAny(element, [ 'bpmn:Task', 'bpmn:TextAnnotation' ]) || |
|
isCollapsedSubProcess(element)) { |
|
|
|
directEditing.activate(element); |
|
} |
|
} |
|
|
|
} |
|
|
|
LabelEditingProvider.$inject = [ |
|
'eventBus', |
|
'bpmnFactory', |
|
'canvas', |
|
'directEditing', |
|
'modeling', |
|
'resizeHandles', |
|
'textRenderer' |
|
]; |
|
|
|
|
|
/** |
|
* Activate direct editing for activities and text annotations. |
|
* |
|
* @param {djs.model.Base} element |
|
* |
|
* @return {Object} an object with properties bounds (position and size), text and options |
|
*/ |
|
LabelEditingProvider.prototype.activate = function(element) { |
|
|
|
// text |
|
var text = getLabel(element); |
|
|
|
if (text === undefined) { |
|
return; |
|
} |
|
|
|
var context = { |
|
text: text |
|
}; |
|
|
|
// bounds |
|
var bounds = this.getEditingBBox(element); |
|
|
|
assign(context, bounds); |
|
|
|
var options = {}; |
|
|
|
// tasks |
|
if ( |
|
isAny(element, [ |
|
'bpmn:Task', |
|
'bpmn:Participant', |
|
'bpmn:Lane', |
|
'bpmn:CallActivity' |
|
]) || |
|
isCollapsedSubProcess(element) |
|
) { |
|
assign(options, { |
|
centerVertically: true |
|
}); |
|
} |
|
|
|
// external labels |
|
if (isLabelExternal(element)) { |
|
assign(options, { |
|
autoResize: true |
|
}); |
|
} |
|
|
|
// text annotations |
|
if (is(element, 'bpmn:TextAnnotation')) { |
|
assign(options, { |
|
resizable: true, |
|
autoResize: true |
|
}); |
|
} |
|
|
|
assign(context, { |
|
options: options |
|
}); |
|
|
|
return context; |
|
}; |
|
|
|
|
|
/** |
|
* Get the editing bounding box based on the element's size and position |
|
* |
|
* @param {djs.model.Base} element |
|
* |
|
* @return {Object} an object containing information about position |
|
* and size (fixed or minimum and/or maximum) |
|
*/ |
|
LabelEditingProvider.prototype.getEditingBBox = function(element) { |
|
var canvas = this._canvas; |
|
|
|
var target = element.label || element; |
|
|
|
var bbox = canvas.getAbsoluteBBox(target); |
|
|
|
var mid = { |
|
x: bbox.x + bbox.width / 2, |
|
y: bbox.y + bbox.height / 2 |
|
}; |
|
|
|
// default position |
|
var bounds = { x: bbox.x, y: bbox.y }; |
|
|
|
var zoom = canvas.zoom(); |
|
|
|
var defaultStyle = this._textRenderer.getDefaultStyle(), |
|
externalStyle = this._textRenderer.getExternalStyle(); |
|
|
|
// take zoom into account |
|
var externalFontSize = externalStyle.fontSize * zoom, |
|
externalLineHeight = externalStyle.lineHeight, |
|
defaultFontSize = defaultStyle.fontSize * zoom, |
|
defaultLineHeight = defaultStyle.lineHeight; |
|
|
|
var style = { |
|
fontFamily: this._textRenderer.getDefaultStyle().fontFamily, |
|
fontWeight: this._textRenderer.getDefaultStyle().fontWeight |
|
}; |
|
|
|
// adjust for expanded pools AND lanes |
|
if (is(element, 'bpmn:Lane') || isExpandedPool(element)) { |
|
|
|
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: defaultFontSize + 'px', |
|
lineHeight: defaultLineHeight, |
|
paddingTop: (7 * zoom) + 'px', |
|
paddingBottom: (7 * zoom) + 'px', |
|
paddingLeft: (5 * zoom) + 'px', |
|
paddingRight: (5 * zoom) + 'px', |
|
transform: 'rotate(-90deg)' |
|
}); |
|
} |
|
|
|
|
|
// internal labels for tasks and collapsed call activities, |
|
// sub processes and participants |
|
if (isAny(element, [ 'bpmn:Task', 'bpmn:CallActivity' ]) || |
|
isCollapsedPool(element) || |
|
isCollapsedSubProcess(element)) { |
|
|
|
assign(bounds, { |
|
width: bbox.width, |
|
height: bbox.height |
|
}); |
|
|
|
assign(style, { |
|
fontSize: defaultFontSize + 'px', |
|
lineHeight: defaultLineHeight, |
|
paddingTop: (7 * zoom) + 'px', |
|
paddingBottom: (7 * zoom) + 'px', |
|
paddingLeft: (5 * zoom) + 'px', |
|
paddingRight: (5 * zoom) + 'px' |
|
}); |
|
} |
|
|
|
|
|
// internal labels for expanded sub processes |
|
if (isExpandedSubProcess(element)) { |
|
assign(bounds, { |
|
width: bbox.width, |
|
x: bbox.x |
|
}); |
|
|
|
assign(style, { |
|
fontSize: defaultFontSize + 'px', |
|
lineHeight: defaultLineHeight, |
|
paddingTop: (7 * zoom) + 'px', |
|
paddingBottom: (7 * zoom) + 'px', |
|
paddingLeft: (5 * zoom) + 'px', |
|
paddingRight: (5 * zoom) + 'px' |
|
}); |
|
} |
|
|
|
var width = 90 * zoom, |
|
paddingTop = 7 * zoom, |
|
paddingBottom = 4 * zoom; |
|
|
|
// external labels for events, data elements, gateways, groups and connections |
|
if (target.labelTarget) { |
|
assign(bounds, { |
|
width: width, |
|
height: bbox.height + paddingTop + paddingBottom, |
|
x: mid.x - width / 2, |
|
y: bbox.y - paddingTop |
|
}); |
|
|
|
assign(style, { |
|
fontSize: externalFontSize + 'px', |
|
lineHeight: externalLineHeight, |
|
paddingTop: paddingTop + 'px', |
|
paddingBottom: paddingBottom + 'px' |
|
}); |
|
} |
|
|
|
// external label not yet created |
|
if (isLabelExternal(target) |
|
&& !hasExternalLabel(target) |
|
&& !isLabel(target)) { |
|
|
|
var externalLabelMid = getExternalLabelMid(element); |
|
|
|
var absoluteBBox = canvas.getAbsoluteBBox({ |
|
x: externalLabelMid.x, |
|
y: externalLabelMid.y, |
|
width: 0, |
|
height: 0 |
|
}); |
|
|
|
var height = externalFontSize + paddingTop + paddingBottom; |
|
|
|
assign(bounds, { |
|
width: width, |
|
height: height, |
|
x: absoluteBBox.x - width / 2, |
|
y: absoluteBBox.y - height / 2 |
|
}); |
|
|
|
assign(style, { |
|
fontSize: externalFontSize + 'px', |
|
lineHeight: externalLineHeight, |
|
paddingTop: paddingTop + 'px', |
|
paddingBottom: paddingBottom + 'px' |
|
}); |
|
} |
|
|
|
// text annotations |
|
if (is(element, 'bpmn:TextAnnotation')) { |
|
assign(bounds, { |
|
width: bbox.width, |
|
height: bbox.height, |
|
minWidth: 30 * zoom, |
|
minHeight: 10 * zoom |
|
}); |
|
|
|
assign(style, { |
|
textAlign: 'left', |
|
paddingTop: (5 * zoom) + 'px', |
|
paddingBottom: (7 * zoom) + 'px', |
|
paddingLeft: (7 * zoom) + 'px', |
|
paddingRight: (5 * zoom) + 'px', |
|
fontSize: defaultFontSize + 'px', |
|
lineHeight: defaultLineHeight |
|
}); |
|
} |
|
|
|
return { bounds: bounds, style: style }; |
|
}; |
|
|
|
|
|
LabelEditingProvider.prototype.update = function( |
|
element, newLabel, |
|
activeContextText, bounds) { |
|
|
|
var newBounds, |
|
bbox; |
|
|
|
if (is(element, 'bpmn:TextAnnotation')) { |
|
|
|
bbox = this._canvas.getAbsoluteBBox(element); |
|
|
|
newBounds = { |
|
x: element.x, |
|
y: element.y, |
|
width: element.width / bbox.width * bounds.width, |
|
height: element.height / bbox.height * bounds.height |
|
}; |
|
} |
|
|
|
if (isEmptyText(newLabel)) { |
|
newLabel = null; |
|
} |
|
|
|
this._modeling.updateLabel(element, newLabel, newBounds); |
|
}; |
|
|
|
|
|
|
|
// helpers ////////////////////// |
|
|
|
function isCollapsedSubProcess(element) { |
|
return is(element, 'bpmn:SubProcess') && !isExpanded(element); |
|
} |
|
|
|
function isExpandedSubProcess(element) { |
|
return is(element, 'bpmn:SubProcess') && isExpanded(element); |
|
} |
|
|
|
function isCollapsedPool(element) { |
|
return is(element, 'bpmn:Participant') && !isExpanded(element); |
|
} |
|
|
|
function isExpandedPool(element) { |
|
return is(element, 'bpmn:Participant') && isExpanded(element); |
|
} |
|
|
|
function isEmptyText(label) { |
|
return !label || !label.trim(); |
|
} |