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. const context = event.context,
  41. shape = context.shape
  42. if (!hasPrimaryModifier(event) || !contextPad.isOpen(shape)) {
  43. return;
  44. }
  45. const 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. const contextPad = this._contextPad,
  68. modeling = this._modeling,
  69. elementFactory = this._elementFactory,
  70. connect = this._connect,
  71. create = this._create,
  72. popupMenu = this._popupMenu,
  73. canvas = this._canvas,
  74. rules = this._rules,
  75. autoPlace = this._autoPlace,
  76. translate = this._translate
  77. const actions = {}
  78. if (element.type === "label") {
  79. return actions;
  80. }
  81. const 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. const Y_OFFSET = 5
  90. const diagramContainer = canvas.getContainer(),
  91. pad = contextPad.getPad(element).html
  92. const diagramRect = diagramContainer.getBoundingClientRect(),
  93. padRect = pad.getBoundingClientRect()
  94. const top = padRect.top - diagramRect.top
  95. const left = padRect.left - diagramRect.left
  96. const 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. const shape = elementFactory.createShape(assign({ type: type }, options))
  119. create.start(event, shape, {
  120. source: element
  121. });
  122. }
  123. const append = autoPlace
  124. ? function(event, element) {
  125. const 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. const 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. const 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. let 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. const isType = eventBo.$instanceOf(type)
  327. let isDefinition = false
  328. const definitions = eventBo.eventDefinitions || []
  329. forEach(definitions, function(def) {
  330. if (def.$type === definition) {
  331. isDefinition = true;
  332. }
  333. });
  334. return isType && isDefinition;
  335. }