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
1 /*!
2  * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
3  *
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
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
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
13  * or implied. See the License for the specific language governing
14  * permissions and limitations under the License.
15  */
16
17 import React from 'react';
18 import Select from 'react-select';
19
20 import Common from '../../../../../../common/Common';
21 import Logger from '../../../../../../common/Logger';
22
23 import Icon from '../../../../../icons/Icon';
24 import iconSettings from '../../../../../../../../../../res/ecomp/asdc/sequencer/sprites/icon/settings.svg';
25 import iconExpanded from '../../../../../../../../../../res/ecomp/asdc/sequencer/sprites/icon/expanded.svg';
26 import iconCollapsed from '../../../../../../../../../../res/ecomp/asdc/sequencer/sprites/icon/collapsed.svg';
27 import iconOccurrenceDefault from '../../../../../../../../../../res/ecomp/asdc/sequencer/sprites/icon/occurrence-default.svg';
28 import iconOccurrenceStart from '../../../../../../../../../../res/ecomp/asdc/sequencer/sprites/icon/occurrence-start.svg';
29 import iconOccurrenceStop from '../../../../../../../../../../res/ecomp/asdc/sequencer/sprites/icon/occurrence-stop.svg';
30 import iconFragmentDefault from '../../../../../../../../../../res/ecomp/asdc/sequencer/sprites/icon/fragment-default.svg';
31 import iconFragmentStart from '../../../../../../../../../../res/ecomp/asdc/sequencer/sprites/icon/fragment-start.svg';
32 import iconFragmentStop from '../../../../../../../../../../res/ecomp/asdc/sequencer/sprites/icon/fragment-stop.svg';
33
34 /**
35  * Action menu view.
36  */
37 export default class Actions extends React.Component {
38
39   // ///////////////////////////////////////////////////////////////////////////////////////////////
40
41   /**
42    * Construct view.
43    * @param props element properties.
44    * @param context react context.
45    */
46   constructor(props, context) {
47     super(props, context);
48
49     Logger.noop();
50
51     this.state = {
52       id: undefined,
53       visible: false,
54     };
55
56     // Bindings.
57
58     this.show = this.show.bind(this);
59
60     this.onClickOccurrenceToggle = this.onClickOccurrenceToggle.bind(this);
61     this.onClickOccurrenceFrom = this.onClickOccurrenceFrom.bind(this);
62     this.onClickOccurrenceTo = this.onClickOccurrenceTo.bind(this);
63
64     this.onClickFragmentToggle = this.onClickFragmentToggle.bind(this);
65     this.onChangeFragmentGuard = this.onChangeFragmentGuard.bind(this);
66     this.onChangeFragmentOperator = this.onChangeFragmentOperator.bind(this);
67
68     this.onMouseOut = this.onMouseOut.bind(this);
69   }
70
71   // ///////////////////////////////////////////////////////////////////////////////////////////////
72
73   /**
74    * Show for message.
75    * @param id message ID.
76    * @param position xy coordinates.
77    */
78   show(id, position) {
79     const message = this.props.model.getMessageById(id);
80
81     let occurrencesToggle = false;
82     let fragmentToggle = false;
83     if (message) {
84
85       message.occurrences = message.occurrences || { start: [], stop: [] };
86       message.occurrences.start = message.occurrences.start || [];
87       message.occurrences.stop = message.occurrences.stop || [];
88       message.fragment = message.fragment || {};
89       message.fragment.start = message.fragment.start || false;
90       message.fragment.stop = message.fragment.stop || false;
91       message.fragment.guard = message.fragment.guard || '';
92       message.fragment.operator = message.fragment.operator || '';
93
94       const mo = message.occurrences;
95       occurrencesToggle = (mo.start.length > 0 || mo.stop.length > 0);
96
97       const mf = message.fragment;
98       fragmentToggle = (mf.start || mf.stop);
99     }
100
101     this.setState({
102       id,
103       message,
104       occurrencesToggle,
105       fragmentToggle,
106       visible: true,
107       x: position.x,
108       y: position.y,
109     });
110   }
111
112   // ///////////////////////////////////////////////////////////////////////////////////////////////
113
114   /**
115    * Toggle occurrence state.
116    */
117   onClickOccurrenceToggle() {
118     const message = this.state.message;
119     if (message) {
120       const oFromState = Actions.getOccurrenceState(message.occurrences, message.from);
121       const oToState = Actions.getOccurrenceState(message.occurrences, message.to);
122       const oExpanded = oFromState > 0 || oToState > 0;
123       if (oExpanded) {
124         this.setState({ occurrencesExpanded: true });
125       } else {
126         const occurrencesExpanded = !this.state.occurrencesExpanded;
127         this.setState({ occurrencesExpanded });
128       }
129     }
130   }
131
132   // ///////////////////////////////////////////////////////////////////////////////////////////////
133
134   /**
135    * Handle menu click.
136    */
137   onClickOccurrenceFrom() {
138     const message = this.state.message;
139     if (message) {
140       Actions._toggleOccurrence(message.occurrences, message.from);
141     }
142     this.setState({ message });
143     this.props.application.renderDiagram();
144   }
145
146   // ///////////////////////////////////////////////////////////////////////////////////////////////
147
148   /**
149    * Handle menu click.
150    */
151   onClickOccurrenceTo() {
152     const message = this.state.message;
153     if (message) {
154       Actions._toggleOccurrence(message.occurrences, message.to);
155     }
156     this.setState({ message });
157     this.props.application.renderDiagram();
158   }
159
160   // ///////////////////////////////////////////////////////////////////////////////////////////////
161
162   /**
163    * Toggle fragment.
164    */
165   onClickFragmentToggle() {
166     const message = this.state.message;
167     if (message) {
168       Actions._toggleFragment(message.fragment);
169     }
170     this.setState({ message });
171     this.props.application.renderDiagram();
172   }
173
174   // ///////////////////////////////////////////////////////////////////////////////////////////////
175
176   /**
177    * Handle menu click.
178    * @param event update event.
179    */
180   onChangeFragmentGuard(event) {
181     const message = this.state.message;
182     if (message) {
183       const options = this.props.application.getOptions();
184       message.fragment.guard = Common.sanitizeText(event.target.value, options, 'guard');
185     }
186     this.setState({ message });
187     this.props.application.renderDiagram();
188   }
189
190   // ///////////////////////////////////////////////////////////////////////////////////////////////
191
192   /**
193    * Handle menu click.
194    * @param value updated value.
195    */
196   onChangeFragmentOperator(value) {
197     const message = this.state.message;
198     if (message) {
199       message.fragment.operator = value.value;
200     }
201     this.setState({ message });
202     this.props.application.renderDiagram();
203   }
204
205   // ///////////////////////////////////////////////////////////////////////////////////////////////
206
207   /**
208    * Handle mouse movement.
209    */
210   onMouseOut() {
211     this.setState({ id: -1, visible: false, x: 0, y: 0 });
212   }
213
214   // ///////////////////////////////////////////////////////////////////////////////////////////////
215
216   /**
217    * Render view.
218    * @returns {XML}
219    */
220   render() {
221
222     const actionsStyles = { };
223     const message = this.state.message;
224     if (!message || !this.state.visible) {
225
226       // Invisible.
227
228       return (<div className="asdcs-actions" ></div>);
229     }
230
231     // Position and display.
232
233     actionsStyles.display = 'block';
234     actionsStyles.left = this.state.x - 10;
235     actionsStyles.top = this.state.y - 10;
236
237     const oFromState = Actions.getOccurrenceState(message.occurrences, message.from);
238     const oToState = Actions.getOccurrenceState(message.occurrences, message.to);
239     const fState = Actions.getFragmentState(message.fragment);
240
241     const oExpanded = this.state.occurrencesExpanded || (oFromState > 0) || (oToState > 0);
242     const oAuxClassName = oExpanded ? '' : 'asdcs-hidden';
243
244     const fExpanded = fState !== 0;
245     const fAuxClassName = fExpanded ? '' : 'asdcs-hidden';
246
247     const fragmentOperatorOptions = [{
248       value: 'alt',
249       label: 'Alternate',
250     }, {
251       value: 'opt',
252       label: 'Optional',
253     }, {
254       value: 'loop',
255       label: 'Loop',
256     }];
257
258     const operator = message.fragment.operator || 'alt';
259
260     return (
261       <div
262         className="asdcs-actions"
263         style={actionsStyles}
264         onMouseLeave={this.onMouseOut}
265       >
266         <div className="asdcs-actions-header">
267           <div className="asdcs-actions-icon">
268             <Icon glyph={iconSettings} />
269           </div>
270         </div>
271
272         <div className="asdcs-actions-options">
273
274           <div className="asdcs-actions-optiongroup asdcs-actions-optiongroup-occurrence">
275             <div
276               className="asdcs-actions-option asdcs-actions-option-occurrence-toggle"
277               onClick={this.onClickOccurrenceToggle}
278             >
279               <span className="asdcs-label">Occurrence</span>
280               <div className="asdcs-actions-state">
281                 <Icon glyph={iconCollapsed} className={oExpanded ? 'asdcs-hidden' : ''} />
282                 <Icon glyph={iconExpanded} className={oExpanded ? '' : 'asdcs-hidden'} />
283               </div>
284             </div>
285           </div>
286
287           <div
288             className={`asdcs-actions-option asdcs-actions-option-occurrence-from ${oAuxClassName}`}
289             onClick={this.onClickOccurrenceFrom}
290           >
291             <span className="asdcs-label">From</span>
292             <div className="asdcs-actions-state">
293               <span className="asdcs-annotation"></span>
294               <Icon glyph={iconOccurrenceDefault} className={oFromState === 0 ? '' : 'asdcs-hidden'} />
295               <Icon glyph={iconOccurrenceStart} className={oFromState === 1 ? '' : 'asdcs-hidden'} />
296               <Icon glyph={iconOccurrenceStop} className={oFromState === 2 ? '' : 'asdcs-hidden'} />
297             </div>
298           </div>
299
300           <div
301             className={`asdcs-actions-option asdcs-actions-option-occurrence-to ${oAuxClassName}`}
302             onClick={this.onClickOccurrenceTo}
303           >
304             <span className="asdcs-label">To</span>
305             <div className="asdcs-actions-state">
306               <span className="asdcs-annotation"></span>
307               <Icon glyph={iconOccurrenceDefault} className={oToState === 0 ? '' : 'asdcs-hidden'} />
308               <Icon glyph={iconOccurrenceStart} className={oToState === 1 ? '' : 'asdcs-hidden'} />
309               <Icon glyph={iconOccurrenceStop} className={oToState === 2 ? '' : 'asdcs-hidden'} />
310             </div>
311           </div>
312
313           <div className="asdcs-actions-optiongroup asdcs-actions-optiongroup-fragment">
314             <div
315               className="asdcs-actions-option asdcs-actions-fragment-toggle"
316               onClick={this.onClickFragmentToggle}
317             >
318               <span className="asdcs-label">Fragment</span>
319               <div className="asdcs-actions-state">
320                 <span className="asdcs-annotation"></span>
321                 <Icon glyph={iconFragmentDefault} className={fState === 0 ? '' : 'asdcs-hidden'} />
322                 <Icon glyph={iconFragmentStart} className={fState === 1 ? '' : 'asdcs-hidden'} />
323                 <Icon glyph={iconFragmentStop} className={fState === 2 ? '' : 'asdcs-hidden'} />
324               </div>
325             </div>
326           </div>
327
328           <div className={`asdcs-actions-option asdcs-actions-fragment-operator ${fAuxClassName}`}>
329             <div className="asdcs-label">Operator</div>
330             <div className="asdcs-value">
331               <Select
332                 className="asdcs-editable-select"
333                 openOnFocus
334                 clearable={false}
335                 searchable={false}
336                 value={operator}
337                 onChange={this.onChangeFragmentOperator}
338                 options={fragmentOperatorOptions}
339               />
340             </div>
341           </div>
342
343           <div className={`asdcs-actions-option asdcs-actions-fragment-guard ${fAuxClassName}`}>
344             <div className="asdcs-label">Guard</div>
345             <div className="asdcs-value">
346               <input
347                 className="asdcs-editable"
348                 type="text"
349                 size="20"
350                 maxLength="80"
351                 value={message.fragment.guard}
352                 placeholder="Condition"
353                 onChange={this.onChangeFragmentGuard}
354               />
355             </div>
356           </div>
357
358         </div>
359
360         <div className="asdcs-actions-footer"></div>
361
362       </div>
363
364     );
365   }
366
367   // ///////////////////////////////////////////////////////////////////////////////////////////////
368
369   /**
370    * Toggle through three occurrence states on click.
371    * @param occurrence occurrences state, updated as side-effect.
372    * @param lifelineId message end that's being toggled.
373    * @private
374    */
375   static _toggleOccurrence(occurrence, lifelineId) {
376     const o = occurrence;
377
378     const rm = function rm(array, value) {
379       const index = array.indexOf(value);
380       if (index !== -1) {
381         array.splice(index, 1);
382       }
383     };
384
385     const add = function add(array, value) {
386       if (array.indexOf(value) === -1) {
387         array.push(value);
388       }
389     };
390
391     if (o.start && o.start.indexOf(lifelineId) !== -1) {
392       // Start -> stop.
393       rm(o.start, lifelineId);
394       add(o.stop, lifelineId);
395     } else if (o.stop && o.stop.indexOf(lifelineId) !== -1) {
396       // Stop -> default.
397       rm(o.start, lifelineId);
398       rm(o.stop, lifelineId);
399     } else {
400       // Default -> start.
401       add(o.start, lifelineId);
402       rm(o.stop, lifelineId);
403     }
404   }
405
406   // ///////////////////////////////////////////////////////////////////////////////////////////////
407
408   /**
409    * Toggle fragment setting on click.
410    * @param fragment
411    * @private
412    **/
413   static _toggleFragment(fragment) {
414     const f = fragment;
415     if (f.start === true) {
416       f.start = false;
417       f.stop = true;
418     } else if (f.stop === true) {
419       f.stop = false;
420       f.start = false;
421     } else {
422       f.start = true;
423       f.stop = false;
424     }
425     f.guard = '';
426     f.operator = '';
427   }
428
429   // ///////////////////////////////////////////////////////////////////////////////////////////////
430
431   /**
432    * Get ternary occurrences state.
433    * @param o occurrences.
434    * @param lifelineId from/to lifeline ID.
435    * @returns {number}
436    * @private
437    */
438   static getOccurrenceState(o, lifelineId) {
439     if (o.start.indexOf(lifelineId) !== -1) {
440       return 1;
441     }
442     if (o.stop.indexOf(lifelineId) !== -1) {
443       return 2;
444     }
445     return 0;
446   }
447
448   // ///////////////////////////////////////////////////////////////////////////////////////////////
449
450   /**
451    * Get ternary fragment state.
452    * @param f fragment.
453    * @returns {number}
454    * @private
455    */
456   static getFragmentState(f) {
457     if (f.start) {
458       return 1;
459     }
460     if (f.stop) {
461       return 2;
462     }
463     return 0;
464   }
465 }
466
467 /** Element properties. */
468 Actions.propTypes = {
469   application: React.PropTypes.object.isRequired,
470   model: React.PropTypes.object.isRequired,
471 };