composition readonly implementation

Issue-ID: SDC-1591
Change-Id: I4a009fe28fb32d9c2407d69087ab9c17f8ac5b2d
Signed-off-by: Stanislav Vishnevetskiy <shlomo-stanisla.vishnevetskiy@amdocs.com>
diff --git a/workflow-designer-ui/src/main/frontend/package.json b/workflow-designer-ui/src/main/frontend/package.json
index 62b06cb..31d9ed0 100644
--- a/workflow-designer-ui/src/main/frontend/package.json
+++ b/workflow-designer-ui/src/main/frontend/package.json
@@ -26,6 +26,7 @@
 		"http-proxy-middleware": "^0.17.4",
 		"inherits": "^2.0.3",
 		"lodash.assign": "^4.2.0",
+		"lodash.foreach": "^4.5.0",
 		"lodash.isempty": "^4.4.0",
 		"lodash.isequal": "^4.5.0",
 		"lodash.map": "^4.6.0",
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 6554219..f442177 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
@@ -55,6 +55,9 @@
 					fill: $blue;
 					cursor: pointer;
 				}
+				&.disabled {
+					fill: $gray;
+				}
 				.svg-icon {
 					width: 25px;
 				    height: 23px;
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
index c9fc4b5..b91ceda 100644
--- 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
@@ -23,6 +23,7 @@
 import { activitiesSelector } from 'features/activities/activitiesSelectors';
 import { getInputOutputForComposition } from 'features/version/inputOutput/inputOutputSelectors';
 import { getVersionInfo } from 'features/version/general/generalSelectors';
+import { getIsCertified } from 'features/version/general/generalSelectors';
 function mapStateToProps(state) {
     return {
         composition: getComposition(state),
@@ -30,7 +31,8 @@
         versionName: getVersionInfo(state).name,
         activities: activitiesSelector(state),
         inputOutput: getInputOutputForComposition(state),
-        errors: getErrors(state)
+        errors: getErrors(state),
+        isReadOnly: getIsCertified(state)
     };
 }
 
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 eac3083..3ec0cb2 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
@@ -28,9 +28,37 @@
 import {
     PROCESS_DEFAULT_ID,
     COMPOSITION_ERROR_COLOR,
-    COMPOSITION_VALID_COLOR
+    COMPOSITION_VALID_COLOR,
+    CAMUNDA_PANEL_INPUTS_NAMES
 } from './compositionConstants';
+import readOnly from './readOnly';
 
+function setStatusToElement(type, status, parent) {
+    let elements = parent.getElementsByTagName(type);
+    for (let item of elements) {
+        if (item.name !== 'selectedExtensionElement') {
+            item.readOnly = status;
+            item.disabled = status;
+        }
+    }
+}
+
+function disablePanelInputs(status) {
+    let panel = document.getElementById('js-properties-panel');
+
+    if (panel) {
+        setStatusToElement('input', status, panel);
+        setStatusToElement('button', status, panel);
+        setStatusToElement('select', status, panel);
+
+        CAMUNDA_PANEL_INPUTS_NAMES.map(name => {
+            const div = document.getElementById(name);
+            if (div) {
+                div.setAttribute('contenteditable', !status);
+            }
+        });
+    }
+}
 class CompositionView extends Component {
     static propTypes = {
         compositionUpdate: PropTypes.func,
@@ -41,7 +69,8 @@
         inputOutput: PropTypes.object,
         activities: PropTypes.array,
         validationUpdate: PropTypes.func,
-        errors: PropTypes.array
+        errors: PropTypes.array,
+        isReadOnly: PropTypes.bool
     };
 
     constructor() {
@@ -53,9 +82,10 @@
         this.state = {
             diagram: false
         };
+        this.versionChanged = false;
     }
     componentDidUpdate(prevProps) {
-        const { errors } = this.props;
+        const { errors, isReadOnly, versionName, composition } = this.props;
         if (!isEqual(prevProps.errors, errors)) {
             errors.map(item => {
                 this.modeling.setColor([item.element], {
@@ -65,22 +95,43 @@
                 });
             });
         }
+        if (prevProps.isReadOnly !== isReadOnly) {
+            this.modeler.get('readOnly').readOnly(isReadOnly);
+            disablePanelInputs(isReadOnly);
+        }
+
+        if (prevProps.versionName !== versionName) {
+            this.versionChanged = true;
+        }
+        if (
+            !isEqual(prevProps.composition, composition) &&
+            this.versionChanged
+        ) {
+            this.setDiagramToBPMN(composition);
+            this.versionChanged = false;
+        }
     }
     componentDidMount() {
         const {
             composition,
             activities,
             inputOutput,
-            validationUpdate
+            validationUpdate,
+            isReadOnly
         } = this.props;
 
+        const readOnlyModule = {
+            __init__: ['readOnly'],
+            readOnly: ['type', readOnly]
+        };
         this.modeler = new CustomModeler({
             propertiesPanel: {
                 parent: '#js-properties-panel'
             },
             additionalModules: [
                 propertiesPanelModule,
-                propertiesProviderModule
+                propertiesProviderModule,
+                readOnlyModule
             ],
             moddleExtensions: {
                 camunda: camundaModuleDescriptor
@@ -96,10 +147,14 @@
         this.modeler.attachTo('#' + this.generatedId);
         this.setDiagramToBPMN(composition ? composition : newDiagramXML);
         this.modeler.on('element.out', () => this.exportDiagramToStore());
+        this.modeler.on('element.click', this.handleCompositionStatus);
         this.bpmnContainer.current.click();
         this.modeling = this.modeler.get('modeling');
+        this.modeler.get('readOnly').readOnly(isReadOnly);
     }
-
+    handleCompositionStatus = () => {
+        disablePanelInputs(this.props.isReadOnly);
+    };
     getActivityInputsOutputs = selectedValue => {
         const selectedActivity = this.props.activities.find(
             el => el.name === selectedValue
@@ -132,6 +187,7 @@
                 this.props.inputOutput,
                 this.modeler.get('moddle')
             );
+            disablePanelInputs(this.props.isReadOnly);
         });
     };
     setDefaultIdAndName = businessObject => {
@@ -213,6 +269,7 @@
                         id="js-properties-panel"
                     />
                     <CompositionButtons
+                        isReadOnly={this.props.isReadOnly}
                         onClean={this.loadNewDiagram}
                         onDownload={this.exportDiagram}
                         onUpload={this.uploadDiagram}
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
index 2fc6618..433e055 100644
--- 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
@@ -17,8 +17,10 @@
 import PropTypes from 'prop-types';
 import SVGIcon from 'sdc-ui/lib/react/SVGIcon';
 
-const CompositionButton = ({ onClick, name, title }) => (
-    <div onClick={onClick} className="diagram-btn">
+const CompositionButton = ({ onClick, name, title, disabled }) => (
+    <div
+        onClick={disabled ? () => {} : onClick}
+        className={`diagram-btn ${disabled ? 'disabled' : ''}`}>
         <SVGIcon title={title} name={name} />
     </div>
 );
@@ -27,7 +29,8 @@
     onClick: PropTypes.func,
     className: PropTypes.string,
     name: PropTypes.string,
-    title: PropTypes.string
+    title: PropTypes.string,
+    disabled: PropTypes.bool
 };
 
 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
index add6490..0292fd4 100644
--- 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
@@ -19,14 +19,16 @@
 
 const Divider = () => <div className="divider" />;
 
-const CompositionButtons = ({ onClean, onUpload, onDownload }) => (
+const CompositionButtons = ({ onClean, onUpload, onDownload, isReadOnly }) => (
     <div className="composition-buttons">
         <CompositionButton
+            disabled={isReadOnly}
             data-test-id="composition-clear-btn"
             onClick={onClean}
             name="trashO"
             title="clear"
         />
+
         <Divider />
         <CompositionButton
             data-test-id="composition-download-btn"
@@ -36,6 +38,7 @@
         />
         <Divider />
         <CompositionButton
+            disabled={isReadOnly}
             data-test-id="composition-download-upload"
             onClick={onUpload}
             name="upload"
@@ -47,6 +50,7 @@
 CompositionButtons.propTypes = {
     onClean: PropTypes.func,
     onUpload: PropTypes.func,
-    onDownload: PropTypes.func
+    onDownload: PropTypes.func,
+    isReadOnly: PropTypes.bool
 };
 export default CompositionButtons;
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
index 79c7e8e..7aaa656 100644
--- 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
@@ -27,3 +27,9 @@
 
 export const COMPOSITION_ERROR_COLOR = '#f0c2c2';
 export const COMPOSITION_VALID_COLOR = 'white';
+
+export const CAMUNDA_PANEL_INPUTS_NAMES = [
+    'camunda-parameterType-text',
+    'camunda-documentation',
+    'camunda-name'
+];
diff --git a/workflow-designer-ui/src/main/frontend/src/features/version/composition/readOnly.js b/workflow-designer-ui/src/main/frontend/src/features/version/composition/readOnly.js
new file mode 100644
index 0000000..828244b
--- /dev/null
+++ b/workflow-designer-ui/src/main/frontend/src/features/version/composition/readOnly.js
@@ -0,0 +1,138 @@
+import forEach from 'lodash.foreach';
+
+const HIGH_PRIORITY = 10001;
+
+function ReadOnly(
+    eventBus,
+    contextPad,
+    dragging,
+    directEditing,
+    editorActions,
+    modeling,
+    palette,
+    paletteProvider
+) {
+    this._readOnly = false;
+    this._eventBus = eventBus;
+
+    let self = this;
+    eventBus.on('readOnly.changed', HIGH_PRIORITY, function(e) {
+        self._readOnly = e.readOnly;
+
+        if (e.readOnly) {
+            directEditing.cancel();
+            contextPad.close();
+            dragging.cancel();
+        }
+
+        palette._update();
+    });
+
+    function intercept(obj, fnName, cb) {
+        var fn = obj[fnName];
+        obj[fnName] = function() {
+            return cb.call(this, fn, arguments);
+        };
+    }
+
+    function ignoreWhenReadOnly(obj, fnName) {
+        intercept(obj, fnName, function(fn, args) {
+            if (self._readOnly) {
+                return;
+            }
+
+            return fn.apply(this, args);
+        });
+    }
+
+    function throwIfReadOnly(obj, fnName) {
+        intercept(obj, fnName, function(fn, args) {
+            if (self._readOnly) {
+                throw new Error('model is read-only');
+            }
+
+            return fn.apply(this, args);
+        });
+    }
+
+    ignoreWhenReadOnly(contextPad, 'open');
+
+    ignoreWhenReadOnly(dragging, 'init');
+
+    ignoreWhenReadOnly(directEditing, 'activate');
+
+    ignoreWhenReadOnly(editorActions._actions, 'undo');
+    ignoreWhenReadOnly(editorActions._actions, 'redo');
+    ignoreWhenReadOnly(editorActions._actions, 'copy');
+    ignoreWhenReadOnly(editorActions._actions, 'paste');
+    ignoreWhenReadOnly(editorActions._actions, 'removeSelection');
+    // BpmnEditorActions
+    ignoreWhenReadOnly(editorActions._actions, 'spaceTool');
+    ignoreWhenReadOnly(editorActions._actions, 'lassoTool');
+    ignoreWhenReadOnly(editorActions._actions, 'globalConnectTool');
+    ignoreWhenReadOnly(editorActions._actions, 'distributeElements');
+    ignoreWhenReadOnly(editorActions._actions, 'alignElements');
+    ignoreWhenReadOnly(editorActions._actions, 'directEditing');
+
+    throwIfReadOnly(modeling, 'moveShape');
+    throwIfReadOnly(modeling, 'updateAttachment');
+    throwIfReadOnly(modeling, 'moveElements');
+    throwIfReadOnly(modeling, 'moveConnection');
+    throwIfReadOnly(modeling, 'layoutConnection');
+    throwIfReadOnly(modeling, 'createConnection');
+    throwIfReadOnly(modeling, 'createShape');
+    throwIfReadOnly(modeling, 'createLabel');
+    throwIfReadOnly(modeling, 'appendShape');
+    throwIfReadOnly(modeling, 'removeElements');
+    throwIfReadOnly(modeling, 'distributeElements');
+    throwIfReadOnly(modeling, 'removeShape');
+    throwIfReadOnly(modeling, 'removeConnection');
+    throwIfReadOnly(modeling, 'replaceShape');
+    throwIfReadOnly(modeling, 'pasteElements');
+    throwIfReadOnly(modeling, 'alignElements');
+    throwIfReadOnly(modeling, 'resizeShape');
+    throwIfReadOnly(modeling, 'createSpace');
+    throwIfReadOnly(modeling, 'updateWaypoints');
+    throwIfReadOnly(modeling, 'reconnectStart');
+    throwIfReadOnly(modeling, 'reconnectEnd');
+
+    intercept(paletteProvider, 'getPaletteEntries', function(fn, args) {
+        var entries = fn.apply(this, args);
+        if (self._readOnly) {
+            let allowedEntries = ['hand-tool'];
+
+            forEach(entries, function(value, key) {
+                if (allowedEntries.indexOf(key) === -1) {
+                    delete entries[key];
+                }
+            });
+        }
+        return entries;
+    });
+}
+
+ReadOnly.$inject = [
+    'eventBus',
+    'contextPad',
+    'dragging',
+    'directEditing',
+    'editorActions',
+    'modeling',
+    'palette',
+    'paletteProvider'
+];
+
+module.exports = ReadOnly;
+
+ReadOnly.prototype.readOnly = function(readOnly) {
+    var newValue = !!readOnly,
+        oldValue = !!this._readOnly;
+
+    if (readOnly === undefined || newValue === oldValue) {
+        return oldValue;
+    }
+
+    this._readOnly = newValue;
+    this._eventBus.fire('readOnly.changed', { readOnly: newValue });
+    return newValue;
+};
diff --git a/workflow-designer-ui/src/main/frontend/yarn.lock b/workflow-designer-ui/src/main/frontend/yarn.lock
index 4235c3d..c428758 100644
--- a/workflow-designer-ui/src/main/frontend/yarn.lock
+++ b/workflow-designer-ui/src/main/frontend/yarn.lock
@@ -7020,6 +7020,10 @@
   version "4.4.0"
   resolved "https://registry.yarnpkg.com/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz#fb030917f86a3134e5bc9bec0d69e0013ddfedb2"
 
+lodash.foreach@^4.5.0:
+  version "4.5.0"
+  resolved "https://registry.yarnpkg.com/lodash.foreach/-/lodash.foreach-4.5.0.tgz#1a6a35eace401280c7f06dddec35165ab27e3e53"
+
 lodash.isarguments@^3.0.0:
   version "3.1.0"
   resolved "https://registry.yarnpkg.com/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz#2f573d85c6a24289ff00663b491c1d338ff3458a"