converting to typescript
parent
16060a58bd
commit
889156b314
|
@ -14,6 +14,136 @@ import mxRectangle from '../util/datatypes/mxRectangle';
|
|||
import mxCellState from '../util/datatypes/mxCellState';
|
||||
|
||||
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
|
||||
*
|
||||
|
@ -78,15 +208,6 @@ class mxText extends mxShape {
|
|||
*/
|
||||
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
|
||||
*
|
||||
|
@ -101,124 +222,8 @@ class mxText extends mxShape {
|
|||
*/
|
||||
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
|
||||
*
|
||||
* Disables offset in IE9 for crisper image output.
|
||||
*/
|
||||
getSvgScreenOffset(): number {
|
||||
return 0;
|
||||
|
@ -342,32 +347,17 @@ class mxText extends mxShape {
|
|||
this.dialect === mxConstants.DIALECT_STRICTHTML)
|
||||
) {
|
||||
if (this.node.nodeName === 'DIV') {
|
||||
if (mxClient.IS_SVG) {
|
||||
this.redrawHtmlShapeWithCss3();
|
||||
} else {
|
||||
this.updateSize(
|
||||
this.node,
|
||||
this.state == null || this.state.view.textDiv == null
|
||||
);
|
||||
|
||||
this.updateHtmlTransform();
|
||||
}
|
||||
|
||||
this.redrawHtmlShape();
|
||||
this.updateBoundingBox();
|
||||
} else {
|
||||
const canvas = this.createCanvas();
|
||||
|
||||
if (canvas != null && canvas.updateText != null) {
|
||||
// Specifies if events should be handled
|
||||
canvas.pointerEvents = this.pointerEvents;
|
||||
|
||||
this.paint(canvas, true);
|
||||
this.destroyCanvas(canvas);
|
||||
this.updateBoundingBox();
|
||||
} else {
|
||||
// Fallback if canvas does not support updateText (VML)
|
||||
super.redraw();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
super.redraw();
|
||||
|
@ -406,7 +396,7 @@ class mxText extends mxShape {
|
|||
delete this.background;
|
||||
delete this.border;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -596,7 +585,6 @@ class mxText extends mxShape {
|
|||
let ow = null;
|
||||
let oh = null;
|
||||
|
||||
if (node.ownerSVGElement != null) {
|
||||
if (
|
||||
node.firstChild != null &&
|
||||
node.firstChild.firstChild != null &&
|
||||
|
@ -632,40 +620,6 @@ class mxText extends mxShape {
|
|||
// Ignores NS_ERROR_FAILURE in FF if container display is none.
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const td = this.state != null ? this.state.view.textDiv : null;
|
||||
|
||||
// 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;
|
||||
|
||||
if (
|
||||
sizeDiv.firstChild != null &&
|
||||
sizeDiv.firstChild.nodeName === 'DIV'
|
||||
) {
|
||||
sizeDiv = sizeDiv.firstChild;
|
||||
}
|
||||
|
||||
this.offsetWidth = sizeDiv.offsetWidth + this.textWidthPadding;
|
||||
this.offsetHeight = sizeDiv.offsetHeight;
|
||||
|
||||
ow = this.offsetWidth * this.scale;
|
||||
oh = this.offsetHeight * this.scale;
|
||||
}
|
||||
}
|
||||
|
||||
if (ow != null && oh != null) {
|
||||
this.boundingBox = new mxRectangle(
|
||||
|
@ -680,7 +634,7 @@ class mxText extends mxShape {
|
|||
if (this.boundingBox != null) {
|
||||
if (rot !== 0) {
|
||||
// Accounts for pre-rotated x and y
|
||||
const bbox = mxUtils.getBoundingBox(
|
||||
const bbox = <mxRectangle>mxUtils.getBoundingBox(
|
||||
new mxRectangle(
|
||||
this.margin.x * this.boundingBox.width,
|
||||
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.
|
||||
*/
|
||||
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 h = Math.max(0, Math.round(this.bounds.height / this.scale));
|
||||
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
|
||||
*
|
||||
|
@ -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
|
||||
*
|
||||
|
|
|
@ -32,12 +32,10 @@ import mxSvgCanvas2D from "../../util/canvas/mxSvgCanvas2D";
|
|||
* 1. This is stored in <strokewidth>.
|
||||
*/
|
||||
class mxRectangleShape extends mxShape {
|
||||
strokewidth: number;
|
||||
|
||||
constructor(
|
||||
bounds: mxRectangle | null=null,
|
||||
fill: string = '#FFFFFF',
|
||||
stroke: string = '#000000',
|
||||
fill: string | null = '#FFFFFF',
|
||||
stroke: string | null = '#000000',
|
||||
strokewidth: number = 1
|
||||
) {
|
||||
super();
|
||||
|
@ -47,6 +45,9 @@ class mxRectangleShape extends mxShape {
|
|||
this.strokewidth = strokewidth;
|
||||
}
|
||||
|
||||
// TODO: Document me!
|
||||
strokewidth: number;
|
||||
|
||||
/**
|
||||
* Function: isHtmlAllowed
|
||||
*
|
||||
|
|
|
@ -15,31 +15,31 @@ import mxGraph from "../../view/graph/mxGraph";
|
|||
|
||||
class mxCellState extends mxRectangle {
|
||||
// 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()
|
||||
control: mxShape | undefined;
|
||||
control: mxShape | null = null;
|
||||
|
||||
// Used by mxCellRenderer's createCellOverlays()
|
||||
overlays: any[] | null | undefined;
|
||||
overlays: any[] | null = null;
|
||||
|
||||
/**
|
||||
* Variable: view
|
||||
*
|
||||
* Reference to the enclosing <mxGraphView>.
|
||||
*/
|
||||
view: mxGraphView | null = null;
|
||||
view: mxGraphView;
|
||||
|
||||
/**
|
||||
* Variable: cell
|
||||
*
|
||||
* Reference to the <mxCell> that is represented by this state.
|
||||
*/
|
||||
cell: mxCell | null = null;
|
||||
cell: mxCell;
|
||||
|
||||
/**
|
||||
* Variable: style
|
||||
|
@ -47,7 +47,7 @@ class mxCellState extends mxRectangle {
|
|||
* Contains an array of key, value pairs that represent the style of the
|
||||
* 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
|
||||
|
@ -69,7 +69,7 @@ class mxCellState extends mxRectangle {
|
|||
* <mxPoint> that holds the origin for all child cells. Default is a new
|
||||
* empty <mxPoint>.
|
||||
*/
|
||||
origin: mxPoint | null = null;
|
||||
origin: mxPoint;
|
||||
|
||||
/**
|
||||
* Variable: absolutePoints
|
||||
|
@ -86,7 +86,7 @@ class mxCellState extends mxRectangle {
|
|||
* absolute coordinates of the label position. For vertices, this is the
|
||||
* offset of the label relative to the top, left corner of the vertex.
|
||||
*/
|
||||
absoluteOffset: mxPoint | null = null;
|
||||
absoluteOffset: mxPoint;
|
||||
|
||||
/**
|
||||
* Variable: visibleSourceState
|
||||
|
|
|
@ -488,7 +488,7 @@ class mxCell {
|
|||
*
|
||||
* child - Child whose index should be returned.
|
||||
*/
|
||||
getIndex(child: mxCell): number {
|
||||
getIndex(child: mxCell | null): number {
|
||||
return mxUtils.indexOf(this.children, child);
|
||||
}
|
||||
|
||||
|
|
|
@ -14,146 +14,10 @@ import mxGraph from '../graph/mxGraph';
|
|||
import mxCell from './mxCell';
|
||||
import mxMouseEvent from '../../util/event/mxMouseEvent';
|
||||
import mxCellState from '../../util/datatypes/mxCellState';
|
||||
import mxShape from "../../shape/mxShape";
|
||||
import mxEventObject from "../../util/event/mxEventObject";
|
||||
|
||||
class mxCellEditor {
|
||||
// TODO: Document me!
|
||||
changeHandler: Function | null;
|
||||
|
||||
zoomHandler: Function | null;
|
||||
|
||||
clearOnChange: boolean = false;
|
||||
|
||||
bounds: mxRectangle | null = null;
|
||||
|
||||
resizeThread: number | null;
|
||||
|
||||
textDirection: '' | 'auto' | 'ltr' | 'rtl' | null;
|
||||
|
||||
/**
|
||||
* Variable: graph
|
||||
*
|
||||
* Reference to the enclosing <mxGraph>.
|
||||
*/
|
||||
graph: mxGraph = null;
|
||||
|
||||
/**
|
||||
* Variable: textarea
|
||||
*
|
||||
* Holds the DIV that is used for text editing. Note that this may be null before the first
|
||||
* edit. Instantiated in <init>.
|
||||
*/
|
||||
textarea: HTMLElement = null;
|
||||
|
||||
/**
|
||||
* Variable: editingCell
|
||||
*
|
||||
* Reference to the <mxCell> that is currently being edited.
|
||||
*/
|
||||
editingCell: mxCell = null;
|
||||
|
||||
/**
|
||||
* Variable: trigger
|
||||
*
|
||||
* Reference to the event that was used to start editing.
|
||||
*/
|
||||
trigger: mxMouseEvent = null;
|
||||
|
||||
/**
|
||||
* Variable: modified
|
||||
*
|
||||
* Specifies if the label has been modified.
|
||||
*/
|
||||
modified: boolean = false;
|
||||
|
||||
/**
|
||||
* Variable: autoSize
|
||||
*
|
||||
* Specifies if the textarea should be resized while the text is being edited.
|
||||
* Default is true.
|
||||
*/
|
||||
autoSize: boolean = true;
|
||||
|
||||
/**
|
||||
* Variable: selectText
|
||||
*
|
||||
* Specifies if the text should be selected when editing starts. Default is
|
||||
* true.
|
||||
*/
|
||||
selectText: boolean = true;
|
||||
|
||||
/**
|
||||
* Variable: emptyLabelText
|
||||
*
|
||||
* Text to be displayed for empty labels. Default is '' or '<br>' in Firefox as
|
||||
* a workaround for the missing cursor bug for empty content editable. This can
|
||||
* be set to eg. "[Type Here]" to easier visualize editing of empty labels. The
|
||||
* value is only displayed before the first keystroke and is never used as the
|
||||
* actual editing value.
|
||||
*/
|
||||
emptyLabelText: string = mxClient.IS_FF ? '<br>' : '';
|
||||
|
||||
/**
|
||||
* Variable: escapeCancelsEditing
|
||||
*
|
||||
* If true, pressing the escape key will stop editing and not accept the new
|
||||
* value. Change this to false to accept the new value on escape, and cancel
|
||||
* editing on Shift+Escape instead. Default is true.
|
||||
*/
|
||||
escapeCancelsEditing: boolean = true;
|
||||
|
||||
/**
|
||||
* Variable: textNode
|
||||
*
|
||||
* Reference to the label DOM node that has been hidden.
|
||||
*/
|
||||
textNode: HTMLElement | null = null;
|
||||
|
||||
/**
|
||||
* Variable: zIndex
|
||||
*
|
||||
* Specifies the zIndex for the textarea. Default is 5.
|
||||
*/
|
||||
zIndex: number = 5;
|
||||
|
||||
/**
|
||||
* Variable: minResize
|
||||
*
|
||||
* Defines the minimum width and height to be used in <resize>. Default is 0x20px.
|
||||
*/
|
||||
minResize: mxRectangle = new mxRectangle(0, 20);
|
||||
|
||||
/**
|
||||
* Variable: wordWrapPadding
|
||||
*
|
||||
* Correction factor for word wrapping width. Default is 2 in quirks, 0 in IE
|
||||
* 11 and 1 in all other browsers and modes.
|
||||
*/
|
||||
wordWrapPadding: number = 0;
|
||||
|
||||
/**
|
||||
* Variable: blurEnabled
|
||||
*
|
||||
* If <focusLost> should be called if <textarea> loses the focus. Default is false.
|
||||
*/
|
||||
blurEnabled: boolean = false;
|
||||
|
||||
/**
|
||||
* Variable: initialValue
|
||||
*
|
||||
* Holds the initial editing value to check if the current value was modified.
|
||||
*/
|
||||
initialValue: string | null = null;
|
||||
|
||||
/**
|
||||
* Variable: align
|
||||
*
|
||||
* Holds the current temporary horizontal alignment for the cell style. If this
|
||||
* is modified then the current text alignment is changed and the cell style is
|
||||
* updated when the value is applied.
|
||||
*/
|
||||
align: string | null = null;
|
||||
|
||||
/**
|
||||
/**
|
||||
* Class: mxCellEditor
|
||||
*
|
||||
* In-place editor for the graph. To control this editor, use
|
||||
|
@ -259,6 +123,7 @@ class mxCellEditor {
|
|||
*
|
||||
* graph - Reference to the enclosing <mxGraph>.
|
||||
*/
|
||||
class mxCellEditor {
|
||||
constructor(graph: mxGraph) {
|
||||
this.graph = graph;
|
||||
|
||||
|
@ -270,7 +135,7 @@ class mxCellEditor {
|
|||
};
|
||||
|
||||
// Handling of deleted cells while editing
|
||||
this.changeHandler = sender => {
|
||||
this.changeHandler = (sender: any) => {
|
||||
if (
|
||||
this.editingCell != null &&
|
||||
this.graph.getView().getState(this.editingCell, false) == null
|
||||
|
@ -284,6 +149,143 @@ class mxCellEditor {
|
|||
this.graph.getModel().addListener(mxEvent.CHANGE, this.changeHandler);
|
||||
}
|
||||
|
||||
// TODO: Document me!
|
||||
changeHandler: Function | null;
|
||||
|
||||
zoomHandler: Function | null;
|
||||
|
||||
clearOnChange: boolean = false;
|
||||
|
||||
bounds: mxRectangle | null = null;
|
||||
|
||||
resizeThread: number | null = null;
|
||||
|
||||
textDirection: '' | 'auto' | 'ltr' | 'rtl' | null = null;
|
||||
|
||||
/**
|
||||
* Variable: graph
|
||||
*
|
||||
* Reference to the enclosing <mxGraph>.
|
||||
*/
|
||||
graph: mxGraph;
|
||||
|
||||
/**
|
||||
* Variable: textarea
|
||||
*
|
||||
* Holds the DIV that is used for text editing. Note that this may be null before the first
|
||||
* edit. Instantiated in <init>.
|
||||
*/
|
||||
textarea: HTMLElement | null = null;
|
||||
|
||||
/**
|
||||
* Variable: editingCell
|
||||
*
|
||||
* Reference to the <mxCell> that is currently being edited.
|
||||
*/
|
||||
editingCell: mxCell | null = null;
|
||||
|
||||
/**
|
||||
* Variable: trigger
|
||||
*
|
||||
* Reference to the event that was used to start editing.
|
||||
*/
|
||||
trigger: mxMouseEvent | null = null;
|
||||
|
||||
/**
|
||||
* Variable: modified
|
||||
*
|
||||
* Specifies if the label has been modified.
|
||||
*/
|
||||
modified: boolean = false;
|
||||
|
||||
/**
|
||||
* Variable: autoSize
|
||||
*
|
||||
* Specifies if the textarea should be resized while the text is being edited.
|
||||
* Default is true.
|
||||
*/
|
||||
autoSize: boolean = true;
|
||||
|
||||
/**
|
||||
* Variable: selectText
|
||||
*
|
||||
* Specifies if the text should be selected when editing starts. Default is
|
||||
* true.
|
||||
*/
|
||||
selectText: boolean = true;
|
||||
|
||||
/**
|
||||
* Variable: emptyLabelText
|
||||
*
|
||||
* Text to be displayed for empty labels. Default is '' or '<br>' in Firefox as
|
||||
* a workaround for the missing cursor bug for empty content editable. This can
|
||||
* be set to eg. "[Type Here]" to easier visualize editing of empty labels. The
|
||||
* value is only displayed before the first keystroke and is never used as the
|
||||
* actual editing value.
|
||||
*/
|
||||
emptyLabelText: string = mxClient.IS_FF ? '<br>' : '';
|
||||
|
||||
/**
|
||||
* Variable: escapeCancelsEditing
|
||||
*
|
||||
* If true, pressing the escape key will stop editing and not accept the new
|
||||
* value. Change this to false to accept the new value on escape, and cancel
|
||||
* editing on Shift+Escape instead. Default is true.
|
||||
*/
|
||||
escapeCancelsEditing: boolean = true;
|
||||
|
||||
/**
|
||||
* Variable: textNode
|
||||
*
|
||||
* Reference to the label DOM node that has been hidden.
|
||||
*/
|
||||
textNode: SVGGElement | null = null;
|
||||
|
||||
/**
|
||||
* Variable: zIndex
|
||||
*
|
||||
* Specifies the zIndex for the textarea. Default is 5.
|
||||
*/
|
||||
zIndex: number = 5;
|
||||
|
||||
/**
|
||||
* Variable: minResize
|
||||
*
|
||||
* Defines the minimum width and height to be used in <resize>. Default is 0x20px.
|
||||
*/
|
||||
minResize: mxRectangle = new mxRectangle(0, 20);
|
||||
|
||||
/**
|
||||
* Variable: wordWrapPadding
|
||||
*
|
||||
* Correction factor for word wrapping width. Default is 2 in quirks, 0 in IE
|
||||
* 11 and 1 in all other browsers and modes.
|
||||
*/
|
||||
wordWrapPadding: number = 0;
|
||||
|
||||
/**
|
||||
* Variable: blurEnabled
|
||||
*
|
||||
* If <focusLost> should be called if <textarea> loses the focus. Default is false.
|
||||
*/
|
||||
blurEnabled: boolean = false;
|
||||
|
||||
/**
|
||||
* Variable: initialValue
|
||||
*
|
||||
* Holds the initial editing value to check if the current value was modified.
|
||||
*/
|
||||
initialValue: string | null = null;
|
||||
|
||||
/**
|
||||
* Variable: align
|
||||
*
|
||||
* Holds the current temporary horizontal alignment for the cell style. If this
|
||||
* is modified then the current text alignment is changed and the cell style is
|
||||
* updated when the value is applied.
|
||||
*/
|
||||
align: string | null = null;
|
||||
|
||||
/**
|
||||
* Function: init
|
||||
*
|
||||
|
@ -309,8 +311,8 @@ class mxCellEditor {
|
|||
*
|
||||
* Called in <stopEditing> if cancel is false to invoke <mxGraph.labelChanged>.
|
||||
*/
|
||||
applyValue(state: mxCellState, value): void {
|
||||
this.graph.labelChanged(state.cell, value, this.trigger);
|
||||
applyValue(state: mxCellState, value: any): void {
|
||||
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.
|
||||
*/
|
||||
setAlign(align) {
|
||||
setAlign(align: string): void {
|
||||
if (this.textarea != null) {
|
||||
this.textarea.style.textAlign = align;
|
||||
}
|
||||
|
@ -332,7 +334,7 @@ class mxCellEditor {
|
|||
*
|
||||
* Gets the initial editing value for the given cell.
|
||||
*/
|
||||
getInitialValue(state, trigger) {
|
||||
getInitialValue(state: mxCellState, trigger: mxEventObject | mxMouseEvent) {
|
||||
let result = mxUtils.htmlEntities(
|
||||
this.graph.getEditingValue(state.cell, trigger),
|
||||
false
|
||||
|
@ -347,6 +349,7 @@ class mxCellEditor {
|
|||
* Returns the current editing value.
|
||||
*/
|
||||
getCurrentValue(state: mxCellState) {
|
||||
// @ts-ignore
|
||||
return mxUtils.extractTextWithWhitespace(this.textarea.childNodes);
|
||||
}
|
||||
|
||||
|
@ -356,7 +359,7 @@ class mxCellEditor {
|
|||
* Returns true if <escapeCancelsEditing> is true and shift, control and meta
|
||||
* are not pressed.
|
||||
*/
|
||||
isCancelEditingKeyEvent(evt) {
|
||||
isCancelEditingKeyEvent(evt: KeyboardEvent) {
|
||||
return (
|
||||
this.escapeCancelsEditing ||
|
||||
mxEvent.isShiftDown(evt) ||
|
||||
|
@ -370,31 +373,31 @@ class mxCellEditor {
|
|||
*
|
||||
* Installs listeners for focus, change and standard key event handling.
|
||||
*/
|
||||
installListeners(elt) {
|
||||
installListeners(elt: HTMLElement) {
|
||||
// Applies value if text is dragged
|
||||
// LATER: Gesture mouse events ignored for starting move
|
||||
mxEvent.addListener(
|
||||
elt,
|
||||
'dragstart',
|
||||
mxUtils.bind(this, evt => {
|
||||
(evt: Event) => {
|
||||
this.graph.stopEditing(false);
|
||||
mxEvent.consume(evt);
|
||||
})
|
||||
}
|
||||
);
|
||||
|
||||
// Applies value if focus is lost
|
||||
mxEvent.addListener(
|
||||
elt,
|
||||
'blur',
|
||||
mxUtils.bind(this, evt => {
|
||||
(evt: Event) => {
|
||||
if (this.blurEnabled) {
|
||||
this.focusLost();
|
||||
}
|
||||
})
|
||||
}
|
||||
);
|
||||
|
||||
// Updates modified state and handles placeholder text
|
||||
mxEvent.addListener(elt, 'keydown', evt => {
|
||||
mxEvent.addListener(elt, 'keydown', (evt: KeyboardEvent) => {
|
||||
if (!mxEvent.isConsumed(evt)) {
|
||||
if (this.isStopEditingEvent(evt)) {
|
||||
this.graph.stopEditing(false);
|
||||
|
@ -407,7 +410,7 @@ class mxCellEditor {
|
|||
});
|
||||
|
||||
// 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) {
|
||||
// Clears the initial empty label on the first keystroke
|
||||
// and workaround for FF which fires keypress for delete and backspace
|
||||
|
@ -428,18 +431,20 @@ class mxCellEditor {
|
|||
mxEvent.addListener(elt, 'paste', keypressHandler);
|
||||
|
||||
// Handler for updating the empty label text value after a change
|
||||
const keyupHandler = evt => {
|
||||
const keyupHandler = (evt: KeyboardEvent) => {
|
||||
if (this.editingCell != null) {
|
||||
// Uses an optional text value for sempty labels which is cleared
|
||||
// when the first keystroke appears. This makes it easier to see
|
||||
// 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
|
||||
const textarea = <HTMLElement>this.textarea;
|
||||
|
||||
if (
|
||||
this.textarea.innerHTML.length === 0 ||
|
||||
this.textarea.innerHTML === '<br>'
|
||||
textarea.innerHTML.length === 0 ||
|
||||
textarea.innerHTML === '<br>'
|
||||
) {
|
||||
this.textarea.innerHTML = this.getEmptyLabelText();
|
||||
this.clearOnChange = this.textarea.innerHTML.length > 0;
|
||||
textarea.innerHTML = this.getEmptyLabelText();
|
||||
this.clearOnChange = textarea.innerHTML.length > 0;
|
||||
} else {
|
||||
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
|
||||
const evtName = 'input';
|
||||
|
||||
const resizeHandler = mxUtils.bind(this, evt => {
|
||||
const resizeHandler = (evt: Event) => {
|
||||
if (
|
||||
this.editingCell != null &&
|
||||
this.autoSize &&
|
||||
|
@ -470,7 +475,7 @@ class mxCellEditor {
|
|||
this.resize();
|
||||
}, 0);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
mxEvent.addListener(elt, evtName, resizeHandler);
|
||||
mxEvent.addListener(window, 'resize', resizeHandler);
|
||||
|
@ -485,7 +490,7 @@ class mxCellEditor {
|
|||
* returns true if F2 is pressed of if <mxGraph.enterStopsCellEditing> is true
|
||||
* and enter is pressed without control or shift.
|
||||
*/
|
||||
isStopEditingEvent(evt) {
|
||||
isStopEditingEvent(evt: KeyboardEvent) {
|
||||
return (
|
||||
evt.keyCode === 113 /* F2 */ ||
|
||||
(this.graph.isEnterStopsCellEditing() &&
|
||||
|
@ -500,7 +505,7 @@ class mxCellEditor {
|
|||
*
|
||||
* 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;
|
||||
}
|
||||
|
||||
|
@ -519,7 +524,7 @@ class mxCellEditor {
|
|||
|
||||
if (!this.autoSize || state.style.overflow === 'fill') {
|
||||
// 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.bounds.width / scale
|
||||
)}px`;
|
||||
|
@ -621,6 +626,7 @@ class mxCellEditor {
|
|||
!state.view.graph.cellRenderer.legacySpacing ||
|
||||
state.style[mxConstants.STYLE_OVERFLOW] !== 'width'
|
||||
) {
|
||||
// @ts-ignore
|
||||
const dummy = new mxText(); // FIXME!!!! ===================================================================================================
|
||||
const spacing = parseInt(state.style.spacing || 2) * scale;
|
||||
const spacingTop =
|
||||
|
@ -758,7 +764,7 @@ class mxCellEditor {
|
|||
* Returns the background color for the in-place editor. This implementation
|
||||
* always returns null.
|
||||
*/
|
||||
getBackgroundColor(state): string | null {
|
||||
getBackgroundColor(state: mxCellState): string | null {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -772,7 +778,7 @@ class mxCellEditor {
|
|||
* cell - <mxCell> to start editing.
|
||||
* 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.align = null;
|
||||
|
||||
|
@ -819,19 +825,20 @@ class mxCellEditor {
|
|||
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`
|
||||
: String(mxConstants.LINE_HEIGHT);
|
||||
this.textarea.style.backgroundColor = this.getBackgroundColor(state);
|
||||
this.textarea.style.textDecoration = txtDecor.join(' ');
|
||||
this.textarea.style.fontWeight = bold ? 'bold' : 'normal';
|
||||
this.textarea.style.fontStyle = italic ? 'italic' : '';
|
||||
this.textarea.style.fontSize = `${Math.round(size)}px`;
|
||||
this.textarea.style.zIndex = String(this.zIndex);
|
||||
this.textarea.style.fontFamily = family;
|
||||
this.textarea.style.textAlign = align;
|
||||
this.textarea.style.outline = 'none';
|
||||
this.textarea.style.color = color;
|
||||
textarea.style.backgroundColor = this.getBackgroundColor(state) || 'transparent';
|
||||
textarea.style.textDecoration = txtDecor.join(' ');
|
||||
textarea.style.fontWeight = bold ? 'bold' : 'normal';
|
||||
textarea.style.fontStyle = italic ? 'italic' : '';
|
||||
textarea.style.fontSize = `${Math.round(size)}px`;
|
||||
textarea.style.zIndex = String(this.zIndex);
|
||||
textarea.style.fontFamily = family;
|
||||
textarea.style.textAlign = align;
|
||||
textarea.style.outline = 'none';
|
||||
textarea.style.color = color;
|
||||
|
||||
let dir = (this.textDirection =
|
||||
state.style.textDirection != null
|
||||
|
@ -849,30 +856,31 @@ class mxCellEditor {
|
|||
}
|
||||
|
||||
if (dir === 'ltr' || dir === 'rtl') {
|
||||
this.textarea.setAttribute('dir', dir);
|
||||
textarea.setAttribute('dir', dir);
|
||||
} else {
|
||||
this.textarea.removeAttribute('dir');
|
||||
textarea.removeAttribute('dir');
|
||||
}
|
||||
|
||||
// Sets the initial editing value
|
||||
this.textarea.innerHTML = this.getInitialValue(state, trigger) || '';
|
||||
this.initialValue = this.textarea.innerHTML;
|
||||
textarea.innerHTML = this.getInitialValue(state, <mxMouseEvent>trigger) || '';
|
||||
this.initialValue = textarea.innerHTML;
|
||||
|
||||
// Uses an optional text value for empty labels which is cleared
|
||||
// when the first keystroke appears. This makes it easier to see
|
||||
// that a label is being edited even if the label is empty.
|
||||
if (
|
||||
this.textarea.innerHTML.length === 0 ||
|
||||
this.textarea.innerHTML === '<br>'
|
||||
textarea.innerHTML.length === 0 ||
|
||||
textarea.innerHTML === '<br>'
|
||||
) {
|
||||
this.textarea.innerHTML = this.getEmptyLabelText();
|
||||
textarea.innerHTML = <string>this.getEmptyLabelText();
|
||||
this.clearOnChange = true;
|
||||
} else {
|
||||
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
|
||||
this.editingCell = cell;
|
||||
|
@ -880,13 +888,14 @@ class mxCellEditor {
|
|||
this.textNode = null;
|
||||
|
||||
if (state.text != null && this.isHideLabel(state)) {
|
||||
this.textNode = state.text.node;
|
||||
this.textNode = <SVGGElement>state.text.node;
|
||||
this.textNode.style.visibility = 'hidden';
|
||||
}
|
||||
|
||||
// Workaround for initial offsetHeight not ready for heading in markup
|
||||
if (
|
||||
this.autoSize &&
|
||||
// @ts-ignore
|
||||
(this.graph.model.isEdge(state.cell) ||
|
||||
state.style[mxConstants.STYLE_OVERFLOW] !== 'fill')
|
||||
) {
|
||||
|
@ -903,15 +912,15 @@ class mxCellEditor {
|
|||
// Workaround for NS_ERROR_FAILURE in FF
|
||||
try {
|
||||
// Prefers blinking cursor over no selected text if empty
|
||||
this.textarea.focus();
|
||||
textarea.focus();
|
||||
|
||||
if (
|
||||
this.isSelectText() &&
|
||||
this.textarea.innerHTML.length > 0 &&
|
||||
(this.textarea.innerHTML !== this.getEmptyLabelText() ||
|
||||
textarea.innerHTML.length > 0 &&
|
||||
(textarea.innerHTML !== this.getEmptyLabelText() ||
|
||||
!this.clearOnChange)
|
||||
) {
|
||||
document.execCommand('selectAll', false, null);
|
||||
document.execCommand('selectAll', false);
|
||||
}
|
||||
} catch (e) {
|
||||
// ignore
|
||||
|
@ -958,30 +967,31 @@ class mxCellEditor {
|
|||
}
|
||||
|
||||
const state = !cancel ? this.graph.view.getState(this.editingCell) : null;
|
||||
const textarea = <HTMLElement>this.textarea;
|
||||
|
||||
const initial = this.initialValue;
|
||||
this.initialValue = null;
|
||||
this.editingCell = null;
|
||||
this.trigger = null;
|
||||
this.bounds = null;
|
||||
this.textarea.blur();
|
||||
textarea.blur();
|
||||
this.clearSelection();
|
||||
|
||||
if (this.textarea.parentNode != null) {
|
||||
this.textarea.parentNode.removeChild(this.textarea);
|
||||
if (textarea.parentNode != null) {
|
||||
textarea.parentNode.removeChild(textarea);
|
||||
}
|
||||
|
||||
if (
|
||||
this.clearOnChange &&
|
||||
this.textarea.innerHTML === this.getEmptyLabelText()
|
||||
textarea.innerHTML === this.getEmptyLabelText()
|
||||
) {
|
||||
this.textarea.innerHTML = '';
|
||||
textarea.innerHTML = '';
|
||||
this.clearOnChange = false;
|
||||
}
|
||||
|
||||
if (
|
||||
state != null &&
|
||||
(this.textarea.innerHTML !== initial || this.align != null)
|
||||
(textarea.innerHTML !== initial || this.align != null)
|
||||
) {
|
||||
this.prepareTextarea();
|
||||
const value = this.getCurrentValue(state);
|
||||
|
@ -1016,11 +1026,12 @@ class mxCellEditor {
|
|||
* This implementation removes the extra trailing linefeed in Firefox.
|
||||
*/
|
||||
prepareTextarea(): void {
|
||||
const textarea = <HTMLElement>this.textarea;
|
||||
if (
|
||||
this.textarea.lastChild != null &&
|
||||
this.textarea.lastChild.nodeName === 'BR'
|
||||
textarea.lastChild != null &&
|
||||
textarea.lastChild.nodeName === 'BR'
|
||||
) {
|
||||
this.textarea.removeChild(this.textarea.lastChild);
|
||||
textarea.removeChild(textarea.lastChild);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1041,12 +1052,13 @@ class mxCellEditor {
|
|||
*/
|
||||
getMinimumSize(state: mxCellState): mxRectangle {
|
||||
const { scale } = this.graph.getView();
|
||||
const textarea = <HTMLElement>this.textarea;
|
||||
|
||||
return new mxRectangle(
|
||||
0,
|
||||
0,
|
||||
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.style[mxConstants.STYLE_OVERFLOW] === 'fill'
|
||||
) {
|
||||
result = state.shape.getLabelBounds(mxRectangle.fromRectangle(state));
|
||||
result = (<mxShape>state.shape).getLabelBounds(mxRectangle.fromRectangle(state));
|
||||
} else {
|
||||
// @ts-ignore
|
||||
const dummy = new mxText(); // FIXME!!!! ===================================================================================================
|
||||
const spacing: number = parseInt(state.style.spacing || 0) * scale;
|
||||
const spacingTop: number =
|
||||
|
@ -1146,7 +1159,7 @@ class mxCellEditor {
|
|||
|
||||
// Applies the horizontal and vertical label positions
|
||||
if (this.graph.getModel().isVertex(state.cell)) {
|
||||
const horizontal: string = mxUtils.getStringValue(
|
||||
const horizontal: string = <string>mxUtils.getStringValue(
|
||||
state.style,
|
||||
mxConstants.STYLE_LABEL_POSITION,
|
||||
mxConstants.ALIGN_CENTER
|
||||
|
@ -1191,8 +1204,8 @@ class mxCellEditor {
|
|||
* cell - <mxCell> for which a text for an empty editing box should be
|
||||
* returned.
|
||||
*/
|
||||
getEmptyLabelText(cell: mxCell | null = null): string | null {
|
||||
return this.emptyLabelText;
|
||||
getEmptyLabelText(cell: mxCell | null = null): string {
|
||||
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
|
||||
* current scale.
|
||||
*/
|
||||
offset: mxPoint | null = null;
|
||||
offset: mxPoint = new mxPoint();
|
||||
|
||||
/**
|
||||
* Variable: cursor
|
||||
|
@ -172,11 +172,12 @@ class mxCellOverlay extends mxEventSource {
|
|||
const s = state.view.scale;
|
||||
let pt = null;
|
||||
|
||||
const w = this.image.width;
|
||||
const h = this.image.height;
|
||||
const image = <mxImage>this.image;
|
||||
const w = image.width;
|
||||
const h = image.height;
|
||||
|
||||
if (isEdge) {
|
||||
const pts = state.absolutePoints;
|
||||
const pts = <mxPoint[]>state.absolutePoints;
|
||||
|
||||
if (pts.length % 2 === 1) {
|
||||
pt = pts[Math.floor(pts.length / 2)];
|
||||
|
|
|
@ -33,6 +33,7 @@ import mxPoint from '../../util/datatypes/mxPoint';
|
|||
import mxShape from '../../shape/mxShape';
|
||||
import mxCellState from '../../util/datatypes/mxCellState';
|
||||
import mxCell from './mxCell';
|
||||
import mxGraphModel from "../graph/mxGraphModel";
|
||||
|
||||
class mxCellRenderer {
|
||||
/**
|
||||
|
@ -56,7 +57,7 @@ class mxCellRenderer {
|
|||
*
|
||||
* Defines the default shape for vertices. Default is <mxRectangleShape>.
|
||||
*/
|
||||
defaultVertexShape: typeof mxShape = mxRectangleShape;
|
||||
defaultVertexShape: typeof mxRectangleShape = mxRectangleShape;
|
||||
|
||||
/**
|
||||
* Variable: defaultTextShape
|
||||
|
@ -1532,7 +1533,8 @@ class mxCellRenderer {
|
|||
force: boolean = false,
|
||||
rendering: boolean = true
|
||||
): boolean {
|
||||
const { model } = state.view.graph;
|
||||
const model = <mxGraphModel>state.view.graph.model;
|
||||
|
||||
let shapeChanged = false;
|
||||
|
||||
// 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 mxCell from './mxCell';
|
||||
import mxGraph from '../graph/mxGraph';
|
||||
import mxGraphView from "../graph/mxGraphView";
|
||||
|
||||
class mxCellStatePreview {
|
||||
/**
|
||||
|
@ -17,14 +18,14 @@ class mxCellStatePreview {
|
|||
*
|
||||
* Reference to the enclosing <mxGraph>.
|
||||
*/
|
||||
graph: mxGraph | null = null;
|
||||
graph: mxGraph;
|
||||
|
||||
/**
|
||||
* Variable: deltas
|
||||
*
|
||||
* Reference to the enclosing <mxGraph>.
|
||||
*/
|
||||
deltas: mxDictionary | null = null;
|
||||
deltas: mxDictionary;
|
||||
|
||||
/**
|
||||
* Variable: count
|
||||
|
@ -96,22 +97,18 @@ class mxCellStatePreview {
|
|||
* Function: show
|
||||
*/
|
||||
show(visitor: Function | null = null) {
|
||||
this.deltas.visit(
|
||||
mxUtils.bind(this, (key, delta) => {
|
||||
this.deltas.visit((key: string, delta: any) => {
|
||||
this.translateState(delta.state, delta.point.x, delta.point.y);
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
this.deltas.visit(
|
||||
mxUtils.bind(this, (key, delta) => {
|
||||
this.deltas.visit((key: string, delta: any) => {
|
||||
this.revalidateState(
|
||||
delta.state,
|
||||
delta.point.x,
|
||||
delta.point.y,
|
||||
visitor
|
||||
);
|
||||
})
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -122,7 +119,7 @@ class mxCellStatePreview {
|
|||
const model = this.graph.getModel();
|
||||
|
||||
if (model.isVertex(state.cell)) {
|
||||
state.view.updateCellState(state);
|
||||
(<mxGraphView>state.view).updateCellState(state);
|
||||
const geo = model.getGeometry(state.cell);
|
||||
|
||||
// Moves selection cells and non-relative vertices in
|
||||
|
@ -142,7 +139,7 @@ class mxCellStatePreview {
|
|||
|
||||
for (let i = 0; i < childCount; i += 1) {
|
||||
this.translateState(
|
||||
state.view.getState(model.getChildAt(state.cell, i)),
|
||||
<mxCellState>(state.view).getState(model.getChildAt(state.cell, i)),
|
||||
dx,
|
||||
dy
|
||||
);
|
||||
|
@ -168,8 +165,8 @@ class mxCellStatePreview {
|
|||
state.view.updateCellState(state);
|
||||
}
|
||||
|
||||
const geo = this.graph.getCellGeometry(state.cell);
|
||||
const pState = state.view.getState(model.getParent(state.cell));
|
||||
const geo = this.graph.getCellGeometry(<mxCell>state.cell);
|
||||
const pState = state.view.getState(model.getParent(<mxCell>state.cell));
|
||||
|
||||
// Moves selection vertices which are relative
|
||||
if (
|
||||
|
@ -210,10 +207,10 @@ class mxCellStatePreview {
|
|||
*/
|
||||
addEdges(state: mxCellState): void {
|
||||
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) {
|
||||
const s = state.view.getState(model.getEdgeAt(state.cell, i));
|
||||
const s = state.view.getState(model.getEdgeAt(<mxCell>state.cell, i));
|
||||
|
||||
if (s != null) {
|
||||
this.moveState(s, 0, 0);
|
||||
|
|
|
@ -63,7 +63,7 @@ class mxConnectionConstraint {
|
|||
*/
|
||||
constructor(point: mxPoint | null=null,
|
||||
perimeter: boolean=true,
|
||||
name: string='',
|
||||
name: string | null=null,
|
||||
dx: number | null=null,
|
||||
dy: number | null=null) {
|
||||
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.
|
||||
*/
|
||||
getTerminal(edge: mxCell | null,
|
||||
isSource: boolean=false) {
|
||||
isSource: boolean=false): mxCell | null {
|
||||
return edge != null ? edge.getTerminal(isSource) : null;
|
||||
}
|
||||
|
||||
|
@ -1623,7 +1623,7 @@ class mxGraphModel extends mxEventSource {
|
|||
*
|
||||
* cell - <mxCell> whose style should be returned.
|
||||
*/
|
||||
getStyle(cell: mxCell): any {
|
||||
getStyle(cell: mxCell | null): any {
|
||||
return cell != null ? cell.getStyle() : null;
|
||||
}
|
||||
|
||||
|
@ -1730,7 +1730,7 @@ class mxGraphModel extends mxEventSource {
|
|||
*
|
||||
* cell - <mxCell> whose visible state should be returned.
|
||||
*/
|
||||
isVisible(cell: mxCell): boolean {
|
||||
isVisible(cell: mxCell | null): boolean {
|
||||
return cell != null ? cell.isVisible() : false;
|
||||
}
|
||||
|
||||
|
|
|
@ -161,7 +161,7 @@ class mxGraphSelectionModel extends mxEventSource {
|
|||
*
|
||||
* cell - <mxCell> to be selected.
|
||||
*/
|
||||
setCell(cell: mxCell): void {
|
||||
setCell(cell: mxCell | null): void {
|
||||
if (cell != null) {
|
||||
this.setCells([cell]);
|
||||
}
|
||||
|
|
|
@ -101,7 +101,7 @@ class mxGraphView extends mxEventSource {
|
|||
*
|
||||
* Returns reference to the enclosing <mxGraph>.
|
||||
*/
|
||||
graph: mxGraph | null = null;
|
||||
graph: mxGraph;
|
||||
|
||||
/**
|
||||
* Variable: currentRoot
|
||||
|
@ -1548,9 +1548,9 @@ class mxGraphView extends mxEventSource {
|
|||
* associated.
|
||||
*/
|
||||
isLoopStyleEnabled(edge: mxCellState,
|
||||
points: mxPoint[],
|
||||
source: mxCellState,
|
||||
target: mxCellState): boolean {
|
||||
points: mxPoint[]=[],
|
||||
source: mxCellState | null=null,
|
||||
target: mxCellState | null=null): boolean {
|
||||
|
||||
const sc = (<mxGraph>this.graph).getConnectionConstraint(edge, source, true);
|
||||
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.
|
||||
*/
|
||||
getEdgeStyle(edge: mxCellState,
|
||||
points: mxPoint[],
|
||||
source: mxCellState,
|
||||
target: mxCellState): any {
|
||||
points: mxPoint[]=[],
|
||||
source: mxCellState | null=null,
|
||||
target: mxCellState | null=null): any {
|
||||
|
||||
let edgeStyle: any = this.isLoopStyleEnabled(edge, points, source, target)
|
||||
? mxUtils.getValue(
|
||||
|
@ -1970,7 +1970,7 @@ class mxGraphView extends mxEventSource {
|
|||
* source - Boolean that specifies if the source or target terminal
|
||||
* should be returned.
|
||||
*/
|
||||
getVisibleTerminal(edge: mxCell,
|
||||
getVisibleTerminal(edge: mxCell | null,
|
||||
source: boolean) {
|
||||
|
||||
const model = (<mxGraph>this.graph).getModel();
|
||||
|
|
|
@ -13,10 +13,175 @@ import mxGraph from './mxGraph';
|
|||
import mxImageShape from '../../shape/node/mxImageShape';
|
||||
import mxEvent from '../../util/event/mxEvent';
|
||||
import mxUtils from '../../util/mxUtils';
|
||||
import mxClient from '../../mxClient';
|
||||
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 {
|
||||
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!!
|
||||
sizer: mxRectangleShape | null=null;
|
||||
|
||||
|
@ -32,7 +197,7 @@ class mxOutline {
|
|||
|
||||
bounds: mxRectangle | null=null;
|
||||
|
||||
zoom: number | null=null;
|
||||
zoom: boolean=false;
|
||||
|
||||
startX: number | null=null;
|
||||
|
||||
|
@ -56,17 +221,14 @@ class mxOutline {
|
|||
*
|
||||
* Reference to the <mxGraph> that renders the outline.
|
||||
*/
|
||||
outline: mxGraph;
|
||||
outline: mxGraph | null=null;
|
||||
|
||||
/**
|
||||
* 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:
|
||||
| mxConstants.RENDERING_HINT_EXACT
|
||||
| mxConstants.RENDERING_HINT_FASTER
|
||||
| mxConstants.RENDERING_HINT_FASTEST = mxConstants.RENDERING_HINT_FASTER;
|
||||
graphRenderHint: string = 'exact';
|
||||
|
||||
/**
|
||||
* Variable: enabled
|
||||
|
@ -143,54 +305,6 @@ class mxOutline {
|
|||
*/
|
||||
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
|
||||
*
|
||||
|
@ -208,118 +322,6 @@ class mxOutline {
|
|||
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
|
||||
*
|
||||
|
@ -355,6 +357,7 @@ class mxOutline {
|
|||
* value - Boolean that specifies the new enabled state.
|
||||
*/
|
||||
setZoomEnabled(value: boolean): void {
|
||||
// @ts-ignore
|
||||
this.sizer.node.style.visibility = value ? 'visible' : 'hidden';
|
||||
}
|
||||
|
||||
|
@ -373,12 +376,13 @@ class mxOutline {
|
|||
* Creates the shape used as the sizer.
|
||||
*/
|
||||
createSizer(): mxRectangleShape {
|
||||
const outline = <mxGraph>this.outline;
|
||||
if (this.sizerImage != null) {
|
||||
const sizer = new mxImageShape(
|
||||
new mxRectangle(0, 0, this.sizerImage.width, this.sizerImage.height),
|
||||
this.sizerImage.src
|
||||
);
|
||||
sizer.dialect = this.outline.dialect;
|
||||
sizer.dialect = outline.dialect;
|
||||
return sizer;
|
||||
}
|
||||
|
||||
|
@ -387,7 +391,7 @@ class mxOutline {
|
|||
mxConstants.OUTLINE_HANDLE_FILLCOLOR,
|
||||
mxConstants.OUTLINE_HANDLE_STROKECOLOR
|
||||
);
|
||||
sizer.dialect = this.outline.dialect;
|
||||
sizer.dialect = outline.dialect;
|
||||
return sizer;
|
||||
}
|
||||
|
||||
|
@ -400,8 +404,8 @@ class mxOutline {
|
|||
return new mxRectangle(
|
||||
0,
|
||||
0,
|
||||
this.source.container.scrollWidth,
|
||||
this.source.container.scrollHeight
|
||||
(<HTMLElement>this.source.container).scrollWidth,
|
||||
(<HTMLElement>this.source.container).scrollHeight
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -410,7 +414,7 @@ class mxOutline {
|
|||
*
|
||||
* Returns the offset for drawing the outline graph.
|
||||
*/
|
||||
getOutlineOffset(scale) {
|
||||
getOutlineOffset(scale: number): mxPoint | null { // TODO: Should number -> mxPoint?
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -533,7 +537,8 @@ class mxOutline {
|
|||
this.bounds.y +=
|
||||
(this.source.container.scrollTop * navView.scale) / scale;
|
||||
|
||||
let b = this.selectionBorder.bounds;
|
||||
const selectionBorder = <mxRectangleShape>this.selectionBorder;
|
||||
let b = <mxRectangle>selectionBorder.bounds;
|
||||
|
||||
if (
|
||||
b.x !== this.bounds.x ||
|
||||
|
@ -541,12 +546,13 @@ class mxOutline {
|
|||
b.width !== this.bounds.width ||
|
||||
b.height !== this.bounds.height
|
||||
) {
|
||||
this.selectionBorder.bounds = this.bounds;
|
||||
this.selectionBorder.redraw();
|
||||
selectionBorder.bounds = this.bounds;
|
||||
selectionBorder.redraw();
|
||||
}
|
||||
|
||||
// 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(
|
||||
this.bounds.x + this.bounds.width - b.width / 2,
|
||||
this.bounds.y + this.bounds.height - b.height / 2,
|
||||
|
@ -560,11 +566,11 @@ class mxOutline {
|
|||
b.width !== b2.width ||
|
||||
b.height !== b2.height
|
||||
) {
|
||||
this.sizer.bounds = b2;
|
||||
sizer.bounds = b2;
|
||||
|
||||
// Avoids update of visibility in redraw for VML
|
||||
if (this.sizer.node.style.visibility !== 'hidden') {
|
||||
this.sizer.redraw();
|
||||
if ((<SVGGElement>sizer.node).style.visibility !== 'hidden') {
|
||||
sizer.redraw();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -580,7 +586,7 @@ class mxOutline {
|
|||
*
|
||||
* Handles the event by starting a translation or zoom.
|
||||
*/
|
||||
mouseDown(sender, me) {
|
||||
mouseDown(sender: any, me: mxMouseEvent) {
|
||||
if (this.enabled && this.showViewport) {
|
||||
const tol = !mxEvent.isMouseEvent(me.getEvent())
|
||||
? this.source.tolerance
|
||||
|
@ -600,13 +606,14 @@ class mxOutline {
|
|||
this.startX = me.getX();
|
||||
this.startY = me.getY();
|
||||
this.active = true;
|
||||
const sourceContainer = <HTMLElement>this.source.container;
|
||||
|
||||
if (
|
||||
this.source.useScrollbarsForPanning &&
|
||||
mxUtils.hasScrollbars(this.source.container)
|
||||
) {
|
||||
this.dx0 = this.source.container.scrollLeft;
|
||||
this.dy0 = this.source.container.scrollTop;
|
||||
this.dx0 = sourceContainer.scrollLeft;
|
||||
this.dy0 = sourceContainer.scrollTop;
|
||||
} else {
|
||||
this.dx0 = 0;
|
||||
this.dy0 = 0;
|
||||
|
@ -622,10 +629,18 @@ class mxOutline {
|
|||
* Handles the event by previewing the viewrect in <graph> and updating the
|
||||
* rectangle that represents the viewrect in the outline.
|
||||
*/
|
||||
mouseMove(sender, me) {
|
||||
mouseMove(sender: any, me: mxMouseEvent) {
|
||||
if (this.active) {
|
||||
this.selectionBorder.node.style.display = this.showViewport ? '' : 'none';
|
||||
this.sizer.node.style.display = this.selectionBorder.node.style.display;
|
||||
const myBounds = <mxRectangle>this.bounds;
|
||||
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);
|
||||
let dx = delta.x;
|
||||
|
@ -634,38 +649,39 @@ class mxOutline {
|
|||
|
||||
if (!this.zoom) {
|
||||
// Previews the panning on the source graph
|
||||
const { scale } = this.outline.getView();
|
||||
const { scale } = outline.getView();
|
||||
bounds = new mxRectangle(
|
||||
this.bounds.x + dx,
|
||||
this.bounds.y + dy,
|
||||
this.bounds.width,
|
||||
this.bounds.height
|
||||
myBounds.x + dx,
|
||||
myBounds.y + dy,
|
||||
myBounds.width,
|
||||
myBounds.height
|
||||
);
|
||||
this.selectionBorder.bounds = bounds;
|
||||
this.selectionBorder.redraw();
|
||||
selectionBorder.bounds = bounds;
|
||||
selectionBorder.redraw();
|
||||
dx /= scale;
|
||||
dx *= this.source.getView().scale;
|
||||
dx *= source.getView().scale;
|
||||
dy /= scale;
|
||||
dy *= this.source.getView().scale;
|
||||
this.source.panGraph(-dx - this.dx0, -dy - this.dy0);
|
||||
dy *= source.getView().scale;
|
||||
source.panGraph(-dx - <number>this.dx0, -dy - <number>this.dy0);
|
||||
} else {
|
||||
// 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;
|
||||
dy = dx / viewRatio;
|
||||
bounds = new mxRectangle(
|
||||
this.bounds.x,
|
||||
this.bounds.y,
|
||||
Math.max(1, this.bounds.width + dx),
|
||||
Math.max(1, this.bounds.height + dy)
|
||||
myBounds.x,
|
||||
myBounds.y,
|
||||
Math.max(1, myBounds.width + dx),
|
||||
Math.max(1, myBounds.height + dy)
|
||||
);
|
||||
this.selectionBorder.bounds = bounds;
|
||||
this.selectionBorder.redraw();
|
||||
selectionBorder.bounds = bounds;
|
||||
selectionBorder.redraw();
|
||||
}
|
||||
|
||||
// Updates the zoom handle
|
||||
const b = this.sizer.bounds;
|
||||
this.sizer.bounds = new mxRectangle(
|
||||
const b = <mxRectangle>sizer.bounds;
|
||||
sizer.bounds = new mxRectangle(
|
||||
bounds.x + bounds.width - b.width / 2,
|
||||
bounds.y + bounds.height - b.height / 2,
|
||||
b.width,
|
||||
|
@ -673,10 +689,9 @@ class mxOutline {
|
|||
);
|
||||
|
||||
// Avoids update of visibility in redraw for VML
|
||||
if (this.sizer.node.style.visibility !== 'hidden') {
|
||||
this.sizer.redraw();
|
||||
if (sizerNode.style.visibility !== 'hidden') {
|
||||
sizer.redraw();
|
||||
}
|
||||
|
||||
me.consume();
|
||||
}
|
||||
}
|
||||
|
@ -703,8 +718,8 @@ class mxOutline {
|
|||
* };
|
||||
* (end)
|
||||
*/
|
||||
getTranslateForEvent(me) {
|
||||
return new mxPoint(me.getX() - this.startX, me.getY() - this.startY);
|
||||
getTranslateForEvent(me: mxMouseEvent) {
|
||||
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>.
|
||||
*/
|
||||
mouseUp(sender, me) {
|
||||
mouseUp(sender: any, me: mxMouseEvent) {
|
||||
if (this.active) {
|
||||
const delta = this.getTranslateForEvent(me);
|
||||
let dx = delta.x;
|
||||
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 (!this.zoom) {
|
||||
// Applies the new translation if the source
|
||||
// has no scrollbars
|
||||
if (
|
||||
!this.source.useScrollbarsForPanning ||
|
||||
!mxUtils.hasScrollbars(this.source.container)
|
||||
!source.useScrollbarsForPanning ||
|
||||
!mxUtils.hasScrollbars(source.container)
|
||||
) {
|
||||
this.source.panGraph(0, 0);
|
||||
dx /= this.outline.getView().scale;
|
||||
dy /= this.outline.getView().scale;
|
||||
const t = this.source.getView().translate;
|
||||
this.source.getView().setTranslate(t.x - dx, t.y - dy);
|
||||
source.panGraph(0, 0);
|
||||
dx /= outline.getView().scale;
|
||||
dy /= outline.getView().scale;
|
||||
const t = source.getView().translate;
|
||||
source.getView().setTranslate(t.x - dx, t.y - dy);
|
||||
}
|
||||
} else {
|
||||
// Applies the new zoom
|
||||
const w = this.selectionBorder.bounds.width;
|
||||
const { scale } = this.source.getView();
|
||||
this.source.zoomTo(
|
||||
const w = (<mxRectangle>selectionBorder.bounds).width;
|
||||
const { scale } = source.getView();
|
||||
source.zoomTo(
|
||||
Math.max(this.minScale, scale - (dx * scale) / w),
|
||||
false
|
||||
);
|
||||
|
@ -768,6 +786,7 @@ class mxOutline {
|
|||
'scroll',
|
||||
this.updateHandler
|
||||
);
|
||||
// @ts-ignore
|
||||
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