diff options
Diffstat (limited to 'dox-sequence-diagram-ui/src/main/webapp/lib/ecomp/asdc/sequencer/components/editor')
12 files changed, 2788 insertions, 0 deletions
diff --git a/dox-sequence-diagram-ui/src/main/webapp/lib/ecomp/asdc/sequencer/components/editor/Editor.jsx b/dox-sequence-diagram-ui/src/main/webapp/lib/ecomp/asdc/sequencer/components/editor/Editor.jsx new file mode 100644 index 0000000000..09703b84bf --- /dev/null +++ b/dox-sequence-diagram-ui/src/main/webapp/lib/ecomp/asdc/sequencer/components/editor/Editor.jsx @@ -0,0 +1,171 @@ +/*! + * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +import React from 'react'; + +import Logger from '../../common/Logger'; +import Common from '../../common/Common'; +import Designer from './components/designer/Designer'; +import Toolbar from './components/toolbar/Toolbar'; +import Source from './components/source/Source'; + +/** + * Editor view, aggregating the designer, the code editor, the toolbar. + */ +export default class Editor extends React.Component { + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Construct React view. + * @param props properties. + * @param context context. + */ + constructor(props, context) { + super(props, context); + + this.application = Common.assertNotNull(props.application); + this.demo = this.application.getOptions().demo; + + // Bindings. + + this.selectMessage = this.selectMessage.bind(this); + this.selectLifeline = this.selectLifeline.bind(this); + + this.onMouseDown = this.onMouseDown.bind(this); + this.onMouseUp = this.onMouseUp.bind(this); + this.onMouseMove = this.onMouseMove.bind(this); + } + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Select message by ID. + * @param id message ID. + */ + selectMessage(id) { + if (this.designer) { + this.designer.selectMessage(id); + } + } + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Select lifeline by ID. + * @param id lifeline ID. + */ + selectLifeline(id) { + if (this.designer) { + this.designer.selectLifeline(id); + } + } + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Record that we're dragging. + */ + onMouseDown() { + if (this.editor) { + this.resize = { + initialWidth: this.editor.offsetWidth, + initialPageX: undefined, + }; + } + } + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Record that we're not dragging. + */ + onMouseUp() { + this.resize = undefined; + } + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Record mouse movement. + */ + onMouseMove(event) { + if (this.resize) { + if (this.editor) { + if (this.resize.initialPageX) { + const deltaX = event.pageX - this.resize.initialPageX; + const newWidth = this.resize.initialWidth + deltaX; + const newWidthBounded = Math.min(800, Math.max(400, newWidth)); + this.editor.style.width = `${newWidthBounded}px`; + } else { + this.resize.initialPageX = event.pageX; + } + } + } + } + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Render editor. + */ + render() { + + Logger.info('Editor.jsx - render()'); + + return ( + + <div + className="asdcs-editor" + ref={(r) => { this.editor = r; }} + > + + <Toolbar application={this.props.application} editor={this} /> + + <div className="asdcs-editor-content"> + <Source application={this.props.application} /> + <Designer + application={this.props.application} + ref={(r) => { + if (r) { + this.designer = r.getDecoratedComponentInstance(); + } else { + this.designer = null; + } + }} + /> + </div> + + <div className="asdcs-editor-statusbar"> + <div className="asdcs-editor-status"></div> + <div className="asdcs-editor-validation"></div> + </div> + + <div + className="asdcs-editor-resize-handle" + onMouseDown={this.onMouseDown} + onMouseUp={this.onMouseUp} + > + </div> + </div> + ); + } +} + +/** Element properties. */ +Editor.propTypes = { + application: React.PropTypes.object.isRequired, +}; diff --git a/dox-sequence-diagram-ui/src/main/webapp/lib/ecomp/asdc/sequencer/components/editor/components/designer/Designer.jsx b/dox-sequence-diagram-ui/src/main/webapp/lib/ecomp/asdc/sequencer/components/editor/components/designer/Designer.jsx new file mode 100644 index 0000000000..69cdd17ed5 --- /dev/null +++ b/dox-sequence-diagram-ui/src/main/webapp/lib/ecomp/asdc/sequencer/components/editor/components/designer/Designer.jsx @@ -0,0 +1,403 @@ +/*! + * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +import React from 'react'; + +import HTML5Backend from 'react-dnd-html5-backend'; +import { DragDropContext } from 'react-dnd'; + +import Common from '../../../../common/Common'; +import Logger from '../../../../common/Logger'; + +import Actions from './components/actions/Actions'; +import Lifelines from './components/lifeline/Lifelines'; +import Messages from './components/message/Messages'; +import Metadata from './components/metadata/Metadata'; + +import Icon from '../../../icons/Icon'; +import iconExpanded from '../../../../../../../../res/ecomp/asdc/sequencer/sprites/icon/expanded.svg'; +import iconCollapsed from '../../../../../../../../res/ecomp/asdc/sequencer/sprites/icon/collapsed.svg'; + +/** + * LHS design wid` view. + */ +class Designer extends React.Component { + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Construct view. + * @param props element properties. + * @param context react context. + */ + constructor(props, context) { + super(props, context); + + Logger.noop(); + + this.application = Common.assertNotNull(props.application); + + this.state = { + lifelinesExpanded: false, + messagesExpanded: true, + activeLifelineId: undefined, + activeMessageId: undefined, + }; + + // Bind this. + + this.onToggle = this.onToggle.bind(this); + this.onMouseEnterLifeline = this.onMouseEnterLifeline.bind(this); + this.onMouseLeaveLifeline = this.onMouseLeaveLifeline.bind(this); + this.onMouseEnterMessage = this.onMouseEnterMessage.bind(this); + this.onMouseLeaveMessage = this.onMouseLeaveMessage.bind(this); + + this.addMessage = this.addMessage.bind(this); + this.updateMessage = this.updateMessage.bind(this); + this.deleteMessage = this.deleteMessage.bind(this); + this.addLifeline = this.addLifeline.bind(this); + this.updateLifeline = this.updateLifeline.bind(this); + this.deleteLifeline = this.deleteLifeline.bind(this); + + this.selectMessage = this.selectMessage.bind(this); + this.selectLifeline = this.selectLifeline.bind(this); + } + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Select message by ID. + * @param id message ID. + */ + selectMessage(id) { + + // TODO: scroll into view. + + this.setState({ activeMessageId: id }); + } + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Select lifeline by ID. + * @param id lifeline ID. + */ + selectLifeline(id) { + + // TODO: scroll into view. + + this.setState({ activeLifelineId: id }); + } + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Show/hide lifelines section. + */ + onToggle() { + const lifelinesExpanded = !this.state.lifelinesExpanded; + const messagesExpanded = !lifelinesExpanded; + this.setState({ lifelinesExpanded, messagesExpanded }); + } + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Handle mouse event. + * @param id lifeline identifier. + */ + onMouseEnterLifeline(id) { + this.application.selectLifeline(id); + } + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Handle mouse event. + */ + onMouseLeaveLifeline() { + this.application.selectLifeline(); + } + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Handle mouse event. + * @param id message identifier. + */ + onMouseEnterMessage(id) { + this.application.selectMessage(id); + } + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Handle mouse event. + */ + onMouseLeaveMessage() { + // Only on next selection. + // this.application.selectMessage(); + } + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Add new message. + */ + addMessage() { + + if (this.application.getModel().unwrap().diagram.lifelines.length < 2) { + self.application.showErrorDialog('You need at least two lifelines.'); + return; + } + + this.application.getModel().addMessage(); + this.forceUpdate(); + this.application.renderDiagram(); + } + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Apply property changes to the message identified by props.id. + * @param props properties to be updated (excluding 'id'). + */ + updateMessage(props) { + Common.assertPlainObject(props); + const model = this.application.getModel(); + const message = model.getMessageById(props.id); + if (message) { + for (const k of Object.keys(props)) { + if (k !== 'id') { + message[k] = props[k]; + } + } + } + this.forceUpdate(); + this.application.renderDiagram(); + } + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Delete message after confirmation. + * @param id ID of message to be deleted. + */ + deleteMessage(id) { + + const self = this; + const model = this.application.getModel(); + + const confirmComplete = function f() { + model.deleteMessageById(id); + self.render(); + self.application.renderDiagram(); + }; + + this.application.showConfirmDialog('Delete this message?', + confirmComplete); + } + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Add new lifeline. + */ + addLifeline() { + this.application.getModel().addLifeline(); + this.forceUpdate(); + this.application.renderDiagram(); + } + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Apply property changes to the lifeline identified by props.id. + * @param props properties to be updated (excluding 'id'). + */ + updateLifeline(props) { + Common.assertPlainObject(props); + const model = this.application.getModel(); + const lifeline = model.getLifelineById(props.id); + if (lifeline) { + for (const k of Object.keys(props)) { + if (k !== 'id') { + lifeline[k] = props[k]; + } + } + } + this.forceUpdate(); + this.application.renderDiagram(); + } + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Delete lifeline after confirmation. + * @param id candidate for deletion. + */ + deleteLifeline(id) { + + const self = this; + const model = this.application.getModel(); + + const confirmComplete = function f() { + model.deleteLifelineById(id); + self.forceUpdate(); + self.application.renderDiagram(); + }; + this.application.showConfirmDialog('Delete this lifeline and all its steps?', + confirmComplete); + } + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Render designer. + */ + render() { + + const application = this.props.application; + const model = application.getModel(); + const diagram = model.unwrap().diagram; + const metadata = diagram.metadata; + + const lifelinesIcon = this.state.lifelinesExpanded ? iconExpanded : iconCollapsed; + const lifelinesClass = this.state.lifelinesExpanded ? '' : 'asdcs-hidden'; + const messagesIcon = this.state.messagesExpanded ? iconExpanded : iconCollapsed; + const messagesClass = this.state.messagesExpanded ? '' : 'asdcs-hidden'; + + return ( + + <div className="asdcs-editor-designer"> + <div className="asdcs-designer-accordion"> + + <div className="asdcs-designer-metadata-container"> + <Metadata metadata={metadata} /> + </div> + + <h3 onClick={this.onToggle}>Lifelines + <div className="asdcs-designer-icon" onClick={this.onToggle}> + <Icon glyph={lifelinesIcon} /> + </div> + </h3> + + <div className={`asdcs-designer-lifelines-container ${lifelinesClass}`}> + <Lifelines + application={this.application} + designer={this} + activeLifelineId={this.state.activeLifelineId} + /> + </div> + + <h3 onClick={this.onToggle}>Steps + <div className="asdcs-designer-icon" onClick={this.onToggle}> + <Icon glyph={messagesIcon} /> + </div> + </h3> + + <div className={`asdcs-designer-steps-container ${messagesClass}`} > + <Messages + application={this.application} + designer={this} + activeMessageId={this.state.activeMessageId} + /> + </div> + + </div> + + <Actions + application={this.props.application} + model={model} + ref={(r) => { this.actions = r; }} + /> + + </div> + ); + } + + // /////////////////////////////////////////////////////////////////////////////////////////////// + // /////////////////////////////////////////////////////////////////////////////////////////////// + // /////////////////////////////////////////////////////////////////////////////////////////////// + // /////////////////////////////////////////////////////////////////////////////////////////////// + // /////////////////////////////////////////////////////////////////////////////////////////////// + // /////////////////////////////////////////////////////////////////////////////////////////////// + // /////////////////////////////////////////////////////////////////////////////////////////////// + // /////////////////////////////////////////////////////////////////////////////////////////////// + // /////////////////////////////////////////////////////////////////////////////////////////////// + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Scroll accordion pane to make + * @param $element focused element. + * @private + */ + static _scrollIntoView($element) { + const $pane = $element.closest('.ui-accordion-content'); + const paneScrollTop = $pane.scrollTop(); + const paneHeight = $pane.height(); + const paneBottom = paneScrollTop + paneHeight; + const elementTop = $element[0].offsetTop - $pane[0].offsetTop; + const elementHeight = $element.height(); + const elementBottom = elementTop + elementHeight; + if (elementBottom > paneBottom) { + $pane.scrollTop(elementTop); + } else if (elementTop < paneScrollTop) { + $pane.scrollTop(elementTop); + } + } + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Show actions menu. + * @param id selected message ID. + * @param position page coordinates. + */ + showActions(id, position) { + if (this.actions) { + this.actions.show(id, position); + } + } + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Show notes popup. + * @param id selected message identifier. + */ + showNotes(id) { + const model = this.application.getModel(); + const options = this.application.getOptions(); + const message = model.getMessageById(id); + const notes = (message.notes && (message.notes.length > 0)) ? message.notes[0] : ''; + const editComplete = function f(p) { + message.notes = []; + if (p && p.text) { + const sanitized = Common.sanitizeText(p.text, options, 'notes'); + message.notes.push(sanitized); + } + }; + this.application.showEditDialog('Notes:', notes, editComplete); + } +} + +/** Element properties. */ +Designer.propTypes = { + application: React.PropTypes.object.isRequired, +}; + +export default DragDropContext(HTML5Backend)(Designer); diff --git a/dox-sequence-diagram-ui/src/main/webapp/lib/ecomp/asdc/sequencer/components/editor/components/designer/components/actions/Actions.jsx b/dox-sequence-diagram-ui/src/main/webapp/lib/ecomp/asdc/sequencer/components/editor/components/designer/components/actions/Actions.jsx new file mode 100644 index 0000000000..851da78870 --- /dev/null +++ b/dox-sequence-diagram-ui/src/main/webapp/lib/ecomp/asdc/sequencer/components/editor/components/designer/components/actions/Actions.jsx @@ -0,0 +1,471 @@ +/*! + * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +import React from 'react'; +import Select from 'react-select'; + +import Common from '../../../../../../common/Common'; +import Logger from '../../../../../../common/Logger'; + +import Icon from '../../../../../icons/Icon'; +import iconSettings from '../../../../../../../../../../res/ecomp/asdc/sequencer/sprites/icon/settings.svg'; +import iconExpanded from '../../../../../../../../../../res/ecomp/asdc/sequencer/sprites/icon/expanded.svg'; +import iconCollapsed from '../../../../../../../../../../res/ecomp/asdc/sequencer/sprites/icon/collapsed.svg'; +import iconOccurrenceDefault from '../../../../../../../../../../res/ecomp/asdc/sequencer/sprites/icon/occurrence-default.svg'; +import iconOccurrenceStart from '../../../../../../../../../../res/ecomp/asdc/sequencer/sprites/icon/occurrence-start.svg'; +import iconOccurrenceStop from '../../../../../../../../../../res/ecomp/asdc/sequencer/sprites/icon/occurrence-stop.svg'; +import iconFragmentDefault from '../../../../../../../../../../res/ecomp/asdc/sequencer/sprites/icon/fragment-default.svg'; +import iconFragmentStart from '../../../../../../../../../../res/ecomp/asdc/sequencer/sprites/icon/fragment-start.svg'; +import iconFragmentStop from '../../../../../../../../../../res/ecomp/asdc/sequencer/sprites/icon/fragment-stop.svg'; + +/** + * Action menu view. + */ +export default class Actions extends React.Component { + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Construct view. + * @param props element properties. + * @param context react context. + */ + constructor(props, context) { + super(props, context); + + Logger.noop(); + + this.state = { + id: undefined, + visible: false, + }; + + // Bindings. + + this.show = this.show.bind(this); + + this.onClickOccurrenceToggle = this.onClickOccurrenceToggle.bind(this); + this.onClickOccurrenceFrom = this.onClickOccurrenceFrom.bind(this); + this.onClickOccurrenceTo = this.onClickOccurrenceTo.bind(this); + + this.onClickFragmentToggle = this.onClickFragmentToggle.bind(this); + this.onChangeFragmentGuard = this.onChangeFragmentGuard.bind(this); + this.onChangeFragmentOperator = this.onChangeFragmentOperator.bind(this); + + this.onMouseOut = this.onMouseOut.bind(this); + } + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Show for message. + * @param id message ID. + * @param position xy coordinates. + */ + show(id, position) { + const message = this.props.model.getMessageById(id); + + let occurrencesToggle = false; + let fragmentToggle = false; + if (message) { + + message.occurrences = message.occurrences || { start: [], stop: [] }; + message.occurrences.start = message.occurrences.start || []; + message.occurrences.stop = message.occurrences.stop || []; + message.fragment = message.fragment || {}; + message.fragment.start = message.fragment.start || false; + message.fragment.stop = message.fragment.stop || false; + message.fragment.guard = message.fragment.guard || ''; + message.fragment.operator = message.fragment.operator || ''; + + const mo = message.occurrences; + occurrencesToggle = (mo.start.length > 0 || mo.stop.length > 0); + + const mf = message.fragment; + fragmentToggle = (mf.start || mf.stop); + } + + this.setState({ + id, + message, + occurrencesToggle, + fragmentToggle, + visible: true, + x: position.x, + y: position.y, + }); + } + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Toggle occurrence state. + */ + onClickOccurrenceToggle() { + const message = this.state.message; + if (message) { + const oFromState = Actions.getOccurrenceState(message.occurrences, message.from); + const oToState = Actions.getOccurrenceState(message.occurrences, message.to); + const oExpanded = oFromState > 0 || oToState > 0; + if (oExpanded) { + this.setState({ occurrencesExpanded: true }); + } else { + const occurrencesExpanded = !this.state.occurrencesExpanded; + this.setState({ occurrencesExpanded }); + } + } + } + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Handle menu click. + */ + onClickOccurrenceFrom() { + const message = this.state.message; + if (message) { + Actions._toggleOccurrence(message.occurrences, message.from); + } + this.setState({ message }); + this.props.application.renderDiagram(); + } + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Handle menu click. + */ + onClickOccurrenceTo() { + const message = this.state.message; + if (message) { + Actions._toggleOccurrence(message.occurrences, message.to); + } + this.setState({ message }); + this.props.application.renderDiagram(); + } + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Toggle fragment. + */ + onClickFragmentToggle() { + const message = this.state.message; + if (message) { + Actions._toggleFragment(message.fragment); + } + this.setState({ message }); + this.props.application.renderDiagram(); + } + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Handle menu click. + * @param event update event. + */ + onChangeFragmentGuard(event) { + const message = this.state.message; + if (message) { + const options = this.props.application.getOptions(); + message.fragment.guard = Common.sanitizeText(event.target.value, options, 'guard'); + } + this.setState({ message }); + this.props.application.renderDiagram(); + } + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Handle menu click. + * @param value updated value. + */ + onChangeFragmentOperator(value) { + const message = this.state.message; + if (message) { + message.fragment.operator = value.value; + } + this.setState({ message }); + this.props.application.renderDiagram(); + } + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Handle mouse movement. + */ + onMouseOut() { + this.setState({ id: -1, visible: false, x: 0, y: 0 }); + } + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Render view. + * @returns {XML} + */ + render() { + + const actionsStyles = { }; + const message = this.state.message; + if (!message || !this.state.visible) { + + // Invisible. + + return (<div className="asdcs-actions" ></div>); + } + + // Position and display. + + actionsStyles.display = 'block'; + actionsStyles.left = this.state.x - 10; + actionsStyles.top = this.state.y - 10; + + const oFromState = Actions.getOccurrenceState(message.occurrences, message.from); + const oToState = Actions.getOccurrenceState(message.occurrences, message.to); + const fState = Actions.getFragmentState(message.fragment); + + const oExpanded = this.state.occurrencesExpanded || (oFromState > 0) || (oToState > 0); + const oAuxClassName = oExpanded ? '' : 'asdcs-hidden'; + + const fExpanded = fState !== 0; + const fAuxClassName = fExpanded ? '' : 'asdcs-hidden'; + + const fragmentOperatorOptions = [{ + value: 'alt', + label: 'Alternate', + }, { + value: 'opt', + label: 'Optional', + }, { + value: 'loop', + label: 'Loop', + }]; + + const operator = message.fragment.operator || 'alt'; + + return ( + <div + className="asdcs-actions" + style={actionsStyles} + onMouseLeave={this.onMouseOut} + > + <div className="asdcs-actions-header"> + <div className="asdcs-actions-icon"> + <Icon glyph={iconSettings} /> + </div> + </div> + + <div className="asdcs-actions-options"> + + <div className="asdcs-actions-optiongroup asdcs-actions-optiongroup-occurrence"> + <div + className="asdcs-actions-option asdcs-actions-option-occurrence-toggle" + onClick={this.onClickOccurrenceToggle} + > + <span className="asdcs-label">Occurrence</span> + <div className="asdcs-actions-state"> + <Icon glyph={iconCollapsed} className={oExpanded ? 'asdcs-hidden' : ''} /> + <Icon glyph={iconExpanded} className={oExpanded ? '' : 'asdcs-hidden'} /> + </div> + </div> + </div> + + <div + className={`asdcs-actions-option asdcs-actions-option-occurrence-from ${oAuxClassName}`} + onClick={this.onClickOccurrenceFrom} + > + <span className="asdcs-label">From</span> + <div className="asdcs-actions-state"> + <span className="asdcs-annotation"></span> + <Icon glyph={iconOccurrenceDefault} className={oFromState === 0 ? '' : 'asdcs-hidden'} /> + <Icon glyph={iconOccurrenceStart} className={oFromState === 1 ? '' : 'asdcs-hidden'} /> + <Icon glyph={iconOccurrenceStop} className={oFromState === 2 ? '' : 'asdcs-hidden'} /> + </div> + </div> + + <div + className={`asdcs-actions-option asdcs-actions-option-occurrence-to ${oAuxClassName}`} + onClick={this.onClickOccurrenceTo} + > + <span className="asdcs-label">To</span> + <div className="asdcs-actions-state"> + <span className="asdcs-annotation"></span> + <Icon glyph={iconOccurrenceDefault} className={oToState === 0 ? '' : 'asdcs-hidden'} /> + <Icon glyph={iconOccurrenceStart} className={oToState === 1 ? '' : 'asdcs-hidden'} /> + <Icon glyph={iconOccurrenceStop} className={oToState === 2 ? '' : 'asdcs-hidden'} /> + </div> + </div> + + <div className="asdcs-actions-optiongroup asdcs-actions-optiongroup-fragment"> + <div + className="asdcs-actions-option asdcs-actions-fragment-toggle" + onClick={this.onClickFragmentToggle} + > + <span className="asdcs-label">Fragment</span> + <div className="asdcs-actions-state"> + <span className="asdcs-annotation"></span> + <Icon glyph={iconFragmentDefault} className={fState === 0 ? '' : 'asdcs-hidden'} /> + <Icon glyph={iconFragmentStart} className={fState === 1 ? '' : 'asdcs-hidden'} /> + <Icon glyph={iconFragmentStop} className={fState === 2 ? '' : 'asdcs-hidden'} /> + </div> + </div> + </div> + + <div className={`asdcs-actions-option asdcs-actions-fragment-operator ${fAuxClassName}`}> + <div className="asdcs-label">Operator</div> + <div className="asdcs-value"> + <Select + className="asdcs-editable-select" + openOnFocus + clearable={false} + searchable={false} + value={operator} + onChange={this.onChangeFragmentOperator} + options={fragmentOperatorOptions} + /> + </div> + </div> + + <div className={`asdcs-actions-option asdcs-actions-fragment-guard ${fAuxClassName}`}> + <div className="asdcs-label">Guard</div> + <div className="asdcs-value"> + <input + className="asdcs-editable" + type="text" + size="20" + maxLength="80" + value={message.fragment.guard} + placeholder="Condition" + onChange={this.onChangeFragmentGuard} + /> + </div> + </div> + + </div> + + <div className="asdcs-actions-footer"></div> + + </div> + + ); + } + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Toggle through three occurrence states on click. + * @param occurrence occurrences state, updated as side-effect. + * @param lifelineId message end that's being toggled. + * @private + */ + static _toggleOccurrence(occurrence, lifelineId) { + const o = occurrence; + + const rm = function rm(array, value) { + const index = array.indexOf(value); + if (index !== -1) { + array.splice(index, 1); + } + }; + + const add = function add(array, value) { + if (array.indexOf(value) === -1) { + array.push(value); + } + }; + + if (o.start && o.start.indexOf(lifelineId) !== -1) { + // Start -> stop. + rm(o.start, lifelineId); + add(o.stop, lifelineId); + } else if (o.stop && o.stop.indexOf(lifelineId) !== -1) { + // Stop -> default. + rm(o.start, lifelineId); + rm(o.stop, lifelineId); + } else { + // Default -> start. + add(o.start, lifelineId); + rm(o.stop, lifelineId); + } + } + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Toggle fragment setting on click. + * @param fragment + * @private + **/ + static _toggleFragment(fragment) { + const f = fragment; + if (f.start === true) { + f.start = false; + f.stop = true; + } else if (f.stop === true) { + f.stop = false; + f.start = false; + } else { + f.start = true; + f.stop = false; + } + f.guard = ''; + f.operator = ''; + } + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Get ternary occurrences state. + * @param o occurrences. + * @param lifelineId from/to lifeline ID. + * @returns {number} + * @private + */ + static getOccurrenceState(o, lifelineId) { + if (o.start.indexOf(lifelineId) !== -1) { + return 1; + } + if (o.stop.indexOf(lifelineId) !== -1) { + return 2; + } + return 0; + } + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Get ternary fragment state. + * @param f fragment. + * @returns {number} + * @private + */ + static getFragmentState(f) { + if (f.start) { + return 1; + } + if (f.stop) { + return 2; + } + return 0; + } +} + +/** Element properties. */ +Actions.propTypes = { + application: React.PropTypes.object.isRequired, + model: React.PropTypes.object.isRequired, +}; diff --git a/dox-sequence-diagram-ui/src/main/webapp/lib/ecomp/asdc/sequencer/components/editor/components/designer/components/lifeline/Lifeline.jsx b/dox-sequence-diagram-ui/src/main/webapp/lib/ecomp/asdc/sequencer/components/editor/components/designer/components/lifeline/Lifeline.jsx new file mode 100644 index 0000000000..e8d8cffb7a --- /dev/null +++ b/dox-sequence-diagram-ui/src/main/webapp/lib/ecomp/asdc/sequencer/components/editor/components/designer/components/lifeline/Lifeline.jsx @@ -0,0 +1,264 @@ +/*! + * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +import React from 'react'; +import { DragSource, DropTarget } from 'react-dnd'; + +import Common from '../../../../../../common/Common'; + +import Icon from '../../../../../icons/Icon'; +import iconHandle from '../../../../../../../../../../res/ecomp/asdc/sequencer/sprites/icon/handle.svg'; +import iconDelete from '../../../../../../../../../../res/ecomp/asdc/sequencer/sprites/icon/delete.svg'; + +/** + * LHS lifeline row view. + */ +class Lifeline extends React.Component { + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Construct editor view. + * @param props element properties. + * @param context react context. + */ + constructor(props, context) { + super(props, context); + + this.state = { + active: false, + name: props.lifeline.name, + }; + + const metamodel = Common.assertNotNull(this.props.metamodel).unwrap(); + this.canReorder = metamodel.diagram.lifelines.constraints.reorder; + this.canDelete = metamodel.diagram.lifelines.constraints.delete; + + // Bindings. + + this.onChangeName = this.onChangeName.bind(this); + this.onBlurName = this.onBlurName.bind(this); + this.onClickDelete = this.onClickDelete.bind(this); + this.onMouseEnter = this.onMouseEnter.bind(this); + this.onMouseLeave = this.onMouseLeave.bind(this); + } + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Handle name change. + * @param event change event. + */ + onChangeName(event) { + this.setState({ name: event.target.value }); + } + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Handle name change. + * @param event change event. + */ + onBlurName(event) { + const options = this.props.application.getOptions(); + const sanitized = Common.sanitizeText(event.target.value, options, 'lifeline'); + const props = { + id: this.props.lifeline.id, + name: sanitized, + }; + this.props.designer.updateLifeline(props); + this.setState({ name: sanitized }); + } + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Handle lifeline delete. + */ + onClickDelete() { + this.props.designer.deleteLifeline(this.props.lifeline.id); + } + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Handle mouseover event. + */ + onMouseEnter() { + this.setState({ active: true }); + this.props.designer.onMouseEnterLifeline(this.props.lifeline.id); + } + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Handle mouseleave event. + */ + onMouseLeave() { + this.setState({ active: false }); + this.props.designer.onMouseLeaveLifeline(this.props.lifeline.id); + } + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Get whether metadata permits reorder. + * @returns true if reorderable. + */ + isCanReorder() { + return this.canReorder; + } + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Get whether metadata permits delete. + * @returns true if lifeline can be deleted. + */ + isCanDelete() { + return this.canDelete; + } + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * React render. + * @returns {*} + */ + render() { + + const id = this.props.lifeline.id; + const activeClass = (this.props.active === true) ? 'asdcs-active' : ''; + const { connectDragSource, connectDropTarget } = this.props; + return connectDragSource(connectDropTarget( + + <div + className={`asdcs-designer-lifeline ${activeClass}`} + data-id={id} + onMouseEnter={this.onMouseEnter} + onMouseLeave={this.onMouseLeave} + > + <table className="asdcs-designer-layout asdcs-designer-lifeline-row1"> + <tbody> + <tr> + <td> + <div className="asdcs-designer-sort asdcs-designer-icon"> + <Icon glyph={iconHandle} /> + </div> + </td> + <td> + <div className="asdcs-designer-lifeline-index">{this.props.lifeline.index}.</div> + </td> + <td> + <div className="asdcs-designer-lifeline-name"> + <input + type="text" + className="asdcs-editable" + placeholder="Unnamed" + value={this.state.name} + onChange={this.onChangeName} + onBlur={this.onBlurName} + /> + </div> + </td> + <td> + <div className="asdcs-designer-delete asdcs-designer-icon" onClick={this.onClickDelete}> + <Icon glyph={iconDelete} /> + </div> + </td> + </tr> + </tbody> + </table> + </div> + )); + } +} + +/** + * Declare properties. + */ +Lifeline.propTypes = { + application: React.PropTypes.object.isRequired, + designer: React.PropTypes.object.isRequired, + lifeline: React.PropTypes.object.isRequired, + active: React.PropTypes.bool.isRequired, + metamodel: React.PropTypes.object.isRequired, + id: React.PropTypes.any.isRequired, + index: React.PropTypes.number.isRequired, + lifelines: React.PropTypes.object.isRequired, + isDragging: React.PropTypes.bool.isRequired, + connectDragSource: React.PropTypes.func.isRequired, + connectDropTarget: React.PropTypes.func.isRequired, +}; + +/** DND. */ +const source = { + beginDrag(props) { + return { + id: props.id, + index: props.index, + }; + }, +}; + +/** DND. */ +const sourceCollect = function collection(connect, monitor) { + return { + connectDragSource: connect.dragSource(), + isDragging: monitor.isDragging(), + }; +}; + +/** DND. */ +const target = { + drop(props, monitor, component) { + Common.assertNotNull(props); + Common.assertNotNull(monitor); + const decorated = component.getDecoratedComponentInstance(); + if (decorated) { + const lifelines = decorated.props.lifelines; + if (lifelines) { + const dragIndex = monitor.getItem().index; + const hoverIndex = lifelines.getHoverIndex(); + lifelines.onDrop(dragIndex, hoverIndex); + } + } + }, + hover(props, monitor, component) { + Common.assertNotNull(props); + Common.assertNotNull(monitor); + if (component) { + const decorated = component.getDecoratedComponentInstance(); + if (decorated) { + const lifelines = decorated.props.lifelines; + if (lifelines) { + lifelines.setHoverIndex(decorated.props.index); + } + } + } + }, +}; + +/** DND. */ +function targetCollect(connect, monitor) { + return { + connectDropTarget: connect.dropTarget(), + isOver: monitor.isOver(), + }; +} + +const wrapper1 = DragSource('lifeline', source, sourceCollect)(Lifeline); +export default DropTarget(['lifeline', 'lifeline-new'], target, targetCollect)(wrapper1); diff --git a/dox-sequence-diagram-ui/src/main/webapp/lib/ecomp/asdc/sequencer/components/editor/components/designer/components/lifeline/LifelineNew.jsx b/dox-sequence-diagram-ui/src/main/webapp/lib/ecomp/asdc/sequencer/components/editor/components/designer/components/lifeline/LifelineNew.jsx new file mode 100644 index 0000000000..a6e9a70703 --- /dev/null +++ b/dox-sequence-diagram-ui/src/main/webapp/lib/ecomp/asdc/sequencer/components/editor/components/designer/components/lifeline/LifelineNew.jsx @@ -0,0 +1,112 @@ +/*! + * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +import React from 'react'; +import { DragSource } from 'react-dnd'; + +import Icon from '../../../../../icons/Icon'; +import iconPlus from '../../../../../../../../../../res/ecomp/asdc/sequencer/sprites/icon/plus.svg'; +import iconHandle from '../../../../../../../../../../res/ecomp/asdc/sequencer/sprites/icon/handle.svg'; + +/** + * LHS lifeline row view. + */ +class LifelineNew extends React.Component { + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Construct view. + * @param props element properties. + * @param context react context. + */ + constructor(props, context) { + super(props, context); + + // Bindings. + + this.onClickAdd = this.onClickAdd.bind(this); + } + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Handle click event. + */ + onClickAdd() { + this.props.designer.addLifeline(); + } + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Render view. + * @returns {*} + */ + render() { + const { connectDragSource } = this.props; + return connectDragSource( + <div className="asdcs-designer-lifeline asdcs-designer-lifeline-new"> + <table className="asdcs-designer-layout asdcs-designer-lifeline-new"> + <tbody> + <tr> + <td> + <div className="asdcs-designer-sort asdcs-designer-icon"> + <Icon glyph={iconHandle} /> + </div> + </td> + <td> + <div className="asdcs-designer-label" onClick={this.onClickAdd}> + Add Lifeline + </div> + </td> + <td> + <div className="asdcs-designer-icon" onClick={this.onClickAdd}> + <Icon glyph={iconPlus} /> + </div> + </td> + <td> </td> + </tr> + </tbody> + </table> + </div> + ); + } +} + +/** Element properties. */ +LifelineNew.propTypes = { + designer: React.PropTypes.object.isRequired, + lifelines: React.PropTypes.object.isRequired, + connectDragSource: React.PropTypes.func.isRequired, +}; + +/** DND. */ +const source = { + beginDrag(props) { + return { id: props.id }; + }, +}; + +/** DND. */ +const collect = function collection(connect, monitor) { + return { + connectDragSource: connect.dragSource(), + isDragging: monitor.isDragging(), + }; +}; + +export default DragSource('lifeline-new', source, collect)(LifelineNew); diff --git a/dox-sequence-diagram-ui/src/main/webapp/lib/ecomp/asdc/sequencer/components/editor/components/designer/components/lifeline/Lifelines.jsx b/dox-sequence-diagram-ui/src/main/webapp/lib/ecomp/asdc/sequencer/components/editor/components/designer/components/lifeline/Lifelines.jsx new file mode 100644 index 0000000000..2e2f2ee7fd --- /dev/null +++ b/dox-sequence-diagram-ui/src/main/webapp/lib/ecomp/asdc/sequencer/components/editor/components/designer/components/lifeline/Lifelines.jsx @@ -0,0 +1,136 @@ +/*! + * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +import React from 'react'; + +import Common from '../../../../../../common/Common'; + +import Lifeline from './Lifeline'; +import LifelineNew from './LifelineNew'; + +/** + * Lifeline container, facilitating DND. + * @param props lifeline element properties. + * @returns {*} + * @constructor + */ +export default class Lifelines extends React.Component { + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Construct view. + * @param props element properties. + * @param context react context. + */ + constructor(props, context) { + super(props, context); + this.setHoverIndex = this.setHoverIndex.bind(this); + this.getHoverIndex = this.getHoverIndex.bind(this); + this.onDrop = this.onDrop.bind(this); + } + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Record last hover index as non-state. + * @param index index. + */ + setHoverIndex(index) { + this.hoverIndex = index; + } + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Get last recorded hover index. + * @returns {*} + */ + getHoverIndex() { + return this.hoverIndex; + } + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Handle drop. + * @param dragIndex dragged item index; undefined if new. + * @param hoverIndex drop index. + */ + onDrop(dragIndex, hoverIndex) { + if (hoverIndex >= 0) { + const application = this.props.application; + const model = application.getModel(); + if (Common.isNumber(dragIndex)) { + if (dragIndex !== hoverIndex) { + model.reorderLifelines(dragIndex, hoverIndex); + } + } else { + model.addLifeline(hoverIndex); + } + this.forceUpdate(); + application.renderDiagram(); + } + } + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Render view. + * @returns {XML} + */ + render() { + const model = this.props.application.getModel(); + const metamodel = model.getMetamodel(); + const diagram = model.unwrap().diagram; + + const lifelines = []; + for (const lifeline of diagram.lifelines) { + lifelines.push(<Lifeline + key={`l${lifeline.id}`} + application={this.props.application} + designer={this.props.designer} + lifeline={lifeline} + active={this.props.activeLifelineId === lifeline.id} + id={lifeline.id} + metamodel={metamodel} + lifelines={this} + index={lifelines.length} + />); + } + + lifelines.push(<LifelineNew + key="_l" + designer={this.props.designer} + lifelines={this} + />); + + return ( + <div className="asdcs-designer-lifelines"> + {lifelines} + </div> + ); + } +} + +/** + * Declare properties. + */ +Lifelines.propTypes = { + application: React.PropTypes.object.isRequired, + designer: React.PropTypes.object.isRequired, + activeLifelineId: React.PropTypes.string, +}; diff --git a/dox-sequence-diagram-ui/src/main/webapp/lib/ecomp/asdc/sequencer/components/editor/components/designer/components/message/Message.jsx b/dox-sequence-diagram-ui/src/main/webapp/lib/ecomp/asdc/sequencer/components/editor/components/designer/components/message/Message.jsx new file mode 100644 index 0000000000..95bff702da --- /dev/null +++ b/dox-sequence-diagram-ui/src/main/webapp/lib/ecomp/asdc/sequencer/components/editor/components/designer/components/message/Message.jsx @@ -0,0 +1,587 @@ +/*! + * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +import React from 'react'; +import Select from 'react-select'; +import { DragSource, DropTarget } from 'react-dnd'; + +import Common from '../../../../../../common/Common'; + +import Icon from '../../../../../icons/Icon'; +import iconDelete from '../../../../../../../../../../res/ecomp/asdc/sequencer/sprites/icon/delete.svg'; +import iconHandle from '../../../../../../../../../../res/ecomp/asdc/sequencer/sprites/icon/handle.svg'; +import iconNotes from '../../../../../../../../../../res/ecomp/asdc/sequencer/sprites/icon/notes.svg'; +import iconSettings from '../../../../../../../../../../res/ecomp/asdc/sequencer/sprites/icon/settings.svg'; +import iconRequestSync from '../../../../../../../../../../res/ecomp/asdc/sequencer/sprites/arrow/request-sync.svg'; +import iconRequestAsync from '../../../../../../../../../../res/ecomp/asdc/sequencer/sprites/arrow/request-async.svg'; +import iconResponse from '../../../../../../../../../../res/ecomp/asdc/sequencer/sprites/arrow/response.svg'; + +/** + * LHS message row view. + */ +class Message extends React.Component { + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Construct view. + * @param props element properties. + * @param context react context. + */ + constructor(props, context) { + super(props, context); + + this.state = { + active: false, + name: props.message.name || '', + }; + + this.combinedOptions = [{ + value: 'REQUEST_SYNC', + }, { + value: 'REQUEST_ASYNC', + }, { + value: 'RESPONSE', + }]; + + // Bindings. + + this.onChangeName = this.onChangeName.bind(this); + this.onBlurName = this.onBlurName.bind(this); + this.onChangeType = this.onChangeType.bind(this); + this.onChangeFrom = this.onChangeFrom.bind(this); + this.onChangeTo = this.onChangeTo.bind(this); + this.onClickDelete = this.onClickDelete.bind(this); + this.onClickActions = this.onClickActions.bind(this); + this.onClickNotes = this.onClickNotes.bind(this); + this.onMouseEnter = this.onMouseEnter.bind(this); + this.onMouseLeave = this.onMouseLeave.bind(this); + } + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Handle name change. + * @param event change event. + */ + onChangeName(event) { + this.setState({ name: event.target.value }); + } + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Handle name change. + * @param event change event. + */ + onBlurName(event) { + const options = this.props.application.getOptions(); + const sanitized = Common.sanitizeText(event.target.value, options, 'message'); + const props = { + id: this.props.message.id, + name: sanitized, + }; + this.props.designer.updateMessage(props); + this.setState({ name: sanitized }); + } + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Handle delete. + */ + onClickDelete() { + this.props.designer.deleteMessage(this.props.message.id); + } + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Handle menu click. + */ + onClickActions(event) { + this.props.designer.showActions(this.props.message.id, { x: event.pageX, y: event.pageY }); + } + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Handle menu click. + */ + onClickNotes() { + this.props.designer.showNotes(this.props.message.id); + } + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Handle selection. + * @param value selection. + */ + onChangeFrom(value) { + if (value.target) { + this.updateMessage({ from: value.target.value }); + } else { + this.updateMessage({ from: value.value }); + } + } + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Handle selection. + * @param value selection. + */ + onChangeTo(value) { + if (value.target) { + this.updateMessage({ to: value.target.value }); + } else { + this.updateMessage({ to: value.value }); + } + } + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Handle selection. + * @param selected selection. + */ + onChangeType(selected) { + + const value = selected.target ? selected.target.value : selected.value; + const props = {}; + if (value.indexOf('RESPONSE') !== -1) { + props.type = 'response'; + props.asynchronous = false; + } else { + props.type = 'request'; + props.asynchronous = (value.indexOf('ASYNC') !== -1); + } + + this.updateMessage(props); + } + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Handle mouse event. + */ + onMouseEnter() { + this.setState({ active: true }); + this.props.designer.onMouseEnterMessage(this.props.message.id); + } + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Handle mouse event. + */ + onMouseLeave() { + this.setState({ active: false }); + this.props.designer.onMouseLeaveMessage(this.props.message.id); + } + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Update message properties. + * @param props properties updates. + */ + updateMessage(props) { + const update = { + id: this.props.message.id, + }; + for (const k of Object.keys(props)) { + update[k] = props[k]; + } + this.props.designer.updateMessage(update); + } + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Render icon. + * @param option selection. + * @returns {XML} + */ + renderOption(option) { + if (option.value === 'RESPONSE') { + return <Icon glyph={iconResponse} />; + } + if (option.value === 'REQUEST_ASYNC') { + return <Icon glyph={iconRequestAsync} />; + } + return <Icon glyph={iconRequestSync} />; + } + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Get request/response and asynchronous combined constant. + * @param message message whose properties define spec. + * @returns {*} + */ + getMessageSpec(message) { + if (message.type === 'response') { + return 'RESPONSE'; + } + if (message.asynchronous) { + return 'REQUEST_ASYNC'; + } + return 'REQUEST_SYNC'; + } + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * @returns {*} + * @private + */ + renderHTMLSelect() { + + const message = this.props.message; + const from = this.props.from; + const to = Common.assertNotNull(this.props.to); + const messageNotesActiveClass = message.notes && message.notes.length > 0 ? 'asdcs-active' : ''; + const combinedValue = this.getMessageSpec(message); + + const lifelineOptions = []; + for (const lifeline of this.props.model.unwrap().diagram.lifelines) { + lifelineOptions.push(<option + key={lifeline.id} + value={lifeline.id} + > + {lifeline.name} + </option>); + } + + const activeClass = (this.state.active || this.props.active) ? 'asdcs-active' : ''; + const { connectDragSource, connectDropTarget } = this.props; + return connectDragSource(connectDropTarget( + <div + className={`asdcs-designer-message ${activeClass}`} + data-id={message.id} + onMouseEnter={this.onMouseEnter} + onMouseLeave={this.onMouseLeave} + > + + <table className="asdcs-designer-layout asdcs-designer-message-row1"> + <tbody> + <tr> + <td> + <div className="asdcs-designer-sort asdcs-designer-icon"> + <Icon glyph={iconHandle} /> + </div> + </td> + <td> + <div className="asdcs-designer-message-index">{message.index}.</div> + </td> + <td> + <div className="asdcs-designer-message-name"> + <input + type="text" + className="asdcs-editable" + value={this.state.name} + placeholder="Unnamed" + onBlur={this.onBlurName} + onChange={this.onChangeName} + /> + </div> + </td> + <td> + <div className="asdcs-designer-actions"> + <div + className="asdcs-designer-settings asdcs-designer-icon" + onClick={this.onClickActions} + > + <Icon glyph={iconSettings} /> + </div> + <div + className={`asdcs-designer-notes asdcs-designer-icon ${messageNotesActiveClass}`} + onClick={this.onClickNotes} + > + <Icon glyph={iconNotes} /> + </div> + <div + className="asdcs-designer-delete asdcs-designer-icon" + onClick={this.onClickDelete} + > + <Icon glyph={iconDelete} /> + </div> + </div> + </td> + </tr> + </tbody> + </table> + + <table className="asdcs-designer-layout asdcs-designer-message-row2"> + <tbody> + <tr> + <td> + <select + onChange={this.onChangeFrom} + className="asdcs-designer-select-message-from" + value={from.id} + onChange={this.onChangeFrom} + > + options={lifelineOptions} + </select> + </td> + <td> + <select + onChange={this.onChangeFrom} + className="asdcs-designer-select-message-type" + value={combinedValue} + onChange={this.onChangeType} + > + <option value="REQUEST_SYNC">⇾</option> + <option value="REQUEST_ASYNC">→</option> + <option value="RESPONSE">⇠</option> + </select> + </td> + <td> + <select + onChange={this.onChangeFrom} + className="asdcs-designer-select-message-to" + value={to.id} + onChange={this.onChangeTo} + > + options={lifelineOptions} + </select> + </td> + </tr> + </tbody> + </table> + + </div> + )); + } + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Render view. + * @returns {*} + * @private + */ + renderReactSelect() { + + const message = this.props.message; + const from = this.props.from; + const to = Common.assertNotNull(this.props.to); + const messageNotesActiveClass = message.notes && message.notes.length > 0 ? 'asdcs-active' : ''; + const combinedValue = this.getMessageSpec(message); + + const lifelineOptions = []; + for (const lifeline of this.props.model.unwrap().diagram.lifelines) { + lifelineOptions.push({ + value: lifeline.id, + label: lifeline.name, + }); + } + + const activeClass = (this.state.active || this.props.active) ? 'asdcs-active' : ''; + const { connectDragSource, connectDropTarget } = this.props; + return connectDragSource(connectDropTarget( + + <div + className={`asdcs-designer-message ${activeClass}`} + data-id={message.id} + onMouseEnter={this.onMouseEnter} + onMouseLeave={this.onMouseLeave} + > + + <table className="asdcs-designer-layout asdcs-designer-message-row1"> + <tbody> + <tr> + <td> + <div className="asdcs-designer-sort asdcs-designer-icon"> + <Icon glyph={iconHandle} /> + </div> + </td> + <td> + <div className="asdcs-designer-message-index">{message.index}.</div> + </td> + <td> + <div className="asdcs-designer-message-name"> + <input + type="text" + className="asdcs-editable" + value={this.state.name} + placeholder="Unnamed" + onBlur={this.onBlurName} + onChange={this.onChangeName} + /> + </div> + </td> + <td> + <div className="asdcs-designer-actions"> + <div + className="asdcs-designer-settings asdcs-designer-icon" + onClick={this.onClickActions} + > + <Icon glyph={iconSettings} /> + </div> + <div + className={`asdcs-designer-notes asdcs-designer-icon ${messageNotesActiveClass}`} + onClick={this.onClickNotes} + > + <Icon glyph={iconNotes} /> + </div> + <div + className="asdcs-designer-delete asdcs-designer-icon" + onClick={this.onClickDelete} + > + <Icon glyph={iconDelete} /> + </div> + </div> + </td> + </tr> + </tbody> + </table> + + <table className="asdcs-designer-layout asdcs-designer-message-row2"> + <tbody> + <tr> + <td> + <Select + className="asdcs-editable-select asdcs-designer-editable-message-from" + openOnFocus + clearable={false} + searchable={false} + value={from.id} + onChange={this.onChangeFrom} + options={lifelineOptions} + /> + </td> + <td> + <Select + className="asdcs-editable-select asdcs-designer-editable-message-type" + openOnFocus + clearable={false} + searchable={false} + value={combinedValue} + onChange={this.onChangeType} + options={this.combinedOptions} + optionRenderer={this.renderOption} + valueRenderer={this.renderOption} + /> + </td> + <td> + <Select + className="asdcs-editable-select asdcs-designer-editable-message-to" + openOnFocus + clearable={false} + searchable={false} + value={to.id} + onChange={this.onChangeTo} + options={lifelineOptions} + /> + </td> + + </tr> + </tbody> + </table> + + </div> + )); + } + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + render() { + const options = this.props.application.getOptions(); + if (options.useHtmlSelect) { + return this.renderHTMLSelect(); + } + return this.renderReactSelect(); + } +} + +/** + * Declare properties. + * @type {{designer: *, message: *, from: *, to: *, model: *, connectDragSource: *}} + */ +Message.propTypes = { + application: React.PropTypes.object.isRequired, + designer: React.PropTypes.object.isRequired, + message: React.PropTypes.object.isRequired, + active: React.PropTypes.bool.isRequired, + from: React.PropTypes.object.isRequired, + to: React.PropTypes.object.isRequired, + model: React.PropTypes.object.isRequired, + index: React.PropTypes.number.isRequired, + messages: React.PropTypes.object.isRequired, + connectDragSource: React.PropTypes.func.isRequired, + connectDropTarget: React.PropTypes.func.isRequired, +}; + +/** DND. */ +const source = { + beginDrag(props) { + return { + id: props.id, + index: props.index, + }; + }, +}; + +/** DND. */ +const sourceCollect = function collection(connect, monitor) { + return { + connectDragSource: connect.dragSource(), + isDragging: monitor.isDragging(), + }; +}; + + +/** DND. */ +const target = { + drop(props, monitor, component) { + Common.assertNotNull(props); + Common.assertNotNull(monitor); + const decorated = component.getDecoratedComponentInstance(); + if (decorated) { + const messages = decorated.props.messages; + if (messages) { + const dragIndex = monitor.getItem().index; + const hoverIndex = messages.getHoverIndex(); + messages.onDrop(dragIndex, hoverIndex); + } + } + }, + hover(props, monitor, component) { + Common.assertNotNull(props); + Common.assertNotNull(monitor); + if (component) { + const decorated = component.getDecoratedComponentInstance(); + if (decorated) { + decorated.props.messages.setHoverIndex(decorated.props.index); + } + } + }, +}; + +/** DND. */ +function targetCollect(connect, monitor) { + return { + connectDropTarget: connect.dropTarget(), + isOver: monitor.isOver(), + }; +} + +const wrapper = DragSource('message', source, sourceCollect)(Message); +export default DropTarget(['message', 'message-new'], target, targetCollect)(wrapper); diff --git a/dox-sequence-diagram-ui/src/main/webapp/lib/ecomp/asdc/sequencer/components/editor/components/designer/components/message/MessageNew.jsx b/dox-sequence-diagram-ui/src/main/webapp/lib/ecomp/asdc/sequencer/components/editor/components/designer/components/message/MessageNew.jsx new file mode 100644 index 0000000000..230cb9fa60 --- /dev/null +++ b/dox-sequence-diagram-ui/src/main/webapp/lib/ecomp/asdc/sequencer/components/editor/components/designer/components/message/MessageNew.jsx @@ -0,0 +1,106 @@ +/*! + * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +import React from 'react'; +import { DragSource } from 'react-dnd'; + +import Icon from '../../../../../icons/Icon'; +import iconPlus from '../../../../../../../../../../res/ecomp/asdc/sequencer/sprites/icon/plus.svg'; +import iconHandle from '../../../../../../../../../../res/ecomp/asdc/sequencer/sprites/icon/handle.svg'; + +/** + * LHS lifeline row view. + */ +class MessageNew extends React.Component { + + /** + * Construct view. + * @param props element properties. + * @param context react context. + */ + constructor(props, context) { + super(props, context); + this.onClickAdd = this.onClickAdd.bind(this); + } + + /** + * Handle add. + */ + onClickAdd() { + this.props.designer.addMessage(); + } + + /** + * Render view. + * @returns {*} + */ + render() { + const { connectDragSource } = this.props; + return connectDragSource( + <div className="asdcs-designer-message asdcs-designer-message-new"> + <table className="asdcs-designer-layout asdcs-designer-message-new"> + <tbody> + <tr> + <td> + <div className="asdcs-designer-sort asdcs-designer-icon"> + <Icon glyph={iconHandle} /> + </div> + </td> + <td> + <div className="asdcs-designer-label" onClick={this.onClickAdd}> + Add Message + </div> + </td> + <td> + <div className="asdcs-designer-icon" onClick={this.onClickAdd}> + <Icon glyph={iconPlus} /> + </div> + </td> + <td> + + </td> + </tr> + </tbody> + </table> + </div> + ); + } +} + +/** Element properties. */ +MessageNew.propTypes = { + designer: React.PropTypes.object.isRequired, + messages: React.PropTypes.object.isRequired, + connectDragSource: React.PropTypes.func.isRequired, +}; + +/** DND. */ +const source = { + beginDrag(props) { + return { id: props.id }; + }, +}; + +/** DND. */ +const collect = function collection(connect, monitor) { + return { + connectDragSource: connect.dragSource(), + isDragging: monitor.isDragging(), + }; +}; + +export default DragSource('message-new', source, collect)(MessageNew); + diff --git a/dox-sequence-diagram-ui/src/main/webapp/lib/ecomp/asdc/sequencer/components/editor/components/designer/components/message/Messages.jsx b/dox-sequence-diagram-ui/src/main/webapp/lib/ecomp/asdc/sequencer/components/editor/components/designer/components/message/Messages.jsx new file mode 100644 index 0000000000..a305a6bd86 --- /dev/null +++ b/dox-sequence-diagram-ui/src/main/webapp/lib/ecomp/asdc/sequencer/components/editor/components/designer/components/message/Messages.jsx @@ -0,0 +1,143 @@ +/*! + * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +import React from 'react'; + +import Common from '../../../../../../common/Common'; + +import Message from './Message'; +import MessageNew from './MessageNew'; + +/** + * Messages container, facilitating DND. + * @param props lifeline element properties. + * @returns {*} + * @constructor + */ +export default class Messages extends React.Component { + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Construct view. + * @param props element properties. + * @param context react context. + */ + constructor(props, context) { + super(props, context); + this.state = { + }; + this.setHoverIndex = this.setHoverIndex.bind(this); + this.getHoverIndex = this.getHoverIndex.bind(this); + } + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Record last hover index as non-state. + * @param index index. + */ + setHoverIndex(index) { + this.hoverIndex = index; + } + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Get last recorded hover index. + * @returns {*} + */ + getHoverIndex() { + return this.hoverIndex; + } + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Handle drop. + * @param dragIndex dragged item index; undefined if new. + * @param hoverIndex drop index. + */ + onDrop(dragIndex, hoverIndex) { + if (hoverIndex >= 0) { + const application = this.props.application; + const model = application.getModel(); + if (Common.isNumber(dragIndex)) { + if (dragIndex !== hoverIndex) { + model.reorderMessages(dragIndex, hoverIndex); + } + } else { + model.addMessage(hoverIndex); + } + this.forceUpdate(); + application.renderDiagram(); + } + } + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Render view. + * @returns {*} + */ + render() { + + const model = this.props.application.getModel(); + const diagram = model.unwrap().diagram; + + // Render existing messages. + + const messages = []; + for (const step of diagram.steps) { + const message = step.message; + const from = model.getLifelineById(message.from); + const to = model.getLifelineById(message.to); + messages.push(<Message + key={`m${message.id}`} + application={this.props.application} + designer={this.props.designer} + message={message} + active={this.props.activeMessageId === message.id} + from={from} + to={to} + model={model} + index={messages.length} + messages={this} + />); + } + + // Render add. + + messages.push(<MessageNew + key="_m" + designer={this.props.designer} + messages={this} + />); + + return ( + <div className="asdcs-designer-steps"> + {messages} + </div> + ); + } +} + +/** Element properties. */ +Messages.propTypes = { + application: React.PropTypes.object.isRequired, + designer: React.PropTypes.object.isRequired, + activeMessageId: React.PropTypes.string, +}; diff --git a/dox-sequence-diagram-ui/src/main/webapp/lib/ecomp/asdc/sequencer/components/editor/components/designer/components/metadata/Metadata.jsx b/dox-sequence-diagram-ui/src/main/webapp/lib/ecomp/asdc/sequencer/components/editor/components/designer/components/metadata/Metadata.jsx new file mode 100644 index 0000000000..a17a197ca0 --- /dev/null +++ b/dox-sequence-diagram-ui/src/main/webapp/lib/ecomp/asdc/sequencer/components/editor/components/designer/components/metadata/Metadata.jsx @@ -0,0 +1,34 @@ +/*! + * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +import React from 'react'; + +/** + * Metadata view. + */ +const Metadata = function Metadata(props) { + return ( + <div className="asdcs-designer-metadata"> + {props.metadata.name} + </div> + ); +}; + +Metadata.propTypes = { + metadata: React.PropTypes.object.isRequired, +}; + +export default Metadata; diff --git a/dox-sequence-diagram-ui/src/main/webapp/lib/ecomp/asdc/sequencer/components/editor/components/source/Source.jsx b/dox-sequence-diagram-ui/src/main/webapp/lib/ecomp/asdc/sequencer/components/editor/components/source/Source.jsx new file mode 100644 index 0000000000..3d13d830da --- /dev/null +++ b/dox-sequence-diagram-ui/src/main/webapp/lib/ecomp/asdc/sequencer/components/editor/components/source/Source.jsx @@ -0,0 +1,86 @@ +/*! + * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +import React from 'react'; + +/** + * Editor view, aggregating the designer, the code editor, the toolbar. + */ +export default class Source extends React.Component { + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Construct view. + */ + constructor(props, context) { + super(props, context); + this.demo = this.props.application.getOptions().demo; + } + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Set JSON mode. + * @param json JSON (stringified) code. + */ + setJSON(json = '') { + if (this.textarea) { + this.textarea.value = json; + } + } + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Set YAML mode. + * @param yaml YAML code. + */ + setYAML(yaml = '') { + if (this.textarea) { + this.textarea.value = yaml; + } + } + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + componentDidMount() { + /* + this.cm = CodeMirror.fromTextArea(this.textarea, { + lineNumbers: true, + readOnly: true, + }); + */ + } + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Render to DOM. + */ + render() { + return ( + <div className="asdcs-editor-code"> + <textarea ref={(r) => { this.textarea = r; }}></textarea> + </div> + ); + } +} + +Source.propTypes = { + application: React.PropTypes.object.isRequired, +}; + diff --git a/dox-sequence-diagram-ui/src/main/webapp/lib/ecomp/asdc/sequencer/components/editor/components/toolbar/Toolbar.jsx b/dox-sequence-diagram-ui/src/main/webapp/lib/ecomp/asdc/sequencer/components/editor/components/toolbar/Toolbar.jsx new file mode 100644 index 0000000000..dd75180b2a --- /dev/null +++ b/dox-sequence-diagram-ui/src/main/webapp/lib/ecomp/asdc/sequencer/components/editor/components/toolbar/Toolbar.jsx @@ -0,0 +1,275 @@ +/*! + * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +import React from 'react'; + +import Common from '../../../../common/Common'; + +import iconPlus from '../../../../../../../../res/ecomp/asdc/sequencer/sprites/icon/plus.svg'; +import iconOpen from '../../../../../../../../res/ecomp/asdc/sequencer/sprites/icon/open.svg'; + +/** + * Toolbar view. Buttons offered in the toolbar depend on the mode. Unless in demo mode, + * all you get are the buttons for toggling between JSON/YAML/Designer. + */ +export default class Toolbar extends React.Component { + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Construct view. + */ + constructor(props, context) { + super(props, context); + this.application = Common.assertType(this.props.application, 'Object'); + this.editor = Common.assertType(this.props.editor, 'Object'); + this.mode = 'design'; + } + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Set editor mode, one of {design, json, yaml}. + * @param mode + */ + setMode(mode = 'design') { + this.mode = mode; + } + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Render into the DOM. + */ + render() { + + const demo = this.application.getOptions().demo; + const demoCss = demo ? '' : 'asdc-hide'; + + return ( + <div className={`asdcs-editor-toolbar ${demoCss}`}> + <div className="asdcs-editor-toolbar-demo"> + <button className="asdcs-button-new" data-title="New sequence"> + <svg> + <use xlinkHref={iconPlus} className="asdcs-icon" /> + </svg> + </button> + <button className="asdcs-button-open" data-title="Open sequence"> + <svg> + <use xlinkHref={iconOpen} className="asdcs-icon" /> + </svg> + </button> + <button className="asdcs-button-save" data-title="Save checkpoint"> + <svg> + <use xlinkHref="#icon--save" className="asdcs-icon" /> + </svg> + </button> + <button className="asdcs-button-validate" data-title="Validate"> + <svg> + <use xlinkHref="#icon--validate" className="asdcs-icon" /> + </svg> + </button> + <button className="asdcs-button-download" data-title="Download"> + <svg> + <use xlinkHref="#icon--download" className="asdcs-icon" /> + </svg> + </button> + <button className="asdcs-button-upload" data-title="Upload"> + <svg> + <use xlinkHref="#icon--upload" className="asdcs-icon" /> + </svg> + </button> + </div> + <div className="asdcs-editor-toolbar-toggle"> + <button className="asdcs-button-design asdcs-button-mode asdcs-button-toggle-left"> + Design + </button> + <button className="asdcs-button-json asdcs-button-mode asdcs-button-toggle-center"> + JSON + </button> + <button className="asdcs-button-yaml asdcs-button-mode asdcs-button-toggle-right"> + YAML + </button> + </div> + </div> + ); + } + + // /////////////////////////////////////////////////////////////////////////////////////////////// + // /////////////////////////////////////////////////////////////////////////////////////////////// + // /////////////////////////////////////////////////////////////////////////////////////////////// + // /////////////////////////////////////////////////////////////////////////////////////////////// + // /////////////////////////////////////////////////////////////////////////////////////////////// + // /////////////////////////////////////////////////////////////////////////////////////////////// + // /////////////////////////////////////////////////////////////////////////////////////////////// + // /////////////////////////////////////////////////////////////////////////////////////////////// + // /////////////////////////////////////////////////////////////////////////////////////////////// + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Initialize eventhandlers. + * @private + * + _initEvents() { + + $('button.asdcs-button-open', this.$el).click(() => { + this._doDemoOpen(); + }); + + $('button.asdcs-button-new', this.$el).click(() => { + this._doDemoNew(); + }); + + $('button.asdcs-button-save', this.$el).click(() => { + this._doDemoSave(); + }); + + $('button.asdcs-button-upload', this.$el).click(() => { + this._doDemoUpload(); + }); + + $('button.asdcs-button-download', this.$el).click(() => { + this._doDemoDownload(); + }); + + $('button.asdcs-button-validate', this.$el).click(() => { + this._doDemoValidate(); + }); + + $('button.asdcs-button-json', this.$el).click((e) => { + if ($(e.target).hasClass('asdcs-active')) { + return; + } + this.editor.toggleToJSON(); + }); + + $('button.asdcs-button-yaml', this.$el).click((e) => { + if ($(e.target).hasClass('asdcs-active')) { + return; + } + this.editor.toggleToYAML(); + }); + + $('button.asdcs-button-design', this.$el).click((e) => { + if ($(e.target).hasClass('asdcs-active')) { + return; + } + this.editor.toggleToDesign(); + }); + } + */ + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Demo action. + * + _doDemoOpen() { + const complete = function complete() { + const sequencer = this.application.getSequencer(); + const scenarios = sequencer.getDemoScenarios(); + sequencer.setModel(scenarios.getECOMP()); + }; + this.application.showConfirmDialog('[DEMO MODE] Open a canned DEMO sequence ' + + 'via the public #setModel() API?', complete); + + } + */ + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Demo action. + * + _doDemoNew() { + const complete = function complete() { + const sequencer = this.application.getSequencer(); + sequencer.newModel(); + }; + this.application.showConfirmDialog('[DEMO MODE] Create an empty sequence via the ' + + 'public #newModel() API?', complete); + } + */ + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Demo action. + * + _doDemoSave() { + const sequencer = this.application.getSequencer(); + Logger.info(`[DEMO MODE] model:\n${JSON.stringify(sequencer.getModel(), null, 4)}`); + this.application.showInfoDialog('[DEMO MODE] Retrieved model via the public #getModel ' + + 'API and logged its JSON to the console.'); + } + */ + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Demo action. + * + _doDemoUpload() { + const sequencer = this.application.getSequencer(); + const svg = sequencer.getSVG(); + // console.log(`[DEMO MODE] SVG:\n${svg}`); + const $control = this.$el.closest('.asdcs-control'); + Logger.info(`parent: ${$control.length}`); + const $form = $('form.asdcs-export', $control); + Logger.info(`form: ${$form.length}`); + $('input[name=svg]', $form).val(svg); + try { + $form.submit(); + } catch (e) { + Logger.error(e); + this.application.showErrorDialog('[DEMO MODE] Export service not available. Retrieved ' + + 'SVG via the public #getSVG API and dumped it to the console.'); + } + } + */ + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Demo action. + * + _doDemoDownload() { + const json = JSON.stringify(this.application.getSequencer().getModel()); + const $control = this.$el.closest('.asdcs-control'); + const $a = $('<a download="model.json" style="display:none">').appendTo($control); + $a.attr('href', `data:application/json;charset=utf-8,${encodeURIComponent(json)}`); + $a[0].click(); + $a.remove(); + } + */ + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Demo action. + * + _doDemoValidate() { + this.application.showInfoDialog('[DEMO MODE] Dumping validation result to the console.'); + const errors = this.application.getModel().validate(); + Logger.info(`[DEMO MODE] Validation: ${JSON.stringify(errors, null, 4)}`); + } + */ +} + +Toolbar.propTypes = { + application: React.PropTypes.object.isRequired, + editor: React.PropTypes.object.isRequired, +}; |