From 8ed2f25c202781fff4d033cf6731680612b0418c Mon Sep 17 00:00:00 2001 From: mcyph <20507948+mcyph@users.noreply.github.com> Date: Sat, 20 Mar 2021 21:35:42 +1100 Subject: [PATCH] updates to js syntax --- src/js/layout/mxGraphLayout.js | 2 +- src/js/layout/mxPartitionLayout.js | 2 +- src/js/model/mxGeometry.js | 2 +- src/js/shape/mxDoubleEllipse.js | 2 +- src/js/shape/mxStencil.js | 2 +- src/js/util/mxAbstractCanvas2D.js | 1175 +- src/js/util/mxAnimation.js | 157 +- src/js/util/mxAutoSaveManager.js | 386 +- src/js/util/mxClipboard.js | 6 +- src/js/util/mxConstants.js | 6 +- src/js/util/mxDictionary.js | 196 +- src/js/util/mxDivResizer.js | 205 +- src/js/util/mxDragSource.js | 1340 +- src/js/util/mxEffects.js | 161 +- src/js/util/mxEventObject.js | 191 +- src/js/util/mxEventSource.js | 325 +- src/js/util/mxForm.js | 357 +- src/js/util/mxGuide.js | 737 +- src/js/util/mxImage.js | 70 +- src/js/util/mxImageBundle.js | 188 +- src/js/util/mxImageExport.js | 330 +- src/js/util/mxLog.js | 262 +- src/js/util/mxMorphing.js | 426 +- src/js/util/mxMouseEvent.js | 411 +- src/js/util/mxObjectIdentity.js | 47 +- src/js/util/mxPanningManager.js | 437 +- src/js/util/mxPoint.js | 96 +- src/js/util/mxPopupMenu.js | 1050 +- src/js/util/mxRectangle.js | 283 +- src/js/util/mxResources.js | 2 + src/js/util/mxSvgCanvas2D.js | 3381 ++-- src/js/util/mxToolbar.js | 926 +- src/js/util/mxUndoManager.js | 402 +- src/js/util/mxUndoableEdit.js | 380 +- src/js/util/mxUrlConverter.js | 271 +- src/js/util/mxWindow.js | 1964 +- src/js/util/mxXmlCanvas2D.js | 2249 ++- src/js/view/mxCellEditor.js | 2068 +- src/js/view/mxCellOverlay.js | 416 +- src/js/view/mxCellRenderer.js | 3033 ++- src/js/view/mxCellState.js | 779 +- src/js/view/mxCellStatePreview.js | 347 +- src/js/view/mxConnectionConstraint.js | 117 +- src/js/view/mxEdgeStyle.js | 836 +- src/js/view/mxGraph.js | 23961 +++++++++++------------- src/js/view/mxGraphSelectionModel.js | 759 +- src/js/view/mxGraphView.js | 5371 +++--- src/js/view/mxLayoutManager.js | 884 +- src/js/view/mxMultiplicity.js | 450 +- src/js/view/mxOutline.js | 1324 +- src/js/view/mxPerimeter.js | 717 +- src/js/view/mxPrintPreview.js | 2030 +- src/js/view/mxStyleRegistry.js | 31 +- src/js/view/mxTemporaryCellStates.js | 224 +- 54 files changed, 28901 insertions(+), 32873 deletions(-) diff --git a/src/js/layout/mxGraphLayout.js b/src/js/layout/mxGraphLayout.js index 62693b182..7ae6755d4 100644 --- a/src/js/layout/mxGraphLayout.js +++ b/src/js/layout/mxGraphLayout.js @@ -3,7 +3,7 @@ * Copyright (c) 2006-2018, Gaudenz Alder */ -import mxRectangle from "FIXME"; +import mxRectangle from "../util/mxRectangle"; import mxDictionary from "FIXME"; class mxGraphLayout { diff --git a/src/js/layout/mxPartitionLayout.js b/src/js/layout/mxPartitionLayout.js index 88897610a..9702ad04e 100644 --- a/src/js/layout/mxPartitionLayout.js +++ b/src/js/layout/mxPartitionLayout.js @@ -3,7 +3,7 @@ * Copyright (c) 2006-2015, Gaudenz Alder */ -import mxRectangle from "FIXME"; +import mxRectangle from "../util/mxRectangle"; class mxPartitionLayout extends mxGraphLayout { /** diff --git a/src/js/model/mxGeometry.js b/src/js/model/mxGeometry.js index 801a1b4ee..8f060061e 100644 --- a/src/js/model/mxGeometry.js +++ b/src/js/model/mxGeometry.js @@ -4,7 +4,7 @@ */ import mxPoint from "FIXME"; -import mxRectangle from "FIXME"; +import mxRectangle from "../util/mxRectangle"; class mxGeometry extends mxRectangle { /** diff --git a/src/js/shape/mxDoubleEllipse.js b/src/js/shape/mxDoubleEllipse.js index f3d2bff0f..4a295edaa 100644 --- a/src/js/shape/mxDoubleEllipse.js +++ b/src/js/shape/mxDoubleEllipse.js @@ -3,7 +3,7 @@ * Copyright (c) 2006-2015, Gaudenz Alder */ -import mxRectangle from "FIXME"; +import mxRectangle from "../util/mxRectangle"; class mxDoubleEllipse extends mxShape { /** diff --git a/src/js/shape/mxStencil.js b/src/js/shape/mxStencil.js index 4ad187457..269f9bc03 100644 --- a/src/js/shape/mxStencil.js +++ b/src/js/shape/mxStencil.js @@ -3,7 +3,7 @@ * Copyright (c) 2006-2015, Gaudenz Alder */ -import mxRectangle from "FIXME"; +import mxRectangle from "../util/mxRectangle"; import mxConnectionConstraint from "FIXME"; class mxStencil extends mxShape { diff --git a/src/js/util/mxAbstractCanvas2D.js b/src/js/util/mxAbstractCanvas2D.js index 3d0b73a55..24e02e777 100644 --- a/src/js/util/mxAbstractCanvas2D.js +++ b/src/js/util/mxAbstractCanvas2D.js @@ -2,641 +2,596 @@ * Copyright (c) 2006-2015, JGraph Ltd * Copyright (c) 2006-2015, Gaudenz Alder */ -/** - * Class: mxAbstractCanvas2D - * - * Base class for all canvases. A description of the public API is available in . - * All color values of will be converted to null in the state. - * - * Constructor: mxAbstractCanvas2D - * - * Constructs a new abstract canvas. - */ -function mxAbstractCanvas2D() -{ +import mxUtils from "./mxUtils"; +import mxConstants from "./mxConstants"; +import mxUrlConverter from "./mxUrlConverter"; +import mxPoint from "./mxPoint"; + +class mxAbstractCanvas2D { /** - * Variable: converter - * - * Holds the to convert image URLs. + * Variable: state + * + * Holds the current state. */ - this.converter = this.createUrlConverter(); - - this.reset(); -}; + mxAbstractCanvas2state = null; -/** - * Variable: state - * - * Holds the current state. - */ -mxAbstractCanvas2state = null; + /** + * Variable: states + * + * Stack of states. + */ + mxAbstractCanvas2states = null; -/** - * Variable: states - * - * Stack of states. - */ -mxAbstractCanvas2states = null; + /** + * Variable: path + * + * Holds the current path as an array. + */ + mxAbstractCanvas2path = null; -/** - * Variable: path - * - * Holds the current path as an array. - */ -mxAbstractCanvas2path = null; + /** + * Variable: rotateHtml + * + * Switch for rotation of HTML. Default is false. + */ + mxAbstractCanvas2rotateHtml = true; -/** - * Variable: rotateHtml - * - * Switch for rotation of HTML. Default is false. - */ -mxAbstractCanvas2rotateHtml = true; + /** + * Variable: lastX + * + * Holds the last x coordinate. + */ + mxAbstractCanvas2lastX = 0; -/** - * Variable: lastX - * - * Holds the last x coordinate. - */ -mxAbstractCanvas2lastX = 0; + /** + * Variable: lastY + * + * Holds the last y coordinate. + */ + mxAbstractCanvas2lastY = 0; -/** - * Variable: lastY - * - * Holds the last y coordinate. - */ -mxAbstractCanvas2lastY = 0; + /** + * Variable: moveOp + * + * Contains the string used for moving in paths. Default is 'M'. + */ + mxAbstractCanvas2moveOp = 'M'; -/** - * Variable: moveOp - * - * Contains the string used for moving in paths. Default is 'M'. - */ -mxAbstractCanvas2moveOp = 'M'; + /** + * Variable: lineOp + * + * Contains the string used for moving in paths. Default is 'L'. + */ + mxAbstractCanvas2lineOp = 'L'; -/** - * Variable: lineOp - * - * Contains the string used for moving in paths. Default is 'L'. - */ -mxAbstractCanvas2lineOp = 'L'; + /** + * Variable: quadOp + * + * Contains the string used for quadratic paths. Default is 'Q'. + */ + mxAbstractCanvas2quadOp = 'Q'; -/** - * Variable: quadOp - * - * Contains the string used for quadratic paths. Default is 'Q'. - */ -mxAbstractCanvas2quadOp = 'Q'; + /** + * Variable: curveOp + * + * Contains the string used for bezier curves. Default is 'C'. + */ + mxAbstractCanvas2curveOp = 'C'; -/** - * Variable: curveOp - * - * Contains the string used for bezier curves. Default is 'C'. - */ -mxAbstractCanvas2curveOp = 'C'; + /** + * Variable: closeOp + * + * Holds the operator for closing curves. Default is 'Z'. + */ + mxAbstractCanvas2closeOp = 'Z'; -/** - * Variable: closeOp - * - * Holds the operator for closing curves. Default is 'Z'. - */ -mxAbstractCanvas2closeOp = 'Z'; + /** + * Variable: pointerEvents + * + * Boolean value that specifies if events should be handled. Default is false. + */ + mxAbstractCanvas2pointerEvents = false; -/** - * Variable: pointerEvents - * - * Boolean value that specifies if events should be handled. Default is false. - */ -mxAbstractCanvas2pointerEvents = false; + /** + * Class: mxAbstractCanvas2D + * + * Base class for all canvases. A description of the public API is available in . + * All color values of will be converted to null in the state. + * + * Constructor: mxAbstractCanvas2D + * + * Constructs a new abstract canvas. + */ + constructor() { + /** + * Variable: converter + * + * Holds the to convert image URLs. + */ + this.converter = this.createUrlConverter(); -/** - * Function: createUrlConverter - * - * Create a new and returns it. - */ -mxAbstractCanvas2createUrlConverter = ()=> -{ - return new mxUrlConverter(); -}; - -/** - * Function: reset - * - * Resets the state of this canvas. - */ -mxAbstractCanvas2reset = ()=> -{ - this.state = this.createState(); - this.states = []; -}; - -/** - * Function: createState - * - * Creates the state of the this canvas. - */ -mxAbstractCanvas2createState = ()=> -{ - return { - dx: 0, - dy: 0, - scale: 1, - alpha: 1, - fillAlpha: 1, - strokeAlpha: 1, - fillColor: null, - gradientFillAlpha: 1, - gradientColor: null, - gradientAlpha: 1, - gradientDirection: null, - strokeColor: null, - strokeWidth: 1, - dashed: false, - dashPattern: '3 3', - fixDash: false, - lineCap: 'flat', - lineJoin: 'miter', - miterLimit: 10, - fontColor: '#000000', - fontBackgroundColor: null, - fontBorderColor: null, - fontSize: mxConstants.DEFAULT_FONTSIZE, - fontFamily: mxConstants.DEFAULT_FONTFAMILY, - fontStyle: 0, - shadow: false, - shadowColor: mxConstants.SHADOWCOLOR, - shadowAlpha: mxConstants.SHADOW_OPACITY, - shadowDx: mxConstants.SHADOW_OFFSET_X, - shadowDy: mxConstants.SHADOW_OFFSET_Y, - rotation: 0, - rotationCx: 0, - rotationCy: 0 + this.reset(); }; -}; -/** - * Function: format - * - * Rounds all numbers to integers. - */ -mxAbstractCanvas2format = (value)=> -{ - return Math.round(parseFloat(value)); -}; + /** + * Function: createUrlConverter + * + * Create a new and returns it. + */ + mxAbstractCanvas2createUrlConverter = () => { + return new mxUrlConverter(); + }; -/** - * Function: addOp - * - * Adds the given operation to the path. - */ -mxAbstractCanvas2addOp = ()=> -{ - if (this.path != null) - { - this.path.push(arguments[0]); - - if (arguments.length > 2) - { - var s = this.state; + /** + * Function: reset + * + * Resets the state of this canvas. + */ + mxAbstractCanvas2reset = () => { + this.state = this.createState(); + this.states = []; + }; - for (var i = 2; i < arguments.length; i += 2) - { - this.lastX = arguments[i - 1]; - this.lastY = arguments[i]; - - this.path.push(this.format((this.lastX + s.dx) * s.scale)); - this.path.push(this.format((this.lastY + s.dy) * s.scale)); + /** + * Function: createState + * + * Creates the state of the this canvas. + */ + mxAbstractCanvas2createState = () => { + return { + dx: 0, + dy: 0, + scale: 1, + alpha: 1, + fillAlpha: 1, + strokeAlpha: 1, + fillColor: null, + gradientFillAlpha: 1, + gradientColor: null, + gradientAlpha: 1, + gradientDirection: null, + strokeColor: null, + strokeWidth: 1, + dashed: false, + dashPattern: '3 3', + fixDash: false, + lineCap: 'flat', + lineJoin: 'miter', + miterLimit: 10, + fontColor: '#000000', + fontBackgroundColor: null, + fontBorderColor: null, + fontSize: mxConstants.DEFAULT_FONTSIZE, + fontFamily: mxConstants.DEFAULT_FONTFAMILY, + fontStyle: 0, + shadow: false, + shadowColor: mxConstants.SHADOWCOLOR, + shadowAlpha: mxConstants.SHADOW_OPACITY, + shadowDx: mxConstants.SHADOW_OFFSET_X, + shadowDy: mxConstants.SHADOW_OFFSET_Y, + rotation: 0, + rotationCx: 0, + rotationCy: 0 + }; + }; + + /** + * Function: format + * + * Rounds all numbers to integers. + */ + mxAbstractCanvas2format = (value) => { + return Math.round(parseFloat(value)); + }; + + /** + * Function: addOp + * + * Adds the given operation to the path. + */ + mxAbstractCanvas2addOp = () => { + if (this.path != null) { + this.path.push(arguments[0]); + + if (arguments.length > 2) { + var s = this.state; + + for (var i = 2; i < arguments.length; i += 2) { + this.lastX = arguments[i - 1]; + this.lastY = arguments[i]; + + this.path.push(this.format((this.lastX + s.dx) * s.scale)); + this.path.push(this.format((this.lastY + s.dy) * s.scale)); + } } } - } -}; + }; -/** - * Function: rotatePoint - * - * Rotates the given point and returns the result as an . - */ -mxAbstractCanvas2rotatePoint = (x, y, theta, cx, cy)=> -{ - var rad = theta * (Math.PI / 180); - - return mxUtils.getRotatedPoint(new mxPoint(x, y), Math.cos(rad), - Math.sin(rad), new mxPoint(cx, cy)); -}; + /** + * Function: rotatePoint + * + * Rotates the given point and returns the result as an . + */ + mxAbstractCanvas2rotatePoint = (x, y, theta, cx, cy) => { + var rad = theta * (Math.PI / 180); -/** - * Function: save - * - * Saves the current state. - */ -mxAbstractCanvas2save = ()=> -{ - this.states.push(this.state); - this.state = mxUtils.clone(this.state); -}; + return mxUtils.getRotatedPoint(new mxPoint(x, y), Math.cos(rad), + Math.sin(rad), new mxPoint(cx, cy)); + }; -/** - * Function: restore - * - * Restores the current state. - */ -mxAbstractCanvas2restore = ()=> -{ - if (this.states.length > 0) - { - this.state = this.states.pop(); - } -}; + /** + * Function: save + * + * Saves the current state. + */ + mxAbstractCanvas2save = () => { + this.states.push(this.state); + this.state = mxUtils.clone(this.state); + }; -/** - * Function: setLink - * - * Sets the current link. Hook for subclassers. - */ -mxAbstractCanvas2setLink = (link)=> -{ - // nop -}; - -/** - * Function: scale - * - * Scales the current state. - */ -mxAbstractCanvas2scale = (value)=> -{ - this.state.scale *= value; - this.state.strokeWidth *= value; -}; - -/** - * Function: translate - * - * Translates the current state. - */ -mxAbstractCanvas2translate = (dx, dy)=> -{ - this.state.dx += dx; - this.state.dy += dy; -}; - -/** - * Function: rotate - * - * Rotates the current state. - */ -mxAbstractCanvas2rotate = (theta, flipH, flipV, cx, cy)=> -{ - // nop -}; - -/** - * Function: setAlpha - * - * Sets the current alpha. - */ -mxAbstractCanvas2setAlpha = (value)=> -{ - this.state.alpha = value; -}; - -/** - * Function: setFillAlpha - * - * Sets the current solid fill alpha. - */ -mxAbstractCanvas2setFillAlpha = (value)=> -{ - this.state.fillAlpha = value; -}; - -/** - * Function: setStrokeAlpha - * - * Sets the current stroke alpha. - */ -mxAbstractCanvas2setStrokeAlpha = (value)=> -{ - this.state.strokeAlpha = value; -}; - -/** - * Function: setFillColor - * - * Sets the current fill color. - */ -mxAbstractCanvas2setFillColor = (value)=> -{ - if (value == mxConstants.NONE) - { - value = null; - } - - this.state.fillColor = value; - this.state.gradientColor = null; -}; - -/** - * Function: setGradient - * - * Sets the current gradient. - */ -mxAbstractCanvas2setGradient = (color1, color2, x, y, w, h, direction, alpha1, alpha2)=> -{ - var s = this.state; - s.fillColor = color1; - s.gradientFillAlpha = (alpha1 != null) ? alpha1 : 1; - s.gradientColor = color2; - s.gradientAlpha = (alpha2 != null) ? alpha2 : 1; - s.gradientDirection = direction; -}; - -/** - * Function: setStrokeColor - * - * Sets the current stroke color. - */ -mxAbstractCanvas2setStrokeColor = (value)=> -{ - if (value == mxConstants.NONE) - { - value = null; - } - - this.state.strokeColor = value; -}; - -/** - * Function: setStrokeWidth - * - * Sets the current stroke width. - */ -mxAbstractCanvas2setStrokeWidth = (value)=> -{ - this.state.strokeWidth = value; -}; - -/** - * Function: setDashed - * - * Enables or disables dashed lines. - */ -mxAbstractCanvas2setDashed = (value, fixDash)=> -{ - this.state.dashed = value; - this.state.fixDash = fixDash; -}; - -/** - * Function: setDashPattern - * - * Sets the current dash pattern. - */ -mxAbstractCanvas2setDashPattern = (value)=> -{ - this.state.dashPattern = value; -}; - -/** - * Function: setLineCap - * - * Sets the current line cap. - */ -mxAbstractCanvas2setLineCap = (value)=> -{ - this.state.lineCap = value; -}; - -/** - * Function: setLineJoin - * - * Sets the current line join. - */ -mxAbstractCanvas2setLineJoin = (value)=> -{ - this.state.lineJoin = value; -}; - -/** - * Function: setMiterLimit - * - * Sets the current miter limit. - */ -mxAbstractCanvas2setMiterLimit = (value)=> -{ - this.state.miterLimit = value; -}; - -/** - * Function: setFontColor - * - * Sets the current font color. - */ -mxAbstractCanvas2setFontColor = (value)=> -{ - if (value == mxConstants.NONE) - { - value = null; - } - - this.state.fontColor = value; -}; - -/** - * Function: setFontBackgroundColor - * - * Sets the current font background color. - */ -mxAbstractCanvas2setFontBackgroundColor = (value)=> -{ - if (value == mxConstants.NONE) - { - value = null; - } - - this.state.fontBackgroundColor = value; -}; - -/** - * Function: setFontBorderColor - * - * Sets the current font border color. - */ -mxAbstractCanvas2setFontBorderColor = (value)=> -{ - if (value == mxConstants.NONE) - { - value = null; - } - - this.state.fontBorderColor = value; -}; - -/** - * Function: setFontSize - * - * Sets the current font size. - */ -mxAbstractCanvas2setFontSize = (value)=> -{ - this.state.fontSize = parseFloat(value); -}; - -/** - * Function: setFontFamily - * - * Sets the current font family. - */ -mxAbstractCanvas2setFontFamily = (value)=> -{ - this.state.fontFamily = value; -}; - -/** - * Function: setFontStyle - * - * Sets the current font style. - */ -mxAbstractCanvas2setFontStyle = (value)=> -{ - if (value == null) - { - value = 0; - } - - this.state.fontStyle = value; -}; - -/** - * Function: setShadow - * - * Enables or disables and configures the current shadow. - */ -mxAbstractCanvas2setShadow = (enabled)=> -{ - this.state.shadow = enabled; -}; - -/** - * Function: setShadowColor - * - * Enables or disables and configures the current shadow. - */ -mxAbstractCanvas2setShadowColor = (value)=> -{ - if (value == mxConstants.NONE) - { - value = null; - } - - this.state.shadowColor = value; -}; - -/** - * Function: setShadowAlpha - * - * Enables or disables and configures the current shadow. - */ -mxAbstractCanvas2setShadowAlpha = (value)=> -{ - this.state.shadowAlpha = value; -}; - -/** - * Function: setShadowOffset - * - * Enables or disables and configures the current shadow. - */ -mxAbstractCanvas2setShadowOffset = (dx, dy)=> -{ - this.state.shadowDx = dx; - this.state.shadowDy = dy; -}; - -/** - * Function: begin - * - * Starts a new path. - */ -mxAbstractCanvas2begin = ()=> -{ - this.lastX = 0; - this.lastY = 0; - this.path = []; -}; - -/** - * Function: moveTo - * - * Moves the current path the given coordinates. - */ -mxAbstractCanvas2moveTo = (x, y)=> -{ - this.addOp(this.moveOp, x, y); -}; - -/** - * Function: lineTo - * - * Draws a line to the given coordinates. Uses moveTo with the op argument. - */ -mxAbstractCanvas2lineTo = (x, y)=> -{ - this.addOp(this.lineOp, x, y); -}; - -/** - * Function: quadTo - * - * Adds a quadratic curve to the current path. - */ -mxAbstractCanvas2quadTo = (x1, y1, x2, y2)=> -{ - this.addOp(this.quadOp, x1, y1, x2, y2); -}; - -/** - * Function: curveTo - * - * Adds a bezier curve to the current path. - */ -mxAbstractCanvas2curveTo = (x1, y1, x2, y2, x3, y3)=> -{ - this.addOp(this.curveOp, x1, y1, x2, y2, x3, y3); -}; - -/** - * Function: arcTo - * - * Adds the given arc to the current path. This is a synthetic operation that - * is broken down into curves. - */ -mxAbstractCanvas2arcTo = (rx, ry, angle, largeArcFlag, sweepFlag, x, y)=> -{ - var curves = mxUtils.arcToCurves(this.lastX, this.lastY, rx, ry, angle, largeArcFlag, sweepFlag, x, y); - - if (curves != null) - { - for (var i = 0; i < curves.length; i += 6) - { - this.curveTo(curves[i], curves[i + 1], curves[i + 2], - curves[i + 3], curves[i + 4], curves[i + 5]); + /** + * Function: restore + * + * Restores the current state. + */ + mxAbstractCanvas2restore = () => { + if (this.states.length > 0) { + this.state = this.states.pop(); } - } -}; + }; -/** - * Function: close - * - * Closes the current path. - */ -mxAbstractCanvas2close = (x1, y1, x2, y2, x3, y3)=> -{ - this.addOp(this.closeOp); -}; + /** + * Function: setLink + * + * Sets the current link. Hook for subclassers. + */ + mxAbstractCanvas2setLink = (link) => { + // nop + }; -/** - * Function: end - * - * Empty implementation for backwards compatibility. This will be removed. - */ -mxAbstractCanvas2end = ()=> { }; + /** + * Function: scale + * + * Scales the current state. + */ + mxAbstractCanvas2scale = (value) => { + this.state.scale *= value; + this.state.strokeWidth *= value; + }; + + /** + * Function: translate + * + * Translates the current state. + */ + mxAbstractCanvas2translate = (dx, dy) => { + this.state.dx += dx; + this.state.dy += dy; + }; + + /** + * Function: rotate + * + * Rotates the current state. + */ + mxAbstractCanvas2rotate = (theta, flipH, flipV, cx, cy) => { + // nop + }; + + /** + * Function: setAlpha + * + * Sets the current alpha. + */ + mxAbstractCanvas2setAlpha = (value) => { + this.state.alpha = value; + }; + + /** + * Function: setFillAlpha + * + * Sets the current solid fill alpha. + */ + mxAbstractCanvas2setFillAlpha = (value) => { + this.state.fillAlpha = value; + }; + + /** + * Function: setStrokeAlpha + * + * Sets the current stroke alpha. + */ + mxAbstractCanvas2setStrokeAlpha = (value) => { + this.state.strokeAlpha = value; + }; + + /** + * Function: setFillColor + * + * Sets the current fill color. + */ + mxAbstractCanvas2setFillColor = (value) => { + if (value == mxConstants.NONE) { + value = null; + } + + this.state.fillColor = value; + this.state.gradientColor = null; + }; + + /** + * Function: setGradient + * + * Sets the current gradient. + */ + mxAbstractCanvas2setGradient = (color1, color2, x, y, w, h, direction, alpha1, alpha2) => { + var s = this.state; + s.fillColor = color1; + s.gradientFillAlpha = (alpha1 != null) ? alpha1 : 1; + s.gradientColor = color2; + s.gradientAlpha = (alpha2 != null) ? alpha2 : 1; + s.gradientDirection = direction; + }; + + /** + * Function: setStrokeColor + * + * Sets the current stroke color. + */ + mxAbstractCanvas2setStrokeColor = (value) => { + if (value == mxConstants.NONE) { + value = null; + } + + this.state.strokeColor = value; + }; + + /** + * Function: setStrokeWidth + * + * Sets the current stroke width. + */ + mxAbstractCanvas2setStrokeWidth = (value) => { + this.state.strokeWidth = value; + }; + + /** + * Function: setDashed + * + * Enables or disables dashed lines. + */ + mxAbstractCanvas2setDashed = (value, fixDash) => { + this.state.dashed = value; + this.state.fixDash = fixDash; + }; + + /** + * Function: setDashPattern + * + * Sets the current dash pattern. + */ + mxAbstractCanvas2setDashPattern = (value) => { + this.state.dashPattern = value; + }; + + /** + * Function: setLineCap + * + * Sets the current line cap. + */ + mxAbstractCanvas2setLineCap = (value) => { + this.state.lineCap = value; + }; + + /** + * Function: setLineJoin + * + * Sets the current line join. + */ + mxAbstractCanvas2setLineJoin = (value) => { + this.state.lineJoin = value; + }; + + /** + * Function: setMiterLimit + * + * Sets the current miter limit. + */ + mxAbstractCanvas2setMiterLimit = (value) => { + this.state.miterLimit = value; + }; + + /** + * Function: setFontColor + * + * Sets the current font color. + */ + mxAbstractCanvas2setFontColor = (value) => { + if (value == mxConstants.NONE) { + value = null; + } + + this.state.fontColor = value; + }; + + /** + * Function: setFontBackgroundColor + * + * Sets the current font background color. + */ + mxAbstractCanvas2setFontBackgroundColor = (value) => { + if (value == mxConstants.NONE) { + value = null; + } + + this.state.fontBackgroundColor = value; + }; + + /** + * Function: setFontBorderColor + * + * Sets the current font border color. + */ + mxAbstractCanvas2setFontBorderColor = (value) => { + if (value == mxConstants.NONE) { + value = null; + } + + this.state.fontBorderColor = value; + }; + + /** + * Function: setFontSize + * + * Sets the current font size. + */ + mxAbstractCanvas2setFontSize = (value) => { + this.state.fontSize = parseFloat(value); + }; + + /** + * Function: setFontFamily + * + * Sets the current font family. + */ + mxAbstractCanvas2setFontFamily = (value) => { + this.state.fontFamily = value; + }; + + /** + * Function: setFontStyle + * + * Sets the current font style. + */ + mxAbstractCanvas2setFontStyle = (value) => { + if (value == null) { + value = 0; + } + + this.state.fontStyle = value; + }; + + /** + * Function: setShadow + * + * Enables or disables and configures the current shadow. + */ + mxAbstractCanvas2setShadow = (enabled) => { + this.state.shadow = enabled; + }; + + /** + * Function: setShadowColor + * + * Enables or disables and configures the current shadow. + */ + mxAbstractCanvas2setShadowColor = (value) => { + if (value == mxConstants.NONE) { + value = null; + } + + this.state.shadowColor = value; + }; + + /** + * Function: setShadowAlpha + * + * Enables or disables and configures the current shadow. + */ + mxAbstractCanvas2setShadowAlpha = (value) => { + this.state.shadowAlpha = value; + }; + + /** + * Function: setShadowOffset + * + * Enables or disables and configures the current shadow. + */ + mxAbstractCanvas2setShadowOffset = (dx, dy) => { + this.state.shadowDx = dx; + this.state.shadowDy = dy; + }; + + /** + * Function: begin + * + * Starts a new path. + */ + mxAbstractCanvas2begin = () => { + this.lastX = 0; + this.lastY = 0; + this.path = []; + }; + + /** + * Function: moveTo + * + * Moves the current path the given coordinates. + */ + mxAbstractCanvas2moveTo = (x, y) => { + this.addOp(this.moveOp, x, y); + }; + + /** + * Function: lineTo + * + * Draws a line to the given coordinates. Uses moveTo with the op argument. + */ + mxAbstractCanvas2lineTo = (x, y) => { + this.addOp(this.lineOp, x, y); + }; + + /** + * Function: quadTo + * + * Adds a quadratic curve to the current path. + */ + mxAbstractCanvas2quadTo = (x1, y1, x2, y2) => { + this.addOp(this.quadOp, x1, y1, x2, y2); + }; + + /** + * Function: curveTo + * + * Adds a bezier curve to the current path. + */ + mxAbstractCanvas2curveTo = (x1, y1, x2, y2, x3, y3) => { + this.addOp(this.curveOp, x1, y1, x2, y2, x3, y3); + }; + + /** + * Function: arcTo + * + * Adds the given arc to the current path. This is a synthetic operation that + * is broken down into curves. + */ + mxAbstractCanvas2arcTo = (rx, ry, angle, largeArcFlag, sweepFlag, x, y) => { + var curves = mxUtils.arcToCurves(this.lastX, this.lastY, rx, ry, angle, largeArcFlag, sweepFlag, x, y); + + if (curves != null) { + for (var i = 0; i < curves.length; i += 6) { + this.curveTo(curves[i], curves[i + 1], curves[i + 2], + curves[i + 3], curves[i + 4], curves[i + 5]); + } + } + }; + + /** + * Function: close + * + * Closes the current path. + */ + mxAbstractCanvas2close = (x1, y1, x2, y2, x3, y3) => { + this.addOp(this.closeOp); + }; + + /** + * Function: end + * + * Empty implementation for backwards compatibility. This will be removed. + */ + mxAbstractCanvas2end = () => { + }; +} + +export default mxAbstractCanvas2D; diff --git a/src/js/util/mxAnimation.js b/src/js/util/mxAnimation.js index cd73f6ef1..d301f9685 100644 --- a/src/js/util/mxAnimation.js +++ b/src/js/util/mxAnimation.js @@ -2,91 +2,86 @@ * Copyright (c) 2006-2015, JGraph Ltd * Copyright (c) 2006-2015, Gaudenz Alder */ -/** - * - * Class: mxAnimation - * - * Implements a basic animation in JavaScript. - * - * Constructor: mxAnimation - * - * Constructs an animation. - * - * Parameters: - * - * graph - Reference to the enclosing . - */ -function mxAnimation(delay) -{ - this.delay = (delay != null) ? delay : 20; -}; +import mxUtils from "./mxUtils"; +import mxEventSource from "./mxEventSource"; +import mxEventObject from "./mxEventObject"; -/** - * Extends mxEventSource. - */ -mxAnimation.prototype = new mxEventSource(); -constructor = mxAnimation; +class mxAnimation extends mxEventSource { + /** + * Variable: delay + * + * Specifies the delay between the animation steps. Defaul is 30ms. + */ + delay = null; -/** - * Variable: delay - * - * Specifies the delay between the animation steps. Defaul is 30ms. - */ -delay = null; + /** + * Variable: thread + * + * Reference to the thread while the animation is running. + */ + thread = null; -/** - * Variable: thread - * - * Reference to the thread while the animation is running. - */ -thread = null; + /** + * + * Class: mxAnimation + * + * Implements a basic animation in JavaScript. + * + * Constructor: mxAnimation + * + * Constructs an animation. + * + * Parameters: + * + * graph - Reference to the enclosing . + */ + constructor(delay) { + this.delay = (delay != null) ? delay : 20; + }; -/** - * Function: isRunning - * - * Returns true if the animation is running. - */ -isRunning = ()=> -{ - return this.thread != null; -}; + /** + * Function: isRunning + * + * Returns true if the animation is running. + */ + isRunning = () => { + return this.thread != null; + }; -/** - * Function: startAnimation - * - * Starts the animation by repeatedly invoking updateAnimation. - */ -startAnimation = ()=> -{ - if (this.thread == null) - { - this.thread = window.setInterval(mxUtils.bind(this, this.updateAnimation), this.delay); - } -}; + /** + * Function: startAnimation + * + * Starts the animation by repeatedly invoking updateAnimation. + */ + startAnimation = () => { + if (this.thread == null) { + this.thread = window.setInterval(mxUtils.bind(this, this.updateAnimation), this.delay); + } + }; -/** - * Function: updateAnimation - * - * Hook for subclassers to implement the animation. Invoke stopAnimation - * when finished, startAnimation to resume. This is called whenever the - * timer fires and fires an mxEvent.EXECUTE event with no properties. - */ -updateAnimation = ()=> -{ - this.fireEvent(new mxEventObject(mxEvent.EXECUTE)); -}; + /** + * Function: updateAnimation + * + * Hook for subclassers to implement the animation. Invoke stopAnimation + * when finished, startAnimation to resume. This is called whenever the + * timer fires and fires an mxEvent.EXECUTE event with no properties. + */ + updateAnimation = () => { + this.fireEvent(new mxEventObject(mxEvent.EXECUTE)); + }; -/** - * Function: stopAnimation - * - * Stops the animation by deleting the timer and fires an . - */ -stopAnimation = ()=> -{ - if (this.thread != null) - { - window.clearInterval(this.thread); - this.thread = null; - this.fireEvent(new mxEventObject(mxEvent.DONE)); - } -}; + /** + * Function: stopAnimation + * + * Stops the animation by deleting the timer and fires an . + */ + stopAnimation = () => { + if (this.thread != null) { + window.clearInterval(this.thread); + this.thread = null; + this.fireEvent(new mxEventObject(mxEvent.DONE)); + } + }; +} + +export default mxAnimation; diff --git a/src/js/util/mxAutoSaveManager.js b/src/js/util/mxAutoSaveManager.js index 5f69b50af..8a4624131 100644 --- a/src/js/util/mxAutoSaveManager.js +++ b/src/js/util/mxAutoSaveManager.js @@ -2,212 +2,198 @@ * Copyright (c) 2006-2015, JGraph Ltd * Copyright (c) 2006-2015, Gaudenz Alder */ -/** - * Class: mxAutoSaveManager - * - * Manager for automatically saving diagrams. The hook must be - * implemented. - * - * Example: - * - * (code) - * var mgr = new mxAutoSaveManager(editor.graph); - * mgr.save = ()=> - * { - * mxLog.show(); - * mxLog.debug('save'); - * }; - * (end) - * - * Constructor: mxAutoSaveManager - * - * Constructs a new automatic layout for the given graph. - * - * Arguments: - * - * graph - Reference to the enclosing graph. - */ -function mxAutoSaveManager(graph) -{ - // Notifies the manager of a change - this.changeHandler = mxUtils.bind(this, (sender, evt)=> - { - if (this.isEnabled()) - { - this.graphModelChanged(evt.getProperty('edit').changes); +import mxEventSource from "./mxEventSource"; +import mxUtils from "./mxUtils"; + +class mxAutoSaveManager extends mxEventSource { + /** + * Variable: graph + * + * Reference to the enclosing . + */ + graph = null; + + /** + * Variable: autoSaveDelay + * + * Minimum amount of seconds between two consecutive autosaves. Eg. a + * value of 1 (s) means the graph is not stored more than once per second. + * Default is 10. + */ + autoSaveDelay = 10; + + /** + * Variable: autoSaveThrottle + * + * Minimum amount of seconds between two consecutive autosaves triggered by + * more than changes within a timespan of less than + * seconds. Eg. a value of 1 (s) means the graph is not + * stored more than once per second even if there are more than + * changes within that timespan. Default is 2. + */ + autoSaveThrottle = 2; + + /** + * Variable: autoSaveThreshold + * + * Minimum amount of ignored changes before an autosave. Eg. a value of 2 + * means after 2 change of the graph model the autosave will trigger if the + * condition below is true. Default is 5. + */ + autoSaveThreshold = 5; + + /** + * Variable: ignoredChanges + * + * Counter for ignored changes in autosave. + */ + ignoredChanges = 0; + + /** + * Variable: lastSnapshot + * + * Used for autosaving. See . + */ + lastSnapshot = 0; + + /** + * Variable: enabled + * + * Specifies if event handling is enabled. Default is true. + */ + enabled = true; + + /** + * Variable: changeHandler + * + * Holds the function that handles graph model changes. + */ + changeHandler = null; + + /** + * Class: mxAutoSaveManager + * + * Manager for automatically saving diagrams. The hook must be + * implemented. + * + * Example: + * + * (code) + * var mgr = new mxAutoSaveManager(editor.graph); + * mgr.save = ()=> + * { + * mxLog.show(); + * mxLog.debug('save'); + * }; + * (end) + * + * Constructor: mxAutoSaveManager + * + * Constructs a new automatic layout for the given graph. + * + * Arguments: + * + * graph - Reference to the enclosing graph. + */ + constructor(graph) { + // Notifies the manager of a change + this.changeHandler = mxUtils.bind(this, (sender, evt) => { + if (this.isEnabled()) { + this.graphModelChanged(evt.getProperty('edit').changes); + } + }); + + this.setGraph(graph); + }; + + /** + * Function: isEnabled + * + * Returns true if events are handled. This implementation + * returns . + */ + isEnabled = () => { + return this.enabled; + }; + + /** + * Function: setEnabled + * + * Enables or disables event handling. This implementation + * updates . + * + * Parameters: + * + * enabled - Boolean that specifies the new enabled state. + */ + setEnabled = (value) => { + this.enabled = value; + }; + + /** + * Function: setGraph + * + * Sets the graph that the layouts operate on. + */ + setGraph = (graph) => { + if (this.graph != null) { + this.graph.getModel().removeListener(this.changeHandler); } - }); - this.setGraph(graph); -}; + this.graph = graph; -/** - * Extends mxEventSource. - */ -mxAutoSaveManager.prototype = new mxEventSource(); -constructor = mxAutoSaveManager; + if (this.graph != null) { + this.graph.getModel().addListener(mxEvent.CHANGE, this.changeHandler); + } + }; -/** - * Variable: graph - * - * Reference to the enclosing . - */ -graph = null; + /** + * Function: save + * + * Empty hook that is called if the graph should be saved. + */ + save = () => { + // empty + }; -/** - * Variable: autoSaveDelay - * - * Minimum amount of seconds between two consecutive autosaves. Eg. a - * value of 1 (s) means the graph is not stored more than once per second. - * Default is 10. - */ -autoSaveDelay = 10; + /** + * Function: graphModelChanged + * + * Invoked when the graph model has changed. + */ + graphModelChanged = (changes) => { + var now = new Date().getTime(); + var dt = (now - this.lastSnapshot) / 1000; -/** - * Variable: autoSaveThrottle - * - * Minimum amount of seconds between two consecutive autosaves triggered by - * more than changes within a timespan of less than - * seconds. Eg. a value of 1 (s) means the graph is not - * stored more than once per second even if there are more than - * changes within that timespan. Default is 2. - */ -autoSaveThrottle = 2; + if (dt > this.autoSaveDelay || + (this.ignoredChanges >= this.autoSaveThreshold && + dt > this.autoSaveThrottle)) { + this.save(); + this.reset(); + } else { + // Increments the number of ignored changes + this.ignoredChanges++; + } + }; -/** - * Variable: autoSaveThreshold - * - * Minimum amount of ignored changes before an autosave. Eg. a value of 2 - * means after 2 change of the graph model the autosave will trigger if the - * condition below is true. Default is 5. - */ -autoSaveThreshold = 5; + /** + * Function: reset + * + * Resets all counters. + */ + reset = () => { + this.lastSnapshot = new Date().getTime(); + this.ignoredChanges = 0; + }; -/** - * Variable: ignoredChanges - * - * Counter for ignored changes in autosave. - */ -ignoredChanges = 0; + /** + * Function: destroy + * + * Removes all handlers from the and deletes the reference to it. + */ + destroy = () => { + this.setGraph(null); + }; +} -/** - * Variable: lastSnapshot - * - * Used for autosaving. See . - */ -lastSnapshot = 0; - -/** - * Variable: enabled - * - * Specifies if event handling is enabled. Default is true. - */ -enabled = true; - -/** - * Variable: changeHandler - * - * Holds the function that handles graph model changes. - */ -changeHandler = null; - -/** - * Function: isEnabled - * - * Returns true if events are handled. This implementation - * returns . - */ -isEnabled = ()=> -{ - return this.enabled; -}; - -/** - * Function: setEnabled - * - * Enables or disables event handling. This implementation - * updates . - * - * Parameters: - * - * enabled - Boolean that specifies the new enabled state. - */ -setEnabled = (value)=> -{ - this.enabled = value; -}; - -/** - * Function: setGraph - * - * Sets the graph that the layouts operate on. - */ -setGraph = (graph)=> -{ - if (this.graph != null) - { - this.graph.getModel().removeListener(this.changeHandler); - } - - this.graph = graph; - - if (this.graph != null) - { - this.graph.getModel().addListener(mxEvent.CHANGE, this.changeHandler); - } -}; - -/** - * Function: save - * - * Empty hook that is called if the graph should be saved. - */ -save = ()=> -{ - // empty -}; - -/** - * Function: graphModelChanged - * - * Invoked when the graph model has changed. - */ -graphModelChanged = (changes)=> -{ - var now = new Date().getTime(); - var dt = (now - this.lastSnapshot) / 1000; - - if (dt > this.autoSaveDelay || - (this.ignoredChanges >= this.autoSaveThreshold && - dt > this.autoSaveThrottle)) - { - this.save(); - this.reset(); - } - else - { - // Increments the number of ignored changes - this.ignoredChanges++; - } -}; - -/** - * Function: reset - * - * Resets all counters. - */ -reset = ()=> -{ - this.lastSnapshot = new Date().getTime(); - this.ignoredChanges = 0; -}; - -/** - * Function: destroy - * - * Removes all handlers from the and deletes the reference to it. - */ -destroy = ()=> -{ - this.setGraph(null); -}; +export default mxAutoSaveManager; diff --git a/src/js/util/mxClipboard.js b/src/js/util/mxClipboard.js index cdec06cec..0ae672370 100644 --- a/src/js/util/mxClipboard.js +++ b/src/js/util/mxClipboard.js @@ -2,8 +2,7 @@ * Copyright (c) 2006-2015, JGraph Ltd * Copyright (c) 2006-2015, Gaudenz Alder */ -var mxClipboard = -{ +var mxClipboard = { /** * Class: mxClipboard * @@ -217,5 +216,6 @@ var mxClipboard = return cells; } - }; + +export default mxClipboard; diff --git a/src/js/util/mxConstants.js b/src/js/util/mxConstants.js index d1074a9ca..01b4065a5 100644 --- a/src/js/util/mxConstants.js +++ b/src/js/util/mxConstants.js @@ -2,8 +2,8 @@ * Copyright (c) 2006-2015, JGraph Ltd * Copyright (c) 2006-2015, Gaudenz Alder */ - var mxConstants = - { + +var mxConstants = { /** * Class: mxConstants * @@ -2336,3 +2336,5 @@ */ PERIMETER_TRIANGLE: 'trianglePerimeter' }; + +export default mxConstants; diff --git a/src/js/util/mxDictionary.js b/src/js/util/mxDictionary.js index fc028d9c9..fc6a8aea1 100644 --- a/src/js/util/mxDictionary.js +++ b/src/js/util/mxDictionary.js @@ -12,119 +12,113 @@ * * Constructs a new dictionary which allows object to be used as keys. */ -function mxDictionary() -{ - this.clear(); -}; -/** - * Function: map - * - * Stores the (key, value) pairs in this dictionary. - */ -map = null; +class mxDictionary { + constructor() { + this.clear(); + }; -/** - * Function: clear - * - * Clears the dictionary. - */ -clear = ()=> -{ - this.map = {}; -}; + /** + * Function: map + * + * Stores the (key, value) pairs in this dictionary. + */ + map = null; -/** - * Function: get - * - * Returns the value for the given key. - */ -get = (key)=> -{ - var id = mxObjectIdentity.get(key); + /** + * Function: clear + * + * Clears the dictionary. + */ + clear = () => { + this.map = {}; + }; - return this.map[id]; -}; + /** + * Function: get + * + * Returns the value for the given key. + */ + get = (key) => { + var id = mxObjectIdentity.get(key); -/** - * Function: put - * - * Stores the value under the given key and returns the previous - * value for that key. - */ -put = (key, value)=> -{ - var id = mxObjectIdentity.get(key); - var previous = this.map[id]; - this.map[id] = value; + return this.map[id]; + }; - return previous; -}; + /** + * Function: put + * + * Stores the value under the given key and returns the previous + * value for that key. + */ + put = (key, value) => { + var id = mxObjectIdentity.get(key); + var previous = this.map[id]; + this.map[id] = value; -/** - * Function: remove - * - * Removes the value for the given key and returns the value that - * has been removed. - */ -remove = (key)=> -{ - var id = mxObjectIdentity.get(key); - var previous = this.map[id]; - delete this.map[id]; + return previous; + }; - return previous; -}; + /** + * Function: remove + * + * Removes the value for the given key and returns the value that + * has been removed. + */ + remove = (key) => { + var id = mxObjectIdentity.get(key); + var previous = this.map[id]; + delete this.map[id]; -/** - * Function: getKeys - * - * Returns all keys as an array. - */ -getKeys = ()=> -{ - var result = []; + return previous; + }; - for (var key in this.map) - { - result.push(key); - } + /** + * Function: getKeys + * + * Returns all keys as an array. + */ + getKeys = () => { + var result = []; - return result; -}; + for (var key in this.map) { + result.push(key); + } -/** - * Function: getValues - * - * Returns all values as an array. - */ -getValues = ()=> -{ - var result = []; + return result; + }; - for (var key in this.map) - { - result.push(this.map[key]); - } + /** + * Function: getValues + * + * Returns all values as an array. + */ + getValues = () => { + var result = []; - return result; -}; + for (var key in this.map) { + result.push(this.map[key]); + } -/** - * Function: visit - * - * Visits all entries in the dictionary using the given function with the - * following signature: (key, value)=> where key is a string and - * value is an object. - * - * Parameters: - * - * visitor - A function that takes the key and value as arguments. - */ -visit = (visitor)=> -{ - for (var key in this.map) - { - visitor(key, this.map[key]); - } -}; + return result; + }; + + /** + * Function: visit + * + * Visits all entries in the dictionary using the given function with the + * following signature: (key, value)=> where key is a string and + * value is an object. + * + * Parameters: + * + * visitor - A function that takes the key and value as arguments. + */ + visit = (visitor) => { + for (var key in this.map) { + visitor(key, this.map[key]); + } + }; +} + +export default mxDictionary; diff --git a/src/js/util/mxDivResizer.js b/src/js/util/mxDivResizer.js index 337db8531..61018c4d0 100644 --- a/src/js/util/mxDivResizer.js +++ b/src/js/util/mxDivResizer.js @@ -37,115 +37,108 @@ * container - Optional Container that contains the div. Default is the * window. */ -function mxDivResizer(div, container) -{ - if (div.nodeName.toLowerCase() == 'div') - { - if (container == null) - { - container = window; +class mxDivResizer { + constructor(div, container) { + if (div.nodeName.toLowerCase() == 'div') { + if (container == null) { + container = window; + } + + this.div = div; + var style = mxUtils.getCurrentStyle(div); + + if (style != null) { + this.resizeWidth = style.width == 'auto'; + this.resizeHeight = style.height == 'auto'; + } + + mxEvent.addListener(container, 'resize', + mxUtils.bind(this, (evt) => { + if (!this.handlingResize) { + this.handlingResize = true; + this.resize(); + this.handlingResize = false; + } + }) + ); + + this.resize(); + } + }; + + /** + * Function: resizeWidth + * + * Boolean specifying if the width should be updated. + */ + resizeWidth = true; + + /** + * Function: resizeHeight + * + * Boolean specifying if the height should be updated. + */ + resizeHeight = true; + + /** + * Function: handlingResize + * + * Boolean specifying if the width should be updated. + */ + handlingResize = false; + + /** + * Function: resize + * + * Updates the style of the DIV after the window has been resized. + */ + resize = () => { + var w = this.getDocumentWidth(); + var h = this.getDocumentHeight(); + + var l = parseInt(this.div.style.left); + var r = parseInt(this.div.style.right); + var t = parseInt(this.div.style.top); + var b = parseInt(this.div.style.bottom); + + if (this.resizeWidth && + !isNaN(l) && + !isNaN(r) && + l >= 0 && + r >= 0 && + w - r - l > 0) { + this.div.style.width = (w - r - l) + 'px'; } - this.div = div; - var style = mxUtils.getCurrentStyle(div); - - if (style != null) - { - this.resizeWidth = style.width == 'auto'; - this.resizeHeight = style.height == 'auto'; + if (this.resizeHeight && + !isNaN(t) && + !isNaN(b) && + t >= 0 && + b >= 0 && + h - t - b > 0) { + this.div.style.height = (h - t - b) + 'px'; } - - mxEvent.addListener(container, 'resize', - mxUtils.bind(this, (evt)=> - { - if (!this.handlingResize) - { - this.handlingResize = true; - this.resize(); - this.handlingResize = false; - } - }) - ); - - this.resize(); - } -}; + }; -/** - * Function: resizeWidth - * - * Boolean specifying if the width should be updated. - */ -resizeWidth = true; + /** + * Function: getDocumentWidth + * + * Hook for subclassers to return the width of the document (without + * scrollbars). + */ + getDocumentWidth = () => { + return document.body.clientWidth; + }; -/** - * Function: resizeHeight - * - * Boolean specifying if the height should be updated. - */ -resizeHeight = true; + /** + * Function: getDocumentHeight + * + * Hook for subclassers to return the height of the document (without + * scrollbars). + */ + getDocumentHeight = () => { + return document.body.clientHeight; + }; +} -/** - * Function: handlingResize - * - * Boolean specifying if the width should be updated. - */ -handlingResize = false; - -/** - * Function: resize - * - * Updates the style of the DIV after the window has been resized. - */ -resize = ()=> -{ - var w = this.getDocumentWidth(); - var h = this.getDocumentHeight(); - - var l = parseInt(this.div.style.left); - var r = parseInt(this.div.style.right); - var t = parseInt(this.div.style.top); - var b = parseInt(this.div.style.bottom); - - if (this.resizeWidth && - !isNaN(l) && - !isNaN(r) && - l >= 0 && - r >= 0 && - w - r - l > 0) - { - this.div.style.width = (w - r - l)+'px'; - } - - if (this.resizeHeight && - !isNaN(t) && - !isNaN(b) && - t >= 0 && - b >= 0 && - h - t - b > 0) - { - this.div.style.height = (h - t - b)+'px'; - } -}; - -/** - * Function: getDocumentWidth - * - * Hook for subclassers to return the width of the document (without - * scrollbars). - */ -getDocumentWidth = ()=> -{ - return document.body.clientWidth; -}; - -/** - * Function: getDocumentHeight - * - * Hook for subclassers to return the height of the document (without - * scrollbars). - */ -getDocumentHeight = ()=> -{ - return document.body.clientHeight; -}; +export default mxDivResizer; diff --git a/src/js/util/mxDragSource.js b/src/js/util/mxDragSource.js index eb721646b..013e8f1e4 100644 --- a/src/js/util/mxDragSource.js +++ b/src/js/util/mxDragSource.js @@ -2,724 +2,664 @@ * Copyright (c) 2006-2015, JGraph Ltd * Copyright (c) 2006-2015, Gaudenz Alder */ -/** - * Class: mxDragSource - * - * Wrapper to create a drag source from a DOM element so that the element can - * be dragged over a graph and dropped into the graph as a new cell. - * - * Problem is that in the dropHandler the current preview location is not - * available, so the preview and the dropHandler must match. - * - * Constructor: mxDragSource - * - * Constructs a new drag source for the given element. - */ -function mxDragSource(element, dropHandler) -{ - this.element = element; - this.dropHandler = dropHandler; - - // Handles a drag gesture on the element - mxEvent.addGestureListeners(element, mxUtils.bind(this, (evt)=> - { - this.mouseDown(evt); - })); - - // Prevents native drag and drop - mxEvent.addListener(element, 'dragstart', (evt)=> - { - mxEvent.consume(evt); - }); - - this.eventConsumer = (sender, evt)=> - { - var evtName = evt.getProperty('eventName'); - var me = evt.getProperty('event'); - - if (evtName != mxEvent.MOUSE_DOWN) - { - me.consume(); +import mxRectangle from "./mxRectangle"; +import mxCellHighlight from "../handler/mxCellHighlight"; +import mxUtils from "./mxUtils"; + +class mxDragSource { + /** + * Variable: element + * + * Reference to the DOM node which was made draggable. + */ + element = null; + + /** + * Variable: dropHandler + * + * Holds the DOM node that is used to represent the drag preview. If this is + * null then the source element will be cloned and used for the drag preview. + */ + dropHandler = null; + + /** + * Variable: dragOffset + * + * that specifies the offset of the . Default is null. + */ + dragOffset = null; + + /** + * Variable: dragElement + * + * Holds the DOM node that is used to represent the drag preview. If this is + * null then the source element will be cloned and used for the drag preview. + */ + dragElement = null; + + /** + * Variable: previewElement + * + * Optional that specifies the unscaled size of the preview. + */ + previewElement = null; + + /** + * Variable: previewOffset + * + * Optional that specifies the offset of the preview in pixels. + */ + previewOffset = null; + + /** + * Variable: enabled + * + * Specifies if this drag source is enabled. Default is true. + */ + enabled = true; + + /** + * Variable: currentGraph + * + * Reference to the that is the current drop target. + */ + currentGraph = null; + + /** + * Variable: currentDropTarget + * + * Holds the current drop target under the mouse. + */ + currentDropTarget = null; + + /** + * Variable: currentPoint + * + * Holds the current drop location. + */ + currentPoint = null; + + /** + * Variable: currentGuide + * + * Holds an for the if is not null. + */ + currentGuide = null; + + /** + * Variable: currentGuide + * + * Holds an for the if is not null. + */ + currentHighlight = null; + + /** + * Variable: autoscroll + * + * Specifies if the graph should scroll automatically. Default is true. + */ + autoscroll = true; + + /** + * Variable: guidesEnabled + * + * Specifies if should be enabled. Default is true. + */ + guidesEnabled = true; + + /** + * Variable: gridEnabled + * + * Specifies if the grid should be allowed. Default is true. + */ + gridEnabled = true; + + /** + * Variable: highlightDropTargets + * + * Specifies if drop targets should be highlighted. Default is true. + */ + highlightDropTargets = true; + + /** + * Variable: dragElementZIndex + * + * ZIndex for the drag element. Default is 100. + */ + dragElementZIndex = 100; + + /** + * Variable: dragElementOpacity + * + * Opacity of the drag element in %. Default is 70. + */ + dragElementOpacity = 70; + + /** + * Variable: checkEventSource + * + * Whether the event source should be checked in . Default + * is true. + */ + checkEventSource = true; + + /** + * Class: mxDragSource + * + * Wrapper to create a drag source from a DOM element so that the element can + * be dragged over a graph and dropped into the graph as a new cell. + * + * Problem is that in the dropHandler the current preview location is not + * available, so the preview and the dropHandler must match. + * + * Constructor: mxDragSource + * + * Constructs a new drag source for the given element. + */ + constructor(element, dropHandler) { + this.element = element; + this.dropHandler = dropHandler; + + // Handles a drag gesture on the element + mxEvent.addGestureListeners(element, mxUtils.bind(this, (evt) => { + this.mouseDown(evt); + })); + + // Prevents native drag and drop + mxEvent.addListener(element, 'dragstart', (evt) => { + mxEvent.consume(evt); + }); + + this.eventConsumer = (sender, evt) => { + var evtName = evt.getProperty('eventName'); + var me = evt.getProperty('event'); + + if (evtName != mxEvent.MOUSE_DOWN) { + me.consume(); + } + }; + }; + + /** + * Function: isEnabled + * + * Returns . + */ + isEnabled = () => { + return this.enabled; + }; + + /** + * Function: setEnabled + * + * Sets . + */ + setEnabled = (value) => { + this.enabled = value; + }; + + /** + * Function: isGuidesEnabled + * + * Returns . + */ + isGuidesEnabled = () => { + return this.guidesEnabled; + }; + + /** + * Function: setGuidesEnabled + * + * Sets . + */ + setGuidesEnabled = (value) => { + this.guidesEnabled = value; + }; + + /** + * Function: isGridEnabled + * + * Returns . + */ + isGridEnabled = () => { + return this.gridEnabled; + }; + + /** + * Function: setGridEnabled + * + * Sets . + */ + setGridEnabled = (value) => { + this.gridEnabled = value; + }; + + /** + * Function: getGraphForEvent + * + * Returns the graph for the given mouse event. This implementation returns + * null. + */ + getGraphForEvent = (evt) => { + return null; + }; + + /** + * Function: getDropTarget + * + * Returns the drop target for the given graph and coordinates. This + * implementation uses . + */ + getDropTarget = (graph, x, y, evt) => { + return graph.getCellAt(x, y); + }; + + /** + * Function: createDragElement + * + * Creates and returns a clone of the or the + * if the former is not defined. + */ + createDragElement = (evt) => { + return this.element.cloneNode(true); + }; + + /** + * Function: createPreviewElement + * + * Creates and returns an element which can be used as a preview in the given + * graph. + */ + createPreviewElement = (graph) => { + return null; + }; + + /** + * Function: isActive + * + * Returns true if this drag source is active. + */ + isActive = () => { + return this.mouseMoveHandler != null; + }; + + /** + * Function: reset + * + * Stops and removes everything and restores the state of the object. + */ + reset = () => { + if (this.currentGraph != null) { + this.dragExit(this.currentGraph); + this.currentGraph = null; + } + + this.removeDragElement(); + this.removeListeners(); + this.stopDrag(); + }; + + /** + * Function: mouseDown + * + * Returns the drop target for the given graph and coordinates. This + * implementation uses . + * + * To ignore popup menu events for a drag source, this function can be + * overridden as follows. + * + * (code) + * var mouseDown = dragSource.mouseDown; + * + * dragSource.mouseDown = (evt)=> + * { + * if (!mxEvent.isPopupTrigger(evt)) + * { + * mouseDown.apply(this, arguments); + * } + * }; + * (end) + */ + mouseDown = (evt) => { + if (this.enabled && !mxEvent.isConsumed(evt) && this.mouseMoveHandler == null) { + this.startDrag(evt); + this.mouseMoveHandler = mxUtils.bind(this, this.mouseMove); + this.mouseUpHandler = mxUtils.bind(this, this.mouseUp); + mxEvent.addGestureListeners(document, null, this.mouseMoveHandler, this.mouseUpHandler); + + if (mxClient.IS_TOUCH && !mxEvent.isMouseEvent(evt)) { + this.eventSource = mxEvent.getSource(evt); + mxEvent.addGestureListeners(this.eventSource, null, this.mouseMoveHandler, this.mouseUpHandler); + } } }; -}; -/** - * Variable: element - * - * Reference to the DOM node which was made draggable. - */ -element = null; + /** + * Function: startDrag + * + * Creates the using . + */ + startDrag = (evt) => { + this.dragElement = this.createDragElement(evt); + this.dragElement.style.position = 'absolute'; + this.dragElement.style.zIndex = this.dragElementZIndex; + mxUtils.setOpacity(this.dragElement, this.dragElementOpacity); -/** - * Variable: dropHandler - * - * Holds the DOM node that is used to represent the drag preview. If this is - * null then the source element will be cloned and used for the drag preview. - */ -dropHandler = null; - -/** - * Variable: dragOffset - * - * that specifies the offset of the . Default is null. - */ -dragOffset = null; - -/** - * Variable: dragElement - * - * Holds the DOM node that is used to represent the drag preview. If this is - * null then the source element will be cloned and used for the drag preview. - */ -dragElement = null; - -/** - * Variable: previewElement - * - * Optional that specifies the unscaled size of the preview. - */ -previewElement = null; - -/** - * Variable: previewOffset - * - * Optional that specifies the offset of the preview in pixels. - */ -previewOffset = null; - -/** - * Variable: enabled - * - * Specifies if this drag source is enabled. Default is true. - */ -enabled = true; - -/** - * Variable: currentGraph - * - * Reference to the that is the current drop target. - */ -currentGraph = null; - -/** - * Variable: currentDropTarget - * - * Holds the current drop target under the mouse. - */ -currentDropTarget = null; - -/** - * Variable: currentPoint - * - * Holds the current drop location. - */ -currentPoint = null; - -/** - * Variable: currentGuide - * - * Holds an for the if is not null. - */ -currentGuide = null; - -/** - * Variable: currentGuide - * - * Holds an for the if is not null. - */ -currentHighlight = null; - -/** - * Variable: autoscroll - * - * Specifies if the graph should scroll automatically. Default is true. - */ -autoscroll = true; - -/** - * Variable: guidesEnabled - * - * Specifies if should be enabled. Default is true. - */ -guidesEnabled = true; - -/** - * Variable: gridEnabled - * - * Specifies if the grid should be allowed. Default is true. - */ -gridEnabled = true; - -/** - * Variable: highlightDropTargets - * - * Specifies if drop targets should be highlighted. Default is true. - */ -highlightDropTargets = true; - -/** - * Variable: dragElementZIndex - * - * ZIndex for the drag element. Default is 100. - */ -dragElementZIndex = 100; - -/** - * Variable: dragElementOpacity - * - * Opacity of the drag element in %. Default is 70. - */ -dragElementOpacity = 70; - -/** - * Variable: checkEventSource - * - * Whether the event source should be checked in . Default - * is true. - */ -checkEventSource = true; - -/** - * Function: isEnabled - * - * Returns . - */ -isEnabled = ()=> -{ - return this.enabled; -}; - -/** - * Function: setEnabled - * - * Sets . - */ -setEnabled = (value)=> -{ - this.enabled = value; -}; - -/** - * Function: isGuidesEnabled - * - * Returns . - */ -isGuidesEnabled = ()=> -{ - return this.guidesEnabled; -}; - -/** - * Function: setGuidesEnabled - * - * Sets . - */ -setGuidesEnabled = (value)=> -{ - this.guidesEnabled = value; -}; - -/** - * Function: isGridEnabled - * - * Returns . - */ -isGridEnabled = ()=> -{ - return this.gridEnabled; -}; - -/** - * Function: setGridEnabled - * - * Sets . - */ -setGridEnabled = (value)=> -{ - this.gridEnabled = value; -}; - -/** - * Function: getGraphForEvent - * - * Returns the graph for the given mouse event. This implementation returns - * null. - */ -getGraphForEvent = (evt)=> -{ - return null; -}; - -/** - * Function: getDropTarget - * - * Returns the drop target for the given graph and coordinates. This - * implementation uses . - */ -getDropTarget = (graph, x, y, evt)=> -{ - return graph.getCellAt(x, y); -}; - -/** - * Function: createDragElement - * - * Creates and returns a clone of the or the - * if the former is not defined. - */ -createDragElement = (evt)=> -{ - return this.element.cloneNode(true); -}; - -/** - * Function: createPreviewElement - * - * Creates and returns an element which can be used as a preview in the given - * graph. - */ -createPreviewElement = (graph)=> -{ - return null; -}; - -/** - * Function: isActive - * - * Returns true if this drag source is active. - */ -isActive = ()=> -{ - return this.mouseMoveHandler != null; -}; - -/** - * Function: reset - * - * Stops and removes everything and restores the state of the object. - */ -reset = ()=> -{ - if (this.currentGraph != null) - { - this.dragExit(this.currentGraph); - this.currentGraph = null; - } - - this.removeDragElement(); - this.removeListeners(); - this.stopDrag(); -}; - -/** - * Function: mouseDown - * - * Returns the drop target for the given graph and coordinates. This - * implementation uses . - * - * To ignore popup menu events for a drag source, this function can be - * overridden as follows. - * - * (code) - * var mouseDown = dragSource.mouseDown; - * - * dragSource.mouseDown = (evt)=> - * { - * if (!mxEvent.isPopupTrigger(evt)) - * { - * mouseDown.apply(this, arguments); - * } - * }; - * (end) - */ -mouseDown = (evt)=> -{ - if (this.enabled && !mxEvent.isConsumed(evt) && this.mouseMoveHandler == null) - { - this.startDrag(evt); - this.mouseMoveHandler = mxUtils.bind(this, this.mouseMove); - this.mouseUpHandler = mxUtils.bind(this, this.mouseUp); - mxEvent.addGestureListeners(document, null, this.mouseMoveHandler, this.mouseUpHandler); - - if (mxClient.IS_TOUCH && !mxEvent.isMouseEvent(evt)) - { - this.eventSource = mxEvent.getSource(evt); - mxEvent.addGestureListeners(this.eventSource, null, this.mouseMoveHandler, this.mouseUpHandler); + if (this.checkEventSource && mxClient.IS_SVG) { + this.dragElement.style.pointerEvents = 'none'; } - } -}; + }; -/** - * Function: startDrag - * - * Creates the using . - */ -startDrag = (evt)=> -{ - this.dragElement = this.createDragElement(evt); - this.dragElement.style.position = 'absolute'; - this.dragElement.style.zIndex = this.dragElementZIndex; - mxUtils.setOpacity(this.dragElement, this.dragElementOpacity); + /** + * Function: stopDrag + * + * Invokes . + */ + stopDrag = () => { + // LATER: This used to have a mouse event. If that is still needed we need to add another + // final call to the DnD protocol to add a cleanup step in the case of escape press, which + // is not associated with a mouse event and which currently calles this method. + this.removeDragElement(); + }; - if (this.checkEventSource && mxClient.IS_SVG) - { - this.dragElement.style.pointerEvents = 'none'; - } -}; + /** + * Function: removeDragElement + * + * Removes and destroys the . + */ + removeDragElement = () => { + if (this.dragElement != null) { + if (this.dragElement.parentNode != null) { + this.dragElement.parentNode.removeChild(this.dragElement); + } -/** - * Function: stopDrag - * - * Invokes . - */ -stopDrag = ()=> -{ - // LATER: This used to have a mouse event. If that is still needed we need to add another - // final call to the DnD protocol to add a cleanup step in the case of escape press, which - // is not associated with a mouse event and which currently calles this method. - this.removeDragElement(); -}; - -/** - * Function: removeDragElement - * - * Removes and destroys the . - */ -removeDragElement = ()=> -{ - if (this.dragElement != null) - { - if (this.dragElement.parentNode != null) - { - this.dragElement.parentNode.removeChild(this.dragElement); + this.dragElement = null; } - - this.dragElement = null; - } -}; + }; -/** - * Function: getElementForEvent - * - * Returns the topmost element under the given event. - */ -getElementForEvent = (evt)=> -{ - return ((mxEvent.isTouchEvent(evt) || mxEvent.isPenEvent(evt)) ? - document.elementFromPoint(mxEvent.getClientX(evt), mxEvent.getClientY(evt)) : + /** + * Function: getElementForEvent + * + * Returns the topmost element under the given event. + */ + getElementForEvent = (evt) => { + return ((mxEvent.isTouchEvent(evt) || mxEvent.isPenEvent(evt)) ? + document.elementFromPoint(mxEvent.getClientX(evt), mxEvent.getClientY(evt)) : mxEvent.getSource(evt)); -}; + }; -/** - * Function: graphContainsEvent - * - * Returns true if the given graph contains the given event. - */ -graphContainsEvent = (graph, evt)=> -{ - var x = mxEvent.getClientX(evt); - var y = mxEvent.getClientY(evt); - var offset = mxUtils.getOffset(graph.container); - var origin = mxUtils.getScrollOrigin(); - var elt = this.getElementForEvent(evt); - - if (this.checkEventSource) - { - while (elt != null && elt != graph.container) - { - elt = elt.parentNode; - } - } - - // Checks if event is inside the bounds of the graph container - return elt != null && x >= offset.x - origin.x && y >= offset.y - origin.y && - x <= offset.x - origin.x + graph.container.offsetWidth && - y <= offset.y - origin.y + graph.container.offsetHeight; -}; - -/** - * Function: mouseMove - * - * Gets the graph for the given event using , updates the - * , calling and on the new and old graph, - * respectively, and invokes if is not null. - */ -mouseMove = (evt)=> -{ - var graph = this.getGraphForEvent(evt); - - // Checks if event is inside the bounds of the graph container - if (graph != null && !this.graphContainsEvent(graph, evt)) - { - graph = null; - } - - if (graph != this.currentGraph) - { - if (this.currentGraph != null) - { - this.dragExit(this.currentGraph, evt); - } - - this.currentGraph = graph; - - if (this.currentGraph != null) - { - this.dragEnter(this.currentGraph, evt); - } - } - - if (this.currentGraph != null) - { - this.dragOver(this.currentGraph, evt); - } - - if (this.dragElement != null && (this.previewElement == null || this.previewElement.style.visibility != 'visible')) - { + /** + * Function: graphContainsEvent + * + * Returns true if the given graph contains the given event. + */ + graphContainsEvent = (graph, evt) => { var x = mxEvent.getClientX(evt); var y = mxEvent.getClientY(evt); - - if (this.dragElement.parentNode == null) - { - document.body.appendChild(this.dragElement); + var offset = mxUtils.getOffset(graph.container); + var origin = mxUtils.getScrollOrigin(); + var elt = this.getElementForEvent(evt); + + if (this.checkEventSource) { + while (elt != null && elt != graph.container) { + elt = elt.parentNode; + } } - this.dragElement.style.visibility = 'visible'; - - if (this.dragOffset != null) - { - x += this.dragOffset.x; - y += this.dragOffset.y; - } - - var offset = mxUtils.getDocumentScrollOrigin(document); - - this.dragElement.style.left = (x + offset.x) + 'px'; - this.dragElement.style.top = (y + offset.y) + 'px'; - } - else if (this.dragElement != null) - { - this.dragElement.style.visibility = 'hidden'; - } - - mxEvent.consume(evt); -}; + // Checks if event is inside the bounds of the graph container + return elt != null && x >= offset.x - origin.x && y >= offset.y - origin.y && + x <= offset.x - origin.x + graph.container.offsetWidth && + y <= offset.y - origin.y + graph.container.offsetHeight; + }; -/** - * Function: mouseUp - * - * Processes the mouse up event and invokes , and - * as required. - */ -mouseUp = (evt)=> -{ - if (this.currentGraph != null) - { - if (this.currentPoint != null && (this.previewElement == null || - this.previewElement.style.visibility != 'hidden')) - { - var scale = this.currentGraph.view.scale; - var tr = this.currentGraph.view.translate; - var x = this.currentPoint.x / scale - tr.x; - var y = this.currentPoint.y / scale - tr.y; - - this.drop(this.currentGraph, evt, this.currentDropTarget, x, y); - } - - this.dragExit(this.currentGraph); - this.currentGraph = null; - } + /** + * Function: mouseMove + * + * Gets the graph for the given event using , updates the + * , calling and on the new and old graph, + * respectively, and invokes if is not null. + */ + mouseMove = (evt) => { + var graph = this.getGraphForEvent(evt); - this.stopDrag(); - this.removeListeners(); - - mxEvent.consume(evt); -}; - -/** - * Function: removeListeners - * - * Actives the given graph as a drop target. - */ -removeListeners = ()=> -{ - if (this.eventSource != null) - { - mxEvent.removeGestureListeners(this.eventSource, null, this.mouseMoveHandler, this.mouseUpHandler); - this.eventSource = null; - } - - mxEvent.removeGestureListeners(document, null, this.mouseMoveHandler, this.mouseUpHandler); - this.mouseMoveHandler = null; - this.mouseUpHandler = null; -}; - -/** - * Function: dragEnter - * - * Actives the given graph as a drop target. - */ -dragEnter = (graph, evt)=> -{ - graph.isMouseDown = true; - graph.isMouseTrigger = mxEvent.isMouseEvent(evt); - this.previewElement = this.createPreviewElement(graph); - - if (this.previewElement != null && this.checkEventSource && mxClient.IS_SVG) - { - this.previewElement.style.pointerEvents = 'none'; - } - - // Guide is only needed if preview element is used - if (this.isGuidesEnabled() && this.previewElement != null) - { - this.currentGuide = new mxGuide(graph, graph.graphHandler.getGuideStates()); - } - - if (this.highlightDropTargets) - { - this.currentHighlight = new mxCellHighlight(graph, mxConstants.DROP_TARGET_COLOR); - } - - // Consumes all events in the current graph before they are fired - graph.addListener(mxEvent.FIRE_MOUSE_EVENT, this.eventConsumer); -}; - -/** - * Function: dragExit - * - * Deactivates the given graph as a drop target. - */ -dragExit = (graph, evt)=> -{ - this.currentDropTarget = null; - this.currentPoint = null; - graph.isMouseDown = false; - - // Consumes all events in the current graph before they are fired - graph.removeListener(this.eventConsumer); - - if (this.previewElement != null) - { - if (this.previewElement.parentNode != null) - { - this.previewElement.parentNode.removeChild(this.previewElement); - } - - this.previewElement = null; - } - - if (this.currentGuide != null) - { - this.currentGuide.destroy(); - this.currentGuide = null; - } - - if (this.currentHighlight != null) - { - this.currentHighlight.destroy(); - this.currentHighlight = null; - } -}; - -/** - * Function: dragOver - * - * Implements autoscroll, updates the , highlights any drop - * targets and updates the preview. - */ -dragOver = (graph, evt)=> -{ - var offset = mxUtils.getOffset(graph.container); - var origin = mxUtils.getScrollOrigin(graph.container); - var x = mxEvent.getClientX(evt) - offset.x + origin.x - graph.panDx; - var y = mxEvent.getClientY(evt) - offset.y + origin.y - graph.panDy; - - if (graph.autoScroll && (this.autoscroll == null || this.autoscroll)) - { - graph.scrollPointToVisible(x, y, graph.autoExtend); - } - - // Highlights the drop target under the mouse - if (this.currentHighlight != null && graph.isDropEnabled()) - { - this.currentDropTarget = this.getDropTarget(graph, x, y, evt); - var state = graph.getView().getState(this.currentDropTarget); - this.currentHighlight.highlight(state); - } - - // Updates the location of the preview - if (this.previewElement != null) - { - if (this.previewElement.parentNode == null) - { - graph.container.appendChild(this.previewElement); - - this.previewElement.style.zIndex = '3'; - this.previewElement.style.position = 'absolute'; - } - - var gridEnabled = this.isGridEnabled() && graph.isGridEnabledEvent(evt); - var hideGuide = true; - - // Grid and guides - if (this.currentGuide != null && this.currentGuide.isEnabledForEvent(evt)) - { - // LATER: HTML preview appears smaller than SVG preview - var w = parseInt(this.previewElement.style.width); - var h = parseInt(this.previewElement.style.height); - var bounds = new mxRectangle(0, 0, w, h); - var delta = new mxPoint(x, y); - delta = this.currentGuide.move(bounds, delta, gridEnabled, true); - hideGuide = false; - x = delta.x; - y = delta.y; - } - else if (gridEnabled) - { - var scale = graph.view.scale; - var tr = graph.view.translate; - var off = graph.gridSize / 2; - x = (graph.snap(x / scale - tr.x - off) + tr.x) * scale; - y = (graph.snap(y / scale - tr.y - off) + tr.y) * scale; - } - - if (this.currentGuide != null && hideGuide) - { - this.currentGuide.hide(); - } - - if (this.previewOffset != null) - { - x += this.previewOffset.x; - y += this.previewOffset.y; + // Checks if event is inside the bounds of the graph container + if (graph != null && !this.graphContainsEvent(graph, evt)) { + graph = null; } - this.previewElement.style.left = Math.round(x) + 'px'; - this.previewElement.style.top = Math.round(y) + 'px'; - this.previewElement.style.visibility = 'visible'; - } - - this.currentPoint = new mxPoint(x, y); -}; + if (graph != this.currentGraph) { + if (this.currentGraph != null) { + this.dragExit(this.currentGraph, evt); + } -/** - * Function: drop - * - * Returns the drop target for the given graph and coordinates. This - * implementation uses . - */ -drop = (graph, evt, dropTarget, x, y)=> -{ - this.dropHandler.apply(this, arguments); - - // Had to move this to after the insert because it will - // affect the scrollbars of the window in IE to try and - // make the complete container visible. - // LATER: Should be made optional. - if (graph.container.style.visibility != 'hidden') - { - graph.container.focus(); - } -}; + this.currentGraph = graph; + + if (this.currentGraph != null) { + this.dragEnter(this.currentGraph, evt); + } + } + + if (this.currentGraph != null) { + this.dragOver(this.currentGraph, evt); + } + + if (this.dragElement != null && (this.previewElement == null || this.previewElement.style.visibility != 'visible')) { + var x = mxEvent.getClientX(evt); + var y = mxEvent.getClientY(evt); + + if (this.dragElement.parentNode == null) { + document.body.appendChild(this.dragElement); + } + + this.dragElement.style.visibility = 'visible'; + + if (this.dragOffset != null) { + x += this.dragOffset.x; + y += this.dragOffset.y; + } + + var offset = mxUtils.getDocumentScrollOrigin(document); + + this.dragElement.style.left = (x + offset.x) + 'px'; + this.dragElement.style.top = (y + offset.y) + 'px'; + } else if (this.dragElement != null) { + this.dragElement.style.visibility = 'hidden'; + } + + mxEvent.consume(evt); + }; + + /** + * Function: mouseUp + * + * Processes the mouse up event and invokes , and + * as required. + */ + mouseUp = (evt) => { + if (this.currentGraph != null) { + if (this.currentPoint != null && (this.previewElement == null || + this.previewElement.style.visibility != 'hidden')) { + var scale = this.currentGraph.view.scale; + var tr = this.currentGraph.view.translate; + var x = this.currentPoint.x / scale - tr.x; + var y = this.currentPoint.y / scale - tr.y; + + this.drop(this.currentGraph, evt, this.currentDropTarget, x, y); + } + + this.dragExit(this.currentGraph); + this.currentGraph = null; + } + + this.stopDrag(); + this.removeListeners(); + + mxEvent.consume(evt); + }; + + /** + * Function: removeListeners + * + * Actives the given graph as a drop target. + */ + removeListeners = () => { + if (this.eventSource != null) { + mxEvent.removeGestureListeners(this.eventSource, null, this.mouseMoveHandler, this.mouseUpHandler); + this.eventSource = null; + } + + mxEvent.removeGestureListeners(document, null, this.mouseMoveHandler, this.mouseUpHandler); + this.mouseMoveHandler = null; + this.mouseUpHandler = null; + }; + + /** + * Function: dragEnter + * + * Actives the given graph as a drop target. + */ + dragEnter = (graph, evt) => { + graph.isMouseDown = true; + graph.isMouseTrigger = mxEvent.isMouseEvent(evt); + this.previewElement = this.createPreviewElement(graph); + + if (this.previewElement != null && this.checkEventSource && mxClient.IS_SVG) { + this.previewElement.style.pointerEvents = 'none'; + } + + // Guide is only needed if preview element is used + if (this.isGuidesEnabled() && this.previewElement != null) { + this.currentGuide = new mxGuide(graph, graph.graphHandler.getGuideStates()); + } + + if (this.highlightDropTargets) { + this.currentHighlight = new mxCellHighlight(graph, mxConstants.DROP_TARGET_COLOR); + } + + // Consumes all events in the current graph before they are fired + graph.addListener(mxEvent.FIRE_MOUSE_EVENT, this.eventConsumer); + }; + + /** + * Function: dragExit + * + * Deactivates the given graph as a drop target. + */ + dragExit = (graph, evt) => { + this.currentDropTarget = null; + this.currentPoint = null; + graph.isMouseDown = false; + + // Consumes all events in the current graph before they are fired + graph.removeListener(this.eventConsumer); + + if (this.previewElement != null) { + if (this.previewElement.parentNode != null) { + this.previewElement.parentNode.removeChild(this.previewElement); + } + + this.previewElement = null; + } + + if (this.currentGuide != null) { + this.currentGuide.destroy(); + this.currentGuide = null; + } + + if (this.currentHighlight != null) { + this.currentHighlight.destroy(); + this.currentHighlight = null; + } + }; + + /** + * Function: dragOver + * + * Implements autoscroll, updates the , highlights any drop + * targets and updates the preview. + */ + dragOver = (graph, evt) => { + var offset = mxUtils.getOffset(graph.container); + var origin = mxUtils.getScrollOrigin(graph.container); + var x = mxEvent.getClientX(evt) - offset.x + origin.x - graph.panDx; + var y = mxEvent.getClientY(evt) - offset.y + origin.y - graph.panDy; + + if (graph.autoScroll && (this.autoscroll == null || this.autoscroll)) { + graph.scrollPointToVisible(x, y, graph.autoExtend); + } + + // Highlights the drop target under the mouse + if (this.currentHighlight != null && graph.isDropEnabled()) { + this.currentDropTarget = this.getDropTarget(graph, x, y, evt); + var state = graph.getView().getState(this.currentDropTarget); + this.currentHighlight.highlight(state); + } + + // Updates the location of the preview + if (this.previewElement != null) { + if (this.previewElement.parentNode == null) { + graph.container.appendChild(this.previewElement); + + this.previewElement.style.zIndex = '3'; + this.previewElement.style.position = 'absolute'; + } + + var gridEnabled = this.isGridEnabled() && graph.isGridEnabledEvent(evt); + var hideGuide = true; + + // Grid and guides + if (this.currentGuide != null && this.currentGuide.isEnabledForEvent(evt)) { + // LATER: HTML preview appears smaller than SVG preview + var w = parseInt(this.previewElement.style.width); + var h = parseInt(this.previewElement.style.height); + var bounds = new mxRectangle(0, 0, w, h); + var delta = new mxPoint(x, y); + delta = this.currentGuide.move(bounds, delta, gridEnabled, true); + hideGuide = false; + x = delta.x; + y = delta.y; + } else if (gridEnabled) { + var scale = graph.view.scale; + var tr = graph.view.translate; + var off = graph.gridSize / 2; + x = (graph.snap(x / scale - tr.x - off) + tr.x) * scale; + y = (graph.snap(y / scale - tr.y - off) + tr.y) * scale; + } + + if (this.currentGuide != null && hideGuide) { + this.currentGuide.hide(); + } + + if (this.previewOffset != null) { + x += this.previewOffset.x; + y += this.previewOffset.y; + } + + this.previewElement.style.left = Math.round(x) + 'px'; + this.previewElement.style.top = Math.round(y) + 'px'; + this.previewElement.style.visibility = 'visible'; + } + + this.currentPoint = new mxPoint(x, y); + }; + + /** + * Function: drop + * + * Returns the drop target for the given graph and coordinates. This + * implementation uses . + */ + drop = (graph, evt, dropTarget, x, y) => { + this.dropHandler.apply(this, arguments); + + // Had to move this to after the insert because it will + // affect the scrollbars of the window in IE to try and + // make the complete container visible. + // LATER: Should be made optional. + if (graph.container.style.visibility != 'hidden') { + graph.container.focus(); + } + }; +} + +export default mxDragSource; diff --git a/src/js/util/mxEffects.js b/src/js/util/mxEffects.js index 53955837d..6959d74ef 100644 --- a/src/js/util/mxEffects.js +++ b/src/js/util/mxEffects.js @@ -2,97 +2,84 @@ * Copyright (c) 2006-2015, JGraph Ltd * Copyright (c) 2006-2015, Gaudenz Alder */ -var mxEffects = -{ - +var mxEffects = { /** * Class: mxEffects - * + * * Provides animation effects. */ /** * Function: animateChanges - * + * * Asynchronous animated move operation. See also: . - * + * * Example: - * + * * (code) * graph.model.addListener(mxEvent.CHANGE, (sender, evt)=> * { * var changes = evt.getProperty('edit').changes; - * + * * if (changes.length < 10) * { * mxEffects.animateChanges(graph, changes); * } * }); * (end) - * + * * Parameters: - * + * * graph - that received the changes. * changes - Array of changes to be animated. * done - Optional function argument that is invoked after the * last step of the animation. */ - animateChanges: (graph, changes, done)=> - { + animateChanges: (graph, changes, done) => { var maxStep = 10; var step = 0; - var animate = ()=> - { + var animate = () => { var isRequired = false; - - for (var i = 0; i < changes.length; i++) - { + + for (var i = 0; i < changes.length; i++) { var change = changes[i]; - + if (change instanceof mxGeometryChange || - change instanceof mxTerminalChange || - change instanceof mxValueChange || - change instanceof mxChildChange || - change instanceof mxStyleChange) - { + change instanceof mxTerminalChange || + change instanceof mxValueChange || + change instanceof mxChildChange || + change instanceof mxStyleChange) { var state = graph.getView().getState(change.cell || change.child, false); - - if (state != null) - { + + if (state != null) { isRequired = true; - - if (change.constructor != mxGeometryChange || graph.model.isEdge(change.cell)) - { + + if (change.constructor != mxGeometryChange || graph.model.isEdge(change.cell)) { mxUtils.setOpacity(state.shape.node, 100 * step / maxStep); - } - else - { - var scale = graph.getView().scale; + } else { + var scale = graph.getView().scale; var dx = (change.geometry.x - change.previous.x) * scale; var dy = (change.geometry.y - change.previous.y) * scale; - + var sx = (change.geometry.width - change.previous.width) * scale; var sy = (change.geometry.height - change.previous.height) * scale; - - if (step == 0) - { + + if (step == 0) { state.x -= dx; state.y -= dy; state.width -= sx; state.height -= sy; - } - else - { + } else { state.x += dx / maxStep; state.y += dy / maxStep; state.width += sx / maxStep; state.height += sy / maxStep; } - + graph.cellRenderer.redraw(state); - + // Fades all connected edges and children mxEffects.cascadeOpacity(graph, change.cell, 100 * step / maxStep); } @@ -100,60 +87,51 @@ var mxEffects = } } - if (step < maxStep && isRequired) - { + if (step < maxStep && isRequired) { step++; window.setTimeout(animate, delay); - } - else if (done != null) - { + } else if (done != null) { done(); } }; - + var delay = 30; animate(); }, - + /** * Function: cascadeOpacity - * + * * Sets the opacity on the given cell and its descendants. - * + * * Parameters: - * + * * graph - that contains the cells. * cell - to set the opacity for. * opacity - New value for the opacity in %. */ - cascadeOpacity: (graph, cell, opacity)=> - { + cascadeOpacity: (graph, cell, opacity) => { // Fades all children var childCount = graph.model.getChildCount(cell); - - for (var i=0; i - { + fadeOut: (node, from, remove, step, delay, isEnabled) => { step = step || 40; delay = delay || 30; - + var opacity = from || 100; - + mxUtils.setOpacity(node, opacity); - - if (isEnabled || isEnabled == null) - { - var f = ()=> - { - opacity = Math.max(opacity-step, 0); + + if (isEnabled || isEnabled == null) { + var f = () => { + opacity = Math.max(opacity - step, 0); mxUtils.setOpacity(node, opacity); - - if (opacity > 0) - { + + if (opacity > 0) { window.setTimeout(f, delay); - } - else - { + } else { node.style.visibility = 'hidden'; - - if (remove && node.parentNode) - { + + if (remove && node.parentNode) { node.parentNode.removeChild(node); } } }; window.setTimeout(f, delay); - } - else - { + } else { node.style.visibility = 'hidden'; - - if (remove && node.parentNode) - { + + if (remove && node.parentNode) { node.parentNode.removeChild(node); } } } - }; + +export default mxEffects; diff --git a/src/js/util/mxEventObject.js b/src/js/util/mxEventObject.js index af281767f..4fca48986 100644 --- a/src/js/util/mxEventObject.js +++ b/src/js/util/mxEventObject.js @@ -2,110 +2,107 @@ * Copyright (c) 2006-2015, JGraph Ltd * Copyright (c) 2006-2015, Gaudenz Alder */ -/** - * Class: mxEventObject - * - * The mxEventObject is a wrapper for all properties of a single event. - * Additionally, it also offers functions to consume the event and check if it - * was consumed as follows: - * - * (code) - * evt.consume(); - * INV: evt.isConsumed() == true - * (end) - * - * Constructor: mxEventObject - * - * Constructs a new event object with the specified name. An optional - * sequence of key, value pairs can be appended to define properties. - * - * Example: - * - * (code) - * new mxEventObject("eventName", key1, val1, .., keyN, valN) - * (end) - */ -function mxEventObject(name) -{ - this.name = name; - this.properties = []; - for (var i = 1; i < arguments.length; i += 2) - { - if (arguments[i + 1] != null) - { - this.properties[arguments[i]] = arguments[i + 1]; +class mxEventObject { + /** + * Class: mxEventObject + * + * The mxEventObject is a wrapper for all properties of a single event. + * Additionally, it also offers functions to consume the event and check if it + * was consumed as follows: + * + * (code) + * evt.consume(); + * INV: evt.isConsumed() == true + * (end) + * + * Constructor: mxEventObject + * + * Constructs a new event object with the specified name. An optional + * sequence of key, value pairs can be appended to define properties. + * + * Example: + * + * (code) + * new mxEventObject("eventName", key1, val1, .., keyN, valN) + * (end) + */ + constructor(name) { + this.name = name; + this.properties = []; + + for (var i = 1; i < arguments.length; i += 2) { + if (arguments[i + 1] != null) { + this.properties[arguments[i]] = arguments[i + 1]; + } } - } -}; + }; -/** - * Variable: name - * - * Holds the name. - */ -name = null; + /** + * Variable: name + * + * Holds the name. + */ + name = null; -/** - * Variable: properties - * - * Holds the properties as an associative array. - */ -properties = null; + /** + * Variable: properties + * + * Holds the properties as an associative array. + */ + properties = null; -/** - * Variable: consumed - * - * Holds the consumed state. Default is false. - */ -consumed = false; + /** + * Variable: consumed + * + * Holds the consumed state. Default is false. + */ + consumed = false; -/** - * Function: getName - * - * Returns . - */ -getName = ()=> -{ - return this.name; -}; + /** + * Function: getName + * + * Returns . + */ + getName = () => { + return this.name; + }; -/** - * Function: getProperties - * - * Returns . - */ -getProperties = ()=> -{ - return this.properties; -}; + /** + * Function: getProperties + * + * Returns . + */ + getProperties = () => { + return this.properties; + }; -/** - * Function: getProperty - * - * Returns the property for the given key. - */ -getProperty = (key)=> -{ - return this.properties[key]; -}; + /** + * Function: getProperty + * + * Returns the property for the given key. + */ + getProperty = (key) => { + return this.properties[key]; + }; -/** - * Function: isConsumed - * - * Returns true if the event has been consumed. - */ -isConsumed = ()=> -{ - return this.consumed; -}; + /** + * Function: isConsumed + * + * Returns true if the event has been consumed. + */ + isConsumed = () => { + return this.consumed; + }; -/** - * Function: consume - * - * Consumes the event. - */ -consume = ()=> -{ - this.consumed = true; -}; + /** + * Function: consume + * + * Consumes the event. + */ + consume = () => { + this.consumed = true; + }; +} + +export default mxEventObject; diff --git a/src/js/util/mxEventSource.js b/src/js/util/mxEventSource.js index fdd0644c0..b018523a3 100644 --- a/src/js/util/mxEventSource.js +++ b/src/js/util/mxEventSource.js @@ -2,188 +2,175 @@ * Copyright (c) 2006-2015, JGraph Ltd * Copyright (c) 2006-2015, Gaudenz Alder */ -/** - * Class: mxEventSource - * - * Base class for objects that dispatch named events. To create a subclass that - * inherits from mxEventSource, the following code is used. - * - * (code) - * function MyClass() { }; - * - * MyClass.prototype = new mxEventSource(); - * constructor = MyClass; - * (end) - * - * Known Subclasses: - * - * , , , , , - * , - * - * Constructor: mxEventSource - * - * Constructs a new event source. - */ -function mxEventSource(eventSource) -{ - this.setEventSource(eventSource); -}; -/** - * Variable: eventListeners - * - * Holds the event names and associated listeners in an array. The array - * contains the event name followed by the respective listener for each - * registered listener. - */ -eventListeners = null; +import mxEventObject from "./mxEventObject"; -/** - * Variable: eventsEnabled - * - * Specifies if events can be fired. Default is true. - */ -eventsEnabled = true; +class mxEventSource { + /** + * Variable: eventListeners + * + * Holds the event names and associated listeners in an array. The array + * contains the event name followed by the respective listener for each + * registered listener. + */ + eventListeners = null; -/** - * Variable: eventSource - * - * Optional source for events. Default is null. - */ -eventSource = null; + /** + * Variable: eventsEnabled + * + * Specifies if events can be fired. Default is true. + */ + eventsEnabled = true; -/** - * Function: isEventsEnabled - * - * Returns . - */ -isEventsEnabled = ()=> -{ - return this.eventsEnabled; -}; + /** + * Variable: eventSource + * + * Optional source for events. Default is null. + */ + eventSource = null; -/** - * Function: setEventsEnabled - * - * Sets . - */ -setEventsEnabled = (value)=> -{ - this.eventsEnabled = value; -}; + /** + * Class: mxEventSource + * + * Base class for objects that dispatch named events. To create a subclass that + * inherits from mxEventSource, the following code is used. + * + * (code) + * function MyClass() { }; + * + * MyClass.prototype = new mxEventSource(); + * constructor = MyClass; + * (end) + * + * Known Subclasses: + * + * , , , , , + * , + * + * Constructor: mxEventSource + * + * Constructs a new event source. + */ + constructor(eventSource) { + this.setEventSource(eventSource); + }; -/** - * Function: getEventSource - * - * Returns . - */ -getEventSource = ()=> -{ - return this.eventSource; -}; + /** + * Function: isEventsEnabled + * + * Returns . + */ + isEventsEnabled = () => { + return this.eventsEnabled; + }; -/** - * Function: setEventSource - * - * Sets . - */ -setEventSource = (value)=> -{ - this.eventSource = value; -}; + /** + * Function: setEventsEnabled + * + * Sets . + */ + setEventsEnabled = (value) => { + this.eventsEnabled = value; + }; -/** - * Function: addListener - * - * Binds the specified function to the given event name. If no event name - * is given, then the listener is registered for all events. - * - * The parameters of the listener are the sender and an . - */ -addListener = (name, funct)=> -{ - if (this.eventListeners == null) - { - this.eventListeners = []; - } - - this.eventListeners.push(name); - this.eventListeners.push(funct); -}; + /** + * Function: getEventSource + * + * Returns . + */ + getEventSource = () => { + return this.eventSource; + }; -/** - * Function: removeListener - * - * Removes all occurrences of the given listener from . - */ -removeListener = (funct)=> -{ - if (this.eventListeners != null) - { - var i = 0; - - while (i < this.eventListeners.length) - { - if (this.eventListeners[i+1] == funct) - { - this.eventListeners.splice(i, 2); - } - else - { - i += 2; + /** + * Function: setEventSource + * + * Sets . + */ + setEventSource = (value) => { + this.eventSource = value; + }; + + /** + * Function: addListener + * + * Binds the specified function to the given event name. If no event name + * is given, then the listener is registered for all events. + * + * The parameters of the listener are the sender and an . + */ + addListener = (name, funct) => { + if (this.eventListeners == null) { + this.eventListeners = []; + } + + this.eventListeners.push(name); + this.eventListeners.push(funct); + }; + + /** + * Function: removeListener + * + * Removes all occurrences of the given listener from . + */ + removeListener = (funct) => { + if (this.eventListeners != null) { + var i = 0; + + while (i < this.eventListeners.length) { + if (this.eventListeners[i + 1] == funct) { + this.eventListeners.splice(i, 2); + } else { + i += 2; + } } } - } -}; + }; -/** - * Function: fireEvent - * - * Dispatches the given event to the listeners which are registered for - * the event. The sender argument is optional. The current execution scope - * ("this") is used for the listener invocation (see ). - * - * Example: - * - * (code) - * fireEvent(new mxEventObject("eventName", key1, val1, .., keyN, valN)) - * (end) - * - * Parameters: - * - * evt - that represents the event. - * sender - Optional sender to be passed to the listener. Default value is - * the return value of . - */ -fireEvent = (evt, sender)=> -{ - if (this.eventListeners != null && this.isEventsEnabled()) - { - if (evt == null) - { - evt = new mxEventObject(); - } - - if (sender == null) - { - sender = this.getEventSource(); - } + /** + * Function: fireEvent + * + * Dispatches the given event to the listeners which are registered for + * the event. The sender argument is optional. The current execution scope + * ("this") is used for the listener invocation (see ). + * + * Example: + * + * (code) + * fireEvent(new mxEventObject("eventName", key1, val1, .., keyN, valN)) + * (end) + * + * Parameters: + * + * evt - that represents the event. + * sender - Optional sender to be passed to the listener. Default value is + * the return value of . + */ + fireEvent = (evt, sender) => { + if (this.eventListeners != null && this.isEventsEnabled()) { + if (evt == null) { + evt = new mxEventObject(); + } - if (sender == null) - { - sender = this; - } + if (sender == null) { + sender = this.getEventSource(); + } - var args = [sender, evt]; - - for (var i = 0; i < this.eventListeners.length; i += 2) - { - var listen = this.eventListeners[i]; - - if (listen == null || listen == evt.getName()) - { - this.eventListeners[i+1].apply(this, args); + if (sender == null) { + sender = this; + } + + var args = [sender, evt]; + + for (var i = 0; i < this.eventListeners.length; i += 2) { + var listen = this.eventListeners[i]; + + if (listen == null || listen == evt.getName()) { + this.eventListeners[i + 1].apply(this, args); + } } } - } -}; + }; +} + +export default mxEventSource; diff --git a/src/js/util/mxForm.js b/src/js/util/mxForm.js index f3fab46f7..835dda2fd 100644 --- a/src/js/util/mxForm.js +++ b/src/js/util/mxForm.js @@ -2,201 +2,190 @@ * Copyright (c) 2006-2015, JGraph Ltd * Copyright (c) 2006-2015, Gaudenz Alder */ -/** - * Class: mxForm - * - * A simple class for creating HTML forms. - * - * Constructor: mxForm - * - * Creates a HTML table using the specified classname. - */ -function mxForm(className) -{ - this.table = document.createElement('table'); - this.table.className = className; - this.body = document.createElement('tbody'); - - this.table.appendChild(this.body); -}; -/** - * Variable: table - * - * Holds the DOM node that represents the table. - */ -table = null; +class mxForm { + /** + * Variable: table + * + * Holds the DOM node that represents the table. + */ + table = null; -/** - * Variable: body - * - * Holds the DOM node that represents the tbody (table body). New rows - * can be added to this object using DOM API. - */ -body = false; + /** + * Variable: body + * + * Holds the DOM node that represents the tbody (table body). New rows + * can be added to this object using DOM API. + */ + body = false; -/** - * Function: getTable - * - * Returns the table that contains this form. - */ -getTable = ()=> -{ - return this.table; -}; + /** + * Class: mxForm + * + * A simple class for creating HTML forms. + * + * Constructor: mxForm + * + * Creates a HTML table using the specified classname. + */ + constructor(className) { + this.table = document.createElement('table'); + this.table.className = className; + this.body = document.createElement('tbody'); -/** - * Function: addButtons - * - * Helper method to add an OK and Cancel button using the respective - * functions. - */ -addButtons = (okFunct, cancelFunct)=> -{ - var tr = document.createElement('tr'); - var td = document.createElement('td'); - tr.appendChild(td); - td = document.createElement('td'); + this.table.appendChild(this.body); + }; - // Adds the ok button - var button = document.createElement('button'); - mxUtils.write(button, mxResources.get('ok') || 'OK'); - td.appendChild(button); + /** + * Function: getTable + * + * Returns the table that contains this form. + */ + getTable = () => { + return this.table; + }; - mxEvent.addListener(button, 'click', ()=> - { - okFunct(); - }); - - // Adds the cancel button - button = document.createElement('button'); - mxUtils.write(button, mxResources.get('cancel') || 'Cancel'); - td.appendChild(button); - - mxEvent.addListener(button, 'click', ()=> - { - cancelFunct(); - }); - - tr.appendChild(td); - this.body.appendChild(tr); -}; + /** + * Function: addButtons + * + * Helper method to add an OK and Cancel button using the respective + * functions. + */ + addButtons = (okFunct, cancelFunct) => { + var tr = document.createElement('tr'); + var td = document.createElement('td'); + tr.appendChild(td); + td = document.createElement('td'); -/** - * Function: addText - * - * Adds an input for the given name, type and value and returns it. - */ -addText = (name, value, type)=> -{ - var input = document.createElement('input'); - - input.setAttribute('type', type || 'text'); - input.value = value; - - return this.addField(name, input); -}; + // Adds the ok button + var button = document.createElement('button'); + mxUtils.write(button, mxResources.get('ok') || 'OK'); + td.appendChild(button); -/** - * Function: addCheckbox - * - * Adds a checkbox for the given name and value and returns the textfield. - */ -addCheckbox = (name, value)=> -{ - var input = document.createElement('input'); - - input.setAttribute('type', 'checkbox'); - this.addField(name, input); + mxEvent.addListener(button, 'click', () => { + okFunct(); + }); - // IE can only change the checked value if the input is inside the DOM - if (value) - { - input.checked = true; - } + // Adds the cancel button + button = document.createElement('button'); + mxUtils.write(button, mxResources.get('cancel') || 'Cancel'); + td.appendChild(button); - return input; -}; + mxEvent.addListener(button, 'click', () => { + cancelFunct(); + }); -/** - * Function: addTextarea - * - * Adds a textarea for the given name and value and returns the textarea. - */ -addTextarea = (name, value, rows)=> -{ - var input = document.createElement('textarea'); - - if (mxClient.IS_NS) - { - rows--; - } - - input.setAttribute('rows', rows || 2); - input.value = value; - - return this.addField(name, input); -}; + tr.appendChild(td); + this.body.appendChild(tr); + }; -/** - * Function: addCombo - * - * Adds a combo for the given name and returns the combo. - */ -addCombo = (name, isMultiSelect, size)=> -{ - var select = document.createElement('select'); - - if (size != null) - { - select.setAttribute('size', size); - } - - if (isMultiSelect) - { - select.setAttribute('multiple', 'true'); - } - - return this.addField(name, select); -}; + /** + * Function: addText + * + * Adds an input for the given name, type and value and returns it. + */ + addText = (name, value, type) => { + var input = document.createElement('input'); -/** - * Function: addOption - * - * Adds an option for the given label to the specified combo. - */ -addOption = (combo, label, value, isSelected)=> -{ - var option = document.createElement('option'); - - mxUtils.writeln(option, label); - option.setAttribute('value', value); - - if (isSelected) - { - option.setAttribute('selected', isSelected); - } - - combo.appendChild(option); -}; + input.setAttribute('type', type || 'text'); + input.value = value; -/** - * Function: addField - * - * Adds a new row with the name and the input field in two columns and - * returns the given input. - */ -addField = (name, input)=> -{ - var tr = document.createElement('tr'); - var td = document.createElement('td'); - mxUtils.write(td, name); - tr.appendChild(td); - - td = document.createElement('td'); - td.appendChild(input); - tr.appendChild(td); - this.body.appendChild(tr); - - return input; -}; + return this.addField(name, input); + }; + + /** + * Function: addCheckbox + * + * Adds a checkbox for the given name and value and returns the textfield. + */ + addCheckbox = (name, value) => { + var input = document.createElement('input'); + + input.setAttribute('type', 'checkbox'); + this.addField(name, input); + + // IE can only change the checked value if the input is inside the DOM + if (value) { + input.checked = true; + } + + return input; + }; + + /** + * Function: addTextarea + * + * Adds a textarea for the given name and value and returns the textarea. + */ + addTextarea = (name, value, rows) => { + var input = document.createElement('textarea'); + + if (mxClient.IS_NS) { + rows--; + } + + input.setAttribute('rows', rows || 2); + input.value = value; + + return this.addField(name, input); + }; + + /** + * Function: addCombo + * + * Adds a combo for the given name and returns the combo. + */ + addCombo = (name, isMultiSelect, size) => { + var select = document.createElement('select'); + + if (size != null) { + select.setAttribute('size', size); + } + + if (isMultiSelect) { + select.setAttribute('multiple', 'true'); + } + + return this.addField(name, select); + }; + + /** + * Function: addOption + * + * Adds an option for the given label to the specified combo. + */ + addOption = (combo, label, value, isSelected) => { + var option = document.createElement('option'); + + mxUtils.writeln(option, label); + option.setAttribute('value', value); + + if (isSelected) { + option.setAttribute('selected', isSelected); + } + + combo.appendChild(option); + }; + + /** + * Function: addField + * + * Adds a new row with the name and the input field in two columns and + * returns the given input. + */ + addField = (name, input) => { + var tr = document.createElement('tr'); + var td = document.createElement('td'); + mxUtils.write(td, name); + tr.appendChild(td); + + td = document.createElement('td'); + td.appendChild(input); + tr.appendChild(td); + this.body.appendChild(tr); + + return input; + }; +} + +export default mxForm; diff --git a/src/js/util/mxGuide.js b/src/js/util/mxGuide.js index 4e38d8eeb..3f5aa47c2 100644 --- a/src/js/util/mxGuide.js +++ b/src/js/util/mxGuide.js @@ -11,438 +11,385 @@ * * Constructs a new guide object. */ -function mxGuide(graph, states) -{ - this.graph = graph; - this.setStates(states); -}; +class mxGuide { + constructor(graph, states) { + this.graph = graph; + this.setStates(states); + }; -/** - * Variable: graph - * - * Reference to the enclosing instance. - */ -graph = null; + /** + * Variable: graph + * + * Reference to the enclosing instance. + */ + graph = null; -/** - * Variable: states - * - * Contains the that are used for alignment. - */ -states = null; + /** + * Variable: states + * + * Contains the that are used for alignment. + */ + states = null; -/** - * Variable: horizontal - * - * Specifies if horizontal guides are enabled. Default is true. - */ -horizontal = true; + /** + * Variable: horizontal + * + * Specifies if horizontal guides are enabled. Default is true. + */ + horizontal = true; -/** - * Variable: vertical - * - * Specifies if vertical guides are enabled. Default is true. - */ -vertical = true; + /** + * Variable: vertical + * + * Specifies if vertical guides are enabled. Default is true. + */ + vertical = true; -/** - * Variable: guideX - * - * Holds the for the horizontal guide. - */ -guideX = null; + /** + * Variable: guideX + * + * Holds the for the horizontal guide. + */ + guideX = null; -/** - * Variable: guideY - * - * Holds the for the vertical guide. - */ -guideY = null; + /** + * Variable: guideY + * + * Holds the for the vertical guide. + */ + guideY = null; -/** - * Variable: rounded - * - * Specifies if rounded coordinates should be used. Default is false. - */ -rounded = false; + /** + * Variable: rounded + * + * Specifies if rounded coordinates should be used. Default is false. + */ + rounded = false; -/** - * Variable: tolerance - * - * Default tolerance in px if grid is disabled. Default is 2. - */ -tolerance = 2; + /** + * Variable: tolerance + * + * Default tolerance in px if grid is disabled. Default is 2. + */ + tolerance = 2; -/** - * Function: setStates - * - * Sets the that should be used for alignment. - */ -setStates = (states)=> -{ - this.states = states; -}; + /** + * Function: setStates + * + * Sets the that should be used for alignment. + */ + setStates = (states) => { + this.states = states; + }; -/** - * Function: isEnabledForEvent - * - * Returns true if the guide should be enabled for the given native event. This - * implementation always returns true. - */ -isEnabledForEvent = (evt)=> -{ - return true; -}; + /** + * Function: isEnabledForEvent + * + * Returns true if the guide should be enabled for the given native event. This + * implementation always returns true. + */ + isEnabledForEvent = (evt) => { + return true; + }; -/** - * Function: getGuideTolerance - * - * Returns the tolerance for the guides. Default value is gridSize / 2. - */ -getGuideTolerance = (gridEnabled)=> -{ - return (gridEnabled && this.graph.gridEnabled) ? this.graph.gridSize / 2 : this.tolerance; -}; + /** + * Function: getGuideTolerance + * + * Returns the tolerance for the guides. Default value is gridSize / 2. + */ + getGuideTolerance = (gridEnabled) => { + return (gridEnabled && this.graph.gridEnabled) ? this.graph.gridSize / 2 : this.tolerance; + }; -/** - * Function: createGuideShape - * - * Returns the mxShape to be used for painting the respective guide. This - * implementation returns a new, dashed and crisp using - * and as the format. - * - * Parameters: - * - * horizontal - Boolean that specifies which guide should be created. - */ -createGuideShape = (horizontal)=> -{ - var guide = new mxPolyline([], mxConstants.GUIDE_COLOR, mxConstants.GUIDE_STROKEWIDTH); - guide.isDashed = true; + /** + * Function: createGuideShape + * + * Returns the mxShape to be used for painting the respective guide. This + * implementation returns a new, dashed and crisp using + * and as the format. + * + * Parameters: + * + * horizontal - Boolean that specifies which guide should be created. + */ + createGuideShape = (horizontal) => { + var guide = new mxPolyline([], mxConstants.GUIDE_COLOR, mxConstants.GUIDE_STROKEWIDTH); + guide.isDashed = true; - return guide; -}; + return guide; + }; -/** - * Function: isStateIgnored - * - * Returns true if the given state should be ignored. - */ -isStateIgnored = (state)=> -{ - return false; -}; + /** + * Function: isStateIgnored + * + * Returns true if the given state should be ignored. + */ + isStateIgnored = (state) => { + return false; + }; -/** - * Function: move - * - * Moves the by the given and returnt the snapped point. - */ -move = (bounds, delta, gridEnabled, clone)=> -{ - if (this.states != null && (this.horizontal || this.vertical) && bounds != null && delta != null) - { - var scale = this.graph.getView().scale; - var tt = this.getGuideTolerance(gridEnabled) * scale; - var b = bounds.clone(); - b.x += delta.x; - b.y += delta.y; - var overrideX = false; - var stateX = null; - var valueX = null; - var overrideY = false; - var stateY = null; - var valueY = null; - var ttX = tt; - var ttY = tt; - var left = b.x; - var right = b.x + b.width; - var center = b.getCenterX(); - var top = b.y; - var bottom = b.y + b.height; - var middle = b.getCenterY(); + /** + * Function: move + * + * Moves the by the given and returnt the snapped point. + */ + move = (bounds, delta, gridEnabled, clone) => { + if (this.states != null && (this.horizontal || this.vertical) && bounds != null && delta != null) { + var scale = this.graph.getView().scale; + var tt = this.getGuideTolerance(gridEnabled) * scale; + var b = bounds.clone(); + b.x += delta.x; + b.y += delta.y; + var overrideX = false; + var stateX = null; + var valueX = null; + var overrideY = false; + var stateY = null; + var valueY = null; + var ttX = tt; + var ttY = tt; + var left = b.x; + var right = b.x + b.width; + var center = b.getCenterX(); + var top = b.y; + var bottom = b.y + b.height; + var middle = b.getCenterY(); - // Snaps the left, center and right to the given x-coordinate - function snapX(x, state, centerAlign) - { - var override = false; + // Snaps the left, center and right to the given x-coordinate + function snapX(x, state, centerAlign) { + var override = false; - if (centerAlign && Math.abs(x - center) < ttX) - { - delta.x = x - bounds.getCenterX(); - ttX = Math.abs(x - center); - override = true; - } - else if (!centerAlign) - { - if (Math.abs(x - left) < ttX) - { - delta.x = x - bounds.x; - ttX = Math.abs(x - left); + if (centerAlign && Math.abs(x - center) < ttX) { + delta.x = x - bounds.getCenterX(); + ttX = Math.abs(x - center); override = true; - } - else if (Math.abs(x - right) < ttX) - { - delta.x = x - bounds.x - bounds.width; - ttX = Math.abs(x - right); - override = true; - } - } - - if (override) - { - stateX = state; - valueX = x; - - if (this.guideX == null) - { - this.guideX = this.createGuideShape(true); - - // Makes sure to use either VML or SVG shapes in order to implement - // event-transparency on the background area of the rectangle since - // HTML shapes do not let mouseevents through even when transparent - this.guideX.dialect = (this.graph.dialect != mxConstants.DIALECT_SVG) ? - mxConstants.DIALECT_VML : mxConstants.DIALECT_SVG; - this.guideX.pointerEvents = false; - this.guideX.init(this.graph.getView().getOverlayPane()); - } - } - - overrideX = overrideX || override; - }; - - // Snaps the top, middle or bottom to the given y-coordinate - function snapY(y, state, centerAlign) - { - var override = false; - - if (centerAlign && Math.abs(y - middle) < ttY) - { - delta.y = y - bounds.getCenterY(); - ttY = Math.abs(y - middle); - override = true; - } - else if (!centerAlign) - { - if (Math.abs(y - top) < ttY) - { - delta.y = y - bounds.y; - ttY = Math.abs(y - top); - override = true; - } - else if (Math.abs(y - bottom) < ttY) - { - delta.y = y - bounds.y - bounds.height; - ttY = Math.abs(y - bottom); - override = true; - } - } - - if (override) - { - stateY = state; - valueY = y; - - if (this.guideY == null) - { - this.guideY = this.createGuideShape(false); - - // Makes sure to use either VML or SVG shapes in order to implement - // event-transparency on the background area of the rectangle since - // HTML shapes do not let mouseevents through even when transparent - this.guideY.dialect = (this.graph.dialect != mxConstants.DIALECT_SVG) ? - mxConstants.DIALECT_VML : mxConstants.DIALECT_SVG; - this.guideY.pointerEvents = false; - this.guideY.init(this.graph.getView().getOverlayPane()); - } - } - - overrideY = overrideY || override; - }; - - for (var i = 0; i < this.states.length; i++) - { - var state = this.states[i]; - - if (state != null && !this.isStateIgnored(state)) - { - // Align x - if (this.horizontal) - { - snapX.call(this, state.getCenterX(), state, true); - snapX.call(this, state.x, state, false); - snapX.call(this, state.x + state.width, state, false); - - // Aligns left and right of shape to center of page - if (state.cell == null) - { - snapX.call(this, state.getCenterX(), state, false); + } else if (!centerAlign) { + if (Math.abs(x - left) < ttX) { + delta.x = x - bounds.x; + ttX = Math.abs(x - left); + override = true; + } else if (Math.abs(x - right) < ttX) { + delta.x = x - bounds.x - bounds.width; + ttX = Math.abs(x - right); + override = true; } } - // Align y - if (this.vertical) - { - snapY.call(this, state.getCenterY(), state, true); - snapY.call(this, state.y, state, false); - snapY.call(this, state.y + state.height, state, false); + if (override) { + stateX = state; + valueX = x; - // Aligns left and right of shape to center of page - if (state.cell == null) - { - snapY.call(this, state.getCenterY(), state, false); + if (this.guideX == null) { + this.guideX = this.createGuideShape(true); + + // Makes sure to use either VML or SVG shapes in order to implement + // event-transparency on the background area of the rectangle since + // HTML shapes do not let mouseevents through even when transparent + this.guideX.dialect = (this.graph.dialect != mxConstants.DIALECT_SVG) ? + mxConstants.DIALECT_VML : mxConstants.DIALECT_SVG; + this.guideX.pointerEvents = false; + this.guideX.init(this.graph.getView().getOverlayPane()); + } + } + + overrideX = overrideX || override; + }; + + // Snaps the top, middle or bottom to the given y-coordinate + function snapY(y, state, centerAlign) { + var override = false; + + if (centerAlign && Math.abs(y - middle) < ttY) { + delta.y = y - bounds.getCenterY(); + ttY = Math.abs(y - middle); + override = true; + } else if (!centerAlign) { + if (Math.abs(y - top) < ttY) { + delta.y = y - bounds.y; + ttY = Math.abs(y - top); + override = true; + } else if (Math.abs(y - bottom) < ttY) { + delta.y = y - bounds.y - bounds.height; + ttY = Math.abs(y - bottom); + override = true; + } + } + + if (override) { + stateY = state; + valueY = y; + + if (this.guideY == null) { + this.guideY = this.createGuideShape(false); + + // Makes sure to use either VML or SVG shapes in order to implement + // event-transparency on the background area of the rectangle since + // HTML shapes do not let mouseevents through even when transparent + this.guideY.dialect = (this.graph.dialect != mxConstants.DIALECT_SVG) ? + mxConstants.DIALECT_VML : mxConstants.DIALECT_SVG; + this.guideY.pointerEvents = false; + this.guideY.init(this.graph.getView().getOverlayPane()); + } + } + + overrideY = overrideY || override; + }; + + for (var i = 0; i < this.states.length; i++) { + var state = this.states[i]; + + if (state != null && !this.isStateIgnored(state)) { + // Align x + if (this.horizontal) { + snapX.call(this, state.getCenterX(), state, true); + snapX.call(this, state.x, state, false); + snapX.call(this, state.x + state.width, state, false); + + // Aligns left and right of shape to center of page + if (state.cell == null) { + snapX.call(this, state.getCenterX(), state, false); + } + } + + // Align y + if (this.vertical) { + snapY.call(this, state.getCenterY(), state, true); + snapY.call(this, state.y, state, false); + snapY.call(this, state.y + state.height, state, false); + + // Aligns left and right of shape to center of page + if (state.cell == null) { + snapY.call(this, state.getCenterY(), state, false); + } } } } + + // Moves cells to the raster if not aligned + this.graph.snapDelta(delta, bounds, !gridEnabled, overrideX, overrideY); + delta = this.getDelta(bounds, stateX, delta.x, stateY, delta.y) + + // Redraws the guides + var c = this.graph.container; + + if (!overrideX && this.guideX != null) { + this.guideX.node.style.visibility = 'hidden'; + } else if (this.guideX != null) { + var minY = null; + var maxY = null; + + if (stateX != null && bounds != null) { + minY = Math.min(bounds.y + delta.y - this.graph.panDy, stateX.y); + maxY = Math.max(bounds.y + bounds.height + delta.y - this.graph.panDy, stateX.y + stateX.height); + } + + if (minY != null && maxY != null) { + this.guideX.points = [new mxPoint(valueX, minY), new mxPoint(valueX, maxY)]; + } else { + this.guideX.points = [new mxPoint(valueX, -this.graph.panDy), + new mxPoint(valueX, c.scrollHeight - 3 - this.graph.panDy)]; + } + + this.guideX.stroke = this.getGuideColor(stateX, true); + this.guideX.node.style.visibility = 'visible'; + this.guideX.redraw(); + } + + if (!overrideY && this.guideY != null) { + this.guideY.node.style.visibility = 'hidden'; + } else if (this.guideY != null) { + var minX = null; + var maxX = null; + + if (stateY != null && bounds != null) { + minX = Math.min(bounds.x + delta.x - this.graph.panDx, stateY.x); + maxX = Math.max(bounds.x + bounds.width + delta.x - this.graph.panDx, stateY.x + stateY.width); + } + + if (minX != null && maxX != null) { + this.guideY.points = [new mxPoint(minX, valueY), new mxPoint(maxX, valueY)]; + } else { + this.guideY.points = [new mxPoint(-this.graph.panDx, valueY), + new mxPoint(c.scrollWidth - 3 - this.graph.panDx, valueY)]; + } + + this.guideY.stroke = this.getGuideColor(stateY, false); + this.guideY.node.style.visibility = 'visible'; + this.guideY.redraw(); + } } - // Moves cells to the raster if not aligned - this.graph.snapDelta(delta, bounds, !gridEnabled, overrideX, overrideY); - delta = this.getDelta(bounds, stateX, delta.x, stateY, delta.y) + return delta; + }; - // Redraws the guides - var c = this.graph.container; + /** + * Function: getDelta + * + * Rounds to pixels for virtual states (eg. page guides) + */ + getDelta = (bounds, stateX, dx, stateY, dy) => { + var s = this.graph.view.scale; - if (!overrideX && this.guideX != null) - { - this.guideX.node.style.visibility = 'hidden'; - } - else if (this.guideX != null) - { - var minY = null; - var maxY = null; - - if (stateX != null && bounds != null) - { - minY = Math.min(bounds.y + delta.y - this.graph.panDy, stateX.y); - maxY = Math.max(bounds.y + bounds.height + delta.y - this.graph.panDy, stateX.y + stateX.height); - } - - if (minY != null && maxY != null) - { - this.guideX.points = [new mxPoint(valueX, minY), new mxPoint(valueX, maxY)]; - } - else - { - this.guideX.points = [new mxPoint(valueX, -this.graph.panDy), - new mxPoint(valueX, c.scrollHeight - 3 - this.graph.panDy)]; - } - - this.guideX.stroke = this.getGuideColor(stateX, true); - this.guideX.node.style.visibility = 'visible'; - this.guideX.redraw(); + if (this.rounded || (stateX != null && stateX.cell == null)) { + dx = Math.round((bounds.x + dx) / s) * s - bounds.x; } - if (!overrideY && this.guideY != null) - { - this.guideY.node.style.visibility = 'hidden'; + if (this.rounded || (stateY != null && stateY.cell == null)) { + dy = Math.round((bounds.y + dy) / s) * s - bounds.y; } - else if (this.guideY != null) - { - var minX = null; - var maxX = null; - if (stateY != null && bounds != null) - { - minX = Math.min(bounds.x + delta.x - this.graph.panDx, stateY.x); - maxX = Math.max(bounds.x + bounds.width + delta.x - this.graph.panDx, stateY.x + stateY.width); - } + return new mxPoint(dx, dy); + }; - if (minX != null && maxX != null) - { - this.guideY.points = [new mxPoint(minX, valueY), new mxPoint(maxX, valueY)]; - } - else - { - this.guideY.points = [new mxPoint(-this.graph.panDx, valueY), - new mxPoint(c.scrollWidth - 3 - this.graph.panDx, valueY)]; - } + /** + * Function: getGuideColor + * + * Returns the color for the given state. + */ + getGuideColor = (state, horizontal) => { + return mxConstants.GUIDE_COLOR; + }; - this.guideY.stroke = this.getGuideColor(stateY, false); - this.guideY.node.style.visibility = 'visible'; - this.guideY.redraw(); + /** + * Function: hide + * + * Hides all current guides. + */ + hide = () => { + this.setVisible(false); + }; + + /** + * Function: setVisible + * + * Shows or hides the current guides. + */ + setVisible = (visible) => { + if (this.guideX != null) { + this.guideX.node.style.visibility = (visible) ? 'visible' : 'hidden'; } - } - return delta; -}; + if (this.guideY != null) { + this.guideY.node.style.visibility = (visible) ? 'visible' : 'hidden'; + } + }; -/** - * Function: getDelta - * - * Rounds to pixels for virtual states (eg. page guides) - */ -getDelta = (bounds, stateX, dx, stateY, dy)=> -{ - var s = this.graph.view.scale; + /** + * Function: destroy + * + * Destroys all resources that this object uses. + */ + destroy = () => { + if (this.guideX != null) { + this.guideX.destroy(); + this.guideX = null; + } - if (this.rounded || (stateX != null && stateX.cell == null)) - { - dx = Math.round((bounds.x + dx) / s) * s - bounds.x; - } + if (this.guideY != null) { + this.guideY.destroy(); + this.guideY = null; + } + }; +} - if (this.rounded || (stateY != null && stateY.cell == null)) - { - dy = Math.round((bounds.y + dy) / s) * s - bounds.y; - } - - return new mxPoint(dx, dy); -}; - -/** - * Function: getGuideColor - * - * Returns the color for the given state. - */ -getGuideColor = (state, horizontal)=> -{ - return mxConstants.GUIDE_COLOR; -}; - -/** - * Function: hide - * - * Hides all current guides. - */ -hide = ()=> -{ - this.setVisible(false); -}; - -/** - * Function: setVisible - * - * Shows or hides the current guides. - */ -setVisible = (visible)=> -{ - if (this.guideX != null) - { - this.guideX.node.style.visibility = (visible) ? 'visible' : 'hidden'; - } - - if (this.guideY != null) - { - this.guideY.node.style.visibility = (visible) ? 'visible' : 'hidden'; - } -}; - -/** - * Function: destroy - * - * Destroys all resources that this object uses. - */ -destroy = ()=> -{ - if (this.guideX != null) - { - this.guideX.destroy(); - this.guideX = null; - } - - if (this.guideY != null) - { - this.guideY.destroy(); - this.guideY = null; - } -}; +export default mxGuide; diff --git a/src/js/util/mxImage.js b/src/js/util/mxImage.js index 13a89ec12..e2be5cdf0 100644 --- a/src/js/util/mxImage.js +++ b/src/js/util/mxImage.js @@ -2,39 +2,43 @@ * Copyright (c) 2006-2015, JGraph Ltd * Copyright (c) 2006-2015, Gaudenz Alder */ -/** - * Class: mxImage - * - * Encapsulates the URL, width and height of an image. - * - * Constructor: mxImage - * - * Constructs a new image. - */ -function mxImage(src, width, height) -{ - this.src = src; - this.width = width; - this.height = height; -}; -/** - * Variable: src - * - * String that specifies the URL of the image. - */ -src = null; +class mxImage { + /** + * Variable: src + * + * String that specifies the URL of the image. + */ + src = null; -/** - * Variable: width - * - * Integer that specifies the width of the image. - */ -width = null; + /** + * Variable: width + * + * Integer that specifies the width of the image. + */ + width = null; -/** - * Variable: height - * - * Integer that specifies the height of the image. - */ -height = null; + /** + * Variable: height + * + * Integer that specifies the height of the image. + */ + height = null; + + /** + * Class: mxImage + * + * Encapsulates the URL, width and height of an image. + * + * Constructor: mxImage + * + * Constructs a new image. + */ + constructor(src, width, height) { + this.src = src; + this.width = width; + this.height = height; + }; +} + +export default mxImage; diff --git a/src/js/util/mxImageBundle.js b/src/js/util/mxImageBundle.js index 71e20ca79..e6f498380 100644 --- a/src/js/util/mxImageBundle.js +++ b/src/js/util/mxImageBundle.js @@ -2,102 +2,102 @@ * Copyright (c) 2006-2015, JGraph Ltd * Copyright (c) 2006-2015, Gaudenz Alder */ -/** - * Class: mxImageBundle - * - * Maps from keys to base64 encoded images or file locations. All values must - * be URLs or use the format data:image/format followed by a comma and the base64 - * encoded image data, eg. "data:image/gif,XYZ", where XYZ is the base64 encoded - * image data. - * - * To add a new image bundle to an existing graph, the following code is used: - * - * (code) - * var bundle = new mxImageBundle(alt); - * bundle.putImage('myImage', 'data:image/gif,R0lGODlhEAAQAMIGAAAAAICAAICAgP' + - * '//AOzp2O3r2////////yH+FUNyZWF0ZWQgd2l0aCBUaGUgR0lNUAAh+QQBCgAHACwAAAAA' + - * 'EAAQAAADTXi63AowynnAMDfjPUDlnAAJhmeBFxAEloliKltWmiYCQvfVr6lBPB1ggxN1hi' + - * 'laSSASFQpIV5HJBDyHpqK2ejVRm2AAgZCdmCGO9CIBADs=', fallback); - * bundle.putImage('mySvgImage', 'data:image/svg+xml,' + encodeURIComponent( - * '' + - * '' + - * '' + - * ''), fallback); - * graph.addImageBundle(bundle); - * (end); - * - * Alt is an optional boolean (default is false) that specifies if the value - * or the fallback should be returned in . - * - * The image can then be referenced in any cell style using image=myImage. - * If you are using mxOutline, you should use the same image bundles in the - * graph that renders the outline. - * - * The keys for images are resolved in and - * turned into a data URI if the returned value has a short data URI format - * as specified above. - * - * A typical value for the fallback is a MTHML link as defined in RFC 2557. - * Note that this format requires a file to be dynamically created on the - * server-side, or the page that contains the graph to be modified to contain - * the resources, this can be done by adding a comment that contains the - * resource in the HEAD section of the page after the title tag. - * - * This type of fallback mechanism should be used in IE6 and IE7. IE8 does - * support data URIs, but the maximum size is limited to 32 KB, which means - * all data URIs should be limited to 32 KB. - */ -function mxImageBundle(alt) -{ - this.images = []; - this.alt = (alt != null) ? alt : false; -}; -/** - * Variable: images - * - * Maps from keys to images. - */ -images = null; +class mxImageBundle { + /** + * Variable: images + * + * Maps from keys to images. + */ + images = null; -/** - * Variable: alt - * - * Specifies if the fallback representation should be returned. - */ -alt = null; + /** + * Variable: alt + * + * Specifies if the fallback representation should be returned. + */ + alt = null; -/** - * Function: putImage - * - * Adds the specified entry to the map. The entry is an object with a value and - * fallback property as specified in the arguments. - */ -putImage = (key, value, fallback)=> -{ - this.images[key] = {value: value, fallback: fallback}; -}; + /** + * Class: mxImageBundle + * + * Maps from keys to base64 encoded images or file locations. All values must + * be URLs or use the format data:image/format followed by a comma and the base64 + * encoded image data, eg. "data:image/gif,XYZ", where XYZ is the base64 encoded + * image data. + * + * To add a new image bundle to an existing graph, the following code is used: + * + * (code) + * var bundle = new mxImageBundle(alt); + * bundle.putImage('myImage', 'data:image/gif,R0lGODlhEAAQAMIGAAAAAICAAICAgP' + + * '//AOzp2O3r2////////yH+FUNyZWF0ZWQgd2l0aCBUaGUgR0lNUAAh+QQBCgAHACwAAAAA' + + * 'EAAQAAADTXi63AowynnAMDfjPUDlnAAJhmeBFxAEloliKltWmiYCQvfVr6lBPB1ggxN1hi' + + * 'laSSASFQpIV5HJBDyHpqK2ejVRm2AAgZCdmCGO9CIBADs=', fallback); + * bundle.putImage('mySvgImage', 'data:image/svg+xml,' + encodeURIComponent( + * '' + + * '' + + * '' + + * ''), fallback); + * graph.addImageBundle(bundle); + * (end); + * + * Alt is an optional boolean (default is false) that specifies if the value + * or the fallback should be returned in . + * + * The image can then be referenced in any cell style using image=myImage. + * If you are using mxOutline, you should use the same image bundles in the + * graph that renders the outline. + * + * The keys for images are resolved in and + * turned into a data URI if the returned value has a short data URI format + * as specified above. + * + * A typical value for the fallback is a MTHML link as defined in RFC 2557. + * Note that this format requires a file to be dynamically created on the + * server-side, or the page that contains the graph to be modified to contain + * the resources, this can be done by adding a comment that contains the + * resource in the HEAD section of the page after the title tag. + * + * This type of fallback mechanism should be used in IE6 and IE7. IE8 does + * support data URIs, but the maximum size is limited to 32 KB, which means + * all data URIs should be limited to 32 KB. + */ + constructor(alt) { + this.images = []; + this.alt = (alt != null) ? alt : false; + }; -/** - * Function: getImage - * - * Returns the value for the given key. This returns the value - * or fallback, depending on . The fallback is returned if - * is true, the value is returned otherwise. - */ -getImage = (key)=> -{ - var result = null; - - if (key != null) - { - var img = this.images[key]; - - if (img != null) - { - result = (this.alt) ? img.fallback : img.value; + /** + * Function: putImage + * + * Adds the specified entry to the map. The entry is an object with a value and + * fallback property as specified in the arguments. + */ + putImage = (key, value, fallback) => { + this.images[key] = {value: value, fallback: fallback}; + }; + + /** + * Function: getImage + * + * Returns the value for the given key. This returns the value + * or fallback, depending on . The fallback is returned if + * is true, the value is returned otherwise. + */ + getImage = (key) => { + var result = null; + + if (key != null) { + var img = this.images[key]; + + if (img != null) { + result = (this.alt) ? img.fallback : img.value; + } } - } - - return result; -}; + + return result; + }; +} + +export default mxImageBundle; diff --git a/src/js/util/mxImageExport.js b/src/js/util/mxImageExport.js index 817289a32..85e6c3130 100644 --- a/src/js/util/mxImageExport.js +++ b/src/js/util/mxImageExport.js @@ -2,182 +2,166 @@ * Copyright (c) 2006-2015, JGraph Ltd * Copyright (c) 2006-2015, Gaudenz Alder */ -/** - * Class: mxImageExport - * - * Creates a new image export instance to be used with an export canvas. Here - * is an example that uses this class to create an image via a backend using - * . - * - * (code) - * var xmlDoc = mxUtils.createXmlDocument(); - * var root = xmlDoc.createElement('output'); - * xmlDoc.appendChild(root); - * - * var xmlCanvas = new mxXmlCanvas2D(root); - * var imgExport = new mxImageExport(); - * imgExport.drawState(graph.getView().getState(graph.model.root), xmlCanvas); - * - * var bounds = graph.getGraphBounds(); - * var w = Math.ceil(bounds.x + bounds.width); - * var h = Math.ceil(bounds.y + bounds.height); - * - * var xml = mxUtils.getXml(root); - * new mxXmlRequest('export', 'format=png&w=' + w + - * '&h=' + h + '&bg=#F9F7ED&xml=' + encodeURIComponent(xml)) - * .simulate(document, '_blank'); - * (end) - * - * Constructor: mxImageExport - * - * Constructs a new image export. - */ -function mxImageExport() { }; -/** - * Variable: includeOverlays - * - * Specifies if overlays should be included in the export. Default is false. - */ -includeOverlays = false; +class mxImageExport { + /** + * Variable: includeOverlays + * + * Specifies if overlays should be included in the export. Default is false. + */ + includeOverlays = false; -/** - * Function: drawState - * - * Draws the given state and all its descendants to the given canvas. - */ -drawState = (state, canvas)=> -{ - if (state != null) - { - this.visitStatesRecursive(state, canvas, mxUtils.bind(this, ()=> - { - this.drawCellState.apply(this, arguments); - })); - - // Paints the overlays - if (this.includeOverlays) - { - this.visitStatesRecursive(state, canvas, mxUtils.bind(this, ()=> - { - this.drawOverlays.apply(this, arguments); + /** + * Class: mxImageExport + * + * Creates a new image export instance to be used with an export canvas. Here + * is an example that uses this class to create an image via a backend using + * . + * + * (code) + * var xmlDoc = mxUtils.createXmlDocument(); + * var root = xmlDoc.createElement('output'); + * xmlDoc.appendChild(root); + * + * var xmlCanvas = new mxXmlCanvas2D(root); + * var imgExport = new mxImageExport(); + * imgExport.drawState(graph.getView().getState(graph.model.root), xmlCanvas); + * + * var bounds = graph.getGraphBounds(); + * var w = Math.ceil(bounds.x + bounds.width); + * var h = Math.ceil(bounds.y + bounds.height); + * + * var xml = mxUtils.getXml(root); + * new mxXmlRequest('export', 'format=png&w=' + w + + * '&h=' + h + '&bg=#F9F7ED&xml=' + encodeURIComponent(xml)) + * .simulate(document, '_blank'); + * (end) + * + * Constructor: mxImageExport + * + * Constructs a new image export. + */ + constructor() {} + + /** + * Function: drawState + * + * Draws the given state and all its descendants to the given canvas. + */ + drawState = (state, canvas) => { + if (state != null) { + this.visitStatesRecursive(state, canvas, mxUtils.bind(this, () => { + this.drawCellState.apply(this, arguments); })); - } - } -}; -/** - * Function: visitStatesRecursive - * - * Visits the given state and all its descendants to the given canvas recursively. - */ -visitStatesRecursive = (state, canvas, visitor)=> -{ - if (state != null) - { - visitor(state, canvas); - - var graph = state.view.graph; - var childCount = graph.model.getChildCount(state.cell); - - for (var i = 0; i < childCount; i++) - { - var childState = graph.view.getState(graph.model.getChildAt(state.cell, i)); - this.visitStatesRecursive(childState, canvas, visitor); - } - } -}; - -/** - * Function: getLinkForCellState - * - * Returns the link for the given cell state and canvas. This returns null. - */ -getLinkForCellState = (state, canvas)=> -{ - return null; -}; - -/** - * Function: drawCellState - * - * Draws the given state to the given canvas. - */ -drawCellState = (state, canvas)=> -{ - // Experimental feature - var link = this.getLinkForCellState(state, canvas); - - if (link != null) - { - canvas.setLink(link); - } - - // Paints the shape and text - this.drawShape(state, canvas); - this.drawText(state, canvas); - - if (link != null) - { - canvas.setLink(null); - } -}; - -/** - * Function: drawShape - * - * Draws the shape of the given state. - */ -drawShape = (state, canvas)=> -{ - if (state.shape instanceof mxShape && state.shape.checkBounds()) - { - canvas.save(); - - state.shape.beforePaint(canvas); - state.shape.paint(canvas); - state.shape.afterPaint(canvas); - - canvas.restore(); - } -}; - -/** - * Function: drawText - * - * Draws the text of the given state. - */ -drawText = (state, canvas)=> -{ - if (state.text != null && state.text.checkBounds()) - { - canvas.save(); - - state.text.beforePaint(canvas); - state.text.paint(canvas); - state.text.afterPaint(canvas); - - canvas.restore(); - } -}; - -/** - * Function: drawOverlays - * - * Draws the overlays for the given state. This is called if - * is true. - */ -drawOverlays = (state, canvas)=> -{ - if (state.overlays != null) - { - state.overlays.visit((id, shape)=> - { - if (shape instanceof mxShape) - { - shape.paint(canvas); + // Paints the overlays + if (this.includeOverlays) { + this.visitStatesRecursive(state, canvas, mxUtils.bind(this, () => { + this.drawOverlays.apply(this, arguments); + })); } - }); - } -}; + } + }; + /** + * Function: visitStatesRecursive + * + * Visits the given state and all its descendants to the given canvas recursively. + */ + visitStatesRecursive = (state, canvas, visitor) => { + if (state != null) { + visitor(state, canvas); + + var graph = state.view.graph; + var childCount = graph.model.getChildCount(state.cell); + + for (var i = 0; i < childCount; i++) { + var childState = graph.view.getState(graph.model.getChildAt(state.cell, i)); + this.visitStatesRecursive(childState, canvas, visitor); + } + } + }; + + /** + * Function: getLinkForCellState + * + * Returns the link for the given cell state and canvas. This returns null. + */ + getLinkForCellState = (state, canvas) => { + return null; + }; + + /** + * Function: drawCellState + * + * Draws the given state to the given canvas. + */ + drawCellState = (state, canvas) => { + // Experimental feature + var link = this.getLinkForCellState(state, canvas); + + if (link != null) { + canvas.setLink(link); + } + + // Paints the shape and text + this.drawShape(state, canvas); + this.drawText(state, canvas); + + if (link != null) { + canvas.setLink(null); + } + }; + + /** + * Function: drawShape + * + * Draws the shape of the given state. + */ + drawShape = (state, canvas) => { + if (state.shape instanceof mxShape && state.shape.checkBounds()) { + canvas.save(); + + state.shape.beforePaint(canvas); + state.shape.paint(canvas); + state.shape.afterPaint(canvas); + + canvas.restore(); + } + }; + + /** + * Function: drawText + * + * Draws the text of the given state. + */ + drawText = (state, canvas) => { + if (state.text != null && state.text.checkBounds()) { + canvas.save(); + + state.text.beforePaint(canvas); + state.text.paint(canvas); + state.text.afterPaint(canvas); + + canvas.restore(); + } + }; + + /** + * Function: drawOverlays + * + * Draws the overlays for the given state. This is called if + * is true. + */ + drawOverlays = (state, canvas) => { + if (state.overlays != null) { + state.overlays.visit((id, shape) => { + if (shape instanceof mxShape) { + shape.paint(canvas); + } + }); + } + }; +} + +export default mxImageExport; diff --git a/src/js/util/mxLog.js b/src/js/util/mxLog.js index 2debdbd19..3cba3187d 100644 --- a/src/js/util/mxLog.js +++ b/src/js/util/mxLog.js @@ -2,22 +2,22 @@ * Copyright (c) 2006-2015, JGraph Ltd * Copyright (c) 2006-2015, Gaudenz Alder */ -var mxLog = -{ + +var mxLog = { /** * Class: mxLog - * + * * A singleton class that implements a simple console. - * + * * Variable: consoleName - * + * * Specifies the name of the console window. Default is 'Console'. */ consoleName: 'Console', - + /** * Variable: TRACE - * + * * Specified if the output for and should be visible in the * console. Default is false. */ @@ -25,7 +25,7 @@ var mxLog = /** * Variable: DEBUG - * + * * Specifies if the output for should be visible in the console. * Default is true. */ @@ -33,7 +33,7 @@ var mxLog = /** * Variable: WARN - * + * * Specifies if the output for should be visible in the console. * Default is true. */ @@ -41,11 +41,11 @@ var mxLog = /** * Variable: buffer - * + * * Buffer for pre-initialized content. */ buffer: '', - + /** * Function: init * @@ -53,10 +53,8 @@ var mxLog = * point to a non-null value. This is called from within if the * log has not yet been initialized. */ - init: ()=> - { - if (mxLog.window == null && document.body != null) - { + init: () => { + if (mxLog.window == null && document.body != null) { var title = mxLog.consoleName + ' - mxGraph ' + mxClient.VERSION; // Creates a table that maintains the layout @@ -68,7 +66,7 @@ var mxLog = var tr = document.createElement('tr'); var td = document.createElement('td'); td.style.verticalAlign = 'top'; - + // Adds the actual console as a textarea mxLog.textarea = document.createElement('textarea'); mxLog.textarea.setAttribute('wrap', 'off'); @@ -78,15 +76,12 @@ var mxLog = mxLog.textarea.value = mxLog.buffer; // Workaround for wrong width in standards mode - if (mxClient.IS_NS && document.compatMode != 'BackCompat') - { + if (mxClient.IS_NS && document.compatMode != 'BackCompat') { mxLog.textarea.style.width = '99%'; - } - else - { + } else { mxLog.textarea.style.width = '100%'; } - + td.appendChild(mxLog.textarea); tr.appendChild(td); tbody.appendChild(tr); @@ -96,77 +91,59 @@ var mxLog = mxLog.td = document.createElement('td'); mxLog.td.style.verticalAlign = 'top'; mxLog.td.setAttribute('height', '30px'); - + tr.appendChild(mxLog.td); tbody.appendChild(tr); table.appendChild(tbody); // Adds various debugging buttons - mxLog.addButton('Info', function (evt) - { + mxLog.addButton('Info', function (evt) { mxLog.info(); }); - - mxLog.addButton('DOM', function (evt) - { + + mxLog.addButton('DOM', function (evt) { var content = mxUtils.getInnerHtml(document.body); mxLog.debug(content); }); - - mxLog.addButton('Trace', function (evt) - { + + mxLog.addButton('Trace', function (evt) { mxLog.TRACE = !mxLog.TRACE; - - if (mxLog.TRACE) - { + + if (mxLog.TRACE) { mxLog.debug('Tracing enabled'); - } - else - { + } else { mxLog.debug('Tracing disabled'); } - }); + }); - mxLog.addButton('Copy', function (evt) - { - try - { + mxLog.addButton('Copy', function (evt) { + try { mxUtils.copy(mxLog.textarea.value); - } - catch (err) - { + } catch (err) { mxUtils.alert(err); } - }); + }); - mxLog.addButton('Show', function (evt) - { - try - { + mxLog.addButton('Show', function (evt) { + try { mxUtils.popup(mxLog.textarea.value); - } - catch (err) - { + } catch (err) { mxUtils.alert(err); } - }); - - mxLog.addButton('Clear', function (evt) - { + }); + + mxLog.addButton('Clear', function (evt) { mxLog.textarea.value = ''; }); // Cross-browser code to get window size var h = 0; var w = 0; - - if (typeof(window.innerWidth) === 'number') - { + + if (typeof (window.innerWidth) === 'number') { h = window.innerHeight; w = window.innerWidth; - } - else - { + } else { h = (document.documentElement.clientHeight || document.body.clientHeight); w = document.body.clientWidth; } @@ -177,18 +154,16 @@ var mxLog = mxLog.window.setResizable(true); mxLog.window.setClosable(true); mxLog.window.destroyOnClose = false; - + // Workaround for ignored textarea height in various setups if ((mxClient.IS_NS && !mxClient.IS_GC && - !mxClient.IS_SF && document.compatMode != 'BackCompat')) - { + !mxClient.IS_SF && document.compatMode != 'BackCompat')) { var elt = mxLog.window.getElement(); - - var resizeHandler = (sender, evt)=> - { + + var resizeHandler = (sender, evt) => { mxLog.textarea.style.height = Math.max(0, elt.offsetHeight - 70) + 'px'; - }; - + }; + mxLog.window.addListener(mxEvent.RESIZE_END, resizeHandler); mxLog.window.addListener(mxEvent.MAXIMIZE, resizeHandler); mxLog.window.addListener(mxEvent.NORMALIZE, resizeHandler); @@ -197,83 +172,75 @@ var mxLog = } } }, - + /** * Function: info - * + * * Writes the current navigator information to the console. */ - info: ()=> - { + info: () => { mxLog.writeln(mxUtils.toString(navigator)); }, - + /** * Function: addButton - * + * * Adds a button to the console using the given label and function. */ - addButton: (lab, funct)=> - { + addButton: (lab, funct) => { var button = document.createElement('button'); mxUtils.write(button, lab); mxEvent.addListener(button, 'click', funct); mxLog.td.appendChild(button); }, - + /** * Function: isVisible - * + * * Returns true if the console is visible. */ - isVisible: ()=> - { - if (mxLog.window != null) - { + isVisible: () => { + if (mxLog.window != null) { return mxLog.window.isVisible(); } - + return false; }, - + /** * Function: show - * + * * Shows the console. */ - show: ()=> - { + show: () => { mxLog.setVisible(true); }, /** * Function: setVisible - * + * * Shows or hides the console. */ - setVisible: (visible)=> - { - if (mxLog.window == null) - { + setVisible: (visible) => { + if (mxLog.window == null) { mxLog.init(); } - if (mxLog.window != null) - { + if (mxLog.window != null) { mxLog.window.setVisible(visible); } }, /** * Function: enter - * + * * Writes the specified string to the console - * if is true and returns the current + * if is true and returns the current * time in milliseconds. * * Example: - * + * * (code) * mxLog.show(); * var t0 = mxLog.enter('Hello'); @@ -281,133 +248,116 @@ var mxLog = * mxLog.leave('World!', t0); * (end) */ - enter: (string)=> - { - if (mxLog.TRACE) - { - mxLog.writeln('Entering '+string); - + enter: (string) => { + if (mxLog.TRACE) { + mxLog.writeln('Entering ' + string); + return new Date().getTime(); } }, /** * Function: leave - * + * * Writes the specified string to the console * if is true and computes the difference * between the current time and t0 in milliseconds. * See for an example. */ - leave: (string, t0)=> - { - if (mxLog.TRACE) - { - var dt = (t0 != 0) ? ' ('+(new Date().getTime() - t0)+' ms)' : ''; - mxLog.writeln('Leaving '+string+dt); + leave: (string, t0) => { + if (mxLog.TRACE) { + var dt = (t0 != 0) ? ' (' + (new Date().getTime() - t0) + ' ms)' : ''; + mxLog.writeln('Leaving ' + string + dt); } }, - + /** * Function: debug - * + * * Adds all arguments to the console if is enabled. * * Example: - * + * * (code) * mxLog.show(); * mxLog.debug('Hello, World!'); * (end) */ - debug: ()=> - { - if (mxLog.DEBUG) - { + debug: () => { + if (mxLog.DEBUG) { mxLog.writeln.apply(this, arguments); } }, - + /** * Function: warn - * + * * Adds all arguments to the console if is enabled. * * Example: - * + * * (code) * mxLog.show(); * mxLog.warn('Hello, World!'); * (end) */ - warn: ()=> - { - if (mxLog.WARN) - { + warn: () => { + if (mxLog.WARN) { mxLog.writeln.apply(this, arguments); } }, /** * Function: write - * + * * Adds the specified strings to the console. */ - write: ()=> - { + write: () => { var string = ''; - - for (var i = 0; i < arguments.length; i++) - { + + for (var i = 0; i < arguments.length; i++) { string += arguments[i]; - - if (i < arguments.length - 1) - { + + if (i < arguments.length - 1) { string += ' '; } } - - if (mxLog.textarea != null) - { + + if (mxLog.textarea != null) { mxLog.textarea.value = mxLog.textarea.value + string; // Workaround for no update in Presto 2.5.22 (Opera 10.5) if (navigator.userAgent != null && - navigator.userAgent.indexOf('Presto/2.5') >= 0) - { + navigator.userAgent.indexOf('Presto/2.5') >= 0) { mxLog.textarea.style.visibility = 'hidden'; mxLog.textarea.style.visibility = 'visible'; } - + mxLog.textarea.scrollTop = mxLog.textarea.scrollHeight; - } - else - { + } else { mxLog.buffer += string; } }, - + /** * Function: writeln - * + * * Adds the specified strings to the console, appending a linefeed at the * end of each string. */ - writeln: ()=> - { + writeln: () => { var string = ''; - - for (var i = 0; i < arguments.length; i++) - { + + for (var i = 0; i < arguments.length; i++) { string += arguments[i]; - - if (i < arguments.length - 1) - { + + if (i < arguments.length - 1) { string += ' '; } } mxLog.write(string + '\n'); } - }; + +export default mxLog; diff --git a/src/js/util/mxMorphing.js b/src/js/util/mxMorphing.js index 5a0c063bf..771519ce4 100644 --- a/src/js/util/mxMorphing.js +++ b/src/js/util/mxMorphing.js @@ -2,248 +2,226 @@ * Copyright (c) 2006-2015, JGraph Ltd * Copyright (c) 2006-2015, Gaudenz Alder */ -/** - * - * Class: mxMorphing - * - * Implements animation for morphing cells. Here is an example of - * using this class for animating the result of a layout algorithm: - * - * (code) - * graph.getModel().beginUpdate(); - * try - * { - * var circleLayout = new mxCircleLayout(graph); - * circleLayout.execute(graph.getDefaultParent()); - * } - * finally - * { - * var morph = new mxMorphing(graph); - * morph.addListener(mxEvent.DONE, ()=> - * { - * graph.getModel().endUpdate(); - * }); - * - * morph.startAnimation(); - * } - * (end) - * - * Constructor: mxMorphing - * - * Constructs an animation. - * - * Parameters: - * - * graph - Reference to the enclosing . - * steps - Optional number of steps in the morphing animation. Default is 6. - * ease - Optional easing constant for the animation. Default is 1.5. - * delay - Optional delay between the animation steps. Passed to . - */ -function mxMorphing(graph, steps, ease, delay) -{ - mxAnimation.call(this, delay); - this.graph = graph; - this.steps = (steps != null) ? steps : 6; - this.ease = (ease != null) ? ease : 1.5; -}; +import mxPoint from "./mxPoint"; +import mxCellStatePreview from "../view/mxCellStatePreview"; -/** - * Extends mxEventSource. - */ -mxMorphing.prototype = new mxAnimation(); -constructor = mxMorphing; +class mxMorphing extends mxAnimation { + /** + * Variable: graph + * + * Specifies the delay between the animation steps. Defaul is 30ms. + */ + graph = null; -/** - * Variable: graph - * - * Specifies the delay between the animation steps. Defaul is 30ms. - */ -graph = null; + /** + * Variable: steps + * + * Specifies the maximum number of steps for the morphing. + */ + steps = null; -/** - * Variable: steps - * - * Specifies the maximum number of steps for the morphing. - */ -steps = null; + /** + * Variable: step + * + * Contains the current step. + */ + step = 0; -/** - * Variable: step - * - * Contains the current step. - */ -step = 0; + /** + * Variable: ease + * + * Ease-off for movement towards the given vector. Larger values are + * slower and smoother. Default is 4. + */ + ease = null; -/** - * Variable: ease - * - * Ease-off for movement towards the given vector. Larger values are - * slower and smoother. Default is 4. - */ -ease = null; + /** + * Variable: cells + * + * Optional array of cells to be animated. If this is not specified + * then all cells are checked and animated if they have been moved + * in the current transaction. + */ + cells = null; -/** - * Variable: cells - * - * Optional array of cells to be animated. If this is not specified - * then all cells are checked and animated if they have been moved - * in the current transaction. - */ -cells = null; + /** + * + * Class: mxMorphing + * + * Implements animation for morphing cells. Here is an example of + * using this class for animating the result of a layout algorithm: + * + * (code) + * graph.getModel().beginUpdate(); + * try + * { + * var circleLayout = new mxCircleLayout(graph); + * circleLayout.execute(graph.getDefaultParent()); + * } + * finally + * { + * var morph = new mxMorphing(graph); + * morph.addListener(mxEvent.DONE, ()=> + * { + * graph.getModel().endUpdate(); + * }); + * + * morph.startAnimation(); + * } + * (end) + * + * Constructor: mxMorphing + * + * Constructs an animation. + * + * Parameters: + * + * graph - Reference to the enclosing . + * steps - Optional number of steps in the morphing animation. Default is 6. + * ease - Optional easing constant for the animation. Default is 1.5. + * delay - Optional delay between the animation steps. Passed to . + */ + constructor(graph, steps, ease, delay) { + super(delay); + this.graph = graph; + this.steps = (steps != null) ? steps : 6; + this.ease = (ease != null) ? ease : 1.5; + }; -/** - * Function: updateAnimation - * - * Animation step. - */ -updateAnimation = ()=> -{ - updateAnimation.apply(this, arguments); - var move = new mxCellStatePreview(this.graph); + /** + * Function: updateAnimation + * + * Animation step. + */ + updateAnimation = () => { + updateAnimation.apply(this, arguments); + var move = new mxCellStatePreview(this.graph); - if (this.cells != null) - { - // Animates the given cells individually without recursion - for (var i = 0; i < this.cells.length; i++) - { - this.animateCell(this.cells[i], move, false); + if (this.cells != null) { + // Animates the given cells individually without recursion + for (var i = 0; i < this.cells.length; i++) { + this.animateCell(this.cells[i], move, false); + } + } else { + // Animates all changed cells by using recursion to find + // the changed cells but not for the animation itself + this.animateCell(this.graph.getModel().getRoot(), move, true); } - } - else - { - // Animates all changed cells by using recursion to find - // the changed cells but not for the animation itself - this.animateCell(this.graph.getModel().getRoot(), move, true); - } - - this.show(move); - - if (move.isEmpty() || this.step++ >= this.steps) - { - this.stopAnimation(); - } -}; -/** - * Function: show - * - * Shows the changes in the given . - */ -show = (move)=> -{ - move.show(); -}; + this.show(move); -/** - * Function: animateCell - * - * Animates the given cell state using . - */ -animateCell = (cell, move, recurse)=> -{ - var state = this.graph.getView().getState(cell); - var delta = null; - - if (state != null) - { - // Moves the animated state from where it will be after the model - // change by subtracting the given delta vector from that location - delta = this.getDelta(state); - - if (this.graph.getModel().isVertex(cell) && (delta.x != 0 || delta.y != 0)) - { - var translate = this.graph.view.getTranslate(); - var scale = this.graph.view.getScale(); - - delta.x += translate.x * scale; - delta.y += translate.y * scale; - - move.moveState(state, -delta.x / this.ease, -delta.y / this.ease); + if (move.isEmpty() || this.step++ >= this.steps) { + this.stopAnimation(); } - } - - if (recurse && !this.stopRecursion(state, delta)) - { - var childCount = this.graph.getModel().getChildCount(cell); + }; - for (var i = 0; i < childCount; i++) - { - this.animateCell(this.graph.getModel().getChildAt(cell, i), move, recurse); + /** + * Function: show + * + * Shows the changes in the given . + */ + show = (move) => { + move.show(); + }; + + /** + * Function: animateCell + * + * Animates the given cell state using . + */ + animateCell = (cell, move, recurse) => { + var state = this.graph.getView().getState(cell); + var delta = null; + + if (state != null) { + // Moves the animated state from where it will be after the model + // change by subtracting the given delta vector from that location + delta = this.getDelta(state); + + if (this.graph.getModel().isVertex(cell) && (delta.x != 0 || delta.y != 0)) { + var translate = this.graph.view.getTranslate(); + var scale = this.graph.view.getScale(); + + delta.x += translate.x * scale; + delta.y += translate.y * scale; + + move.moveState(state, -delta.x / this.ease, -delta.y / this.ease); + } } - } -}; -/** - * Function: stopRecursion - * - * Returns true if the animation should not recursively find more - * deltas for children if the given parent state has been animated. - */ -stopRecursion = (state, delta)=> -{ - return delta != null && (delta.x != 0 || delta.y != 0); -}; + if (recurse && !this.stopRecursion(state, delta)) { + var childCount = this.graph.getModel().getChildCount(cell); -/** - * Function: getDelta - * - * Returns the vector between the current rendered state and the future - * location of the state after the display will be updated. - */ -getDelta = (state)=> -{ - var origin = this.getOriginForCell(state.cell); - var translate = this.graph.getView().getTranslate(); - var scale = this.graph.getView().getScale(); - var x = state.x / scale - translate.x; - var y = state.y / scale - translate.y; + for (var i = 0; i < childCount; i++) { + this.animateCell(this.graph.getModel().getChildAt(cell, i), move, recurse); + } + } + }; - return new mxPoint((origin.x - x) * scale, (origin.y - y) * scale); -}; + /** + * Function: stopRecursion + * + * Returns true if the animation should not recursively find more + * deltas for children if the given parent state has been animated. + */ + stopRecursion = (state, delta) => { + return delta != null && (delta.x != 0 || delta.y != 0); + }; -/** - * Function: getOriginForCell - * - * Returns the top, left corner of the given cell. TODO: Improve performance - * by using caching inside this method as the result per cell never changes - * during the lifecycle of this object. - */ -getOriginForCell = (cell)=> -{ - var result = null; - - if (cell != null) - { - var parent = this.graph.getModel().getParent(cell); - var geo = this.graph.getCellGeometry(cell); - result = this.getOriginForCell(parent); - - // TODO: Handle offsets - if (geo != null) - { - if (geo.relative) - { - var pgeo = this.graph.getCellGeometry(parent); - - if (pgeo != null) - { - result.x += geo.x * pgeo.width; - result.y += geo.y * pgeo.height; + /** + * Function: getDelta + * + * Returns the vector between the current rendered state and the future + * location of the state after the display will be updated. + */ + getDelta = (state) => { + var origin = this.getOriginForCell(state.cell); + var translate = this.graph.getView().getTranslate(); + var scale = this.graph.getView().getScale(); + var x = state.x / scale - translate.x; + var y = state.y / scale - translate.y; + + return new mxPoint((origin.x - x) * scale, (origin.y - y) * scale); + }; + + /** + * Function: getOriginForCell + * + * Returns the top, left corner of the given cell. TODO: Improve performance + * by using caching inside this method as the result per cell never changes + * during the lifecycle of this object. + */ + getOriginForCell = (cell) => { + var result = null; + + if (cell != null) { + var parent = this.graph.getModel().getParent(cell); + var geo = this.graph.getCellGeometry(cell); + result = this.getOriginForCell(parent); + + // TODO: Handle offsets + if (geo != null) { + if (geo.relative) { + var pgeo = this.graph.getCellGeometry(parent); + + if (pgeo != null) { + result.x += geo.x * pgeo.width; + result.y += geo.y * pgeo.height; + } + } else { + result.x += geo.x; + result.y += geo.y; } } - else - { - result.x += geo.x; - result.y += geo.y; - } } - } - - if (result == null) - { - var t = this.graph.view.getTranslate(); - result = new mxPoint(-t.x, -t.y); - } - - return result; -}; + + if (result == null) { + var t = this.graph.view.getTranslate(); + result = new mxPoint(-t.x, -t.y); + } + + return result; + }; +} + +export default mxMorphing; diff --git a/src/js/util/mxMouseEvent.js b/src/js/util/mxMouseEvent.js index fe96266b1..8b5112e46 100644 --- a/src/js/util/mxMouseEvent.js +++ b/src/js/util/mxMouseEvent.js @@ -2,237 +2,226 @@ * Copyright (c) 2006-2015, JGraph Ltd * Copyright (c) 2006-2015, Gaudenz Alder */ -/** - * Class: mxMouseEvent - * - * Base class for all mouse events in mxGraph. A listener for this event should - * implement the following methods: - * - * (code) - * graph.addMouseListener( - * { - * mouseDown: (sender, evt)=> - * { - * mxLog.debug('mouseDown'); - * }, - * mouseMove: (sender, evt)=> - * { - * mxLog.debug('mouseMove'); - * }, - * mouseUp: (sender, evt)=> - * { - * mxLog.debug('mouseUp'); - * } - * }); - * (end) - * - * Constructor: mxMouseEvent - * - * Constructs a new event object for the given arguments. - * - * Parameters: - * - * evt - Native mouse event. - * state - Optional under the mouse. - * - */ -function mxMouseEvent(evt, state) -{ - this.evt = evt; - this.state = state; - this.sourceState = state; -}; -/** - * Variable: consumed - * - * Holds the consumed state of this event. - */ -consumed = false; +class mxMouseEvent { + /** + * Variable: consumed + * + * Holds the consumed state of this event. + */ + consumed = false; -/** - * Variable: evt - * - * Holds the inner event object. - */ -evt = null; + /** + * Variable: evt + * + * Holds the inner event object. + */ + evt = null; -/** - * Variable: graphX - * - * Holds the x-coordinate of the event in the graph. This value is set in - * . - */ -graphX = null; + /** + * Variable: graphX + * + * Holds the x-coordinate of the event in the graph. This value is set in + * . + */ + graphX = null; -/** - * Variable: graphY - * - * Holds the y-coordinate of the event in the graph. This value is set in - * . - */ -graphY = null; + /** + * Variable: graphY + * + * Holds the y-coordinate of the event in the graph. This value is set in + * . + */ + graphY = null; -/** - * Variable: state - * - * Holds the optional associated with this event. - */ -state = null; + /** + * Variable: state + * + * Holds the optional associated with this event. + */ + state = null; -/** - * Variable: sourceState - * - * Holds the that was passed to the constructor. This can be - * different from depending on the result of . - */ -sourceState = null; + /** + * Variable: sourceState + * + * Holds the that was passed to the constructor. This can be + * different from depending on the result of . + */ + sourceState = null; -/** - * Function: getEvent - * - * Returns . - */ -getEvent = ()=> -{ - return this.evt; -}; + /** + * Class: mxMouseEvent + * + * Base class for all mouse events in mxGraph. A listener for this event should + * implement the following methods: + * + * (code) + * graph.addMouseListener( + * { + * mouseDown: (sender, evt)=> + * { + * mxLog.debug('mouseDown'); + * }, + * mouseMove: (sender, evt)=> + * { + * mxLog.debug('mouseMove'); + * }, + * mouseUp: (sender, evt)=> + * { + * mxLog.debug('mouseUp'); + * } + * }); + * (end) + * + * Constructor: mxMouseEvent + * + * Constructs a new event object for the given arguments. + * + * Parameters: + * + * evt - Native mouse event. + * state - Optional under the mouse. + * + */ + constructor(evt, state) { + this.evt = evt; + this.state = state; + this.sourceState = state; + }; -/** - * Function: getSource - * - * Returns the target DOM element using for . - */ -getSource = ()=> -{ - return mxEvent.getSource(this.evt); -}; + /** + * Function: getEvent + * + * Returns . + */ + getEvent = () => { + return this.evt; + }; -/** - * Function: isSource - * - * Returns true if the given is the source of . - */ -isSource = (shape)=> -{ - if (shape != null) - { - return mxUtils.isAncestorNode(shape.node, this.getSource()); - } + /** + * Function: getSource + * + * Returns the target DOM element using for . + */ + getSource = () => { + return mxEvent.getSource(this.evt); + }; - return false; -}; + /** + * Function: isSource + * + * Returns true if the given is the source of . + */ + isSource = (shape) => { + if (shape != null) { + return mxUtils.isAncestorNode(shape.node, this.getSource()); + } -/** - * Function: getX - * - * Returns . - */ -getX = ()=> -{ - return mxEvent.getClientX(this.getEvent()); -}; + return false; + }; -/** - * Function: getY - * - * Returns . - */ -getY = ()=> -{ - return mxEvent.getClientY(this.getEvent()); -}; + /** + * Function: getX + * + * Returns . + */ + getX = () => { + return mxEvent.getClientX(this.getEvent()); + }; -/** - * Function: getGraphX - * - * Returns . - */ -getGraphX = ()=> -{ - return this.graphX; -}; + /** + * Function: getY + * + * Returns . + */ + getY = () => { + return mxEvent.getClientY(this.getEvent()); + }; -/** - * Function: getGraphY - * - * Returns . - */ -getGraphY = ()=> -{ - return this.graphY; -}; + /** + * Function: getGraphX + * + * Returns . + */ + getGraphX = () => { + return this.graphX; + }; -/** - * Function: getState - * - * Returns . - */ -getState = ()=> -{ - return this.state; -}; + /** + * Function: getGraphY + * + * Returns . + */ + getGraphY = () => { + return this.graphY; + }; -/** - * Function: getCell - * - * Returns the in is not null. - */ -getCell = ()=> -{ - var state = this.getState(); + /** + * Function: getState + * + * Returns . + */ + getState = () => { + return this.state; + }; - if (state != null) - { - return state.cell; - } + /** + * Function: getCell + * + * Returns the in is not null. + */ + getCell = () => { + var state = this.getState(); - return null; -}; + if (state != null) { + return state.cell; + } -/** - * Function: isPopupTrigger - * - * Returns true if the event is a popup trigger. - */ -isPopupTrigger = ()=> -{ - return mxEvent.isPopupTrigger(this.getEvent()); -}; + return null; + }; -/** - * Function: isConsumed - * - * Returns . - */ -isConsumed = ()=> -{ - return this.consumed; -}; + /** + * Function: isPopupTrigger + * + * Returns true if the event is a popup trigger. + */ + isPopupTrigger = () => { + return mxEvent.isPopupTrigger(this.getEvent()); + }; -/** - * Function: consume - * - * Sets to true and invokes preventDefault on the native event - * if such a method is defined. This is used mainly to avoid the cursor from - * being changed to a text cursor in Webkit. You can use the preventDefault - * flag to disable this functionality. - * - * Parameters: - * - * preventDefault - Specifies if the native event should be canceled. Default - * is true. - */ -consume = (preventDefault)=> -{ - preventDefault = (preventDefault != null) ? preventDefault : - (this.evt.touches != null || mxEvent.isMouseEvent(this.evt)); + /** + * Function: isConsumed + * + * Returns . + */ + isConsumed = () => { + return this.consumed; + }; - if (preventDefault && this.evt.preventDefault) - { - this.evt.preventDefault(); - } + /** + * Function: consume + * + * Sets to true and invokes preventDefault on the native event + * if such a method is defined. This is used mainly to avoid the cursor from + * being changed to a text cursor in Webkit. You can use the preventDefault + * flag to disable this functionality. + * + * Parameters: + * + * preventDefault - Specifies if the native event should be canceled. Default + * is true. + */ + consume = (preventDefault) => { + preventDefault = (preventDefault != null) ? preventDefault : + (this.evt.touches != null || mxEvent.isMouseEvent(this.evt)); - // Sets local consumed state - this.consumed = true; -}; + if (preventDefault && this.evt.preventDefault) { + this.evt.preventDefault(); + } + + // Sets local consumed state + this.consumed = true; + }; +} + +export default mxMouseEvent; diff --git a/src/js/util/mxObjectIdentity.js b/src/js/util/mxObjectIdentity.js index 6deb253dc..f20d9cf23 100644 --- a/src/js/util/mxObjectIdentity.js +++ b/src/js/util/mxObjectIdentity.js @@ -2,19 +2,19 @@ * Copyright (c) 2006-2015, JGraph Ltd * Copyright (c) 2006-2015, Gaudenz Alder */ -var mxObjectIdentity = -{ + +var mxObjectIdentity = { /** * Class: mxObjectIdentity - * + * * Identity for JavaScript objects and functions. This is implemented using * a simple incrementing counter which is stored in each object under * . - * + * * The identity for an object does not change during its lifecycle. - * + * * Variable: FIELD_NAME - * + * * Name of the field to be used to store the object ID. Default is * mxObjectId. */ @@ -22,51 +22,44 @@ var mxObjectIdentity = /** * Variable: counter - * + * * Current counter. */ counter: 0, /** * Function: get - * + * * Returns the ID for the given object or function or null if no object * is specified. */ - get: (obj)=> - { - if (obj != null) - { - if (obj[mxObjectIdentity.FIELD_NAME] == null) - { - if (typeof obj === 'object') - { + get: (obj) => { + if (obj != null) { + if (obj[mxObjectIdentity.FIELD_NAME] == null) { + if (typeof obj === 'object') { var ctor = mxUtils.getFunctionName(obj.constructor); obj[mxObjectIdentity.FIELD_NAME] = ctor + '#' + mxObjectIdentity.counter++; - } - else if (typeof obj === 'function') - { + } else if (typeof obj === 'function') { obj[mxObjectIdentity.FIELD_NAME] = 'Function#' + mxObjectIdentity.counter++; } } - + return obj[mxObjectIdentity.FIELD_NAME]; } - + return null; }, /** * Function: clear - * + * * Deletes the ID from the given object or function. */ - clear: (obj)=> - { - if (typeof(obj) === 'object' || typeof obj === 'function') - { + clear: (obj) => { + if (typeof (obj) === 'object' || typeof obj === 'function') { delete obj[mxObjectIdentity.FIELD_NAME]; } } - }; + +export default mxObjectIdentity; diff --git a/src/js/util/mxPanningManager.js b/src/js/util/mxPanningManager.js index 50026c67f..473c9094e 100644 --- a/src/js/util/mxPanningManager.js +++ b/src/js/util/mxPanningManager.js @@ -2,264 +2,221 @@ * Copyright (c) 2006-2015, JGraph Ltd * Copyright (c) 2006-2015, Gaudenz Alder */ -/** - * Class: mxPanningManager - * - * Implements a handler for panning. - */ -function mxPanningManager(graph) -{ - this.thread = null; - this.active = false; - this.tdx = 0; - this.tdy = 0; - this.t0x = 0; - this.t0y = 0; - this.dx = 0; - this.dy = 0; - this.scrollbars = false; - this.scrollLeft = 0; - this.scrollTop = 0; - - this.mouseListener = - { - mouseDown: (sender, me)=> { }, - mouseMove: (sender, me)=> { }, - mouseUp: mxUtils.bind(this, (sender, me)=> - { - if (this.active) - { - this.stop(); - } - }) - }; - - graph.addMouseListener(this.mouseListener); - - this.mouseUpListener = mxUtils.bind(this, ()=> - { - if (this.active) - { - this.stop(); - } - }); - - // Stops scrolling on every mouseup anywhere in the document - mxEvent.addListener(document, 'mouseup', this.mouseUpListener); - - var createThread = mxUtils.bind(this, ()=> - { - this.scrollbars = mxUtils.hasScrollbars(graph.container); - this.scrollLeft = graph.container.scrollLeft; - this.scrollTop = graph.container.scrollTop; - - return window.setInterval(mxUtils.bind(this, ()=> - { - this.tdx -= this.dx; - this.tdy -= this.dy; - if (this.scrollbars) - { - var left = -graph.container.scrollLeft - Math.ceil(this.dx); - var top = -graph.container.scrollTop - Math.ceil(this.dy); - graph.panGraph(left, top); - graph.panDx = this.scrollLeft - graph.container.scrollLeft; - graph.panDy = this.scrollTop - graph.container.scrollTop; - graph.fireEvent(new mxEventObject(mxEvent.PAN)); - // TODO: Implement graph.autoExtend +class mxPanningManager { + /** + * Variable: damper + * + * Damper value for the panning. Default is 1/6. + */ + damper = 1 / 6; + + /** + * Variable: delay + * + * Delay in milliseconds for the panning. Default is 10. + */ + delay = 10; + + /** + * Variable: handleMouseOut + * + * Specifies if mouse events outside of the component should be handled. Default is true. + */ + handleMouseOut = true; + + /** + * Variable: border + * + * Border to handle automatic panning inside the component. Default is 0 (disabled). + */ + border = 0; + + /** + * Class: mxPanningManager + * + * Implements a handler for panning. + */ + constructor(graph) { + this.thread = null; + this.active = false; + this.tdx = 0; + this.tdy = 0; + this.t0x = 0; + this.t0y = 0; + this.dx = 0; + this.dy = 0; + this.scrollbars = false; + this.scrollLeft = 0; + this.scrollTop = 0; + + this.mouseListener = + { + mouseDown: (sender, me) => { + }, + mouseMove: (sender, me) => { + }, + mouseUp: mxUtils.bind(this, (sender, me) => { + if (this.active) { + this.stop(); + } + }) + }; + + graph.addMouseListener(this.mouseListener); + + this.mouseUpListener = mxUtils.bind(this, () => { + if (this.active) { + this.stop(); } - else - { - graph.panGraph(this.getDx(), this.getDy()); - } - }), this.delay); - }); - - this.isActive = ()=> - { - return active; - }; - - this.getDx = ()=> - { - return Math.round(this.tdx); - }; - - this.getDy = ()=> - { - return Math.round(this.tdy); - }; - - this.start = ()=> - { - this.t0x = graph.view.translate.x; - this.t0y = graph.view.translate.y; - this.active = true; - }; - - this.panTo = (x, y, w, h)=> - { - if (!this.active) - { - this.start(); - } - + }); + + // Stops scrolling on every mouseup anywhere in the document + mxEvent.addListener(document, 'mouseup', this.mouseUpListener); + + var createThread = mxUtils.bind(this, () => { + this.scrollbars = mxUtils.hasScrollbars(graph.container); this.scrollLeft = graph.container.scrollLeft; this.scrollTop = graph.container.scrollTop; - - w = (w != null) ? w : 0; - h = (h != null) ? h : 0; - - var c = graph.container; - this.dx = x + w - c.scrollLeft - c.clientWidth; - - if (this.dx < 0 && Math.abs(this.dx) < this.border) - { - this.dx = this.border + this.dx; - } - else if (this.handleMouseOut) - { - this.dx = Math.max(this.dx, 0); - } - else - { - this.dx = 0; - } - - if (this.dx == 0) - { - this.dx = x - c.scrollLeft; - - if (this.dx > 0 && this.dx < this.border) - { - this.dx = this.dx - this.border; + + return window.setInterval(mxUtils.bind(this, () => { + this.tdx -= this.dx; + this.tdy -= this.dy; + + if (this.scrollbars) { + var left = -graph.container.scrollLeft - Math.ceil(this.dx); + var top = -graph.container.scrollTop - Math.ceil(this.dy); + graph.panGraph(left, top); + graph.panDx = this.scrollLeft - graph.container.scrollLeft; + graph.panDy = this.scrollTop - graph.container.scrollTop; + graph.fireEvent(new mxEventObject(mxEvent.PAN)); + // TODO: Implement graph.autoExtend + } else { + graph.panGraph(this.getDx(), this.getDy()); + } + }), this.delay); + }); + + this.isActive = () => { + return active; + }; + + this.getDx = () => { + return Math.round(this.tdx); + }; + + this.getDy = () => { + return Math.round(this.tdy); + }; + + this.start = () => { + this.t0x = graph.view.translate.x; + this.t0y = graph.view.translate.y; + this.active = true; + }; + + this.panTo = (x, y, w, h) => { + if (!this.active) { + this.start(); } - else if (this.handleMouseOut) - { - this.dx = Math.min(0, this.dx); - } - else - { + + this.scrollLeft = graph.container.scrollLeft; + this.scrollTop = graph.container.scrollTop; + + w = (w != null) ? w : 0; + h = (h != null) ? h : 0; + + var c = graph.container; + this.dx = x + w - c.scrollLeft - c.clientWidth; + + if (this.dx < 0 && Math.abs(this.dx) < this.border) { + this.dx = this.border + this.dx; + } else if (this.handleMouseOut) { + this.dx = Math.max(this.dx, 0); + } else { this.dx = 0; } - } - - this.dy = y + h - c.scrollTop - c.clientHeight; - if (this.dy < 0 && Math.abs(this.dy) < this.border) - { - this.dy = this.border + this.dy; - } - else if (this.handleMouseOut) - { - this.dy = Math.max(this.dy, 0); - } - else - { - this.dy = 0; - } - - if (this.dy == 0) - { - this.dy = y - c.scrollTop; - - if (this.dy > 0 && this.dy < this.border) - { - this.dy = this.dy - this.border; + if (this.dx == 0) { + this.dx = x - c.scrollLeft; + + if (this.dx > 0 && this.dx < this.border) { + this.dx = this.dx - this.border; + } else if (this.handleMouseOut) { + this.dx = Math.min(0, this.dx); + } else { + this.dx = 0; + } } - else if (this.handleMouseOut) - { - this.dy = Math.min(0, this.dy); - } - else - { + + this.dy = y + h - c.scrollTop - c.clientHeight; + + if (this.dy < 0 && Math.abs(this.dy) < this.border) { + this.dy = this.border + this.dy; + } else if (this.handleMouseOut) { + this.dy = Math.max(this.dy, 0); + } else { this.dy = 0; } - } - - if (this.dx != 0 || this.dy != 0) - { - this.dx *= this.damper; - this.dy *= this.damper; - - if (this.thread == null) - { - this.thread = createThread(); + + if (this.dy == 0) { + this.dy = y - c.scrollTop; + + if (this.dy > 0 && this.dy < this.border) { + this.dy = this.dy - this.border; + } else if (this.handleMouseOut) { + this.dy = Math.min(0, this.dy); + } else { + this.dy = 0; + } } - } - else if (this.thread != null) - { - window.clearInterval(this.thread); - this.thread = null; - } - }; - - this.stop = ()=> - { - if (this.active) - { - this.active = false; - - if (this.thread != null) - { + + if (this.dx != 0 || this.dy != 0) { + this.dx *= this.damper; + this.dy *= this.damper; + + if (this.thread == null) { + this.thread = createThread(); + } + } else if (this.thread != null) { window.clearInterval(this.thread); this.thread = null; + } + }; + + this.stop = () => { + if (this.active) { + this.active = false; + + if (this.thread != null) { + window.clearInterval(this.thread); + this.thread = null; } - - this.tdx = 0; - this.tdy = 0; - - if (!this.scrollbars) - { - var px = graph.panDx; - var py = graph.panDy; - - if (px != 0 || py != 0) - { + + this.tdx = 0; + this.tdy = 0; + + if (!this.scrollbars) { + var px = graph.panDx; + var py = graph.panDy; + + if (px != 0 || py != 0) { graph.panGraph(0, 0); graph.view.setTranslate(this.t0x + px / graph.view.scale, this.t0y + py / graph.view.scale); } + } else { + graph.panDx = 0; + graph.panDy = 0; + graph.fireEvent(new mxEventObject(mxEvent.PAN)); + } } - else - { - graph.panDx = 0; - graph.panDy = 0; - graph.fireEvent(new mxEventObject(mxEvent.PAN)); - } - } + }; + + this.destroy = () => { + graph.removeMouseListener(this.mouseListener); + mxEvent.removeListener(document, 'mouseup', this.mouseUpListener); + }; }; - - this.destroy = ()=> - { - graph.removeMouseListener(this.mouseListener); - mxEvent.removeListener(document, 'mouseup', this.mouseUpListener); - }; -}; +} -/** - * Variable: damper - * - * Damper value for the panning. Default is 1/6. - */ -damper = 1/6; - -/** - * Variable: delay - * - * Delay in milliseconds for the panning. Default is 10. - */ -delay = 10; - -/** - * Variable: handleMouseOut - * - * Specifies if mouse events outside of the component should be handled. Default is true. - */ -handleMouseOut = true; - -/** - * Variable: border - * - * Border to handle automatic panning inside the component. Default is 0 (disabled). - */ -border = 0; +export default mxPanningManager; diff --git a/src/js/util/mxPoint.js b/src/js/util/mxPoint.js index fd6fca2e9..86b9ee8df 100644 --- a/src/js/util/mxPoint.js +++ b/src/js/util/mxPoint.js @@ -2,53 +2,57 @@ * Copyright (c) 2006-2015, JGraph Ltd * Copyright (c) 2006-2015, Gaudenz Alder */ -/** - * Class: mxPoint - * - * Implements a 2-dimensional vector with double precision coordinates. - * - * Constructor: mxPoint - * - * Constructs a new point for the optional x and y coordinates. If no - * coordinates are given, then the default values for and are used. - */ -function mxPoint(x, y) -{ - this.x = (x != null) ? x : 0; - this.y = (y != null) ? y : 0; -}; -/** - * Variable: x - * - * Holds the x-coordinate of the point. Default is 0. - */ -x = null; +import mxUtils from "../util/mxUtils"; -/** - * Variable: y - * - * Holds the y-coordinate of the point. Default is 0. - */ -y = null; +class mxPoint { + /** + * Class: mxPoint + * + * Implements a 2-dimensional vector with double precision coordinates. + * + * Constructor: mxPoint + * + * Constructs a new point for the optional x and y coordinates. If no + * coordinates are given, then the default values for and are used. + */ + constructor(x, y) { + this.x = (x != null) ? x : 0; + this.y = (y != null) ? y : 0; + }; -/** - * Function: equals - * - * Returns true if the given object equals this point. - */ -equals = (obj)=> -{ - return obj != null && obj.x == this.x && obj.y == this.y; -}; + /** + * Variable: x + * + * Holds the x-coordinate of the point. Default is 0. + */ + x = null; -/** - * Function: clone - * - * Returns a clone of this . - */ -clone = ()=> -{ - // Handles subclasses as well - return mxUtils.clone(this); -}; + /** + * Variable: y + * + * Holds the y-coordinate of the point. Default is 0. + */ + y = null; + + /** + * Function: equals + * + * Returns true if the given object equals this point. + */ + equals = (obj) => { + return obj != null && obj.x == this.x && obj.y == this.y; + }; + + /** + * Function: clone + * + * Returns a clone of this . + */ + clone = () => { + // Handles subclasses as well + return mxUtils.clone(this); + }; +} + +export default mxPoint; diff --git a/src/js/util/mxPopupMenu.js b/src/js/util/mxPopupMenu.js index be8d11152..07724bebe 100644 --- a/src/js/util/mxPopupMenu.js +++ b/src/js/util/mxPopupMenu.js @@ -2,607 +2,551 @@ * Copyright (c) 2006-2015, JGraph Ltd * Copyright (c) 2006-2015, Gaudenz Alder */ -/** - * Class: mxPopupMenu - * - * Basic popup menu. To add a vertical scrollbar to a given submenu, the - * following code can be used. - * - * (code) - * var mxPopupMenuShowMenu = showMenu; - * showMenu = ()=> - * { - * mxPopupMenuShowMenu.apply(this, arguments); - * - * this.div.style.overflowY = 'auto'; - * this.div.style.overflowX = 'hidden'; - * this.div.style.maxHeight = '160px'; - * }; - * (end) - * - * Constructor: mxPopupMenu - * - * Constructs a popupmenu. - * - * Event: mxEvent.SHOW - * - * Fires after the menu has been shown in . - */ -function mxPopupMenu(factoryMethod) -{ - this.factoryMethod = factoryMethod; +import mxEventSource from "./mxEventSource"; +import mxUtils from "./mxUtils"; +import mxEventObject from "./mxEventObject"; - if (factoryMethod != null) - { - this.init(); - } -}; +class mxPopupMenu extends mxEventSource { + /** + * Class: mxPopupMenu + * + * Basic popup menu. To add a vertical scrollbar to a given submenu, the + * following code can be used. + * + * (code) + * var mxPopupMenuShowMenu = showMenu; + * showMenu = ()=> + * { + * mxPopupMenuShowMenu.apply(this, arguments); + * + * this.div.style.overflowY = 'auto'; + * this.div.style.overflowX = 'hidden'; + * this.div.style.maxHeight = '160px'; + * }; + * (end) + * + * Constructor: mxPopupMenu + * + * Constructs a popupmenu. + * + * Event: mxEvent.SHOW + * + * Fires after the menu has been shown in . + */ + constructor(factoryMethod) { + this.factoryMethod = factoryMethod; -/** - * Extends mxEventSource. - */ -mxPopupMenu.prototype = new mxEventSource(); -constructor = mxPopupMenu; + if (factoryMethod != null) { + this.init(); + } + }; -/** - * Variable: submenuImage - * - * URL of the image to be used for the submenu icon. - */ -submenuImage = mxClient.imageBasePath + '/submenu.gif'; + /** + * Variable: submenuImage + * + * URL of the image to be used for the submenu icon. + */ + submenuImage = mxClient.imageBasePath + '/submenu.gif'; -/** - * Variable: zIndex - * - * Specifies the zIndex for the popupmenu and its shadow. Default is 10006. - */ -zIndex = 10006; + /** + * Variable: zIndex + * + * Specifies the zIndex for the popupmenu and its shadow. Default is 10006. + */ + zIndex = 10006; -/** - * Variable: factoryMethod - * - * Function that is used to create the popup menu. The function takes the - * current panning handler, the under the mouse and the mouse - * event that triggered the call as arguments. - */ -factoryMethod = null; + /** + * Variable: factoryMethod + * + * Function that is used to create the popup menu. The function takes the + * current panning handler, the under the mouse and the mouse + * event that triggered the call as arguments. + */ + factoryMethod = null; -/** - * Variable: useLeftButtonForPopup - * - * Specifies if popupmenus should be activated by clicking the left mouse - * button. Default is false. - */ -useLeftButtonForPopup = false; + /** + * Variable: useLeftButtonForPopup + * + * Specifies if popupmenus should be activated by clicking the left mouse + * button. Default is false. + */ + useLeftButtonForPopup = false; -/** - * Variable: enabled - * - * Specifies if events are handled. Default is true. - */ -enabled = true; + /** + * Variable: enabled + * + * Specifies if events are handled. Default is true. + */ + enabled = true; -/** - * Variable: itemCount - * - * Contains the number of times has been called for a new menu. - */ -itemCount = 0; + /** + * Variable: itemCount + * + * Contains the number of times has been called for a new menu. + */ + itemCount = 0; -/** - * Variable: autoExpand - * - * Specifies if submenus should be expanded on mouseover. Default is false. - */ -autoExpand = false; + /** + * Variable: autoExpand + * + * Specifies if submenus should be expanded on mouseover. Default is false. + */ + autoExpand = false; -/** - * Variable: smartSeparators - * - * Specifies if separators should only be added if a menu item follows them. - * Default is false. - */ -smartSeparators = false; + /** + * Variable: smartSeparators + * + * Specifies if separators should only be added if a menu item follows them. + * Default is false. + */ + smartSeparators = false; -/** - * Variable: labels - * - * Specifies if any labels should be visible. Default is true. - */ -labels = true; + /** + * Variable: labels + * + * Specifies if any labels should be visible. Default is true. + */ + labels = true; -/** - * Function: init - * - * Initializes the shapes required for this vertex handler. - */ -init = ()=> -{ - // Adds the inner table - this.table = document.createElement('table'); - this.table.className = 'mxPopupMenu'; + /** + * Function: init + * + * Initializes the shapes required for this vertex handler. + */ + init = () => { + // Adds the inner table + this.table = document.createElement('table'); + this.table.className = 'mxPopupMenu'; - this.tbody = document.createElement('tbody'); - this.table.appendChild(this.tbody); + this.tbody = document.createElement('tbody'); + this.table.appendChild(this.tbody); - // Adds the outer div - this.div = document.createElement('div'); - this.div.className = 'mxPopupMenu'; - this.div.style.display = 'inline'; - this.div.style.zIndex = this.zIndex; - this.div.appendChild(this.table); + // Adds the outer div + this.div = document.createElement('div'); + this.div.className = 'mxPopupMenu'; + this.div.style.display = 'inline'; + this.div.style.zIndex = this.zIndex; + this.div.appendChild(this.table); - // Disables the context menu on the outer div - mxEvent.disableContextMenu(this.div); -}; + // Disables the context menu on the outer div + mxEvent.disableContextMenu(this.div); + }; -/** - * Function: isEnabled - * - * Returns true if events are handled. This implementation - * returns . - */ -isEnabled = ()=> -{ - return this.enabled; -}; + /** + * Function: isEnabled + * + * Returns true if events are handled. This implementation + * returns . + */ + isEnabled = () => { + return this.enabled; + }; -/** - * Function: setEnabled - * - * Enables or disables event handling. This implementation - * updates . - */ -setEnabled = (enabled)=> -{ - this.enabled = enabled; -}; + /** + * Function: setEnabled + * + * Enables or disables event handling. This implementation + * updates . + */ + setEnabled = (enabled) => { + this.enabled = enabled; + }; -/** - * Function: isPopupTrigger - * - * Returns true if the given event is a popupmenu trigger for the optional - * given cell. - * - * Parameters: - * - * me - that represents the mouse event. - */ -isPopupTrigger = (me)=> -{ - return me.isPopupTrigger() || (this.useLeftButtonForPopup && mxEvent.isLeftMouseButton(me.getEvent())); -}; + /** + * Function: isPopupTrigger + * + * Returns true if the given event is a popupmenu trigger for the optional + * given cell. + * + * Parameters: + * + * me - that represents the mouse event. + */ + isPopupTrigger = (me) => { + return me.isPopupTrigger() || (this.useLeftButtonForPopup && mxEvent.isLeftMouseButton(me.getEvent())); + }; -/** - * Function: addItem - * - * Adds the given item to the given parent item. If no parent item is specified - * then the item is added to the top-level menu. The return value may be used - * as the parent argument, ie. as a submenu item. The return value is the table - * row that represents the item. - * - * Paramters: - * - * title - String that represents the title of the menu item. - * image - Optional URL for the image icon. - * funct - Function associated that takes a mouseup or touchend event. - * parent - Optional item returned by . - * iconCls - Optional string that represents the CSS class for the image icon. - * IconsCls is ignored if image is given. - * enabled - Optional boolean indicating if the item is enabled. Default is true. - * active - Optional boolean indicating if the menu should implement any event handling. - * Default is true. - * noHover - Optional boolean to disable hover state. - */ -addItem = (title, image, funct, parent, iconCls, enabled, active, noHover)=> -{ - parent = parent || this; - this.itemCount++; + /** + * Function: addItem + * + * Adds the given item to the given parent item. If no parent item is specified + * then the item is added to the top-level menu. The return value may be used + * as the parent argument, ie. as a submenu item. The return value is the table + * row that represents the item. + * + * Paramters: + * + * title - String that represents the title of the menu item. + * image - Optional URL for the image icon. + * funct - Function associated that takes a mouseup or touchend event. + * parent - Optional item returned by . + * iconCls - Optional string that represents the CSS class for the image icon. + * IconsCls is ignored if image is given. + * enabled - Optional boolean indicating if the item is enabled. Default is true. + * active - Optional boolean indicating if the menu should implement any event handling. + * Default is true. + * noHover - Optional boolean to disable hover state. + */ + addItem = (title, image, funct, parent, iconCls, enabled, active, noHover) => { + parent = parent || this; + this.itemCount++; - // Smart separators only added if element contains items - if (parent.willAddSeparator) - { - if (parent.containsItems) - { - this.addSeparator(parent, true); + // Smart separators only added if element contains items + if (parent.willAddSeparator) { + if (parent.containsItems) { + this.addSeparator(parent, true); + } + + parent.willAddSeparator = false; } - parent.willAddSeparator = false; - } - - parent.containsItems = true; - var tr = document.createElement('tr'); - tr.className = 'mxPopupMenuItem'; - var col1 = document.createElement('td'); - col1.className = 'mxPopupMenuIcon'; - - // Adds the given image into the first column - if (image != null) - { - var img = document.createElement('img'); - img.src = image; - col1.appendChild(img); - } - else if (iconCls != null) - { - var div = document.createElement('div'); - div.className = iconCls; - col1.appendChild(div); - } - - tr.appendChild(col1); - - if (this.labels) - { - var col2 = document.createElement('td'); - col2.className = 'mxPopupMenuItem' + - ((enabled != null && !enabled) ? ' mxDisabled' : ''); - - mxUtils.write(col2, title); - col2.align = 'left'; - tr.appendChild(col2); - - var col3 = document.createElement('td'); - col3.className = 'mxPopupMenuItem' + - ((enabled != null && !enabled) ? ' mxDisabled' : ''); - col3.style.paddingRight = '6px'; - col3.style.textAlign = 'right'; - - tr.appendChild(col3); - - if (parent.div == null) - { - this.createSubmenu(parent); - } - } - - parent.tbody.appendChild(tr); - - if (active != false && enabled != false) - { - var currentSelection = null; - - mxEvent.addGestureListeners(tr, - mxUtils.bind(this, (evt)=> - { - this.eventReceiver = tr; - - if (parent.activeRow != tr && parent.activeRow != parent) - { - if (parent.activeRow != null && parent.activeRow.div.parentNode != null) - { - this.hideSubmenu(parent); - } - - if (tr.div != null) - { - this.showSubmenu(parent, tr); - parent.activeRow = tr; - } - } - - mxEvent.consume(evt); - }), - mxUtils.bind(this, (evt)=> - { - if (parent.activeRow != tr && parent.activeRow != parent) - { - if (parent.activeRow != null && parent.activeRow.div.parentNode != null) - { - this.hideSubmenu(parent); - } - - if (this.autoExpand && tr.div != null) - { - this.showSubmenu(parent, tr); - parent.activeRow = tr; - } - } - - // Sets hover style because TR in IE doesn't have hover - if (!noHover) - { - tr.className = 'mxPopupMenuItemHover'; - } - }), - mxUtils.bind(this, (evt)=> - { - // EventReceiver avoids clicks on a submenu item - // which has just been shown in the mousedown - if (this.eventReceiver == tr) - { - if (parent.activeRow != tr) - { - this.hideMenu(); - } - - // Workaround for lost current selection in page because of focus in IE - if (currentSelection != null) - { - // Workaround for "unspecified error" in IE8 standards - try - { - currentSelection.select(); - } - catch (e) - { - // ignore - } - - currentSelection = null; - } - - if (funct != null) - { - funct(evt); - } - } - - this.eventReceiver = null; - mxEvent.consume(evt); - }) - ); - - // Resets hover style because TR in IE doesn't have hover - if (!noHover) - { - mxEvent.addListener(tr, 'mouseout', - mxUtils.bind(this, (evt)=> - { - tr.className = 'mxPopupMenuItem'; - }) - ); - } - } - - return tr; -}; - -/** - * Adds a checkmark to the given menuitem. - */ -addCheckmark = (item, img)=> -{ - var td = item.firstChild.nextSibling; - td.style.backgroundImage = 'url(\'' + img + '\')'; - td.style.backgroundRepeat = 'no-repeat'; - td.style.backgroundPosition = '2px 50%'; -}; - -/** - * Function: createSubmenu - * - * Creates the nodes required to add submenu items inside the given parent - * item. This is called in if a parent item is used for the first - * time. This adds various DOM nodes and a to the parent. - * - * Parameters: - * - * parent - An item returned by . - */ -createSubmenu = (parent)=> -{ - parent.table = document.createElement('table'); - parent.table.className = 'mxPopupMenu'; - - parent.tbody = document.createElement('tbody'); - parent.table.appendChild(parent.tbody); - - parent.div = document.createElement('div'); - parent.div.className = 'mxPopupMenu'; - - parent.div.style.position = 'absolute'; - parent.div.style.display = 'inline'; - parent.div.style.zIndex = this.zIndex; - - parent.div.appendChild(parent.table); - - var img = document.createElement('img'); - img.setAttribute('src', this.submenuImage); - - // Last column of the submenu item in the parent menu - td = parent.firstChild.nextSibling.nextSibling; - td.appendChild(img); -}; - -/** - * Function: showSubmenu - * - * Shows the submenu inside the given parent row. - */ -showSubmenu = (parent, row)=> -{ - if (row.div != null) - { - row.div.style.left = (parent.div.offsetLeft + - row.offsetLeft+row.offsetWidth - 1) + 'px'; - row.div.style.top = (parent.div.offsetTop+row.offsetTop) + 'px'; - document.body.appendChild(row.div); - - // Moves the submenu to the left side if there is no space - var left = parseInt(row.div.offsetLeft); - var width = parseInt(row.div.offsetWidth); - var offset = mxUtils.getDocumentScrollOrigin(document); - - var b = document.body; - var d = document.documentElement; - - var right = offset.x + (b.clientWidth || d.clientWidth); - - if (left + width > right) - { - row.div.style.left = Math.max(0, (parent.div.offsetLeft - width-6)) + 'px'; - } - - mxUtils.fit(row.div); - } -}; - -/** - * Function: addSeparator - * - * Adds a horizontal separator in the given parent item or the top-level menu - * if no parent is specified. - * - * Parameters: - * - * parent - Optional item returned by . - * force - Optional boolean to ignore . Default is false. - */ -addSeparator = (parent, force)=> -{ - parent = parent || this; - - if (this.smartSeparators && !force) - { - parent.willAddSeparator = true; - } - else if (parent.tbody != null) - { - parent.willAddSeparator = false; + parent.containsItems = true; var tr = document.createElement('tr'); - + tr.className = 'mxPopupMenuItem'; var col1 = document.createElement('td'); col1.className = 'mxPopupMenuIcon'; - col1.style.padding = '0 0 0 0px'; + + // Adds the given image into the first column + if (image != null) { + var img = document.createElement('img'); + img.src = image; + col1.appendChild(img); + } else if (iconCls != null) { + var div = document.createElement('div'); + div.className = iconCls; + col1.appendChild(div); + } tr.appendChild(col1); - var col2 = document.createElement('td'); - col2.style.padding = '0 0 0 0px'; - col2.setAttribute('colSpan', '2'); + if (this.labels) { + var col2 = document.createElement('td'); + col2.className = 'mxPopupMenuItem' + + ((enabled != null && !enabled) ? ' mxDisabled' : ''); - var hr = document.createElement('hr'); - hr.setAttribute('size', '1'); - col2.appendChild(hr); + mxUtils.write(col2, title); + col2.align = 'left'; + tr.appendChild(col2); - tr.appendChild(col2); + var col3 = document.createElement('td'); + col3.className = 'mxPopupMenuItem' + + ((enabled != null && !enabled) ? ' mxDisabled' : ''); + col3.style.paddingRight = '6px'; + col3.style.textAlign = 'right'; + + tr.appendChild(col3); + + if (parent.div == null) { + this.createSubmenu(parent); + } + } parent.tbody.appendChild(tr); - } -}; -/** - * Function: popup - * - * Shows the popup menu for the given event and cell. - * - * Example: - * - * (code) - * graph.panningHandler.popup = (x, y, cell, evt)=> - * { - * mxUtils.alert('Hello, World!'); - * } - * (end) - */ -popup = (x, y, cell, evt)=> -{ - if (this.div != null && this.tbody != null && this.factoryMethod != null) - { - this.div.style.left = x + 'px'; - this.div.style.top = y + 'px'; + if (active != false && enabled != false) { + var currentSelection = null; - // Removes all child nodes from the existing menu - while (this.tbody.firstChild != null) - { - mxEvent.release(this.tbody.firstChild); - this.tbody.removeChild(this.tbody.firstChild); + mxEvent.addGestureListeners(tr, + mxUtils.bind(this, (evt) => { + this.eventReceiver = tr; + + if (parent.activeRow != tr && parent.activeRow != parent) { + if (parent.activeRow != null && parent.activeRow.div.parentNode != null) { + this.hideSubmenu(parent); + } + + if (tr.div != null) { + this.showSubmenu(parent, tr); + parent.activeRow = tr; + } + } + + mxEvent.consume(evt); + }), + mxUtils.bind(this, (evt) => { + if (parent.activeRow != tr && parent.activeRow != parent) { + if (parent.activeRow != null && parent.activeRow.div.parentNode != null) { + this.hideSubmenu(parent); + } + + if (this.autoExpand && tr.div != null) { + this.showSubmenu(parent, tr); + parent.activeRow = tr; + } + } + + // Sets hover style because TR in IE doesn't have hover + if (!noHover) { + tr.className = 'mxPopupMenuItemHover'; + } + }), + mxUtils.bind(this, (evt) => { + // EventReceiver avoids clicks on a submenu item + // which has just been shown in the mousedown + if (this.eventReceiver == tr) { + if (parent.activeRow != tr) { + this.hideMenu(); + } + + // Workaround for lost current selection in page because of focus in IE + if (currentSelection != null) { + // Workaround for "unspecified error" in IE8 standards + try { + currentSelection.select(); + } catch (e) { + // ignore + } + + currentSelection = null; + } + + if (funct != null) { + funct(evt); + } + } + + this.eventReceiver = null; + mxEvent.consume(evt); + }) + ); + + // Resets hover style because TR in IE doesn't have hover + if (!noHover) { + mxEvent.addListener(tr, 'mouseout', + mxUtils.bind(this, (evt) => { + tr.className = 'mxPopupMenuItem'; + }) + ); + } } - this.itemCount = 0; - this.factoryMethod(this, cell, evt); + return tr; + }; - if (this.itemCount > 0) - { - this.showMenu(); - this.fireEvent(new mxEventObject(mxEvent.SHOW)); + /** + * Adds a checkmark to the given menuitem. + */ + addCheckmark = (item, img) => { + var td = item.firstChild.nextSibling; + td.style.backgroundImage = 'url(\'' + img + '\')'; + td.style.backgroundRepeat = 'no-repeat'; + td.style.backgroundPosition = '2px 50%'; + }; + + /** + * Function: createSubmenu + * + * Creates the nodes required to add submenu items inside the given parent + * item. This is called in if a parent item is used for the first + * time. This adds various DOM nodes and a to the parent. + * + * Parameters: + * + * parent - An item returned by . + */ + createSubmenu = (parent) => { + parent.table = document.createElement('table'); + parent.table.className = 'mxPopupMenu'; + + parent.tbody = document.createElement('tbody'); + parent.table.appendChild(parent.tbody); + + parent.div = document.createElement('div'); + parent.div.className = 'mxPopupMenu'; + + parent.div.style.position = 'absolute'; + parent.div.style.display = 'inline'; + parent.div.style.zIndex = this.zIndex; + + parent.div.appendChild(parent.table); + + var img = document.createElement('img'); + img.setAttribute('src', this.submenuImage); + + // Last column of the submenu item in the parent menu + td = parent.firstChild.nextSibling.nextSibling; + td.appendChild(img); + }; + + /** + * Function: showSubmenu + * + * Shows the submenu inside the given parent row. + */ + showSubmenu = (parent, row) => { + if (row.div != null) { + row.div.style.left = (parent.div.offsetLeft + + row.offsetLeft + row.offsetWidth - 1) + 'px'; + row.div.style.top = (parent.div.offsetTop + row.offsetTop) + 'px'; + document.body.appendChild(row.div); + + // Moves the submenu to the left side if there is no space + var left = parseInt(row.div.offsetLeft); + var width = parseInt(row.div.offsetWidth); + var offset = mxUtils.getDocumentScrollOrigin(document); + + var b = document.body; + var d = document.documentElement; + + var right = offset.x + (b.clientWidth || d.clientWidth); + + if (left + width > right) { + row.div.style.left = Math.max(0, (parent.div.offsetLeft - width - 6)) + 'px'; + } + + mxUtils.fit(row.div); } - } -}; + }; -/** - * Function: isMenuShowing - * - * Returns true if the menu is showing. - */ -isMenuShowing = ()=> -{ - return this.div != null && this.div.parentNode == document.body; -}; + /** + * Function: addSeparator + * + * Adds a horizontal separator in the given parent item or the top-level menu + * if no parent is specified. + * + * Parameters: + * + * parent - Optional item returned by . + * force - Optional boolean to ignore . Default is false. + */ + addSeparator = (parent, force) => { + parent = parent || this; -/** - * Function: showMenu - * - * Shows the menu. - */ -showMenu = ()=> -{ - // Fits the div inside the viewport - document.body.appendChild(this.div); - mxUtils.fit(this.div); -}; + if (this.smartSeparators && !force) { + parent.willAddSeparator = true; + } else if (parent.tbody != null) { + parent.willAddSeparator = false; + var tr = document.createElement('tr'); -/** - * Function: hideMenu - * - * Removes the menu and all submenus. - */ -hideMenu = ()=> -{ - if (this.div != null) - { - if (this.div.parentNode != null) - { - this.div.parentNode.removeChild(this.div); + var col1 = document.createElement('td'); + col1.className = 'mxPopupMenuIcon'; + col1.style.padding = '0 0 0 0px'; + + tr.appendChild(col1); + + var col2 = document.createElement('td'); + col2.style.padding = '0 0 0 0px'; + col2.setAttribute('colSpan', '2'); + + var hr = document.createElement('hr'); + hr.setAttribute('size', '1'); + col2.appendChild(hr); + + tr.appendChild(col2); + + parent.tbody.appendChild(tr); } + }; - this.hideSubmenu(this); - this.containsItems = false; - this.fireEvent(new mxEventObject(mxEvent.HIDE)); - } -}; + /** + * Function: popup + * + * Shows the popup menu for the given event and cell. + * + * Example: + * + * (code) + * graph.panningHandler.popup = (x, y, cell, evt)=> + * { + * mxUtils.alert('Hello, World!'); + * } + * (end) + */ + popup = (x, y, cell, evt) => { + if (this.div != null && this.tbody != null && this.factoryMethod != null) { + this.div.style.left = x + 'px'; + this.div.style.top = y + 'px'; -/** - * Function: hideSubmenu - * - * Removes all submenus inside the given parent. - * - * Parameters: - * - * parent - An item returned by . - */ -hideSubmenu = (parent)=> -{ - if (parent.activeRow != null) - { - this.hideSubmenu(parent.activeRow); + // Removes all child nodes from the existing menu + while (this.tbody.firstChild != null) { + mxEvent.release(this.tbody.firstChild); + this.tbody.removeChild(this.tbody.firstChild); + } - if (parent.activeRow.div.parentNode != null) - { - parent.activeRow.div.parentNode.removeChild(parent.activeRow.div); + this.itemCount = 0; + this.factoryMethod(this, cell, evt); + + if (this.itemCount > 0) { + this.showMenu(); + this.fireEvent(new mxEventObject(mxEvent.SHOW)); + } } + }; - parent.activeRow = null; - } -}; + /** + * Function: isMenuShowing + * + * Returns true if the menu is showing. + */ + isMenuShowing = () => { + return this.div != null && this.div.parentNode == document.body; + }; -/** - * Function: destroy - * - * Destroys the handler and all its resources and DOM nodes. - */ -destroy = ()=> -{ - if (this.div != null) - { - mxEvent.release(this.div); + /** + * Function: showMenu + * + * Shows the menu. + */ + showMenu = () => { + // Fits the div inside the viewport + document.body.appendChild(this.div); + mxUtils.fit(this.div); + }; - if (this.div.parentNode != null) - { - this.div.parentNode.removeChild(this.div); + /** + * Function: hideMenu + * + * Removes the menu and all submenus. + */ + hideMenu = () => { + if (this.div != null) { + if (this.div.parentNode != null) { + this.div.parentNode.removeChild(this.div); + } + + this.hideSubmenu(this); + this.containsItems = false; + this.fireEvent(new mxEventObject(mxEvent.HIDE)); } + }; - this.div = null; - } -}; + /** + * Function: hideSubmenu + * + * Removes all submenus inside the given parent. + * + * Parameters: + * + * parent - An item returned by . + */ + hideSubmenu = (parent) => { + if (parent.activeRow != null) { + this.hideSubmenu(parent.activeRow); + + if (parent.activeRow.div.parentNode != null) { + parent.activeRow.div.parentNode.removeChild(parent.activeRow.div); + } + + parent.activeRow = null; + } + }; + + /** + * Function: destroy + * + * Destroys the handler and all its resources and DOM nodes. + */ + destroy = () => { + if (this.div != null) { + mxEvent.release(this.div); + + if (this.div.parentNode != null) { + this.div.parentNode.removeChild(this.div); + } + + this.div = null; + } + }; +} + +export default mxPopupMenu; diff --git a/src/js/util/mxRectangle.js b/src/js/util/mxRectangle.js index 6c351345d..99df24358 100644 --- a/src/js/util/mxRectangle.js +++ b/src/js/util/mxRectangle.js @@ -13,169 +13,156 @@ * Constructs a new rectangle for the optional parameters. If no parameters * are given then the respective default values are used. */ -function mxRectangle(x, y, width, height) -{ - mxPoint.call(this, x, y); - this.width = (width != null) ? width : 0; - this.height = (height != null) ? height : 0; -}; +import mxPoint from "./mxPoint"; -/** - * Extends mxPoint. - */ -mxRectangle.prototype = new mxPoint(); -constructor = mxRectangle; +class mxRectangle extends mxPoint { + constructor(x, y, width, height) { + super(x, y); + this.width = (width != null) ? width : 0; + this.height = (height != null) ? height : 0; + }; -/** - * Variable: width - * - * Holds the width of the rectangle. Default is 0. - */ -width = null; + /** + * Variable: width + * + * Holds the width of the rectangle. Default is 0. + */ + width = null; -/** - * Variable: height - * - * Holds the height of the rectangle. Default is 0. - */ -height = null; + /** + * Variable: height + * + * Holds the height of the rectangle. Default is 0. + */ + height = null; -/** - * Function: setRect - * - * Sets this rectangle to the specified values - */ -setRect = (x, y, w, h)=> -{ + /** + * Function: setRect + * + * Sets this rectangle to the specified values + */ + setRect = (x, y, w, h) => { this.x = x; this.y = y; this.width = w; this.height = h; -}; + }; -/** - * Function: getCenterX - * - * Returns the x-coordinate of the center point. - */ -getCenterX = function () -{ - return this.x + this.width/2; -}; + /** + * Function: getCenterX + * + * Returns the x-coordinate of the center point. + */ + getCenterX = function () { + return this.x + this.width / 2; + }; -/** - * Function: getCenterY - * - * Returns the y-coordinate of the center point. - */ -getCenterY = function () -{ - return this.y + this.height/2; -}; + /** + * Function: getCenterY + * + * Returns the y-coordinate of the center point. + */ + getCenterY = function () { + return this.y + this.height / 2; + }; -/** - * Function: add - * - * Adds the given rectangle to this rectangle. - */ -add = (rect)=> -{ - if (rect != null) - { - var minX = Math.min(this.x, rect.x); - var minY = Math.min(this.y, rect.y); - var maxX = Math.max(this.x + this.width, rect.x + rect.width); - var maxY = Math.max(this.y + this.height, rect.y + rect.height); - - this.x = minX; - this.y = minY; - this.width = maxX - minX; - this.height = maxY - minY; - } -}; + /** + * Function: add + * + * Adds the given rectangle to this rectangle. + */ + add = (rect) => { + if (rect != null) { + var minX = Math.min(this.x, rect.x); + var minY = Math.min(this.y, rect.y); + var maxX = Math.max(this.x + this.width, rect.x + rect.width); + var maxY = Math.max(this.y + this.height, rect.y + rect.height); -/** - * Function: intersect - * - * Changes this rectangle to where it overlaps with the given rectangle. - */ -intersect = (rect)=> -{ - if (rect != null) - { - var r1 = this.x + this.width; - var r2 = rect.x + rect.width; - - var b1 = this.y + this.height; - var b2 = rect.y + rect.height; - - this.x = Math.max(this.x, rect.x); - this.y = Math.max(this.y, rect.y); - this.width = Math.min(r1, r2) - this.x; - this.height = Math.min(b1, b2) - this.y; - } -}; + this.x = minX; + this.y = minY; + this.width = maxX - minX; + this.height = maxY - minY; + } + }; -/** - * Function: grow - * - * Grows the rectangle by the given amount, that is, this method subtracts - * the given amount from the x- and y-coordinates and adds twice the amount - * to the width and height. - */ -grow = (amount)=> -{ - this.x -= amount; - this.y -= amount; - this.width += 2 * amount; - this.height += 2 * amount; - - return this; -}; + /** + * Function: intersect + * + * Changes this rectangle to where it overlaps with the given rectangle. + */ + intersect = (rect) => { + if (rect != null) { + var r1 = this.x + this.width; + var r2 = rect.x + rect.width; -/** - * Function: getPoint - * - * Returns the top, left corner as a new . - */ -getPoint = ()=> -{ - return new mxPoint(this.x, this.y); -}; + var b1 = this.y + this.height; + var b2 = rect.y + rect.height; -/** - * Function: rotate90 - * - * Rotates this rectangle by 90 degree around its center point. - */ -rotate90 = ()=> -{ - var t = (this.width - this.height) / 2; - this.x += t; - this.y -= t; - var tmp = this.width; - this.width = this.height; - this.height = tmp; -}; + this.x = Math.max(this.x, rect.x); + this.y = Math.max(this.y, rect.y); + this.width = Math.min(r1, r2) - this.x; + this.height = Math.min(b1, b2) - this.y; + } + }; -/** - * Function: equals - * - * Returns true if the given object equals this rectangle. - */ -equals = (obj)=> -{ - return obj != null && obj.x == this.x && obj.y == this.y && - obj.width == this.width && obj.height == this.height; -}; + /** + * Function: grow + * + * Grows the rectangle by the given amount, that is, this method subtracts + * the given amount from the x- and y-coordinates and adds twice the amount + * to the width and height. + */ + grow = (amount) => { + this.x -= amount; + this.y -= amount; + this.width += 2 * amount; + this.height += 2 * amount; -/** - * Function: fromRectangle - * - * Returns a new which is a copy of the given rectangle. - */ -mxRectangle.fromRectangle = (rect)=> -{ - return new mxRectangle(rect.x, rect.y, rect.width, rect.height); -}; + return this; + }; + + /** + * Function: getPoint + * + * Returns the top, left corner as a new . + */ + getPoint = () => { + return new mxPoint(this.x, this.y); + }; + + /** + * Function: rotate90 + * + * Rotates this rectangle by 90 degree around its center point. + */ + rotate90 = () => { + var t = (this.width - this.height) / 2; + this.x += t; + this.y -= t; + var tmp = this.width; + this.width = this.height; + this.height = tmp; + }; + + /** + * Function: equals + * + * Returns true if the given object equals this rectangle. + */ + equals = (obj) => { + return obj != null && obj.x == this.x && obj.y == this.y && + obj.width == this.width && obj.height == this.height; + }; + + /** + * Function: fromRectangle + * + * Returns a new which is a copy of the given rectangle. + */ + static fromRectangle = (rect) => { + return new mxRectangle(rect.x, rect.y, rect.width, rect.height); + }; +} + +export default mxRectangle; diff --git a/src/js/util/mxResources.js b/src/js/util/mxResources.js index 5ea1e6cb9..f3fb71702 100644 --- a/src/js/util/mxResources.js +++ b/src/js/util/mxResources.js @@ -448,3 +448,5 @@ var mxResources = } }; + +export default mxResources; diff --git a/src/js/util/mxSvgCanvas2D.js b/src/js/util/mxSvgCanvas2D.js index 335ede1bb..26f4842fa 100644 --- a/src/js/util/mxSvgCanvas2D.js +++ b/src/js/util/mxSvgCanvas2D.js @@ -2,1938 +2,1651 @@ * Copyright (c) 2006-2015, JGraph Ltd * Copyright (c) 2006-2015, Gaudenz Alder */ -/** - * Class: mxSvgCanvas2D - * - * Extends to implement a canvas for SVG. This canvas writes all - * calls as SVG output to the given SVG root node. - * - * (code) - * var svgDoc = mxUtils.createXmlDocument(); - * var root = (svgDoc.createElementNS != null) ? - * svgDoc.createElementNS(mxConstants.NS_SVG, 'svg') : svgDoc.createElement('svg'); - * - * if (svgDoc.createElementNS == null) - * { - * root.setAttribute('xmlns', mxConstants.NS_SVG); - * root.setAttribute('xmlns:xlink', mxConstants.NS_XLINK); - * } - * else - * { - * root.setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:xlink', mxConstants.NS_XLINK); - * } - * - * var bounds = graph.getGraphBounds(); - * root.setAttribute('width', (bounds.x + bounds.width + 4) + 'px'); - * root.setAttribute('height', (bounds.y + bounds.height + 4) + 'px'); - * root.setAttribute('version', '1.1'); - * - * svgDoc.appendChild(root); - * - * var svgCanvas = new mxSvgCanvas2D(root); - * (end) - * - * A description of the public API is available in . - * - * To disable anti-aliasing in the output, use the following code. - * - * (code) - * graph.view.canvas.ownerSVGElement.setAttribute('shape-rendering', 'crispEdges'); - * (end) - * - * Or set the respective attribute in the SVG element directly. - * - * Constructor: mxSvgCanvas2D - * - * Constructs a new SVG canvas. - * - * Parameters: - * - * root - SVG container for the output. - * styleEnabled - Optional boolean that specifies if a style section should be - * added. The style section sets the default font-size, font-family and - * stroke-miterlimit globally. Default is false. - */ -function mxSvgCanvas2D(root, styleEnabled) -{ - mxAbstractCanvas2D.call(this); - /** - * Variable: root - * - * Reference to the container for the SVG content. - */ - this.root = root; - - /** - * Variable: gradients - * - * Local cache of gradients for quick lookups. - */ - this.gradients = []; - - /** - * Variable: defs - * - * Reference to the defs section of the SVG document. Only for export. - */ - this.defs = null; - - /** - * Variable: styleEnabled - * - * Stores the value of styleEnabled passed to the constructor. - */ - this.styleEnabled = (styleEnabled != null) ? styleEnabled : false; - - var svg = null; - - // Adds optional defs section for export - if (root.ownerDocument != document) - { - var node = root; - - // Finds owner SVG element in XML DOM - while (node != null && node.nodeName != 'svg') - { - node = node.parentNode; - } - - svg = node; - } - - if (svg != null) - { - // Tries to get existing defs section - var tmp = svg.getElementsByTagName('defs'); - - if (tmp.length > 0) - { - this.defs = svg.getElementsByTagName('defs')[0]; - } - - // Adds defs section if none exists - if (this.defs == null) - { - this.defs = this.createElement('defs'); - - if (svg.firstChild != null) - { - svg.insertBefore(this.defs, svg.firstChild); - } - else - { - svg.appendChild(this.defs); - } - } - - // Adds stylesheet - if (this.styleEnabled) - { - this.defs.appendChild(this.createStyle()); - } - } -}; - -/** - * Extends mxAbstractCanvas2D - */ -mxUtils.extend(mxSvgCanvas2D, mxAbstractCanvas2D); +import mxUtils from "./mxUtils"; +import mxConstants from "./mxConstants"; /** * Capability check for DOM parser and checks if base tag is used. */ -(()=> -{ - mxSvgCanvas2useDomParser = typeof DOMParser === 'function' && typeof XMLSerializer === 'function'; - - if (mxSvgCanvas2useDomParser) - { - // Checks using a generic test text if the parsing actually works. This is a workaround - // for older browsers where the capability check returns true but the parsing fails. - try - { - var doc = new DOMParser().parseFromString('test text', 'text/html'); - mxSvgCanvas2useDomParser = doc != null; +/* +let mxSvgCanvas2useDomParser = typeof DOMParser === 'function' && typeof XMLSerializer === 'function'; + +if (mxSvgCanvas2useDomParser) { + // Checks using a generic test text if the parsing actually works. This is a workaround + // for older browsers where the capability check returns true but the parsing fails. + try { + var doc = new DOMParser().parseFromString('test text', 'text/html'); + mxSvgCanvas2useDomParser = doc != null; + } catch (e) { + mxSvgCanvas2useDomParser = false; + } +} + +// Activates workaround for gradient ID resolution if base tag is used. +let mxSvgCanvas2useAbsoluteIds = !mxClient.IS_CHROMEAPP && + !mxClient.IS_EDGE && document.getElementsByTagName('base').length > 0; +*/ + + */ +class mxSvgCanvas2D extends mxAbstractCanvas2D { + /** + * Variable: path + * + * Holds the current DOM node. + */ + mxSvgCanvas2node = null; + + /** + * Variable: matchHtmlAlignment + * + * Specifies if plain text output should match the vertical HTML alignment. + * Defaul is true. + */ + mxSvgCanvas2matchHtmlAlignment = true; + + /** + * Variable: textEnabled + * + * Specifies if text output should be enabled. Default is true. + */ + mxSvgCanvas2textEnabled = true; + + /** + * Variable: foEnabled + * + * Specifies if use of foreignObject for HTML markup is allowed. Default is true. + */ + mxSvgCanvas2foEnabled = true; + + /** + * Variable: foAltText + * + * Specifies the fallback text for unsupported foreignObjects in exported + * documents. Default is '[Object]'. If this is set to null then no fallback + * text is added to the exported document. + */ + mxSvgCanvas2foAltText = '[Object]'; + + /** + * Variable: foOffset + * + * Offset to be used for foreignObjects. + */ + mxSvgCanvas2foOffset = 0; + + /** + * Variable: textOffset + * + * Offset to be used for text elements. + */ + mxSvgCanvas2textOffset = 0; + + /** + * Variable: imageOffset + * + * Offset to be used for image elements. + */ + mxSvgCanvas2imageOffset = 0; + + /** + * Variable: strokeTolerance + * + * Adds transparent paths for strokes. + */ + mxSvgCanvas2strokeTolerance = 0; + + /** + * Variable: minStrokeWidth + * + * Minimum stroke width for output. + */ + mxSvgCanvas2minStrokeWidth = 1; + + /** + * Variable: refCount + * + * Local counter for references in SVG export. + */ + mxSvgCanvas2refCount = 0; + + /** + * Variable: lineHeightCorrection + * + * Correction factor for in HTML output. Default is 1. + */ + mxSvgCanvas2lineHeightCorrection = 1; + + /** + * Variable: pointerEventsValue + * + * Default value for active pointer events. Default is all. + */ + mxSvgCanvas2pointerEventsValue = 'all'; + + /** + * Variable: fontMetricsPadding + * + * Padding to be added for text that is not wrapped to account for differences + * in font metrics on different platforms in pixels. Default is 10. + */ + mxSvgCanvas2fontMetricsPadding = 10; + + /** + * Variable: cacheOffsetSize + * + * Specifies if offsetWidth and offsetHeight should be cached. Default is true. + * This is used to speed up repaint of text in . + */ + mxSvgCanvas2cacheOffsetSize = true; + + /** + * Class: mxSvgCanvas2D + * + * Extends to implement a canvas for SVG. This canvas writes all + * calls as SVG output to the given SVG root node. + * + * (code) + * var svgDoc = mxUtils.createXmlDocument(); + * var root = (svgDoc.createElementNS != null) ? + * svgDoc.createElementNS(mxConstants.NS_SVG, 'svg') : svgDoc.createElement('svg'); + * + * if (svgDoc.createElementNS == null) + * { + * root.setAttribute('xmlns', mxConstants.NS_SVG); + * root.setAttribute('xmlns:xlink', mxConstants.NS_XLINK); + * } + * else + * { + * root.setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:xlink', mxConstants.NS_XLINK); + * } + * + * var bounds = graph.getGraphBounds(); + * root.setAttribute('width', (bounds.x + bounds.width + 4) + 'px'); + * root.setAttribute('height', (bounds.y + bounds.height + 4) + 'px'); + * root.setAttribute('version', '1.1'); + * + * svgDoc.appendChild(root); + * + * var svgCanvas = new mxSvgCanvas2D(root); + * (end) + * + * A description of the public API is available in . + * + * To disable anti-aliasing in the output, use the following code. + * + * (code) + * graph.view.canvas.ownerSVGElement.setAttribute('shape-rendering', 'crispEdges'); + * (end) + * + * Or set the respective attribute in the SVG element directly. + * + * Constructor: mxSvgCanvas2D + * + * Constructs a new SVG canvas. + * + * Parameters: + * + * root - SVG container for the output. + * styleEnabled - Optional boolean that specifies if a style section should be + * added. The style section sets the default font-size, font-family and + * stroke-miterlimit globally. Default is false. + */ + constructor(root, styleEnabled) { + super(); + + /** + * Variable: root + * + * Reference to the container for the SVG content. + */ + this.root = root; + + /** + * Variable: gradients + * + * Local cache of gradients for quick lookups. + */ + this.gradients = []; + + /** + * Variable: defs + * + * Reference to the defs section of the SVG document. Only for export. + */ + this.defs = null; + + /** + * Variable: styleEnabled + * + * Stores the value of styleEnabled passed to the constructor. + */ + this.styleEnabled = (styleEnabled != null) ? styleEnabled : false; + + var svg = null; + + // Adds optional defs section for export + if (root.ownerDocument != document) { + var node = root; + + // Finds owner SVG element in XML DOM + while (node != null && node.nodeName != 'svg') { + node = node.parentNode; + } + + svg = node; } - catch (e) - { - mxSvgCanvas2useDomParser = false; + + if (svg != null) { + // Tries to get existing defs section + var tmp = svg.getElementsByTagName('defs'); + + if (tmp.length > 0) { + this.defs = svg.getElementsByTagName('defs')[0]; + } + + // Adds defs section if none exists + if (this.defs == null) { + this.defs = this.createElement('defs'); + + if (svg.firstChild != null) { + svg.insertBefore(this.defs, svg.firstChild); + } else { + svg.appendChild(this.defs); + } + } + + // Adds stylesheet + if (this.styleEnabled) { + this.defs.appendChild(this.createStyle()); + } } - } - - // Activates workaround for gradient ID resolution if base tag is used. - mxSvgCanvas2useAbsoluteIds = !mxClient.IS_CHROMEAPP && - !mxClient.IS_EDGE && document.getElementsByTagName('base').length > 0; -})(); + }; -/** - * Variable: path - * - * Holds the current DOM node. - */ -mxSvgCanvas2node = null; + /** + * Function: format + * + * Rounds all numbers to 2 decimal points. + */ + mxSvgCanvas2format = (value) => { + return parseFloat(parseFloat(value).toFixed(2)); + }; -/** - * Variable: matchHtmlAlignment - * - * Specifies if plain text output should match the vertical HTML alignment. - * Defaul is true. - */ -mxSvgCanvas2matchHtmlAlignment = true; + /** + * Function: getBaseUrl + * + * Returns the URL of the page without the hash part. This needs to use href to + * include any search part with no params (ie question mark alone). This is a + * workaround for the fact that window.location.search is empty if there is + * no search string behind the question mark. + */ + mxSvgCanvas2getBaseUrl = () => { + var href = window.location.href; + var hash = href.lastIndexOf('#'); -/** - * Variable: textEnabled - * - * Specifies if text output should be enabled. Default is true. - */ -mxSvgCanvas2textEnabled = true; - -/** - * Variable: foEnabled - * - * Specifies if use of foreignObject for HTML markup is allowed. Default is true. - */ -mxSvgCanvas2foEnabled = true; - -/** - * Variable: foAltText - * - * Specifies the fallback text for unsupported foreignObjects in exported - * documents. Default is '[Object]'. If this is set to null then no fallback - * text is added to the exported document. - */ -mxSvgCanvas2foAltText = '[Object]'; - -/** - * Variable: foOffset - * - * Offset to be used for foreignObjects. - */ -mxSvgCanvas2foOffset = 0; - -/** - * Variable: textOffset - * - * Offset to be used for text elements. - */ -mxSvgCanvas2textOffset = 0; - -/** - * Variable: imageOffset - * - * Offset to be used for image elements. - */ -mxSvgCanvas2imageOffset = 0; - -/** - * Variable: strokeTolerance - * - * Adds transparent paths for strokes. - */ -mxSvgCanvas2strokeTolerance = 0; - -/** - * Variable: minStrokeWidth - * - * Minimum stroke width for output. - */ -mxSvgCanvas2minStrokeWidth = 1; - -/** - * Variable: refCount - * - * Local counter for references in SVG export. - */ -mxSvgCanvas2refCount = 0; - -/** - * Variable: lineHeightCorrection - * - * Correction factor for in HTML output. Default is 1. - */ -mxSvgCanvas2lineHeightCorrection = 1; - -/** - * Variable: pointerEventsValue - * - * Default value for active pointer events. Default is all. - */ -mxSvgCanvas2pointerEventsValue = 'all'; - -/** - * Variable: fontMetricsPadding - * - * Padding to be added for text that is not wrapped to account for differences - * in font metrics on different platforms in pixels. Default is 10. - */ -mxSvgCanvas2fontMetricsPadding = 10; - -/** - * Variable: cacheOffsetSize - * - * Specifies if offsetWidth and offsetHeight should be cached. Default is true. - * This is used to speed up repaint of text in . - */ -mxSvgCanvas2cacheOffsetSize = true; - -/** - * Function: format - * - * Rounds all numbers to 2 decimal points. - */ -mxSvgCanvas2format = (value)=> -{ - return parseFloat(parseFloat(value).toFixed(2)); -}; - -/** - * Function: getBaseUrl - * - * Returns the URL of the page without the hash part. This needs to use href to - * include any search part with no params (ie question mark alone). This is a - * workaround for the fact that window.location.search is empty if there is - * no search string behind the question mark. - */ -mxSvgCanvas2getBaseUrl = ()=> -{ - var href = window.location.href; - var hash = href.lastIndexOf('#'); - - if (hash > 0) - { - href = href.substring(0, hash); - } - - return href; -}; - -/** - * Function: reset - * - * Returns any offsets for rendering pixels. - */ -mxSvgCanvas2reset = ()=> -{ - mxAbstractCanvas2reset.apply(this, arguments); - this.gradients = []; -}; - -/** - * Function: createStyle - * - * Creates the optional style section. - */ -mxSvgCanvas2createStyle = (x)=> -{ - var style = this.createElement('style'); - style.setAttribute('type', 'text/css'); - mxUtils.write(style, 'svg{font-family:' + mxConstants.DEFAULT_FONTFAMILY + - ';font-size:' + mxConstants.DEFAULT_FONTSIZE + - ';fill:none;stroke-miterlimit:10}'); - - return style; -}; - -/** - * Function: createElement - * - * Private helper function to create SVG elements - */ -mxSvgCanvas2createElement = (tagName, namespace)=> -{ - if (this.root.ownerDocument.createElementNS != null) - { - return this.root.ownerDocument.createElementNS(namespace || mxConstants.NS_SVG, tagName); - } - else - { - var elt = this.root.ownerDocument.createElement(tagName); - - if (namespace != null) - { - elt.setAttribute('xmlns', namespace); + if (hash > 0) { + href = href.substring(0, hash); } - - return elt; - } -}; -/** - * Function: getAlternateText - * - * Returns the alternate text string for the given foreignObject. - */ -mxSvgCanvas2getAlternateText = (fo, x, y, w, h, str, align, valign, wrap, format, overflow, clip, rotation)=> -{ - return (str != null) ? this.foAltText : null; -}; + return href; + }; -/** - * Function: getAlternateContent - * - * Returns the alternate content for the given foreignObject. - */ -mxSvgCanvas2createAlternateContent = (fo, x, y, w, h, str, align, valign, wrap, format, overflow, clip, rotation)=> -{ - var text = this.getAlternateText(fo, x, y, w, h, str, align, valign, wrap, format, overflow, clip, rotation); - var s = this.state; + /** + * Function: reset + * + * Returns any offsets for rendering pixels. + */ + mxSvgCanvas2reset = () => { + mxAbstractCanvas2reset.apply(this, arguments); + this.gradients = []; + }; - if (text != null && s.fontSize > 0) - { - var dy = (valign == mxConstants.ALIGN_TOP) ? 1 : - (valign == mxConstants.ALIGN_BOTTOM) ? 0 : 0.3; - var anchor = (align == mxConstants.ALIGN_RIGHT) ? 'end' : - (align == mxConstants.ALIGN_LEFT) ? 'start' : - 'middle'; - - var alt = this.createElement('text'); - alt.setAttribute('x', Math.round(x + s.dx)); - alt.setAttribute('y', Math.round(y + s.dy + dy * s.fontSize)); - alt.setAttribute('fill', s.fontColor || 'black'); - alt.setAttribute('font-family', s.fontFamily); - alt.setAttribute('font-size', Math.round(s.fontSize) + 'px'); + /** + * Function: createStyle + * + * Creates the optional style section. + */ + mxSvgCanvas2createStyle = (x) => { + var style = this.createElement('style'); + style.setAttribute('type', 'text/css'); + mxUtils.write(style, 'svg{font-family:' + mxConstants.DEFAULT_FONTFAMILY + + ';font-size:' + mxConstants.DEFAULT_FONTSIZE + + ';fill:none;stroke-miterlimit:10}'); - // Text-anchor start is default in SVG - if (anchor != 'start') - { - alt.setAttribute('text-anchor', anchor); - } - - if ((s.fontStyle & mxConstants.FONT_BOLD) == mxConstants.FONT_BOLD) - { - alt.setAttribute('font-weight', 'bold'); - } - - if ((s.fontStyle & mxConstants.FONT_ITALIC) == mxConstants.FONT_ITALIC) - { - alt.setAttribute('font-style', 'italic'); - } - - var txtDecor = []; - - if ((s.fontStyle & mxConstants.FONT_UNDERLINE) == mxConstants.FONT_UNDERLINE) - { - txtDecor.push('underline'); - } - - if ((s.fontStyle & mxConstants.FONT_STRIKETHROUGH) == mxConstants.FONT_STRIKETHROUGH) - { - txtDecor.push('line-through'); - } - - if (txtDecor.length > 0) - { - alt.setAttribute('text-decoration', txtDecor.join(' ')); - } - - mxUtils.write(alt, text); - - return alt; - } - else - { - return null; - } -}; + return style; + }; -/** - * Function: createGradientId - * - * Private helper function to create SVG elements - */ -mxSvgCanvas2createGradientId = (start, end, alpha1, alpha2, direction)=> -{ - // Removes illegal characters from gradient ID - if (start.charAt(0) == '#') - { - start = start.substring(1); - } - - if (end.charAt(0) == '#') - { - end = end.substring(1); - } - - // Workaround for gradient IDs not working in Safari 5 / Chrome 6 - // if they contain uppercase characters - start = start.toLowerCase() + '-' + alpha1; - end = end.toLowerCase() + '-' + alpha2; + /** + * Function: createElement + * + * Private helper function to create SVG elements + */ + mxSvgCanvas2createElement = (tagName, namespace) => { + if (this.root.ownerDocument.createElementNS != null) { + return this.root.ownerDocument.createElementNS(namespace || mxConstants.NS_SVG, tagName); + } else { + var elt = this.root.ownerDocument.createElement(tagName); - // Wrong gradient directions possible? - var dir = null; - - if (direction == null || direction == mxConstants.DIRECTION_SOUTH) - { - dir = 's'; - } - else if (direction == mxConstants.DIRECTION_EAST) - { - dir = 'e'; - } - else - { - var tmp = start; - start = end; - end = tmp; - - if (direction == mxConstants.DIRECTION_NORTH) - { + if (namespace != null) { + elt.setAttribute('xmlns', namespace); + } + + return elt; + } + }; + + /** + * Function: getAlternateText + * + * Returns the alternate text string for the given foreignObject. + */ + mxSvgCanvas2getAlternateText = (fo, x, y, w, h, str, align, valign, wrap, format, overflow, clip, rotation) => { + return (str != null) ? this.foAltText : null; + }; + + /** + * Function: getAlternateContent + * + * Returns the alternate content for the given foreignObject. + */ + mxSvgCanvas2createAlternateContent = (fo, x, y, w, h, str, align, valign, wrap, format, overflow, clip, rotation) => { + var text = this.getAlternateText(fo, x, y, w, h, str, align, valign, wrap, format, overflow, clip, rotation); + var s = this.state; + + if (text != null && s.fontSize > 0) { + var dy = (valign == mxConstants.ALIGN_TOP) ? 1 : + (valign == mxConstants.ALIGN_BOTTOM) ? 0 : 0.3; + var anchor = (align == mxConstants.ALIGN_RIGHT) ? 'end' : + (align == mxConstants.ALIGN_LEFT) ? 'start' : + 'middle'; + + var alt = this.createElement('text'); + alt.setAttribute('x', Math.round(x + s.dx)); + alt.setAttribute('y', Math.round(y + s.dy + dy * s.fontSize)); + alt.setAttribute('fill', s.fontColor || 'black'); + alt.setAttribute('font-family', s.fontFamily); + alt.setAttribute('font-size', Math.round(s.fontSize) + 'px'); + + // Text-anchor start is default in SVG + if (anchor != 'start') { + alt.setAttribute('text-anchor', anchor); + } + + if ((s.fontStyle & mxConstants.FONT_BOLD) == mxConstants.FONT_BOLD) { + alt.setAttribute('font-weight', 'bold'); + } + + if ((s.fontStyle & mxConstants.FONT_ITALIC) == mxConstants.FONT_ITALIC) { + alt.setAttribute('font-style', 'italic'); + } + + var txtDecor = []; + + if ((s.fontStyle & mxConstants.FONT_UNDERLINE) == mxConstants.FONT_UNDERLINE) { + txtDecor.push('underline'); + } + + if ((s.fontStyle & mxConstants.FONT_STRIKETHROUGH) == mxConstants.FONT_STRIKETHROUGH) { + txtDecor.push('line-through'); + } + + if (txtDecor.length > 0) { + alt.setAttribute('text-decoration', txtDecor.join(' ')); + } + + mxUtils.write(alt, text); + + return alt; + } else { + return null; + } + }; + + /** + * Function: createGradientId + * + * Private helper function to create SVG elements + */ + mxSvgCanvas2createGradientId = (start, end, alpha1, alpha2, direction) => { + // Removes illegal characters from gradient ID + if (start.charAt(0) == '#') { + start = start.substring(1); + } + + if (end.charAt(0) == '#') { + end = end.substring(1); + } + + // Workaround for gradient IDs not working in Safari 5 / Chrome 6 + // if they contain uppercase characters + start = start.toLowerCase() + '-' + alpha1; + end = end.toLowerCase() + '-' + alpha2; + + // Wrong gradient directions possible? + var dir = null; + + if (direction == null || direction == mxConstants.DIRECTION_SOUTH) { dir = 's'; - } - else if (direction == mxConstants.DIRECTION_WEST) - { + } else if (direction == mxConstants.DIRECTION_EAST) { dir = 'e'; + } else { + var tmp = start; + start = end; + end = tmp; + + if (direction == mxConstants.DIRECTION_NORTH) { + dir = 's'; + } else if (direction == mxConstants.DIRECTION_WEST) { + dir = 'e'; + } } - } - - return 'mx-gradient-' + start + '-' + end + '-' + dir; -}; -/** - * Function: getSvgGradient - * - * Private helper function to create SVG elements - */ -mxSvgCanvas2getSvgGradient = (start, end, alpha1, alpha2, direction)=> -{ - var id = this.createGradientId(start, end, alpha1, alpha2, direction); - var gradient = this.gradients[id]; - - if (gradient == null) - { - var svg = this.root.ownerSVGElement; + return 'mx-gradient-' + start + '-' + end + '-' + dir; + }; - var counter = 0; - var tmpId = id + '-' + counter; + /** + * Function: getSvgGradient + * + * Private helper function to create SVG elements + */ + mxSvgCanvas2getSvgGradient = (start, end, alpha1, alpha2, direction) => { + var id = this.createGradientId(start, end, alpha1, alpha2, direction); + var gradient = this.gradients[id]; - if (svg != null) - { - gradient = svg.ownerDocument.getElementById(tmpId); - - while (gradient != null && gradient.ownerSVGElement != svg) - { - tmpId = id + '-' + counter++; + if (gradient == null) { + var svg = this.root.ownerSVGElement; + + var counter = 0; + var tmpId = id + '-' + counter; + + if (svg != null) { gradient = svg.ownerDocument.getElementById(tmpId); + + while (gradient != null && gradient.ownerSVGElement != svg) { + tmpId = id + '-' + counter++; + gradient = svg.ownerDocument.getElementById(tmpId); + } + } else { + // Uses shorter IDs for export + tmpId = 'id' + (++this.refCount); + } + + if (gradient == null) { + gradient = this.createSvgGradient(start, end, alpha1, alpha2, direction); + gradient.setAttribute('id', tmpId); + + if (this.defs != null) { + this.defs.appendChild(gradient); + } else { + svg.appendChild(gradient); + } + } + + this.gradients[id] = gradient; + } + + return gradient.getAttribute('id'); + }; + + /** + * Function: createSvgGradient + * + * Creates the given SVG gradient. + */ + mxSvgCanvas2createSvgGradient = (start, end, alpha1, alpha2, direction) => { + var gradient = this.createElement('linearGradient'); + gradient.setAttribute('x1', '0%'); + gradient.setAttribute('y1', '0%'); + gradient.setAttribute('x2', '0%'); + gradient.setAttribute('y2', '0%'); + + if (direction == null || direction == mxConstants.DIRECTION_SOUTH) { + gradient.setAttribute('y2', '100%'); + } else if (direction == mxConstants.DIRECTION_EAST) { + gradient.setAttribute('x2', '100%'); + } else if (direction == mxConstants.DIRECTION_NORTH) { + gradient.setAttribute('y1', '100%'); + } else if (direction == mxConstants.DIRECTION_WEST) { + gradient.setAttribute('x1', '100%'); + } + + var op = (alpha1 < 1) ? ';stop-opacity:' + alpha1 : ''; + + var stop = this.createElement('stop'); + stop.setAttribute('offset', '0%'); + stop.setAttribute('style', 'stop-color:' + start + op); + gradient.appendChild(stop); + + op = (alpha2 < 1) ? ';stop-opacity:' + alpha2 : ''; + + stop = this.createElement('stop'); + stop.setAttribute('offset', '100%'); + stop.setAttribute('style', 'stop-color:' + end + op); + gradient.appendChild(stop); + + return gradient; + }; + + /** + * Function: addNode + * + * Private helper function to create SVG elements + */ + mxSvgCanvas2addNode = (filled, stroked) => { + var node = this.node; + var s = this.state; + + if (node != null) { + if (node.nodeName == 'path') { + // Checks if the path is not empty + if (this.path != null && this.path.length > 0) { + node.setAttribute('d', this.path.join(' ')); + } else { + return; + } + } + + if (filled && s.fillColor != null) { + this.updateFill(); + } else if (!this.styleEnabled) { + // Workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=814952 + if (node.nodeName == 'ellipse' && mxClient.IS_FF) { + node.setAttribute('fill', 'transparent'); + } else { + node.setAttribute('fill', 'none'); + } + + // Sets the actual filled state for stroke tolerance + filled = false; + } + + if (stroked && s.strokeColor != null) { + this.updateStroke(); + } else if (!this.styleEnabled) { + node.setAttribute('stroke', 'none'); + } + + if (s.transform != null && s.transform.length > 0) { + node.setAttribute('transform', s.transform); + } + + if (s.shadow) { + this.root.appendChild(this.createShadow(node)); + } + + // Adds stroke tolerance + if (this.strokeTolerance > 0 && !filled) { + this.root.appendChild(this.createTolerance(node)); + } + + // Adds pointer events + if (this.pointerEvents) { + node.setAttribute('pointer-events', this.pointerEventsValue); + } + // Enables clicks for nodes inside a link element + else if (!this.pointerEvents && this.originalRoot == null) { + node.setAttribute('pointer-events', 'none'); + } + + // Removes invisible nodes from output if they don't handle events + if ((node.nodeName != 'rect' && node.nodeName != 'path' && node.nodeName != 'ellipse') || + (node.getAttribute('fill') != 'none' && node.getAttribute('fill') != 'transparent') || + node.getAttribute('stroke') != 'none' || node.getAttribute('pointer-events') != 'none') { + // LATER: Update existing DOM for performance + this.root.appendChild(node); + } + + this.node = null; + } + }; + + /** + * Function: updateFill + * + * Transfers the stroke attributes from to . + */ + mxSvgCanvas2updateFill = () => { + var s = this.state; + + if (s.alpha < 1 || s.fillAlpha < 1) { + this.node.setAttribute('fill-opacity', s.alpha * s.fillAlpha); + } + + if (s.fillColor != null) { + if (s.gradientColor != null) { + var id = this.getSvgGradient(String(s.fillColor), String(s.gradientColor), + s.gradientFillAlpha, s.gradientAlpha, s.gradientDirection); + + if (this.root.ownerDocument == document && this.useAbsoluteIds) { + // Workaround for no fill with base tag in page (escape brackets) + var base = this.getBaseUrl().replace(/([\(\)])/g, '\\$1'); + this.node.setAttribute('fill', 'url(' + base + '#' + id + ')'); + } else { + this.node.setAttribute('fill', 'url(#' + id + ')'); + } + } else { + this.node.setAttribute('fill', String(s.fillColor).toLowerCase()); } } - else - { - // Uses shorter IDs for export - tmpId = 'id' + (++this.refCount); + }; + + /** + * Function: getCurrentStrokeWidth + * + * Returns the current stroke width (>= 1), ie. max(1, this.format(this.state.strokeWidth * this.state.scale)). + */ + mxSvgCanvas2getCurrentStrokeWidth = () => { + return Math.max(this.minStrokeWidth, Math.max(0.01, this.format(this.state.strokeWidth * this.state.scale))); + }; + + /** + * Function: updateStroke + * + * Transfers the stroke attributes from to . + */ + mxSvgCanvas2updateStroke = () => { + var s = this.state; + + this.node.setAttribute('stroke', String(s.strokeColor).toLowerCase()); + + if (s.alpha < 1 || s.strokeAlpha < 1) { + this.node.setAttribute('stroke-opacity', s.alpha * s.strokeAlpha); } - - if (gradient == null) - { - gradient = this.createSvgGradient(start, end, alpha1, alpha2, direction); - gradient.setAttribute('id', tmpId); - - if (this.defs != null) - { - this.defs.appendChild(gradient); + + var sw = this.getCurrentStrokeWidth(); + + if (sw != 1) { + this.node.setAttribute('stroke-width', sw); + } + + if (this.node.nodeName == 'path') { + this.updateStrokeAttributes(); + } + + if (s.dashed) { + this.node.setAttribute('stroke-dasharray', this.createDashPattern( + ((s.fixDash) ? 1 : s.strokeWidth) * s.scale)); + } + }; + + /** + * Function: updateStrokeAttributes + * + * Transfers the stroke attributes from to . + */ + mxSvgCanvas2updateStrokeAttributes = () => { + var s = this.state; + + // Linejoin miter is default in SVG + if (s.lineJoin != null && s.lineJoin != 'miter') { + this.node.setAttribute('stroke-linejoin', s.lineJoin); + } + + if (s.lineCap != null) { + // flat is called butt in SVG + var value = s.lineCap; + + if (value == 'flat') { + value = 'butt'; } - else - { - svg.appendChild(gradient); + + // Linecap butt is default in SVG + if (value != 'butt') { + this.node.setAttribute('stroke-linecap', value); } } - this.gradients[id] = gradient; - } + // Miterlimit 10 is default in our document + if (s.miterLimit != null && (!this.styleEnabled || s.miterLimit != 10)) { + this.node.setAttribute('stroke-miterlimit', s.miterLimit); + } + }; - return gradient.getAttribute('id'); -}; + /** + * Function: createDashPattern + * + * Creates the SVG dash pattern for the given state. + */ + mxSvgCanvas2createDashPattern = (scale) => { + var pat = []; -/** - * Function: createSvgGradient - * - * Creates the given SVG gradient. - */ -mxSvgCanvas2createSvgGradient = (start, end, alpha1, alpha2, direction)=> -{ - var gradient = this.createElement('linearGradient'); - gradient.setAttribute('x1', '0%'); - gradient.setAttribute('y1', '0%'); - gradient.setAttribute('x2', '0%'); - gradient.setAttribute('y2', '0%'); - - if (direction == null || direction == mxConstants.DIRECTION_SOUTH) - { - gradient.setAttribute('y2', '100%'); - } - else if (direction == mxConstants.DIRECTION_EAST) - { - gradient.setAttribute('x2', '100%'); - } - else if (direction == mxConstants.DIRECTION_NORTH) - { - gradient.setAttribute('y1', '100%'); - } - else if (direction == mxConstants.DIRECTION_WEST) - { - gradient.setAttribute('x1', '100%'); - } - - var op = (alpha1 < 1) ? ';stop-opacity:' + alpha1 : ''; - - var stop = this.createElement('stop'); - stop.setAttribute('offset', '0%'); - stop.setAttribute('style', 'stop-color:' + start + op); - gradient.appendChild(stop); - - op = (alpha2 < 1) ? ';stop-opacity:' + alpha2 : ''; - - stop = this.createElement('stop'); - stop.setAttribute('offset', '100%'); - stop.setAttribute('style', 'stop-color:' + end + op); - gradient.appendChild(stop); - - return gradient; -}; + if (typeof (this.state.dashPattern) === 'string') { + var dash = this.state.dashPattern.split(' '); -/** - * Function: addNode - * - * Private helper function to create SVG elements - */ -mxSvgCanvas2addNode = (filled, stroked)=> -{ - var node = this.node; - var s = this.state; - - if (node != null) - { - if (node.nodeName == 'path') - { - // Checks if the path is not empty - if (this.path != null && this.path.length > 0) - { - node.setAttribute('d', this.path.join(' ')); - } - else - { - return; + if (dash.length > 0) { + for (var i = 0; i < dash.length; i++) { + pat[i] = Number(dash[i]) * scale; + } } } - if (filled && s.fillColor != null) - { - this.updateFill(); - } - else if (!this.styleEnabled) - { - // Workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=814952 - if (node.nodeName == 'ellipse' && mxClient.IS_FF) - { - node.setAttribute('fill', 'transparent'); - } - else - { - node.setAttribute('fill', 'none'); - } - - // Sets the actual filled state for stroke tolerance - filled = false; - } - - if (stroked && s.strokeColor != null) - { - this.updateStroke(); - } - else if (!this.styleEnabled) - { - node.setAttribute('stroke', 'none'); - } - - if (s.transform != null && s.transform.length > 0) - { - node.setAttribute('transform', s.transform); - } - - if (s.shadow) - { - this.root.appendChild(this.createShadow(node)); - } - - // Adds stroke tolerance - if (this.strokeTolerance > 0 && !filled) - { - this.root.appendChild(this.createTolerance(node)); + return pat.join(' '); + }; + + /** + * Function: createTolerance + * + * Creates a hit detection tolerance shape for the given node. + */ + mxSvgCanvas2createTolerance = (node) => { + var tol = node.cloneNode(true); + var sw = parseFloat(tol.getAttribute('stroke-width') || 1) + this.strokeTolerance; + tol.setAttribute('pointer-events', 'stroke'); + tol.setAttribute('visibility', 'hidden'); + tol.removeAttribute('stroke-dasharray'); + tol.setAttribute('stroke-width', sw); + tol.setAttribute('fill', 'none'); + tol.setAttribute('stroke', 'white'); + return tol; + }; + + /** + * Function: createShadow + * + * Creates a shadow for the given node. + */ + mxSvgCanvas2createShadow = (node) => { + var shadow = node.cloneNode(true); + var s = this.state; + + // Firefox uses transparent for no fill in ellipses + if (shadow.getAttribute('fill') != 'none' && (!mxClient.IS_FF || shadow.getAttribute('fill') != 'transparent')) { + shadow.setAttribute('fill', s.shadowColor); } - // Adds pointer events - if (this.pointerEvents) - { - node.setAttribute('pointer-events', this.pointerEventsValue); + if (shadow.getAttribute('stroke') != 'none') { + shadow.setAttribute('stroke', s.shadowColor); } - // Enables clicks for nodes inside a link element - else if (!this.pointerEvents && this.originalRoot == null) - { + + shadow.setAttribute('transform', 'translate(' + this.format(s.shadowDx * s.scale) + + ',' + this.format(s.shadowDy * s.scale) + ')' + (s.transform || '')); + shadow.setAttribute('opacity', s.shadowAlpha); + + return shadow; + }; + + /** + * Function: setLink + * + * Experimental implementation for hyperlinks. + */ + mxSvgCanvas2setLink = (link) => { + if (link == null) { + this.root = this.originalRoot; + } else { + this.originalRoot = this.root; + + var node = this.createElement('a'); + + // Workaround for implicit namespace handling in HTML5 export, IE adds NS1 namespace so use code below + // in all IE versions except quirks mode. KNOWN: Adds xlink namespace to each image tag in output. + if (node.setAttributeNS == null || (this.root.ownerDocument != document)) { + node.setAttribute('xlink:href', link); + } else { + node.setAttributeNS(mxConstants.NS_XLINK, 'xlink:href', link); + } + + this.root.appendChild(node); + this.root = node; + } + }; + + /** + * Function: rotate + * + * Sets the rotation of the canvas. Note that rotation cannot be concatenated. + */ + mxSvgCanvas2rotate = (theta, flipH, flipV, cx, cy) => { + if (theta != 0 || flipH || flipV) { + var s = this.state; + cx += s.dx; + cy += s.dy; + + cx *= s.scale; + cy *= s.scale; + + s.transform = s.transform || ''; + + // This implementation uses custom scale/translate and built-in rotation + // Rotation state is part of the AffineTransform in state.transform + if (flipH && flipV) { + theta += 180; + } else if (flipH != flipV) { + var tx = (flipH) ? cx : 0; + var sx = (flipH) ? -1 : 1; + + var ty = (flipV) ? cy : 0; + var sy = (flipV) ? -1 : 1; + + s.transform += 'translate(' + this.format(tx) + ',' + this.format(ty) + ')' + + 'scale(' + this.format(sx) + ',' + this.format(sy) + ')' + + 'translate(' + this.format(-tx) + ',' + this.format(-ty) + ')'; + } + + if (flipH ? !flipV : flipV) { + theta *= -1; + } + + if (theta != 0) { + s.transform += 'rotate(' + this.format(theta) + ',' + this.format(cx) + ',' + this.format(cy) + ')'; + } + + s.rotation = s.rotation + theta; + s.rotationCx = cx; + s.rotationCy = cy; + } + }; + + /** + * Function: begin + * + * Extends superclass to create path. + */ + mxSvgCanvas2begin = () => { + mxAbstractCanvas2begin.apply(this, arguments); + this.node = this.createElement('path'); + }; + + /** + * Function: rect + * + * Private helper function to create SVG elements + */ + mxSvgCanvas2rect = (x, y, w, h) => { + var s = this.state; + var n = this.createElement('rect'); + n.setAttribute('x', this.format((x + s.dx) * s.scale)); + n.setAttribute('y', this.format((y + s.dy) * s.scale)); + n.setAttribute('width', this.format(w * s.scale)); + n.setAttribute('height', this.format(h * s.scale)); + + this.node = n; + }; + + /** + * Function: roundrect + * + * Private helper function to create SVG elements + */ + mxSvgCanvas2roundrect = (x, y, w, h, dx, dy) => { + this.rect(x, y, w, h); + + if (dx > 0) { + this.node.setAttribute('rx', this.format(dx * this.state.scale)); + } + + if (dy > 0) { + this.node.setAttribute('ry', this.format(dy * this.state.scale)); + } + }; + + /** + * Function: ellipse + * + * Private helper function to create SVG elements + */ + mxSvgCanvas2ellipse = (x, y, w, h) => { + var s = this.state; + var n = this.createElement('ellipse'); + // No rounding for consistent output with 1.x + n.setAttribute('cx', this.format((x + w / 2 + s.dx) * s.scale)); + n.setAttribute('cy', this.format((y + h / 2 + s.dy) * s.scale)); + n.setAttribute('rx', w / 2 * s.scale); + n.setAttribute('ry', h / 2 * s.scale); + this.node = n; + }; + + /** + * Function: image + * + * Private helper function to create SVG elements + */ + mxSvgCanvas2image = (x, y, w, h, src, aspect, flipH, flipV) => { + src = this.converter.convert(src); + + // LATER: Add option for embedding images as base64. + aspect = (aspect != null) ? aspect : true; + flipH = (flipH != null) ? flipH : false; + flipV = (flipV != null) ? flipV : false; + + var s = this.state; + x += s.dx; + y += s.dy; + + var node = this.createElement('image'); + node.setAttribute('x', this.format(x * s.scale) + this.imageOffset); + node.setAttribute('y', this.format(y * s.scale) + this.imageOffset); + node.setAttribute('width', this.format(w * s.scale)); + node.setAttribute('height', this.format(h * s.scale)); + + // Workaround for missing namespace support + if (node.setAttributeNS == null) { + node.setAttribute('xlink:href', src); + } else { + node.setAttributeNS(mxConstants.NS_XLINK, 'xlink:href', src); + } + + if (!aspect) { + node.setAttribute('preserveAspectRatio', 'none'); + } + + if (s.alpha < 1 || s.fillAlpha < 1) { + node.setAttribute('opacity', s.alpha * s.fillAlpha); + } + + var tr = this.state.transform || ''; + + if (flipH || flipV) { + var sx = 1; + var sy = 1; + var dx = 0; + var dy = 0; + + if (flipH) { + sx = -1; + dx = -w - 2 * x; + } + + if (flipV) { + sy = -1; + dy = -h - 2 * y; + } + + // Adds image tansformation to existing transform + tr += 'scale(' + sx + ',' + sy + ')translate(' + (dx * s.scale) + ',' + (dy * s.scale) + ')'; + } + + if (tr.length > 0) { + node.setAttribute('transform', tr); + } + + if (!this.pointerEvents) { node.setAttribute('pointer-events', 'none'); } - - // Removes invisible nodes from output if they don't handle events - if ((node.nodeName != 'rect' && node.nodeName != 'path' && node.nodeName != 'ellipse') || - (node.getAttribute('fill') != 'none' && node.getAttribute('fill') != 'transparent') || - node.getAttribute('stroke') != 'none' || node.getAttribute('pointer-events') != 'none') - { - // LATER: Update existing DOM for performance - this.root.appendChild(node); - } - - this.node = null; - } -}; -/** - * Function: updateFill - * - * Transfers the stroke attributes from to . - */ -mxSvgCanvas2updateFill = ()=> -{ - var s = this.state; - - if (s.alpha < 1 || s.fillAlpha < 1) - { - this.node.setAttribute('fill-opacity', s.alpha * s.fillAlpha); - } - - if (s.fillColor != null) - { - if (s.gradientColor != null) - { - var id = this.getSvgGradient(String(s.fillColor), String(s.gradientColor), - s.gradientFillAlpha, s.gradientAlpha, s.gradientDirection); - - if (this.root.ownerDocument == document && this.useAbsoluteIds) - { - // Workaround for no fill with base tag in page (escape brackets) - var base = this.getBaseUrl().replace(/([\(\)])/g, '\\$1'); - this.node.setAttribute('fill', 'url(' + base + '#' + id + ')'); - } - else - { - this.node.setAttribute('fill', 'url(#' + id + ')'); - } - } - else - { - this.node.setAttribute('fill', String(s.fillColor).toLowerCase()); - } - } -}; - -/** - * Function: getCurrentStrokeWidth - * - * Returns the current stroke width (>= 1), ie. max(1, this.format(this.state.strokeWidth * this.state.scale)). - */ -mxSvgCanvas2getCurrentStrokeWidth = ()=> -{ - return Math.max(this.minStrokeWidth, Math.max(0.01, this.format(this.state.strokeWidth * this.state.scale))); -}; - -/** - * Function: updateStroke - * - * Transfers the stroke attributes from to . - */ -mxSvgCanvas2updateStroke = ()=> -{ - var s = this.state; - - this.node.setAttribute('stroke', String(s.strokeColor).toLowerCase()); - - if (s.alpha < 1 || s.strokeAlpha < 1) - { - this.node.setAttribute('stroke-opacity', s.alpha * s.strokeAlpha); - } - - var sw = this.getCurrentStrokeWidth(); - - if (sw != 1) - { - this.node.setAttribute('stroke-width', sw); - } - - if (this.node.nodeName == 'path') - { - this.updateStrokeAttributes(); - } - - if (s.dashed) - { - this.node.setAttribute('stroke-dasharray', this.createDashPattern( - ((s.fixDash) ? 1 : s.strokeWidth) * s.scale)); - } -}; - -/** - * Function: updateStrokeAttributes - * - * Transfers the stroke attributes from to . - */ -mxSvgCanvas2updateStrokeAttributes = ()=> -{ - var s = this.state; - - // Linejoin miter is default in SVG - if (s.lineJoin != null && s.lineJoin != 'miter') - { - this.node.setAttribute('stroke-linejoin', s.lineJoin); - } - - if (s.lineCap != null) - { - // flat is called butt in SVG - var value = s.lineCap; - - if (value == 'flat') - { - value = 'butt'; - } - - // Linecap butt is default in SVG - if (value != 'butt') - { - this.node.setAttribute('stroke-linecap', value); - } - } - - // Miterlimit 10 is default in our document - if (s.miterLimit != null && (!this.styleEnabled || s.miterLimit != 10)) - { - this.node.setAttribute('stroke-miterlimit', s.miterLimit); - } -}; - -/** - * Function: createDashPattern - * - * Creates the SVG dash pattern for the given state. - */ -mxSvgCanvas2createDashPattern = (scale)=> -{ - var pat = []; - - if (typeof(this.state.dashPattern) === 'string') - { - var dash = this.state.dashPattern.split(' '); - - if (dash.length > 0) - { - for (var i = 0; i < dash.length; i++) - { - pat[i] = Number(dash[i]) * scale; - } - } - } - - return pat.join(' '); -}; - -/** - * Function: createTolerance - * - * Creates a hit detection tolerance shape for the given node. - */ -mxSvgCanvas2createTolerance = (node)=> -{ - var tol = node.cloneNode(true); - var sw = parseFloat(tol.getAttribute('stroke-width') || 1) + this.strokeTolerance; - tol.setAttribute('pointer-events', 'stroke'); - tol.setAttribute('visibility', 'hidden'); - tol.removeAttribute('stroke-dasharray'); - tol.setAttribute('stroke-width', sw); - tol.setAttribute('fill', 'none'); - tol.setAttribute('stroke', 'white'); - return tol; -}; - -/** - * Function: createShadow - * - * Creates a shadow for the given node. - */ -mxSvgCanvas2createShadow = (node)=> -{ - var shadow = node.cloneNode(true); - var s = this.state; - - // Firefox uses transparent for no fill in ellipses - if (shadow.getAttribute('fill') != 'none' && (!mxClient.IS_FF || shadow.getAttribute('fill') != 'transparent')) - { - shadow.setAttribute('fill', s.shadowColor); - } - - if (shadow.getAttribute('stroke') != 'none') - { - shadow.setAttribute('stroke', s.shadowColor); - } - - shadow.setAttribute('transform', 'translate(' + this.format(s.shadowDx * s.scale) + - ',' + this.format(s.shadowDy * s.scale) + ')' + (s.transform || '')); - shadow.setAttribute('opacity', s.shadowAlpha); - - return shadow; -}; - -/** - * Function: setLink - * - * Experimental implementation for hyperlinks. - */ -mxSvgCanvas2setLink = (link)=> -{ - if (link == null) - { - this.root = this.originalRoot; - } - else - { - this.originalRoot = this.root; - - var node = this.createElement('a'); - - // Workaround for implicit namespace handling in HTML5 export, IE adds NS1 namespace so use code below - // in all IE versions except quirks mode. KNOWN: Adds xlink namespace to each image tag in output. - if (node.setAttributeNS == null || (this.root.ownerDocument != document)) - { - node.setAttribute('xlink:href', link); - } - else - { - node.setAttributeNS(mxConstants.NS_XLINK, 'xlink:href', link); - } - this.root.appendChild(node); - this.root = node; - } -}; + }; -/** - * Function: rotate - * - * Sets the rotation of the canvas. Note that rotation cannot be concatenated. - */ -mxSvgCanvas2rotate = (theta, flipH, flipV, cx, cy)=> -{ - if (theta != 0 || flipH || flipV) - { - var s = this.state; - cx += s.dx; - cy += s.dy; - - cx *= s.scale; - cy *= s.scale; + /** + * Function: convertHtml + * + * Converts the given HTML string to XHTML. + */ + mxSvgCanvas2convertHtml = (val) => { + if (this.useDomParser) { + var doc = new DOMParser().parseFromString(val, 'text/html'); - s.transform = s.transform || ''; - - // This implementation uses custom scale/translate and built-in rotation - // Rotation state is part of the AffineTransform in state.transform - if (flipH && flipV) - { - theta += 180; - } - else if (flipH != flipV) - { - var tx = (flipH) ? cx : 0; - var sx = (flipH) ? -1 : 1; - - var ty = (flipV) ? cy : 0; - var sy = (flipV) ? -1 : 1; + if (doc != null) { + val = new XMLSerializer().serializeToString(doc.body); - s.transform += 'translate(' + this.format(tx) + ',' + this.format(ty) + ')' + - 'scale(' + this.format(sx) + ',' + this.format(sy) + ')' + - 'translate(' + this.format(-tx) + ',' + this.format(-ty) + ')'; - } - - if (flipH ? !flipV : flipV) - { - theta *= -1; - } - - if (theta != 0) - { - s.transform += 'rotate(' + this.format(theta) + ',' + this.format(cx) + ',' + this.format(cy) + ')'; - } - - s.rotation = s.rotation + theta; - s.rotationCx = cx; - s.rotationCy = cy; - } -}; + // Extracts body content from DOM + if (val.substring(0, 5) == '', 5) + 1); + } -/** - * Function: begin - * - * Extends superclass to create path. - */ -mxSvgCanvas2begin = ()=> -{ - mxAbstractCanvas2begin.apply(this, arguments); - this.node = this.createElement('path'); -}; - -/** - * Function: rect - * - * Private helper function to create SVG elements - */ -mxSvgCanvas2rect = (x, y, w, h)=> -{ - var s = this.state; - var n = this.createElement('rect'); - n.setAttribute('x', this.format((x + s.dx) * s.scale)); - n.setAttribute('y', this.format((y + s.dy) * s.scale)); - n.setAttribute('width', this.format(w * s.scale)); - n.setAttribute('height', this.format(h * s.scale)); - - this.node = n; -}; - -/** - * Function: roundrect - * - * Private helper function to create SVG elements - */ -mxSvgCanvas2roundrect = (x, y, w, h, dx, dy)=> -{ - this.rect(x, y, w, h); - - if (dx > 0) - { - this.node.setAttribute('rx', this.format(dx * this.state.scale)); - } - - if (dy > 0) - { - this.node.setAttribute('ry', this.format(dy * this.state.scale)); - } -}; - -/** - * Function: ellipse - * - * Private helper function to create SVG elements - */ -mxSvgCanvas2ellipse = (x, y, w, h)=> -{ - var s = this.state; - var n = this.createElement('ellipse'); - // No rounding for consistent output with 1.x - n.setAttribute('cx', this.format((x + w / 2 + s.dx) * s.scale)); - n.setAttribute('cy', this.format((y + h / 2 + s.dy) * s.scale)); - n.setAttribute('rx', w / 2 * s.scale); - n.setAttribute('ry', h / 2 * s.scale); - this.node = n; -}; - -/** - * Function: image - * - * Private helper function to create SVG elements - */ -mxSvgCanvas2image = (x, y, w, h, src, aspect, flipH, flipV)=> -{ - src = this.converter.convert(src); - - // LATER: Add option for embedding images as base64. - aspect = (aspect != null) ? aspect : true; - flipH = (flipH != null) ? flipH : false; - flipV = (flipV != null) ? flipV : false; - - var s = this.state; - x += s.dx; - y += s.dy; - - var node = this.createElement('image'); - node.setAttribute('x', this.format(x * s.scale) + this.imageOffset); - node.setAttribute('y', this.format(y * s.scale) + this.imageOffset); - node.setAttribute('width', this.format(w * s.scale)); - node.setAttribute('height', this.format(h * s.scale)); - - // Workaround for missing namespace support - if (node.setAttributeNS == null) - { - node.setAttribute('xlink:href', src); - } - else - { - node.setAttributeNS(mxConstants.NS_XLINK, 'xlink:href', src); - } - - if (!aspect) - { - node.setAttribute('preserveAspectRatio', 'none'); - } - - if (s.alpha < 1 || s.fillAlpha < 1) - { - node.setAttribute('opacity', s.alpha * s.fillAlpha); - } - - var tr = this.state.transform || ''; - - if (flipH || flipV) - { - var sx = 1; - var sy = 1; - var dx = 0; - var dy = 0; - - if (flipH) - { - sx = -1; - dx = -w - 2 * x; - } - - if (flipV) - { - sy = -1; - dy = -h - 2 * y; - } - - // Adds image tansformation to existing transform - tr += 'scale(' + sx + ',' + sy + ')translate(' + (dx * s.scale) + ',' + (dy * s.scale) + ')'; - } - - if (tr.length > 0) - { - node.setAttribute('transform', tr); - } - - if (!this.pointerEvents) - { - node.setAttribute('pointer-events', 'none'); - } - - this.root.appendChild(node); -}; - -/** - * Function: convertHtml - * - * Converts the given HTML string to XHTML. - */ -mxSvgCanvas2convertHtml = (val)=> -{ - if (this.useDomParser) - { - var doc = new DOMParser().parseFromString(val, 'text/html'); - - if (doc != null) - { - val = new XMLSerializer().serializeToString(doc.body); - - // Extracts body content from DOM - if (val.substring(0, 5) == '', 5) + 1); + if (val.substring(val.length - 7, val.length) == '') { + val = val.substring(0, val.length - 7); + } } - - if (val.substring(val.length - 7, val.length) == '') - { - val = val.substring(0, val.length - 7); - } - } - } - else if (document.implementation != null && document.implementation.createDocument != null) - { - var xd = document.implementation.createDocument('http://www.w3.org/1999/xhtml', 'html', null); - var xb = xd.createElement('body'); - xd.documentElement.appendChild(xb); - - var div = document.createElement('div'); - div.innerHTML = val; - var child = div.firstChild; - - while (child != null) - { - var next = child.nextSibling; - xb.appendChild(xd.adoptNode(child)); - child = next; - } - - return xb.innerHTML; - } - else - { - var ta = document.createElement('textarea'); - - // Handles special HTML entities < and > and double escaping - // and converts unclosed br, hr and img tags to XHTML - // LATER: Convert all unclosed tags - ta.innerHTML = val.replace(/&/g, '&amp;'). - replace(/</g, '&lt;').replace(/>/g, '&gt;'). - replace(/</g, '&lt;').replace(/>/g, '&gt;'). - replace(//g, '>'); - val = ta.value.replace(/&/g, '&').replace(/&lt;/g, '<'). - replace(/&gt;/g, '>').replace(/&amp;/g, '&'). - replace(/
/g, '
').replace(/
/g, '
'). - replace(/(]+)>/gm, "$1 />"); - } - - return val; -}; + } else if (document.implementation != null && document.implementation.createDocument != null) { + var xd = document.implementation.createDocument('http://www.w3.org/1999/xhtml', 'html', null); + var xb = xd.createElement('body'); + xd.documentElement.appendChild(xb); -/** - * Function: createDiv - * - * Private helper function to create SVG elements - */ -mxSvgCanvas2createDiv = (str)=> -{ - var val = str; - - if (!mxUtils.isNode(val)) - { - val = '
' + this.convertHtml(val) + '
'; - } - - if (document.createElementNS) - { - var div = document.createElementNS('http://www.w3.org/1999/xhtml', 'div'); - - if (mxUtils.isNode(val)) - { - var div2 = document.createElement('div'); - var div3 = div2.cloneNode(false); - - // Creates a copy for export - if (this.root.ownerDocument != document) - { - div2.appendChild(val.cloneNode(true)); - } - else - { - div2.appendChild(val); - } - - div3.appendChild(div2); - div.appendChild(div3); - } - else - { + var div = document.createElement('div'); div.innerHTML = val; + var child = div.firstChild; + + while (child != null) { + var next = child.nextSibling; + xb.appendChild(xd.adoptNode(child)); + child = next; + } + + return xb.innerHTML; + } else { + var ta = document.createElement('textarea'); + + // Handles special HTML entities < and > and double escaping + // and converts unclosed br, hr and img tags to XHTML + // LATER: Convert all unclosed tags + ta.innerHTML = val.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(//g, '>'); + val = ta.value.replace(/&/g, '&').replace(/&lt;/g, '<').replace(/&gt;/g, '>').replace(/&amp;/g, '&').replace(/
/g, '
').replace(/
/g, '
').replace(/(]+)>/gm, "$1 />"); } - - return div; - } - else - { - if (mxUtils.isNode(val)) - { - val = '
' + mxUtils.getXml(val) + '
'; + + return val; + }; + + /** + * Function: createDiv + * + * Private helper function to create SVG elements + */ + mxSvgCanvas2createDiv = (str) => { + var val = str; + + if (!mxUtils.isNode(val)) { + val = '
' + this.convertHtml(val) + '
'; } - - val = '
' + val + '
'; - // NOTE: FF 3.6 crashes if content CSS contains "height:100%" - return mxUtils.parseXml(val).documentElement; - } -}; + if (document.createElementNS) { + var div = document.createElementNS('http://www.w3.org/1999/xhtml', 'div'); -/** - * Updates existing DOM nodes for text rendering. LATER: Merge common parts with text function below. - */ -mxSvgCanvas2updateText = (x, y, w, h, align, valign, wrap, overflow, clip, rotation, node)=> -{ - if (node != null && node.firstChild != null && node.firstChild.firstChild != null) - { - this.updateTextNodes(x, y, w, h, align, valign, wrap, overflow, clip, rotation, node.firstChild); - } -}; + if (mxUtils.isNode(val)) { + var div2 = document.createElement('div'); + var div3 = div2.cloneNode(false); -/** - * Function: addForeignObject - * - * Creates a foreignObject for the given string and adds it to the given root. - */ -mxSvgCanvas2addForeignObject = (x, y, w, h, str, align, valign, wrap, format, overflow, clip, rotation, dir, div, root)=> -{ - var group = this.createElement('g'); - var fo = this.createElement('foreignObject'); - - // Workarounds for print clipping and static position in Safari - fo.setAttribute('style', 'overflow: visible; text-align: left;'); - fo.setAttribute('pointer-events', 'none'); - - // Import needed for older versions of IE - if (div.ownerDocument != document) - { - div = mxUtils.importNodeImplementation(fo.ownerDocument, div, true); - } + // Creates a copy for export + if (this.root.ownerDocument != document) { + div2.appendChild(val.cloneNode(true)); + } else { + div2.appendChild(val); + } - fo.appendChild(div); - group.appendChild(fo); + div3.appendChild(div2); + div.appendChild(div3); + } else { + div.innerHTML = val; + } - this.updateTextNodes(x, y, w, h, align, valign, wrap, overflow, clip, rotation, group); - - // Alternate content if foreignObject not supported - if (this.root.ownerDocument != document) - { - var alt = this.createAlternateContent(fo, x, y, w, h, str, align, valign, wrap, format, overflow, clip, rotation); - - if (alt != null) - { - fo.setAttribute('requiredFeatures', 'http://www.w3.org/TR/SVG11/feature#Extensibility'); - var sw = this.createElement('switch'); - sw.appendChild(fo); - sw.appendChild(alt); - group.appendChild(sw); + return div; + } else { + if (mxUtils.isNode(val)) { + val = '
' + mxUtils.getXml(val) + '
'; + } + + val = '
' + val + '
'; + + // NOTE: FF 3.6 crashes if content CSS contains "height:100%" + return mxUtils.parseXml(val).documentElement; } - } - - root.appendChild(group); -}; + }; -/** - * Updates existing DOM nodes for text rendering. - */ -mxSvgCanvas2updateTextNodes = (x, y, w, h, align, valign, wrap, overflow, clip, rotation, g)=> -{ - var s = this.state.scale; + /** + * Updates existing DOM nodes for text rendering. LATER: Merge common parts with text function below. + */ + mxSvgCanvas2updateText = (x, y, w, h, align, valign, wrap, overflow, clip, rotation, node) => { + if (node != null && node.firstChild != null && node.firstChild.firstChild != null) { + this.updateTextNodes(x, y, w, h, align, valign, wrap, overflow, clip, rotation, node.firstChild); + } + }; - mxSvgCanvas2D.createCss(w + 2, h, align, valign, wrap, overflow, clip, - (this.state.fontBackgroundColor != null) ? this.state.fontBackgroundColor : null, - (this.state.fontBorderColor != null) ? this.state.fontBorderColor : null, - 'display: flex; align-items: unsafe ' + - ((valign == mxConstants.ALIGN_TOP) ? 'flex-start' : - ((valign == mxConstants.ALIGN_BOTTOM) ? 'flex-end' : 'center')) + '; ' + - 'justify-content: unsafe ' + ((align == mxConstants.ALIGN_LEFT) ? 'flex-start' : - ((align == mxConstants.ALIGN_RIGHT) ? 'flex-end' : 'center')) + '; ', - this.getTextCss(), s, mxUtils.bind(this, (dx, dy, flex, item, block)=> - { - x += this.state.dx; - y += this.state.dy; + /** + * Function: addForeignObject + * + * Creates a foreignObject for the given string and adds it to the given root. + */ + mxSvgCanvas2addForeignObject = (x, y, w, h, str, align, valign, wrap, format, overflow, clip, rotation, dir, div, root) => { + var group = this.createElement('g'); + var fo = this.createElement('foreignObject'); - var fo = g.firstChild; - var div = fo.firstChild; - var box = div.firstChild; - var text = box.firstChild; - var r = ((this.rotateHtml) ? this.state.rotation : 0) + ((rotation != null) ? rotation : 0); - var t = ((this.foOffset != 0) ? 'translate(' + this.foOffset + ' ' + this.foOffset + ')' : '') + - ((s != 1) ? 'scale(' + s + ')' : ''); - - text.setAttribute('style', block); - box.setAttribute('style', item); - - // Workaround for clipping in Webkit with scrolling and zoom - fo.setAttribute('width', Math.ceil(1 / Math.min(1, s) * 100) + '%'); - fo.setAttribute('height', Math.ceil(1 / Math.min(1, s) * 100) + '%'); - var yp = Math.round(y + dy); - - // Allows for negative values which are causing problems with - // transformed content where the top edge of the foreignObject - // limits the text box being moved further up in the diagram. - // KNOWN: Possible clipping problems with zoom and scrolling - // but this is normally not used with scrollbars as the - // coordinates are always positive with scrollbars. - // Margin-top is ignored in Safari and no negative values allowed - // for padding. - if (yp < 0) - { - fo.setAttribute('y', yp); - } - else - { - fo.removeAttribute('y'); - flex += 'padding-top: ' + yp + 'px; '; - } - - div.setAttribute('style', flex + 'margin-left: ' + Math.round(x + dx) + 'px;'); - t += ((r != 0) ? ('rotate(' + r + ' ' + x + ' ' + y + ')') : ''); + // Workarounds for print clipping and static position in Safari + fo.setAttribute('style', 'overflow: visible; text-align: left;'); + fo.setAttribute('pointer-events', 'none'); - // Output allows for reflow but Safari cannot use absolute position, - // transforms or opacity. https://bugs.webkit.org/show_bug.cgi?id=23113 - if (t != '') - { - g.setAttribute('transform', t); + // Import needed for older versions of IE + if (div.ownerDocument != document) { + div = mxUtils.importNodeImplementation(fo.ownerDocument, div, true); } - else - { - g.removeAttribute('transform'); - } - - if (this.state.alpha != 1) - { - g.setAttribute('opacity', this.state.alpha); - } - else - { - g.removeAttribute('opacity'); - } - })); -}; -/** - * Updates existing DOM nodes for text rendering. - */ -mxSvgCanvas2D.createCss = (w, h, align, valign, wrap, overflow, clip, bg, border, flex, block, s, callback)=> -{ - var item = 'box-sizing: border-box; font-size: 0; text-align: ' + ((align == mxConstants.ALIGN_LEFT) ? 'left' : - ((align == mxConstants.ALIGN_RIGHT) ? 'right' : 'center')) + '; '; - var pt = mxUtils.getAlignmentAsPoint(align, valign); - var ofl = 'overflow: hidden; '; - var fw = 'width: 1px; '; - var fh = 'height: 1px; '; - var dx = pt.x * w; - var dy = pt.y * h; - - if (clip) - { - fw = 'width: ' + Math.round(w) + 'px; '; - item += 'max-height: ' + Math.round(h) + 'px; '; - dy = 0; - } - else if (overflow == 'fill') - { - fw = 'width: ' + Math.round(w) + 'px; '; - fh = 'height: ' + Math.round(h) + 'px; '; - block += 'width: 100%; height: 100%; '; - item += fw + fh; - } - else if (overflow == 'width') - { - fw = 'width: ' + Math.round(w) + 'px; '; - block += 'width: 100%; '; - item += fw; - dy = 0; - - if (h > 0) - { + fo.appendChild(div); + group.appendChild(fo); + + this.updateTextNodes(x, y, w, h, align, valign, wrap, overflow, clip, rotation, group); + + // Alternate content if foreignObject not supported + if (this.root.ownerDocument != document) { + var alt = this.createAlternateContent(fo, x, y, w, h, str, align, valign, wrap, format, overflow, clip, rotation); + + if (alt != null) { + fo.setAttribute('requiredFeatures', 'http://www.w3.org/TR/SVG11/feature#Extensibility'); + var sw = this.createElement('switch'); + sw.appendChild(fo); + sw.appendChild(alt); + group.appendChild(sw); + } + } + + root.appendChild(group); + }; + + /** + * Updates existing DOM nodes for text rendering. + */ + mxSvgCanvas2updateTextNodes = (x, y, w, h, align, valign, wrap, overflow, clip, rotation, g) => { + var s = this.state.scale; + + mxSvgCanvas2D.createCss(w + 2, h, align, valign, wrap, overflow, clip, + (this.state.fontBackgroundColor != null) ? this.state.fontBackgroundColor : null, + (this.state.fontBorderColor != null) ? this.state.fontBorderColor : null, + 'display: flex; align-items: unsafe ' + + ((valign == mxConstants.ALIGN_TOP) ? 'flex-start' : + ((valign == mxConstants.ALIGN_BOTTOM) ? 'flex-end' : 'center')) + '; ' + + 'justify-content: unsafe ' + ((align == mxConstants.ALIGN_LEFT) ? 'flex-start' : + ((align == mxConstants.ALIGN_RIGHT) ? 'flex-end' : 'center')) + '; ', + this.getTextCss(), s, mxUtils.bind(this, (dx, dy, flex, item, block) => { + x += this.state.dx; + y += this.state.dy; + + var fo = g.firstChild; + var div = fo.firstChild; + var box = div.firstChild; + var text = box.firstChild; + var r = ((this.rotateHtml) ? this.state.rotation : 0) + ((rotation != null) ? rotation : 0); + var t = ((this.foOffset != 0) ? 'translate(' + this.foOffset + ' ' + this.foOffset + ')' : '') + + ((s != 1) ? 'scale(' + s + ')' : ''); + + text.setAttribute('style', block); + box.setAttribute('style', item); + + // Workaround for clipping in Webkit with scrolling and zoom + fo.setAttribute('width', Math.ceil(1 / Math.min(1, s) * 100) + '%'); + fo.setAttribute('height', Math.ceil(1 / Math.min(1, s) * 100) + '%'); + var yp = Math.round(y + dy); + + // Allows for negative values which are causing problems with + // transformed content where the top edge of the foreignObject + // limits the text box being moved further up in the diagram. + // KNOWN: Possible clipping problems with zoom and scrolling + // but this is normally not used with scrollbars as the + // coordinates are always positive with scrollbars. + // Margin-top is ignored in Safari and no negative values allowed + // for padding. + if (yp < 0) { + fo.setAttribute('y', yp); + } else { + fo.removeAttribute('y'); + flex += 'padding-top: ' + yp + 'px; '; + } + + div.setAttribute('style', flex + 'margin-left: ' + Math.round(x + dx) + 'px;'); + t += ((r != 0) ? ('rotate(' + r + ' ' + x + ' ' + y + ')') : ''); + + // Output allows for reflow but Safari cannot use absolute position, + // transforms or opacity. https://bugs.webkit.org/show_bug.cgi?id=23113 + if (t != '') { + g.setAttribute('transform', t); + } else { + g.removeAttribute('transform'); + } + + if (this.state.alpha != 1) { + g.setAttribute('opacity', this.state.alpha); + } else { + g.removeAttribute('opacity'); + } + })); + }; + + /** + * Updates existing DOM nodes for text rendering. + */ + mxSvgCanvas2D +. + createCss = (w, h, align, valign, wrap, overflow, clip, bg, border, flex, block, s, callback) => { + var item = 'box-sizing: border-box; font-size: 0; text-align: ' + ((align == mxConstants.ALIGN_LEFT) ? 'left' : + ((align == mxConstants.ALIGN_RIGHT) ? 'right' : 'center')) + '; '; + var pt = mxUtils.getAlignmentAsPoint(align, valign); + var ofl = 'overflow: hidden; '; + var fw = 'width: 1px; '; + var fh = 'height: 1px; '; + var dx = pt.x * w; + var dy = pt.y * h; + + if (clip) { + fw = 'width: ' + Math.round(w) + 'px; '; item += 'max-height: ' + Math.round(h) + 'px; '; - } - } - else - { - ofl = ''; - dy = 0; - } - - var bgc = ''; - - if (bg != null) - { - bgc += 'background-color: ' + bg + '; '; - } - - if (border != null) - { - bgc += 'border: 1px solid ' + border + '; '; - } - - if (ofl == '' || clip) - { - block += bgc; - } - else - { - item += bgc; - } + dy = 0; + } else if (overflow == 'fill') { + fw = 'width: ' + Math.round(w) + 'px; '; + fh = 'height: ' + Math.round(h) + 'px; '; + block += 'width: 100%; height: 100%; '; + item += fw + fh; + } else if (overflow == 'width') { + fw = 'width: ' + Math.round(w) + 'px; '; + block += 'width: 100%; '; + item += fw; + dy = 0; - if (wrap && w > 0) - { - block += 'white-space: normal; word-wrap: ' + mxConstants.WORD_WRAP + '; '; - fw = 'width: ' + Math.round(w) + 'px; '; - - if (ofl != '' && overflow != 'fill') - { + if (h > 0) { + item += 'max-height: ' + Math.round(h) + 'px; '; + } + } else { + ofl = ''; dy = 0; } - } - else - { - block += 'white-space: nowrap; '; - - if (ofl == '') - { - dx = 0; + + var bgc = ''; + + if (bg != null) { + bgc += 'background-color: ' + bg + '; '; } - } - - callback(dx, dy, flex + fw + fh, item + ofl, block, ofl); -}; -/** - * Function: getTextCss - * - * Private helper function to create SVG elements - */ -mxSvgCanvas2getTextCss = ()=> -{ - var s = this.state; - var lh = (mxConstants.ABSOLUTE_LINE_HEIGHT) ? (s.fontSize * mxConstants.LINE_HEIGHT) + 'px' : - (mxConstants.LINE_HEIGHT * this.lineHeightCorrection); - var css = 'display: inline-block; font-size: ' + s.fontSize + 'px; ' + - 'font-family: ' + s.fontFamily + '; color: ' + s.fontColor + '; line-height: ' + lh + - '; pointer-events: ' + ((this.pointerEvents) ? this.pointerEventsValue : 'none') + '; '; - - if ((s.fontStyle & mxConstants.FONT_BOLD) == mxConstants.FONT_BOLD) - { - css += 'font-weight: bold; '; - } + if (border != null) { + bgc += 'border: 1px solid ' + border + '; '; + } - if ((s.fontStyle & mxConstants.FONT_ITALIC) == mxConstants.FONT_ITALIC) - { - css += 'font-style: italic; '; - } + if (ofl == '' || clip) { + block += bgc; + } else { + item += bgc; + } - var deco = []; - - if ((s.fontStyle & mxConstants.FONT_UNDERLINE) == mxConstants.FONT_UNDERLINE) - { - deco.push('underline'); - } - - if ((s.fontStyle & mxConstants.FONT_STRIKETHROUGH) == mxConstants.FONT_STRIKETHROUGH) - { - deco.push('line-through'); - } - - if (deco.length > 0) - { - css += 'text-decoration: ' + deco.join(' ') + '; '; - } + if (wrap && w > 0) { + block += 'white-space: normal; word-wrap: ' + mxConstants.WORD_WRAP + '; '; + fw = 'width: ' + Math.round(w) + 'px; '; - return css; -}; + if (ofl != '' && overflow != 'fill') { + dy = 0; + } + } else { + block += 'white-space: nowrap; '; -/** - * Function: text - * - * Paints the given text. Possible values for format are empty string for plain - * text and html for HTML markup. Note that HTML markup is only supported if - * foreignObject is supported and is true. (This means IE9 and later - * does currently not support HTML text as part of shapes.) - */ -mxSvgCanvas2text = (x, y, w, h, str, align, valign, wrap, format, overflow, clip, rotation, dir)=> -{ - if (this.textEnabled && str != null) - { - rotation = (rotation != null) ? rotation : 0; + if (ofl == '') { + dx = 0; + } + } - if (this.foEnabled && format == 'html') - { - var div = this.createDiv(str); - - // Ignores invalid XHTML labels - if (div != null) - { - if (dir != null) - { - div.setAttribute('dir', dir); + callback(dx, dy, flex + fw + fh, item + ofl, block, ofl); + }; + + /** + * Function: getTextCss + * + * Private helper function to create SVG elements + */ + mxSvgCanvas2getTextCss = () => { + var s = this.state; + var lh = (mxConstants.ABSOLUTE_LINE_HEIGHT) ? (s.fontSize * mxConstants.LINE_HEIGHT) + 'px' : + (mxConstants.LINE_HEIGHT * this.lineHeightCorrection); + var css = 'display: inline-block; font-size: ' + s.fontSize + 'px; ' + + 'font-family: ' + s.fontFamily + '; color: ' + s.fontColor + '; line-height: ' + lh + + '; pointer-events: ' + ((this.pointerEvents) ? this.pointerEventsValue : 'none') + '; '; + + if ((s.fontStyle & mxConstants.FONT_BOLD) == mxConstants.FONT_BOLD) { + css += 'font-weight: bold; '; + } + + if ((s.fontStyle & mxConstants.FONT_ITALIC) == mxConstants.FONT_ITALIC) { + css += 'font-style: italic; '; + } + + var deco = []; + + if ((s.fontStyle & mxConstants.FONT_UNDERLINE) == mxConstants.FONT_UNDERLINE) { + deco.push('underline'); + } + + if ((s.fontStyle & mxConstants.FONT_STRIKETHROUGH) == mxConstants.FONT_STRIKETHROUGH) { + deco.push('line-through'); + } + + if (deco.length > 0) { + css += 'text-decoration: ' + deco.join(' ') + '; '; + } + + return css; + }; + + /** + * Function: text + * + * Paints the given text. Possible values for format are empty string for plain + * text and html for HTML markup. Note that HTML markup is only supported if + * foreignObject is supported and is true. (This means IE9 and later + * does currently not support HTML text as part of shapes.) + */ + mxSvgCanvas2text = (x, y, w, h, str, align, valign, wrap, format, overflow, clip, rotation, dir) => { + if (this.textEnabled && str != null) { + rotation = (rotation != null) ? rotation : 0; + + if (this.foEnabled && format == 'html') { + var div = this.createDiv(str); + + // Ignores invalid XHTML labels + if (div != null) { + if (dir != null) { + div.setAttribute('dir', dir); + } + + this.addForeignObject(x, y, w, h, str, align, valign, wrap, + format, overflow, clip, rotation, dir, div, this.root); } - - this.addForeignObject(x, y, w, h, str, align, valign, wrap, - format, overflow, clip, rotation, dir, div, this.root); + } else { + this.plainText(x + this.state.dx, y + this.state.dy, w, h, str, + align, valign, wrap, overflow, clip, rotation, dir); } } - else - { - this.plainText(x + this.state.dx, y + this.state.dy, w, h, str, - align, valign, wrap, overflow, clip, rotation, dir); + }; + + /** + * Function: createClip + * + * Creates a clip for the given coordinates. + */ + mxSvgCanvas2createClip = (x, y, w, h) => { + x = Math.round(x); + y = Math.round(y); + w = Math.round(w); + h = Math.round(h); + + var id = 'mx-clip-' + x + '-' + y + '-' + w + '-' + h; + + var counter = 0; + var tmp = id + '-' + counter; + + // Resolves ID conflicts + while (document.getElementById(tmp) != null) { + tmp = id + '-' + (++counter); } - } -}; -/** - * Function: createClip - * - * Creates a clip for the given coordinates. - */ -mxSvgCanvas2createClip = (x, y, w, h)=> -{ - x = Math.round(x); - y = Math.round(y); - w = Math.round(w); - h = Math.round(h); - - var id = 'mx-clip-' + x + '-' + y + '-' + w + '-' + h; + clip = this.createElement('clipPath'); + clip.setAttribute('id', tmp); - var counter = 0; - var tmp = id + '-' + counter; - - // Resolves ID conflicts - while (document.getElementById(tmp) != null) - { - tmp = id + '-' + (++counter); - } - - clip = this.createElement('clipPath'); - clip.setAttribute('id', tmp); - - var rect = this.createElement('rect'); - rect.setAttribute('x', x); - rect.setAttribute('y', y); - rect.setAttribute('width', w); - rect.setAttribute('height', h); - - clip.appendChild(rect); - - return clip; -}; + var rect = this.createElement('rect'); + rect.setAttribute('x', x); + rect.setAttribute('y', y); + rect.setAttribute('width', w); + rect.setAttribute('height', h); -/** - * Function: plainText - * - * Paints the given text. Possible values for format are empty string for - * plain text and html for HTML markup. - */ -mxSvgCanvas2plainText = (x, y, w, h, str, align, valign, wrap, overflow, clip, rotation, dir)=> -{ - rotation = (rotation != null) ? rotation : 0; - var s = this.state; - var size = s.fontSize; - var node = this.createElement('g'); - var tr = s.transform || ''; - this.updateFont(node); - - // Ignores pointer events - if (!this.pointerEvents && this.originalRoot == null) - { - node.setAttribute('pointer-events', 'none'); - } - - // Non-rotated text - if (rotation != 0) - { - tr += 'rotate(' + rotation + ',' + this.format(x * s.scale) + ',' + this.format(y * s.scale) + ')'; - } - - if (dir != null) - { - node.setAttribute('direction', dir); - } + clip.appendChild(rect); - if (clip && w > 0 && h > 0) - { - var cx = x; - var cy = y; - - if (align == mxConstants.ALIGN_CENTER) - { - cx -= w / 2; + return clip; + }; + + /** + * Function: plainText + * + * Paints the given text. Possible values for format are empty string for + * plain text and html for HTML markup. + */ + mxSvgCanvas2plainText = (x, y, w, h, str, align, valign, wrap, overflow, clip, rotation, dir) => { + rotation = (rotation != null) ? rotation : 0; + var s = this.state; + var size = s.fontSize; + var node = this.createElement('g'); + var tr = s.transform || ''; + this.updateFont(node); + + // Ignores pointer events + if (!this.pointerEvents && this.originalRoot == null) { + node.setAttribute('pointer-events', 'none'); } - else if (align == mxConstants.ALIGN_RIGHT) - { - cx -= w; + + // Non-rotated text + if (rotation != 0) { + tr += 'rotate(' + rotation + ',' + this.format(x * s.scale) + ',' + this.format(y * s.scale) + ')'; } - - if (overflow != 'fill') - { - if (valign == mxConstants.ALIGN_MIDDLE) - { + + if (dir != null) { + node.setAttribute('direction', dir); + } + + if (clip && w > 0 && h > 0) { + var cx = x; + var cy = y; + + if (align == mxConstants.ALIGN_CENTER) { + cx -= w / 2; + } else if (align == mxConstants.ALIGN_RIGHT) { + cx -= w; + } + + if (overflow != 'fill') { + if (valign == mxConstants.ALIGN_MIDDLE) { + cy -= h / 2; + } else if (valign == mxConstants.ALIGN_BOTTOM) { + cy -= h; + } + } + + // LATER: Remove spacing from clip rectangle + var c = this.createClip(cx * s.scale - 2, cy * s.scale - 2, w * s.scale + 4, h * s.scale + 4); + + if (this.defs != null) { + this.defs.appendChild(c); + } else { + // Makes sure clip is removed with referencing node + this.root.appendChild(c); + } + + if (!mxClient.IS_CHROMEAPP && !mxClient.IS_EDGE && this.root.ownerDocument == document) { + // Workaround for potential base tag + var base = this.getBaseUrl().replace(/([\(\)])/g, '\\$1'); + node.setAttribute('clip-path', 'url(' + base + '#' + c.getAttribute('id') + ')'); + } else { + node.setAttribute('clip-path', 'url(#' + c.getAttribute('id') + ')'); + } + } + + // Default is left + var anchor = (align == mxConstants.ALIGN_RIGHT) ? 'end' : + (align == mxConstants.ALIGN_CENTER) ? 'middle' : + 'start'; + + // Text-anchor start is default in SVG + if (anchor != 'start') { + node.setAttribute('text-anchor', anchor); + } + + if (!this.styleEnabled || size != mxConstants.DEFAULT_FONTSIZE) { + node.setAttribute('font-size', (size * s.scale) + 'px'); + } + + if (tr.length > 0) { + node.setAttribute('transform', tr); + } + + if (s.alpha < 1) { + node.setAttribute('opacity', s.alpha); + } + + var lines = str.split('\n'); + var lh = Math.round(size * mxConstants.LINE_HEIGHT); + var textHeight = size + (lines.length - 1) * lh; + + var cy = y + size - 1; + + if (valign == mxConstants.ALIGN_MIDDLE) { + if (overflow == 'fill') { cy -= h / 2; + } else { + var dy = ((this.matchHtmlAlignment && clip && h > 0) ? Math.min(textHeight, h) : textHeight) / 2; + cy -= dy; } - else if (valign == mxConstants.ALIGN_BOTTOM) - { + } else if (valign == mxConstants.ALIGN_BOTTOM) { + if (overflow == 'fill') { cy -= h; + } else { + var dy = (this.matchHtmlAlignment && clip && h > 0) ? Math.min(textHeight, h) : textHeight; + cy -= dy + 1; } } - - // LATER: Remove spacing from clip rectangle - var c = this.createClip(cx * s.scale - 2, cy * s.scale - 2, w * s.scale + 4, h * s.scale + 4); - - if (this.defs != null) - { - this.defs.appendChild(c); - } - else - { - // Makes sure clip is removed with referencing node - this.root.appendChild(c); - } - - if (!mxClient.IS_CHROMEAPP && !mxClient.IS_EDGE && this.root.ownerDocument == document) - { - // Workaround for potential base tag - var base = this.getBaseUrl().replace(/([\(\)])/g, '\\$1'); - node.setAttribute('clip-path', 'url(' + base + '#' + c.getAttribute('id') + ')'); - } - else - { - node.setAttribute('clip-path', 'url(#' + c.getAttribute('id') + ')'); - } - } - // Default is left - var anchor = (align == mxConstants.ALIGN_RIGHT) ? 'end' : - (align == mxConstants.ALIGN_CENTER) ? 'middle' : - 'start'; + for (var i = 0; i < lines.length; i++) { + // Workaround for bounding box of empty lines and spaces + if (lines[i].length > 0 && mxUtils.trim(lines[i]).length > 0) { + var text = this.createElement('text'); + // LATER: Match horizontal HTML alignment + text.setAttribute('x', this.format(x * s.scale) + this.textOffset); + text.setAttribute('y', this.format(cy * s.scale) + this.textOffset); - // Text-anchor start is default in SVG - if (anchor != 'start') - { - node.setAttribute('text-anchor', anchor); - } - - if (!this.styleEnabled || size != mxConstants.DEFAULT_FONTSIZE) - { - node.setAttribute('font-size', (size * s.scale) + 'px'); - } - - if (tr.length > 0) - { - node.setAttribute('transform', tr); - } - - if (s.alpha < 1) - { - node.setAttribute('opacity', s.alpha); - } - - var lines = str.split('\n'); - var lh = Math.round(size * mxConstants.LINE_HEIGHT); - var textHeight = size + (lines.length - 1) * lh; - - var cy = y + size - 1; - - if (valign == mxConstants.ALIGN_MIDDLE) - { - if (overflow == 'fill') - { - cy -= h / 2; - } - else - { - var dy = ((this.matchHtmlAlignment && clip && h > 0) ? Math.min(textHeight, h) : textHeight) / 2; - cy -= dy; - } - } - else if (valign == mxConstants.ALIGN_BOTTOM) - { - if (overflow == 'fill') - { - cy -= h; - } - else - { - var dy = (this.matchHtmlAlignment && clip && h > 0) ? Math.min(textHeight, h) : textHeight; - cy -= dy + 1; - } - } - - for (var i = 0; i < lines.length; i++) - { - // Workaround for bounding box of empty lines and spaces - if (lines[i].length > 0 && mxUtils.trim(lines[i]).length > 0) - { - var text = this.createElement('text'); - // LATER: Match horizontal HTML alignment - text.setAttribute('x', this.format(x * s.scale) + this.textOffset); - text.setAttribute('y', this.format(cy * s.scale) + this.textOffset); - - mxUtils.write(text, lines[i]); - node.appendChild(text); - } - - cy += lh; - } - - this.root.appendChild(node); - this.addTextBackground(node, str, x, y, w, (overflow == 'fill') ? h : textHeight, align, valign, overflow); -}; - -/** - * Function: updateFont - * - * Updates the text properties for the given node. (NOTE: For this to work in - * IE, the given node must be a text or tspan element.) - */ -mxSvgCanvas2updateFont = (node)=> -{ - var s = this.state; - - node.setAttribute('fill', s.fontColor); - - if (!this.styleEnabled || s.fontFamily != mxConstants.DEFAULT_FONTFAMILY) - { - node.setAttribute('font-family', s.fontFamily); - } - - if ((s.fontStyle & mxConstants.FONT_BOLD) == mxConstants.FONT_BOLD) - { - node.setAttribute('font-weight', 'bold'); - } - - if ((s.fontStyle & mxConstants.FONT_ITALIC) == mxConstants.FONT_ITALIC) - { - node.setAttribute('font-style', 'italic'); - } - - var txtDecor = []; - - if ((s.fontStyle & mxConstants.FONT_UNDERLINE) == mxConstants.FONT_UNDERLINE) - { - txtDecor.push('underline'); - } - - if ((s.fontStyle & mxConstants.FONT_STRIKETHROUGH) == mxConstants.FONT_STRIKETHROUGH) - { - txtDecor.push('line-through'); - } - - if (txtDecor.length > 0) - { - node.setAttribute('text-decoration', txtDecor.join(' ')); - } -}; - -/** - * Function: addTextBackground - * - * Background color and border - */ -mxSvgCanvas2addTextBackground = (node, str, x, y, w, h, align, valign, overflow)=> -{ - var s = this.state; - - if (s.fontBackgroundColor != null || s.fontBorderColor != null) - { - var bbox = null; - - if (overflow == 'fill' || overflow == 'width') - { - if (align == mxConstants.ALIGN_CENTER) - { - x -= w / 2; - } - else if (align == mxConstants.ALIGN_RIGHT) - { - x -= w; - } - - if (valign == mxConstants.ALIGN_MIDDLE) - { - y -= h / 2; - } - else if (valign == mxConstants.ALIGN_BOTTOM) - { - y -= h; - } - - bbox = new mxRectangle((x + 1) * s.scale, y * s.scale, (w - 2) * s.scale, (h + 2) * s.scale); - } - else if (node.getBBox != null && this.root.ownerDocument == document) - { - // Uses getBBox only if inside document for correct size - try - { - bbox = node.getBBox(); - bbox = new mxRectangle(bbox.x, bbox.y + 1, bbox.width, bbox.height + 0); - } - catch (e) - { - // Ignores NS_ERROR_FAILURE in FF if container display is none. - } - } - - if (bbox == null || bbox.width == 0 || bbox.height == 0) - { - // Computes size if not in document or no getBBox available - var div = document.createElement('div'); - - // Wrapping and clipping can be ignored here - div.style.lineHeight = (mxConstants.ABSOLUTE_LINE_HEIGHT) ? (s.fontSize * mxConstants.LINE_HEIGHT) + 'px' : mxConstants.LINE_HEIGHT; - div.style.fontSize = s.fontSize + 'px'; - div.style.fontFamily = s.fontFamily; - div.style.whiteSpace = 'nowrap'; - div.style.position = 'absolute'; - div.style.visibility = 'hidden'; - div.style.display = 'inline-block'; - - if ((s.fontStyle & mxConstants.FONT_BOLD) == mxConstants.FONT_BOLD) - { - div.style.fontWeight = 'bold'; + mxUtils.write(text, lines[i]); + node.appendChild(text); } - if ((s.fontStyle & mxConstants.FONT_ITALIC) == mxConstants.FONT_ITALIC) - { - div.style.fontStyle = 'italic'; - } - - str = mxUtils.htmlEntities(str, false); - div.innerHTML = str.replace(/\n/g, '
'); - - document.body.appendChild(div); - var w = div.offsetWidth; - var h = div.offsetHeight; - div.parentNode.removeChild(div); - - if (align == mxConstants.ALIGN_CENTER) - { - x -= w / 2; - } - else if (align == mxConstants.ALIGN_RIGHT) - { - x -= w; - } - - if (valign == mxConstants.ALIGN_MIDDLE) - { - y -= h / 2; - } - else if (valign == mxConstants.ALIGN_BOTTOM) - { - y -= h; - } - - bbox = new mxRectangle((x + 1) * s.scale, (y + 2) * s.scale, w * s.scale, (h + 1) * s.scale); + cy += lh; } - - if (bbox != null) - { - var n = this.createElement('rect'); - n.setAttribute('fill', s.fontBackgroundColor || 'none'); - n.setAttribute('stroke', s.fontBorderColor || 'none'); - n.setAttribute('x', Math.floor(bbox.x - 1)); - n.setAttribute('y', Math.floor(bbox.y - 1)); - n.setAttribute('width', Math.ceil(bbox.width + 2)); - n.setAttribute('height', Math.ceil(bbox.height)); - var sw = (s.fontBorderColor != null) ? Math.max(1, this.format(s.scale)) : 0; - n.setAttribute('stroke-width', sw); - - // Workaround for crisp rendering - only required if not exporting - if (this.root.ownerDocument == document && mxUtils.mod(sw, 2) == 1) - { - n.setAttribute('transform', 'translate(0.5, 0.5)'); - } - - node.insertBefore(n, node.firstChild); + this.root.appendChild(node); + this.addTextBackground(node, str, x, y, w, (overflow == 'fill') ? h : textHeight, align, valign, overflow); + }; + + /** + * Function: updateFont + * + * Updates the text properties for the given node. (NOTE: For this to work in + * IE, the given node must be a text or tspan element.) + */ + mxSvgCanvas2updateFont = (node) => { + var s = this.state; + + node.setAttribute('fill', s.fontColor); + + if (!this.styleEnabled || s.fontFamily != mxConstants.DEFAULT_FONTFAMILY) { + node.setAttribute('font-family', s.fontFamily); } - } -}; -/** - * Function: stroke - * - * Paints the outline of the current path. - */ -mxSvgCanvas2stroke = ()=> -{ - this.addNode(false, true); -}; + if ((s.fontStyle & mxConstants.FONT_BOLD) == mxConstants.FONT_BOLD) { + node.setAttribute('font-weight', 'bold'); + } -/** - * Function: fill - * - * Fills the current path. - */ -mxSvgCanvas2fill = ()=> -{ - this.addNode(true, false); -}; + if ((s.fontStyle & mxConstants.FONT_ITALIC) == mxConstants.FONT_ITALIC) { + node.setAttribute('font-style', 'italic'); + } -/** - * Function: fillAndStroke - * - * Fills and paints the outline of the current path. - */ -mxSvgCanvas2fillAndStroke = ()=> -{ - this.addNode(true, true); -}; + var txtDecor = []; + + if ((s.fontStyle & mxConstants.FONT_UNDERLINE) == mxConstants.FONT_UNDERLINE) { + txtDecor.push('underline'); + } + + if ((s.fontStyle & mxConstants.FONT_STRIKETHROUGH) == mxConstants.FONT_STRIKETHROUGH) { + txtDecor.push('line-through'); + } + + if (txtDecor.length > 0) { + node.setAttribute('text-decoration', txtDecor.join(' ')); + } + }; + + /** + * Function: addTextBackground + * + * Background color and border + */ + mxSvgCanvas2addTextBackground = (node, str, x, y, w, h, align, valign, overflow) => { + var s = this.state; + + if (s.fontBackgroundColor != null || s.fontBorderColor != null) { + var bbox = null; + + if (overflow == 'fill' || overflow == 'width') { + if (align == mxConstants.ALIGN_CENTER) { + x -= w / 2; + } else if (align == mxConstants.ALIGN_RIGHT) { + x -= w; + } + + if (valign == mxConstants.ALIGN_MIDDLE) { + y -= h / 2; + } else if (valign == mxConstants.ALIGN_BOTTOM) { + y -= h; + } + + bbox = new mxRectangle((x + 1) * s.scale, y * s.scale, (w - 2) * s.scale, (h + 2) * s.scale); + } else if (node.getBBox != null && this.root.ownerDocument == document) { + // Uses getBBox only if inside document for correct size + try { + bbox = node.getBBox(); + bbox = new mxRectangle(bbox.x, bbox.y + 1, bbox.width, bbox.height + 0); + } catch (e) { + // Ignores NS_ERROR_FAILURE in FF if container display is none. + } + } + + if (bbox == null || bbox.width == 0 || bbox.height == 0) { + // Computes size if not in document or no getBBox available + var div = document.createElement('div'); + + // Wrapping and clipping can be ignored here + div.style.lineHeight = (mxConstants.ABSOLUTE_LINE_HEIGHT) ? (s.fontSize * mxConstants.LINE_HEIGHT) + 'px' : mxConstants.LINE_HEIGHT; + div.style.fontSize = s.fontSize + 'px'; + div.style.fontFamily = s.fontFamily; + div.style.whiteSpace = 'nowrap'; + div.style.position = 'absolute'; + div.style.visibility = 'hidden'; + div.style.display = 'inline-block'; + + if ((s.fontStyle & mxConstants.FONT_BOLD) == mxConstants.FONT_BOLD) { + div.style.fontWeight = 'bold'; + } + + if ((s.fontStyle & mxConstants.FONT_ITALIC) == mxConstants.FONT_ITALIC) { + div.style.fontStyle = 'italic'; + } + + str = mxUtils.htmlEntities(str, false); + div.innerHTML = str.replace(/\n/g, '
'); + + document.body.appendChild(div); + var w = div.offsetWidth; + var h = div.offsetHeight; + div.parentNode.removeChild(div); + + if (align == mxConstants.ALIGN_CENTER) { + x -= w / 2; + } else if (align == mxConstants.ALIGN_RIGHT) { + x -= w; + } + + if (valign == mxConstants.ALIGN_MIDDLE) { + y -= h / 2; + } else if (valign == mxConstants.ALIGN_BOTTOM) { + y -= h; + } + + bbox = new mxRectangle((x + 1) * s.scale, (y + 2) * s.scale, w * s.scale, (h + 1) * s.scale); + } + + if (bbox != null) { + var n = this.createElement('rect'); + n.setAttribute('fill', s.fontBackgroundColor || 'none'); + n.setAttribute('stroke', s.fontBorderColor || 'none'); + n.setAttribute('x', Math.floor(bbox.x - 1)); + n.setAttribute('y', Math.floor(bbox.y - 1)); + n.setAttribute('width', Math.ceil(bbox.width + 2)); + n.setAttribute('height', Math.ceil(bbox.height)); + + var sw = (s.fontBorderColor != null) ? Math.max(1, this.format(s.scale)) : 0; + n.setAttribute('stroke-width', sw); + + // Workaround for crisp rendering - only required if not exporting + if (this.root.ownerDocument == document && mxUtils.mod(sw, 2) == 1) { + n.setAttribute('transform', 'translate(0.5, 0.5)'); + } + + node.insertBefore(n, node.firstChild); + } + } + }; + + /** + * Function: stroke + * + * Paints the outline of the current path. + */ + mxSvgCanvas2stroke = () => { + this.addNode(false, true); + }; + + /** + * Function: fill + * + * Fills the current path. + */ + mxSvgCanvas2fill = () => { + this.addNode(true, false); + }; + + /** + * Function: fillAndStroke + * + * Fills and paints the outline of the current path. + */ + mxSvgCanvas2fillAndStroke = () => { + this.addNode(true, true); + }; +} + +export default mxSvgCanvas2D; diff --git a/src/js/util/mxToolbar.js b/src/js/util/mxToolbar.js index 6cee18e80..a3caaea75 100644 --- a/src/js/util/mxToolbar.js +++ b/src/js/util/mxToolbar.js @@ -2,526 +2,468 @@ * Copyright (c) 2006-2015, JGraph Ltd * Copyright (c) 2006-2015, Gaudenz Alder */ -/** - * Class: mxToolbar - * - * Creates a toolbar inside a given DOM node. The toolbar may contain icons, - * buttons and combo boxes. - * - * Event: mxEvent.SELECT - * - * Fires when an item was selected in the toolbar. The function - * property contains the function that was selected in . - * - * Constructor: mxToolbar - * - * Constructs a toolbar in the specified container. - * - * Parameters: - * - * container - DOM node that contains the toolbar. - */ -function mxToolbar(container) -{ - this.container = container; -}; -/** - * Extends mxEventSource. - */ -mxToolbar.prototype = new mxEventSource(); -constructor = mxToolbar; +import mxUtils from "./mxUtils"; +import mxPoint from "./mxPoint"; +import mxPopupMenu from "./mxPopupMenu"; +import mxEventSource from "./mxEventSource"; +import mxEventObject from "./mxEventObject"; -/** - * Variable: container - * - * Reference to the DOM nodes that contains the toolbar. - */ -container = null; +class mxToolbar extends mxEventSource { + /** + * Variable: container + * + * Reference to the DOM nodes that contains the toolbar. + */ + container = null; -/** - * Variable: enabled - * - * Specifies if events are handled. Default is true. - */ -enabled = true; + /** + * Variable: enabled + * + * Specifies if events are handled. Default is true. + */ + enabled = true; -/** - * Variable: noReset - * - * Specifies if requires a forced flag of true for resetting - * the current mode in the toolbar. Default is false. This is set to true - * if the toolbar item is double clicked to avoid a reset after a single - * use of the item. - */ -noReset = false; + /** + * Variable: noReset + * + * Specifies if requires a forced flag of true for resetting + * the current mode in the toolbar. Default is false. This is set to true + * if the toolbar item is double clicked to avoid a reset after a single + * use of the item. + */ + noReset = false; -/** - * Variable: updateDefaultMode - * - * Boolean indicating if the default mode should be the last selected - * switch mode or the first inserted switch mode. Default is true, that - * is the last selected switch mode is the default mode. The default mode - * is the mode to be selected after a reset of the toolbar. If this is - * false, then the default mode is the first inserted mode item regardless - * of what was last selected. Otherwise, the selected item after a reset is - * the previously selected item. - */ -updateDefaultMode = true; + /** + * Variable: updateDefaultMode + * + * Boolean indicating if the default mode should be the last selected + * switch mode or the first inserted switch mode. Default is true, that + * is the last selected switch mode is the default mode. The default mode + * is the mode to be selected after a reset of the toolbar. If this is + * false, then the default mode is the first inserted mode item regardless + * of what was last selected. Otherwise, the selected item after a reset is + * the previously selected item. + */ + updateDefaultMode = true; -/** - * Function: addItem - * - * Adds the given function as an image with the specified title and icon - * and returns the new image node. - * - * Parameters: - * - * title - Optional string that is used as the tooltip. - * icon - Optional URL of the image to be used. If no URL is given, then a - * button is created. - * funct - Function to execute on a mouse click. - * pressedIcon - Optional URL of the pressed image. Default is a gray - * background. - * style - Optional style classname. Default is mxToolbarItem. - * factoryMethod - Optional factory method for popup menu, eg. - * (menu, evt, cell)=> { menu.addItem('Hello, World!'); } - */ -addItem = (title, icon, funct, pressedIcon, style, factoryMethod)=> -{ - var img = document.createElement((icon != null) ? 'img' : 'button'); - var initialClassName = style || ((factoryMethod != null) ? - 'mxToolbarMode' : 'mxToolbarItem'); - img.className = initialClassName; - img.setAttribute('src', icon); - - if (title != null) - { - if (icon != null) - { - img.setAttribute('title', title); - } - else - { - mxUtils.write(img, title); - } - } - - this.container.appendChild(img); + /** + * Class: mxToolbar + * + * Creates a toolbar inside a given DOM node. The toolbar may contain icons, + * buttons and combo boxes. + * + * Event: mxEvent.SELECT + * + * Fires when an item was selected in the toolbar. The function + * property contains the function that was selected in . + * + * Constructor: mxToolbar + * + * Constructs a toolbar in the specified container. + * + * Parameters: + * + * container - DOM node that contains the toolbar. + */ + constructor(container) { + this.container = container; + }; - // Invokes the function on a click on the toolbar item - if (funct != null) - { - mxEvent.addListener(img, 'click', funct); - - if (mxClient.IS_TOUCH) - { - mxEvent.addListener(img, 'touchend', funct); - } - } + /** + * Function: addItem + * + * Adds the given function as an image with the specified title and icon + * and returns the new image node. + * + * Parameters: + * + * title - Optional string that is used as the tooltip. + * icon - Optional URL of the image to be used. If no URL is given, then a + * button is created. + * funct - Function to execute on a mouse click. + * pressedIcon - Optional URL of the pressed image. Default is a gray + * background. + * style - Optional style classname. Default is mxToolbarItem. + * factoryMethod - Optional factory method for popup menu, eg. + * (menu, evt, cell)=> { menu.addItem('Hello, World!'); } + */ + addItem = (title, icon, funct, pressedIcon, style, factoryMethod) => { + var img = document.createElement((icon != null) ? 'img' : 'button'); + var initialClassName = style || ((factoryMethod != null) ? + 'mxToolbarMode' : 'mxToolbarItem'); + img.className = initialClassName; + img.setAttribute('src', icon); - var mouseHandler = mxUtils.bind(this, (evt)=> - { - if (pressedIcon != null) - { - img.setAttribute('src', icon); - } - else - { - img.style.backgroundColor = ''; - } - }); - - // Highlights the toolbar item with a gray background - // while it is being clicked with the mouse - mxEvent.addGestureListeners(img, mxUtils.bind(this, (evt)=> - { - if (pressedIcon != null) - { - img.setAttribute('src', pressedIcon); - } - else - { - img.style.backgroundColor = 'gray'; - } - - // Popup Menu - if (factoryMethod != null) - { - if (this.menu == null) - { - this.menu = new mxPopupMenu(); - this.menu.init(); + if (title != null) { + if (icon != null) { + img.setAttribute('title', title); + } else { + mxUtils.write(img, title); } - - var last = this.currentImg; - - if (this.menu.isMenuShowing()) - { - this.menu.hideMenu(); - } - - if (last != img) - { - // Redirects factory method to local factory method - this.currentImg = img; - this.menu.factoryMethod = factoryMethod; - - var point = new mxPoint( - img.offsetLeft, - img.offsetTop + img.offsetHeight); - this.menu.popup(point.x, point.y, null, evt); + } - // Sets and overrides to restore classname - if (this.menu.isMenuShowing()) - { - img.className = initialClassName + 'Selected'; - - this.menu.hideMenu = ()=> - { - hideMenu.apply(this); - img.className = initialClassName; - this.currentImg = null; - }; + this.container.appendChild(img); + + // Invokes the function on a click on the toolbar item + if (funct != null) { + mxEvent.addListener(img, 'click', funct); + + if (mxClient.IS_TOUCH) { + mxEvent.addListener(img, 'touchend', funct); + } + } + + var mouseHandler = mxUtils.bind(this, (evt) => { + if (pressedIcon != null) { + img.setAttribute('src', icon); + } else { + img.style.backgroundColor = ''; + } + }); + + // Highlights the toolbar item with a gray background + // while it is being clicked with the mouse + mxEvent.addGestureListeners(img, mxUtils.bind(this, (evt) => { + if (pressedIcon != null) { + img.setAttribute('src', pressedIcon); + } else { + img.style.backgroundColor = 'gray'; + } + + // Popup Menu + if (factoryMethod != null) { + if (this.menu == null) { + this.menu = new mxPopupMenu(); + this.menu.init(); + } + + var last = this.currentImg; + + if (this.menu.isMenuShowing()) { + this.menu.hideMenu(); + } + + if (last != img) { + // Redirects factory method to local factory method + this.currentImg = img; + this.menu.factoryMethod = factoryMethod; + + var point = new mxPoint( + img.offsetLeft, + img.offsetTop + img.offsetHeight); + this.menu.popup(point.x, point.y, null, evt); + + // Sets and overrides to restore classname + if (this.menu.isMenuShowing()) { + img.className = initialClassName + 'Selected'; + + this.menu.hideMenu = () => { + hideMenu.apply(this); + img.className = initialClassName; + this.currentImg = null; + }; + } } } + }), null, mouseHandler); + + mxEvent.addListener(img, 'mouseout', mouseHandler); + + return img; + }; + + /** + * Function: addCombo + * + * Adds and returns a new SELECT element using the given style. The element + * is placed inside a DIV with the mxToolbarComboContainer style classname. + * + * Parameters: + * + * style - Optional style classname. Default is mxToolbarCombo. + */ + addCombo = (style) => { + var div = document.createElement('div'); + div.style.display = 'inline'; + div.className = 'mxToolbarComboContainer'; + + var select = document.createElement('select'); + select.className = style || 'mxToolbarCombo'; + div.appendChild(select); + + this.container.appendChild(div); + + return select; + }; + + /** + * Function: addActionCombo + * + * Adds and returns a new SELECT element using the given title as the + * default element. The selection is reset to this element after each + * change. + * + * Parameters: + * + * title - String that specifies the title of the default element. + * style - Optional style classname. Default is mxToolbarCombo. + */ + addActionCombo = (title, style) => { + var select = document.createElement('select'); + select.className = style || 'mxToolbarCombo'; + this.addOption(select, title, null); + + mxEvent.addListener(select, 'change', (evt) => { + var value = select.options[select.selectedIndex]; + select.selectedIndex = 0; + + if (value.funct != null) { + value.funct(evt); + } + }); + + this.container.appendChild(select); + + return select; + }; + + /** + * Function: addOption + * + * Adds and returns a new OPTION element inside the given SELECT element. + * If the given value is a function then it is stored in the option's funct + * field. + * + * Parameters: + * + * combo - SELECT element that will contain the new entry. + * title - String that specifies the title of the option. + * value - Specifies the value associated with this option. + */ + addOption = (combo, title, value) => { + var option = document.createElement('option'); + mxUtils.writeln(option, title); + + if (typeof (value) == 'function') { + option.funct = value; + } else { + option.setAttribute('value', value); } - }), null, mouseHandler); - mxEvent.addListener(img, 'mouseout', mouseHandler); - - return img; -}; + combo.appendChild(option); -/** - * Function: addCombo - * - * Adds and returns a new SELECT element using the given style. The element - * is placed inside a DIV with the mxToolbarComboContainer style classname. - * - * Parameters: - * - * style - Optional style classname. Default is mxToolbarCombo. - */ -addCombo = (style)=> -{ - var div = document.createElement('div'); - div.style.display = 'inline'; - div.className = 'mxToolbarComboContainer'; - - var select = document.createElement('select'); - select.className = style || 'mxToolbarCombo'; - div.appendChild(select); - - this.container.appendChild(div); - - return select; -}; + return option; + }; -/** - * Function: addActionCombo - * - * Adds and returns a new SELECT element using the given title as the - * default element. The selection is reset to this element after each - * change. - * - * Parameters: - * - * title - String that specifies the title of the default element. - * style - Optional style classname. Default is mxToolbarCombo. - */ -addActionCombo = (title, style)=> -{ - var select = document.createElement('select'); - select.className = style || 'mxToolbarCombo'; - this.addOption(select, title, null); - - mxEvent.addListener(select, 'change', (evt)=> - { - var value = select.options[select.selectedIndex]; - select.selectedIndex = 0; - - if (value.funct != null) - { - value.funct(evt); + /** + * Function: addSwitchMode + * + * Adds a new selectable item to the toolbar. Only one switch mode item may + * be selected at a time. The currently selected item is the default item + * after a reset of the toolbar. + */ + addSwitchMode = (title, icon, funct, pressedIcon, style) => { + var img = document.createElement('img'); + img.initialClassName = style || 'mxToolbarMode'; + img.className = img.initialClassName; + img.setAttribute('src', icon); + img.altIcon = pressedIcon; + + if (title != null) { + img.setAttribute('title', title); } - }); - - this.container.appendChild(select); - - return select; -}; -/** - * Function: addOption - * - * Adds and returns a new OPTION element inside the given SELECT element. - * If the given value is a function then it is stored in the option's funct - * field. - * - * Parameters: - * - * combo - SELECT element that will contain the new entry. - * title - String that specifies the title of the option. - * value - Specifies the value associated with this option. - */ -addOption = (combo, title, value)=> -{ - var option = document.createElement('option'); - mxUtils.writeln(option, title); - - if (typeof(value) == 'function') - { - option.funct = value; - } - else - { - option.setAttribute('value', value); - } - - combo.appendChild(option); - - return option; -}; - -/** - * Function: addSwitchMode - * - * Adds a new selectable item to the toolbar. Only one switch mode item may - * be selected at a time. The currently selected item is the default item - * after a reset of the toolbar. - */ -addSwitchMode = (title, icon, funct, pressedIcon, style)=> -{ - var img = document.createElement('img'); - img.initialClassName = style || 'mxToolbarMode'; - img.className = img.initialClassName; - img.setAttribute('src', icon); - img.altIcon = pressedIcon; - - if (title != null) - { - img.setAttribute('title', title); - } - - mxEvent.addListener(img, 'click', mxUtils.bind(this, (evt)=> - { - var tmp = this.selectedMode.altIcon; - - if (tmp != null) - { - this.selectedMode.altIcon = this.selectedMode.getAttribute('src'); - this.selectedMode.setAttribute('src', tmp); - } - else - { - this.selectedMode.className = this.selectedMode.initialClassName; - } - - if (this.updateDefaultMode) - { - this.defaultMode = img; - } - - this.selectedMode = img; - - var tmp = img.altIcon; - - if (tmp != null) - { - img.altIcon = img.getAttribute('src'); - img.setAttribute('src', tmp); - } - else - { - img.className = img.initialClassName+'Selected'; - } - - this.fireEvent(new mxEventObject(mxEvent.SELECT)); - funct(); - })); - - this.container.appendChild(img); - - if (this.defaultMode == null) - { - this.defaultMode = img; - - // Function should fire only once so - // do not pass it with the select event - this.selectMode(img); - funct(); - } - - return img; -}; - -/** - * Function: addMode - * - * Adds a new item to the toolbar. The selection is typically reset after - * the item has been consumed, for example by adding a new vertex to the - * graph. The reset is not carried out if the item is double clicked. - * - * The function argument uses the following signature: funct(evt, cell) where - * evt is the native mouse event and cell is the cell under the mouse. - */ -addMode = (title, icon, funct, pressedIcon, style, toggle)=> -{ - toggle = (toggle != null) ? toggle : true; - var img = document.createElement((icon != null) ? 'img' : 'button'); - - img.initialClassName = style || 'mxToolbarMode'; - img.className = img.initialClassName; - img.setAttribute('src', icon); - img.altIcon = pressedIcon; - - if (title != null) - { - img.setAttribute('title', title); - } - - if (this.enabled && toggle) - { - mxEvent.addListener(img, 'click', mxUtils.bind(this, (evt)=> - { - this.selectMode(img, funct); - this.noReset = false; - })); - - mxEvent.addListener(img, 'dblclick', mxUtils.bind(this, (evt)=> - { - this.selectMode(img, funct); - this.noReset = true; - })); - - if (this.defaultMode == null) - { - this.defaultMode = img; - this.defaultFunction = funct; - this.selectMode(img, funct); - } - } - - this.container.appendChild(img); - - return img; -}; - -/** - * Function: selectMode - * - * Resets the state of the previously selected mode and displays the given - * DOM node as selected. This function fires a select event with the given - * function as a parameter. - */ -selectMode = (domNode, funct)=> -{ - if (this.selectedMode != domNode) - { - if (this.selectedMode != null) - { + mxEvent.addListener(img, 'click', mxUtils.bind(this, (evt) => { var tmp = this.selectedMode.altIcon; - - if (tmp != null) - { + + if (tmp != null) { this.selectedMode.altIcon = this.selectedMode.getAttribute('src'); this.selectedMode.setAttribute('src', tmp); - } - else - { + } else { this.selectedMode.className = this.selectedMode.initialClassName; } + + if (this.updateDefaultMode) { + this.defaultMode = img; + } + + this.selectedMode = img; + + var tmp = img.altIcon; + + if (tmp != null) { + img.altIcon = img.getAttribute('src'); + img.setAttribute('src', tmp); + } else { + img.className = img.initialClassName + 'Selected'; + } + + this.fireEvent(new mxEventObject(mxEvent.SELECT)); + funct(); + })); + + this.container.appendChild(img); + + if (this.defaultMode == null) { + this.defaultMode = img; + + // Function should fire only once so + // do not pass it with the select event + this.selectMode(img); + funct(); } - - this.selectedMode = domNode; - var tmp = this.selectedMode.altIcon; - - if (tmp != null) - { - this.selectedMode.altIcon = this.selectedMode.getAttribute('src'); - this.selectedMode.setAttribute('src', tmp); + + return img; + }; + + /** + * Function: addMode + * + * Adds a new item to the toolbar. The selection is typically reset after + * the item has been consumed, for example by adding a new vertex to the + * graph. The reset is not carried out if the item is double clicked. + * + * The function argument uses the following signature: funct(evt, cell) where + * evt is the native mouse event and cell is the cell under the mouse. + */ + addMode = (title, icon, funct, pressedIcon, style, toggle) => { + toggle = (toggle != null) ? toggle : true; + var img = document.createElement((icon != null) ? 'img' : 'button'); + + img.initialClassName = style || 'mxToolbarMode'; + img.className = img.initialClassName; + img.setAttribute('src', icon); + img.altIcon = pressedIcon; + + if (title != null) { + img.setAttribute('title', title); } - else - { - this.selectedMode.className = this.selectedMode.initialClassName+'Selected'; + + if (this.enabled && toggle) { + mxEvent.addListener(img, 'click', mxUtils.bind(this, (evt) => { + this.selectMode(img, funct); + this.noReset = false; + })); + + mxEvent.addListener(img, 'dblclick', mxUtils.bind(this, (evt) => { + this.selectMode(img, funct); + this.noReset = true; + })); + + if (this.defaultMode == null) { + this.defaultMode = img; + this.defaultFunction = funct; + this.selectMode(img, funct); + } } - - this.fireEvent(new mxEventObject(mxEvent.SELECT, "function", funct)); - } -}; -/** - * Function: resetMode - * - * Selects the default mode and resets the state of the previously selected - * mode. - */ -resetMode = (forced)=> -{ - if ((forced || !this.noReset) && this.selectedMode != this.defaultMode) - { - // The last selected switch mode will be activated - // so the function was already executed and is - // no longer required here - this.selectMode(this.defaultMode, this.defaultFunction); - } -}; + this.container.appendChild(img); -/** - * Function: addSeparator - * - * Adds the specifies image as a separator. - * - * Parameters: - * - * icon - URL of the separator icon. - */ -addSeparator = (icon)=> -{ - return this.addItem(null, icon, null); -}; + return img; + }; -/** - * Function: addBreak - * - * Adds a break to the container. - */ -addBreak = ()=> -{ - mxUtils.br(this.container); -}; + /** + * Function: selectMode + * + * Resets the state of the previously selected mode and displays the given + * DOM node as selected. This function fires a select event with the given + * function as a parameter. + */ + selectMode = (domNode, funct) => { + if (this.selectedMode != domNode) { + if (this.selectedMode != null) { + var tmp = this.selectedMode.altIcon; -/** - * Function: addLine - * - * Adds a horizontal line to the container. - */ -addLine = ()=> -{ - var hr = document.createElement('hr'); - - hr.style.marginRight = '6px'; - hr.setAttribute('size', '1'); - - this.container.appendChild(hr); -}; + if (tmp != null) { + this.selectedMode.altIcon = this.selectedMode.getAttribute('src'); + this.selectedMode.setAttribute('src', tmp); + } else { + this.selectedMode.className = this.selectedMode.initialClassName; + } + } -/** - * Function: destroy - * - * Removes the toolbar and all its associated resources. - */ -destroy = function () -{ - mxEvent.release(this.container); - this.container = null; - this.defaultMode = null; - this.defaultFunction = null; - this.selectedMode = null; - - if (this.menu != null) - { - this.menu.destroy(); - } -}; + this.selectedMode = domNode; + var tmp = this.selectedMode.altIcon; + + if (tmp != null) { + this.selectedMode.altIcon = this.selectedMode.getAttribute('src'); + this.selectedMode.setAttribute('src', tmp); + } else { + this.selectedMode.className = this.selectedMode.initialClassName + 'Selected'; + } + + this.fireEvent(new mxEventObject(mxEvent.SELECT, "function", funct)); + } + }; + + /** + * Function: resetMode + * + * Selects the default mode and resets the state of the previously selected + * mode. + */ + resetMode = (forced) => { + if ((forced || !this.noReset) && this.selectedMode != this.defaultMode) { + // The last selected switch mode will be activated + // so the function was already executed and is + // no longer required here + this.selectMode(this.defaultMode, this.defaultFunction); + } + }; + + /** + * Function: addSeparator + * + * Adds the specifies image as a separator. + * + * Parameters: + * + * icon - URL of the separator icon. + */ + addSeparator = (icon) => { + return this.addItem(null, icon, null); + }; + + /** + * Function: addBreak + * + * Adds a break to the container. + */ + addBreak = () => { + mxUtils.br(this.container); + }; + + /** + * Function: addLine + * + * Adds a horizontal line to the container. + */ + addLine = () => { + var hr = document.createElement('hr'); + + hr.style.marginRight = '6px'; + hr.setAttribute('size', '1'); + + this.container.appendChild(hr); + }; + + /** + * Function: destroy + * + * Removes the toolbar and all its associated resources. + */ + destroy = function () { + mxEvent.release(this.container); + this.container = null; + this.defaultMode = null; + this.defaultFunction = null; + this.selectedMode = null; + + if (this.menu != null) { + this.menu.destroy(); + } + }; +} + +export default mxToolbar; diff --git a/src/js/util/mxUndoManager.js b/src/js/util/mxUndoManager.js index c891eaf8e..3f3275f5c 100644 --- a/src/js/util/mxUndoManager.js +++ b/src/js/util/mxUndoManager.js @@ -2,228 +2,214 @@ * Copyright (c) 2006-2015, JGraph Ltd * Copyright (c) 2006-2015, Gaudenz Alder */ -/** - * Class: mxUndoManager - * - * Implements a command history. When changing the graph model, an - * object is created at the start of the transaction (when - * model.beginUpdate is called). All atomic changes are then added to this - * object until the last model.endUpdate call, at which point the - * is dispatched in an event, and added to the history inside - * . This is done by an event listener in - * . - * - * Each atomic change of the model is represented by an object (eg. - * , , etc) which contains the - * complete undo information. The also listens to the - * and stores it's changes to the current root as insignificant - * undoable changes, so that drilling (step into, step up) is undone. - * - * This means when you execute an atomic change on the model, then change the - * current root on the view and click undo, the change of the root will be - * undone together with the change of the model so that the display represents - * the state at which the model was changed. However, these changes are not - * transmitted for sharing as they do not represent a state change. - * - * Example: - * - * When adding an undo manager to a graph, make sure to add it - * to the model and the view as well to maintain a consistent - * display across multiple undo/redo steps. - * - * (code) - * var undoManager = new mxUndoManager(); - * var listener = (sender, evt)=> - * { - * undoManager.undoableEditHappened(evt.getProperty('edit')); - * }; - * graph.getModel().addListener(mxEvent.UNDO, listener); - * graph.getView().addListener(mxEvent.UNDO, listener); - * (end) - * - * The code creates a function that informs the undoManager - * of an undoable edit and binds it to the undo event of - * and using - * . - * - * Event: mxEvent.CLEAR - * - * Fires after was invoked. This event has no properties. - * - * Event: mxEvent.UNDO - * - * Fires afer a significant edit was undone in . The edit - * property contains the that was undone. - * - * Event: mxEvent.REDO - * - * Fires afer a significant edit was redone in . The edit - * property contains the that was redone. - * - * Event: mxEvent.ADD - * - * Fires after an undoable edit was added to the history. The edit - * property contains the that was added. - * - * Constructor: mxUndoManager - * - * Constructs a new undo manager with the given history size. If no history - * size is given, then a default size of 100 steps is used. - */ -function mxUndoManager(size) -{ - this.size = (size != null) ? size : 100; - this.clear(); -}; -/** - * Extends mxEventSource. - */ -mxUndoManager.prototype = new mxEventSource(); -constructor = mxUndoManager; +import mxEventObject from "./mxEventObject"; +import mxEventSource from "./mxEventSource"; -/** - * Variable: size - * - * Maximum command history size. 0 means unlimited history. Default is - * 100. - */ -size = null; +class mxUndoManager extends mxEventSource { + /** + * Variable: size + * + * Maximum command history size. 0 means unlimited history. Default is + * 100. + */ + size = null; -/** - * Variable: history - * - * Array that contains the steps of the command history. - */ -history = null; + /** + * Variable: history + * + * Array that contains the steps of the command history. + */ + history = null; -/** - * Variable: indexOfNextAdd - * - * Index of the element to be added next. - */ -indexOfNextAdd = 0; + /** + * Variable: indexOfNextAdd + * + * Index of the element to be added next. + */ + indexOfNextAdd = 0; -/** - * Function: isEmpty - * - * Returns true if the history is empty. - */ -isEmpty = ()=> -{ - return this.history.length == 0; -}; + /** + * Class: mxUndoManager + * + * Implements a command history. When changing the graph model, an + * object is created at the start of the transaction (when + * model.beginUpdate is called). All atomic changes are then added to this + * object until the last model.endUpdate call, at which point the + * is dispatched in an event, and added to the history inside + * . This is done by an event listener in + * . + * + * Each atomic change of the model is represented by an object (eg. + * , , etc) which contains the + * complete undo information. The also listens to the + * and stores it's changes to the current root as insignificant + * undoable changes, so that drilling (step into, step up) is undone. + * + * This means when you execute an atomic change on the model, then change the + * current root on the view and click undo, the change of the root will be + * undone together with the change of the model so that the display represents + * the state at which the model was changed. However, these changes are not + * transmitted for sharing as they do not represent a state change. + * + * Example: + * + * When adding an undo manager to a graph, make sure to add it + * to the model and the view as well to maintain a consistent + * display across multiple undo/redo steps. + * + * (code) + * var undoManager = new mxUndoManager(); + * var listener = (sender, evt)=> + * { + * undoManager.undoableEditHappened(evt.getProperty('edit')); + * }; + * graph.getModel().addListener(mxEvent.UNDO, listener); + * graph.getView().addListener(mxEvent.UNDO, listener); + * (end) + * + * The code creates a function that informs the undoManager + * of an undoable edit and binds it to the undo event of + * and using + * . + * + * Event: mxEvent.CLEAR + * + * Fires after was invoked. This event has no properties. + * + * Event: mxEvent.UNDO + * + * Fires afer a significant edit was undone in . The edit + * property contains the that was undone. + * + * Event: mxEvent.REDO + * + * Fires afer a significant edit was redone in . The edit + * property contains the that was redone. + * + * Event: mxEvent.ADD + * + * Fires after an undoable edit was added to the history. The edit + * property contains the that was added. + * + * Constructor: mxUndoManager + * + * Constructs a new undo manager with the given history size. If no history + * size is given, then a default size of 100 steps is used. + */ + constructor(size) { + this.size = (size != null) ? size : 100; + this.clear(); + }; -/** - * Function: clear - * - * Clears the command history. - */ -clear = ()=> -{ - this.history = []; - this.indexOfNextAdd = 0; - this.fireEvent(new mxEventObject(mxEvent.CLEAR)); -}; + /** + * Function: isEmpty + * + * Returns true if the history is empty. + */ + isEmpty = () => { + return this.history.length == 0; + }; -/** - * Function: canUndo - * - * Returns true if an undo is possible. - */ -canUndo = ()=> -{ - return this.indexOfNextAdd > 0; -}; + /** + * Function: clear + * + * Clears the command history. + */ + clear = () => { + this.history = []; + this.indexOfNextAdd = 0; + this.fireEvent(new mxEventObject(mxEvent.CLEAR)); + }; -/** - * Function: undo - * - * Undoes the last change. - */ -undo = ()=> -{ - while (this.indexOfNextAdd > 0) - { - var edit = this.history[--this.indexOfNextAdd]; - edit.undo(); + /** + * Function: canUndo + * + * Returns true if an undo is possible. + */ + canUndo = () => { + return this.indexOfNextAdd > 0; + }; - if (edit.isSignificant()) - { - this.fireEvent(new mxEventObject(mxEvent.UNDO, 'edit', edit)); - break; - } + /** + * Function: undo + * + * Undoes the last change. + */ + undo = () => { + while (this.indexOfNextAdd > 0) { + var edit = this.history[--this.indexOfNextAdd]; + edit.undo(); + + if (edit.isSignificant()) { + this.fireEvent(new mxEventObject(mxEvent.UNDO, 'edit', edit)); + break; + } } -}; + }; -/** - * Function: canRedo - * - * Returns true if a redo is possible. - */ -canRedo = ()=> -{ - return this.indexOfNextAdd < this.history.length; -}; + /** + * Function: canRedo + * + * Returns true if a redo is possible. + */ + canRedo = () => { + return this.indexOfNextAdd < this.history.length; + }; -/** - * Function: redo - * - * Redoes the last change. - */ -redo = ()=> -{ + /** + * Function: redo + * + * Redoes the last change. + */ + redo = () => { var n = this.history.length; - - while (this.indexOfNextAdd < n) - { - var edit = this.history[this.indexOfNextAdd++]; - edit.redo(); - - if (edit.isSignificant()) - { - this.fireEvent(new mxEventObject(mxEvent.REDO, 'edit', edit)); - break; - } + + while (this.indexOfNextAdd < n) { + var edit = this.history[this.indexOfNextAdd++]; + edit.redo(); + + if (edit.isSignificant()) { + this.fireEvent(new mxEventObject(mxEvent.REDO, 'edit', edit)); + break; + } } -}; + }; -/** - * Function: undoableEditHappened - * - * Method to be called to add new undoable edits to the . - */ -undoableEditHappened = (undoableEdit)=> -{ - this.trim(); + /** + * Function: undoableEditHappened + * + * Method to be called to add new undoable edits to the . + */ + undoableEditHappened = (undoableEdit) => { + this.trim(); - if (this.size > 0 && - this.size == this.history.length) - { - this.history.shift(); - } - - this.history.push(undoableEdit); - this.indexOfNextAdd = this.history.length; - this.fireEvent(new mxEventObject(mxEvent.ADD, 'edit', undoableEdit)); -}; - -/** - * Function: trim - * - * Removes all pending steps after from the history, - * invoking die on each edit. This is called from . - */ -trim = ()=> -{ - if (this.history.length > this.indexOfNextAdd) - { - var edits = this.history.splice(this.indexOfNextAdd, - this.history.length - this.indexOfNextAdd); - - for (var i = 0; i < edits.length; i++) - { - edits[i].die(); + if (this.size > 0 && + this.size == this.history.length) { + this.history.shift(); } - } -}; + + this.history.push(undoableEdit); + this.indexOfNextAdd = this.history.length; + this.fireEvent(new mxEventObject(mxEvent.ADD, 'edit', undoableEdit)); + }; + + /** + * Function: trim + * + * Removes all pending steps after from the history, + * invoking die on each edit. This is called from . + */ + trim = () => { + if (this.history.length > this.indexOfNextAdd) { + var edits = this.history.splice(this.indexOfNextAdd, + this.history.length - this.indexOfNextAdd); + + for (var i = 0; i < edits.length; i++) { + edits[i].die(); + } + } + }; +} + +export default mxUndoManager; diff --git a/src/js/util/mxUndoableEdit.js b/src/js/util/mxUndoableEdit.js index 3632ac283..9797342a1 100644 --- a/src/js/util/mxUndoableEdit.js +++ b/src/js/util/mxUndoableEdit.js @@ -2,212 +2,206 @@ * Copyright (c) 2006-2015, JGraph Ltd * Copyright (c) 2006-2015, Gaudenz Alder */ -/** - * Class: mxUndoableEdit - * - * Implements a composite undoable edit. Here is an example for a custom change - * which gets executed via the model: - * - * (code) - * function CustomChange(model, name) - * { - * this.model = model; - * this.name = name; - * this.previous = name; - * }; - * - * execute = ()=> - * { - * var tmp = this.model.name; - * this.model.name = this.previous; - * this.previous = tmp; - * }; - * - * var name = prompt('Enter name'); - * graph.model.execute(new CustomChange(graph.model, name)); - * (end) - * - * Event: mxEvent.EXECUTED - * - * Fires between START_EDIT and END_EDIT after an atomic change was executed. - * The change property contains the change that was executed. - * - * Event: mxEvent.START_EDIT - * - * Fires before a set of changes will be executed in or . - * This event contains no properties. - * - * Event: mxEvent.END_EDIT - * - * Fires after a set of changeswas executed in or . - * This event contains no properties. - * - * Constructor: mxUndoableEdit - * - * Constructs a new undoable edit for the given source. - */ -function mxUndoableEdit(source, significant) -{ - this.source = source; - this.changes = []; - this.significant = (significant != null) ? significant : true; -}; -/** - * Variable: source - * - * Specifies the source of the edit. - */ -source = null; +import mxEvent from "./mxEvent"; +import mxEventObject from "./mxEventObject"; -/** - * Variable: changes - * - * Array that contains the changes that make up this edit. The changes are - * expected to either have an undo and redo function, or an execute - * function. Default is an empty array. - */ -changes = null; +class mxUndoableEdit { + /** + * Variable: source + * + * Specifies the source of the edit. + */ + source = null; -/** - * Variable: significant - * - * Specifies if the undoable change is significant. - * Default is true. - */ -significant = null; + /** + * Variable: changes + * + * Array that contains the changes that make up this edit. The changes are + * expected to either have an undo and redo function, or an execute + * function. Default is an empty array. + */ + changes = null; -/** - * Variable: undone - * - * Specifies if this edit has been undone. Default is false. - */ -undone = false; + /** + * Variable: significant + * + * Specifies if the undoable change is significant. + * Default is true. + */ + significant = null; -/** - * Variable: redone - * - * Specifies if this edit has been redone. Default is false. - */ -redone = false; + /** + * Variable: undone + * + * Specifies if this edit has been undone. Default is false. + */ + undone = false; -/** - * Function: isEmpty - * - * Returns true if the this edit contains no changes. - */ -isEmpty = ()=> -{ - return this.changes.length == 0; -}; + /** + * Variable: redone + * + * Specifies if this edit has been redone. Default is false. + */ + redone = false; -/** - * Function: isSignificant - * - * Returns . - */ -isSignificant = ()=> -{ - return this.significant; -}; + /** + * Class: mxUndoableEdit + * + * Implements a composite undoable edit. Here is an example for a custom change + * which gets executed via the model: + * + * (code) + * function CustomChange(model, name) + * { + * this.model = model; + * this.name = name; + * this.previous = name; + * }; + * + * execute = ()=> + * { + * var tmp = this.model.name; + * this.model.name = this.previous; + * this.previous = tmp; + * }; + * + * var name = prompt('Enter name'); + * graph.model.execute(new CustomChange(graph.model, name)); + * (end) + * + * Event: mxEvent.EXECUTED + * + * Fires between START_EDIT and END_EDIT after an atomic change was executed. + * The change property contains the change that was executed. + * + * Event: mxEvent.START_EDIT + * + * Fires before a set of changes will be executed in or . + * This event contains no properties. + * + * Event: mxEvent.END_EDIT + * + * Fires after a set of changeswas executed in or . + * This event contains no properties. + * + * Constructor: mxUndoableEdit + * + * Constructs a new undoable edit for the given source. + */ + constructor(source, significant) { + this.source = source; + this.changes = []; + this.significant = (significant != null) ? significant : true; + }; -/** - * Function: add - * - * Adds the specified change to this edit. The change is an object that is - * expected to either have an undo and redo, or an execute function. - */ -add = (change)=> -{ - this.changes.push(change); -}; + /** + * Function: isEmpty + * + * Returns true if the this edit contains no changes. + */ + isEmpty = () => { + return this.changes.length == 0; + }; -/** - * Function: notify - * - * Hook to notify any listeners of the changes after an or - * has been carried out. This implementation is empty. - */ -notify = ()=> { }; + /** + * Function: isSignificant + * + * Returns . + */ + isSignificant = () => { + return this.significant; + }; -/** - * Function: die - * - * Hook to free resources after the edit has been removed from the command - * history. This implementation is empty. - */ -die = ()=> { }; + /** + * Function: add + * + * Adds the specified change to this edit. The change is an object that is + * expected to either have an undo and redo, or an execute function. + */ + add = (change) => { + this.changes.push(change); + }; -/** - * Function: undo - * - * Undoes all changes in this edit. - */ -undo = ()=> -{ - if (!this.undone) - { - this.source.fireEvent(new mxEventObject(mxEvent.START_EDIT)); - var count = this.changes.length; - - for (var i = count - 1; i >= 0; i--) - { - var change = this.changes[i]; - - if (change.execute != null) - { - change.execute(); + /** + * Function: notify + * + * Hook to notify any listeners of the changes after an or + * has been carried out. This implementation is empty. + */ + notify = () => { + }; + + /** + * Function: die + * + * Hook to free resources after the edit has been removed from the command + * history. This implementation is empty. + */ + die = () => { + }; + + /** + * Function: undo + * + * Undoes all changes in this edit. + */ + undo = () => { + if (!this.undone) { + this.source.fireEvent(new mxEventObject(mxEvent.START_EDIT)); + var count = this.changes.length; + + for (var i = count - 1; i >= 0; i--) { + var change = this.changes[i]; + + if (change.execute != null) { + change.execute(); + } else if (change.undo != null) { + change.undo(); + } + + // New global executed event + this.source.fireEvent(new mxEventObject(mxEvent.EXECUTED, 'change', change)); } - else if (change.undo != null) - { - change.undo(); - } - - // New global executed event - this.source.fireEvent(new mxEventObject(mxEvent.EXECUTED, 'change', change)); + + this.undone = true; + this.redone = false; + this.source.fireEvent(new mxEventObject(mxEvent.END_EDIT)); } - - this.undone = true; - this.redone = false; - this.source.fireEvent(new mxEventObject(mxEvent.END_EDIT)); - } - - this.notify(); -}; -/** - * Function: redo - * - * Redoes all changes in this edit. - */ -redo = ()=> -{ - if (!this.redone) - { - this.source.fireEvent(new mxEventObject(mxEvent.START_EDIT)); - var count = this.changes.length; - - for (var i = 0; i < count; i++) - { - var change = this.changes[i]; - - if (change.execute != null) - { - change.execute(); + this.notify(); + }; + + /** + * Function: redo + * + * Redoes all changes in this edit. + */ + redo = () => { + if (!this.redone) { + this.source.fireEvent(new mxEventObject(mxEvent.START_EDIT)); + var count = this.changes.length; + + for (var i = 0; i < count; i++) { + var change = this.changes[i]; + + if (change.execute != null) { + change.execute(); + } else if (change.redo != null) { + change.redo(); + } + + // New global executed event + this.source.fireEvent(new mxEventObject(mxEvent.EXECUTED, 'change', change)); } - else if (change.redo != null) - { - change.redo(); - } - - // New global executed event - this.source.fireEvent(new mxEventObject(mxEvent.EXECUTED, 'change', change)); + + this.undone = false; + this.redone = true; + this.source.fireEvent(new mxEventObject(mxEvent.END_EDIT)); } - - this.undone = false; - this.redone = true; - this.source.fireEvent(new mxEventObject(mxEvent.END_EDIT)); - } - - this.notify(); -}; + + this.notify(); + }; +} + +export default mxUndoableEdit; diff --git a/src/js/util/mxUrlConverter.js b/src/js/util/mxUrlConverter.js index 96ebb32a4..384836f43 100644 --- a/src/js/util/mxUrlConverter.js +++ b/src/js/util/mxUrlConverter.js @@ -2,152 +2,141 @@ * Copyright (c) 2006-2015, JGraph Ltd * Copyright (c) 2006-2015, Gaudenz Alder */ -/** - * - * Class: mxUrlConverter - * - * Converts relative and absolute URLs to absolute URLs with protocol and domain. - */ -var mxUrlConverter = ()=> -{ - // Empty constructor -}; -/** - * Variable: enabled - * - * Specifies if the converter is enabled. Default is true. - */ -enabled = true; +class mxUrlConverter { + /** + * + * Class: mxUrlConverter + * + * Converts relative and absolute URLs to absolute URLs with protocol and domain. + */ + constructor() { + // Empty constructor + }; -/** - * Variable: baseUrl - * - * Specifies the base URL to be used as a prefix for relative URLs. - */ -baseUrl = null; + /** + * Variable: enabled + * + * Specifies if the converter is enabled. Default is true. + */ + enabled = true; -/** - * Variable: baseDomain - * - * Specifies the base domain to be used as a prefix for absolute URLs. - */ -baseDomain = null; + /** + * Variable: baseUrl + * + * Specifies the base URL to be used as a prefix for relative URLs. + */ + baseUrl = null; -/** - * Function: updateBaseUrl - * - * Private helper function to update the base URL. - */ -updateBaseUrl = ()=> -{ - this.baseDomain = location.protocol + '//' + location.host; - this.baseUrl = this.baseDomain + location.pathname; - var tmp = this.baseUrl.lastIndexOf('/'); - - // Strips filename etc - if (tmp > 0) - { - this.baseUrl = this.baseUrl.substring(0, tmp + 1); - } -}; + /** + * Variable: baseDomain + * + * Specifies the base domain to be used as a prefix for absolute URLs. + */ + baseDomain = null; -/** - * Function: isEnabled - * - * Returns . - */ -isEnabled = ()=> -{ - return this.enabled; -}; + /** + * Function: updateBaseUrl + * + * Private helper function to update the base URL. + */ + updateBaseUrl = () => { + this.baseDomain = location.protocol + '//' + location.host; + this.baseUrl = this.baseDomain + location.pathname; + var tmp = this.baseUrl.lastIndexOf('/'); -/** - * Function: setEnabled - * - * Sets . - */ -setEnabled = (value)=> -{ - this.enabled = value; -}; - -/** - * Function: getBaseUrl - * - * Returns . - */ -getBaseUrl = ()=> -{ - return this.baseUrl; -}; - -/** - * Function: setBaseUrl - * - * Sets . - */ -setBaseUrl = (value)=> -{ - this.baseUrl = value; -}; - -/** - * Function: getBaseDomain - * - * Returns . - */ -getBaseDomain = ()=> -{ - return this.baseDomain; -}; - -/** - * Function: setBaseDomain - * - * Sets . - */ -setBaseDomain = (value)=> -{ - this.baseDomain = value; -}; - -/** - * Function: isRelativeUrl - * - * Returns true if the given URL is relative. - */ -isRelativeUrl = (url)=> -{ - return url != null && url.substring(0, 2) != '//' && url.substring(0, 7) != 'http://' && - url.substring(0, 8) != 'https://' && url.substring(0, 10) != 'data:image' && - url.substring(0, 7) != 'file://'; -}; - -/** - * Function: convert - * - * Converts the given URL to an absolute URL with protol and domain. - * Relative URLs are first converted to absolute URLs. - */ -convert = (url)=> -{ - if (this.isEnabled() && this.isRelativeUrl(url)) - { - if (this.getBaseUrl() == null) - { - this.updateBaseUrl(); + // Strips filename etc + if (tmp > 0) { + this.baseUrl = this.baseUrl.substring(0, tmp + 1); } - - if (url.charAt(0) == '/') - { - url = this.getBaseDomain() + url; + }; + + /** + * Function: isEnabled + * + * Returns . + */ + isEnabled = () => { + return this.enabled; + }; + + /** + * Function: setEnabled + * + * Sets . + */ + setEnabled = (value) => { + this.enabled = value; + }; + + /** + * Function: getBaseUrl + * + * Returns . + */ + getBaseUrl = () => { + return this.baseUrl; + }; + + /** + * Function: setBaseUrl + * + * Sets . + */ + setBaseUrl = (value) => { + this.baseUrl = value; + }; + + /** + * Function: getBaseDomain + * + * Returns . + */ + getBaseDomain = () => { + return this.baseDomain; + }; + + /** + * Function: setBaseDomain + * + * Sets . + */ + setBaseDomain = (value) => { + this.baseDomain = value; + }; + + /** + * Function: isRelativeUrl + * + * Returns true if the given URL is relative. + */ + isRelativeUrl = (url) => { + return url != null && url.substring(0, 2) != '//' && url.substring(0, 7) != 'http://' && + url.substring(0, 8) != 'https://' && url.substring(0, 10) != 'data:image' && + url.substring(0, 7) != 'file://'; + }; + + /** + * Function: convert + * + * Converts the given URL to an absolute URL with protol and domain. + * Relative URLs are first converted to absolute URLs. + */ + convert = (url) => { + if (this.isEnabled() && this.isRelativeUrl(url)) { + if (this.getBaseUrl() == null) { + this.updateBaseUrl(); + } + + if (url.charAt(0) == '/') { + url = this.getBaseDomain() + url; + } else { + url = this.getBaseUrl() + url; + } } - else - { - url = this.getBaseUrl() + url; - } - } - - return url; -}; + + return url; + }; +} + +export default mxUrlConverter; diff --git a/src/js/util/mxWindow.js b/src/js/util/mxWindow.js index 83d296881..a8f4336b0 100644 --- a/src/js/util/mxWindow.js +++ b/src/js/util/mxWindow.js @@ -2,1082 +2,996 @@ * Copyright (c) 2006-2015, JGraph Ltd * Copyright (c) 2006-2015, Gaudenz Alder */ -/** - * Class: mxWindow - * - * Basic window inside a document. - * - * Examples: - * - * Creating a simple window. - * - * (code) - * var tb = document.createElement('div'); - * var wnd = new mxWindow('Title', tb, 100, 100, 200, 200, true, true); - * wnd.setVisible(true); - * (end) - * - * Creating a window that contains an iframe. - * - * (code) - * var frame = document.createElement('iframe'); - * frame.setAttribute('width', '192px'); - * frame.setAttribute('height', '172px'); - * frame.setAttribute('src', 'http://www.example.com/'); - * frame.style.backgroundColor = 'white'; - * - * var w = document.body.clientWidth; - * var h = (document.body.clientHeight || document.documentElement.clientHeight); - * var wnd = new mxWindow('Title', frame, (w-200)/2, (h-200)/3, 200, 200); - * wnd.setVisible(true); - * (end) - * - * To limit the movement of a window, eg. to keep it from being moved beyond - * the top, left corner the following method can be overridden (recommended): - * - * (code) - * wnd.setLocation = (x, y)=> - * { - * x = Math.max(0, x); - * y = Math.max(0, y); - * setLocation.apply(this, arguments); - * }; - * (end) - * - * Or the following event handler can be used: - * - * (code) - * wnd.addListener(mxEvent.MOVE, (e)=> - * { - * wnd.setLocation(Math.max(0, wnd.getX()), Math.max(0, wnd.getY())); - * }); - * (end) - * - * To keep a window inside the current window: - * - * (code) - * mxEvent.addListener(window, 'resize', mxUtils.bind(this, ()=> - * { - * var iw = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth; - * var ih = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight; - * - * var x = this.window.getX(); - * var y = this.window.getY(); - * - * if (x + this.window.table.clientWidth > iw) - * { - * x = Math.max(0, iw - this.window.table.clientWidth); - * } - * - * if (y + this.window.table.clientHeight > ih) - * { - * y = Math.max(0, ih - this.window.table.clientHeight); - * } - * - * if (this.window.getX() != x || this.window.getY() != y) - * { - * this.window.setLocation(x, y); - * } - * })); - * (end) - * - * Event: mxEvent.MOVE_START - * - * Fires before the window is moved. The event property contains - * the corresponding mouse event. - * - * Event: mxEvent.MOVE - * - * Fires while the window is being moved. The event property - * contains the corresponding mouse event. - * - * Event: mxEvent.MOVE_END - * - * Fires after the window is moved. The event property contains - * the corresponding mouse event. - * - * Event: mxEvent.RESIZE_START - * - * Fires before the window is resized. The event property contains - * the corresponding mouse event. - * - * Event: mxEvent.RESIZE - * - * Fires while the window is being resized. The event property - * contains the corresponding mouse event. - * - * Event: mxEvent.RESIZE_END - * - * Fires after the window is resized. The event property contains - * the corresponding mouse event. - * - * Event: mxEvent.MAXIMIZE - * - * Fires after the window is maximized. The event property - * contains the corresponding mouse event. - * - * Event: mxEvent.MINIMIZE - * - * Fires after the window is minimized. The event property - * contains the corresponding mouse event. - * - * Event: mxEvent.NORMALIZE - * - * Fires after the window is normalized, that is, it returned from - * maximized or minimized state. The event property contains the - * corresponding mouse event. - * - * Event: mxEvent.ACTIVATE - * - * Fires after a window is activated. The previousWindow property - * contains the previous window. The event sender is the active window. - * - * Event: mxEvent.SHOW - * - * Fires after the window is shown. This event has no properties. - * - * Event: mxEvent.HIDE - * - * Fires after the window is hidden. This event has no properties. - * - * Event: mxEvent.CLOSE - * - * Fires before the window is closed. The event property contains - * the corresponding mouse event. - * - * Event: mxEvent.DESTROY - * - * Fires before the window is destroyed. This event has no properties. - * - * Constructor: mxWindow - * - * Constructs a new window with the given dimension and title to display - * the specified content. The window elements use the given style as a - * prefix for the classnames of the respective window elements, namely, - * the window title and window pane. The respective postfixes are appended - * to the given stylename as follows: - * - * style - Base style for the window. - * style+Title - Style for the window title. - * style+Pane - Style for the window pane. - * - * The default value for style is mxWindow, resulting in the following - * classnames for the window elements: mxWindow, mxWindowTitle and - * mxWindowPane. - * - * If replaceNode is given then the window replaces the given DOM node in - * the document. - * - * Parameters: - * - * title - String that represents the title of the new window. - * content - DOM node that is used as the window content. - * x - X-coordinate of the window location. - * y - Y-coordinate of the window location. - * width - Width of the window. - * height - Optional height of the window. Default is to match the height - * of the content at the specified width. - * minimizable - Optional boolean indicating if the window is minimizable. - * Default is true. - * movable - Optional boolean indicating if the window is movable. Default - * is true. - * replaceNode - Optional DOM node that the window should replace. - * style - Optional base classname for the window elements. Default is - * mxWindow. - */ -function mxWindow(title, content, x, y, width, height, minimizable, movable, replaceNode, style) -{ - if (content != null) - { - minimizable = (minimizable != null) ? minimizable : true; - this.content = content; - this.init(x, y, width, height, style); - - this.installMaximizeHandler(); - this.installMinimizeHandler(); - this.installCloseHandler(); - this.setMinimizable(minimizable); - this.setTitle(title); - - if (movable == null || movable) - { - this.installMoveHandler(); + +import mxRectangle from "./mxRectangle"; +import mxEventObject from "./mxEventObject"; +import mxEventSource from "./mxEventSource"; +import mxUtils from "./mxUtils"; + +class mxWindow extends mxEventSource { + /** + * Variable: closeImage + * + * URL of the image to be used for the close icon in the titlebar. + */ + closeImage = mxClient.imageBasePath + '/close.gif'; + + /** + * Variable: minimizeImage + * + * URL of the image to be used for the minimize icon in the titlebar. + */ + minimizeImage = mxClient.imageBasePath + '/minimize.gif'; + + /** + * Variable: normalizeImage + * + * URL of the image to be used for the normalize icon in the titlebar. + */ + normalizeImage = mxClient.imageBasePath + '/normalize.gif'; + + /** + * Variable: maximizeImage + * + * URL of the image to be used for the maximize icon in the titlebar. + */ + maximizeImage = mxClient.imageBasePath + '/maximize.gif'; + + /** + * Variable: resizeImage + * + * URL of the image to be used for the resize icon. + */ + resizeImage = mxClient.imageBasePath + '/resize.gif'; + + /** + * Variable: visible + * + * Boolean flag that represents the visible state of the window. + */ + visible = false; + + /** + * Variable: minimumSize + * + * that specifies the minimum width and height of the window. + * Default is (50, 40). + */ + minimumSize = new mxRectangle(0, 0, 50, 40); + + /** + * Variable: destroyOnClose + * + * Specifies if the window should be destroyed when it is closed. If this + * is false then the window is hidden using . Default is true. + */ + destroyOnClose = true; + + /** + * Variable: title + * + * Reference to the DOM node (TD) that contains the title. + */ + title = null; + + /** + * Variable: content + * + * Reference to the DOM node that represents the window content. + */ + content = null; + + /** + * Class: mxWindow + * + * Basic window inside a document. + * + * Examples: + * + * Creating a simple window. + * + * (code) + * var tb = document.createElement('div'); + * var wnd = new mxWindow('Title', tb, 100, 100, 200, 200, true, true); + * wnd.setVisible(true); + * (end) + * + * Creating a window that contains an iframe. + * + * (code) + * var frame = document.createElement('iframe'); + * frame.setAttribute('width', '192px'); + * frame.setAttribute('height', '172px'); + * frame.setAttribute('src', 'http://www.example.com/'); + * frame.style.backgroundColor = 'white'; + * + * var w = document.body.clientWidth; + * var h = (document.body.clientHeight || document.documentElement.clientHeight); + * var wnd = new mxWindow('Title', frame, (w-200)/2, (h-200)/3, 200, 200); + * wnd.setVisible(true); + * (end) + * + * To limit the movement of a window, eg. to keep it from being moved beyond + * the top, left corner the following method can be overridden (recommended): + * + * (code) + * wnd.setLocation = (x, y)=> + * { + * x = Math.max(0, x); + * y = Math.max(0, y); + * setLocation.apply(this, arguments); + * }; + * (end) + * + * Or the following event handler can be used: + * + * (code) + * wnd.addListener(mxEvent.MOVE, (e)=> + * { + * wnd.setLocation(Math.max(0, wnd.getX()), Math.max(0, wnd.getY())); + * }); + * (end) + * + * To keep a window inside the current window: + * + * (code) + * mxEvent.addListener(window, 'resize', mxUtils.bind(this, ()=> + * { + * var iw = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth; + * var ih = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight; + * + * var x = this.window.getX(); + * var y = this.window.getY(); + * + * if (x + this.window.table.clientWidth > iw) + * { + * x = Math.max(0, iw - this.window.table.clientWidth); + * } + * + * if (y + this.window.table.clientHeight > ih) + * { + * y = Math.max(0, ih - this.window.table.clientHeight); + * } + * + * if (this.window.getX() != x || this.window.getY() != y) + * { + * this.window.setLocation(x, y); + * } + * })); + * (end) + * + * Event: mxEvent.MOVE_START + * + * Fires before the window is moved. The event property contains + * the corresponding mouse event. + * + * Event: mxEvent.MOVE + * + * Fires while the window is being moved. The event property + * contains the corresponding mouse event. + * + * Event: mxEvent.MOVE_END + * + * Fires after the window is moved. The event property contains + * the corresponding mouse event. + * + * Event: mxEvent.RESIZE_START + * + * Fires before the window is resized. The event property contains + * the corresponding mouse event. + * + * Event: mxEvent.RESIZE + * + * Fires while the window is being resized. The event property + * contains the corresponding mouse event. + * + * Event: mxEvent.RESIZE_END + * + * Fires after the window is resized. The event property contains + * the corresponding mouse event. + * + * Event: mxEvent.MAXIMIZE + * + * Fires after the window is maximized. The event property + * contains the corresponding mouse event. + * + * Event: mxEvent.MINIMIZE + * + * Fires after the window is minimized. The event property + * contains the corresponding mouse event. + * + * Event: mxEvent.NORMALIZE + * + * Fires after the window is normalized, that is, it returned from + * maximized or minimized state. The event property contains the + * corresponding mouse event. + * + * Event: mxEvent.ACTIVATE + * + * Fires after a window is activated. The previousWindow property + * contains the previous window. The event sender is the active window. + * + * Event: mxEvent.SHOW + * + * Fires after the window is shown. This event has no properties. + * + * Event: mxEvent.HIDE + * + * Fires after the window is hidden. This event has no properties. + * + * Event: mxEvent.CLOSE + * + * Fires before the window is closed. The event property contains + * the corresponding mouse event. + * + * Event: mxEvent.DESTROY + * + * Fires before the window is destroyed. This event has no properties. + * + * Constructor: mxWindow + * + * Constructs a new window with the given dimension and title to display + * the specified content. The window elements use the given style as a + * prefix for the classnames of the respective window elements, namely, + * the window title and window pane. The respective postfixes are appended + * to the given stylename as follows: + * + * style - Base style for the window. + * style+Title - Style for the window title. + * style+Pane - Style for the window pane. + * + * The default value for style is mxWindow, resulting in the following + * classnames for the window elements: mxWindow, mxWindowTitle and + * mxWindowPane. + * + * If replaceNode is given then the window replaces the given DOM node in + * the document. + * + * Parameters: + * + * title - String that represents the title of the new window. + * content - DOM node that is used as the window content. + * x - X-coordinate of the window location. + * y - Y-coordinate of the window location. + * width - Width of the window. + * height - Optional height of the window. Default is to match the height + * of the content at the specified width. + * minimizable - Optional boolean indicating if the window is minimizable. + * Default is true. + * movable - Optional boolean indicating if the window is movable. Default + * is true. + * replaceNode - Optional DOM node that the window should replace. + * style - Optional base classname for the window elements. Default is + * mxWindow. + */ + constructor(title, content, x, y, width, height, minimizable, movable, replaceNode, style) { + if (content != null) { + minimizable = (minimizable != null) ? minimizable : true; + this.content = content; + this.init(x, y, width, height, style); + + this.installMaximizeHandler(); + this.installMinimizeHandler(); + this.installCloseHandler(); + this.setMinimizable(minimizable); + this.setTitle(title); + + if (movable == null || movable) { + this.installMoveHandler(); + } + + if (replaceNode != null && replaceNode.parentNode != null) { + replaceNode.parentNode.replaceChild(this.div, replaceNode); + } else { + document.body.appendChild(this.div); + } + } + }; + + /** + * Function: init + * + * Initializes the DOM tree that represents the window. + */ + init = (x, y, width, height, style) => { + style = (style != null) ? style : 'mxWindow'; + + this.div = document.createElement('div'); + this.div.className = style; + + this.div.style.left = x + 'px'; + this.div.style.top = y + 'px'; + this.table = document.createElement('table'); + this.table.className = style; + + // Disables built-in pan and zoom in IE10 and later + if (mxClient.IS_POINTER) { + this.div.style.touchAction = 'none'; } - if (replaceNode != null && replaceNode.parentNode != null) - { - replaceNode.parentNode.replaceChild(this.div, replaceNode); + // Workaround for table size problems in FF + if (width != null) { + this.div.style.width = width + 'px'; + this.table.style.width = width + 'px'; } - else - { - document.body.appendChild(this.div); + + if (height != null) { + this.div.style.height = height + 'px'; + this.table.style.height = height + 'px'; } - } -}; -/** - * Extends mxEventSource. - */ -mxWindow.prototype = new mxEventSource(); -constructor = mxWindow; + // Creates title row + var tbody = document.createElement('tbody'); + var tr = document.createElement('tr'); -/** - * Variable: closeImage - * - * URL of the image to be used for the close icon in the titlebar. - */ -closeImage = mxClient.imageBasePath + '/close.gif'; + this.title = document.createElement('td'); + this.title.className = style + 'Title'; -/** - * Variable: minimizeImage - * - * URL of the image to be used for the minimize icon in the titlebar. - */ -minimizeImage = mxClient.imageBasePath + '/minimize.gif'; - -/** - * Variable: normalizeImage - * - * URL of the image to be used for the normalize icon in the titlebar. - */ -normalizeImage = mxClient.imageBasePath + '/normalize.gif'; - -/** - * Variable: maximizeImage - * - * URL of the image to be used for the maximize icon in the titlebar. - */ -maximizeImage = mxClient.imageBasePath + '/maximize.gif'; + this.buttons = document.createElement('div'); + this.buttons.style.position = 'absolute'; + this.buttons.style.display = 'inline-block'; + this.buttons.style.right = '4px'; + this.buttons.style.top = '5px'; + this.title.appendChild(this.buttons); -/** - * Variable: resizeImage - * - * URL of the image to be used for the resize icon. - */ -resizeImage = mxClient.imageBasePath + '/resize.gif'; + tr.appendChild(this.title); + tbody.appendChild(tr); -/** - * Variable: visible - * - * Boolean flag that represents the visible state of the window. - */ -visible = false; + // Creates content row and table cell + tr = document.createElement('tr'); + this.td = document.createElement('td'); + this.td.className = style + 'Pane'; -/** - * Variable: minimumSize - * - * that specifies the minimum width and height of the window. - * Default is (50, 40). - */ -minimumSize = new mxRectangle(0, 0, 50, 40); + this.contentWrapper = document.createElement('div'); + this.contentWrapper.className = style + 'Pane'; + this.contentWrapper.style.width = '100%'; + this.contentWrapper.appendChild(this.content); -/** - * Variable: destroyOnClose - * - * Specifies if the window should be destroyed when it is closed. If this - * is false then the window is hidden using . Default is true. - */ -destroyOnClose = true; + // Workaround for div around div restricts height + // of inner div if outerdiv has hidden overflow + if (this.content.nodeName.toUpperCase() !== 'DIV') { + this.contentWrapper.style.height = '100%'; + } -/** - * Variable: title - * - * Reference to the DOM node (TD) that contains the title. - */ -title = null; + // Puts all content into the DOM + this.td.appendChild(this.contentWrapper); + tr.appendChild(this.td); + tbody.appendChild(tr); + this.table.appendChild(tbody); + this.div.appendChild(this.table); -/** - * Variable: content - * - * Reference to the DOM node that represents the window content. - */ -content = null; + // Puts the window on top of other windows when clicked + var activator = mxUtils.bind(this, (evt) => { + this.activate(); + }); -/** - * Function: init - * - * Initializes the DOM tree that represents the window. - */ -init = (x, y, width, height, style)=> -{ - style = (style != null) ? style : 'mxWindow'; - - this.div = document.createElement('div'); - this.div.className = style; + mxEvent.addGestureListeners(this.title, activator); + mxEvent.addGestureListeners(this.table, activator); - this.div.style.left = x + 'px'; - this.div.style.top = y + 'px'; - this.table = document.createElement('table'); - this.table.className = style; + this.hide(); + }; - // Disables built-in pan and zoom in IE10 and later - if (mxClient.IS_POINTER) - { - this.div.style.touchAction = 'none'; - } - - // Workaround for table size problems in FF - if (width != null) - { + /** + * Function: setTitle + * + * Sets the window title to the given string. HTML markup inside the title + * will be escaped. + */ + setTitle = (title) => { + // Removes all text content nodes (normally just one) + var child = this.title.firstChild; + + while (child != null) { + var next = child.nextSibling; + + if (child.nodeType === mxConstants.NODETYPE_TEXT) { + child.parentNode.removeChild(child); + } + + child = next; + } + + mxUtils.write(this.title, title || ''); + this.title.appendChild(this.buttons); + }; + + /** + * Function: setScrollable + * + * Sets if the window contents should be scrollable. + */ + setScrollable = (scrollable) => { + // Workaround for hang in Presto 2.5.22 (Opera 10.5) + if (navigator.userAgent == null || + navigator.userAgent.indexOf('Presto/2.5') < 0) { + if (scrollable) { + this.contentWrapper.style.overflow = 'auto'; + } else { + this.contentWrapper.style.overflow = 'hidden'; + } + } + }; + + /** + * Function: activate + * + * Puts the window on top of all other windows. + */ + activate = () => { + if (mxWindow.activeWindow !== this) { + var style = mxUtils.getCurrentStyle(this.getElement()); + var index = (style != null) ? style.zIndex : 3; + + if (mxWindow.activeWindow) { + var elt = mxWindow.activeWindow.getElement(); + + if (elt != null && elt.style != null) { + elt.style.zIndex = index; + } + } + + var previousWindow = mxWindow.activeWindow; + this.getElement().style.zIndex = parseInt(index) + 1; + mxWindow.activeWindow = this; + + this.fireEvent(new mxEventObject(mxEvent.ACTIVATE, 'previousWindow', previousWindow)); + } + }; + + /** + * Function: getElement + * + * Returuns the outermost DOM node that makes up the window. + */ + getElement = () => { + return this.div; + }; + + /** + * Function: fit + * + * Makes sure the window is inside the client area of the window. + */ + fit = () => { + mxUtils.fit(this.div); + }; + + /** + * Function: isResizable + * + * Returns true if the window is resizable. + */ + isResizable = () => { + if (this.resize != null) { + return this.resize.style.display != 'none'; + } + + return false; + }; + + /** + * Function: setResizable + * + * Sets if the window should be resizable. To avoid interference with some + * built-in features of IE10 and later, the use of the following code is + * recommended if there are resizable s in the page: + * + * (code) + * if (mxClient.IS_POINTER) + * { + * document.body.style.msTouchAction = 'none'; + * } + * (end) + */ + setResizable = (resizable) => { + if (resizable) { + if (this.resize == null) { + this.resize = document.createElement('img'); + this.resize.style.position = 'absolute'; + this.resize.style.bottom = '2px'; + this.resize.style.right = '2px'; + + this.resize.setAttribute('src', this.resizeImage); + this.resize.style.cursor = 'nw-resize'; + + var startX = null; + var startY = null; + var width = null; + var height = null; + + var start = mxUtils.bind(this, (evt) => { + // LATER: pointerdown starting on border of resize does start + // the drag operation but does not fire consecutive events via + // one of the listeners below (does pan instead). + // Workaround: document.body.style.msTouchAction = 'none' + this.activate(); + startX = mxEvent.getClientX(evt); + startY = mxEvent.getClientY(evt); + width = this.div.offsetWidth; + height = this.div.offsetHeight; + + mxEvent.addGestureListeners(document, null, dragHandler, dropHandler); + this.fireEvent(new mxEventObject(mxEvent.RESIZE_START, 'event', evt)); + mxEvent.consume(evt); + }); + + // Adds a temporary pair of listeners to intercept + // the gesture event in the document + var dragHandler = mxUtils.bind(this, (evt) => { + if (startX != null && startY != null) { + var dx = mxEvent.getClientX(evt) - startX; + var dy = mxEvent.getClientY(evt) - startY; + + this.setSize(width + dx, height + dy); + + this.fireEvent(new mxEventObject(mxEvent.RESIZE, 'event', evt)); + mxEvent.consume(evt); + } + }); + + var dropHandler = mxUtils.bind(this, (evt) => { + if (startX != null && startY != null) { + startX = null; + startY = null; + mxEvent.removeGestureListeners(document, null, dragHandler, dropHandler); + this.fireEvent(new mxEventObject(mxEvent.RESIZE_END, 'event', evt)); + mxEvent.consume(evt); + } + }); + + mxEvent.addGestureListeners(this.resize, start, dragHandler, dropHandler); + this.div.appendChild(this.resize); + } else { + this.resize.style.display = 'inline'; + } + } else if (this.resize != null) { + this.resize.style.display = 'none'; + } + }; + + /** + * Function: setSize + * + * Sets the size of the window. + */ + setSize = (width, height) => { + width = Math.max(this.minimumSize.width, width); + height = Math.max(this.minimumSize.height, height); + + // Workaround for table size problems in FF this.div.style.width = width + 'px'; - this.table.style.width = width + 'px'; - } - - if (height != null) - { this.div.style.height = height + 'px'; + + this.table.style.width = width + 'px'; this.table.style.height = height + 'px'; - } - - // Creates title row - var tbody = document.createElement('tbody'); - var tr = document.createElement('tr'); - - this.title = document.createElement('td'); - this.title.className = style + 'Title'; - - this.buttons = document.createElement('div'); - this.buttons.style.position = 'absolute'; - this.buttons.style.display = 'inline-block'; - this.buttons.style.right = '4px'; - this.buttons.style.top = '5px'; - this.title.appendChild(this.buttons); - - tr.appendChild(this.title); - tbody.appendChild(tr); - - // Creates content row and table cell - tr = document.createElement('tr'); - this.td = document.createElement('td'); - this.td.className = style + 'Pane'; - this.contentWrapper = document.createElement('div'); - this.contentWrapper.className = style + 'Pane'; - this.contentWrapper.style.width = '100%'; - this.contentWrapper.appendChild(this.content); + this.contentWrapper.style.height = (this.div.offsetHeight - + this.title.offsetHeight - this.contentHeightCorrection) + 'px'; + }; - // Workaround for div around div restricts height - // of inner div if outerdiv has hidden overflow - if (this.content.nodeName.toUpperCase() != 'DIV') - { - this.contentWrapper.style.height = '100%'; - } + /** + * Function: setMinimizable + * + * Sets if the window is minimizable. + */ + setMinimizable = (minimizable) => { + this.minimize.style.display = (minimizable) ? '' : 'none'; + }; - // Puts all content into the DOM - this.td.appendChild(this.contentWrapper); - tr.appendChild(this.td); - tbody.appendChild(tr); - this.table.appendChild(tbody); - this.div.appendChild(this.table); - - // Puts the window on top of other windows when clicked - var activator = mxUtils.bind(this, (evt)=> - { - this.activate(); - }); - - mxEvent.addGestureListeners(this.title, activator); - mxEvent.addGestureListeners(this.table, activator); + /** + * Function: getMinimumSize + * + * Returns an that specifies the size for the minimized window. + * A width or height of 0 means keep the existing width or height. This + * implementation returns the height of the window title and keeps the width. + */ + getMinimumSize = () => { + return new mxRectangle(0, 0, 0, this.title.offsetHeight); + }; - this.hide(); -}; + /** + * Function: installMinimizeHandler + * + * Installs the event listeners required for minimizing the window. + */ + installMinimizeHandler = () => { + this.minimize = document.createElement('img'); -/** - * Function: setTitle - * - * Sets the window title to the given string. HTML markup inside the title - * will be escaped. - */ -setTitle = (title)=> -{ - // Removes all text content nodes (normally just one) - var child = this.title.firstChild; - - while (child != null) - { - var next = child.nextSibling; - - if (child.nodeType == mxConstants.NODETYPE_TEXT) - { - child.parentNode.removeChild(child); - } - - child = next; - } - - mxUtils.write(this.title, title || ''); - this.title.appendChild(this.buttons); -}; + this.minimize.setAttribute('src', this.minimizeImage); + this.minimize.setAttribute('title', 'Minimize'); + this.minimize.style.cursor = 'pointer'; + this.minimize.style.marginLeft = '2px'; + this.minimize.style.display = 'none'; -/** - * Function: setScrollable - * - * Sets if the window contents should be scrollable. - */ -setScrollable = (scrollable)=> -{ - // Workaround for hang in Presto 2.5.22 (Opera 10.5) - if (navigator.userAgent == null || - navigator.userAgent.indexOf('Presto/2.5') < 0) - { - if (scrollable) - { - this.contentWrapper.style.overflow = 'auto'; - } - else - { - this.contentWrapper.style.overflow = 'hidden'; - } - } -}; + this.buttons.appendChild(this.minimize); -/** - * Function: activate - * - * Puts the window on top of all other windows. - */ -activate = ()=> -{ - if (mxWindow.activeWindow != this) - { - var style = mxUtils.getCurrentStyle(this.getElement()); - var index = (style != null) ? style.zIndex : 3; + var minimized = false; + var maxDisplay = null; + var height = null; - if (mxWindow.activeWindow) - { - var elt = mxWindow.activeWindow.getElement(); - - if (elt != null && elt.style != null) - { - elt.style.zIndex = index; - } - } - - var previousWindow = mxWindow.activeWindow; - this.getElement().style.zIndex = parseInt(index) + 1; - mxWindow.activeWindow = this; - - this.fireEvent(new mxEventObject(mxEvent.ACTIVATE, 'previousWindow', previousWindow)); - } -}; + var funct = mxUtils.bind(this, (evt) => { + this.activate(); -/** - * Function: getElement - * - * Returuns the outermost DOM node that makes up the window. - */ -getElement = ()=> -{ - return this.div; -}; + if (!minimized) { + minimized = true; -/** - * Function: fit - * - * Makes sure the window is inside the client area of the window. - */ -fit = ()=> -{ - mxUtils.fit(this.div); -}; + this.minimize.setAttribute('src', this.normalizeImage); + this.minimize.setAttribute('title', 'Normalize'); + this.contentWrapper.style.display = 'none'; + maxDisplay = this.maximize.style.display; -/** - * Function: isResizable - * - * Returns true if the window is resizable. - */ -isResizable = ()=> -{ - if (this.resize != null) - { - return this.resize.style.display != 'none'; - } - - return false; -}; - -/** - * Function: setResizable - * - * Sets if the window should be resizable. To avoid interference with some - * built-in features of IE10 and later, the use of the following code is - * recommended if there are resizable s in the page: - * - * (code) - * if (mxClient.IS_POINTER) - * { - * document.body.style.msTouchAction = 'none'; - * } - * (end) - */ -setResizable = (resizable)=> -{ - if (resizable) - { - if (this.resize == null) - { - this.resize = document.createElement('img'); - this.resize.style.position = 'absolute'; - this.resize.style.bottom = '2px'; - this.resize.style.right = '2px'; - - this.resize.setAttribute('src', this.resizeImage); - this.resize.style.cursor = 'nw-resize'; - - var startX = null; - var startY = null; - var width = null; - var height = null; - - var start = mxUtils.bind(this, (evt)=> - { - // LATER: pointerdown starting on border of resize does start - // the drag operation but does not fire consecutive events via - // one of the listeners below (does pan instead). - // Workaround: document.body.style.msTouchAction = 'none' - this.activate(); - startX = mxEvent.getClientX(evt); - startY = mxEvent.getClientY(evt); - width = this.div.offsetWidth; - height = this.div.offsetHeight; - - mxEvent.addGestureListeners(document, null, dragHandler, dropHandler); - this.fireEvent(new mxEventObject(mxEvent.RESIZE_START, 'event', evt)); - mxEvent.consume(evt); - }); - - // Adds a temporary pair of listeners to intercept - // the gesture event in the document - var dragHandler = mxUtils.bind(this, (evt)=> - { - if (startX != null && startY != null) - { - var dx = mxEvent.getClientX(evt) - startX; - var dy = mxEvent.getClientY(evt) - startY; - - this.setSize(width + dx, height + dy); - - this.fireEvent(new mxEventObject(mxEvent.RESIZE, 'event', evt)); - mxEvent.consume(evt); - } - }); - - var dropHandler = mxUtils.bind(this, (evt)=> - { - if (startX != null && startY != null) - { - startX = null; - startY = null; - mxEvent.removeGestureListeners(document, null, dragHandler, dropHandler); - this.fireEvent(new mxEventObject(mxEvent.RESIZE_END, 'event', evt)); - mxEvent.consume(evt); - } - }); - - mxEvent.addGestureListeners(this.resize, start, dragHandler, dropHandler); - this.div.appendChild(this.resize); - } - else - { - this.resize.style.display = 'inline'; - } - } - else if (this.resize != null) - { - this.resize.style.display = 'none'; - } -}; - -/** - * Function: setSize - * - * Sets the size of the window. - */ -setSize = (width, height)=> -{ - width = Math.max(this.minimumSize.width, width); - height = Math.max(this.minimumSize.height, height); - - // Workaround for table size problems in FF - this.div.style.width = width + 'px'; - this.div.style.height = height + 'px'; - - this.table.style.width = width + 'px'; - this.table.style.height = height + 'px'; - - this.contentWrapper.style.height = (this.div.offsetHeight - - this.title.offsetHeight - this.contentHeightCorrection) + 'px'; -}; - -/** - * Function: setMinimizable - * - * Sets if the window is minimizable. - */ -setMinimizable = (minimizable)=> -{ - this.minimize.style.display = (minimizable) ? '' : 'none'; -}; - -/** - * Function: getMinimumSize - * - * Returns an that specifies the size for the minimized window. - * A width or height of 0 means keep the existing width or height. This - * implementation returns the height of the window title and keeps the width. - */ -getMinimumSize = ()=> -{ - return new mxRectangle(0, 0, 0, this.title.offsetHeight); -}; - -/** - * Function: installMinimizeHandler - * - * Installs the event listeners required for minimizing the window. - */ -installMinimizeHandler = ()=> -{ - this.minimize = document.createElement('img'); - - this.minimize.setAttribute('src', this.minimizeImage); - this.minimize.setAttribute('title', 'Minimize'); - this.minimize.style.cursor = 'pointer'; - this.minimize.style.marginLeft = '2px'; - this.minimize.style.display = 'none'; - - this.buttons.appendChild(this.minimize); - - var minimized = false; - var maxDisplay = null; - var height = null; - - var funct = mxUtils.bind(this, (evt)=> - { - this.activate(); - - if (!minimized) - { - minimized = true; - - this.minimize.setAttribute('src', this.normalizeImage); - this.minimize.setAttribute('title', 'Normalize'); - this.contentWrapper.style.display = 'none'; - maxDisplay = this.maximize.style.display; - - this.maximize.style.display = 'none'; - height = this.table.style.height; - - var minSize = this.getMinimumSize(); - - if (minSize.height > 0) - { - this.div.style.height = minSize.height + 'px'; - this.table.style.height = minSize.height + 'px'; - } - - if (minSize.width > 0) - { - this.div.style.width = minSize.width + 'px'; - this.table.style.width = minSize.width + 'px'; - } - - if (this.resize != null) - { - this.resize.style.visibility = 'hidden'; - } - - this.fireEvent(new mxEventObject(mxEvent.MINIMIZE, 'event', evt)); - } - else - { - minimized = false; - - this.minimize.setAttribute('src', this.minimizeImage); - this.minimize.setAttribute('title', 'Minimize'); - this.contentWrapper.style.display = ''; // default - this.maximize.style.display = maxDisplay; - this.div.style.height = height; - this.table.style.height = height; - - if (this.resize != null) - { - this.resize.style.visibility = ''; - } - - this.fireEvent(new mxEventObject(mxEvent.NORMALIZE, 'event', evt)); - } - - mxEvent.consume(evt); - }); - - mxEvent.addGestureListeners(this.minimize, funct); -}; - -/** - * Function: setMaximizable - * - * Sets if the window is maximizable. - */ -setMaximizable = (maximizable)=> -{ - this.maximize.style.display = (maximizable) ? '' : 'none'; -}; - -/** - * Function: installMaximizeHandler - * - * Installs the event listeners required for maximizing the window. - */ -installMaximizeHandler = ()=> -{ - this.maximize = document.createElement('img'); - - this.maximize.setAttribute('src', this.maximizeImage); - this.maximize.setAttribute('title', 'Maximize'); - this.maximize.style.cursor = 'default'; - this.maximize.style.marginLeft = '2px'; - this.maximize.style.cursor = 'pointer'; - this.maximize.style.display = 'none'; - - this.buttons.appendChild(this.maximize); - - var maximized = false; - var x = null; - var y = null; - var height = null; - var width = null; - var minDisplay = null; - - var funct = mxUtils.bind(this, (evt)=> - { - this.activate(); - - if (this.maximize.style.display != 'none') - { - if (!maximized) - { - maximized = true; - - this.maximize.setAttribute('src', this.normalizeImage); - this.maximize.setAttribute('title', 'Normalize'); - this.contentWrapper.style.display = ''; - minDisplay = this.minimize.style.display; - this.minimize.style.display = 'none'; - - // Saves window state - x = parseInt(this.div.style.left); - y = parseInt(this.div.style.top); + this.maximize.style.display = 'none'; height = this.table.style.height; - width = this.table.style.width; - this.div.style.left = '0px'; - this.div.style.top = '0px'; - var docHeight = Math.max(document.body.clientHeight || 0, document.documentElement.clientHeight || 0); + var minSize = this.getMinimumSize(); - this.div.style.width = (document.body.clientWidth - 2) + 'px'; - this.div.style.height = (docHeight - 2) + 'px'; + if (minSize.height > 0) { + this.div.style.height = minSize.height + 'px'; + this.table.style.height = minSize.height + 'px'; + } - this.table.style.width = (document.body.clientWidth - 2) + 'px'; - this.table.style.height = (docHeight - 2) + 'px'; - - if (this.resize != null) - { + if (minSize.width > 0) { + this.div.style.width = minSize.width + 'px'; + this.table.style.width = minSize.width + 'px'; + } + + if (this.resize != null) { this.resize.style.visibility = 'hidden'; } - var style = mxUtils.getCurrentStyle(this.contentWrapper); + this.fireEvent(new mxEventObject(mxEvent.MINIMIZE, 'event', evt)); + } else { + minimized = false; - if (style.overflow == 'auto' || this.resize != null) - { - this.contentWrapper.style.height = (this.div.offsetHeight - - this.title.offsetHeight - this.contentHeightCorrection) + 'px'; - } - - this.fireEvent(new mxEventObject(mxEvent.MAXIMIZE, 'event', evt)); - } - else - { - maximized = false; - - this.maximize.setAttribute('src', this.maximizeImage); - this.maximize.setAttribute('title', 'Maximize'); - this.contentWrapper.style.display = ''; - this.minimize.style.display = minDisplay; - - // Restores window state - this.div.style.left = x+'px'; - this.div.style.top = y+'px'; - + this.minimize.setAttribute('src', this.minimizeImage); + this.minimize.setAttribute('title', 'Minimize'); + this.contentWrapper.style.display = ''; // default + this.maximize.style.display = maxDisplay; this.div.style.height = height; - this.div.style.width = width; - - var style = mxUtils.getCurrentStyle(this.contentWrapper); - - if (style.overflow == 'auto' || this.resize != null) - { - this.contentWrapper.style.height = (this.div.offsetHeight - - this.title.offsetHeight - this.contentHeightCorrection) + 'px'; - } - this.table.style.height = height; - this.table.style.width = width; - if (this.resize != null) - { + if (this.resize != null) { this.resize.style.visibility = ''; } - + this.fireEvent(new mxEventObject(mxEvent.NORMALIZE, 'event', evt)); } - + mxEvent.consume(evt); - } - }); - - mxEvent.addGestureListeners(this.maximize, funct); - mxEvent.addListener(this.title, 'dblclick', funct); -}; - -/** - * Function: installMoveHandler - * - * Installs the event listeners required for moving the window. - */ -installMoveHandler = ()=> -{ - this.title.style.cursor = 'move'; - - mxEvent.addGestureListeners(this.title, - mxUtils.bind(this, (evt)=> - { - var startX = mxEvent.getClientX(evt); - var startY = mxEvent.getClientY(evt); - var x = this.getX(); - var y = this.getY(); - - // Adds a temporary pair of listeners to intercept - // the gesture event in the document - var dragHandler = mxUtils.bind(this, (evt)=> - { - var dx = mxEvent.getClientX(evt) - startX; - var dy = mxEvent.getClientY(evt) - startY; - this.setLocation(x + dx, y + dy); - this.fireEvent(new mxEventObject(mxEvent.MOVE, 'event', evt)); + }); + + mxEvent.addGestureListeners(this.minimize, funct); + }; + + /** + * Function: setMaximizable + * + * Sets if the window is maximizable. + */ + setMaximizable = (maximizable) => { + this.maximize.style.display = (maximizable) ? '' : 'none'; + }; + + /** + * Function: installMaximizeHandler + * + * Installs the event listeners required for maximizing the window. + */ + installMaximizeHandler = () => { + this.maximize = document.createElement('img'); + + this.maximize.setAttribute('src', this.maximizeImage); + this.maximize.setAttribute('title', 'Maximize'); + this.maximize.style.cursor = 'default'; + this.maximize.style.marginLeft = '2px'; + this.maximize.style.cursor = 'pointer'; + this.maximize.style.display = 'none'; + + this.buttons.appendChild(this.maximize); + + var maximized = false; + var x = null; + var y = null; + var height = null; + var width = null; + var minDisplay = null; + + var funct = mxUtils.bind(this, (evt) => { + this.activate(); + + if (this.maximize.style.display != 'none') { + if (!maximized) { + maximized = true; + + this.maximize.setAttribute('src', this.normalizeImage); + this.maximize.setAttribute('title', 'Normalize'); + this.contentWrapper.style.display = ''; + minDisplay = this.minimize.style.display; + this.minimize.style.display = 'none'; + + // Saves window state + x = parseInt(this.div.style.left); + y = parseInt(this.div.style.top); + height = this.table.style.height; + width = this.table.style.width; + + this.div.style.left = '0px'; + this.div.style.top = '0px'; + var docHeight = Math.max(document.body.clientHeight || 0, document.documentElement.clientHeight || 0); + + this.div.style.width = (document.body.clientWidth - 2) + 'px'; + this.div.style.height = (docHeight - 2) + 'px'; + + this.table.style.width = (document.body.clientWidth - 2) + 'px'; + this.table.style.height = (docHeight - 2) + 'px'; + + if (this.resize != null) { + this.resize.style.visibility = 'hidden'; + } + + var style = mxUtils.getCurrentStyle(this.contentWrapper); + + if (style.overflow == 'auto' || this.resize != null) { + this.contentWrapper.style.height = (this.div.offsetHeight - + this.title.offsetHeight - this.contentHeightCorrection) + 'px'; + } + + this.fireEvent(new mxEventObject(mxEvent.MAXIMIZE, 'event', evt)); + } else { + maximized = false; + + this.maximize.setAttribute('src', this.maximizeImage); + this.maximize.setAttribute('title', 'Maximize'); + this.contentWrapper.style.display = ''; + this.minimize.style.display = minDisplay; + + // Restores window state + this.div.style.left = x + 'px'; + this.div.style.top = y + 'px'; + + this.div.style.height = height; + this.div.style.width = width; + + var style = mxUtils.getCurrentStyle(this.contentWrapper); + + if (style.overflow == 'auto' || this.resize != null) { + this.contentWrapper.style.height = (this.div.offsetHeight - + this.title.offsetHeight - this.contentHeightCorrection) + 'px'; + } + + this.table.style.height = height; + this.table.style.width = width; + + if (this.resize != null) { + this.resize.style.visibility = ''; + } + + this.fireEvent(new mxEventObject(mxEvent.NORMALIZE, 'event', evt)); + } + mxEvent.consume(evt); - }); - - var dropHandler = mxUtils.bind(this, (evt)=> - { - mxEvent.removeGestureListeners(document, null, dragHandler, dropHandler); - this.fireEvent(new mxEventObject(mxEvent.MOVE_END, 'event', evt)); - mxEvent.consume(evt); - }); - - mxEvent.addGestureListeners(document, null, dragHandler, dropHandler); - this.fireEvent(new mxEventObject(mxEvent.MOVE_START, 'event', evt)); - mxEvent.consume(evt); - })); - - // Disables built-in pan and zoom in IE10 and later - if (mxClient.IS_POINTER) - { - this.title.style.touchAction = 'none'; - } -}; - -/** - * Function: setLocation - * - * Sets the upper, left corner of the window. - */ - setLocation = (x, y)=> - { - this.div.style.left = x + 'px'; - this.div.style.top = y + 'px'; - }; - -/** - * Function: getX - * - * Returns the current position on the x-axis. - */ -getX = ()=> -{ - return parseInt(this.div.style.left); -}; - -/** - * Function: getY - * - * Returns the current position on the y-axis. - */ -getY = ()=> -{ - return parseInt(this.div.style.top); -}; - -/** - * Function: installCloseHandler - * - * Adds the as a new image node in and installs the - * event. - */ -installCloseHandler = ()=> -{ - this.closeImg = document.createElement('img'); - - this.closeImg.setAttribute('src', this.closeImage); - this.closeImg.setAttribute('title', 'Close'); - this.closeImg.style.marginLeft = '2px'; - this.closeImg.style.cursor = 'pointer'; - this.closeImg.style.display = 'none'; - - this.buttons.appendChild(this.closeImg); - - mxEvent.addGestureListeners(this.closeImg, - mxUtils.bind(this, (evt)=> - { - this.fireEvent(new mxEventObject(mxEvent.CLOSE, 'event', evt)); - - if (this.destroyOnClose) - { - this.destroy(); } - else - { - this.setVisible(false); + }); + + mxEvent.addGestureListeners(this.maximize, funct); + mxEvent.addListener(this.title, 'dblclick', funct); + }; + + /** + * Function: installMoveHandler + * + * Installs the event listeners required for moving the window. + */ + installMoveHandler = () => { + this.title.style.cursor = 'move'; + + mxEvent.addGestureListeners(this.title, + mxUtils.bind(this, (evt) => { + var startX = mxEvent.getClientX(evt); + var startY = mxEvent.getClientY(evt); + var x = this.getX(); + var y = this.getY(); + + // Adds a temporary pair of listeners to intercept + // the gesture event in the document + var dragHandler = mxUtils.bind(this, (evt) => { + var dx = mxEvent.getClientX(evt) - startX; + var dy = mxEvent.getClientY(evt) - startY; + this.setLocation(x + dx, y + dy); + this.fireEvent(new mxEventObject(mxEvent.MOVE, 'event', evt)); + mxEvent.consume(evt); + }); + + var dropHandler = mxUtils.bind(this, (evt) => { + mxEvent.removeGestureListeners(document, null, dragHandler, dropHandler); + this.fireEvent(new mxEventObject(mxEvent.MOVE_END, 'event', evt)); + mxEvent.consume(evt); + }); + + mxEvent.addGestureListeners(document, null, dragHandler, dropHandler); + this.fireEvent(new mxEventObject(mxEvent.MOVE_START, 'event', evt)); + mxEvent.consume(evt); + })); + + // Disables built-in pan and zoom in IE10 and later + if (mxClient.IS_POINTER) { + this.title.style.touchAction = 'none'; + } + }; + + /** + * Function: setLocation + * + * Sets the upper, left corner of the window. + */ + setLocation = (x, y) => { + this.div.style.left = x + 'px'; + this.div.style.top = y + 'px'; + }; + + /** + * Function: getX + * + * Returns the current position on the x-axis. + */ + getX = () => { + return parseInt(this.div.style.left); + }; + + /** + * Function: getY + * + * Returns the current position on the y-axis. + */ + getY = () => { + return parseInt(this.div.style.top); + }; + + /** + * Function: installCloseHandler + * + * Adds the as a new image node in and installs the + * event. + */ + installCloseHandler = () => { + this.closeImg = document.createElement('img'); + + this.closeImg.setAttribute('src', this.closeImage); + this.closeImg.setAttribute('title', 'Close'); + this.closeImg.style.marginLeft = '2px'; + this.closeImg.style.cursor = 'pointer'; + this.closeImg.style.display = 'none'; + + this.buttons.appendChild(this.closeImg); + + mxEvent.addGestureListeners(this.closeImg, + mxUtils.bind(this, (evt) => { + this.fireEvent(new mxEventObject(mxEvent.CLOSE, 'event', evt)); + + if (this.destroyOnClose) { + this.destroy(); + } else { + this.setVisible(false); + } + + mxEvent.consume(evt); + })); + }; + + /** + * Function: setImage + * + * Sets the image associated with the window. + * + * Parameters: + * + * image - URL of the image to be used. + */ + setImage = (image) => { + this.image = document.createElement('img'); + this.image.setAttribute('src', image); + this.image.setAttribute('align', 'left'); + this.image.style.marginRight = '4px'; + this.image.style.marginLeft = '0px'; + this.image.style.marginTop = '-2px'; + + this.title.insertBefore(this.image, this.title.firstChild); + }; + + /** + * Function: setClosable + * + * Sets the image associated with the window. + * + * Parameters: + * + * closable - Boolean specifying if the window should be closable. + */ + setClosable = (closable) => { + this.closeImg.style.display = (closable) ? '' : 'none'; + }; + + /** + * Function: isVisible + * + * Returns true if the window is visible. + */ + isVisible = () => { + if (this.div != null) { + return this.div.style.display != 'none'; + } + + return false; + }; + + /** + * Function: setVisible + * + * Shows or hides the window depending on the given flag. + * + * Parameters: + * + * visible - Boolean indicating if the window should be made visible. + */ + setVisible = (visible) => { + if (this.div != null && this.isVisible() != visible) { + if (visible) { + this.show(); + } else { + this.hide(); } - - mxEvent.consume(evt); - })); -}; - -/** - * Function: setImage - * - * Sets the image associated with the window. - * - * Parameters: - * - * image - URL of the image to be used. - */ -setImage = (image)=> -{ - this.image = document.createElement('img'); - this.image.setAttribute('src', image); - this.image.setAttribute('align', 'left'); - this.image.style.marginRight = '4px'; - this.image.style.marginLeft = '0px'; - this.image.style.marginTop = '-2px'; - - this.title.insertBefore(this.image, this.title.firstChild); -}; - -/** - * Function: setClosable - * - * Sets the image associated with the window. - * - * Parameters: - * - * closable - Boolean specifying if the window should be closable. - */ -setClosable = (closable)=> -{ - this.closeImg.style.display = (closable) ? '' : 'none'; -}; - -/** - * Function: isVisible - * - * Returns true if the window is visible. - */ -isVisible = ()=> -{ - if (this.div != null) - { - return this.div.style.display != 'none'; - } - - return false; -}; - -/** - * Function: setVisible - * - * Shows or hides the window depending on the given flag. - * - * Parameters: - * - * visible - Boolean indicating if the window should be made visible. - */ -setVisible = (visible)=> -{ - if (this.div != null && this.isVisible() != visible) - { - if (visible) - { - this.show(); } - else - { - this.hide(); + }; + + /** + * Function: show + * + * Shows the window. + */ + show = () => { + this.div.style.display = ''; + this.activate(); + + var style = mxUtils.getCurrentStyle(this.contentWrapper); + + if ((style.overflow == 'auto' || this.resize != null) && + this.contentWrapper.style.display != 'none') { + this.contentWrapper.style.height = (this.div.offsetHeight - + this.title.offsetHeight - this.contentHeightCorrection) + 'px'; } - } -}; -/** - * Function: show - * - * Shows the window. - */ -show = ()=> -{ - this.div.style.display = ''; - this.activate(); - - var style = mxUtils.getCurrentStyle(this.contentWrapper); - - if ((style.overflow == 'auto' || this.resize != null) && - this.contentWrapper.style.display != 'none') - { - this.contentWrapper.style.height = (this.div.offsetHeight - - this.title.offsetHeight - this.contentHeightCorrection) + 'px'; - } - - this.fireEvent(new mxEventObject(mxEvent.SHOW)); -}; + this.fireEvent(new mxEventObject(mxEvent.SHOW)); + }; -/** - * Function: hide - * - * Hides the window. - */ -hide = ()=> -{ - this.div.style.display = 'none'; - this.fireEvent(new mxEventObject(mxEvent.HIDE)); -}; + /** + * Function: hide + * + * Hides the window. + */ + hide = () => { + this.div.style.display = 'none'; + this.fireEvent(new mxEventObject(mxEvent.HIDE)); + }; -/** - * Function: destroy - * - * Destroys the window and removes all associated resources. Fires a - * event prior to destroying the window. - */ -destroy = ()=> -{ - this.fireEvent(new mxEventObject(mxEvent.DESTROY)); - - if (this.div != null) - { - mxEvent.release(this.div); - this.div.parentNode.removeChild(this.div); - this.div = null; - } - - this.title = null; - this.content = null; - this.contentWrapper = null; -}; + /** + * Function: destroy + * + * Destroys the window and removes all associated resources. Fires a + * event prior to destroying the window. + */ + destroy = () => { + this.fireEvent(new mxEventObject(mxEvent.DESTROY)); + + if (this.div != null) { + mxEvent.release(this.div); + this.div.parentNode.removeChild(this.div); + this.div = null; + } + + this.title = null; + this.content = null; + this.contentWrapper = null; + }; +} + +export default mxWindow; diff --git a/src/js/util/mxXmlCanvas2D.js b/src/js/util/mxXmlCanvas2D.js index c914ea717..5e2afd1bc 100644 --- a/src/js/util/mxXmlCanvas2D.js +++ b/src/js/util/mxXmlCanvas2D.js @@ -2,1216 +2,1099 @@ * Copyright (c) 2006-2015, JGraph Ltd * Copyright (c) 2006-2015, Gaudenz Alder */ -/** - * Class: mxXmlCanvas2D - * - * Base class for all canvases. The following methods make up the public - * interface of the canvas 2D for all painting in mxGraph: - * - * - , - * - , , - * - , , , , , - * , , , , , - * , - * - , , , , - * , - * - , , , - * - , , , , - * - , , , , - * - , , - * - * is an additional method for drawing paths. This is - * a synthetic method, meaning that it is turned into a sequence of curves by - * default. Subclassers may add native support for arcs. - * - * Constructor: mxXmlCanvas2D - * - * Constructs a new abstract canvas. - */ -function mxXmlCanvas2D(root) -{ - mxAbstractCanvas2D.call(this); + +class mxXmlCanvas2D extends mxAbstractCanvas2D { + /** + * Variable: textEnabled + * + * Specifies if text output should be enabled. Default is true. + */ + mxXmlCanvas2textEnabled = true; /** - * Variable: root - * - * Reference to the container for the SVG content. + * Variable: compressed + * + * Specifies if the output should be compressed by removing redundant calls. + * Default is true. */ - this.root = root; + mxXmlCanvas2compressed = true; - // Writes default settings; - this.writeDefaults(); -}; + /** + * Class: mxXmlCanvas2D + * + * Base class for all canvases. The following methods make up the public + * interface of the canvas 2D for all painting in mxGraph: + * + * - , + * - , , + * - , , , , , + * , , , , , + * , + * - , , , , + * , + * - , , , + * - , , , , + * - , , , , + * - , , + * + * is an additional method for drawing paths. This is + * a synthetic method, meaning that it is turned into a sequence of curves by + * default. Subclassers may add native support for arcs. + * + * Constructor: mxXmlCanvas2D + * + * Constructs a new abstract canvas. + */ + constructor(root) { + super(); -/** - * Extends mxAbstractCanvas2D - */ -mxUtils.extend(mxXmlCanvas2D, mxAbstractCanvas2D); + /** + * Variable: root + * + * Reference to the container for the SVG content. + */ + this.root = root; -/** - * Variable: textEnabled - * - * Specifies if text output should be enabled. Default is true. - */ -mxXmlCanvas2textEnabled = true; + // Writes default settings; + this.writeDefaults(); + }; -/** - * Variable: compressed - * - * Specifies if the output should be compressed by removing redundant calls. - * Default is true. - */ -mxXmlCanvas2compressed = true; + /** + * Function: writeDefaults + * + * Writes the rendering defaults to : + */ + mxXmlCanvas2writeDefaults = () => { + var elem; -/** - * Function: writeDefaults - * - * Writes the rendering defaults to : - */ -mxXmlCanvas2writeDefaults = ()=> -{ - var elem; - - // Writes font defaults - elem = this.createElement('fontfamily'); - elem.setAttribute('family', mxConstants.DEFAULT_FONTFAMILY); - this.root.appendChild(elem); - - elem = this.createElement('fontsize'); - elem.setAttribute('size', mxConstants.DEFAULT_FONTSIZE); - this.root.appendChild(elem); - - // Writes shadow defaults - elem = this.createElement('shadowcolor'); - elem.setAttribute('color', mxConstants.SHADOWCOLOR); - this.root.appendChild(elem); - - elem = this.createElement('shadowalpha'); - elem.setAttribute('alpha', mxConstants.SHADOW_OPACITY); - this.root.appendChild(elem); - - elem = this.createElement('shadowoffset'); - elem.setAttribute('dx', mxConstants.SHADOW_OFFSET_X); - elem.setAttribute('dy', mxConstants.SHADOW_OFFSET_Y); - this.root.appendChild(elem); -}; + // Writes font defaults + elem = this.createElement('fontfamily'); + elem.setAttribute('family', mxConstants.DEFAULT_FONTFAMILY); + this.root.appendChild(elem); -/** - * Function: format - * - * Returns a formatted number with 2 decimal places. - */ -mxXmlCanvas2format = (value)=> -{ - return parseFloat(parseFloat(value).toFixed(2)); -}; + elem = this.createElement('fontsize'); + elem.setAttribute('size', mxConstants.DEFAULT_FONTSIZE); + this.root.appendChild(elem); -/** - * Function: createElement - * - * Creates the given element using the owner document of . - */ -mxXmlCanvas2createElement = (name)=> -{ - return this.root.ownerDocument.createElement(name); -}; + // Writes shadow defaults + elem = this.createElement('shadowcolor'); + elem.setAttribute('color', mxConstants.SHADOWCOLOR); + this.root.appendChild(elem); -/** - * Function: save - * - * Saves the drawing state. - */ -mxXmlCanvas2save = ()=> -{ - if (this.compressed) - { - mxAbstractCanvas2save.apply(this, arguments); - } - - this.root.appendChild(this.createElement('save')); -}; + elem = this.createElement('shadowalpha'); + elem.setAttribute('alpha', mxConstants.SHADOW_OPACITY); + this.root.appendChild(elem); -/** - * Function: restore - * - * Restores the drawing state. - */ -mxXmlCanvas2restore = ()=> -{ - if (this.compressed) - { - mxAbstractCanvas2restore.apply(this, arguments); - } - - this.root.appendChild(this.createElement('restore')); -}; + elem = this.createElement('shadowoffset'); + elem.setAttribute('dx', mxConstants.SHADOW_OFFSET_X); + elem.setAttribute('dy', mxConstants.SHADOW_OFFSET_Y); + this.root.appendChild(elem); + }; -/** - * Function: scale - * - * Scales the output. - * - * Parameters: - * - * scale - Number that represents the scale where 1 is equal to 100%. - */ -mxXmlCanvas2scale = (value)=> -{ - var elem = this.createElement('scale'); - elem.setAttribute('scale', value); - this.root.appendChild(elem); -}; + /** + * Function: format + * + * Returns a formatted number with 2 decimal places. + */ + mxXmlCanvas2format = (value) => { + return parseFloat(parseFloat(value).toFixed(2)); + }; -/** - * Function: translate - * - * Translates the output. - * - * Parameters: - * - * dx - Number that specifies the horizontal translation. - * dy - Number that specifies the vertical translation. - */ -mxXmlCanvas2translate = (dx, dy)=> -{ - var elem = this.createElement('translate'); - elem.setAttribute('dx', this.format(dx)); - elem.setAttribute('dy', this.format(dy)); - this.root.appendChild(elem); -}; + /** + * Function: createElement + * + * Creates the given element using the owner document of . + */ + mxXmlCanvas2createElement = (name) => { + return this.root.ownerDocument.createElement(name); + }; -/** - * Function: rotate - * - * Rotates and/or flips the output around a given center. (Note: Due to - * limitations in VML, the rotation cannot be concatenated.) - * - * Parameters: - * - * theta - Number that represents the angle of the rotation (in degrees). - * flipH - Boolean indicating if the output should be flipped horizontally. - * flipV - Boolean indicating if the output should be flipped vertically. - * cx - Number that represents the x-coordinate of the rotation center. - * cy - Number that represents the y-coordinate of the rotation center. - */ -mxXmlCanvas2rotate = (theta, flipH, flipV, cx, cy)=> -{ - var elem = this.createElement('rotate'); - - if (theta != 0 || flipH || flipV) - { - elem.setAttribute('theta', this.format(theta)); + /** + * Function: save + * + * Saves the drawing state. + */ + mxXmlCanvas2save = () => { + if (this.compressed) { + mxAbstractCanvas2save.apply(this, arguments); + } + + this.root.appendChild(this.createElement('save')); + }; + + /** + * Function: restore + * + * Restores the drawing state. + */ + mxXmlCanvas2restore = () => { + if (this.compressed) { + mxAbstractCanvas2restore.apply(this, arguments); + } + + this.root.appendChild(this.createElement('restore')); + }; + + /** + * Function: scale + * + * Scales the output. + * + * Parameters: + * + * scale - Number that represents the scale where 1 is equal to 100%. + */ + mxXmlCanvas2scale = (value) => { + var elem = this.createElement('scale'); + elem.setAttribute('scale', value); + this.root.appendChild(elem); + }; + + /** + * Function: translate + * + * Translates the output. + * + * Parameters: + * + * dx - Number that specifies the horizontal translation. + * dy - Number that specifies the vertical translation. + */ + mxXmlCanvas2translate = (dx, dy) => { + var elem = this.createElement('translate'); + elem.setAttribute('dx', this.format(dx)); + elem.setAttribute('dy', this.format(dy)); + this.root.appendChild(elem); + }; + + /** + * Function: rotate + * + * Rotates and/or flips the output around a given center. (Note: Due to + * limitations in VML, the rotation cannot be concatenated.) + * + * Parameters: + * + * theta - Number that represents the angle of the rotation (in degrees). + * flipH - Boolean indicating if the output should be flipped horizontally. + * flipV - Boolean indicating if the output should be flipped vertically. + * cx - Number that represents the x-coordinate of the rotation center. + * cy - Number that represents the y-coordinate of the rotation center. + */ + mxXmlCanvas2rotate = (theta, flipH, flipV, cx, cy) => { + var elem = this.createElement('rotate'); + + if (theta != 0 || flipH || flipV) { + elem.setAttribute('theta', this.format(theta)); + elem.setAttribute('flipH', (flipH) ? '1' : '0'); + elem.setAttribute('flipV', (flipV) ? '1' : '0'); + elem.setAttribute('cx', this.format(cx)); + elem.setAttribute('cy', this.format(cy)); + this.root.appendChild(elem); + } + }; + + /** + * Function: setAlpha + * + * Sets the current alpha. + * + * Parameters: + * + * value - Number that represents the new alpha. Possible values are between + * 1 (opaque) and 0 (transparent). + */ + mxXmlCanvas2setAlpha = (value) => { + if (this.compressed) { + if (this.state.alpha == value) { + return; + } + + mxAbstractCanvas2setAlpha.apply(this, arguments); + } + + var elem = this.createElement('alpha'); + elem.setAttribute('alpha', this.format(value)); + this.root.appendChild(elem); + }; + + /** + * Function: setFillAlpha + * + * Sets the current fill alpha. + * + * Parameters: + * + * value - Number that represents the new fill alpha. Possible values are between + * 1 (opaque) and 0 (transparent). + */ + mxXmlCanvas2setFillAlpha = (value) => { + if (this.compressed) { + if (this.state.fillAlpha == value) { + return; + } + + mxAbstractCanvas2setFillAlpha.apply(this, arguments); + } + + var elem = this.createElement('fillalpha'); + elem.setAttribute('alpha', this.format(value)); + this.root.appendChild(elem); + }; + + /** + * Function: setStrokeAlpha + * + * Sets the current stroke alpha. + * + * Parameters: + * + * value - Number that represents the new stroke alpha. Possible values are between + * 1 (opaque) and 0 (transparent). + */ + mxXmlCanvas2setStrokeAlpha = (value) => { + if (this.compressed) { + if (this.state.strokeAlpha == value) { + return; + } + + mxAbstractCanvas2setStrokeAlpha.apply(this, arguments); + } + + var elem = this.createElement('strokealpha'); + elem.setAttribute('alpha', this.format(value)); + this.root.appendChild(elem); + }; + + /** + * Function: setFillColor + * + * Sets the current fill color. + * + * Parameters: + * + * value - Hexadecimal representation of the color or 'none'. + */ + mxXmlCanvas2setFillColor = (value) => { + if (value == mxConstants.NONE) { + value = null; + } + + if (this.compressed) { + if (this.state.fillColor == value) { + return; + } + + mxAbstractCanvas2setFillColor.apply(this, arguments); + } + + var elem = this.createElement('fillcolor'); + elem.setAttribute('color', (value != null) ? value : mxConstants.NONE); + this.root.appendChild(elem); + }; + + /** + * Function: setGradient + * + * Sets the gradient. Note that the coordinates may be ignored by some implementations. + * + * Parameters: + * + * color1 - Hexadecimal representation of the start color. + * color2 - Hexadecimal representation of the end color. + * x - X-coordinate of the gradient region. + * y - y-coordinate of the gradient region. + * w - Width of the gradient region. + * h - Height of the gradient region. + * direction - One of , , + * or . + * alpha1 - Optional alpha of the start color. Default is 1. Possible values + * are between 1 (opaque) and 0 (transparent). + * alpha2 - Optional alpha of the end color. Default is 1. Possible values + * are between 1 (opaque) and 0 (transparent). + */ + mxXmlCanvas2setGradient = (color1, color2, x, y, w, h, direction, alpha1, alpha2) => { + if (color1 != null && color2 != null) { + mxAbstractCanvas2setGradient.apply(this, arguments); + + var elem = this.createElement('gradient'); + elem.setAttribute('c1', color1); + elem.setAttribute('c2', color2); + elem.setAttribute('x', this.format(x)); + elem.setAttribute('y', this.format(y)); + elem.setAttribute('w', this.format(w)); + elem.setAttribute('h', this.format(h)); + + // Default direction is south + if (direction != null) { + elem.setAttribute('direction', direction); + } + + if (alpha1 != null) { + elem.setAttribute('alpha1', alpha1); + } + + if (alpha2 != null) { + elem.setAttribute('alpha2', alpha2); + } + + this.root.appendChild(elem); + } + }; + + /** + * Function: setStrokeColor + * + * Sets the current stroke color. + * + * Parameters: + * + * value - Hexadecimal representation of the color or 'none'. + */ + mxXmlCanvas2setStrokeColor = (value) => { + if (value == mxConstants.NONE) { + value = null; + } + + if (this.compressed) { + if (this.state.strokeColor == value) { + return; + } + + mxAbstractCanvas2setStrokeColor.apply(this, arguments); + } + + var elem = this.createElement('strokecolor'); + elem.setAttribute('color', (value != null) ? value : mxConstants.NONE); + this.root.appendChild(elem); + }; + + /** + * Function: setStrokeWidth + * + * Sets the current stroke width. + * + * Parameters: + * + * value - Numeric representation of the stroke width. + */ + mxXmlCanvas2setStrokeWidth = (value) => { + if (this.compressed) { + if (this.state.strokeWidth == value) { + return; + } + + mxAbstractCanvas2setStrokeWidth.apply(this, arguments); + } + + var elem = this.createElement('strokewidth'); + elem.setAttribute('width', this.format(value)); + this.root.appendChild(elem); + }; + + /** + * Function: setDashed + * + * Enables or disables dashed lines. + * + * Parameters: + * + * value - Boolean that specifies if dashed lines should be enabled. + * value - Boolean that specifies if the stroke width should be ignored + * for the dash pattern. Default is false. + */ + mxXmlCanvas2setDashed = (value, fixDash) => { + if (this.compressed) { + if (this.state.dashed == value) { + return; + } + + mxAbstractCanvas2setDashed.apply(this, arguments); + } + + var elem = this.createElement('dashed'); + elem.setAttribute('dashed', (value) ? '1' : '0'); + + if (fixDash != null) { + elem.setAttribute('fixDash', (fixDash) ? '1' : '0'); + } + + this.root.appendChild(elem); + }; + + /** + * Function: setDashPattern + * + * Sets the current dash pattern. Default is '3 3'. + * + * Parameters: + * + * value - String that represents the dash pattern, which is a sequence of + * numbers defining the length of the dashes and the length of the spaces + * between the dashes. The lengths are relative to the line width - a length + * of 1 is equals to the line width. + */ + mxXmlCanvas2setDashPattern = (value) => { + if (this.compressed) { + if (this.state.dashPattern == value) { + return; + } + + mxAbstractCanvas2setDashPattern.apply(this, arguments); + } + + var elem = this.createElement('dashpattern'); + elem.setAttribute('pattern', value); + this.root.appendChild(elem); + }; + + /** + * Function: setLineCap + * + * Sets the line cap. Default is 'flat' which corresponds to 'butt' in SVG. + * + * Parameters: + * + * value - String that represents the line cap. Possible values are flat, round + * and square. + */ + mxXmlCanvas2setLineCap = (value) => { + if (this.compressed) { + if (this.state.lineCap == value) { + return; + } + + mxAbstractCanvas2setLineCap.apply(this, arguments); + } + + var elem = this.createElement('linecap'); + elem.setAttribute('cap', value); + this.root.appendChild(elem); + }; + + /** + * Function: setLineJoin + * + * Sets the line join. Default is 'miter'. + * + * Parameters: + * + * value - String that represents the line join. Possible values are miter, + * round and bevel. + */ + mxXmlCanvas2setLineJoin = (value) => { + if (this.compressed) { + if (this.state.lineJoin == value) { + return; + } + + mxAbstractCanvas2setLineJoin.apply(this, arguments); + } + + var elem = this.createElement('linejoin'); + elem.setAttribute('join', value); + this.root.appendChild(elem); + }; + + /** + * Function: setMiterLimit + * + * Sets the miter limit. Default is 10. + * + * Parameters: + * + * value - Number that represents the miter limit. + */ + mxXmlCanvas2setMiterLimit = (value) => { + if (this.compressed) { + if (this.state.miterLimit == value) { + return; + } + + mxAbstractCanvas2setMiterLimit.apply(this, arguments); + } + + var elem = this.createElement('miterlimit'); + elem.setAttribute('limit', value); + this.root.appendChild(elem); + }; + + /** + * Function: setFontColor + * + * Sets the current font color. Default is '#000000'. + * + * Parameters: + * + * value - Hexadecimal representation of the color or 'none'. + */ + mxXmlCanvas2setFontColor = (value) => { + if (this.textEnabled) { + if (value == mxConstants.NONE) { + value = null; + } + + if (this.compressed) { + if (this.state.fontColor == value) { + return; + } + + mxAbstractCanvas2setFontColor.apply(this, arguments); + } + + var elem = this.createElement('fontcolor'); + elem.setAttribute('color', (value != null) ? value : mxConstants.NONE); + this.root.appendChild(elem); + } + }; + + /** + * Function: setFontBackgroundColor + * + * Sets the current font background color. + * + * Parameters: + * + * value - Hexadecimal representation of the color or 'none'. + */ + mxXmlCanvas2setFontBackgroundColor = (value) => { + if (this.textEnabled) { + if (value == mxConstants.NONE) { + value = null; + } + + if (this.compressed) { + if (this.state.fontBackgroundColor == value) { + return; + } + + mxAbstractCanvas2setFontBackgroundColor.apply(this, arguments); + } + + var elem = this.createElement('fontbackgroundcolor'); + elem.setAttribute('color', (value != null) ? value : mxConstants.NONE); + this.root.appendChild(elem); + } + }; + + /** + * Function: setFontBorderColor + * + * Sets the current font border color. + * + * Parameters: + * + * value - Hexadecimal representation of the color or 'none'. + */ + mxXmlCanvas2setFontBorderColor = (value) => { + if (this.textEnabled) { + if (value == mxConstants.NONE) { + value = null; + } + + if (this.compressed) { + if (this.state.fontBorderColor == value) { + return; + } + + mxAbstractCanvas2setFontBorderColor.apply(this, arguments); + } + + var elem = this.createElement('fontbordercolor'); + elem.setAttribute('color', (value != null) ? value : mxConstants.NONE); + this.root.appendChild(elem); + } + }; + + /** + * Function: setFontSize + * + * Sets the current font size. Default is . + * + * Parameters: + * + * value - Numeric representation of the font size. + */ + mxXmlCanvas2setFontSize = (value) => { + if (this.textEnabled) { + if (this.compressed) { + if (this.state.fontSize == value) { + return; + } + + mxAbstractCanvas2setFontSize.apply(this, arguments); + } + + var elem = this.createElement('fontsize'); + elem.setAttribute('size', value); + this.root.appendChild(elem); + } + }; + + /** + * Function: setFontFamily + * + * Sets the current font family. Default is . + * + * Parameters: + * + * value - String representation of the font family. This handles the same + * values as the CSS font-family property. + */ + mxXmlCanvas2setFontFamily = (value) => { + if (this.textEnabled) { + if (this.compressed) { + if (this.state.fontFamily == value) { + return; + } + + mxAbstractCanvas2setFontFamily.apply(this, arguments); + } + + var elem = this.createElement('fontfamily'); + elem.setAttribute('family', value); + this.root.appendChild(elem); + } + }; + + /** + * Function: setFontStyle + * + * Sets the current font style. + * + * Parameters: + * + * value - Numeric representation of the font family. This is the sum of the + * font styles from . + */ + mxXmlCanvas2setFontStyle = (value) => { + if (this.textEnabled) { + if (value == null) { + value = 0; + } + + if (this.compressed) { + if (this.state.fontStyle == value) { + return; + } + + mxAbstractCanvas2setFontStyle.apply(this, arguments); + } + + var elem = this.createElement('fontstyle'); + elem.setAttribute('style', value); + this.root.appendChild(elem); + } + }; + + /** + * Function: setShadow + * + * Enables or disables shadows. + * + * Parameters: + * + * value - Boolean that specifies if shadows should be enabled. + */ + mxXmlCanvas2setShadow = (value) => { + if (this.compressed) { + if (this.state.shadow == value) { + return; + } + + mxAbstractCanvas2setShadow.apply(this, arguments); + } + + var elem = this.createElement('shadow'); + elem.setAttribute('enabled', (value) ? '1' : '0'); + this.root.appendChild(elem); + }; + + /** + * Function: setShadowColor + * + * Sets the current shadow color. Default is . + * + * Parameters: + * + * value - Hexadecimal representation of the color or 'none'. + */ + mxXmlCanvas2setShadowColor = (value) => { + if (this.compressed) { + if (value == mxConstants.NONE) { + value = null; + } + + if (this.state.shadowColor == value) { + return; + } + + mxAbstractCanvas2setShadowColor.apply(this, arguments); + } + + var elem = this.createElement('shadowcolor'); + elem.setAttribute('color', (value != null) ? value : mxConstants.NONE); + this.root.appendChild(elem); + }; + + /** + * Function: setShadowAlpha + * + * Sets the current shadows alpha. Default is . + * + * Parameters: + * + * value - Number that represents the new alpha. Possible values are between + * 1 (opaque) and 0 (transparent). + */ + mxXmlCanvas2setShadowAlpha = (value) => { + if (this.compressed) { + if (this.state.shadowAlpha == value) { + return; + } + + mxAbstractCanvas2setShadowAlpha.apply(this, arguments); + } + + var elem = this.createElement('shadowalpha'); + elem.setAttribute('alpha', value); + this.root.appendChild(elem); + + }; + + /** + * Function: setShadowOffset + * + * Sets the current shadow offset. + * + * Parameters: + * + * dx - Number that represents the horizontal offset of the shadow. + * dy - Number that represents the vertical offset of the shadow. + */ + mxXmlCanvas2setShadowOffset = (dx, dy) => { + if (this.compressed) { + if (this.state.shadowDx == dx && this.state.shadowDy == dy) { + return; + } + + mxAbstractCanvas2setShadowOffset.apply(this, arguments); + } + + var elem = this.createElement('shadowoffset'); + elem.setAttribute('dx', dx); + elem.setAttribute('dy', dy); + this.root.appendChild(elem); + + }; + + /** + * Function: rect + * + * Puts a rectangle into the drawing buffer. + * + * Parameters: + * + * x - Number that represents the x-coordinate of the rectangle. + * y - Number that represents the y-coordinate of the rectangle. + * w - Number that represents the width of the rectangle. + * h - Number that represents the height of the rectangle. + */ + mxXmlCanvas2rect = (x, y, w, h) => { + var elem = this.createElement('rect'); + elem.setAttribute('x', this.format(x)); + elem.setAttribute('y', this.format(y)); + elem.setAttribute('w', this.format(w)); + elem.setAttribute('h', this.format(h)); + this.root.appendChild(elem); + }; + + /** + * Function: roundrect + * + * Puts a rounded rectangle into the drawing buffer. + * + * Parameters: + * + * x - Number that represents the x-coordinate of the rectangle. + * y - Number that represents the y-coordinate of the rectangle. + * w - Number that represents the width of the rectangle. + * h - Number that represents the height of the rectangle. + * dx - Number that represents the horizontal rounding. + * dy - Number that represents the vertical rounding. + */ + mxXmlCanvas2roundrect = (x, y, w, h, dx, dy) => { + var elem = this.createElement('roundrect'); + elem.setAttribute('x', this.format(x)); + elem.setAttribute('y', this.format(y)); + elem.setAttribute('w', this.format(w)); + elem.setAttribute('h', this.format(h)); + elem.setAttribute('dx', this.format(dx)); + elem.setAttribute('dy', this.format(dy)); + this.root.appendChild(elem); + }; + + /** + * Function: ellipse + * + * Puts an ellipse into the drawing buffer. + * + * Parameters: + * + * x - Number that represents the x-coordinate of the ellipse. + * y - Number that represents the y-coordinate of the ellipse. + * w - Number that represents the width of the ellipse. + * h - Number that represents the height of the ellipse. + */ + mxXmlCanvas2ellipse = (x, y, w, h) => { + var elem = this.createElement('ellipse'); + elem.setAttribute('x', this.format(x)); + elem.setAttribute('y', this.format(y)); + elem.setAttribute('w', this.format(w)); + elem.setAttribute('h', this.format(h)); + this.root.appendChild(elem); + }; + + /** + * Function: image + * + * Paints an image. + * + * Parameters: + * + * x - Number that represents the x-coordinate of the image. + * y - Number that represents the y-coordinate of the image. + * w - Number that represents the width of the image. + * h - Number that represents the height of the image. + * src - String that specifies the URL of the image. + * aspect - Boolean indicating if the aspect of the image should be preserved. + * flipH - Boolean indicating if the image should be flipped horizontally. + * flipV - Boolean indicating if the image should be flipped vertically. + */ + mxXmlCanvas2image = (x, y, w, h, src, aspect, flipH, flipV) => { + src = this.converter.convert(src); + + // LATER: Add option for embedding images as base64. + var elem = this.createElement('image'); + elem.setAttribute('x', this.format(x)); + elem.setAttribute('y', this.format(y)); + elem.setAttribute('w', this.format(w)); + elem.setAttribute('h', this.format(h)); + elem.setAttribute('src', src); + elem.setAttribute('aspect', (aspect) ? '1' : '0'); elem.setAttribute('flipH', (flipH) ? '1' : '0'); elem.setAttribute('flipV', (flipV) ? '1' : '0'); - elem.setAttribute('cx', this.format(cx)); - elem.setAttribute('cy', this.format(cy)); this.root.appendChild(elem); - } -}; + }; -/** - * Function: setAlpha - * - * Sets the current alpha. - * - * Parameters: - * - * value - Number that represents the new alpha. Possible values are between - * 1 (opaque) and 0 (transparent). - */ -mxXmlCanvas2setAlpha = (value)=> -{ - if (this.compressed) - { - if (this.state.alpha == value) - { - return; - } - - mxAbstractCanvas2setAlpha.apply(this, arguments); - } - - var elem = this.createElement('alpha'); - elem.setAttribute('alpha', this.format(value)); - this.root.appendChild(elem); -}; + /** + * Function: begin + * + * Starts a new path and puts it into the drawing buffer. + */ + mxXmlCanvas2begin = () => { + this.root.appendChild(this.createElement('begin')); + this.lastX = 0; + this.lastY = 0; + }; -/** - * Function: setFillAlpha - * - * Sets the current fill alpha. - * - * Parameters: - * - * value - Number that represents the new fill alpha. Possible values are between - * 1 (opaque) and 0 (transparent). - */ -mxXmlCanvas2setFillAlpha = (value)=> -{ - if (this.compressed) - { - if (this.state.fillAlpha == value) - { - return; - } - - mxAbstractCanvas2setFillAlpha.apply(this, arguments); - } - - var elem = this.createElement('fillalpha'); - elem.setAttribute('alpha', this.format(value)); - this.root.appendChild(elem); -}; - -/** - * Function: setStrokeAlpha - * - * Sets the current stroke alpha. - * - * Parameters: - * - * value - Number that represents the new stroke alpha. Possible values are between - * 1 (opaque) and 0 (transparent). - */ -mxXmlCanvas2setStrokeAlpha = (value)=> -{ - if (this.compressed) - { - if (this.state.strokeAlpha == value) - { - return; - } - - mxAbstractCanvas2setStrokeAlpha.apply(this, arguments); - } - - var elem = this.createElement('strokealpha'); - elem.setAttribute('alpha', this.format(value)); - this.root.appendChild(elem); -}; - -/** - * Function: setFillColor - * - * Sets the current fill color. - * - * Parameters: - * - * value - Hexadecimal representation of the color or 'none'. - */ -mxXmlCanvas2setFillColor = (value)=> -{ - if (value == mxConstants.NONE) - { - value = null; - } - - if (this.compressed) - { - if (this.state.fillColor == value) - { - return; - } - - mxAbstractCanvas2setFillColor.apply(this, arguments); - } - - var elem = this.createElement('fillcolor'); - elem.setAttribute('color', (value != null) ? value : mxConstants.NONE); - this.root.appendChild(elem); -}; - -/** - * Function: setGradient - * - * Sets the gradient. Note that the coordinates may be ignored by some implementations. - * - * Parameters: - * - * color1 - Hexadecimal representation of the start color. - * color2 - Hexadecimal representation of the end color. - * x - X-coordinate of the gradient region. - * y - y-coordinate of the gradient region. - * w - Width of the gradient region. - * h - Height of the gradient region. - * direction - One of , , - * or . - * alpha1 - Optional alpha of the start color. Default is 1. Possible values - * are between 1 (opaque) and 0 (transparent). - * alpha2 - Optional alpha of the end color. Default is 1. Possible values - * are between 1 (opaque) and 0 (transparent). - */ -mxXmlCanvas2setGradient = (color1, color2, x, y, w, h, direction, alpha1, alpha2)=> -{ - if (color1 != null && color2 != null) - { - mxAbstractCanvas2setGradient.apply(this, arguments); - - var elem = this.createElement('gradient'); - elem.setAttribute('c1', color1); - elem.setAttribute('c2', color2); + /** + * Function: moveTo + * + * Moves the current path the given point. + * + * Parameters: + * + * x - Number that represents the x-coordinate of the point. + * y - Number that represents the y-coordinate of the point. + */ + mxXmlCanvas2moveTo = (x, y) => { + var elem = this.createElement('move'); elem.setAttribute('x', this.format(x)); elem.setAttribute('y', this.format(y)); - elem.setAttribute('w', this.format(w)); - elem.setAttribute('h', this.format(h)); - - // Default direction is south - if (direction != null) - { - elem.setAttribute('direction', direction); - } - - if (alpha1 != null) - { - elem.setAttribute('alpha1', alpha1); - } - - if (alpha2 != null) - { - elem.setAttribute('alpha2', alpha2); - } - this.root.appendChild(elem); - } -}; + this.lastX = x; + this.lastY = y; + }; -/** - * Function: setStrokeColor - * - * Sets the current stroke color. - * - * Parameters: - * - * value - Hexadecimal representation of the color or 'none'. - */ -mxXmlCanvas2setStrokeColor = (value)=> -{ - if (value == mxConstants.NONE) - { - value = null; - } - - if (this.compressed) - { - if (this.state.strokeColor == value) - { - return; - } - - mxAbstractCanvas2setStrokeColor.apply(this, arguments); - } - - var elem = this.createElement('strokecolor'); - elem.setAttribute('color', (value != null) ? value : mxConstants.NONE); - this.root.appendChild(elem); -}; - -/** - * Function: setStrokeWidth - * - * Sets the current stroke width. - * - * Parameters: - * - * value - Numeric representation of the stroke width. - */ -mxXmlCanvas2setStrokeWidth = (value)=> -{ - if (this.compressed) - { - if (this.state.strokeWidth == value) - { - return; - } - - mxAbstractCanvas2setStrokeWidth.apply(this, arguments); - } - - var elem = this.createElement('strokewidth'); - elem.setAttribute('width', this.format(value)); - this.root.appendChild(elem); -}; - -/** - * Function: setDashed - * - * Enables or disables dashed lines. - * - * Parameters: - * - * value - Boolean that specifies if dashed lines should be enabled. - * value - Boolean that specifies if the stroke width should be ignored - * for the dash pattern. Default is false. - */ -mxXmlCanvas2setDashed = (value, fixDash)=> -{ - if (this.compressed) - { - if (this.state.dashed == value) - { - return; - } - - mxAbstractCanvas2setDashed.apply(this, arguments); - } - - var elem = this.createElement('dashed'); - elem.setAttribute('dashed', (value) ? '1' : '0'); - - if (fixDash != null) - { - elem.setAttribute('fixDash', (fixDash) ? '1' : '0'); - } - - this.root.appendChild(elem); -}; - -/** - * Function: setDashPattern - * - * Sets the current dash pattern. Default is '3 3'. - * - * Parameters: - * - * value - String that represents the dash pattern, which is a sequence of - * numbers defining the length of the dashes and the length of the spaces - * between the dashes. The lengths are relative to the line width - a length - * of 1 is equals to the line width. - */ -mxXmlCanvas2setDashPattern = (value)=> -{ - if (this.compressed) - { - if (this.state.dashPattern == value) - { - return; - } - - mxAbstractCanvas2setDashPattern.apply(this, arguments); - } - - var elem = this.createElement('dashpattern'); - elem.setAttribute('pattern', value); - this.root.appendChild(elem); -}; - -/** - * Function: setLineCap - * - * Sets the line cap. Default is 'flat' which corresponds to 'butt' in SVG. - * - * Parameters: - * - * value - String that represents the line cap. Possible values are flat, round - * and square. - */ -mxXmlCanvas2setLineCap = (value)=> -{ - if (this.compressed) - { - if (this.state.lineCap == value) - { - return; - } - - mxAbstractCanvas2setLineCap.apply(this, arguments); - } - - var elem = this.createElement('linecap'); - elem.setAttribute('cap', value); - this.root.appendChild(elem); -}; - -/** - * Function: setLineJoin - * - * Sets the line join. Default is 'miter'. - * - * Parameters: - * - * value - String that represents the line join. Possible values are miter, - * round and bevel. - */ -mxXmlCanvas2setLineJoin = (value)=> -{ - if (this.compressed) - { - if (this.state.lineJoin == value) - { - return; - } - - mxAbstractCanvas2setLineJoin.apply(this, arguments); - } - - var elem = this.createElement('linejoin'); - elem.setAttribute('join', value); - this.root.appendChild(elem); -}; - -/** - * Function: setMiterLimit - * - * Sets the miter limit. Default is 10. - * - * Parameters: - * - * value - Number that represents the miter limit. - */ -mxXmlCanvas2setMiterLimit = (value)=> -{ - if (this.compressed) - { - if (this.state.miterLimit == value) - { - return; - } - - mxAbstractCanvas2setMiterLimit.apply(this, arguments); - } - - var elem = this.createElement('miterlimit'); - elem.setAttribute('limit', value); - this.root.appendChild(elem); -}; - -/** - * Function: setFontColor - * - * Sets the current font color. Default is '#000000'. - * - * Parameters: - * - * value - Hexadecimal representation of the color or 'none'. - */ -mxXmlCanvas2setFontColor = (value)=> -{ - if (this.textEnabled) - { - if (value == mxConstants.NONE) - { - value = null; - } - - if (this.compressed) - { - if (this.state.fontColor == value) - { - return; - } - - mxAbstractCanvas2setFontColor.apply(this, arguments); - } - - var elem = this.createElement('fontcolor'); - elem.setAttribute('color', (value != null) ? value : mxConstants.NONE); - this.root.appendChild(elem); - } -}; - -/** - * Function: setFontBackgroundColor - * - * Sets the current font background color. - * - * Parameters: - * - * value - Hexadecimal representation of the color or 'none'. - */ -mxXmlCanvas2setFontBackgroundColor = (value)=> -{ - if (this.textEnabled) - { - if (value == mxConstants.NONE) - { - value = null; - } - - if (this.compressed) - { - if (this.state.fontBackgroundColor == value) - { - return; - } - - mxAbstractCanvas2setFontBackgroundColor.apply(this, arguments); - } - - var elem = this.createElement('fontbackgroundcolor'); - elem.setAttribute('color', (value != null) ? value : mxConstants.NONE); - this.root.appendChild(elem); - } -}; - -/** - * Function: setFontBorderColor - * - * Sets the current font border color. - * - * Parameters: - * - * value - Hexadecimal representation of the color or 'none'. - */ -mxXmlCanvas2setFontBorderColor = (value)=> -{ - if (this.textEnabled) - { - if (value == mxConstants.NONE) - { - value = null; - } - - if (this.compressed) - { - if (this.state.fontBorderColor == value) - { - return; - } - - mxAbstractCanvas2setFontBorderColor.apply(this, arguments); - } - - var elem = this.createElement('fontbordercolor'); - elem.setAttribute('color', (value != null) ? value : mxConstants.NONE); - this.root.appendChild(elem); - } -}; - -/** - * Function: setFontSize - * - * Sets the current font size. Default is . - * - * Parameters: - * - * value - Numeric representation of the font size. - */ -mxXmlCanvas2setFontSize = (value)=> -{ - if (this.textEnabled) - { - if (this.compressed) - { - if (this.state.fontSize == value) - { - return; - } - - mxAbstractCanvas2setFontSize.apply(this, arguments); - } - - var elem = this.createElement('fontsize'); - elem.setAttribute('size', value); - this.root.appendChild(elem); - } -}; - -/** - * Function: setFontFamily - * - * Sets the current font family. Default is . - * - * Parameters: - * - * value - String representation of the font family. This handles the same - * values as the CSS font-family property. - */ -mxXmlCanvas2setFontFamily = (value)=> -{ - if (this.textEnabled) - { - if (this.compressed) - { - if (this.state.fontFamily == value) - { - return; - } - - mxAbstractCanvas2setFontFamily.apply(this, arguments); - } - - var elem = this.createElement('fontfamily'); - elem.setAttribute('family', value); - this.root.appendChild(elem); - } -}; - -/** - * Function: setFontStyle - * - * Sets the current font style. - * - * Parameters: - * - * value - Numeric representation of the font family. This is the sum of the - * font styles from . - */ -mxXmlCanvas2setFontStyle = (value)=> -{ - if (this.textEnabled) - { - if (value == null) - { - value = 0; - } - - if (this.compressed) - { - if (this.state.fontStyle == value) - { - return; - } - - mxAbstractCanvas2setFontStyle.apply(this, arguments); - } - - var elem = this.createElement('fontstyle'); - elem.setAttribute('style', value); - this.root.appendChild(elem); - } -}; - -/** - * Function: setShadow - * - * Enables or disables shadows. - * - * Parameters: - * - * value - Boolean that specifies if shadows should be enabled. - */ -mxXmlCanvas2setShadow = (value)=> -{ - if (this.compressed) - { - if (this.state.shadow == value) - { - return; - } - - mxAbstractCanvas2setShadow.apply(this, arguments); - } - - var elem = this.createElement('shadow'); - elem.setAttribute('enabled', (value) ? '1' : '0'); - this.root.appendChild(elem); -}; - -/** - * Function: setShadowColor - * - * Sets the current shadow color. Default is . - * - * Parameters: - * - * value - Hexadecimal representation of the color or 'none'. - */ -mxXmlCanvas2setShadowColor = (value)=> -{ - if (this.compressed) - { - if (value == mxConstants.NONE) - { - value = null; - } - - if (this.state.shadowColor == value) - { - return; - } - - mxAbstractCanvas2setShadowColor.apply(this, arguments); - } - - var elem = this.createElement('shadowcolor'); - elem.setAttribute('color', (value != null) ? value : mxConstants.NONE); - this.root.appendChild(elem); -}; - -/** - * Function: setShadowAlpha - * - * Sets the current shadows alpha. Default is . - * - * Parameters: - * - * value - Number that represents the new alpha. Possible values are between - * 1 (opaque) and 0 (transparent). - */ -mxXmlCanvas2setShadowAlpha = (value)=> -{ - if (this.compressed) - { - if (this.state.shadowAlpha == value) - { - return; - } - - mxAbstractCanvas2setShadowAlpha.apply(this, arguments); - } - - var elem = this.createElement('shadowalpha'); - elem.setAttribute('alpha', value); - this.root.appendChild(elem); - -}; - -/** - * Function: setShadowOffset - * - * Sets the current shadow offset. - * - * Parameters: - * - * dx - Number that represents the horizontal offset of the shadow. - * dy - Number that represents the vertical offset of the shadow. - */ -mxXmlCanvas2setShadowOffset = (dx, dy)=> -{ - if (this.compressed) - { - if (this.state.shadowDx == dx && this.state.shadowDy == dy) - { - return; - } - - mxAbstractCanvas2setShadowOffset.apply(this, arguments); - } - - var elem = this.createElement('shadowoffset'); - elem.setAttribute('dx', dx); - elem.setAttribute('dy', dy); - this.root.appendChild(elem); - -}; - -/** - * Function: rect - * - * Puts a rectangle into the drawing buffer. - * - * Parameters: - * - * x - Number that represents the x-coordinate of the rectangle. - * y - Number that represents the y-coordinate of the rectangle. - * w - Number that represents the width of the rectangle. - * h - Number that represents the height of the rectangle. - */ -mxXmlCanvas2rect = (x, y, w, h)=> -{ - var elem = this.createElement('rect'); - elem.setAttribute('x', this.format(x)); - elem.setAttribute('y', this.format(y)); - elem.setAttribute('w', this.format(w)); - elem.setAttribute('h', this.format(h)); - this.root.appendChild(elem); -}; - -/** - * Function: roundrect - * - * Puts a rounded rectangle into the drawing buffer. - * - * Parameters: - * - * x - Number that represents the x-coordinate of the rectangle. - * y - Number that represents the y-coordinate of the rectangle. - * w - Number that represents the width of the rectangle. - * h - Number that represents the height of the rectangle. - * dx - Number that represents the horizontal rounding. - * dy - Number that represents the vertical rounding. - */ -mxXmlCanvas2roundrect = (x, y, w, h, dx, dy)=> -{ - var elem = this.createElement('roundrect'); - elem.setAttribute('x', this.format(x)); - elem.setAttribute('y', this.format(y)); - elem.setAttribute('w', this.format(w)); - elem.setAttribute('h', this.format(h)); - elem.setAttribute('dx', this.format(dx)); - elem.setAttribute('dy', this.format(dy)); - this.root.appendChild(elem); -}; - -/** - * Function: ellipse - * - * Puts an ellipse into the drawing buffer. - * - * Parameters: - * - * x - Number that represents the x-coordinate of the ellipse. - * y - Number that represents the y-coordinate of the ellipse. - * w - Number that represents the width of the ellipse. - * h - Number that represents the height of the ellipse. - */ -mxXmlCanvas2ellipse = (x, y, w, h)=> -{ - var elem = this.createElement('ellipse'); - elem.setAttribute('x', this.format(x)); - elem.setAttribute('y', this.format(y)); - elem.setAttribute('w', this.format(w)); - elem.setAttribute('h', this.format(h)); - this.root.appendChild(elem); -}; - -/** - * Function: image - * - * Paints an image. - * - * Parameters: - * - * x - Number that represents the x-coordinate of the image. - * y - Number that represents the y-coordinate of the image. - * w - Number that represents the width of the image. - * h - Number that represents the height of the image. - * src - String that specifies the URL of the image. - * aspect - Boolean indicating if the aspect of the image should be preserved. - * flipH - Boolean indicating if the image should be flipped horizontally. - * flipV - Boolean indicating if the image should be flipped vertically. - */ -mxXmlCanvas2image = (x, y, w, h, src, aspect, flipH, flipV)=> -{ - src = this.converter.convert(src); - - // LATER: Add option for embedding images as base64. - var elem = this.createElement('image'); - elem.setAttribute('x', this.format(x)); - elem.setAttribute('y', this.format(y)); - elem.setAttribute('w', this.format(w)); - elem.setAttribute('h', this.format(h)); - elem.setAttribute('src', src); - elem.setAttribute('aspect', (aspect) ? '1' : '0'); - elem.setAttribute('flipH', (flipH) ? '1' : '0'); - elem.setAttribute('flipV', (flipV) ? '1' : '0'); - this.root.appendChild(elem); -}; - -/** - * Function: begin - * - * Starts a new path and puts it into the drawing buffer. - */ -mxXmlCanvas2begin = ()=> -{ - this.root.appendChild(this.createElement('begin')); - this.lastX = 0; - this.lastY = 0; -}; - -/** - * Function: moveTo - * - * Moves the current path the given point. - * - * Parameters: - * - * x - Number that represents the x-coordinate of the point. - * y - Number that represents the y-coordinate of the point. - */ -mxXmlCanvas2moveTo = (x, y)=> -{ - var elem = this.createElement('move'); - elem.setAttribute('x', this.format(x)); - elem.setAttribute('y', this.format(y)); - this.root.appendChild(elem); - this.lastX = x; - this.lastY = y; -}; - -/** - * Function: lineTo - * - * Draws a line to the given coordinates. - * - * Parameters: - * - * x - Number that represents the x-coordinate of the endpoint. - * y - Number that represents the y-coordinate of the endpoint. - */ -mxXmlCanvas2lineTo = (x, y)=> -{ - var elem = this.createElement('line'); - elem.setAttribute('x', this.format(x)); - elem.setAttribute('y', this.format(y)); - this.root.appendChild(elem); - this.lastX = x; - this.lastY = y; -}; - -/** - * Function: quadTo - * - * Adds a quadratic curve to the current path. - * - * Parameters: - * - * x1 - Number that represents the x-coordinate of the control point. - * y1 - Number that represents the y-coordinate of the control point. - * x2 - Number that represents the x-coordinate of the endpoint. - * y2 - Number that represents the y-coordinate of the endpoint. - */ -mxXmlCanvas2quadTo = (x1, y1, x2, y2)=> -{ - var elem = this.createElement('quad'); - elem.setAttribute('x1', this.format(x1)); - elem.setAttribute('y1', this.format(y1)); - elem.setAttribute('x2', this.format(x2)); - elem.setAttribute('y2', this.format(y2)); - this.root.appendChild(elem); - this.lastX = x2; - this.lastY = y2; -}; - -/** - * Function: curveTo - * - * Adds a bezier curve to the current path. - * - * Parameters: - * - * x1 - Number that represents the x-coordinate of the first control point. - * y1 - Number that represents the y-coordinate of the first control point. - * x2 - Number that represents the x-coordinate of the second control point. - * y2 - Number that represents the y-coordinate of the second control point. - * x3 - Number that represents the x-coordinate of the endpoint. - * y3 - Number that represents the y-coordinate of the endpoint. - */ -mxXmlCanvas2curveTo = (x1, y1, x2, y2, x3, y3)=> -{ - var elem = this.createElement('curve'); - elem.setAttribute('x1', this.format(x1)); - elem.setAttribute('y1', this.format(y1)); - elem.setAttribute('x2', this.format(x2)); - elem.setAttribute('y2', this.format(y2)); - elem.setAttribute('x3', this.format(x3)); - elem.setAttribute('y3', this.format(y3)); - this.root.appendChild(elem); - this.lastX = x3; - this.lastY = y3; -}; - -/** - * Function: close - * - * Closes the current path. - */ -mxXmlCanvas2close = ()=> -{ - this.root.appendChild(this.createElement('close')); -}; - -/** - * Function: text - * - * Paints the given text. Possible values for format are empty string for - * plain text and html for HTML markup. Background and border color as well - * as clipping is not available in plain text labels for VML. HTML labels - * are not available as part of shapes with no foreignObject support in SVG - * (eg. IE9, IE10). - * - * Parameters: - * - * x - Number that represents the x-coordinate of the text. - * y - Number that represents the y-coordinate of the text. - * w - Number that represents the available width for the text or 0 for automatic width. - * h - Number that represents the available height for the text or 0 for automatic height. - * str - String that specifies the text to be painted. - * align - String that represents the horizontal alignment. - * valign - String that represents the vertical alignment. - * wrap - Boolean that specifies if word-wrapping is enabled. Requires w > 0. - * format - Empty string for plain text or 'html' for HTML markup. - * overflow - Specifies the overflow behaviour of the label. Requires w > 0 and/or h > 0. - * clip - Boolean that specifies if the label should be clipped. Requires w > 0 and/or h > 0. - * rotation - Number that specifies the angle of the rotation around the anchor point of the text. - * dir - Optional string that specifies the text direction. Possible values are rtl and lrt. - */ -mxXmlCanvas2text = (x, y, w, h, str, align, valign, wrap, format, overflow, clip, rotation, dir)=> -{ - if (this.textEnabled && str != null) - { - if (mxUtils.isNode(str)) - { - str = mxUtils.getOuterHtml(str); - } - - var elem = this.createElement('text'); + /** + * Function: lineTo + * + * Draws a line to the given coordinates. + * + * Parameters: + * + * x - Number that represents the x-coordinate of the endpoint. + * y - Number that represents the y-coordinate of the endpoint. + */ + mxXmlCanvas2lineTo = (x, y) => { + var elem = this.createElement('line'); elem.setAttribute('x', this.format(x)); elem.setAttribute('y', this.format(y)); - elem.setAttribute('w', this.format(w)); - elem.setAttribute('h', this.format(h)); - elem.setAttribute('str', str); - - if (align != null) - { - elem.setAttribute('align', align); - } - - if (valign != null) - { - elem.setAttribute('valign', valign); - } - - elem.setAttribute('wrap', (wrap) ? '1' : '0'); - - if (format == null) - { - format = ''; - } - - elem.setAttribute('format', format); - - if (overflow != null) - { - elem.setAttribute('overflow', overflow); - } - - if (clip != null) - { - elem.setAttribute('clip', (clip) ? '1' : '0'); - } - - if (rotation != null) - { - elem.setAttribute('rotation', rotation); - } - - if (dir != null) - { - elem.setAttribute('dir', dir); - } - this.root.appendChild(elem); - } -}; + this.lastX = x; + this.lastY = y; + }; -/** - * Function: stroke - * - * Paints the outline of the current drawing buffer. - */ -mxXmlCanvas2stroke = ()=> -{ - this.root.appendChild(this.createElement('stroke')); -}; + /** + * Function: quadTo + * + * Adds a quadratic curve to the current path. + * + * Parameters: + * + * x1 - Number that represents the x-coordinate of the control point. + * y1 - Number that represents the y-coordinate of the control point. + * x2 - Number that represents the x-coordinate of the endpoint. + * y2 - Number that represents the y-coordinate of the endpoint. + */ + mxXmlCanvas2quadTo = (x1, y1, x2, y2) => { + var elem = this.createElement('quad'); + elem.setAttribute('x1', this.format(x1)); + elem.setAttribute('y1', this.format(y1)); + elem.setAttribute('x2', this.format(x2)); + elem.setAttribute('y2', this.format(y2)); + this.root.appendChild(elem); + this.lastX = x2; + this.lastY = y2; + }; -/** - * Function: fill - * - * Fills the current drawing buffer. - */ -mxXmlCanvas2fill = ()=> -{ - this.root.appendChild(this.createElement('fill')); -}; + /** + * Function: curveTo + * + * Adds a bezier curve to the current path. + * + * Parameters: + * + * x1 - Number that represents the x-coordinate of the first control point. + * y1 - Number that represents the y-coordinate of the first control point. + * x2 - Number that represents the x-coordinate of the second control point. + * y2 - Number that represents the y-coordinate of the second control point. + * x3 - Number that represents the x-coordinate of the endpoint. + * y3 - Number that represents the y-coordinate of the endpoint. + */ + mxXmlCanvas2curveTo = (x1, y1, x2, y2, x3, y3) => { + var elem = this.createElement('curve'); + elem.setAttribute('x1', this.format(x1)); + elem.setAttribute('y1', this.format(y1)); + elem.setAttribute('x2', this.format(x2)); + elem.setAttribute('y2', this.format(y2)); + elem.setAttribute('x3', this.format(x3)); + elem.setAttribute('y3', this.format(y3)); + this.root.appendChild(elem); + this.lastX = x3; + this.lastY = y3; + }; -/** - * Function: fillAndStroke - * - * Fills the current drawing buffer and its outline. - */ -mxXmlCanvas2fillAndStroke = ()=> -{ - this.root.appendChild(this.createElement('fillstroke')); -}; + /** + * Function: close + * + * Closes the current path. + */ + mxXmlCanvas2close = () => { + this.root.appendChild(this.createElement('close')); + }; + + /** + * Function: text + * + * Paints the given text. Possible values for format are empty string for + * plain text and html for HTML markup. Background and border color as well + * as clipping is not available in plain text labels for VML. HTML labels + * are not available as part of shapes with no foreignObject support in SVG + * (eg. IE9, IE10). + * + * Parameters: + * + * x - Number that represents the x-coordinate of the text. + * y - Number that represents the y-coordinate of the text. + * w - Number that represents the available width for the text or 0 for automatic width. + * h - Number that represents the available height for the text or 0 for automatic height. + * str - String that specifies the text to be painted. + * align - String that represents the horizontal alignment. + * valign - String that represents the vertical alignment. + * wrap - Boolean that specifies if word-wrapping is enabled. Requires w > 0. + * format - Empty string for plain text or 'html' for HTML markup. + * overflow - Specifies the overflow behaviour of the label. Requires w > 0 and/or h > 0. + * clip - Boolean that specifies if the label should be clipped. Requires w > 0 and/or h > 0. + * rotation - Number that specifies the angle of the rotation around the anchor point of the text. + * dir - Optional string that specifies the text direction. Possible values are rtl and lrt. + */ + mxXmlCanvas2text = (x, y, w, h, str, align, valign, wrap, format, overflow, clip, rotation, dir) => { + if (this.textEnabled && str != null) { + if (mxUtils.isNode(str)) { + str = mxUtils.getOuterHtml(str); + } + + var elem = this.createElement('text'); + elem.setAttribute('x', this.format(x)); + elem.setAttribute('y', this.format(y)); + elem.setAttribute('w', this.format(w)); + elem.setAttribute('h', this.format(h)); + elem.setAttribute('str', str); + + if (align != null) { + elem.setAttribute('align', align); + } + + if (valign != null) { + elem.setAttribute('valign', valign); + } + + elem.setAttribute('wrap', (wrap) ? '1' : '0'); + + if (format == null) { + format = ''; + } + + elem.setAttribute('format', format); + + if (overflow != null) { + elem.setAttribute('overflow', overflow); + } + + if (clip != null) { + elem.setAttribute('clip', (clip) ? '1' : '0'); + } + + if (rotation != null) { + elem.setAttribute('rotation', rotation); + } + + if (dir != null) { + elem.setAttribute('dir', dir); + } + + this.root.appendChild(elem); + } + }; + + /** + * Function: stroke + * + * Paints the outline of the current drawing buffer. + */ + mxXmlCanvas2stroke = () => { + this.root.appendChild(this.createElement('stroke')); + }; + + /** + * Function: fill + * + * Fills the current drawing buffer. + */ + mxXmlCanvas2fill = () => { + this.root.appendChild(this.createElement('fill')); + }; + + /** + * Function: fillAndStroke + * + * Fills the current drawing buffer and its outline. + */ + mxXmlCanvas2fillAndStroke = () => { + this.root.appendChild(this.createElement('fillstroke')); + }; +} + +export default mxXmlCanvas2D; diff --git a/src/js/view/mxCellEditor.js b/src/js/view/mxCellEditor.js index 1bb932804..5adfddda2 100644 --- a/src/js/view/mxCellEditor.js +++ b/src/js/view/mxCellEditor.js @@ -2,1156 +2,1024 @@ * Copyright (c) 2006-2015, JGraph Ltd * Copyright (c) 2006-2015, Gaudenz Alder */ -/** - * Class: mxCellEditor - * - * In-place editor for the graph. To control this editor, use - * , and - * . If is true then - * ctrl-enter or shift-enter can be used to create a linefeed. The F2 and - * escape keys can always be used to stop editing. - * - * To customize the location of the textbox in the graph, override - * as follows: - * - * (code) - * graph.cellEditor.getEditorBounds = (state)=> - * { - * var result = getEditorBounds.apply(this, arguments); - * - * if (this.graph.getModel().isEdge(state.cell)) - * { - * result.x = state.getCenterX() - result.width / 2; - * result.y = state.getCenterY() - result.height / 2; - * } - * - * return result; - * }; - * (end) - * - * Note that this hook is only called if is false. If is true, - * then is used to compute the current bounds of the textbox. - * - * The textarea uses the mxCellEditor CSS class. You can modify this class in - * your custom CSS. Note: You should modify the CSS after loading the client - * in the page. - * - * Example: - * - * To only allow numeric input in the in-place editor, use the following code. - * - * (code) - * var text = graph.cellEditor.textarea; - * - * mxEvent.addListener(text, 'keydown', function (evt) - * { - * if (!(evt.keyCode >= 48 && evt.keyCode <= 57) && - * !(evt.keyCode >= 96 && evt.keyCode <= 105)) - * { - * mxEvent.consume(evt); - * } - * }); - * (end) - * - * Placeholder: - * - * To implement a placeholder for cells without a label, use the - * variable. - * - * Resize in Chrome: - * - * Resize of the textarea is disabled by default. If you want to enable - * this feature extend and set this.textarea.style.resize = ''. - * - * To start editing on a key press event, the container of the graph - * should have focus or a focusable parent should be used to add the - * key press handler as follows. - * - * (code) - * mxEvent.addListener(graph.container, 'keypress', mxUtils.bind(this, (evt)=> - * { - * if (!graph.isEditing() && !graph.isSelectionEmpty() && evt.which !== 0 && - * !mxEvent.isAltDown(evt) && !mxEvent.isControlDown(evt) && !mxEvent.isMetaDown(evt)) - * { - * graph.startEditing(); - * - * if (mxClient.IS_FF) - * { - * graph.cellEditor.textarea.value = String.fromCharCode(evt.which); - * } - * } - * })); - * (end) - * - * To allow focus for a DIV, and hence to receive key press events, some browsers - * require it to have a valid tabindex attribute. In this case the following - * code may be used to keep the container focused. - * - * (code) - * var graphFireMouseEvent = graph.fireMouseEvent; - * graph.fireMouseEvent = (evtName, me, sender)=> - * { - * if (evtName == mxEvent.MOUSE_DOWN) - * { - * this.container.focus(); - * } - * - * graphFireMouseEvent.apply(this, arguments); - * }; - * (end) - * - * Constructor: mxCellEditor - * - * Constructs a new in-place editor for the specified graph. - * - * Parameters: - * - * graph - Reference to the enclosing . - */ -function mxCellEditor(graph) -{ - this.graph = graph; - - // Stops editing after zoom changes - this.zoomHandler = mxUtils.bind(this, ()=> - { - if (this.graph.isEditing()) - { - this.resize(); - } - }); - - this.graph.view.addListener(mxEvent.SCALE, this.zoomHandler); - this.graph.view.addListener(mxEvent.SCALE_AND_TRANSLATE, this.zoomHandler); - - // Adds handling of deleted cells while editing - this.changeHandler = mxUtils.bind(this, (sender)=> - { - if (this.editingCell != null && this.graph.getView().getState(this.editingCell) == null) - { - this.stopEditing(true); - } - }); - this.graph.getModel().addListener(mxEvent.CHANGE, this.changeHandler); -}; +import mxUtils from "../util/mxUtils" +import mxRectangle from "../util/mxRectangle"; +import mxEvent from "FIXME"; -/** - * Variable: graph - * - * Reference to the enclosing . - */ -graph = null; +class mxCellEditor { + /** + * Variable: graph + * + * Reference to the enclosing . + */ + graph = null; -/** - * Variable: textarea - * - * Holds the DIV that is used for text editing. Note that this may be null before the first - * edit. Instantiated in . - */ -textarea = null; + /** + * Variable: textarea + * + * Holds the DIV that is used for text editing. Note that this may be null before the first + * edit. Instantiated in . + */ + textarea = null; -/** - * Variable: editingCell - * - * Reference to the that is currently being edited. - */ -editingCell = null; + /** + * Variable: editingCell + * + * Reference to the that is currently being edited. + */ + editingCell = null; -/** - * Variable: trigger - * - * Reference to the event that was used to start editing. - */ -trigger = null; + /** + * Variable: trigger + * + * Reference to the event that was used to start editing. + */ + trigger = null; -/** - * Variable: modified - * - * Specifies if the label has been modified. - */ -modified = false; + /** + * Variable: modified + * + * Specifies if the label has been modified. + */ + modified = false; -/** - * Variable: autoSize - * - * Specifies if the textarea should be resized while the text is being edited. - * Default is true. - */ -autoSize = true; + /** + * Variable: autoSize + * + * Specifies if the textarea should be resized while the text is being edited. + * Default is true. + */ + autoSize = true; -/** - * Variable: selectText - * - * Specifies if the text should be selected when editing starts. Default is - * true. - */ -selectText = true; + /** + * Variable: selectText + * + * Specifies if the text should be selected when editing starts. Default is + * true. + */ + selectText = true; -/** - * Variable: emptyLabelText - * - * Text to be displayed for empty labels. Default is '' or '
' in Firefox as - * a workaround for the missing cursor bug for empty content editable. This can - * be set to eg. "[Type Here]" to easier visualize editing of empty labels. The - * value is only displayed before the first keystroke and is never used as the - * actual editing value. - */ -emptyLabelText = (mxClient.IS_FF) ? '
' : ''; + /** + * Variable: emptyLabelText + * + * Text to be displayed for empty labels. Default is '' or '
' in Firefox as + * a workaround for the missing cursor bug for empty content editable. This can + * be set to eg. "[Type Here]" to easier visualize editing of empty labels. The + * value is only displayed before the first keystroke and is never used as the + * actual editing value. + */ + emptyLabelText = (mxClient.IS_FF) ? '
' : ''; -/** - * Variable: escapeCancelsEditing - * - * If true, pressing the escape key will stop editing and not accept the new - * value. Change this to false to accept the new value on escape, and cancel - * editing on Shift+Escape instead. Default is true. - */ -escapeCancelsEditing = true; + /** + * Variable: escapeCancelsEditing + * + * If true, pressing the escape key will stop editing and not accept the new + * value. Change this to false to accept the new value on escape, and cancel + * editing on Shift+Escape instead. Default is true. + */ + escapeCancelsEditing = true; -/** - * Variable: textNode - * - * Reference to the label DOM node that has been hidden. - */ -textNode = ''; + /** + * Variable: textNode + * + * Reference to the label DOM node that has been hidden. + */ + textNode = ''; -/** - * Variable: zIndex - * - * Specifies the zIndex for the textarea. Default is 5. - */ -zIndex = 5; + /** + * Variable: zIndex + * + * Specifies the zIndex for the textarea. Default is 5. + */ + zIndex = 5; -/** - * Variable: minResize - * - * Defines the minimum width and height to be used in . Default is 0x20px. - */ -minResize = new mxRectangle(0, 20); + /** + * Variable: minResize + * + * Defines the minimum width and height to be used in . Default is 0x20px. + */ + minResize = new mxRectangle(0, 20); -/** - * Variable: wordWrapPadding - * - * Correction factor for word wrapping width. Default is 2 in quirks, 0 in IE - * 11 and 1 in all other browsers and modes. - */ -wordWrapPadding = 0; + /** + * Variable: wordWrapPadding + * + * Correction factor for word wrapping width. Default is 2 in quirks, 0 in IE + * 11 and 1 in all other browsers and modes. + */ + wordWrapPadding = 0; -/** - * Variable: blurEnabled - * - * If should be called if