Comparing sensitive data, confidential files or internal emails?

Most legal and privacy policies prohibit uploading sensitive data online. Diffchecker Desktop ensures your confidential information never leaves your computer. Work offline and compare documents securely.

Modeler BPMN Editor

Created Diff never expires
138 removals
899 lines
6 additions
770 lines
/**
/**
* Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH
* Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH
* under one or more contributor license agreements. See the NOTICE file
* under one or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information regarding copyright
* distributed with this work for additional information regarding copyright
* ownership.
* ownership.
*
*
* Camunda licenses this file to you under the MIT; you may not use this file
* Camunda licenses this file to you under the MIT; you may not use this file
* except in compliance with the MIT License.
* except in compliance with the MIT License.
*/
*/


import React, { Component } from 'react';
import React, { Component } from 'react';


import { isFunction } from 'min-dash';
import { isFunction } from 'min-dash';


import { Fill } from '../../slot-fill';
import { Fill } from '../../slot-fill';


import {
import {
Button,
Button,
DropdownButton,
DropdownButton,
Icon,
Icon,
Loader
Loader
} from '../../primitives';
} from '../../primitives';


import {
import {
debounce
debounce
} from '../../../util';
} from '../../../util';


import {
import {
WithCache,
WithCache,
WithCachedState,
WithCachedState,
CachedComponent
CachedComponent
} from '../../cached';
} from '../../cached';


import PropertiesContainer from '../PropertiesContainer';
import PropertiesContainer from '../PropertiesContainer';


import CamundaBpmnModeler from './modeler';
import BpmnModeler from './modeler';


import { active as isInputActive } from '../../../util/dom/isInput';
import { active as isInputActive } from '../../../util/dom/isInput';


import getBpmnContextMenu from './getBpmnContextMenu';
import getBpmnContextMenu from './getBpmnContextMenu';


import { getBpmnEditMenu } from './getBpmnEditMenu';
import { getBpmnEditMenu } from './getBpmnEditMenu';


import getBpmnWindowMenu from './getBpmnWindowMenu';
import getBpmnWindowMenu from './getBpmnWindowMenu';


import css from './BpmnEditor.less';
import css from './BpmnEditor.less';


import generateImage from '../../util/generateImage';
import generateImage from '../../util/generateImage';


import applyDefaultTemplates from './modeler/features/apply-default-templates/applyDefaultTemplates';

import {
findUsages as findNamespaceUsages,
replaceUsages as replaceNamespaceUsages
} from '../util/namespace';

import configureModeler from './util/configure';
import configureModeler from './util/configure';


import Metadata from '../../../util/Metadata';
import Metadata from '../../../util/Metadata';



const NAMESPACE_URL_ACTIVITI = 'http://activiti.org/bpmn';

const NAMESPACE_CAMUNDA = {
uri: 'http://camunda.org/schema/1.0/bpmn',
prefix: 'camunda'
};

const EXPORT_AS = [ 'png', 'jpeg', 'svg' ];
const EXPORT_AS = [ 'png', 'jpeg', 'svg' ];


const COLORS = [{
const COLORS = [{
title: 'White',
title: 'White',
fill: 'white',
fill: 'white',
stroke: 'black'
stroke: 'black'
}, {
}, {
title: 'Blue',
title: 'Blue',
fill: 'rgb(187, 222, 251)',
fill: 'rgb(187, 222, 251)',
stroke: 'rgb(30, 136, 229)'
stroke: 'rgb(30, 136, 229)'
}, {
}, {
title: 'Orange',
title: 'Orange',
fill: 'rgb(255, 224, 178)',
fill: 'rgb(255, 224, 178)',
stroke: 'rgb(251, 140, 0)'
stroke: 'rgb(251, 140, 0)'
}, {
}, {
title: 'Green',
title: 'Green',
fill: 'rgb(200, 230, 201)',
fill: 'rgb(200, 230, 201)',
stroke: 'rgb(67, 160, 71)'
stroke: 'rgb(67, 160, 71)'
}, {
}, {
title: 'Red',
title: 'Red',
fill: 'rgb(255, 205, 210)',
fill: 'rgb(255, 205, 210)',
stroke: 'rgb(229, 57, 53)'
stroke: 'rgb(229, 57, 53)'
}, {
}, {
title: 'Purple',
title: 'Purple',
fill: 'rgb(225, 190, 231)',
fill: 'rgb(225, 190, 231)',
stroke: 'rgb(142, 36, 170)'
stroke: 'rgb(142, 36, 170)'
}];
}];




export class BpmnEditor extends CachedComponent {
export class BpmnEditor extends CachedComponent {


constructor(props) {
constructor(props) {
super(props);
super(props);


this.state = {};
this.state = {};


this.ref = React.createRef();
this.ref = React.createRef();
this.propertiesPanelRef = React.createRef();
this.propertiesPanelRef = React.createRef();


this.handleResize = debounce(this.handleResize);
this.handleResize = debounce(this.handleResize);
}
}


async componentDidMount() {
async componentDidMount() {
this._isMounted = true;
this._isMounted = true;


const {
const {
layout
layout
} = this.props;
} = this.props;


const modeler = this.getModeler();
const modeler = this.getModeler();


this.listen('on');
this.listen('on');


modeler.attachTo(this.ref.current);
modeler.attachTo(this.ref.current);


const minimap = modeler.get('minimap');
const minimap = modeler.get('minimap');


if (layout.minimap) {
if (layout.minimap) {
minimap.toggle(layout.minimap && !!layout.minimap.open);
minimap.toggle(layout.minimap && !!layout.minimap.open);
}
}


const propertiesPanel = modeler.get('propertiesPanel');
const propertiesPanel = modeler.get('propertiesPanel');


propertiesPanel.attachTo(this.propertiesPanelRef.current);
propertiesPanel.attachTo(this.propertiesPanelRef.current);



try {
await this.loadTemplates();
} catch (error) {
this.handleError({ error });
}

this.checkImport();
this.checkImport();
}
}


componentWillUnmount() {
componentWillUnmount() {
this._isMounted = false;
this._isMounted = false;


const modeler = this.getModeler();
const modeler = this.getModeler();


this.listen('off');
this.listen('off');


modeler.detach();
modeler.detach();


const propertiesPanel = modeler.get('propertiesPanel');
const propertiesPanel = modeler.get('propertiesPanel');


propertiesPanel.detach();
propertiesPanel.detach();
}
}


componentDidUpdate(prevProps) {
componentDidUpdate(prevProps) {
this.checkImport(prevProps);
this.checkImport(prevProps);


if (isCacheStateChanged(prevProps, this.props)) {
if (isCacheStateChanged(prevProps, this.props)) {
this.handleChanged();
this.handleChanged();
}
}
}
}


listen(fn) {
listen(fn) {
const modeler = this.getModeler();
const modeler = this.getModeler();


[
[
'import.done',
'import.done',
'saveXML.done',
'saveXML.done',
'commandStack.changed',
'commandStack.changed',
'selection.changed',
'selection.changed',
'attach',
'attach',
'elements.copied',
'elements.copied',
'propertiesPanel.focusin',
'propertiesPanel.focusin',
'propertiesPanel.focusout',
'propertiesPanel.focusout',
'directEditing.activate',
'directEditing.activate',
'directEditing.deactivate',
'directEditing.deactivate',
'searchPad.closed',
'searchPad.closed',
'searchPad.opened'
'searchPad.opened'
].forEach((event) => {
].forEach((event) => {
modeler[fn](event, this.handleChanged);
modeler[fn](event, this.handleChanged);
});
});


modeler[fn]('elementTemplates.errors', this.handleElementTemplateErrors);

modeler[fn]('error', 1500, this.handleError);
modeler[fn]('error', 1500, this.handleError);


modeler[fn]('minimap.toggle', this.handleMinimapToggle);
modeler[fn]('minimap.toggle', this.handleMinimapToggle);
}
}


async loadTemplates() {
const { getConfig } = this.props;

const modeler = this.getModeler();

const templatesLoader = modeler.get('elementTemplatesLoader');

const templates = await getConfig('bpmn.elementTemplates');

templatesLoader.setTemplates(templates);

const propertiesPanel = modeler.get('propertiesPanel', false);

if (propertiesPanel) {
const currentElement = propertiesPanel._current && propertiesPanel._current.element;

if (currentElement) {
propertiesPanel.update(currentElement);
}
}
}

undo = () => {
undo = () => {
const modeler = this.getModeler();
const modeler = this.getModeler();


modeler.get('commandStack').undo();
modeler.get('commandStack').undo();
}
}


redo = () => {
redo = () => {
const modeler = this.getModeler();
const modeler = this.getModeler();


modeler.get('commandStack').redo();
modeler.get('commandStack').redo();
}
}


handleAlignElements = (type) => {
handleAlignElements = (type) => {
this.triggerAction('alignElements', {
this.triggerAction('alignElements', {
type
type
});
});
}
}


handleMinimapToggle = (event) => {
handleMinimapToggle = (event) => {
this.handleLayoutChange({
this.handleLayoutChange({
minimap: {
minimap: {
open: event.open
open: event.open
}
}
});
});
}
}


handleElementTemplateErrors = (event) => {
const {
errors
} = event;

errors.forEach(error => {
this.handleError({ error });
});
}

handleError = (event) => {
handleError = (event) => {
const {
const {
error
error
} = event;
} = event;


const {
const {
onError
onError
} = this.props;
} = this.props;


onError(error);
onError(error);
}
}


handleNamespace = async (xml) => {
const used = findNamespaceUsages(xml, NAMESPACE_URL_ACTIVITI);

if (!used) {
return xml;
}

const shouldConvert = await this.shouldConvert();

if (!shouldConvert) {
return xml;
}

const {
onContentUpdated
} = this.props;

const convertedXML = await replaceNamespaceUsages(xml, used, NAMESPACE_CAMUNDA);

onContentUpdated(convertedXML);

return convertedXML;
}

async shouldConvert() {
const { button } = await this.props.onAction('show-dialog', getNamespaceDialog());

return button === 'yes';
}

handleImport = (error, warnings) => {
handleImport = (error, warnings) => {
const {
const {
isNew,
onImport,
onImport,
xml
xml
} = this.props;
} = this.props;


let {
defaultTemplatesApplied
} = this.getCached();

const modeler = this.getModeler();
const modeler = this.getModeler();


const commandStack = modeler.get('commandStack');
const commandStack = modeler.get('commandStack');


const stackIdx = commandStack._stackIdx;
const stackIdx = commandStack._stackIdx;


if (error) {
if (!error) {
this.setCached({
defaultTemplatesApplied: false,
lastXML: null
});
} else {

if (isNew && !defaultTemplatesApplied) {
modeler.invoke(applyDefaultTemplates);

defaultTemplatesApplied = true;
}


this.setCached({
this.setCached({
defaultTemplatesApplied,
lastXML: xml,
lastXML: xml,
stackIdx
stackIdx
});
});


this.setState({
this.setState({
importing: false
importing: false
});
});
}
}


onImport(error, warnings);
onImport(error, warnings);
}
}


handleChanged = () => {
handleChanged = () => {
const modeler = this.getModeler();
const modeler = this.getModeler();


const {
const {
onChanged
onChanged
} = this.props;
} = this.props;


const dirty = this.isDirty();
const dirty = this.isDirty();


const commandStack = modeler.get('commandStack');
const commandStack = modeler.get('commandStack');
const selection = modeler.get('selection');
const selection = modeler.get('selection');


const selectionLength = selection.get().length;
const selectionLength = selection.get().length;


const inputActive = isInputActive();
const inputActive = isInputActive();


const newState = {
const newState = {
align: selectionLength > 1,
align: selectionLength > 1,
close: true,
close: true,
copy: !!selectionLength,
copy: !!selectionLength,
cut: false,
cut: false,
defaultCopyCutPaste: inputActive,
defaultCopyCutPaste: inputActive,
defaultUndoRedo: inputActive,
defaultUndoRedo: inputActive,
dirty,
dirty,
distribute: selectionLength > 2,
distribute: selectionLength > 2,
editLabel: !inputActive && !!selectionLength,
editLabel: !inputActive && !!selectionLength,
exportAs: EXPORT_AS,
exportAs: EXPORT_AS,
find: !inputActive,
find: !inputActive,
globalConnectTool: !inputActive,
globalConnectTool: !inputActive,
handTool: !inputActive,
handTool: !inputActive,
inputActive,
inputActive,
lassoTool: !inputActive,
lassoTool: !inputActive,
moveCanvas: !inputActive,
moveCanvas: !inputActive,
moveToOrigin: !inputActive,
moveToOrigin: !inputActive,
moveSelection: !inputActive && !!selectionLength,
moveSelection: !inputActive && !!selectionLength,
paste: !modeler.get('clipboard').isEmpty(),
paste: !modeler.get('clipboard').isEmpty(),
propertiesPanel: true,
propertiesPanel: true,
redo: commandStack.canRedo(),
redo: commandStack.canRedo(),
removeSelected: !!selectionLength || inputActive,
removeSelected: !!selectionLength || inputActive,
save: true,
save: true,
selectAll: true,
selectAll: true,
setColor: !!selectionLength,
setColor: !!selectionLength,
spaceTool: !inputActive,
spaceTool: !inputActive,
undo: commandStack.canUndo(),
undo: commandStack.canUndo(),
zoom: true
zoom: true
};
};


// ensure backwards compatibility
// ensure backwards compatibility
// https://github.com/camunda/camunda-modeler/commit/78357e3ed9e6e0255ac8225fbdf451a90457e8bf#diff-bd5be70c4e5eadf1a316c16085a72f0fL17
// https://github.com/camunda/camunda-modeler/commit/78357e3ed9e6e0255ac8225fbdf451a90457e8bf#diff-bd5be70c4e5eadf1a316c16085a72f0fL17
newState.bpmn = true;
newState.bpmn = true;
newState.editable = true;
newState.editable = true;
newState.elementsSelected = !!selectionLength;
newState.elementsSelected = !!selectionLength;
newState.inactiveInput = !inputActive;
newState.inactiveInput = !inputActive;


const contextMenu = getBpmnContextMenu(newState);
const contextMenu = getBpmnContextMenu(newState);


const editMenu = getBpmnEditMenu(newState);
const editMenu = getBpmnEditMenu(newState);


const windowMenu = getBpmnWindowMenu(newState);
const windowMenu = getBpmnWindowMenu(newState);


if (isFunction(onChanged)) {
if (isFunction(onChanged)) {
onChanged({
onChanged({
...newState,
...newState,
contextMenu,
contextMenu,
editMenu,
editMenu,
windowMenu
windowMenu
});
});
}
}


this.setState(newState);
this.setState(newState);
}
}


isDirty() {
isDirty() {
const {
const {
modeler,
modeler,
stackIdx
stackIdx
} = this.getCached();
} = this.getCached();


const commandStack = modeler.get('commandStack');
const commandStack = modeler.get('commandStack');


return commandStack._stackIdx !== stackIdx;
return commandStack._stackIdx !== stackIdx;
}
}


checkImport(prevProps) {
checkImport(prevProps) {


if (!this.isImportNeeded(prevProps)) {
if (!this.isImportNeeded(prevProps)) {
return;
return;
}
}


this.importXML();
this.importXML();
}
}


isImportNeeded(prevProps) {
isImportNeeded(prevProps) {
const {
const {
importing
importing
} = this.state;
} = this.state;


if (importing) {
if (importing) {
return false;
return false;
}
}


const {
const {
xml
xml
} = this.props;
} = this.props;


if (prevProps && prevProps.xml === xml) {
if (prevProps && prevProps.xml === xml) {
return false;
return false;
}
}


const {
const {
lastXML
lastXML
} = this.getCached();
} = this.getCached();


return xml !== lastXML;
return xml !== lastXML;
}
}


async importXML() {
async importXML() {
const {
const {
xml
xml
} = this.props;
} = this.props;


this.setState({
this.setState({
importing: true
importing: true
});
});


const modeler = this.getModeler();
const modeler = this.getModeler();


const importedXML = await this.handleNamespace(xml);



let error = null, warnings = null;
let error = null, warnings = null;
try {
try {


const result = await modeler.importXML(importedXML);
const result = await modeler.importXML(xml);
warnings = result.warnings;
warnings = result.warnings;
} catch (err) {
} catch (err) {


error = err;
error = err;
warnings = err.warnings;
warnings = err.warnings;
}
}


if (this._isMounted) {
if (this._isMounted) {
this.handleImport(error, warnings);
this.handleImport(error, warnings);
}
}
}
}


/**
/**
* @returns {CamundaBpmnModeler}
* @returns {ZeebeModeler}
*/
*/
getModeler() {
getModeler() {
const {
const {
modeler
modeler
} = this.getCached();
} = this.getCached();


return modeler;
return modeler;
}
}


async getXML() {
async getXML() {
const {
const {
lastXML,
lastXML,
modeler
modeler
} = this.getCached();
} = this.getCached();


const commandStack = modeler.get('commandStack');
const commandStack = modeler.get('commandStack');


const stackIdx = commandStack._stackIdx;
const stackIdx = commandStack._stackIdx;


if (!this.isDirty()) {
if (!this.isDirty()) {
return lastXML || this.props.xml;
return lastXML || this.props.xml;
}
}


try {
try {


const { xml } = await modeler.saveXML({ format: true });
const { xml } = await modeler.saveXML({ format: true });


this.setCached({ lastXML: xml, stackIdx });
this.setCached({ lastXML: xml, stackIdx });


return xml;
return xml;
} catch (error) {
} catch (error) {


this.handleError({ error });
this.handleError({ error });


return Promise.reject(error);
return Promise.reject(error);
}
}
}
}


async exportAs(type) {
async exportAs(type) {
const svg = await this.exportSVG();
const svg = await this.exportSVG();


if (type === 'svg') {
if (type === 'svg') {
return svg;
return svg;
}
}


return generateImage(type, svg);
return generateImage(type, svg);
}
}


async exportSVG() {
async exportSVG() {
const modeler = this.getModeler();
const modeler = this.getModeler();


try {
try {


const { svg } = await modeler.saveSVG();
const { svg } = await modeler.saveSVG();


return svg;
return svg;
} catch (err) {
} catch (err) {


return Promise.reject(err);
return Promise.reject(err);
}
}
}
}


triggerAction = (action, context) => {
triggerAction = (action, context) => {
const { propertiesPanel: propertiesPanelLayout } = this.props.layout;
const { propertiesPanel: propertiesPanelLayout } = this.props.layout;
const modeler = this.getModeler();
const modeler = this.getModeler();


if (action === 'resize') {
if (action === 'resize') {
return this.handleResize();
return this.handleResize();
}
}


if (action === 'toggleProperties') {
if (action === 'toggleProperties') {
const newLayout = {
const newLayout = {
propertiesPanel: {
propertiesPanel: {
...propertiesPanelLayout,
...propertiesPanelLayout,
open: !propertiesPanelLayout.open
open: !propertiesPanelLayout.open
}
}
};
};


return this.handleLayoutChange(newLayout);
return this.handleLayoutChange(newLayout);
}
}


if (action === 'resetProperties') {
if (action === 'resetProperties') {
const newLayout = {
const newLayout = {
propertiesPanel: {
propertiesPanel: {
width: 250,
width: 250,
open: true
open: true
}
}
};
};


return this.handleLayoutChange(newLayout);
return this.handleLayoutChange(newLayout);
}
}


if (action === 'zoomIn') {
if (action === 'zoomIn') {
action = 'stepZoom';
action = 'stepZoom';


context = {
context = {
value: 1
value: 1
};
};
}
}


if (action === 'zoomOut') {
if (action === 'zoomOut') {
action = 'stepZoom';
action = 'stepZoom';


context = {
context = {
value: -1
value: -1
};
};
}
}


if (action === 'resetZoom') {
if (action === 'resetZoom') {
action = 'zoom';
action = 'zoom';


context = {
context = {
value: 1
value: 1
};
};
}
}


if (action === 'zoomFit') {
if (action === 'zoomFit') {
action = 'zoom';
action = 'zoom';


context = {
context = {
value: 'fit-viewport'
value: 'fit-viewport'
};
};
}
}


if (action === 'elementTemplates.reload') {
return this.loadTemplates();
}

// TODO(nikku): handle all editor actions
// TODO(nikku): handle all editor actions
return modeler.get('editorActions').trigger(action, context);
modeler.get('editorActions').trigger(action, context);
}
}


handleSetColor = (fill, stroke) => {
handleSetColor = (fill, stroke) => {
this.triggerAction('setColor', {
this.triggerAction('setColor', {
fill,
fill,
stroke
stroke
});
});
}
}


handleDistributeElements = (type) => {
handleDistributeElements = (type) => {
this.triggerAction('distributeElements', {
this.triggerAction('distributeElements', {
type
type
});
});
}
}


handleContextMenu = (event) => {
handleContextMenu = (event) => {


const {
const {
onContextMenu
onContextMenu
} = this.props;
} = this.props;


if (isFunction(onContextMenu)) {
if (isFunction(onContextMenu)) {
onContextMenu(event);
onContextMenu(event);
}
}
}
}


handleLayoutChange(newLayout) {
handleLayoutChange(newLayout) {
const {
const {
onLayoutChanged
onLayoutChanged
} = this.props;
} = this.props;


if (isFunction(onLayoutChanged)) {
if (isFunction(onLayoutChanged)) {
onLayoutChanged(newLayout);
onLayoutChanged(newLayout);
}
}
}
}


handleResize = () => {
handleResize = () => {
const modeler = this.getModeler();
const modeler = this.getModeler();


const canvas = modeler.get('canvas');
const canvas = modeler.get('canvas');
const eventBus = modeler.get('eventBus');
const eventBus = modeler.get('eventBus');


canvas.resized();
canvas.resized();
eventBus.fire('propertiesPanel.resized');
eventBus.fire('propertiesPanel.resized');
}
}


render() {
render() {


const {
const {
layout,
layout,
onLayoutChanged
onLayoutChanged
} = this.props;
} = this.props;


const imported = this.getModeler().getDefinitions();
const imported = this.getModeler().getDefinitions();


const {
const {
importing
importing
} = this.state;
} = this.state;


return (
return (
<div className={ css.BpmnEditor }>
<div className={ css.BpmnEditor }>


<Loader hidden={ imported && !importing } />
<Loader hidden={ imported && !importing } />


<Fill slot="toolbar" group="5_color">
<Fill slot="toolbar" group="5_color">
<DropdownButton
<DropdownButton
title="Set element color"
title="Set element color"
disabled={ !this.state.setColor }
disabled={ !this.state.setColor }
items={
items={
() => COLORS.map((color, index) => {
() => COLORS.map((color, index) => {
const { fill, stroke, title } = color;
const { fill, stroke, title } = color;


return (
return (
<Color
<Color
fill={ fill }
fill={ fill }
key={ index }
key={ index }
stroke={ stroke }
stroke={ stroke }
title={ title }
title={ title }
onClick={ () => this.handleSetColor(fill, stroke) } />
onClick={ () => this.handleSetColor(fill, stroke) } />
);
);
})
})
}
}
>
>
<Icon name="set-color-tool" />
<Icon name="set-color-tool" />
</DropdownButton>
</DropdownButton>
</Fill>
</Fill>


<Fill slot="toolbar" group="6_align">
<Fill slot="toolbar" group="6_align">
<Button
<Button
title="Align elements left"
title="Align elements left"
disabled={ !this.state.align }
disabled={ !this.state.align }
onClick={ () => this.handleAlignElements('left') }
onClick={ () => this.handleAlignElements('left') }
>
>
<Icon name="align-left-tool" />
<Icon name="align-left-tool" />
</Button>
</Button>
<Button
<Button
title="Align elements center"
title="Align elements center"
disabled={ !this.state.align }
disabled={ !this.state.align }
onClick={ () => this.handleAlignElements('center') }
onClick={ () => this.handleAlignElements('center') }
>
>
<Icon name="align-center-tool" />
<Icon name="align-center-tool" />
</Button>
</Button>
<Button
<Button
title="Align elements right"
title="Align elements right"
disabled={ !this.state.align }
disabled={ !this.state.align }
onClick={ () => this.handleAlignElements('right') }
onClick={ () => this.handleAlignElements('right') }
>
>
<Icon name="align-right-tool" />
<Icon name="align-right-tool" />
</Button>
</Button>
<Button
<Button
title="Align elements top"
title="Align elements top"
disabled={ !this.state.align }
disabled={ !this.state.align }
onClick={ () => this.handleAlignElements('top') }>
onClick={ () => this.handleAlignElements('top') }>
<Icon name="align-top-tool" />
<Icon name="align-top-tool" />
</Button>
</Button>
<Button
<Button
title="Align elements middle"
title="Align elements middle"
disabled={ !this.state.align }
disabled={ !this.state.align }
onClick={ () => this.handleAlignElements('middle') }
onClick={ () => this.handleAlignElements('middle') }
>
>
<Icon name="align-middle-tool" />
<Icon name="align-middle-tool" />
</Button>
</Button>
<Button
<Button
title="Align elements bottom"
title="Align elements bottom"
disabled={ !this.state.align }
disabled={ !this.state.align }
onClick={ () => this.handleAlignElements('bottom') }
onClick={ () => this.handleAlignElements('bottom') }
>
>
<Icon name="align-bottom-tool" />
<Icon name="align-bottom-tool" />
</Button>
</Button>
</Fill>
</Fill>


<Fill slot="toolbar" group="7_distribute">
<Fill slot="toolbar" group="7_distribute">
<Button
<Button
title="Distribute elements horizontally"
title="Distribute elements horizontally"
disabled={ !this.state.distribute }
disabled={ !this.state.distribute }
onClick={ () => this.handleDistributeElements('horizontal') }
onClick={ () => this.handleDistributeElements('horizontal') }
>
>
<Icon name="distribute-horizontal-tool" />
<Icon name="distribute-horizontal-tool" />
</Button>
</Button>
<Button
<Button
title="Distribute elements vertically"
title="Distribute elements vertically"
disabled={ !this.state.distribute }
disabled={ !this.state.distribute }
onClick={ () => this.handleDistributeElements('vertical') }
onClick={ () => this.handleDistributeElements('vertical') }
>
>
<Icon name="distribute-vertical-tool" />
<Icon name="distribute-vertical-tool" />
</Button>
</Button>
</Fill>
</Fill>

<div
<div
className="diagram"
className="diagram"
ref={ this.ref }
ref={ this.ref }
onFocus={ this.handleChanged }
onFocus={ this.handleChanged }
onContextMenu={ this.handleContextMenu }
onContextMenu={ this.handleContextMenu }
></div>
></div>


<PropertiesContainer
<PropertiesContainer
className="properties"
className="properties"
layout={ layout }
layout={ layout }
ref={ this.propertiesPanelRef }
ref={ this.propertiesPanelRef }
onLayoutChanged={ onLayoutChanged } />
onLayoutChanged={ onLayoutChanged } />
</div>
</div>
);
);
}
}


static createCachedState(props) {
static createCachedState(props) {


const {
const {
name,
name,
version
version
} = Metadata;
} = Metadata;


const {
const {
getPlugins,
getPlugins,
onAction,
onAction,
onError
onError
} = props;
} = props;


// notify interested parties that modeler will be configured
// notify interested parties that modeler will be configured
const handleMiddlewareExtensions = (middlewares) => {
const handleMiddlewareExtensions = (middlewares) => {
onAction('emit-event', {
onAction('emit-event', {
type: 'bpmn.modeler.configure',
type: 'bpmn.modeler.configure',
payload: {
payload: {
middlewares
middlewares
}
}
});
});
};
};


const {
const {
options,
options,
warnings
warnings
} = configureModeler(getPlugins, {
} = configureModeler(getPlugins, {
exporter: {
exporter: {
name,
name,
version
version
},
},
}, handleMiddlewareExtensions);
}, handleMiddlewareExtensions);


if (warnings.length && isFunction(onError)) {
if (warnings.length && isFunction(onError)) {
onError(
onError(
'Problem(s) configuring BPMN editor: \n\t' +
'Problem(s) configuring BPMN editor: \n\t' +
warnings.map(error => error.message).join('\n\t') +
warnings.map(error => error.message).join('\n\t') +
'\n'
'\n'
);
);
}
}


const modeler = new CamundaBpmnModeler({
const modeler = new BpmnModeler({
...options,
...options,
position: 'absolute'
position: 'absolute'
});
});


const commandStack = modeler.get('commandStack');
const commandStack = modeler.get('commandStack');


const stackIdx = commandStack._stackIdx;
const stackIdx = commandStack._stackIdx;


// notify interested parties that modeler was created
// notify interested parties that modeler was created
onAction('emit-event', {
onAction('emit-event', {
type: 'bpmn.modeler.created',
type: 'bpmn.modeler.created',
payload: {
payload: {
modeler
modeler
}
}
});
});


return {
return {
__destroy: () => {
__destroy: () => {
modeler.destroy();
modeler.destroy();
},
},
lastXML: null,
lastXML: null,
modeler,
modeler,
namespaceDialogShown: false,
stackIdx
stackIdx,
templatesLoaded: false
};
};
}
}


}
}




export default WithCache(WithCachedState(BpmnEditor));
export default WithCache(WithCachedState(BpmnEditor));


class Color extends Component {
class Color extends Component {
render() {
render() {
const {
const {
fill,
fill,
onClick,
onClick,
stroke,
stroke,
title,
title,
...rest
...rest
} = this.props;
} = this.props;


return (
return (
<div
<div
className={ css.Color }
className={ css.Color }
onClick={ onClick }
onClick={ onClick }
style={ {
style={ {
backgroundColor: fill,
backgroundColor: fill,
borderColor: stroke
borderColor: stroke
} }
} }
title={ title }
title={ title }
{ ...rest }></div>
{ ...rest }></div>
);
);
}
}
}
}


// helpers //////////
// helpers //////////


function getNamespaceDialog() {
return {
type: 'warning',
title: 'Deprecated <activiti> namespace detected',
buttons: [
{ id: 'cancel', label: 'Cancel' },
{ id: 'yes', label: 'Yes' }
],
message: 'Would you like to convert your diagram to the <camunda> namespace?',
detail: [
'This will allow you to maintain execution related properties.',
'',
'<camunda> namespace support works from Camunda BPM versions 7.4.0, 7.3.3, 7.2.6 onwards.'
].join('\n')
};
}

function isCacheStateChanged(prevProps, props) {
function isCacheStateChanged(prevProps, props) {
return prevProps.cachedState !== props.cachedState;
return prevProps.cachedState !== props.cachedState;
}
}