From b0a7e9384564f54d441e7da6503e4b4fec7f3d5b Mon Sep 17 00:00:00 2001 From: Stanislav Vishnevetskiy Date: Thu, 2 Aug 2018 13:36:01 +0300 Subject: composition save & load Issue-ID: SDC-1591 Change-Id: I5b347a3ca1737b95045097051da462ac4fb2d781 Signed-off-by: Stanislav Vishnevetskiy --- workflow-designer-ui/src/main/frontend/.gitignore | 2 +- workflow-designer-ui/src/main/frontend/index.html | 4 +- .../src/main/frontend/package.json | 1 + .../resources/scss/components/_notifications.scss | 1 + .../resources/scss/features/_composition.scss | 41 ++++- .../src/main/frontend/resources/scss/style.scss | 2 + .../features/version/composition/Composition.js | 47 ++++++ .../version/composition/CompositionView.js | 141 +++++++++++++---- .../composition/components/CompositionButton.js | 33 ++++ .../components/CompositionButtonsPanel.js | 52 ++++++ .../version/composition/compositionActions.js | 21 +++ .../version/composition/compositionConstants.js | 16 ++ .../version/composition/compositionReducer.js | 27 ++++ .../version/composition/compositionSelectors.js | 17 ++ .../custom/CustomContextPadProvider.js | 43 +++++ .../custom-modeler/custom/CustomElementFactory.js | 101 ++++++++++++ .../custom-modeler/custom/CustomPalette.js | 151 ++++++++++++++++++ .../custom-modeler/custom/CustomRenderer.js | 176 +++++++++++++++++++++ .../custom-modeler/custom/CustomRules.js | 136 ++++++++++++++++ .../custom-modeler/custom/CustomUpdater.js | 136 ++++++++++++++++ .../composition/custom-modeler/custom/index.js | 22 +++ .../version/composition/custom-modeler/index.js | 99 ++++++++++++ .../frontend/src/features/version/versionApi.js | 15 ++ .../versionControllerSelectors.js | 5 +- .../frontend/src/features/version/versionSaga.js | 29 +++- .../overview/__tests__/overviewReducer-test.js | 4 +- .../features/workflow/overview/overviewReducer.js | 2 +- .../src/main/frontend/src/i18n/languages.json | 3 + .../src/main/frontend/src/rootReducers.js | 5 +- .../src/main/frontend/src/routes.js | 4 +- .../src/main/frontend/webpack.config.js | 9 +- workflow-designer-ui/src/main/frontend/yarn.lock | 4 + 32 files changed, 1294 insertions(+), 55 deletions(-) create mode 100644 workflow-designer-ui/src/main/frontend/src/features/version/composition/Composition.js create mode 100644 workflow-designer-ui/src/main/frontend/src/features/version/composition/components/CompositionButton.js create mode 100644 workflow-designer-ui/src/main/frontend/src/features/version/composition/components/CompositionButtonsPanel.js create mode 100644 workflow-designer-ui/src/main/frontend/src/features/version/composition/compositionActions.js create mode 100644 workflow-designer-ui/src/main/frontend/src/features/version/composition/compositionConstants.js create mode 100644 workflow-designer-ui/src/main/frontend/src/features/version/composition/compositionReducer.js create mode 100644 workflow-designer-ui/src/main/frontend/src/features/version/composition/compositionSelectors.js create mode 100644 workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-modeler/custom/CustomContextPadProvider.js create mode 100644 workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-modeler/custom/CustomElementFactory.js create mode 100644 workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-modeler/custom/CustomPalette.js create mode 100644 workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-modeler/custom/CustomRenderer.js create mode 100644 workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-modeler/custom/CustomRules.js create mode 100644 workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-modeler/custom/CustomUpdater.js create mode 100644 workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-modeler/custom/index.js create mode 100644 workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-modeler/index.js (limited to 'workflow-designer-ui') diff --git a/workflow-designer-ui/src/main/frontend/.gitignore b/workflow-designer-ui/src/main/frontend/.gitignore index d2d69816..3491fa20 100644 --- a/workflow-designer-ui/src/main/frontend/.gitignore +++ b/workflow-designer-ui/src/main/frontend/.gitignore @@ -4,7 +4,7 @@ /node_modules /node-install .idea/ - +.vscode/ # testing /coverage diff --git a/workflow-designer-ui/src/main/frontend/index.html b/workflow-designer-ui/src/main/frontend/index.html index c3a6f3b6..09d2d0a9 100644 --- a/workflow-designer-ui/src/main/frontend/index.html +++ b/workflow-designer-ui/src/main/frontend/index.html @@ -2,9 +2,7 @@ - - - + SDC Workflow App diff --git a/workflow-designer-ui/src/main/frontend/package.json b/workflow-designer-ui/src/main/frontend/package.json index 529d9096..bd224ba2 100644 --- a/workflow-designer-ui/src/main/frontend/package.json +++ b/workflow-designer-ui/src/main/frontend/package.json @@ -23,6 +23,7 @@ "dateformat": "^3.0.3", "enzyme": "^3.3.0", "enzyme-adapter-react-16": "^1.1.1", + "file-saver": "^1.3.8", "http-proxy-middleware": "^0.17.4", "lodash": "^3.0.1", "md5": "^2.2.1", diff --git a/workflow-designer-ui/src/main/frontend/resources/scss/components/_notifications.scss b/workflow-designer-ui/src/main/frontend/resources/scss/components/_notifications.scss index cc44cfbc..855c372e 100644 --- a/workflow-designer-ui/src/main/frontend/resources/scss/components/_notifications.scss +++ b/workflow-designer-ui/src/main/frontend/resources/scss/components/_notifications.scss @@ -1,5 +1,6 @@ .workflow-notifications-container { position: absolute; + z-index: 99999; &.position-top-right { right: 30px; top: 50px; diff --git a/workflow-designer-ui/src/main/frontend/resources/scss/features/_composition.scss b/workflow-designer-ui/src/main/frontend/resources/scss/features/_composition.scss index 7ab294a2..a159a4b7 100644 --- a/workflow-designer-ui/src/main/frontend/resources/scss/features/_composition.scss +++ b/workflow-designer-ui/src/main/frontend/resources/scss/features/_composition.scss @@ -4,12 +4,43 @@ .bpmn-container { flex-basis: 100%; - height: 100%; + flex-grow: 1 } - - .properties-panel { - &, .bpp-properties-panel { - height: 100%; + .bpmn-sidebar { + height: 100%; + .properties-panel { + &, .bpp-properties-panel { + height: 100%; + } + } + .composition-buttons { + position: fixed; + background-color: #fafafa; + left: 265px; + bottom: 46px; + border: 1px solid lightgray; + width: 189px; + display: flex; + flex-direction: row; + justify-content: space-around; + height: 57px; + align-items: center; + padding: 10px; + .divider { + height: 35px; + border: 1px solid $silver; + } + .diagram-btn { + + &:hover { + fill: $blue; + cursor: pointer; + } + .svg-icon { + width: 25px; + height: 23px; + } + } } } } diff --git a/workflow-designer-ui/src/main/frontend/resources/scss/style.scss b/workflow-designer-ui/src/main/frontend/resources/scss/style.scss index 95828ae2..49278565 100644 --- a/workflow-designer-ui/src/main/frontend/resources/scss/style.scss +++ b/workflow-designer-ui/src/main/frontend/resources/scss/style.scss @@ -1,3 +1,5 @@ +@import '../../node_modules/bpmn-js/dist/assets/diagram-js.css'; +@import '../../node_modules/bpmn-js/dist/assets/bpmn-font/css/bpmn.css'; @import 'common'; @import '../../node_modules/sdc-ui/lib/css/style.css'; @import 'components'; diff --git a/workflow-designer-ui/src/main/frontend/src/features/version/composition/Composition.js b/workflow-designer-ui/src/main/frontend/src/features/version/composition/Composition.js new file mode 100644 index 00000000..d2c273c4 --- /dev/null +++ b/workflow-designer-ui/src/main/frontend/src/features/version/composition/Composition.js @@ -0,0 +1,47 @@ +/* +* Copyright © 2018 European Support Limited +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ +import { connect } from 'react-redux'; +import { I18n } from 'react-redux-i18n'; +import { updateComposition } from './compositionActions'; +import CompositionView from './CompositionView'; +import { showErrorModalAction } from '../../../shared/modal/modalWrapperActions'; +import { getComposition } from './compositionSelectors'; +import { getWorkflowName } from '../../workflow/workflowSelectors'; + +function mapStateToProps(state) { + return { + composition: getComposition(state), + name: getWorkflowName(state) + }; +} + +function mapDispatchToProps(dispatch) { + return { + compositionUpdate: composition => + dispatch(updateComposition(composition)), + showErrorModal: msg => + dispatch( + showErrorModalAction({ + title: I18n.t('workflow.composition.bpmnError'), + body: msg, + withButtons: true, + closeButtonText: 'Ok' + }) + ) + }; +} + +export default connect(mapStateToProps, mapDispatchToProps)(CompositionView); diff --git a/workflow-designer-ui/src/main/frontend/src/features/version/composition/CompositionView.js b/workflow-designer-ui/src/main/frontend/src/features/version/composition/CompositionView.js index ba0351b6..d549456f 100644 --- a/workflow-designer-ui/src/main/frontend/src/features/version/composition/CompositionView.js +++ b/workflow-designer-ui/src/main/frontend/src/features/version/composition/CompositionView.js @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 +*http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -14,78 +14,151 @@ * limitations under the License. */ import React, { Component } from 'react'; -import { connect } from 'react-redux'; - -import BpmnModeler from 'bpmn-js/lib/Modeler'; -// import propertiesPanelModule from 'bpmn-js-properties-panel'; +import fileSaver from 'file-saver'; +import CustomModeler from './custom-modeler'; +import propertiesPanelModule from 'bpmn-js-properties-panel'; import propertiesProviderModule from 'bpmn-js-properties-panel/lib/provider/camunda'; -import camundaModdleDescriptor from 'camunda-bpmn-moddle/resources/camunda'; - +import camundaModuleDescriptor from 'camunda-bpmn-moddle/resources/camunda'; import newDiagramXML from './newDiagram.bpmn'; +import PropTypes from 'prop-types'; +import CompositionButtons from './components/CompositionButtonsPanel'; class CompositionView extends Component { + static propTypes = { + compositionUpdate: PropTypes.func, + showErrorModal: PropTypes.func, + composition: PropTypes.string, + name: PropTypes.string + }; constructor() { super(); this.generatedId = 'bpmn-container' + Date.now(); + this.fileInput = React.createRef(); + this.state = { + diagram: false + }; } componentDidMount() { - this.modeler = new BpmnModeler({ + const { composition } = this.props; + + this.modeler = new CustomModeler({ propertiesPanel: { - parent: '#js-properties-navigationSideBar' + parent: '#js-properties-panel' }, additionalModules: [ - //TODO:: need to fix - // propertiesPanelModule, + propertiesPanelModule, propertiesProviderModule ], moddleExtensions: { - camunda: camundaModdleDescriptor + camunda: camundaModuleDescriptor } }); window.modeler = this.modeler; this.modeler.attachTo('#' + this.generatedId); - // let diagramXML = - // '\r\n\r\n\r\n \r\n \r\n\r\n\r\n \r\n \r\n \r\n sid-D7F237E8-56D0-4283-A3CE-4F0EFE446138\r\n sid-52EB1772-F36E-433E-8F5B-D5DFD26E6F26\r\n SCAN_OK\r\n sid-E49425CF-8287-4798-B622-D2A7D78EF00B\r\n sid-E433566C-2289-4BEB-A19C-1697048900D2\r\n sid-5134932A-1863-4FFA-BB3C-A4B4078B11A9\r\n \r\n \r\n \r\n sid-7B791A11-2F2E-4D80-AFB3-91A02CF2B4FD\r\n \r\n \r\n sid-4DC479E5-5C20-4948-BCFC-9EC5E2F66D8D\r\n sid-EE8A7BA0-5D66-4F8B-80E3-CC2751B3856A\r\n \r\n \r\n sid-EE8A7BA0-5D66-4F8B-80E3-CC2751B3856A\r\n sid-8B820AF5-DC5C-4618-B854-E08B71FB55CB\r\n sid-337A23B9-A923-4CCE-B613-3E247B773CCE\r\n \r\n \r\n sid-8B820AF5-DC5C-4618-B854-E08B71FB55CB\r\n sid-57EB1F24-BD94-479A-BF1F-57F1EAA19C6C\r\n \r\n \r\n sid-57EB1F24-BD94-479A-BF1F-57F1EAA19C6C\r\n \r\n \r\n sid-7B791A11-2F2E-4D80-AFB3-91A02CF2B4FD\r\n sid-337A23B9-A923-4CCE-B613-3E247B773CCE\r\n sid-4DC479E5-5C20-4948-BCFC-9EC5E2F66D8D\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n\r\n\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n\r\n\r\n\r\n'; - this.importXML(newDiagramXML); + this.setDiagram(composition ? composition : newDiagramXML); + var eventBus = this.modeler.get('eventBus'); + eventBus.on('element.out', () => { + this.exportDiagramToStore(); + }); } - importXML(xml) { + setDiagram = diagram => { + this.setState( + { + diagram + }, + this.importXML + ); + }; + + importXML = () => { + const { diagram } = this.state; let modeler = this.modeler; - this.modeler.importXML(xml, function(err) { + this.modeler.importXML(diagram, err => { if (err) { - return console.error('could not import BPMN file'); + //TDOD add i18n + return this.props.showErrorModal('could not import diagram'); } let canvas = modeler.get('canvas'); canvas.zoom('fit-viewport'); }); - } + }; - exportDiagram() { + exportDiagramToStore = () => { this.modeler.saveXML({ format: true }, (err, xml) => { if (err) { - return console.error('could not save diagram'); + //TODO add i18n + return this.props.showErrorModal('could not save diagram'); } - console.log('Exported diagram: ', xml); + return this.props.compositionUpdate(xml); }); - } + }; + + exportDiagram = () => { + const { name, showErrorModal } = this.props; + this.modeler.saveXML({ format: true }, (err, xml) => { + if (err) { + //TODO add i18n + return showErrorModal('could not save diagram'); + } + const blob = new Blob([xml], { type: 'text/html;charset=utf-8' }); + fileSaver.saveAs(blob, `${name}-diagram.bpmn`); + }); + }; + + loadNewDiagram = () => { + this.setDiagram(newDiagramXML); + }; + + uploadDiagram = () => { + this.fileInput.current.click(); + }; + + handleFileInputChange = filesList => { + const file = filesList[0]; + const reader = new FileReader(); + reader.onloadend = event => { + var xml = event.target.result; + this.setDiagram(xml); + this.fileInput.value = ''; + }; + reader.readAsText(file); + }; render() { return (
-
-
+ this.handleFileInputChange(e.target.files)} + id="file-input" + accept=".bpmn, .xml" + type="file" + name="file-input" + style={{ display: 'none' }} + /> +
{ + this.exportDiagramToStore(); + }} + className="bpmn-container" + id={this.generatedId} + /> +
+
+ +
); } } -function mapStateToProps() { - return {}; -} - -function mapDispatchToProps() { - return {}; -} - -export default connect(mapStateToProps, mapDispatchToProps)(CompositionView); +export default CompositionView; diff --git a/workflow-designer-ui/src/main/frontend/src/features/version/composition/components/CompositionButton.js b/workflow-designer-ui/src/main/frontend/src/features/version/composition/components/CompositionButton.js new file mode 100644 index 00000000..2fc6618c --- /dev/null +++ b/workflow-designer-ui/src/main/frontend/src/features/version/composition/components/CompositionButton.js @@ -0,0 +1,33 @@ +/* +* Copyright © 2018 European Support Limited +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ +import React from 'react'; +import PropTypes from 'prop-types'; +import SVGIcon from 'sdc-ui/lib/react/SVGIcon'; + +const CompositionButton = ({ onClick, name, title }) => ( +
+ +
+); + +CompositionButton.propTypes = { + onClick: PropTypes.func, + className: PropTypes.string, + name: PropTypes.string, + title: PropTypes.string +}; + +export default CompositionButton; diff --git a/workflow-designer-ui/src/main/frontend/src/features/version/composition/components/CompositionButtonsPanel.js b/workflow-designer-ui/src/main/frontend/src/features/version/composition/components/CompositionButtonsPanel.js new file mode 100644 index 00000000..add64902 --- /dev/null +++ b/workflow-designer-ui/src/main/frontend/src/features/version/composition/components/CompositionButtonsPanel.js @@ -0,0 +1,52 @@ +/* +* Copyright © 2018 European Support Limited +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ +import React from 'react'; +import PropTypes from 'prop-types'; +import CompositionButton from './CompositionButton'; + +const Divider = () =>
; + +const CompositionButtons = ({ onClean, onUpload, onDownload }) => ( +
+ + + + + +
+); + +CompositionButtons.propTypes = { + onClean: PropTypes.func, + onUpload: PropTypes.func, + onDownload: PropTypes.func +}; +export default CompositionButtons; diff --git a/workflow-designer-ui/src/main/frontend/src/features/version/composition/compositionActions.js b/workflow-designer-ui/src/main/frontend/src/features/version/composition/compositionActions.js new file mode 100644 index 00000000..3f1755dd --- /dev/null +++ b/workflow-designer-ui/src/main/frontend/src/features/version/composition/compositionActions.js @@ -0,0 +1,21 @@ +/* +* Copyright © 2018 European Support Limited +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +*http://www.apache.org/licenses/LICENSE-2.0 +* + * Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ +import { SET_COMPOSITION } from './compositionConstants'; + +export const updateComposition = payload => ({ + type: SET_COMPOSITION, + payload +}); diff --git a/workflow-designer-ui/src/main/frontend/src/features/version/composition/compositionConstants.js b/workflow-designer-ui/src/main/frontend/src/features/version/composition/compositionConstants.js new file mode 100644 index 00000000..74cab0cb --- /dev/null +++ b/workflow-designer-ui/src/main/frontend/src/features/version/composition/compositionConstants.js @@ -0,0 +1,16 @@ +/* +* Copyright © 2018 European Support Limited +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +*http://www.apache.org/licenses/LICENSE-2.0 +* + * Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ +export const SET_COMPOSITION = 'composition/SET_COMPOSITION'; diff --git a/workflow-designer-ui/src/main/frontend/src/features/version/composition/compositionReducer.js b/workflow-designer-ui/src/main/frontend/src/features/version/composition/compositionReducer.js new file mode 100644 index 00000000..9c707362 --- /dev/null +++ b/workflow-designer-ui/src/main/frontend/src/features/version/composition/compositionReducer.js @@ -0,0 +1,27 @@ +/* +* Copyright © 2018 European Support Limited +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +*http://www.apache.org/licenses/LICENSE-2.0 +* + * Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ +import { SET_COMPOSITION } from './compositionConstants'; + +export default (state = {}, action) => { + switch (action.type) { + case SET_COMPOSITION: + return { + diagram: action.payload + }; + default: + return state; + } +}; diff --git a/workflow-designer-ui/src/main/frontend/src/features/version/composition/compositionSelectors.js b/workflow-designer-ui/src/main/frontend/src/features/version/composition/compositionSelectors.js new file mode 100644 index 00000000..7e28ca64 --- /dev/null +++ b/workflow-designer-ui/src/main/frontend/src/features/version/composition/compositionSelectors.js @@ -0,0 +1,17 @@ +/* +* Copyright © 2018 European Support Limited +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +*http://www.apache.org/licenses/LICENSE-2.0 +* + * Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ +export const getComposition = state => + state && state.currentVersion && state.currentVersion.composition.diagram; diff --git a/workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-modeler/custom/CustomContextPadProvider.js b/workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-modeler/custom/CustomContextPadProvider.js new file mode 100644 index 00000000..0f2ba528 --- /dev/null +++ b/workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-modeler/custom/CustomContextPadProvider.js @@ -0,0 +1,43 @@ +import inherits from 'inherits'; + +import ContextPadProvider from 'bpmn-js/lib/features/context-pad/ContextPadProvider'; + +import { isAny } from 'bpmn-js/lib/features/modeling/util/ModelingUtil'; + +import { assign, bind } from 'min-dash'; + +export default function CustomContextPadProvider(injector, connect, translate) { + injector.invoke(ContextPadProvider, this); + + var cached = bind(this.getContextPadEntries, this); + + this.getContextPadEntries = function(element) { + var actions = cached(element); + + var businessObject = element.businessObject; + + function startConnect(event, element, autoActivate) { + connect.start(event, element, autoActivate); + } + + if (isAny(businessObject, ['custom:triangle', 'custom:circle'])) { + assign(actions, { + connect: { + group: 'connect', + className: 'bpmn-icon-connection-multi', + title: translate('Connect using custom connection'), + action: { + click: startConnect, + dragstart: startConnect + } + } + }); + } + + return actions; + }; +} + +inherits(CustomContextPadProvider, ContextPadProvider); + +CustomContextPadProvider.$inject = ['injector', 'connect', 'translate']; diff --git a/workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-modeler/custom/CustomElementFactory.js b/workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-modeler/custom/CustomElementFactory.js new file mode 100644 index 00000000..01d4d278 --- /dev/null +++ b/workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-modeler/custom/CustomElementFactory.js @@ -0,0 +1,101 @@ +import { assign } from 'min-dash'; + +import inherits from 'inherits'; + +import BpmnElementFactory from 'bpmn-js/lib/features/modeling/ElementFactory'; +import { DEFAULT_LABEL_SIZE } from 'bpmn-js/lib/util/LabelUtil'; + +/** + * A custom factory that knows how to create BPMN _and_ custom elements. + */ +export default function CustomElementFactory(bpmnFactory, moddle) { + BpmnElementFactory.call(this, bpmnFactory, moddle); + + var self = this; + + /** + * Create a diagram-js element with the given type (any of shape, connection, label). + * + * @param {String} elementType + * @param {Object} attrs + * + * @return {djs.model.Base} + */ + this.create = function(elementType, attrs) { + var type = attrs.type; + + if (elementType === 'label') { + return self.baseCreate( + elementType, + assign({ type: 'label' }, DEFAULT_LABEL_SIZE, attrs) + ); + } + + // add type to businessObject if custom + if (/^custom:/.test(type)) { + if (!attrs.businessObject) { + attrs.businessObject = { + type: type + }; + + if (attrs.id) { + assign(attrs.businessObject, { + id: attrs.id + }); + } + } + + // add width and height if shape + if (!/:connection$/.test(type)) { + assign(attrs, self._getCustomElementSize(type)); + } + + if (!('$instanceOf' in attrs.businessObject)) { + // ensure we can use ModelUtil#is for type checks + Object.defineProperty(attrs.businessObject, '$instanceOf', { + value: function(type) { + return this.type === type; + } + }); + } + + return self.baseCreate(elementType, attrs); + } + + return self.createBpmnElement(elementType, attrs); + }; +} + +inherits(CustomElementFactory, BpmnElementFactory); + +CustomElementFactory.$inject = ['bpmnFactory', 'moddle']; + +/** + * Returns the default size of custom shapes. + * + * The following example shows an interface on how + * to setup the custom shapes's dimensions. + * + * @example + * + * var shapes = { + * triangle: { width: 40, height: 40 }, + * rectangle: { width: 100, height: 20 } + * }; + * + * return shapes[type]; + * + * + * @param {String} type + * + * @return {Dimensions} a {width, height} object representing the size of the element + */ +CustomElementFactory.prototype._getCustomElementSize = function(type) { + var shapes = { + __default: { width: 100, height: 80 }, + 'custom:triangle': { width: 40, height: 40 }, + 'custom:circle': { width: 140, height: 140 } + }; + + return shapes[type] || shapes.__default; +}; diff --git a/workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-modeler/custom/CustomPalette.js b/workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-modeler/custom/CustomPalette.js new file mode 100644 index 00000000..a8adb2fd --- /dev/null +++ b/workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-modeler/custom/CustomPalette.js @@ -0,0 +1,151 @@ +import { assign } from 'min-dash'; + +/** + * A palette that allows you to create BPMN _and_ custom elements. + */ +export default function PaletteProvider( + palette, + create, + elementFactory, + spaceTool, + lassoTool, + handTool, + globalConnect, + translate +) { + this._create = create; + this._elementFactory = elementFactory; + this._spaceTool = spaceTool; + this._lassoTool = lassoTool; + this._handTool = handTool; + this._globalConnect = globalConnect; + this._translate = translate; + + palette.registerProvider(this); +} + +PaletteProvider.$inject = [ + 'palette', + 'create', + 'elementFactory', + 'spaceTool', + 'lassoTool', + 'handTool', + 'globalConnect', + 'translate' +]; + +PaletteProvider.prototype.getPaletteEntries = function() { + var actions = {}, + create = this._create, + elementFactory = this._elementFactory, + spaceTool = this._spaceTool, + lassoTool = this._lassoTool, + handTool = this._handTool, + globalConnect = this._globalConnect, + translate = this._translate; + + function createAction(type, group, className, title, options) { + function createListener(event) { + var shape = elementFactory.createShape( + assign({ type: type }, options) + ); + + if (options) { + shape.businessObject.di.isExpanded = options.isExpanded; + } + + create.start(event, shape); + } + + var shortType = type.replace(/^bpmn:/, ''); + + return { + group: group, + className: className, + title: title || 'Create ' + shortType, + action: { + dragstart: createListener, + click: createListener + } + }; + } + + assign(actions, { + 'hand-tool': { + group: 'tools', + className: 'bpmn-icon-hand-tool', + title: translate('Activate the hand tool'), + action: { + click: function(event) { + handTool.activateHand(event); + } + } + }, + 'lasso-tool': { + group: 'tools', + className: 'bpmn-icon-lasso-tool', + title: translate('Activate the lasso tool'), + action: { + click: function(event) { + lassoTool.activateSelection(event); + } + } + }, + 'space-tool': { + group: 'tools', + className: 'bpmn-icon-space-tool', + title: translate('Activate the create/remove space tool'), + action: { + click: function(event) { + spaceTool.activateSelection(event); + } + } + }, + 'global-connect-tool': { + group: 'tools', + className: 'bpmn-icon-connection-multi', + title: translate('Activate the global connect tool'), + action: { + click: function(event) { + globalConnect.toggle(event); + } + } + }, + 'tool-separator': { + group: 'tools', + separator: true + }, + 'create.start-event': createAction( + 'bpmn:StartEvent', + 'event', + 'bpmn-icon-start-event-none' + ), + 'create.intermediate-event': createAction( + 'bpmn:IntermediateThrowEvent', + 'event', + 'bpmn-icon-intermediate-event-none', + translate('Create Intermediate/Boundary Event') + ), + 'create.end-event': createAction( + 'bpmn:EndEvent', + 'event', + 'bpmn-icon-end-event-none' + ), + 'create.exclusive-gateway': createAction( + 'bpmn:ExclusiveGateway', + 'gateway', + 'bpmn-icon-gateway-none', + translate('Create Gateway') + ), + 'create.task': createAction('bpmn:Task', 'activity', 'bpmn-icon-task'), + 'create.subprocess-expanded': createAction( + 'bpmn:SubProcess', + 'activity', + 'bpmn-icon-subprocess-expanded', + translate('Create expanded SubProcess'), + { isExpanded: true } + ) + }); + return actions; +}; diff --git a/workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-modeler/custom/CustomRenderer.js b/workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-modeler/custom/CustomRenderer.js new file mode 100644 index 00000000..f397fed9 --- /dev/null +++ b/workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-modeler/custom/CustomRenderer.js @@ -0,0 +1,176 @@ +import inherits from 'inherits'; + +import BaseRenderer from 'diagram-js/lib/draw/BaseRenderer'; + +import { componentsToPath, createLine } from 'diagram-js/lib/util/RenderUtil'; + +import { + append as svgAppend, + attr as svgAttr, + create as svgCreate +} from 'tiny-svg'; + +/** + * A renderer that knows how to render custom elements. + */ +export default function CustomRenderer(eventBus, styles) { + BaseRenderer.call(this, eventBus, 2000); + + var computeStyle = styles.computeStyle; + + this.drawTriangle = function(p, side) { + var halfSide = side / 2, + points, + attrs; + + points = [halfSide, 0, side, side, 0, side]; + + attrs = computeStyle(attrs, { + stroke: '#3CAA82', + strokeWidth: 2, + fill: '#3CAA82' + }); + + var polygon = svgCreate('polygon'); + + svgAttr(polygon, { + points: points + }); + + svgAttr(polygon, attrs); + + svgAppend(p, polygon); + + return polygon; + }; + + this.getTrianglePath = function(element) { + var x = element.x, + y = element.y, + width = element.width, + height = element.height; + + var trianglePath = [ + ['M', x + width / 2, y], + ['l', width / 2, height], + ['l', -width, 0], + ['z'] + ]; + + return componentsToPath(trianglePath); + }; + + this.drawCircle = function(p, width, height) { + var cx = width / 2, + cy = height / 2; + + var attrs = computeStyle(attrs, { + stroke: '#4488aa', + strokeWidth: 4, + fill: 'white' + }); + + var circle = svgCreate('circle'); + + svgAttr(circle, { + cx: cx, + cy: cy, + r: Math.round((width + height) / 4) + }); + + svgAttr(circle, attrs); + + svgAppend(p, circle); + + return circle; + }; + + this.getCirclePath = function(shape) { + var cx = shape.x + shape.width / 2, + cy = shape.y + shape.height / 2, + radius = shape.width / 2; + + var circlePath = [ + ['M', cx, cy], + ['m', 0, -radius], + ['a', radius, radius, 0, 1, 1, 0, 2 * radius], + ['a', radius, radius, 0, 1, 1, 0, -2 * radius], + ['z'] + ]; + + return componentsToPath(circlePath); + }; + + this.drawCustomConnection = function(p, element) { + var attrs = computeStyle(attrs, { + stroke: '#ff471a', + strokeWidth: 2 + }); + + return svgAppend(p, createLine(element.waypoints, attrs)); + }; + + this.getCustomConnectionPath = function(connection) { + var waypoints = connection.waypoints.map(function(p) { + return p.original || p; + }); + + var connectionPath = [['M', waypoints[0].x, waypoints[0].y]]; + + waypoints.forEach(function(waypoint, index) { + if (index !== 0) { + connectionPath.push(['L', waypoint.x, waypoint.y]); + } + }); + + return componentsToPath(connectionPath); + }; +} + +inherits(CustomRenderer, BaseRenderer); + +CustomRenderer.$inject = ['eventBus', 'styles']; + +CustomRenderer.prototype.canRender = function(element) { + return /^custom:/.test(element.type); +}; + +CustomRenderer.prototype.drawShape = function(p, element) { + var type = element.type; + + if (type === 'custom:triangle') { + return this.drawTriangle(p, element.width); + } + + if (type === 'custom:circle') { + return this.drawCircle(p, element.width, element.height); + } +}; + +CustomRenderer.prototype.getShapePath = function(shape) { + var type = shape.type; + + if (type === 'custom:triangle') { + return this.getTrianglePath(shape); + } + + if (type === 'custom:circle') { + return this.getCirclePath(shape); + } +}; + +CustomRenderer.prototype.drawConnection = function(p, element) { + var type = element.type; + + if (type === 'custom:connection') { + return this.drawCustomConnection(p, element); + } +}; + +CustomRenderer.prototype.getConnectionPath = function(connection) { + var type = connection.type; + + if (type === 'custom:connection') { + return this.getCustomConnectionPath(connection); + } +}; diff --git a/workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-modeler/custom/CustomRules.js b/workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-modeler/custom/CustomRules.js new file mode 100644 index 00000000..1dce143d --- /dev/null +++ b/workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-modeler/custom/CustomRules.js @@ -0,0 +1,136 @@ +import { reduce } from 'min-dash'; + +import inherits from 'inherits'; + +import { is } from 'bpmn-js/lib/util/ModelUtil'; + +import RuleProvider from 'diagram-js/lib/features/rules/RuleProvider'; + +var HIGH_PRIORITY = 1500; + +function isCustom(element) { + return element && /^custom:/.test(element.type); +} + +/** + * Specific rules for custom elements + */ +export default function CustomRules(eventBus) { + RuleProvider.call(this, eventBus); +} + +inherits(CustomRules, RuleProvider); + +CustomRules.$inject = ['eventBus']; + +CustomRules.prototype.init = function() { + /** + * Can shape be created on target container? + */ + function canCreate(shape, target) { + // only judge about custom elements + if (!isCustom(shape)) { + return; + } + + // allow creation on processes + return ( + is(target, 'bpmn:Process') || + is(target, 'bpmn:Participant') || + is(target, 'bpmn:Collaboration') + ); + } + + /** + * Can source and target be connected? + */ + function canConnect(source, target) { + // only judge about custom elements + if (!isCustom(source) && !isCustom(target)) { + return; + } + + // allow connection between custom shape and task + if (isCustom(source)) { + if (is(target, 'bpmn:Task')) { + return { type: 'custom:connection' }; + } else { + return false; + } + } else if (isCustom(target)) { + if (is(source, 'bpmn:Task')) { + return { type: 'custom:connection' }; + } else { + return false; + } + } + } + + this.addRule('elements.move', HIGH_PRIORITY, function(context) { + var target = context.target, + shapes = context.shapes; + + var type; + + // do not allow mixed movements of custom / BPMN shapes + // if any shape cannot be moved, the group cannot be moved, too + var allowed = reduce( + shapes, + function(result, s) { + if (type === undefined) { + type = isCustom(s); + } + + if (type !== isCustom(s) || result === false) { + return false; + } + + return canCreate(s, target); + }, + undefined + ); + + // reject, if we have at least one + // custom element that cannot be moved + return allowed; + }); + + this.addRule('shape.create', HIGH_PRIORITY, function(context) { + var target = context.target, + shape = context.shape; + + return canCreate(shape, target); + }); + + this.addRule('shape.resize', HIGH_PRIORITY, function(context) { + var shape = context.shape; + + if (isCustom(shape)) { + // cannot resize custom elements + return false; + } + }); + + this.addRule('connection.create', HIGH_PRIORITY, function(context) { + var source = context.source, + target = context.target; + + return canConnect(source, target); + }); + + this.addRule('connection.reconnectStart', HIGH_PRIORITY, function(context) { + var connection = context.connection, + source = context.hover || context.source, + target = connection.target; + + return canConnect(source, target, connection); + }); + + this.addRule('connection.reconnectEnd', HIGH_PRIORITY, function(context) { + var connection = context.connection, + source = connection.source, + target = context.hover || context.target; + + return canConnect(source, target, connection); + }); +}; diff --git a/workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-modeler/custom/CustomUpdater.js b/workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-modeler/custom/CustomUpdater.js new file mode 100644 index 00000000..532c24f3 --- /dev/null +++ b/workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-modeler/custom/CustomUpdater.js @@ -0,0 +1,136 @@ +import inherits from 'inherits'; + +import { pick, assign } from 'min-dash'; + +import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor'; + +import { + add as collectionAdd, + remove as collectionRemove +} from 'diagram-js/lib/util/Collections'; + +/** + * A handler responsible for updating the custom element's businessObject + * once changes on the diagram happen. + */ +export default function CustomUpdater(eventBus, bpmnjs) { + CommandInterceptor.call(this, eventBus); + + function updateCustomElement(e) { + var context = e.context, + shape = context.shape, + businessObject = shape.businessObject; + + if (!isCustom(shape)) { + return; + } + + var parent = shape.parent; + + var customElements = bpmnjs._customElements; + + // make sure element is added / removed from bpmnjs.customElements + if (!parent) { + collectionRemove(customElements, businessObject); + } else { + collectionAdd(customElements, businessObject); + } + + // save custom element position + assign(businessObject, pick(shape, ['x', 'y'])); + } + + function updateCustomConnection(e) { + var context = e.context, + connection = context.connection, + source = connection.source, + target = connection.target, + businessObject = connection.businessObject; + + var parent = connection.parent; + + var customElements = bpmnjs._customElements; + + // make sure element is added / removed from bpmnjs.customElements + if (!parent) { + collectionRemove(customElements, businessObject); + } else { + collectionAdd(customElements, businessObject); + } + + // update waypoints + assign(businessObject, { + waypoints: copyWaypoints(connection) + }); + + if (source && target) { + assign(businessObject, { + source: source.id, + target: target.id + }); + } + } + + this.executed( + ['shape.create', 'shape.move', 'shape.delete'], + ifCustomElement(updateCustomElement) + ); + + this.reverted( + ['shape.create', 'shape.move', 'shape.delete'], + ifCustomElement(updateCustomElement) + ); + + this.executed( + [ + 'connection.create', + 'connection.reconnectStart', + 'connection.reconnectEnd', + 'connection.updateWaypoints', + 'connection.delete', + 'connection.layout', + 'connection.move' + ], + ifCustomElement(updateCustomConnection) + ); + + this.reverted( + [ + 'connection.create', + 'connection.reconnectStart', + 'connection.reconnectEnd', + 'connection.updateWaypoints', + 'connection.delete', + 'connection.layout', + 'connection.move' + ], + ifCustomElement(updateCustomConnection) + ); +} + +inherits(CustomUpdater, CommandInterceptor); + +CustomUpdater.$inject = ['eventBus', 'bpmnjs']; + +/////// helpers /////////////////////////////////// + +function copyWaypoints(connection) { + return connection.waypoints.map(function(p) { + return { x: p.x, y: p.y }; + }); +} + +function isCustom(element) { + return element && /custom:/.test(element.type); +} + +function ifCustomElement(fn) { + return function(event) { + var context = event.context, + element = context.shape || context.connection; + + if (isCustom(element)) { + fn(event); + } + }; +} diff --git a/workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-modeler/custom/index.js b/workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-modeler/custom/index.js new file mode 100644 index 00000000..f1085390 --- /dev/null +++ b/workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-modeler/custom/index.js @@ -0,0 +1,22 @@ +import CustomElementFactory from './CustomElementFactory'; +import CustomRenderer from './CustomRenderer'; +import CustomPalette from './CustomPalette'; +import CustomRules from './CustomRules'; +import CustomUpdater from './CustomUpdater'; +import CustomContextPadProvider from './CustomContextPadProvider'; + +export default { + __init__: [ + 'customRenderer', + 'paletteProvider', + 'customRules', + 'customUpdater', + 'contextPadProvider' + ], + elementFactory: ['type', CustomElementFactory], + customRenderer: ['type', CustomRenderer], + paletteProvider: ['type', CustomPalette], + customRules: ['type', CustomRules], + customUpdater: ['type', CustomUpdater], + contextPadProvider: ['type', CustomContextPadProvider] +}; diff --git a/workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-modeler/index.js b/workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-modeler/index.js new file mode 100644 index 00000000..86fbff6a --- /dev/null +++ b/workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-modeler/index.js @@ -0,0 +1,99 @@ +import Modeler from 'bpmn-js/lib/Modeler'; + +import { assign, isArray } from 'min-dash'; + +import inherits from 'inherits'; + +import CustomModule from './custom'; + +export default function CustomModeler(options) { + Modeler.call(this, options); + + this._customElements = []; +} + +inherits(CustomModeler, Modeler); + +CustomModeler.prototype._modules = [].concat(CustomModeler.prototype._modules, [ + CustomModule +]); + +/** + * Add a single custom element to the underlying diagram + * + * @param {Object} customElement + */ +CustomModeler.prototype._addCustomShape = function(customElement) { + this._customElements.push(customElement); + + var canvas = this.get('canvas'), + elementFactory = this.get('elementFactory'); + + var customAttrs = assign({ businessObject: customElement }, customElement); + + var customShape = elementFactory.create('shape', customAttrs); + + return canvas.addShape(customShape); +}; + +CustomModeler.prototype._addCustomConnection = function(customElement) { + this._customElements.push(customElement); + + var canvas = this.get('canvas'), + elementFactory = this.get('elementFactory'), + elementRegistry = this.get('elementRegistry'); + + var customAttrs = assign({ businessObject: customElement }, customElement); + + var connection = elementFactory.create( + 'connection', + assign(customAttrs, { + source: elementRegistry.get(customElement.source), + target: elementRegistry.get(customElement.target) + }), + elementRegistry.get(customElement.source).parent + ); + + return canvas.addConnection(connection); +}; + +/** + * Add a number of custom elements and connections to the underlying diagram. + * + * @param {Array} customElements + */ +CustomModeler.prototype.addCustomElements = function(customElements) { + if (!isArray(customElements)) { + throw new Error('argument must be an array'); + } + + var shapes = [], + connections = []; + + customElements.forEach(function(customElement) { + if (isCustomConnection(customElement)) { + connections.push(customElement); + } else { + shapes.push(customElement); + } + }); + + // add shapes before connections so that connections + // can already rely on the shapes being part of the diagram + shapes.forEach(this._addCustomShape, this); + + connections.forEach(this._addCustomConnection, this); +}; + +/** + * Get custom elements with their current status. + * + * @return {Array} custom elements on the diagram + */ +CustomModeler.prototype.getCustomElements = function() { + return this._customElements; +}; + +function isCustomConnection(element) { + return element.type === 'custom:connection'; +} diff --git a/workflow-designer-ui/src/main/frontend/src/features/version/versionApi.js b/workflow-designer-ui/src/main/frontend/src/features/version/versionApi.js index 7848b1f0..4e591c9b 100644 --- a/workflow-designer-ui/src/main/frontend/src/features/version/versionApi.js +++ b/workflow-designer-ui/src/main/frontend/src/features/version/versionApi.js @@ -41,6 +41,21 @@ const Api = { } ); }, + fetchVersionArtifact: ({ workflowId, versionId }) => { + return RestfulAPIUtil.fetch( + `${baseUrl(workflowId)}/${versionId}/artifact` + ); + }, + updateVersionArtifact: ({ workflowId, versionId, payload }) => { + let formData = new FormData(); + var blob = new Blob([payload], { type: 'text/xml' }); + formData.append('fileToUpload', blob); + + return RestfulAPIUtil.put( + `${baseUrl(workflowId)}/${versionId}/artifact`, + formData + ); + }, certifyVersion: ({ workflowId, versionId }) => { return RestfulAPIUtil.post( `${baseUrl(workflowId)}/${versionId}/state`, diff --git a/workflow-designer-ui/src/main/frontend/src/features/version/versionController/versionControllerSelectors.js b/workflow-designer-ui/src/main/frontend/src/features/version/versionController/versionControllerSelectors.js index aa2ea8d0..a3ead31f 100644 --- a/workflow-designer-ui/src/main/frontend/src/features/version/versionController/versionControllerSelectors.js +++ b/workflow-designer-ui/src/main/frontend/src/features/version/versionController/versionControllerSelectors.js @@ -20,15 +20,18 @@ import { getOutputs } from 'features/version/inputOutput/inputOutputSelectors'; import { getVersionInfo } from 'features/version/general/generalSelectors'; +import { getComposition } from 'features/version/composition/compositionSelectors'; export const getVersionsList = state => state && state.workflow.versions; export const getSavedObjParams = createSelector( getOutputs, getInputs, + getComposition, getVersionInfo, - (outputs, inputs, general) => ({ + (outputs, inputs, composition, general) => ({ outputs, inputs, + composition, ...general }) ); diff --git a/workflow-designer-ui/src/main/frontend/src/features/version/versionSaga.js b/workflow-designer-ui/src/main/frontend/src/features/version/versionSaga.js index 9ef88f9b..78b82ab1 100644 --- a/workflow-designer-ui/src/main/frontend/src/features/version/versionSaga.js +++ b/workflow-designer-ui/src/main/frontend/src/features/version/versionSaga.js @@ -32,14 +32,24 @@ import { notificationActions } from 'shared/notifications/notificationsActions'; import { versionState } from 'features/version/versionConstants'; import overviewApi from '../workflow/overview/overviewApi'; import { versionListFetchAction } from '../workflow/overview/overviewConstansts'; +import { updateComposition } from 'features/version/composition/compositionActions'; function* fetchVersion(action) { try { const data = yield call(versionApi.fetchVersion, action.payload); const { inputs, outputs, ...rest } = data; + let composition = false; + + if (rest.hasArtifact) { + composition = yield call( + versionApi.fetchVersionArtifact, + action.payload + ); + } yield all([ put(setWorkflowVersionAction(rest)), - put(setInputsOutputs({ inputs, outputs })) + put(setInputsOutputs({ inputs, outputs })), + put(updateComposition(composition)) ]); } catch (error) { yield put(genericNetworkErrorAction(error)); @@ -62,7 +72,22 @@ function* watchSubmitVersion(action) { function* watchUpdateVersion(action) { try { - yield call(versionApi.updateVersion, action.payload); + //const { composition, ...versionData } = action.payload; + const { + workflowId, + params: { composition, ...versionData } + } = action.payload; + yield call(versionApi.updateVersion, { + workflowId, + params: versionData + }); + if (composition) { + yield call(versionApi.updateVersionArtifact, { + workflowId, + versionId: versionData.id, + payload: composition + }); + } yield put( notificationActions.showSuccess({ title: 'Update Workflow Version', diff --git a/workflow-designer-ui/src/main/frontend/src/features/workflow/overview/__tests__/overviewReducer-test.js b/workflow-designer-ui/src/main/frontend/src/features/workflow/overview/__tests__/overviewReducer-test.js index 4db8f49d..87600be2 100644 --- a/workflow-designer-ui/src/main/frontend/src/features/workflow/overview/__tests__/overviewReducer-test.js +++ b/workflow-designer-ui/src/main/frontend/src/features/workflow/overview/__tests__/overviewReducer-test.js @@ -23,7 +23,7 @@ describe('Overview reducer', () => { total: 2, size: 0, page: 0, - results: [ + items: [ { id: '99adf5bc36764628b8018033d285b591', name: '1.0', @@ -91,6 +91,6 @@ describe('Overview reducer', () => { expect( overviewReducer([], versionListFetchAction(versionResponse)) - ).toEqual([...versionResponse.results]); + ).toEqual([...versionResponse.items]); }); }); diff --git a/workflow-designer-ui/src/main/frontend/src/features/workflow/overview/overviewReducer.js b/workflow-designer-ui/src/main/frontend/src/features/workflow/overview/overviewReducer.js index 720d410c..bab482a4 100644 --- a/workflow-designer-ui/src/main/frontend/src/features/workflow/overview/overviewReducer.js +++ b/workflow-designer-ui/src/main/frontend/src/features/workflow/overview/overviewReducer.js @@ -19,7 +19,7 @@ import { FETCH_VERSION_LIST } from 'features/workflow/overview/overviewConstanst export default function overviewReducer(state = [], action) { switch (action.type) { case FETCH_VERSION_LIST: - return [...action.payload.results]; + return [...action.payload.items]; default: return state; } diff --git a/workflow-designer-ui/src/main/frontend/src/i18n/languages.json b/workflow-designer-ui/src/main/frontend/src/i18n/languages.json index 986cf128..5fd444a6 100644 --- a/workflow-designer-ui/src/main/frontend/src/i18n/languages.json +++ b/workflow-designer-ui/src/main/frontend/src/i18n/languages.json @@ -59,6 +59,9 @@ "confirmDlete": "Are you sure you want to delete \"%{name}\"?", "alreadyExists": "Already exists", "invalidCharacters": "Alphanumeric and underscore only" + }, + "composition": { + "bpmnError" : "BPMN.IO Error" } }, "version": { diff --git a/workflow-designer-ui/src/main/frontend/src/rootReducers.js b/workflow-designer-ui/src/main/frontend/src/rootReducers.js index 9dbef266..9fab8140 100644 --- a/workflow-designer-ui/src/main/frontend/src/rootReducers.js +++ b/workflow-designer-ui/src/main/frontend/src/rootReducers.js @@ -25,14 +25,15 @@ import loader from 'shared/loader/LoaderReducer'; import modal from 'shared/modal/modalWrapperReducer'; import overviewReducer from 'features/workflow/overview/overviewReducer'; import workflowReducer from 'features/workflow/workflowReducer'; - +import compositionReducer from 'features/version/composition/compositionReducer'; export default combineReducers({ i18n: i18nReducer, catalog, notifications: notificationsReducer, currentVersion: combineReducers({ general: versionReducer, - inputOutput + inputOutput, + composition: compositionReducer }), workflow: combineReducers({ data: workflowReducer, diff --git a/workflow-designer-ui/src/main/frontend/src/routes.js b/workflow-designer-ui/src/main/frontend/src/routes.js index 79abe303..ea433cca 100644 --- a/workflow-designer-ui/src/main/frontend/src/routes.js +++ b/workflow-designer-ui/src/main/frontend/src/routes.js @@ -19,7 +19,7 @@ import Version from 'features/version/Version'; import GeneralView from 'features/version/general/General'; import OverviewView from 'features/workflow/overview/Overview'; import InputOutput from 'features/version/inputOutput/InputOutput'; -import CompositionView from 'features/version/composition/CompositionView'; +import Composition from 'features/version/composition/Composition'; export const routes = [ { @@ -41,7 +41,7 @@ export const routes = [ }, { path: '/composition', - component: CompositionView, + component: Composition, i18nName: 'workflow.sideBar.composition', id: 'COMPOSITION' } diff --git a/workflow-designer-ui/src/main/frontend/webpack.config.js b/workflow-designer-ui/src/main/frontend/webpack.config.js index 23f53343..fa4e5d83 100644 --- a/workflow-designer-ui/src/main/frontend/webpack.config.js +++ b/workflow-designer-ui/src/main/frontend/webpack.config.js @@ -127,8 +127,13 @@ module.exports = (env, argv) => { include: srcPath }, { - test: /\.woff|\.woff2$/, - loader: 'file-loader' + test: /\.(eot|svg|ttf|woff|woff2)(\?.*)?$/, + use: [ + { + loader: 'file-loader', + options: {} + } + ] } ] }, diff --git a/workflow-designer-ui/src/main/frontend/yarn.lock b/workflow-designer-ui/src/main/frontend/yarn.lock index 7e31f62b..dbfc8bbe 100644 --- a/workflow-designer-ui/src/main/frontend/yarn.lock +++ b/workflow-designer-ui/src/main/frontend/yarn.lock @@ -4450,6 +4450,10 @@ file-loader@^1.1.11: loader-utils "^1.0.2" schema-utils "^0.4.5" +file-saver@^1.3.8: + version "1.3.8" + resolved "https://registry.yarnpkg.com/file-saver/-/file-saver-1.3.8.tgz#e68a30c7cb044e2fb362b428469feb291c2e09d8" + filename-regex@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/filename-regex/-/filename-regex-2.0.1.tgz#c1c4b9bee3e09725ddb106b75c1e301fe2f18b26" -- cgit 1.2.3-korg