converting to typescript
parent
16060a58bd
commit
889156b314
|
@ -14,6 +14,136 @@ import mxRectangle from '../util/datatypes/mxRectangle';
|
||||||
import mxCellState from '../util/datatypes/mxCellState';
|
import mxCellState from '../util/datatypes/mxCellState';
|
||||||
|
|
||||||
class mxText extends mxShape {
|
class mxText extends mxShape {
|
||||||
|
/**
|
||||||
|
* Class: mxText
|
||||||
|
*
|
||||||
|
* Extends <mxShape> to implement a text shape. To change vertical text from
|
||||||
|
* bottom to top to top to bottom, the following code can be used:
|
||||||
|
*
|
||||||
|
* (code)
|
||||||
|
* verticalTextRotation = 90;
|
||||||
|
* (end)
|
||||||
|
*
|
||||||
|
* Constructor: mxText
|
||||||
|
*
|
||||||
|
* Constructs a new text shape.
|
||||||
|
*
|
||||||
|
* Parameters:
|
||||||
|
*
|
||||||
|
* value - String that represents the text to be displayed. This is stored in
|
||||||
|
* <value>.
|
||||||
|
* bounds - <mxRectangle> that defines the bounds. This is stored in
|
||||||
|
* <mxShape.bounds>.
|
||||||
|
* align - Specifies the horizontal alignment. Default is ''. This is stored in
|
||||||
|
* <align>.
|
||||||
|
* valign - Specifies the vertical alignment. Default is ''. This is stored in
|
||||||
|
* <valign>.
|
||||||
|
* color - String that specifies the text color. Default is 'black'. This is
|
||||||
|
* stored in <color>.
|
||||||
|
* family - String that specifies the font family. Default is
|
||||||
|
* <mxConstants.DEFAULT_FONTFAMILY>. This is stored in <family>.
|
||||||
|
* size - Integer that specifies the font size. Default is
|
||||||
|
* <mxConstants.DEFAULT_FONTSIZE>. This is stored in <size>.
|
||||||
|
* fontStyle - Specifies the font style. Default is 0. This is stored in
|
||||||
|
* <fontStyle>.
|
||||||
|
* spacing - Integer that specifies the global spacing. Default is 2. This is
|
||||||
|
* stored in <spacing>.
|
||||||
|
* spacingTop - Integer that specifies the top spacing. Default is 0. The
|
||||||
|
* sum of the spacing and this is stored in <spacingTop>.
|
||||||
|
* spacingRight - Integer that specifies the right spacing. Default is 0. The
|
||||||
|
* sum of the spacing and this is stored in <spacingRight>.
|
||||||
|
* spacingBottom - Integer that specifies the bottom spacing. Default is 0.The
|
||||||
|
* sum of the spacing and this is stored in <spacingBottom>.
|
||||||
|
* spacingLeft - Integer that specifies the left spacing. Default is 0. The
|
||||||
|
* sum of the spacing and this is stored in <spacingLeft>.
|
||||||
|
* horizontal - Boolean that specifies if the label is horizontal. Default is
|
||||||
|
* true. This is stored in <horizontal>.
|
||||||
|
* background - String that specifies the background color. Default is null.
|
||||||
|
* This is stored in <background>.
|
||||||
|
* border - String that specifies the label border color. Default is null.
|
||||||
|
* This is stored in <border>.
|
||||||
|
* wrap - Specifies if word-wrapping should be enabled. Default is false.
|
||||||
|
* This is stored in <wrap>.
|
||||||
|
* clipped - Specifies if the label should be clipped. Default is false.
|
||||||
|
* This is stored in <clipped>.
|
||||||
|
* overflow - Value of the overflow style. Default is 'visible'.
|
||||||
|
*/
|
||||||
|
constructor(
|
||||||
|
value: string,
|
||||||
|
bounds: mxRectangle,
|
||||||
|
align: string=mxConstants.ALIGN_CENTER,
|
||||||
|
valign: string=mxConstants.ALIGN_MIDDLE,
|
||||||
|
color: string='black',
|
||||||
|
family: string=mxConstants.DEFAULT_FONTFAMILY,
|
||||||
|
size: number=mxConstants.DEFAULT_FONTSIZE,
|
||||||
|
fontStyle: number=mxConstants.DEFAULT_FONTSTYLE,
|
||||||
|
spacing: number=2,
|
||||||
|
spacingTop: number=0,
|
||||||
|
spacingRight: number=0,
|
||||||
|
spacingBottom: number=0,
|
||||||
|
spacingLeft: number=0,
|
||||||
|
horizontal: boolean=true,
|
||||||
|
background: string | null=null,
|
||||||
|
border: string | null=null,
|
||||||
|
wrap: boolean=false,
|
||||||
|
clipped: boolean=false,
|
||||||
|
overflow: string='visible',
|
||||||
|
labelPadding: number=0,
|
||||||
|
textDirection
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
this.value = value;
|
||||||
|
this.bounds = bounds;
|
||||||
|
this.color = color;
|
||||||
|
this.align = align;
|
||||||
|
this.valign = valign;
|
||||||
|
this.family = family;
|
||||||
|
this.size = size;
|
||||||
|
this.fontStyle = fontStyle;
|
||||||
|
this.spacing = parseInt(String(spacing || 2));
|
||||||
|
this.spacingTop = parseInt(String(spacing || 2)) + parseInt(String(spacingTop || 0));
|
||||||
|
this.spacingRight = parseInt(String(spacing || 2)) + parseInt(String(spacingRight || 0));
|
||||||
|
this.spacingBottom = parseInt(String(spacing || 2)) + parseInt(String(spacingBottom || 0));
|
||||||
|
this.spacingLeft = parseInt(String(spacing || 2)) + parseInt(String(spacingLeft || 0));
|
||||||
|
this.horizontal = horizontal;
|
||||||
|
this.background = background;
|
||||||
|
this.border = border;
|
||||||
|
this.wrap = wrap;
|
||||||
|
this.clipped = clipped;
|
||||||
|
this.overflow = overflow;
|
||||||
|
this.labelPadding = labelPadding;
|
||||||
|
this.textDirection = textDirection;
|
||||||
|
this.rotation = 0;
|
||||||
|
this.updateMargin();
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Document me!
|
||||||
|
value: string;
|
||||||
|
bounds: mxRectangle;
|
||||||
|
align: string=mxConstants.ALIGN_CENTER;
|
||||||
|
valign: string=mxConstants.ALIGN_MIDDLE;
|
||||||
|
color: string='black';
|
||||||
|
family: string=mxConstants.DEFAULT_FONTFAMILY;
|
||||||
|
size: number=mxConstants.DEFAULT_FONTSIZE;
|
||||||
|
fontStyle: number=mxConstants.DEFAULT_FONTSTYLE;
|
||||||
|
spacing: number=2;
|
||||||
|
spacingTop: number=0;
|
||||||
|
spacingRight: number=0;
|
||||||
|
spacingBottom: number=0;
|
||||||
|
spacingLeft: number=0;
|
||||||
|
horizontal: boolean=true;
|
||||||
|
background: string | null=null;
|
||||||
|
border: string | null=null;
|
||||||
|
wrap: boolean=false;
|
||||||
|
clipped: boolean=false;
|
||||||
|
overflow: string='visible';
|
||||||
|
labelPadding: number=0;
|
||||||
|
textDirection;
|
||||||
|
margin: mxRectangle | null=null;
|
||||||
|
unrotatedBoundingBox: mxRectangle | null=null;
|
||||||
|
flipH: boolean=false;
|
||||||
|
flipV: boolean=false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Variable: baseSpacingTop
|
* Variable: baseSpacingTop
|
||||||
*
|
*
|
||||||
|
@ -78,15 +208,6 @@ class mxText extends mxShape {
|
||||||
*/
|
*/
|
||||||
ignoreStringSize: boolean = false;
|
ignoreStringSize: boolean = false;
|
||||||
|
|
||||||
/**
|
|
||||||
* Variable: textWidthPadding
|
|
||||||
*
|
|
||||||
* Specifies the padding to be added to the text width for the bounding box.
|
|
||||||
* This is needed to make sure no clipping is applied to borders. Default is 4
|
|
||||||
* for IE 8 standards mode and 3 for all others.
|
|
||||||
*/
|
|
||||||
textWidthPadding: number = 3;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Variable: lastValue
|
* Variable: lastValue
|
||||||
*
|
*
|
||||||
|
@ -101,124 +222,8 @@ class mxText extends mxShape {
|
||||||
*/
|
*/
|
||||||
cacheEnabled: boolean = true;
|
cacheEnabled: boolean = true;
|
||||||
|
|
||||||
/**
|
|
||||||
* Class: mxText
|
|
||||||
*
|
|
||||||
* Extends <mxShape> to implement a text shape. To change vertical text from
|
|
||||||
* bottom to top to top to bottom, the following code can be used:
|
|
||||||
*
|
|
||||||
* (code)
|
|
||||||
* verticalTextRotation = 90;
|
|
||||||
* (end)
|
|
||||||
*
|
|
||||||
* Constructor: mxText
|
|
||||||
*
|
|
||||||
* Constructs a new text shape.
|
|
||||||
*
|
|
||||||
* Parameters:
|
|
||||||
*
|
|
||||||
* value - String that represents the text to be displayed. This is stored in
|
|
||||||
* <value>.
|
|
||||||
* bounds - <mxRectangle> that defines the bounds. This is stored in
|
|
||||||
* <mxShape.bounds>.
|
|
||||||
* align - Specifies the horizontal alignment. Default is ''. This is stored in
|
|
||||||
* <align>.
|
|
||||||
* valign - Specifies the vertical alignment. Default is ''. This is stored in
|
|
||||||
* <valign>.
|
|
||||||
* color - String that specifies the text color. Default is 'black'. This is
|
|
||||||
* stored in <color>.
|
|
||||||
* family - String that specifies the font family. Default is
|
|
||||||
* <mxConstants.DEFAULT_FONTFAMILY>. This is stored in <family>.
|
|
||||||
* size - Integer that specifies the font size. Default is
|
|
||||||
* <mxConstants.DEFAULT_FONTSIZE>. This is stored in <size>.
|
|
||||||
* fontStyle - Specifies the font style. Default is 0. This is stored in
|
|
||||||
* <fontStyle>.
|
|
||||||
* spacing - Integer that specifies the global spacing. Default is 2. This is
|
|
||||||
* stored in <spacing>.
|
|
||||||
* spacingTop - Integer that specifies the top spacing. Default is 0. The
|
|
||||||
* sum of the spacing and this is stored in <spacingTop>.
|
|
||||||
* spacingRight - Integer that specifies the right spacing. Default is 0. The
|
|
||||||
* sum of the spacing and this is stored in <spacingRight>.
|
|
||||||
* spacingBottom - Integer that specifies the bottom spacing. Default is 0.The
|
|
||||||
* sum of the spacing and this is stored in <spacingBottom>.
|
|
||||||
* spacingLeft - Integer that specifies the left spacing. Default is 0. The
|
|
||||||
* sum of the spacing and this is stored in <spacingLeft>.
|
|
||||||
* horizontal - Boolean that specifies if the label is horizontal. Default is
|
|
||||||
* true. This is stored in <horizontal>.
|
|
||||||
* background - String that specifies the background color. Default is null.
|
|
||||||
* This is stored in <background>.
|
|
||||||
* border - String that specifies the label border color. Default is null.
|
|
||||||
* This is stored in <border>.
|
|
||||||
* wrap - Specifies if word-wrapping should be enabled. Default is false.
|
|
||||||
* This is stored in <wrap>.
|
|
||||||
* clipped - Specifies if the label should be clipped. Default is false.
|
|
||||||
* This is stored in <clipped>.
|
|
||||||
* overflow - Value of the overflow style. Default is 'visible'.
|
|
||||||
*/
|
|
||||||
constructor(
|
|
||||||
value,
|
|
||||||
bounds,
|
|
||||||
align,
|
|
||||||
valign,
|
|
||||||
color,
|
|
||||||
family,
|
|
||||||
size,
|
|
||||||
fontStyle,
|
|
||||||
spacing,
|
|
||||||
spacingTop,
|
|
||||||
spacingRight,
|
|
||||||
spacingBottom,
|
|
||||||
spacingLeft,
|
|
||||||
horizontal,
|
|
||||||
background,
|
|
||||||
border,
|
|
||||||
wrap,
|
|
||||||
clipped,
|
|
||||||
overflow,
|
|
||||||
labelPadding,
|
|
||||||
textDirection
|
|
||||||
) {
|
|
||||||
super();
|
|
||||||
this.value = value;
|
|
||||||
this.bounds = bounds;
|
|
||||||
this.color = color != null ? color : 'black';
|
|
||||||
this.align = align != null ? align : mxConstants.ALIGN_CENTER;
|
|
||||||
this.valign = valign != null ? valign : mxConstants.ALIGN_MIDDLE;
|
|
||||||
this.family = family != null ? family : mxConstants.DEFAULT_FONTFAMILY;
|
|
||||||
this.size = size != null ? size : mxConstants.DEFAULT_FONTSIZE;
|
|
||||||
this.fontStyle =
|
|
||||||
fontStyle != null ? fontStyle : mxConstants.DEFAULT_FONTSTYLE;
|
|
||||||
this.spacing = parseInt(spacing || 2);
|
|
||||||
this.spacingTop = this.spacing + parseInt(spacingTop || 0);
|
|
||||||
this.spacingRight = this.spacing + parseInt(spacingRight || 0);
|
|
||||||
this.spacingBottom = this.spacing + parseInt(spacingBottom || 0);
|
|
||||||
this.spacingLeft = this.spacing + parseInt(spacingLeft || 0);
|
|
||||||
this.horizontal = horizontal != null ? horizontal : true;
|
|
||||||
this.background = background;
|
|
||||||
this.border = border;
|
|
||||||
this.wrap = wrap != null ? wrap : false;
|
|
||||||
this.clipped = clipped != null ? clipped : false;
|
|
||||||
this.overflow = overflow != null ? overflow : 'visible';
|
|
||||||
this.labelPadding = labelPadding != null ? labelPadding : 0;
|
|
||||||
this.textDirection = textDirection;
|
|
||||||
this.rotation = 0;
|
|
||||||
this.updateMargin();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Function: isHtmlAllowed
|
|
||||||
*
|
|
||||||
* Returns true if HTML is allowed for this shape. This implementation returns
|
|
||||||
* true if the browser is not in IE8 standards mode.
|
|
||||||
*/
|
|
||||||
isHtmlAllowed(): boolean {
|
|
||||||
return document.documentMode !== 8 || mxClient.IS_EM;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Function: getSvgScreenOffset
|
* Function: getSvgScreenOffset
|
||||||
*
|
|
||||||
* Disables offset in IE9 for crisper image output.
|
|
||||||
*/
|
*/
|
||||||
getSvgScreenOffset(): number {
|
getSvgScreenOffset(): number {
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -342,32 +347,17 @@ class mxText extends mxShape {
|
||||||
this.dialect === mxConstants.DIALECT_STRICTHTML)
|
this.dialect === mxConstants.DIALECT_STRICTHTML)
|
||||||
) {
|
) {
|
||||||
if (this.node.nodeName === 'DIV') {
|
if (this.node.nodeName === 'DIV') {
|
||||||
if (mxClient.IS_SVG) {
|
this.redrawHtmlShape();
|
||||||
this.redrawHtmlShapeWithCss3();
|
|
||||||
} else {
|
|
||||||
this.updateSize(
|
|
||||||
this.node,
|
|
||||||
this.state == null || this.state.view.textDiv == null
|
|
||||||
);
|
|
||||||
|
|
||||||
this.updateHtmlTransform();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.updateBoundingBox();
|
this.updateBoundingBox();
|
||||||
} else {
|
} else {
|
||||||
const canvas = this.createCanvas();
|
const canvas = this.createCanvas();
|
||||||
|
|
||||||
if (canvas != null && canvas.updateText != null) {
|
// Specifies if events should be handled
|
||||||
// Specifies if events should be handled
|
canvas.pointerEvents = this.pointerEvents;
|
||||||
canvas.pointerEvents = this.pointerEvents;
|
|
||||||
|
|
||||||
this.paint(canvas, true);
|
this.paint(canvas, true);
|
||||||
this.destroyCanvas(canvas);
|
this.destroyCanvas(canvas);
|
||||||
this.updateBoundingBox();
|
this.updateBoundingBox();
|
||||||
} else {
|
|
||||||
// Fallback if canvas does not support updateText (VML)
|
|
||||||
super.redraw();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
super.redraw();
|
super.redraw();
|
||||||
|
@ -406,7 +396,7 @@ class mxText extends mxShape {
|
||||||
delete this.background;
|
delete this.background;
|
||||||
delete this.border;
|
delete this.border;
|
||||||
this.textDirection = mxConstants.DEFAULT_TEXT_DIRECTION;
|
this.textDirection = mxConstants.DEFAULT_TEXT_DIRECTION;
|
||||||
delete this.margin;
|
this.margin = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -553,7 +543,6 @@ class mxText extends mxShape {
|
||||||
result = result.firstChild.firstChild.firstChild.firstChild.firstChild;
|
result = result.firstChild.firstChild.firstChild.firstChild.firstChild;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -596,74 +585,39 @@ class mxText extends mxShape {
|
||||||
let ow = null;
|
let ow = null;
|
||||||
let oh = null;
|
let oh = null;
|
||||||
|
|
||||||
if (node.ownerSVGElement != null) {
|
if (
|
||||||
if (
|
node.firstChild != null &&
|
||||||
node.firstChild != null &&
|
node.firstChild.firstChild != null &&
|
||||||
node.firstChild.firstChild != null &&
|
node.firstChild.firstChild.nodeName === 'foreignObject'
|
||||||
node.firstChild.firstChild.nodeName === 'foreignObject'
|
) {
|
||||||
) {
|
// Uses second inner DIV for font metrics
|
||||||
// Uses second inner DIV for font metrics
|
node = node.firstChild.firstChild.firstChild.firstChild;
|
||||||
node = node.firstChild.firstChild.firstChild.firstChild;
|
oh = node.offsetHeight * this.scale;
|
||||||
oh = node.offsetHeight * this.scale;
|
|
||||||
|
|
||||||
if (this.overflow === 'width') {
|
if (this.overflow === 'width') {
|
||||||
ow = this.boundingBox.width;
|
ow = this.boundingBox.width;
|
||||||
} else {
|
|
||||||
ow = node.offsetWidth * this.scale;
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
try {
|
ow = node.offsetWidth * this.scale;
|
||||||
const b = node.getBBox();
|
|
||||||
|
|
||||||
// Workaround for bounding box of empty string
|
|
||||||
if (
|
|
||||||
typeof this.value === 'string' &&
|
|
||||||
mxUtils.trim(this.value) == 0
|
|
||||||
) {
|
|
||||||
this.boundingBox = null;
|
|
||||||
} else if (b.width === 0 && b.height === 0) {
|
|
||||||
this.boundingBox = null;
|
|
||||||
} else {
|
|
||||||
this.boundingBox = new mxRectangle(b.x, b.y, b.width, b.height);
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
} catch (e) {
|
|
||||||
// Ignores NS_ERROR_FAILURE in FF if container display is none.
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const td = this.state != null ? this.state.view.textDiv : null;
|
try {
|
||||||
|
const b = node.getBBox();
|
||||||
// Use cached offset size
|
|
||||||
if (this.offsetWidth != null && this.offsetHeight != null) {
|
|
||||||
ow = this.offsetWidth * this.scale;
|
|
||||||
oh = this.offsetHeight * this.scale;
|
|
||||||
} else {
|
|
||||||
// Cannot get node size while container hidden so a
|
|
||||||
// shared temporary DIV is used for text measuring
|
|
||||||
if (td != null) {
|
|
||||||
this.updateFont(td);
|
|
||||||
this.updateSize(td, false);
|
|
||||||
this.updateInnerHtml(td);
|
|
||||||
|
|
||||||
node = td;
|
|
||||||
}
|
|
||||||
|
|
||||||
let sizeDiv = node;
|
|
||||||
|
|
||||||
|
// Workaround for bounding box of empty string
|
||||||
if (
|
if (
|
||||||
sizeDiv.firstChild != null &&
|
typeof this.value === 'string' &&
|
||||||
sizeDiv.firstChild.nodeName === 'DIV'
|
mxUtils.trim(this.value) == 0
|
||||||
) {
|
) {
|
||||||
sizeDiv = sizeDiv.firstChild;
|
this.boundingBox = null;
|
||||||
|
} else if (b.width === 0 && b.height === 0) {
|
||||||
|
this.boundingBox = null;
|
||||||
|
} else {
|
||||||
|
this.boundingBox = new mxRectangle(b.x, b.y, b.width, b.height);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.offsetWidth = sizeDiv.offsetWidth + this.textWidthPadding;
|
return;
|
||||||
this.offsetHeight = sizeDiv.offsetHeight;
|
} catch (e) {
|
||||||
|
// Ignores NS_ERROR_FAILURE in FF if container display is none.
|
||||||
ow = this.offsetWidth * this.scale;
|
|
||||||
oh = this.offsetHeight * this.scale;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -680,7 +634,7 @@ class mxText extends mxShape {
|
||||||
if (this.boundingBox != null) {
|
if (this.boundingBox != null) {
|
||||||
if (rot !== 0) {
|
if (rot !== 0) {
|
||||||
// Accounts for pre-rotated x and y
|
// Accounts for pre-rotated x and y
|
||||||
const bbox = mxUtils.getBoundingBox(
|
const bbox = <mxRectangle>mxUtils.getBoundingBox(
|
||||||
new mxRectangle(
|
new mxRectangle(
|
||||||
this.margin.x * this.boundingBox.width,
|
this.margin.x * this.boundingBox.width,
|
||||||
this.margin.y * this.boundingBox.height,
|
this.margin.y * this.boundingBox.height,
|
||||||
|
@ -836,37 +790,6 @@ class mxText extends mxShape {
|
||||||
* Updates the HTML node(s) to reflect the latest bounds and scale.
|
* Updates the HTML node(s) to reflect the latest bounds and scale.
|
||||||
*/
|
*/
|
||||||
redrawHtmlShape() {
|
redrawHtmlShape() {
|
||||||
if (mxClient.IS_SVG) {
|
|
||||||
this.redrawHtmlShapeWithCss3();
|
|
||||||
} else {
|
|
||||||
const { style } = this.node;
|
|
||||||
|
|
||||||
// Resets CSS styles
|
|
||||||
style.whiteSpace = 'normal';
|
|
||||||
style.overflow = '';
|
|
||||||
style.width = '';
|
|
||||||
style.height = '';
|
|
||||||
|
|
||||||
this.updateValue();
|
|
||||||
this.updateFont(this.node);
|
|
||||||
this.updateSize(
|
|
||||||
this.node,
|
|
||||||
this.state == null || this.state.view.textDiv == null
|
|
||||||
);
|
|
||||||
|
|
||||||
this.offsetWidth = null;
|
|
||||||
this.offsetHeight = null;
|
|
||||||
|
|
||||||
this.updateHtmlTransform();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Function: redrawHtmlShapeWithCss3
|
|
||||||
*
|
|
||||||
* Updates the HTML node(s) to reflect the latest bounds and scale.
|
|
||||||
*/
|
|
||||||
redrawHtmlShapeWithCss3() {
|
|
||||||
const w = Math.max(0, Math.round(this.bounds.width / this.scale));
|
const w = Math.max(0, Math.round(this.bounds.width / this.scale));
|
||||||
const h = Math.max(0, Math.round(this.bounds.height / this.scale));
|
const h = Math.max(0, Math.round(this.bounds.height / this.scale));
|
||||||
const flex =
|
const flex =
|
||||||
|
@ -931,56 +854,6 @@ class mxText extends mxShape {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Function: updateHtmlTransform
|
|
||||||
*
|
|
||||||
* Returns the spacing as an <mxPoint>.
|
|
||||||
*/
|
|
||||||
updateHtmlTransform() {
|
|
||||||
const theta = this.getTextRotation();
|
|
||||||
const { style } = this.node;
|
|
||||||
const dx = this.margin.x;
|
|
||||||
const dy = this.margin.y;
|
|
||||||
|
|
||||||
if (theta !== 0) {
|
|
||||||
mxUtils.setPrefixedStyle(
|
|
||||||
style,
|
|
||||||
'transformOrigin',
|
|
||||||
`${-dx * 100}%` + ` ${-dy * 100}%`
|
|
||||||
);
|
|
||||||
mxUtils.setPrefixedStyle(
|
|
||||||
style,
|
|
||||||
'transform',
|
|
||||||
`translate(${dx * 100}%` +
|
|
||||||
`,${dy * 100}%) ` +
|
|
||||||
`scale(${this.scale}) rotate(${theta}deg)`
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
mxUtils.setPrefixedStyle(style, 'transformOrigin', '0% 0%');
|
|
||||||
mxUtils.setPrefixedStyle(
|
|
||||||
style,
|
|
||||||
'transform',
|
|
||||||
`scale(${this.scale}) ` + `translate(${dx * 100}%` + `,${dy * 100}%)`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
style.left = `${Math.round(
|
|
||||||
this.bounds.x -
|
|
||||||
Math.ceil(
|
|
||||||
dx * (this.overflow !== 'fill' && this.overflow !== 'width' ? 3 : 1)
|
|
||||||
)
|
|
||||||
)}px`;
|
|
||||||
style.top = `${Math.round(
|
|
||||||
this.bounds.y - dy * (this.overflow !== 'fill' ? 3 : 1)
|
|
||||||
)}px`;
|
|
||||||
|
|
||||||
if (this.opacity < 100) {
|
|
||||||
style.opacity = this.opacity / 100;
|
|
||||||
} else {
|
|
||||||
style.opacity = '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Function: updateInnerHtml
|
* Function: updateInnerHtml
|
||||||
*
|
*
|
||||||
|
@ -1006,159 +879,6 @@ class mxText extends mxShape {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Function: updateHtmlFilter
|
|
||||||
*
|
|
||||||
* Rotated text rendering quality is bad for IE9 quirks/IE8 standards
|
|
||||||
*/
|
|
||||||
updateHtmlFilter() {
|
|
||||||
const { style } = this.node;
|
|
||||||
const dx = this.margin.x;
|
|
||||||
let dy = this.margin.y;
|
|
||||||
const s = this.scale;
|
|
||||||
|
|
||||||
// Resets filter before getting offsetWidth
|
|
||||||
mxUtils.setOpacity(this.node, this.opacity);
|
|
||||||
|
|
||||||
// Adds 1 to match table height in 1.x
|
|
||||||
let ow = 0;
|
|
||||||
let oh = 0;
|
|
||||||
const td = this.state != null ? this.state.view.textDiv : null;
|
|
||||||
let sizeDiv = this.node;
|
|
||||||
|
|
||||||
// Fallback for hidden text rendering in IE quirks mode
|
|
||||||
if (td != null) {
|
|
||||||
td.style.overflow = '';
|
|
||||||
td.style.height = '';
|
|
||||||
td.style.width = '';
|
|
||||||
|
|
||||||
this.updateFont(td);
|
|
||||||
this.updateSize(td, false);
|
|
||||||
this.updateInnerHtml(td);
|
|
||||||
|
|
||||||
const w = Math.round(this.bounds.width / this.scale);
|
|
||||||
|
|
||||||
if (this.wrap && w > 0) {
|
|
||||||
td.style.whiteSpace = 'normal';
|
|
||||||
td.style.wordWrap = mxConstants.WORD_WRAP;
|
|
||||||
ow = w;
|
|
||||||
|
|
||||||
if (this.clipped) {
|
|
||||||
ow = Math.min(ow, this.bounds.width);
|
|
||||||
}
|
|
||||||
|
|
||||||
td.style.width = `${ow}px`;
|
|
||||||
} else {
|
|
||||||
td.style.whiteSpace = 'nowrap';
|
|
||||||
}
|
|
||||||
|
|
||||||
sizeDiv = td;
|
|
||||||
|
|
||||||
if (sizeDiv.firstChild != null && sizeDiv.firstChild.nodeName === 'DIV') {
|
|
||||||
sizeDiv = sizeDiv.firstChild;
|
|
||||||
|
|
||||||
if (this.wrap && td.style.wordWrap === 'break-word') {
|
|
||||||
sizeDiv.style.width = '100%';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Required to update the height of the text box after wrapping width is known
|
|
||||||
if (!this.clipped && this.wrap && w > 0) {
|
|
||||||
ow = sizeDiv.offsetWidth + this.textWidthPadding;
|
|
||||||
td.style.width = `${ow}px`;
|
|
||||||
}
|
|
||||||
|
|
||||||
oh = sizeDiv.offsetHeight + 2;
|
|
||||||
} else if (
|
|
||||||
sizeDiv.firstChild != null &&
|
|
||||||
sizeDiv.firstChild.nodeName === 'DIV'
|
|
||||||
) {
|
|
||||||
sizeDiv = sizeDiv.firstChild;
|
|
||||||
oh = sizeDiv.offsetHeight;
|
|
||||||
}
|
|
||||||
|
|
||||||
ow = sizeDiv.offsetWidth + this.textWidthPadding;
|
|
||||||
|
|
||||||
if (this.clipped) {
|
|
||||||
oh = Math.min(oh, this.bounds.height);
|
|
||||||
}
|
|
||||||
|
|
||||||
let w = this.bounds.width / s;
|
|
||||||
let h = this.bounds.height / s;
|
|
||||||
|
|
||||||
// Handles special case for live preview with no wrapper DIV and no textDiv
|
|
||||||
if (this.overflow === 'fill') {
|
|
||||||
oh = h;
|
|
||||||
ow = w;
|
|
||||||
} else if (this.overflow === 'width') {
|
|
||||||
oh = sizeDiv.scrollHeight;
|
|
||||||
ow = w;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stores for later use
|
|
||||||
this.offsetWidth = ow;
|
|
||||||
this.offsetHeight = oh;
|
|
||||||
|
|
||||||
h = oh;
|
|
||||||
|
|
||||||
if (this.overflow !== 'fill' && this.overflow !== 'width') {
|
|
||||||
if (this.clipped) {
|
|
||||||
ow = Math.min(w, ow);
|
|
||||||
}
|
|
||||||
|
|
||||||
w = ow;
|
|
||||||
}
|
|
||||||
|
|
||||||
h *= s;
|
|
||||||
w *= s;
|
|
||||||
|
|
||||||
// Rotation case is handled via VML canvas
|
|
||||||
let rad = this.getTextRotation() * (Math.PI / 180);
|
|
||||||
|
|
||||||
// Precalculate cos and sin for the rotation
|
|
||||||
const real_cos = parseFloat(parseFloat(Math.cos(rad)).toFixed(8));
|
|
||||||
const real_sin = parseFloat(parseFloat(Math.sin(-rad)).toFixed(8));
|
|
||||||
|
|
||||||
rad %= 2 * Math.PI;
|
|
||||||
|
|
||||||
if (rad < 0) {
|
|
||||||
rad += 2 * Math.PI;
|
|
||||||
}
|
|
||||||
|
|
||||||
rad %= Math.PI;
|
|
||||||
|
|
||||||
if (rad > Math.PI / 2) {
|
|
||||||
rad = Math.PI - rad;
|
|
||||||
}
|
|
||||||
|
|
||||||
const cos = Math.cos(rad);
|
|
||||||
const sin = Math.sin(-rad);
|
|
||||||
|
|
||||||
const tx = w * -(dx + 0.5);
|
|
||||||
const ty = h * -(dy + 0.5);
|
|
||||||
|
|
||||||
const top_fix = (h - h * cos + w * sin) / 2 + real_sin * tx - real_cos * ty;
|
|
||||||
const left_fix =
|
|
||||||
(w - w * cos + h * sin) / 2 - real_cos * tx - real_sin * ty;
|
|
||||||
|
|
||||||
if (rad !== 0) {
|
|
||||||
const f = `progid:DXImageTransform.Microsoft.Matrix(M11=${real_cos}, M12=${real_sin}, M21=${-real_sin}, M22=${real_cos}, sizingMethod='auto expand')`;
|
|
||||||
|
|
||||||
if (style.filter != null && style.filter.length > 0) {
|
|
||||||
style.filter += ` ${f}`;
|
|
||||||
} else {
|
|
||||||
style.filter = f;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Workaround for rendering offsets
|
|
||||||
dy = 0;
|
|
||||||
|
|
||||||
style.zoom = s;
|
|
||||||
style.left = `${Math.round(this.bounds.x + left_fix - w / 2)}px`;
|
|
||||||
style.top = `${Math.round(this.bounds.y + top_fix - h / 2 + dy)}px`;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Function: updateValue
|
* Function: updateValue
|
||||||
*
|
*
|
||||||
|
|
|
@ -32,12 +32,10 @@ import mxSvgCanvas2D from "../../util/canvas/mxSvgCanvas2D";
|
||||||
* 1. This is stored in <strokewidth>.
|
* 1. This is stored in <strokewidth>.
|
||||||
*/
|
*/
|
||||||
class mxRectangleShape extends mxShape {
|
class mxRectangleShape extends mxShape {
|
||||||
strokewidth: number;
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
bounds: mxRectangle | null=null,
|
bounds: mxRectangle | null=null,
|
||||||
fill: string = '#FFFFFF',
|
fill: string | null = '#FFFFFF',
|
||||||
stroke: string = '#000000',
|
stroke: string | null = '#000000',
|
||||||
strokewidth: number = 1
|
strokewidth: number = 1
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
|
@ -47,6 +45,9 @@ class mxRectangleShape extends mxShape {
|
||||||
this.strokewidth = strokewidth;
|
this.strokewidth = strokewidth;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Document me!
|
||||||
|
strokewidth: number;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Function: isHtmlAllowed
|
* Function: isHtmlAllowed
|
||||||
*
|
*
|
||||||
|
|
|
@ -15,31 +15,31 @@ import mxGraph from "../../view/graph/mxGraph";
|
||||||
|
|
||||||
class mxCellState extends mxRectangle {
|
class mxCellState extends mxRectangle {
|
||||||
// TODO: Document me!!
|
// TODO: Document me!!
|
||||||
cellBounds: mxRectangle | undefined;
|
cellBounds: mxRectangle | null = null;
|
||||||
|
|
||||||
paintBounds: mxRectangle | undefined;
|
paintBounds: mxRectangle | null = null;
|
||||||
|
|
||||||
boundingBox: mxRectangle | undefined;
|
boundingBox: mxRectangle | null = null;
|
||||||
|
|
||||||
// Used by mxCellRenderer's createControl()
|
// Used by mxCellRenderer's createControl()
|
||||||
control: mxShape | undefined;
|
control: mxShape | null = null;
|
||||||
|
|
||||||
// Used by mxCellRenderer's createCellOverlays()
|
// Used by mxCellRenderer's createCellOverlays()
|
||||||
overlays: any[] | null | undefined;
|
overlays: any[] | null = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Variable: view
|
* Variable: view
|
||||||
*
|
*
|
||||||
* Reference to the enclosing <mxGraphView>.
|
* Reference to the enclosing <mxGraphView>.
|
||||||
*/
|
*/
|
||||||
view: mxGraphView | null = null;
|
view: mxGraphView;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Variable: cell
|
* Variable: cell
|
||||||
*
|
*
|
||||||
* Reference to the <mxCell> that is represented by this state.
|
* Reference to the <mxCell> that is represented by this state.
|
||||||
*/
|
*/
|
||||||
cell: mxCell | null = null;
|
cell: mxCell;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Variable: style
|
* Variable: style
|
||||||
|
@ -47,7 +47,7 @@ class mxCellState extends mxRectangle {
|
||||||
* Contains an array of key, value pairs that represent the style of the
|
* Contains an array of key, value pairs that represent the style of the
|
||||||
* cell.
|
* cell.
|
||||||
*/
|
*/
|
||||||
style: any | null = null; // TODO: Important - make the style type more strictly typed to allow for typescript checking of individual properties!!!
|
style: any; // TODO: Important - make the style type more strictly typed to allow for typescript checking of individual properties!!!
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Variable: invalidStyle
|
* Variable: invalidStyle
|
||||||
|
@ -69,7 +69,7 @@ class mxCellState extends mxRectangle {
|
||||||
* <mxPoint> that holds the origin for all child cells. Default is a new
|
* <mxPoint> that holds the origin for all child cells. Default is a new
|
||||||
* empty <mxPoint>.
|
* empty <mxPoint>.
|
||||||
*/
|
*/
|
||||||
origin: mxPoint | null = null;
|
origin: mxPoint;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Variable: absolutePoints
|
* Variable: absolutePoints
|
||||||
|
@ -86,7 +86,7 @@ class mxCellState extends mxRectangle {
|
||||||
* absolute coordinates of the label position. For vertices, this is the
|
* absolute coordinates of the label position. For vertices, this is the
|
||||||
* offset of the label relative to the top, left corner of the vertex.
|
* offset of the label relative to the top, left corner of the vertex.
|
||||||
*/
|
*/
|
||||||
absoluteOffset: mxPoint | null = null;
|
absoluteOffset: mxPoint;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Variable: visibleSourceState
|
* Variable: visibleSourceState
|
||||||
|
|
|
@ -488,7 +488,7 @@ class mxCell {
|
||||||
*
|
*
|
||||||
* child - Child whose index should be returned.
|
* child - Child whose index should be returned.
|
||||||
*/
|
*/
|
||||||
getIndex(child: mxCell): number {
|
getIndex(child: mxCell | null): number {
|
||||||
return mxUtils.indexOf(this.children, child);
|
return mxUtils.indexOf(this.children, child);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,8 +14,141 @@ import mxGraph from '../graph/mxGraph';
|
||||||
import mxCell from './mxCell';
|
import mxCell from './mxCell';
|
||||||
import mxMouseEvent from '../../util/event/mxMouseEvent';
|
import mxMouseEvent from '../../util/event/mxMouseEvent';
|
||||||
import mxCellState from '../../util/datatypes/mxCellState';
|
import mxCellState from '../../util/datatypes/mxCellState';
|
||||||
|
import mxShape from "../../shape/mxShape";
|
||||||
|
import mxEventObject from "../../util/event/mxEventObject";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class: mxCellEditor
|
||||||
|
*
|
||||||
|
* In-place editor for the graph. To control this editor, use
|
||||||
|
* <mxGraph.invokesStopCellEditing>, <mxGraph.enterStopsCellEditing> and
|
||||||
|
* <mxGraph.escapeEnabled>. If <mxGraph.enterStopsCellEditing> 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
|
||||||
|
* <getEditorBounds> as follows:
|
||||||
|
*
|
||||||
|
* (code)
|
||||||
|
* graph.cellEditor.getEditorBounds = (state)=>
|
||||||
|
* {
|
||||||
|
* let 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 <autoSize> is false. If <autoSize> is true,
|
||||||
|
* then <mxShape.getLabelBounds> 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)
|
||||||
|
* let 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
|
||||||
|
* <emptyLabelText> variable.
|
||||||
|
*
|
||||||
|
* Resize in Chrome:
|
||||||
|
*
|
||||||
|
* Resize of the textarea is disabled by default. If you want to enable
|
||||||
|
* this feature extend <init> 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)
|
||||||
|
* let 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 <mxGraph>.
|
||||||
|
*/
|
||||||
class mxCellEditor {
|
class mxCellEditor {
|
||||||
|
constructor(graph: mxGraph) {
|
||||||
|
this.graph = graph;
|
||||||
|
|
||||||
|
// Stops editing after zoom changes
|
||||||
|
this.zoomHandler = () => {
|
||||||
|
if (this.graph.isEditing()) {
|
||||||
|
this.resize();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handling of deleted cells while editing
|
||||||
|
this.changeHandler = (sender: any) => {
|
||||||
|
if (
|
||||||
|
this.editingCell != null &&
|
||||||
|
this.graph.getView().getState(this.editingCell, false) == null
|
||||||
|
) {
|
||||||
|
this.stopEditing(true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.graph.view.addListener(mxEvent.SCALE, this.zoomHandler);
|
||||||
|
this.graph.view.addListener(mxEvent.SCALE_AND_TRANSLATE, this.zoomHandler);
|
||||||
|
this.graph.getModel().addListener(mxEvent.CHANGE, this.changeHandler);
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: Document me!
|
// TODO: Document me!
|
||||||
changeHandler: Function | null;
|
changeHandler: Function | null;
|
||||||
|
|
||||||
|
@ -25,16 +158,16 @@ class mxCellEditor {
|
||||||
|
|
||||||
bounds: mxRectangle | null = null;
|
bounds: mxRectangle | null = null;
|
||||||
|
|
||||||
resizeThread: number | null;
|
resizeThread: number | null = null;
|
||||||
|
|
||||||
textDirection: '' | 'auto' | 'ltr' | 'rtl' | null;
|
textDirection: '' | 'auto' | 'ltr' | 'rtl' | null = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Variable: graph
|
* Variable: graph
|
||||||
*
|
*
|
||||||
* Reference to the enclosing <mxGraph>.
|
* Reference to the enclosing <mxGraph>.
|
||||||
*/
|
*/
|
||||||
graph: mxGraph = null;
|
graph: mxGraph;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Variable: textarea
|
* Variable: textarea
|
||||||
|
@ -42,21 +175,21 @@ class mxCellEditor {
|
||||||
* Holds the DIV that is used for text editing. Note that this may be null before the first
|
* Holds the DIV that is used for text editing. Note that this may be null before the first
|
||||||
* edit. Instantiated in <init>.
|
* edit. Instantiated in <init>.
|
||||||
*/
|
*/
|
||||||
textarea: HTMLElement = null;
|
textarea: HTMLElement | null = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Variable: editingCell
|
* Variable: editingCell
|
||||||
*
|
*
|
||||||
* Reference to the <mxCell> that is currently being edited.
|
* Reference to the <mxCell> that is currently being edited.
|
||||||
*/
|
*/
|
||||||
editingCell: mxCell = null;
|
editingCell: mxCell | null = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Variable: trigger
|
* Variable: trigger
|
||||||
*
|
*
|
||||||
* Reference to the event that was used to start editing.
|
* Reference to the event that was used to start editing.
|
||||||
*/
|
*/
|
||||||
trigger: mxMouseEvent = null;
|
trigger: mxMouseEvent | null = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Variable: modified
|
* Variable: modified
|
||||||
|
@ -106,7 +239,7 @@ class mxCellEditor {
|
||||||
*
|
*
|
||||||
* Reference to the label DOM node that has been hidden.
|
* Reference to the label DOM node that has been hidden.
|
||||||
*/
|
*/
|
||||||
textNode: HTMLElement | null = null;
|
textNode: SVGGElement | null = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Variable: zIndex
|
* Variable: zIndex
|
||||||
|
@ -153,137 +286,6 @@ class mxCellEditor {
|
||||||
*/
|
*/
|
||||||
align: string | null = null;
|
align: string | null = null;
|
||||||
|
|
||||||
/**
|
|
||||||
* Class: mxCellEditor
|
|
||||||
*
|
|
||||||
* In-place editor for the graph. To control this editor, use
|
|
||||||
* <mxGraph.invokesStopCellEditing>, <mxGraph.enterStopsCellEditing> and
|
|
||||||
* <mxGraph.escapeEnabled>. If <mxGraph.enterStopsCellEditing> 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
|
|
||||||
* <getEditorBounds> as follows:
|
|
||||||
*
|
|
||||||
* (code)
|
|
||||||
* graph.cellEditor.getEditorBounds = (state)=>
|
|
||||||
* {
|
|
||||||
* let 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 <autoSize> is false. If <autoSize> is true,
|
|
||||||
* then <mxShape.getLabelBounds> 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)
|
|
||||||
* let 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
|
|
||||||
* <emptyLabelText> variable.
|
|
||||||
*
|
|
||||||
* Resize in Chrome:
|
|
||||||
*
|
|
||||||
* Resize of the textarea is disabled by default. If you want to enable
|
|
||||||
* this feature extend <init> 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)
|
|
||||||
* let 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 <mxGraph>.
|
|
||||||
*/
|
|
||||||
constructor(graph: mxGraph) {
|
|
||||||
this.graph = graph;
|
|
||||||
|
|
||||||
// Stops editing after zoom changes
|
|
||||||
this.zoomHandler = () => {
|
|
||||||
if (this.graph.isEditing()) {
|
|
||||||
this.resize();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Handling of deleted cells while editing
|
|
||||||
this.changeHandler = sender => {
|
|
||||||
if (
|
|
||||||
this.editingCell != null &&
|
|
||||||
this.graph.getView().getState(this.editingCell, false) == null
|
|
||||||
) {
|
|
||||||
this.stopEditing(true);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
this.graph.view.addListener(mxEvent.SCALE, this.zoomHandler);
|
|
||||||
this.graph.view.addListener(mxEvent.SCALE_AND_TRANSLATE, this.zoomHandler);
|
|
||||||
this.graph.getModel().addListener(mxEvent.CHANGE, this.changeHandler);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Function: init
|
* Function: init
|
||||||
*
|
*
|
||||||
|
@ -309,8 +311,8 @@ class mxCellEditor {
|
||||||
*
|
*
|
||||||
* Called in <stopEditing> if cancel is false to invoke <mxGraph.labelChanged>.
|
* Called in <stopEditing> if cancel is false to invoke <mxGraph.labelChanged>.
|
||||||
*/
|
*/
|
||||||
applyValue(state: mxCellState, value): void {
|
applyValue(state: mxCellState, value: any): void {
|
||||||
this.graph.labelChanged(state.cell, value, this.trigger);
|
this.graph.labelChanged(state.cell, value, <mxMouseEvent>this.trigger);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -318,7 +320,7 @@ class mxCellEditor {
|
||||||
*
|
*
|
||||||
* Sets the temporary horizontal alignment for the current editing session.
|
* Sets the temporary horizontal alignment for the current editing session.
|
||||||
*/
|
*/
|
||||||
setAlign(align) {
|
setAlign(align: string): void {
|
||||||
if (this.textarea != null) {
|
if (this.textarea != null) {
|
||||||
this.textarea.style.textAlign = align;
|
this.textarea.style.textAlign = align;
|
||||||
}
|
}
|
||||||
|
@ -332,7 +334,7 @@ class mxCellEditor {
|
||||||
*
|
*
|
||||||
* Gets the initial editing value for the given cell.
|
* Gets the initial editing value for the given cell.
|
||||||
*/
|
*/
|
||||||
getInitialValue(state, trigger) {
|
getInitialValue(state: mxCellState, trigger: mxEventObject | mxMouseEvent) {
|
||||||
let result = mxUtils.htmlEntities(
|
let result = mxUtils.htmlEntities(
|
||||||
this.graph.getEditingValue(state.cell, trigger),
|
this.graph.getEditingValue(state.cell, trigger),
|
||||||
false
|
false
|
||||||
|
@ -347,6 +349,7 @@ class mxCellEditor {
|
||||||
* Returns the current editing value.
|
* Returns the current editing value.
|
||||||
*/
|
*/
|
||||||
getCurrentValue(state: mxCellState) {
|
getCurrentValue(state: mxCellState) {
|
||||||
|
// @ts-ignore
|
||||||
return mxUtils.extractTextWithWhitespace(this.textarea.childNodes);
|
return mxUtils.extractTextWithWhitespace(this.textarea.childNodes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -356,7 +359,7 @@ class mxCellEditor {
|
||||||
* Returns true if <escapeCancelsEditing> is true and shift, control and meta
|
* Returns true if <escapeCancelsEditing> is true and shift, control and meta
|
||||||
* are not pressed.
|
* are not pressed.
|
||||||
*/
|
*/
|
||||||
isCancelEditingKeyEvent(evt) {
|
isCancelEditingKeyEvent(evt: KeyboardEvent) {
|
||||||
return (
|
return (
|
||||||
this.escapeCancelsEditing ||
|
this.escapeCancelsEditing ||
|
||||||
mxEvent.isShiftDown(evt) ||
|
mxEvent.isShiftDown(evt) ||
|
||||||
|
@ -370,31 +373,31 @@ class mxCellEditor {
|
||||||
*
|
*
|
||||||
* Installs listeners for focus, change and standard key event handling.
|
* Installs listeners for focus, change and standard key event handling.
|
||||||
*/
|
*/
|
||||||
installListeners(elt) {
|
installListeners(elt: HTMLElement) {
|
||||||
// Applies value if text is dragged
|
// Applies value if text is dragged
|
||||||
// LATER: Gesture mouse events ignored for starting move
|
// LATER: Gesture mouse events ignored for starting move
|
||||||
mxEvent.addListener(
|
mxEvent.addListener(
|
||||||
elt,
|
elt,
|
||||||
'dragstart',
|
'dragstart',
|
||||||
mxUtils.bind(this, evt => {
|
(evt: Event) => {
|
||||||
this.graph.stopEditing(false);
|
this.graph.stopEditing(false);
|
||||||
mxEvent.consume(evt);
|
mxEvent.consume(evt);
|
||||||
})
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
// Applies value if focus is lost
|
// Applies value if focus is lost
|
||||||
mxEvent.addListener(
|
mxEvent.addListener(
|
||||||
elt,
|
elt,
|
||||||
'blur',
|
'blur',
|
||||||
mxUtils.bind(this, evt => {
|
(evt: Event) => {
|
||||||
if (this.blurEnabled) {
|
if (this.blurEnabled) {
|
||||||
this.focusLost();
|
this.focusLost();
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
// Updates modified state and handles placeholder text
|
// Updates modified state and handles placeholder text
|
||||||
mxEvent.addListener(elt, 'keydown', evt => {
|
mxEvent.addListener(elt, 'keydown', (evt: KeyboardEvent) => {
|
||||||
if (!mxEvent.isConsumed(evt)) {
|
if (!mxEvent.isConsumed(evt)) {
|
||||||
if (this.isStopEditingEvent(evt)) {
|
if (this.isStopEditingEvent(evt)) {
|
||||||
this.graph.stopEditing(false);
|
this.graph.stopEditing(false);
|
||||||
|
@ -407,7 +410,7 @@ class mxCellEditor {
|
||||||
});
|
});
|
||||||
|
|
||||||
// Keypress only fires if printable key was pressed and handles removing the empty placeholder
|
// Keypress only fires if printable key was pressed and handles removing the empty placeholder
|
||||||
const keypressHandler = evt => {
|
const keypressHandler = (evt: KeyboardEvent) => {
|
||||||
if (this.editingCell != null) {
|
if (this.editingCell != null) {
|
||||||
// Clears the initial empty label on the first keystroke
|
// Clears the initial empty label on the first keystroke
|
||||||
// and workaround for FF which fires keypress for delete and backspace
|
// and workaround for FF which fires keypress for delete and backspace
|
||||||
|
@ -428,18 +431,20 @@ class mxCellEditor {
|
||||||
mxEvent.addListener(elt, 'paste', keypressHandler);
|
mxEvent.addListener(elt, 'paste', keypressHandler);
|
||||||
|
|
||||||
// Handler for updating the empty label text value after a change
|
// Handler for updating the empty label text value after a change
|
||||||
const keyupHandler = evt => {
|
const keyupHandler = (evt: KeyboardEvent) => {
|
||||||
if (this.editingCell != null) {
|
if (this.editingCell != null) {
|
||||||
// Uses an optional text value for sempty labels which is cleared
|
// Uses an optional text value for sempty labels which is cleared
|
||||||
// when the first keystroke appears. This makes it easier to see
|
// when the first keystroke appears. This makes it easier to see
|
||||||
// that a label is being edited even if the label is empty.
|
// that a label is being edited even if the label is empty.
|
||||||
// In Safari and FF, an empty text is represented by <BR> which isn't enough to force a valid size
|
// In Safari and FF, an empty text is represented by <BR> which isn't enough to force a valid size
|
||||||
|
const textarea = <HTMLElement>this.textarea;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
this.textarea.innerHTML.length === 0 ||
|
textarea.innerHTML.length === 0 ||
|
||||||
this.textarea.innerHTML === '<br>'
|
textarea.innerHTML === '<br>'
|
||||||
) {
|
) {
|
||||||
this.textarea.innerHTML = this.getEmptyLabelText();
|
textarea.innerHTML = this.getEmptyLabelText();
|
||||||
this.clearOnChange = this.textarea.innerHTML.length > 0;
|
this.clearOnChange = textarea.innerHTML.length > 0;
|
||||||
} else {
|
} else {
|
||||||
this.clearOnChange = false;
|
this.clearOnChange = false;
|
||||||
}
|
}
|
||||||
|
@ -453,7 +458,7 @@ class mxCellEditor {
|
||||||
// Adds automatic resizing of the textbox while typing using input, keyup and/or DOM change events
|
// Adds automatic resizing of the textbox while typing using input, keyup and/or DOM change events
|
||||||
const evtName = 'input';
|
const evtName = 'input';
|
||||||
|
|
||||||
const resizeHandler = mxUtils.bind(this, evt => {
|
const resizeHandler = (evt: Event) => {
|
||||||
if (
|
if (
|
||||||
this.editingCell != null &&
|
this.editingCell != null &&
|
||||||
this.autoSize &&
|
this.autoSize &&
|
||||||
|
@ -470,7 +475,7 @@ class mxCellEditor {
|
||||||
this.resize();
|
this.resize();
|
||||||
}, 0);
|
}, 0);
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
|
|
||||||
mxEvent.addListener(elt, evtName, resizeHandler);
|
mxEvent.addListener(elt, evtName, resizeHandler);
|
||||||
mxEvent.addListener(window, 'resize', resizeHandler);
|
mxEvent.addListener(window, 'resize', resizeHandler);
|
||||||
|
@ -485,7 +490,7 @@ class mxCellEditor {
|
||||||
* returns true if F2 is pressed of if <mxGraph.enterStopsCellEditing> is true
|
* returns true if F2 is pressed of if <mxGraph.enterStopsCellEditing> is true
|
||||||
* and enter is pressed without control or shift.
|
* and enter is pressed without control or shift.
|
||||||
*/
|
*/
|
||||||
isStopEditingEvent(evt) {
|
isStopEditingEvent(evt: KeyboardEvent) {
|
||||||
return (
|
return (
|
||||||
evt.keyCode === 113 /* F2 */ ||
|
evt.keyCode === 113 /* F2 */ ||
|
||||||
(this.graph.isEnterStopsCellEditing() &&
|
(this.graph.isEnterStopsCellEditing() &&
|
||||||
|
@ -500,7 +505,7 @@ class mxCellEditor {
|
||||||
*
|
*
|
||||||
* Returns true if this editor is the source for the given native event.
|
* Returns true if this editor is the source for the given native event.
|
||||||
*/
|
*/
|
||||||
isEventSource(evt): boolean {
|
isEventSource(evt: Event): boolean {
|
||||||
return mxEvent.getSource(evt) === this.textarea;
|
return mxEvent.getSource(evt) === this.textarea;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -519,7 +524,7 @@ class mxCellEditor {
|
||||||
|
|
||||||
if (!this.autoSize || state.style.overflow === 'fill') {
|
if (!this.autoSize || state.style.overflow === 'fill') {
|
||||||
// Specifies the bounds of the editor box
|
// Specifies the bounds of the editor box
|
||||||
this.bounds = this.getEditorBounds(state);
|
this.bounds = <mxRectangle>this.getEditorBounds(state);
|
||||||
this.textarea.style.width = `${Math.round(
|
this.textarea.style.width = `${Math.round(
|
||||||
this.bounds.width / scale
|
this.bounds.width / scale
|
||||||
)}px`;
|
)}px`;
|
||||||
|
@ -621,6 +626,7 @@ class mxCellEditor {
|
||||||
!state.view.graph.cellRenderer.legacySpacing ||
|
!state.view.graph.cellRenderer.legacySpacing ||
|
||||||
state.style[mxConstants.STYLE_OVERFLOW] !== 'width'
|
state.style[mxConstants.STYLE_OVERFLOW] !== 'width'
|
||||||
) {
|
) {
|
||||||
|
// @ts-ignore
|
||||||
const dummy = new mxText(); // FIXME!!!! ===================================================================================================
|
const dummy = new mxText(); // FIXME!!!! ===================================================================================================
|
||||||
const spacing = parseInt(state.style.spacing || 2) * scale;
|
const spacing = parseInt(state.style.spacing || 2) * scale;
|
||||||
const spacingTop =
|
const spacingTop =
|
||||||
|
@ -758,7 +764,7 @@ class mxCellEditor {
|
||||||
* Returns the background color for the in-place editor. This implementation
|
* Returns the background color for the in-place editor. This implementation
|
||||||
* always returns null.
|
* always returns null.
|
||||||
*/
|
*/
|
||||||
getBackgroundColor(state): string | null {
|
getBackgroundColor(state: mxCellState): string | null {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -772,7 +778,7 @@ class mxCellEditor {
|
||||||
* cell - <mxCell> to start editing.
|
* cell - <mxCell> to start editing.
|
||||||
* trigger - Optional mouse event that triggered the editor.
|
* trigger - Optional mouse event that triggered the editor.
|
||||||
*/
|
*/
|
||||||
startEditing(cell: mxCell, trigger: MouseEvent | null = null): void {
|
startEditing(cell: mxCell, trigger: mxMouseEvent | null = null): void {
|
||||||
this.stopEditing(true);
|
this.stopEditing(true);
|
||||||
this.align = null;
|
this.align = null;
|
||||||
|
|
||||||
|
@ -819,19 +825,20 @@ class mxCellEditor {
|
||||||
txtDecor.push('line-through');
|
txtDecor.push('line-through');
|
||||||
}
|
}
|
||||||
|
|
||||||
this.textarea.style.lineHeight = mxConstants.ABSOLUTE_LINE_HEIGHT
|
const textarea = <HTMLElement>this.textarea;
|
||||||
|
textarea.style.lineHeight = mxConstants.ABSOLUTE_LINE_HEIGHT
|
||||||
? `${Math.round(size * mxConstants.LINE_HEIGHT)}px`
|
? `${Math.round(size * mxConstants.LINE_HEIGHT)}px`
|
||||||
: String(mxConstants.LINE_HEIGHT);
|
: String(mxConstants.LINE_HEIGHT);
|
||||||
this.textarea.style.backgroundColor = this.getBackgroundColor(state);
|
textarea.style.backgroundColor = this.getBackgroundColor(state) || 'transparent';
|
||||||
this.textarea.style.textDecoration = txtDecor.join(' ');
|
textarea.style.textDecoration = txtDecor.join(' ');
|
||||||
this.textarea.style.fontWeight = bold ? 'bold' : 'normal';
|
textarea.style.fontWeight = bold ? 'bold' : 'normal';
|
||||||
this.textarea.style.fontStyle = italic ? 'italic' : '';
|
textarea.style.fontStyle = italic ? 'italic' : '';
|
||||||
this.textarea.style.fontSize = `${Math.round(size)}px`;
|
textarea.style.fontSize = `${Math.round(size)}px`;
|
||||||
this.textarea.style.zIndex = String(this.zIndex);
|
textarea.style.zIndex = String(this.zIndex);
|
||||||
this.textarea.style.fontFamily = family;
|
textarea.style.fontFamily = family;
|
||||||
this.textarea.style.textAlign = align;
|
textarea.style.textAlign = align;
|
||||||
this.textarea.style.outline = 'none';
|
textarea.style.outline = 'none';
|
||||||
this.textarea.style.color = color;
|
textarea.style.color = color;
|
||||||
|
|
||||||
let dir = (this.textDirection =
|
let dir = (this.textDirection =
|
||||||
state.style.textDirection != null
|
state.style.textDirection != null
|
||||||
|
@ -849,30 +856,31 @@ class mxCellEditor {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dir === 'ltr' || dir === 'rtl') {
|
if (dir === 'ltr' || dir === 'rtl') {
|
||||||
this.textarea.setAttribute('dir', dir);
|
textarea.setAttribute('dir', dir);
|
||||||
} else {
|
} else {
|
||||||
this.textarea.removeAttribute('dir');
|
textarea.removeAttribute('dir');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sets the initial editing value
|
// Sets the initial editing value
|
||||||
this.textarea.innerHTML = this.getInitialValue(state, trigger) || '';
|
textarea.innerHTML = this.getInitialValue(state, <mxMouseEvent>trigger) || '';
|
||||||
this.initialValue = this.textarea.innerHTML;
|
this.initialValue = textarea.innerHTML;
|
||||||
|
|
||||||
// Uses an optional text value for empty labels which is cleared
|
// Uses an optional text value for empty labels which is cleared
|
||||||
// when the first keystroke appears. This makes it easier to see
|
// when the first keystroke appears. This makes it easier to see
|
||||||
// that a label is being edited even if the label is empty.
|
// that a label is being edited even if the label is empty.
|
||||||
if (
|
if (
|
||||||
this.textarea.innerHTML.length === 0 ||
|
textarea.innerHTML.length === 0 ||
|
||||||
this.textarea.innerHTML === '<br>'
|
textarea.innerHTML === '<br>'
|
||||||
) {
|
) {
|
||||||
this.textarea.innerHTML = this.getEmptyLabelText();
|
textarea.innerHTML = <string>this.getEmptyLabelText();
|
||||||
this.clearOnChange = true;
|
this.clearOnChange = true;
|
||||||
} else {
|
} else {
|
||||||
this.clearOnChange =
|
this.clearOnChange =
|
||||||
this.textarea.innerHTML === this.getEmptyLabelText();
|
textarea.innerHTML === this.getEmptyLabelText();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.graph.container.appendChild(this.textarea);
|
// @ts-ignore
|
||||||
|
this.graph.container.appendChild(textarea);
|
||||||
|
|
||||||
// Update this after firing all potential events that could update the cleanOnChange flag
|
// Update this after firing all potential events that could update the cleanOnChange flag
|
||||||
this.editingCell = cell;
|
this.editingCell = cell;
|
||||||
|
@ -880,13 +888,14 @@ class mxCellEditor {
|
||||||
this.textNode = null;
|
this.textNode = null;
|
||||||
|
|
||||||
if (state.text != null && this.isHideLabel(state)) {
|
if (state.text != null && this.isHideLabel(state)) {
|
||||||
this.textNode = state.text.node;
|
this.textNode = <SVGGElement>state.text.node;
|
||||||
this.textNode.style.visibility = 'hidden';
|
this.textNode.style.visibility = 'hidden';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Workaround for initial offsetHeight not ready for heading in markup
|
// Workaround for initial offsetHeight not ready for heading in markup
|
||||||
if (
|
if (
|
||||||
this.autoSize &&
|
this.autoSize &&
|
||||||
|
// @ts-ignore
|
||||||
(this.graph.model.isEdge(state.cell) ||
|
(this.graph.model.isEdge(state.cell) ||
|
||||||
state.style[mxConstants.STYLE_OVERFLOW] !== 'fill')
|
state.style[mxConstants.STYLE_OVERFLOW] !== 'fill')
|
||||||
) {
|
) {
|
||||||
|
@ -903,15 +912,15 @@ class mxCellEditor {
|
||||||
// Workaround for NS_ERROR_FAILURE in FF
|
// Workaround for NS_ERROR_FAILURE in FF
|
||||||
try {
|
try {
|
||||||
// Prefers blinking cursor over no selected text if empty
|
// Prefers blinking cursor over no selected text if empty
|
||||||
this.textarea.focus();
|
textarea.focus();
|
||||||
|
|
||||||
if (
|
if (
|
||||||
this.isSelectText() &&
|
this.isSelectText() &&
|
||||||
this.textarea.innerHTML.length > 0 &&
|
textarea.innerHTML.length > 0 &&
|
||||||
(this.textarea.innerHTML !== this.getEmptyLabelText() ||
|
(textarea.innerHTML !== this.getEmptyLabelText() ||
|
||||||
!this.clearOnChange)
|
!this.clearOnChange)
|
||||||
) {
|
) {
|
||||||
document.execCommand('selectAll', false, null);
|
document.execCommand('selectAll', false);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// ignore
|
// ignore
|
||||||
|
@ -958,30 +967,31 @@ class mxCellEditor {
|
||||||
}
|
}
|
||||||
|
|
||||||
const state = !cancel ? this.graph.view.getState(this.editingCell) : null;
|
const state = !cancel ? this.graph.view.getState(this.editingCell) : null;
|
||||||
|
const textarea = <HTMLElement>this.textarea;
|
||||||
|
|
||||||
const initial = this.initialValue;
|
const initial = this.initialValue;
|
||||||
this.initialValue = null;
|
this.initialValue = null;
|
||||||
this.editingCell = null;
|
this.editingCell = null;
|
||||||
this.trigger = null;
|
this.trigger = null;
|
||||||
this.bounds = null;
|
this.bounds = null;
|
||||||
this.textarea.blur();
|
textarea.blur();
|
||||||
this.clearSelection();
|
this.clearSelection();
|
||||||
|
|
||||||
if (this.textarea.parentNode != null) {
|
if (textarea.parentNode != null) {
|
||||||
this.textarea.parentNode.removeChild(this.textarea);
|
textarea.parentNode.removeChild(textarea);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
this.clearOnChange &&
|
this.clearOnChange &&
|
||||||
this.textarea.innerHTML === this.getEmptyLabelText()
|
textarea.innerHTML === this.getEmptyLabelText()
|
||||||
) {
|
) {
|
||||||
this.textarea.innerHTML = '';
|
textarea.innerHTML = '';
|
||||||
this.clearOnChange = false;
|
this.clearOnChange = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
state != null &&
|
state != null &&
|
||||||
(this.textarea.innerHTML !== initial || this.align != null)
|
(textarea.innerHTML !== initial || this.align != null)
|
||||||
) {
|
) {
|
||||||
this.prepareTextarea();
|
this.prepareTextarea();
|
||||||
const value = this.getCurrentValue(state);
|
const value = this.getCurrentValue(state);
|
||||||
|
@ -1016,11 +1026,12 @@ class mxCellEditor {
|
||||||
* This implementation removes the extra trailing linefeed in Firefox.
|
* This implementation removes the extra trailing linefeed in Firefox.
|
||||||
*/
|
*/
|
||||||
prepareTextarea(): void {
|
prepareTextarea(): void {
|
||||||
|
const textarea = <HTMLElement>this.textarea;
|
||||||
if (
|
if (
|
||||||
this.textarea.lastChild != null &&
|
textarea.lastChild != null &&
|
||||||
this.textarea.lastChild.nodeName === 'BR'
|
textarea.lastChild.nodeName === 'BR'
|
||||||
) {
|
) {
|
||||||
this.textarea.removeChild(this.textarea.lastChild);
|
textarea.removeChild(textarea.lastChild);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1041,12 +1052,13 @@ class mxCellEditor {
|
||||||
*/
|
*/
|
||||||
getMinimumSize(state: mxCellState): mxRectangle {
|
getMinimumSize(state: mxCellState): mxRectangle {
|
||||||
const { scale } = this.graph.getView();
|
const { scale } = this.graph.getView();
|
||||||
|
const textarea = <HTMLElement>this.textarea;
|
||||||
|
|
||||||
return new mxRectangle(
|
return new mxRectangle(
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
state.text == null ? 30 : state.text.size * scale + 20,
|
state.text == null ? 30 : state.text.size * scale + 20,
|
||||||
this.textarea.style.textAlign === 'left' ? 120 : 40
|
textarea.style.textAlign === 'left' ? 120 : 40
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1068,8 +1080,9 @@ class mxCellEditor {
|
||||||
state.view.graph.cellRenderer.legacySpacing &&
|
state.view.graph.cellRenderer.legacySpacing &&
|
||||||
state.style[mxConstants.STYLE_OVERFLOW] === 'fill'
|
state.style[mxConstants.STYLE_OVERFLOW] === 'fill'
|
||||||
) {
|
) {
|
||||||
result = state.shape.getLabelBounds(mxRectangle.fromRectangle(state));
|
result = (<mxShape>state.shape).getLabelBounds(mxRectangle.fromRectangle(state));
|
||||||
} else {
|
} else {
|
||||||
|
// @ts-ignore
|
||||||
const dummy = new mxText(); // FIXME!!!! ===================================================================================================
|
const dummy = new mxText(); // FIXME!!!! ===================================================================================================
|
||||||
const spacing: number = parseInt(state.style.spacing || 0) * scale;
|
const spacing: number = parseInt(state.style.spacing || 0) * scale;
|
||||||
const spacingTop: number =
|
const spacingTop: number =
|
||||||
|
@ -1146,7 +1159,7 @@ class mxCellEditor {
|
||||||
|
|
||||||
// Applies the horizontal and vertical label positions
|
// Applies the horizontal and vertical label positions
|
||||||
if (this.graph.getModel().isVertex(state.cell)) {
|
if (this.graph.getModel().isVertex(state.cell)) {
|
||||||
const horizontal: string = mxUtils.getStringValue(
|
const horizontal: string = <string>mxUtils.getStringValue(
|
||||||
state.style,
|
state.style,
|
||||||
mxConstants.STYLE_LABEL_POSITION,
|
mxConstants.STYLE_LABEL_POSITION,
|
||||||
mxConstants.ALIGN_CENTER
|
mxConstants.ALIGN_CENTER
|
||||||
|
@ -1191,8 +1204,8 @@ class mxCellEditor {
|
||||||
* cell - <mxCell> for which a text for an empty editing box should be
|
* cell - <mxCell> for which a text for an empty editing box should be
|
||||||
* returned.
|
* returned.
|
||||||
*/
|
*/
|
||||||
getEmptyLabelText(cell: mxCell | null = null): string | null {
|
getEmptyLabelText(cell: mxCell | null = null): string {
|
||||||
return this.emptyLabelText;
|
return this.emptyLabelText || '';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -49,7 +49,7 @@ class mxCellOverlay extends mxEventSource {
|
||||||
* Holds the offset as an <mxPoint>. The offset will be scaled according to the
|
* Holds the offset as an <mxPoint>. The offset will be scaled according to the
|
||||||
* current scale.
|
* current scale.
|
||||||
*/
|
*/
|
||||||
offset: mxPoint | null = null;
|
offset: mxPoint = new mxPoint();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Variable: cursor
|
* Variable: cursor
|
||||||
|
@ -172,11 +172,12 @@ class mxCellOverlay extends mxEventSource {
|
||||||
const s = state.view.scale;
|
const s = state.view.scale;
|
||||||
let pt = null;
|
let pt = null;
|
||||||
|
|
||||||
const w = this.image.width;
|
const image = <mxImage>this.image;
|
||||||
const h = this.image.height;
|
const w = image.width;
|
||||||
|
const h = image.height;
|
||||||
|
|
||||||
if (isEdge) {
|
if (isEdge) {
|
||||||
const pts = state.absolutePoints;
|
const pts = <mxPoint[]>state.absolutePoints;
|
||||||
|
|
||||||
if (pts.length % 2 === 1) {
|
if (pts.length % 2 === 1) {
|
||||||
pt = pts[Math.floor(pts.length / 2)];
|
pt = pts[Math.floor(pts.length / 2)];
|
||||||
|
|
|
@ -33,6 +33,7 @@ import mxPoint from '../../util/datatypes/mxPoint';
|
||||||
import mxShape from '../../shape/mxShape';
|
import mxShape from '../../shape/mxShape';
|
||||||
import mxCellState from '../../util/datatypes/mxCellState';
|
import mxCellState from '../../util/datatypes/mxCellState';
|
||||||
import mxCell from './mxCell';
|
import mxCell from './mxCell';
|
||||||
|
import mxGraphModel from "../graph/mxGraphModel";
|
||||||
|
|
||||||
class mxCellRenderer {
|
class mxCellRenderer {
|
||||||
/**
|
/**
|
||||||
|
@ -56,7 +57,7 @@ class mxCellRenderer {
|
||||||
*
|
*
|
||||||
* Defines the default shape for vertices. Default is <mxRectangleShape>.
|
* Defines the default shape for vertices. Default is <mxRectangleShape>.
|
||||||
*/
|
*/
|
||||||
defaultVertexShape: typeof mxShape = mxRectangleShape;
|
defaultVertexShape: typeof mxRectangleShape = mxRectangleShape;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Variable: defaultTextShape
|
* Variable: defaultTextShape
|
||||||
|
@ -1532,7 +1533,8 @@ class mxCellRenderer {
|
||||||
force: boolean = false,
|
force: boolean = false,
|
||||||
rendering: boolean = true
|
rendering: boolean = true
|
||||||
): boolean {
|
): boolean {
|
||||||
const { model } = state.view.graph;
|
const model = <mxGraphModel>state.view.graph.model;
|
||||||
|
|
||||||
let shapeChanged = false;
|
let shapeChanged = false;
|
||||||
|
|
||||||
// Forces creation of new shape if shape style has changed
|
// Forces creation of new shape if shape style has changed
|
||||||
|
|
|
@ -10,6 +10,7 @@ import mxDictionary from '../../util/datatypes/mxDictionary';
|
||||||
import mxCellState from '../../util/datatypes/mxCellState';
|
import mxCellState from '../../util/datatypes/mxCellState';
|
||||||
import mxCell from './mxCell';
|
import mxCell from './mxCell';
|
||||||
import mxGraph from '../graph/mxGraph';
|
import mxGraph from '../graph/mxGraph';
|
||||||
|
import mxGraphView from "../graph/mxGraphView";
|
||||||
|
|
||||||
class mxCellStatePreview {
|
class mxCellStatePreview {
|
||||||
/**
|
/**
|
||||||
|
@ -17,14 +18,14 @@ class mxCellStatePreview {
|
||||||
*
|
*
|
||||||
* Reference to the enclosing <mxGraph>.
|
* Reference to the enclosing <mxGraph>.
|
||||||
*/
|
*/
|
||||||
graph: mxGraph | null = null;
|
graph: mxGraph;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Variable: deltas
|
* Variable: deltas
|
||||||
*
|
*
|
||||||
* Reference to the enclosing <mxGraph>.
|
* Reference to the enclosing <mxGraph>.
|
||||||
*/
|
*/
|
||||||
deltas: mxDictionary | null = null;
|
deltas: mxDictionary;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Variable: count
|
* Variable: count
|
||||||
|
@ -96,22 +97,18 @@ class mxCellStatePreview {
|
||||||
* Function: show
|
* Function: show
|
||||||
*/
|
*/
|
||||||
show(visitor: Function | null = null) {
|
show(visitor: Function | null = null) {
|
||||||
this.deltas.visit(
|
this.deltas.visit((key: string, delta: any) => {
|
||||||
mxUtils.bind(this, (key, delta) => {
|
this.translateState(delta.state, delta.point.x, delta.point.y);
|
||||||
this.translateState(delta.state, delta.point.x, delta.point.y);
|
});
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
this.deltas.visit(
|
this.deltas.visit((key: string, delta: any) => {
|
||||||
mxUtils.bind(this, (key, delta) => {
|
this.revalidateState(
|
||||||
this.revalidateState(
|
delta.state,
|
||||||
delta.state,
|
delta.point.x,
|
||||||
delta.point.x,
|
delta.point.y,
|
||||||
delta.point.y,
|
visitor
|
||||||
visitor
|
);
|
||||||
);
|
});
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -122,7 +119,7 @@ class mxCellStatePreview {
|
||||||
const model = this.graph.getModel();
|
const model = this.graph.getModel();
|
||||||
|
|
||||||
if (model.isVertex(state.cell)) {
|
if (model.isVertex(state.cell)) {
|
||||||
state.view.updateCellState(state);
|
(<mxGraphView>state.view).updateCellState(state);
|
||||||
const geo = model.getGeometry(state.cell);
|
const geo = model.getGeometry(state.cell);
|
||||||
|
|
||||||
// Moves selection cells and non-relative vertices in
|
// Moves selection cells and non-relative vertices in
|
||||||
|
@ -142,7 +139,7 @@ class mxCellStatePreview {
|
||||||
|
|
||||||
for (let i = 0; i < childCount; i += 1) {
|
for (let i = 0; i < childCount; i += 1) {
|
||||||
this.translateState(
|
this.translateState(
|
||||||
state.view.getState(model.getChildAt(state.cell, i)),
|
<mxCellState>(state.view).getState(model.getChildAt(state.cell, i)),
|
||||||
dx,
|
dx,
|
||||||
dy
|
dy
|
||||||
);
|
);
|
||||||
|
@ -168,8 +165,8 @@ class mxCellStatePreview {
|
||||||
state.view.updateCellState(state);
|
state.view.updateCellState(state);
|
||||||
}
|
}
|
||||||
|
|
||||||
const geo = this.graph.getCellGeometry(state.cell);
|
const geo = this.graph.getCellGeometry(<mxCell>state.cell);
|
||||||
const pState = state.view.getState(model.getParent(state.cell));
|
const pState = state.view.getState(model.getParent(<mxCell>state.cell));
|
||||||
|
|
||||||
// Moves selection vertices which are relative
|
// Moves selection vertices which are relative
|
||||||
if (
|
if (
|
||||||
|
@ -210,10 +207,10 @@ class mxCellStatePreview {
|
||||||
*/
|
*/
|
||||||
addEdges(state: mxCellState): void {
|
addEdges(state: mxCellState): void {
|
||||||
const model = this.graph.getModel();
|
const model = this.graph.getModel();
|
||||||
const edgeCount = model.getEdgeCount(state.cell);
|
const edgeCount = model.getEdgeCount(<mxCell>state.cell);
|
||||||
|
|
||||||
for (let i = 0; i < edgeCount; i += 1) {
|
for (let i = 0; i < edgeCount; i += 1) {
|
||||||
const s = state.view.getState(model.getEdgeAt(state.cell, i));
|
const s = state.view.getState(model.getEdgeAt(<mxCell>state.cell, i));
|
||||||
|
|
||||||
if (s != null) {
|
if (s != null) {
|
||||||
this.moveState(s, 0, 0);
|
this.moveState(s, 0, 0);
|
||||||
|
|
|
@ -63,7 +63,7 @@ class mxConnectionConstraint {
|
||||||
*/
|
*/
|
||||||
constructor(point: mxPoint | null=null,
|
constructor(point: mxPoint | null=null,
|
||||||
perimeter: boolean=true,
|
perimeter: boolean=true,
|
||||||
name: string='',
|
name: string | null=null,
|
||||||
dx: number | null=null,
|
dx: number | null=null,
|
||||||
dy: number | null=null) {
|
dy: number | null=null) {
|
||||||
this.point = point;
|
this.point = point;
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1094,7 +1094,7 @@ class mxGraphModel extends mxEventSource {
|
||||||
* isSource - Boolean indicating which end of the edge should be returned.
|
* isSource - Boolean indicating which end of the edge should be returned.
|
||||||
*/
|
*/
|
||||||
getTerminal(edge: mxCell | null,
|
getTerminal(edge: mxCell | null,
|
||||||
isSource: boolean=false) {
|
isSource: boolean=false): mxCell | null {
|
||||||
return edge != null ? edge.getTerminal(isSource) : null;
|
return edge != null ? edge.getTerminal(isSource) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1623,7 +1623,7 @@ class mxGraphModel extends mxEventSource {
|
||||||
*
|
*
|
||||||
* cell - <mxCell> whose style should be returned.
|
* cell - <mxCell> whose style should be returned.
|
||||||
*/
|
*/
|
||||||
getStyle(cell: mxCell): any {
|
getStyle(cell: mxCell | null): any {
|
||||||
return cell != null ? cell.getStyle() : null;
|
return cell != null ? cell.getStyle() : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1730,7 +1730,7 @@ class mxGraphModel extends mxEventSource {
|
||||||
*
|
*
|
||||||
* cell - <mxCell> whose visible state should be returned.
|
* cell - <mxCell> whose visible state should be returned.
|
||||||
*/
|
*/
|
||||||
isVisible(cell: mxCell): boolean {
|
isVisible(cell: mxCell | null): boolean {
|
||||||
return cell != null ? cell.isVisible() : false;
|
return cell != null ? cell.isVisible() : false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -161,7 +161,7 @@ class mxGraphSelectionModel extends mxEventSource {
|
||||||
*
|
*
|
||||||
* cell - <mxCell> to be selected.
|
* cell - <mxCell> to be selected.
|
||||||
*/
|
*/
|
||||||
setCell(cell: mxCell): void {
|
setCell(cell: mxCell | null): void {
|
||||||
if (cell != null) {
|
if (cell != null) {
|
||||||
this.setCells([cell]);
|
this.setCells([cell]);
|
||||||
}
|
}
|
||||||
|
|
|
@ -101,7 +101,7 @@ class mxGraphView extends mxEventSource {
|
||||||
*
|
*
|
||||||
* Returns reference to the enclosing <mxGraph>.
|
* Returns reference to the enclosing <mxGraph>.
|
||||||
*/
|
*/
|
||||||
graph: mxGraph | null = null;
|
graph: mxGraph;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Variable: currentRoot
|
* Variable: currentRoot
|
||||||
|
@ -1548,9 +1548,9 @@ class mxGraphView extends mxEventSource {
|
||||||
* associated.
|
* associated.
|
||||||
*/
|
*/
|
||||||
isLoopStyleEnabled(edge: mxCellState,
|
isLoopStyleEnabled(edge: mxCellState,
|
||||||
points: mxPoint[],
|
points: mxPoint[]=[],
|
||||||
source: mxCellState,
|
source: mxCellState | null=null,
|
||||||
target: mxCellState): boolean {
|
target: mxCellState | null=null): boolean {
|
||||||
|
|
||||||
const sc = (<mxGraph>this.graph).getConnectionConstraint(edge, source, true);
|
const sc = (<mxGraph>this.graph).getConnectionConstraint(edge, source, true);
|
||||||
const tc = (<mxGraph>this.graph).getConnectionConstraint(edge, target, false);
|
const tc = (<mxGraph>this.graph).getConnectionConstraint(edge, target, false);
|
||||||
|
@ -1575,9 +1575,9 @@ class mxGraphView extends mxEventSource {
|
||||||
* Returns the edge style function to be used to render the given edge state.
|
* Returns the edge style function to be used to render the given edge state.
|
||||||
*/
|
*/
|
||||||
getEdgeStyle(edge: mxCellState,
|
getEdgeStyle(edge: mxCellState,
|
||||||
points: mxPoint[],
|
points: mxPoint[]=[],
|
||||||
source: mxCellState,
|
source: mxCellState | null=null,
|
||||||
target: mxCellState): any {
|
target: mxCellState | null=null): any {
|
||||||
|
|
||||||
let edgeStyle: any = this.isLoopStyleEnabled(edge, points, source, target)
|
let edgeStyle: any = this.isLoopStyleEnabled(edge, points, source, target)
|
||||||
? mxUtils.getValue(
|
? mxUtils.getValue(
|
||||||
|
@ -1970,7 +1970,7 @@ class mxGraphView extends mxEventSource {
|
||||||
* source - Boolean that specifies if the source or target terminal
|
* source - Boolean that specifies if the source or target terminal
|
||||||
* should be returned.
|
* should be returned.
|
||||||
*/
|
*/
|
||||||
getVisibleTerminal(edge: mxCell,
|
getVisibleTerminal(edge: mxCell | null,
|
||||||
source: boolean) {
|
source: boolean) {
|
||||||
|
|
||||||
const model = (<mxGraph>this.graph).getModel();
|
const model = (<mxGraph>this.graph).getModel();
|
||||||
|
|
|
@ -13,10 +13,175 @@ import mxGraph from './mxGraph';
|
||||||
import mxImageShape from '../../shape/node/mxImageShape';
|
import mxImageShape from '../../shape/node/mxImageShape';
|
||||||
import mxEvent from '../../util/event/mxEvent';
|
import mxEvent from '../../util/event/mxEvent';
|
||||||
import mxUtils from '../../util/mxUtils';
|
import mxUtils from '../../util/mxUtils';
|
||||||
import mxClient from '../../mxClient';
|
|
||||||
import mxImage from '../../util/image/mxImage';
|
import mxImage from '../../util/image/mxImage';
|
||||||
|
import mxEventObject from "../../util/event/mxEventObject";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class: mxOutline
|
||||||
|
*
|
||||||
|
* Implements an outline (aka overview) for a graph. Set <updateOnPan> to true
|
||||||
|
* to enable updates while the source graph is panning.
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
*
|
||||||
|
* (code)
|
||||||
|
* let outline = new mxOutline(graph, div);
|
||||||
|
* (end)
|
||||||
|
*
|
||||||
|
* To move the graph to the top, left corner the following code can be used.
|
||||||
|
*
|
||||||
|
* (code)
|
||||||
|
* let scale = graph.view.scale;
|
||||||
|
* let bounds = graph.getGraphBounds();
|
||||||
|
* graph.view.setTranslate(-bounds.x / scale, -bounds.y / scale);
|
||||||
|
* (end)
|
||||||
|
*
|
||||||
|
* To toggle the suspended mode, the following can be used.
|
||||||
|
*
|
||||||
|
* (code)
|
||||||
|
* outline.suspended = !outln.suspended;
|
||||||
|
* if (!outline.suspended)
|
||||||
|
* {
|
||||||
|
* outline.update(true);
|
||||||
|
* }
|
||||||
|
* (end)
|
||||||
|
*
|
||||||
|
* Constructor: mxOutline
|
||||||
|
*
|
||||||
|
* Constructs a new outline for the specified graph inside the given
|
||||||
|
* container.
|
||||||
|
*
|
||||||
|
* Parameters:
|
||||||
|
*
|
||||||
|
* source - <mxGraph> to create the outline for.
|
||||||
|
* container - DOM node that will contain the outline.
|
||||||
|
*/
|
||||||
class mxOutline {
|
class mxOutline {
|
||||||
|
constructor(source: mxGraph, container: HTMLElement | null = null) {
|
||||||
|
this.source = source;
|
||||||
|
|
||||||
|
if (container != null) {
|
||||||
|
this.init(container);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function: init
|
||||||
|
*
|
||||||
|
* Initializes the outline inside the given container.
|
||||||
|
*/
|
||||||
|
init(container: HTMLElement) {
|
||||||
|
this.outline = this.createGraph(container);
|
||||||
|
|
||||||
|
// Do not repaint when suspended
|
||||||
|
const outlineGraphModelChanged = this.outline.graphModelChanged;
|
||||||
|
this.outline.graphModelChanged = mxUtils.bind(this, (changes: any) => {
|
||||||
|
if (!this.suspended && this.outline != null) {
|
||||||
|
outlineGraphModelChanged.apply(this.outline, [changes]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Enable faster painting in SVG
|
||||||
|
//const node = <SVGElement>this.outline.getView().getCanvas().parentNode;
|
||||||
|
//node.setAttribute('shape-rendering', 'optimizeSpeed');
|
||||||
|
//node.setAttribute('image-rendering', 'optimizeSpeed');
|
||||||
|
|
||||||
|
// Hides cursors and labels
|
||||||
|
this.outline.labelsVisible = this.labelsVisible;
|
||||||
|
this.outline.setEnabled(false);
|
||||||
|
|
||||||
|
this.updateHandler = (sender: any, evt: mxEventObject) => {
|
||||||
|
if (!this.suspended && !this.active) {
|
||||||
|
this.update();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Updates the scale of the outline after a change of the main graph
|
||||||
|
this.source.getModel().addListener(mxEvent.CHANGE, this.updateHandler);
|
||||||
|
this.outline.addMouseListener(this);
|
||||||
|
|
||||||
|
// Adds listeners to keep the outline in sync with the source graph
|
||||||
|
const view = this.source.getView();
|
||||||
|
view.addListener(mxEvent.SCALE, this.updateHandler);
|
||||||
|
view.addListener(mxEvent.TRANSLATE, this.updateHandler);
|
||||||
|
view.addListener(mxEvent.SCALE_AND_TRANSLATE, this.updateHandler);
|
||||||
|
view.addListener(mxEvent.DOWN, this.updateHandler);
|
||||||
|
view.addListener(mxEvent.UP, this.updateHandler);
|
||||||
|
|
||||||
|
// Updates blue rectangle on scroll
|
||||||
|
mxEvent.addListener(this.source.container, 'scroll', this.updateHandler);
|
||||||
|
|
||||||
|
this.panHandler = (sender: any, evt: mxEventObject) => {
|
||||||
|
if (this.updateOnPan) {
|
||||||
|
(<Function>this.updateHandler)(sender, evt);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
this.source.addListener(mxEvent.PAN, this.panHandler);
|
||||||
|
|
||||||
|
// Refreshes the graph in the outline after a refresh of the main graph
|
||||||
|
this.refreshHandler = (sender: any) => {
|
||||||
|
const outline = <mxGraph>this.outline;
|
||||||
|
outline.setStylesheet(this.source.getStylesheet());
|
||||||
|
outline.refresh();
|
||||||
|
};
|
||||||
|
this.source.addListener(mxEvent.REFRESH, this.refreshHandler);
|
||||||
|
|
||||||
|
// Creates the blue rectangle for the viewport
|
||||||
|
this.bounds = new mxRectangle(0, 0, 0, 0);
|
||||||
|
this.selectionBorder = new mxRectangleShape(
|
||||||
|
this.bounds,
|
||||||
|
null,
|
||||||
|
mxConstants.OUTLINE_COLOR,
|
||||||
|
mxConstants.OUTLINE_STROKEWIDTH
|
||||||
|
);
|
||||||
|
this.selectionBorder.dialect = this.outline.dialect;
|
||||||
|
this.selectionBorder.init(this.outline.getView().getOverlayPane());
|
||||||
|
const selectionBorderNode = <SVGGElement>this.selectionBorder.node;
|
||||||
|
|
||||||
|
// Handles event by catching the initial pointer start and then listening to the
|
||||||
|
// complete gesture on the event target. This is needed because all the events
|
||||||
|
// are routed via the initial element even if that element is removed from the
|
||||||
|
// DOM, which happens when we repaint the selection border and zoom handles.
|
||||||
|
const handler = (evt: Event) => {
|
||||||
|
const t = mxEvent.getSource(evt);
|
||||||
|
|
||||||
|
const redirect = (evt: Event) => {
|
||||||
|
const outline = <mxGraph>this.outline;
|
||||||
|
outline.fireMouseEvent(mxEvent.MOUSE_MOVE, new mxMouseEvent(evt));
|
||||||
|
};
|
||||||
|
|
||||||
|
var redirect2 = (evt: Event) => {
|
||||||
|
const outline = <mxGraph>this.outline;
|
||||||
|
mxEvent.removeGestureListeners(t, null, redirect, redirect2);
|
||||||
|
outline.fireMouseEvent(mxEvent.MOUSE_UP, new mxMouseEvent(evt));
|
||||||
|
};
|
||||||
|
|
||||||
|
const outline = <mxGraph>this.outline;
|
||||||
|
mxEvent.addGestureListeners(t, null, redirect, redirect2);
|
||||||
|
outline.fireMouseEvent(mxEvent.MOUSE_DOWN, new mxMouseEvent(evt));
|
||||||
|
};
|
||||||
|
|
||||||
|
mxEvent.addGestureListeners(this.selectionBorder.node, handler);
|
||||||
|
|
||||||
|
// Creates a small blue rectangle for sizing (sizer handle)
|
||||||
|
const sizer = this.sizer = this.createSizer();
|
||||||
|
const sizerNode = <SVGGElement>sizer.node;
|
||||||
|
|
||||||
|
sizer.init(this.outline.getView().getOverlayPane());
|
||||||
|
|
||||||
|
if (this.enabled) {
|
||||||
|
sizerNode.style.cursor = 'nwse-resize';
|
||||||
|
}
|
||||||
|
|
||||||
|
mxEvent.addGestureListeners(this.sizer.node, handler);
|
||||||
|
|
||||||
|
selectionBorderNode.style.display = this.showViewport ? '' : 'none';
|
||||||
|
sizerNode.style.display = selectionBorderNode.style.display;
|
||||||
|
selectionBorderNode.style.cursor = 'move';
|
||||||
|
|
||||||
|
this.update(false);
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: Document me!!
|
// TODO: Document me!!
|
||||||
sizer: mxRectangleShape | null=null;
|
sizer: mxRectangleShape | null=null;
|
||||||
|
|
||||||
|
@ -32,7 +197,7 @@ class mxOutline {
|
||||||
|
|
||||||
bounds: mxRectangle | null=null;
|
bounds: mxRectangle | null=null;
|
||||||
|
|
||||||
zoom: number | null=null;
|
zoom: boolean=false;
|
||||||
|
|
||||||
startX: number | null=null;
|
startX: number | null=null;
|
||||||
|
|
||||||
|
@ -56,17 +221,14 @@ class mxOutline {
|
||||||
*
|
*
|
||||||
* Reference to the <mxGraph> that renders the outline.
|
* Reference to the <mxGraph> that renders the outline.
|
||||||
*/
|
*/
|
||||||
outline: mxGraph;
|
outline: mxGraph | null=null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Function: graphRenderHint
|
* Function: graphRenderHint
|
||||||
*
|
*
|
||||||
* Renderhint to be used for the outline graph. Default is faster.
|
* Renderhint to be used for the outline graph. Default is exact.
|
||||||
*/
|
*/
|
||||||
graphRenderHint:
|
graphRenderHint: string = 'exact';
|
||||||
| mxConstants.RENDERING_HINT_EXACT
|
|
||||||
| mxConstants.RENDERING_HINT_FASTER
|
|
||||||
| mxConstants.RENDERING_HINT_FASTEST = mxConstants.RENDERING_HINT_FASTER;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Variable: enabled
|
* Variable: enabled
|
||||||
|
@ -143,54 +305,6 @@ class mxOutline {
|
||||||
*/
|
*/
|
||||||
suspended: boolean = false;
|
suspended: boolean = false;
|
||||||
|
|
||||||
/**
|
|
||||||
* Class: mxOutline
|
|
||||||
*
|
|
||||||
* Implements an outline (aka overview) for a graph. Set <updateOnPan> to true
|
|
||||||
* to enable updates while the source graph is panning.
|
|
||||||
*
|
|
||||||
* Example:
|
|
||||||
*
|
|
||||||
* (code)
|
|
||||||
* let outline = new mxOutline(graph, div);
|
|
||||||
* (end)
|
|
||||||
*
|
|
||||||
* To move the graph to the top, left corner the following code can be used.
|
|
||||||
*
|
|
||||||
* (code)
|
|
||||||
* let scale = graph.view.scale;
|
|
||||||
* let bounds = graph.getGraphBounds();
|
|
||||||
* graph.view.setTranslate(-bounds.x / scale, -bounds.y / scale);
|
|
||||||
* (end)
|
|
||||||
*
|
|
||||||
* To toggle the suspended mode, the following can be used.
|
|
||||||
*
|
|
||||||
* (code)
|
|
||||||
* outline.suspended = !outln.suspended;
|
|
||||||
* if (!outline.suspended)
|
|
||||||
* {
|
|
||||||
* outline.update(true);
|
|
||||||
* }
|
|
||||||
* (end)
|
|
||||||
*
|
|
||||||
* Constructor: mxOutline
|
|
||||||
*
|
|
||||||
* Constructs a new outline for the specified graph inside the given
|
|
||||||
* container.
|
|
||||||
*
|
|
||||||
* Parameters:
|
|
||||||
*
|
|
||||||
* source - <mxGraph> to create the outline for.
|
|
||||||
* container - DOM node that will contain the outline.
|
|
||||||
*/
|
|
||||||
constructor(source: mxGraph, container: HTMLElement | null = null) {
|
|
||||||
this.source = source;
|
|
||||||
|
|
||||||
if (container != null) {
|
|
||||||
this.init(container);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Function: createGraph
|
* Function: createGraph
|
||||||
*
|
*
|
||||||
|
@ -208,118 +322,6 @@ class mxOutline {
|
||||||
return graph;
|
return graph;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Function: init
|
|
||||||
*
|
|
||||||
* Initializes the outline inside the given container.
|
|
||||||
*/
|
|
||||||
init(container: HTMLElement) {
|
|
||||||
this.outline = this.createGraph(container);
|
|
||||||
|
|
||||||
// Do not repaint when suspended
|
|
||||||
const outlineGraphModelChanged = this.outline.graphModelChanged;
|
|
||||||
this.outline.graphModelChanged = mxUtils.bind(this, changes => {
|
|
||||||
if (!this.suspended && this.outline != null) {
|
|
||||||
outlineGraphModelChanged.apply(this.outline, [changes]);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Enable faster painting in SVG
|
|
||||||
const node = <SVGElement>this.outline.getView().getCanvas().parentNode;
|
|
||||||
node.setAttribute('shape-rendering', 'optimizeSpeed');
|
|
||||||
node.setAttribute('image-rendering', 'optimizeSpeed');
|
|
||||||
|
|
||||||
// Hides cursors and labels
|
|
||||||
this.outline.labelsVisible = this.labelsVisible;
|
|
||||||
this.outline.setEnabled(false);
|
|
||||||
|
|
||||||
this.updateHandler = (sender, evt) => {
|
|
||||||
if (!this.suspended && !this.active) {
|
|
||||||
this.update();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Updates the scale of the outline after a change of the main graph
|
|
||||||
this.source.getModel().addListener(mxEvent.CHANGE, this.updateHandler);
|
|
||||||
this.outline.addMouseListener(this);
|
|
||||||
|
|
||||||
// Adds listeners to keep the outline in sync with the source graph
|
|
||||||
const view = this.source.getView();
|
|
||||||
view.addListener(mxEvent.SCALE, this.updateHandler);
|
|
||||||
view.addListener(mxEvent.TRANSLATE, this.updateHandler);
|
|
||||||
view.addListener(mxEvent.SCALE_AND_TRANSLATE, this.updateHandler);
|
|
||||||
view.addListener(mxEvent.DOWN, this.updateHandler);
|
|
||||||
view.addListener(mxEvent.UP, this.updateHandler);
|
|
||||||
|
|
||||||
// Updates blue rectangle on scroll
|
|
||||||
mxEvent.addListener(this.source.container, 'scroll', this.updateHandler);
|
|
||||||
|
|
||||||
this.panHandler = (sender, evt) => {
|
|
||||||
if (this.updateOnPan) {
|
|
||||||
this.updateHandler.apply(this, [sender, evt]);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
this.source.addListener(mxEvent.PAN, this.panHandler);
|
|
||||||
|
|
||||||
// Refreshes the graph in the outline after a refresh of the main graph
|
|
||||||
this.refreshHandler = sender => {
|
|
||||||
this.outline.setStylesheet(this.source.getStylesheet());
|
|
||||||
this.outline.refresh();
|
|
||||||
};
|
|
||||||
this.source.addListener(mxEvent.REFRESH, this.refreshHandler);
|
|
||||||
|
|
||||||
// Creates the blue rectangle for the viewport
|
|
||||||
this.bounds = new mxRectangle(0, 0, 0, 0);
|
|
||||||
this.selectionBorder = new mxRectangleShape(
|
|
||||||
this.bounds,
|
|
||||||
null,
|
|
||||||
mxConstants.OUTLINE_COLOR,
|
|
||||||
mxConstants.OUTLINE_STROKEWIDTH
|
|
||||||
);
|
|
||||||
this.selectionBorder.dialect = this.outline.dialect;
|
|
||||||
|
|
||||||
this.selectionBorder.init(this.outline.getView().getOverlayPane());
|
|
||||||
|
|
||||||
// Handles event by catching the initial pointer start and then listening to the
|
|
||||||
// complete gesture on the event target. This is needed because all the events
|
|
||||||
// are routed via the initial element even if that element is removed from the
|
|
||||||
// DOM, which happens when we repaint the selection border and zoom handles.
|
|
||||||
const handler = mxUtils.bind(this, evt => {
|
|
||||||
const t = mxEvent.getSource(evt);
|
|
||||||
|
|
||||||
const redirect = mxUtils.bind(this, evt => {
|
|
||||||
this.outline.fireMouseEvent(mxEvent.MOUSE_MOVE, new mxMouseEvent(evt));
|
|
||||||
});
|
|
||||||
|
|
||||||
var redirect2 = mxUtils.bind(this, evt => {
|
|
||||||
mxEvent.removeGestureListeners(t, null, redirect, redirect2);
|
|
||||||
this.outline.fireMouseEvent(mxEvent.MOUSE_UP, new mxMouseEvent(evt));
|
|
||||||
});
|
|
||||||
|
|
||||||
mxEvent.addGestureListeners(t, null, redirect, redirect2);
|
|
||||||
this.outline.fireMouseEvent(mxEvent.MOUSE_DOWN, new mxMouseEvent(evt));
|
|
||||||
});
|
|
||||||
|
|
||||||
mxEvent.addGestureListeners(this.selectionBorder.node, handler);
|
|
||||||
|
|
||||||
// Creates a small blue rectangle for sizing (sizer handle)
|
|
||||||
this.sizer = this.createSizer();
|
|
||||||
|
|
||||||
this.sizer.init(this.outline.getView().getOverlayPane());
|
|
||||||
|
|
||||||
if (this.enabled) {
|
|
||||||
this.sizer.node.style.cursor = 'nwse-resize';
|
|
||||||
}
|
|
||||||
|
|
||||||
mxEvent.addGestureListeners(this.sizer.node, handler);
|
|
||||||
|
|
||||||
this.selectionBorder.node.style.display = this.showViewport ? '' : 'none';
|
|
||||||
this.sizer.node.style.display = this.selectionBorder.node.style.display;
|
|
||||||
this.selectionBorder.node.style.cursor = 'move';
|
|
||||||
|
|
||||||
this.update(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Function: isEnabled
|
* Function: isEnabled
|
||||||
*
|
*
|
||||||
|
@ -355,6 +357,7 @@ class mxOutline {
|
||||||
* value - Boolean that specifies the new enabled state.
|
* value - Boolean that specifies the new enabled state.
|
||||||
*/
|
*/
|
||||||
setZoomEnabled(value: boolean): void {
|
setZoomEnabled(value: boolean): void {
|
||||||
|
// @ts-ignore
|
||||||
this.sizer.node.style.visibility = value ? 'visible' : 'hidden';
|
this.sizer.node.style.visibility = value ? 'visible' : 'hidden';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -373,12 +376,13 @@ class mxOutline {
|
||||||
* Creates the shape used as the sizer.
|
* Creates the shape used as the sizer.
|
||||||
*/
|
*/
|
||||||
createSizer(): mxRectangleShape {
|
createSizer(): mxRectangleShape {
|
||||||
|
const outline = <mxGraph>this.outline;
|
||||||
if (this.sizerImage != null) {
|
if (this.sizerImage != null) {
|
||||||
const sizer = new mxImageShape(
|
const sizer = new mxImageShape(
|
||||||
new mxRectangle(0, 0, this.sizerImage.width, this.sizerImage.height),
|
new mxRectangle(0, 0, this.sizerImage.width, this.sizerImage.height),
|
||||||
this.sizerImage.src
|
this.sizerImage.src
|
||||||
);
|
);
|
||||||
sizer.dialect = this.outline.dialect;
|
sizer.dialect = outline.dialect;
|
||||||
return sizer;
|
return sizer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -387,7 +391,7 @@ class mxOutline {
|
||||||
mxConstants.OUTLINE_HANDLE_FILLCOLOR,
|
mxConstants.OUTLINE_HANDLE_FILLCOLOR,
|
||||||
mxConstants.OUTLINE_HANDLE_STROKECOLOR
|
mxConstants.OUTLINE_HANDLE_STROKECOLOR
|
||||||
);
|
);
|
||||||
sizer.dialect = this.outline.dialect;
|
sizer.dialect = outline.dialect;
|
||||||
return sizer;
|
return sizer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -400,8 +404,8 @@ class mxOutline {
|
||||||
return new mxRectangle(
|
return new mxRectangle(
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
this.source.container.scrollWidth,
|
(<HTMLElement>this.source.container).scrollWidth,
|
||||||
this.source.container.scrollHeight
|
(<HTMLElement>this.source.container).scrollHeight
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -410,7 +414,7 @@ class mxOutline {
|
||||||
*
|
*
|
||||||
* Returns the offset for drawing the outline graph.
|
* Returns the offset for drawing the outline graph.
|
||||||
*/
|
*/
|
||||||
getOutlineOffset(scale) {
|
getOutlineOffset(scale: number): mxPoint | null { // TODO: Should number -> mxPoint?
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -533,7 +537,8 @@ class mxOutline {
|
||||||
this.bounds.y +=
|
this.bounds.y +=
|
||||||
(this.source.container.scrollTop * navView.scale) / scale;
|
(this.source.container.scrollTop * navView.scale) / scale;
|
||||||
|
|
||||||
let b = this.selectionBorder.bounds;
|
const selectionBorder = <mxRectangleShape>this.selectionBorder;
|
||||||
|
let b = <mxRectangle>selectionBorder.bounds;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
b.x !== this.bounds.x ||
|
b.x !== this.bounds.x ||
|
||||||
|
@ -541,12 +546,13 @@ class mxOutline {
|
||||||
b.width !== this.bounds.width ||
|
b.width !== this.bounds.width ||
|
||||||
b.height !== this.bounds.height
|
b.height !== this.bounds.height
|
||||||
) {
|
) {
|
||||||
this.selectionBorder.bounds = this.bounds;
|
selectionBorder.bounds = this.bounds;
|
||||||
this.selectionBorder.redraw();
|
selectionBorder.redraw();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Updates the bounds of the zoom handle at the bottom right
|
// Updates the bounds of the zoom handle at the bottom right
|
||||||
b = this.sizer.bounds;
|
const sizer = <mxRectangleShape>this.sizer;
|
||||||
|
b = <mxRectangle>sizer.bounds;
|
||||||
const b2 = new mxRectangle(
|
const b2 = new mxRectangle(
|
||||||
this.bounds.x + this.bounds.width - b.width / 2,
|
this.bounds.x + this.bounds.width - b.width / 2,
|
||||||
this.bounds.y + this.bounds.height - b.height / 2,
|
this.bounds.y + this.bounds.height - b.height / 2,
|
||||||
|
@ -560,11 +566,11 @@ class mxOutline {
|
||||||
b.width !== b2.width ||
|
b.width !== b2.width ||
|
||||||
b.height !== b2.height
|
b.height !== b2.height
|
||||||
) {
|
) {
|
||||||
this.sizer.bounds = b2;
|
sizer.bounds = b2;
|
||||||
|
|
||||||
// Avoids update of visibility in redraw for VML
|
// Avoids update of visibility in redraw for VML
|
||||||
if (this.sizer.node.style.visibility !== 'hidden') {
|
if ((<SVGGElement>sizer.node).style.visibility !== 'hidden') {
|
||||||
this.sizer.redraw();
|
sizer.redraw();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -580,7 +586,7 @@ class mxOutline {
|
||||||
*
|
*
|
||||||
* Handles the event by starting a translation or zoom.
|
* Handles the event by starting a translation or zoom.
|
||||||
*/
|
*/
|
||||||
mouseDown(sender, me) {
|
mouseDown(sender: any, me: mxMouseEvent) {
|
||||||
if (this.enabled && this.showViewport) {
|
if (this.enabled && this.showViewport) {
|
||||||
const tol = !mxEvent.isMouseEvent(me.getEvent())
|
const tol = !mxEvent.isMouseEvent(me.getEvent())
|
||||||
? this.source.tolerance
|
? this.source.tolerance
|
||||||
|
@ -600,13 +606,14 @@ class mxOutline {
|
||||||
this.startX = me.getX();
|
this.startX = me.getX();
|
||||||
this.startY = me.getY();
|
this.startY = me.getY();
|
||||||
this.active = true;
|
this.active = true;
|
||||||
|
const sourceContainer = <HTMLElement>this.source.container;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
this.source.useScrollbarsForPanning &&
|
this.source.useScrollbarsForPanning &&
|
||||||
mxUtils.hasScrollbars(this.source.container)
|
mxUtils.hasScrollbars(this.source.container)
|
||||||
) {
|
) {
|
||||||
this.dx0 = this.source.container.scrollLeft;
|
this.dx0 = sourceContainer.scrollLeft;
|
||||||
this.dy0 = this.source.container.scrollTop;
|
this.dy0 = sourceContainer.scrollTop;
|
||||||
} else {
|
} else {
|
||||||
this.dx0 = 0;
|
this.dx0 = 0;
|
||||||
this.dy0 = 0;
|
this.dy0 = 0;
|
||||||
|
@ -622,10 +629,18 @@ class mxOutline {
|
||||||
* Handles the event by previewing the viewrect in <graph> and updating the
|
* Handles the event by previewing the viewrect in <graph> and updating the
|
||||||
* rectangle that represents the viewrect in the outline.
|
* rectangle that represents the viewrect in the outline.
|
||||||
*/
|
*/
|
||||||
mouseMove(sender, me) {
|
mouseMove(sender: any, me: mxMouseEvent) {
|
||||||
if (this.active) {
|
if (this.active) {
|
||||||
this.selectionBorder.node.style.display = this.showViewport ? '' : 'none';
|
const myBounds = <mxRectangle>this.bounds;
|
||||||
this.sizer.node.style.display = this.selectionBorder.node.style.display;
|
const sizer = <mxRectangleShape>this.sizer;
|
||||||
|
const sizerNode = <SVGGElement>sizer.node;
|
||||||
|
const selectionBorder = <mxRectangleShape>this.selectionBorder;
|
||||||
|
const selectionBorderNode = <SVGGElement>selectionBorder.node;
|
||||||
|
const source = <mxGraph>this.source;
|
||||||
|
const outline = <mxGraph>this.outline;
|
||||||
|
|
||||||
|
selectionBorderNode.style.display = this.showViewport ? '' : 'none';
|
||||||
|
sizerNode.style.display = selectionBorderNode.style.display;
|
||||||
|
|
||||||
const delta = this.getTranslateForEvent(me);
|
const delta = this.getTranslateForEvent(me);
|
||||||
let dx = delta.x;
|
let dx = delta.x;
|
||||||
|
@ -634,38 +649,39 @@ class mxOutline {
|
||||||
|
|
||||||
if (!this.zoom) {
|
if (!this.zoom) {
|
||||||
// Previews the panning on the source graph
|
// Previews the panning on the source graph
|
||||||
const { scale } = this.outline.getView();
|
const { scale } = outline.getView();
|
||||||
bounds = new mxRectangle(
|
bounds = new mxRectangle(
|
||||||
this.bounds.x + dx,
|
myBounds.x + dx,
|
||||||
this.bounds.y + dy,
|
myBounds.y + dy,
|
||||||
this.bounds.width,
|
myBounds.width,
|
||||||
this.bounds.height
|
myBounds.height
|
||||||
);
|
);
|
||||||
this.selectionBorder.bounds = bounds;
|
selectionBorder.bounds = bounds;
|
||||||
this.selectionBorder.redraw();
|
selectionBorder.redraw();
|
||||||
dx /= scale;
|
dx /= scale;
|
||||||
dx *= this.source.getView().scale;
|
dx *= source.getView().scale;
|
||||||
dy /= scale;
|
dy /= scale;
|
||||||
dy *= this.source.getView().scale;
|
dy *= source.getView().scale;
|
||||||
this.source.panGraph(-dx - this.dx0, -dy - this.dy0);
|
source.panGraph(-dx - <number>this.dx0, -dy - <number>this.dy0);
|
||||||
} else {
|
} else {
|
||||||
// Does *not* preview zooming on the source graph
|
// Does *not* preview zooming on the source graph
|
||||||
const { container } = this.source;
|
const { container } = <mxGraph>this.source;
|
||||||
|
// @ts-ignore
|
||||||
const viewRatio = container.clientWidth / container.clientHeight;
|
const viewRatio = container.clientWidth / container.clientHeight;
|
||||||
dy = dx / viewRatio;
|
dy = dx / viewRatio;
|
||||||
bounds = new mxRectangle(
|
bounds = new mxRectangle(
|
||||||
this.bounds.x,
|
myBounds.x,
|
||||||
this.bounds.y,
|
myBounds.y,
|
||||||
Math.max(1, this.bounds.width + dx),
|
Math.max(1, myBounds.width + dx),
|
||||||
Math.max(1, this.bounds.height + dy)
|
Math.max(1, myBounds.height + dy)
|
||||||
);
|
);
|
||||||
this.selectionBorder.bounds = bounds;
|
selectionBorder.bounds = bounds;
|
||||||
this.selectionBorder.redraw();
|
selectionBorder.redraw();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Updates the zoom handle
|
// Updates the zoom handle
|
||||||
const b = this.sizer.bounds;
|
const b = <mxRectangle>sizer.bounds;
|
||||||
this.sizer.bounds = new mxRectangle(
|
sizer.bounds = new mxRectangle(
|
||||||
bounds.x + bounds.width - b.width / 2,
|
bounds.x + bounds.width - b.width / 2,
|
||||||
bounds.y + bounds.height - b.height / 2,
|
bounds.y + bounds.height - b.height / 2,
|
||||||
b.width,
|
b.width,
|
||||||
|
@ -673,10 +689,9 @@ class mxOutline {
|
||||||
);
|
);
|
||||||
|
|
||||||
// Avoids update of visibility in redraw for VML
|
// Avoids update of visibility in redraw for VML
|
||||||
if (this.sizer.node.style.visibility !== 'hidden') {
|
if (sizerNode.style.visibility !== 'hidden') {
|
||||||
this.sizer.redraw();
|
sizer.redraw();
|
||||||
}
|
}
|
||||||
|
|
||||||
me.consume();
|
me.consume();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -703,8 +718,8 @@ class mxOutline {
|
||||||
* };
|
* };
|
||||||
* (end)
|
* (end)
|
||||||
*/
|
*/
|
||||||
getTranslateForEvent(me) {
|
getTranslateForEvent(me: mxMouseEvent) {
|
||||||
return new mxPoint(me.getX() - this.startX, me.getY() - this.startY);
|
return new mxPoint(me.getX() - <number>this.startX, me.getY() - <number>this.startY);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -712,31 +727,34 @@ class mxOutline {
|
||||||
*
|
*
|
||||||
* Handles the event by applying the translation or zoom to <graph>.
|
* Handles the event by applying the translation or zoom to <graph>.
|
||||||
*/
|
*/
|
||||||
mouseUp(sender, me) {
|
mouseUp(sender: any, me: mxMouseEvent) {
|
||||||
if (this.active) {
|
if (this.active) {
|
||||||
const delta = this.getTranslateForEvent(me);
|
const delta = this.getTranslateForEvent(me);
|
||||||
let dx = delta.x;
|
let dx = delta.x;
|
||||||
let dy = delta.y;
|
let dy = delta.y;
|
||||||
|
const source = <mxGraph>this.source;
|
||||||
|
const outline = <mxGraph>this.outline;
|
||||||
|
const selectionBorder = <mxRectangleShape>this.selectionBorder;
|
||||||
|
|
||||||
if (Math.abs(dx) > 0 || Math.abs(dy) > 0) {
|
if (Math.abs(dx) > 0 || Math.abs(dy) > 0) {
|
||||||
if (!this.zoom) {
|
if (!this.zoom) {
|
||||||
// Applies the new translation if the source
|
// Applies the new translation if the source
|
||||||
// has no scrollbars
|
// has no scrollbars
|
||||||
if (
|
if (
|
||||||
!this.source.useScrollbarsForPanning ||
|
!source.useScrollbarsForPanning ||
|
||||||
!mxUtils.hasScrollbars(this.source.container)
|
!mxUtils.hasScrollbars(source.container)
|
||||||
) {
|
) {
|
||||||
this.source.panGraph(0, 0);
|
source.panGraph(0, 0);
|
||||||
dx /= this.outline.getView().scale;
|
dx /= outline.getView().scale;
|
||||||
dy /= this.outline.getView().scale;
|
dy /= outline.getView().scale;
|
||||||
const t = this.source.getView().translate;
|
const t = source.getView().translate;
|
||||||
this.source.getView().setTranslate(t.x - dx, t.y - dy);
|
source.getView().setTranslate(t.x - dx, t.y - dy);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Applies the new zoom
|
// Applies the new zoom
|
||||||
const w = this.selectionBorder.bounds.width;
|
const w = (<mxRectangle>selectionBorder.bounds).width;
|
||||||
const { scale } = this.source.getView();
|
const { scale } = source.getView();
|
||||||
this.source.zoomTo(
|
source.zoomTo(
|
||||||
Math.max(this.minScale, scale - (dx * scale) / w),
|
Math.max(this.minScale, scale - (dx * scale) / w),
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
|
@ -768,6 +786,7 @@ class mxOutline {
|
||||||
'scroll',
|
'scroll',
|
||||||
this.updateHandler
|
this.updateHandler
|
||||||
);
|
);
|
||||||
|
// @ts-ignore
|
||||||
this.source = null;
|
this.source = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,27 +0,0 @@
|
||||||
class ExampleBase {
|
|
||||||
props: object;
|
|
||||||
|
|
||||||
constructor(props: object) {
|
|
||||||
this.props = props;
|
|
||||||
}
|
|
||||||
|
|
||||||
appendToElement(element: HTMLElement): HTMLElement {
|
|
||||||
const html: string = this.getHTML();
|
|
||||||
const cont: HTMLElement =
|
|
||||||
document.createElement('div');
|
|
||||||
cont.innerHTML = html;
|
|
||||||
this.afterHTMLSet()
|
|
||||||
element.appendChild(cont);
|
|
||||||
return cont;
|
|
||||||
}
|
|
||||||
|
|
||||||
getHTML(): void {
|
|
||||||
throw new Error("Not implemented");
|
|
||||||
}
|
|
||||||
|
|
||||||
afterHTMLSet(): void {
|
|
||||||
throw new Error("Not implemented");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default ExampleBase;
|
|
|
@ -0,0 +1,272 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2006-2013, JGraph Ltd
|
||||||
|
* Converted to ES9 syntax/React by David Morrissey 2021
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import mxEvent from '../../mxgraph/util/mxEvent';
|
||||||
|
import mxGraph from '../../mxgraph/view/mxGraph';
|
||||||
|
import mxRubberband from '../../mxgraph/handler/mxRubberband';
|
||||||
|
import mxRectangle from '../../mxgraph/util/mxRectangle';
|
||||||
|
import mxUtils from '../../mxgraph/util/mxUtils';
|
||||||
|
import mxPoint from '../../mxgraph/util/mxPoint';
|
||||||
|
|
||||||
|
class ExtendCanvas extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
// A container for the graph
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<h1>Extend canvas</h1>
|
||||||
|
This example demonstrates implementing an infinite canvas with
|
||||||
|
scrollbars.
|
||||||
|
<div
|
||||||
|
ref={el => {
|
||||||
|
this.el = el;
|
||||||
|
}}
|
||||||
|
style={{
|
||||||
|
position: 'relative',
|
||||||
|
overflow: 'auto',
|
||||||
|
height: '241px',
|
||||||
|
background: "url('editors/images/grid.gif')",
|
||||||
|
cursor: 'default',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
// Disables the built-in context menu
|
||||||
|
mxEvent.disableContextMenu(this.el);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specifies the size of the size for "tiles" to be used for a graph with
|
||||||
|
* scrollbars but no visible background page. A good value is large
|
||||||
|
* enough to reduce the number of repaints that is caused for auto-
|
||||||
|
* translation, which depends on this value, and small enough to give
|
||||||
|
* a small empty buffer around the graph. Default is 400x400.
|
||||||
|
*/
|
||||||
|
const scrollTileSize = new mxRectangle(0, 0, 400, 400);
|
||||||
|
|
||||||
|
class MyCustomGraph extends mxGraph {
|
||||||
|
/**
|
||||||
|
* Returns the padding for pages in page view with scrollbars.
|
||||||
|
*/
|
||||||
|
getPagePadding() {
|
||||||
|
return new mxPoint(
|
||||||
|
Math.max(0, Math.round(this.container.offsetWidth - 34)),
|
||||||
|
Math.max(0, Math.round(this.container.offsetHeight - 34))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the size of the page format scaled with the page size.
|
||||||
|
*/
|
||||||
|
getPageSize() {
|
||||||
|
return this.pageVisible
|
||||||
|
? new mxRectangle(
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
this.pageFormat.width * this.pageScale,
|
||||||
|
this.pageFormat.height * this.pageScale
|
||||||
|
)
|
||||||
|
: scrollTileSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a rectangle describing the position and count of the
|
||||||
|
* background pages, where x and y are the position of the top,
|
||||||
|
* left page and width and height are the vertical and horizontal
|
||||||
|
* page count.
|
||||||
|
*/
|
||||||
|
getPageLayout() {
|
||||||
|
const size = this.pageVisible
|
||||||
|
? this.getPageSize()
|
||||||
|
: scrollTileSize;
|
||||||
|
const bounds = this.getGraphBounds();
|
||||||
|
|
||||||
|
if (bounds.width === 0 || bounds.height === 0) {
|
||||||
|
return new mxRectangle(0, 0, 1, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Computes untransformed graph bounds
|
||||||
|
const x = Math.ceil(bounds.x / this.view.scale - this.view.translate.x);
|
||||||
|
const y = Math.ceil(bounds.y / this.view.scale - this.view.translate.y);
|
||||||
|
const w = Math.floor(bounds.width / this.view.scale);
|
||||||
|
const h = Math.floor(bounds.height / this.view.scale);
|
||||||
|
|
||||||
|
const x0 = Math.floor(x / size.width);
|
||||||
|
const y0 = Math.floor(y / size.height);
|
||||||
|
const w0 = Math.ceil((x + w) / size.width) - x0;
|
||||||
|
const h0 = Math.ceil((y + h) / size.height) - y0;
|
||||||
|
|
||||||
|
return new mxRectangle(x0, y0, w0, h0);
|
||||||
|
}
|
||||||
|
|
||||||
|
getPreferredPageSize(bounds, width, height) {
|
||||||
|
const pages = this.getPageLayout();
|
||||||
|
const size = this.getPageSize();
|
||||||
|
|
||||||
|
return new mxRectangle(
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
pages.width * size.width,
|
||||||
|
pages.height * size.height
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
sizeDidChange() {
|
||||||
|
if (this.container != null && mxUtils.hasScrollbars(this.container)) {
|
||||||
|
const pages = this.getPageLayout();
|
||||||
|
const pad = this.getPagePadding();
|
||||||
|
const size = this.getPageSize();
|
||||||
|
|
||||||
|
// Updates the minimum graph size
|
||||||
|
const minw = Math.ceil(
|
||||||
|
(2 * pad.x) / this.view.scale + pages.width * size.width
|
||||||
|
);
|
||||||
|
const minh = Math.ceil(
|
||||||
|
(2 * pad.y) / this.view.scale + pages.height * size.height
|
||||||
|
);
|
||||||
|
|
||||||
|
const min = this.minimumGraphSize;
|
||||||
|
|
||||||
|
// LATER: Fix flicker of scrollbar size in IE quirks mode
|
||||||
|
// after delayed call in window.resize event handler
|
||||||
|
if (min == null || min.width !== minw || min.height !== minh) {
|
||||||
|
this.minimumGraphSize = new mxRectangle(0, 0, minw, minh);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Updates auto-translate to include padding and graph size
|
||||||
|
const dx = pad.x / this.view.scale - pages.x * size.width;
|
||||||
|
const dy = pad.y / this.view.scale - pages.y * size.height;
|
||||||
|
|
||||||
|
if (
|
||||||
|
!this.autoTranslate &&
|
||||||
|
(this.view.translate.x !== dx || this.view.translate.y !== dy)
|
||||||
|
) {
|
||||||
|
this.autoTranslate = true;
|
||||||
|
this.view.x0 = pages.x;
|
||||||
|
this.view.y0 = pages.y;
|
||||||
|
|
||||||
|
// NOTE: THIS INVOKES THIS METHOD AGAIN. UNFORTUNATELY THERE IS NO WAY AROUND THIS SINCE THE
|
||||||
|
// BOUNDS ARE KNOWN AFTER THE VALIDATION AND SETTING THE TRANSLATE TRIGGERS A REVALIDATION.
|
||||||
|
// SHOULD MOVE TRANSLATE/SCALE TO VIEW.
|
||||||
|
const tx = this.view.translate.x;
|
||||||
|
const ty = this.view.translate.y;
|
||||||
|
|
||||||
|
this.view.setTranslate(dx, dy);
|
||||||
|
this.container.scrollLeft += (dx - tx) * this.view.scale;
|
||||||
|
this.container.scrollTop += (dy - ty) * this.view.scale;
|
||||||
|
|
||||||
|
this.autoTranslate = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
super.sizeDidChange();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creates the graph inside the given container
|
||||||
|
const graph = this.graph = new MyCustomGraph(this.el);
|
||||||
|
graph.panningHandler.ignoreCell = true;
|
||||||
|
graph.setPanning(true);
|
||||||
|
|
||||||
|
// Fits the number of background pages to the graph
|
||||||
|
graph.view.getBackgroundPageBounds = function() {
|
||||||
|
const layout = this.graph.getPageLayout();
|
||||||
|
const page = this.graph.getPageSize();
|
||||||
|
|
||||||
|
return new mxRectangle(
|
||||||
|
this.scale * (this.translate.x + layout.x * page.width),
|
||||||
|
this.scale * (this.translate.y + layout.y * page.height),
|
||||||
|
this.scale * layout.width * page.width,
|
||||||
|
this.scale * layout.height * page.height
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Guesses autoTranslate to avoid another repaint (see below).
|
||||||
|
* Works if only the scale of the graph changes or if pages
|
||||||
|
* are visible and the visible pages do not change.
|
||||||
|
*/
|
||||||
|
const graphViewValidate = graph.view.validate;
|
||||||
|
graph.view.validate = function() {
|
||||||
|
if (
|
||||||
|
this.graph.container != null &&
|
||||||
|
mxUtils.hasScrollbars(this.graph.container)
|
||||||
|
) {
|
||||||
|
const pad = this.graph.getPagePadding();
|
||||||
|
const size = this.graph.getPageSize();
|
||||||
|
|
||||||
|
// Updating scrollbars here causes flickering in quirks and is not needed
|
||||||
|
// if zoom method is always used to set the current scale on the graph.
|
||||||
|
const tx = this.translate.x;
|
||||||
|
const ty = this.translate.y;
|
||||||
|
this.translate.x = pad.x / this.scale - (this.x0 || 0) * size.width;
|
||||||
|
this.translate.y = pad.y / this.scale - (this.y0 || 0) * size.height;
|
||||||
|
}
|
||||||
|
|
||||||
|
graphViewValidate.apply(this, arguments);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Enables rubberband selection
|
||||||
|
new mxRubberband(graph);
|
||||||
|
|
||||||
|
// Gets the default parent for inserting new cells. This
|
||||||
|
// is normally the first child of the root (ie. layer 0).
|
||||||
|
const parent = graph.getDefaultParent();
|
||||||
|
|
||||||
|
// Adds cells to the model in a single step
|
||||||
|
graph.batchUpdate(() => {
|
||||||
|
const v1 = graph.insertVertex({
|
||||||
|
parent,
|
||||||
|
value: 'Hello,',
|
||||||
|
position: [20, 20],
|
||||||
|
size: [80, 30],
|
||||||
|
});
|
||||||
|
const v2 = graph.insertVertex({
|
||||||
|
parent,
|
||||||
|
value: 'World!',
|
||||||
|
position: [200, 150],
|
||||||
|
size: [80, 30],
|
||||||
|
});
|
||||||
|
const e1 = graph.insertEdge({
|
||||||
|
parent,
|
||||||
|
source: v1,
|
||||||
|
target: v2,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Sets initial scrollbar positions
|
||||||
|
window.setTimeout(() => {
|
||||||
|
const bounds = graph.getGraphBounds();
|
||||||
|
const width = Math.max(
|
||||||
|
bounds.width,
|
||||||
|
scrollTileSize.width * graph.view.scale
|
||||||
|
);
|
||||||
|
const height = Math.max(
|
||||||
|
bounds.height,
|
||||||
|
scrollTileSize.height * graph.view.scale
|
||||||
|
);
|
||||||
|
graph.container.scrollTop = Math.floor(
|
||||||
|
Math.max(
|
||||||
|
0,
|
||||||
|
bounds.y - Math.max(20, (graph.container.clientHeight - height) / 4)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
graph.container.scrollLeft = Math.floor(
|
||||||
|
Math.max(
|
||||||
|
0,
|
||||||
|
bounds.x - Math.max(0, (graph.container.clientWidth - width) / 2)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ExtendCanvas;
|
|
@ -1,282 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright (c) 2006-2013, JGraph Ltd
|
|
||||||
* Converted to ES9 syntax/React by David Morrissey 2021
|
|
||||||
*/
|
|
||||||
|
|
||||||
import mxEvent from '../../mxgraph/util/event/mxEvent';
|
|
||||||
import mxGraph from '../../mxgraph/view/graph/mxGraph';
|
|
||||||
import mxGraphView from '../../mxgraph/view/graph/mxGraphView'
|
|
||||||
import mxRubberband from '../../mxgraph/handler/mxRubberband';
|
|
||||||
import mxRectangle from '../../mxgraph/util/datatypes/mxRectangle';
|
|
||||||
import mxUtils from '../../mxgraph/util/mxUtils';
|
|
||||||
import mxPoint from '../../mxgraph/util/datatypes/mxPoint';
|
|
||||||
import mxCell from '../../mxgraph/view/cell/mxCell';
|
|
||||||
import ExampleBase from "./ExampleBase";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Specifies the size of the size for "tiles" to be used for a graph with
|
|
||||||
* scrollbars but no visible background page. A good value is large
|
|
||||||
* enough to reduce the number of repaints that is caused for auto-
|
|
||||||
* translation, which depends on this value, and small enough to give
|
|
||||||
* a small empty buffer around the graph. Default is 400x400.
|
|
||||||
*/
|
|
||||||
const scrollTileSize = new mxRectangle(0, 0, 400, 400);
|
|
||||||
|
|
||||||
class ExtendCanvas extends ExampleBase {
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
}
|
|
||||||
|
|
||||||
getHTML() {
|
|
||||||
// A container for the graph
|
|
||||||
return `
|
|
||||||
<h1>Extend canvas</h1>
|
|
||||||
This example demonstrates implementing an infinite canvas with
|
|
||||||
scrollbars.
|
|
||||||
|
|
||||||
<div id="el"
|
|
||||||
style="position: relative;
|
|
||||||
overflow: auto;
|
|
||||||
height: 241px;
|
|
||||||
background: url('editors/images/grid.gif');
|
|
||||||
cursor: default"
|
|
||||||
/>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
afterHTMLSet(): void {
|
|
||||||
// Executed after the HTML has been set
|
|
||||||
const el = document.getElementById("el");
|
|
||||||
el.id = '';
|
|
||||||
|
|
||||||
// Disables the built-in context menu
|
|
||||||
mxEvent.disableContextMenu(el);
|
|
||||||
|
|
||||||
// Creates the graph inside the given container
|
|
||||||
const graph: MyCustomGraph = new MyCustomGraph(el);
|
|
||||||
graph.panningHandler.ignoreCell = true;
|
|
||||||
graph.setPanning(true);
|
|
||||||
|
|
||||||
// Enables rubberband selection
|
|
||||||
new mxRubberband(graph);
|
|
||||||
|
|
||||||
// Gets the default parent for inserting new cells. This
|
|
||||||
// is normally the first child of the root (ie. layer 0).
|
|
||||||
const parent = graph.getDefaultParent();
|
|
||||||
|
|
||||||
// Adds cells to the model in a single step
|
|
||||||
graph.batchUpdate(() => {
|
|
||||||
const v1: mxCell = graph.insertVertex({
|
|
||||||
parent,
|
|
||||||
value: 'Hello,',
|
|
||||||
position: [20, 20],
|
|
||||||
size: [80, 30],
|
|
||||||
});
|
|
||||||
const v2: mxCell = graph.insertVertex({
|
|
||||||
parent,
|
|
||||||
value: 'World!',
|
|
||||||
position: [200, 150],
|
|
||||||
size: [80, 30],
|
|
||||||
});
|
|
||||||
const e1: mxCell = graph.insertEdge({
|
|
||||||
parent,
|
|
||||||
source: v1,
|
|
||||||
target: v2,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Sets initial scrollbar positions
|
|
||||||
window.setTimeout(() => {
|
|
||||||
const bounds: mxRectangle = graph.getGraphBounds();
|
|
||||||
const width: number = Math.max(
|
|
||||||
bounds.width,
|
|
||||||
scrollTileSize.width * graph.view.scale
|
|
||||||
);
|
|
||||||
const height: number = Math.max(
|
|
||||||
bounds.height,
|
|
||||||
scrollTileSize.height * graph.view.scale
|
|
||||||
);
|
|
||||||
graph.container.scrollTop = Math.floor(
|
|
||||||
Math.max(
|
|
||||||
0,
|
|
||||||
bounds.y - Math.max(20, (graph.container.clientHeight - height) / 4)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
graph.container.scrollLeft = Math.floor(
|
|
||||||
Math.max(
|
|
||||||
0,
|
|
||||||
bounds.x - Math.max(0, (graph.container.clientWidth - width) / 2)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class MyCustomGraph extends mxGraph {
|
|
||||||
autoTranslate: boolean;
|
|
||||||
|
|
||||||
createGraphView(): mxGraphView {
|
|
||||||
return new MyCustomGraphView(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the padding for pages in page view with scrollbars.
|
|
||||||
*/
|
|
||||||
getPagePadding(): mxPoint {
|
|
||||||
return new mxPoint(
|
|
||||||
Math.max(0, Math.round(this.container.offsetWidth - 34)),
|
|
||||||
Math.max(0, Math.round(this.container.offsetHeight - 34))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the size of the page format scaled with the page size.
|
|
||||||
*/
|
|
||||||
getPageSize(): mxRectangle {
|
|
||||||
return this.pageVisible
|
|
||||||
? new mxRectangle(
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
this.pageFormat.width * this.pageScale,
|
|
||||||
this.pageFormat.height * this.pageScale
|
|
||||||
)
|
|
||||||
: scrollTileSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a rectangle describing the position and count of the
|
|
||||||
* background pages, where x and y are the position of the top,
|
|
||||||
* left page and width and height are the vertical and horizontal
|
|
||||||
* page count.
|
|
||||||
*/
|
|
||||||
getPageLayout(): mxRectangle {
|
|
||||||
const size: mxRectangle = this.pageVisible
|
|
||||||
? this.getPageSize()
|
|
||||||
: scrollTileSize;
|
|
||||||
const bounds = this.getGraphBounds();
|
|
||||||
|
|
||||||
if (bounds.width === 0 || bounds.height === 0) {
|
|
||||||
return new mxRectangle(0, 0, 1, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Computes untransformed graph bounds
|
|
||||||
const x: number = Math.ceil(bounds.x / this.view.scale - this.view.translate.x);
|
|
||||||
const y: number = Math.ceil(bounds.y / this.view.scale - this.view.translate.y);
|
|
||||||
const w: number = Math.floor(bounds.width / this.view.scale);
|
|
||||||
const h: number = Math.floor(bounds.height / this.view.scale);
|
|
||||||
|
|
||||||
const x0: number = Math.floor(x / size.width);
|
|
||||||
const y0: number = Math.floor(y / size.height);
|
|
||||||
const w0: number = Math.ceil((x + w) / size.width) - x0;
|
|
||||||
const h0: number = Math.ceil((y + h) / size.height) - y0;
|
|
||||||
|
|
||||||
return new mxRectangle(x0, y0, w0, h0);
|
|
||||||
}
|
|
||||||
|
|
||||||
getPreferredPageSize(bounds, width, height): mxRectangle {
|
|
||||||
const pages: mxRectangle = this.getPageLayout();
|
|
||||||
const size: mxRectangle = this.getPageSize();
|
|
||||||
|
|
||||||
return new mxRectangle(
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
pages.width * size.width,
|
|
||||||
pages.height * size.height
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
sizeDidChange(): mxRectangle {
|
|
||||||
if (this.container != null && mxUtils.hasScrollbars(this.container)) {
|
|
||||||
const pages: mxRectangle = this.getPageLayout();
|
|
||||||
const pad: mxPoint = this.getPagePadding();
|
|
||||||
const size: mxRectangle = this.getPageSize();
|
|
||||||
|
|
||||||
// Updates the minimum graph size
|
|
||||||
const minw: number = Math.ceil(
|
|
||||||
(2 * pad.x) / this.view.scale + pages.width * size.width
|
|
||||||
);
|
|
||||||
const minh: number = Math.ceil(
|
|
||||||
(2 * pad.y) / this.view.scale + pages.height * size.height
|
|
||||||
);
|
|
||||||
|
|
||||||
const min: number = this.minimumGraphSize;
|
|
||||||
|
|
||||||
// LATER: Fix flicker of scrollbar size in IE quirks mode
|
|
||||||
// after delayed call in window.resize event handler
|
|
||||||
if (min == null || min.width !== minw || min.height !== minh) {
|
|
||||||
this.minimumGraphSize = new mxRectangle(0, 0, minw, minh);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Updates auto-translate to include padding and graph size
|
|
||||||
const dx: number = pad.x / this.view.scale - pages.x * size.width;
|
|
||||||
const dy: number = pad.y / this.view.scale - pages.y * size.height;
|
|
||||||
|
|
||||||
if (
|
|
||||||
!this.autoTranslate &&
|
|
||||||
(this.view.translate.x !== dx || this.view.translate.y !== dy)
|
|
||||||
) {
|
|
||||||
this.autoTranslate = true;
|
|
||||||
this.view.x0 = pages.x;
|
|
||||||
this.view.y0 = pages.y;
|
|
||||||
|
|
||||||
// NOTE: THIS INVOKES THIS METHOD AGAIN. UNFORTUNATELY THERE IS NO WAY AROUND THIS SINCE THE
|
|
||||||
// BOUNDS ARE KNOWN AFTER THE VALIDATION AND SETTING THE TRANSLATE TRIGGERS A REVALIDATION.
|
|
||||||
// SHOULD MOVE TRANSLATE/SCALE TO VIEW.
|
|
||||||
const tx: number = this.view.translate.x;
|
|
||||||
const ty: number = this.view.translate.y;
|
|
||||||
|
|
||||||
this.view.setTranslate(dx, dy);
|
|
||||||
this.container.scrollLeft += (dx - tx) * this.view.scale;
|
|
||||||
this.container.scrollTop += (dy - ty) * this.view.scale;
|
|
||||||
|
|
||||||
this.autoTranslate = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
super.sizeDidChange();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class MyCustomGraphView extends mxGraphView {
|
|
||||||
/**
|
|
||||||
* Fits the number of background pages to the graph
|
|
||||||
*/
|
|
||||||
getBackgroundPageBounds(): mxRectangle {
|
|
||||||
const layout: mxRectangle = this.graph.getPageLayout();
|
|
||||||
const page: mxRectangle = this.graph.getPageSize();
|
|
||||||
|
|
||||||
return new mxRectangle(
|
|
||||||
this.scale * (this.translate.x + layout.x * page.width),
|
|
||||||
this.scale * (this.translate.y + layout.y * page.height),
|
|
||||||
this.scale * layout.width * page.width,
|
|
||||||
this.scale * layout.height * page.height
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Guesses autoTranslate to avoid another repaint (see below).
|
|
||||||
* Works if only the scale of the graph changes or if pages
|
|
||||||
* are visible and the visible pages do not change.
|
|
||||||
*/
|
|
||||||
validate(): void {
|
|
||||||
if (
|
|
||||||
this.graph.container != null &&
|
|
||||||
mxUtils.hasScrollbars(this.graph.container)
|
|
||||||
) {
|
|
||||||
const pad: mxPoint = this.graph.getPagePadding();
|
|
||||||
const size: mxRectangle = this.graph.getPageSize();
|
|
||||||
|
|
||||||
// Updating scrollbars here causes flickering in quirks and is not needed
|
|
||||||
// if zoom method is always used to set the current scale on the graph.
|
|
||||||
//const tx = this.translate.x;
|
|
||||||
//const ty = this.translate.y;
|
|
||||||
this.translate.x = pad.x / this.scale - (this.x0 || 0) * size.width;
|
|
||||||
this.translate.y = pad.y / this.scale - (this.y0 || 0) * size.height;
|
|
||||||
}
|
|
||||||
|
|
||||||
super.validate();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export default ExtendCanvas;
|
|
Loading…
Reference in New Issue