UNPKG

9.46 kBJavaScriptView Raw
1import {
2 assign
3} from 'min-dash';
4
5import { is } from '../util/ModelUtil';
6
7import {
8 isLabelExternal,
9 getExternalLabelBounds,
10 getLabel
11} from '../util/LabelUtil';
12
13import {
14 getMid
15} from 'diagram-js/lib/layout/LayoutUtil';
16
17import {
18 isExpanded
19} from '../util/DiUtil';
20
21import {
22 elementToString
23} from './Util';
24
25/**
26 * @typedef {import('diagram-js/lib/core/Canvas').default} Canvas
27 * @typedef {import('diagram-js/lib/core/ElementRegistry').default} ElementRegistry
28 * @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
29 * @typedef {import('diagram-js/lib/i18n/translate/translate').default} Translate
30 *
31 * @typedef {import('../features/modeling/ElementFactory').default} ElementFactory
32 * @typedef {import('../draw/TextRenderer').default} TextRenderer
33 *
34 * @typedef {import('../model/Types').Element} Element
35 * @typedef {import('../model/Types').Label} Label
36 * @typedef {import('../model/Types').Shape} Shape
37 * @typedef {import('../model/Types').Connection} Connection
38 * @typedef {import('../model/Types').Root} Root
39 * @typedef {import('../model/Types').ModdleElement} ModdleElement
40 */
41
42/**
43 * @param {ModdleElement} semantic
44 * @param {ModdleElement} di
45 * @param {Object} [attrs=null]
46 *
47 * @return {Object}
48 */
49function elementData(semantic, di, attrs) {
50 return assign({
51 id: semantic.id,
52 type: semantic.$type,
53 businessObject: semantic,
54 di: di
55 }, attrs);
56}
57
58function getWaypoints(di, source, target) {
59
60 var waypoints = di.waypoint;
61
62 if (!waypoints || waypoints.length < 2) {
63 return [ getMid(source), getMid(target) ];
64 }
65
66 return waypoints.map(function(p) {
67 return { x: p.x, y: p.y };
68 });
69}
70
71function notYetDrawn(translate, semantic, refSemantic, property) {
72 return new Error(translate('element {element} referenced by {referenced}#{property} not yet drawn', {
73 element: elementToString(refSemantic),
74 referenced: elementToString(semantic),
75 property: property
76 }));
77}
78
79
80/**
81 * An importer that adds bpmn elements to the canvas
82 *
83 * @param {EventBus} eventBus
84 * @param {Canvas} canvas
85 * @param {ElementFactory} elementFactory
86 * @param {ElementRegistry} elementRegistry
87 * @param {Function} translate
88 * @param {TextRenderer} textRenderer
89 */
90export default function BpmnImporter(
91 eventBus, canvas, elementFactory,
92 elementRegistry, translate, textRenderer) {
93
94 this._eventBus = eventBus;
95 this._canvas = canvas;
96 this._elementFactory = elementFactory;
97 this._elementRegistry = elementRegistry;
98 this._translate = translate;
99 this._textRenderer = textRenderer;
100}
101
102BpmnImporter.$inject = [
103 'eventBus',
104 'canvas',
105 'elementFactory',
106 'elementRegistry',
107 'translate',
108 'textRenderer'
109];
110
111
112/**
113 * Add a BPMN element (semantic) to the canvas making it a child of the
114 * given parent.
115 *
116 * @param {ModdleElement} semantic
117 * @param {ModdleElement} di
118 * @param {Shape} parentElement
119 *
120 * @return {Shape | Root | Connection}
121 */
122BpmnImporter.prototype.add = function(semantic, di, parentElement) {
123 var element,
124 translate = this._translate,
125 hidden;
126
127 var parentIndex;
128
129 // ROOT ELEMENT
130 // handle the special case that we deal with a
131 // invisible root element (process, subprocess or collaboration)
132 if (is(di, 'bpmndi:BPMNPlane')) {
133
134 var attrs = is(semantic, 'bpmn:SubProcess')
135 ? { id: semantic.id + '_plane' }
136 : {};
137
138 // add a virtual element (not being drawn)
139 element = this._elementFactory.createRoot(elementData(semantic, di, attrs));
140
141 this._canvas.addRootElement(element);
142 }
143
144 // SHAPE
145 else if (is(di, 'bpmndi:BPMNShape')) {
146
147 var collapsed = !isExpanded(semantic, di),
148 isFrame = isFrameElement(semantic);
149
150 hidden = parentElement && (parentElement.hidden || parentElement.collapsed);
151
152 var bounds = di.bounds;
153
154 element = this._elementFactory.createShape(elementData(semantic, di, {
155 collapsed: collapsed,
156 hidden: hidden,
157 x: Math.round(bounds.x),
158 y: Math.round(bounds.y),
159 width: Math.round(bounds.width),
160 height: Math.round(bounds.height),
161 isFrame: isFrame
162 }));
163
164 if (is(semantic, 'bpmn:BoundaryEvent')) {
165 this._attachBoundary(semantic, element);
166 }
167
168 // insert lanes behind other flow nodes (cf. #727)
169 if (is(semantic, 'bpmn:Lane')) {
170 parentIndex = 0;
171 }
172
173 if (is(semantic, 'bpmn:DataStoreReference')) {
174
175 // check whether data store is inside our outside of its semantic parent
176 if (!isPointInsideBBox(parentElement, getMid(bounds))) {
177 parentElement = this._canvas.findRoot(parentElement);
178 }
179 }
180
181 this._canvas.addShape(element, parentElement, parentIndex);
182 }
183
184 // CONNECTION
185 else if (is(di, 'bpmndi:BPMNEdge')) {
186
187 var source = this._getSource(semantic),
188 target = this._getTarget(semantic);
189
190 hidden = parentElement && (parentElement.hidden || parentElement.collapsed);
191
192 element = this._elementFactory.createConnection(elementData(semantic, di, {
193 hidden: hidden,
194 source: source,
195 target: target,
196 waypoints: getWaypoints(di, source, target)
197 }));
198
199 if (is(semantic, 'bpmn:DataAssociation')) {
200
201 // render always on top; this ensures DataAssociations
202 // are rendered correctly across different "hacks" people
203 // love to model such as cross participant / sub process
204 // associations
205 parentElement = this._canvas.findRoot(parentElement);
206 }
207
208 this._canvas.addConnection(element, parentElement, parentIndex);
209 } else {
210 throw new Error(translate('unknown di {di} for element {semantic}', {
211 di: elementToString(di),
212 semantic: elementToString(semantic)
213 }));
214 }
215
216 // (optional) LABEL
217 if (isLabelExternal(semantic) && getLabel(element)) {
218 this.addLabel(semantic, di, element);
219 }
220
221
222 this._eventBus.fire('bpmnElement.added', { element: element });
223
224 return element;
225};
226
227
228/**
229 * Attach a boundary element to the given host.
230 *
231 * @param {ModdleElement} boundarySemantic
232 * @param {Shape} boundaryElement
233 */
234BpmnImporter.prototype._attachBoundary = function(boundarySemantic, boundaryElement) {
235 var translate = this._translate;
236 var hostSemantic = boundarySemantic.attachedToRef;
237
238 if (!hostSemantic) {
239 throw new Error(translate('missing {semantic}#attachedToRef', {
240 semantic: elementToString(boundarySemantic)
241 }));
242 }
243
244 var host = this._elementRegistry.get(hostSemantic.id),
245 attachers = host && host.attachers;
246
247 if (!host) {
248 throw notYetDrawn(translate, boundarySemantic, hostSemantic, 'attachedToRef');
249 }
250
251 // wire element.host <> host.attachers
252 boundaryElement.host = host;
253
254 if (!attachers) {
255 host.attachers = attachers = [];
256 }
257
258 if (attachers.indexOf(boundaryElement) === -1) {
259 attachers.push(boundaryElement);
260 }
261};
262
263
264/**
265 * Add a label to a given element.
266 *
267 * @param {ModdleElement} semantic
268 * @param {ModdleElement} di
269 * @param {Element} element
270 *
271 * @return {Label}
272 */
273BpmnImporter.prototype.addLabel = function(semantic, di, element) {
274 var bounds,
275 text,
276 label;
277
278 bounds = getExternalLabelBounds(di, element);
279
280 text = getLabel(element);
281
282 if (text) {
283
284 // get corrected bounds from actual layouted text
285 bounds = this._textRenderer.getExternalLabelBounds(bounds, text);
286 }
287
288 label = this._elementFactory.createLabel(elementData(semantic, di, {
289 id: semantic.id + '_label',
290 labelTarget: element,
291 type: 'label',
292 hidden: element.hidden || !getLabel(element),
293 x: Math.round(bounds.x),
294 y: Math.round(bounds.y),
295 width: Math.round(bounds.width),
296 height: Math.round(bounds.height)
297 }));
298
299 return this._canvas.addShape(label, element.parent);
300};
301
302/**
303 * Get the source or target of the given connection.
304 *
305 * @param {ModdleElement} semantic
306 * @param {'source' | 'target'} side
307 *
308 * @return {Element}
309 */
310BpmnImporter.prototype._getConnectedElement = function(semantic, side) {
311
312 var element,
313 refSemantic,
314 type = semantic.$type,
315 translate = this._translate;
316
317 refSemantic = semantic[side + 'Ref'];
318
319 // handle mysterious isMany DataAssociation#sourceRef
320 if (side === 'source' && type === 'bpmn:DataInputAssociation') {
321 refSemantic = refSemantic && refSemantic[0];
322 }
323
324 // fix source / target for DataInputAssociation / DataOutputAssociation
325 if (side === 'source' && type === 'bpmn:DataOutputAssociation' ||
326 side === 'target' && type === 'bpmn:DataInputAssociation') {
327
328 refSemantic = semantic.$parent;
329 }
330
331 element = refSemantic && this._getElement(refSemantic);
332
333 if (element) {
334 return element;
335 }
336
337 if (refSemantic) {
338 throw notYetDrawn(translate, semantic, refSemantic, side + 'Ref');
339 } else {
340 throw new Error(translate('{semantic}#{side} Ref not specified', {
341 semantic: elementToString(semantic),
342 side: side
343 }));
344 }
345};
346
347BpmnImporter.prototype._getSource = function(semantic) {
348 return this._getConnectedElement(semantic, 'source');
349};
350
351BpmnImporter.prototype._getTarget = function(semantic) {
352 return this._getConnectedElement(semantic, 'target');
353};
354
355
356BpmnImporter.prototype._getElement = function(semantic) {
357 return this._elementRegistry.get(semantic.id);
358};
359
360
361// helpers ////////////////////
362
363function isPointInsideBBox(bbox, point) {
364 var x = point.x,
365 y = point.y;
366
367 return x >= bbox.x &&
368 x <= bbox.x + bbox.width &&
369 y >= bbox.y &&
370 y <= bbox.y + bbox.height;
371}
372
373function isFrameElement(semantic) {
374 return is(semantic, 'bpmn:Group');
375}