UNPKG

bpmn-js

Version:

A bpmn 2.0 toolkit and web modeler

637 lines (506 loc) 18.7 kB
import { getBusinessObject, is } from '../../util/ModelUtil'; import { isEventSubProcess, isExpanded } from '../../util/DiUtil'; import { isDifferentType } from './util/TypeUtil'; import { forEach, filter, isArray, isUndefined } from 'min-dash'; import * as replaceOptions from '../replace/ReplaceOptions'; /** * @typedef {import('../features/BpmnFactory').default} BpmnFactory * @typedef {import('diagram-js/lib/features/popup-menu/PopupMenu').default} PopupMenu * @typedef {import('../features/Modeling').default} Modeling * @typedef {import('../features/BpmnReplace').default} BpmnReplace * @typedef {import('diagram-js/lib/features/Rules').default} Rules * @typedef {import('diagram-js/lib/i18n/translate/translate').default} Translate * * @typedef {import('../../model/Types').Element} Element * @typedef {import('../../model/Types').Moddle} Moddle * * @typedef {import('diagram-js/lib/features/popup-menu/PopupMenuProvider').PopupMenuEntries} PopupMenuEntries * @typedef {import('diagram-js/lib/features/popup-menu/PopupMenuProvider').PopupMenuEntry} PopupMenuEntry * @typedef {import('diagram-js/lib/features/popup-menu/PopupMenuProvider').PopupMenuEntryAction} PopupMenuEntryAction * @typedef {import('diagram-js/lib/features/popup-menu/PopupMenuProvider').PopupMenuHeaderEntries} PopupMenuHeaderEntries * @typedef {import('diagram-js/lib/features/popup-menu/PopupMenuProvider').default} PopupMenuProvider * @typedef {import('diagram-js/lib/features/popup-menu/PopupMenu').PopupMenuTarget} PopupMenuTarget * * @typedef {import('./ReplaceOptions').ReplaceOption} ReplaceOption */ /** * A BPMN-specific popup menu provider. * * @implements {PopupMenuProvider} * * @param {BpmnFactory} bpmnFactory * @param {PopupMenu} popupMenu * @param {Modeling} modeling * @param {Moddle} moddle * @param {BpmnReplace} bpmnReplace * @param {Rules} rules * @param {Translate} translate */ export default function ReplaceMenuProvider( bpmnFactory, popupMenu, modeling, moddle, bpmnReplace, rules, translate) { this._bpmnFactory = bpmnFactory; this._popupMenu = popupMenu; this._modeling = modeling; this._moddle = moddle; this._bpmnReplace = bpmnReplace; this._rules = rules; this._translate = translate; this._register(); } ReplaceMenuProvider.$inject = [ 'bpmnFactory', 'popupMenu', 'modeling', 'moddle', 'bpmnReplace', 'rules', 'translate' ]; ReplaceMenuProvider.prototype._register = function() { this._popupMenu.registerProvider('bpmn-replace', this); }; /** * @param {PopupMenuTarget} target * * @return {PopupMenuEntries} */ ReplaceMenuProvider.prototype.getPopupMenuEntries = function(target) { var businessObject = target.businessObject; var rules = this._rules; var filteredReplaceOptions = []; if (isArray(target) || !rules.allowed('shape.replace', { element: target })) { return {}; } var differentType = isDifferentType(target); if (is(businessObject, 'bpmn:DataObjectReference')) { return this._createEntries(target, replaceOptions.DATA_OBJECT_REFERENCE); } if (is(businessObject, 'bpmn:DataStoreReference') && !is(target.parent, 'bpmn:Collaboration')) { return this._createEntries(target, replaceOptions.DATA_STORE_REFERENCE); } // start events outside sub processes if (is(businessObject, 'bpmn:StartEvent') && !is(businessObject.$parent, 'bpmn:SubProcess')) { filteredReplaceOptions = filter(replaceOptions.START_EVENT, differentType); return this._createEntries(target, filteredReplaceOptions); } // expanded/collapsed pools if (is(businessObject, 'bpmn:Participant')) { filteredReplaceOptions = filter(replaceOptions.PARTICIPANT, function(replaceOption) { return isExpanded(target) !== replaceOption.target.isExpanded; }); return this._createEntries(target, filteredReplaceOptions); } // start events inside event sub processes if (is(businessObject, 'bpmn:StartEvent') && isEventSubProcess(businessObject.$parent)) { filteredReplaceOptions = filter(replaceOptions.EVENT_SUB_PROCESS_START_EVENT, function(replaceOption) { var target = replaceOption.target; var isInterrupting = target.isInterrupting !== false; var isInterruptingEqual = getBusinessObject(target).isInterrupting === isInterrupting; // filters elements which types and event definition are equal but have have different interrupting types return differentType(replaceOption) || !differentType(replaceOption) && !isInterruptingEqual; }); return this._createEntries(target, filteredReplaceOptions); } // start events inside sub processes if (is(businessObject, 'bpmn:StartEvent') && !isEventSubProcess(businessObject.$parent) && is(businessObject.$parent, 'bpmn:SubProcess')) { filteredReplaceOptions = filter(replaceOptions.START_EVENT_SUB_PROCESS, differentType); return this._createEntries(target, filteredReplaceOptions); } // end events if (is(businessObject, 'bpmn:EndEvent')) { filteredReplaceOptions = filter(replaceOptions.END_EVENT, function(replaceOption) { var target = replaceOption.target; // hide cancel end events outside transactions if (target.eventDefinitionType == 'bpmn:CancelEventDefinition' && !is(businessObject.$parent, 'bpmn:Transaction')) { return false; } return differentType(replaceOption); }); return this._createEntries(target, filteredReplaceOptions); } // boundary events if (is(businessObject, 'bpmn:BoundaryEvent')) { filteredReplaceOptions = filter(replaceOptions.BOUNDARY_EVENT, function(replaceOption) { var target = replaceOption.target; if (target.eventDefinitionType == 'bpmn:CancelEventDefinition' && !is(businessObject.attachedToRef, 'bpmn:Transaction')) { return false; } var cancelActivity = target.cancelActivity !== false; var isCancelActivityEqual = businessObject.cancelActivity == cancelActivity; return differentType(replaceOption) || !differentType(replaceOption) && !isCancelActivityEqual; }); return this._createEntries(target, filteredReplaceOptions); } // intermediate events if (is(businessObject, 'bpmn:IntermediateCatchEvent') || is(businessObject, 'bpmn:IntermediateThrowEvent')) { filteredReplaceOptions = filter(replaceOptions.INTERMEDIATE_EVENT, differentType); return this._createEntries(target, filteredReplaceOptions); } // gateways if (is(businessObject, 'bpmn:Gateway')) { filteredReplaceOptions = filter(replaceOptions.GATEWAY, differentType); return this._createEntries(target, filteredReplaceOptions); } // transactions if (is(businessObject, 'bpmn:Transaction')) { filteredReplaceOptions = filter(replaceOptions.TRANSACTION, differentType); return this._createEntries(target, filteredReplaceOptions); } // expanded event sub processes if (isEventSubProcess(businessObject) && isExpanded(target)) { filteredReplaceOptions = filter(replaceOptions.EVENT_SUB_PROCESS, differentType); return this._createEntries(target, filteredReplaceOptions); } // expanded sub processes if (is(businessObject, 'bpmn:SubProcess') && isExpanded(target)) { filteredReplaceOptions = filter(replaceOptions.SUBPROCESS_EXPANDED, differentType); return this._createEntries(target, filteredReplaceOptions); } // collapsed ad hoc sub processes if (is(businessObject, 'bpmn:AdHocSubProcess') && !isExpanded(target)) { filteredReplaceOptions = filter(replaceOptions.TASK, function(replaceOption) { var target = replaceOption.target; var isTargetSubProcess = target.type === 'bpmn:SubProcess'; var isTargetExpanded = target.isExpanded === true; return isDifferentType(target, target) && (!isTargetSubProcess || isTargetExpanded); }); return this._createEntries(target, filteredReplaceOptions); } // sequence flows if (is(businessObject, 'bpmn:SequenceFlow')) { return this._createSequenceFlowEntries(target, replaceOptions.SEQUENCE_FLOW); } // flow nodes if (is(businessObject, 'bpmn:FlowNode')) { filteredReplaceOptions = filter(replaceOptions.TASK, differentType); // collapsed sub process cannot be replaced with itself if (is(businessObject, 'bpmn:SubProcess') && !isExpanded(target)) { filteredReplaceOptions = filter(filteredReplaceOptions, function(replaceOption) { return replaceOption.label !== 'Sub Process (collapsed)'; }); } return this._createEntries(target, filteredReplaceOptions); } return {}; }; /** * @param {PopupMenuTarget} target * * @return {PopupMenuHeaderEntries} */ ReplaceMenuProvider.prototype.getPopupMenuHeaderEntries = function(target) { var headerEntries = {}; if (is(target, 'bpmn:Activity') && !isEventSubProcess(target)) { headerEntries = { ...headerEntries, ...this._getLoopCharacteristicsHeaderEntries(target) }; } if (is(target, 'bpmn:DataObjectReference')) { headerEntries = { ...headerEntries, ...this._getCollectionHeaderEntries(target) }; } if (is(target, 'bpmn:Participant')) { headerEntries = { ...headerEntries, ...this._getParticipantMultiplicityHeaderEntries(target) }; } if (is(target, 'bpmn:SubProcess') && !is(target, 'bpmn:Transaction') && !isEventSubProcess(target)) { headerEntries = { ...headerEntries, ...this._getAdHocHeaderEntries(target) }; } return headerEntries; }; /** * Create popup menu entries for the given target. * * @param {PopupMenuTarget} target * @param {ReplaceOption[]} replaceOptions * * @return {PopupMenuEntries} */ ReplaceMenuProvider.prototype._createEntries = function(target, replaceOptions) { var entries = {}; var self = this; forEach(replaceOptions, function(replaceOption) { entries[ replaceOption.actionName ] = self._createEntry(replaceOption, target); }); return entries; }; /** * Creates popup menu entries for the given sequence flow. * * @param {PopupMenuTarget} target * @param {ReplaceOption[]} replaceOptions * * @return {PopupMenuEntries} */ ReplaceMenuProvider.prototype._createSequenceFlowEntries = function(target, replaceOptions) { var businessObject = getBusinessObject(target); var entries = {}; var modeling = this._modeling, moddle = this._moddle; var self = this; forEach(replaceOptions, function(replaceOption) { switch (replaceOption.actionName) { case 'replace-with-default-flow': if (businessObject.sourceRef.default !== businessObject && (is(businessObject.sourceRef, 'bpmn:ExclusiveGateway') || is(businessObject.sourceRef, 'bpmn:InclusiveGateway') || is(businessObject.sourceRef, 'bpmn:ComplexGateway') || is(businessObject.sourceRef, 'bpmn:Activity'))) { entries = { ...entries, [ replaceOption.actionName ]: self._createEntry(replaceOption, target, function() { modeling.updateProperties(target.source, { default: businessObject }); }) }; } break; case 'replace-with-conditional-flow': if (!businessObject.conditionExpression && is(businessObject.sourceRef, 'bpmn:Activity')) { entries = { ...entries, [ replaceOption.actionName ]: self._createEntry(replaceOption, target, function() { var conditionExpression = moddle.create('bpmn:FormalExpression', { body: '' }); modeling.updateProperties(target, { conditionExpression: conditionExpression }); }) }; } break; default: // conditional flow -> sequence flow if (is(businessObject.sourceRef, 'bpmn:Activity') && businessObject.conditionExpression) { entries = { ...entries, [ replaceOption.actionName ]: self._createEntry(replaceOption, target, function() { modeling.updateProperties(target, { conditionExpression: undefined }); }) }; } // default flow -> sequence flow if ((is(businessObject.sourceRef, 'bpmn:ExclusiveGateway') || is(businessObject.sourceRef, 'bpmn:InclusiveGateway') || is(businessObject.sourceRef, 'bpmn:ComplexGateway') || is(businessObject.sourceRef, 'bpmn:Activity')) && businessObject.sourceRef.default === businessObject) { entries = { ...entries, [ replaceOption.actionName ]: self._createEntry(replaceOption, target, function() { modeling.updateProperties(target.source, { default: undefined }); }) }; } } }); return entries; }; /** * Create a popup menu entry for the given replace option. * * @param {ReplaceOption} replaceOption * @param {PopupMenuTarget} target * @param {PopupMenuEntryAction} [action] * * @return {PopupMenuEntry} */ ReplaceMenuProvider.prototype._createEntry = function(replaceOption, target, action) { var translate = this._translate; var replaceElement = this._bpmnReplace.replaceElement; var replaceAction = function() { return replaceElement(target, replaceOption.target); }; var label = replaceOption.label; if (label && typeof label === 'function') { label = label(target); } action = action || replaceAction; return { label: translate(label), className: replaceOption.className, action: action }; }; /** * Get popup menu header entries for the loop characteristics of the given BPMN element. * * @param {PopupMenuTarget} target * * @return {PopupMenuHeaderEntries} */ ReplaceMenuProvider.prototype._getLoopCharacteristicsHeaderEntries = function(target) { var self = this; var translate = this._translate; function toggleLoopEntry(event, entry) { var newLoopCharacteristics = getBusinessObject(target).loopCharacteristics; if (entry.active) { newLoopCharacteristics = undefined; } else { if (isUndefined(entry.options.isSequential) || !newLoopCharacteristics || !is(newLoopCharacteristics, entry.options.loopCharacteristics)) { newLoopCharacteristics = self._moddle.create(entry.options.loopCharacteristics); } newLoopCharacteristics.isSequential = entry.options.isSequential; } self._modeling.updateProperties(target, { loopCharacteristics: newLoopCharacteristics }); } var businessObject = getBusinessObject(target), loopCharacteristics = businessObject.loopCharacteristics; var isSequential, isLoop, isParallel; if (loopCharacteristics) { isSequential = loopCharacteristics.isSequential; isLoop = loopCharacteristics.isSequential === undefined; isParallel = loopCharacteristics.isSequential !== undefined && !loopCharacteristics.isSequential; } return { 'toggle-parallel-mi' : { className: 'bpmn-icon-parallel-mi-marker', title: translate('Parallel Multi Instance'), active: isParallel, action: toggleLoopEntry, options: { loopCharacteristics: 'bpmn:MultiInstanceLoopCharacteristics', isSequential: false } }, 'toggle-sequential-mi': { className: 'bpmn-icon-sequential-mi-marker', title: translate('Sequential Multi Instance'), active: isSequential, action: toggleLoopEntry, options: { loopCharacteristics: 'bpmn:MultiInstanceLoopCharacteristics', isSequential: true } }, 'toggle-loop': { className: 'bpmn-icon-loop-marker', title: translate('Loop'), active: isLoop, action: toggleLoopEntry, options: { loopCharacteristics: 'bpmn:StandardLoopCharacteristics' } } }; }; /** * Get popup menu header entries for the collection property of the given BPMN element. * * @param {PopupMenuTarget} target * * @return {PopupMenuHeaderEntries} */ ReplaceMenuProvider.prototype._getCollectionHeaderEntries = function(target) { var self = this; var translate = this._translate; var dataObject = target.businessObject.dataObjectRef; if (!dataObject) { return {}; } function toggleIsCollection(event, entry) { self._modeling.updateModdleProperties( target, dataObject, { isCollection: !entry.active }); } var isCollection = dataObject.isCollection; return { 'toggle-is-collection': { className: 'bpmn-icon-parallel-mi-marker', title: translate('Collection'), active: isCollection, action: toggleIsCollection, } }; }; /** * Get popup menu header entries for the participant multiplicity property of the given BPMN element. * * @param {PopupMenuTarget} target * * @return {PopupMenuHeaderEntries} */ ReplaceMenuProvider.prototype._getParticipantMultiplicityHeaderEntries = function(target) { var self = this; var bpmnFactory = this._bpmnFactory; var translate = this._translate; function toggleParticipantMultiplicity(event, entry) { var isActive = entry.active; var participantMultiplicity; if (!isActive) { participantMultiplicity = bpmnFactory.create('bpmn:ParticipantMultiplicity'); } self._modeling.updateProperties( target, { participantMultiplicity: participantMultiplicity }); } var participantMultiplicity = target.businessObject.participantMultiplicity; return { 'toggle-participant-multiplicity': { className: 'bpmn-icon-parallel-mi-marker', title: translate('Participant Multiplicity'), active: !!participantMultiplicity, action: toggleParticipantMultiplicity, } }; }; /** * Get popup menu header entries for the ad-hoc property of the given BPMN element. * * @param {PopupMenuTarget} element * * @return {PopupMenuHeaderEntries} */ ReplaceMenuProvider.prototype._getAdHocHeaderEntries = function(element) { var translate = this._translate; var businessObject = getBusinessObject(element); var isAdHoc = is(businessObject, 'bpmn:AdHocSubProcess'); var replaceElement = this._bpmnReplace.replaceElement; return { 'toggle-adhoc': { className: 'bpmn-icon-ad-hoc-marker', title: translate('Ad-hoc'), active: isAdHoc, action: function(event, entry) { if (isAdHoc) { return replaceElement(element, { type: 'bpmn:SubProcess' }, { autoResize: false, layoutConnection: false }); } else { return replaceElement(element, { type: 'bpmn:AdHocSubProcess' }, { autoResize: false, layoutConnection: false }); } } } }; };