Skip to content

Commit

Permalink
feat(create-append-anything): create with templates
Browse files Browse the repository at this point in the history
Closes #243
  • Loading branch information
smbea committed Feb 13, 2023
1 parent 9961c1d commit 3cef448
Show file tree
Hide file tree
Showing 4 changed files with 347 additions and 2 deletions.
Original file line number Diff line number Diff line change
@@ -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<Object>} 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);
};
};
9 changes: 7 additions & 2 deletions lib/camunda-cloud/features/create-append-anything/index.js
Original file line number Diff line number Diff line change
@@ -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 ]
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:modeler="http://camunda.org/schema/modeler/1.0" id="Definitions_0su05ks" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="5.8.0-rc.0" modeler:executionPlatform="Camunda Cloud" modeler:executionPlatformVersion="8.1.0">
<bpmn:process id="Process_1uc9zgy" isExecutable="true" />
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_1uc9zgy" />
</bpmndi:BPMNDiagram>
</bpmn:definitions>
Original file line number Diff line number Diff line change
@@ -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('<ElementTemplatesCreateProviderSpec>', 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
});
});
}

0 comments on commit 3cef448

Please sign in to comment.