Add new code new version
[sdc.git] / dox-sequence-diagram-ui / src / main / webapp / lib / ecomp / asdc / sequencer / components / editor / components / designer / Designer.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
19 import HTML5Backend from 'react-dnd-html5-backend';
20 import { DragDropContext } from 'react-dnd';
21
22 import Common from '../../../../common/Common';
23 import Logger from '../../../../common/Logger';
24
25 import Actions from './components/actions/Actions';
26 import Lifelines from './components/lifeline/Lifelines';
27 import Messages from './components/message/Messages';
28 import Metadata from './components/metadata/Metadata';
29
30 import Icon from '../../../icons/Icon';
31 import iconExpanded from '../../../../../../../../res/ecomp/asdc/sequencer/sprites/icon/expanded.svg';
32 import iconCollapsed from '../../../../../../../../res/ecomp/asdc/sequencer/sprites/icon/collapsed.svg';
33
34 /**
35  * LHS design wid` view.
36  */
37 class Designer 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.application = Common.assertNotNull(props.application);
52
53     this.state = {
54       lifelinesExpanded: false,
55       messagesExpanded: true,
56       activeLifelineId: undefined,
57       activeMessageId: undefined,
58     };
59
60     // Bind this.
61
62     this.onToggle = this.onToggle.bind(this);
63     this.onMouseEnterLifeline = this.onMouseEnterLifeline.bind(this);
64     this.onMouseLeaveLifeline = this.onMouseLeaveLifeline.bind(this);
65     this.onMouseEnterMessage = this.onMouseEnterMessage.bind(this);
66     this.onMouseLeaveMessage = this.onMouseLeaveMessage.bind(this);
67
68     this.addMessage = this.addMessage.bind(this);
69     this.updateMessage = this.updateMessage.bind(this);
70     this.deleteMessage = this.deleteMessage.bind(this);
71     this.addLifeline = this.addLifeline.bind(this);
72     this.updateLifeline = this.updateLifeline.bind(this);
73     this.deleteLifeline = this.deleteLifeline.bind(this);
74
75     this.selectMessage = this.selectMessage.bind(this);
76     this.selectLifeline = this.selectLifeline.bind(this);
77   }
78
79   // ///////////////////////////////////////////////////////////////////////////////////////////////
80
81   /**
82    * Select message by ID.
83    * @param id message ID.
84    */
85   selectMessage(id) {
86
87     // TODO: scroll into view.
88
89     this.setState({ activeMessageId: id });
90   }
91
92   // ///////////////////////////////////////////////////////////////////////////////////////////////
93
94   /**
95    * Select lifeline by ID.
96    * @param id lifeline ID.
97    */
98   selectLifeline(id) {
99
100     // TODO: scroll into view.
101
102     this.setState({ activeLifelineId: id });
103   }
104
105   // ///////////////////////////////////////////////////////////////////////////////////////////////
106
107   /**
108    * Show/hide lifelines section.
109    */
110   onToggle() {
111     const lifelinesExpanded = !this.state.lifelinesExpanded;
112     const messagesExpanded = !lifelinesExpanded;
113     this.setState({ lifelinesExpanded, messagesExpanded });
114   }
115
116   // ///////////////////////////////////////////////////////////////////////////////////////////////
117
118   /**
119    * Handle mouse event.
120    * @param id lifeline identifier.
121    */
122   onMouseEnterLifeline(id) {
123     this.application.selectLifeline(id);
124   }
125
126   // ///////////////////////////////////////////////////////////////////////////////////////////////
127
128   /**
129    * Handle mouse event.
130    */
131   onMouseLeaveLifeline() {
132     this.application.selectLifeline();
133   }
134
135   // ///////////////////////////////////////////////////////////////////////////////////////////////
136
137   /**
138    * Handle mouse event.
139    * @param id message identifier.
140    */
141   onMouseEnterMessage(id) {
142     this.application.selectMessage(id);
143   }
144
145   // ///////////////////////////////////////////////////////////////////////////////////////////////
146
147   /**
148    * Handle mouse event.
149    */
150   onMouseLeaveMessage() {
151     // Only on next selection.
152     // this.application.selectMessage();
153   }
154
155   // ///////////////////////////////////////////////////////////////////////////////////////////////
156
157   /**
158    * Add new message.
159    */
160   addMessage() {
161
162     if (this.application.getModel().unwrap().diagram.lifelines.length < 2) {
163       self.application.showErrorDialog('You need at least two lifelines.');
164       return;
165     }
166
167     this.application.getModel().addMessage();
168     this.forceUpdate();
169     this.application.renderDiagram();
170   }
171
172   // ///////////////////////////////////////////////////////////////////////////////////////////////
173
174   /**
175    * Apply property changes to the message identified by props.id.
176    * @param props properties to be updated (excluding 'id').
177    */
178   updateMessage(props) {
179     Common.assertPlainObject(props);
180     const model = this.application.getModel();
181     const message = model.getMessageById(props.id);
182     if (message) {
183       for (const k of Object.keys(props)) {
184         if (k !== 'id') {
185           message[k] = props[k];
186         }
187       }
188     }
189     this.forceUpdate();
190     this.application.renderDiagram();
191   }
192
193   // ///////////////////////////////////////////////////////////////////////////////////////////////
194
195   /**
196    * Delete message after confirmation.
197    * @param id ID of message to be deleted.
198    */
199   deleteMessage(id) {
200
201     const self = this;
202     const model = this.application.getModel();
203
204     const confirmComplete = function f() {
205       model.deleteMessageById(id);
206       self.render();
207       self.application.renderDiagram();
208     };
209
210     this.application.showConfirmDialog('Delete this message?',
211       confirmComplete);
212   }
213
214   // ///////////////////////////////////////////////////////////////////////////////////////////////
215
216   /**
217    * Add new lifeline.
218    */
219   addLifeline() {
220     this.application.getModel().addLifeline();
221     this.forceUpdate();
222     this.application.renderDiagram();
223   }
224
225   // ///////////////////////////////////////////////////////////////////////////////////////////////
226
227   /**
228    * Apply property changes to the lifeline identified by props.id.
229    * @param props properties to be updated (excluding 'id').
230    */
231   updateLifeline(props) {
232     Common.assertPlainObject(props);
233     const model = this.application.getModel();
234     const lifeline = model.getLifelineById(props.id);
235     if (lifeline) {
236       for (const k of Object.keys(props)) {
237         if (k !== 'id') {
238           lifeline[k] = props[k];
239         }
240       }
241     }
242     this.forceUpdate();
243     this.application.renderDiagram();
244   }
245
246   // ///////////////////////////////////////////////////////////////////////////////////////////////
247
248   /**
249    * Delete lifeline after confirmation.
250    * @param id candidate for deletion.
251    */
252   deleteLifeline(id) {
253
254     const self = this;
255     const model = this.application.getModel();
256
257     const confirmComplete = function f() {
258       model.deleteLifelineById(id);
259       self.forceUpdate();
260       self.application.renderDiagram();
261     };
262     this.application.showConfirmDialog('Delete this lifeline and all its steps?',
263       confirmComplete);
264   }
265
266   // ///////////////////////////////////////////////////////////////////////////////////////////////
267
268   /**
269    * Render designer.
270    */
271   render() {
272
273     const application = this.props.application;
274     const model = application.getModel();
275     const diagram = model.unwrap().diagram;
276     const metadata = diagram.metadata;
277
278     const lifelinesIcon = this.state.lifelinesExpanded ? iconExpanded : iconCollapsed;
279     const lifelinesClass = this.state.lifelinesExpanded ? '' : 'asdcs-hidden';
280     const messagesIcon = this.state.messagesExpanded ? iconExpanded : iconCollapsed;
281     const messagesClass = this.state.messagesExpanded ? '' : 'asdcs-hidden';
282
283     return (
284
285       <div className="asdcs-editor-designer">
286         <div className="asdcs-designer-accordion">
287
288           <div className="asdcs-designer-metadata-container">
289             <Metadata metadata={metadata} />
290           </div>
291
292           <h3 onClick={this.onToggle}>Lifelines
293             <div className="asdcs-designer-icon" onClick={this.onToggle}>
294               <Icon glyph={lifelinesIcon} />
295             </div>
296           </h3>
297
298           <div className={`asdcs-designer-lifelines-container ${lifelinesClass}`}>
299             <Lifelines
300               application={this.application}
301               designer={this}
302               activeLifelineId={this.state.activeLifelineId}
303             />
304           </div>
305
306           <h3 onClick={this.onToggle}>Steps
307             <div className="asdcs-designer-icon" onClick={this.onToggle}>
308               <Icon glyph={messagesIcon} />
309             </div>
310           </h3>
311
312           <div className={`asdcs-designer-steps-container ${messagesClass}`} >
313             <Messages
314               application={this.application}
315               designer={this}
316               activeMessageId={this.state.activeMessageId}
317             />
318           </div>
319
320         </div>
321
322         <Actions
323           application={this.props.application}
324           model={model}
325           ref={(r) => { this.actions = r; }}
326         />
327
328       </div>
329     );
330   }
331
332   // ///////////////////////////////////////////////////////////////////////////////////////////////
333   // ///////////////////////////////////////////////////////////////////////////////////////////////
334   // ///////////////////////////////////////////////////////////////////////////////////////////////
335   // ///////////////////////////////////////////////////////////////////////////////////////////////
336   // ///////////////////////////////////////////////////////////////////////////////////////////////
337   // ///////////////////////////////////////////////////////////////////////////////////////////////
338   // ///////////////////////////////////////////////////////////////////////////////////////////////
339   // ///////////////////////////////////////////////////////////////////////////////////////////////
340   // ///////////////////////////////////////////////////////////////////////////////////////////////
341   // ///////////////////////////////////////////////////////////////////////////////////////////////
342
343   /**
344    * Scroll accordion pane to make
345    * @param $element focused element.
346    * @private
347    */
348   static _scrollIntoView($element) {
349     const $pane = $element.closest('.ui-accordion-content');
350     const paneScrollTop = $pane.scrollTop();
351     const paneHeight = $pane.height();
352     const paneBottom = paneScrollTop + paneHeight;
353     const elementTop = $element[0].offsetTop - $pane[0].offsetTop;
354     const elementHeight = $element.height();
355     const elementBottom = elementTop + elementHeight;
356     if (elementBottom > paneBottom) {
357       $pane.scrollTop(elementTop);
358     } else if (elementTop < paneScrollTop) {
359       $pane.scrollTop(elementTop);
360     }
361   }
362
363   // ///////////////////////////////////////////////////////////////////////////////////////////////
364
365   /**
366    * Show actions menu.
367    * @param id selected message ID.
368    * @param position page coordinates.
369    */
370   showActions(id, position) {
371     if (this.actions) {
372       this.actions.show(id, position);
373     }
374   }
375
376   // ///////////////////////////////////////////////////////////////////////////////////////////////
377
378   /**
379    * Show notes popup.
380    * @param id selected message identifier.
381    */
382   showNotes(id) {
383     const model = this.application.getModel();
384     const options = this.application.getOptions();
385     const message = model.getMessageById(id);
386     const notes = (message.notes && (message.notes.length > 0)) ? message.notes[0] : '';
387     const editComplete = function f(p) {
388       message.notes = [];
389       if (p && p.text) {
390         const sanitized = Common.sanitizeText(p.text, options, 'notes');
391         message.notes.push(sanitized);
392       }
393     };
394     this.application.showEditDialog('Notes:', notes, editComplete);
395   }
396 }
397
398 /** Element properties. */
399 Designer.propTypes = {
400   application: React.PropTypes.object.isRequired,
401 };
402
403 export default DragDropContext(HTML5Backend)(Designer);