This commit is contained in:
hanjian
2024-08-14 15:17:51 +08:00
parent 20a221c1a2
commit b610f94b2e
3483 changed files with 650965 additions and 0 deletions

View File

@@ -0,0 +1,138 @@
import {
append as svgAppend,
attr as svgAttr,
create as svgCreate,
remove as svgRemove
} from 'tiny-svg';
import {
getBusinessObject,
is
} from '../../util/ModelUtil';
import {
translate
} from 'diagram-js/lib/util/SvgTransformUtil';
var MARKER_HIDDEN = 'djs-element-hidden',
MARKER_LABEL_HIDDEN = 'djs-label-hidden';
export default 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'
];
// helpers ///////////////////
function getStrokeColor(element, defaultColor) {
var bo = getBusinessObject(element);
return bo.di.get('stroke') || defaultColor || 'black';
}

View File

@@ -0,0 +1,429 @@
import {
assign
} from 'min-dash';
import {
getLabel
} from './LabelUtil';
import {
getBusinessObject,
is
} from '../../util/ModelUtil';
import {
createCategoryValue
} from '../modeling/behavior/util/CategoryUtil';
import { isAny } from '../modeling/util/ModelingUtil';
import { isExpanded } from '../../util/DiUtil';
import {
getExternalLabelMid,
isLabelExternal,
hasExternalLabel,
isLabel
} from '../../util/LabelUtil';
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([
'element.mousedown',
'drag.init',
'canvas.viewbox.changing',
'autoPlace',
'popupMenu.open'
], function(event) {
if (directEditing.isActive()) {
directEditing.complete();
}
});
// 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 element = event.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;
}
activateDirectEdit(element);
});
eventBus.on('autoPlace.end', 500, function(event) {
activateDirectEdit(event.shape);
});
function activateDirectEdit(element, force) {
if (force ||
isAny(element, [ 'bpmn:Task', 'bpmn:TextAnnotation', 'bpmn:Group' ]) ||
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 (is(element, 'bpmn:Group')) {
var businessObject = getBusinessObject(element);
// initialize categoryValue if not existing
if (!businessObject.categoryValueRef) {
var rootElement = this._canvas.getRootElement(),
definitions = getBusinessObject(rootElement).$parent;
var categoryValue = createCategoryValue(definitions, this._bpmnFactory);
getBusinessObject(element).categoryValueRef = categoryValue;
}
}
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();
}

View File

@@ -0,0 +1,67 @@
import { is } from '../../util/ModelUtil';
function getLabelAttr(semantic) {
if (
is(semantic, 'bpmn:FlowElement') ||
is(semantic, 'bpmn:Participant') ||
is(semantic, 'bpmn:Lane') ||
is(semantic, 'bpmn:SequenceFlow') ||
is(semantic, 'bpmn:MessageFlow') ||
is(semantic, 'bpmn:DataInput') ||
is(semantic, 'bpmn:DataOutput')
) {
return 'name';
}
if (is(semantic, 'bpmn:TextAnnotation')) {
return 'text';
}
if (is(semantic, 'bpmn:Group')) {
return 'categoryValueRef';
}
}
function getCategoryValue(semantic) {
var categoryValueRef = semantic['categoryValueRef'];
if (!categoryValueRef) {
return '';
}
return categoryValueRef.value || '';
}
export function getLabel(element) {
var semantic = element.businessObject,
attr = getLabelAttr(semantic);
if (attr) {
if (attr === 'categoryValueRef') {
return getCategoryValue(semantic);
}
return semantic[attr] || '';
}
}
export function setLabel(element, text, isExternal) {
var semantic = element.businessObject,
attr = getLabelAttr(semantic);
if (attr) {
if (attr === 'categoryValueRef') {
semantic['categoryValueRef'].value = text;
} else {
semantic[attr] = text;
}
}
return element;
}

View File

@@ -0,0 +1,142 @@
import {
setLabel,
getLabel
} from '../LabelUtil';
import {
getExternalLabelMid,
isLabelExternal,
hasExternalLabel,
isLabel
} from '../../../util/LabelUtil';
import {
is
} from '../../../util/ModelUtil';
var NULL_DIMENSIONS = {
width: 0,
height: 0
};
/**
* A handler that updates the text of a BPMN element.
*/
export default function UpdateLabelHandler(modeling, textRenderer) {
/**
* Set the label and return the changed elements.
*
* Element parameter can be label itself or connection (i.e. sequence flow).
*
* @param {djs.model.Base} element
* @param {String} text
*/
function setText(element, text) {
// external label if present
var label = element.label || element;
var labelTarget = element.labelTarget || element;
setLabel(label, text, labelTarget !== label);
return [ label, labelTarget ];
}
function preExecute(ctx) {
var element = ctx.element,
businessObject = element.businessObject,
newLabel = ctx.newLabel;
if (!isLabel(element)
&& isLabelExternal(element)
&& !hasExternalLabel(element)
&& !isEmptyText(newLabel)) {
// create label
var paddingTop = 7;
var labelCenter = getExternalLabelMid(element);
labelCenter = {
x: labelCenter.x,
y: labelCenter.y + paddingTop
};
modeling.createLabel(element, labelCenter, {
id: businessObject.id + '_label',
businessObject: businessObject
});
}
}
function execute(ctx) {
ctx.oldLabel = getLabel(ctx.element);
return setText(ctx.element, ctx.newLabel);
}
function revert(ctx) {
return setText(ctx.element, ctx.oldLabel);
}
function postExecute(ctx) {
var element = ctx.element,
label = element.label || element,
newLabel = ctx.newLabel,
newBounds = ctx.newBounds,
hints = ctx.hints || {};
if (isLabel(label) && isEmptyText(newLabel)) {
if (hints.removeShape !== false) {
modeling.removeShape(label, { unsetLabel: false });
}
return;
}
// ignore internal labels for elements except text annotations
if (!isLabelExternal(element) && !is(element, 'bpmn:TextAnnotation')) {
return;
}
var text = getLabel(label);
// don't resize without text
if (!text) {
return;
}
// resize element based on label _or_ pre-defined bounds
if (typeof newBounds === 'undefined') {
newBounds = textRenderer.getExternalLabelBounds(label, text);
}
// setting newBounds to false or _null_ will
// disable the postExecute resize operation
if (newBounds) {
modeling.resizeShape(label, newBounds, NULL_DIMENSIONS);
}
}
// API
this.preExecute = preExecute;
this.execute = execute;
this.revert = revert;
this.postExecute = postExecute;
}
UpdateLabelHandler.$inject = [
'modeling',
'textRenderer'
];
// helpers ///////////////////////
function isEmptyText(label) {
return !label || !label.trim();
}

View File

@@ -0,0 +1,21 @@
import ChangeSupportModule from 'diagram-js/lib/features/change-support';
import ResizeModule from 'diagram-js/lib/features/resize';
import DirectEditingModule from 'diagram-js-direct-editing';
import LabelEditingProvider from './LabelEditingProvider';
import LabelEditingPreview from './LabelEditingPreview';
export default {
__depends__: [
ChangeSupportModule,
ResizeModule,
DirectEditingModule
],
__init__: [
'labelEditingProvider',
'labelEditingPreview'
],
labelEditingProvider: [ 'type', LabelEditingProvider ],
labelEditingPreview: [ 'type', LabelEditingPreview ]
};