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;
}
}