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