2 * Copyright © 2018 European Support Limited
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 *http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 import React, { Component } from 'react';
17 import fileSaver from 'file-saver';
18 import isEqual from 'lodash.isequal';
19 import CustomModeler from './custom-modeler';
20 import propertiesPanelModule from 'bpmn-js-properties-panel';
21 import propertiesProviderModule from './custom-properties-provider/provider/camunda';
22 import camundaModuleDescriptor from './custom-properties-provider/descriptors/camunda';
23 import newDiagramXML from './newDiagram.bpmn';
24 import PropTypes from 'prop-types';
25 import CompositionButtons from './components/CompositionButtonsPanel';
26 import { setElementInputsOutputs } from './bpmnUtils.js';
27 import { I18n } from 'react-redux-i18n';
30 COMPOSITION_ERROR_COLOR,
31 COMPOSITION_VALID_COLOR,
32 CAMUNDA_PANEL_INPUTS_NAMES
33 } from './compositionConstants';
34 import readOnly from './readOnly';
36 function setStatusToElement(type, status, parent) {
37 let elements = parent.getElementsByTagName(type);
38 for (let item of elements) {
39 if (item.name !== 'selectedExtensionElement') {
40 item.readOnly = status;
41 item.disabled = status;
46 function disablePanelInputs(status) {
47 let panel = document.getElementById('js-properties-panel');
50 setStatusToElement('input', status, panel);
51 setStatusToElement('button', status, panel);
52 setStatusToElement('select', status, panel);
54 CAMUNDA_PANEL_INPUTS_NAMES.map(name => {
55 const div = document.getElementById(name);
57 div.setAttribute('contenteditable', !status);
62 class CompositionView extends Component {
64 compositionUpdate: PropTypes.func,
65 showErrorModal: PropTypes.func,
66 composition: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]),
67 name: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]),
68 versionName: PropTypes.string,
69 inputOutput: PropTypes.object,
70 activities: PropTypes.array,
71 validationUpdate: PropTypes.func,
72 errors: PropTypes.array,
73 isReadOnly: PropTypes.bool
78 this.generatedId = 'bpmn-container' + Date.now();
79 this.fileInput = React.createRef();
80 this.bpmnContainer = React.createRef();
81 this.selectedElement = false;
85 this.versionChanged = false;
87 componentDidUpdate(prevProps) {
88 const { errors, isReadOnly, versionName, composition } = this.props;
89 if (!isEqual(prevProps.errors, errors)) {
91 this.modeling.setColor([item.element], {
93 ? COMPOSITION_VALID_COLOR
94 : COMPOSITION_ERROR_COLOR
98 if (prevProps.isReadOnly !== isReadOnly) {
99 this.modeler.get('readOnly').readOnly(isReadOnly);
100 disablePanelInputs(isReadOnly);
103 if (prevProps.versionName !== versionName) {
104 this.versionChanged = true;
107 !isEqual(prevProps.composition, composition) &&
110 this.setDiagramToBPMN(composition);
111 this.versionChanged = false;
114 componentDidMount() {
123 const readOnlyModule = {
124 __init__: ['readOnly'],
125 readOnly: ['type', readOnly]
127 this.modeler = new CustomModeler({
129 parent: '#js-properties-panel'
132 propertiesPanelModule,
133 propertiesProviderModule,
137 camunda: camundaModuleDescriptor
140 activities: activities,
141 getActivityInputsOutputs: this.getActivityInputsOutputs,
142 workflowInputOutput: inputOutput,
143 validationUpdate: validationUpdate
147 this.modeler.attachTo('#' + this.generatedId);
148 this.setDiagramToBPMN(composition ? composition : newDiagramXML);
149 this.modeler.on('element.out', () => this.exportDiagramToStore());
150 this.modeler.on('element.click', this.handleCompositionStatus);
151 this.bpmnContainer.current.click();
152 this.modeling = this.modeler.get('modeling');
153 this.modeler.get('readOnly').readOnly(isReadOnly);
155 handleCompositionStatus = () => {
156 disablePanelInputs(this.props.isReadOnly);
158 getActivityInputsOutputs = selectedValue => {
159 const selectedActivity = this.props.activities.find(
160 el => el.name === selectedValue
163 if (selectedActivity) {
164 const inputsOutputs = {
165 inputs: selectedActivity.inputs,
166 outputs: selectedActivity.outputs
168 return inputsOutputs;
169 } else return { inputs: [], outputs: [] };
172 setDiagramToBPMN = diagram => {
173 let modeler = this.modeler;
174 this.modeler.importXML(diagram, err => {
176 return this.props.showErrorModal(
177 I18n.t('workflow.composition.importErrorMsg')
180 const canvas = modeler.get('canvas');
181 canvas.zoom('fit-viewport');
182 const { businessObject } = canvas._rootElement;
184 this.setDefaultIdAndName(businessObject);
185 setElementInputsOutputs(
187 this.props.inputOutput,
188 this.modeler.get('moddle')
190 disablePanelInputs(this.props.isReadOnly);
193 setDefaultIdAndName = businessObject => {
194 const { name = '' } = this.props;
195 if (!businessObject.name) {
196 businessObject.name = name;
199 if (businessObject.id === PROCESS_DEFAULT_ID) {
200 businessObject.id = name.toLowerCase().replace(/\s/g, '_');
203 exportDiagramToStore = () => {
204 this.modeler.saveXML({ format: true }, (err, xml) => {
206 return this.props.showErrorModal(
207 I18n.t('workflow.composition.saveErrorMsg')
210 return this.props.compositionUpdate(xml);
214 exportDiagram = () => {
215 const { name, showErrorModal, versionName } = this.props;
216 this.modeler.saveXML({ format: true }, (err, xml) => {
218 return showErrorModal(
219 I18n.t('workflow.composition.exportErrorMsg')
222 const blob = new Blob([xml], { type: 'text/html;charset=utf-8' });
225 `${name.replace(/\s/g, '').toLowerCase()}-${versionName}.bpmn`
230 loadNewDiagram = () => {
231 this.setDiagramToBPMN(newDiagramXML);
234 uploadDiagram = () => {
235 this.fileInput.current.click();
238 handleFileInputChange = filesList => {
239 const file = filesList[0];
240 const reader = new FileReader();
241 reader.onloadend = event => {
242 var xml = event.target.result;
243 this.setDiagramToBPMN(xml);
244 this.fileInput.value = '';
246 reader.readAsText(file);
251 <div className="composition-view content">
254 onChange={e => this.handleFileInputChange(e.target.files)}
259 style={{ display: 'none' }}
262 ref={this.bpmnContainer}
263 className="bpmn-container"
264 id={this.generatedId}
266 <div className="bpmn-sidebar">
268 className="properties-panel"
269 id="js-properties-panel"
272 isReadOnly={this.props.isReadOnly}
273 onClean={this.loadNewDiagram}
274 onDownload={this.exportDiagram}
275 onUpload={this.uploadDiagram}
283 export default CompositionView;