LabelEditingProvider.js 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402
  1. import {
  2. assign
  3. } from 'min-dash';
  4. import {
  5. getLabel
  6. } from './LabelUtil';
  7. import { is } from '../../util/ModelUtil';
  8. import { isAny } from '../modeling/util/ModelingUtil';
  9. import { isExpanded } from '../../util/DiUtil';
  10. import {
  11. getExternalLabelMid,
  12. isLabelExternal,
  13. hasExternalLabel,
  14. isLabel
  15. } from '../../util/LabelUtil';
  16. export default function LabelEditingProvider(
  17. eventBus, canvas, directEditing,
  18. modeling, resizeHandles, textRenderer) {
  19. this._canvas = canvas;
  20. this._modeling = modeling;
  21. this._textRenderer = textRenderer;
  22. directEditing.registerProvider(this);
  23. // listen to dblclick on non-root elements
  24. eventBus.on('element.dblclick', function(event) {
  25. activateDirectEdit(event.element, true);
  26. });
  27. // complete on followup canvas operation
  28. eventBus.on([
  29. 'element.mousedown',
  30. 'drag.init',
  31. 'canvas.viewbox.changing',
  32. 'autoPlace',
  33. 'popupMenu.open'
  34. ], function(event) {
  35. if (directEditing.isActive()) {
  36. directEditing.complete();
  37. }
  38. });
  39. // cancel on command stack changes
  40. eventBus.on([ 'commandStack.changed' ], function(e) {
  41. if (directEditing.isActive()) {
  42. directEditing.cancel();
  43. }
  44. });
  45. eventBus.on('directEditing.activate', function(event) {
  46. resizeHandles.removeResizers();
  47. });
  48. eventBus.on('create.end', 500, function(event) {
  49. var element = event.shape,
  50. canExecute = event.context.canExecute,
  51. isTouch = event.isTouch;
  52. // TODO(nikku): we need to find a way to support the
  53. // direct editing on mobile devices; right now this will
  54. // break for desworkflowediting on mobile devices
  55. // as it breaks the user interaction workflow
  56. // TODO(nre): we should temporarily focus the edited element
  57. // here and release the focused viewport after the direct edit
  58. // operation is finished
  59. if (isTouch) {
  60. return;
  61. }
  62. if (!canExecute) {
  63. return;
  64. }
  65. activateDirectEdit(element);
  66. });
  67. eventBus.on('autoPlace.end', 500, function(event) {
  68. activateDirectEdit(event.shape);
  69. });
  70. function activateDirectEdit(element, force) {
  71. if (force ||
  72. isAny(element, [ 'bpmn:Task', 'bpmn:TextAnnotation' ]) ||
  73. isCollapsedSubProcess(element)) {
  74. directEditing.activate(element);
  75. }
  76. }
  77. }
  78. LabelEditingProvider.$inject = [
  79. 'eventBus',
  80. 'canvas',
  81. 'directEditing',
  82. 'modeling',
  83. 'resizeHandles',
  84. 'textRenderer'
  85. ];
  86. /**
  87. * Activate direct editing for activities and text annotations.
  88. *
  89. * @param {djs.model.Base} element
  90. *
  91. * @return {Object} an object with properties bounds (position and size), text and options
  92. */
  93. LabelEditingProvider.prototype.activate = function(element) {
  94. // text
  95. var text = getLabel(element);
  96. if (text === undefined) {
  97. return;
  98. }
  99. var context = {
  100. text: text
  101. };
  102. // bounds
  103. var bounds = this.getEditingBBox(element);
  104. assign(context, bounds);
  105. var options = {};
  106. // tasks
  107. if (
  108. isAny(element, [
  109. 'bpmn:Task',
  110. 'bpmn:Participant',
  111. 'bpmn:Lane',
  112. 'bpmn:CallActivity'
  113. ]) ||
  114. isCollapsedSubProcess(element)
  115. ) {
  116. assign(options, {
  117. centerVertically: true
  118. });
  119. }
  120. // external labels
  121. if (isLabelExternal(element)) {
  122. assign(options, {
  123. autoResize: true
  124. });
  125. }
  126. // text annotations
  127. if (is(element, 'bpmn:TextAnnotation')) {
  128. assign(options, {
  129. resizable: true,
  130. autoResize: true
  131. });
  132. }
  133. assign(context, {
  134. options: options
  135. });
  136. return context;
  137. };
  138. /**
  139. * Get the editing bounding box based on the element's size and position
  140. *
  141. * @param {djs.model.Base} element
  142. *
  143. * @return {Object} an object containing information about position
  144. * and size (fixed or minimum and/or maximum)
  145. */
  146. LabelEditingProvider.prototype.getEditingBBox = function(element) {
  147. var canvas = this._canvas;
  148. var target = element.label || element;
  149. var bbox = canvas.getAbsoluteBBox(target);
  150. var mid = {
  151. x: bbox.x + bbox.width / 2,
  152. y: bbox.y + bbox.height / 2
  153. };
  154. // default position
  155. var bounds = { x: bbox.x, y: bbox.y };
  156. var zoom = canvas.zoom();
  157. var defaultStyle = this._textRenderer.getDefaultStyle(),
  158. externalStyle = this._textRenderer.getExternalStyle();
  159. // take zoom into account
  160. var externalFontSize = externalStyle.fontSize * zoom,
  161. externalLineHeight = externalStyle.lineHeight,
  162. defaultFontSize = defaultStyle.fontSize * zoom,
  163. defaultLineHeight = defaultStyle.lineHeight;
  164. var style = {
  165. fontFamily: this._textRenderer.getDefaultStyle().fontFamily,
  166. fontWeight: this._textRenderer.getDefaultStyle().fontWeight
  167. };
  168. // adjust for expanded pools AND lanes
  169. if (is(element, 'bpmn:Lane') || isExpandedPool(element)) {
  170. assign(bounds, {
  171. width: bbox.height,
  172. height: 30 * zoom,
  173. x: bbox.x - bbox.height / 2 + (15 * zoom),
  174. y: mid.y - (30 * zoom) / 2
  175. });
  176. assign(style, {
  177. fontSize: defaultFontSize + 'px',
  178. lineHeight: defaultLineHeight,
  179. paddingTop: (7 * zoom) + 'px',
  180. paddingBottom: (7 * zoom) + 'px',
  181. paddingLeft: (5 * zoom) + 'px',
  182. paddingRight: (5 * zoom) + 'px',
  183. transform: 'rotate(-90deg)'
  184. });
  185. }
  186. // internal labels for tasks and collapsed call activities,
  187. // sub processes and participants
  188. if (isAny(element, [ 'bpmn:Task', 'bpmn:CallActivity']) ||
  189. isCollapsedPool(element) ||
  190. isCollapsedSubProcess(element)) {
  191. assign(bounds, {
  192. width: bbox.width,
  193. height: bbox.height
  194. });
  195. assign(style, {
  196. fontSize: defaultFontSize + 'px',
  197. lineHeight: defaultLineHeight,
  198. paddingTop: (7 * zoom) + 'px',
  199. paddingBottom: (7 * zoom) + 'px',
  200. paddingLeft: (5 * zoom) + 'px',
  201. paddingRight: (5 * zoom) + 'px'
  202. });
  203. }
  204. // internal labels for expanded sub processes
  205. if (isExpandedSubProcess(element)) {
  206. assign(bounds, {
  207. width: bbox.width,
  208. x: bbox.x
  209. });
  210. assign(style, {
  211. fontSize: defaultFontSize + 'px',
  212. lineHeight: defaultLineHeight,
  213. paddingTop: (7 * zoom) + 'px',
  214. paddingBottom: (7 * zoom) + 'px',
  215. paddingLeft: (5 * zoom) + 'px',
  216. paddingRight: (5 * zoom) + 'px'
  217. });
  218. }
  219. var width = 90 * zoom,
  220. paddingTop = 7 * zoom,
  221. paddingBottom = 4 * zoom;
  222. // external labels for events, data elements, gateways and connections
  223. if (target.labelTarget) {
  224. assign(bounds, {
  225. width: width,
  226. height: bbox.height + paddingTop + paddingBottom,
  227. x: mid.x - width / 2,
  228. y: bbox.y - paddingTop
  229. });
  230. assign(style, {
  231. fontSize: externalFontSize + 'px',
  232. lineHeight: externalLineHeight,
  233. paddingTop: paddingTop + 'px',
  234. paddingBottom: paddingBottom + 'px'
  235. });
  236. }
  237. // external label not yet created
  238. if (isLabelExternal(target)
  239. && !hasExternalLabel(target)
  240. && !isLabel(target)) {
  241. var externalLabelMid = getExternalLabelMid(element);
  242. var absoluteBBox = canvas.getAbsoluteBBox({
  243. x: externalLabelMid.x,
  244. y: externalLabelMid.y,
  245. width: 0,
  246. height: 0
  247. });
  248. var height = externalFontSize + paddingTop + paddingBottom;
  249. assign(bounds, {
  250. width: width,
  251. height: height,
  252. x: absoluteBBox.x - width / 2,
  253. y: absoluteBBox.y - height / 2
  254. });
  255. assign(style, {
  256. fontSize: externalFontSize + 'px',
  257. lineHeight: externalLineHeight,
  258. paddingTop: paddingTop + 'px',
  259. paddingBottom: paddingBottom + 'px'
  260. });
  261. }
  262. // text annotations
  263. if (is(element, 'bpmn:TextAnnotation')) {
  264. assign(bounds, {
  265. width: bbox.width,
  266. height: bbox.height,
  267. minWidth: 30 * zoom,
  268. minHeight: 10 * zoom
  269. });
  270. assign(style, {
  271. textAlign: 'left',
  272. paddingTop: (5 * zoom) + 'px',
  273. paddingBottom: (7 * zoom) + 'px',
  274. paddingLeft: (7 * zoom) + 'px',
  275. paddingRight: (5 * zoom) + 'px',
  276. fontSize: defaultFontSize + 'px',
  277. lineHeight: defaultLineHeight
  278. });
  279. }
  280. return { bounds: bounds, style: style };
  281. };
  282. LabelEditingProvider.prototype.update = function(
  283. element, newLabel,
  284. activeContextText, bounds) {
  285. var newBounds,
  286. bbox;
  287. if (is(element, 'bpmn:TextAnnotation')) {
  288. bbox = this._canvas.getAbsoluteBBox(element);
  289. newBounds = {
  290. x: element.x,
  291. y: element.y,
  292. width: element.width / bbox.width * bounds.width,
  293. height: element.height / bbox.height * bounds.height
  294. };
  295. }
  296. if (isEmptyText(newLabel)) {
  297. newLabel = null;
  298. }
  299. this._modeling.updateLabel(element, newLabel, newBounds);
  300. };
  301. // helpers //////////////////////
  302. function isCollapsedSubProcess(element) {
  303. return is(element, 'bpmn:SubProcess') && !isExpanded(element);
  304. }
  305. function isExpandedSubProcess(element) {
  306. return is(element, 'bpmn:SubProcess') && isExpanded(element);
  307. }
  308. function isCollapsedPool(element) {
  309. return is(element, 'bpmn:Participant') && !isExpanded(element);
  310. }
  311. function isExpandedPool(element) {
  312. return is(element, 'bpmn:Participant') && isExpanded(element);
  313. }
  314. function isEmptyText(label) {
  315. return !label || !label.trim();
  316. }