2 * Copyright © 2016-2017 European Support Limited
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 PropTypes from 'prop-types';
19 import Select from 'react-select';
20 import { DragSource, DropTarget } from 'react-dnd';
22 import Common from '../../../../../../common/Common';
24 import Icon from '../../../../../icons/Icon';
25 import iconDelete from '../../../../../../../../../../res/ecomp/asdc/sequencer/sprites/icons/delete.svg';
26 import iconHandle from '../../../../../../../../../../res/ecomp/asdc/sequencer/sprites/icons/handle.svg';
27 import iconNotes from '../../../../../../../../../../res/ecomp/asdc/sequencer/sprites/icons/notes.svg';
28 import iconSettings from '../../../../../../../../../../res/ecomp/asdc/sequencer/sprites/icons/settings.svg';
29 import iconRequestSync from '../../../../../../../../../../res/ecomp/asdc/sequencer/sprites/arrow/request-sync.svg';
30 import iconRequestAsync from '../../../../../../../../../../res/ecomp/asdc/sequencer/sprites/arrow/request-async.svg';
31 import iconResponse from '../../../../../../../../../../res/ecomp/asdc/sequencer/sprites/arrow/response.svg';
34 * LHS message row view.
36 class Message extends React.Component {
38 // ///////////////////////////////////////////////////////////////////////////////////////////////
42 * @param props element properties.
43 * @param context react context.
45 constructor(props, context) {
46 super(props, context);
50 name: props.message.name || '',
53 this.combinedOptions = [{
54 value: 'REQUEST_SYNC',
56 value: 'REQUEST_ASYNC',
63 this.onChangeName = this.onChangeName.bind(this);
64 this.onBlurName = this.onBlurName.bind(this);
65 this.onChangeType = this.onChangeType.bind(this);
66 this.onChangeFrom = this.onChangeFrom.bind(this);
67 this.onChangeTo = this.onChangeTo.bind(this);
68 this.onClickDelete = this.onClickDelete.bind(this);
69 this.onClickActions = this.onClickActions.bind(this);
70 this.onClickNotes = this.onClickNotes.bind(this);
71 this.onMouseEnter = this.onMouseEnter.bind(this);
72 this.onMouseLeave = this.onMouseLeave.bind(this);
75 // ///////////////////////////////////////////////////////////////////////////////////////////////
79 * @param event change event.
82 this.setState({ name: event.target.value });
85 // ///////////////////////////////////////////////////////////////////////////////////////////////
89 * @param event change event.
92 const options = this.props.application.getOptions();
93 const sanitized = Common.sanitizeText(event.target.value, options, 'message');
95 id: this.props.message.id,
98 this.props.designer.updateMessage(props);
99 this.setState({ name: sanitized });
102 // ///////////////////////////////////////////////////////////////////////////////////////////////
108 this.props.designer.deleteMessage(this.props.message.id);
111 // ///////////////////////////////////////////////////////////////////////////////////////////////
116 onClickActions(event) {
117 this.props.designer.showActions(this.props.message.id, { x: event.pageX, y: event.pageY });
120 // ///////////////////////////////////////////////////////////////////////////////////////////////
126 this.props.designer.showNotes(this.props.message.id);
129 // ///////////////////////////////////////////////////////////////////////////////////////////////
133 * @param value selection.
135 onChangeFrom(value) {
137 this.updateMessage({ from: value.target.value });
139 this.updateMessage({ from: value.value });
143 // ///////////////////////////////////////////////////////////////////////////////////////////////
147 * @param value selection.
151 this.updateMessage({ to: value.target.value });
153 this.updateMessage({ to: value.value });
157 // ///////////////////////////////////////////////////////////////////////////////////////////////
161 * @param selected selection.
163 onChangeType(selected) {
165 const value = selected.target ? selected.target.value : selected.value;
167 if (value.indexOf('RESPONSE') !== -1) {
168 props.type = 'response';
169 props.asynchronous = false;
171 props.type = 'request';
172 props.asynchronous = (value.indexOf('ASYNC') !== -1);
175 this.updateMessage(props);
178 // ///////////////////////////////////////////////////////////////////////////////////////////////
181 * Handle mouse event.
184 this.setState({ active: true });
185 this.props.designer.onMouseEnterMessage(this.props.message.id);
188 // ///////////////////////////////////////////////////////////////////////////////////////////////
191 * Handle mouse event.
194 this.setState({ active: false });
195 this.props.designer.onMouseLeaveMessage(this.props.message.id);
198 // ///////////////////////////////////////////////////////////////////////////////////////////////
201 * Update message properties.
202 * @param props properties updates.
204 updateMessage(props) {
206 id: this.props.message.id,
208 for (const k of Object.keys(props)) {
209 update[k] = props[k];
211 this.props.designer.updateMessage(update);
214 // ///////////////////////////////////////////////////////////////////////////////////////////////
218 * @param option selection.
221 renderOption(option) {
222 if (option.value === 'RESPONSE') {
223 return <Icon glyph={iconResponse} />;
225 if (option.value === 'REQUEST_ASYNC') {
226 return <Icon glyph={iconRequestAsync} />;
228 return <Icon glyph={iconRequestSync} />;
231 // ///////////////////////////////////////////////////////////////////////////////////////////////
234 * Get request/response and asynchronous combined constant.
235 * @param message message whose properties define spec.
238 getMessageSpec(message) {
239 if (message.type === 'response') {
242 if (message.asynchronous) {
243 return 'REQUEST_ASYNC';
245 return 'REQUEST_SYNC';
248 // ///////////////////////////////////////////////////////////////////////////////////////////////
256 const message = this.props.message;
257 const from = this.props.from;
258 const to = Common.assertNotNull(this.props.to);
259 const messageNotesActiveClass = message.notes && message.notes.length > 0 ? 'asdcs-active' : '';
260 const combinedValue = this.getMessageSpec(message);
262 const lifelineOptions = [];
263 for (const lifeline of this.props.model.unwrap().diagram.lifelines) {
264 lifelineOptions.push(<option
272 const activeClass = (this.state.active || this.props.active) ? 'asdcs-active' : '';
273 const { connectDragSource, connectDropTarget } = this.props;
274 return connectDragSource(connectDropTarget(
276 className={`asdcs-designer-message ${activeClass}`}
278 onMouseEnter={this.onMouseEnter}
279 onMouseLeave={this.onMouseLeave}
282 <table className="asdcs-designer-layout asdcs-designer-message-row1">
286 <div className="asdcs-designer-sort asdcs-designer-icon">
287 <Icon glyph={iconHandle} />
291 <div className="asdcs-designer-message-index">{message.index}.</div>
294 <div className="asdcs-designer-message-name">
297 className="asdcs-editable"
298 value={this.state.name}
299 placeholder="Unnamed"
300 onBlur={this.onBlurName}
301 onChange={this.onChangeName}
306 <div className="asdcs-designer-actions">
308 className="asdcs-designer-settings asdcs-designer-icon"
309 onClick={this.onClickActions}
311 <Icon glyph={iconSettings} />
314 className={`asdcs-designer-notes asdcs-designer-icon ${messageNotesActiveClass}`}
315 onClick={this.onClickNotes}
317 <Icon glyph={iconNotes} />
320 className="asdcs-designer-delete asdcs-designer-icon"
321 onClick={this.onClickDelete}
323 <Icon glyph={iconDelete} />
331 <table className="asdcs-designer-layout asdcs-designer-message-row2">
336 onChange={this.onChangeFrom}
337 className="asdcs-designer-select-message-from"
339 onChange={this.onChangeFrom}
341 options={lifelineOptions}
346 onChange={this.onChangeFrom}
347 className="asdcs-designer-select-message-type"
348 value={combinedValue}
349 onChange={this.onChangeType}
351 <option value="REQUEST_SYNC">⇾</option>
352 <option value="REQUEST_ASYNC">→</option>
353 <option value="RESPONSE">⇠</option>
358 onChange={this.onChangeFrom}
359 className="asdcs-designer-select-message-to"
361 onChange={this.onChangeTo}
363 options={lifelineOptions}
374 // ///////////////////////////////////////////////////////////////////////////////////////////////
381 renderReactSelect() {
383 const message = this.props.message;
384 const from = this.props.from;
385 const to = Common.assertNotNull(this.props.to);
386 const messageNotesActiveClass = message.notes && message.notes.length > 0 ? 'asdcs-active' : '';
387 const combinedValue = this.getMessageSpec(message);
389 const lifelineOptions = [];
390 for (const lifeline of this.props.model.unwrap().diagram.lifelines) {
391 lifelineOptions.push({
393 label: lifeline.name,
397 const activeClass = (this.state.active || this.props.active) ? 'asdcs-active' : '';
398 const { connectDragSource, connectDropTarget } = this.props;
399 return connectDragSource(connectDropTarget(
402 className={`asdcs-designer-message ${activeClass}`}
404 onMouseEnter={this.onMouseEnter}
405 onMouseLeave={this.onMouseLeave}
408 <table className="asdcs-designer-layout asdcs-designer-message-row1">
412 <div className="asdcs-designer-sort asdcs-designer-icon">
413 <Icon glyph={iconHandle} />
417 <div className="asdcs-designer-message-index">{message.index}.</div>
420 <div className="asdcs-designer-message-name">
423 className="asdcs-editable"
424 value={this.state.name}
425 placeholder="Unnamed"
426 onBlur={this.onBlurName}
427 onChange={this.onChangeName}
432 <div className="asdcs-designer-actions">
434 className="asdcs-designer-settings asdcs-designer-icon"
435 onClick={this.onClickActions}
437 <Icon glyph={iconSettings} />
440 className={`asdcs-designer-notes asdcs-designer-icon ${messageNotesActiveClass}`}
441 onClick={this.onClickNotes}
443 <Icon glyph={iconNotes} />
446 className="asdcs-designer-delete asdcs-designer-icon"
447 onClick={this.onClickDelete}
449 <Icon glyph={iconDelete} />
457 <table className="asdcs-designer-layout asdcs-designer-message-row2">
462 className="asdcs-editable-select asdcs-designer-editable-message-from"
467 onChange={this.onChangeFrom}
468 options={lifelineOptions}
473 className="asdcs-editable-select asdcs-designer-editable-message-type"
477 value={combinedValue}
478 onChange={this.onChangeType}
479 options={this.combinedOptions}
480 optionRenderer={this.renderOption}
481 valueRenderer={this.renderOption}
486 className="asdcs-editable-select asdcs-designer-editable-message-to"
491 onChange={this.onChangeTo}
492 options={lifelineOptions}
504 // ///////////////////////////////////////////////////////////////////////////////////////////////
507 const options = this.props.application.getOptions();
508 if (options.useHtmlSelect) {
509 return this.renderHTMLSelect();
511 return this.renderReactSelect();
516 * Declare properties.
517 * @type {{designer: *, message: *, from: *, to: *, model: *, connectDragSource: *}}
519 Message.propTypes = {
520 application: PropTypes.object.isRequired,
521 designer: PropTypes.object.isRequired,
522 message: PropTypes.object.isRequired,
523 active: PropTypes.bool.isRequired,
524 from: PropTypes.object.isRequired,
525 to: PropTypes.object.isRequired,
526 model: PropTypes.object.isRequired,
527 index: PropTypes.number.isRequired,
528 messages: PropTypes.object.isRequired,
529 connectDragSource: PropTypes.func.isRequired,
530 connectDropTarget: PropTypes.func.isRequired,
544 const sourceCollect = function collection(connect, monitor) {
546 connectDragSource: connect.dragSource(),
547 isDragging: monitor.isDragging(),
554 drop(props, monitor, component) {
555 Common.assertNotNull(props);
556 Common.assertNotNull(monitor);
557 const decorated = component.getDecoratedComponentInstance();
559 const messages = decorated.props.messages;
561 const dragIndex = monitor.getItem().index;
562 const hoverIndex = messages.getHoverIndex();
563 messages.onDrop(dragIndex, hoverIndex);
567 hover(props, monitor, component) {
568 Common.assertNotNull(props);
569 Common.assertNotNull(monitor);
571 const decorated = component.getDecoratedComponentInstance();
573 decorated.props.messages.setHoverIndex(decorated.props.index);
580 function targetCollect(connect, monitor) {
582 connectDropTarget: connect.dropTarget(),
583 isOver: monitor.isOver(),
587 const wrapper = DragSource('message', source, sourceCollect)(Message);
588 export default DropTarget(['message', 'message-new'], target, targetCollect)(wrapper);