From 7fdf733a64670fceefc3ded35cfa581e1c458179 Mon Sep 17 00:00:00 2001 From: Einav Weiss Keidar Date: Tue, 20 Mar 2018 14:45:40 +0200 Subject: Adding Prettier and fixing up eslint version Issue-ID: SDC-1094 Change-Id: Ie83ad95a03899345dd90235daf0323cbe3bc6afd Signed-off-by: Einav Weiss Keidar --- .../webapp/lib/ecomp/asdc/sequencer/Sequencer.jsx | 307 ++-- .../lib/ecomp/asdc/sequencer/common/Common.js | 622 +++---- .../lib/ecomp/asdc/sequencer/common/Logger.js | 191 ++- .../lib/ecomp/asdc/sequencer/common/Options.js | 193 ++- .../components/application/Application.jsx | 450 ++--- .../asdc/sequencer/components/diagram/Diagram.jsx | 1729 +++++++++++--------- .../components/diagram/components/popup/Popup.jsx | 148 +- .../asdc/sequencer/components/dialog/Dialog.jsx | 405 ++--- .../asdc/sequencer/components/editor/Editor.jsx | 248 ++- .../editor/components/designer/Designer.jsx | 708 ++++---- .../designer/components/actions/Actions.jsx | 860 +++++----- .../designer/components/lifeline/Lifeline.jsx | 397 ++--- .../designer/components/lifeline/LifelineNew.jsx | 132 +- .../designer/components/lifeline/Lifelines.jsx | 189 ++- .../designer/components/message/Message.jsx | 1012 ++++++------ .../designer/components/message/MessageNew.jsx | 126 +- .../designer/components/message/Messages.jsx | 201 ++- .../designer/components/metadata/Metadata.jsx | 8 +- .../components/editor/components/source/Source.jsx | 90 +- .../editor/components/toolbar/Toolbar.jsx | 228 +-- .../asdc/sequencer/components/export/Export.jsx | 21 +- .../ecomp/asdc/sequencer/components/icons/Icon.jsx | 15 +- .../asdc/sequencer/components/overlay/Overlay.jsx | 65 +- .../lib/ecomp/asdc/sequencer/model/Metamodel.js | 116 +- .../lib/ecomp/asdc/sequencer/model/Metamodels.js | 97 +- .../webapp/lib/ecomp/asdc/sequencer/model/Model.js | 864 +++++----- .../sequencer/model/demo/scenarios/Scenarios.js | 181 +- .../src/main/webapp/lib/main.jsx | 14 +- 28 files changed, 4921 insertions(+), 4696 deletions(-) (limited to 'dox-sequence-diagram-ui/src/main/webapp') diff --git a/dox-sequence-diagram-ui/src/main/webapp/lib/ecomp/asdc/sequencer/Sequencer.jsx b/dox-sequence-diagram-ui/src/main/webapp/lib/ecomp/asdc/sequencer/Sequencer.jsx index fc2bfbc0e3..c0e9483e92 100644 --- a/dox-sequence-diagram-ui/src/main/webapp/lib/ecomp/asdc/sequencer/Sequencer.jsx +++ b/dox-sequence-diagram-ui/src/main/webapp/lib/ecomp/asdc/sequencer/Sequencer.jsx @@ -28,176 +28,177 @@ import '../../../../res/sdc-sequencer.scss'; * ASDC Sequencer entry point. */ class Sequencer extends React.Component { + // ////////////////////////////////////////////////////////////////////////////////////////////// - // ////////////////////////////////////////////////////////////////////////////////////////////// - - constructor(props, context) { - super(props, context); - - - this.setMetamodel.bind(this); - this.setModel.bind(this); - this.getModel.bind(this); - this.getMetamodel.bind(this); - this.getSVG.bind(this); - this.getDemoScenarios.bind(this); - this.newModel.bind(this); - - // Parse options. - - this.options = new Options(props.options); - - // Default scenarios. - - const scenarios = this.getDemoScenarios(); - this.setMetamodel(scenarios.getMetamodels()); - - // this.setModel(scenarios.getBlank()); - this.setModel(scenarios.getDimensions()); - // this.setModel(scenarios.getECOMP()); - - } - - // ////////////////////////////////////////////////////////////////////////////////////////////// - - /** - * Optionally save known metamodels so that subsequent loading and unloading - * of models needn't include the corresponding metamodel. - * @param metamodels array of conformant metamodel JSON definitions. - * @return this. - */ - setMetamodel(metamodels) { - Common.assertType(metamodels, 'Array'); - this.metamodels = new Metamodels(metamodels); - return this; - } - - // ////////////////////////////////////////////////////////////////////////////////////////////// - - /** - * Set current diagram. - * @param modelJSON JSON diagram spec. - * @param metamodelIdOrDefinition optional metamodel definition or reference. Defaults to - * the model's metadata @ref, or the default (permissive) metamodel. - * @return this. - */ - setModel(modelJSON, metamodelIdOrDefinition) { - Common.assertType(modelJSON, 'Object'); - const ref = (modelJSON.metadata) ? modelJSON.metadata.ref : undefined; - const metamodel = this.getMetamodel(metamodelIdOrDefinition || ref); - Common.assertInstanceOf(metamodel, Metamodel); - this.model = new Model(modelJSON, metamodel); - if (this.application) { - this.application.setModel(this.model); + constructor(props, context) { + super(props, context); + + this.setMetamodel.bind(this); + this.setModel.bind(this); + this.getModel.bind(this); + this.getMetamodel.bind(this); + this.getSVG.bind(this); + this.getDemoScenarios.bind(this); + this.newModel.bind(this); + + // Parse options. + + this.options = new Options(props.options); + + // Default scenarios. + + const scenarios = this.getDemoScenarios(); + this.setMetamodel(scenarios.getMetamodels()); + + // this.setModel(scenarios.getBlank()); + this.setModel(scenarios.getDimensions()); + // this.setModel(scenarios.getECOMP()); } - return this; - } - - // ////////////////////////////////////////////////////////////////////////////////////////////// - - /** - * Get current diagram state. At any given instant the diagram might not make *sense* - * but it should always be syntactically valid. - * @return current Model. - */ - getModel() { - - if (this.application) { - const model = this.application.getModel(); - if (model) { - return model.unwrap(); - } + + // ////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Optionally save known metamodels so that subsequent loading and unloading + * of models needn't include the corresponding metamodel. + * @param metamodels array of conformant metamodel JSON definitions. + * @return this. + */ + setMetamodel(metamodels) { + Common.assertType(metamodels, 'Array'); + this.metamodels = new Metamodels(metamodels); + return this; } - return this.model; - } - - // ////////////////////////////////////////////////////////////////////////////////////////////// - - /** - * Extract SVG element. - * @return stringified SVG element. - */ - getSVG() { - return this.application.getSVG(); - } - - // ////////////////////////////////////////////////////////////////////////////////////////////// - - /** - * Get demo scenarios, allowing initialization in demo mode from the outside. - * @returns {Scenarios} - */ - getDemoScenarios() { - return new Scenarios(); - } - - // ////////////////////////////////////////////////////////////////////////////////////////////// - - /** - * Create new model. - * @param metamodelIdOrDefinition - * @return newly-created model. - */ - newModel(metamodelIdOrDefinition) { - const metamodel = this.getMetamodel(metamodelIdOrDefinition); - Common.assertInstanceOf(metamodel, Metamodel); - const model = new Model({}, metamodel); - if (this.application) { - this.application.setModel(model); + // ////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Set current diagram. + * @param modelJSON JSON diagram spec. + * @param metamodelIdOrDefinition optional metamodel definition or reference. Defaults to + * the model's metadata @ref, or the default (permissive) metamodel. + * @return this. + */ + setModel(modelJSON, metamodelIdOrDefinition) { + Common.assertType(modelJSON, 'Object'); + const ref = modelJSON.metadata ? modelJSON.metadata.ref : undefined; + const metamodel = this.getMetamodel(metamodelIdOrDefinition || ref); + Common.assertInstanceOf(metamodel, Metamodel); + this.model = new Model(modelJSON, metamodel); + if (this.application) { + this.application.setModel(this.model); + } + return this; } - return model; - } - - // ////////////////////////////////////////////////////////////////////////////////////////////// - - /** - * Get Metamodel instance corresponding to an ID or JSON definition. - * @param metamodelIdOrDefinition String ID or JSON definition. - * @returns Metamodel instance. - * @private - */ - getMetamodel(metamodelIdOrDefinition) { - const metamodelType = Common.getType(metamodelIdOrDefinition); - if (metamodelType === 'Object') { - return new Metamodel(metamodelIdOrDefinition); + + // ////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Get current diagram state. At any given instant the diagram might not make *sense* + * but it should always be syntactically valid. + * @return current Model. + */ + getModel() { + if (this.application) { + const model = this.application.getModel(); + if (model) { + return model.unwrap(); + } + } + + return this.model; } - return this.metamodels.getMetamodelOrDefault(metamodelIdOrDefinition); - } - // ////////////////////////////////////////////////////////////////////////////////////////////// + // ////////////////////////////////////////////////////////////////////////////////////////////// - /** - * Render current diagram state. - */ - render() { + /** + * Extract SVG element. + * @return stringified SVG element. + */ + getSVG() { + return this.application.getSVG(); + } - if (this.props.model) { + // ////////////////////////////////////////////////////////////////////////////////////////////// - // If a model was specified as a property, apply it. Otherwise - // fall back to the demo model. + /** + * Get demo scenarios, allowing initialization in demo mode from the outside. + * @returns {Scenarios} + */ + getDemoScenarios() { + return new Scenarios(); + } - const scenarios = this.getDemoScenarios(); - const metamodel = [scenarios.getBlankMetamodel(), scenarios.getECOMPMetamodel()]; - if (this.props.metamodel) { - metamodel.push(this.props.metamodel); - } - this.setMetamodel(metamodel); - this.setModel(this.props.model); + // ////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Create new model. + * @param metamodelIdOrDefinition + * @return newly-created model. + */ + newModel(metamodelIdOrDefinition) { + const metamodel = this.getMetamodel(metamodelIdOrDefinition); + Common.assertInstanceOf(metamodel, Metamodel); + const model = new Model({}, metamodel); + if (this.application) { + this.application.setModel(model); + } + return model; } - return ( - { this.application = a; }} /> - ); - } + // ////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Get Metamodel instance corresponding to an ID or JSON definition. + * @param metamodelIdOrDefinition String ID or JSON definition. + * @returns Metamodel instance. + * @private + */ + getMetamodel(metamodelIdOrDefinition) { + const metamodelType = Common.getType(metamodelIdOrDefinition); + if (metamodelType === 'Object') { + return new Metamodel(metamodelIdOrDefinition); + } + return this.metamodels.getMetamodelOrDefault(metamodelIdOrDefinition); + } + // ////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Render current diagram state. + */ + render() { + if (this.props.model) { + // If a model was specified as a property, apply it. Otherwise + // fall back to the demo model. + + const scenarios = this.getDemoScenarios(); + const metamodel = [ + scenarios.getBlankMetamodel(), + scenarios.getECOMPMetamodel() + ]; + if (this.props.metamodel) { + metamodel.push(this.props.metamodel); + } + this.setMetamodel(metamodel); + this.setModel(this.props.model); + } + + return ( + { + this.application = a; + }} + /> + ); + } } Sequencer.propTypes = { - options: PropTypes.object.isRequired, - model: PropTypes.object, - metamodel: PropTypes.object, + options: PropTypes.object.isRequired, + model: PropTypes.object, + metamodel: PropTypes.object }; - export default Sequencer; diff --git a/dox-sequence-diagram-ui/src/main/webapp/lib/ecomp/asdc/sequencer/common/Common.js b/dox-sequence-diagram-ui/src/main/webapp/lib/ecomp/asdc/sequencer/common/Common.js index 7337367dca..1c6bd69dc2 100644 --- a/dox-sequence-diagram-ui/src/main/webapp/lib/ecomp/asdc/sequencer/common/Common.js +++ b/dox-sequence-diagram-ui/src/main/webapp/lib/ecomp/asdc/sequencer/common/Common.js @@ -18,339 +18,347 @@ * Common operations. */ export default class Common { + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Retrieve and start a simple timer. Retrieve elapsed time by calling #ms(). + * @returns {*} + */ + static timer() { + const start = new Date().getTime(); + return { + ms() { + return new Date().getTime() - start; + } + }; + } - // /////////////////////////////////////////////////////////////////////////////////////////////// - - /** - * Retrieve and start a simple timer. Retrieve elapsed time by calling #ms(). - * @returns {*} - */ - static timer() { - const start = new Date().getTime(); - return { - ms() { - return (new Date().getTime() - start); - }, - }; - } - - // /////////////////////////////////////////////////////////////////////////////////////////////// - - /** - * Get datatype, stripping '[object Boolean]' to just 'Boolean'. - * @param o JS object. - * @return String like String, Number, Date, Null, Undefined, stuff like that. - */ - static getType(o) { - const str = Object.prototype.toString.call(o); - const prefix = '[object '; - if (str.substr(str, prefix.length) === prefix) { - return str.substr(prefix.length, str.length - (prefix.length + 1)); + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Get datatype, stripping '[object Boolean]' to just 'Boolean'. + * @param o JS object. + * @return String like String, Number, Date, Null, Undefined, stuff like that. + */ + static getType(o) { + const str = Object.prototype.toString.call(o); + const prefix = '[object '; + if (str.substr(str, prefix.length) === prefix) { + return str.substr(prefix.length, str.length - (prefix.length + 1)); + } + return str; } - return str; - } - - // /////////////////////////////////////////////////////////////////////////////////////////////// - - /** - * Assert that an argument was provided. - * @param value to be checked. - * @param message message on assertion failure. - * @return value. - */ - static assertNotNull(value, message = 'Unexpected null value') { - if (!value) { - throw new Error(message); + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Assert that an argument was provided. + * @param value to be checked. + * @param message message on assertion failure. + * @return value. + */ + static assertNotNull(value, message = 'Unexpected null value') { + if (!value) { + throw new Error(message); + } + return value; } - return value; - } - - // /////////////////////////////////////////////////////////////////////////////////////////////// - - /** - * Assert argument type. - * @param value to be checked. - * @param expected expected type string, e,g. Number from [object Number]. - * @return value. - */ - static assertType(value, expected) { - const type = this.getType(value); - if (type !== expected) { - throw new Error(`Expected type ${expected}, got ${type}`); + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Assert argument type. + * @param value to be checked. + * @param expected expected type string, e,g. Number from [object Number]. + * @return value. + */ + static assertType(value, expected) { + const type = this.getType(value); + if (type !== expected) { + throw new Error(`Expected type ${expected}, got ${type}`); + } + return value; } - return value; - } - - // /////////////////////////////////////////////////////////////////////////////////////////////// - - /** - * Assert argument type. - * @param value to be checked. - * @param unexpected unexpected type string, e,g. Number from [object Number]. - * @return value. - */ - static assertNotType(value, unexpected) { - const type = this.getType(value); - if (type === unexpected) { - throw new Error(`Forbidden type "${unexpected}"`); + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Assert argument type. + * @param value to be checked. + * @param unexpected unexpected type string, e,g. Number from [object Number]. + * @return value. + */ + static assertNotType(value, unexpected) { + const type = this.getType(value); + if (type === unexpected) { + throw new Error(`Forbidden type "${unexpected}"`); + } + return value; } - return value; - } - - // /////////////////////////////////////////////////////////////////////////////////////////////// - - /** - * Assert argument is a simple JSON object, and specifically not (something like an) ES6 class. - * @param value to be checked. - * @return value. - */ - static assertPlainObject(value) { - Common.assertType(value, 'Object'); - // TODO - /* + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Assert argument is a simple JSON object, and specifically not (something like an) ES6 class. + * @param value to be checked. + * @return value. + */ + static assertPlainObject(value) { + Common.assertType(value, 'Object'); + // TODO + /* if (!($.isPlainObject(value))) { throw new Error(`Expected plain object: ${value}`); } */ - return value; - } - - // /////////////////////////////////////////////////////////////////////////////////////////////// - - /** - * Assert argument type. - * @param value to be checked. - * @param c expected class. - * @return value. - */ - static assertInstanceOf(value, c) { - Common.assertNotNull(value); - if (!(value instanceof c)) { - throw new Error(`Expected instanceof ${c}: ${value}`); - } - return value; - } - - // /////////////////////////////////////////////////////////////////////////////////////////////// - - /** - * Assert that a string matches a regex. - * @param value value to be tested. - * @param re pattern to be applied. - * @return value. - */ - static assertMatches(value, re) { - this.assertType(value, 'String'); - this.assertType(re, 'RegExp'); - if (!re.test(value)) { - throw new Error(`Value ${value} doesn't match pattern ${re}`); + return value; } - return value; - } - - // /////////////////////////////////////////////////////////////////////////////////////////////// - - /** - * Assert the value of a boolean. - * - * @param bool to be checked. - * @param message optional message on assertion failure. - * @return value. - */ - static assertThat(bool, message) { - if (!bool) { - throw new Error(message || `Unexpected: ${bool}`); - } - return bool; - } - - // /////////////////////////////////////////////////////////////////////////////////////////////// - - /** - * Verify that a value, generally a function arg, is a DOM element. - * @param value to be checked. - * @return value. - */ - static assertHTMLElement(value) { - if (!Common.isHTMLElement(value)) { - throw new Error(`Expected HTMLElement: ${value}`); - } - return value; - } - - // /////////////////////////////////////////////////////////////////////////////////////////////// - - /** - * Check whether a value, generally a function arg, is an HTML DOM element. - * @param o to be checked. - * @return true if DOM element. - */ - static isHTMLElement(o) { - if (typeof HTMLElement === 'object') { - return o instanceof HTMLElement; + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Assert argument type. + * @param value to be checked. + * @param c expected class. + * @return value. + */ + static assertInstanceOf(value, c) { + Common.assertNotNull(value); + if (!(value instanceof c)) { + throw new Error(`Expected instanceof ${c}: ${value}`); + } + return value; } - return o && typeof o === 'object' && o !== null - && o.nodeType === 1 && typeof o.nodeName === 'string'; - } - - // /////////////////////////////////////////////////////////////////////////////////////////////// - - /** - * Check if a string is non-empty. - * @param s string to be checked. - * @returns false if non-blank string, true otherwise. - */ - static isBlank(s) { - if (Common.getType(s) === 'String') { - return (s.trim().length === 0); + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Assert that a string matches a regex. + * @param value value to be tested. + * @param re pattern to be applied. + * @return value. + */ + static assertMatches(value, re) { + this.assertType(value, 'String'); + this.assertType(re, 'RegExp'); + if (!re.test(value)) { + throw new Error(`Value ${value} doesn't match pattern ${re}`); + } + return value; } - return true; - } - - // /////////////////////////////////////////////////////////////////////////////////////////////// - - /** - * Detect dates that are numbers, milli/seconds since epoch.. - * - * @param n candidate number. - * @returns {boolean} - */ - static isNumber(n) { - return !isNaN(parseFloat(n)) && isFinite(n); - } - - // /////////////////////////////////////////////////////////////////////////////////////////////// - - /** - * Parse the text output from a template to a DOM element. - * @param txt input text. - * @returns {Element} - */ - static txt2dom(txt) { - return new DOMParser().parseFromString(txt, 'image/svg+xml').documentElement; - } - - // /////////////////////////////////////////////////////////////////////////////////////////////// - - /** - * Recursively convert a DOM element to an SVG (namespaced) element. Otherwise - * you get HTML elements that *happen* to have SVG names, but which aren't actually SVG. - * - * @param node DOM node to be converted. - * @param svg to be updated. - * @returns {*} for chaining. - */ - static dom2svg(node, svg) { - - Common.assertNotType(node, 'String'); - - if (node.childNodes && node.childNodes.length > 0) { - - for (const c of node.childNodes) { - switch (c.nodeType) { - case document.TEXT_NODE: - svg.text(c.nodeValue); - break; - default: - break; + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Assert the value of a boolean. + * + * @param bool to be checked. + * @param message optional message on assertion failure. + * @return value. + */ + static assertThat(bool, message) { + if (!bool) { + throw new Error(message || `Unexpected: ${bool}`); } - } - - for (const c of node.childNodes) { - switch (c.nodeType) { - case document.ELEMENT_NODE: - Common.dom2svg(c, svg.append(`svg:${c.nodeName.toLowerCase()}`)); - break; - default: - break; + return bool; + } + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Verify that a value, generally a function arg, is a DOM element. + * @param value to be checked. + * @return value. + */ + static assertHTMLElement(value) { + if (!Common.isHTMLElement(value)) { + throw new Error(`Expected HTMLElement: ${value}`); } - } + return value; } - if (node.hasAttributes()) { - for (let i = 0; i < node.attributes.length; i++) { - const a = node.attributes.item(i); - svg.attr(a.name, a.value); - } + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Check whether a value, generally a function arg, is an HTML DOM element. + * @param o to be checked. + * @return true if DOM element. + */ + static isHTMLElement(o) { + /* eslint-disable no-undef */ + if (typeof HTMLElement === 'object') { + return o instanceof HTMLElement; + } + /* eslint-enable no-undef */ + return ( + o && + typeof o === 'object' && + o !== null && + o.nodeType === 1 && + typeof o.nodeName === 'string' + ); } - return svg; - } - - // /////////////////////////////////////////////////////////////////////////////////////////////// - - /** - * Get the lines to be shown in the label. - * - * @param labelText original label text. - * @param wordWrapAt chars at which to break words. - * @param lineWrapAt chars at which to wrap. - * @param maximumLines lines at which to truncate. - * @returns {Array} - */ - static tokenize(labelText = '', wordWrapAt, lineWrapAt, maximumLines) { - - let l = labelText; - - // Hyphenate and break long words. - - const regex = new RegExp(`(\\w{${wordWrapAt - 1}})(?=\\w)`, 'g'); - l = l.replace(regex, '$1- '); - - const labelTokens = l.split(/\s+/); - const lines = []; - let label = ''; - for (const labelToken of labelTokens) { - if (label.length > 0) { - const length = label.length + labelToken.length + 1; - if (length > lineWrapAt) { - lines.push(label.trim()); - label = labelToken; - continue; + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Check if a string is non-empty. + * @param s string to be checked. + * @returns false if non-blank string, true otherwise. + */ + static isBlank(s) { + if (Common.getType(s) === 'String') { + return s.trim().length === 0; } - } - label = `${label} ${labelToken}`; + return true; } - if (label) { - lines.push(label.trim()); + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Detect dates that are numbers, milli/seconds since epoch.. + * + * @param n candidate number. + * @returns {boolean} + */ + static isNumber(n) { + return !isNaN(parseFloat(n)) && isFinite(n); } - const truncated = lines.slice(0, maximumLines); - if (truncated.length < lines.length) { - let finalLine = truncated[maximumLines - 1]; - if (finalLine.length > (lineWrapAt - 4)) { - finalLine = finalLine.substring(0, lineWrapAt - 4); - } - finalLine = `${finalLine} ...`; - truncated[maximumLines - 1] = finalLine; + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Parse the text output from a template to a DOM element. + * @param txt input text. + * @returns {Element} + */ + static txt2dom(txt) { + /* eslint-disable no-undef */ + return new DOMParser().parseFromString(txt, 'image/svg+xml') + .documentElement; + /* eslint-enable no-undef */ } - return truncated; - } - - // /////////////////////////////////////////////////////////////////////////////////////////////// - - /** - * Brutally sanitize an input string. We have no syntax rules, and hence no specific - * rules to apply, but we have very few unconstrained fields, so we can implement a - * crude default and devolve the rest to options. - * @param value value to be sanitized. - * @param options control options including validation rules. - * @param type validation type. - * @returns {*} sanitized string. - * @private - */ - static sanitizeText(value, options, type) { - const rules = Common.assertNotNull(options.validation[type]); - let v = value || rules.defaultValue || ''; - if (rules.replace) { - v = v.replace(rules.replace, ''); + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Recursively convert a DOM element to an SVG (namespaced) element. Otherwise + * you get HTML elements that *happen* to have SVG names, but which aren't actually SVG. + * + * @param node DOM node to be converted. + * @param svg to be updated. + * @returns {*} for chaining. + */ + static dom2svg(node, svg) { + Common.assertNotType(node, 'String'); + + if (node.childNodes && node.childNodes.length > 0) { + for (const c of node.childNodes) { + switch (c.nodeType) { + case document.TEXT_NODE: + svg.text(c.nodeValue); + break; + default: + break; + } + } + + for (const c of node.childNodes) { + switch (c.nodeType) { + case document.ELEMENT_NODE: + Common.dom2svg( + c, + svg.append(`svg:${c.nodeName.toLowerCase()}`) + ); + break; + default: + break; + } + } + } + + if (node.hasAttributes()) { + for (let i = 0; i < node.attributes.length; i++) { + const a = node.attributes.item(i); + svg.attr(a.name, a.value); + } + } + + return svg; } - if (v.length > rules.maxLength) { - v = `${v.substring(0, rules.maxLength)}...`; + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Get the lines to be shown in the label. + * + * @param labelText original label text. + * @param wordWrapAt chars at which to break words. + * @param lineWrapAt chars at which to wrap. + * @param maximumLines lines at which to truncate. + * @returns {Array} + */ + static tokenize(labelText = '', wordWrapAt, lineWrapAt, maximumLines) { + let l = labelText; + + // Hyphenate and break long words. + + const regex = new RegExp(`(\\w{${wordWrapAt - 1}})(?=\\w)`, 'g'); + l = l.replace(regex, '$1- '); + + const labelTokens = l.split(/\s+/); + const lines = []; + let label = ''; + for (const labelToken of labelTokens) { + if (label.length > 0) { + const length = label.length + labelToken.length + 1; + if (length > lineWrapAt) { + lines.push(label.trim()); + label = labelToken; + continue; + } + } + label = `${label} ${labelToken}`; + } + + if (label) { + lines.push(label.trim()); + } + + const truncated = lines.slice(0, maximumLines); + if (truncated.length < lines.length) { + let finalLine = truncated[maximumLines - 1]; + if (finalLine.length > lineWrapAt - 4) { + finalLine = finalLine.substring(0, lineWrapAt - 4); + } + finalLine = `${finalLine} ...`; + truncated[maximumLines - 1] = finalLine; + } + + return truncated; } - return v; - } + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Brutally sanitize an input string. We have no syntax rules, and hence no specific + * rules to apply, but we have very few unconstrained fields, so we can implement a + * crude default and devolve the rest to options. + * @param value value to be sanitized. + * @param options control options including validation rules. + * @param type validation type. + * @returns {*} sanitized string. + * @private + */ + static sanitizeText(value, options, type) { + const rules = Common.assertNotNull(options.validation[type]); + let v = value || rules.defaultValue || ''; + if (rules.replace) { + v = v.replace(rules.replace, ''); + } + if (v.length > rules.maxLength) { + v = `${v.substring(0, rules.maxLength)}...`; + } + return v; + } } diff --git a/dox-sequence-diagram-ui/src/main/webapp/lib/ecomp/asdc/sequencer/common/Logger.js b/dox-sequence-diagram-ui/src/main/webapp/lib/ecomp/asdc/sequencer/common/Logger.js index 187f49bb08..8b6ae67ca7 100644 --- a/dox-sequence-diagram-ui/src/main/webapp/lib/ecomp/asdc/sequencer/common/Logger.js +++ b/dox-sequence-diagram-ui/src/main/webapp/lib/ecomp/asdc/sequencer/common/Logger.js @@ -23,108 +23,107 @@ import Common from './Common'; * disable them for production. */ export default class Logger { + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * No-op call so that we can leave imports in place, + * even when there's no debugging. + */ + static noop() { + // Nothing. + } + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Set debug level. + * @param level threshold. + */ + static setLevel(level) { + this.level = Logger.OFF; + if (Common.getType(level) === 'Number') { + this.level = level; + } else { + this.level = Logger[level]; + } + } + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Get debug level. + * @returns {number|*} + */ + static getLevel() { + return this.level; + } - // /////////////////////////////////////////////////////////////////////////////////////////////// - - /** - * No-op call so that we can leave imports in place, - * even when there's no debugging. - */ - static noop() { - // Nothing. - } - - // /////////////////////////////////////////////////////////////////////////////////////////////// - - /** - * Set debug level. - * @param level threshold. - */ - static setLevel(level) { - this.level = Logger.OFF; - if (Common.getType(level) === 'Number') { - this.level = level; - } else { - this.level = Logger[level]; + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Write DEBUG-level log. + * @param msg message or tokens. + */ + static debug(...msg) { + if (this.level >= Logger.DEBUG) { + const out = this.serialize(msg); + console.info(`ASDCS [DEBUG] ${out}`); + } } - } - - // /////////////////////////////////////////////////////////////////////////////////////////////// - - /** - * Get debug level. - * @returns {number|*} - */ - static getLevel() { - return this.level; - } - - // /////////////////////////////////////////////////////////////////////////////////////////////// - - /** - * Write DEBUG-level log. - * @param msg message or tokens. - */ - static debug(...msg) { - if (this.level >= Logger.DEBUG) { - const out = this.serialize(msg); - console.info(`ASDCS [DEBUG] ${out}`); + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Write INFO-level log. + * @param msg message or tokens. + */ + static info(...msg) { + if (this.level >= Logger.INFO) { + const out = this.serialize(msg); + console.info(`ASDCS [INFO] ${out}`); + } } - } - - // /////////////////////////////////////////////////////////////////////////////////////////////// - - /** - * Write INFO-level log. - * @param msg message or tokens. - */ - static info(...msg) { - if (this.level >= Logger.INFO) { - const out = this.serialize(msg); - console.info(`ASDCS [INFO] ${out}`); + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Write debug. + * @param msg message or tokens. + */ + static warn(msg) { + if (this.level >= Logger.WARN) { + const out = this.serialize(msg); + console.warn(`ASDCS [WARN] ${out}`); + } } - } - - // /////////////////////////////////////////////////////////////////////////////////////////////// - - /** - * Write debug. - * @param msg message or tokens. - */ - static warn(msg) { - if (this.level >= Logger.WARN) { - const out = this.serialize(msg); - console.warn(`ASDCS [WARN] ${out}`); + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Write error. + * @param msg message or tokens. + */ + static error(...msg) { + if (this.level >= Logger.ERROR) { + const out = this.serialize(msg); + console.error(`ASDCS [ERROR] ${out}`); + } } - } - - // /////////////////////////////////////////////////////////////////////////////////////////////// - - /** - * Write error. - * @param msg message or tokens. - */ - static error(...msg) { - if (this.level >= Logger.ERROR) { - const out = this.serialize(msg); - console.error(`ASDCS [ERROR] ${out}`); + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Serialize msg. + * @param msg message or tokens. + * @returns {string} + */ + static serialize(...msg) { + let out = ''; + msg.forEach(token => { + out = `${out}${token}`; + }); + return out; } - } - - // /////////////////////////////////////////////////////////////////////////////////////////////// - - /** - * Serialize msg. - * @param msg message or tokens. - * @returns {string} - */ - static serialize(...msg) { - let out = ''; - msg.forEach((token) => { - out = `${out}${token}`; - }); - return out; - } } // ///////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/dox-sequence-diagram-ui/src/main/webapp/lib/ecomp/asdc/sequencer/common/Options.js b/dox-sequence-diagram-ui/src/main/webapp/lib/ecomp/asdc/sequencer/common/Options.js index 15897d7ee3..b054cb26e7 100644 --- a/dox-sequence-diagram-ui/src/main/webapp/lib/ecomp/asdc/sequencer/common/Options.js +++ b/dox-sequence-diagram-ui/src/main/webapp/lib/ecomp/asdc/sequencer/common/Options.js @@ -23,26 +23,25 @@ import Logger from './Logger'; * and the result -- runtime options -- are available by calling #getOptions(). */ export default class Options { + // /////////////////////////////////////////////////////////////////////////////////////////////// - // /////////////////////////////////////////////////////////////////////////////////////////////// + /** + * Construct options, applying defaults. + * @param options optional override options. + */ + constructor(options = {}) { + this.options = _merge({}, Options.DEFAULTS, options); + } - /** - * Construct options, applying defaults. - * @param options optional override options. - */ - constructor(options = {}) { - this.options = _merge({}, Options.DEFAULTS, options); - } + // /////////////////////////////////////////////////////////////////////////////////////////////// - // /////////////////////////////////////////////////////////////////////////////////////////////// - - /** - * Unwrap options. - * @returns {*} - */ - unwrap() { - return this.options; - } + /** + * Unwrap options. + * @returns {*} + */ + unwrap() { + return this.options; + } } // ///////////////////////////////////////////////////////////////////////////////////////////////// @@ -51,86 +50,86 @@ export default class Options { * Default options, overridden by anything of the same name. */ Options.DEFAULTS = { - log: { - level: Logger.WARN, - }, - demo: false, - useHtmlSelect: true, - diagram: { - svg: { - x: 0, - y: 0, - width: 1600, - height: 1200, - margin: 50, - floodColor: '#009fdb', - scale: { - height: true, - width: true, - minimum: 0.25, - }, - }, - title: { - height: 0, - }, - metadata: false, - lifelines: { - header: { - height: 225, - width: 350, - wrapWords: 14, - wrapLines: 18, - maxLines: 5, - }, - occurrences: { - marginTop: 50, - marginBottom: 75, - foreshortening: 5, - width: 50, - }, - spacing: { - horizontal: 400, - vertical: 400, - }, - }, - messages: { - label: { - wrapWords: 14, - wrapLines: 18, - maxLines: 4, - }, - }, - fragments: { - leftMargin: 150, - topMargin: 200, - widthMargin: 300, - heightMargin: 350, - label: { - wrapWords: 50, - wrapLines: 50, - maxLines: 2, - }, - }, - }, - validation: { - lifeline: { - maxLength: 100, - defaultValue: '', - replace: /[^\-\.\+ &%#@\?\(\)\[\]<>\w\d]/g, - }, - message: { - maxLength: 100, - defaultValue: '', - replace: /[^\-\.\+ &%#@\?\(\)\[\]<>\w\d]/g, - }, - notes: { - maxLength: 255, - defaultValue: '', + log: { + level: Logger.WARN }, - guard: { - maxLength: 80, - defaultValue: '', - replace: /[^\-\.\+ &%#@\?\(\)\[\]<>\w\d]/g, + demo: false, + useHtmlSelect: true, + diagram: { + svg: { + x: 0, + y: 0, + width: 1600, + height: 1200, + margin: 50, + floodColor: '#009fdb', + scale: { + height: true, + width: true, + minimum: 0.25 + } + }, + title: { + height: 0 + }, + metadata: false, + lifelines: { + header: { + height: 225, + width: 350, + wrapWords: 14, + wrapLines: 18, + maxLines: 5 + }, + occurrences: { + marginTop: 50, + marginBottom: 75, + foreshortening: 5, + width: 50 + }, + spacing: { + horizontal: 400, + vertical: 400 + } + }, + messages: { + label: { + wrapWords: 14, + wrapLines: 18, + maxLines: 4 + } + }, + fragments: { + leftMargin: 150, + topMargin: 200, + widthMargin: 300, + heightMargin: 350, + label: { + wrapWords: 50, + wrapLines: 50, + maxLines: 2 + } + } }, - }, + validation: { + lifeline: { + maxLength: 100, + defaultValue: '', + replace: /[^\-\.\+ &%#@\?\(\)\[\]<>\w\d]/g + }, + message: { + maxLength: 100, + defaultValue: '', + replace: /[^\-\.\+ &%#@\?\(\)\[\]<>\w\d]/g + }, + notes: { + maxLength: 255, + defaultValue: '' + }, + guard: { + maxLength: 80, + defaultValue: '', + replace: /[^\-\.\+ &%#@\?\(\)\[\]<>\w\d]/g + } + } }; 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 index 6889e0ab9f..b63e69a4ed 100644 --- 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 @@ -28,258 +28,274 @@ import Overlay from '../overlay/Overlay'; * Application controller, also a view. */ 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); + } + + // /////////////////////////////////////////////////////////////////////////////////////////////// - /** - * 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(); + /** + * Get application options. + * @returns JSON options, see Options.js. + */ + getOptions() { + return this.options.unwrap(); } - if (this.diagram) { - this.diagram.render(); + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Set diagram name. + * @param n diagram (human-readable) name. + */ + setName(n) { + this.diagram.setName(n); } - } - - // /////////////////////////////////////////////////////////////////////////////////////////////// - - /** - * 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); + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * 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(); + } } - if (this.diagram) { - this.diagram.selectLifeline(id); + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Get Model wrapper. + * @returns Model. + */ + getModel() { + return this.model; } - } - // /////////////////////////////////////////////////////////////////////////////////////////////// + // /////////////////////////////////////////////////////////////////////////////////////////////// - /** - * Select message by ID. - * @param id message ID. - */ - selectMessage(id) { - if (this.editor) { - this.editor.selectMessage(id); + /** + * Get SVG element. + * @returns {*} + */ + getSVG() { + return this.diagram.getSVG(); } - if (this.diagram) { - this.diagram.selectMessage(id); + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Get top-level widget. Provides the demo toolbar with access to the public API. + * @returns {*} + */ + getSequencer() { + return this.sequencer; } - } - // /////////////////////////////////////////////////////////////////////////////////////////////// + // /////////////////////////////////////////////////////////////////////////////////////////////// - /** - * (Re)render just the diagram. - */ - renderDiagram() { - this.diagram.redraw(); - } + /** + * Present info dialog. + * @param msg info message. + */ + showInfoDialog(msg) { + this.dialog.showInfoDialog(msg); + } - // /////////////////////////////////////////////////////////////////////////////////////////////// + // /////////////////////////////////////////////////////////////////////////////////////////////// - /** - * Show overlay between application and modal dialog. - */ - showOverlay() { - if (this.overlay) { - this.overlay.setVisible(true); + /** + * 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); + } - /** - * Hide overlay between application and modal dialog. - */ - hideOverlay() { - if (this.overlay) { - this.overlay.setVisible(false); + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * 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); + } + } - /** - * Capture mouse move events, for resize. - * @param event move event. - */ - onMouseMove(event) { - if (this.editor) { - this.editor.onMouseMove(event); + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Select message by ID. + * @param id message ID. + */ + selectMessage(id) { + if (this.editor) { + this.editor.selectMessage(id); + } + if (this.diagram) { + this.diagram.selectMessage(id); + } } - } - // /////////////////////////////////////////////////////////////////////////////////////////////// + // /////////////////////////////////////////////////////////////////////////////////////////////// - /** - * Propagate mouse event to the editor that manages the resize. - */ - onMouseUp() { - if (this.editor) { - this.editor.onMouseUp(); + /** + * (Re)render just the diagram. + */ + renderDiagram() { + this.diagram.redraw(); } - } - // /////////////////////////////////////////////////////////////////////////////////////////////// + // /////////////////////////////////////////////////////////////////////////////////////////////// - /** - * Render current model state. - */ - render() { + /** + * Show overlay between application and modal dialog. + */ + showOverlay() { + if (this.overlay) { + this.overlay.setVisible(true); + } + } - return ( + // /////////////////////////////////////////////////////////////////////////////////////////////// -
+ /** + * Hide overlay between application and modal dialog. + */ + hideOverlay() { + if (this.overlay) { + this.overlay.setVisible(false); + } + } - { this.editor = r; }} /> - { this.diagram = r; }} /> - { this.dialog = r; }} /> - - { this.overlay = r; }} /> + // /////////////////////////////////////////////////////////////////////////////////////////////// -
- ); - } + /** + * 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: PropTypes.object.isRequired, - sequencer: PropTypes.object.isRequired, + options: PropTypes.object.isRequired, + sequencer: PropTypes.object.isRequired }; export default Application; 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 index b3544d7066..129a1afc2b 100644 --- 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 @@ -28,898 +28,1013 @@ import Popup from './components/popup/Popup'; * SVG diagram view. */ 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); - this.initialTransformX = 0; - this.initialTransformY = 0; - } - - // /////////////////////////////////////////////////////////////////////////////////////////////// - - /** - * 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); + /** + * Set diagram name. + * @param n name. + */ + setName(n) { + this.svg.select('').text(n); } + // /////////////////////////////////////////////////////////////////////////////////////////////// - // 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; - } + /** + * 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; + }} + /> +
+ ); + } - const header = this.options.lifelines.header; - const spacing = this.options.lifelines.spacing; + // /////////////////////////////////////////////////////////////////////////////////////////////// - // Make separate container elements so that we can control Z order. + redraw() { + this.updateSVG(); + } - 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 ----------------------------------------------------------------------------------- + /** + * Initial render. + */ + componentDidMount() { + window.addEventListener('resize', this.handleResize); + this.updateSVG(); - 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; - } + // Insurance: - // 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; + setTimeout(() => { + this.handleResize(); + }, 500); } - // --------------------------------------------------------------------------------------------- - - // 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; + componentWillUnmount() { + window.removeEventListener('resize', this.handleResize); } - // Occurrences. -------------------------------------------------------------------------------- + // /////////////////////////////////////////////////////////////////////////////////////////////// - if (message.occurrence) { - Logger.debug(`Found occurrence for ${message.name}: ${JSON.stringify(message.occurrence)}`); + /** + * Render on update. + */ + componentDidUpdate() { + this.updateSVG(); } - 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); + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * 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'); + } - let messagePath; - if (loopback) { + if (this.state.height === 0) { + // We'll get a resize event, and the height will be non-zero when it's actually time. - // To self. + return; + } - messagePath = `M${x1},${y}`; - messagePath = `${messagePath} L${x1 + 200},${y}`; - messagePath = `${messagePath} L${x1 + 200},${y + 50}`; - messagePath = `${messagePath} L${x1},${y + 50}`; - } else { + 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); + } - // Between lifelines. + // If we've already rendered, then save the current scale/translate so that we + // can reapply it after rendering. - messagePath = `M${x0},${y}`; - messagePath = `${messagePath} L${x1},${y}`; - } + const gContentSelection = this.svg.selectAll('g.asdcs-diagram-content'); + if (gContentSelection.size() === 1) { + const transform = gContentSelection.attr('transform'); + if (transform) { + this.savedTransform = transform; + } + } - const styles = Diagram._getMessageStyles(message); + // Empty the document. We're starting again. - // Split message over lines. + this.svg.selectAll('.asdcs-diagram-content').remove(); - 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); + // Extract the model. - 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 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; + } - const messageEl = Common.txt2dom(messageTxt); - const gMessage = gMessages.append('g'); - Common.dom2svg(messageEl, gMessage); + // 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; + } - // 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); + // Draw the actual (dashed) lifelines in a background . - // Fragments. ---------------------------------------------------------------------------------- + this._drawLifelines(gLifelines, lifelines, y); - const fragment = fData[message.id]; - if (fragment) { + // Initialize mouse event handlers. - // It ends on this message. + this._initMouseEvents(gLifelines, gCanvas); - this._drawFragment(gCanvas, fragment, positionsByMessageId); + // 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; + } - /** - * 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) { + if (!toActor) { + Logger.warn( + `Cannot draw message ${JSON.stringify( + message + )}: 'to' not found.` + ); + return; + } - Common.assertType(oData, 'Object'); - Common.assertType(positionsByMessageId, 'Object'); - Common.assertType(actor, 'Object'); - Common.assertType(messageId, 'String'); + // Occurrences. -------------------------------------------------------------------------------- - const gOccurrences = gCanvas.select('g.asdcs-diagram-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. - 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]; + messagePath = `M${x0},${y}`; + messagePath = `${messagePath} L${x1},${y}`; + } - const active = Diagram._calcActive(oData, actor.id); + 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); - const x = (actor.x - oHalfWidth) + (active * oWidth); - const positions = positionsByMessageId[messageId]; - const y = positions.y; + // Fragments. ---------------------------------------------------------------------------------- - let draw = true; - if (o) { + const fragment = fData[message.id]; + if (fragment) { + // It ends on this message. - if (o.start[messageId]) { + this._drawFragment(gCanvas, fragment, positionsByMessageId); + } + } - // Starting, but drawing nothing until we find the end. + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * 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; + } + } - o.active.push(messageId); - 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')); + } + } - } else if (active > 0) { + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * 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)}`); + } + } + } + } - const startMessageId = o.stop[messageId]; - if (startMessageId) { + // /////////////////////////////////////////////////////////////////////////////////////////////// + + _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; + } - // OK, it ends here. Draw the occurrence box. + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * 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')); + } + } - 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, - }; + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * 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); + }); + } - const occurrenceTxt = this.templates.occurrence(oProps); - const occurrenceEl = Common.txt2dom(occurrenceTxt); - Common.dom2svg(occurrenceEl, gOccurrences.append('g')); + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * 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`; } - draw = false; - } + + return { css, marker, dasharray }; } - if (draw) { + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * 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() { + if (!this.initialTransformX && !this.initialTransformY) { + this.initialTransformX = d3.event.transform.x; + this.initialTransformY = d3.event.transform.y; + } - // Seems this is a singleton occurrence. We just draw a wee box around it. + gContent.attr( + 'transform', + `translate(${d3.event.transform.x - + this.initialTransformX}, ${d3.event.transform.y - + this.initialTransformY})scale(${d3.event.transform.k}, ${ + d3.event.transform.k + })` + ); + }; + + 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); + } - 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')); - } + scale = Math.max(scale, scaleMinimum); - } - - // /////////////////////////////////////////////////////////////////////////////////////////////// - - /** - * 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); + 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 { - Logger.warn(`Bad fragment: ${JSON.stringify(fragment)}`); + gContent.attr('transform', `scale(${scale})`); } - } - } - } - - // /////////////////////////////////////////////////////////////////////////////////////////////// - - _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'; - } + const zoom = d3.zoom().on('zoom', zoomed); - if (message.asynchronous) { - css = `${css} asdcs-diagram-message-asynchronous`; - marker = 'asdcsDiagramArrowOpen'; - } else { - css = `${css} asdcs-diagram-message-synchronous`; - } + this.svg.call(zoom); + this.svg.call(zoom.scaleBy, scale); - 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() { - if (!this.initialTransformX && !this.initialTransformY) { - this.initialTransformX = d3.event.transform.x; - this.initialTransformY = d3.event.transform.y; - } - - gContent.attr('transform', - `translate(${d3.event.transform.x - this.initialTransformX}, ${d3.event.transform.y - - this.initialTransformY})scale(${d3.event.transform.k}, ${d3.event.transform.k})`); - }; - - 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); + gContent.attr( + 'transform', + `translate(${translate[0]}, ${translate[1]})` + ); + gContent.attr('transform', `scale(${scale})`); } - 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})`); + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * 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; } - const zoom = d3.zoom() - .on('zoom', zoomed); - - this.svg.call(zoom); - this.svg.call(zoom.scaleBy, scale); - - gContent.attr('transform', `translate(${translate[0]}, ${translate[1]})`); - gContent.attr('transform', `scale(${scale})`); - - } - - // /////////////////////////////////////////////////////////////////////////////////////////////// - - /** - * 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; + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * 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; } - 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; - } + + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * 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; } - 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], - }); + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * 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: '' }); + } } - } else { - this.popup.setState({ visible: false, notes: '' }); - } } - } } - Diagram.propTypes = { - application: PropTypes.object.isRequired, + application: PropTypes.object.isRequired }; export default Diagram; 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 index 8f8f859aad..a2a6582ab0 100644 --- 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 @@ -25,85 +25,81 @@ import iconEdit from '../../../../../../../../res/ecomp/asdc/sequencer/sprites/i * @constructor */ 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} ...`; + // /////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * 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: '' + }; } - return ( -
-
Notes
-
-
- -
-
-
- {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}
+
+
+
-
-
-
-
- ); - } + ); + } } export default Popup; 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 index d48ef3bd88..96a709948c 100644 --- 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 @@ -28,211 +28,216 @@ import iconClose from '../../../../../../res/ecomp/asdc/sequencer/sprites/icons/ * configured, shown and hidden as required. */ 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 || '' + }); + } - // /////////////////////////////////////////////////////////////////////////////////////////////// - - /** - * 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 }); + // /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * 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}
+
+ -
- ); - } + /** + * Render to DOM. + */ + render() { + return ( +
+