From efa037d34be7b1570efdc767c79fad8d4005f10e Mon Sep 17 00:00:00 2001 From: Michael Lando Date: Sun, 19 Feb 2017 12:57:33 +0200 Subject: Add new code new version Change-Id: Ic02a76313503b526f17c3df29eb387a29fe6a42a Signed-off-by: Michael Lando --- .../components/application/Application.jsx | 268 ++++++ .../asdc/sequencer/components/diagram/Diagram.jsx | 896 +++++++++++++++++++++ .../components/diagram/components/popup/Popup.jsx | 94 +++ .../components/diagram/templates/diagram.html | 56 ++ .../components/diagram/templates/fragment.html | 18 + .../components/diagram/templates/lifeline.html | 19 + .../components/diagram/templates/message.html | 29 + .../components/diagram/templates/occurrence.html | 7 + .../components/diagram/templates/title.html | 3 + .../asdc/sequencer/components/dialog/Dialog.jsx | 222 +++++ .../asdc/sequencer/components/editor/Editor.jsx | 171 ++++ .../editor/components/designer/Designer.jsx | 403 +++++++++ .../designer/components/actions/Actions.jsx | 471 +++++++++++ .../designer/components/lifeline/Lifeline.jsx | 264 ++++++ .../designer/components/lifeline/LifelineNew.jsx | 112 +++ .../designer/components/lifeline/Lifelines.jsx | 136 ++++ .../designer/components/message/Message.jsx | 587 ++++++++++++++ .../designer/components/message/MessageNew.jsx | 106 +++ .../designer/components/message/Messages.jsx | 143 ++++ .../designer/components/metadata/Metadata.jsx | 34 + .../components/editor/components/source/Source.jsx | 86 ++ .../editor/components/toolbar/Toolbar.jsx | 275 +++++++ .../asdc/sequencer/components/export/Export.jsx | 31 + .../ecomp/asdc/sequencer/components/icons/Icon.jsx | 41 + .../asdc/sequencer/components/overlay/Overlay.jsx | 61 ++ 25 files changed, 4533 insertions(+) create mode 100644 dox-sequence-diagram-ui/src/main/webapp/lib/ecomp/asdc/sequencer/components/application/Application.jsx create mode 100644 dox-sequence-diagram-ui/src/main/webapp/lib/ecomp/asdc/sequencer/components/diagram/Diagram.jsx create mode 100644 dox-sequence-diagram-ui/src/main/webapp/lib/ecomp/asdc/sequencer/components/diagram/components/popup/Popup.jsx create mode 100644 dox-sequence-diagram-ui/src/main/webapp/lib/ecomp/asdc/sequencer/components/diagram/templates/diagram.html create mode 100644 dox-sequence-diagram-ui/src/main/webapp/lib/ecomp/asdc/sequencer/components/diagram/templates/fragment.html create mode 100644 dox-sequence-diagram-ui/src/main/webapp/lib/ecomp/asdc/sequencer/components/diagram/templates/lifeline.html create mode 100644 dox-sequence-diagram-ui/src/main/webapp/lib/ecomp/asdc/sequencer/components/diagram/templates/message.html create mode 100644 dox-sequence-diagram-ui/src/main/webapp/lib/ecomp/asdc/sequencer/components/diagram/templates/occurrence.html create mode 100644 dox-sequence-diagram-ui/src/main/webapp/lib/ecomp/asdc/sequencer/components/diagram/templates/title.html create mode 100644 dox-sequence-diagram-ui/src/main/webapp/lib/ecomp/asdc/sequencer/components/dialog/Dialog.jsx create mode 100644 dox-sequence-diagram-ui/src/main/webapp/lib/ecomp/asdc/sequencer/components/editor/Editor.jsx create mode 100644 dox-sequence-diagram-ui/src/main/webapp/lib/ecomp/asdc/sequencer/components/editor/components/designer/Designer.jsx create mode 100644 dox-sequence-diagram-ui/src/main/webapp/lib/ecomp/asdc/sequencer/components/editor/components/designer/components/actions/Actions.jsx create mode 100644 dox-sequence-diagram-ui/src/main/webapp/lib/ecomp/asdc/sequencer/components/editor/components/designer/components/lifeline/Lifeline.jsx create mode 100644 dox-sequence-diagram-ui/src/main/webapp/lib/ecomp/asdc/sequencer/components/editor/components/designer/components/lifeline/LifelineNew.jsx create mode 100644 dox-sequence-diagram-ui/src/main/webapp/lib/ecomp/asdc/sequencer/components/editor/components/designer/components/lifeline/Lifelines.jsx create mode 100644 dox-sequence-diagram-ui/src/main/webapp/lib/ecomp/asdc/sequencer/components/editor/components/designer/components/message/Message.jsx create mode 100644 dox-sequence-diagram-ui/src/main/webapp/lib/ecomp/asdc/sequencer/components/editor/components/designer/components/message/MessageNew.jsx create mode 100644 dox-sequence-diagram-ui/src/main/webapp/lib/ecomp/asdc/sequencer/components/editor/components/designer/components/message/Messages.jsx create mode 100644 dox-sequence-diagram-ui/src/main/webapp/lib/ecomp/asdc/sequencer/components/editor/components/designer/components/metadata/Metadata.jsx create mode 100644 dox-sequence-diagram-ui/src/main/webapp/lib/ecomp/asdc/sequencer/components/editor/components/source/Source.jsx create mode 100644 dox-sequence-diagram-ui/src/main/webapp/lib/ecomp/asdc/sequencer/components/editor/components/toolbar/Toolbar.jsx create mode 100644 dox-sequence-diagram-ui/src/main/webapp/lib/ecomp/asdc/sequencer/components/export/Export.jsx create mode 100644 dox-sequence-diagram-ui/src/main/webapp/lib/ecomp/asdc/sequencer/components/icons/Icon.jsx create mode 100644 dox-sequence-diagram-ui/src/main/webapp/lib/ecomp/asdc/sequencer/components/overlay/Overlay.jsx (limited to 'dox-sequence-diagram-ui/src/main/webapp/lib/ecomp/asdc/sequencer/components') diff --git a/dox-sequence-diagram-ui/src/main/webapp/lib/ecomp/asdc/sequencer/components/application/Application.jsx b/dox-sequence-diagram-ui/src/main/webapp/lib/ecomp/asdc/sequencer/components/application/Application.jsx new file mode 100644 index 0000000000..20b06922c8 --- /dev/null +++ b/dox-sequence-diagram-ui/src/main/webapp/lib/ecomp/asdc/sequencer/components/application/Application.jsx @@ -0,0 +1,268 @@ + +import React from 'react'; + +import Common from '../../common/Common'; +import Logger from '../../common/Logger'; +import Diagram from '../diagram/Diagram'; +import Dialog from '../dialog/Dialog'; +import Editor from '../editor/Editor'; +import Export from '../export/Export'; +import Overlay from '../overlay/Overlay'; + +/** + * Application controller, also a view. + */ +export default class Application extends React.Component { + + /** + * Construct application view. + * @param props element properties. + * @param context react context. + */ + constructor(props, context) { + super(props, context); + + this.sequencer = Common.assertNotNull(props.sequencer); + this.model = this.sequencer.getModel(); + this.metamodel = this.sequencer.getMetamodel(); + this.options = props.options; + Logger.setLevel(this.options.unwrap().log.level); + + // Bindings. + + this.showInfoDialog = this.showInfoDialog.bind(this); + this.showEditDialog = this.showEditDialog.bind(this); + this.showConfirmDialog = this.showConfirmDialog.bind(this); + this.hideOverlay = this.hideOverlay.bind(this); + this.onMouseMove = this.onMouseMove.bind(this); + this.onMouseUp = this.onMouseUp.bind(this); + } + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Get application options. + * @returns JSON options, see Options.js. + */ + getOptions() { + return this.options.unwrap(); + } + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Set diagram name. + * @param n diagram (human-readable) name. + */ + setName(n) { + this.diagram.setName(n); + } + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Set diagram model. + * @param model diagram instance. + */ + setModel(model) { + + Common.assertNotNull(model); + + this.model = model; + + if (this.editor) { + this.editor.render(); + } + + if (this.diagram) { + this.diagram.render(); + } + } + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Get Model wrapper. + * @returns Model. + */ + getModel() { + return this.model; + } + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Get SVG element. + * @returns {*} + */ + getSVG() { + return this.diagram.getSVG(); + } + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Get top-level widget. Provides the demo toolbar with access to the public API. + * @returns {*} + */ + getSequencer() { + return this.sequencer; + } + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Present info dialog. + * @param msg info message. + */ + showInfoDialog(msg) { + this.dialog.showInfoDialog(msg); + } + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Present error dialog. + * @param msg error message. + */ + showErrorDialog(msg) { + this.dialog.showErrorDialog(msg); + } + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Present confirmation dialog. + * @param msg info message. + * @param cb callback function to be invoked on OK. + */ + showConfirmDialog(msg, cb) { + Common.assertType(cb, 'Function'); + this.dialog.showConfirmDialog(msg, cb); + } + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Present edit (textarea) dialog. + * @param msg prompt. + * @param text current edit text. + * @param cb callback function to be invoked on OK, taking the updated text + * as an argument. + */ + showEditDialog(msg, text, cb) { + this.dialog.showEditDialog(msg, text, cb); + } + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Select lifeline by ID. + * @param id lifeline ID. + */ + selectLifeline(id) { + if (this.editor) { + this.editor.selectLifeline(id); + } + if (this.diagram) { + this.diagram.selectLifeline(id); + } + } + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Select message by ID. + * @param id message ID. + */ + selectMessage(id) { + if (this.editor) { + this.editor.selectMessage(id); + } + if (this.diagram) { + this.diagram.selectMessage(id); + } + } + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * (Re)render just the diagram. + */ + renderDiagram() { + this.diagram.redraw(); + } + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Show overlay between application and modal dialog. + */ + showOverlay() { + if (this.overlay) { + this.overlay.setVisible(true); + } + } + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Hide overlay between application and modal dialog. + */ + hideOverlay() { + if (this.overlay) { + this.overlay.setVisible(false); + } + } + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Capture mouse move events, for resize. + * @param event move event. + */ + onMouseMove(event) { + if (this.editor) { + this.editor.onMouseMove(event); + } + } + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Propagate mouse event to the editor that manages the resize. + */ + onMouseUp() { + if (this.editor) { + this.editor.onMouseUp(); + } + } + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Render current model state. + */ + render() { + + return ( + +
+ + { this.editor = r; }} /> + { this.diagram = r; }} /> + { this.dialog = r; }} /> + + { this.overlay = r; }} /> + +
+ ); + } + +} + +/** React properties. */ +Application.propTypes = { + options: React.PropTypes.object.isRequired, + sequencer: React.PropTypes.object.isRequired, +}; diff --git a/dox-sequence-diagram-ui/src/main/webapp/lib/ecomp/asdc/sequencer/components/diagram/Diagram.jsx b/dox-sequence-diagram-ui/src/main/webapp/lib/ecomp/asdc/sequencer/components/diagram/Diagram.jsx new file mode 100644 index 0000000000..f2da7a5a1b --- /dev/null +++ b/dox-sequence-diagram-ui/src/main/webapp/lib/ecomp/asdc/sequencer/components/diagram/Diagram.jsx @@ -0,0 +1,896 @@ + +import React from 'react'; +import _template from 'lodash/template'; +import _merge from 'lodash/merge'; +import d3 from 'd3'; + +import Common from '../../common/Common'; +import Logger from '../../common/Logger'; +import Popup from './components/popup/Popup'; + +/** + * SVG diagram view. + */ +export default class Diagram 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.options = this.application.getOptions().diagram; + + this.events = {}; + this.state = { + height: 0, + width: 0, + }; + + this.templates = { + diagram: _template(require('./templates/diagram.html')), + lifeline: _template(require('./templates/lifeline.html')), + message: _template(require('./templates/message.html')), + occurrence: _template(require('./templates/occurrence.html')), + fragment: _template(require('./templates/fragment.html')), + title: _template(require('./templates/title.html')), + }; + + this.handleResize = this.handleResize.bind(this); + } + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Set diagram name. + * @param n name. + */ + setName(n) { + this.svg.select('').text(n); + } + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Get SVG from diagram. + * @returns {*|string} + */ + getSVG() { + const svg = this.svg.node().outerHTML; + return svg.replace(' 0) ? `height:${titleHeight}` : 'asdcs-hidden'; + + return ( +
+
{name}
+
{ this.wrapper = r; }}>
+ { this.popup = r; }} /> +
+ ); + } + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + redraw() { + this.updateSVG(); + } + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Initial render. + */ + componentDidMount() { + window.addEventListener('resize', this.handleResize); + this.updateSVG(); + + // Insurance: + + setTimeout(() => { + this.handleResize(); + }, 500); + } + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + componentWillUnmount() { + window.removeEventListener('resize', this.handleResize); + } + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Render on update. + */ + componentDidUpdate() { + this.updateSVG(); + } + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Redraw SVG diagram. So far it's fast enough that it doesn't seem to matter whether + * it's completely redrawn. + */ + updateSVG() { + + if (!this.svg) { + const svgparams = _merge({}, this.options.svg); + this.wrapper.innerHTML = this.templates.diagram(svgparams); + this.svg = d3.select(this.wrapper).select('svg'); + } + + if (this.state.height === 0) { + + // We'll get a resize event, and the height will be non-zero when it's actually time. + + return; + } + + if (this.state.height && this.state.width) { + const margin = this.options.svg.margin; + const x = -margin; + const y = -margin; + const height = this.state.height + (margin * 2); + const width = this.state.width + (margin * 2); + const viewBox = `${x} ${y} ${width} ${height}`; + this.svg.attr('viewBox', viewBox); + } + + + // If we've already rendered, then save the current scale/translate so that we + // can reapply it after rendering. + + const gContentSelection = this.svg.selectAll('g.asdcs-diagram-content'); + if (gContentSelection.size() === 1) { + const transform = gContentSelection.attr('transform'); + if (transform) { + this.savedTransform = transform; + } + } + + // Empty the document. We're starting again. + + this.svg.selectAll('.asdcs-diagram-content').remove(); + + // Extract the model. + + const model = this.application.getModel(); + if (!model) { + return; + } + const modelJSON = model.unwrap(); + + // Extract dimension options. + + const header = this.options.lifelines.header; + const spacing = this.options.lifelines.spacing; + + // Make separate container elements so that we can control Z order. + + const gContent = this.svg.append('g').attr('class', 'asdcs-diagram-content'); + const gLifelines = gContent.append('g').attr('class', 'asdcs-diagram-lifelines'); + const gCanvas = gContent.append('g').attr('class', 'asdcs-diagram-canvas'); + gCanvas.append('g').attr('class', 'asdcs-diagram-occurrences'); + gCanvas.append('g').attr('class', 'asdcs-diagram-fragments'); + gCanvas.append('g').attr('class', 'asdcs-diagram-messages'); + + // Lifelines ----------------------------------------------------------------------------------- + + const actorsById = {}; + const positionsByMessageId = {}; + const lifelines = []; + for (const actor of modelJSON.diagram.lifelines) { + const x = (header.width / 2) + (lifelines.length * spacing.horizontal); + Diagram._processLifeline(actor, x); + lifelines.push({ x, actor }); + actorsById[actor.id] = actor; + } + + // Messages ------------------------------------------------------------------------------------ + + // Analyze occurrence information. + + const occurrences = model.analyzeOccurrences(); + const fragments = model.analyzeFragments(); + let y = this.options.lifelines.header.height + spacing.vertical; + let messageIndex = 0; + for (const step of modelJSON.diagram.steps) { + if (step.message) { + positionsByMessageId[step.message.id] = positionsByMessageId[step.message.id] || {}; + positionsByMessageId[step.message.id].y = y; + this._drawMessage(gCanvas, step.message, y, actorsById, + positionsByMessageId, ++messageIndex, occurrences, fragments); + } + y += spacing.vertical; + } + + // --------------------------------------------------------------------------------------------- + + // Draw the actual (dashed) lifelines in a background . + + this._drawLifelines(gLifelines, lifelines, y); + + // Initialize mouse event handlers. + + this._initMouseEvents(gLifelines, gCanvas); + + // Scale to fit. + + const bb = gContent.node().getBBox(); + this._initZoom(gContent, bb.width, bb.height); + } + + // /////////////////////////////////////////////////////////////////////////////////////////////// + // /////////////////////////////////////////////////////////////////////////////////////////////// + // /////////////////////////////////////////////////////////////////////////////////////////////// + // /////////////////////////////////////////////////////////////////////////////////////////////// + // /////////////////////////////////////////////////////////////////////////////////////////////// + // /////////////////////////////////////////////////////////////////////////////////////////////// + // /////////////////////////////////////////////////////////////////////////////////////////////// + // /////////////////////////////////////////////////////////////////////////////////////////////// + // /////////////////////////////////////////////////////////////////////////////////////////////// + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Draw message into SVG canvas. + * @param gCanvas container. + * @param message message to be rendered. + * @param y current y position. + * @param actorsById actor lookup. + * @param positionsByMessageId x- and y-position of each message. + * @param messageIndex where we are in the set of messages to be rendered. + * @param oData occurrences info. + * @param fData fragments info. + * @private + */ + _drawMessage(gCanvas, message, y, actorsById, positionsByMessageId, + messageIndex, oData, fData) { + + Common.assertNotNull(oData); + + const request = message.type === 'request'; + const fromActor = request ? actorsById[message.from] : actorsById[message.to]; + const toActor = request ? actorsById[message.to] : actorsById[message.from]; + + if (!fromActor) { + Logger.warn(`Cannot draw message ${JSON.stringify(message)}: 'from' not found.`); + return; + } + + if (!toActor) { + Logger.warn(`Cannot draw message ${JSON.stringify(message)}: 'to' not found.`); + return; + } + + // Occurrences. -------------------------------------------------------------------------------- + + if (message.occurrence) { + Logger.debug(`Found occurrence for ${message.name}: ${JSON.stringify(message.occurrence)}`); + } + const activeTo = Diagram._calcActive(oData, toActor.id); + this._drawOccurrence(gCanvas, oData, positionsByMessageId, fromActor, message.id); + this._drawOccurrence(gCanvas, oData, positionsByMessageId, toActor, message.id); + const activeFrom = Diagram._calcActive(oData, fromActor.id); + + // Messages. ----------------------------------------------------------------------------------- + + const gMessages = gCanvas.select('g.asdcs-diagram-messages'); + + // Save positions for later. + + const positions = positionsByMessageId[message.id]; + positions.x0 = fromActor.x; + positions.x1 = toActor.x; + + // Calculate. + + const leftToRight = fromActor.x < toActor.x; + const loopback = (message.to === message.from); + const x1 = this._calcMessageX(activeTo, toActor.x, true, leftToRight); + const x0 = loopback ? x1 : this._calcMessageX(activeFrom, fromActor.x, false, leftToRight); + + let messagePath; + if (loopback) { + + // To self. + + messagePath = `M${x1},${y}`; + messagePath = `${messagePath} L${x1 + 200},${y}`; + messagePath = `${messagePath} L${x1 + 200},${y + 50}`; + messagePath = `${messagePath} L${x1},${y + 50}`; + } else { + + // Between lifelines. + + messagePath = `M${x0},${y}`; + messagePath = `${messagePath} L${x1},${y}`; + } + + const styles = Diagram._getMessageStyles(message); + + // Split message over lines. + + const messageWithPrefix = `${messageIndex}. ${message.name}`; + const maxLines = this.options.messages.label.maxLines; + const wrapWords = this.options.messages.label.wrapWords; + const wrapLines = this.options.messages.label.wrapLines; + const messageLines = Common.tokenize(messageWithPrefix, wrapWords, wrapLines, maxLines); + + const messageTxt = this.templates.message({ + id: message.id, + classes: styles.css, + marker: styles.marker, + dasharray: styles.dasharray, + labels: messageLines, + lines: maxLines, + path: messagePath, + index: messageIndex, + x0, x1, y, + }); + + const messageEl = Common.txt2dom(messageTxt); + const gMessage = gMessages.append('g'); + Common.dom2svg(messageEl, gMessage); + + // Set the background's bounding box to that of the text, + // so that they fit snugly. + + const labelBB = gMessage.select('.asdcs-diagram-message-label').node().getBBox(); + gMessage.select('.asdcs-diagram-message-label-bg') + .attr('x', labelBB.x) + .attr('y', labelBB.y) + .attr('height', labelBB.height) + .attr('width', labelBB.width); + + // Fragments. ---------------------------------------------------------------------------------- + + const fragment = fData[message.id]; + if (fragment) { + + // It ends on this message. + + this._drawFragment(gCanvas, fragment, positionsByMessageId); + + } + } + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Draw a single occurrence. + * @param gCanvas container. + * @param oData occurrence data. + * @param positionsByMessageId map of y positions by message ID. + * @param actor wrapper containing lifeline ID (.id), position (.x) and name (.name). + * @param messageId message identifier. + * @private + */ + _drawOccurrence(gCanvas, oData, positionsByMessageId, actor, messageId) { + + Common.assertType(oData, 'Object'); + Common.assertType(positionsByMessageId, 'Object'); + Common.assertType(actor, 'Object'); + Common.assertType(messageId, 'String'); + + const gOccurrences = gCanvas.select('g.asdcs-diagram-occurrences'); + + const oOptions = this.options.lifelines.occurrences; + const oWidth = oOptions.width; + const oHalfWidth = oWidth / 2; + const oForeshortening = oOptions.foreshortening; + const oMarginTop = oOptions.marginTop; + const oMarginBottom = oOptions.marginBottom; + const o = oData[actor.id]; + + const active = Diagram._calcActive(oData, actor.id); + + const x = (actor.x - oHalfWidth) + (active * oWidth); + const positions = positionsByMessageId[messageId]; + const y = positions.y; + + let draw = true; + if (o) { + + if (o.start[messageId]) { + + // Starting, but drawing nothing until we find the end. + + o.active.push(messageId); + draw = false; + + } else if (active > 0) { + + const startMessageId = o.stop[messageId]; + if (startMessageId) { + + // OK, it ends here. Draw the occurrence box. + + o.active.pop(); + const foreshorteningY = active * oForeshortening; + const startY = positionsByMessageId[startMessageId].y; + const height = ((oMarginTop + oMarginBottom) + (y - startY)) - (foreshorteningY * 2); + const oProps = { + x: (actor.x - oHalfWidth) + ((active - 1) * oWidth), + y: ((startY - oMarginTop) + foreshorteningY), + height, + width: oWidth, + }; + + const occurrenceTxt = this.templates.occurrence(oProps); + const occurrenceEl = Common.txt2dom(occurrenceTxt); + Common.dom2svg(occurrenceEl, gOccurrences.append('g')); + + } + draw = false; + } + } + + if (draw) { + + // Seems this is a singleton occurrence. We just draw a wee box around it. + + const height = (oMarginTop + oMarginBottom); + const occurrenceProperties = { x, y: y - oMarginTop, height, width: oWidth }; + const defaultTxt = this.templates.occurrence(occurrenceProperties); + const defaultEl = Common.txt2dom(defaultTxt); + Common.dom2svg(defaultEl, gOccurrences.append('g')); + } + + } + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Draw box(es) around fragment(s). + * @param gCanvas container. + * @param fragment fragment definition, corresponding to its final (stop) message. + * @param positionsByMessageId message dimensions. + * @private + */ + _drawFragment(gCanvas, fragment, positionsByMessageId) { + + const optFragments = this.options.fragments; + const gFragments = gCanvas.select('g.asdcs-diagram-fragments'); + const p1 = positionsByMessageId[fragment.stop]; + if (p1 && fragment.start && fragment.start.length > 0) { + + for (const start of fragment.start) { + + const message = this.application.getModel().getMessageById(start); + const bounds = this._calcFragmentBounds(message, fragment, positionsByMessageId); + if (bounds) { + + const maxLines = this.options.fragments.label.maxLines; + const wrapWords = this.options.fragments.label.wrapWords; + const wrapLines = this.options.fragments.label.wrapLines; + const lines = Common.tokenize(message.fragment.guard, wrapWords, wrapLines, maxLines); + + const params = { + id: start, + x: bounds.x0 - optFragments.leftMargin, + y: bounds.y0 - optFragments.topMargin, + height: (bounds.y1 - bounds.y0) + optFragments.heightMargin, + width: (bounds.x1 - bounds.x0) + optFragments.widthMargin, + operator: (message.fragment.operator || 'alt'), + lines, + }; + + const fragmentTxt = this.templates.fragment(params); + const fragmentEl = Common.txt2dom(fragmentTxt); + const gFragment = gFragments.append('g'); + Common.dom2svg(fragmentEl, gFragment); + + const labelBB = gFragment.select('.asdcs-diagram-fragment-guard').node().getBBox(); + gFragment.select('.asdcs-diagram-fragment-guard-bg') + .attr('x', labelBB.x) + .attr('y', labelBB.y) + .attr('height', labelBB.height) + .attr('width', labelBB.width); + + } else { + Logger.warn(`Bad fragment: ${JSON.stringify(fragment)}`); + } + } + } + } + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + _calcFragmentBounds(startMessage, fragment, positionsByMessageId) { + if (startMessage) { + const steps = this.application.getModel().unwrap().diagram.steps; + const bounds = { x0: 99999, x1: 0, y0: 99999, y1: 0 }; + let foundStart = false; + let foundStop = false; + for (const step of steps) { + const message = step.message; + if (message) { + if (message.id === startMessage.id) { + foundStart = true; + } + if (foundStart && !foundStop) { + const positions = positionsByMessageId[message.id]; + if (positions) { + bounds.x0 = Math.min(bounds.x0, Math.min(positions.x0, positions.x1)); + bounds.y0 = Math.min(bounds.y0, positions.y); + bounds.x1 = Math.max(bounds.x1, Math.max(positions.x0, positions.x1)); + bounds.y1 = Math.max(bounds.y1, positions.y); + } else { + // This probably means it hasn't been recorded yet, which is fine, because + // we draw fragments from where they END. + foundStop = true; + } + } + + if (message.id === fragment.stop) { + foundStop = true; + } + } + } + return bounds; + } + return undefined; + } + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Draw all lifelines. + * @param gLifelines lifelines container. + * @param lifelines lifelines definitions. + * @param y height. + * @private + */ + _drawLifelines(gLifelines, lifelines, y) { + + const maxLines = this.options.lifelines.header.maxLines; + const wrapWords = this.options.lifelines.header.wrapWords; + const wrapLines = this.options.lifelines.header.wrapLines; + + for (const lifeline of lifelines) { + const lines = Common.tokenize(lifeline.actor.name, wrapWords, wrapLines, maxLines); + const lifelineTxt = this.templates.lifeline({ + x: lifeline.x, + y0: 0, + y1: y, + lines, + rows: maxLines, + headerHeight: this.options.lifelines.header.height, + headerWidth: this.options.lifelines.header.width, + id: lifeline.actor.id, + }); + + const lifelineEl = Common.txt2dom(lifelineTxt); + Common.dom2svg(lifelineEl, gLifelines.append('g')); + } + } + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Initialize all mouse events. + * @param gLifelines lifelines container. + * @param gCanvas top-level canvas container. + * @private + */ + _initMouseEvents(gLifelines, gCanvas) { + + const self = this; + const source = 'asdcs'; + const origin = `${window.location.protocol}//${window.location.host}`; + + let timer; + gLifelines.selectAll('.asdcs-diagram-lifeline-selectable') + .on('mouseenter', function f() { + timer = setTimeout(() => { + self.application.selectLifeline(d3.select(this.parentNode).attr('data-id')); + }, 150); + }) + .on('mouseleave', () => { + clearTimeout(timer); + self.application.selectLifeline(); + }) + .on('click', function f() { + const id = d3.select(this.parentNode).attr('data-id'); + window.postMessage({ source, id, type: 'lifeline' }, origin); + }); + + gLifelines.selectAll('.asdcs-diagram-lifeline-heading-box') + .on('mouseenter', function f() { + timer = setTimeout(() => { + self.application.selectLifeline(d3.select(this.parentNode).attr('data-id')); + }, 150); + }) + .on('mouseleave', () => { + clearTimeout(timer); + self.application.selectLifeline(); + }) + .on('click', function f() { + const id = d3.select(this.parentNode).attr('data-id'); + window.postMessage({ source, id, type: 'lifelineHeader' }, origin); + }); + + gCanvas.selectAll('.asdcs-diagram-message-selectable') + .on('mouseenter', function f() { + self.events.message = { x: d3.event.pageX, y: d3.event.pageY }; + timer = setTimeout(() => { + self.application.selectMessage(d3.select(this.parentNode).attr('data-id')); + }, 200); + }) + .on('mouseleave', () => { + delete self.events.message; + clearTimeout(timer); + self.application.selectMessage(); + }) + .on('click', function f() { + const id = d3.select(this.parentNode).attr('data-id'); + window.postMessage({ source, id, type: 'message' }, origin); + }); + + } + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Get CSS classes to be applied to a message, according to whether request/response + * or synchronous/asynchronous. + * @param message message being rendered. + * @returns CSS class name(s). + * @private + */ + static _getMessageStyles(message) { + + let marker = 'asdcsDiagramArrowSolid'; + let dasharray = ''; + let css = 'asdcs-diagram-message'; + if (message.type === 'request') { + css = `${css} asdcs-diagram-message-request`; + } else { + css = `${css} asdcs-diagram-message-response`; + marker = 'asdcsDiagramArrowOpen'; + dasharray = '30, 10'; + } + + if (message.asynchronous) { + css = `${css} asdcs-diagram-message-asynchronous`; + marker = 'asdcsDiagramArrowOpen'; + } else { + css = `${css} asdcs-diagram-message-synchronous`; + } + + return { css, marker, dasharray }; + } + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Initialize or reinitialize zoom. This sets the initial zoom in the case of + * a re-rendering, and initializes the eventhandling in all cases. + * + * It does some fairly risky parsing of the 'transform' attribute, assuming that it + * can contain scale() and translate(). But only the zoom handler and us are writing + * the transform values, so that's probably OK. + * + * @param gContent container. + * @param width diagram width. + * @param height diagram height. + * @private + */ + _initZoom(gContent, width, height) { + + const zoomed = function zoomed() { + gContent.attr('transform', + `translate(${d3.event.translate})scale(${d3.event.scale})`); + }; + + const viewWidth = this.state.width || this.options.svg.width; + const viewHeight = this.state.height || this.options.svg.height; + const scaleMinimum = this.options.svg.scale.minimum; + const scaleWidth = viewWidth / width; + const scaleHeight = viewHeight / height; + + let scale = scaleMinimum; + if (this.options.svg.scale.width) { + scale = Math.max(scale, scaleWidth); + } + if (this.options.svg.scale.height) { + scale = Math.min(scale, scaleHeight); + } + + scale = Math.max(scale, scaleMinimum); + + let translate = [0, 0]; + if (this.savedTransform) { + const s = this.savedTransform; + const scaleStart = s.indexOf('scale('); + if (scaleStart !== -1) { + scale = parseFloat(s.substring(scaleStart + 6, s.length - 1)); + } + const translateStart = s.indexOf('translate('); + if (translateStart !== -1) { + const spec = s.substring(translateStart + 10, s.indexOf(')', translateStart)); + const tokens = spec.split(','); + translate = [parseFloat(tokens[0]), parseFloat(tokens[1])]; + } + + gContent.attr('transform', this.savedTransform); + } else { + gContent.attr('transform', `scale(${scale})`); + } + + const zoom = d3.behavior.zoom() + .scale(scale) + .scaleExtent([scaleMinimum, 10]) + .translate(translate) + .on('zoom', zoomed); + this.svg.call(zoom); + } + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Hide from the linter the fact that we're modifying the lifeline. + * @param lifeline to be updated with X position. + * @param x X position. + * @private + */ + static _processLifeline(lifeline, x) { + const actor = lifeline; + actor.id = actor.id || actor.name; + actor.x = x; + } + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Derive active occurrences for lifeline. + * @param oData occurrences data. + * @param lifelineId lifeline to be analyzed. + * @returns {number} + * @private + */ + static _calcActive(oData, lifelineId) { + const o = oData[lifelineId]; + let active = 0; + if (o && o.active) { + active = o.active.length; + } + return active; + } + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Derive the X position of an occurrence on a lifeline, taking into account how + * many occurrences are active. + * @param active active count. + * @param x lifeline X position; basis for offset. + * @param arrow whether this is the arrow (to) end. + * @param leftToRight whether this message goes left-to-right. + * @returns {*} calculated X position for occurrence left-hand side. + * @private + */ + _calcMessageX(active, x, arrow, leftToRight) { + const width = this.options.lifelines.occurrences.width; + const halfWidth = width / 2; + const active0 = Math.max(0, active - 1); + let calculated = x + (active0 * width); + if (arrow) { + // End (ARROW). + if (leftToRight) { + calculated -= halfWidth; + } else { + calculated += halfWidth; + } + } else { + // Start (NOT ARROW). + if (leftToRight) { + calculated += halfWidth; + } else { + calculated -= halfWidth; + } + } + + return calculated; + } + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Show popup upon hovering over a messages that has associated notes. + * @param id + * @private + */ + _showNotesPopup(id) { + if (this.popup) { + if (id) { + const message = this.application.getModel().getMessageById(id); + if (message && message.notes && message.notes.length > 0 && this.events.message) { + this.popup.setState({ + visible: true, + left: this.events.message.x - 50, + top: this.events.message.y + 20, + notes: message.notes[0], + }); + } + } else { + this.popup.setState({ visible: false, notes: '' }); + } + } + } +} + + +Diagram.propTypes = { + application: React.PropTypes.object.isRequired, +}; diff --git a/dox-sequence-diagram-ui/src/main/webapp/lib/ecomp/asdc/sequencer/components/diagram/components/popup/Popup.jsx b/dox-sequence-diagram-ui/src/main/webapp/lib/ecomp/asdc/sequencer/components/diagram/components/popup/Popup.jsx new file mode 100644 index 0000000000..08c6da1e76 --- /dev/null +++ b/dox-sequence-diagram-ui/src/main/webapp/lib/ecomp/asdc/sequencer/components/diagram/components/popup/Popup.jsx @@ -0,0 +1,94 @@ + + +import React from 'react'; + +import Icon from '../../../icons/Icon'; +import iconEdit from '../../../../../../../../res/ecomp/asdc/sequencer/sprites/icon/edit.svg'; + +/** + * A hover-over popup. It shows notes, but perhaps will be put to other uses. + * @param props React properties. + * @returns {XML} + * @constructor + */ +export default class Popup extends React.Component { + + // /////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Construct react view. + * @param props element properties (of which there are none). + * @param context react context. + */ + constructor(props, context) { + super(props, context); + this.state = { + top: 0, + left: 0, + visible: false, + notes: '', + }; + } + + // /////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Render view. + * @returns {XML} + */ + render() { + + // Build CSS + styles to position and configure popup. + + let top = this.state.top; + let left = this.state.left; + + const popupHeight = 200; + const popupWidth = 320; + + let auxCssVertical = 'top'; + let auxCssHorizontal = 'left'; + + if (this.state.top > (window.innerHeight - popupHeight)) { + top -= (popupHeight + 50); + auxCssVertical = 'bottom'; + } + + if (this.state.left > (window.innerWidth - popupWidth)) { + left -= (popupWidth - 80); + auxCssHorizontal = 'right'; + } + + const auxCss = `asdcs-diagram-popup-${auxCssVertical}${auxCssHorizontal}`; + const styles = { + top, + left, + display: (this.state.visible ? 'block' : 'none'), + }; + + // Render element. + + let notes = this.state.notes || ''; + if (notes.length > 255) { + notes = notes.substring(0, 255); + notes = `${notes} ...`; + } + + return ( +
+
Notes
+
+
+ +
+
+
+ {notes} +
+
+
+
+
+ ); + } +} diff --git a/dox-sequence-diagram-ui/src/main/webapp/lib/ecomp/asdc/sequencer/components/diagram/templates/diagram.html b/dox-sequence-diagram-ui/src/main/webapp/lib/ecomp/asdc/sequencer/components/diagram/templates/diagram.html new file mode 100644 index 0000000000..22893ce864 --- /dev/null +++ b/dox-sequence-diagram-ui/src/main/webapp/lib/ecomp/asdc/sequencer/components/diagram/templates/diagram.html @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dox-sequence-diagram-ui/src/main/webapp/lib/ecomp/asdc/sequencer/components/diagram/templates/fragment.html b/dox-sequence-diagram-ui/src/main/webapp/lib/ecomp/asdc/sequencer/components/diagram/templates/fragment.html new file mode 100644 index 0000000000..812f5fcfb8 --- /dev/null +++ b/dox-sequence-diagram-ui/src/main/webapp/lib/ecomp/asdc/sequencer/components/diagram/templates/fragment.html @@ -0,0 +1,18 @@ + + + + + + + <%-operator%> + + + + <% + for (var lineIndex = 0; lineIndex < lines.length ; lineIndex++) { + %><%- lines[lineIndex] %><% + }%> + + diff --git a/dox-sequence-diagram-ui/src/main/webapp/lib/ecomp/asdc/sequencer/components/diagram/templates/lifeline.html b/dox-sequence-diagram-ui/src/main/webapp/lib/ecomp/asdc/sequencer/components/diagram/templates/lifeline.html new file mode 100644 index 0000000000..cd01d42c5a --- /dev/null +++ b/dox-sequence-diagram-ui/src/main/webapp/lib/ecomp/asdc/sequencer/components/diagram/templates/lifeline.html @@ -0,0 +1,19 @@ + + + + + <% + for (var linesIndex = 0; linesIndex < lines.length && linesIndex < rows ; linesIndex++) { + %><%- lines[linesIndex] %><% + } + %> + + + + + + + + diff --git a/dox-sequence-diagram-ui/src/main/webapp/lib/ecomp/asdc/sequencer/components/diagram/templates/message.html b/dox-sequence-diagram-ui/src/main/webapp/lib/ecomp/asdc/sequencer/components/diagram/templates/message.html new file mode 100644 index 0000000000..bd4c33a016 --- /dev/null +++ b/dox-sequence-diagram-ui/src/main/webapp/lib/ecomp/asdc/sequencer/components/diagram/templates/message.html @@ -0,0 +1,29 @@ + + + <% + var delta = 40; + var x = (x0 + x1) / 2; + var y0 = y - ((labels.length + 1) * delta); + %> + + + + <% + for (var labelIndex = 0; labelIndex < labels.length && labelIndex < lines ; labelIndex++) { + %><%- labels[labelIndex] %><% + }%> + + + + + + + + + diff --git a/dox-sequence-diagram-ui/src/main/webapp/lib/ecomp/asdc/sequencer/components/diagram/templates/occurrence.html b/dox-sequence-diagram-ui/src/main/webapp/lib/ecomp/asdc/sequencer/components/diagram/templates/occurrence.html new file mode 100644 index 0000000000..0af9ff3d68 --- /dev/null +++ b/dox-sequence-diagram-ui/src/main/webapp/lib/ecomp/asdc/sequencer/components/diagram/templates/occurrence.html @@ -0,0 +1,7 @@ + + + diff --git a/dox-sequence-diagram-ui/src/main/webapp/lib/ecomp/asdc/sequencer/components/diagram/templates/title.html b/dox-sequence-diagram-ui/src/main/webapp/lib/ecomp/asdc/sequencer/components/diagram/templates/title.html new file mode 100644 index 0000000000..b7a5d68a6d --- /dev/null +++ b/dox-sequence-diagram-ui/src/main/webapp/lib/ecomp/asdc/sequencer/components/diagram/templates/title.html @@ -0,0 +1,3 @@ + + <%-title%> + diff --git a/dox-sequence-diagram-ui/src/main/webapp/lib/ecomp/asdc/sequencer/components/dialog/Dialog.jsx b/dox-sequence-diagram-ui/src/main/webapp/lib/ecomp/asdc/sequencer/components/dialog/Dialog.jsx new file mode 100644 index 0000000000..4429d80bc6 --- /dev/null +++ b/dox-sequence-diagram-ui/src/main/webapp/lib/ecomp/asdc/sequencer/components/dialog/Dialog.jsx @@ -0,0 +1,222 @@ + + +import React from 'react'; + +import Icon from '../icons/Icon'; +import iconQuestion from '../../../../../../res/ecomp/asdc/sequencer/sprites/icon/question.svg'; +import iconExclaim from '../../../../../../res/ecomp/asdc/sequencer/sprites/icon/exclaim.svg'; +import iconInfo from '../../../../../../res/ecomp/asdc/sequencer/sprites/icon/info.svg'; +import iconEdit from '../../../../../../res/ecomp/asdc/sequencer/sprites/icon/edit.svg'; +import iconClose from '../../../../../../res/ecomp/asdc/sequencer/sprites/icon/close.svg'; + +/** + * Multi-purpose dialog. Rendered into the page on initialization, and then + * configured, shown and hidden as required. + */ +export default class Dialog extends React.Component { + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Construct view. + */ + constructor(props, context) { + + super(props, context); + + this.MODE = { + INFO: { + icon: 'asdcs-icon-info', + heading: 'Information', + }, + ERROR: { + icon: 'asdcs-icon-exclaim', + heading: 'Error', + }, + EDIT: { + icon: 'asdcs-icon-edit', + heading: 'Edit', + edit: true, + confirm: true, + }, + CONFIRM: { + icon: 'asdcs-icon-question', + heading: 'Confirm', + confirm: true, + }, + }; + + this.state = { + mode: this.MODE.INFO, + message: '', + text: '', + visible: false, + }; + + // Bindings. + + this.onClickOK = this.onClickOK.bind(this); + this.onClickCancel = this.onClickCancel.bind(this); + this.onChangeText = this.onChangeText.bind(this); + this.showConfirmDialog = this.showConfirmDialog.bind(this); + this.showInfoDialog = this.showInfoDialog.bind(this); + this.showEditDialog = this.showEditDialog.bind(this); + this.showErrorDialog = this.showErrorDialog.bind(this); + this.showDialog = this.showDialog.bind(this); + + } + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Show info dialog. + * @param message info message. + */ + showInfoDialog(message) { + this.showDialog(this.MODE.INFO, { message }); + } + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Show error dialog. + * @param message error message. + */ + showErrorDialog(message) { + this.showDialog(this.MODE.ERROR, { message }); + } + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Show edit dialog. + * @param message dialog message. + * @param text current edit text. + * @param callback callback function to be invoked on OK. + */ + showEditDialog(message, text, callback) { + this.showDialog(this.MODE.EDIT, { message, text, callback }); + } + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Show confirmation dialog. + * @param message dialog message. + * @param callback callback function to be invoked on OK. + */ + showConfirmDialog(message, callback) { + this.showDialog(this.MODE.CONFIRM, { message, callback }); + } + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Handle buttonclick. + */ + onClickOK() { + this.props.application.hideOverlay(); + this.setState({ visible: false }); + if (this.callback) { + + // So far the only thing we can return is edit text, but send it back + // as properties to allow for future return values. + + this.callback({ text: this.state.text }); + } + } + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Handle buttonclick. + */ + onClickCancel() { + this.props.application.hideOverlay(); + this.setState({ visible: false }); + } + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Handle text changes. + * @param event update event. + */ + onChangeText(event) { + this.setState({ text: event.target.value }); + } + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Show dialog in specified configuration. + * @param mode dialog mode. + * @param args dialog parameters, varying slightly by dialog type. + * @private + */ + showDialog(mode, args) { + this.props.application.showOverlay(); + this.callback = args.callback; + this.setState({ + mode, + visible: true, + message: args.message || '', + text: args.text || '', + }); + } + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Render dialog into the page, initially hidden. + */ + render() { + + const dialogClass = (this.state.visible) ? '' : 'asdcs-hidden'; + const cancelClass = (this.callback) ? '' : 'asdcs-hidden'; + const textClass = (this.state.mode === this.MODE.EDIT) ? '' : 'asdcs-hidden'; + + return ( +
+
{this.state.mode.heading}
+
+ +
+
+ + + + +
+
+ {this.state.message} +
+
+ +
+ ); + } +} + +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 ( +
+
+ + + + + + +
+
+ + + +
+
+ ); + } + + // /////////////////////////////////////////////////////////////////////////////////////////////// + // /////////////////////////////////////////////////////////////////////////////////////////////// + // /////////////////////////////////////////////////////////////////////////////////////////////// + // /////////////////////////////////////////////////////////////////////////////////////////////// + // /////////////////////////////////////////////////////////////////////////////////////////////// + // /////////////////////////////////////////////////////////////////////////////////////////////// + // /////////////////////////////////////////////////////////////////////////////////////////////// + // /////////////////////////////////////////////////////////////////////////////////////////////// + // /////////////////////////////////////////////////////////////////////////////////////////////// + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * 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 = $('').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, +}; diff --git a/dox-sequence-diagram-ui/src/main/webapp/lib/ecomp/asdc/sequencer/components/export/Export.jsx b/dox-sequence-diagram-ui/src/main/webapp/lib/ecomp/asdc/sequencer/components/export/Export.jsx new file mode 100644 index 0000000000..529ae92ded --- /dev/null +++ b/dox-sequence-diagram-ui/src/main/webapp/lib/ecomp/asdc/sequencer/components/export/Export.jsx @@ -0,0 +1,31 @@ +/*! + * 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'; + +const Export = function Export() { + return ( +
+ + + + + +
+ ); +}; + +export default Export; diff --git a/dox-sequence-diagram-ui/src/main/webapp/lib/ecomp/asdc/sequencer/components/icons/Icon.jsx b/dox-sequence-diagram-ui/src/main/webapp/lib/ecomp/asdc/sequencer/components/icons/Icon.jsx new file mode 100644 index 0000000000..6bc04f997f --- /dev/null +++ b/dox-sequence-diagram-ui/src/main/webapp/lib/ecomp/asdc/sequencer/components/icons/Icon.jsx @@ -0,0 +1,41 @@ +/*! + * 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'; + +/** + * Simple icon view. + * @param glyph glyph definition, from import. + * @param className optional classname, for svg element. + * @returns {XML} + * @constructor + */ +const Icon = function Icon({ glyph, className }) { + return ( + + + + ); +}; + +/** Declare properties. */ +Icon.propTypes = { + className: React.PropTypes.string, + glyph: React.PropTypes.string.isRequired, +}; + +export default Icon; + diff --git a/dox-sequence-diagram-ui/src/main/webapp/lib/ecomp/asdc/sequencer/components/overlay/Overlay.jsx b/dox-sequence-diagram-ui/src/main/webapp/lib/ecomp/asdc/sequencer/components/overlay/Overlay.jsx new file mode 100644 index 0000000000..817f4f1697 --- /dev/null +++ b/dox-sequence-diagram-ui/src/main/webapp/lib/ecomp/asdc/sequencer/components/overlay/Overlay.jsx @@ -0,0 +1,61 @@ +/*! + * 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'; + +/** + * Overlay view. + */ +export default class Overlay extends React.Component { + + /** + * Construct view. + * @param props element properties. + * @param context react context. + */ + constructor(props, context) { + super(props, context); + this.state = { + visible: false, + }; + this.setVisible = this.setVisible.bind(this); + } + + /** + * Set visibility. + * @param visible true if visible. + */ + setVisible(visible) { + this.setState({ + visible, + }); + } + + /** + * Render view. + * @returns {XML} + */ + render() { + const display = this.state.visible ? 'block' : 'none'; + return ( +
+
+ ); + } +} -- cgit 1.2.3-korg