UNPKG

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