123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402 |
- import {
- assign
- } from 'min-dash';
- import inherits from 'inherits';
- import {
- is,
- getBusinessObject
- } from '../../../util/ModelUtil';
- import {
- isLabelExternal,
- getExternalLabelMid,
- hasExternalLabel,
- isLabel
- } from '../../../util/LabelUtil';
- import {
- getLabel
- } from '../../label-editing/LabelUtil';
- import {
- getLabelAdjustment
- } from './util/LabelLayoutUtil';
- import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';
- import {
- getNewAttachPoint
- } from 'diagram-js/lib/util/AttachUtil';
- import {
- getMid,
- roundPoint
- } from 'diagram-js/lib/layout/LayoutUtil';
- import {
- delta
- } from 'diagram-js/lib/util/PositionUtil';
- import {
- sortBy
- } from 'min-dash';
- import {
- getDistancePointLine,
- perpendicularFoot
- } from './util/GeometricUtil';
- var DEFAULT_LABEL_DIMENSIONS = {
- width: 90,
- height: 20
- };
- var NAME_PROPERTY = 'name';
- var TEXT_PROPERTY = 'text';
- /**
- * A component that makes sure that external labels are added
- * together with respective elements and properly updated (DI wise)
- * during move.
- *
- * @param {EventBus} eventBus
- * @param {Modeling} modeling
- * @param {BpmnFactory} bpmnFactory
- * @param {TextRenderer} textRenderer
- */
- export default function LabelBehavior(
- eventBus, modeling, bpmnFactory,
- textRenderer) {
- CommandInterceptor.call(this, eventBus);
- // update label if name property was updated
- this.postExecute('element.updateProperties', function(e) {
- var context = e.context,
- element = context.element,
- properties = context.properties;
- if (NAME_PROPERTY in properties) {
- modeling.updateLabel(element, properties[NAME_PROPERTY]);
- }
- if (TEXT_PROPERTY in properties
- && is(element, 'bpmn:TextAnnotation')) {
- var newBounds = textRenderer.getTextAnnotationBounds(
- {
- x: element.x,
- y: element.y,
- width: element.width,
- height: element.height
- },
- properties[TEXT_PROPERTY] || ''
- );
- modeling.updateLabel(element, properties.text, newBounds);
- }
- });
- // create label shape after shape/connection was created
- this.postExecute([ 'shape.create', 'connection.create' ], function(e) {
- var context = e.context,
- hints = context.hints || {};
- if (hints.createElementsBehavior === false) {
- return;
- }
- var element = context.shape || context.connection,
- businessObject = element.businessObject;
- if (isLabel(element) || !isLabelExternal(element)) {
- return;
- }
- // only create label if attribute available
- if (!getLabel(element)) {
- return;
- }
- var labelCenter = getExternalLabelMid(element);
- // we don't care about x and y
- var labelDimensions = textRenderer.getExternalLabelBounds(
- DEFAULT_LABEL_DIMENSIONS,
- getLabel(element)
- );
- modeling.createLabel(element, labelCenter, {
- id: businessObject.id + '_label',
- businessObject: businessObject,
- width: labelDimensions.width,
- height: labelDimensions.height
- });
- });
- // update label after label shape was deleted
- this.postExecute('shape.delete', function(event) {
- var context = event.context,
- labelTarget = context.labelTarget,
- hints = context.hints || {};
- // check if label
- if (labelTarget && hints.unsetLabel !== false) {
- modeling.updateLabel(labelTarget, null, null, { removeShape: false });
- }
- });
- // update di information on label creation
- this.postExecute([ 'label.create' ], function(event) {
- var context = event.context,
- element = context.shape,
- businessObject,
- di;
- // we want to trigger on real labels only
- if (!element.labelTarget) {
- return;
- }
- // we want to trigger on BPMN elements only
- if (!is(element.labelTarget || element, 'bpmn:BaseElement')) {
- return;
- }
- businessObject = element.businessObject,
- di = businessObject.di;
- if (!di.label) {
- di.label = bpmnFactory.create('bpmndi:BPMNLabel', {
- bounds: bpmnFactory.create('dc:Bounds')
- });
- }
- assign(di.label.bounds, {
- x: element.x,
- y: element.y,
- width: element.width,
- height: element.height
- });
- });
- function getVisibleLabelAdjustment(event) {
- var context = event.context,
- connection = context.connection,
- label = connection.label,
- hints = assign({}, context.hints),
- newWaypoints = context.newWaypoints || connection.waypoints,
- oldWaypoints = context.oldWaypoints;
- if (typeof hints.startChanged === 'undefined') {
- hints.startChanged = !!hints.connectionStart;
- }
- if (typeof hints.endChanged === 'undefined') {
- hints.endChanged = !!hints.connectionEnd;
- }
- return getLabelAdjustment(label, newWaypoints, oldWaypoints, hints);
- }
- this.postExecute([
- 'connection.layout',
- 'connection.updateWaypoints'
- ], function(event) {
- var context = event.context,
- hints = context.hints || {};
- if (hints.labelBehavior === false) {
- return;
- }
- var connection = context.connection,
- label = connection.label,
- labelAdjustment;
- // handle missing label as well as the case
- // that the label parent does not exist (yet),
- // because it is being pasted / created via multi element create
- //
- // Cf. https://github.com/bpmn-io/bpmn-js/pull/1227
- if (!label || !label.parent) {
- return;
- }
- labelAdjustment = getVisibleLabelAdjustment(event);
- modeling.moveShape(label, labelAdjustment);
- });
- // keep label position on shape replace
- this.postExecute([ 'shape.replace' ], function(event) {
- var context = event.context,
- newShape = context.newShape,
- oldShape = context.oldShape;
- var businessObject = getBusinessObject(newShape);
- if (businessObject
- && isLabelExternal(businessObject)
- && oldShape.label
- && newShape.label) {
- newShape.label.x = oldShape.label.x;
- newShape.label.y = oldShape.label.y;
- }
- });
- // move external label after resizing
- this.postExecute('shape.resize', function(event) {
- var context = event.context,
- shape = context.shape,
- newBounds = context.newBounds,
- oldBounds = context.oldBounds;
- if (hasExternalLabel(shape)) {
- var label = shape.label,
- labelMid = getMid(label),
- edges = asEdges(oldBounds);
- // get nearest border point to label as reference point
- var referencePoint = getReferencePoint(labelMid, edges);
- var delta = getReferencePointDelta(referencePoint, oldBounds, newBounds);
- modeling.moveShape(label, delta);
- }
- });
- }
- inherits(LabelBehavior, CommandInterceptor);
- LabelBehavior.$inject = [
- 'eventBus',
- 'modeling',
- 'bpmnFactory',
- 'textRenderer'
- ];
- // helpers //////////////////////
- /**
- * Calculates a reference point delta relative to a new position
- * of a certain element's bounds
- *
- * @param {Point} point
- * @param {Bounds} oldBounds
- * @param {Bounds} newBounds
- *
- * @return {Delta} delta
- */
- export function getReferencePointDelta(referencePoint, oldBounds, newBounds) {
- var newReferencePoint = getNewAttachPoint(referencePoint, oldBounds, newBounds);
- return roundPoint(delta(newReferencePoint, referencePoint));
- }
- /**
- * Generates the nearest point (reference point) for a given point
- * onto given set of lines
- *
- * @param {Array<Point, Point>} lines
- * @param {Point} point
- *
- * @param {Point}
- */
- export function getReferencePoint(point, lines) {
- if (!lines.length) {
- return;
- }
- var nearestLine = getNearestLine(point, lines);
- return perpendicularFoot(point, nearestLine);
- }
- /**
- * Convert the given bounds to a lines array containing all edges
- *
- * @param {Bounds|Point} bounds
- *
- * @return Array<Point>
- */
- export function asEdges(bounds) {
- return [
- [ // top
- {
- x: bounds.x,
- y: bounds.y
- },
- {
- x: bounds.x + (bounds.width || 0),
- y: bounds.y
- }
- ],
- [ // right
- {
- x: bounds.x + (bounds.width || 0),
- y: bounds.y
- },
- {
- x: bounds.x + (bounds.width || 0),
- y: bounds.y + (bounds.height || 0)
- }
- ],
- [ // bottom
- {
- x: bounds.x,
- y: bounds.y + (bounds.height || 0)
- },
- {
- x: bounds.x + (bounds.width || 0),
- y: bounds.y + (bounds.height || 0)
- }
- ],
- [ // left
- {
- x: bounds.x,
- y: bounds.y
- },
- {
- x: bounds.x,
- y: bounds.y + (bounds.height || 0)
- }
- ]
- ];
- }
- /**
- * Returns the nearest line for a given point by distance
- * @param {Point} point
- * @param Array<Point> lines
- *
- * @return Array<Point>
- */
- function getNearestLine(point, lines) {
- var distances = lines.map(function(l) {
- return {
- line: l,
- distance: getDistancePointLine(point, l)
- };
- });
- var sorted = sortBy(distances, 'distance');
- return sorted[0].line;
- }
|