BpmnImporter.js 9.2 KB

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