Add new code new version
[sdc.git] / dox-sequence-diagram-ui / src / main / webapp / lib / ecomp / asdc / sequencer / components / editor / components / designer / components / actions / Actions.jsx
diff --git a/dox-sequence-diagram-ui/src/main/webapp/lib/ecomp/asdc/sequencer/components/editor/components/designer/components/actions/Actions.jsx b/dox-sequence-diagram-ui/src/main/webapp/lib/ecomp/asdc/sequencer/components/editor/components/designer/components/actions/Actions.jsx
new file mode 100644 (file)
index 0000000..851da78
--- /dev/null
@@ -0,0 +1,471 @@
+/*!
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ *
+ * 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 Select from 'react-select';
+
+import Common from '../../../../../../common/Common';
+import Logger from '../../../../../../common/Logger';
+
+import Icon from '../../../../../icons/Icon';
+import iconSettings from '../../../../../../../../../../res/ecomp/asdc/sequencer/sprites/icon/settings.svg';
+import iconExpanded from '../../../../../../../../../../res/ecomp/asdc/sequencer/sprites/icon/expanded.svg';
+import iconCollapsed from '../../../../../../../../../../res/ecomp/asdc/sequencer/sprites/icon/collapsed.svg';
+import iconOccurrenceDefault from '../../../../../../../../../../res/ecomp/asdc/sequencer/sprites/icon/occurrence-default.svg';
+import iconOccurrenceStart from '../../../../../../../../../../res/ecomp/asdc/sequencer/sprites/icon/occurrence-start.svg';
+import iconOccurrenceStop from '../../../../../../../../../../res/ecomp/asdc/sequencer/sprites/icon/occurrence-stop.svg';
+import iconFragmentDefault from '../../../../../../../../../../res/ecomp/asdc/sequencer/sprites/icon/fragment-default.svg';
+import iconFragmentStart from '../../../../../../../../../../res/ecomp/asdc/sequencer/sprites/icon/fragment-start.svg';
+import iconFragmentStop from '../../../../../../../../../../res/ecomp/asdc/sequencer/sprites/icon/fragment-stop.svg';
+
+/**
+ * Action menu view.
+ */
+export default class Actions extends React.Component {
+
+  // ///////////////////////////////////////////////////////////////////////////////////////////////
+
+  /**
+   * Construct view.
+   * @param props element properties.
+   * @param context react context.
+   */
+  constructor(props, context) {
+    super(props, context);
+
+    Logger.noop();
+
+    this.state = {
+      id: undefined,
+      visible: false,
+    };
+
+    // Bindings.
+
+    this.show = this.show.bind(this);
+
+    this.onClickOccurrenceToggle = this.onClickOccurrenceToggle.bind(this);
+    this.onClickOccurrenceFrom = this.onClickOccurrenceFrom.bind(this);
+    this.onClickOccurrenceTo = this.onClickOccurrenceTo.bind(this);
+
+    this.onClickFragmentToggle = this.onClickFragmentToggle.bind(this);
+    this.onChangeFragmentGuard = this.onChangeFragmentGuard.bind(this);
+    this.onChangeFragmentOperator = this.onChangeFragmentOperator.bind(this);
+
+    this.onMouseOut = this.onMouseOut.bind(this);
+  }
+
+  // ///////////////////////////////////////////////////////////////////////////////////////////////
+
+  /**
+   * Show for message.
+   * @param id message ID.
+   * @param position xy coordinates.
+   */
+  show(id, position) {
+    const message = this.props.model.getMessageById(id);
+
+    let occurrencesToggle = false;
+    let fragmentToggle = false;
+    if (message) {
+
+      message.occurrences = message.occurrences || { start: [], stop: [] };
+      message.occurrences.start = message.occurrences.start || [];
+      message.occurrences.stop = message.occurrences.stop || [];
+      message.fragment = message.fragment || {};
+      message.fragment.start = message.fragment.start || false;
+      message.fragment.stop = message.fragment.stop || false;
+      message.fragment.guard = message.fragment.guard || '';
+      message.fragment.operator = message.fragment.operator || '';
+
+      const mo = message.occurrences;
+      occurrencesToggle = (mo.start.length > 0 || mo.stop.length > 0);
+
+      const mf = message.fragment;
+      fragmentToggle = (mf.start || mf.stop);
+    }
+
+    this.setState({
+      id,
+      message,
+      occurrencesToggle,
+      fragmentToggle,
+      visible: true,
+      x: position.x,
+      y: position.y,
+    });
+  }
+
+  // ///////////////////////////////////////////////////////////////////////////////////////////////
+
+  /**
+   * Toggle occurrence state.
+   */
+  onClickOccurrenceToggle() {
+    const message = this.state.message;
+    if (message) {
+      const oFromState = Actions.getOccurrenceState(message.occurrences, message.from);
+      const oToState = Actions.getOccurrenceState(message.occurrences, message.to);
+      const oExpanded = oFromState > 0 || oToState > 0;
+      if (oExpanded) {
+        this.setState({ occurrencesExpanded: true });
+      } else {
+        const occurrencesExpanded = !this.state.occurrencesExpanded;
+        this.setState({ occurrencesExpanded });
+      }
+    }
+  }
+
+  // ///////////////////////////////////////////////////////////////////////////////////////////////
+
+  /**
+   * Handle menu click.
+   */
+  onClickOccurrenceFrom() {
+    const message = this.state.message;
+    if (message) {
+      Actions._toggleOccurrence(message.occurrences, message.from);
+    }
+    this.setState({ message });
+    this.props.application.renderDiagram();
+  }
+
+  // ///////////////////////////////////////////////////////////////////////////////////////////////
+
+  /**
+   * Handle menu click.
+   */
+  onClickOccurrenceTo() {
+    const message = this.state.message;
+    if (message) {
+      Actions._toggleOccurrence(message.occurrences, message.to);
+    }
+    this.setState({ message });
+    this.props.application.renderDiagram();
+  }
+
+  // ///////////////////////////////////////////////////////////////////////////////////////////////
+
+  /**
+   * Toggle fragment.
+   */
+  onClickFragmentToggle() {
+    const message = this.state.message;
+    if (message) {
+      Actions._toggleFragment(message.fragment);
+    }
+    this.setState({ message });
+    this.props.application.renderDiagram();
+  }
+
+  // ///////////////////////////////////////////////////////////////////////////////////////////////
+
+  /**
+   * Handle menu click.
+   * @param event update event.
+   */
+  onChangeFragmentGuard(event) {
+    const message = this.state.message;
+    if (message) {
+      const options = this.props.application.getOptions();
+      message.fragment.guard = Common.sanitizeText(event.target.value, options, 'guard');
+    }
+    this.setState({ message });
+    this.props.application.renderDiagram();
+  }
+
+  // ///////////////////////////////////////////////////////////////////////////////////////////////
+
+  /**
+   * Handle menu click.
+   * @param value updated value.
+   */
+  onChangeFragmentOperator(value) {
+    const message = this.state.message;
+    if (message) {
+      message.fragment.operator = value.value;
+    }
+    this.setState({ message });
+    this.props.application.renderDiagram();
+  }
+
+  // ///////////////////////////////////////////////////////////////////////////////////////////////
+
+  /**
+   * Handle mouse movement.
+   */
+  onMouseOut() {
+    this.setState({ id: -1, visible: false, x: 0, y: 0 });
+  }
+
+  // ///////////////////////////////////////////////////////////////////////////////////////////////
+
+  /**
+   * Render view.
+   * @returns {XML}
+   */
+  render() {
+
+    const actionsStyles = { };
+    const message = this.state.message;
+    if (!message || !this.state.visible) {
+
+      // Invisible.
+
+      return (<div className="asdcs-actions" ></div>);
+    }
+
+    // Position and display.
+
+    actionsStyles.display = 'block';
+    actionsStyles.left = this.state.x - 10;
+    actionsStyles.top = this.state.y - 10;
+
+    const oFromState = Actions.getOccurrenceState(message.occurrences, message.from);
+    const oToState = Actions.getOccurrenceState(message.occurrences, message.to);
+    const fState = Actions.getFragmentState(message.fragment);
+
+    const oExpanded = this.state.occurrencesExpanded || (oFromState > 0) || (oToState > 0);
+    const oAuxClassName = oExpanded ? '' : 'asdcs-hidden';
+
+    const fExpanded = fState !== 0;
+    const fAuxClassName = fExpanded ? '' : 'asdcs-hidden';
+
+    const fragmentOperatorOptions = [{
+      value: 'alt',
+      label: 'Alternate',
+    }, {
+      value: 'opt',
+      label: 'Optional',
+    }, {
+      value: 'loop',
+      label: 'Loop',
+    }];
+
+    const operator = message.fragment.operator || 'alt';
+
+    return (
+      <div
+        className="asdcs-actions"
+        style={actionsStyles}
+        onMouseLeave={this.onMouseOut}
+      >
+        <div className="asdcs-actions-header">
+          <div className="asdcs-actions-icon">
+            <Icon glyph={iconSettings} />
+          </div>
+        </div>
+
+        <div className="asdcs-actions-options">
+
+          <div className="asdcs-actions-optiongroup asdcs-actions-optiongroup-occurrence">
+            <div
+              className="asdcs-actions-option asdcs-actions-option-occurrence-toggle"
+              onClick={this.onClickOccurrenceToggle}
+            >
+              <span className="asdcs-label">Occurrence</span>
+              <div className="asdcs-actions-state">
+                <Icon glyph={iconCollapsed} className={oExpanded ? 'asdcs-hidden' : ''} />
+                <Icon glyph={iconExpanded} className={oExpanded ? '' : 'asdcs-hidden'} />
+              </div>
+            </div>
+          </div>
+
+          <div
+            className={`asdcs-actions-option asdcs-actions-option-occurrence-from ${oAuxClassName}`}
+            onClick={this.onClickOccurrenceFrom}
+          >
+            <span className="asdcs-label">From</span>
+            <div className="asdcs-actions-state">
+              <span className="asdcs-annotation"></span>
+              <Icon glyph={iconOccurrenceDefault} className={oFromState === 0 ? '' : 'asdcs-hidden'} />
+              <Icon glyph={iconOccurrenceStart} className={oFromState === 1 ? '' : 'asdcs-hidden'} />
+              <Icon glyph={iconOccurrenceStop} className={oFromState === 2 ? '' : 'asdcs-hidden'} />
+            </div>
+          </div>
+
+          <div
+            className={`asdcs-actions-option asdcs-actions-option-occurrence-to ${oAuxClassName}`}
+            onClick={this.onClickOccurrenceTo}
+          >
+            <span className="asdcs-label">To</span>
+            <div className="asdcs-actions-state">
+              <span className="asdcs-annotation"></span>
+              <Icon glyph={iconOccurrenceDefault} className={oToState === 0 ? '' : 'asdcs-hidden'} />
+              <Icon glyph={iconOccurrenceStart} className={oToState === 1 ? '' : 'asdcs-hidden'} />
+              <Icon glyph={iconOccurrenceStop} className={oToState === 2 ? '' : 'asdcs-hidden'} />
+            </div>
+          </div>
+
+          <div className="asdcs-actions-optiongroup asdcs-actions-optiongroup-fragment">
+            <div
+              className="asdcs-actions-option asdcs-actions-fragment-toggle"
+              onClick={this.onClickFragmentToggle}
+            >
+              <span className="asdcs-label">Fragment</span>
+              <div className="asdcs-actions-state">
+                <span className="asdcs-annotation"></span>
+                <Icon glyph={iconFragmentDefault} className={fState === 0 ? '' : 'asdcs-hidden'} />
+                <Icon glyph={iconFragmentStart} className={fState === 1 ? '' : 'asdcs-hidden'} />
+                <Icon glyph={iconFragmentStop} className={fState === 2 ? '' : 'asdcs-hidden'} />
+              </div>
+            </div>
+          </div>
+
+          <div className={`asdcs-actions-option asdcs-actions-fragment-operator ${fAuxClassName}`}>
+            <div className="asdcs-label">Operator</div>
+            <div className="asdcs-value">
+              <Select
+                className="asdcs-editable-select"
+                openOnFocus
+                clearable={false}
+                searchable={false}
+                value={operator}
+                onChange={this.onChangeFragmentOperator}
+                options={fragmentOperatorOptions}
+              />
+            </div>
+          </div>
+
+          <div className={`asdcs-actions-option asdcs-actions-fragment-guard ${fAuxClassName}`}>
+            <div className="asdcs-label">Guard</div>
+            <div className="asdcs-value">
+              <input
+                className="asdcs-editable"
+                type="text"
+                size="20"
+                maxLength="80"
+                value={message.fragment.guard}
+                placeholder="Condition"
+                onChange={this.onChangeFragmentGuard}
+              />
+            </div>
+          </div>
+
+        </div>
+
+        <div className="asdcs-actions-footer"></div>
+
+      </div>
+
+    );
+  }
+
+  // ///////////////////////////////////////////////////////////////////////////////////////////////
+
+  /**
+   * Toggle through three occurrence states on click.
+   * @param occurrence occurrences state, updated as side-effect.
+   * @param lifelineId message end that's being toggled.
+   * @private
+   */
+  static _toggleOccurrence(occurrence, lifelineId) {
+    const o = occurrence;
+
+    const rm = function rm(array, value) {
+      const index = array.indexOf(value);
+      if (index !== -1) {
+        array.splice(index, 1);
+      }
+    };
+
+    const add = function add(array, value) {
+      if (array.indexOf(value) === -1) {
+        array.push(value);
+      }
+    };
+
+    if (o.start && o.start.indexOf(lifelineId) !== -1) {
+      // Start -> stop.
+      rm(o.start, lifelineId);
+      add(o.stop, lifelineId);
+    } else if (o.stop && o.stop.indexOf(lifelineId) !== -1) {
+      // Stop -> default.
+      rm(o.start, lifelineId);
+      rm(o.stop, lifelineId);
+    } else {
+      // Default -> start.
+      add(o.start, lifelineId);
+      rm(o.stop, lifelineId);
+    }
+  }
+
+  // ///////////////////////////////////////////////////////////////////////////////////////////////
+
+  /**
+   * Toggle fragment setting on click.
+   * @param fragment
+   * @private
+   **/
+  static _toggleFragment(fragment) {
+    const f = fragment;
+    if (f.start === true) {
+      f.start = false;
+      f.stop = true;
+    } else if (f.stop === true) {
+      f.stop = false;
+      f.start = false;
+    } else {
+      f.start = true;
+      f.stop = false;
+    }
+    f.guard = '';
+    f.operator = '';
+  }
+
+  // ///////////////////////////////////////////////////////////////////////////////////////////////
+
+  /**
+   * Get ternary occurrences state.
+   * @param o occurrences.
+   * @param lifelineId from/to lifeline ID.
+   * @returns {number}
+   * @private
+   */
+  static getOccurrenceState(o, lifelineId) {
+    if (o.start.indexOf(lifelineId) !== -1) {
+      return 1;
+    }
+    if (o.stop.indexOf(lifelineId) !== -1) {
+      return 2;
+    }
+    return 0;
+  }
+
+  // ///////////////////////////////////////////////////////////////////////////////////////////////
+
+  /**
+   * Get ternary fragment state.
+   * @param f fragment.
+   * @returns {number}
+   * @private
+   */
+  static getFragmentState(f) {
+    if (f.start) {
+      return 1;
+    }
+    if (f.stop) {
+      return 2;
+    }
+    return 0;
+  }
+}
+
+/** Element properties. */
+Actions.propTypes = {
+  application: React.PropTypes.object.isRequired,
+  model: React.PropTypes.object.isRequired,
+};