contentPadProvider.js 10 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: '修改类型',
  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. }