contentPadProvider.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390
  1. import { assign, forEach, isArray } from 'min-dash';
  2. import { is } from 'bpmn-js/lib/util/ModelUtil';
  3. import { isExpanded, isEventSubProcess } from 'bpmn-js/lib/util/DiUtil';
  4. import { isAny } from 'bpmn-js/lib/features/modeling/util/ModelingUtil';
  5. import { getChildLanes } from 'bpmn-js/lib/features/modeling/util/LaneUtil';
  6. import { hasPrimaryModifier } from 'diagram-js/lib/util/Mouse';
  7. /**
  8. * A provider for BPMN 2.0 elements context pad
  9. */
  10. export default function ContextPadProvider(
  11. config,
  12. injector,
  13. eventBus,
  14. contextPad,
  15. modeling,
  16. elementFactory,
  17. connect,
  18. create,
  19. popupMenu,
  20. canvas,
  21. rules,
  22. translate,
  23. elementRegistry
  24. ) {
  25. config = config || {};
  26. contextPad.registerProvider(this);
  27. this._contextPad = contextPad;
  28. this._modeling = modeling;
  29. this._elementFactory = elementFactory;
  30. this._connect = connect;
  31. this._create = create;
  32. this._popupMenu = popupMenu;
  33. this._canvas = canvas;
  34. this._rules = rules;
  35. this._translate = translate;
  36. if (config.autoPlace !== false) {
  37. this._autoPlace = injector.get('autoPlace', false);
  38. }
  39. eventBus.on('create.end', 250, function(event) {
  40. var context = event.context;
  41. var shape = context.shape;
  42. if (!hasPrimaryModifier(event) || !contextPad.isOpen(shape)) {
  43. return;
  44. }
  45. var entries = contextPad.getEntries(shape);
  46. if (entries.replace) {
  47. entries.replace.action.click(event, shape);
  48. }
  49. });
  50. }
  51. ContextPadProvider.$inject = [
  52. 'config.contextPad',
  53. 'injector',
  54. 'eventBus',
  55. 'contextPad',
  56. 'modeling',
  57. 'elementFactory',
  58. 'connect',
  59. 'create',
  60. 'popupMenu',
  61. 'canvas',
  62. 'rules',
  63. 'translate',
  64. 'elementRegistry'
  65. ];
  66. ContextPadProvider.prototype.getContextPadEntries = function(element) {
  67. var contextPad = this._contextPad;
  68. var modeling = this._modeling;
  69. var elementFactory = this._elementFactory;
  70. var connect = this._connect;
  71. var create = this._create;
  72. var popupMenu = this._popupMenu;
  73. var canvas = this._canvas;
  74. var rules = this._rules;
  75. var autoPlace = this._autoPlace;
  76. var translate = this._translate;
  77. var actions = {};
  78. if (element.type === 'label') {
  79. return actions;
  80. }
  81. var businessObject = element.businessObject;
  82. function startConnect(event, element) {
  83. connect.start(event, element);
  84. }
  85. function removeElement() {
  86. modeling.removeElements([element]);
  87. }
  88. function getReplaceMenuPosition(element) {
  89. var Y_OFFSET = 5;
  90. var diagramContainer = canvas.getContainer();
  91. var pad = contextPad.getPad(element).html;
  92. var diagramRect = diagramContainer.getBoundingClientRect();
  93. var padRect = pad.getBoundingClientRect();
  94. var top = padRect.top - diagramRect.top;
  95. var left = padRect.left - diagramRect.left;
  96. var pos = {
  97. x: left,
  98. y: top + padRect.height + Y_OFFSET
  99. };
  100. return pos;
  101. }
  102. /**
  103. * Create an append action
  104. *
  105. * @param {string} type
  106. * @param {string} className
  107. * @param {string} [title]
  108. * @param {Object} [options]
  109. *
  110. * @return {Object} descriptor
  111. */
  112. function appendAction(type, className, title, options) {
  113. if (typeof title !== 'string') {
  114. options = title;
  115. title = translate('Append {type}', { type: type.replace(/^bpmn:/, '') });
  116. }
  117. function appendStart(event, element) {
  118. var shape = elementFactory.createShape(assign({ type: type }, options));
  119. create.start(event, shape, {
  120. source: element
  121. });
  122. }
  123. var append = autoPlace
  124. ? function(event, element) {
  125. var shape = elementFactory.createShape(assign({ type: type }, options));
  126. autoPlace.append(element, shape);
  127. }
  128. : appendStart;
  129. return {
  130. group: 'model',
  131. className: className,
  132. title: title,
  133. action: {
  134. dragstart: appendStart,
  135. click: append
  136. }
  137. };
  138. }
  139. function splitLaneHandler(count) {
  140. return function(event, element) {
  141. // actual split
  142. modeling.splitLane(element, count);
  143. // refresh context pad after split to
  144. // get rid of split icons
  145. contextPad.open(element, true);
  146. };
  147. }
  148. if (isAny(businessObject, ['bpmn:Lane', 'bpmn:Participant']) && isExpanded(businessObject)) {
  149. var childLanes = getChildLanes(element);
  150. assign(actions, {
  151. 'lane-insert-above': {
  152. group: 'lane-insert-above',
  153. className: 'bpmn-icon-lane-insert-above',
  154. title: translate('Add Lane above'),
  155. action: {
  156. click: function(event, element) {
  157. modeling.addLane(element, 'top');
  158. }
  159. }
  160. }
  161. });
  162. if (childLanes.length < 2) {
  163. if (element.height >= 120) {
  164. assign(actions, {
  165. 'lane-divide-two': {
  166. group: 'lane-divide',
  167. className: 'bpmn-icon-lane-divide-two',
  168. title: translate('Divide into two Lanes'),
  169. action: {
  170. click: splitLaneHandler(2)
  171. }
  172. }
  173. });
  174. }
  175. if (element.height >= 180) {
  176. assign(actions, {
  177. 'lane-divide-three': {
  178. group: 'lane-divide',
  179. className: 'bpmn-icon-lane-divide-three',
  180. title: translate('Divide into three Lanes'),
  181. action: {
  182. click: splitLaneHandler(3)
  183. }
  184. }
  185. });
  186. }
  187. }
  188. assign(actions, {
  189. 'lane-insert-below': {
  190. group: 'lane-insert-below',
  191. className: 'bpmn-icon-lane-insert-below',
  192. title: translate('Add Lane below'),
  193. action: {
  194. click: function(event, element) {
  195. modeling.addLane(element, 'bottom');
  196. }
  197. }
  198. }
  199. });
  200. }
  201. if (is(businessObject, 'bpmn:FlowNode')) {
  202. if (is(businessObject, 'bpmn:EventBasedGateway')) {
  203. assign(actions, {
  204. 'append.receive-task': appendAction('bpmn:ReceiveTask', 'bpmn-icon-receive-task', translate('Append ReceiveTask')),
  205. 'append.message-intermediate-event': appendAction(
  206. 'bpmn:IntermediateCatchEvent',
  207. 'bpmn-icon-intermediate-event-catch-message',
  208. translate('Append MessageIntermediateCatchEvent'),
  209. { eventDefinitionType: 'bpmn:MessageEventDefinition' }
  210. ),
  211. 'append.timer-intermediate-event': appendAction(
  212. 'bpmn:IntermediateCatchEvent',
  213. 'bpmn-icon-intermediate-event-catch-timer',
  214. translate('Append TimerIntermediateCatchEvent'),
  215. { eventDefinitionType: 'bpmn:TimerEventDefinition' }
  216. ),
  217. 'append.condition-intermediate-event': appendAction(
  218. 'bpmn:IntermediateCatchEvent',
  219. 'bpmn-icon-intermediate-event-catch-condition',
  220. translate('Append ConditionIntermediateCatchEvent'),
  221. { eventDefinitionType: 'bpmn:ConditionalEventDefinition' }
  222. ),
  223. 'append.signal-intermediate-event': appendAction(
  224. 'bpmn:IntermediateCatchEvent',
  225. 'bpmn-icon-intermediate-event-catch-signal',
  226. translate('Append SignalIntermediateCatchEvent'),
  227. { eventDefinitionType: 'bpmn:SignalEventDefinition' }
  228. )
  229. });
  230. } else if (isEventType(businessObject, 'bpmn:BoundaryEvent', 'bpmn:CompensateEventDefinition')) {
  231. assign(actions, {
  232. 'append.compensation-activity': appendAction('bpmn:Task', 'bpmn-icon-task', translate('Append compensation activity'), {
  233. isForCompensation: true
  234. })
  235. });
  236. } else if (
  237. !is(businessObject, 'bpmn:EndEvent') &&
  238. !businessObject.isForCompensation &&
  239. !isEventType(businessObject, 'bpmn:IntermediateThrowEvent', 'bpmn:LinkEventDefinition') &&
  240. !isEventSubProcess(businessObject)
  241. ) {
  242. assign(actions, {
  243. 'append.end-event': appendAction('bpmn:EndEvent', 'bpmn-icon-end-event-none', translate('Append EndEvent')),
  244. 'append.gateway': appendAction('bpmn:ExclusiveGateway', 'bpmn-icon-gateway-none', translate('Append Gateway')),
  245. 'append.append-task': appendAction('bpmn:UserTask', 'bpmn-icon-user-task', translate('Append Task')),
  246. 'append.intermediate-event': appendAction(
  247. 'bpmn:IntermediateThrowEvent',
  248. 'bpmn-icon-intermediate-event-none',
  249. translate('Append Intermediate/Boundary Event')
  250. )
  251. });
  252. }
  253. }
  254. if (!popupMenu.isEmpty(element, 'bpmn-replace')) {
  255. // Replace menu entry
  256. assign(actions, {
  257. replace: {
  258. group: 'edit',
  259. className: 'bpmn-icon-screw-wrench',
  260. title: translate('Change type'),
  261. action: {
  262. click: function(event, element) {
  263. var position = assign(getReplaceMenuPosition(element), {
  264. cursor: { x: event.x, y: event.y }
  265. });
  266. popupMenu.open(element, 'bpmn-replace', position);
  267. }
  268. }
  269. }
  270. });
  271. }
  272. if (isAny(businessObject, ['bpmn:FlowNode', 'bpmn:InteractionNode', 'bpmn:DataObjectReference', 'bpmn:DataStoreReference'])) {
  273. assign(actions, {
  274. 'append.text-annotation': appendAction('bpmn:TextAnnotation', 'bpmn-icon-text-annotation'),
  275. connect: {
  276. group: 'connect',
  277. className: 'bpmn-icon-connection-multi',
  278. title: translate('Connect using ' + (businessObject.isForCompensation ? '' : 'Sequence/MessageFlow or ') + 'Association'),
  279. action: {
  280. click: startConnect,
  281. dragstart: startConnect
  282. }
  283. }
  284. });
  285. }
  286. if (isAny(businessObject, ['bpmn:DataObjectReference', 'bpmn:DataStoreReference'])) {
  287. assign(actions, {
  288. connect: {
  289. group: 'connect',
  290. className: 'bpmn-icon-connection-multi',
  291. title: translate('Connect using DataInputAssociation'),
  292. action: {
  293. click: startConnect,
  294. dragstart: startConnect
  295. }
  296. }
  297. });
  298. }
  299. if (is(businessObject, 'bpmn:Group')) {
  300. assign(actions, {
  301. 'append.text-annotation': appendAction('bpmn:TextAnnotation', 'bpmn-icon-text-annotation')
  302. });
  303. }
  304. // delete element entry, only show if allowed by rules
  305. var deleteAllowed = rules.allowed('elements.delete', { elements: [element] });
  306. if (isArray(deleteAllowed)) {
  307. // was the element returned as a deletion candidate?
  308. deleteAllowed = deleteAllowed[0] === element;
  309. }
  310. if (deleteAllowed) {
  311. assign(actions, {
  312. delete: {
  313. group: 'edit',
  314. className: 'bpmn-icon-trash',
  315. title: translate('Remove'),
  316. action: {
  317. click: removeElement
  318. }
  319. }
  320. });
  321. }
  322. return actions;
  323. };
  324. // helpers /////////
  325. function isEventType(eventBo, type, definition) {
  326. var isType = eventBo.$instanceOf(type);
  327. var isDefinition = false;
  328. var definitions = eventBo.eventDefinitions || [];
  329. forEach(definitions, function(def) {
  330. if (def.$type === definition) {
  331. isDefinition = true;
  332. }
  333. });
  334. return isType && isDefinition;
  335. }