- Converted Graph* classes into mixins.

- Created MaxGraph type to expose.
- CellStateStyles is now more concrete.
- More compiler errors are resolved.
development
Junsik Shim 2021-08-01 11:21:57 +09:00
parent 49b307a557
commit bc400a3ae3
40 changed files with 3153 additions and 3220 deletions

View File

@ -1,5 +1,9 @@
import type Cell from './view/cell/datatypes/Cell';
import Shape from './view/geometry/shape/Shape';
import CellState from './view/cell/datatypes/CellState';
import RectangleShape from './view/geometry/shape/node/RectangleShape';
import type Shape from './view/geometry/shape/Shape';
import type Graph from './view/Graph';
import ImageBox from './view/image/ImageBox';
export type CellMap = {
[id: string]: Cell;
@ -27,20 +31,34 @@ export type CellStateStyles = {
absoluteArcSize: number;
align: AlignValue;
arcSize: number;
aspect: string;
autosize: boolean;
backgroundColor: ColorValue;
backgroundOutline: number;
bendable: boolean;
cloneable: boolean;
curved: boolean;
dashed: boolean;
dashPattern: string;
defaultEdge: CellStateStyles;
defaultVertex: CellStateStyles;
deletable: boolean;
direction: DirectionValue;
edge: string;
editable: boolean;
endArrow: ArrowType;
endFill: boolean;
endSize: number;
entryX: number;
entryY: number;
exitX: number;
exitY: number;
fillColor: ColorValue;
fillOpacity: number;
fixDash: boolean;
flipH: boolean;
flipV: boolean;
foldable: boolean;
fontColor: ColorValue;
fontFamily: string;
fontSize: number;
@ -63,13 +81,28 @@ export type CellStateStyles = {
indicatorWidth: number;
labelBorderColor: ColorValue;
labelPosition: AlignValue;
loop: Function;
margin: number;
movable: boolean;
noEdgeStyle: boolean;
opacity: number;
overflow: OverflowValue;
perimeter: Function | string | null;
perimeterSpacing: number;
pointerEvents: boolean;
resizeable: boolean;
resizeHeight: boolean;
resizeWidth: boolean;
rotatable: boolean;
rotation: number;
rounded: boolean;
routingCenterX: number;
routingCenterY: number;
separatorColor: ColorValue;
shadow: boolean;
shape: ShapeValue;
sourcePerimeterSpacing: number;
sourcePort: string;
spacing: number;
spacingBottom: number;
spacingLeft: number;
@ -83,10 +116,13 @@ export type CellStateStyles = {
strokeWidth: number;
swimlaneFillColor: ColorValue;
swimlaneLine: boolean;
targetPerimeterSpacing: number;
targetPort: string;
textDirection: TextDirectionValue;
textOpacity: number;
verticalAlign: VAlignValue;
verticalLabelPosition: VAlignValue;
whiteSpace: WhiteSpaceValue;
};
export type ColorValue = string;
@ -95,6 +131,7 @@ export type TextDirectionValue = '' | 'ltr' | 'rtl' | 'auto';
export type AlignValue = 'left' | 'center' | 'right';
export type VAlignValue = 'top' | 'middle' | 'bottom';
export type OverflowValue = 'fill' | 'width' | 'auto' | 'hidden' | 'scroll' | 'visible';
export type WhiteSpaceValue = 'normal' | 'wrap' | 'nowrap' | 'pre';
export type ArrowType =
| 'none'
| 'classic'
@ -106,6 +143,23 @@ export type ArrowType =
| 'oval'
| 'diamond'
| 'diamondThin';
export type ShapeValue =
| 'rectangle'
| 'ellipse'
| 'doubleEllipse'
| 'rhombus'
| 'line'
| 'image'
| 'arrow'
| 'arrowConnector'
| 'label'
| 'cylinder'
| 'swimlane'
| 'connector'
| 'actor'
| 'cloud'
| 'triangle'
| 'hexagon';
export type CanvasState = {
dx: number;
@ -151,3 +205,37 @@ export interface Gradient extends SVGLinearGradientElement {
export type GradientMap = {
[k: string]: Gradient;
};
export interface GraphPlugin {
onInit: (graph: Graph) => void;
onDestroy: () => void;
}
// Events
export type Listener = {
name: string;
f: EventListener;
};
export type ListenerTarget = {
mxListenerList?: Listener[];
};
export type Listenable = (Node | Window) & ListenerTarget;
export type GestureEvent = Event &
MouseEvent & {
scale?: number;
pointerId?: number;
};
export type EventCache = GestureEvent[];
export interface CellHandle {
state: CellState;
cursor: string;
image: ImageBox | null;
shape: Shape | null;
setVisible: (v: boolean) => void;
}

View File

@ -54,7 +54,7 @@ class Dictionary<T, U> {
get(key: T) {
const id = ObjectIdentity.get(key);
return this.map[id];
return this.map[id] ?? null;
}
/**
@ -68,7 +68,7 @@ class Dictionary<T, U> {
const previous = this.map[id];
this.map[id] = value;
return previous;
return previous ?? null;
}
/**
@ -82,7 +82,7 @@ class Dictionary<T, U> {
const previous = this.map[id];
delete this.map[id];
return previous;
return previous ?? null;
}
/**

View File

@ -1257,7 +1257,7 @@ export const getDocumentScrollOrigin = (doc: Document) => {
* included. Default is true.
*/
export const getScrollOrigin = (
node: HTMLElement | null,
node: HTMLElement | null = null,
includeAncestors = false,
includeDocument = true
) => {
@ -1548,7 +1548,7 @@ export const relativeCcw = (
* node - DOM node to set the opacity for.
* value - Opacity in %. Possible values are between 0 and 100.
*/
export const setOpacity = (node: HTMLElement, value: number) => {
export const setOpacity = (node: HTMLElement | SVGElement, value: number) => {
node.style.opacity = String(value / 100);
};
@ -1743,7 +1743,12 @@ export const removeAllStylenames = (style: string) => {
* key - Key of the style to be changed.
* value - New value for the given key.
*/
export const setCellStyles = (model: Model, cells: Cell[], key: string, value: any) => {
export const setCellStyles = (
model: Model,
cells: CellArray,
key: string,
value: any
) => {
if (cells.length > 0) {
model.beginUpdate();
try {
@ -1842,7 +1847,7 @@ export const setStyle = (style: string | null, key: string, value: any) => {
*/
export const setCellStyleFlags = (
model: Model,
cells: Cell[],
cells: CellArray,
key: string,
flag: number,
value: boolean
@ -1985,8 +1990,8 @@ export const getSizeForString = (
text: string,
fontSize = DEFAULT_FONTSIZE,
fontFamily = DEFAULT_FONTFAMILY,
textWidth: number,
fontStyle: number
textWidth: number | null = null,
fontStyle: number | null = null
) => {
const div = document.createElement('div');
@ -1996,7 +2001,7 @@ export const getSizeForString = (
div.style.lineHeight = `${Math.round(fontSize * LINE_HEIGHT)}px`;
// Sets the font style
if (fontStyle != null) {
if (fontStyle !== null) {
if ((fontStyle & FONT_BOLD) === FONT_BOLD) {
div.style.fontWeight = 'bold';
}
@ -2025,7 +2030,7 @@ export const getSizeForString = (
div.style.visibility = 'hidden';
div.style.display = 'inline-block';
if (textWidth != null) {
if (textWidth !== null) {
div.style.width = `${textWidth}px`;
div.style.whiteSpace = 'normal';
} else {
@ -2342,4 +2347,19 @@ export const isNullish = (v: string | object | null | undefined | number) =>
export const isNotNullish = (v: string | object | null | undefined | number) =>
!isNullish(v);
// Mixins support
export const applyMixins = (derivedCtor: any, constructors: any[]) => {
constructors.forEach((baseCtor) => {
Object.getOwnPropertyNames(baseCtor.prototype).forEach((name) => {
Object.defineProperty(
derivedCtor.prototype,
name,
Object.getOwnPropertyDescriptor(baseCtor.prototype, name) || Object.create(null)
);
});
});
};
export const autoImplement = <T>(): new () => T => class {} as any;
export default utils;

View File

@ -17,12 +17,13 @@ import ConnectionHandler from './connection/ConnectionHandler';
import GraphHandler from './GraphHandler';
import PanningHandler from './panning/PanningHandler';
import PopupMenuHandler from './popups_menus/PopupMenuHandler';
import mxGraphSelectionModel from './selection/mxGraphSelectionModel';
import GraphView from './view/GraphView';
import CellRenderer from './cell/CellRenderer';
import CellEditor from './editing/CellEditor';
import Point from './geometry/Point';
import {
applyMixins,
autoImplement,
getBoundingBox,
getCurrentStyle,
getValue,
@ -48,6 +49,90 @@ import EdgeHandler from './cell/edge/EdgeHandler';
import VertexHandler from './cell/vertex/VertexHandler';
import EdgeSegmentHandler from './cell/edge/EdgeSegmentHandler';
import ElbowEdgeHandler from './cell/edge/ElbowEdgeHandler';
import GraphEvents from './event/GraphEvents';
import GraphImage from './image/GraphImage';
import GraphCells from './cell/GraphCells';
import GraphSelection from './selection/GraphSelection';
import GraphConnections from './connection/GraphConnections';
import GraphEdge from './cell/edge/GraphEdge';
import GraphVertex from './cell/vertex/GraphVertex';
import GraphOverlays from './layout/GraphOverlays';
import GraphEditing from './editing/GraphEditing';
import GraphFolding from './folding/GraphFolding';
import GraphLabel from './label/GraphLabel';
import GraphValidation from './validation/GraphValidation';
import GraphSnap from './snap/GraphSnap';
import type { GraphPlugin } from '../types';
import GraphTooltip from './tooltip/GraphTooltip';
import GraphTerminal from './terminal/GraphTerminal';
type PartialEvents = Pick<
GraphEvents,
| 'sizeDidChange'
| 'isNativeDblClickEnabled'
| 'dblClick'
| 'fireMouseEvent'
| 'isMouseDown'
| 'fireGestureEvent'
| 'addMouseListener'
| 'removeMouseListener'
| 'isGridEnabledEvent'
| 'isIgnoreTerminalEvent'
| 'isCloneEvent'
| 'isToggleEvent'
| 'getClickTolerance'
>;
type PartialSelection = Pick<
GraphSelection,
| 'clearSelection'
| 'isCellSelected'
| 'getSelectionCount'
| 'selectCellForEvent'
| 'setSelectionCell'
>;
type PartialCells = Pick<
GraphCells,
| 'removeStateForCell'
| 'getCellStyle'
| 'getCellAt'
| 'isCellBendable'
| 'isCellsCloneable'
| 'cloneCell'
| 'setCellStyles'
>;
type PartialConnections = Pick<
GraphConnections,
| 'getConnectionConstraint'
| 'getConnectionPoint'
| 'isCellDisconnectable'
| 'getOutlineConstraint'
| 'connectCell'
>;
type PartialEditing = Pick<GraphEditing, 'isEditing'>;
type PartialTooltip = Pick<GraphTooltip, 'getTooltip'>;
type PartialValidation = Pick<
GraphValidation,
'getEdgeValidationError' | 'validationAlert'
>;
type PartialLabel = Pick<GraphLabel, 'isLabelMovable'>;
type PartialTerminal = Pick<GraphTerminal, 'isTerminalPointMovable'>;
type PartialSnap = Pick<GraphSnap, 'snap' | 'getGridSize'>;
type PartialEdge = Pick<GraphEdge, 'isAllowDanglingEdges' | 'isResetEdgesOnConnect'>;
type PartialClass = PartialEvents &
PartialSelection &
PartialCells &
PartialConnections &
PartialEditing &
PartialTooltip &
PartialValidation &
PartialLabel &
PartialTerminal &
PartialSnap &
PartialEdge &
EventSource;
export type MaxGraph = Graph & PartialClass;
/**
* Extends {@link EventSource} to implement a graph component for
@ -66,21 +151,19 @@ import ElbowEdgeHandler from './cell/edge/ElbowEdgeHandler';
* @class graph
* @extends {EventSource}
*/
class Graph extends EventSource {
// @ts-ignore
class Graph extends autoImplement<PartialClass>() {
constructor(
container: HTMLElement,
model: Model,
renderHint: string = DIALECT_SVG,
plugins: GraphPlugin[] = [],
stylesheet: Stylesheet | null = null
) {
super();
// Converts the renderHint into a dialect
this.renderHint = renderHint;
this.dialect = 'svg';
// Initializes the main members that do not require a container
this.model = model != null ? model : new Model();
this.container = container;
this.model = model;
this.plugins = plugins;
this.cellRenderer = this.createCellRenderer();
this.setSelectionModel(this.createSelectionModel());
this.setStylesheet(stylesheet != null ? stylesheet : this.createStylesheet());
@ -97,9 +180,7 @@ class Graph extends EventSource {
this.createHandlers();
// Initializes the display if a container was specified
if (container != null) {
this.init(container);
}
this.init();
this.view.revalidate();
}
@ -109,9 +190,7 @@ class Graph extends EventSource {
*
* @param container DOM node that will contain the graph display.
*/
init(container: HTMLElement): void {
this.container = container;
init() {
// Initializes the in-place editor
this.cellEditor = this.createCellEditor();
@ -122,27 +201,45 @@ class Graph extends EventSource {
this.sizeDidChange();
// Hides tooltips and resets tooltip timer if mouse leaves container
InternalEvent.addListener(container, 'mouseleave', (evt: Event) => {
InternalEvent.addListener(this.container, 'mouseleave', (evt: Event) => {
if (
this.tooltipHandler != null &&
this.tooltipHandler.div != null &&
this.tooltipHandler.div != (<MouseEvent>evt).relatedTarget
this.tooltipHandler.div &&
this.tooltipHandler.div !== (<MouseEvent>evt).relatedTarget
) {
this.tooltipHandler.hide();
}
});
// Initiailzes plugins
this.plugins.forEach((p) => p.onInit(this));
}
// TODO: Document me!
// @ts-ignore
container: HTMLElement;
getContainer = () => this.container;
destroyed: boolean = false;
tooltipHandler: TooltipHandler | null = null;
selectionCellsHandler: SelectionCellsHandler | null = null;
popupMenuHandler: PopupMenuHandler | null = null;
connectionHandler: ConnectionHandler | null = null;
graphHandler: GraphHandler | null = null;
// Handlers
// @ts-ignore Cannot be null.
tooltipHandler: TooltipHandler;
// @ts-ignore Cannot be null.
selectionCellsHandler: SelectionCellsHandler;
// @ts-ignore Cannot be null.
popupMenuHandler: PopupMenuHandler;
// @ts-ignore Cannot be null.
connectionHandler: ConnectionHandler;
// @ts-ignore Cannot be null.
graphHandler: GraphHandler;
getTooltipHandler = () => this.tooltipHandler;
getSelectionCellsHandler = () => this.selectionCellsHandler;
getPopupMenuHandler = () => this.popupMenuHandler;
getConnectionHandler = () => this.connectionHandler;
getGraphHandler = () => this.graphHandler;
graphModelChangeListener: Function | null = null;
paintBackground: Function | null = null;
@ -155,6 +252,8 @@ class Graph extends EventSource {
*/
model: Model;
plugins: GraphPlugin[];
/**
* Holds the {@link GraphView} that caches the {@link CellState}s for the cells.
*/
@ -192,6 +291,10 @@ class Graph extends EventSource {
*/
cellRenderer: CellRenderer;
getCellRenderer() {
return this.cellRenderer;
}
/**
* RenderHint as it was passed to the constructor.
*/
@ -298,12 +401,16 @@ class Graph extends EventSource {
*/
exportEnabled: boolean = true;
isExportEnabled = () => this.exportEnabled;
/**
* Specifies the return value for {@link canImportCell}.
* @default true
*/
importEnabled: boolean = true;
isImportEnabled = () => this.importEnabled;
/**
* Specifies if the graph should automatically scroll regardless of the
* scrollbars. This will scroll the container using positive values for
@ -424,18 +531,6 @@ class Graph extends EventSource {
*/
multigraph: boolean = true;
/**
* Specifies if labels should be visible. This is used in {@link getLabel}. Default
* is true.
*/
labelsVisible: boolean = true;
/**
* Specifies the return value for {@link isHtmlLabel}.
* @default false
*/
htmlLabels: boolean = false;
/**
* Specifies the minimum scale to be applied in {@link fit}. Set this to `null` to allow any value.
* @default 0.1
@ -460,6 +555,10 @@ class Graph extends EventSource {
16
);
getWarningImage() {
return this.warningImage;
}
/**
* Specifies the resource key for the error message to be displayed in
* non-multigraphs when two vertices are already connected. If the resource
@ -469,6 +568,8 @@ class Graph extends EventSource {
alreadyConnectedResource: string =
mxClient.language != 'none' ? 'alreadyConnected' : '';
getAlreadyConnectedResource = () => this.alreadyConnectedResource;
/**
* Specifies the resource key for the warning message to be displayed when
* a collapsed cell contains validation errors. If the resource for this
@ -478,6 +579,8 @@ class Graph extends EventSource {
containsValidationErrorsResource: string =
mxClient.language != 'none' ? 'containsValidationErrors' : '';
getContainsValidationErrorsResource = () => this.containsValidationErrorsResource;
// TODO: Document me!!
batchUpdate(fn: Function): void {
(<Model>this.getModel()).beginUpdate();
@ -505,7 +608,7 @@ class Graph extends EventSource {
/**
* Creates and returns a new {@link TooltipHandler} to be used in this graph.
*/
createTooltipHandler(): TooltipHandler {
createTooltipHandler() {
return new TooltipHandler(this);
}
@ -554,7 +657,7 @@ class Graph extends EventSource {
/**
* Creates a new {@link GraphView} to be used in this graph.
*/
createGraphView(): GraphView {
createGraphView() {
return new GraphView(this);
}
@ -575,28 +678,28 @@ class Graph extends EventSource {
/**
* Returns the {@link Model} that contains the cells.
*/
getModel(): Model {
return <Model>this.model;
getModel() {
return this.model;
}
/**
* Returns the {@link GraphView} that contains the {@link mxCellStates}.
*/
getView(): GraphView {
return <GraphView>this.view;
getView() {
return this.view;
}
/**
* Returns the {@link Stylesheet} that defines the style.
*/
getStylesheet(): Stylesheet | null {
getStylesheet() {
return this.stylesheet;
}
/**
* Sets the {@link Stylesheet} that defines the style.
*/
setStylesheet(stylesheet: Stylesheet | null): void {
setStylesheet(stylesheet: Stylesheet) {
this.stylesheet = stylesheet;
}
@ -627,7 +730,7 @@ class Graph extends EventSource {
// Resets the view settings, removes all cells and clears
// the selection if the root changes.
if (change instanceof RootChange) {
this.selection.clearSelection();
this.clearSelection();
this.setDefaultParent(null);
this.cells.removeStateForCell(change.previous);
@ -1006,8 +1109,8 @@ class Graph extends EventSource {
*
* @param state {@link mxCellState} whose handler should be created.
*/
createHandler(state: CellState): mxEdgeHandler | VertexHandler | null {
let result: mxEdgeHandler | VertexHandler | null = null;
createHandler(state: CellState): EdgeHandler | VertexHandler | null {
let result: EdgeHandler | VertexHandler | null = null;
if (state.cell.isEdge()) {
const source = state.getVisibleTerminalState(true);
@ -1041,7 +1144,7 @@ class Graph extends EventSource {
*
* @param state {@link mxCellState} to create the handler for.
*/
createEdgeHandler(state: CellState, edgeStyle: any): mxEdgeHandler {
createEdgeHandler(state: CellState, edgeStyle: any): EdgeHandler {
let result = null;
if (
edgeStyle == EdgeStyle.Loop ||
@ -1056,7 +1159,7 @@ class Graph extends EventSource {
) {
result = this.createEdgeSegmentHandler(state);
} else {
result = new mxEdgeHandler(state);
result = new EdgeHandler(state);
}
return result;
}
@ -1120,7 +1223,7 @@ class Graph extends EventSource {
*
* @param cell {@link mxCell} that represents the root.
*/
getTranslateForRoot(cell: Cell): Point | null {
getTranslateForRoot(cell: Cell | null): Point | null {
return null;
}
@ -1534,6 +1637,10 @@ class Graph extends EventSource {
this.defaultParent = cell;
}
getCellEditor() {
return this.cellEditor;
}
/**
* Destroys the graph and all its resources.
*/
@ -1560,5 +1667,20 @@ class Graph extends EventSource {
}
}
applyMixins(Graph, [
GraphEvents,
GraphImage,
GraphCells,
GraphSelection,
GraphConnections,
GraphEdge,
GraphVertex,
GraphOverlays,
GraphEditing,
GraphFolding,
GraphLabel,
GraphValidation,
GraphSnap,
]);
export default Graph;
// import("../../serialization/mxGraphCodec");

View File

@ -23,12 +23,8 @@ import {
import Dictionary from '../util/Dictionary';
import CellHighlight from './selection/CellHighlight';
import Rectangle from './geometry/Rectangle';
import {
getClientX,
getClientY,
isAltDown,
isMultiTouchEvent,
} from '../util/EventUtils';
import { getClientX, getClientY, isAltDown, isMultiTouchEvent } from '../util/EventUtils';
import { MaxGraph } from './Graph';
/**
* Class: mxGraphHandler
@ -51,7 +47,7 @@ import {
* graph - Reference to the enclosing <mxGraph>.
*/
class GraphHandler {
constructor(graph) {
constructor(graph: MaxGraph) {
this.graph = graph;
this.graph.addMouseListener(this);
@ -150,8 +146,7 @@ class GraphHandler {
*
* Reference to the enclosing <mxGraph>.
*/
// graph: mxGraph;
graph = null;
graph: MaxGraph;
/**
* Variable: maxCells
@ -484,8 +479,7 @@ class GraphHandler {
!this.graph.isCellSelected(cell) &&
!this.graph.isSwimlane(parent)) ||
this.graph.isCellSelected(parent)) &&
(this.graph.isToggleEvent(me.getEvent()) ||
!this.graph.isCellSelected(parent))
(this.graph.isToggleEvent(me.getEvent()) || !this.graph.isCellSelected(parent))
);
}
@ -570,10 +564,7 @@ class GraphHandler {
if (me.isSource(state.control)) {
this.graph.selectCellForEvent(cell, me.getEvent());
} else {
if (
!this.graph.isToggleEvent(me.getEvent()) ||
!isAltDown(me.getEvent())
) {
if (!this.graph.isToggleEvent(me.getEvent()) || !isAltDown(me.getEvent())) {
const model = this.graph.getModel();
let parent = cell.getParent();
@ -654,8 +645,7 @@ class GraphHandler {
cell.getTerminal(true) == null ||
cell.getTerminal(false) == null ||
this.graph.allowDanglingEdges ||
(this.graph.isCloneEvent(me.getEvent()) &&
this.graph.isCellsCloneable()))
(this.graph.isCloneEvent(me.getEvent()) && this.graph.isCellsCloneable()))
) {
this.start(cell, me.getX(), me.getY());
} else if (this.delayedSelection) {
@ -687,9 +677,7 @@ class GraphHandler {
);
};
return this.graph.view.getCellStates(
model.filterDescendants(filter, parent)
);
return this.graph.view.getCellStates(model.filterDescendants(filter, parent));
}
/**
@ -863,10 +851,7 @@ class GraphHandler {
// Uses connected states as guides
const connected = new Dictionary();
const opps = this.graph.getOpposites(
this.graph.getEdges(this.cell),
this.cell
);
const opps = this.graph.getOpposites(this.graph.getEdges(this.cell), this.cell);
for (let i = 0; i < opps.length; i += 1) {
const state = this.graph.view.getState(opps[i]);
@ -962,11 +947,7 @@ class GraphHandler {
*/
// getDelta(me: mxMouseEvent): mxPoint;
getDelta(me) {
const point = utils.convertPoint(
this.graph.container,
me.getX(),
me.getY()
);
const point = utils.convertPoint(this.graph.container, me.getX(), me.getY());
return new point(
point.x - this.first.x - this.graph.panDx,
@ -1067,11 +1048,7 @@ class GraphHandler {
) {
// Highlight is used for highlighting drop targets
if (this.highlight == null) {
this.highlight = new CellHighlight(
this.graph,
DROP_TARGET_COLOR,
3
);
this.highlight = new CellHighlight(this.graph, DROP_TARGET_COLOR, 3);
}
const clone =
@ -1113,8 +1090,7 @@ class GraphHandler {
if (state != null) {
const error = graph.getEdgeValidationError(null, this.cell, cell);
const color =
error == null ? VALID_COLOR : INVALID_CONNECT_TARGET_COLOR;
const color = error == null ? VALID_COLOR : INVALID_CONNECT_TARGET_COLOR;
this.setHighlightColor(color);
highlight = true;
}
@ -1131,13 +1107,7 @@ class GraphHandler {
delta = this.guide.move(this.bounds, delta, gridEnabled, clone);
hideGuide = false;
} else {
delta = this.graph.snapDelta(
delta,
this.bounds,
!gridEnabled,
false,
false
);
delta = this.graph.snapDelta(delta, this.bounds, !gridEnabled, false, false);
}
if (this.guide != null && hideGuide) {
@ -1178,11 +1148,7 @@ class GraphHandler {
) {
let cursor = graph.getCursorForMouseEvent(me);
if (
cursor == null &&
graph.isEnabled() &&
graph.isCellMovable(me.getCell())
) {
if (cursor == null && graph.isEnabled() && graph.isCellMovable(me.getCell())) {
if (me.getCell().isEdge()) {
cursor = CURSOR_MOVABLE_EDGE;
} else {
@ -1355,10 +1321,7 @@ class GraphHandler {
if (source == null || !this.isCellMoving(source.cell)) {
const pt0 = pts[0];
state.setAbsoluteTerminalPoint(
new Point(pt0.x + dx, pt0.y + dy),
true
);
state.setAbsoluteTerminalPoint(new Point(pt0.x + dx, pt0.y + dy), true);
source = null;
} else {
state.view.updateFixedTerminalPoint(
@ -1371,10 +1334,7 @@ class GraphHandler {
if (target == null || !this.isCellMoving(target.cell)) {
const ptn = pts[pts.length - 1];
state.setAbsoluteTerminalPoint(
new Point(ptn.x + dx, ptn.y + dy),
false
);
state.setAbsoluteTerminalPoint(new Point(ptn.x + dx, ptn.y + dy), false);
target = null;
} else {
state.view.updateFixedTerminalPoint(
@ -1411,9 +1371,7 @@ class GraphHandler {
*/
redrawHandles(states) {
for (let i = 0; i < states.length; i += 1) {
const handler = this.graph.selectionCellsHandler.getHandler(
states[i][0].cell
);
const handler = this.graph.selectionCellsHandler.getHandler(states[i][0].cell);
if (handler != null) {
handler.redraw(true);
@ -1627,21 +1585,10 @@ class GraphHandler {
me.getGraphY()
);
} else {
this.moveCells(
this.cells,
dx,
dy,
clone,
this.target,
me.getEvent()
);
this.moveCells(this.cells, dx, dy, clone, this.target, me.getEvent());
}
}
} else if (
this.isSelectEnabled() &&
this.delayedSelection &&
this.cell != null
) {
} else if (this.isSelectEnabled() && this.delayedSelection && this.cell != null) {
this.selectDelayed(me);
}
}
@ -1707,9 +1654,7 @@ class GraphHandler {
getClientX(evt),
getClientY(evt)
);
const alpha = utils.toRadians(
utils.getValue(pState.style, 'rotation') || 0
);
const alpha = utils.toRadians(utils.getValue(pState.style, 'rotation') || 0);
if (alpha !== 0) {
const cos = Math.cos(-alpha);
@ -1748,9 +1693,7 @@ class GraphHandler {
}
// Cloning into locked cells is not allowed
clone =
clone &&
!this.graph.isCellLocked(target || this.graph.getDefaultParent());
clone = clone && !this.graph.isCellLocked(target || this.graph.getDefaultParent());
this.graph.getModel().beginUpdate();
try {

View File

@ -16,7 +16,7 @@ import CellHighlight from '../selection/CellHighlight';
import EventObject from '../event/EventObject';
import InternalEvent from '../event/InternalEvent';
import utils, { intersectsHotspot, isNumeric } from '../../util/Utils';
import graph from '../Graph';
import { MaxGraph } from '../Graph';
import { ColorValue } from '../../types';
import CellState from './datatypes/CellState';
import InternalMouseEvent from '../event/InternalMouseEvent';
@ -64,7 +64,7 @@ import Cell from './datatypes/Cell';
*/
class CellMarker extends EventSource {
constructor(
graph: graph,
graph: MaxGraph,
validColor: ColorValue = DEFAULT_VALID_COLOR,
invalidColor: ColorValue = DEFAULT_INVALID_COLOR,
hotspot: number = DEFAULT_HOTSPOT
@ -83,7 +83,7 @@ class CellMarker extends EventSource {
*
* Reference to the enclosing <mxGraph>.
*/
graph: graph;
graph: MaxGraph;
/**
* Variable: enabled
@ -362,12 +362,15 @@ class CellMarker extends EventSource {
* Uses <getCell>, <getStateToMark> and <intersects> to return the
* <mxCellState> for the given <mxMouseEvent>.
*/
getState(me: InternalMouseEvent): CellState | null {
getState(me: InternalMouseEvent) {
const view = this.graph.getView();
const cell = this.getCell(me);
const state = this.getStateToMark(view.getState(cell));
return state != null && this.intersects(state, me) ? state : null;
if (!cell) return null;
const state = this.getStateToMark(view.getState(cell) as CellState);
return this.intersects(state, me) ? state : null;
}
/**
@ -386,7 +389,7 @@ class CellMarker extends EventSource {
* Returns the <mxCellState> to be marked for the given <mxCellState> under
* the mouse. This returns the given state.
*/
getStateToMark(state: CellState): CellState {
getStateToMark(state: CellState) {
return state;
}

View File

@ -1413,7 +1413,7 @@ class CellRenderer {
state: CellState,
node: HTMLElement | SVGElement | null,
htmlNode: HTMLElement | SVGElement | null
): void {
) {
const shapes = this.getShapesForState(state);
for (let i = 0; i < shapes.length; i += 1) {

File diff suppressed because it is too large Load Diff

View File

@ -11,7 +11,7 @@ import CellOverlay from '../CellOverlay';
import { clone } from '../../../util/CloneUtils';
import Point from '../../geometry/Point';
import CellPath from './CellPath';
import CellArray from "./CellArray";
import CellArray from './CellArray';
import { isNotNullish } from '../../../util/Utils';
import type { FilterFunction } from '../../../types';
@ -88,7 +88,7 @@ class Cell {
onInit: (() => void) | null = null;
// used by addCellOverlay() of mxGraph
overlays: CellOverlay[] | null = [];
overlays: CellOverlay[] = [];
/**
* Holds the Id. Default is null.
@ -755,9 +755,7 @@ class Cell {
* incoming edges should be returned.
* @param {Cell} ignoredEdge that represents an edge to be ignored.
*/
getDirectedEdgeCount(
outgoing: boolean,
ignoredEdge: Cell | null = null) {
getDirectedEdgeCount(outgoing: boolean, ignoredEdge: Cell | null = null) {
let count = 0;
const edgeCount = this.getEdgeCount();

View File

@ -3,8 +3,7 @@ import Dictionary from '../../../util/Dictionary';
import ObjectIdentity from '../../../util/ObjectIdentity';
class CellArray extends Array<Cell> {
// @ts-ignore
constructor(...items: Cell[] | CellArray) {
constructor(...items: Cell[]) {
super(...items);
}

View File

@ -13,8 +13,8 @@ import Shape from '../../geometry/shape/Shape';
import TextShape from '../../geometry/shape/node/TextShape';
import Dictionary from '../../../util/Dictionary';
import { NONE } from '../../../util/Constants';
import type { CellStateStyles } from '../../../types';
import { CellStateStyles } from 'packages/core/src/types';
import RectangleShape from '../../geometry/shape/node/RectangleShape';
/**
* Class: mxCellState
@ -89,7 +89,7 @@ class CellState extends Rectangle {
* Contains an array of key, value pairs that represent the style of the
* cell.
*/
style: CellStateStyles; // TODO: Important - make the style type more strictly typed to allow for typescript checking of individual properties!!!
style: CellStateStyles;
/**
* Variable: invalidStyle
@ -186,14 +186,16 @@ class CellState extends Rectangle {
*
* Holds the unscaled width of the state.
*/
unscaledWidth: number | null = null;
unscaledWidth = 0;
/**
* Variable: unscaledHeight
*
* Holds the unscaled height of the state.
*/
unscaledHeight: number | null = null;
unscaledHeight = 0;
parentHighlight: RectangleShape | null = null;
/**
* Function: getPerimeterBounds
@ -313,7 +315,7 @@ class CellState extends Rectangle {
* terminalState - <mxCellState> that represents the terminal.
* source - Boolean that specifies if the source or target state should be set.
*/
setVisibleTerminalState(terminalState: CellState, source = false) {
setVisibleTerminalState(terminalState: CellState | null, source = false) {
if (source) {
this.visibleSourceState = terminalState;
} else {

File diff suppressed because it is too large Load Diff

View File

@ -1,68 +1,82 @@
import Cell from "../datatypes/Cell";
import CellArray from "../datatypes/CellArray";
import {
findNearestSegment,
removeDuplicates,
} from "../../../util/Utils";
import Geometry from "../../geometry/Geometry";
import EventObject from "../../event/EventObject";
import InternalEvent from "../../event/InternalEvent";
import Dictionary from "../../../util/Dictionary";
import Graph from "../../Graph";
import Cell from '../datatypes/Cell';
import CellArray from '../datatypes/CellArray';
import { autoImplement, findNearestSegment, removeDuplicates } from '../../../util/Utils';
import Geometry from '../../geometry/Geometry';
import EventObject from '../../event/EventObject';
import InternalEvent from '../../event/InternalEvent';
import Dictionary from '../../../util/Dictionary';
class GraphEdge {
constructor(graph: Graph) {
this.graph = graph;
}
import type Graph from '../../Graph';
import type GraphCells from '../GraphCells';
import type GraphConnections from '../../connection/GraphConnections';
graph: Graph;
type PartialGraph = Pick<Graph, 'batchUpdate' | 'fireEvent' | 'getModel' | 'getView'>;
type PartialCells = Pick<
GraphCells,
| 'cloneCell'
| 'cellsMoved'
| 'cellsAdded'
| 'addCell'
| 'isValidAncestor'
| 'getChildCells'
>;
type PartialConnections = Pick<GraphConnections, 'cellConnected'>;
type PartialClass = PartialGraph & PartialCells & PartialConnections;
// @ts-ignore recursive reference error
class GraphEdge extends autoImplement<PartialClass>() {
/**
* Specifies if edge control points should be reset after the resize of a
* connected cell.
* @default false
*/
resetEdgesOnResize: boolean = false;
resetEdgesOnResize = false;
isResetEdgesOnResize = () => this.resetEdgesOnResize;
/**
* Specifies if edge control points should be reset after the move of a
* connected cell.
* @default false
*/
resetEdgesOnMove: boolean = false;
resetEdgesOnMove = false;
isResetEdgesOnMove = () => this.resetEdgesOnMove;
/**
* Specifies if edge control points should be reset after the the edge has been
* reconnected.
* @default true
*/
resetEdgesOnConnect: boolean = true;
resetEdgesOnConnect = true;
isResetEdgesOnConnect = () => this.resetEdgesOnConnect;
/**
* Specifies if edges are connectable. This overrides the connectable field in edges.
* @default false
*/
connectableEdges: boolean = false;
connectableEdges = false;
/**
* Specifies if edges with disconnected terminals are allowed in the graph.
* @default true
*/
allowDanglingEdges: boolean = true;
allowDanglingEdges = true;
/**
* Specifies if edges that are cloned should be validated and only inserted
* if they are valid.
* @default true
*/
cloneInvalidEdges: boolean = false;
cloneInvalidEdges = false;
/**
* Specifies if edges should be disconnected from their terminals when they
* are moved.
* @default true
*/
disconnectOnMove: boolean = true;
disconnectOnMove = true;
/**
* Specifies the alternate edge style to be used if the main control point
@ -75,7 +89,7 @@ class GraphEdge {
* Specifies the return value for edges in {@link isLabelMovable}.
* @default true
*/
edgeLabelsMovable: boolean = true;
edgeLabelsMovable = true;
/*****************************************************************************
* Group: Graph Behaviour
@ -84,14 +98,14 @@ class GraphEdge {
/**
* Returns {@link edgeLabelsMovable}.
*/
isEdgeLabelsMovable(): boolean {
isEdgeLabelsMovable() {
return this.edgeLabelsMovable;
}
/**
* Sets {@link edgeLabelsMovable}.
*/
setEdgeLabelsMovable(value: boolean): void {
setEdgeLabelsMovable(value: boolean) {
this.edgeLabelsMovable = value;
}
@ -101,14 +115,14 @@ class GraphEdge {
*
* @param value Boolean indicating if dangling edges are allowed.
*/
setAllowDanglingEdges(value: boolean): void {
setAllowDanglingEdges(value: boolean) {
this.allowDanglingEdges = value;
}
/**
* Returns {@link allowDanglingEdges} as a boolean.
*/
isAllowDanglingEdges(): boolean {
isAllowDanglingEdges() {
return this.allowDanglingEdges;
}
@ -117,14 +131,14 @@ class GraphEdge {
*
* @param value Boolean indicating if edges should be connectable.
*/
setConnectableEdges(value: boolean): void {
setConnectableEdges(value: boolean) {
this.connectableEdges = value;
}
/**
* Returns {@link connectableEdges} as a boolean.
*/
isConnectableEdges(): boolean {
isConnectableEdges() {
return this.connectableEdges;
}
@ -135,14 +149,14 @@ class GraphEdge {
* @param value Boolean indicating if cloned invalid edges should be
* inserted into the graph or ignored.
*/
setCloneInvalidEdges(value: boolean): void {
setCloneInvalidEdges(value: boolean) {
this.cloneInvalidEdges = value;
}
/**
* Returns {@link cloneInvalidEdges} as a boolean.
*/
isCloneInvalidEdges(): boolean {
isCloneInvalidEdges() {
return this.cloneInvalidEdges;
}
@ -176,20 +190,20 @@ class GraphEdge {
* @param edge {@link mxCell} whose style should be changed.
*/
// flipEdge(edge: mxCell): mxCell;
flipEdge(edge: Cell): Cell {
if (this.alternateEdgeStyle != null) {
this.graph.batchUpdate(() => {
flipEdge(edge: Cell) {
if (this.alternateEdgeStyle) {
this.batchUpdate(() => {
const style = edge.getStyle();
if (style == null || style.length === 0) {
this.graph.model.setStyle(edge, this.alternateEdgeStyle);
if (!style || style.length === 0) {
this.getModel().setStyle(edge, this.alternateEdgeStyle);
} else {
this.graph.model.setStyle(edge, null);
this.getModel().setStyle(edge, null);
}
// Removes all existing control points
this.resetEdge(edge);
this.graph.fireEvent(new EventObject(InternalEvent.FLIP_EDGE, 'edge', edge));
this.fireEvent(new EventObject(InternalEvent.FLIP_EDGE, 'edge', edge));
});
}
return edge;
@ -219,35 +233,35 @@ class GraphEdge {
edge: Cell,
cells: CellArray,
newEdge: Cell,
dx: number = 0,
dy: number = 0,
dx = 0,
dy = 0,
x: number,
y: number,
parent: Cell | null = null
) {
parent = parent != null ? parent : edge.getParent();
parent = parent ?? edge.getParent();
const source = edge.getTerminal(true);
this.graph.batchUpdate(() => {
if (newEdge == null) {
newEdge = <Cell>this.cloneCell(edge);
this.batchUpdate(() => {
if (!newEdge) {
newEdge = this.cloneCell(edge);
// Removes waypoints before/after new cell
const state = this.graph.view.getState(edge);
let geo = newEdge.getGeometry();
const state = this.getView().getState(edge);
let geo: Geometry | null = newEdge.getGeometry();
if (geo != null && geo.points != null && state != null) {
const t = this.graph.view.translate;
const s = this.graph.view.scale;
if (geo && state) {
const t = this.getView().translate;
const s = this.getView().scale;
const idx = findNearestSegment(state, (dx + t.x) * s, (dy + t.y) * s);
geo.points = geo.points.slice(0, idx);
geo = <Geometry>edge.getGeometry();
geo = edge.getGeometry();
if (geo != null && geo.points != null) {
geo = <Geometry>geo.clone();
if (geo) {
geo = geo.clone();
geo.points = geo.points.slice(idx);
this.graph.model.setGeometry(edge, geo);
this.getModel().setGeometry(edge, geo);
}
}
}
@ -255,7 +269,7 @@ class GraphEdge {
this.cellsMoved(cells, dx, dy, false, false);
this.cellsAdded(
cells,
parent,
parent as Cell,
parent ? parent.getChildCount() : 0,
null,
null,
@ -263,18 +277,15 @@ class GraphEdge {
);
this.cellsAdded(
new CellArray(newEdge),
parent,
parent as Cell,
parent ? parent.getChildCount() : 0,
source,
cells[0],
false
);
this.cellConnected(edge, cells[0], true);
this.graph.fireEvent(
new EventObject(
InternalEvent.SPLIT_EDGE,
{ edge, cells, newEdge, dx, dy }
)
this.fireEvent(
new EventObject(InternalEvent.SPLIT_EDGE, { edge, cells, newEdge, dx, dy })
);
});
@ -294,8 +305,7 @@ class GraphEdge {
* @param target {@link mxCell} that defines the target of the edge.
* @param style Optional string that defines the cell style.
*/
// insertEdge(parent: mxCell, id: string | null, value: any, source: mxCell, target: mxCell, style?: string): mxCell;
insertEdge(...args: any[]): Cell {
insertEdge(...args: any[]) {
let parent: Cell;
let id: string = '';
let value: any; // note me - can be a string or a class instance!!!
@ -328,7 +338,6 @@ class GraphEdge {
* are set when the edge is added to the model.
*
*/
// createEdge(parent: mxCell, id: string | null, value: any, source: mxCell, target: mxCell, style?: string): mxCell;
createEdge(
parent: Cell | null = null,
id: string,
@ -363,7 +372,7 @@ class GraphEdge {
source: Cell | null = null,
target: Cell | null = null,
index: number | null = null
): Cell {
) {
return this.addCell(edge, parent, index, source, target);
}
@ -375,7 +384,7 @@ class GraphEdge {
* Returns an array with the given cells and all edges that are connected
* to a cell or one of its descendants.
*/
addAllEdges(cells: CellArray): CellArray {
addAllEdges(cells: CellArray) {
const allCells = cells.slice();
return new CellArray(...removeDuplicates(allCells.concat(this.getAllEdges(cells))));
}
@ -383,19 +392,20 @@ class GraphEdge {
/**
* Returns all edges connected to the given cells or its descendants.
*/
getAllEdges(cells: CellArray | null): CellArray {
getAllEdges(cells: CellArray | null) {
let edges: CellArray = new CellArray();
if (cells != null) {
if (cells) {
for (let i = 0; i < cells.length; i += 1) {
const edgeCount = cells[i].getEdgeCount();
for (let j = 0; j < edgeCount; j++) {
edges.push(<Cell>cells[i].getEdgeAt(j));
edges.push(cells[i].getEdgeAt(j));
}
// Recurses
const children = cells[i].getChildren();
edges = edges.concat(this.getAllEdges(<CellArray>children));
edges = edges.concat(this.getAllEdges(children));
}
}
return edges;
@ -410,8 +420,7 @@ class GraphEdge {
* @param parent Optional parent of the opposite end for an edge to be
* returned.
*/
getIncomingEdges(cell: Cell,
parent: Cell | null = null): CellArray {
getIncomingEdges(cell: Cell, parent: Cell | null = null) {
return this.getEdges(cell, parent, true, false, false);
}
@ -424,8 +433,7 @@ class GraphEdge {
* @param parent Optional parent of the opposite end for an edge to be
* returned.
*/
getOutgoingEdges(cell: Cell,
parent: Cell | null = null): CellArray {
getOutgoingEdges(cell: Cell, parent: Cell | null = null) {
return this.getEdges(cell, parent, false, true, false);
}
@ -456,11 +464,11 @@ class GraphEdge {
getEdges(
cell: Cell,
parent: Cell | null = null,
incoming: boolean = true,
outgoing: boolean = true,
includeLoops: boolean = true,
recurse: boolean = false
): CellArray {
incoming = true,
outgoing = true,
includeLoops = true,
recurse = false
) {
let edges: CellArray = new CellArray();
const isCollapsed = cell.isCollapsed();
const childCount = cell.getChildCount();
@ -468,39 +476,33 @@ class GraphEdge {
for (let i = 0; i < childCount; i += 1) {
const child = cell.getChildAt(i);
if (isCollapsed || !(<Cell>child).isVisible()) {
edges = edges.concat((<Cell>child).getEdges(incoming, outgoing));
if (isCollapsed || !child.isVisible()) {
edges = edges.concat(child.getEdges(incoming, outgoing));
}
}
edges = edges.concat(
<CellArray>cell.getEdges(incoming, outgoing)
);
edges = edges.concat(cell.getEdges(incoming, outgoing));
const result = new CellArray();
for (let i = 0; i < edges.length; i += 1) {
const state = this.getView().getState(edges[i]);
const source =
state != null
? state.getVisibleTerminal(true)
: this.getView().getVisibleTerminal(edges[i], true);
const target =
state != null
? state.getVisibleTerminal(false)
: this.getView().getVisibleTerminal(edges[i], false);
const source = state
? state.getVisibleTerminal(true)
: this.getView().getVisibleTerminal(edges[i], true);
const target = state
? state.getVisibleTerminal(false)
: this.getView().getVisibleTerminal(edges[i], false);
if (
(includeLoops && source == target) ||
(source != target &&
(includeLoops && source === target) ||
(source !== target &&
((incoming &&
target == cell &&
(parent == null ||
this.isValidAncestor(<Cell>source, parent, recurse))) ||
target === cell &&
(!parent || this.isValidAncestor(<Cell>source, parent, recurse))) ||
(outgoing &&
source == cell &&
(parent == null ||
this.isValidAncestor(<Cell>target, parent, recurse)))))
source === cell &&
(!parent || this.isValidAncestor(<Cell>target, parent, recurse)))))
) {
result.push(edges[i]);
}
@ -517,11 +519,10 @@ class GraphEdge {
*
* @param parent {@link mxCell} whose child vertices should be returned.
*/
getChildEdges(parent: Cell): CellArray {
getChildEdges(parent: Cell) {
return this.getChildCells(parent, false, true);
}
/**
* Returns the edges between the given source and target. This takes into
* account collapsed and invisible cells and returns the connected edges
@ -531,7 +532,7 @@ class GraphEdge {
* target -
* directed -
*/
getEdgesBetween(source: Cell, target: Cell, directed: boolean = false): CellArray {
getEdgesBetween(source: Cell, target: Cell, directed = false) {
const edges = this.getEdges(source);
const result = new CellArray();
@ -540,18 +541,16 @@ class GraphEdge {
for (let i = 0; i < edges.length; i += 1) {
const state = this.getView().getState(edges[i]);
const src =
state != null
? state.getVisibleTerminal(true)
: this.getView().getVisibleTerminal(edges[i], true);
const trg =
state != null
? state.getVisibleTerminal(false)
: this.getView().getVisibleTerminal(edges[i], false);
const src = state
? state.getVisibleTerminal(true)
: this.getView().getVisibleTerminal(edges[i], true);
const trg = state
? state.getVisibleTerminal(false)
: this.getView().getVisibleTerminal(edges[i], false);
if (
(src == source && trg == target) ||
(!directed && src == target && trg == source)
(src === source && trg === target) ||
(!directed && src === target && trg === source)
) {
result.push(edges[i]);
}
@ -570,45 +569,39 @@ class GraphEdge {
* @param cells Array of {@link Cell} for which the connected edges should be
* reset.
*/
resetEdges(cells: CellArray): void {
if (cells != null) {
// Prepares faster cells lookup
const dict = new Dictionary();
resetEdges(cells: CellArray) {
// Prepares faster cells lookup
const dict = new Dictionary();
for (let i = 0; i < cells.length; i += 1) {
dict.put(cells[i], true);
}
this.getModel().beginUpdate();
try {
for (let i = 0; i < cells.length; i += 1) {
dict.put(cells[i], true);
}
const edges = cells[i].getEdges();
this.getModel().beginUpdate();
try {
for (let i = 0; i < cells.length; i += 1) {
const edges = cells[i].getEdges();
for (let j = 0; j < edges.length; j++) {
const state = this.getView().getState(edges[j]);
if (edges != null) {
for (let j = 0; j < edges.length; j++) {
const state = this.getView().getState(edges[j]);
const source = state
? state.getVisibleTerminal(true)
: this.getView().getVisibleTerminal(edges[j], true);
const target = state
? state.getVisibleTerminal(false)
: this.getView().getVisibleTerminal(edges[j], false);
const source =
state != null
? state.getVisibleTerminal(true)
: this.getView().getVisibleTerminal(edges[j], true);
const target =
state != null
? state.getVisibleTerminal(false)
: this.getView().getVisibleTerminal(edges[j], false);
// Checks if one of the terminals is not in the given array
if (!dict.get(source) || !dict.get(target)) {
this.resetEdge(<Cell>edges[j]);
}
}
// Checks if one of the terminals is not in the given array
if (!dict.get(source) || !dict.get(target)) {
this.resetEdge(edges[j]);
}
this.resetEdges(cells[i].getChildren());
}
} finally {
this.getModel().endUpdate();
this.resetEdges(cells[i].getChildren());
}
} finally {
this.getModel().endUpdate();
}
}
@ -617,15 +610,16 @@ class GraphEdge {
*
* @param edge {@link mxCell} whose points should be reset.
*/
resetEdge(edge: Cell): Cell | null {
resetEdge(edge: Cell) {
let geo = edge.getGeometry();
// Resets the control points
if (geo != null && geo.points != null && geo.points.length > 0) {
geo = <Geometry>geo.clone();
if (geo && geo.points.length > 0) {
geo = geo.clone();
geo.points = [];
this.getModel().setGeometry(edge, geo);
}
return edge;
}
}

View File

@ -1,15 +1,14 @@
import Cell from "../datatypes/Cell";
import Geometry from "../../geometry/Geometry";
import CellArray from "../datatypes/CellArray";
import Graph from '../../Graph';
import Cell from '../datatypes/Cell';
import Geometry from '../../geometry/Geometry';
import CellArray from '../datatypes/CellArray';
import { autoImplement } from 'packages/core/src/util/Utils';
class GraphVertex {
constructor(graph: Graph) {
this.graph = graph;
}
import type GraphCells from '../GraphCells';
graph: Graph;
type PartialCells = Pick<GraphCells, 'addCell' | 'getChildCells'>;
type PartialClass = PartialCells;
class GraphVertex extends autoImplement<PartialClass>() {
/**
* Specifies the return value for vertices in {@link isLabelMovable}.
* @default false
@ -95,18 +94,7 @@ class GraphVertex {
geometryClass = params.geometryClass;
} else {
// Otherwise treat as arguments
[
parent,
id,
value,
x,
y,
width,
height,
style,
relative,
geometryClass,
] = args;
[parent, id, value, x, y, width, height, style, relative, geometryClass] = args;
}
const vertex = this.createVertex(
@ -121,7 +109,7 @@ class GraphVertex {
relative,
geometryClass
);
return this.graph.cell.addCell(vertex, parent);
return this.addCell(vertex, parent);
};
/**
@ -160,7 +148,7 @@ class GraphVertex {
* @param parent {@link mxCell} whose children should be returned.
*/
getChildVertices(parent: Cell): CellArray {
return this.graph.cell.getChildCells(parent, true, false);
return this.getChildCells(parent, true, false);
}
/*****************************************************************************

View File

@ -5,7 +5,7 @@
* Type definitions from the typed-mxgraph project
*/
import utils, { getRotatedPoint, toRadians } from '../../../util/Utils';
import { getRotatedPoint, toRadians } from '../../../util/Utils';
import Point from '../../geometry/Point';
import ImageShape from '../../geometry/shape/node/ImageShape';
import Rectangle from '../../geometry/Rectangle';
@ -21,60 +21,65 @@ import {
import InternalEvent from '../../event/InternalEvent';
import Shape from '../../geometry/shape/Shape';
import InternalMouseEvent from '../../event/InternalMouseEvent';
import Image from '../../image/ImageBox';
import Graph from '../../Graph';
import ImageBox from '../../image/ImageBox';
import CellState from '../datatypes/CellState';
import CellArray from '../datatypes/CellArray';
import type { MaxGraph } from '../../Graph';
import type { CellHandle, CellStateStyles } from 'packages/core/src/types';
/**
* Implements a single custom handle for vertices.
*
* @class VertexHandle
*/
class VertexHandle {
class VertexHandle implements CellHandle {
dependencies = ['snap', 'cells'];
constructor(
state: CellState,
cursor: string | null = 'default',
image: Image | null = null,
cursor: string = 'default',
image: ImageBox | null = null,
shape: Shape | null = null
) {
this.graph = state.view.graph;
this.state = state;
this.cursor = cursor != null ? cursor : this.cursor;
this.image = image != null ? image : this.image;
this.cursor = cursor;
this.image = image;
this.shape = shape;
this.init();
}
graph: Graph;
graph: MaxGraph;
state: CellState;
shape: Shape | ImageShape | null;
/**
* Specifies the cursor to be used for this handle. Default is 'default'.
*/
cursor: string = 'default';
cursor = 'default';
/**
* Specifies the <mxImage> to be used to render the handle. Default is null.
*/
image: Image | null = null;
image: ImageBox | null = null;
/**
* Default is false.
*/
ignoreGrid: boolean = false;
ignoreGrid = false;
/**
* Hook for subclassers to return the current position of the handle.
*/
getPosition(bounds: Rectangle) {}
getPosition(bounds: Rectangle | null): Point {
return new Point();
}
/**
* Hooks for subclassers to update the style in the <state>.
*/
setPosition(bounds: Rectangle, pt: Point, me: InternalMouseEvent) {}
setPosition(bounds: Rectangle | null, pt: Point, me: InternalMouseEvent) {}
/**
* Hook for subclassers to execute the handle.
@ -84,8 +89,8 @@ class VertexHandle {
/**
* Sets the cell style with the given name to the corresponding value in <state>.
*/
copyStyle(key: string): void {
this.graph.setCellStyles(key, this.state.style[key], [this.state.cell]);
copyStyle(key: keyof CellStateStyles) {
this.graph.setCellStyles(key, this.state.style[key], new CellArray(this.state.cell));
}
/**
@ -94,10 +99,7 @@ class VertexHandle {
processEvent(me: InternalMouseEvent): void {
const { scale } = this.graph.view;
const tr = this.graph.view.translate;
let pt = new Point(
<number>me.getGraphX() / scale - tr.x,
<number>me.getGraphY() / scale - tr.y
);
let pt = new Point(me.getGraphX() / scale - tr.x, me.getGraphY() / scale - tr.y);
// Center shape on mouse cursor
if (this.shape != null && this.shape.bounds != null) {
@ -112,7 +114,7 @@ class VertexHandle {
this.rotatePoint(
this.snapPoint(
this.rotatePoint(pt, alpha1),
this.ignoreGrid || !this.graph.snap.isGridEnabledEvent(me.getEvent())
this.ignoreGrid || !this.graph.isGridEnabledEvent(me.getEvent())
),
alpha2
)
@ -161,16 +163,16 @@ class VertexHandle {
/**
* Creates and initializes the shapes required for this handle.
*/
init(): void {
init() {
const html = this.isHtmlRequired();
if (this.image != null) {
if (this.image) {
this.shape = new ImageShape(
new Rectangle(0, 0, this.image.width, this.image.height),
this.image.src
);
this.shape.preserveImageAspect = false;
} else if (this.shape == null) {
} else if (!this.shape) {
this.shape = this.createShape(html);
}
@ -180,7 +182,7 @@ class VertexHandle {
/**
* Creates and returns the shape for this handle.
*/
createShape(html: any): Shape {
createShape(html: boolean): Shape {
const bounds = new Rectangle(0, 0, HANDLE_SIZE, HANDLE_SIZE);
return new RectangleShape(bounds, HANDLE_FILLCOLOR, HANDLE_STROKECOLOR);
}
@ -188,8 +190,8 @@ class VertexHandle {
/**
* Initializes <shape> and sets its cursor.
*/
initShape(html: any): void {
const shape = <Shape>this.shape;
initShape(html: boolean) {
const shape = this.shape as Shape; // `this.shape` cannot be null.
if (html && shape.isHtmlAllowed()) {
shape.dialect = DIALECT_STRICTHTML;
@ -198,7 +200,7 @@ class VertexHandle {
shape.dialect =
this.graph.dialect !== DIALECT_SVG ? DIALECT_MIXEDHTML : DIALECT_SVG;
if (this.cursor != null) {
if (this.cursor) {
shape.init(this.graph.getView().getOverlayPane());
}
}
@ -210,11 +212,11 @@ class VertexHandle {
/**
* Renders the shape for this handle.
*/
redraw(): void {
if (this.shape != null && this.state.shape != null) {
redraw() {
if (this.shape && this.state.shape) {
let pt = this.getPosition(this.state.getPaintBounds());
if (pt != null) {
if (pt) {
const alpha = toRadians(this.getTotalRotation());
pt = this.rotatePoint(this.flipPoint(pt), alpha);
@ -235,16 +237,14 @@ class VertexHandle {
* Returns true if this handle should be rendered in HTML. This returns true if
* the text node is in the graph container.
*/
isHtmlRequired(): boolean {
return (
this.state.text != null && this.state.text.node.parentNode === this.graph.container
);
isHtmlRequired() {
return !!this.state.text && this.state.text.node.parentNode === this.graph.container;
}
/**
* Rotates the point by the given angle.
*/
rotatePoint(pt: Point, alpha: number): Point {
rotatePoint(pt: Point, alpha: number) {
const bounds = <Rectangle>this.state.getCellBounds();
const cx = new Point(bounds.getCenterX(), bounds.getCenterY());
const cos = Math.cos(alpha);
@ -256,8 +256,8 @@ class VertexHandle {
/**
* Flips the given point vertically and/or horizontally.
*/
flipPoint(pt: Point): Point {
if (this.state.shape != null) {
flipPoint(pt: Point) {
if (this.state.shape) {
const bounds = <Rectangle>this.state.getCellBounds();
if (this.state.shape.flipH) {
@ -275,10 +275,10 @@ class VertexHandle {
* Snaps the given point to the grid if ignore is false. This modifies
* the given point in-place and also returns it.
*/
snapPoint(pt: Point, ignore: boolean): Point {
snapPoint(pt: Point, ignore: boolean) {
if (!ignore) {
pt.x = this.graph.snap.snap(pt.x);
pt.y = this.graph.snap.snap(pt.y);
pt.x = this.graph.snap(pt.x);
pt.y = this.graph.snap(pt.y);
}
return pt;
}
@ -286,8 +286,8 @@ class VertexHandle {
/**
* Shows or hides this handle.
*/
setVisible(visible: boolean): void {
if (this.shape != null && this.shape.node != null) {
setVisible(visible: boolean) {
if (this.shape && this.shape.node) {
this.shape.node.style.display = visible ? '' : 'none';
}
}
@ -295,9 +295,9 @@ class VertexHandle {
/**
* Resets the state of this handle by setting its visibility to true.
*/
reset(): void {
reset() {
this.setVisible(true);
this.state.style = this.graph.cell.getCellStyle(this.state.cell);
this.state.style = this.graph.getCellStyle(this.state.cell);
this.positionChanged();
}
@ -305,7 +305,7 @@ class VertexHandle {
* Destroys this handle.
*/
destroy(): void {
if (this.shape != null) {
if (this.shape) {
this.shape.destroy();
this.shape = null;
}

View File

@ -1,18 +1,18 @@
import Point from "../geometry/Point";
import CellState from "../cell/datatypes/CellState";
import InternalMouseEvent from "../event/InternalMouseEvent";
import ConnectionConstraint from "./ConnectionConstraint";
import Rectangle from "../geometry/Rectangle";
import {DIRECTION_NORTH, DIRECTION_SOUTH, DIRECTION_WEST} from "../../util/Constants";
import utils, {getRotatedPoint, getValue, toRadians} from "../../util/Utils";
import Cell from "../cell/datatypes/Cell";
import CellArray from "../cell/datatypes/CellArray";
import EventObject from "../event/EventObject";
import InternalEvent from "../event/InternalEvent";
import Dictionary from "../../util/Dictionary";
import Geometry from "../geometry/Geometry";
import Graph from "../Graph";
import ConnectionHandler from "./ConnectionHandler";
import Point from '../geometry/Point';
import CellState from '../cell/datatypes/CellState';
import InternalMouseEvent from '../event/InternalMouseEvent';
import ConnectionConstraint from './ConnectionConstraint';
import Rectangle from '../geometry/Rectangle';
import { DIRECTION_NORTH, DIRECTION_SOUTH, DIRECTION_WEST } from '../../util/Constants';
import utils, { getRotatedPoint, getValue, toRadians } from '../../util/Utils';
import Cell from '../cell/datatypes/Cell';
import CellArray from '../cell/datatypes/CellArray';
import EventObject from '../event/EventObject';
import InternalEvent from '../event/InternalEvent';
import Dictionary from '../../util/Dictionary';
import Geometry from '../geometry/Geometry';
import Graph from '../Graph';
import ConnectionHandler from './ConnectionHandler';
class GraphConnections {
constructor(graph: Graph) {
@ -34,9 +34,7 @@ class GraphConnections {
me: InternalMouseEvent
): ConnectionConstraint | null {
if (terminalState.shape != null) {
const bounds = <Rectangle>(
this.graph.view.getPerimeterBounds(terminalState)
);
const bounds = <Rectangle>this.graph.view.getPerimeterBounds(terminalState);
const direction = terminalState.style.direction;
if (direction === DIRECTION_NORTH || direction === DIRECTION_SOUTH) {
@ -68,16 +66,9 @@ class GraphConnections {
let flipV = terminalState.style.flipV;
// Legacy support for stencilFlipH/V
if (
terminalState.shape != null &&
terminalState.shape.stencil != null
) {
flipH =
getValue(terminalState.style, 'stencilFlipH', 0) == 1 ||
flipH;
flipV =
getValue(terminalState.style, 'stencilFlipV', 0) == 1 ||
flipV;
if (terminalState.shape != null && terminalState.shape.stencil != null) {
flipH = getValue(terminalState.style, 'stencilFlipH', 0) == 1 || flipH;
flipV = getValue(terminalState.style, 'stencilFlipV', 0) == 1 || flipV;
}
if (direction === DIRECTION_NORTH || direction === DIRECTION_SOUTH) {
@ -128,11 +119,7 @@ class GraphConnections {
terminal: CellState,
source: boolean
): ConnectionConstraint[] | null {
if (
terminal != null &&
terminal.shape != null &&
terminal.shape.stencil != null
) {
if (terminal != null && terminal.shape != null && terminal.shape.stencil != null) {
return terminal.shape.stencil.constraints;
}
return null;
@ -169,21 +156,13 @@ class GraphConnections {
let dy = 0;
if (point != null) {
perimeter = getValue(
edge.style,
source ? 'exitPerimeter' : 'entryPerimeter',
true
);
perimeter = getValue(edge.style, source ? 'exitPerimeter' : 'entryPerimeter', true);
// Add entry/exit offset
// @ts-ignore
dx = parseFloat(
<string>edge.style[source ? 'exitDx' : 'entryDx']
);
dx = parseFloat(<string>edge.style[source ? 'exitDx' : 'entryDx']);
// @ts-ignore
dy = parseFloat(
<string>edge.style[source ? 'exitDy' : 'entryDy']
);
dy = parseFloat(<string>edge.style[source ? 'exitDy' : 'entryDy']);
dx = Number.isFinite(dx) ? dx : 0;
dy = Number.isFinite(dy) ? dy : 0;
@ -288,10 +267,7 @@ class GraphConnections {
let r1 = 0;
// Bounds need to be rotated by 90 degrees for further computation
if (
direction != null &&
getValue(vertex.style, 'anchorPointDirection', 1) == 1
) {
if (direction != null && getValue(vertex.style, 'anchorPointDirection', 1) == 1) {
if (direction === DIRECTION_NORTH) {
r1 += 270;
} else if (direction === DIRECTION_WEST) {
@ -308,12 +284,8 @@ class GraphConnections {
const { scale } = this.view;
point = new point(
bounds.x +
constraint.point.x * bounds.width +
<number>constraint.dx * scale,
bounds.y +
constraint.point.y * bounds.height +
<number>constraint.dy * scale
bounds.x + constraint.point.x * bounds.width + <number>constraint.dx * scale,
bounds.y + constraint.point.y * bounds.height + <number>constraint.dy * scale
);
// Rotation for direction before projection on perimeter
@ -346,10 +318,8 @@ class GraphConnections {
// Legacy support for stencilFlipH/V
if (vertex.shape != null && vertex.shape.stencil != null) {
flipH =
utils.getValue(vertex.style, 'stencilFlipH', 0) == 1 || flipH;
flipV =
utils.getValue(vertex.style, 'stencilFlipV', 0) == 1 || flipV;
flipH = utils.getValue(vertex.style, 'stencilFlipH', 0) == 1 || flipH;
flipV = utils.getValue(vertex.style, 'stencilFlipV', 0) == 1 || flipV;
}
if (direction === DIRECTION_NORTH || direction === DIRECTION_SOUTH) {
@ -515,9 +485,7 @@ class GraphConnections {
if (geo != null) {
const state = this.graph.view.getState(cell);
const pstate = <CellState>(
this.graph.view.getState(cell.getParent())
);
const pstate = <CellState>this.graph.view.getState(cell.getParent());
if (state != null && pstate != null) {
geo = geo.clone();
@ -549,10 +517,7 @@ class GraphConnections {
let trg = cell.getTerminal(false);
if (
trg != null &&
this.isCellDisconnectable(cell, trg, false)
) {
if (trg != null && this.isCellDisconnectable(cell, trg, false)) {
while (trg != null && !dict.get(trg)) {
trg = trg.getParent();
}
@ -588,8 +553,7 @@ class GraphConnections {
* @param parent Optional parent of the opposite end for a connection to be
* returned.
*/
getConnections(cell: Cell,
parent: Cell | null = null): CellArray {
getConnections(cell: Cell, parent: Cell | null = null): CellArray {
return this.getEdges(cell, parent, true, true, false);
}
@ -698,12 +662,10 @@ class GraphConnections {
*
* @param cell {@link mxCell} that represents a possible source or null.
*/
isValidSource(cell: Cell): boolean {
isValidSource(cell: Cell | null): boolean {
return (
(cell == null && this.allowDanglingEdges) ||
(cell != null &&
(!cell.isEdge() || this.connectableEdges) &&
cell.isConnectable())
(cell != null && (!cell.isEdge() || this.connectableEdges) && cell.isConnectable())
);
}
@ -713,7 +675,7 @@ class GraphConnections {
*
* @param cell {@link mxCell} that represents a possible target or null.
*/
isValidTarget(cell: Cell): boolean {
isValidTarget(cell: Cell | null): boolean {
return this.isValidSource(cell);
}
@ -727,7 +689,7 @@ class GraphConnections {
* @param source {@link mxCell} that represents the source cell.
* @param target {@link mxCell} that represents the target cell.
*/
isValidConnection(source: Cell, target: Cell): boolean {
isValidConnection(source: Cell | null, target: Cell | null): boolean {
return this.isValidSource(source) && this.isValidTarget(target);
}

View File

@ -1,23 +1,33 @@
import Cell from "../cell/datatypes/Cell";
import {isMultiTouchEvent} from "../../util/EventUtils";
import EventObject from "../event/EventObject";
import InternalEvent from "../event/InternalEvent";
import CellEditor from "./CellEditor";
import InternalMouseEvent from "../event/InternalMouseEvent";
import Graph from "../Graph";
import Cell from '../cell/datatypes/Cell';
import { isMultiTouchEvent } from '../../util/EventUtils';
import EventObject from '../event/EventObject';
import InternalEvent from '../event/InternalEvent';
import InternalMouseEvent from '../event/InternalMouseEvent';
import { autoImplement } from '../../util/Utils';
class GraphEditing {
constructor(graph: Graph) {
this.graph = graph;
}
import type GraphSelection from '../selection/GraphSelection';
import type GraphEvents from '../event/GraphEvents';
import type Graph from '../Graph';
import type GraphCells from '../cell/GraphCells';
graph: Graph;
type PartialGraph = Pick<
Graph,
'getCellEditor' | 'convertValueToString' | 'batchUpdate' | 'getModel'
>;
type PartialSelection = Pick<GraphSelection, 'getSelectionCell'>;
type PartialEvents = Pick<GraphEvents, 'fireEvent'>;
type PartialCells = Pick<
GraphCells,
'isAutoSizeCell' | 'cellSizeUpdated' | 'getCurrentCellStyle' | 'isCellLocked'
>;
type PartialClass = PartialGraph & PartialSelection & PartialEvents & PartialCells;
class GraphEditing extends autoImplement<PartialClass>() {
/**
* Specifies the return value for {@link isCellEditable}.
* @default true
*/
cellsEditable: boolean = true;
cellsEditable = true;
/*****************************************************************************
* Group: Cell in-place editing
@ -29,7 +39,7 @@ class GraphEditing {
*
* @param evt Optional mouse event that triggered the editing.
*/
startEditing(evt: MouseEvent): void {
startEditing(evt: MouseEvent) {
this.startEditingAtCell(null, evt);
}
@ -41,21 +51,20 @@ class GraphEditing {
* @param cell {@link mxCell} to start the in-place editor for.
* @param evt Optional mouse event that triggered the editing.
*/
startEditingAtCell(cell: Cell | null = null, evt: MouseEvent): void {
if (evt == null || !isMultiTouchEvent(evt)) {
if (cell == null) {
cell = this.graph.selection.getSelectionCell();
if (cell != null && !this.isCellEditable(cell)) {
startEditingAtCell(cell: Cell | null = null, evt: MouseEvent) {
if (!evt || !isMultiTouchEvent(evt)) {
if (!cell) {
cell = this.getSelectionCell();
if (cell && !this.isCellEditable(cell)) {
cell = null;
}
}
if (cell != null) {
this.graph.event.fireEvent(
} else {
this.fireEvent(
new EventObject(InternalEvent.START_EDITING, 'cell', cell, 'event', evt)
);
(<CellEditor>this.graph.editing.cellEditor).startEditing(cell, evt);
this.graph.event.fireEvent(
this.getCellEditor().startEditing(cell, evt);
this.fireEvent(
new EventObject(InternalEvent.EDITING_STARTED, 'cell', cell, 'event', evt)
);
}
@ -71,10 +80,7 @@ class GraphEditing {
* @param cell {@link mxCell} for which the initial editing value should be returned.
* @param evt Optional mouse event that triggered the editor.
*/
getEditingValue(
cell: Cell,
evt: EventObject | InternalMouseEvent
): string | null {
getEditingValue(cell: Cell, evt: EventObject) {
return this.convertValueToString(cell);
}
@ -84,11 +90,9 @@ class GraphEditing {
* @param cancel Boolean that specifies if the current editing value
* should be stored.
*/
stopEditing(cancel: boolean = false): void {
(<CellEditor>this.graph.editing.cellEditor).stopEditing(cancel);
this.graph.event.fireEvent(
new EventObject(InternalEvent.EDITING_STOPPED, 'cancel', cancel)
);
stopEditing(cancel: boolean = false) {
this.getCellEditor().stopEditing(cancel);
this.fireEvent(new EventObject(InternalEvent.EDITING_STOPPED, 'cancel', cancel));
}
/**
@ -100,24 +104,18 @@ class GraphEditing {
* @param value New label to be assigned.
* @param evt Optional event that triggered the change.
*/
// labelChanged(cell: mxCell, value: any, evt?: MouseEvent): mxCell;
labelChanged(
cell: Cell,
value: any,
evt: InternalMouseEvent | EventObject
): Cell {
this.graph.batchUpdate(() => {
labelChanged(cell: Cell, value: any, evt: InternalMouseEvent | EventObject) {
this.batchUpdate(() => {
const old = cell.value;
this.cellLabelChanged(cell, value, this.graph.cell.isAutoSizeCell(cell));
this.graph.event.fireEvent(new EventObject(
InternalEvent.LABEL_CHANGED,
{
this.cellLabelChanged(cell, value, this.isAutoSizeCell(cell));
this.fireEvent(
new EventObject(InternalEvent.LABEL_CHANGED, {
cell: cell,
value: value,
old: old,
event: evt,
}
));
})
);
});
return cell;
}
@ -149,12 +147,10 @@ class GraphEditing {
* @param value New label to be assigned.
* @param autoSize Boolean that specifies if {@link cellSizeUpdated} should be called.
*/
cellLabelChanged(cell: Cell,
value: any,
autoSize: boolean = false): void {
cellLabelChanged(cell: Cell, value: any, autoSize: boolean = false) {
this.batchUpdate(() => {
this.getModel().setValue(cell, value);
this.graph.batchUpdate(() => {
this.graph.model.setValue(cell, value);
if (autoSize) {
this.cellSizeUpdated(cell, false);
}
@ -172,12 +168,9 @@ class GraphEditing {
*
* @param cell {@link mxCell} that should be checked.
*/
isEditing(cell: Cell | null = null): boolean {
if (this.cellEditor != null) {
const editingCell = this.cellEditor.getEditingCell();
return cell == null ? editingCell != null : cell === editingCell;
}
return false;
isEditing(cell: Cell | null = null) {
const editingCell = this.getCellEditor().getEditingCell();
return !cell ? !!editingCell : cell === editingCell;
}
/**
@ -187,20 +180,16 @@ class GraphEditing {
*
* @param cell {@link mxCell} whose editable state should be returned.
*/
isCellEditable(cell: Cell): boolean {
const style = this.graph.cell.getCurrentCellStyle(cell);
isCellEditable(cell: Cell) {
const style = this.getCurrentCellStyle(cell);
return (
this.isCellsEditable() &&
!this.graph.cell.isCellLocked(cell) &&
style.editable != 0
);
return this.isCellsEditable() && !this.isCellLocked(cell) && style.editable;
}
/**
* Returns {@link cellsEditable}.
*/
isCellsEditable(): boolean {
isCellsEditable() {
return this.cellsEditable;
}
@ -211,7 +200,7 @@ class GraphEditing {
* @param value Boolean indicating if the graph should allow in-place
* editing.
*/
setCellsEditable(value: boolean): void {
setCellsEditable(value: boolean) {
this.cellsEditable = value;
}
}

View File

@ -94,7 +94,7 @@ class EventSource {
*
* Sets <eventSource>.
*/
setEventSource(value: EventSource) {
setEventSource(value: EventSource | null) {
this.eventSource = value;
}

View File

@ -1,45 +1,77 @@
import InternalMouseEvent from "./InternalMouseEvent";
import EventObject from "./EventObject";
import InternalEvent from "./InternalEvent";
import InternalMouseEvent from './InternalMouseEvent';
import EventObject from './EventObject';
import InternalEvent from './InternalEvent';
import {
getClientX,
getClientY,
isAltDown,
isConsumed, isControlDown, isLeftMouseButton, isMetaDown,
isMouseEvent, isMultiTouchEvent,
isConsumed,
isControlDown,
isLeftMouseButton,
isMetaDown,
isMouseEvent,
isMultiTouchEvent,
isPenEvent,
isPopupTrigger, isShiftDown, isTouchEvent
} from "../../util/EventUtils";
import CellState from "../cell/datatypes/CellState";
import Cell from "../cell/datatypes/Cell";
import PanningHandler from "../panning/PanningHandler";
import ConnectionHandler from "../connection/ConnectionHandler";
import Point from "../geometry/Point";
import {convertPoint, getValue} from "../../util/Utils";
import {NONE, SHAPE_SWIMLANE} from "../../util/Constants";
import mxClient from "../../mxClient";
import EventSource from "./EventSource";
import CellEditor from "../editing/CellEditor";
import Graph from "../Graph";
isPopupTrigger,
isShiftDown,
isTouchEvent,
} from '../../util/EventUtils';
import CellState from '../cell/datatypes/CellState';
import Cell from '../cell/datatypes/Cell';
import PanningHandler from '../panning/PanningHandler';
import ConnectionHandler from '../connection/ConnectionHandler';
import Point from '../geometry/Point';
import { convertPoint, getValue, autoImplement } from '../../util/Utils';
import { NONE, SHAPE_SWIMLANE } from '../../util/Constants';
import mxClient from '../../mxClient';
import EventSource from './EventSource';
import CellEditor from '../editing/CellEditor';
class GraphEvents {
constructor(graph: Graph) {
this.graph = graph;
import type Graph from '../Graph';
import type GraphCells from '../cell/GraphCells';
import type GraphSelection from '../selection/GraphSelection';
import GraphEditing from '../editing/GraphEditing';
import GraphSnap from '../snap/GraphSnap';
// Initializes the variable in case the prototype has been
// modified to hold some listeners (which is possible because
// the createHandlers call is executed regardless of the
// arguments passed into the ctor).
this.mouseListeners = null;
}
type PartialGraph = Pick<
Graph,
| 'fireEvent'
| 'isEnabled'
| 'getView'
| 'getGraphBounds'
| 'getContainer'
| 'paintBackground'
>;
type PartialCells = Pick<GraphCells, 'getCellAt' | 'getCursorForCell'>;
type PartialSelection = Pick<
GraphSelection,
'isCellSelected' | 'selectCellForEvent' | 'clearSelection'
>;
type PartialEditing = Pick<
GraphEditing,
'isCellEditable' | 'isEditing' | 'startEditingAtCell' | 'stopEditing'
>;
type PartialSnap = Pick<GraphSnap, 'getGridSize' | 'snap'>;
type PartialClass = PartialGraph &
PartialCells &
PartialSelection &
PartialEditing &
PartialSnap &
EventSource;
graph: Graph;
type MouseListener = {
mouseDown: Function;
mouseMove: Function;
mouseUp: Function;
};
// @ts-ignore recursive reference error
class GraphEvents extends autoImplement<PartialClass>() {
/**
* Holds the mouse event listeners. See {@link fireMouseEvent}.
*/
mouseListeners: any[] | null = null;
mouseListeners: MouseListener[] = [];
// TODO: Document me!
lastTouchEvent: InternalMouseEvent | null = null;
doubleClickCounter: number = 0;
@ -83,13 +115,14 @@ class GraphEvents {
*/
isMouseDown: boolean = false;
/**
* Specifies if native double click events should be detected.
* @default true
*/
nativeDblClickEnabled: boolean = true;
isNativeDblClickEnabled = () => this.nativeDblClickEnabled;
/**
* Specifies if double taps on touch-based devices should be handled as a
* double click.
@ -166,6 +199,8 @@ class GraphEvents {
*/
tolerance: number = 4;
getClickTolerance = () => this.tolerance;
/*****************************************************************************
* Group: Event processing
*****************************************************************************/
@ -176,7 +211,7 @@ class GraphEvents {
* @param evt Mouseevent that represents the keystroke.
*/
escape(evt: InternalMouseEvent): void {
this.graph.fireEvent(new EventObject(InternalEvent.ESCAPE, 'event', evt));
this.fireEvent(new EventObject(InternalEvent.ESCAPE, 'event', evt));
}
/**
@ -205,7 +240,7 @@ class GraphEvents {
*
* @param me {@link mxMouseEvent} that represents the single click.
*/
click(me: InternalMouseEvent): boolean {
click(me: InternalMouseEvent) {
const evt = me.getEvent();
let cell = me.getCell();
const mxe = new EventObject(InternalEvent.CLICK, 'event', evt, 'cell', cell);
@ -214,38 +249,38 @@ class GraphEvents {
mxe.consume();
}
this.graph.fireEvent(mxe);
this.fireEvent(mxe);
if (this.graph.isEnabled() && !isConsumed(evt) && !mxe.isConsumed()) {
if (cell != null) {
if (this.isEnabled() && !isConsumed(evt) && !mxe.isConsumed()) {
if (cell) {
if (this.isTransparentClickEvent(evt)) {
let active = false;
const tmp = this.graph.cell.getCellAt(
const tmp = this.getCellAt(
me.graphX,
me.graphY,
null,
false,
false,
(state: CellState): boolean => {
const selected = this.graph.cell.isCellSelected(<Cell>state.cell);
(state: CellState) => {
const selected = this.isCellSelected(state.cell);
active = active || selected;
return (
!active ||
selected ||
(state.cell !== cell &&
state.cell.isAncestor(cell))
(state.cell !== cell && state.cell.isAncestor(cell))
);
}
);
if (tmp != null) {
if (tmp) {
cell = tmp;
}
}
} else if (this.graph.swimlane.isSwimlaneSelectionEnabled()) {
cell = this.graph.swimlane.getSwimlaneAt(me.getGraphX(), me.getGraphY());
/* comment out swimlane for now... perhaps make it a plugin?
} else if (this.swimlane.isSwimlaneSelectionEnabled()) {
cell = this.swimlane.getSwimlaneAt(me.getGraphX(), me.getGraphY());
if (cell != null && (!this.isToggleEvent(evt) || !isAltDown(evt))) {
let temp = cell;
@ -253,9 +288,9 @@ class GraphEvents {
while (temp != null) {
temp = temp.getParent();
const state = this.graph.view.getState(temp);
const state = this.getView().getState(temp);
if (this.graph.swimlane.isSwimlane(temp) && state != null) {
if (this.swimlane.isSwimlane(temp) && state != null) {
swimlanes.push(temp);
}
}
@ -267,18 +302,18 @@ class GraphEvents {
swimlanes.push(cell);
for (let i = 0; i < swimlanes.length - 1; i += 1) {
if (this.graph.cell.isCellSelected(swimlanes[i])) {
if (this.isCellSelected(swimlanes[i])) {
cell = swimlanes[this.isToggleEvent(evt) ? i : i + 1];
}
}
}
}
}*/
}
if (cell != null) {
this.graph.selection.selectCellForEvent(cell, evt);
if (cell) {
this.selectCellForEvent(cell, evt);
} else if (!this.isToggleEvent(evt)) {
this.graph.selection.clearSelection();
this.clearSelection();
}
}
return false;
@ -296,7 +331,7 @@ class GraphEvents {
* graph.dblClick = function(evt, cell)
* {
* var mxe = new mxEventObject(mxEvent.DOUBLE_CLICK, 'event', evt, 'cell', cell);
* this.graph.fireEvent(mxe);
* this.fireEvent(mxe);
*
* if (this.isEnabled() && !mxEvent.isConsumed(evt) && !mxe.isConsumed())
* {
@ -320,21 +355,20 @@ class GraphEvents {
* @param evt Mouseevent that represents the doubleclick.
* @param cell Optional {@link Cell} under the mousepointer.
*/
dblClick(evt: MouseEvent,
cell?: Cell): void {
const mxe = new EventObject(InternalEvent.DOUBLE_CLICK, {event: evt, cell: cell});
this.graph.fireEvent(mxe);
dblClick(evt: MouseEvent, cell?: Cell): void {
const mxe = new EventObject(InternalEvent.DOUBLE_CLICK, { event: evt, cell: cell });
this.fireEvent(mxe);
// Handles the event if it has not been consumed
if (
this.graph.isEnabled() &&
this.isEnabled() &&
!isConsumed(evt) &&
!mxe.isConsumed() &&
cell != null &&
this.graph.cell.isCellEditable(cell) &&
!this.graph.editing.isEditing(cell)
this.isCellEditable(cell) &&
!this.isEditing(cell)
) {
this.graph.editing.startEditingAtCell(cell, evt);
this.startEditingAtCell(cell, evt);
InternalEvent.consume(evt);
}
}
@ -354,11 +388,11 @@ class GraphEvents {
'cell',
me.getCell()
);
const panningHandler = <PanningHandler>this.graph.panning.panningHandler;
const connectionHandler = <ConnectionHandler>this.graph.connectionHandler;
const panningHandler = <PanningHandler>this.panning.panningHandler;
const connectionHandler = <ConnectionHandler>this.connectionHandler;
// LATER: Check if event should be consumed if me is consumed
this.graph.fireEvent(mxe);
this.fireEvent(mxe);
if (mxe.isConsumed()) {
// Resets the state of the panning handler
@ -367,18 +401,15 @@ class GraphEvents {
// Handles the event if it has not been consumed
if (
this.graph.isEnabled() &&
this.isEnabled() &&
!isConsumed(evt) &&
!mxe.isConsumed() &&
connectionHandler.isEnabled()
) {
const state = this.graph.view.getState(
connectionHandler.marker.getCell(me)
);
const state = this.getView().getState(connectionHandler.marker.getCell(me));
if (state != null) {
connectionHandler.marker.currentColor =
connectionHandler.marker.validColor;
connectionHandler.marker.currentColor = connectionHandler.marker.validColor;
connectionHandler.marker.markedState = state;
connectionHandler.marker.mark();
@ -404,10 +435,7 @@ class GraphEvents {
* @param listener Listener to be added to the graph event listeners.
*/
// addMouseListener(listener: { [key: string]: (sender: mxEventSource, me: mxMouseEvent) => void }): void;
addMouseListener(listener: any): void {
if (this.mouseListeners == null) {
this.mouseListeners = [];
}
addMouseListener(listener: MouseListener): void {
this.mouseListeners.push(listener);
}
@ -417,13 +445,11 @@ class GraphEvents {
* @param listener Listener to be removed from the graph event listeners.
*/
// removeMouseListener(listener: { [key: string]: (sender: mxEventSource, me: mxMouseEvent) => void }): void;
removeMouseListener(listener: any) {
if (this.mouseListeners != null) {
for (let i = 0; i < this.mouseListeners.length; i += 1) {
if (this.mouseListeners[i] === listener) {
this.mouseListeners.splice(i, 1);
break;
}
removeMouseListener(listener: MouseListener) {
for (let i = 0; i < this.mouseListeners.length; i += 1) {
if (this.mouseListeners[i] === listener) {
this.mouseListeners.splice(i, 1);
break;
}
}
}
@ -435,14 +461,12 @@ class GraphEvents {
* @param me {@link mxMouseEvent} to be updated.
* @param evtName Name of the mouse event.
*/
updateMouseEvent(me: InternalMouseEvent,
evtName: string): InternalMouseEvent {
updateMouseEvent(me: InternalMouseEvent, evtName: string): InternalMouseEvent {
if (me.graphX == null || me.graphY == null) {
const pt = convertPoint(this.graph.container, me.getX(), me.getY());
const pt = convertPoint(this.getContainer(), me.getX(), me.getY());
me.graphX = pt.x - this.graph.panning.panDx;
me.graphY = pt.y - this.graph.panning.panDy;
me.graphX = pt.x - this.panning.panDx;
me.graphY = pt.y - this.panning.panDy;
// Searches for rectangles using method if native hit detection is disabled on shape
if (
@ -450,7 +474,7 @@ class GraphEvents {
this.isMouseDown &&
evtName === InternalEvent.MOUSE_MOVE
) {
me.state = this.graph.view.getState(
me.state = this.getView().getState(
this.getCellAt(pt.x, pt.y, null, true, true, (state: CellState) => {
return (
state.shape == null ||
@ -476,9 +500,9 @@ class GraphEvents {
// Dispatches the drop event to the graph which
// consumes and executes the source function
const pt = convertPoint(this.graph.container, x, y);
const pt = convertPoint(this.getContainer(), x, y);
return this.graph.view.getState(this.graph.cell.getCellAt(pt.x, pt.y));
return this.getView().getState(this.getCellAt(pt.x, pt.y));
}
/**
@ -499,21 +523,19 @@ class GraphEvents {
// Installs event listeners to capture the complete gesture from the event source
// for non-MS touch events as a workaround for all events for the same geture being
// fired from the event source even if that was removed from the DOM.
if (this.eventSource != null && evtName !== InternalEvent.MOUSE_MOVE) {
const eventSource = this.getEventSource();
if (eventSource && evtName !== InternalEvent.MOUSE_MOVE) {
InternalEvent.removeGestureListeners(
this.eventSource,
eventSource,
null,
this.mouseMoveRedirect,
this.mouseUpRedirect
);
this.mouseMoveRedirect = null;
this.mouseUpRedirect = null;
this.eventSource = null;
} else if (
!mxClient.IS_GC &&
this.eventSource != null &&
me.getSource() !== this.eventSource
) {
this.setEventSource(null);
} else if (!mxClient.IS_GC && eventSource && me.getSource() !== eventSource) {
result = true;
} else if (
mxClient.IS_TOUCH &&
@ -521,7 +543,7 @@ class GraphEvents {
!mouseEvent &&
!isPenEvent(me.getEvent())
) {
this.eventSource = me.getSource();
this.setEventSource(me.getSource());
this.mouseMoveRedirect = (evt: InternalMouseEvent) => {
this.fireMouseEvent(
@ -537,7 +559,7 @@ class GraphEvents {
};
InternalEvent.addGestureListeners(
this.eventSource,
eventSource,
null,
this.mouseMoveRedirect,
this.mouseUpRedirect
@ -566,7 +588,7 @@ class GraphEvents {
this.isMouseDown = true;
this.isMouseTrigger = mouseEvent;
}
// Drops mouse events that are fired during touch gestures as a workaround for Webkit
// Drops mouse events that are fired during touch gestures as a workaround for Webkit
// and mouse events that are not in sync with the current internal button state
else if (
!result &&
@ -591,20 +613,12 @@ class GraphEvents {
* Hook for ignoring synthetic mouse events after touchend in Firefox.
*/
// isSyntheticEventIgnored(evtName: string, me: mxMouseEvent, sender: mxEventSource): boolean;
isSyntheticEventIgnored(
evtName: string,
me: InternalMouseEvent,
sender: any
): boolean {
isSyntheticEventIgnored(evtName: string, me: InternalMouseEvent, sender: any): boolean {
let result = false;
const mouseEvent = isMouseEvent(me.getEvent());
// LATER: This does not cover all possible cases that can go wrong in FF
if (
this.ignoreMouseEvents &&
mouseEvent &&
evtName !== InternalEvent.MOUSE_MOVE
) {
if (this.ignoreMouseEvents && mouseEvent && evtName !== InternalEvent.MOUSE_MOVE) {
this.ignoreMouseEvents = evtName !== InternalEvent.MOUSE_UP;
result = true;
} else if (mxClient.IS_FF && !mouseEvent && evtName === InternalEvent.MOUSE_UP) {
@ -625,8 +639,7 @@ class GraphEvents {
isEventSourceIgnored(evtName: string, me: InternalMouseEvent): boolean {
const source = me.getSource();
const name = source.nodeName != null ? source.nodeName.toLowerCase() : '';
const candidate =
!isMouseEvent(me.getEvent()) || isLeftMouseButton(me.getEvent());
const candidate = !isMouseEvent(me.getEvent()) || isLeftMouseButton(me.getEvent());
return (
evtName === InternalEvent.MOUSE_DOWN &&
@ -662,19 +675,20 @@ class GraphEvents {
* @param me {@link mxMouseEvent} to be fired.
* @param sender Optional sender argument. Default is `this`.
*/
fireMouseEvent(evtName: string,
me: InternalMouseEvent,
sender: EventSource = this): void {
fireMouseEvent(
evtName: string,
me: InternalMouseEvent,
sender: EventSource = this
): void {
if (this.isEventSourceIgnored(evtName, me)) {
if (this.graph.tooltipHandler != null) {
this.graph.tooltipHandler.hide();
if (this.tooltipHandler != null) {
this.tooltipHandler.hide();
}
return;
}
if (sender == null) {
sender = this.graph;
sender = this;
}
// Updates the graph coordinates in the event
@ -706,10 +720,7 @@ class GraphEvents {
let doubleClickFired = false;
if (evtName === InternalEvent.MOUSE_UP) {
if (
me.getCell() === this.lastTouchCell &&
this.lastTouchCell !== null
) {
if (me.getCell() === this.lastTouchCell && this.lastTouchCell !== null) {
this.lastTouchTime = 0;
const cell = this.lastTouchCell;
this.lastTouchCell = null;
@ -726,10 +737,7 @@ class GraphEvents {
InternalEvent.consume(me.getEvent());
return;
}
} else if (
this.lastTouchEvent == null ||
this.lastTouchEvent !== me.getEvent()
) {
} else if (this.lastTouchEvent == null || this.lastTouchEvent !== me.getEvent()) {
this.lastTouchCell = me.getCell();
this.lastTouchX = me.getX();
this.lastTouchY = me.getY();
@ -769,21 +777,11 @@ class GraphEvents {
if (!this.isEventIgnored(evtName, me, sender)) {
// Updates the event state via getEventState
me.state = this.getEventState(me.getState());
this.graph.fireEvent(
new EventObject(
InternalEvent.FIRE_MOUSE_EVENT,
'eventName',
evtName,
'event',
me
)
this.fireEvent(
new EventObject(InternalEvent.FIRE_MOUSE_EVENT, 'eventName', evtName, 'event', me)
);
if (
mxClient.IS_SF ||
mxClient.IS_GC ||
me.getEvent().target !== this.container
) {
if (mxClient.IS_SF || mxClient.IS_GC || me.getEvent().target !== this.container) {
const container = <HTMLElement>this.container;
if (
@ -792,20 +790,16 @@ class GraphEvents {
this.autoScroll &&
!isMultiTouchEvent(me.getEvent)
) {
this.scrollPointToVisible(
me.getGraphX(),
me.getGraphY(),
this.autoExtend
);
this.scrollPointToVisible(me.getGraphX(), me.getGraphY(), this.autoExtend);
} else if (
evtName === InternalEvent.MOUSE_UP &&
this.ignoreScrollbars &&
this.translateToScrollPosition &&
(container.scrollLeft !== 0 || container.scrollTop !== 0)
) {
const s = this.graph.view.scale;
const tr = this.graph.view.translate;
this.graph.view.setTranslate(
const s = this.getView().scale;
const tr = this.getView().translate;
this.getView().setTranslate(
tr.x - container.scrollLeft / s,
tr.y - container.scrollTop / s
);
@ -813,23 +807,21 @@ class GraphEvents {
container.scrollTop = 0;
}
if (this.mouseListeners != null) {
const args = [sender, me];
const mouseListeners = this.mouseListeners;
const args = [sender, me];
const mouseListeners = this.mouseListeners;
// Does not change returnValue in Opera
if (!me.getEvent().preventDefault) {
me.getEvent().returnValue = true;
}
// Does not change returnValue in Opera
if (!me.getEvent().preventDefault) {
me.getEvent().returnValue = true;
}
for (const l of mouseListeners) {
if (evtName === InternalEvent.MOUSE_DOWN) {
l.mouseDown.apply(l, args);
} else if (evtName === InternalEvent.MOUSE_MOVE) {
l.mouseMove.apply(l, args);
} else if (evtName === InternalEvent.MOUSE_UP) {
l.mouseUp.apply(l, args);
}
for (const l of mouseListeners) {
if (evtName === InternalEvent.MOUSE_DOWN) {
l.mouseDown.apply(l, args);
} else if (evtName === InternalEvent.MOUSE_MOVE) {
l.mouseMove.apply(l, args);
} else if (evtName === InternalEvent.MOUSE_UP) {
l.mouseUp.apply(l, args);
}
}
@ -863,10 +855,7 @@ class GraphEvents {
window.clearTimeout(this.tapAndHoldThread);
}
this.tapAndHoldThread = window.setTimeout(
handler,
this.tapAndHoldDelay
);
this.tapAndHoldThread = window.setTimeout(handler, this.tapAndHoldDelay);
this.tapAndHoldValid = true;
} else if (evtName === InternalEvent.MOUSE_UP) {
this.tapAndHoldInProgress = false;
@ -893,9 +882,7 @@ class GraphEvents {
/**
* Consumes the given {@link InternalMouseEvent} if it's a touchStart event.
*/
consumeMouseEvent(evtName: string,
me: InternalMouseEvent,
sender: any = this): void {
consumeMouseEvent(evtName: string, me: InternalMouseEvent, sender: any = this): void {
// Workaround for duplicate click in Windows 8 with Chrome/FF/Opera with touch
if (evtName === InternalEvent.MOUSE_DOWN && isTouchEvent(me.getEvent())) {
me.consume(false);
@ -933,13 +920,10 @@ class GraphEvents {
* @param evt Gestureend event that represents the gesture.
* @param cell Optional {@link Cell} associated with the gesture.
*/
fireGestureEvent(evt: MouseEvent,
cell: Cell | null = null): void {
fireGestureEvent(evt: MouseEvent, cell: Cell | null = null): void {
// Resets double tap event handling when gestures take place
this.lastTouchTime = 0;
this.graph.fireEvent(
new EventObject(InternalEvent.GESTURE, 'event', evt, 'cell', cell)
);
this.fireEvent(new EventObject(InternalEvent.GESTURE, 'event', evt, 'cell', cell));
}
/**
@ -948,63 +932,56 @@ class GraphEvents {
* SVG-bases browsers.
*/
sizeDidChange(): void {
const bounds = this.graph.getGraphBounds();
const bounds = this.getGraphBounds();
if (this.graph.container != null) {
const border = this.graph.getBorder();
const border = this.getBorder();
let width = Math.max(0, bounds.x) + bounds.width + 2 * border;
let height = Math.max(0, bounds.y) + bounds.height + 2 * border;
let width = Math.max(0, bounds.x) + bounds.width + 2 * border;
let height = Math.max(0, bounds.y) + bounds.height + 2 * border;
if (this.graph.minimumContainerSize != null) {
width = Math.max(width, this.graph.minimumContainerSize.width);
height = Math.max(height, this.graph.minimumContainerSize.height);
}
if (this.graph.resizeContainer) {
this.graph.doResizeContainer(width, height);
}
if (this.preferPageSize || this.pageVisible) {
const size = this.getPreferredPageSize(
bounds,
Math.max(1, width),
Math.max(1, height)
);
if (size != null) {
width = size.width * this.graph.view.scale;
height = size.height * this.graph.view.scale;
}
}
if (this.graph.minimumGraphSize != null) {
width = Math.max(
width,
this.graph.minimumGraphSize.width * this.graph.view.scale
);
height = Math.max(
height,
this.graph.minimumGraphSize.height * this.graph.view.scale
);
}
width = Math.ceil(width);
height = Math.ceil(height);
// @ts-ignore
const root = this.graph.view.getDrawPane().ownerSVGElement;
if (root != null) {
root.style.minWidth = `${Math.max(1, width)}px`;
root.style.minHeight = `${Math.max(1, height)}px`;
root.style.width = '100%';
root.style.height = '100%';
}
this.graph.pageBreaks.updatePageBreaks(this.graph.pageBreaksVisible, width, height);
if (this.minimumContainerSize != null) {
width = Math.max(width, this.minimumContainerSize.width);
height = Math.max(height, this.minimumContainerSize.height);
}
this.graph.fireEvent(new EventObject(InternalEvent.SIZE, 'bounds', bounds));
if (this.resizeContainer) {
this.doResizeContainer(width, height);
}
if (this.preferPageSize || this.pageVisible) {
const size = this.getPreferredPageSize(
bounds,
Math.max(1, width),
Math.max(1, height)
);
if (size != null) {
width = size.width * this.getView().scale;
height = size.height * this.getView().scale;
}
}
if (this.minimumGraphSize != null) {
width = Math.max(width, this.minimumGraphSize.width * this.getView().scale);
height = Math.max(height, this.minimumGraphSize.height * this.getView().scale);
}
width = Math.ceil(width);
height = Math.ceil(height);
// @ts-ignore
const root = this.getView().getDrawPane().ownerSVGElement;
if (root != null) {
root.style.minWidth = `${Math.max(1, width)}px`;
root.style.minHeight = `${Math.max(1, height)}px`;
root.style.width = '100%';
root.style.height = '100%';
}
this.pageBreaks.updatePageBreaks(this.pageBreaksVisible, width, height);
this.fireEvent(new EventObject(InternalEvent.SIZE, 'bounds', bounds));
}
/*****************************************************************************
@ -1068,13 +1045,13 @@ class GraphEvents {
* offset by half of the {@link gridSize}. Default is `true`.
*/
getPointForEvent(evt: InternalMouseEvent, addOffset: boolean = true): Point {
const p = convertPoint(this.graph.container, getClientX(evt), getClientY(evt));
const s = this.graph.view.scale;
const tr = this.graph.view.translate;
const off = addOffset ? this.graph.snap.gridSize / 2 : 0;
const p = convertPoint(this.getContainer(), getClientX(evt), getClientY(evt));
const s = this.getView().scale;
const tr = this.getView().translate;
const off = addOffset ? this.getGridSize() / 2 : 0;
p.x = this.graph.snap.snap(p.x / s - tr.x - off);
p.y = this.graph.snap.snap(p.y / s - tr.y - off);
p.x = this.snap(p.x / s - tr.x - off);
p.y = this.snap(p.y / s - tr.y - off);
return p;
}
@ -1137,7 +1114,7 @@ class GraphEvents {
* @param me {@link mxMouseEvent} whose cursor should be returned.
*/
getCursorForMouseEvent(me: InternalMouseEvent): string | null {
return this.graph.cell.getCursorForCell(me.getCell());
return this.getCursorForCell(me.getCell());
}
}

View File

@ -9,25 +9,7 @@ import mxClient from '../../mxClient';
import { isConsumed, isMouseEvent } from '../../util/EventUtils';
import graph from '../Graph';
import CellState from '../cell/datatypes/CellState';
type Listener = {
name: string;
f: EventListener;
};
type ListenerTarget = {
mxListenerList?: Listener[];
};
type Listenable = (Node | Window) & ListenerTarget;
type GestureEvent = Event &
MouseEvent & {
scale?: number;
pointerId?: number;
};
type EventCache = GestureEvent[];
import { EventCache, GestureEvent, Listenable } from '../../types';
// Checks if passive event listeners are supported
// see https://github.com/Modernizr/Modernizr/issues/1894
@ -69,11 +51,7 @@ class InternalEvent {
* to a given execution scope.
*/
// static addListener(element: Node | Window, eventName: string, funct: Function): void;
static addListener(
element: Listenable,
eventName: string,
funct: EventListener
) {
static addListener(element: Listenable, eventName: string, funct: EventListener) {
element.addEventListener(
eventName,
funct,
@ -92,11 +70,7 @@ class InternalEvent {
* Removes the specified listener from the given element.
*/
// static removeListener(element: Node | Window, eventName: string, funct: Function): void;
static removeListener(
element: Listenable,
eventName: string,
funct: EventListener
) {
static removeListener(element: Listenable, eventName: string, funct: EventListener) {
element.removeEventListener(eventName, funct, false);
if (element.mxListenerList) {
@ -264,7 +238,7 @@ class InternalEvent {
} else if (!isConsumed(evt)) {
graph.fireMouseEvent(
InternalEvent.MOUSE_DOWN,
new InternalMouseEvent(evt, getState(evt))
new InternalMouseEvent(evt as MouseEvent, getState(evt))
);
}
},
@ -274,7 +248,7 @@ class InternalEvent {
} else if (!isConsumed(evt)) {
graph.fireMouseEvent(
InternalEvent.MOUSE_MOVE,
new InternalMouseEvent(evt, getState(evt))
new InternalMouseEvent(evt as MouseEvent, getState(evt))
);
}
},
@ -284,7 +258,7 @@ class InternalEvent {
} else if (!isConsumed(evt)) {
graph.fireMouseEvent(
InternalEvent.MOUSE_UP,
new InternalMouseEvent(evt, getState(evt))
new InternalMouseEvent(evt as MouseEvent, getState(evt))
);
}
}
@ -348,13 +322,7 @@ class InternalEvent {
* https://www.chromestatus.com/features/6662647093133312.
*/
static addMouseWheelListener(
funct: (
event: Event,
up: boolean,
force?: boolean,
cx?: number,
cy?: number
) => void,
funct: (event: Event, up: boolean, force?: boolean, cx?: number, cy?: number) => void,
target: Listenable
) {
if (funct != null) {
@ -430,11 +398,9 @@ class InternalEvent {
ty > InternalEvent.PINCH_THRESHOLD
) {
const cx =
evtCache[0].clientX +
(evtCache[1].clientX - evtCache[0].clientX) / 2;
evtCache[0].clientX + (evtCache[1].clientX - evtCache[0].clientX) / 2;
const cy =
evtCache[0].clientY +
(evtCache[1].clientY - evtCache[0].clientY) / 2;
evtCache[0].clientY + (evtCache[1].clientY - evtCache[0].clientY) / 2;
funct(evtCache[0], tx > ty ? dx > dx0 : dy > dy0, true, cx, cy);

View File

@ -14,7 +14,6 @@ import {
import { isAncestorNode } from '../../util/DomUtils';
import CellState from '../cell/datatypes/CellState';
import Shape from '../geometry/shape/Shape';
import Cell from '../cell/datatypes/Cell';
/**
* Class: mxMouseEvent
@ -51,10 +50,15 @@ import Cell from '../cell/datatypes/Cell';
*
*/
class InternalMouseEvent {
constructor(evt: MouseEvent, state?: CellState) {
constructor(evt: MouseEvent, state: CellState | null = null) {
this.evt = evt;
this.state = state;
this.sourceState = state;
// graphX and graphY are updated right after this constructor is executed,
// so let them default to 0 and make them not nullable.
this.graphX = 0;
this.graphY = 0;
}
/**
@ -62,7 +66,7 @@ class InternalMouseEvent {
*
* Holds the consumed state of this event.
*/
consumed: boolean = false;
consumed = false;
/**
* Variable: evt
@ -77,7 +81,7 @@ class InternalMouseEvent {
* Holds the x-coordinate of the event in the graph. This value is set in
* <mxGraph.fireMouseEvent>.
*/
graphX?: number;
graphX: number;
/**
* Variable: graphY
@ -85,14 +89,14 @@ class InternalMouseEvent {
* Holds the y-coordinate of the event in the graph. This value is set in
* <mxGraph.fireMouseEvent>.
*/
graphY?: number;
graphY: number;
/**
* Variable: state
*
* Holds the optional <mxCellState> associated with this event.
*/
state?: CellState;
state: CellState | null;
/**
* Variable: sourceState
@ -100,14 +104,14 @@ class InternalMouseEvent {
* Holds the <mxCellState> that was passed to the constructor. This can be
* different from <state> depending on the result of <mxGraph.getEventState>.
*/
sourceState?: CellState;
sourceState: CellState | null;
/**
* Function: getEvent
*
* Returns <evt>.
*/
getEvent(): MouseEvent {
getEvent() {
return this.evt;
}
@ -116,7 +120,7 @@ class InternalMouseEvent {
*
* Returns the target DOM element using <mxEvent.getSource> for <evt>.
*/
getSource(): Element {
getSource() {
return getSource(this.evt);
}
@ -125,7 +129,7 @@ class InternalMouseEvent {
*
* Returns true if the given <mxShape> is the source of <evt>.
*/
isSource(shape: Shape) {
isSource(shape: Shape | null) {
return shape ? isAncestorNode(shape.node, this.getSource()) : false;
}
@ -152,7 +156,7 @@ class InternalMouseEvent {
*
* Returns <graphX>.
*/
getGraphX(): number | undefined {
getGraphX() {
return this.graphX;
}
@ -161,7 +165,7 @@ class InternalMouseEvent {
*
* Returns <graphY>.
*/
getGraphY(): number | undefined {
getGraphY() {
return this.graphY;
}
@ -170,7 +174,7 @@ class InternalMouseEvent {
*
* Returns <state>.
*/
getState(): CellState | undefined {
getState() {
return this.state;
}
@ -179,12 +183,9 @@ class InternalMouseEvent {
*
* Returns the <mxCell> in <state> is not null.
*/
getCell(): Cell | null {
getCell() {
const state = this.getState();
if (state != null) {
return state.cell;
}
return null;
return state ? state.cell : null;
}
/**
@ -192,7 +193,7 @@ class InternalMouseEvent {
*
* Returns true if the event is a popup trigger.
*/
isPopupTrigger(): boolean {
isPopupTrigger() {
return isPopupTrigger(this.getEvent());
}
@ -201,7 +202,7 @@ class InternalMouseEvent {
*
* Returns <consumed>.
*/
isConsumed(): boolean {
isConsumed() {
return this.consumed;
}
@ -218,11 +219,10 @@ class InternalMouseEvent {
* preventDefault - Specifies if the native event should be canceled. Default
* is true.
*/
consume(preventDefault?: boolean): void {
preventDefault =
preventDefault != null
? preventDefault
: this.evt.touches != null || isMouseEvent(this.evt);
consume(preventDefault?: boolean) {
preventDefault = preventDefault
? preventDefault
: this.evt instanceof TouchEvent || isMouseEvent(this.evt);
if (preventDefault && this.evt.preventDefault) {
this.evt.preventDefault();

View File

@ -1,15 +1,19 @@
import Image from '../image/ImageBox';
import mxClient from '../../mxClient';
import Graph from '../Graph';
import CellState from '../cell/datatypes/CellState';
import Cell from '../cell/datatypes/Cell';
import CellArray from '../cell/datatypes/CellArray';
import EventObject from '../event/EventObject';
import InternalEvent from '../event/InternalEvent';
import Geometry from '../geometry/Geometry';
import { getValue, toRadians } from '../../util/Utils';
import { autoImplement, getValue, toRadians } from '../../util/Utils';
import Rectangle from '../geometry/Rectangle';
import type Graph from '../Graph';
import type GraphCells from '../cell/GraphCells';
import type GraphSelection from '../selection/GraphSelection';
import type GraphEditing from '../editing/GraphEditing';
/**
* GraphFoldingOptions
*
@ -31,22 +35,26 @@ type GraphFoldingOptions = {
collapseToPreferredSize: boolean;
};
class GraphFolding {
constructor(
graph: Graph,
options: GraphFoldingOptions = {
foldingEnabled: true,
collapsedImage: new Image(`${mxClient.imageBasePath}/collapsed.gif`, 9, 9),
expandedImage: new Image(`${mxClient.imageBasePath}/expanded.gif`, 9, 9),
collapseToPreferredSize: true,
}
) {
this.graph = graph;
this.options = options;
}
type PartialGraph = Pick<Graph, 'getModel' | 'fireEvent'>;
type PartialCells = Pick<
GraphCells,
| 'getCurrentCellStyle'
| 'isExtendParent'
| 'extendParent'
| 'constrainChild'
| 'getPreferredSizeForCell'
>;
type PartialSelection = Pick<GraphSelection, 'getSelectionCells'>;
type PartialEditing = Pick<GraphEditing, 'stopEditing'>;
type PartialClass = PartialGraph & PartialCells & PartialSelection & PartialEditing;
graph: Graph;
options: GraphFoldingOptions;
class GraphFolding extends autoImplement<PartialClass>() {
options: GraphFoldingOptions = {
foldingEnabled: true,
collapsedImage: new Image(`${mxClient.imageBasePath}/collapsed.gif`, 9, 9),
expandedImage: new Image(`${mxClient.imageBasePath}/expanded.gif`, 9, 9),
collapseToPreferredSize: true,
};
/**
* Specifies the resource key for the tooltip on the collapse/expand icon.
@ -56,6 +64,8 @@ class GraphFolding {
*/
collapseExpandResource: string = mxClient.language != 'none' ? 'collapse-expand' : '';
getCollapseExpandResource = () => this.collapseExpandResource;
/**
*
* @default true
@ -65,7 +75,7 @@ class GraphFolding {
* Returns the cells which are movable in the given array of cells.
*/
getFoldableCells(cells: CellArray, collapse: boolean = false): CellArray | null {
return this.graph.model.filterCells(cells, (cell: Cell) => {
return this.getModel().filterCells(cells, (cell: Cell) => {
return this.isCellFoldable(cell, collapse);
});
}
@ -80,7 +90,7 @@ class GraphFolding {
// isCellFoldable(cell: mxCell, collapse: boolean): boolean;
isCellFoldable(cell: Cell, collapse: boolean = false): boolean {
const style = this.getCurrentCellStyle(cell);
return cell.getChildCount() > 0 && style.foldable != 0;
return cell.getChildCount() > 0 && style.foldable;
}
/**
@ -131,7 +141,7 @@ class GraphFolding {
this.stopEditing(false);
this.graph.model.beginUpdate();
this.getModel().beginUpdate();
try {
this.cellsFolded(cells, collapse, recurse, checkFoldable);
this.fireEvent(
@ -146,7 +156,7 @@ class GraphFolding {
)
);
} finally {
this.graph.model.endUpdate();
this.getModel().endUpdate();
}
return cells;
}
@ -171,14 +181,14 @@ class GraphFolding {
checkFoldable: boolean = false
): void {
if (cells != null && cells.length > 0) {
this.graph.model.beginUpdate();
this.getModel().beginUpdate();
try {
for (let i = 0; i < cells.length; i += 1) {
if (
(!checkFoldable || this.isCellFoldable(cells[i], collapse)) &&
collapse !== cells[i].isCollapsed()
) {
this.graph.model.setCollapsed(cells[i], collapse);
this.getModel().setCollapsed(cells[i], collapse);
this.swapBounds(cells[i], collapse);
if (this.isExtendParent(cells[i])) {
@ -194,7 +204,7 @@ class GraphFolding {
}
}
this.graph.fireEvent(
this.fireEvent(
new EventObject(
InternalEvent.CELLS_FOLDED,
'cells',
@ -206,7 +216,7 @@ class GraphFolding {
)
);
} finally {
this.graph.model.endUpdate();
this.getModel().endUpdate();
}
}
}
@ -227,7 +237,7 @@ class GraphFolding {
this.updateAlternateBounds(cell, geo, willCollapse);
geo.swap();
this.graph.model.setGeometry(cell, geo);
this.getModel().setGeometry(cell, geo);
}
}

View File

@ -105,7 +105,7 @@ class Shape {
*
* container - DOM node that will contain the shape.
*/
init(container: SVGElement) {
init(container: HTMLElement | SVGElement) {
if (!this.node.parentNode) {
container.appendChild(this.node);
}

View File

@ -323,7 +323,7 @@ class StencilShape extends Shape {
* direction - Optional direction of the shape to be darwn.
*/
computeAspect(
shape: Shape,
shape: Shape | null = null,
x: number,
y: number,
w: number,

View File

@ -1,11 +1,6 @@
import Graph from '../Graph';
import ImageBundle from './ImageBundle';
class GraphImage {
constructor(graph: Graph) {
this.imageBundles = [];
}
/**
* Holds the list of image bundles.
*/

View File

@ -8,7 +8,7 @@
type ImageMap = {
[key: string]: {
value: string;
fallback: Function;
fallback: string;
};
};
@ -83,7 +83,7 @@ class ImageBundle {
* Adds the specified entry to the map. The entry is an object with a value and
* fallback property as specified in the arguments.
*/
putImage(key: string, value: string, fallback: Function): void {
putImage(key: string, value: string, fallback: string) {
this.images[key] = { value, fallback };
}

View File

@ -1,7 +1,18 @@
import Cell from "../cell/datatypes/Cell";
import {getValue} from "../../util/Utils";
import Cell from '../cell/datatypes/Cell';
import { autoImplement, getValue } from '../../util/Utils';
class GraphLabel {
import type Graph from '../Graph';
import type GraphCells from '../cell/GraphCells';
import type GraphEdge from '../cell/edge/GraphEdge';
import type GraphVertex from '../cell/vertex/GraphVertex';
type PartialGraph = Pick<Graph, 'convertValueToString'>;
type PartialCells = Pick<GraphCells, 'getCurrentCellStyle' | 'isCellLocked'>;
type PartialEdge = Pick<GraphEdge, 'isEdgeLabelsMovable'>;
type PartialVertex = Pick<GraphVertex, 'isVertexLabelsMovable'>;
type PartialClass = PartialGraph & PartialCells & PartialEdge & PartialVertex;
class GraphLabel extends autoImplement<PartialClass>() {
/**
* Returns a string or DOM node that represents the label for the given
* cell. This implementation uses {@link convertValueToString} if {@link labelsVisible}
@ -53,7 +64,7 @@ class GraphLabel {
getLabel(cell: Cell): string | Node | null {
let result: string | null = '';
if (this.labelsVisible && cell != null) {
if (this.isLabelsVisible() && cell != null) {
const style = this.getCurrentCellStyle(cell);
if (!getValue(style, 'noLabel', false)) {
@ -73,6 +84,20 @@ class GraphLabel {
return this.isHtmlLabels();
}
/**
* Specifies if labels should be visible. This is used in {@link getLabel}. Default
* is true.
*/
labelsVisible: boolean = true;
isLabelsVisible = () => this.labelsVisible;
/**
* Specifies the return value for {@link isHtmlLabel}.
* @default false
*/
htmlLabels: boolean = false;
/**
* Returns {@link htmlLabels}.
*/
@ -154,8 +179,8 @@ class GraphLabel {
isLabelMovable(cell: Cell): boolean {
return (
!this.isCellLocked(cell) &&
((cell.isEdge() && this.edgeLabelsMovable) ||
(cell.isVertex() && this.vertexLabelsMovable))
((cell.isEdge() && this.isEdgeLabelsMovable()) ||
(cell.isVertex() && this.isVertexLabelsMovable()))
);
}
}

View File

@ -4,15 +4,24 @@ import EventObject from '../event/EventObject';
import InternalEvent from '../event/InternalEvent';
import Image from '../image/ImageBox';
import InternalMouseEvent from '../event/InternalMouseEvent';
import Graph from '../Graph';
import { autoImplement } from '../../util/Utils';
class Overlays {
constructor(graph: Graph) {
this.graph = graph;
}
import type Graph from '../Graph';
import type GraphSelection from '../selection/GraphSelection';
graph: Graph;
type PartialGraph = Pick<
Graph,
| 'getView'
| 'fireEvent'
| 'getModel'
| 'isEnabled'
| 'getWarningImage'
| 'getCellRenderer'
>;
type PartialSelection = Pick<GraphSelection, 'setSelectionCell'>;
type PartialClass = PartialGraph & PartialSelection;
class GraphOverlays extends autoImplement<PartialClass>() {
/*****************************************************************************
* Group: Overlays
*****************************************************************************/
@ -25,15 +34,13 @@ class Overlays {
* @param overlay {@link mxCellOverlay} to be added for the cell.
*/
addCellOverlay(cell: Cell, overlay: CellOverlay): CellOverlay {
if (cell.overlays == null) {
cell.overlays = [];
}
cell.overlays.push(overlay);
// Immediately update the cell display if the state exists
const state = this.getView().getState(cell);
if (state != null) {
this.cellRenderer.redraw(state);
if (state) {
this.getCellRenderer().redraw(state);
}
this.fireEvent(
@ -48,7 +55,7 @@ class Overlays {
*
* @param cell {@link mxCell} whose overlays should be returned.
*/
getCellOverlays(cell: Cell): CellOverlay[] | null {
getCellOverlays(cell: Cell) {
return cell.overlays;
}
@ -61,24 +68,20 @@ class Overlays {
* @param overlay Optional {@link CellOverlay} to be removed.
*/
// removeCellOverlay(cell: mxCell, overlay: mxCellOverlay): mxCellOverlay;
removeCellOverlay(cell: Cell, overlay: CellOverlay | null = null): any {
if (overlay == null) {
removeCellOverlay(cell: Cell, overlay: CellOverlay | null = null) {
if (!overlay) {
this.removeCellOverlays(cell);
} else {
const index = cell.overlays ? cell.overlays.indexOf(overlay) : -1;
const index = cell.overlays.indexOf(overlay);
if (index >= 0) {
(<CellOverlay[]>cell.overlays).splice(index, 1);
if ((<CellOverlay[]>cell.overlays).length === 0) {
cell.overlays = null;
}
cell.overlays.splice(index, 1);
// Immediately updates the cell display if the state exists
const state = this.getView().getState(cell);
if (state != null) {
this.cellRenderer.redraw(state);
if (state) {
this.getCellRenderer().redraw(state);
}
this.fireEvent(
@ -99,33 +102,31 @@ class Overlays {
*
* @param cell {@link mxCell} whose overlays should be removed
*/
removeCellOverlays(cell: Cell): CellOverlay[] {
removeCellOverlays(cell: Cell) {
const { overlays } = cell;
if (overlays != null) {
cell.overlays = null;
cell.overlays = [];
// Immediately updates the cell display if the state exists
const state = this.getView().getState(cell);
// Immediately updates the cell display if the state exists
const state = this.getView().getState(cell);
if (state != null) {
this.cellRenderer.redraw(state);
}
for (let i = 0; i < overlays.length; i += 1) {
this.fireEvent(
new EventObject(
InternalEvent.REMOVE_OVERLAY,
'cell',
cell,
'overlay',
overlays[i]
)
);
}
if (state) {
this.getCellRenderer().redraw(state);
}
return <CellOverlay[]>overlays;
for (let i = 0; i < overlays.length; i += 1) {
this.fireEvent(
new EventObject(
InternalEvent.REMOVE_OVERLAY,
'cell',
cell,
'overlay',
overlays[i]
)
);
}
return overlays;
}
/**
@ -137,15 +138,19 @@ class Overlays {
* @param cell Optional {@link Cell} that represents the root of the subtree to
* remove the overlays from. Default is the root in the model.
*/
clearCellOverlays(cell: Cell = <Cell>this.getModel().getRoot()): void {
this.removeCellOverlays(<Cell>cell);
clearCellOverlays(cell: Cell | null = null) {
cell = cell ?? this.getModel().getRoot();
if (!cell) return;
this.removeCellOverlays(cell);
// Recursively removes all overlays from the children
const childCount = cell.getChildCount();
for (let i = 0; i < childCount; i += 1) {
const child = cell.getChildAt(i);
this.clearCellOverlays(<Cell>child); // recurse
this.clearCellOverlays(child); // recurse
}
}
@ -171,12 +176,10 @@ class Overlays {
setCellWarning(
cell: Cell,
warning: string | null = null,
img: Image | null = null,
isSelect: boolean = false
): CellOverlay | null {
if (warning != null && warning.length > 0) {
img = img != null ? img : this.warningImage;
img: Image = this.getWarningImage(),
isSelect = false
) {
if (warning && warning.length > 0) {
// Creates the overlay with the image and warning
const overlay = new CellOverlay(img, `<font color=red>${warning}</font>`);
@ -201,4 +204,4 @@ class Overlays {
}
}
export default Overlays;
export default GraphOverlays;

View File

@ -8,7 +8,7 @@ import EventSource from '../event/EventSource';
import UndoableEdit from './UndoableEdit';
import CellPath from '../cell/datatypes/CellPath';
import Cell from '../cell/datatypes/Cell';
import utils, {isNumeric} from '../../util/Utils';
import utils, { isNumeric } from '../../util/Utils';
import EventObject from '../event/EventObject';
import InternalEvent from '../event/InternalEvent';
import Point from '../geometry/Point';
@ -20,8 +20,8 @@ import StyleChange from '../style/StyleChange';
import TerminalChange from '../cell/edge/TerminalChange';
import ValueChange from '../cell/ValueChange';
import VisibleChange from '../style/VisibleChange';
import Geometry from "../geometry/Geometry";
import CellArray from "../cell/datatypes/CellArray";
import Geometry from '../geometry/Geometry';
import CellArray from '../cell/datatypes/CellArray';
import type { CellMap, FilterFunction, UndoableChange } from '../../types';
@ -207,7 +207,7 @@ import type { CellMap, FilterFunction, UndoableChange } from '../../types';
* @class Model
*/
class Model extends EventSource {
constructor(root: Cell | null=null) {
constructor(root: Cell | null = null) {
super();
this.currentEdit = this.createUndoableEdit();
@ -320,16 +320,15 @@ class Model extends EventSource {
*
* @param {string} id A string representing the Id of the cell.
*/
getCell(id: string): Cell | null {
return this.cells != null ? this.cells[id] : null;
getCell(id: string) {
return this.cells ? this.cells[id] : null;
}
filterCells(cells: CellArray,
filter: FilterFunction): CellArray | null {
filterCells(cells: CellArray, filter: FilterFunction) {
return new CellArray(...cells).filterCells(filter);
}
getRoot(cell: Cell | null = null): Cell | null {
getRoot(cell: Cell | null = null) {
return cell ? cell.getRoot() : this.root;
}
@ -349,7 +348,7 @@ class Model extends EventSource {
*
* @param {Cell} root that specifies the new root.
*/
setRoot(root: Cell | null): Cell | null {
setRoot(root: Cell | null) {
this.execute(new RootChange(this, root));
return root;
}
@ -360,7 +359,7 @@ class Model extends EventSource {
*
* @param {Cell} root that specifies the new root.
*/
rootChanged(root: Cell | null): Cell | null {
rootChanged(root: Cell | null) {
const oldRoot = this.root;
this.root = root;
@ -378,7 +377,7 @@ class Model extends EventSource {
*
* @param {Cell} cell that represents the possible root.
*/
isRoot(cell: Cell | null=null): boolean {
isRoot(cell: Cell | null = null) {
return cell != null && this.root === cell;
}
@ -387,7 +386,7 @@ class Model extends EventSource {
*
* @param {Cell} cell that represents the possible layer.
*/
isLayer(cell: Cell): boolean {
isLayer(cell: Cell) {
return this.isRoot(cell.getParent());
}
@ -396,7 +395,7 @@ class Model extends EventSource {
*
* @param {Cell} cell that specifies the cell.
*/
contains(cell: Cell): boolean {
contains(cell: Cell) {
return (<Cell>this.root).isAncestor(cell);
}
@ -410,10 +409,7 @@ class Model extends EventSource {
* @param {Cell} child that specifies the child to be inserted.
* @param index Optional integer that specifies the index of the child.
*/
add(parent: Cell | null,
child: Cell | null,
index: number | null=null): Cell | null {
add(parent: Cell | null, child: Cell | null, index: number | null = null) {
if (child !== parent && parent != null && child != null) {
// Appends the child if no index was specified
if (index == null) {
@ -450,7 +446,7 @@ class Model extends EventSource {
*
* @param {Cell} cell that specifies the cell that has been added.
*/
cellAdded(cell: Cell | null): void {
cellAdded(cell: Cell | null) {
if (cell != null) {
// Creates an Id for the cell if not Id exists
if (cell.getId() == null && this.createIds) {
@ -497,7 +493,7 @@ class Model extends EventSource {
*
* @param {Cell} cell to create the Id for.
*/
createId(cell: Cell): string {
createId(cell: Cell) {
const id = this.nextId;
this.nextId++;
return this.prefix + id + this.postfix;
@ -507,9 +503,7 @@ class Model extends EventSource {
* Updates the parent for all edges that are connected to cell or one of
* its descendants using {@link updateEdgeParent}.
*/
updateEdgeParents(cell: Cell,
root: Cell=<Cell>this.getRoot(cell)): void {
updateEdgeParents(cell: Cell, root: Cell = <Cell>this.getRoot(cell)) {
// Updates edges on children first
const childCount = cell.getChildCount();
@ -545,9 +539,7 @@ class Model extends EventSource {
* @param {Cell} edge that specifies the edge.
* @param {Cell} root that represents the current root of the model.
*/
updateEdgeParent(edge: Cell,
root: Cell): void {
updateEdgeParent(edge: Cell, root: Cell): void {
let source = edge.getTerminal(true);
let target = edge.getTerminal(false);
let cell = null;
@ -583,7 +575,8 @@ class Model extends EventSource {
if (
cell != null &&
(cell.getParent() !== this.root || cell.isAncestor(edge)) &&
edge && edge.getParent() !== cell
edge &&
edge.getParent() !== cell
) {
let geo = edge.getGeometry();
@ -651,10 +644,7 @@ class Model extends EventSource {
* @param index Optional integer that defines the index of the child
* in the parent's child array.
*/
parentForCellChanged(cell: Cell,
parent: Cell | null,
index: number): Cell {
parentForCellChanged(cell: Cell, parent: Cell | null, index: number): Cell {
const previous = <Cell>cell.getParent();
if (parent != null) {
@ -690,10 +680,7 @@ class Model extends EventSource {
* target terminal of the edge.
*/
// setTerminal(edge: mxCell, terminal: mxCell, isSource: boolean): mxCell;
setTerminal(edge: Cell,
terminal: Cell | null,
isSource: boolean): Cell | null {
setTerminal(edge: Cell, terminal: Cell | null, isSource: boolean): Cell | null {
const terminalChanged = terminal !== edge.getTerminal(isSource);
this.execute(new TerminalChange(this, edge, terminal, isSource));
@ -712,10 +699,7 @@ class Model extends EventSource {
* @param {Cell} target that specifies the new target terminal.
*/
// setTerminals(edge: mxCell, source: mxCell, target: mxCell): void;
setTerminals(edge: Cell,
source: Cell | null,
target: Cell | null): void {
setTerminals(edge: Cell, source: Cell | null, target: Cell | null): void {
this.beginUpdate();
try {
this.setTerminal(edge, source, true);
@ -735,10 +719,11 @@ class Model extends EventSource {
* target terminal of the edge.
*/
// terminalForCellChanged(edge: mxCell, terminal: mxCell, isSource: boolean): mxCell;
terminalForCellChanged(edge: Cell,
terminal: Cell | null,
isSource: boolean=false): Cell | null {
terminalForCellChanged(
edge: Cell,
terminal: Cell | null,
isSource: boolean = false
): Cell | null {
const previous = edge.getTerminal(isSource);
if (terminal != null) {
terminal.insertEdge(edge, isSource);
@ -760,10 +745,7 @@ class Model extends EventSource {
* @param directed Optional boolean that specifies if the direction of the
* edge should be taken into account. Default is false.
*/
getEdgesBetween(source: Cell,
target: Cell,
directed: boolean=false): CellArray {
getEdgesBetween(source: Cell, target: Cell, directed: boolean = false): CellArray {
const tmp1 = source.getEdgeCount();
const tmp2 = target.getEdgeCount();
@ -803,8 +785,7 @@ class Model extends EventSource {
* @param {Cell} cell whose user object should be changed.
* @param value Object that defines the new user object.
*/
setValue(cell: Cell,
value: any): any {
setValue(cell: Cell, value: any): any {
this.execute(new ValueChange(this, cell, value));
return value;
}
@ -827,8 +808,7 @@ class Model extends EventSource {
* };
* ```
*/
valueForCellChanged(cell: Cell,
value: any): any {
valueForCellChanged(cell: Cell, value: any): any {
return cell.valueChanged(value);
}
@ -840,9 +820,7 @@ class Model extends EventSource {
* @param {Cell} cell whose geometry should be changed.
* @param {Geometry} geometry that defines the new geometry.
*/
setGeometry(cell: Cell,
geometry: Geometry): Geometry {
setGeometry(cell: Cell, geometry: Geometry): Geometry {
if (geometry !== cell.getGeometry()) {
this.execute(new GeometryChange(this, cell, geometry));
}
@ -853,9 +831,7 @@ class Model extends EventSource {
* Inner callback to update the {@link Geometry} of the given {@link Cell} using
* <mxCell.setGeometry> and return the previous {@link Geometry}.
*/
geometryForCellChanged(cell: Cell,
geometry: Geometry | null): Geometry | null {
geometryForCellChanged(cell: Cell, geometry: Geometry | null): Geometry | null {
const previous = cell.getGeometry();
cell.setGeometry(geometry);
return previous;
@ -869,13 +845,10 @@ class Model extends EventSource {
* @param style String of the form [stylename;|key=value;] to specify
* the new cell style.
*/
setStyle(cell: Cell,
style: string): string {
setStyle(cell: Cell, style: string | null) {
if (style !== cell.getStyle()) {
this.execute(new StyleChange(this, cell, style));
}
return style;
}
/**
@ -886,9 +859,7 @@ class Model extends EventSource {
* @param style String of the form [stylename;|key=value;] to specify
* the new cell style.
*/
styleForCellChanged(cell: Cell,
style: string | null): string | null {
styleForCellChanged(cell: Cell, style: string | null) {
const previous = cell.getStyle();
cell.setStyle(style);
return previous;
@ -901,9 +872,7 @@ class Model extends EventSource {
* @param {Cell} cell whose collapsed state should be changed.
* @param collapsed Boolean that specifies the new collpased state.
*/
setCollapsed(cell: Cell,
collapsed: boolean): boolean {
setCollapsed(cell: Cell, collapsed: boolean): boolean {
if (collapsed !== cell.isCollapsed()) {
this.execute(new CollapseChange(this, cell, collapsed));
}
@ -918,9 +887,7 @@ class Model extends EventSource {
* @param {Cell} cell that specifies the cell to be updated.
* @param collapsed Boolean that specifies the new collpased state.
*/
collapsedStateForCellChanged(cell: Cell,
collapsed: boolean): boolean {
collapsedStateForCellChanged(cell: Cell, collapsed: boolean): boolean {
const previous = cell.isCollapsed();
cell.setCollapsed(collapsed);
return previous;
@ -933,9 +900,7 @@ class Model extends EventSource {
* @param {Cell} cell whose visible state should be changed.
* @param visible Boolean that specifies the new visible state.
*/
setVisible(cell: Cell,
visible: boolean): boolean {
setVisible(cell: Cell, visible: boolean): boolean {
if (visible !== cell.isVisible()) {
this.execute(new VisibleChange(this, cell, visible));
}
@ -950,8 +915,7 @@ class Model extends EventSource {
* @param {Cell} cell that specifies the cell to be updated.
* @param visible Boolean that specifies the new visible state.
*/
visibleStateForCellChanged(cell: Cell,
visible: boolean): boolean {
visibleStateForCellChanged(cell: Cell, visible: boolean): boolean {
const previous = cell.isVisible();
cell.setVisible(visible);
return previous;
@ -1045,9 +1009,7 @@ class Model extends EventSource {
if (!this.endingUpdate) {
this.endingUpdate = this.updateLevel === 0;
this.fireEvent(
new EventObject(InternalEvent.END_UPDATE, 'edit', this.currentEdit)
);
this.fireEvent(new EventObject(InternalEvent.END_UPDATE, 'edit', this.currentEdit));
try {
if (this.endingUpdate && !this.currentEdit.isEmpty()) {
@ -1073,7 +1035,7 @@ class Model extends EventSource {
* @param significant Optional boolean that specifies if the edit to be created is
* significant. Default is true.
*/
createUndoableEdit(significant: boolean=true): UndoableEdit {
createUndoableEdit(significant: boolean = true): UndoableEdit {
const edit = new UndoableEdit(this, significant);
edit.notify = () => {
@ -1100,10 +1062,7 @@ class Model extends EventSource {
* source edges.
*/
// mergeChildren(from: Transactions, to: Transactions, cloneAllEdges?: boolean): void;
mergeChildren(from: Cell,
to: Cell,
cloneAllEdges: boolean=true): void {
mergeChildren(from: Cell, to: Cell, cloneAllEdges: boolean = true): void {
this.beginUpdate();
try {
const mapping: any = {};
@ -1140,11 +1099,7 @@ class Model extends EventSource {
* that was inserted into this model.
*/
// mergeChildrenImpl(from: Transactions, to: Transactions, cloneAllEdges: boolean, mapping: any): void;
mergeChildrenImpl(from: Cell,
to: Cell,
cloneAllEdges: boolean,
mapping: any={}) {
mergeChildrenImpl(from: Cell, to: Cell, cloneAllEdges: boolean, mapping: any = {}) {
this.beginUpdate();
try {
const childCount = from.getChildCount();
@ -1155,9 +1110,7 @@ class Model extends EventSource {
if (typeof cell.getId === 'function') {
const id: string = <string>cell.getId();
let target =
id != null && (!cell.isEdge() || !cloneAllEdges)
? this.getCell(id)
: null;
id != null && (!cell.isEdge() || !cloneAllEdges) ? this.getCell(id) : null;
// Clones and adds the child if no cell exists for the id
if (target == null) {
@ -1198,8 +1151,7 @@ class Model extends EventSource {
*
* @param {Cell} cell to be cloned.
*/
cloneCell(cell: Cell | null,
includeChildren: boolean): Cell | null {
cloneCell(cell: Cell | null, includeChildren: boolean): Cell | null {
if (cell != null) {
return new CellArray(cell).cloneCells(includeChildren)[0];
}

View File

@ -1,28 +1,33 @@
import Cell from '../cell/datatypes/Cell';
import CellArray from '../cell/datatypes/CellArray';
import Rectangle from '../geometry/Rectangle';
import InternalMouseEvent from '../event/InternalMouseEvent';
import graph from '../Graph';
import mxClient from '../../mxClient';
import SelectionChange from './SelectionChange';
import UndoableEdit from '../model/UndoableEdit';
import EventObject from '../event/EventObject';
import InternalEvent from '../event/InternalEvent';
import EventSource from '../event/EventSource';
import Dictionary from '../../util/Dictionary';
import RootChange from '../model/RootChange';
import ChildChange from '../model/ChildChange';
import { autoImplement } from '../../util/Utils';
class GraphSelection extends EventSource {
constructor(graph: graph) {
super();
import type GraphCells from '../cell/GraphCells';
import type Graph from '../Graph';
import type GraphEvents from '../event/GraphEvents';
import type EventSource from '../event/EventSource';
this.graph = graph;
this.cells = new CellArray();
}
type PartialGraph = Pick<
Graph,
'fireEvent' | 'getDefaultParent' | 'getView' | 'getCurrentRoot' | 'getModel'
>;
type PartialCells = Pick<GraphCells, 'isCellSelectable' | 'getCells'>;
type PartialEvents = Pick<GraphEvents, 'isToggleEvent'>;
type PartialClass = PartialGraph & PartialCells & PartialEvents & EventSource;
// @ts-ignore recursive reference error
class GraphSelection extends autoImplement<PartialClass>() {
// TODO: Document me!!
cells: CellArray;
cells: CellArray = new CellArray();
/**
* Specifies the resource key for the status message after a long operation.
@ -39,11 +44,6 @@ class GraphSelection extends EventSource {
updatingSelectionResource: string =
mxClient.language !== 'none' ? 'updatingSelection' : '';
/**
* Reference to the enclosing {@link graph}.
*/
graph: graph;
/**
* Specifies if only one selected item at a time is allowed.
* Default is false.
@ -87,17 +87,14 @@ class GraphSelection extends EventSource {
/**
* Returns true if the given {@link Cell} is selected.
*/
isSelected(cell: Cell): boolean {
if (cell != null) {
return this.cells.indexOf(cell) >= 0;
}
return false;
isSelected(cell: Cell) {
return this.cells.indexOf(cell) >= 0;
}
/**
* Returns true if no cells are currently selected.
*/
isEmpty(): boolean {
isEmpty() {
return this.cells.length === 0;
}
@ -105,7 +102,7 @@ class GraphSelection extends EventSource {
* Clears the selection and fires a {@link change} event if the selection was not
* empty.
*/
clear(): void {
clear() {
this.changeSelection(null, this.cells);
}
@ -114,10 +111,8 @@ class GraphSelection extends EventSource {
*
* @param cell {@link mxCell} to be selected.
*/
setCell(cell: Cell | null): void {
if (cell != null) {
this.setCells(new CellArray(cell));
}
setCell(cell: Cell) {
this.setCells(new CellArray(cell));
}
/**
@ -126,32 +121,29 @@ class GraphSelection extends EventSource {
* @param cells Array of {@link Cell} to be selected.
*/
setCells(cells: CellArray): void {
if (cells != null) {
if (this.singleSelection) {
cells = new CellArray(<Cell>this.getFirstSelectableCell(cells));
}
const tmp = new CellArray();
for (let i = 0; i < cells.length; i += 1) {
if ((<graph>this.graph).isCellSelectable(cells[i])) {
tmp.push(cells[i]);
}
}
this.changeSelection(tmp, this.cells);
if (this.singleSelection) {
cells = new CellArray(<Cell>this.getFirstSelectableCell(cells));
}
const tmp = new CellArray();
for (let i = 0; i < cells.length; i += 1) {
if (this.isCellSelectable(cells[i])) {
tmp.push(cells[i]);
}
}
this.changeSelection(tmp, this.cells);
}
/**
* Returns the first selectable cell in the given array of cells.
*/
getFirstSelectableCell(cells: CellArray): Cell | null {
if (cells != null) {
for (let i = 0; i < cells.length; i += 1) {
if ((<graph>this.graph).isCellSelectable(cells[i])) {
return cells[i];
}
getFirstSelectableCell(cells: CellArray) {
for (let i = 0; i < cells.length; i += 1) {
if (this.isCellSelectable(cells[i])) {
return cells[i];
}
}
return null;
}
@ -160,10 +152,8 @@ class GraphSelection extends EventSource {
*
* @param cell {@link mxCell} to add to the selection.
*/
addCell(cell: Cell | null = null): void {
if (cell != null) {
this.addCells(new CellArray(cell));
}
addCell(cell: Cell) {
this.addCells(new CellArray(cell));
}
/**
@ -172,26 +162,24 @@ class GraphSelection extends EventSource {
*
* @param cells Array of {@link Cell} to add to the selection.
*/
addCells(cells: CellArray): void {
if (cells != null) {
let remove = null;
if (this.singleSelection) {
remove = this.cells;
cells = new CellArray(<Cell>this.getFirstSelectableCell(cells));
}
addCells(cells: CellArray) {
let remove = null;
if (this.singleSelection) {
remove = this.cells;
const tmp = new CellArray();
for (let i = 0; i < cells.length; i += 1) {
if (
!this.isSelected(cells[i]) &&
(<graph>this.graph).isCellSelectable(cells[i])
) {
tmp.push(cells[i]);
}
}
const selectableCell = this.getFirstSelectableCell(cells);
this.changeSelection(tmp, remove);
cells = selectableCell ? new CellArray(selectableCell) : new CellArray();
}
const tmp = new CellArray();
for (let i = 0; i < cells.length; i += 1) {
if (!this.isSelected(cells[i]) && this.isCellSelectable(cells[i])) {
tmp.push(cells[i]);
}
}
this.changeSelection(tmp, remove);
}
/**
@ -200,10 +188,8 @@ class GraphSelection extends EventSource {
*
* @param cell {@link mxCell} to remove from the selection.
*/
removeCell(cell: Cell | null = null): void {
if (cell != null) {
this.removeCells(new CellArray(cell));
}
removeCell(cell: Cell) {
this.removeCells(new CellArray(cell));
}
/**
@ -212,16 +198,16 @@ class GraphSelection extends EventSource {
*
* @param cells {@link mxCell}s to remove from the selection.
*/
removeCells(cells: CellArray | null = null): void {
if (cells != null) {
const tmp = new CellArray();
for (let i = 0; i < cells.length; i += 1) {
if (this.isSelected(cells[i])) {
tmp.push(cells[i]);
}
removeCells(cells: CellArray) {
const tmp = new CellArray();
for (let i = 0; i < cells.length; i += 1) {
if (this.isSelected(cells[i])) {
tmp.push(cells[i]);
}
this.changeSelection(null, tmp);
}
this.changeSelection(null, tmp);
}
/**
@ -230,13 +216,10 @@ class GraphSelection extends EventSource {
* @param added Array of {@link Cell} to add to the selection.
* @param remove Array of {@link Cell} to remove from the selection.
*/
changeSelection(
added: CellArray | null = null,
removed: CellArray | null = null
): void {
changeSelection(added: CellArray | null = null, removed: CellArray | null = null) {
if (
(added != null && added.length > 0 && added[0] != null) ||
(removed != null && removed.length > 0 && removed[0] != null)
(added && added.length > 0 && added[0]) ||
(removed && removed.length > 0 && removed[0])
) {
const change = new SelectionChange(
this,
@ -258,8 +241,8 @@ class GraphSelection extends EventSource {
*
* @param cell {@link mxCell} to add to the selection.
*/
cellAdded(cell: Cell): void {
if (cell != null && !this.isSelected(cell)) {
cellAdded(cell: Cell) {
if (!this.isSelected(cell)) {
this.cells.push(cell);
}
}
@ -270,12 +253,10 @@ class GraphSelection extends EventSource {
*
* @param cell {@link mxCell} to remove from the selection.
*/
cellRemoved(cell: Cell): void {
if (cell != null) {
const index = this.cells.indexOf(cell);
if (index >= 0) {
this.cells.splice(index, 1);
}
cellRemoved(cell: Cell) {
const index = this.cells.indexOf(cell);
if (index >= 0) {
this.cells.splice(index, 1);
}
}
@ -288,42 +269,42 @@ class GraphSelection extends EventSource {
*
* @param cell {@link mxCell} for which the selection state should be returned.
*/
isCellSelected(cell: Cell): boolean {
isCellSelected(cell: Cell) {
return this.isSelected(cell);
}
/**
* Returns true if the selection is empty.
*/
isSelectionEmpty(): boolean {
isSelectionEmpty() {
return this.isEmpty();
}
/**
* Clears the selection using {@link mxGraphSelectionModel.clear}.
*/
clearSelection(): void {
return this.clear();
clearSelection() {
this.clear();
}
/**
* Returns the number of selected cells.
*/
getSelectionCount(): number {
getSelectionCount() {
return this.cells.length;
}
/**
* Returns the first cell from the array of selected {@link Cell}.
*/
getSelectionCell(): Cell {
getSelectionCell() {
return this.cells[0];
}
/**
* Returns the array of selected {@link Cell}.
*/
getSelectionCells(): CellArray {
getSelectionCells() {
return this.cells.slice();
}
@ -332,7 +313,7 @@ class GraphSelection extends EventSource {
*
* @param cell {@link mxCell} to be selected.
*/
setSelectionCell(cell: Cell | null): void {
setSelectionCell(cell: Cell) {
this.setCell(cell);
}
@ -341,7 +322,7 @@ class GraphSelection extends EventSource {
*
* @param cells Array of {@link Cell} to be selected.
*/
setSelectionCells(cells: CellArray): void {
setSelectionCells(cells: CellArray) {
this.setCells(cells);
}
@ -350,7 +331,7 @@ class GraphSelection extends EventSource {
*
* @param cell {@link mxCell} to be add to the selection.
*/
addSelectionCell(cell: Cell): void {
addSelectionCell(cell: Cell) {
this.addCell(cell);
}
@ -359,7 +340,7 @@ class GraphSelection extends EventSource {
*
* @param cells Array of {@link Cell} to be added to the selection.
*/
addSelectionCells(cells: CellArray): void {
addSelectionCells(cells: CellArray) {
this.addCells(cells);
}
@ -368,7 +349,7 @@ class GraphSelection extends EventSource {
*
* @param cell {@link mxCell} to be removed from the selection.
*/
removeSelectionCell(cell: Cell): void {
removeSelectionCell(cell: Cell) {
this.removeCell(cell);
}
@ -377,7 +358,7 @@ class GraphSelection extends EventSource {
*
* @param cells Array of {@link Cell} to be removed from the selection.
*/
removeSelectionCells(cells: CellArray): void {
removeSelectionCells(cells: CellArray) {
this.removeCells(cells);
}
@ -389,8 +370,8 @@ class GraphSelection extends EventSource {
* @param evt Mouseevent that triggered the selection.
*/
// selectRegion(rect: mxRectangle, evt: Event): mxCellArray;
selectRegion(rect: Rectangle, evt: InternalMouseEvent): CellArray | null {
const cells = this.graph.getCells(rect.x, rect.y, rect.width, rect.height);
selectRegion(rect: Rectangle, evt: MouseEvent) {
const cells = this.getCells(rect.x, rect.y, rect.width, rect.height);
this.selectCellsForEvent(cells, evt);
return cells;
}
@ -398,28 +379,28 @@ class GraphSelection extends EventSource {
/**
* Selects the next cell.
*/
selectNextCell(): void {
selectNextCell() {
this.selectCell(true);
}
/**
* Selects the previous cell.
*/
selectPreviousCell(): void {
selectPreviousCell() {
this.selectCell();
}
/**
* Selects the parent cell.
*/
selectParentCell(): void {
selectParentCell() {
this.selectCell(false, true);
}
/**
* Selects the first child cell.
*/
selectChildCell(): void {
selectChildCell() {
this.selectCell(false, false, true);
}
@ -431,36 +412,29 @@ class GraphSelection extends EventSource {
* @param isParent Boolean indicating if the parent cell should be selected.
* @param isChild Boolean indicating if the first child cell should be selected.
*/
selectCell(
isNext: boolean = false,
isParent: boolean = false,
isChild: boolean = false
): void {
selectCell(isNext = false, isParent = false, isChild = false) {
const cell = this.cells.length > 0 ? this.cells[0] : null;
if (this.cells.length > 1) {
this.clear();
}
const parent = <Cell>(
(cell != null ? cell.getParent() : this.graph.getDefaultParent())
);
const parent = cell ? cell.getParent() : this.getDefaultParent();
const childCount = parent.getChildCount();
if (cell == null && childCount > 0) {
if (!cell && childCount > 0) {
const child = parent.getChildAt(0);
this.setSelectionCell(child);
} else if (
parent &&
(cell == null || isParent) &&
this.graph.getView().getState(parent) != null &&
parent.getGeometry() != null
(!cell || isParent) &&
this.getView().getState(parent) &&
parent.getGeometry()
) {
if (this.graph.getCurrentRoot() != parent) {
if (this.getCurrentRoot() !== parent) {
this.setSelectionCell(parent);
}
} else if (cell != null && isChild) {
} else if (cell && isChild) {
const tmp = cell.getChildCount();
if (tmp > 0) {
@ -468,7 +442,7 @@ class GraphSelection extends EventSource {
this.setSelectionCell(child);
}
} else if (childCount > 0) {
let i = (<Cell>parent).getIndex(cell);
let i = parent.getIndex(cell);
if (isNext) {
i++;
@ -493,32 +467,27 @@ class GraphSelection extends EventSource {
* @param descendants Optional boolean specifying whether all descendants should be
* selected. Default is `false`.
*/
selectAll(
parent: Cell = this.graph.getDefaultParent(),
descendants: boolean = false
): void {
selectAll(parent: Cell = this.getDefaultParent(), descendants: boolean = false) {
const cells = descendants
? parent.filterDescendants((cell: Cell) => {
return cell != parent && this.graph.getView().getState(cell) != null;
return cell !== parent && !!this.getView().getState(cell);
})
: parent.getChildren();
if (cells != null) {
this.setSelectionCells(cells);
}
this.setSelectionCells(cells);
}
/**
* Select all vertices inside the given parent or the default parent.
*/
selectVertices(parent: Cell, selectGroups: boolean = false): void {
selectVertices(parent: Cell, selectGroups = false) {
this.selectCells(true, false, parent, selectGroups);
}
/**
* Select all vertices inside the given parent or the default parent.
*/
selectEdges(parent: Cell): void {
selectEdges(parent: Cell) {
this.selectCells(false, true, parent);
}
@ -536,27 +505,25 @@ class GraphSelection extends EventSource {
* selected. Default is `false`.
*/
selectCells(
vertices: boolean = false,
edges: boolean = false,
parent: Cell = this.graph.getDefaultParent(),
selectGroups: boolean = false
): void {
vertices = false,
edges = false,
parent: Cell = this.getDefaultParent(),
selectGroups = false
) {
const filter = (cell: Cell) => {
return (
this.graph.getView().getState(cell) != null &&
(((selectGroups || cell.getChildCount() == 0) &&
this.getView().getState(cell) &&
(((selectGroups || cell.getChildCount() === 0) &&
cell.isVertex() &&
vertices &&
cell.getParent() &&
!(<Cell>cell.getParent()).isEdge()) ||
!cell.getParent().isEdge()) ||
(cell.isEdge() && edges))
);
};
const cells = parent.filterDescendants(filter);
if (cells != null) {
this.setSelectionCells(cells);
}
this.setSelectionCells(cells);
}
/**
@ -567,16 +534,16 @@ class GraphSelection extends EventSource {
* @param cell {@link mxCell} to be selected.
* @param evt Optional mouseevent that triggered the selection.
*/
selectCellForEvent(cell: Cell, evt: InternalMouseEvent): void {
selectCellForEvent(cell: Cell, evt: MouseEvent) {
const isSelected = this.isCellSelected(cell);
if (this.graph.isToggleEvent(evt)) {
if (this.isToggleEvent(evt)) {
if (isSelected) {
this.removeSelectionCell(cell);
} else {
this.addSelectionCell(cell);
}
} else if (!isSelected || this.getSelectionCount() != 1) {
} else if (!isSelected || this.getSelectionCount() !== 1) {
this.setSelectionCell(cell);
}
}
@ -589,8 +556,8 @@ class GraphSelection extends EventSource {
* @param cells Array of {@link Cell} to be selected.
* @param evt Optional mouseevent that triggered the selection.
*/
selectCellsForEvent(cells: CellArray, evt: InternalMouseEvent): void {
if (this.graph.isToggleEvent(evt)) {
selectCellsForEvent(cells: CellArray, evt: MouseEvent) {
if (this.isToggleEvent(evt)) {
this.addSelectionCells(cells);
} else {
this.setSelectionCells(cells);
@ -600,16 +567,17 @@ class GraphSelection extends EventSource {
/**
* Returns true if any sibling of the given cell is selected.
*/
isSiblingSelected(cell: Cell): boolean {
const parent = <Cell>cell.getParent();
isSiblingSelected(cell: Cell) {
const parent = cell.getParent();
const childCount = parent.getChildCount();
for (let i = 0; i < childCount; i += 1) {
const child = <Cell>parent.getChildAt(i);
const child = parent.getChildAt(i);
if (cell !== child && this.isCellSelected(child)) {
return true;
}
}
return false;
}
@ -628,10 +596,7 @@ class GraphSelection extends EventSource {
* change should be ignored.
*
*/
getSelectionCellsForChanges(
changes: any[],
ignoreFn: Function | null = null
): CellArray {
getSelectionCellsForChanges(changes: any[], ignoreFn: Function | null = null) {
const dict = new Dictionary();
const cells: CellArray = new CellArray();
@ -644,7 +609,7 @@ class GraphSelection extends EventSource {
const childCount = cell.getChildCount();
for (let i = 0; i < childCount; i += 1) {
addCell(<Cell>cell.getChildAt(i));
addCell(cell.getChildAt(i));
}
}
}
@ -653,16 +618,16 @@ class GraphSelection extends EventSource {
for (let i = 0; i < changes.length; i += 1) {
const change = changes[i];
if (change.constructor !== RootChange && (ignoreFn == null || !ignoreFn(change))) {
if (change.constructor !== RootChange && (!ignoreFn || !ignoreFn(change))) {
let cell = null;
if (change instanceof ChildChange) {
cell = change.child;
} else if (change.cell != null && change.cell instanceof Cell) {
} else if (change.cell && change.cell instanceof Cell) {
cell = change.cell;
}
if (cell != null) {
if (cell) {
addCell(cell);
}
}
@ -673,7 +638,7 @@ class GraphSelection extends EventSource {
/**
* Removes selection cells that are not in the model from the selection.
*/
updateSelection(): void {
updateSelection() {
const cells = this.getSelectionCells();
const removed = new CellArray();
@ -683,7 +648,7 @@ class GraphSelection extends EventSource {
} else {
let par = cell.getParent();
while (par != null && par !== this.view.currentRoot) {
while (par && par !== this.getView().currentRoot) {
if (par.isCollapsed() || !par.isVisible()) {
removed.push(cell);
break;
@ -693,7 +658,7 @@ class GraphSelection extends EventSource {
}
}
}
this.selection.removeSelectionCells(removed);
this.removeSelectionCells(removed);
}
}

View File

@ -18,28 +18,32 @@ import Rectangle from '../geometry/Rectangle';
import { isAltDown, isMultiTouchEvent } from '../../util/EventUtils';
import { clearSelection } from '../../util/DomUtils';
import Graph from '../Graph';
import { GraphPlugin } from '../../types';
import EventObject from '../event/EventObject';
/**
* Event handler that selects rectangular regions.
* This is not built-into [mxGraph].
* To enable rubberband selection in a graph, use the following code.
*/
class RubberBand {
forceRubberbandHandler: Function;
panHandler: Function;
gestureHandler: Function;
graph: Graph;
class RubberBand implements GraphPlugin {
forceRubberbandHandler?: Function;
panHandler?: Function;
gestureHandler?: Function;
graph?: Graph;
first: Point | null = null;
destroyed: boolean = false;
dragHandler?: Function;
dropHandler?: Function;
constructor(graph: Graph) {
constructor() {}
onInit(graph: Graph) {
this.graph = graph;
this.graph.addMouseListener(this);
// Handles force rubberband event
this.forceRubberbandHandler = (sender, evt) => {
this.forceRubberbandHandler = (sender: any, evt: EventObject) => {
const evtName = evt.getProperty('eventName');
const me = evt.getProperty('event');
@ -379,7 +383,7 @@ class RubberBand {
* normally not need to be called, it is called automatically when the
* window unloads.
*/
destroy() {
onDestroy() {
if (!this.destroyed) {
this.destroyed = true;
this.graph.removeMouseListener(this);

View File

@ -1,27 +1,27 @@
import Point from "../geometry/Point";
import Rectangle from "../geometry/Rectangle";
import Graph from '../Graph';
import { autoImplement } from '../../util/Utils';
import Point from '../geometry/Point';
import Rectangle from '../geometry/Rectangle';
class GraphSnap {
constructor(graph: Graph) {
this.graph = graph;
}
import type Graph from '../Graph';
type PartialGraph = Pick<Graph, 'getView'>;
type PartialClass = PartialGraph;
class GraphSnap extends autoImplement<PartialClass>() {
// TODO: Document me!
tolerance: number | null = null;
graph: Graph;
tolerance: number = 0;
/**
* Specifies the grid size.
* @default 10
*/
gridSize: number = 10;
gridSize = 10;
/**
* Specifies if the grid is enabled. This is used in {@link snap}.
* @default true
*/
gridEnabled: boolean = true;
gridEnabled = true;
/*****************************************************************************
* Group: Graph display
@ -32,7 +32,7 @@ class GraphSnap {
*
* @param value Numeric value to be snapped to the grid.
*/
snap(value: number): number {
snap(value: number) {
if (this.gridEnabled) {
value = Math.round(value / this.gridSize) * this.gridSize;
}
@ -47,12 +47,12 @@ class GraphSnap {
snapDelta(
delta: Point,
bounds: Rectangle,
ignoreGrid: boolean = false,
ignoreHorizontal: boolean = false,
ignoreVertical: boolean = false
): Point {
const t = this.graph.view.translate;
const s = this.graph.view.scale;
ignoreGrid = false,
ignoreHorizontal = false,
ignoreVertical = false
) {
const t = this.getView().translate;
const s = this.getView().scale;
if (!ignoreGrid && this.gridEnabled) {
const tol = this.gridSize * s * 0.5;
@ -109,7 +109,7 @@ class GraphSnap {
/**
* Returns {@link gridEnabled} as a boolean.
*/
isGridEnabled(): boolean {
isGridEnabled() {
return this.gridEnabled;
}
@ -118,36 +118,35 @@ class GraphSnap {
*
* @param value Boolean indicating if the grid should be enabled.
*/
setGridEnabled(value: boolean): void {
setGridEnabled(value: boolean) {
this.gridEnabled = value;
}
/**
* Returns {@link gridSize}.
*/
getGridSize(): number {
getGridSize() {
return this.gridSize;
}
/**
* Sets {@link gridSize}.
*/
setGridSize(value: number): void {
setGridSize(value: number) {
this.gridSize = value;
}
/**
* Returns {@link tolerance}.
*/
getTolerance(): number | null {
getTolerance() {
return this.tolerance;
}
/**
* Sets {@link tolerance}.
*/
setTolerance(value: number): void {
setTolerance(value: number) {
this.tolerance = value;
}
}

View File

@ -1,6 +1,4 @@
import {Style} from "util";
class StyleMap {
class StyleMap implements Record<string, any> {
defaultVertex?: StyleMap;
defaultEdge?: StyleMap;
@ -160,7 +158,6 @@ class StyleMap {
*/
exitY: any;
/**
* Variable: STYLE_EXIT_DX
*

View File

@ -10,12 +10,13 @@ import {
ARROW_CLASSIC,
NONE,
SHAPE_CONNECTOR,
SHAPE_RECTANGLE
SHAPE_RECTANGLE,
} from '../../util/Constants';
import Perimeter from './Perimeter';
import utils from '../../util/Utils';
import { isNumeric } from '../../util/Utils';
import { clone } from '../../util/CloneUtils';
import StyleMap from "./StyleMap";
import type { CellStateStyles } from '../../types';
/**
* @class Stylesheet
@ -68,7 +69,7 @@ import StyleMap from "./StyleMap";
*/
class Stylesheet {
constructor() {
this.styles = new StyleMap();
this.styles = {} as CellStateStyles;
this.putDefaultVertexStyle(this.createDefaultVertexStyle());
this.putDefaultEdgeStyle(this.createDefaultEdgeStyle());
@ -78,13 +79,13 @@ class Stylesheet {
* Maps from names to cell styles. Each cell style is a map of key,
* value pairs.
*/
styles: StyleMap;
styles: CellStateStyles;
/**
* Creates and returns the default vertex style.
*/
createDefaultVertexStyle(): StyleMap {
const style = new StyleMap();
createDefaultVertexStyle() {
const style = {} as CellStateStyles;
style.shape = SHAPE_RECTANGLE;
style.perimeter = Perimeter.RectanglePerimeter;
style.verticalAlign = ALIGN_MIDDLE;
@ -98,8 +99,8 @@ class Stylesheet {
/**
* Creates and returns the default edge style.
*/
createDefaultEdgeStyle(): StyleMap {
const style = new StyleMap();
createDefaultEdgeStyle() {
const style = {} as CellStateStyles;
style.shape = SHAPE_CONNECTOR;
style.endArrow = ARROW_CLASSIC;
style.verticalAlign = ALIGN_MIDDLE;
@ -114,29 +115,29 @@ class Stylesheet {
* stylename.
* @param style Key, value pairs that define the style.
*/
putDefaultVertexStyle(style: StyleMap): void {
putDefaultVertexStyle(style: CellStateStyles) {
this.putCellStyle('defaultVertex', style);
}
/**
* Sets the default style for edges using defaultEdge as the stylename.
*/
putDefaultEdgeStyle(style: StyleMap): void {
putDefaultEdgeStyle(style: CellStateStyles) {
this.putCellStyle('defaultEdge', style);
}
/**
* Returns the default style for vertices.
*/
getDefaultVertexStyle(): StyleMap {
return <StyleMap>this.styles.defaultVertex;
getDefaultVertexStyle() {
return this.styles.defaultVertex;
}
/**
* Sets the default style for edges.
*/
getDefaultEdgeStyle(): StyleMap {
return <StyleMap>this.styles.defaultEdge;
getDefaultEdgeStyle() {
return this.styles.defaultEdge;
}
/**
@ -172,8 +173,11 @@ class Stylesheet {
* @param name Name for the style to be stored.
* @param style Key, value pairs that define the style.
*/
putCellStyle(name: string, style: StyleMap): void {
this.styles[name] = style;
putCellStyle(
name: keyof CellStateStyles,
style: CellStateStyles[keyof CellStateStyles]
) {
(this.styles[name] as any) = style;
}
/**
@ -183,18 +187,16 @@ class Stylesheet {
* @param name String of the form [(stylename|key=value);] that represents the style.
* @param defaultStyle Default style to be returned if no style can be found.
*/
getCellStyle(name: string,
defaultStyle: StyleMap=new StyleMap()): StyleMap {
getCellStyle(name: string, defaultStyle: CellStateStyles) {
let style = defaultStyle;
if (name != null && name.length > 0) {
if (name.length > 0) {
const pairs = name.split(';');
if (style != null && name.charAt(0) !== ';') {
if (style && name.charAt(0) !== ';') {
style = clone(style);
} else {
style = new StyleMap();
style = {} as CellStateStyles;
}
// Parses each key, value pair into the existing style
@ -202,23 +204,24 @@ class Stylesheet {
const pos = tmp.indexOf('=');
if (pos >= 0) {
const key = tmp.substring(0, pos);
const key = tmp.substring(0, pos) as keyof CellStateStyles;
const value = tmp.substring(pos + 1);
if (value === NONE) {
delete style[key];
} else if (utils.isNumeric(value)) {
style[key] = parseFloat(value);
} else if (isNumeric(value)) {
(style[key] as any) = parseFloat(value);
} else {
style[key] = value;
(style[key] as any) = value;
}
} else {
// Merges the entries from a named style
const tmpStyle = this.styles[tmp];
const tmpStyle = this.styles[tmp as keyof CellStateStyles] as CellStateStyles;
if (tmpStyle != null) {
if (tmpStyle && typeof tmpStyle === 'object') {
for (const key in tmpStyle) {
style[key] = tmpStyle[key];
const k = key as keyof CellStateStyles;
(style[k] as any) = tmpStyle[k];
}
}
}
@ -229,4 +232,3 @@ class Stylesheet {
}
export default Stylesheet;
// import('../../../serialization/mxStylesheetCodec');

View File

@ -1,15 +1,14 @@
import CellArray from "../cell/datatypes/CellArray";
import Cell from "../cell/datatypes/Cell";
import Dictionary from "../../util/Dictionary";
import Graph from '../Graph';
import CellArray from '../cell/datatypes/CellArray';
import Cell from '../cell/datatypes/Cell';
import Dictionary from '../../util/Dictionary';
import { autoImplement } from '../../util/Utils';
class GraphTerminal {
constructor(graph: Graph) {
this.graph = graph;
}
import type Graph from '../Graph';
graph: Graph;
type PartialGraph = Pick<Graph, 'getView'>;
type PartialClass = PartialGraph;
class GraphTerminal extends autoImplement<PartialClass>() {
/*****************************************************************************
* Group: Graph behaviour
*****************************************************************************/
@ -24,7 +23,7 @@ class GraphTerminal {
* @param cell {@link mxCell} whose terminal point should be moved.
* @param source Boolean indicating if the source or target terminal should be moved.
*/
isTerminalPointMovable(cell: Cell, source: boolean): boolean {
isTerminalPointMovable(cell: Cell, source: boolean) {
return true;
}
@ -48,48 +47,36 @@ class GraphTerminal {
getOpposites(
edges: CellArray,
terminal: Cell | null = null,
sources: boolean = true,
targets: boolean = true
sources = true,
targets = true
): CellArray {
const terminals = new CellArray();
// Fast lookup to avoid duplicates in terminals array
const dict = new Dictionary();
const dict = new Dictionary<Cell, boolean>();
for (let i = 0; i < edges.length; i += 1) {
const state = this.graph.view.getState(edges[i]);
const state = this.getView().getState(edges[i]);
const source =
state != null
? state.getVisibleTerminal(true)
: this.graph.view.getVisibleTerminal(edges[i], true);
const target =
state != null
? state.getVisibleTerminal(false)
: this.graph.view.getVisibleTerminal(edges[i], false);
const source = state
? state.getVisibleTerminal(true)
: this.getView().getVisibleTerminal(edges[i], true);
const target = state
? state.getVisibleTerminal(false)
: this.getView().getVisibleTerminal(edges[i], false);
// Checks if the terminal is the source of the edge and if the
// target should be stored in the result
if (
source == terminal &&
target != null &&
target != terminal &&
targets
) {
if (source === terminal && target && target !== terminal && targets) {
if (!dict.get(target)) {
dict.put(target, true);
terminals.push(target);
}
}
// Checks if the terminal is the taget of the edge and if the
// Checks if the terminal is the taget of the edge and if the
// source should be stored in the result
else if (
target == terminal &&
source != null &&
source != terminal &&
sources
) {
else if (target === terminal && source && source !== terminal && sources) {
if (!dict.get(source)) {
dict.put(source, true);
terminals.push(source);

View File

@ -1,19 +1,21 @@
import CellState from "../cell/datatypes/CellState";
import {htmlEntities} from "../../util/StringUtils";
import Resources from "../../util/Resources";
import Shape from "../geometry/shape/Shape";
import SelectionCellsHandler from "../selection/SelectionCellsHandler";
import Cell from "../cell/datatypes/Cell";
import TooltipHandler from "./TooltipHandler";
import Graph from '../Graph';
import CellState from '../cell/datatypes/CellState';
import { htmlEntities } from '../../util/StringUtils';
import Resources from '../../util/Resources';
import Shape from '../geometry/shape/Shape';
import Cell from '../cell/datatypes/Cell';
import { autoImplement } from '../../util/Utils';
class GraphTooltip {
constructor(graph: Graph) {
this.graph = graph;
}
import type Graph from '../Graph';
import type GraphFolding from '../folding/GraphFolding';
graph: Graph;
type PartialGraph = Pick<
Graph,
'getSelectionCellsHandler' | 'convertValueToString' | 'getTooltipHandler'
>;
type PartialFolding = Pick<GraphFolding, 'getCollapseExpandResource'>;
type PartialClass = PartialGraph & PartialFolding;
class GraphTooltip extends autoImplement<PartialClass>() {
/**
* Returns the string or DOM node that represents the tooltip for the given
* state, node and coordinate pair. This implementation checks if the given
@ -29,58 +31,38 @@ class GraphTooltip {
* @param x X-coordinate of the mouse.
* @param y Y-coordinate of the mouse.
*/
getTooltip(
state: CellState,
node: HTMLElement,
x: number,
y: number
): string | null {
let tip: string | null = null;
getTooltip(state: CellState, node: HTMLElement | SVGElement, x: number, y: number) {
let tip: HTMLElement | string | null = null;
if (state != null) {
// Checks if the mouse is over the folding icon
if (
state.control != null &&
// @ts-ignore
(node === state.control.node || node.parentNode === state.control.node)
) {
tip = this.graph.collapseExpandResource;
tip = htmlEntities(Resources.get(tip) || tip, true).replace(
/\\n/g,
'<br>'
);
}
// Checks if the mouse is over the folding icon
if (
state.control &&
(node === state.control.node || node.parentNode === state.control.node)
) {
tip = this.getCollapseExpandResource();
tip = htmlEntities(Resources.get(tip) || tip, true).replace(/\\n/g, '<br>');
}
if (tip == null && state.overlays != null) {
state.overlays.visit((id: string, shape: Shape) => {
// LATER: Exit loop if tip is not null
if (
tip == null &&
// @ts-ignore
(node === shape.node || node.parentNode === shape.node)
) {
// @ts-ignore
tip = shape.overlay.toString();
}
});
}
if (tip == null) {
const handler = (<SelectionCellsHandler>(
this.graph.selectionCellsHandler
)).getHandler(<Cell>state.cell);
if (
handler != null &&
typeof handler.getTooltipForNode === 'function'
) {
tip = handler.getTooltipForNode(node);
if (!tip && state.overlays) {
state.overlays.visit((id: string, shape: Shape) => {
// LATER: Exit loop if tip is not null
if (!tip && (node === shape.node || node.parentNode === shape.node)) {
tip = shape.overlay ? shape.overlay.toString() ?? null : null;
}
}
});
}
if (tip == null) {
tip = this.getTooltipForCell(<Cell>state.cell);
if (!tip) {
const handler = this.getSelectionCellsHandler().getHandler(state.cell);
if (handler && typeof handler.getTooltipForNode === 'function') {
tip = handler.getTooltipForNode(node);
}
}
if (!tip) {
tip = this.getTooltipForCell(state.cell);
}
return tip;
}
@ -102,15 +84,16 @@ class GraphTooltip {
*
* @param cell {@link mxCell} whose tooltip should be returned.
*/
getTooltipForCell(cell: Cell): string | null {
getTooltipForCell(cell: Cell) {
let tip = null;
if (cell != null && 'getTooltip' in cell) {
// @ts-ignore
if (cell && 'getTooltip' in cell) {
// @ts-ignore getTooltip() must exists.
tip = cell.getTooltip();
} else {
tip = this.graph.convertValueToString(cell);
tip = this.convertValueToString(cell);
}
return tip;
}
@ -124,10 +107,9 @@ class GraphTooltip {
*
* @param enabled Boolean indicating if tooltips should be enabled.
*/
setTooltips(enabled: boolean): void {
(<TooltipHandler>this.graph.tooltipHandler).setEnabled(enabled);
setTooltips(enabled: boolean) {
this.getTooltipHandler().setEnabled(enabled);
}
}
export default GraphTooltip;

View File

@ -9,9 +9,10 @@ import { fit, getScrollOrigin } from '../../util/Utils';
import { TOOLTIP_VERTICAL_OFFSET } from '../../util/Constants';
import { getSource, isMouseEvent } from '../../util/EventUtils';
import { isNode } from '../../util/DomUtils';
import Graph from '../Graph';
import Graph, { MaxGraph } from '../Graph';
import CellState from '../cell/datatypes/CellState';
import InternalMouseEvent from '../event/InternalMouseEvent';
import { Listenable } from '../../types';
/**
* Class: mxTooltipHandler
@ -38,41 +39,42 @@ import InternalMouseEvent from '../event/InternalMouseEvent';
* delay - Optional delay in milliseconds.
*/
class TooltipHandler {
constructor(graph: Graph | null, delay: number) {
if (graph != null) {
this.graph = graph;
this.delay = delay || 500;
this.graph.addMouseListener(this);
}
constructor(graph: MaxGraph, delay: number = 500) {
this.graph = graph;
this.delay = delay;
this.graph.addMouseListener(this);
}
// @ts-ignore Cannot be null.
div: HTMLElement;
/**
* Variable: zIndex
*
* Specifies the zIndex for the tooltip and its shadow. Default is 10005.
*/
zIndex: number = 10005;
zIndex = 10005;
/**
* Variable: graph
*
* Reference to the enclosing <mxGraph>.
*/
graph: Graph = null;
graph: Graph;
/**
* Variable: delay
*
* Delay to show the tooltip in milliseconds. Default is 500.
*/
delay: number = null;
delay: number;
/**
* Variable: ignoreTouchEvents
*
* Specifies if touch and pen events should be ignored. Default is true.
*/
ignoreTouchEvents: boolean = true;
ignoreTouchEvents = true;
/**
* Variable: hideOnHover
@ -80,21 +82,28 @@ class TooltipHandler {
* Specifies if the tooltip should be hidden if the mouse is moved over the
* current cell. Default is false.
*/
hideOnHover: boolean = false;
hideOnHover = false;
/**
* Variable: destroyed
*
* True if this handler was destroyed using <destroy>.
*/
destroyed: boolean = false;
destroyed = false;
lastX: number = 0;
lastY: number = 0;
state: CellState | null = null;
stateSource: boolean = false;
node: any;
thread: number | null = null;
/**
* Variable: enabled
*
* Specifies if events are handled. Default is true.
*/
enabled: boolean = true;
enabled = true;
/**
* Function: isEnabled
@ -102,7 +111,7 @@ class TooltipHandler {
* Returns true if events are handled. This implementation
* returns <enabled>.
*/
isEnabled(): boolean {
isEnabled() {
return this.enabled;
}
@ -112,7 +121,7 @@ class TooltipHandler {
* Enables or disables event handling. This implementation
* updates <enabled>.
*/
setEnabled(enabled: boolean): void {
setEnabled(enabled: boolean) {
this.enabled = enabled;
}
@ -121,7 +130,7 @@ class TooltipHandler {
*
* Returns <hideOnHover>.
*/
isHideOnHover(): boolean {
isHideOnHover() {
return this.hideOnHover;
}
@ -130,7 +139,7 @@ class TooltipHandler {
*
* Sets <hideOnHover>.
*/
setHideOnHover(value: boolean): void {
setHideOnHover(value: boolean) {
this.hideOnHover = value;
}
@ -139,22 +148,20 @@ class TooltipHandler {
*
* Initializes the DOM nodes required for this tooltip handler.
*/
init(): void {
if (document.body != null) {
this.div = document.createElement('div');
this.div.className = 'mxTooltip';
this.div.style.visibility = 'hidden';
init() {
this.div = document.createElement('div');
this.div.className = 'mxTooltip';
this.div.style.visibility = 'hidden';
document.body.appendChild(this.div);
document.body.appendChild(this.div);
InternalEvent.addGestureListeners(this.div, (evt) => {
const source = getSource(evt);
InternalEvent.addGestureListeners(this.div, (evt) => {
const source = getSource(evt);
if (source.nodeName !== 'A') {
this.hideTooltip();
}
});
}
if (source.nodeName !== 'A') {
this.hideTooltip();
}
});
}
/**
@ -162,7 +169,7 @@ class TooltipHandler {
*
* Returns the <mxCellState> to be used for showing a tooltip for this event.
*/
getStateForEvent(me: MouseEvent): CellState {
getStateForEvent(me: InternalMouseEvent) {
return me.getState();
}
@ -173,7 +180,7 @@ class TooltipHandler {
* event all subsequent events of the gesture are redirected to this
* handler.
*/
mouseDown(sender: any, me: InternalMouseEvent): void {
mouseDown(sender: any, me: InternalMouseEvent) {
this.reset(me, false);
this.hideTooltip();
}
@ -183,7 +190,7 @@ class TooltipHandler {
*
* Handles the event by updating the rubberband selection.
*/
mouseMove(sender: any, me: InternalMouseEvent): void {
mouseMove(sender: Listenable, me: InternalMouseEvent) {
if (me.getX() !== this.lastX || me.getY() !== this.lastY) {
this.reset(me, true);
const state = this.getStateForEvent(me);
@ -222,7 +229,7 @@ class TooltipHandler {
* Resets the timer.
*/
resetTimer(): void {
if (this.thread != null) {
if (this.thread !== null) {
window.clearTimeout(this.thread);
this.thread = null;
}
@ -233,16 +240,16 @@ class TooltipHandler {
*
* Resets and/or restarts the timer to trigger the display of the tooltip.
*/
reset(me: InternalMouseEvent, restart: boolean, state: CellState): void {
reset(me: InternalMouseEvent, restart: boolean, state: CellState | null = null) {
if (!this.ignoreTouchEvents || isMouseEvent(me.getEvent())) {
this.resetTimer();
state = state != null ? state : this.getStateForEvent(me);
state = state ?? this.getStateForEvent(me);
if (
restart &&
this.isEnabled() &&
state != null &&
(this.div == null || this.div.style.visibility === 'hidden')
state &&
this.div.style.visibility === 'hidden'
) {
const node = me.getSource();
const x = me.getX();
@ -251,6 +258,7 @@ class TooltipHandler {
this.thread = window.setTimeout(() => {
if (
state &&
!this.graph.isEditing() &&
!this.graph.popupMenuHandler.isMenuShowing() &&
!this.graph.isMouseDown
@ -274,7 +282,7 @@ class TooltipHandler {
*
* Hides the tooltip and resets the timer.
*/
hide(): void {
hide() {
this.resetTimer();
this.hideTooltip();
}
@ -284,11 +292,9 @@ class TooltipHandler {
*
* Hides the tooltip.
*/
hideTooltip(): void {
if (this.div != null) {
this.div.style.visibility = 'hidden';
this.div.innerHTML = '';
}
hideTooltip() {
this.div.style.visibility = 'hidden';
this.div.innerHTML = '';
}
/**
@ -297,24 +303,19 @@ class TooltipHandler {
* Shows the tooltip for the specified cell and optional index at the
* specified location (with a vertical offset of 10 pixels).
*/
show(tip: string, x: number, y: number): void {
if (!this.destroyed && tip != null && tip.length > 0) {
// Initializes the DOM nodes if required
if (this.div == null) {
this.init();
}
show(tip: HTMLElement | string | null, x: number, y: number) {
if (!this.destroyed && tip && tip !== '') {
const origin = getScrollOrigin();
this.div.style.zIndex = this.zIndex;
this.div.style.zIndex = String(this.zIndex);
this.div.style.left = `${x + origin.x}px`;
this.div.style.top = `${y + TOOLTIP_VERTICAL_OFFSET + origin.y}px`;
if (!isNode(tip)) {
this.div.innerHTML = tip.replace(/\n/g, '<br>');
this.div.innerHTML = (tip as string).replace(/\n/g, '<br>');
} else {
this.div.innerHTML = '';
this.div.appendChild(tip);
this.div.appendChild(tip as HTMLElement);
}
this.div.style.visibility = '';
@ -327,17 +328,16 @@ class TooltipHandler {
*
* Destroys the handler and all its resources and DOM nodes.
*/
destroy(): void {
destroy() {
if (!this.destroyed) {
this.graph.removeMouseListener(this);
InternalEvent.release(this.div);
if (this.div != null && this.div.parentNode != null) {
if (this.div.parentNode != null) {
this.div.parentNode.removeChild(this.div);
}
this.destroyed = true;
this.div = null;
}
}
}

View File

@ -1,24 +1,36 @@
import Cell from "../cell/datatypes/Cell";
import Resources from "../../util/Resources";
import {isNode} from "../../util/DomUtils";
import CellState from "../cell/datatypes/CellState";
import Multiplicity from "./Multiplicity";
import Graph from "../Graph";
import Cell from '../cell/datatypes/Cell';
import Resources from '../../util/Resources';
import { isNode } from '../../util/DomUtils';
import CellState from '../cell/datatypes/CellState';
import Multiplicity from './Multiplicity';
import { autoImplement } from '../../util/Utils';
class GraphValidation {
constructor(graph: Graph) {
this.graph = graph;
import type Graph from '../Graph';
import type GraphEdge from '../cell/edge/GraphEdge';
import type GraphConnections from '../connection/GraphConnections';
import type GraphOverlays from '../layout/GraphOverlays';
this.multiplicities = [];
}
graph: Graph;
type PartialGraph = Pick<
Graph,
| 'getModel'
| 'isAllowLoops'
| 'isMultigraph'
| 'getView'
| 'isValidRoot'
| 'getContainsValidationErrorsResource'
| 'getAlreadyConnectedResource'
>;
type PartialEdge = Pick<GraphEdge, 'isAllowDanglingEdges'>;
type PartialConnections = Pick<GraphConnections, 'isValidConnection'>;
type PartialOverlays = Pick<GraphOverlays, 'setCellWarning'>;
type PartialClass = PartialGraph & PartialEdge & PartialConnections & PartialOverlays;
class GraphValidation extends autoImplement<PartialClass>() {
/**
* An array of {@link Multiplicity} describing the allowed
* connections in a graph.
*/
multiplicities: Multiplicity[] | null = null;
multiplicities: Multiplicity[] = [];
/*****************************************************************************
* Group: Validation
@ -28,7 +40,7 @@ class GraphValidation {
* Displays the given validation error in a dialog. This implementation uses
* mxUtils.alert.
*/
validationAlert(message: any): void {
validationAlert(message: string) {
alert(message);
}
@ -40,8 +52,8 @@ class GraphValidation {
* @param source {@link mxCell} that represents the source terminal.
* @param target {@link mxCell} that represents the target terminal.
*/
isEdgeValid(edge: Cell, source: Cell, target: Cell): boolean {
return this.getEdgeValidationError(edge, source, target) == null;
isEdgeValid(edge: Cell, source: Cell, target: Cell) {
return !this.getEdgeValidationError(edge, source, target);
}
/**
@ -86,45 +98,37 @@ class GraphValidation {
source: Cell | null = null,
target: Cell | null = null
): string | null {
if (
edge != null &&
!this.isAllowDanglingEdges() &&
(source == null || target == null)
) {
if (edge && !this.isAllowDanglingEdges() && (!source || !target)) {
return '';
}
if (
edge != null &&
edge.getTerminal(true) == null &&
edge.getTerminal(false) == null
) {
if (edge && !edge.getTerminal(true) && !edge.getTerminal(false)) {
return null;
}
// Checks if we're dealing with a loop
if (!this.allowLoops && source === target && source != null) {
if (!this.isAllowLoops() && source === target && source) {
return '';
}
// Checks if the connection is generally allowed
if (!this.isValidConnection(<Cell>source, <Cell>target)) {
if (!this.isValidConnection(source, target)) {
return '';
}
if (source != null && target != null) {
if (source && target) {
let error = '';
// Checks if the cells are already connected
// and adds an error message if required
if (!this.multigraph) {
if (!this.isMultigraph()) {
const tmp = this.getModel().getEdgesBetween(source, target, true);
// Checks if the source and target are not connected by another edge
if (tmp.length > 1 || (tmp.length === 1 && tmp[0] !== edge)) {
error += `${
Resources.get(this.alreadyConnectedResource) ||
this.alreadyConnectedResource
Resources.get(this.getAlreadyConnectedResource()) ||
this.getAlreadyConnectedResource()
}\n`;
}
}
@ -136,20 +140,18 @@ class GraphValidation {
const targetIn = target.getDirectedEdgeCount(false, edge);
// Checks the change against each multiplicity rule
if (this.multiplicities != null) {
for (const multiplicity of this.multiplicities) {
const err = multiplicity.check(
this,
<Cell>edge,
source,
target,
sourceOut,
targetIn
);
for (const multiplicity of this.multiplicities) {
const err = multiplicity.check(
<Graph>(<unknown>this), // needs to cast to Graph
<Cell>edge,
source,
target,
sourceOut,
targetIn
);
if (err != null) {
error += err;
}
if (err != null) {
error += err;
}
}
@ -161,7 +163,7 @@ class GraphValidation {
return error.length > 0 ? error : null;
}
return this.allowDanglingEdges ? null : '';
return this.isAllowDanglingEdges() ? null : '';
}
/**
@ -191,17 +193,20 @@ class GraphValidation {
* the graph root.
* @param context Object that represents the global validation state.
*/
validateGraph(
cell: Cell = <Cell>this.graph.model.getRoot(),
context: any
): string | null {
context = context != null ? context : {};
validateGraph(cell: Cell | null, context: any): string | null {
cell = cell ?? this.getModel().getRoot();
if (!cell) {
return 'The root does not exist!';
}
context = context ?? {};
let isValid = true;
const childCount = cell.getChildCount();
for (let i = 0; i < childCount; i += 1) {
const tmp = <Cell>cell.getChildAt(i);
const tmp = cell.getChildAt(i);
let ctx = context;
if (this.isValidRoot(tmp)) {
@ -210,7 +215,7 @@ class GraphValidation {
const warn = this.validateGraph(tmp, ctx);
if (warn != null) {
if (warn) {
this.setCellWarning(tmp, warn.replace(/\n/g, '<br>'));
} else {
this.setCellWarning(tmp, null);
@ -224,8 +229,8 @@ class GraphValidation {
// Adds error for invalid children if collapsed (children invisible)
if (cell && cell.isCollapsed() && !isValid) {
warning += `${
Resources.get(this.containsValidationErrorsResource) ||
this.containsValidationErrorsResource
Resources.get(this.getContainsValidationErrorsResource()) ||
this.getContainsValidationErrorsResource()
}\n`;
}
@ -265,31 +270,30 @@ class GraphValidation {
*
* @param cell {@link mxCell} for which the multiplicities should be checked.
*/
getCellValidationError(cell: Cell): string | null {
getCellValidationError(cell: Cell) {
const outCount = cell.getDirectedEdgeCount(true);
const inCount = cell.getDirectedEdgeCount(false);
const value = cell.getValue();
let error = '';
if (this.multiplicities != null) {
for (let i = 0; i < this.multiplicities.length; i += 1) {
const rule = this.multiplicities[i];
for (let i = 0; i < this.multiplicities.length; i += 1) {
const rule = this.multiplicities[i];
if (
rule.source &&
isNode(value, rule.type, rule.attr, rule.value) &&
(outCount > rule.max || outCount < rule.min)
) {
error += `${rule.countError}\n`;
} else if (
!rule.source &&
isNode(value, rule.type, rule.attr, rule.value) &&
(inCount > rule.max || inCount < rule.min)
) {
error += `${rule.countError}\n`;
}
if (
rule.source &&
isNode(value, rule.type, rule.attr, rule.value) &&
(outCount > rule.max || outCount < rule.min)
) {
error += `${rule.countError}\n`;
} else if (
!rule.source &&
isNode(value, rule.type, rule.attr, rule.value) &&
(inCount > rule.max || inCount < rule.min)
) {
error += `${rule.countError}\n`;
}
}
return error.length > 0 ? error : null;
}
@ -301,8 +305,7 @@ class GraphValidation {
* @param cell {@link mxCell} that represents the cell to validate.
* @param context Object that represents the global validation state.
*/
// validateCell(cell: mxCell, context: any): string | null;
validateCell(cell: Cell, context: CellState): void | null {
validateCell(cell: Cell, context: CellState): string | null {
return null;
}
}

View File

@ -12,12 +12,12 @@ import type { UndoableChange } from '../../types';
* Action to change the current root in a view.
*/
class CurrentRootChange implements UndoableChange {
view: mxGraphView;
view: GraphView;
root: Cell | null;
previous: Cell | null;
isUp: boolean;
constructor(view: mxGraphView, root: Cell | null) {
constructor(view: GraphView, root: Cell | null) {
this.view = view;
this.root = root;
this.previous = root;
@ -44,9 +44,7 @@ class CurrentRootChange implements UndoableChange {
this.view.currentRoot = this.previous;
this.previous = tmp;
const translate = this.view.graph.getTranslateForRoot(
this.view.currentRoot
);
const translate = this.view.graph.getTranslateForRoot(this.view.currentRoot);
if (translate) {
this.view.translate = new Point(-translate.x, -translate.y);
@ -62,13 +60,7 @@ class CurrentRootChange implements UndoableChange {
const name = this.isUp ? InternalEvent.UP : InternalEvent.DOWN;
this.view.fireEvent(
new EventObject(
name,
'root',
this.view.currentRoot,
'previous',
this.previous
)
new EventObject(name, 'root', this.view.currentRoot, 'previous', this.previous)
);
this.isUp = !this.isUp;

File diff suppressed because it is too large Load Diff