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