2 * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 import React from 'react';
18 import Select from 'react-select';
19 import { DragSource, DropTarget } from 'react-dnd';
21 import Common from '../../../../../../common/Common';
23 import Icon from '../../../../../icons/Icon';
24 import iconDelete from '../../../../../../../../../../res/ecomp/asdc/sequencer/sprites/icons/delete.svg';
25 import iconHandle from '../../../../../../../../../../res/ecomp/asdc/sequencer/sprites/icons/handle.svg';
26 import iconNotes from '../../../../../../../../../../res/ecomp/asdc/sequencer/sprites/icons/notes.svg';
27 import iconSettings from '../../../../../../../../../../res/ecomp/asdc/sequencer/sprites/icons/settings.svg';
28 import iconRequestSync from '../../../../../../../../../../res/ecomp/asdc/sequencer/sprites/arrow/request-sync.svg';
29 import iconRequestAsync from '../../../../../../../../../../res/ecomp/asdc/sequencer/sprites/arrow/request-async.svg';
30 import iconResponse from '../../../../../../../../../../res/ecomp/asdc/sequencer/sprites/arrow/response.svg';
33 * LHS message row view.
35 class Message extends React.Component {
37 // ///////////////////////////////////////////////////////////////////////////////////////////////
41 * @param props element properties.
42 * @param context react context.
44 constructor(props, context) {
45 super(props, context);
49 name: props.message.name || '',
52 this.combinedOptions = [{
53 value: 'REQUEST_SYNC',
55 value: 'REQUEST_ASYNC',
62 this.onChangeName = this.onChangeName.bind(this);
63 this.onBlurName = this.onBlurName.bind(this);
64 this.onChangeType = this.onChangeType.bind(this);
65 this.onChangeFrom = this.onChangeFrom.bind(this);
66 this.onChangeTo = this.onChangeTo.bind(this);
67 this.onClickDelete = this.onClickDelete.bind(this);
68 this.onClickActions = this.onClickActions.bind(this);
69 this.onClickNotes = this.onClickNotes.bind(this);
70 this.onMouseEnter = this.onMouseEnter.bind(this);
71 this.onMouseLeave = this.onMouseLeave.bind(this);
74 // ///////////////////////////////////////////////////////////////////////////////////////////////
78 * @param event change event.
81 this.setState({ name: event.target.value });
84 // ///////////////////////////////////////////////////////////////////////////////////////////////
88 * @param event change event.
91 const options = this.props.application.getOptions();
92 const sanitized = Common.sanitizeText(event.target.value, options, 'message');
94 id: this.props.message.id,
97 this.props.designer.updateMessage(props);
98 this.setState({ name: sanitized });
101 // ///////////////////////////////////////////////////////////////////////////////////////////////
107 this.props.designer.deleteMessage(this.props.message.id);
110 // ///////////////////////////////////////////////////////////////////////////////////////////////
115 onClickActions(event) {
116 this.props.designer.showActions(this.props.message.id, { x: event.pageX, y: event.pageY });
119 // ///////////////////////////////////////////////////////////////////////////////////////////////
125 this.props.designer.showNotes(this.props.message.id);
128 // ///////////////////////////////////////////////////////////////////////////////////////////////
132 * @param value selection.
134 onChangeFrom(value) {
136 this.updateMessage({ from: value.target.value });
138 this.updateMessage({ from: value.value });
142 // ///////////////////////////////////////////////////////////////////////////////////////////////
146 * @param value selection.
150 this.updateMessage({ to: value.target.value });
152 this.updateMessage({ to: value.value });
156 // ///////////////////////////////////////////////////////////////////////////////////////////////
160 * @param selected selection.
162 onChangeType(selected) {
164 const value = selected.target ? selected.target.value : selected.value;
166 if (value.indexOf('RESPONSE') !== -1) {
167 props.type = 'response';
168 props.asynchronous = false;
170 props.type = 'request';
171 props.asynchronous = (value.indexOf('ASYNC') !== -1);
174 this.updateMessage(props);
177 // ///////////////////////////////////////////////////////////////////////////////////////////////
180 * Handle mouse event.
183 this.setState({ active: true });
184 this.props.designer.onMouseEnterMessage(this.props.message.id);
187 // ///////////////////////////////////////////////////////////////////////////////////////////////
190 * Handle mouse event.
193 this.setState({ active: false });
194 this.props.designer.onMouseLeaveMessage(this.props.message.id);
197 // ///////////////////////////////////////////////////////////////////////////////////////////////
200 * Update message properties.
201 * @param props properties updates.
203 updateMessage(props) {
205 id: this.props.message.id,
207 for (const k of Object.keys(props)) {
208 update[k] = props[k];
210 this.props.designer.updateMessage(update);
213 // ///////////////////////////////////////////////////////////////////////////////////////////////
217 * @param option selection.
220 renderOption(option) {
221 if (option.value === 'RESPONSE') {
222 return <Icon glyph={iconResponse} />;
224 if (option.value === 'REQUEST_ASYNC') {
225 return <Icon glyph={iconRequestAsync} />;
227 return <Icon glyph={iconRequestSync} />;
230 // ///////////////////////////////////////////////////////////////////////////////////////////////
233 * Get request/response and asynchronous combined constant.
234 * @param message message whose properties define spec.
237 getMessageSpec(message) {
238 if (message.type === 'response') {
241 if (message.asynchronous) {
242 return 'REQUEST_ASYNC';
244 return 'REQUEST_SYNC';
247 // ///////////////////////////////////////////////////////////////////////////////////////////////
255 const message = this.props.message;
256 const from = this.props.from;
257 const to = Common.assertNotNull(this.props.to);
258 const messageNotesActiveClass = message.notes && message.notes.length > 0 ? 'asdcs-active' : '';
259 const combinedValue = this.getMessageSpec(message);
261 const lifelineOptions = [];
262 for (const lifeline of this.props.model.unwrap().diagram.lifelines) {
263 lifelineOptions.push(<option
271 const activeClass = (this.state.active || this.props.active) ? 'asdcs-active' : '';
272 const { connectDragSource, connectDropTarget } = this.props;
273 return connectDragSource(connectDropTarget(
275 className={`asdcs-designer-message ${activeClass}`}
277 onMouseEnter={this.onMouseEnter}
278 onMouseLeave={this.onMouseLeave}
281 <table className="asdcs-designer-layout asdcs-designer-message-row1">
285 <div className="asdcs-designer-sort asdcs-designer-icon">
286 <Icon glyph={iconHandle} />
290 <div className="asdcs-designer-message-index">{message.index}.</div>
293 <div className="asdcs-designer-message-name">
296 className="asdcs-editable"
297 value={this.state.name}
298 placeholder="Unnamed"
299 onBlur={this.onBlurName}
300 onChange={this.onChangeName}
305 <div className="asdcs-designer-actions">
307 className="asdcs-designer-settings asdcs-designer-icon"
308 onClick={this.onClickActions}
310 <Icon glyph={iconSettings} />
313 className={`asdcs-designer-notes asdcs-designer-icon ${messageNotesActiveClass}`}
314 onClick={this.onClickNotes}
316 <Icon glyph={iconNotes} />
319 className="asdcs-designer-delete asdcs-designer-icon"
320 onClick={this.onClickDelete}
322 <Icon glyph={iconDelete} />
330 <table className="asdcs-designer-layout asdcs-designer-message-row2">
335 onChange={this.onChangeFrom}
336 className="asdcs-designer-select-message-from"
338 onChange={this.onChangeFrom}
340 options={lifelineOptions}
345 onChange={this.onChangeFrom}
346 className="asdcs-designer-select-message-type"
347 value={combinedValue}
348 onChange={this.onChangeType}
350 <option value="REQUEST_SYNC">⇾</option>
351 <option value="REQUEST_ASYNC">→</option>
352 <option value="RESPONSE">⇠</option>
357 onChange={this.onChangeFrom}
358 className="asdcs-designer-select-message-to"
360 onChange={this.onChangeTo}
362 options={lifelineOptions}
373 // ///////////////////////////////////////////////////////////////////////////////////////////////
380 renderReactSelect() {
382 const message = this.props.message;
383 const from = this.props.from;
384 const to = Common.assertNotNull(this.props.to);
385 const messageNotesActiveClass = message.notes && message.notes.length > 0 ? 'asdcs-active' : '';
386 const combinedValue = this.getMessageSpec(message);
388 const lifelineOptions = [];
389 for (const lifeline of this.props.model.unwrap().diagram.lifelines) {
390 lifelineOptions.push({
392 label: lifeline.name,
396 const activeClass = (this.state.active || this.props.active) ? 'asdcs-active' : '';
397 const { connectDragSource, connectDropTarget } = this.props;
398 return connectDragSource(connectDropTarget(
401 className={`asdcs-designer-message ${activeClass}`}
403 onMouseEnter={this.onMouseEnter}
404 onMouseLeave={this.onMouseLeave}
407 <table className="asdcs-designer-layout asdcs-designer-message-row1">
411 <div className="asdcs-designer-sort asdcs-designer-icon">
412 <Icon glyph={iconHandle} />
416 <div className="asdcs-designer-message-index">{message.index}.</div>
419 <div className="asdcs-designer-message-name">
422 className="asdcs-editable"
423 value={this.state.name}
424 placeholder="Unnamed"
425 onBlur={this.onBlurName}
426 onChange={this.onChangeName}
431 <div className="asdcs-designer-actions">
433 className="asdcs-designer-settings asdcs-designer-icon"
434 onClick={this.onClickActions}
436 <Icon glyph={iconSettings} />
439 className={`asdcs-designer-notes asdcs-designer-icon ${messageNotesActiveClass}`}
440 onClick={this.onClickNotes}
442 <Icon glyph={iconNotes} />
445 className="asdcs-designer-delete asdcs-designer-icon"
446 onClick={this.onClickDelete}
448 <Icon glyph={iconDelete} />
456 <table className="asdcs-designer-layout asdcs-designer-message-row2">
461 className="asdcs-editable-select asdcs-designer-editable-message-from"
466 onChange={this.onChangeFrom}
467 options={lifelineOptions}
472 className="asdcs-editable-select asdcs-designer-editable-message-type"
476 value={combinedValue}
477 onChange={this.onChangeType}
478 options={this.combinedOptions}
479 optionRenderer={this.renderOption}
480 valueRenderer={this.renderOption}
485 className="asdcs-editable-select asdcs-designer-editable-message-to"
490 onChange={this.onChangeTo}
491 options={lifelineOptions}
503 // ///////////////////////////////////////////////////////////////////////////////////////////////
506 const options = this.props.application.getOptions();
507 if (options.useHtmlSelect) {
508 return this.renderHTMLSelect();
510 return this.renderReactSelect();
515 * Declare properties.
516 * @type {{designer: *, message: *, from: *, to: *, model: *, connectDragSource: *}}
518 Message.propTypes = {
519 application: React.PropTypes.object.isRequired,
520 designer: React.PropTypes.object.isRequired,
521 message: React.PropTypes.object.isRequired,
522 active: React.PropTypes.bool.isRequired,
523 from: React.PropTypes.object.isRequired,
524 to: React.PropTypes.object.isRequired,
525 model: React.PropTypes.object.isRequired,
526 index: React.PropTypes.number.isRequired,
527 messages: React.PropTypes.object.isRequired,
528 connectDragSource: React.PropTypes.func.isRequired,
529 connectDropTarget: React.PropTypes.func.isRequired,
543 const sourceCollect = function collection(connect, monitor) {
545 connectDragSource: connect.dragSource(),
546 isDragging: monitor.isDragging(),
553 drop(props, monitor, component) {
554 Common.assertNotNull(props);
555 Common.assertNotNull(monitor);
556 const decorated = component.getDecoratedComponentInstance();
558 const messages = decorated.props.messages;
560 const dragIndex = monitor.getItem().index;
561 const hoverIndex = messages.getHoverIndex();
562 messages.onDrop(dragIndex, hoverIndex);
566 hover(props, monitor, component) {
567 Common.assertNotNull(props);
568 Common.assertNotNull(monitor);
570 const decorated = component.getDecoratedComponentInstance();
572 decorated.props.messages.setHoverIndex(decorated.props.index);
579 function targetCollect(connect, monitor) {
581 connectDropTarget: connect.dropTarget(),
582 isOver: monitor.isOver(),
586 const wrapper = DragSource('message', source, sourceCollect)(Message);
587 export default DropTarget(['message', 'message-new'], target, targetCollect)(wrapper);