Skip to content

Commit

Permalink
feat(cloud): templates as replace menu entries
Browse files Browse the repository at this point in the history
Closes #204
  • Loading branch information
smbea committed Nov 30, 2022
1 parent 8c07bfd commit 3719ce6
Show file tree
Hide file tree
Showing 7 changed files with 1,270 additions and 1 deletion.
4 changes: 3 additions & 1 deletion lib/camunda-cloud/Modeler.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
ZeebeDescriptionProvider
} from 'bpmn-js-properties-panel';

import replaceModule from './features/replace';

import { commonModdleExtensions, commonModules } from './util/commonModules';

Expand Down Expand Up @@ -44,7 +45,8 @@ Modeler.prototype._camundaCloudModules = [
behaviorsModule,
rulesModule,
zeebePropertiesProviderModule,
cloudElementTemplatesPropertiesProvider
cloudElementTemplatesPropertiesProvider,
replaceModule
];

Modeler.prototype._modules = [].concat(
Expand Down
215 changes: 215 additions & 0 deletions lib/camunda-cloud/features/replace/ElementTemplatesReplaceProvider.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
import {
isDifferentType
} from 'bpmn-js/lib/features/popup-menu/util/TypeUtil';

import {
getBusinessObject,
isAny
} from 'bpmn-js/lib/util/ModelUtil';

import {
getReplaceOptions
} from './ReplaceOptionsUtil';

/**
* A replace menu provider that allows to replace elements with
* element templates.
*/
export default function ElementTemplatesReplaceProvider(popupMenu, translate, elementTemplates) {

this._popupMenu = popupMenu;
this._translate = translate;
this._elementTemplates = elementTemplates;

this.register();
}

ElementTemplatesReplaceProvider.$inject = [
'popupMenu',
'translate',
'elementTemplates'
];

/**
* Register replace menu provider in the popup menu
*/
ElementTemplatesReplaceProvider.prototype.register = function() {
this._popupMenu.registerProvider('bpmn-replace', this);
};

/**
* Adds the element templates to the replace menu.
* @param {djs.model.Base} element
*
* @returns {Object}
*/
ElementTemplatesReplaceProvider.prototype.getPopupMenuEntries = function(element) {

return (entries) => {

// convert our entries into something sortable
let entrySet = Object.entries(entries);

// add unlink template option
this._addPlainElementEntry(element, entrySet);

// add template entries
entrySet = [ ...entrySet, ...this.getTemplateEntries(element) ];

// convert back to object
return entrySet.reduce((entries, [ key, value ]) => {
entries[key] = value;

return entries;
}, {});
};
};

/**
* Adds the option to replace with plain element (unlink template).
*
* @param {djs.model.Base} element
* @param {Array<Object>} entries
*/
ElementTemplatesReplaceProvider.prototype._addPlainElementEntry = function(element, entries) {

const replaceOption = this._getPlainEntry(element, entries);

const [
insertIndex,
entry
] = replaceOption;

// insert unlink entry
entries.splice(insertIndex, 0, [ entry.id, entry ]);
};

/**
* Returns the option to replace with plain element and the index where it should be inserted.
*
* @param {djs.model.Base} element
* @param {Array<Object>} entries
*
* @returns {Array<Object, number>}
*/
ElementTemplatesReplaceProvider.prototype._getPlainEntry = function(element, entries) {

const options = getReplaceOptions();

const isSameType = (element, option) => option.target && !isDifferentType(element)(option);

const optionIndex = options.findIndex(option => isSameType(element, option));

if (optionIndex === -1) {
return;
}

const option = options[optionIndex];

const entry = {
id: option.actionName,
action: () => {
this._elementTemplates.applyTemplate(element, null);
},
label: this._translate(option.label),
className: option.className
};

// insert after previous option, if it exists
const previousIndex = getOptionIndex(options, optionIndex - 1, entries);

if (previousIndex) {
return [
previousIndex + 1,
entry
];
}

// insert before next option, if it exists
const nextIndex = getOptionIndex(options, optionIndex + 1, entries);

if (nextIndex) {
return [
nextIndex,
entry
];
}

// fallback to insert at start
return [
0,
entry
];
};

/**
* Get all element templates that can be used to replace the given element.
*
* @param {djs.model.Base} element
*
* @return {Array<Object>} a list of element templates as menu entries
*/
ElementTemplatesReplaceProvider.prototype.getTemplateEntries = function(element) {

const templates = this._getMatchingTemplates(element);
return templates.map(template => {

const {
icon = {},
group = {
id: 'templates',
name: this._translate('Templates')
}
} = template;

const entryId = `replace-with-template-${template.id}`;

return [ entryId, {
name: template.name,
description: template.description,
documentationRef: template.documentationRef,
imageUrl: icon.contents,
group,
action: () => {
this._elementTemplates.applyTemplate(element, template);
}
} ];
});
};

/**
* Returns the templates that can the element can be replaced with.
*
* @param {djs.model.Base} element
*
* @return {Array<ElementTemplate>}
*/
ElementTemplatesReplaceProvider.prototype._getMatchingTemplates = function(element) {
return this._elementTemplates.getLatest().filter(template => {
return isAny(element, template.appliesTo) && !isTemplateApplied(element, template);
});
};


// helpers ////////////
export function isTemplateApplied(element, template) {
const businessObject = getBusinessObject(element);

if (businessObject) {
return businessObject.get('zeebe:modelerTemplate') === template.id;
}

return false;
}

function getOptionIndex(options, index, entries) {
const option = options[index];

if (!option) {
return false;
}

return entries.findIndex(
([ key ]) => key === option.actionName
);
}
7 changes: 7 additions & 0 deletions lib/camunda-cloud/features/replace/ReplaceOptionsUtil.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import * as replaceOptions from 'bpmn-js/lib/features/replace/ReplaceOptions';

const ALL_OPTIONS = Object.values(replaceOptions).flat();

export function getReplaceOptions() {
return ALL_OPTIONS;
}
6 changes: 6 additions & 0 deletions lib/camunda-cloud/features/replace/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import ElementTemplatesReplaceProvider from './ElementTemplatesReplaceProvider';

export default {
__init__: [ 'elementTemplatesProvider' ],
elementTemplatesProvider: [ 'type', ElementTemplatesReplaceProvider ]
};
Loading

0 comments on commit 3719ce6

Please sign in to comment.