From 3cef448665adc3838688b57db4daa2ac689b1c69 Mon Sep 17 00:00:00 2001 From: Beatriz Mendes Date: Mon, 6 Feb 2023 13:31:08 +0100 Subject: [PATCH] feat(create-append-anything): create with templates Closes #243 --- .../ElementTemplatesCreateProvider.js | 115 +++++++++ .../features/create-append-anything/index.js | 9 +- .../ElementTemplatesCreateProvider.bpmn | 7 + .../ElementTemplatesCreateProviderSpec.js | 218 ++++++++++++++++++ 4 files changed, 347 insertions(+), 2 deletions(-) create mode 100644 lib/camunda-cloud/features/create-append-anything/ElementTemplatesCreateProvider.js create mode 100644 test/camunda-cloud/features/create-append-anything/ElementTemplatesCreateProvider.bpmn create mode 100644 test/camunda-cloud/features/create-append-anything/ElementTemplatesCreateProviderSpec.js diff --git a/lib/camunda-cloud/features/create-append-anything/ElementTemplatesCreateProvider.js b/lib/camunda-cloud/features/create-append-anything/ElementTemplatesCreateProvider.js new file mode 100644 index 00000000..2717a892 --- /dev/null +++ b/lib/camunda-cloud/features/create-append-anything/ElementTemplatesCreateProvider.js @@ -0,0 +1,115 @@ +import { assign } from 'min-dash'; + + +/** + * A popup menu provider that allows to create elements with + * element templates. + */ +export default function ElementTemplatesCreateProvider( + popupMenu, translate, elementTemplates, + mouse, create) { + + this._popupMenu = popupMenu; + this._translate = translate; + this._elementTemplates = elementTemplates; + this._mouse = mouse; + this._create = create; + + this.register(); +} + +ElementTemplatesCreateProvider.$inject = [ + 'popupMenu', + 'translate', + 'elementTemplates', + 'mouse', + 'create' +]; + +/** + * Register create menu provider in the popup menu + */ +ElementTemplatesCreateProvider.prototype.register = function() { + this._popupMenu.registerProvider('bpmn-create', this); +}; + +/** + * Adds the element templates to the create menu. + * @param {djs.model.Base} element + * + * @returns {Object} + */ +ElementTemplatesCreateProvider.prototype.getPopupMenuEntries = function(element) { + return (entries) => { + + // add template entries + assign(entries, this.getTemplateEntries(element)); + + return entries; + }; +}; + +/** + * Get all element templates. + * + * @param {djs.model.Base} element + * + * @return {Array} a list of element templates as menu entries + */ +ElementTemplatesCreateProvider.prototype.getTemplateEntries = function() { + + const templates = this._elementTemplates.getLatest(); + const templateEntries = {}; + + templates.map(template => { + + const { + icon = {}, + category, + } = template; + + const entryId = `create.template-${template.id}`; + + const defaultGroup = { + id: 'templates', + name: this._translate('Templates') + }; + + templateEntries[entryId] = { + label: template.name, + description: template.description, + documentationRef: template.documentationRef, + imageUrl: icon.contents, + group: category || defaultGroup, + action: { + click: this._getEntryAction(template), + dragstart: this._getEntryAction(template) + } + }; + }); + + return templateEntries; +}; + + +ElementTemplatesCreateProvider.prototype._getEntryAction = function(template) { + const create = this._create; + const popupMenu = this._popupMenu; + const elementTemplates = this._elementTemplates; + const mouse = this._mouse; + + return (event) => { + + popupMenu.close(); + + // create the new element + let newElement = elementTemplates.createElement(template); + + // use last mouse event if triggered via keyboard + if (event instanceof KeyboardEvent) { + event = mouse.getLastMoveEvent(); + } + + return create.start(event, newElement); + }; +}; diff --git a/lib/camunda-cloud/features/create-append-anything/index.js b/lib/camunda-cloud/features/create-append-anything/index.js index cc23dd73..703764a3 100644 --- a/lib/camunda-cloud/features/create-append-anything/index.js +++ b/lib/camunda-cloud/features/create-append-anything/index.js @@ -1,6 +1,11 @@ import ElementTemplatesAppendProvider from './ElementTemplatesAppendProvider'; +import ElementTemplatesCreateProvider from './ElementTemplatesCreateProvider'; export default { - __init__: [ 'elementTemplatesAppendProvider' ], - elementTemplatesAppendProvider: [ 'type', ElementTemplatesAppendProvider ] + __init__: [ + 'elementTemplatesAppendProvider', + 'elementTemplatesCreateProvider' + ], + elementTemplatesAppendProvider: [ 'type', ElementTemplatesAppendProvider ], + elementTemplatesCreateProvider: [ 'type', ElementTemplatesCreateProvider ] }; \ No newline at end of file diff --git a/test/camunda-cloud/features/create-append-anything/ElementTemplatesCreateProvider.bpmn b/test/camunda-cloud/features/create-append-anything/ElementTemplatesCreateProvider.bpmn new file mode 100644 index 00000000..4390af48 --- /dev/null +++ b/test/camunda-cloud/features/create-append-anything/ElementTemplatesCreateProvider.bpmn @@ -0,0 +1,7 @@ + + + + + + + diff --git a/test/camunda-cloud/features/create-append-anything/ElementTemplatesCreateProviderSpec.js b/test/camunda-cloud/features/create-append-anything/ElementTemplatesCreateProviderSpec.js new file mode 100644 index 00000000..18e83f9c --- /dev/null +++ b/test/camunda-cloud/features/create-append-anything/ElementTemplatesCreateProviderSpec.js @@ -0,0 +1,218 @@ +import { + inject, + getBpmnJS, + bootstrapCamundaCloudModeler, +} from 'test/TestHelper'; + +import { + query as domQuery +} from 'min-dom'; + +import diagramXML from './ElementTemplatesCreateProvider.bpmn'; +import templates from './ElementTemplatesAppendProvider.json'; + +import { + getBusinessObject, + is +} from 'bpmn-js/lib/util/ModelUtil'; + + +describe('', function() { + + beforeEach(bootstrapCamundaCloudModeler(diagramXML)); + + beforeEach(inject(function(elementTemplates) { + elementTemplates.set(templates); + })); + + + describe('display', function() { + + it('should display template options', inject(function(canvas) { + + // given + const rootElement = canvas.getRootElement(); + + // when + openPopup(rootElement); + + // then + const entries = Object.keys(getEntries()); + const templateEntries = entries.filter((entry) => entry.startsWith('create.template-')); + + expect(templateEntries.length).to.eql(templates.length); + })); + + }); + + + describe('create', function() { + + it('should create template', inject(function(elementRegistry, selection) { + + // given + const template = templates[0]; + + // when + triggerEntry(`create.template-${template.id}`); + + // then + expectElementWithTemplate(elementRegistry, 'bpmn:Task', template, true); + expectSelected(selection, 'bpmn:Task'); + })); + + + it('should undo', inject(function(elementRegistry, commandStack, selection) { + + // given + const template = templates[0]; + + // when + triggerEntry(`create.template-${template.id}`); + + // then + expectElementWithTemplate(elementRegistry, 'bpmn:Task', template); + expectSelected(selection, 'bpmn:Task'); + + // when + commandStack.undo(); + + // then + expectElementWithTemplate(elementRegistry, 'bpmn:Task', template, false); + expectSelected(selection, 'bpmn:Task', false); + })); + + + it('should redo', inject(function(elementRegistry, commandStack, selection) { + + // given + const template = templates[0]; + + // when + triggerEntry(`create.template-${template.id}`); + commandStack.undo(); + + // then + expectElementWithTemplate(elementRegistry, 'bpmn:Task', template, false); + expectSelected(selection, 'bpmn:Task', false); + + // when + commandStack.redo(); + + // then + expectElementWithTemplate(elementRegistry, 'bpmn:Task', template); + })); + + }); + +}); + + +// helpers //////////// + +function openPopup(element, offset) { + offset = offset || 100; + + getBpmnJS().invoke(function(popupMenu) { + popupMenu.open(element, 'bpmn-create', { + x: element.x, y: element.y + }); + + }); +} + +function queryEntry(id) { + var container = getMenuContainer(); + + return domQuery('.djs-popup [data-id="' + id + '"]', container); +} + +function getMenuContainer() { + const popup = getBpmnJS().get('popupMenu'); + return popup._current.container; +} + +function triggerAction(id, position = { x: 0, y: 0 }) { + const entry = queryEntry(id); + + if (!entry) { + throw new Error('entry "' + id + '" not found in append menu'); + } + + const popupMenu = getBpmnJS().get('popupMenu'); + const eventBus = getBpmnJS().get('eventBus'); + + return popupMenu.trigger( + eventBus.createEvent({ + target: entry, + clientX: position.x, + clientY: position.y + },) + ); +} + +function getEntries() { + const popupMenu = getBpmnJS().get('popupMenu'); + return popupMenu._current.entries; +} + +function isTemaplateApplied(element, template) { + const businessObject = getBusinessObject(element); + + return businessObject.get('modelerTemplate') === template.id; +} + +function expectElementWithTemplate(elementRegistry, type, template, result = true) { + const element = elementRegistry.find((element) => is(element, type)); + + if (!result) { + expect(element).to.not.exist; + } else { + expect(element).to.exist; + expect(isTemaplateApplied(element, template)).to.be.true; + } +} + +function expectSelected(selection, type, result = true) { + const selected = selection.get(); + + if (!result) { + expect(selected).to.have.length(0); + } else { + expect(selected).to.have.length(1); + expect(is(selected[0], type)).to.be.true; + } +} + +function triggerEntry(id) { + + return getBpmnJS().invoke(function(canvas, dragging) { + + var rootElement = canvas.getRootElement(), + rootGfx = canvas.getGraphics(rootElement); + + openPopup(rootElement); + triggerAction(id); + + dragging.hover({ element: rootElement, gfx: rootGfx }); + dragging.move(createCanvasEvent({ x: 200, y: 300 })); + + // when + dragging.end(); + + }); +} + +function createCanvasEvent(position) { + + return getBpmnJS().invoke(function(canvas, eventBus) { + + var target = canvas._svg; + + return eventBus.createEvent({ + target: target, + clientX: position.x, + clientY: position.y + }); + }); +}