From bc400a3ae34144915126f4b0d8a93c13789e67dd Mon Sep 17 00:00:00 2001 From: Junsik Shim Date: Sun, 1 Aug 2021 11:21:57 +0900 Subject: [PATCH] - Converted Graph* classes into mixins. - Created MaxGraph type to expose. - CellStateStyles is now more concrete. - More compiler errors are resolved. --- packages/core/src/types.ts | 90 +- packages/core/src/util/Dictionary.ts | 6 +- packages/core/src/util/Utils.ts | 36 +- packages/core/src/view/Graph.ts | 226 ++- packages/core/src/view/GraphHandler.ts | 99 +- packages/core/src/view/cell/CellMarker.ts | 17 +- packages/core/src/view/cell/CellRenderer.ts | 2 +- packages/core/src/view/cell/GraphCells.ts | 1135 +++++++------- packages/core/src/view/cell/datatypes/Cell.ts | 8 +- .../core/src/view/cell/datatypes/CellArray.ts | 3 +- .../core/src/view/cell/datatypes/CellState.ts | 14 +- .../core/src/view/cell/edge/EdgeHandler.ts | 1302 ++++++++--------- packages/core/src/view/cell/edge/GraphEdge.ts | 288 ++-- .../core/src/view/cell/vertex/GraphVertex.ts | 34 +- .../core/src/view/cell/vertex/VertexHandle.ts | 92 +- .../src/view/connection/GraphConnections.ts | 108 +- .../core/src/view/editing/GraphEditing.ts | 123 +- packages/core/src/view/event/EventSource.ts | 2 +- packages/core/src/view/event/GraphEvents.ts | 447 +++--- packages/core/src/view/event/InternalEvent.ts | 52 +- .../core/src/view/event/InternalMouseEvent.ts | 50 +- .../core/src/view/folding/GraphFolding.ts | 62 +- .../core/src/view/geometry/shape/Shape.ts | 2 +- .../view/geometry/shape/node/StencilShape.ts | 2 +- packages/core/src/view/image/GraphImage.ts | 5 - packages/core/src/view/image/ImageBundle.ts | 4 +- packages/core/src/view/label/GraphLabel.ts | 37 +- .../layout/{Overlays.ts => GraphOverlays.ts} | 109 +- packages/core/src/view/model/Model.ts | 138 +- .../core/src/view/selection/GraphSelection.ts | 297 ++-- .../core/src/view/selection/RubberBand.ts | 20 +- packages/core/src/view/snap/GraphSnap.ts | 49 +- packages/core/src/view/style/StyleMap.ts | 5 +- packages/core/src/view/style/Stylesheet.ts | 64 +- .../core/src/view/terminal/GraphTerminal.ts | 57 +- .../core/src/view/tooltip/GraphTooltip.ts | 114 +- .../core/src/view/tooltip/TooltipHandler.ts | 116 +- .../src/view/validation/GraphValidation.ts | 153 +- .../core/src/view/view/CurrentRootChange.ts | 16 +- packages/core/src/view/view/GraphView.ts | 989 ++++++------- 40 files changed, 3153 insertions(+), 3220 deletions(-) rename packages/core/src/view/layout/{Overlays.ts => GraphOverlays.ts} (74%) diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index d37f85c4b..9d325b0af 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -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; +} diff --git a/packages/core/src/util/Dictionary.ts b/packages/core/src/util/Dictionary.ts index f43208c6e..7cd6a0564 100644 --- a/packages/core/src/util/Dictionary.ts +++ b/packages/core/src/util/Dictionary.ts @@ -54,7 +54,7 @@ class Dictionary { get(key: T) { const id = ObjectIdentity.get(key); - return this.map[id]; + return this.map[id] ?? null; } /** @@ -68,7 +68,7 @@ class Dictionary { const previous = this.map[id]; this.map[id] = value; - return previous; + return previous ?? null; } /** @@ -82,7 +82,7 @@ class Dictionary { const previous = this.map[id]; delete this.map[id]; - return previous; + return previous ?? null; } /** diff --git a/packages/core/src/util/Utils.ts b/packages/core/src/util/Utils.ts index 7fe726110..4fa324dd3 100644 --- a/packages/core/src/util/Utils.ts +++ b/packages/core/src/util/Utils.ts @@ -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 = (): new () => T => class {} as any; + export default utils; diff --git a/packages/core/src/view/Graph.ts b/packages/core/src/view/Graph.ts index 49cf7aeb3..771dbc041 100644 --- a/packages/core/src/view/Graph.ts +++ b/packages/core/src/view/Graph.ts @@ -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; +type PartialTooltip = Pick; +type PartialValidation = Pick< + GraphValidation, + 'getEdgeValidationError' | 'validationAlert' +>; +type PartialLabel = Pick; +type PartialTerminal = Pick; +type PartialSnap = Pick; +type PartialEdge = Pick; +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() { 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 != (evt).relatedTarget + this.tooltipHandler.div && + this.tooltipHandler.div !== (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 { (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 this.model; + getModel() { + return this.model; } /** * Returns the {@link GraphView} that contains the {@link mxCellStates}. */ - getView(): GraphView { - return 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"); diff --git a/packages/core/src/view/GraphHandler.ts b/packages/core/src/view/GraphHandler.ts index e190147a6..c53c47f93 100644 --- a/packages/core/src/view/GraphHandler.ts +++ b/packages/core/src/view/GraphHandler.ts @@ -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 . */ class GraphHandler { - constructor(graph) { + constructor(graph: MaxGraph) { this.graph = graph; this.graph.addMouseListener(this); @@ -150,8 +146,7 @@ class GraphHandler { * * Reference to the enclosing . */ - // 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 { diff --git a/packages/core/src/view/cell/CellMarker.ts b/packages/core/src/view/cell/CellMarker.ts index e6a7a8b81..1e8cb6029 100644 --- a/packages/core/src/view/cell/CellMarker.ts +++ b/packages/core/src/view/cell/CellMarker.ts @@ -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 . */ - graph: graph; + graph: MaxGraph; /** * Variable: enabled @@ -362,12 +362,15 @@ class CellMarker extends EventSource { * Uses , and to return the * for the given . */ - 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 to be marked for the given under * the mouse. This returns the given state. */ - getStateToMark(state: CellState): CellState { + getStateToMark(state: CellState) { return state; } diff --git a/packages/core/src/view/cell/CellRenderer.ts b/packages/core/src/view/cell/CellRenderer.ts index a488cf324..d0df01707 100644 --- a/packages/core/src/view/cell/CellRenderer.ts +++ b/packages/core/src/view/cell/CellRenderer.ts @@ -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) { diff --git a/packages/core/src/view/cell/GraphCells.ts b/packages/core/src/view/cell/GraphCells.ts index 834fe568c..5808a94f4 100644 --- a/packages/core/src/view/cell/GraphCells.ts +++ b/packages/core/src/view/cell/GraphCells.ts @@ -1,20 +1,17 @@ import Cell from './datatypes/Cell'; import StyleMap from '../style/StyleMap'; -import Stylesheet from '../style/Stylesheet'; import CellArray from './datatypes/CellArray'; -import utils, { +import { + autoImplement, contains, - findNearestSegment, getBoundingBox, getRotatedPoint, getSizeForString, getValue, intersects, ptSegDistSq, - removeDuplicates, setCellStyleFlags, setCellStyles, - setStyle, toRadians, } from '../../util/Utils'; import { @@ -24,113 +21,169 @@ import { ALIGN_RIGHT, ALIGN_TOP, DEFAULT_FONTSIZE, - DIRECTION_EAST, - DIRECTION_NORTH, - DIRECTION_SOUTH, - DIRECTION_WEST, + DEFAULT_IMAGESIZE, SHAPE_LABEL, } from '../../util/Constants'; import Geometry from '../geometry/Geometry'; import EventObject from '../event/EventObject'; import InternalEvent from '../event/InternalEvent'; -import ImageBundle from '../image/ImageBundle'; import Rectangle from '../geometry/Rectangle'; import Dictionary from '../../util/Dictionary'; import Point from '../geometry/Point'; -import Label from '../geometry/shape/node/LabelShape'; import { htmlEntities } from '../../util/StringUtils'; import InternalMouseEvent from '../event/InternalMouseEvent'; -import Graph from '../Graph'; import CellState from './datatypes/CellState'; -class GraphCells { - constructor(graph: Graph) { - this.graph = graph; - } +import type Graph from '../Graph'; +import type GraphImage from '../image/GraphImage'; +import type GraphSelection from '../selection/GraphSelection'; +import type GraphEdge from './edge/GraphEdge'; +import type GraphConnections from '../connection/GraphConnections'; +import type GraphValidation from '../validation/GraphValidation'; +import type GraphFolding from '../folding/GraphFolding'; +import type GraphLabel from '../label/GraphLabel'; +import type GraphSnap from '../snap/GraphSnap'; - graph: Graph; +import type { CellStateStyles } from '../../types'; +type PartialGraph = Pick< + Graph, + | 'getView' + | 'getStylesheet' + | 'batchUpdate' + | 'getModel' + | 'fireEvent' + | 'getDefaultParent' + | 'getCurrentRoot' + | 'isAllowNegativeCoordinates' + | 'setAllowNegativeCoordinates' + | 'getOverlap' + | 'isRecursiveResize' + | 'getCellRenderer' + | 'getMaximumGraphBounds' + | 'isExportEnabled' + | 'isImportEnabled' +>; +type PartialImage = Pick; +type PartialSelection = Pick; +type PartialEdge = Pick< + GraphEdge, + | 'addAllEdges' + | 'getAllEdges' + | 'isCloneInvalidEdges' + | 'isAllowDanglingEdges' + | 'resetEdges' + | 'isResetEdgesOnResize' + | 'isResetEdgesOnMove' +>; +type PartialConnections = Pick< + GraphConnections, + | 'isConstrainChild' + | 'cellConnected' + | 'isDisconnectOnMove' + | 'isConstrainRelativeChildren' + | 'disconnectGraph' +>; +type PartialValidation = Pick; +type PartialFolding = Pick; +type PartialLabel = Pick; +type PartialSnap = Pick< + GraphSnap, + 'isGridEnabled' | 'snap' | 'getGridSize' | 'getTolerance' +>; +type PartialClass = PartialGraph & + PartialImage & + PartialSelection & + PartialEdge & + PartialConnections & + PartialValidation & + PartialFolding & + PartialLabel & + PartialSnap; + +// @ts-ignore recursive reference error +class GraphCells extends autoImplement() { /** * Specifies the return value for {@link isCellsResizable}. * @default true */ - cellsResizable: boolean = true; + cellsResizable = true; /** * Specifies the return value for {@link isCellsBendable}. * @default true */ - cellsBendable: boolean = true; + cellsBendable = true; /** * Specifies the return value for {@link isCellsSelectable}. * @default true */ - cellsSelectable: boolean = true; + cellsSelectable = true; /** * Specifies the return value for {@link isCellsDisconnectable}. * @default true */ - cellsDisconnectable: boolean = true; + cellsDisconnectable = true; /** * Specifies if the graph should automatically update the cell size after an * edit. This is used in {@link isAutoSizeCell}. * @default false */ - autoSizeCells: boolean = false; + autoSizeCells = false; /** * Specifies if autoSize style should be applied when cells are added. * @default false */ - autoSizeCellsOnAdd: boolean = false; + autoSizeCellsOnAdd = false; /** * Specifies the return value for {@link isCellLocked}. * @default false */ - cellsLocked: boolean = false; + cellsLocked = false; /** * Specifies the return value for {@link isCellCloneable}. * @default true */ - cellsCloneable: boolean = true; + cellsCloneable = true; /** * Specifies the return value for {@link isCellDeletable}. * @default true */ - cellsDeletable: boolean = true; + cellsDeletable = true; /** * Specifies the return value for {@link isCellMovable}. * @default true */ - cellsMovable: boolean = true; + cellsMovable = true; /** * Specifies if a parent should contain the child bounds after a resize of * the child. This has precedence over {@link constrainChildren}. * @default true */ - extendParents: boolean = true; + extendParents = true; /** * Specifies if parents should be extended according to the {@link extendParents} * switch if cells are added. * @default true */ - extendParentsOnAdd: boolean = true; + extendParentsOnAdd = true; /** * Specifies if parents should be extended according to the {@link extendParents} * switch if cells are added. * @default false (for backwards compatibility) */ - extendParentsOnMove: boolean = false; + extendParentsOnMove = false; /** * Returns the bounding box for the given array of {@link Cell}. The bounding box for @@ -138,19 +191,16 @@ class GraphCells { * * @param cells Array of {@link Cell} whose bounding box should be returned. */ - getBoundingBox(cells: CellArray): Rectangle { + getBoundingBox(cells: CellArray) { let result = null; if (cells.length > 0) { for (const cell of cells) { if (cell.isVertex() || cell.isEdge()) { - const bbox = this.graph.view.getBoundingBox( - this.graph.view.getState(cell), - true - ); + const bbox = this.getView().getBoundingBox(this.getView().getState(cell), true); - if (bbox != null) { - if (result == null) { + if (bbox) { + if (!result) { result = Rectangle.fromRectangle(bbox); } else { result.add(bbox); @@ -170,12 +220,13 @@ class GraphCells { * * @param cell {@link mxCell} that was removed from the model. */ - removeStateForCell(cell: Cell): void { + removeStateForCell(cell: Cell) { for (const child of cell.getChildren()) { this.removeStateForCell(child); } - this.graph.view.invalidate(cell, false, true); - this.graph.view.removeState(cell); + + this.getView().invalidate(cell, false, true); + this.getView().removeState(cell); } /***************************************************************************** @@ -189,9 +240,9 @@ class GraphCells { * @param cell {@link mxCell} whose style should be returned as an array. * @param ignoreState Optional boolean that specifies if the cell state should be ignored. */ - getCurrentCellStyle(cell: Cell, ignoreState: boolean = false): StyleMap { - const state = ignoreState ? null : this.graph.view.getState(cell); - return state != null ? state.style : this.getCellStyle(cell); + getCurrentCellStyle(cell: Cell, ignoreState = false) { + const state = ignoreState ? null : this.getView().getState(cell); + return state ? state.style : this.getCellStyle(cell); } /** @@ -204,10 +255,10 @@ class GraphCells { * * @param cell {@link mxCell} whose style should be returned as an array. */ - getCellStyle(cell: Cell): StyleMap { + getCellStyle(cell: Cell) { const stylename = cell.getStyle(); let style; - const stylesheet = this.graph.stylesheet; + const stylesheet = this.getStylesheet(); // Gets the default style for the cell if (cell.isEdge()) { @@ -217,13 +268,13 @@ class GraphCells { } // Resolves the stylename using the above as the default - if (stylename != null) { + if (stylename) { style = this.postProcessCellStyle(stylesheet.getCellStyle(stylename, style)); } // Returns a non-null value if no style can be found - if (style == null) { - style = new StyleMap(); + if (!style) { + style = {} as CellStateStyles; } return style; } @@ -233,18 +284,18 @@ class GraphCells { * turns short data URIs as defined in mxImageBundle to data URIs as * defined in RFC 2397 of the IETF. */ - postProcessCellStyle(style: StyleMap): StyleMap { + postProcessCellStyle(style: CellStateStyles) { const key = style.image; - let image = this.graph.image.getImageFromBundles(key); + let image = this.getImageFromBundles(key); - if (image != null) { + if (image) { style.image = image; } else { image = key; } // Converts short data uris to normal data uris - if (image != null && image.substring(0, 11) === 'data:image/') { + if (image && image.substring(0, 11) === 'data:image/') { if (image.substring(0, 20) === 'data:image/svg+xml,<') { // Required for FF and IE11 image = image.substring(0, 19) + encodeURIComponent(image.substring(19)); @@ -270,10 +321,13 @@ class GraphCells { * @param cells Optional array of {@link Cell} to set the style for. Default is the * selection cells. */ - setCellStyle(style: any, cells: CellArray = this.graph.selection.getSelectionCells()) { - this.graph.batchUpdate(() => { + setCellStyle( + style: keyof CellStateStyles, + cells: CellArray = this.getSelectionCells() + ) { + this.batchUpdate(() => { for (const cell of cells) { - this.graph.model.setStyle(cell, style); + this.getModel().setStyle(cell, style); } }); } @@ -292,9 +346,9 @@ class GraphCells { * the selection cell. */ toggleCellStyle( - key: string, - defaultValue: boolean = false, - cell: Cell = this.graph.selection.getSelectionCell() + key: keyof CellStateStyles, + defaultValue = false, + cell: Cell = this.getSelectionCell() ) { return this.toggleCellStyles(key, defaultValue, new CellArray(cell)); } @@ -314,17 +368,18 @@ class GraphCells { * Default is the selection cells. */ toggleCellStyles( - key: string, - defaultValue: boolean = false, - cells: CellArray = this.graph.selection.getSelectionCells() + key: keyof CellStateStyles, + defaultValue = false, + cells: CellArray = this.getSelectionCells() ) { let value = null; - if (cells != null && cells.length > 0) { - const style = this.getCurrentCellStyle(cells[0]); + if (cells.length > 0) { + const style = this.getCurrentCellStyle(cells[0]); value = getValue(style, key, defaultValue) ? 0 : 1; this.setCellStyles(key, value, cells); } + return value; } @@ -341,11 +396,11 @@ class GraphCells { * the selection cells. */ setCellStyles( - key: string, - value: string | number | null = null, - cells: CellArray = this.graph.selection.getSelectionCells() - ): void { - setCellStyles(this.graph.model, cells, key, value); + key: keyof CellStateStyles, + value: CellStateStyles[keyof CellStateStyles], + cells: CellArray = this.getSelectionCells() + ) { + setCellStyles(this.getModel(), cells, key, value); } /** @@ -357,11 +412,10 @@ class GraphCells { * @param cells Optional array of {@link Cell} to change the style for. Default is * the selection cells. */ - // toggleCellStyleFlags(key: string, flag: number, cells?: mxCellArray): void; toggleCellStyleFlags( - key: string, + key: keyof CellStateStyles, flag: number, - cells: CellArray = this.graph.selection.getSelectionCells() + cells: CellArray = this.getSelectionCells() ) { this.setCellStyleFlags(key, flag, null, cells); } @@ -376,21 +430,20 @@ class GraphCells { * @param cells Optional array of {@link Cell} to change the style for. Default is * the selection cells. */ - // setCellStyleFlags(key: string, flag: number, value: boolean, cells?: mxCellArray): void; setCellStyleFlags( - key: string, + key: keyof CellStateStyles, flag: number, value: boolean | null = null, - cells: CellArray = this.graph.selection.getSelectionCells() + cells: CellArray = this.getSelectionCells() ) { - if (cells != null && cells.length > 0) { - if (value == null) { + if (cells.length > 0) { + if (value === null) { const style = this.getCurrentCellStyle(cells[0]); - // @ts-ignore - const current = parseInt(style[key] || 0); + + const current = (style[key] as number) || 0; value = !((current & flag) === flag); } - setCellStyleFlags(this.graph.model, cells, key, flag, value); + setCellStyleFlags(this.getModel(), cells, key, flag, value); } } @@ -407,19 +460,19 @@ class GraphCells { * @param cells Array of {@link Cell} to be aligned. * @param param Optional coordinate for the alignment. */ - alignCells(align: string, cells: CellArray, param: number | null = null): CellArray { - if (cells == null) { - cells = this.graph.selection.getSelectionCells(); - } - - if (cells != null && cells.length > 1) { + alignCells( + align: string, + cells: CellArray = this.getSelectionCells(), + param: number | null = null + ) { + if (cells.length > 1) { // Finds the required coordinate for the alignment - if (param == null) { + if (param === null) { for (const cell of cells) { - const state = this.graph.view.getState(cell); + const state = this.getView().getState(cell); - if (state != null && !cell.isEdge()) { - if (param == null) { + if (state && !cell.isEdge()) { + if (param === null) { if (align === ALIGN_CENTER) { param = state.x + state.width / 2; break; @@ -449,12 +502,14 @@ class GraphCells { } // Aligns the cells to the coordinate - if (param != null) { - const s = this.graph.view.scale; + if (param !== null) { + const s = this.getView().scale; + + this.batchUpdate(() => { + const p = param as number; - this.graph.batchUpdate(() => { for (const cell of cells) { - const state = this.graph.view.getState(cell); + const state = this.getView().getState(cell); if (state != null) { let geo = cell.getGeometry(); @@ -463,17 +518,17 @@ class GraphCells { geo = geo.clone(); if (align === ALIGN_CENTER) { - geo.x += (param - state.x - state.width / 2) / s; + geo.x += (p - state.x - state.width / 2) / s; } else if (align === ALIGN_RIGHT) { - geo.x += (param - state.x - state.width) / s; + geo.x += (p - state.x - state.width) / s; } else if (align === ALIGN_TOP) { - geo.y += (param - state.y) / s; + geo.y += (p - state.y) / s; } else if (align === ALIGN_MIDDLE) { - geo.y += (param - state.y - state.height / 2) / s; + geo.y += (p - state.y - state.height / 2) / s; } else if (align === ALIGN_BOTTOM) { - geo.y += (param - state.y - state.height) / s; + geo.y += (p - state.y - state.height) / s; } else { - geo.x += (param - state.x) / s; + geo.x += (p - state.x) / s; } this.resizeCell(cell, geo); @@ -481,9 +536,7 @@ class GraphCells { } } - this.graph.fireEvent( - new EventObject(InternalEvent.ALIGN_CELLS, { align, cells }) - ); + this.fireEvent(new EventObject(InternalEvent.ALIGN_CELLS, { align, cells })); }); } } @@ -508,9 +561,9 @@ class GraphCells { // cloneCell(cell: mxCell, allowInvalidEdges?: boolean, mapping?: any, keepPosition?: boolean): mxCellArray; cloneCell( cell: Cell, - allowInvalidEdges: boolean = false, + allowInvalidEdges = false, mapping: any = null, - keepPosition: boolean = false + keepPosition = false ): Cell { return (( this.cloneCells(new CellArray(cell), allowInvalidEdges, mapping, keepPosition) @@ -533,15 +586,14 @@ class GraphCells { // cloneCells(cells: mxCellArray, allowInvalidEdges?: boolean, mapping?: any, keepPosition?: boolean): mxCellArray; cloneCells( cells: CellArray, - allowInvalidEdges: boolean = true, + allowInvalidEdges = true, mapping: any = {}, - keepPosition: boolean = false - ): CellArray | null { - allowInvalidEdges = allowInvalidEdges != null ? allowInvalidEdges : true; + keepPosition = false + ) { let clones; // Creates a dictionary for fast lookups - const dict = new Dictionary(); + const dict = new Dictionary(); const tmp = []; for (const cell of cells) { @@ -550,8 +602,8 @@ class GraphCells { } if (tmp.length > 0) { - const { scale } = this.graph.view; - const trans = this.graph.view.translate; + const { scale } = this.getView(); + const trans = this.getView().translate; const out: CellArray = new CellArray(); clones = cells.cloneCells(true, mapping); @@ -566,61 +618,60 @@ class GraphCells { clone, clone.getTerminal(true), clone.getTerminal(false) - ) != null + ) !== null ) { //clones[i] = null; } else { out.push(clone); const g = clone.getGeometry(); - if (g != null) { - const state = this.graph.view.getState(cell); - const pstate = this.graph.view.getState(cell.getParent()); + if (g) { + const state = this.getView().getState(cell); + const pstate = this.getView().getState(cell.getParent()); - if (state != null && pstate != null) { + if (state && pstate) { const dx = keepPosition ? 0 : (pstate.origin).x; const dy = keepPosition ? 0 : (pstate.origin).y; if (clone.isEdge()) { - const pts = state.absolutePoints; + const pts = state.absolutePoints; - if (pts != null) { - // Checks if the source is cloned or sets the terminal point - let src = cell.getTerminal(true); + // Checks if the source is cloned or sets the terminal point + let src = cell.getTerminal(true); - while (src != null && !dict.get(src)) { - src = src.getParent(); - } + while (src && !dict.get(src)) { + src = src.getParent(); + } - if (src == null && pts[0] != null) { - g.setTerminalPoint( - new Point(pts[0].x / scale - trans.x, pts[0].y / scale - trans.y), - true - ); - } + if (!src && pts[0]) { + g.setTerminalPoint( + new Point(pts[0].x / scale - trans.x, pts[0].y / scale - trans.y), + true + ); + } - // Checks if the target is cloned or sets the terminal point - let trg = cell.getTerminal(false); - while (trg != null && !dict.get(trg)) { - trg = trg.getParent(); - } + // Checks if the target is cloned or sets the terminal point + let trg = cell.getTerminal(false); + while (trg && !dict.get(trg)) { + trg = trg.getParent(); + } - const n = pts.length - 1; + const n = pts.length - 1; + const p = pts[n]; - if (trg == null && pts[n] != null) { - g.setTerminalPoint( - new Point(pts[n].x / scale - trans.x, pts[n].y / scale - trans.y), - false - ); - } + if (!trg && p) { + g.setTerminalPoint( + new Point(p.x / scale - trans.x, p.y / scale - trans.y), + false + ); + } - // Translates the control points - const { points } = g; - if (points != null) { - for (const point of points) { - point.x += dx; - point.y += dy; - } + // Translates the control points + const { points } = g; + if (points) { + for (const point of points) { + point.x += dx; + point.y += dy; } } } else { @@ -655,7 +706,7 @@ class GraphCells { index: number | null = null, source: Cell | null = null, target: Cell | null = null - ): Cell { + ) { return this.addCells(new CellArray(cell), parent, index, source, target)[0]; } @@ -684,28 +735,15 @@ class GraphCells { index: number | null = null, source: Cell | null = null, target: Cell | null = null, - absolute: boolean = false + absolute = false ) { - if (parent == null) { - parent = this.getDefaultParent(); - } + const p = parent ?? this.getDefaultParent(); + const i = index ?? p.getChildCount(); - if (index == null) { - index = parent.getChildCount(); - } - - this.graph.batchUpdate(() => { - this.cellsAdded( - cells, - parent, - index, - source, - target, - absolute != null ? absolute : false, - true - ); - this.graph.fireEvent( - new EventObject(InternalEvent.ADD_CELLS, { cells, parent, index, source, target }) + this.batchUpdate(() => { + this.cellsAdded(cells, p, i, source, target, absolute, true); + this.fireEvent( + new EventObject(InternalEvent.ADD_CELLS, { cells, p, i, source, target }) ); }); @@ -724,88 +762,86 @@ class GraphCells { index: number, source: Cell | null = null, target: Cell | null = null, - absolute: boolean = false, - constrain: boolean = false, - extend: boolean = true - ): void { - this.graph.batchUpdate(() => { - const parentState = absolute ? this.graph.view.getState(parent) : null; - const o1 = parentState != null ? parentState.origin : null; + absolute = false, + constrain = false, + extend = true + ) { + this.batchUpdate(() => { + const parentState = absolute ? this.getView().getState(parent) : null; + const o1 = parentState ? parentState.origin : null; const zero = new Point(0, 0); cells.forEach((cell, i) => { + /* Can cells include null values? if (cell == null) { index--; } else { - const previous = cell.getParent(); + */ + const previous = cell.getParent(); - // Keeps the cell at its absolute location - if (o1 != null && cell !== parent && parent !== previous) { - const oldState = this.graph.view.getState(previous); - const o2 = (oldState != null ? oldState.origin : zero); - let geo = cell.getGeometry(); + // Keeps the cell at its absolute location + if (o1 && cell !== parent && parent !== previous) { + const oldState = this.getView().getState(previous); + const o2 = oldState ? oldState.origin : zero; + let geo = cell.getGeometry(); - if (geo != null) { - const dx = o2.x - o1.x; - const dy = o2.y - o1.y; + if (geo) { + const dx = o2.x - o1.x; + const dy = o2.y - o1.y; - // FIXME: Cells should always be inserted first before any other edit - // to avoid forward references in sessions. - geo = geo.clone(); - geo.translate(dx, dy); + // FIXME: Cells should always be inserted first before any other edit + // to avoid forward references in sessions. + geo = geo.clone(); + geo.translate(dx, dy); - if ( - !geo.relative && - cell.isVertex() && - !this.isAllowNegativeCoordinates() - ) { - geo.x = Math.max(0, geo.x); - geo.y = Math.max(0, geo.y); - } - - this.graph.model.setGeometry(cell, geo); + if (!geo.relative && cell.isVertex() && !this.isAllowNegativeCoordinates()) { + geo.x = Math.max(0, geo.x); + geo.y = Math.max(0, geo.y); } - } - // Decrements all following indices - // if cell is already in parent - if (parent === previous && index + i > parent.getChildCount()) { - index--; - } - - this.graph.model.add(parent, cell, index + i); - - if (this.autoSizeCellsOnAdd) { - this.autoSizeCell(cell, true); - } - - // Extends the parent or constrains the child - if ( - (extend == null || extend) && - this.isExtendParentsOnAdd(cell) && - this.isExtendParent(cell) - ) { - this.extendParent(cell); - } - - // Additionally constrains the child after extending the parent - if (constrain == null || constrain) { - this.constrainChild(cell); - } - - // Sets the source terminal - if (source != null) { - this.cellConnected(cell, source, true); - } - - // Sets the target terminal - if (target != null) { - this.cellConnected(cell, target, false); + this.getModel().setGeometry(cell, geo); } } + + // Decrements all following indices + // if cell is already in parent + if (parent === previous && index + i > parent.getChildCount()) { + index--; + } + + this.getModel().add(parent, cell, index + i); + + if (this.autoSizeCellsOnAdd) { + this.autoSizeCell(cell, true); + } + + // Extends the parent or constrains the child + if ( + (!extend || extend) && + this.isExtendParentsOnAdd(cell) && + this.isExtendParent(cell) + ) { + this.extendParent(cell); + } + + // Additionally constrains the child after extending the parent + if (!constrain || constrain) { + this.constrainChild(cell); + } + + // Sets the source terminal + if (source) { + this.cellConnected(cell, source, true); + } + + // Sets the target terminal + if (target) { + this.cellConnected(cell, target, false); + } + /*}*/ }); - this.graph.fireEvent( + this.fireEvent( new EventObject(InternalEvent.CELLS_ADDED, { cells, parent, @@ -825,7 +861,7 @@ class GraphCells { * @param recurse Optional boolean which specifies if all descendants should be * autosized. Default is `true`. */ - autoSizeCell(cell: Cell, recurse: boolean = true) { + autoSizeCell(cell: Cell, recurse = true) { if (recurse) { for (const child of cell.getChildren()) { this.autoSizeCell(child); @@ -848,14 +884,9 @@ class GraphCells { * @param includeEdges Optional boolean which specifies if all connected edges * should be removed as well. Default is `true`. */ - removeCells( - cells: CellArray | null = null, - includeEdges: boolean = true - ): CellArray | null { - includeEdges = includeEdges != null ? includeEdges : true; - - if (cells == null) { - cells = this.getDeletableCells(this.graph.selection.getSelectionCells()); + removeCells(cells: CellArray | null = null, includeEdges = true) { + if (!cells) { + cells = this.getDeletableCells(this.getSelectionCells()); } // Adds all edges to the cells @@ -868,24 +899,24 @@ class GraphCells { // Removes edges that are currently not // visible as those cannot be updated - const edges = this.getDeletableCells(this.graph.edge.getAllEdges(cells)); - const dict = new Dictionary(); + const edges = this.getDeletableCells(this.getAllEdges(cells)); + const dict = new Dictionary(); for (const cell of cells) { dict.put(cell, true); } for (const edge of edges) { - if (this.graph.view.getState(edge) == null && !dict.get(edge)) { + if (!this.getView().getState(edge) && !dict.get(edge)) { dict.put(edge, true); cells.push(edge); } } } - this.graph.batchUpdate(() => { + this.batchUpdate(() => { this.cellsRemoved(cells); - this.graph.fireEvent( + this.fireEvent( new EventObject(InternalEvent.REMOVE_CELLS, { cells, includeEdges }) ); }); @@ -899,14 +930,14 @@ class GraphCells { * * @param cells Array of {@link Cell} to remove. */ - cellsRemoved(cells: CellArray): void { + cellsRemoved(cells: CellArray) { if (cells.length > 0) { - const { scale } = this.graph.view; - const tr = this.graph.view.translate; + const { scale } = this.getView(); + const tr = this.getView().translate; - this.graph.batchUpdate(() => { + this.batchUpdate(() => { // Creates hashtable for faster lookup - const dict = new Dictionary(); + const dict = new Dictionary(); for (const cell of cells) { dict.put(cell, true); @@ -919,13 +950,13 @@ class GraphCells { const disconnectTerminal = (edge: Cell, source: boolean) => { let geo = edge.getGeometry(); - if (geo != null) { + if (geo) { // Checks if terminal is being removed const terminal = edge.getTerminal(source); let connected = false; let tmp = terminal; - while (tmp != null) { + while (tmp) { if (cell === tmp) { connected = true; break; @@ -934,29 +965,30 @@ class GraphCells { } if (connected) { - geo = geo.clone(); - const state = this.graph.view.getState(edge); + geo = geo.clone(); + const state = this.getView().getState(edge); - if (state != null && state.absolutePoints != null) { - const pts = state.absolutePoints; + if (state) { + const pts = state.absolutePoints; const n = source ? 0 : pts.length - 1; + const p = pts[n] as Point; geo.setTerminalPoint( new Point( - pts[n].x / scale - tr.x - (state.origin).x, - pts[n].y / scale - tr.y - (state.origin).y + p.x / scale - tr.x - state.origin.x, + p.y / scale - tr.y - state.origin.y ), source ); - } else { + } else if (terminal) { // Fallback to center of terminal if routing // points are not available to add new point // KNOWN: Should recurse to find parent offset // of edge for nested groups but invisible edges // should be removed in removeCells step - const tstate = this.graph.view.getState(terminal); + const tstate = this.getView().getState(terminal); - if (tstate != null) { + if (tstate) { geo.setTerminalPoint( new Point( tstate.getCenterX() / scale - tr.x, @@ -967,8 +999,8 @@ class GraphCells { } } - this.graph.model.setGeometry(edge, geo); - this.graph.model.setTerminal(edge, null, source); + this.getModel().setGeometry(edge, geo); + this.getModel().setTerminal(edge, null, source); } } }; @@ -981,10 +1013,10 @@ class GraphCells { } } - this.graph.model.remove(cell); + this.getModel().remove(cell); } - this.graph.fireEvent(new EventObject(InternalEvent.CELLS_REMOVED, { cells })); + this.fireEvent(new EventObject(InternalEvent.CELLS_REMOVED, { cells })); }); } } @@ -1006,18 +1038,18 @@ class GraphCells { * connected edges should be changed as well. Default is `true`. */ toggleCells( - show: boolean = false, + show = false, cells: CellArray = this.getSelectionCells(), - includeEdges: boolean = true - ): CellArray | null { + includeEdges = true + ) { // Adds all connected edges recursively if (includeEdges) { cells = this.addAllEdges(cells); } - this.graph.batchUpdate(() => { + this.batchUpdate(() => { this.cellsToggled(cells, show); - this.graph.fireEvent( + this.fireEvent( new EventObject(InternalEvent.TOGGLE_CELLS, { show, cells, includeEdges }) ); }); @@ -1030,11 +1062,11 @@ class GraphCells { * @param cells Array of {@link Cell} whose visible state should be changed. * @param show Boolean that specifies the visible state to be assigned. */ - cellsToggled(cells: CellArray, show: boolean = false): void { + cellsToggled(cells: CellArray, show = false) { if (cells.length > 0) { - this.graph.batchUpdate(() => { + this.batchUpdate(() => { for (const cell of cells) { - this.graph.model.setVisible(cell, show); + this.getModel().setVisible(cell, show); } }); } @@ -1051,10 +1083,10 @@ class GraphCells { * * @param cell {@link mxCell} whose size should be updated. */ - updateCellSize(cell: Cell, ignoreChildren: boolean = false): Cell { - this.graph.batchUpdate(() => { + updateCellSize(cell: Cell, ignoreChildren = false) { + this.batchUpdate(() => { this.cellSizeUpdated(cell, ignoreChildren); - this.graph.fireEvent( + this.fireEvent( new EventObject(InternalEvent.UPDATE_CELL_SIZE, { cell, ignoreChildren }) ); }); @@ -1067,15 +1099,16 @@ class GraphCells { * * @param cell {@link mxCell} for which the size should be changed. */ - cellSizeUpdated(cell: Cell, ignoreChildren: boolean = false): void { - this.graph.batchUpdate(() => { + cellSizeUpdated(cell: Cell, ignoreChildren = false) { + this.batchUpdate(() => { const size = this.getPreferredSizeForCell(cell); let geo = cell.getGeometry(); - if (size != null && geo != null) { + if (size && geo) { const collapsed = cell.isCollapsed(); - geo = geo.clone(); + geo = geo.clone(); + /* disable swimlane for now if (this.graph.swimlane.isSwimlane(cell)) { const style = this.getCellStyle(cell); let cellStyle = cell.getStyle(); @@ -1102,35 +1135,35 @@ class GraphCells { geo.height = size.height; } - this.graph.model.setStyle(cell, cellStyle); - } else { - const state = this.graph.view.createState(cell); - const align = state.style.align || ALIGN_CENTER; + this.getModel().setStyle(cell, cellStyle); + } else {*/ + const state = this.getView().createState(cell); + const align = state.style.align || ALIGN_CENTER; - if (align === ALIGN_RIGHT) { - geo.x += geo.width - size.width; - } else if (align === ALIGN_CENTER) { - geo.x += Math.round((geo.width - size.width) / 2); - } - - const valign = state.getVerticalAlign(); - - if (valign === ALIGN_BOTTOM) { - geo.y += geo.height - size.height; - } else if (valign === ALIGN_MIDDLE) { - geo.y += Math.round((geo.height - size.height) / 2); - } - - geo.width = size.width; - geo.height = size.height; + if (align === ALIGN_RIGHT) { + geo.x += geo.width - size.width; + } else if (align === ALIGN_CENTER) { + geo.x += Math.round((geo.width - size.width) / 2); } + const valign = state.getVerticalAlign(); + + if (valign === ALIGN_BOTTOM) { + geo.y += geo.height - size.height; + } else if (valign === ALIGN_MIDDLE) { + geo.y += Math.round((geo.height - size.height) / 2); + } + + geo.width = size.width; + geo.height = size.height; + /*}*/ + if (!ignoreChildren && !collapsed) { - const bounds = this.graph.view.getBounds(cell.getChildren()); + const bounds = this.getView().getBounds(cell.getChildren()); if (bounds != null) { - const tr = this.graph.view.translate; - const { scale } = this.view; + const tr = this.getView().translate; + const { scale } = this.getView(); const width = (bounds.x + bounds.width) / scale - geo.x - tr.x; const height = (bounds.y + bounds.height) / scale - geo.y - tr.y; @@ -1169,10 +1202,10 @@ class GraphCells { * @param cell {@link mxCell} for which the preferred size should be returned. * @param textWidth Optional maximum text width for word wrapping. */ - getPreferredSizeForCell(cell: Cell, textWidth: number | null = null): Rectangle | null { + getPreferredSizeForCell(cell: Cell, textWidth: number | null = null) { let result = null; - const state = this.graph.view.createState(cell); + const state = this.getView().createState(cell); const { style } = state; if (!cell.isEdge()) { @@ -1181,14 +1214,14 @@ class GraphCells { let dy = 0; // Adds dimension of image if shape is a label - if (state.getImage() != null || style.image != null) { + if (state.getImageSrc() || style.image) { if (style.shape === SHAPE_LABEL) { if (style.verticalAlign === ALIGN_MIDDLE) { - dx += parseFloat(style.imageWidth) || new Label().imageSize; + dx += style.imageWidth || DEFAULT_IMAGESIZE; } if (style.align !== ALIGN_CENTER) { - dy += parseFloat(style.imageHeight) || new Label().imageSize; + dy += style.imageHeight || DEFAULT_IMAGESIZE; } } } @@ -1207,15 +1240,15 @@ class GraphCells { // for image spacing const image = this.getFoldingImage(state); - if (image != null) { + if (image) { dx += image.width + 8; } // Adds space for label - let value = this.cellRenderer.getLabelValue(state); + let value = this.getCellRenderer().getLabelValue(state); - if (value != null && value.length > 0) { - if (!this.isHtmlLabel(state.cell)) { + if (value && value.length > 0) { + if (!this.isHtmlLabel(state.cell)) { value = htmlEntities(value, false); } @@ -1237,14 +1270,14 @@ class GraphCells { width = tmp; } - if (this.gridEnabled) { - width = this.snap(width + this.gridSize / 2); - height = this.snap(height + this.gridSize / 2); + if (this.isGridEnabled()) { + width = this.snap(width + this.getGridSize() / 2); + height = this.snap(height + this.getGridSize() / 2); } result = new Rectangle(0, 0, width, height); } else { - const gs2 = 4 * this.gridSize; + const gs2 = 4 * this.getGridSize(); result = new Rectangle(0, 0, gs2, gs2); } } @@ -1259,7 +1292,7 @@ class GraphCells { * @param cell {@link mxCell} whose bounds should be changed. * @param bounds {@link mxRectangle} that represents the new bounds. */ - resizeCell(cell: Cell, bounds: Rectangle, recurse: boolean = false): Cell { + resizeCell(cell: Cell, bounds: Rectangle, recurse = false) { return this.resizeCells(new CellArray(cell), [bounds], recurse)[0]; } @@ -1274,11 +1307,11 @@ class GraphCells { resizeCells( cells: CellArray, bounds: Rectangle[], - recurse: boolean = this.isRecursiveResize() + recurse = this.isRecursiveResize() ): CellArray { - this.graph.batchUpdate(() => { + this.batchUpdate(() => { const prev = this.cellsResized(cells, bounds, recurse); - this.graph.fireEvent( + this.fireEvent( new EventObject(InternalEvent.RESIZE_CELLS, { cells, bounds, prev }) ); }); @@ -1327,15 +1360,11 @@ class GraphCells { * @param bounds Array of {@link mxRectangles} that represent the new bounds. * @param recurse Optional boolean that specifies if the children should be resized. */ - cellsResized( - cells: CellArray, - bounds: Rectangle[], - recurse: boolean = false - ): (Geometry | null)[] { - const prev = []; + cellsResized(cells: CellArray, bounds: Rectangle[], recurse = false) { + const prev: (Geometry | null)[] = []; if (cells.length === bounds.length) { - this.graph.batchUpdate(() => { + this.batchUpdate(() => { cells.forEach((cell, i) => { prev.push(this.cellResized(cell, bounds[i], false, recurse)); @@ -1346,11 +1375,11 @@ class GraphCells { this.constrainChild(cell); }); - if (this.resetEdgesOnResize) { + if (this.isResetEdgesOnResize()) { this.resetEdges(cells); } - this.graph.fireEvent( + this.fireEvent( new EventObject(InternalEvent.CELLS_RESIZED, { cells, bounds, prev }) ); }); @@ -1367,16 +1396,11 @@ class GraphCells { * @param ignoreRelative Boolean that indicates if relative cells should be ignored. * @param recurse Optional boolean that specifies if the children should be resized. */ - cellResized( - cell: Cell, - bounds: Rectangle, - ignoreRelative: boolean = false, - recurse: boolean = false - ): Geometry | null { + cellResized(cell: Cell, bounds: Rectangle, ignoreRelative = false, recurse = false) { const prev = cell.getGeometry(); if ( - prev != null && + prev && (prev.x !== bounds.x || prev.y !== bounds.y || prev.width !== bounds.width || @@ -1387,7 +1411,7 @@ class GraphCells { if (!ignoreRelative && geo.relative) { const { offset } = geo; - if (offset != null) { + if (offset) { offset.x += bounds.x - geo.x; offset.y += bounds.y - geo.y; } @@ -1404,15 +1428,16 @@ class GraphCells { geo.y = Math.max(0, geo.y); } - this.graph.batchUpdate(() => { + this.batchUpdate(() => { if (recurse) { this.resizeChildCells(cell, geo); } - this.graph.model.setGeometry(cell, geo); + this.getModel().setGeometry(cell, geo); this.constrainChildCells(cell); }); } + return prev; } @@ -1423,13 +1448,16 @@ class GraphCells { * @param cell {@link mxCell} that has been resized. * @param newGeo {@link mxGeometry} that represents the new bounds. */ - resizeChildCells(cell: Cell, newGeo: Geometry): void { - const geo = cell.getGeometry(); - const dx = geo.width !== 0 ? newGeo.width / geo.width : 1; - const dy = geo.height !== 0 ? newGeo.height / geo.height : 1; + resizeChildCells(cell: Cell, newGeo: Geometry) { + const geo = cell.getGeometry(); - for (const child of cell.getChildren()) { - this.scaleCell(child, dx, dy, true); + if (geo) { + const dx = geo.width !== 0 ? newGeo.width / geo.width : 1; + const dy = geo.height !== 0 ? newGeo.height / geo.height : 1; + + for (const child of cell.getChildren()) { + this.scaleCell(child, dx, dy, true); + } } } @@ -1438,7 +1466,7 @@ class GraphCells { * * @param cell {@link mxCell} that has been resized. */ - constrainChildCells(cell: Cell): void { + constrainChildCells(cell: Cell) { for (const child of cell.getChildren()) { this.constrainChild(child); } @@ -1453,12 +1481,12 @@ class GraphCells { * @param dy Vertical scaling factor. * @param recurse Boolean indicating if the child cells should be scaled. */ - scaleCell(cell: Cell, dx: number, dy: number, recurse: boolean = false): void { + scaleCell(cell: Cell, dx: number, dy: number, recurse = false) { let geo = cell.getGeometry(); - if (geo != null) { + if (geo) { const style = this.getCurrentCellStyle(cell); - geo = geo.clone(); + geo = geo.clone(); // Stores values for restoring based on style const { x } = geo; @@ -1468,15 +1496,15 @@ class GraphCells { geo.scale(dx, dy, style.aspect === 'fixed'); - if (style.resizeWidth == '1') { + if (style.resizeWidth) { geo.width = w * dx; - } else if (style.resizeWidth == '0') { + } else if (!style.resizeWidth) { geo.width = w; } - if (style.resizeHeight == '1') { + if (style.resizeHeight) { geo.height = h * dy; - } else if (style.resizeHeight == '0') { + } else if (!style.resizeHeight) { geo.height = h; } @@ -1493,7 +1521,7 @@ class GraphCells { if (cell.isVertex()) { this.cellResized(cell, geo, true, recurse); } else { - this.graph.model.setGeometry(cell, geo); + this.getModel().setGeometry(cell, geo); } } } @@ -1504,19 +1532,19 @@ class GraphCells { * * @param cell {@link mxCell} that has been resized. */ - extendParent(cell: Cell): void { - const parent = cell.getParent(); + extendParent(cell: Cell) { + const parent = cell.getParent(); let p = parent.getGeometry(); - if (parent != null && p != null && !parent.isCollapsed()) { + if (parent && p && !parent.isCollapsed()) { const geo = cell.getGeometry(); if ( - geo != null && + geo && !geo.relative && (p.width < geo.x + geo.width || p.height < geo.y + geo.height) ) { - p = p.clone(); + p = p.clone(); p.width = Math.max(p.width, geo.x + geo.width); p.height = Math.max(p.height, geo.y + geo.height); @@ -1549,7 +1577,7 @@ class GraphCells { target: Cell | null = null, evt: InternalMouseEvent | null = null, mapping: any = {} - ): CellArray | null { + ) { return this.moveCells(cells, dx, dy, true, target, evt, mapping); } @@ -1580,39 +1608,33 @@ class GraphCells { */ moveCells( cells: CellArray, - dx: number, - dy: number, - clone: boolean = false, + dx: number = 0, + dy: number = 0, + clone = false, target: Cell | null = null, evt: InternalMouseEvent | null = null, mapping: any = null - ): CellArray | null { - dx = dx != null ? dx : 0; - dy = dy != null ? dy : 0; - clone = clone != null ? clone : false; - - // alert(`moveCells: ${cells} ${dx} ${dy} ${clone} ${target}`) - - if (dx !== 0 || dy !== 0 || clone || target != null) { + ) { + if (dx !== 0 || dy !== 0 || clone || target) { // Removes descendants with ancestors in cells to avoid multiple moving cells = cells.getTopmostCells(); const origCells = cells; - this.graph.batchUpdate(() => { + this.batchUpdate(() => { // Faster cell lookups to remove relative edge labels with selected // terminals to avoid explicit and implicit move at same time - const dict = new Dictionary(); + const dict = new Dictionary(); for (const cell of cells) { dict.put(cell, true); } const isSelected = (cell: Cell | null) => { - while (cell != null) { + while (cell) { if (dict.get(cell)) { return true; } - cell = cell.getParent(); + cell = cell.getParent(); } return false; }; @@ -1625,7 +1647,7 @@ class GraphCells { const parent = cell.getParent(); if ( - geo == null || + !geo || !geo.relative || (parent && !parent.isEdge()) || (parent && @@ -1639,9 +1661,9 @@ class GraphCells { cells = checked; if (clone) { - cells = this.cloneCells(cells, this.isCloneInvalidEdges(), mapping); + cells = this.cloneCells(cells, this.isCloneInvalidEdges(), mapping); - if (target == null) { + if (!target) { target = this.getDefaultParent(); } } @@ -1652,7 +1674,7 @@ class GraphCells { // allow for temporary negative numbers until cellsAdded is called. const previous = this.isAllowNegativeCoordinates(); - if (target != null) { + if (target) { this.setAllowNegativeCoordinates(true); } @@ -1661,13 +1683,13 @@ class GraphCells { dx, dy, !clone && this.isDisconnectOnMove() && this.isAllowDanglingEdges(), - target == null, - this.isExtendParentsOnMove() && target == null + !target, + this.isExtendParentsOnMove() && !target ); this.setAllowNegativeCoordinates(previous); - if (target != null) { + if (target) { const index = target.getChildCount(); this.cellsAdded(cells, target, index, null, null, true); @@ -1675,22 +1697,22 @@ class GraphCells { if (clone) { cells.forEach((cell, i) => { const geo = cell.getGeometry(); - const parent = origCells[i].getParent(); + const parent = origCells[i].getParent(); if ( - geo != null && + geo && geo.relative && parent.isEdge() && - this.graph.model.contains(parent) + this.getModel().contains(parent) ) { - this.graph.model.add(parent, cell); + this.getModel().add(parent, cell); } }); } } // Dispatches a move event - this.graph.fireEvent( + this.fireEvent( new EventObject(InternalEvent.MOVE_CELLS, { cells, dx, @@ -1716,12 +1738,12 @@ class GraphCells { cells: CellArray, dx: number, dy: number, - disconnect: boolean = false, - constrain: boolean = false, - extend: boolean = false - ): void { + disconnect = false, + constrain = false, + extend = false + ) { if (dx !== 0 || dy !== 0) { - this.graph.batchUpdate(() => { + this.batchUpdate(() => { if (disconnect) { this.disconnectGraph(cells); } @@ -1736,11 +1758,11 @@ class GraphCells { } } - if (this.resetEdgesOnMove) { + if (this.isResetEdgesOnMove()) { this.resetEdges(cells); } - this.graph.fireEvent( + this.fireEvent( new EventObject(InternalEvent.CELLS_MOVED, { cells, dx, dy, disconnect }) ); }); @@ -1751,13 +1773,11 @@ class GraphCells { * Translates the geometry of the given cell and stores the new, * translated geometry in the model as an atomic change. */ - translateCell(cell: Cell, dx: number, dy: number): void { + translateCell(cell: Cell, dx: number, dy: number) { let geometry = cell.getGeometry(); - if (geometry != null) { - dx = parseFloat(String(dx)); - dy = parseFloat(String(dy)); - geometry = geometry.clone(); + if (geometry) { + geometry = geometry.clone(); geometry.translate(dx, dy); if (!geometry.relative && cell.isVertex() && !this.isAllowNegativeCoordinates()) { @@ -1783,14 +1803,14 @@ class GraphCells { dy = pt.y; } - if (geometry.offset == null) { + if (!geometry.offset) { geometry.offset = new Point(dx, dy); } else { - geometry.offset.x = parseFloat(geometry.offset.x) + dx; - geometry.offset.y = parseFloat(geometry.offset.y) + dy; + geometry.offset.x = geometry.offset.x + dx; + geometry.offset.y = geometry.offset.y + dy; } } - this.graph.model.setGeometry(cell, geometry); + this.getModel().setGeometry(cell, geometry); } } @@ -1799,19 +1819,20 @@ class GraphCells { * * @param cell {@link mxCell} for which the area should be returned. */ - getCellContainmentArea(cell: Cell): Rectangle | null { + getCellContainmentArea(cell: Cell) { if (!cell.isEdge()) { const parent = cell.getParent(); - if (parent != null && parent !== this.getDefaultParent()) { + if (parent && parent !== this.getDefaultParent()) { const g = parent.getGeometry(); - if (g != null) { + if (g) { let x = 0; let y = 0; let w = g.width; let h = g.height; + /* disable swimlane for now if (this.isSwimlane(parent)) { const size = this.getStartSize(parent); const style = this.getCurrentCellStyle(parent); @@ -1838,6 +1859,7 @@ class GraphCells { w -= size.width; h -= size.height; } + */ return new Rectangle(x, y, w, h); } @@ -1855,19 +1877,19 @@ class GraphCells { * @param cell {@link mxCell} which should be constrained. * @param sizeFirst Specifies if the size should be changed first. Default is `true`. */ - constrainChild(cell: Cell, sizeFirst: boolean = true): void { + constrainChild(cell: Cell, sizeFirst = true) { let geo = cell.getGeometry(); - if (geo != null && (this.isConstrainRelativeChildren() || !geo.relative)) { + if (geo && (this.isConstrainRelativeChildren() || !geo.relative)) { const parent = cell.getParent(); - const pgeo = (parent).getGeometry(); + const pgeo = parent.getGeometry(); let max = this.getMaximumGraphBounds(); // Finds parent offset - if (max != null) { - const off = this.getBoundingBoxFromGeometry(new CellArray(parent), false); + if (max) { + const off = this.getBoundingBoxFromGeometry(new CellArray(parent), false); - if (off != null) { + if (off) { max = Rectangle.fromRectangle(max); max.x -= off.x; @@ -1878,7 +1900,7 @@ class GraphCells { if (this.isConstrainChild(cell)) { let tmp = this.getCellContainmentArea(cell); - if (tmp != null) { + if (tmp) { const overlap = this.getOverlap(cell); if (overlap > 0) { @@ -1891,7 +1913,7 @@ class GraphCells { } // Find the intersection between max and tmp - if (max == null) { + if (!max) { max = tmp; } else { max = Rectangle.fromRectangle(max); @@ -1900,7 +1922,7 @@ class GraphCells { } } - if (max != null) { + if (max) { const cells = new CellArray(cell); if (!cell.isCollapsed()) { @@ -1915,7 +1937,7 @@ class GraphCells { const bbox = this.getBoundingBoxFromGeometry(cells, false); - if (bbox != null) { + if (bbox) { geo = geo.clone(); // Cumulative horizontal movement @@ -1953,7 +1975,7 @@ class GraphCells { if (dx !== 0 || dy !== 0) { if (geo.relative) { // Relative geometries are moved via absolute offset - if (geo.offset == null) { + if (!geo.offset) { geo.offset = new Point(); } @@ -1965,7 +1987,7 @@ class GraphCells { } } - this.graph.model.setGeometry(cell, geo); + this.getModel().setGeometry(cell, geo); } } } @@ -1985,11 +2007,7 @@ class GraphCells { * @param edges Optional boolean that specifies if child edges should * be returned. Default is `false`. */ - getChildCells( - parent: Cell = this.getDefaultParent(), - vertices: boolean = false, - edges: boolean = false - ): CellArray { + getChildCells(parent: Cell = this.getDefaultParent(), vertices = false, edges = false) { const cells = parent.getChildCells(vertices, edges); const result = new CellArray(); @@ -2024,38 +2042,38 @@ class GraphCells { getCellAt( x: number, y: number, - parent: Cell, - vertices: boolean = true, - edges: boolean = true, + parent: Cell | null = null, + vertices = true, + edges = true, ignoreFn: Function | null = null ): Cell | null { - if (parent == null) { - parent = this.getCurrentRoot(); + if (!parent) { + parent = this.getCurrentRoot(); - if (parent == null) { - parent = this.graph.model.getRoot(); + if (!parent) { + parent = this.getModel().getRoot(); } } - if (parent != null) { + if (parent) { const childCount = parent.getChildCount(); for (let i = childCount - 1; i >= 0; i--) { - const cell = parent.getChildAt(i); + const cell = parent.getChildAt(i); const result = this.getCellAt(x, y, cell, vertices, edges, ignoreFn); - if (result != null) { + if (result) { return result; } if ( cell.isVisible() && ((edges && cell.isEdge()) || (vertices && cell.isVertex())) ) { - const state = this.graph.view.getState(cell); + const state = this.getView().getState(cell); if ( - state != null && - (ignoreFn == null || !ignoreFn(state, x, y)) && + state && + (!ignoreFn || !ignoreFn(state, x, y)) && this.intersects(state, x, y) ) { return cell; @@ -2089,44 +2107,36 @@ class GraphCells { result: CellArray = new CellArray(), intersection: Rectangle | null = null, ignoreFn: Function | null = null, - includeDescendants: boolean = false - ): CellArray { - if (width > 0 || height > 0 || intersection != null) { - const model = this.graph.model; + includeDescendants = false + ) { + if (width > 0 || height > 0 || intersection) { + const model = this.getModel(); const right = x + width; const bottom = y + height; - if (parent == null) { + if (!parent) { parent = this.getCurrentRoot(); - if (parent == null) { + if (!parent) { parent = model.getRoot(); } } - if (parent != null) { - const childCount = parent.getChildCount(); - + if (parent) { for (const cell of parent.getChildren()) { - const state: CellState = this.graph.view.getState(cell); + const state = this.getView().getState(cell); - if ( - state != null && - cell.isVisible() && - (ignoreFn == null || !ignoreFn(state)) - ) { - const deg = getValue(state.style, 'rotation') || 0; + if (state && cell.isVisible() && (!ignoreFn || !ignoreFn(state))) { + const deg = state.style.rotation; let box: CellState | Rectangle = state; // TODO: CHECK ME!!!! ========================================================== - if (deg != 0) { + if (deg !== 0) { box = getBoundingBox(box, deg); } const hit = - (intersection != null && - cell.isVertex() && - intersects(intersection, box)) || - (intersection == null && + (intersection && cell.isVertex() && intersects(intersection, box)) || + (!intersection && (cell.isEdge() || cell.isVertex()) && box.x >= x && box.y + box.height <= bottom && @@ -2179,20 +2189,20 @@ class GraphCells { x0: number, y0: number, parent: Cell | null = null, - rightHalfpane: boolean = false, - bottomHalfpane: boolean = false + rightHalfpane = false, + bottomHalfpane = false ) { const result = []; if (rightHalfpane || bottomHalfpane) { - if (parent == null) { - parent = this.graph.getDefaultParent(); + if (!parent) { + parent = this.getDefaultParent(); } - if (parent != null) { + if (parent) { for (const child of parent.getChildren()) { - const state = this.graph.view.getState(child); - if (child.isVisible() && state != null) { + const state = this.getView().getState(child); + if (child.isVisible() && state) { if ((!rightHalfpane || state.x >= x0) && (!bottomHalfpane || state.y >= y0)) { result.push(child); } @@ -2212,26 +2222,29 @@ class GraphCells { * @param y Y-coordinate of the location to be checked. */ intersects(state: CellState, x: number, y: number): boolean { - const pts = state.absolutePoints; + const pts = state.absolutePoints; - if (pts != null) { - const t2 = this.tolerance * this.tolerance; + if (pts.length > 0) { + const t2 = this.getTolerance() * this.getTolerance(); let pt = pts[0]; for (let i = 1; i < pts.length; i += 1) { const next = pts[i]; - const dist = ptSegDistSq(pt.x, pt.y, next.x, next.y, x, y); - if (dist <= t2) { - return true; + if (pt && next) { + const dist = ptSegDistSq(pt.x, pt.y, next.x, next.y, x, y); + + if (dist <= t2) { + return true; + } } pt = next; } } else { - const alpha = toRadians(getValue(state.style, 'rotation') || 0); + const alpha = toRadians(state.style.rotation); - if (alpha != 0) { + if (alpha !== 0) { const cos = Math.cos(-alpha); const sin = Math.sin(-alpha); const cx = new Point(state.getCenterX(), state.getCenterY()); @@ -2256,8 +2269,8 @@ class GraphCells { * @param parent {@link mxCell} the possible parent cell * @param recurse boolean whether or not to recurse the child ancestors */ - isValidAncestor(cell: Cell, parent: Cell, recurse: boolean = false): boolean { - return recurse ? parent.isAncestor(cell) : cell.getParent() == parent; + isValidAncestor(cell: Cell, parent: Cell, recurse: boolean = false) { + return recurse ? parent.isAncestor(cell) : cell.getParent() === parent; } /***************************************************************************** @@ -2271,12 +2284,10 @@ class GraphCells { * * @param cell {@link mxCell} whose locked state should be returned. */ - isCellLocked(cell: Cell): boolean { + isCellLocked(cell: Cell) { const geometry = cell.getGeometry(); - return ( - this.isCellsLocked() || (geometry != null && cell.isVertex() && geometry.relative) - ); + return this.isCellsLocked() || (geometry && cell.isVertex() && geometry.relative); } /** @@ -2286,7 +2297,7 @@ class GraphCells { * * @param cell {@link mxCell} whose locked state should be returned. */ - isCellsLocked(): boolean { + isCellsLocked() { return this.cellsLocked; } @@ -2303,8 +2314,8 @@ class GraphCells { /** * Returns the cells which may be exported in the given array of cells. */ - getCloneableCells(cells: CellArray): CellArray | null { - return this.graph.model.filterCells(cells, (cell: Cell) => { + getCloneableCells(cells: CellArray) { + return this.getModel().filterCells(cells, (cell: Cell) => { return this.isCellCloneable(cell); }); } @@ -2316,16 +2327,16 @@ class GraphCells { * * @param cell Optional {@link Cell} whose cloneable state should be returned. */ - isCellCloneable(cell: Cell): boolean { + isCellCloneable(cell: Cell) { const style = this.getCurrentCellStyle(cell); - return this.isCellsCloneable() && style.cloneable !== 0; + return this.isCellsCloneable() && style.cloneable; } /** * Returns {@link cellsCloneable}, that is, if the graph allows cloning of cells * by using control-drag. */ - isCellsCloneable(): boolean { + isCellsCloneable() { return this.cellsCloneable; } @@ -2336,15 +2347,15 @@ class GraphCells { * * @param value Boolean indicating if the graph should be cloneable. */ - setCellsCloneable(value: boolean): void { + setCellsCloneable(value: boolean) { this.cellsCloneable = value; } /** * Returns the cells which may be exported in the given array of cells. */ - getExportableCells(cells: CellArray): CellArray | null { - return this.graph.model.filterCells(cells, (cell: Cell) => { + getExportableCells(cells: CellArray) { + return this.getModel().filterCells(cells, (cell: Cell) => { return this.canExportCell(cell); }); } @@ -2355,15 +2366,15 @@ class GraphCells { * * @param cell {@link mxCell} that represents the cell to be exported. */ - canExportCell(cell: Cell | null = null): boolean { - return this.exportEnabled; + canExportCell(cell: Cell | null = null) { + return this.isExportEnabled(); } /** * Returns the cells which may be imported in the given array of cells. */ - getImportableCells(cells: CellArray): CellArray | null { - return this.graph.model.filterCells(cells, (cell: Cell) => { + getImportableCells(cells: CellArray) { + return this.getModel().filterCells(cells, (cell: Cell) => { return this.canImportCell(cell); }); } @@ -2374,8 +2385,8 @@ class GraphCells { * * @param cell {@link mxCell} that represents the cell to be imported. */ - canImportCell(cell: Cell | null = null): boolean { - return this.importEnabled; + canImportCell(cell: Cell | null = null) { + return this.isImportEnabled(); } /** @@ -2401,29 +2412,29 @@ class GraphCells { * * @param cell {@link mxCell} whose selectable state should be returned. */ - isCellSelectable(cell: Cell): boolean { + isCellSelectable(cell: Cell) { return this.isCellsSelectable(); } /** * Returns {@link cellsSelectable}. */ - isCellsSelectable(): boolean { + isCellsSelectable() { return this.cellsSelectable; } /** * Sets {@link cellsSelectable}. */ - setCellsSelectable(value: boolean): void { + setCellsSelectable(value: boolean) { this.cellsSelectable = value; } /** * Returns the cells which may be exported in the given array of cells. */ - getDeletableCells(cells: CellArray): CellArray | null { - return this.graph.model.filterCells(cells, (cell: Cell) => { + getDeletableCells(cells: CellArray) { + return this.getModel().filterCells(cells, (cell: Cell) => { return this.isCellDeletable(cell); }); } @@ -2435,15 +2446,15 @@ class GraphCells { * * @param cell {@link mxCell} whose deletable state should be returned. */ - isCellDeletable(cell: Cell): boolean { + isCellDeletable(cell: Cell) { const style = this.getCurrentCellStyle(cell); - return this.isCellsDeletable() && style.deletable !== 0; + return this.isCellsDeletable() && style.deletable; } /** * Returns {@link cellsDeletable}. */ - isCellsDeletable(): boolean { + isCellsDeletable() { return this.cellsDeletable; } @@ -2452,7 +2463,7 @@ class GraphCells { * * @param value Boolean indicating if the graph should allow deletion of cells. */ - setCellsDeletable(value: boolean): void { + setCellsDeletable(value: boolean) { this.cellsDeletable = value; } @@ -2462,16 +2473,16 @@ class GraphCells { * * @param cell {@link mxCell} whose rotatable state should be returned. */ - isCellRotatable(cell: Cell): boolean { + isCellRotatable(cell: Cell) { const style = this.getCurrentCellStyle(cell); - return style.rotatable !== 0; + return style.rotatable; } /** * Returns the cells which are movable in the given array of cells. */ - getMovableCells(cells: CellArray): CellArray | null { - return this.graph.model.filterCells(cells, (cell: Cell) => { + getMovableCells(cells: CellArray) { + return this.getModel().filterCells(cells, (cell: Cell) => { return this.isCellMovable(cell); }); } @@ -2483,16 +2494,16 @@ class GraphCells { * * @param cell {@link mxCell} whose movable state should be returned. */ - isCellMovable(cell: Cell): boolean { + isCellMovable(cell: Cell) { const style = this.getCurrentCellStyle(cell); - return this.isCellsMovable() && !this.isCellLocked(cell) && style.movable !== 0; + return this.isCellsMovable() && !this.isCellLocked(cell) && style.movable; } /** * Returns {@link cellsMovable}. */ - isCellsMovable(): boolean { + isCellsMovable() { return this.cellsMovable; } @@ -2502,7 +2513,7 @@ class GraphCells { * * @param value Boolean indicating if the graph should allow moving of cells. */ - setCellsMovable(value: boolean): void { + setCellsMovable(value: boolean) { this.cellsMovable = value; } @@ -2514,21 +2525,18 @@ class GraphCells { * * @param cell {@link mxCell} whose resizable state should be returned. */ - isCellResizable(cell: Cell): boolean { + isCellResizable(cell: Cell) { const style = this.getCurrentCellStyle(cell); - const r = - this.isCellsResizable() && - !this.isCellLocked(cell) && - getValue(style, 'resizeable', '1') != '0'; - // alert(r); + const r = this.isCellsResizable() && !this.isCellLocked(cell) && style.resizeable; + return r; } /** * Returns {@link cellsResizable}. */ - isCellsResizable(): boolean { + isCellsResizable() { return this.cellsResizable; } @@ -2539,7 +2547,7 @@ class GraphCells { * @param value Boolean indicating if the graph should allow resizing of * cells. */ - setCellsResizable(value: boolean): void { + setCellsResizable(value: boolean) { this.cellsResizable = value; } @@ -2550,16 +2558,16 @@ class GraphCells { * * @param cell {@link mxCell} whose bendable state should be returned. */ - isCellBendable(cell: Cell): boolean { + isCellBendable(cell: Cell) { const style = this.getCurrentCellStyle(cell); - return this.isCellsBendable() && !this.isCellLocked(cell) && style.bendable !== 0; + return this.isCellsBendable() && !this.isCellLocked(cell) && style.bendable; } /** * Returns {@link cellsBenadable}. */ - isCellsBendable(): boolean { + isCellsBendable() { return this.cellsBendable; } @@ -2570,7 +2578,7 @@ class GraphCells { * @param value Boolean indicating if the graph should allow bending of * edges. */ - setCellsBendable(value: boolean): void { + setCellsBendable(value: boolean) { this.cellsBendable = value; } @@ -2582,16 +2590,16 @@ class GraphCells { * * @param cell {@link mxCell} that should be resized. */ - isAutoSizeCell(cell: Cell): boolean { + isAutoSizeCell(cell: Cell) { const style = this.getCurrentCellStyle(cell); - return this.isAutoSizeCells() || style.autosize == 1; + return this.isAutoSizeCells() || style.autosize; } /** * Returns {@link autoSizeCells}. */ - isAutoSizeCells(): boolean { + isAutoSizeCells() { return this.autoSizeCells; } @@ -2604,7 +2612,7 @@ class GraphCells { * @param value Boolean indicating if cells should be resized * automatically. */ - setAutoSizeCells(value: boolean): void { + setAutoSizeCells(value: boolean) { this.autoSizeCells = value; } @@ -2615,14 +2623,14 @@ class GraphCells { * * @param cell {@link mxCell} that has been resized. */ - isExtendParent(cell: Cell): boolean { + isExtendParent(cell: Cell) { return !cell.isEdge() && this.isExtendParents(); } /** * Returns {@link extendParents}. */ - isExtendParents(): boolean { + isExtendParents() { return this.extendParents; } @@ -2631,14 +2639,14 @@ class GraphCells { * * @param value New boolean value for {@link extendParents}. */ - setExtendParents(value: boolean): void { + setExtendParents(value: boolean) { this.extendParents = value; } /** * Returns {@link extendParentsOnAdd}. */ - isExtendParentsOnAdd(cell: Cell): boolean { + isExtendParentsOnAdd(cell: Cell) { return this.extendParentsOnAdd; } @@ -2647,14 +2655,14 @@ class GraphCells { * * @param value New boolean value for {@link extendParentsOnAdd}. */ - setExtendParentsOnAdd(value: boolean): void { + setExtendParentsOnAdd(value: boolean) { this.extendParentsOnAdd = value; } /** * Returns {@link extendParentsOnMove}. */ - isExtendParentsOnMove(): boolean { + isExtendParentsOnMove() { return this.extendParentsOnMove; } @@ -2663,7 +2671,7 @@ class GraphCells { * * @param value New boolean value for {@link extendParentsOnAdd}. */ - setExtendParentsOnMove(value: boolean): void { + setExtendParentsOnMove(value: boolean) { this.extendParentsOnMove = value; } @@ -2695,26 +2703,22 @@ class GraphCells { * @param includeDescendants Optional boolean that specifies if the bounds * of all descendants should be included. Default is `false`. */ - getCellBounds( - cell: Cell, - includeEdges: boolean = false, - includeDescendants: boolean = false - ): Rectangle | null { + getCellBounds(cell: Cell, includeEdges = false, includeDescendants = false) { let cells = new CellArray(cell); // Includes all connected edges if (includeEdges) { - cells = cells.concat(cell.getEdges()); + cells = cells.concat(cell.getEdges()); } - let result = this.view.getBounds(cells); + let result = this.getView().getBounds(cells); // Recursively includes the bounds of the children if (includeDescendants) { for (const child of cell.getChildren()) { const tmp = this.getCellBounds(child, includeEdges, true); - if (result != null) { + if (result && tmp) { result.add(tmp); } else { result = tmp; @@ -2748,7 +2752,7 @@ class GraphCells { * ```javascript * if (bounds.x < 0 || bounds.y < 0) * { - * graph.view.setTranslate(-Math.min(bounds.x, 0), -Math.min(bounds.y, 0)); + * getView().setTranslate(-Math.min(bounds.x, 0), -Math.min(bounds.y, 0)); * } * ``` * @@ -2756,11 +2760,7 @@ class GraphCells { * @param includeEdges Specifies if edge bounds should be included by computing * the bounding box for all points in geometry. Default is `false`. */ - getBoundingBoxFromGeometry( - cells: CellArray, - includeEdges: boolean = false - ): Rectangle | null { - includeEdges = includeEdges != null ? includeEdges : false; + getBoundingBoxFromGeometry(cells: CellArray, includeEdges = false) { let result = null; let tmp: Rectangle | null = null; @@ -2769,13 +2769,13 @@ class GraphCells { // Computes the bounding box for the points in the geometry const geo = cell.getGeometry(); - if (geo != null) { + if (geo) { let bbox = null; if (cell.isEdge()) { const addPoint = (pt: Point | null) => { - if (pt != null) { - if (tmp == null) { + if (pt) { + if (!tmp) { tmp = new Rectangle(pt.x, pt.y, 0, 0); } else { tmp.add(new Rectangle(pt.x, pt.y, 0, 0)); @@ -2783,17 +2783,17 @@ class GraphCells { } }; - if (cell.getTerminal(true) == null) { + if (!cell.getTerminal(true)) { addPoint(geo.getTerminalPoint(true)); } - if (cell.getTerminal(false) == null) { + if (!cell.getTerminal(false)) { addPoint(geo.getTerminalPoint(false)); } const pts = geo.points; - if (pts != null && pts.length > 0) { + if (pts && pts.length > 0) { tmp = new Rectangle(pts[0].x, pts[0].y, 0, 0); for (let j = 1; j < pts.length; j++) { @@ -2803,16 +2803,13 @@ class GraphCells { bbox = tmp; } else { - const parent = cell.getParent(); + const parent = cell.getParent(); if (geo.relative) { - if ( - parent.isVertex() && - parent !== this.view.currentRoot - ) { + if (parent.isVertex() && parent !== this.getView().currentRoot) { tmp = this.getBoundingBoxFromGeometry(new CellArray(parent), false); - if (tmp != null) { + if (tmp) { bbox = new Rectangle( geo.x * tmp.width, geo.y * tmp.height, @@ -2832,22 +2829,22 @@ class GraphCells { if (parent.isVertex() && cells.indexOf(parent) >= 0) { tmp = this.getBoundingBoxFromGeometry(new CellArray(parent), false); - if (tmp != null) { + if (tmp) { bbox.x += tmp.x; bbox.y += tmp.y; } } } - if (bbox != null && geo.offset != null) { + if (bbox && geo.offset) { bbox.x += geo.offset.x; bbox.y += geo.offset.y; } const style = this.getCurrentCellStyle(cell); - if (bbox != null) { - const angle = getValue(style, 'rotation', 0); + if (bbox) { + const angle = style.rotation; if (angle !== 0) { bbox = getBoundingBox(bbox, angle); @@ -2855,8 +2852,8 @@ class GraphCells { } } - if (bbox != null) { - if (result == null) { + if (bbox) { + if (!result) { result = Rectangle.fromRectangle(bbox); } else { result.add(bbox); diff --git a/packages/core/src/view/cell/datatypes/Cell.ts b/packages/core/src/view/cell/datatypes/Cell.ts index 317474c3c..3e309a666 100644 --- a/packages/core/src/view/cell/datatypes/Cell.ts +++ b/packages/core/src/view/cell/datatypes/Cell.ts @@ -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(); diff --git a/packages/core/src/view/cell/datatypes/CellArray.ts b/packages/core/src/view/cell/datatypes/CellArray.ts index 05219d533..9b7a98e99 100644 --- a/packages/core/src/view/cell/datatypes/CellArray.ts +++ b/packages/core/src/view/cell/datatypes/CellArray.ts @@ -3,8 +3,7 @@ import Dictionary from '../../../util/Dictionary'; import ObjectIdentity from '../../../util/ObjectIdentity'; class CellArray extends Array { - // @ts-ignore - constructor(...items: Cell[] | CellArray) { + constructor(...items: Cell[]) { super(...items); } diff --git a/packages/core/src/view/cell/datatypes/CellState.ts b/packages/core/src/view/cell/datatypes/CellState.ts index e1f045b32..dbd5e5b65 100644 --- a/packages/core/src/view/cell/datatypes/CellState.ts +++ b/packages/core/src/view/cell/datatypes/CellState.ts @@ -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 - 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 { diff --git a/packages/core/src/view/cell/edge/EdgeHandler.ts b/packages/core/src/view/cell/edge/EdgeHandler.ts index fb8d67d7b..d3492fcf0 100644 --- a/packages/core/src/view/cell/edge/EdgeHandler.ts +++ b/packages/core/src/view/cell/edge/EdgeHandler.ts @@ -31,7 +31,15 @@ import { OUTLINE_HIGHLIGHT_COLOR, OUTLINE_HIGHLIGHT_STROKEWIDTH, } from '../../../util/Constants'; -import utils from '../../../util/Utils'; +import utils, { + contains, + convertPoint, + findNearestSegment, + getOffset, + intersects, + ptSegDistSq, + setOpacity, +} from '../../../util/Utils'; import ImageShape from '../../geometry/shape/node/ImageShape'; import RectangleShape from '../../geometry/shape/node/RectangleShape'; import ConnectionConstraint from '../../connection/ConnectionConstraint'; @@ -47,9 +55,14 @@ import { isMouseEvent, isShiftDown, } from '../../../util/EventUtils'; -import Graph from '../../Graph'; +import Graph, { MaxGraph } from '../../Graph'; import CellState from '../datatypes/CellState'; import Shape from '../../geometry/shape/Shape'; +import { CellHandle, ColorValue, Listenable } from 'packages/core/src/types'; +import InternalMouseEvent from '../../event/InternalMouseEvent'; +import Cell from '../datatypes/Cell'; +import ImageBox from '../../image/ImageBox'; +import Marker from '../../geometry/shape/edge/Marker'; /** * Graph event handler that reconnects edges and modifies control points and the edge @@ -66,296 +79,10 @@ import Shape from '../../geometry/shape/Shape'; * @class EdgeHandler */ class EdgeHandler { - constructor(state) { - if (state != null && state.shape != null) { - this.state = state; - this.init(); + constructor(state: CellState) { + // `state.shape` must exists. + this.state = state; - // Handles escape keystrokes - this.escapeHandler = (sender, evt) => { - const dirty = this.index != null; - this.reset(); - - if (dirty) { - this.graph.cellRenderer.redraw(this.state, false, state.view.isRendering()); - } - }; - - this.state.view.graph.addListener(InternalEvent.ESCAPE, this.escapeHandler); - } - } - - /** - * Variable: graph - * - * Reference to the enclosing . - */ - graph: Graph; - - /** - * Variable: state - * - * Reference to the being modified. - */ - state: CellState = null; - - /** - * Variable: marker - * - * Holds the which is used for highlighting terminals. - */ - // marker: any; - marker = null; - - /** - * Variable: constraintHandler - * - * Holds the used for drawing and highlighting - * constraints. - */ - constraintHandler: ConstraintHandler = null; - - /** - * Variable: error - * - * Holds the current validation error while a connection is being changed. - */ - error: string = null; - - /** - * Variable: shape - * - * Holds the that represents the preview edge. - */ - shape: Shape = null; - - /** - * Variable: bends - * - * Holds the that represent the points. - */ - // bends: mxShape[]; - bends = null; - - /** - * Variable: labelShape - * - * Holds the that represents the label position. - */ - // labelShape: mxShape; - labelShape = null; - - /** - * Variable: cloneEnabled - * - * Specifies if cloning by control-drag is enabled. Default is true. - */ - // cloneEnabled: boolean; - cloneEnabled = true; - - /** - * Variable: addEnabled - * - * Specifies if adding bends by shift-click is enabled. Default is false. - * Note: This experimental feature is not recommended for production use. - */ - // addEnabled: boolean; - addEnabled = false; - - /** - * Variable: removeEnabled - * - * Specifies if removing bends by shift-click is enabled. Default is false. - * Note: This experimental feature is not recommended for production use. - */ - // removeEnabled: boolean; - removeEnabled = false; - - /** - * Variable: dblClickRemoveEnabled - * - * Specifies if removing bends by double click is enabled. Default is false. - */ - // dblClickRemoveEnabled: boolean; - dblClickRemoveEnabled = false; - - /** - * Variable: mergeRemoveEnabled - * - * Specifies if removing bends by dropping them on other bends is enabled. - * Default is false. - */ - // mergeRemoveEnabled: boolean; - mergeRemoveEnabled = false; - - /** - * Variable: straightRemoveEnabled - * - * Specifies if removing bends by creating straight segments should be enabled. - * If enabled, this can be overridden by holding down the alt key while moving. - * Default is false. - */ - // straightRemoveEnabled: boolean; - straightRemoveEnabled = false; - - /** - * Variable: virtualBendsEnabled - * - * Specifies if virtual bends should be added in the center of each - * segments. These bends can then be used to add new waypoints. - * Default is false. - */ - // virtualBendsEnabled: boolean; - virtualBendsEnabled = false; - - /** - * Variable: virtualBendOpacity - * - * Opacity to be used for virtual bends (see ). - * Default is 20. - */ - // virtualBendOpacity: number; - virtualBendOpacity = 20; - - /** - * Variable: parentHighlightEnabled - * - * Specifies if the parent should be highlighted if a child cell is selected. - * Default is false. - */ - // parentHighlightEnabled: boolean; - parentHighlightEnabled = false; - - /** - * Variable: preferHtml - * - * Specifies if bends should be added to the graph container. This is updated - * in based on whether the edge or one of its terminals has an HTML - * label in the container. - */ - // preferHtml: boolean; - preferHtml = false; - - /** - * Variable: allowHandleBoundsCheck - * - * Specifies if the bounds of handles should be used for hit-detection in IE - * Default is true. - */ - // allowHandleBoundsCheck: boolean; - allowHandleBoundsCheck = true; - - /** - * Variable: snapToTerminals - * - * Specifies if waypoints should snap to the routing centers of terminals. - * Default is false. - */ - // snapToTerminals: boolean; - snapToTerminals = false; - - /** - * Variable: handleImage - * - * Optional to be used as handles. Default is null. - */ - // handleImage: mxImage; - handleImage = null; - - /** - * Variable: tolerance - * - * Optional tolerance for hit-detection in . Default is 0. - */ - // tolerance: number; - tolerance = 0; - - /** - * Variable: outlineConnect - * - * Specifies if connections to the outline of a highlighted target should be - * enabled. This will allow to place the connection point along the outline of - * the highlighted target. Default is false. - */ - // outlineConnect: boolean; - outlineConnect = false; - - /** - * Variable: manageLabelHandle - * - * Specifies if the label handle should be moved if it intersects with another - * handle. Uses for checking and moving. Default is false. - */ - // manageLabelHandle: boolean; - manageLabelHandle = false; - - /** - * Function: isParentHighlightVisible - * - * Returns true if the parent highlight should be visible. This implementation - * always returns true. - */ - isParentHighlightVisible() { - return !this.graph.isCellSelected(this.state.cell.getParent()); - } - - /** - * Function: updateParentHighlight - * - * Updates the highlight of the parent if is true. - */ - updateParentHighlight() { - if (!this.isDestroyed()) { - const visible = this.isParentHighlightVisible(); - const parent = this.state.cell.getParent(); - const pstate = this.graph.view.getState(parent); - - if (this.parentHighlight != null) { - if (parent.isVertex() && visible) { - const b = this.parentHighlight.bounds; - - if ( - pstate != null && - (b.x !== pstate.x || - b.y !== pstate.y || - b.width !== pstate.width || - b.height !== pstate.height) - ) { - this.parentHighlight.bounds = Rectangle.fromRectangle(pstate); - this.parentHighlight.redraw(); - } - } else { - if (pstate != null && pstate.parentHighlight === this.parentHighlight) { - pstate.parentHighlight = null; - } - - this.parentHighlight.destroy(); - this.parentHighlight = null; - } - } else if (this.parentHighlightEnabled && visible) { - if (parent.isVertex() && pstate != null && pstate.parentHighlight == null) { - this.parentHighlight = this.createParentHighlightShape(pstate); - // VML dialect required here for event transparency in IE - this.parentHighlight.dialect = DIALECT_SVG; - this.parentHighlight.pointerEvents = false; - this.parentHighlight.rotation = Number(pstate.style.rotation || '0'); - this.parentHighlight.init(this.graph.getView().getOverlayPane()); - this.parentHighlight.redraw(); - - // Shows highlight once per parent - pstate.parentHighlight = this.parentHighlight; - } - } - } - } - - /** - * Function: init - * - * Initializes the shapes required for this edge handler. - */ - // init(): void; - init() { this.graph = this.state.view.graph; this.marker = this.createMarker(); this.constraintHandler = new ConstraintHandler(this.graph); @@ -424,16 +151,307 @@ class EdgeHandler { this.updateParentHighlight(); this.redraw(); + + // Handles escape keystrokes + this.escapeHandler = (sender: Listenable, evt: Event) => { + const dirty = this.index != null; + this.reset(); + + if (dirty) { + this.graph.cellRenderer.redraw(this.state, false, state.view.isRendering()); + } + }; + + this.state.view.graph.addListener(InternalEvent.ESCAPE, this.escapeHandler); + } + + /** + * Variable: graph + * + * Reference to the enclosing . + */ + graph: MaxGraph; + + /** + * Variable: state + * + * Reference to the being modified. + */ + state: CellState; + + /** + * Variable: marker + * + * Holds the which is used for highlighting terminals. + */ + marker: CellMarker; + + /** + * Variable: constraintHandler + * + * Holds the used for drawing and highlighting + * constraints. + */ + constraintHandler: ConstraintHandler = null; + + /** + * Variable: error + * + * Holds the current validation error while a connection is being changed. + */ + error: string | null = null; + + /** + * Variable: shape + * + * Holds the that represents the preview edge. + */ + shape: Shape | null = null; + + /** + * Variable: bends + * + * Holds the that represent the points. + */ + bends: Shape[] = []; + + virtualBends: Shape[] = []; + + /** + * Variable: labelShape + * + * Holds the that represents the label position. + */ + labelShape: Shape | null = null; + + /** + * Variable: cloneEnabled + * + * Specifies if cloning by control-drag is enabled. Default is true. + */ + cloneEnabled = true; + + /** + * Variable: addEnabled + * + * Specifies if adding bends by shift-click is enabled. Default is false. + * Note: This experimental feature is not recommended for production use. + */ + addEnabled = false; + + /** + * Variable: removeEnabled + * + * Specifies if removing bends by shift-click is enabled. Default is false. + * Note: This experimental feature is not recommended for production use. + */ + removeEnabled = false; + + /** + * Variable: dblClickRemoveEnabled + * + * Specifies if removing bends by double click is enabled. Default is false. + */ + dblClickRemoveEnabled = false; + + /** + * Variable: mergeRemoveEnabled + * + * Specifies if removing bends by dropping them on other bends is enabled. + * Default is false. + */ + mergeRemoveEnabled = false; + + /** + * Variable: straightRemoveEnabled + * + * Specifies if removing bends by creating straight segments should be enabled. + * If enabled, this can be overridden by holding down the alt key while moving. + * Default is false. + */ + straightRemoveEnabled = false; + + /** + * Variable: virtualBendsEnabled + * + * Specifies if virtual bends should be added in the center of each + * segments. These bends can then be used to add new waypoints. + * Default is false. + */ + virtualBendsEnabled = false; + + /** + * Variable: virtualBendOpacity + * + * Opacity to be used for virtual bends (see ). + * Default is 20. + */ + virtualBendOpacity = 20; + + /** + * Variable: parentHighlightEnabled + * + * Specifies if the parent should be highlighted if a child cell is selected. + * Default is false. + */ + parentHighlightEnabled = false; + + /** + * Variable: preferHtml + * + * Specifies if bends should be added to the graph container. This is updated + * in based on whether the edge or one of its terminals has an HTML + * label in the container. + */ + preferHtml = false; + + /** + * Variable: allowHandleBoundsCheck + * + * Specifies if the bounds of handles should be used for hit-detection in IE + * Default is true. + */ + allowHandleBoundsCheck = true; + + /** + * Variable: snapToTerminals + * + * Specifies if waypoints should snap to the routing centers of terminals. + * Default is false. + */ + snapToTerminals = false; + + /** + * Variable: handleImage + * + * Optional to be used as handles. Default is null. + */ + handleImage: ImageBox | null = null; + + labelHandleImage: ImageBox | null = null; + + /** + * Variable: tolerance + * + * Optional tolerance for hit-detection in . Default is 0. + */ + // tolerance: number; + tolerance = 0; + + /** + * Variable: outlineConnect + * + * Specifies if connections to the outline of a highlighted target should be + * enabled. This will allow to place the connection point along the outline of + * the highlighted target. Default is false. + */ + // outlineConnect: boolean; + outlineConnect = false; + + /** + * Variable: manageLabelHandle + * + * Specifies if the label handle should be moved if it intersects with another + * handle. Uses for checking and moving. Default is false. + */ + // manageLabelHandle: boolean; + manageLabelHandle = false; + + escapeHandler: (sender: Listenable, evt: Event) => void; + + currentPoint: Point | null = null; + + parentHighlight: RectangleShape | null = null; + + index: number | null = null; + + isSource: boolean = false; + + isTarget: boolean = false; + + label: Point | null = null; + + isLabel = false; + + points: Point[] = []; + + snapPoint: Point | null = null; + + abspoints: (Point | null)[] = []; + + customHandles: CellHandle[] = []; + + startX: number = 0; + startY: number = 0; + + /** + * Function: isParentHighlightVisible + * + * Returns true if the parent highlight should be visible. This implementation + * always returns true. + */ + isParentHighlightVisible() { + return !this.graph.isCellSelected(this.state.cell.getParent()); + } + + /** + * Function: updateParentHighlight + * + * Updates the highlight of the parent if is true. + */ + updateParentHighlight() { + if (!this.isDestroyed()) { + const visible = this.isParentHighlightVisible(); + const parent = this.state.cell.getParent(); + const pstate = this.graph.view.getState(parent); + + if (this.parentHighlight) { + if (parent.isVertex() && visible) { + const b = this.parentHighlight.bounds; + + if ( + pstate && + b && + (b.x !== pstate.x || + b.y !== pstate.y || + b.width !== pstate.width || + b.height !== pstate.height) + ) { + this.parentHighlight.bounds = Rectangle.fromRectangle(pstate); + this.parentHighlight.redraw(); + } + } else { + if (pstate && pstate.parentHighlight === this.parentHighlight) { + pstate.parentHighlight = null; + } + + this.parentHighlight.destroy(); + this.parentHighlight = null; + } + } else if (this.parentHighlightEnabled && visible) { + if (parent.isVertex() && pstate && !pstate.parentHighlight) { + this.parentHighlight = this.createParentHighlightShape(pstate); + // VML dialect required here for event transparency in IE + this.parentHighlight.dialect = DIALECT_SVG; + this.parentHighlight.pointerEvents = false; + this.parentHighlight.rotation = pstate.style.rotation; + this.parentHighlight.init(this.graph.getView().getOverlayPane()); + this.parentHighlight.redraw(); + + // Shows highlight once per parent + pstate.parentHighlight = this.parentHighlight; + } + } + } } /** * Function: createCustomHandles * - * Returns an array of custom handles. This implementation returns null. + * Returns an array of custom handles. This implementation returns an empty array. */ - // createCustomHandles(): any[]; - createCustomHandles() { - return null; + createCustomHandles(): CellHandle[] { + return []; } /** @@ -443,14 +461,13 @@ class EdgeHandler { * is true and the current style allows and * renders custom waypoints. */ - // isVirtualBendsEnabled(evt: Event): boolean; - isVirtualBendsEnabled(evt) { + isVirtualBendsEnabled(evt: Event) { return ( this.virtualBendsEnabled && (this.state.style.edge == null || this.state.style.edge === NONE || - this.state.style.noEdgeStyle == 1) && - utils.getValue(this.state.style, 'shape', null) != 'arrow' + this.state.style.noEdgeStyle) && + this.state.style.shape !== 'arrow' ); } @@ -460,18 +477,17 @@ class EdgeHandler { * Returns true if the given cell allows new connections to be created. This implementation * always returns true. */ - isCellEnabled(cell) { + isCellEnabled(cell: Cell) { return true; } /** * Function: isAddPointEvent * - * Returns true if the given event is a trigger to add a new point. This + * Returns true if the given event is a trigger to add a new Point. This * implementation returns true if shift is pressed. */ - // isAddPointEvent(evt: Event): boolean; - isAddPointEvent(evt) { + isAddPointEvent(evt: Event) { return isShiftDown(evt); } @@ -481,8 +497,7 @@ class EdgeHandler { * Returns true if the given event is a trigger to remove a point. This * implementation returns true if shift is pressed. */ - // isRemovePointEvent(evt: Event): boolean; - isRemovePointEvent(evt) { + isRemovePointEvent(evt: Event) { return isShiftDown(evt); } @@ -491,8 +506,7 @@ class EdgeHandler { * * Returns the list of points that defines the selection stroke. */ - // getSelectionPoints(state: mxCellState): mxPoint[]; - getSelectionPoints(state) { + getSelectionPoints(state: CellState) { return state.absolutePoints; } @@ -501,11 +515,10 @@ class EdgeHandler { * * Creates the shape used to draw the selection border. */ - // createParentHighlightShape(bounds: mxRectangle): mxRectangleShape; - createParentHighlightShape(bounds) { + createParentHighlightShape(bounds: Rectangle) { const shape = new RectangleShape( Rectangle.fromRectangle(bounds), - null, + NONE, this.getSelectionColor() ); shape.strokeWidth = this.getSelectionStrokeWidth(); @@ -519,9 +532,10 @@ class EdgeHandler { * * Creates the shape used to draw the selection border. */ - // createSelectionShape(points: mxPoint[]): mxShape; - createSelectionShape(points) { - const shape = new this.state.shape.constructor(); + createSelectionShape(points: (Point | null)[]) { + const c = this.state.shape!.constructor as new () => Shape; + + const shape = new c(); shape.outline = true; shape.apply(this.state); @@ -537,7 +551,6 @@ class EdgeHandler { * * Returns . */ - // getSelectionColor(): string; getSelectionColor() { return EDGE_SELECTION_COLOR; } @@ -547,7 +560,6 @@ class EdgeHandler { * * Returns . */ - // getSelectionStrokeWidth(): number; getSelectionStrokeWidth() { return EDGE_SELECTION_STROKEWIDTH; } @@ -557,7 +569,6 @@ class EdgeHandler { * * Returns . */ - // isSelectionDashed(): boolean; isSelectionDashed() { return EDGE_SELECTION_DASHED; } @@ -568,8 +579,7 @@ class EdgeHandler { * Returns true if the given cell is connectable. This is a hook to * disable floating connections. This implementation returns true. */ - // isConnectableCell(cell: mxCell): boolean; - isConnectableCell(cell) { + isConnectableCell(cell: Cell) { return true; } @@ -578,8 +588,7 @@ class EdgeHandler { * * Creates and returns the used in . */ - // getCellAt(x: number, y: number): mxCell; - getCellAt(x, y) { + getCellAt(x: number, y: number) { return !this.outlineConnect ? this.graph.getCellAt(x, y) : null; } @@ -588,23 +597,22 @@ class EdgeHandler { * * Creates and returns the used in . */ - // createMarker(): mxCellMarker; createMarker() { const self = this; // closure class MyMarker extends CellMarker { // Only returns edges if they are connectable and never returns // the edge that is currently being modified - getCell = (me) => { + getCell = (me: InternalMouseEvent) => { let cell = super.getCell(me); // Checks for cell at preview point (with grid) - if ((cell === self.state.cell || cell == null) && self.currentPoint != null) { + if ((cell === self.state.cell || !cell) && self.currentPoint) { cell = self.graph.getCellAt(self.currentPoint.x, self.currentPoint.y); } // Uses connectable parent vertex if one exists - if (cell != null && !cell.isConnectable()) { + if (cell && !cell.isConnectable()) { const parent = cell.getParent(); if (parent.isVertex() && parent.isConnectable()) { @@ -612,6 +620,7 @@ class EdgeHandler { } } + /* disable swimlane for now const model = self.graph.getModel(); if ( @@ -629,6 +638,7 @@ class EdgeHandler { ) { cell = null; } + */ if (cell && !cell.isConnectable()) { cell = null; @@ -637,21 +647,18 @@ class EdgeHandler { }; // Sets the highlight color according to validateConnection - isValidState = (state) => { - const model = self.graph.getModel(); - const other = self.graph.view.getTerminalPort( - state, - self.graph.view.getState(self.state.cell.getTerminal(!self.isSource)), - !self.isSource - ); - const otherCell = other != null ? other.cell : null; + isValidState = (state: CellState) => { + const cell = self.state.cell.getTerminal(!self.isSource) as Cell; + const cellState = self.graph.view.getState(cell) as CellState; + const other = self.graph.view.getTerminalPort(state, cellState, !self.isSource); + const otherCell = other ? other.cell : null; const source = self.isSource ? state.cell : otherCell; const target = self.isSource ? otherCell : state.cell; // Updates the error message of the handler self.error = self.validateConnection(source, target); - return self.error == null; + return !self.error; }; } @@ -670,8 +677,7 @@ class EdgeHandler { * source - that represents the source terminal. * target - that represents the target terminal. */ - // validateConnection(source: mxCell, target: mxCell): string; - validateConnection(source, target) { + validateConnection(source: Cell | null, target: Cell | null) { return this.graph.getEdgeValidationError(this.state.cell, source, target); } @@ -681,7 +687,6 @@ class EdgeHandler { * Creates and returns the bends used for modifying the edge. This is * typically an array of . */ - // createBends(): mxRectangleShape[]; createBends() { const { cell } = this.state; const bends = []; @@ -749,8 +754,7 @@ class EdgeHandler { * * Creates the shape used to display the given bend. */ - // isHandleEnabled(index: number): boolean; - isHandleEnabled(index) { + isHandleEnabled(index: number) { return true; } @@ -759,18 +763,16 @@ class EdgeHandler { * * Returns true if the handle at the given index is visible. */ - // isHandleVisible(index: number): boolean; - isHandleVisible(index) { + isHandleVisible(index: number) { const source = this.state.getVisibleTerminalState(true); const target = this.state.getVisibleTerminalState(false); const geo = this.state.cell.getGeometry(); - const edgeStyle = - geo != null - ? this.graph.view.getEdgeStyle(this.state, geo.points, source, target) - : null; + const edgeStyle = geo + ? this.graph.view.getEdgeStyle(this.state, geo.points, source, target) + : null; return ( - edgeStyle !== mxEdgeStyle.EntityRelation || + edgeStyle !== EdgeStyle.EntityRelation || index === 0 || index === this.abspoints.length - 1 ); @@ -785,9 +787,8 @@ class EdgeHandler { * returned if support for HTML labels with not foreign objects is required. * Index if null for virtual handles. */ - // createHandleShape(index: number): mxRectangleShape; - createHandleShape(index) { - if (this.handleImage != null) { + createHandleShape(index?: number) { + if (this.handleImage) { const shape = new ImageShape( new Rectangle(0, 0, this.handleImage.width, this.handleImage.height), this.handleImage.src @@ -816,9 +817,8 @@ class EdgeHandler { * * Creates the shape used to display the the label handle. */ - // createLabelHandleShape(): mxRectangleShape; createLabelHandleShape() { - if (this.labelHandleImage != null) { + if (this.labelHandleImage) { const shape = new ImageShape( new Rectangle(0, 0, this.labelHandleImage.width, this.labelHandleImage.height), this.labelHandleImage.src @@ -846,8 +846,7 @@ class EdgeHandler { * * bend - that represents the bend to be initialized. */ - // initBend(bend: mxShape, dblClick: (evt: Event) => void): boolean; - initBend(bend, dblClick) { + initBend(bend: Shape, dblClick?: EventListener) { if (this.preferHtml) { bend.dialect = DIALECT_STRICTHTML; bend.init(this.graph.container); @@ -876,68 +875,64 @@ class EdgeHandler { * * Returns the index of the handle for the given event. */ - // getHandleForEvent(me: mxMouseEvent): number | boolean; - getHandleForEvent(me) { + getHandleForEvent(me: InternalMouseEvent) { let result = null; - if (this.state != null) { - // Connection highlight may consume events before they reach sizer handle - const tol = !isMouseEvent(me.getEvent()) ? this.tolerance : 1; - const hit = - this.allowHandleBoundsCheck && tol > 0 - ? new Rectangle(me.getGraphX() - tol, me.getGraphY() - tol, 2 * tol, 2 * tol) - : null; - let minDistSq = null; + // Connection highlight may consume events before they reach sizer handle + const tol = !isMouseEvent(me.getEvent()) ? this.tolerance : 1; + const hit = + this.allowHandleBoundsCheck && tol > 0 + ? new Rectangle(me.getGraphX() - tol, me.getGraphY() - tol, 2 * tol, 2 * tol) + : null; + let minDistSq = Number.POSITIVE_INFINITY; - function checkShape(shape) { - if ( - shape != null && - shape.node != null && - shape.node.style.display !== 'none' && - shape.node.style.visibility !== 'hidden' && - (me.isSource(shape) || (hit != null && utils.intersects(shape.bounds, hit))) - ) { - const dx = me.getGraphX() - shape.bounds.getCenterX(); - const dy = me.getGraphY() - shape.bounds.getCenterY(); - const tmp = dx * dx + dy * dy; + function checkShape(shape: Shape | null) { + if ( + shape && + shape.bounds && + shape.node && + shape.node.style.display !== 'none' && + shape.node.style.visibility !== 'hidden' && + (me.isSource(shape) || (hit && intersects(shape.bounds, hit))) + ) { + const dx = me.getGraphX() - shape.bounds.getCenterX(); + const dy = me.getGraphY() - shape.bounds.getCenterY(); + const tmp = dx * dx + dy * dy; - if (minDistSq == null || tmp <= minDistSq) { - minDistSq = tmp; + if (tmp <= minDistSq) { + minDistSq = tmp; - return true; - } - } - - return false; - } - - if (this.customHandles != null && this.isCustomHandleEvent(me)) { - // Inverse loop order to match display order - for (let i = this.customHandles.length - 1; i >= 0; i--) { - if (checkShape(this.customHandles[i].shape)) { - // LATER: Return reference to active shape - return InternalEvent.CUSTOM_HANDLE - i; - } + return true; } } - if (me.isSource(this.state.text) || checkShape(this.labelShape)) { - result = InternalEvent.LABEL_HANDLE; - } + return false; + } - if (this.bends != null) { - for (let i = 0; i < this.bends.length; i += 1) { - if (checkShape(this.bends[i])) { - result = i; - } + if (this.isCustomHandleEvent(me)) { + // Inverse loop order to match display order + for (let i = this.customHandles.length - 1; i >= 0; i--) { + if (checkShape(this.customHandles[i].shape)) { + // LATER: Return reference to active shape + return InternalEvent.CUSTOM_HANDLE - i; } } + } - if (this.virtualBends != null && this.isAddVirtualBendEvent(me)) { - for (let i = 0; i < this.virtualBends.length; i += 1) { - if (checkShape(this.virtualBends[i])) { - result = InternalEvent.VIRTUAL_HANDLE - i; - } + if (me.isSource(this.state.text) || checkShape(this.labelShape)) { + result = InternalEvent.LABEL_HANDLE; + } + + for (let i = 0; i < this.bends.length; i += 1) { + if (checkShape(this.bends[i])) { + result = i; + } + } + + if (this.isAddVirtualBendEvent(me)) { + for (let i = 0; i < this.virtualBends.length; i += 1) { + if (checkShape(this.virtualBends[i])) { + result = InternalEvent.VIRTUAL_HANDLE - i; } } } @@ -951,8 +946,8 @@ class EdgeHandler { * Returns true if the given event allows virtual bends to be added. This * implementation returns true. */ - // isAddVirtualBendEvent(me: mxMouseEvent): boolean; - isAddVirtualBendEvent(me) { + + isAddVirtualBendEvent(me: InternalMouseEvent) { return true; } @@ -962,8 +957,7 @@ class EdgeHandler { * Returns true if the given event allows custom handles to be changed. This * implementation returns true. */ - // isCustomHandleEvent(me: mxMouseEvent): boolean; - isCustomHandleEvent(me) { + isCustomHandleEvent(me: InternalMouseEvent) { return true; } @@ -976,30 +970,29 @@ class EdgeHandler { * control point. The source and target points are used for reconnecting * the edge. */ - // mouseDown(sender: any, me: mxMouseEvent): void; - mouseDown(sender, me) { + mouseDown(sender: Listenable, me: InternalMouseEvent) { const handle = this.getHandleForEvent(me); - if (this.bends != null && this.bends[handle] != null) { + if (handle !== null && this.bends[handle]) { const b = this.bends[handle].bounds; - this.snapPoint = new Point(b.getCenterX(), b.getCenterY()); + + if (b) this.snapPoint = new Point(b.getCenterX(), b.getCenterY()); } - if (this.addEnabled && handle == null && this.isAddPointEvent(me.getEvent())) { + if (this.addEnabled && handle === null && this.isAddPointEvent(me.getEvent())) { this.addPoint(this.state, me.getEvent()); me.consume(); - } else if (handle != null && !me.isConsumed() && this.graph.isEnabled()) { + } else if (handle !== null && !me.isConsumed() && this.graph.isEnabled()) { + const cell = me.getCell(); + if (this.removeEnabled && this.isRemovePointEvent(me.getEvent())) { this.removePoint(this.state, handle); } else if ( handle !== InternalEvent.LABEL_HANDLE || - this.graph.isLabelMovable(me.getCell()) + (cell && this.graph.isLabelMovable(cell)) ) { if (handle <= InternalEvent.VIRTUAL_HANDLE) { - utils.setOpacity( - this.virtualBends[InternalEvent.VIRTUAL_HANDLE - handle].node, - 100 - ); + setOpacity(this.virtualBends[InternalEvent.VIRTUAL_HANDLE - handle].node, 100); } this.start(me.getX(), me.getY(), handle); @@ -1014,13 +1007,12 @@ class EdgeHandler { * * Starts the handling of the mouse gesture. */ - // start(x: number, y: number, index: number): void; - start(x, y, index) { + start(x: number, y: number, index: number) { this.startX = x; this.startY = y; - this.isSource = this.bends == null ? false : index === 0; - this.isTarget = this.bends == null ? false : index === this.bends.length - 1; + this.isSource = this.bends.length === 0 ? false : index === 0; + this.isTarget = this.bends.length === 0 ? false : index === this.bends.length - 1; this.isLabel = index === InternalEvent.LABEL_HANDLE; if (this.isSource || this.isTarget) { @@ -1040,6 +1032,7 @@ class EdgeHandler { // Hides other custom handles if ( + this.index !== null && this.index <= InternalEvent.CUSTOM_HANDLE && this.index > InternalEvent.VIRTUAL_HANDLE ) { @@ -1058,8 +1051,7 @@ class EdgeHandler { * * Returns a clone of the current preview state for the given point and terminal. */ - // clonePreviewState(point: mxPoint, terminal: mxCell): mxCellState; - clonePreviewState(point, terminal) { + clonePreviewState(point: Point, terminal: Cell) { return this.state.clone(); } @@ -1069,9 +1061,8 @@ class EdgeHandler { * Returns the tolerance for the guides. Default value is * gridSize * scale / 2. */ - // getSnapToTerminalTolerance(): number; getSnapToTerminalTolerance() { - return (this.graph.gridSize * this.graph.view.scale) / 2; + return (this.graph.getGridSize() * this.graph.getView().scale) / 2; } /** @@ -1079,15 +1070,14 @@ class EdgeHandler { * * Hook for subclassers do show details while the handler is active. */ - // updateHint(me: mxMouseEvent, point: mxPoint): void; - updateHint(me, point) {} + + updateHint(me: InternalMouseEvent, point: Point) {} /** * Function: removeHint * * Hooks for subclassers to hide details when the handler gets inactive. */ - // removeHint(): void; removeHint() {} /** @@ -1095,8 +1085,7 @@ class EdgeHandler { * * Hook for rounding the unscaled width or height. This uses Math.round. */ - // roundLength(length: number): number; - roundLength(length) { + roundLength(length: number) { return Math.round(length); } @@ -1105,8 +1094,7 @@ class EdgeHandler { * * Returns true if is true and if alt is not pressed. */ - // isSnapToTerminalsEvent(me: mxMouseEvent): boolean; - isSnapToTerminalsEvent(me) { + isSnapToTerminalsEvent(me: InternalMouseEvent) { return this.snapToTerminals && !isAltDown(me.getEvent()); } @@ -1116,7 +1104,7 @@ class EdgeHandler { * Returns the point for the given event. */ // getPointForEvent(me: mxMouseEvent): mxPoint; - getPointForEvent(me) { + getPointForEvent(me: InternalMouseEvent) { const view = this.graph.getView(); const { scale } = view; const point = new Point( @@ -1129,8 +1117,8 @@ class EdgeHandler { let overrideY = false; if (tt > 0 && this.isSnapToTerminalsEvent(me)) { - const snapToPoint = (pt) => { - if (pt != null) { + const snapToPoint = (pt: Point | null) => { + if (pt) { const { x } = pt; if (Math.abs(point.x - x) < tt) { point.x = x; @@ -1146,10 +1134,10 @@ class EdgeHandler { }; // Temporary function - const snapToTerminal = (terminal) => { - if (terminal != null) { + const snapToTerminal = (terminal: CellState | null) => { + if (terminal) { snapToPoint( - new point(view.getRoutingCenterX(terminal), view.getRoutingCenterY(terminal)) + new Point(view.getRoutingCenterX(terminal), view.getRoutingCenterY(terminal)) ); } }; @@ -1157,10 +1145,8 @@ class EdgeHandler { snapToTerminal(this.state.getVisibleTerminalState(true)); snapToTerminal(this.state.getVisibleTerminalState(false)); - if (this.state.absolutePoints != null) { - for (let i = 0; i < this.state.absolutePoints.length; i += 1) { - snapToPoint(this.state.absolutePoints[i]); - } + for (let i = 0; i < this.state.absolutePoints.length; i += 1) { + snapToPoint(this.state.absolutePoints[i]); } } @@ -1184,8 +1170,7 @@ class EdgeHandler { * * Updates the given preview state taking into account the state of the constraint handler. */ - // getPreviewTerminalState(me: mxMouseEvent): mxCellState; - getPreviewTerminalState(me) { + getPreviewTerminalState(me: InternalMouseEvent) { this.constraintHandler.update( me, this.isSource, @@ -1193,15 +1178,13 @@ class EdgeHandler { me.isSource(this.marker.highlight.shape) ? null : this.currentPoint ); - if ( - this.constraintHandler.currentFocus != null && - this.constraintHandler.currentConstraint != null - ) { + if (this.constraintHandler.currentFocus && this.constraintHandler.currentConstraint) { // Handles special case where grid is large and connection point is at actual point in which // case the outline is not followed as long as we're < gridSize / 2 away from that point if ( - this.marker.highlight != null && - this.marker.highlight.state != null && + this.marker.highlight && + this.marker.highlight.shape && + this.marker.highlight.state && this.marker.highlight.state.cell === this.constraintHandler.currentFocus.cell ) { // Direct repaint needed if cell already highlighted @@ -1213,13 +1196,14 @@ class EdgeHandler { this.marker.markCell(this.constraintHandler.currentFocus.cell, 'transparent'); } - const model = this.graph.getModel(); const other = this.graph.view.getTerminalPort( this.state, - this.graph.view.getState(this.state.cell.getTerminal(!this.isSource)), + this.graph.view.getState( + this.state.cell.getTerminal(!this.isSource) as Cell + ) as CellState, !this.isSource ); - const otherCell = other != null ? other.cell : null; + const otherCell = other ? other.cell : null; const source = this.isSource ? this.constraintHandler.currentFocus.cell : otherCell; const target = this.isSource ? otherCell : this.constraintHandler.currentFocus.cell; @@ -1227,11 +1211,11 @@ class EdgeHandler { this.error = this.validateConnection(source, target); let result = null; - if (this.error == null) { + if (this.error === null) { result = this.constraintHandler.currentFocus; } - if (this.error != null || (result != null && !this.isCellEnabled(result.cell))) { + if (this.error !== null || (result && !this.isCellEnabled(result.cell))) { this.constraintHandler.reset(); } @@ -1241,7 +1225,7 @@ class EdgeHandler { this.marker.process(me); const state = this.marker.getValidState(); - if (state != null && !this.isCellEnabled(state.cell)) { + if (state && !this.isCellEnabled(state.cell)) { this.constraintHandler.reset(); this.marker.reset(); } @@ -1263,111 +1247,105 @@ class EdgeHandler { * pt - that contains the current pointer position. * me - Optional that contains the current event. */ - // getPreviewPoints(pt: mxPoint, me?: mxMouseEvent): mxPoint[]; - getPreviewPoints(pt, me) { + getPreviewPoints(pt: Point, me: InternalMouseEvent) { const geometry = this.state.cell.getGeometry(); - let points = geometry.points != null ? geometry.points.slice() : null; - const point = new Point(pt.x, pt.y); - let result = null; - if (!this.isSource && !this.isTarget) { + if (!geometry) return null; + + let points = geometry.points.slice(); + const point = new Point(pt.x, pt.y); + let result: Point[] | null = null; + + if (!this.isSource && !this.isTarget && this.index !== null) { this.convertPoint(point, false); - if (points == null) { - points = [point]; - } else { - // Adds point from virtual bend - if (this.index <= InternalEvent.VIRTUAL_HANDLE) { - points.splice(InternalEvent.VIRTUAL_HANDLE - this.index, 0, point); - } + // Adds point from virtual bend + if (this.index <= InternalEvent.VIRTUAL_HANDLE) { + points.splice(InternalEvent.VIRTUAL_HANDLE - this.index, 0, point); + } - // Removes point if dragged on terminal point - if (!this.isSource && !this.isTarget) { - for (let i = 0; i < this.bends.length; i += 1) { - if (i !== this.index) { - const bend = this.bends[i]; + // Removes point if dragged on terminal point + if (!this.isSource && !this.isTarget) { + for (let i = 0; i < this.bends.length; i += 1) { + if (i !== this.index) { + const bend = this.bends[i]; - if (bend != null && utils.contains(bend.bounds, pt.x, pt.y)) { - if (this.index <= InternalEvent.VIRTUAL_HANDLE) { - points.splice(InternalEvent.VIRTUAL_HANDLE - this.index, 1); - } else { - points.splice(this.index - 1, 1); - } - - result = points; + if (bend && contains(bend.bounds as Rectangle, pt.x, pt.y)) { + if (this.index <= InternalEvent.VIRTUAL_HANDLE) { + points.splice(InternalEvent.VIRTUAL_HANDLE - this.index, 1); + } else { + points.splice(this.index - 1, 1); } + + result = points; } } - - // Removes point if user tries to straighten a segment - if ( - result == null && - this.straightRemoveEnabled && - (me == null || !isAltDown(me.getEvent())) - ) { - const tol = this.graph.tolerance * this.graph.tolerance; - const abs = this.state.absolutePoints.slice(); - abs[this.index] = pt; - - // Handes special case where removing waypoint affects tolerance (flickering) - const src = this.state.getVisibleTerminalState(true); - - if (src != null) { - const c = this.graph.getConnectionConstraint(this.state, src, true); - - // Checks if point is not fixed - if (c == null || this.graph.getConnectionPoint(src, c) == null) { - abs[0] = new point( - src.view.getRoutingCenterX(src), - src.view.getRoutingCenterY(src) - ); - } - } - - const trg = this.state.getVisibleTerminalState(false); - - if (trg != null) { - const c = this.graph.getConnectionConstraint(this.state, trg, false); - - // Checks if point is not fixed - if (c == null || this.graph.getConnectionPoint(trg, c) == null) { - abs[abs.length - 1] = new point( - trg.view.getRoutingCenterX(trg), - trg.view.getRoutingCenterY(trg) - ); - } - } - - const checkRemove = (idx, tmp) => { - if ( - idx > 0 && - idx < abs.length - 1 && - utils.ptSegDistSq( - abs[idx - 1].x, - abs[idx - 1].y, - abs[idx + 1].x, - abs[idx + 1].y, - tmp.x, - tmp.y - ) < tol - ) { - points.splice(idx - 1, 1); - result = points; - } - }; - - // LATER: Check if other points can be removed if a segment is made straight - checkRemove(this.index, pt); - } } - // Updates existing point - if (result == null && this.index > InternalEvent.VIRTUAL_HANDLE) { - points[this.index - 1] = point; + // Removes point if user tries to straighten a segment + if (!result && this.straightRemoveEnabled && (!me || !isAltDown(me.getEvent()))) { + const tol = this.graph.getClickTolerance() * this.graph.getClickTolerance(); + const abs = this.state.absolutePoints.slice(); + abs[this.index] = pt; + + // Handes special case where removing waypoint affects tolerance (flickering) + const src = this.state.getVisibleTerminalState(true); + + if (src != null) { + const c = this.graph.getConnectionConstraint(this.state, src, true); + + // Checks if point is not fixed + if (c == null || this.graph.getConnectionPoint(src, c) == null) { + abs[0] = new Point( + src.view.getRoutingCenterX(src), + src.view.getRoutingCenterY(src) + ); + } + } + + const trg = this.state.getVisibleTerminalState(false); + + if (trg != null) { + const c = this.graph.getConnectionConstraint(this.state, trg, false); + + // Checks if point is not fixed + if (c == null || this.graph.getConnectionPoint(trg, c) == null) { + abs[abs.length - 1] = new Point( + trg.view.getRoutingCenterX(trg), + trg.view.getRoutingCenterY(trg) + ); + } + } + + const checkRemove = (idx: number, tmp: Point) => { + if ( + idx > 0 && + idx < abs.length - 1 && + ptSegDistSq( + abs[idx - 1]!.x, + abs[idx - 1]!.y, + abs[idx + 1]!.x, + abs[idx + 1]!.y, + tmp.x, + tmp.y + ) < tol + ) { + points.splice(idx - 1, 1); + result = points; + } + }; + + // LATER: Check if other points can be removed if a segment is made straight + checkRemove(this.index, pt); } } - } else if (this.graph.resetEdgesOnConnect) { - points = null; + + // Updates existing point + if (result == null && this.index > InternalEvent.VIRTUAL_HANDLE) { + points[this.index - 1] = point; + } + } else if (this.graph.isResetEdgesOnConnect()) { + points = []; } return result != null ? result : points; @@ -1379,9 +1357,10 @@ class EdgeHandler { * Returns true if is true and the source of the event is the outline shape * or shift is pressed. */ - // isOutlineConnectEvent(me: mxMouseEvent): boolean; - isOutlineConnectEvent(me) { - const offset = utils.getOffset(this.graph.container); + isOutlineConnectEvent(me: InternalMouseEvent) { + if (!this.currentPoint) return false; + + const offset = getOffset(this.graph.container); const evt = me.getEvent(); const clientX = getClientX(evt); @@ -1411,7 +1390,13 @@ class EdgeHandler { * * Updates the given preview state taking into account the state of the constraint handler. */ - updatePreviewState(edge, point, terminalState, me, outline) { + updatePreviewState( + edgeState: CellState, + point: Point, + terminalState: CellState, + me: InternalMouseEvent, + outline: boolean + ) { // Computes the points for the edge style and terminals const sourceState = this.isSource ? terminalState @@ -1420,8 +1405,16 @@ class EdgeHandler { ? terminalState : this.state.getVisibleTerminalState(false); - let sourceConstraint = this.graph.getConnectionConstraint(edge, sourceState, true); - let targetConstraint = this.graph.getConnectionConstraint(edge, targetState, false); + let sourceConstraint = this.graph.getConnectionConstraint( + edgeState, + sourceState, + true + ); + let targetConstraint = this.graph.getConnectionConstraint( + edgeState, + targetState, + false + ); let constraint = this.constraintHandler.currentConstraint; @@ -1430,7 +1423,7 @@ class EdgeHandler { // Handles special case where mouse is on outline away from actual end point // in which case the grid is ignored and mouse point is used instead if (me.isSource(this.marker.highlight.shape)) { - point = new point(me.getGraphX(), me.getGraphY()); + point = new Point(me.getGraphX(), me.getGraphY()); } constraint = this.graph.getOutlineConstraint(point, terminalState, me); @@ -1456,14 +1449,16 @@ class EdgeHandler { this.marker.highlight.shape.stroke = outline ? OUTLINE_HIGHLIGHT_COLOR : 'transparent'; - this.marker.highlight.shape.strokewidth = OUTLINE_HIGHLIGHT_STROKEWIDTH / s / s; + this.marker.highlight.shape.strokeWidth = OUTLINE_HIGHLIGHT_STROKEWIDTH / s / s; this.marker.highlight.repaint(); } else if (this.marker.hasValidState()) { + const cell = me.getCell(); + this.marker.highlight.shape.stroke = - me.getCell().isConnectable() && this.marker.getValidState() !== me.getState() + cell && cell.isConnectable() && this.marker.getValidState() !== me.getState() ? 'transparent' : DEFAULT_VALID_COLOR; - this.marker.highlight.shape.strokewidth = HIGHLIGHT_STROKEWIDTH / s / s; + this.marker.highlight.shape.strokeWidth = HIGHLIGHT_STROKEWIDTH / s / s; this.marker.highlight.repaint(); } } @@ -1476,35 +1471,45 @@ class EdgeHandler { if (this.isSource || this.isTarget) { if (constraint != null && constraint.point != null) { - edge.style[this.isSource ? 'exitX' : 'entryX'] = constraint.point.x; - edge.style[this.isSource ? 'exitY' : 'entryY'] = constraint.point.y; + edgeState.style[this.isSource ? 'exitX' : 'entryX'] = constraint.point.x; + edgeState.style[this.isSource ? 'exitY' : 'entryY'] = constraint.point.y; } else { - delete edge.style[this.isSource ? 'exitX' : 'entryX']; - delete edge.style[this.isSource ? 'exitY' : 'entryY']; + delete edgeState.style[this.isSource ? 'exitX' : 'entryX']; + delete edgeState.style[this.isSource ? 'exitY' : 'entryY']; } } - edge.setVisibleTerminalState(sourceState, true); - edge.setVisibleTerminalState(targetState, false); + edgeState.setVisibleTerminalState(sourceState, true); + edgeState.setVisibleTerminalState(targetState, false); if (!this.isSource || sourceState != null) { - edge.view.updateFixedTerminalPoint(edge, sourceState, true, sourceConstraint); + edgeState.view.updateFixedTerminalPoint( + edgeState, + sourceState, + true, + sourceConstraint + ); } if (!this.isTarget || targetState != null) { - edge.view.updateFixedTerminalPoint(edge, targetState, false, targetConstraint); + edgeState.view.updateFixedTerminalPoint( + edgeState, + targetState, + false, + targetConstraint + ); } if ((this.isSource || this.isTarget) && terminalState == null) { - edge.setAbsoluteTerminalPoint(point, this.isSource); + edgeState.setAbsoluteTerminalPoint(point, this.isSource); if (this.marker.getMarkedState() == null) { - this.error = this.graph.allowDanglingEdges ? null : ''; + this.error = this.graph.isAllowDanglingEdges() ? null : ''; } } - edge.view.updatePoints(edge, this.points, sourceState, targetState); - edge.view.updateFloatingTerminalPoints(edge, sourceState, targetState); + edgeState.view.updatePoints(edgeState, this.points, sourceState, targetState); + edgeState.view.updateFloatingTerminalPoints(edgeState, sourceState, targetState); } /** @@ -1513,7 +1518,7 @@ class EdgeHandler { * Handles the event by updating the preview. */ // mouseMove(sender: any, me: mxMouseEvent): void; - mouseMove(sender, me) { + mouseMove(sender: Listenable, me: InternalMouseEvent) { if (this.index != null && this.marker != null) { this.currentPoint = this.getPointForEvent(me); this.error = null; @@ -1546,11 +1551,11 @@ class EdgeHandler { this.shape.node.style.display = 'none'; } } - } else if (this.isLabel) { + } else if (this.isLabel && this.label) { this.label.x = this.currentPoint.x; this.label.y = this.currentPoint.y; } else { - this.points = this.getPreviewPoints(this.currentPoint, me); + this.points = this.getPreviewPoints(this.currentPoint, me) as Point[]; let terminalState = this.isSource || this.isTarget ? this.getPreviewTerminalState(me) : null; @@ -1570,7 +1575,7 @@ class EdgeHandler { } else if ( terminalState != null && terminalState !== me.getState() && - me.getCell().isConnectable() && + me.getCell()?.isConnectable() && this.marker.highlight.shape != null ) { this.marker.highlight.shape.stroke = 'transparent'; @@ -1584,26 +1589,28 @@ class EdgeHandler { this.marker.reset(); } - const clone = this.clonePreviewState( - this.currentPoint, - terminalState != null ? terminalState.cell : null - ); - this.updatePreviewState( - clone, - this.currentPoint, - terminalState, - me, - this.outline - ); + if (this.currentPoint) { + const clone = this.clonePreviewState( + this.currentPoint, + terminalState != null ? terminalState.cell : null + ); + this.updatePreviewState( + clone, + this.currentPoint, + terminalState, + me, + this.outline + ); - // Sets the color of the preview to valid or invalid, updates the - // points of the preview and redraws - const color = - this.error == null ? this.marker.validColor : this.marker.invalidColor; - this.setPreviewColor(color); - this.abspoints = clone.absolutePoints; - this.active = true; - this.updateHint(me, this.currentPoint); + // Sets the color of the preview to valid or invalid, updates the + // points of the preview and redraws + const color = + this.error == null ? this.marker.validColor : this.marker.invalidColor; + this.setPreviewColor(color); + this.abspoints = clone.absolutePoints; + this.active = true; + this.updateHint(me, this.currentPoint); + } } // This should go before calling isOutlineConnectEvent above. As a workaround @@ -1621,8 +1628,7 @@ class EdgeHandler { * Handles the event to applying the previewed changes on the edge by * using , or . */ - // mouseUp(sender: any, me: mxMouseEvent): void; - mouseUp(sender, me) { + mouseUp(sender: Listenable, me: InternalMouseEvent) { // Workaround for wrong event source in Webkit if (this.index != null && this.marker != null) { if (this.shape != null && this.shape.node != null) { @@ -1666,7 +1672,7 @@ class EdgeHandler { model.endUpdate(); } } - } else if (this.isLabel) { + } else if (this.isLabel && this.label) { this.moveLabel(this.state, this.label.x, this.label.y); } else if (this.isSource || this.isTarget) { let terminal = null; @@ -1717,7 +1723,9 @@ class EdgeHandler { model.endUpdate(); } } else if (this.graph.isAllowDanglingEdges()) { - const pt = this.abspoints[this.isSource ? 0 : this.abspoints.length - 1]; + const pt = this.abspoints[ + this.isSource ? 0 : this.abspoints.length - 1 + ] as Point; pt.x = this.roundLength( pt.x / this.graph.view.scale - this.graph.view.translate.x ); @@ -1816,8 +1824,7 @@ class EdgeHandler { * * Sets the color of the preview to the given value. */ - // setPreviewColor(color: string): void; - setPreviewColor(color) { + setPreviewColor(color: ColorValue) { if (this.shape != null) { this.shape.stroke = color; } @@ -1835,8 +1842,7 @@ class EdgeHandler { * point - to be converted. * gridEnabled - Boolean that specifies if the grid should be applied. */ - // convertPoint(point: mxPoint, gridEnabled: boolean): void; - convertPoint(point, gridEnabled) { + convertPoint(point: Point, gridEnabled: boolean) { const scale = this.graph.getView().getScale(); const tr = this.graph.getView().getTranslate(); @@ -1869,8 +1875,7 @@ class EdgeHandler { * x - Integer that specifies the x-coordinate of the new location. * y - Integer that specifies the y-coordinate of the new location. */ - // moveLabel(edgeState: mxCellState, x: number, y: number): void; - moveLabel(edgeState, x, y) { + moveLabel(edgeState: CellState, x: number, y: number) { const model = this.graph.getModel(); let geometry = edgeState.cell.getGeometry(); @@ -1930,8 +1935,13 @@ class EdgeHandler { * the old edge. * me - that contains the mouse up event. */ - // connect(edge: mxCell, terminal: mxCell, isSource: boolean, isClone: boolean, me: mxMouseEvent): mxCell; - connect(edge, terminal, isSource, isClone, me) { + connect( + edge: Cell, + terminal: Cell, + isSource: boolean, + isClone: boolean, + me: InternalMouseEvent + ) { const model = this.graph.getModel(); const parent = edge.getParent(); @@ -1956,8 +1966,7 @@ class EdgeHandler { * * Changes the terminal point of the given edge. */ - // changeTerminalPoint(edge: mxCell, point: mxPoint, isSource: boolean, clone: boolean): mxCell; - changeTerminalPoint(edge, point, isSource, clone) { + changeTerminalPoint(edge: Cell, point: Point, isSource: boolean, clone: boolean) { const model = this.graph.getModel(); model.beginUpdate(); @@ -1990,8 +1999,7 @@ class EdgeHandler { * * Changes the control points of the given edge in the graph model. */ - // changePoints(edge: mxCell, points: mxPoint[], clone: boolean): mxCell; - changePoints(edge, points, clone) { + changePoints(edge: Cell, points: Point[], clone: boolean) { const model = this.graph.getModel(); model.beginUpdate(); try { @@ -2025,9 +2033,8 @@ class EdgeHandler { * * Adds a control point for the given state and event. */ - // addPoint(state: mxCellState, evt: Event): void; - addPoint(state, evt) { - const pt = utils.convertPoint(this.graph.container, getClientX(evt), getClientY(evt)); + addPoint(state: CellState, evt: MouseEvent) { + const pt = convertPoint(this.graph.container, getClientX(evt), getClientY(evt)); const gridEnabled = this.graph.isGridEnabledEvent(evt); this.convertPoint(pt, gridEnabled); this.addPointAt(state, pt.x, pt.y); @@ -2039,8 +2046,7 @@ class EdgeHandler { * * Adds a control point at the given point. */ - // addPointAt(state: mxCellState, x: number, y: number): void; - addPointAt(state, x, y) { + addPointAt(state: CellState, x: number, y: number) { let geo = state.cell.getGeometry(); const pt = new Point(x, y); @@ -2057,11 +2063,7 @@ class EdgeHandler { offset = new Point(pState.x, pState.y); } - const index = utils.findNearestSegment( - state, - pt.x * s + offset.x, - pt.y * s + offset.y - ); + const index = findNearestSegment(state, pt.x * s + offset.x, pt.y * s + offset.y); if (geo.points == null) { geo.points = [pt]; @@ -2080,8 +2082,7 @@ class EdgeHandler { * * Removes the control point at the given index from the given state. */ - // removePoint(state: mxCellState, index: number): void; - removePoint(state, index) { + removePoint(state: CellState, index: number) { if (index > 0 && index < this.abspoints.length - 1) { let geo = this.state.cell.getGeometry(); @@ -2100,8 +2101,7 @@ class EdgeHandler { * * Returns the fillcolor for the handle at the given index. */ - // getHandleFillColor(index: number): string; - getHandleFillColor(index) { + getHandleFillColor(index: number) { const isSource = index === 0; const { cell } = this.state; const terminal = cell.getTerminal(isSource); @@ -2127,35 +2127,32 @@ class EdgeHandler { * * Redraws the preview, and the bends- and label control points. */ - // redraw(): void; - redraw(ignoreHandles) { - if (this.state != null) { - this.abspoints = this.state.absolutePoints.slice(); - const g = this.state.cell.getGeometry(); + redraw(ignoreHandles: boolean) { + this.abspoints = this.state.absolutePoints.slice(); + const g = this.state.cell.getGeometry(); - if (g != null) { - const pts = g.points; + if (g) { + const pts = g.points; - if (this.bends != null && this.bends.length > 0) { - if (pts != null) { - if (this.points == null) { - this.points = []; - } + if (this.bends != null && this.bends.length > 0) { + if (pts != null) { + if (this.points == null) { + this.points = []; + } - for (let i = 1; i < this.bends.length - 1; i += 1) { - if (this.bends[i] != null && this.abspoints[i] != null) { - this.points[i - 1] = pts[i - 1]; - } + for (let i = 1; i < this.bends.length - 1; i += 1) { + if (this.bends[i] != null && this.abspoints[i] != null) { + this.points[i - 1] = pts[i - 1]; } } } } + } - this.drawPreview(); + this.drawPreview(); - if (!ignoreHandles) { - this.redrawHandles(); - } + if (!ignoreHandles) { + this.redrawHandles(); } } @@ -2164,7 +2161,6 @@ class EdgeHandler { * * Redraws the handles. */ - // redrawHandles(): void; redrawHandles() { const { cell } = this.state; @@ -2281,7 +2277,7 @@ class EdgeHandler { * * Returns true if the given custom handle is visible. */ - isCustomHandleVisible(handle) { + isCustomHandleVisible(handle: CellHandle) { return !this.graph.isEditing() && this.state.view.graph.getSelectionCount() === 1; } @@ -2290,8 +2286,7 @@ class EdgeHandler { * * Shortcut to . */ - // setHandlesVisible(visible: boolean): void; - setHandlesVisible(visible) { + setHandlesVisible(visible: boolean) { if (this.bends != null) { for (let i = 0; i < this.bends.length; i += 1) { this.bends[i].node.style.display = visible ? '' : 'none'; @@ -2325,8 +2320,7 @@ class EdgeHandler { * p0 - that represents the location of the first point. * pe - that represents the location of the last point. */ - // redrawInnerBends(p0: mxPoint, pe: mxPoint): void; - redrawInnerBends(p0, pe) { + redrawInnerBends(p0: Point, pe: Point) { for (let i = 1; i < this.bends.length - 1; i += 1) { if (this.bends[i] != null) { if (this.abspoints[i] != null) { @@ -2374,8 +2368,7 @@ class EdgeHandler { * Checks if the label handle intersects the given bounds and moves it if it * intersects. */ - // checkLabelHandle(b: mxRectangle): void; - checkLabelHandle(b) { + checkLabelHandle(b: Rectangle) { if (this.labelShape != null) { const b2 = this.labelShape.bounds; @@ -2394,7 +2387,6 @@ class EdgeHandler { * * Redraws the preview. */ - // drawPreview(): void; drawPreview() { try { if (this.isLabel) { @@ -2435,7 +2427,6 @@ class EdgeHandler { * * Refreshes the bends of this handler. */ - // refresh(): void; refresh() { if (this.state != null) { this.abspoints = this.getSelectionPoints(this.state); @@ -2481,8 +2472,7 @@ class EdgeHandler { * * Destroys all elements in . */ - // destroyBends(bends: mxShape[]): void; - destroyBends(bends) { + destroyBends(bends: Shape[]) { if (bends != null) { for (let i = 0; i < bends.length; i += 1) { if (bends[i] != null) { @@ -2545,7 +2535,7 @@ class EdgeHandler { this.customHandles = null; this.destroyBends(this.bends); - this.bends = null; + this.bends = []; this.removeHint(); } diff --git a/packages/core/src/view/cell/edge/GraphEdge.ts b/packages/core/src/view/cell/edge/GraphEdge.ts index 6c2b08ada..91ed278e7 100644 --- a/packages/core/src/view/cell/edge/GraphEdge.ts +++ b/packages/core/src/view/cell/edge/GraphEdge.ts @@ -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; +type PartialCells = Pick< + GraphCells, + | 'cloneCell' + | 'cellsMoved' + | 'cellsAdded' + | 'addCell' + | 'isValidAncestor' + | 'getChildCells' +>; +type PartialConnections = Pick; +type PartialClass = PartialGraph & PartialCells & PartialConnections; +// @ts-ignore recursive reference error +class GraphEdge extends autoImplement() { /** * 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 = 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 = edge.getGeometry(); + geo = edge.getGeometry(); - if (geo != null && geo.points != null) { - geo = 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(cells[i].getEdgeAt(j)); + edges.push(cells[i].getEdgeAt(j)); } // Recurses const children = cells[i].getChildren(); - edges = edges.concat(this.getAllEdges(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 || !(child).isVisible()) { - edges = edges.concat((child).getEdges(incoming, outgoing)); + if (isCollapsed || !child.isVisible()) { + edges = edges.concat(child.getEdges(incoming, outgoing)); } } - edges = edges.concat( - 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(source, parent, recurse))) || + target === cell && + (!parent || this.isValidAncestor(source, parent, recurse))) || (outgoing && - source == cell && - (parent == null || - this.isValidAncestor(target, parent, recurse))))) + source === cell && + (!parent || this.isValidAncestor(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(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 = geo.clone(); + if (geo && geo.points.length > 0) { + geo = geo.clone(); geo.points = []; this.getModel().setGeometry(edge, geo); } + return edge; } } diff --git a/packages/core/src/view/cell/vertex/GraphVertex.ts b/packages/core/src/view/cell/vertex/GraphVertex.ts index 4f1e56190..c05c0779a 100644 --- a/packages/core/src/view/cell/vertex/GraphVertex.ts +++ b/packages/core/src/view/cell/vertex/GraphVertex.ts @@ -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; +type PartialClass = PartialCells; +class GraphVertex extends autoImplement() { /** * 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); } /***************************************************************************** diff --git a/packages/core/src/view/cell/vertex/VertexHandle.ts b/packages/core/src/view/cell/vertex/VertexHandle.ts index 16c721c59..2df019fd8 100644 --- a/packages/core/src/view/cell/vertex/VertexHandle.ts +++ b/packages/core/src/view/cell/vertex/VertexHandle.ts @@ -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 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 . */ - 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 . */ - 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( - me.getGraphX() / scale - tr.x, - 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 and sets its cursor. */ - initShape(html: any): void { - const 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 = 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 = 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; } diff --git a/packages/core/src/view/connection/GraphConnections.ts b/packages/core/src/view/connection/GraphConnections.ts index 13ed9ad68..18341e407 100644 --- a/packages/core/src/view/connection/GraphConnections.ts +++ b/packages/core/src/view/connection/GraphConnections.ts @@ -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 = ( - this.graph.view.getPerimeterBounds(terminalState) - ); + const bounds = 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( - edge.style[source ? 'exitDx' : 'entryDx'] - ); + dx = parseFloat(edge.style[source ? 'exitDx' : 'entryDx']); // @ts-ignore - dy = parseFloat( - edge.style[source ? 'exitDy' : 'entryDy'] - ); + dy = parseFloat(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 + - constraint.dx * scale, - bounds.y + - constraint.point.y * bounds.height + - constraint.dy * scale + bounds.x + constraint.point.x * bounds.width + constraint.dx * scale, + bounds.y + constraint.point.y * bounds.height + 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 = ( - this.graph.view.getState(cell.getParent()) - ); + const pstate = 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); } diff --git a/packages/core/src/view/editing/GraphEditing.ts b/packages/core/src/view/editing/GraphEditing.ts index 596b3c6f1..fdfc56a9a 100644 --- a/packages/core/src/view/editing/GraphEditing.ts +++ b/packages/core/src/view/editing/GraphEditing.ts @@ -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; +type PartialEvents = Pick; +type PartialCells = Pick< + GraphCells, + 'isAutoSizeCell' | 'cellSizeUpdated' | 'getCurrentCellStyle' | 'isCellLocked' +>; +type PartialClass = PartialGraph & PartialSelection & PartialEvents & PartialCells; +class GraphEditing extends autoImplement() { /** * 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) ); - (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 { - (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; } } diff --git a/packages/core/src/view/event/EventSource.ts b/packages/core/src/view/event/EventSource.ts index f2349c332..18ca5228a 100644 --- a/packages/core/src/view/event/EventSource.ts +++ b/packages/core/src/view/event/EventSource.ts @@ -94,7 +94,7 @@ class EventSource { * * Sets . */ - setEventSource(value: EventSource) { + setEventSource(value: EventSource | null) { this.eventSource = value; } diff --git a/packages/core/src/view/event/GraphEvents.ts b/packages/core/src/view/event/GraphEvents.ts index 27b8136f2..9a219d5c5 100644 --- a/packages/core/src/view/event/GraphEvents.ts +++ b/packages/core/src/view/event/GraphEvents.ts @@ -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; +type PartialSelection = Pick< + GraphSelection, + 'isCellSelected' | 'selectCellForEvent' | 'clearSelection' +>; +type PartialEditing = Pick< + GraphEditing, + 'isCellEditable' | 'isEditing' | 'startEditingAtCell' | 'stopEditing' +>; +type PartialSnap = Pick; +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() { /** * 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(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 = this.graph.panning.panningHandler; - const connectionHandler = this.graph.connectionHandler; + const panningHandler = this.panning.panningHandler; + const 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 = 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()); } } diff --git a/packages/core/src/view/event/InternalEvent.ts b/packages/core/src/view/event/InternalEvent.ts index 4c5d6f00f..ffbe4ca54 100644 --- a/packages/core/src/view/event/InternalEvent.ts +++ b/packages/core/src/view/event/InternalEvent.ts @@ -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); diff --git a/packages/core/src/view/event/InternalMouseEvent.ts b/packages/core/src/view/event/InternalMouseEvent.ts index cc3fd3169..1350f219d 100644 --- a/packages/core/src/view/event/InternalMouseEvent.ts +++ b/packages/core/src/view/event/InternalMouseEvent.ts @@ -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 * . */ - 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 * . */ - graphY?: number; + graphY: number; /** * Variable: state * * Holds the optional associated with this event. */ - state?: CellState; + state: CellState | null; /** * Variable: sourceState @@ -100,14 +104,14 @@ class InternalMouseEvent { * Holds the that was passed to the constructor. This can be * different from depending on the result of . */ - sourceState?: CellState; + sourceState: CellState | null; /** * Function: getEvent * * Returns . */ - getEvent(): MouseEvent { + getEvent() { return this.evt; } @@ -116,7 +120,7 @@ class InternalMouseEvent { * * Returns the target DOM element using for . */ - getSource(): Element { + getSource() { return getSource(this.evt); } @@ -125,7 +129,7 @@ class InternalMouseEvent { * * Returns true if the given is the source of . */ - isSource(shape: Shape) { + isSource(shape: Shape | null) { return shape ? isAncestorNode(shape.node, this.getSource()) : false; } @@ -152,7 +156,7 @@ class InternalMouseEvent { * * Returns . */ - getGraphX(): number | undefined { + getGraphX() { return this.graphX; } @@ -161,7 +165,7 @@ class InternalMouseEvent { * * Returns . */ - getGraphY(): number | undefined { + getGraphY() { return this.graphY; } @@ -170,7 +174,7 @@ class InternalMouseEvent { * * Returns . */ - getState(): CellState | undefined { + getState() { return this.state; } @@ -179,12 +183,9 @@ class InternalMouseEvent { * * Returns the in 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 . */ - 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(); diff --git a/packages/core/src/view/folding/GraphFolding.ts b/packages/core/src/view/folding/GraphFolding.ts index 5e9d59942..3233f7813 100644 --- a/packages/core/src/view/folding/GraphFolding.ts +++ b/packages/core/src/view/folding/GraphFolding.ts @@ -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; +type PartialCells = Pick< + GraphCells, + | 'getCurrentCellStyle' + | 'isExtendParent' + | 'extendParent' + | 'constrainChild' + | 'getPreferredSizeForCell' +>; +type PartialSelection = Pick; +type PartialEditing = Pick; +type PartialClass = PartialGraph & PartialCells & PartialSelection & PartialEditing; - graph: Graph; - options: GraphFoldingOptions; +class GraphFolding extends autoImplement() { + 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); } } diff --git a/packages/core/src/view/geometry/shape/Shape.ts b/packages/core/src/view/geometry/shape/Shape.ts index fa8053b61..c5d85a2bc 100644 --- a/packages/core/src/view/geometry/shape/Shape.ts +++ b/packages/core/src/view/geometry/shape/Shape.ts @@ -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); } diff --git a/packages/core/src/view/geometry/shape/node/StencilShape.ts b/packages/core/src/view/geometry/shape/node/StencilShape.ts index bc9148bf0..096e8f264 100644 --- a/packages/core/src/view/geometry/shape/node/StencilShape.ts +++ b/packages/core/src/view/geometry/shape/node/StencilShape.ts @@ -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, diff --git a/packages/core/src/view/image/GraphImage.ts b/packages/core/src/view/image/GraphImage.ts index 91f601fab..2fe2506d4 100644 --- a/packages/core/src/view/image/GraphImage.ts +++ b/packages/core/src/view/image/GraphImage.ts @@ -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. */ diff --git a/packages/core/src/view/image/ImageBundle.ts b/packages/core/src/view/image/ImageBundle.ts index 9340d56ac..c4112fa71 100644 --- a/packages/core/src/view/image/ImageBundle.ts +++ b/packages/core/src/view/image/ImageBundle.ts @@ -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 }; } diff --git a/packages/core/src/view/label/GraphLabel.ts b/packages/core/src/view/label/GraphLabel.ts index 2d0bff57a..efabe4272 100644 --- a/packages/core/src/view/label/GraphLabel.ts +++ b/packages/core/src/view/label/GraphLabel.ts @@ -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; +type PartialCells = Pick; +type PartialEdge = Pick; +type PartialVertex = Pick; +type PartialClass = PartialGraph & PartialCells & PartialEdge & PartialVertex; + +class GraphLabel extends autoImplement() { /** * 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())) ); } } diff --git a/packages/core/src/view/layout/Overlays.ts b/packages/core/src/view/layout/GraphOverlays.ts similarity index 74% rename from packages/core/src/view/layout/Overlays.ts rename to packages/core/src/view/layout/GraphOverlays.ts index 956a1b8aa..58c988191 100644 --- a/packages/core/src/view/layout/Overlays.ts +++ b/packages/core/src/view/layout/GraphOverlays.ts @@ -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; +type PartialClass = PartialGraph & PartialSelection; +class GraphOverlays extends autoImplement() { /***************************************************************************** * 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) { - (cell.overlays).splice(index, 1); - - if ((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 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 = this.getModel().getRoot()): void { - this.removeCellOverlays(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(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, `${warning}`); @@ -201,4 +204,4 @@ class Overlays { } } -export default Overlays; +export default GraphOverlays; diff --git a/packages/core/src/view/model/Model.ts b/packages/core/src/view/model/Model.ts index 4aafbca62..bf247cd79 100644 --- a/packages/core/src/view/model/Model.ts +++ b/packages/core/src/view/model/Model.ts @@ -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 (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=this.getRoot(cell)): void { - + updateEdgeParents(cell: Cell, root: 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.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 * 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 = 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]; } diff --git a/packages/core/src/view/selection/GraphSelection.ts b/packages/core/src/view/selection/GraphSelection.ts index 04f813144..b8d3eed48 100644 --- a/packages/core/src/view/selection/GraphSelection.ts +++ b/packages/core/src/view/selection/GraphSelection.ts @@ -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; +type PartialEvents = Pick; +type PartialClass = PartialGraph & PartialCells & PartialEvents & EventSource; +// @ts-ignore recursive reference error +class GraphSelection extends autoImplement() { // 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(this.getFirstSelectableCell(cells)); - } - - const tmp = new CellArray(); - for (let i = 0; i < cells.length; i += 1) { - if ((this.graph).isCellSelectable(cells[i])) { - tmp.push(cells[i]); - } - } - this.changeSelection(tmp, this.cells); + if (this.singleSelection) { + cells = new CellArray(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 ((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(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]) && - (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 != 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 = (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.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.getParent(); + isSiblingSelected(cell: Cell) { + const parent = cell.getParent(); const childCount = parent.getChildCount(); for (let i = 0; i < childCount; i += 1) { - const child = 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.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); } } diff --git a/packages/core/src/view/selection/RubberBand.ts b/packages/core/src/view/selection/RubberBand.ts index ea53d051b..37e3eec9f 100644 --- a/packages/core/src/view/selection/RubberBand.ts +++ b/packages/core/src/view/selection/RubberBand.ts @@ -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); diff --git a/packages/core/src/view/snap/GraphSnap.ts b/packages/core/src/view/snap/GraphSnap.ts index 1bb17470b..a68c4f95c 100644 --- a/packages/core/src/view/snap/GraphSnap.ts +++ b/packages/core/src/view/snap/GraphSnap.ts @@ -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; +type PartialClass = PartialGraph; + +class GraphSnap extends autoImplement() { // 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; } } diff --git a/packages/core/src/view/style/StyleMap.ts b/packages/core/src/view/style/StyleMap.ts index 03573e199..60d17b185 100644 --- a/packages/core/src/view/style/StyleMap.ts +++ b/packages/core/src/view/style/StyleMap.ts @@ -1,6 +1,4 @@ -import {Style} from "util"; - -class StyleMap { +class StyleMap implements Record { defaultVertex?: StyleMap; defaultEdge?: StyleMap; @@ -160,7 +158,6 @@ class StyleMap { */ exitY: any; - /** * Variable: STYLE_EXIT_DX * diff --git a/packages/core/src/view/style/Stylesheet.ts b/packages/core/src/view/style/Stylesheet.ts index 5678542c2..60fa1bef7 100644 --- a/packages/core/src/view/style/Stylesheet.ts +++ b/packages/core/src/view/style/Stylesheet.ts @@ -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 this.styles.defaultVertex; + getDefaultVertexStyle() { + return this.styles.defaultVertex; } /** * Sets the default style for edges. */ - getDefaultEdgeStyle(): StyleMap { - return 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'); diff --git a/packages/core/src/view/terminal/GraphTerminal.ts b/packages/core/src/view/terminal/GraphTerminal.ts index 6465f1f0d..3086c4431 100644 --- a/packages/core/src/view/terminal/GraphTerminal.ts +++ b/packages/core/src/view/terminal/GraphTerminal.ts @@ -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; +type PartialClass = PartialGraph; +class GraphTerminal extends autoImplement() { /***************************************************************************** * 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(); 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); diff --git a/packages/core/src/view/tooltip/GraphTooltip.ts b/packages/core/src/view/tooltip/GraphTooltip.ts index ea10c8497..44caabfd9 100644 --- a/packages/core/src/view/tooltip/GraphTooltip.ts +++ b/packages/core/src/view/tooltip/GraphTooltip.ts @@ -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; +type PartialClass = PartialGraph & PartialFolding; +class GraphTooltip extends autoImplement() { /** * 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, - '
' - ); - } + // 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, '
'); + } - 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 = (( - this.graph.selectionCellsHandler - )).getHandler(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(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 { - (this.graph.tooltipHandler).setEnabled(enabled); + setTooltips(enabled: boolean) { + this.getTooltipHandler().setEnabled(enabled); } - } export default GraphTooltip; diff --git a/packages/core/src/view/tooltip/TooltipHandler.ts b/packages/core/src/view/tooltip/TooltipHandler.ts index 699895385..438c882ef 100644 --- a/packages/core/src/view/tooltip/TooltipHandler.ts +++ b/packages/core/src/view/tooltip/TooltipHandler.ts @@ -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 . */ - 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 . */ - 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 . */ - isEnabled(): boolean { + isEnabled() { return this.enabled; } @@ -112,7 +121,7 @@ class TooltipHandler { * Enables or disables event handling. This implementation * updates . */ - setEnabled(enabled: boolean): void { + setEnabled(enabled: boolean) { this.enabled = enabled; } @@ -121,7 +130,7 @@ class TooltipHandler { * * Returns . */ - isHideOnHover(): boolean { + isHideOnHover() { return this.hideOnHover; } @@ -130,7 +139,7 @@ class TooltipHandler { * * Sets . */ - 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 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, '
'); + this.div.innerHTML = (tip as string).replace(/\n/g, '
'); } 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; } } } diff --git a/packages/core/src/view/validation/GraphValidation.ts b/packages/core/src/view/validation/GraphValidation.ts index c70ee03bc..596852644 100644 --- a/packages/core/src/view/validation/GraphValidation.ts +++ b/packages/core/src/view/validation/GraphValidation.ts @@ -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; +type PartialConnections = Pick; +type PartialOverlays = Pick; +type PartialClass = PartialGraph & PartialEdge & PartialConnections & PartialOverlays; +class GraphValidation extends autoImplement() { /** * 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(source, 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, - edge, - source, - target, - sourceOut, - targetIn - ); + for (const multiplicity of this.multiplicities) { + const err = multiplicity.check( + (this), // needs to cast to Graph + 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 = 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.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, '
')); } 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; } } diff --git a/packages/core/src/view/view/CurrentRootChange.ts b/packages/core/src/view/view/CurrentRootChange.ts index ed31c4a41..b5bb6a3fa 100644 --- a/packages/core/src/view/view/CurrentRootChange.ts +++ b/packages/core/src/view/view/CurrentRootChange.ts @@ -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; diff --git a/packages/core/src/view/view/GraphView.ts b/packages/core/src/view/view/GraphView.ts index 02b1f8b2b..c4a5a5276 100644 --- a/packages/core/src/view/view/GraphView.ts +++ b/packages/core/src/view/view/GraphView.ts @@ -37,8 +37,6 @@ import CellState from '../cell/datatypes/CellState'; import UndoableEdit from '../model/UndoableEdit'; import ImageShape from '../geometry/shape/node/ImageShape'; import InternalMouseEvent from '../event/InternalMouseEvent'; -import StyleRegistry from '../style/StyleRegistry'; -import graph from '../Graph'; import Cell from '../cell/datatypes/Cell'; import Image from '../image/ImageBox'; import CurrentRootChange from './CurrentRootChange'; @@ -51,6 +49,9 @@ import { getClientX, getClientY, getSource, isConsumed } from '../../util/EventU import { clone } from '../../util/CloneUtils'; import CellArray from '../cell/datatypes/CellArray'; +import type { MaxGraph } from '../Graph'; +import StyleRegistry from '../style/StyleRegistry'; + /** * @class GraphView * @extends {EventSource} @@ -95,7 +96,7 @@ import CellArray from '../cell/datatypes/CellArray'; * respectively. */ class GraphView extends EventSource { - constructor(graph: graph) { + constructor(graph: MaxGraph) { super(); this.graph = graph; @@ -108,15 +109,16 @@ class GraphView extends EventSource { EMPTY_POINT = new Point(); - canvas: SVGElement | null = null; - - backgroundPane: SVGElement | null = null; - - drawPane: SVGElement | null = null; - - overlayPane: SVGElement | null = null; - - decoratorPane: SVGElement | null = null; + // @ts-ignore Cannot be null + canvas: SVGElement; + // @ts-ignore Cannot be null + backgroundPane: SVGElement; + // @ts-ignore Cannot be null + drawPane: SVGElement; + // @ts-ignore Cannot be null + overlayPane: SVGElement; + // @ts-ignore Cannot be null + decoratorPane: SVGElement; /** * Specifies the resource key for the status message after a long operation. @@ -155,7 +157,7 @@ class GraphView extends EventSource { /** * Reference to the enclosing {@link graph}. */ - graph: graph; + graph: MaxGraph; /** * {@link Cell} that acts as the root of the displayed cell hierarchy. @@ -172,7 +174,7 @@ class GraphView extends EventSource { */ translate = new Point(); - states = new Dictionary(); + states = new Dictionary(); /** * Specifies if the style should be updated in each validation step. If this @@ -295,14 +297,14 @@ class GraphView extends EventSource { /** * Returns {@link states}. */ - getStates(): Dictionary { + getStates() { return this.states; } /** * Sets {@link states}. */ - setStates(value: Dictionary): void { + setStates(value: Dictionary) { this.states = value; } @@ -310,35 +312,35 @@ class GraphView extends EventSource { * Returns the DOM node that contains the background-, draw- and * overlay- and decoratorpanes. */ - getCanvas(): SVGElement | null { + getCanvas() { return this.canvas; } /** * Returns the DOM node that represents the background layer. */ - getBackgroundPane(): SVGElement | null { + getBackgroundPane() { return this.backgroundPane; } /** * Returns the DOM node that represents the main drawing layer. */ - getDrawPane(): SVGElement | null { + getDrawPane() { return this.drawPane; } /** * Returns the DOM node that represents the layer above the drawing layer. */ - getOverlayPane(): SVGElement | null { + getOverlayPane() { return this.overlayPane; } /** * Returns the DOM node that represents the topmost drawing layer. */ - getDecoratorPane(): SVGElement | null { + getDecoratorPane() { return this.decoratorPane; } @@ -347,16 +349,16 @@ class GraphView extends EventSource { * * @param cells Array of {@link Cell} whose bounds should be returned. */ - getBounds(cells: CellArray): Rectangle | null { - let result = null; + getBounds(cells: CellArray) { + let result: Rectangle | null = null; - if (cells != null && cells.length > 0) { + if (cells.length > 0) { for (let i = 0; i < cells.length; i += 1) { if (cells[i].isVertex() || cells[i].isEdge()) { const state = this.getState(cells[i]); - if (state != null) { - if (result == null) { + if (state) { + if (!result) { result = Rectangle.fromRectangle(state); } else { result.add(state); @@ -365,6 +367,7 @@ class GraphView extends EventSource { } } } + return result; } @@ -374,16 +377,16 @@ class GraphView extends EventSource { * * @param root {@link mxCell} that specifies the root of the displayed cell hierarchy. */ - setCurrentRoot(root: Cell | null): Cell | null { - if (this.currentRoot != root) { - const change = new CurrentRootChange(this, root); + setCurrentRoot(root: Cell | null) { + if (this.currentRoot !== root) { + const change = new CurrentRootChange(this, root); change.execute(); const edit = new UndoableEdit(this, true); edit.add(change); this.fireEvent(new EventObject(InternalEvent.UNDO, 'edit', edit)); - (this.graph).sizeDidChange(); + this.graph.sizeDidChange(); this.currentRoot = root; } @@ -398,11 +401,11 @@ class GraphView extends EventSource { * @param dx X-coordinate of the translation. * @param dy Y-coordinate of the translation. */ - scaleAndTranslate(scale: number, dx: number, dy: number): void { + scaleAndTranslate(scale: number, dx: number, dy: number) { const previousScale = this.scale; const previousTranslate = new Point(this.translate.x, this.translate.y); - if (this.scale != scale || this.translate.x != dx || this.translate.y != dy) { + if (this.scale !== scale || this.translate.x !== dx || this.translate.y !== dy) { this.scale = scale; this.translate.x = dx; @@ -426,16 +429,16 @@ class GraphView extends EventSource { /** * Invoked after {@link scale} and/or {@link translate} has changed. */ - viewStateChanged(): void { + viewStateChanged() { this.revalidate(); - (this.graph).sizeDidChange(); + this.graph.sizeDidChange(); } /** * Clears the view if {@link currentRoot} is not null and revalidates. */ - refresh(): void { - if (this.currentRoot != null) { + refresh() { + if (this.currentRoot) { this.clear(); } this.revalidate(); @@ -444,7 +447,7 @@ class GraphView extends EventSource { /** * Revalidates the complete view with all cell states. */ - revalidate(): void { + revalidate() { this.invalidate(); this.validate(); } @@ -458,7 +461,7 @@ class GraphView extends EventSource { * @param force Boolean indicating if the current root should be ignored for * recursion. */ - clear(cell?: Cell | null, force: boolean = false, recurse: boolean = true): void { + clear(cell?: Cell | null, force = false, recurse = true) { if (!cell) { cell = this.graph.getModel().getRoot(); } @@ -485,44 +488,42 @@ class GraphView extends EventSource { * @param cell Optional {@link Cell} to be invalidated. Default is the root of the * model. */ - invalidate( - cell: Cell | null = null, - recurse: boolean = true, - includeEdges: boolean = true - ): void { - const model: Model = (this.graph).getModel(); - const state: CellState = this.getState(cell); + invalidate(cell: Cell | null = null, recurse = true, includeEdges = true) { + const model = this.graph.getModel(); + cell = cell ?? model.getRoot(); - cell = (cell || model.getRoot()); + if (cell) { + const state = this.getState(cell); - if (state != null) { - state.invalid = true; - } - - // Avoids infinite loops for invalid graphs - if (!cell.invalidating) { - cell.invalidating = true; - - // Recursively invalidates all descendants - if (recurse) { - const childCount = cell.getChildCount(); - - for (let i = 0; i < childCount; i += 1) { - const child = cell.getChildAt(i); - this.invalidate(child, recurse, includeEdges); - } + if (state) { + state.invalid = true; } - // Propagates invalidation to all connected edges - if (includeEdges) { - const edgeCount = cell.getEdgeCount(); + // Avoids infinite loops for invalid graphs + if (!cell.invalidating) { + cell.invalidating = true; - for (let i = 0; i < edgeCount; i += 1) { - this.invalidate(cell.getEdgeAt(i), recurse, includeEdges); + // Recursively invalidates all descendants + if (recurse) { + const childCount = cell.getChildCount(); + + for (let i = 0; i < childCount; i += 1) { + const child = cell.getChildAt(i); + this.invalidate(child, recurse, includeEdges); + } } - } - cell.invalidating = false; + // Propagates invalidation to all connected edges + if (includeEdges) { + const edgeCount = cell.getEdgeCount(); + + for (let i = 0; i < edgeCount; i += 1) { + this.invalidate(cell.getEdgeAt(i), recurse, includeEdges); + } + } + + cell.invalidating = false; + } } } @@ -534,32 +535,25 @@ class GraphView extends EventSource { * @param cell Optional {@link Cell} to be used as the root of the validation. * Default is {@link currentRoot} or the root of the model. */ - validate(cell: Cell | null = null): void { + validate(cell: Cell | null = null) { const t0 = mxLog.enter('mxGraphView.validate'); window.status = Resources.get(this.updatingDocumentResource) || this.updatingDocumentResource; this.resetValidationState(); - const graphBounds = this.getBoundingBox( - this.validateCellState( - ( - this.validateCell( - ( - (cell || - (this.currentRoot != null - ? this.currentRoot - : (this.graph).getModel().getRoot())) - ) - ) - ) - ) - ); + const c = cell || (this.currentRoot ?? this.graph.getModel().getRoot()); - this.setGraphBounds(graphBounds != null ? graphBounds : this.getEmptyBounds()); - this.validateBackground(); + if (c) { + const graphBounds = this.getBoundingBox( + this.validateCellState(c ? this.validateCell(c) : null) + ); - this.resetValidationState(); + this.setGraphBounds(graphBounds ?? this.getEmptyBounds()); + this.validateBackground(); + + this.resetValidationState(); + } window.status = Resources.get(this.doneResource) || this.doneResource; mxLog.leave('mxGraphView.validate', t0); @@ -569,7 +563,7 @@ class GraphView extends EventSource { * Returns the bounds for an empty graph. This returns a rectangle at * {@link translate} with the size of 0 x 0. */ - getEmptyBounds(): Rectangle { + getEmptyBounds() { return new Rectangle(this.translate.x * this.scale, this.translate.y * this.scale); } @@ -581,20 +575,17 @@ class GraphView extends EventSource { * @param recurse Optional boolean indicating if the children should be included. * Default is true. */ - getBoundingBox( - state: CellState | null = null, - recurse: boolean = true - ): Rectangle | null { + getBoundingBox(state: CellState | null = null, recurse = true): Rectangle | null { let bbox = null; - if (state != null) { - if (state.shape != null && state.shape.boundingBox != null) { + if (state) { + if (state.shape && state.shape.boundingBox) { bbox = state.shape.boundingBox.clone(); } // Adds label bounding box to graph bounds - if (state.text != null && state.text.boundingBox != null) { - if (bbox != null) { + if (state.text && state.text.boundingBox) { + if (bbox) { bbox.add(state.text.boundingBox); } else { bbox = state.text.boundingBox.clone(); @@ -602,14 +593,13 @@ class GraphView extends EventSource { } if (recurse) { - const model = (this.graph).getModel(); const childCount = state.cell.getChildCount(); for (let i = 0; i < childCount; i += 1) { const bounds = this.getBoundingBox(this.getState(state.cell.getChildAt(i))); - if (bounds != null) { - if (bbox == null) { + if (bounds) { + if (!bbox) { bbox = bounds; } else { bbox.add(bounds); @@ -618,6 +608,7 @@ class GraphView extends EventSource { } } } + return bbox; } @@ -626,14 +617,14 @@ class GraphView extends EventSource { * * @param bounds {@link mxRectangle} that represents the bounds of the shape. */ - createBackgroundPageShape(bounds: Rectangle): RectangleShape { + createBackgroundPageShape(bounds: Rectangle) { return new RectangleShape(bounds, 'white', 'black'); } /** * Calls {@link validateBackgroundImage} and {@link validateBackgroundPage}. */ - validateBackground(): void { + validateBackground() { this.validateBackgroundImage(); this.validateBackgroundPage(); } @@ -641,25 +632,25 @@ class GraphView extends EventSource { /** * Validates the background image. */ - validateBackgroundImage(): void { - const bg = (this.graph).getBackgroundImage(); + validateBackgroundImage() { + const bg = this.graph.getBackgroundImage(); - if (bg != null) { - if (this.backgroundImage == null || this.backgroundImage.imageSrc !== bg.src) { - if (this.backgroundImage != null) { + if (bg) { + if (!this.backgroundImage || this.backgroundImage.imageSrc !== bg.src) { + if (this.backgroundImage) { this.backgroundImage.destroy(); } const bounds = new Rectangle(0, 0, 1, 1); this.backgroundImage = new ImageShape(bounds, bg.src); - this.backgroundImage.dialect = (this.graph).dialect; + this.backgroundImage.dialect = this.graph.dialect; this.backgroundImage.init(this.backgroundPane); this.backgroundImage.redraw(); } this.redrawBackgroundImage(this.backgroundImage, bg); - } else if (this.backgroundImage != null) { + } else if (this.backgroundImage) { this.backgroundImage.destroy(); this.backgroundImage = null; } @@ -668,8 +659,8 @@ class GraphView extends EventSource { /** * Validates the background page. */ - validateBackgroundPage(): void { - const graph = this.graph; + validateBackgroundPage() { + const graph = this.graph; if (graph.pageVisible) { const bounds = this.getBackgroundPageBounds(); @@ -678,13 +669,13 @@ class GraphView extends EventSource { this.backgroundPageShape = this.createBackgroundPageShape(bounds); this.backgroundPageShape.scale = this.scale; this.backgroundPageShape.isShadow = true; - this.backgroundPageShape.dialect = (this.graph).dialect; + this.backgroundPageShape.dialect = this.graph.dialect; this.backgroundPageShape.init(this.backgroundPane); this.backgroundPageShape.redraw(); if (this.backgroundPageShape.node) { // Adds listener for double click handling on background - if (graph.nativeDblClickEnabled) { + if (graph.isNativeDblClickEnabled()) { InternalEvent.addListener(this.backgroundPageShape.node, 'dblclick', (( evt: MouseEvent ) => { @@ -697,7 +688,10 @@ class GraphView extends EventSource { InternalEvent.addGestureListeners( this.backgroundPageShape.node, (evt: Event) => { - graph.fireMouseEvent(InternalEvent.MOUSE_DOWN, new InternalMouseEvent(evt)); + graph.fireMouseEvent( + InternalEvent.MOUSE_DOWN, + new InternalMouseEvent(evt as MouseEvent) + ); }, (evt: Event) => { // Hides the tooltip if mouse is outside container @@ -708,12 +702,15 @@ class GraphView extends EventSource { if (graph.isMouseDown && !isConsumed(evt)) { graph.fireMouseEvent( InternalEvent.MOUSE_MOVE, - new InternalMouseEvent(evt) + new InternalMouseEvent(evt as MouseEvent) ); } }, (evt: Event) => { - graph.fireMouseEvent(InternalEvent.MOUSE_UP, new InternalMouseEvent(evt)); + graph.fireMouseEvent( + InternalEvent.MOUSE_UP, + new InternalMouseEvent(evt as MouseEvent) + ); } ); } @@ -722,7 +719,7 @@ class GraphView extends EventSource { this.backgroundPageShape.bounds = bounds; this.backgroundPageShape.redraw(); } - } else if (this.backgroundPageShape != null) { + } else if (this.backgroundPageShape) { this.backgroundPageShape.destroy(); this.backgroundPageShape = null; } @@ -731,9 +728,9 @@ class GraphView extends EventSource { /** * Returns the bounds for the background page. */ - getBackgroundPageBounds(): Rectangle { - const fmt = (this.graph).pageFormat; - const ps = this.scale * (this.graph).pageScale; + getBackgroundPageBounds() { + const fmt = this.graph.pageFormat; + const ps = this.scale * this.graph.pageScale; return new Rectangle( this.scale * this.translate.x, @@ -767,13 +764,17 @@ class GraphView extends EventSource { * @param backgroundImage {@link mxImageShape} that represents the background image. * @param bg {@link mxImage} that specifies the image and its dimensions. */ - redrawBackgroundImage(backgroundImage: ImageShape, bg: Image): void { + redrawBackgroundImage(backgroundImage: ImageShape, bg: Image) { backgroundImage.scale = this.scale; - const bounds = backgroundImage.bounds; - bounds.x = this.scale * this.translate.x; - bounds.y = this.scale * this.translate.y; - bounds.width = this.scale * bg.width; - bounds.height = this.scale * bg.height; + + if (backgroundImage.bounds) { + const bounds = backgroundImage.bounds; + bounds.x = this.scale * this.translate.x; + bounds.y = this.scale * this.translate.y; + bounds.width = this.scale * bg.width; + bounds.height = this.scale * bg.height; + } + backgroundImage.redraw(); } @@ -786,23 +787,23 @@ class GraphView extends EventSource { * @param visible Optional boolean indicating if the cell should be visible. Default * is true. */ - validateCell(cell: Cell, visible: boolean = true): Cell | null { + validateCell(cell: Cell, visible = true) { visible = visible && cell.isVisible(); const state = this.getState(cell, visible); - if (state != null && !visible) { + if (state && !visible) { this.removeState(cell); } else { - const model = (this.graph).getModel(); const childCount = cell.getChildCount(); for (let i = 0; i < childCount; i += 1) { this.validateCell( - cell.getChildAt(i), + cell.getChildAt(i), visible && (!cell.isCollapsed() || cell === this.currentRoot) ); } } + return cell; } @@ -813,37 +814,31 @@ class GraphView extends EventSource { * @param recurse Optional boolean indicating if the children of the cell should be * validated. Default is true. */ - validateCellState(cell: Cell, recurse: boolean = true): CellState | null { + validateCellState(cell: Cell | null, recurse = true) { let state: CellState | null = null; - if (cell != null) { + if (cell) { state = this.getState(cell); - if (state != null) { - const model = (this.graph).getModel(); - + if (state) { if (state.invalid) { state.invalid = false; - if (state.style == null || state.invalidStyle) { - state.style = (this.graph).getCellStyle(state.cell); + if (!state.style || state.invalidStyle) { + state.style = this.graph.getCellStyle(state.cell); state.invalidStyle = false; } if (cell !== this.currentRoot) { - this.validateCellState(cell.getParent(), false); + this.validateCellState(cell.getParent(), false); } state.setVisibleTerminalState( - ( - this.validateCellState(this.getVisibleTerminal(cell, true), false) - ), + this.validateCellState(this.getVisibleTerminal(cell, true), false), true ); state.setVisibleTerminalState( - ( - this.validateCellState(this.getVisibleTerminal(cell, false), false) - ), + this.validateCellState(this.getVisibleTerminal(cell, false), false), false ); @@ -851,7 +846,7 @@ class GraphView extends EventSource { // Repaint happens immediately after the cell is validated if (cell !== this.currentRoot && !state.invalid) { - (this.graph).cellRenderer.redraw(state, false, this.isRendering()); + this.graph.cellRenderer.redraw(state, false, this.isRendering()); // Handles changes to invertex paintbounds after update of rendering shape state.updateCachedBounds(); @@ -860,13 +855,13 @@ class GraphView extends EventSource { if (recurse && !state.invalid) { // Updates order in DOM if recursively traversing - if (state.shape != null) { + if (state.shape) { this.stateValidated(state); } const childCount = cell.getChildCount(); for (let i = 0; i < childCount; i += 1) { - this.validateCellState(cell.getChildAt(i)); + this.validateCellState(cell.getChildAt(i)); } } } @@ -879,9 +874,9 @@ class GraphView extends EventSource { * * @param state {@link mxCellState} to be updated. */ - updateCellState(state: CellState): void { - const absoluteOffset = state.absoluteOffset; - const origin = state.origin; + updateCellState(state: CellState) { + const absoluteOffset = state.absoluteOffset; + const origin = state.origin; absoluteOffset.x = 0; absoluteOffset.y = 0; @@ -890,40 +885,37 @@ class GraphView extends EventSource { state.length = 0; if (state.cell !== this.currentRoot) { - const model = (this.graph).getModel(); - const pState = this.getState(state.cell.getParent()); + const pState = this.getState(state.cell.getParent()); - if (pState != null && pState.cell !== this.currentRoot) { - origin.x += (pState.origin).x; - origin.y += (pState.origin).y; + if (pState && pState.cell !== this.currentRoot) { + origin.x += pState.origin.x; + origin.y += pState.origin.y; } - let offset = (this.graph).getChildOffsetForCell(state.cell); + let offset = this.graph.getChildOffsetForCell(state.cell); - if (offset != null) { + if (offset) { origin.x += offset.x; origin.y += offset.y; } - const geo = (state.cell).getGeometry(); + const geo = state.cell.getGeometry(); - if (geo != null) { + if (geo) { if (!state.cell.isEdge()) { - offset = (geo.offset != null ? geo.offset : this.EMPTY_POINT); + offset = geo.offset ? geo.offset : this.EMPTY_POINT; - if (geo.relative && pState != null) { + if (geo.relative && pState) { if (pState.cell.isEdge()) { const origin = this.getPoint(pState, geo); - if (origin != null) { - origin.x += - origin.x / this.scale - (pState.origin).x - this.translate.x; - origin.y += - origin.y / this.scale - (pState.origin).y - this.translate.y; + if (origin) { + origin.x += origin.x / this.scale - pState.origin.x - this.translate.x; + origin.y += origin.y / this.scale - pState.origin.y - this.translate.y; } } else { - origin.x += geo.x * pState.unscaledWidth + offset.x; - origin.y += geo.y * pState.unscaledHeight + offset.y; + origin.x += geo.x * pState.unscaledWidth + offset.x; + origin.y += geo.y * pState.unscaledHeight + offset.y; } } else { absoluteOffset.x = this.scale * offset.x; @@ -956,12 +948,11 @@ class GraphView extends EventSource { /** * Validates the given cell state. */ - updateVertexState(state: CellState, geo: Geometry): void { - const model = (this.graph).getModel(); + updateVertexState(state: CellState, geo: Geometry) { const pState = this.getState(state.cell.getParent()); - if (geo.relative && pState != null && !pState.cell.isEdge()) { - const alpha = toRadians(pState.style.rotation || '0'); + if (geo.relative && pState && !pState.cell.isEdge()) { + const alpha = toRadians(pState.style.rotation); if (alpha !== 0) { const cos = Math.cos(alpha); @@ -980,18 +971,18 @@ class GraphView extends EventSource { /** * Validates the given cell state. */ - updateEdgeState(state: CellState, geo: Geometry): void { - const source = state.getVisibleTerminalState(true); - const target = state.getVisibleTerminalState(false); + updateEdgeState(state: CellState, geo: Geometry) { + const source = state.getVisibleTerminalState(true); + const target = state.getVisibleTerminalState(false); // This will remove edges with no terminals and no terminal points // as such edges are invalid and produce NPEs in the edge styles. // Also removes connected edges that have no visible terminals. if ( - (state.cell.getTerminal(true) != null && source == null) || - (source == null && geo.getTerminalPoint(true) == null) || - (state.cell.getTerminal(false) != null && target == null) || - (target == null && geo.getTerminalPoint(false) == null) + (state.cell.getTerminal(true) && !source) || + (!source && !geo.getTerminalPoint(true)) || + (state.cell.getTerminal(false) && !target) || + (!target && !geo.getTerminalPoint(false)) ) { this.clear(state.cell, true); } else { @@ -1087,7 +1078,7 @@ class GraphView extends EventSource { * @param state {@link mxCellState} that represents the cell state. */ stateValidated(state: CellState): void { - const graph = this.graph; + const graph = this.graph; const fg = (state.cell.isEdge() && graph.keepEdgesInForeground) || (state.cell.isVertex() && graph.keepEdgesInBackground); @@ -1114,18 +1105,22 @@ class GraphView extends EventSource { * @param source {@link mxCellState} which represents the source terminal. * @param target {@link mxCellState} which represents the target terminal. */ - updateFixedTerminalPoints(edge: CellState, source: CellState, target: CellState): void { + updateFixedTerminalPoints( + edge: CellState, + source: CellState | null, + target: CellState | null + ) { this.updateFixedTerminalPoint( edge, source, true, - (this.graph).getConnectionConstraint(edge, source, true) + this.graph.getConnectionConstraint(edge, source, true) ); this.updateFixedTerminalPoint( edge, target, false, - (this.graph).getConnectionConstraint(edge, target, false) + this.graph.getConnectionConstraint(edge, target, false) ); } @@ -1143,7 +1138,7 @@ class GraphView extends EventSource { */ updateFixedTerminalPoint( edge: CellState, - terminal: CellState, + terminal: CellState | null, source: boolean, constraint: ConnectionConstraint ) { @@ -1167,24 +1162,24 @@ class GraphView extends EventSource { */ getFixedTerminalPoint( edge: CellState, - terminal: CellState, + terminal: CellState | null, source: boolean, - constraint: ConnectionConstraint + constraint: ConnectionConstraint | null ): Point | null { let pt = null; - if (constraint != null) { - pt = (this.graph).getConnectionPoint(terminal, constraint, false); // FIXME Rounding introduced bugs when calculating label positions -> , this.graph.isOrthogonal(edge)); + if (constraint && terminal) { + pt = this.graph.getConnectionPoint(terminal, constraint, false); // FIXME Rounding introduced bugs when calculating label positions -> , this.graph.isOrthogonal(edge)); } - if (pt == null && terminal == null) { + if (!pt && !terminal) { const s = this.scale; const tr = this.translate; - const orig = edge.origin; - const geo = (edge.cell).getGeometry(); + const orig = edge.origin; + const geo = edge.cell.getGeometry(); pt = geo.getTerminalPoint(source); - if (pt != null) { + if (pt) { pt = new Point(s * (tr.x + pt.x + orig.x), s * (tr.y + pt.y + orig.y)); } } @@ -1199,18 +1194,18 @@ class GraphView extends EventSource { * * @param edge {@link mxCellState} whose bounds should be updated. */ - updateBoundsFromStencil(state: CellState): Rectangle { + updateBoundsFromStencil(state: CellState | null) { let previous = null; if ( - state != null && - state.shape != null && - state.shape.stencil != null && + state && + state.shape && + state.shape.stencil && state.shape.stencil.aspect === 'fixed' ) { previous = Rectangle.fromRectangle(state); const asp = state.shape.stencil.computeAspect( - state.style, + null, // this argument is not used state.x, state.y, state.width, @@ -1238,46 +1233,44 @@ class GraphView extends EventSource { updatePoints( edge: CellState, points: Point[], - source: CellState, - target: CellState - ): void { - if (edge != null) { - const pts = []; - pts.push((edge.absolutePoints)[0]); - const edgeStyle = this.getEdgeStyle(edge, points, source, target); + source: CellState | null, + target: CellState | null + ) { + const pts = []; + pts.push((edge.absolutePoints)[0]); + const edgeStyle = this.getEdgeStyle(edge, points, source, target); - if (edgeStyle != null) { - const src = this.getTerminalPort(edge, source, true); - const trg = this.getTerminalPort(edge, target, false); + if (edgeStyle && source && target) { + const src = this.getTerminalPort(edge, source, true); + const trg = this.getTerminalPort(edge, target, false); - // Uses the stencil bounds for routing and restores after routing - const srcBounds = this.updateBoundsFromStencil(src); - const trgBounds = this.updateBoundsFromStencil(trg); + // Uses the stencil bounds for routing and restores after routing + const srcBounds = this.updateBoundsFromStencil(src); + const trgBounds = this.updateBoundsFromStencil(trg); - edgeStyle(edge, src, trg, points, pts); + edgeStyle(edge, src, trg, points, pts); - // Restores previous bounds - if (srcBounds != null) { - src.setRect(srcBounds.x, srcBounds.y, srcBounds.width, srcBounds.height); - } - - if (trgBounds != null) { - trg.setRect(trgBounds.x, trgBounds.y, trgBounds.width, trgBounds.height); - } - } else if (points != null) { - for (let i = 0; i < points.length; i += 1) { - if (points[i] != null) { - const pt = clone(points[i]); - pts.push(this.transformControlPoint(edge, pt)); - } - } + // Restores previous bounds + if (srcBounds) { + src.setRect(srcBounds.x, srcBounds.y, srcBounds.width, srcBounds.height); } - const tmp = edge.absolutePoints; - pts.push(tmp[tmp.length - 1]); - - edge.absolutePoints = pts; + if (trgBounds) { + trg.setRect(trgBounds.x, trgBounds.y, trgBounds.width, trgBounds.height); + } + } else if (points) { + for (let i = 0; i < points.length; i += 1) { + if (points[i]) { + const pt = clone(points[i]); + pts.push(this.transformControlPoint(edge, pt)); + } + } } + + const tmp = edge.absolutePoints; + pts.push(tmp[tmp.length - 1]); + + edge.absolutePoints = pts; } /** @@ -1288,7 +1281,7 @@ class GraphView extends EventSource { pt: Point, ignoreScale: boolean = false ): Point | null { - if (state != null && pt != null) { + if (state && pt) { const orig = state.origin; const scale = ignoreScale ? 1 : this.scale; @@ -1311,8 +1304,8 @@ class GraphView extends EventSource { source: CellState | null = null, target: CellState | null = null ): boolean { - const sc = (this.graph).getConnectionConstraint(edge, source, true); - const tc = (this.graph).getConnectionConstraint(edge, target, false); + const sc = this.graph.getConnectionConstraint(edge, source, true); + const tc = this.graph.getConnectionConstraint(edge, target, false); if ( (points == null || points.length < 2) && @@ -1332,17 +1325,17 @@ class GraphView extends EventSource { points: Point[] = [], source: CellState | null = null, target: CellState | null = null - ): any { - let edgeStyle: any = this.isLoopStyleEnabled(edge, points, source, target) - ? getValue(edge.style, 'loop', (this.graph).defaultLoopStyle) - : !getValue(edge.style, 'noEdgeStyle', false) + ) { + let edgeStyle = this.isLoopStyleEnabled(edge, points, source, target) + ? edge.style.loop ?? this.graph.defaultLoopStyle + : !edge.style.noEdgeStyle ?? false ? edge.style.edge : null; // Converts string values to objects if (typeof edgeStyle === 'string') { - let tmp = mxStyleRegistry.getValue(edgeStyle); - if (tmp == null && this.isAllowEval()) { + let tmp = StyleRegistry.getValue(edgeStyle); + if (!tmp && this.isAllowEval()) { tmp = eval(edgeStyle); } edgeStyle = tmp; @@ -1351,6 +1344,7 @@ class GraphView extends EventSource { if (typeof edgeStyle === 'function') { return edgeStyle; } + return null; } @@ -1364,18 +1358,18 @@ class GraphView extends EventSource { */ updateFloatingTerminalPoints( state: CellState, - source: CellState, - target: CellState + source: CellState | null, + target: CellState | null ): void { - const pts = state.absolutePoints; + const pts = state.absolutePoints; const p0 = pts[0]; const pe = pts[pts.length - 1]; - if (pe == null && target != null) { + if (!pe && target) { this.updateFloatingTerminalPoint(state, target, source, false); } - if (p0 == null && source != null) { + if (!p0 && source) { this.updateFloatingTerminalPoint(state, source, target, true); } } @@ -1392,11 +1386,11 @@ class GraphView extends EventSource { updateFloatingTerminalPoint( edge: CellState, start: CellState, - end: CellState, + end: CellState | null, source: boolean ): void { edge.setAbsoluteTerminalPoint( - this.getFloatingTerminalPoint(edge, start, end, source), + this.getFloatingTerminalPoint(edge, start, end, source), source ); } @@ -1413,14 +1407,14 @@ class GraphView extends EventSource { getFloatingTerminalPoint( edge: CellState, start: CellState, - end: CellState, + end: CellState | null, source: boolean - ): Point | null { - start = this.getTerminalPort(edge, start, source); + ) { + start = this.getTerminalPort(edge, start, source); let next = this.getNextPoint(edge, end, source); - const orth = (this.graph).isOrthogonal(edge); - const alpha = toRadians(Number(start.style.rotation || '0')); + const orth = this.graph.isOrthogonal(edge); + const alpha = toRadians(start.style.rotation); const center = new Point(start.getCenterX(), start.getCenterY()); if (alpha !== 0) { @@ -1429,10 +1423,9 @@ class GraphView extends EventSource { next = getRotatedPoint(next, cos, sin, center); } - let border = parseFloat(edge.style.perimeterSpacing || 0); - border += parseFloat( - edge.style[source ? 'sourcePerimeterSpacing' : 'targetPerimeterSpacing'] || 0 - ); + let border = edge.style.perimeterSpacing; + border += + edge.style[source ? 'sourcePerimeterSpacing' : 'targetPerimeterSpacing'] || 0; let pt = this.getPerimeterPoint(start, next, alpha === 0 && orth, border); if (alpha !== 0) { @@ -1452,22 +1445,23 @@ class GraphView extends EventSource { * @param terminal {@link mxCellState} that represents the terminal. * @param source Boolean indicating if the given terminal is the source terminal. */ - getTerminalPort( - state: CellState, - terminal: CellState, - source: boolean = false - ): CellState | null { + getTerminalPort(state: CellState, terminal: CellState, source: boolean = false) { const key = source ? 'sourcePort' : 'targetPort'; - const id = getValue(state.style, key); + const id = state.style[key]; - if (id != null) { - const tmp = this.getState((this.graph).getModel().getCell(id), false); + if (id) { + const cell = this.graph.getModel().getCell(id); - // Only uses ports where a cell state exists - if (tmp != null) { - terminal = tmp; + if (cell) { + const tmp = this.getState(cell, false); + + // Only uses ports where a cell state exists + if (tmp) { + terminal = tmp; + } } } + return terminal; } @@ -1487,7 +1481,7 @@ class GraphView extends EventSource { terminal: CellState, next: Point, orthogonal: boolean, - border: number = 0 + border = 0 ): Point { let point = null; @@ -1498,7 +1492,7 @@ class GraphView extends EventSource { const bounds = this.getPerimeterBounds(terminal, border); if (bounds.width > 0 || bounds.height > 0) { - point = new point(next.x, next.y); + point = new Point(next.x, next.y); let flipH = false; let flipV = false; @@ -1546,7 +1540,7 @@ class GraphView extends EventSource { * Returns the x-coordinate of the center point for automatic routing. */ getRoutingCenterX(state: CellState): number { - const f = state.style != null ? parseFloat(state.style.routingCenterX) || 0 : 0; + const f = state.style ? state.style.routingCenterX : 0; return state.getCenterX() + f * state.width; } @@ -1554,7 +1548,7 @@ class GraphView extends EventSource { * Returns the y-coordinate of the center point for automatic routing. */ getRoutingCenterY(state: CellState): number { - const f = state.style != null ? parseFloat(state.style.routingCenterY) || 0 : 0; + const f = state.style ? state.style.routingCenterY : 0; return state.getCenterY() + f * state.height; } @@ -1602,8 +1596,8 @@ class GraphView extends EventSource { terminal: CellState | null = null, border: number = 0 ): Rectangle | null { - if (terminal != null) { - border += parseFloat(terminal.style.perimeterSpacing || 0); + if (terminal) { + border += terminal.style.perimeterSpacing; } return (terminal).getPerimeterBounds(border * this.scale); } @@ -1611,12 +1605,12 @@ class GraphView extends EventSource { /** * Returns the perimeter function for the given state. */ - getPerimeterFunction(state: CellState): Function | null { + getPerimeterFunction(state: CellState) { let perimeter = state.style.perimeter; // Converts string values to objects if (typeof perimeter === 'string') { - let tmp = mxStyleRegistry.getValue(perimeter); + let tmp = StyleRegistry.getValue(perimeter); if (tmp == null && this.isAllowEval()) { tmp = eval(perimeter); } @@ -1626,6 +1620,7 @@ class GraphView extends EventSource { if (typeof perimeter === 'function') { return perimeter; } + return null; } @@ -1638,24 +1633,20 @@ class GraphView extends EventSource { * @param source Boolean indicating if the next point for the source or target * should be returned. */ - getNextPoint( - edge: CellState, - opposite: CellState | null, - source: boolean = false - ): Point | null { + getNextPoint(edge: CellState, opposite: CellState | null, source = false) { const pts = edge.absolutePoints; let point = null; - if (pts != null && pts.length >= 2) { + if (pts.length >= 2) { const count = pts.length; point = pts[source ? Math.min(1, count - 1) : Math.max(0, count - 2)]; } - if (point == null && opposite != null) { - point = new point(opposite.getCenterX(), opposite.getCenterY()); + if (!point && opposite) { + point = new Point(opposite.getCenterX(), opposite.getCenterY()); } - return point; + return point as Point; // shouldn't return null, but really? } /** @@ -1667,12 +1658,12 @@ class GraphView extends EventSource { * @param source Boolean that specifies if the source or target terminal * should be returned. */ - getVisibleTerminal(edge: Cell, source: boolean): Cell | null { - const model = (this.graph).getModel(); + getVisibleTerminal(edge: Cell, source: boolean) { + const model = this.graph.getModel(); let result = edge.getTerminal(source); let best = result; - while (result != null && result != this.currentRoot) { + while (result && result !== this.currentRoot) { if ((best && !best.isVisible()) || result.isCollapsed()) { best = result; } @@ -1682,7 +1673,7 @@ class GraphView extends EventSource { // Checks if the result is valid for the current view state if ( - best != null && + best && (!model.contains(best) || best.getParent() === model.getRoot() || best === this.currentRoot) @@ -1701,12 +1692,12 @@ class GraphView extends EventSource { * * @param state {@link mxCellState} whose bounds should be updated. */ - updateEdgeBounds(state: CellState): void { - const points = state.absolutePoints; + updateEdgeBounds(state: CellState) { + const points = <(Point | null)[]>state.absolutePoints; const p0 = points[0]; const pe = points[points.length - 1]; - if (p0.x !== pe.x || p0.y !== pe.y) { + if (p0 && pe && (p0.x !== pe.x || p0.y !== pe.y)) { const dx = pe.x - p0.x; const dy = pe.y - p0.y; state.terminalDistance = Math.sqrt(dx * dx + dy * dy); @@ -1718,7 +1709,7 @@ class GraphView extends EventSource { const segments = []; let pt = p0; - if (pt != null) { + if (pt) { let minX = pt.x; let minY = pt.y; let maxX = minX; @@ -1727,7 +1718,7 @@ class GraphView extends EventSource { for (let i = 1; i < points.length; i += 1) { const tmp = points[i]; - if (tmp != null) { + if (tmp) { const dx = pt.x - tmp.x; const dy = pt.y - tmp.y; @@ -1828,22 +1819,21 @@ class GraphView extends EventSource { * @param x Specifies the x-coordinate of the absolute label location. * @param y Specifies the y-coordinate of the absolute label location. */ - getRelativePoint(edgeState: CellState, x: number, y: number): Point { - const model = (this.graph).getModel(); + getRelativePoint(edgeState: CellState, x: number, y: number) { const geometry = edgeState.cell.getGeometry(); - if (geometry != null) { - const absolutePoints = edgeState.absolutePoints; + if (geometry) { + const absolutePoints = edgeState.absolutePoints; const pointCount = absolutePoints.length; if (geometry.relative && pointCount > 1) { const totalLength = edgeState.length; let { segments } = edgeState; - segments = segments; // Works out which line segment the point of the label is closest to - let p0 = absolutePoints[0]; - let pe = absolutePoints[1]; + let p0 = absolutePoints[0] as Point; + let pe = absolutePoints[1] as Point; + let minDist = ptSegDistSq(p0.x, p0.y, pe.x, pe.y, x, y); let length = 0; let index = 0; @@ -1851,7 +1841,7 @@ class GraphView extends EventSource { for (let i = 2; i < pointCount; i += 1) { p0 = pe; - pe = absolutePoints[i]; + pe = absolutePoints[i] as Point; const dist = ptSegDistSq(p0.x, p0.y, pe.x, pe.y, x, y); tmp += segments[i - 2]; @@ -1863,8 +1853,8 @@ class GraphView extends EventSource { } const seg = segments[index]; - p0 = absolutePoints[index]; - pe = absolutePoints[index + 1]; + p0 = absolutePoints[index] as Point; + pe = absolutePoints[index + 1] as Point; const x2 = p0.x; const y2 = p0.y; @@ -1912,6 +1902,7 @@ class GraphView extends EventSource { ); } } + return new Point(); } @@ -1925,43 +1916,42 @@ class GraphView extends EventSource { * * @param state {@link mxCellState} whose absolute offset should be updated. */ - updateEdgeLabelOffset(state: CellState): void { + updateEdgeLabelOffset(state: CellState) { const points = state.absolutePoints; - const absoluteOffset = state.absoluteOffset; + const absoluteOffset = state.absoluteOffset; absoluteOffset.x = state.getCenterX(); absoluteOffset.y = state.getCenterY(); - if (points != null && points.length > 0 && state.segments != null) { - const geometry = (state.cell).getGeometry(); + if (points.length > 0 && state.segments) { + const geometry = state.cell.getGeometry(); - if (geometry.relative) { - const offset = this.getPoint(state, geometry); - - if (offset != null) { + if (geometry) { + if (geometry.relative) { + const offset = this.getPoint(state, geometry); state.absoluteOffset = offset; - } - } else { - const p0 = points[0]; - const pe = points[points.length - 1]; + } else { + const p0 = points[0]; + const pe = points[points.length - 1]; - if (p0 != null && pe != null) { - const dx = pe.x - p0.x; - const dy = pe.y - p0.y; - let x0 = 0; - let y0 = 0; + if (p0 && pe) { + const dx = pe.x - p0.x; + const dy = pe.y - p0.y; + let x0 = 0; + let y0 = 0; - const off = geometry.offset; + const off = geometry.offset; - if (off != null) { - x0 = off.x; - y0 = off.y; + if (off) { + x0 = off.x; + y0 = off.y; + } + + const x = p0.x + dx / 2 + x0 * this.scale; + const y = p0.y + dy / 2 + y0 * this.scale; + + absoluteOffset.x = x; + absoluteOffset.y = y; } - - const x = p0.x + dx / 2 + x0 * this.scale; - const y = p0.y + dy / 2 + y0 * this.scale; - - absoluteOffset.x = x; - absoluteOffset.y = y; } } } @@ -1975,21 +1965,18 @@ class GraphView extends EventSource { * @param create Optional boolean indicating if a new state should be created * if it does not yet exist. Default is false. */ - getState(cell: Cell | null = null, create: boolean = false): CellState { - let state: CellState | null = null; + getState(cell: Cell, create = false) { + let state: CellState | null = this.states.get(cell); - if (cell != null) { - state = this.states.get(cell); - - if (create && (state == null || this.updateStyle) && cell.isVisible()) { - if (state == null) { - state = this.createState(cell); - this.states.put(cell, state); - } else { - state.style = (this.graph).getCellStyle(cell); - } + if (create && (!state || this.updateStyle) && cell.isVisible()) { + if (!state) { + state = this.createState(cell); + this.states.put(cell, state); + } else { + state.style = this.graph.getCellStyle(cell); } } + return state; } @@ -1999,18 +1986,20 @@ class GraphView extends EventSource { * have less elements than the given array. If no argument is given, then * this returns {@link states}. */ - getCellStates(cells: CellArray | null): CellState[] | Dictionary | null { - if (cells == null) { - return this.states; + getCellStates(cells: CellArray | null = null) { + if (!cells) { + return this.states.getValues(); } const result: CellState[] = []; + for (const cell of cells) { const state = this.getState(cell); - if (state != null) { + if (state) { result.push(state); } } + return result; } @@ -2019,18 +2008,15 @@ class GraphView extends EventSource { * * @param cell {@link mxCell} for which the {@link CellState} should be removed. */ - removeState(cell: Cell): CellState | null { - let state: CellState | null = null; + removeState(cell: Cell) { + let state: CellState | null = this.states.remove(cell); - if (cell != null) { - state = this.states.remove(cell); - - if (state != null) { - (this.graph).cellRenderer.destroy(state); - state.invalid = true; - state.destroy(); - } + if (state) { + this.graph.cellRenderer.destroy(state); + state.invalid = true; + state.destroy(); } + return state; } @@ -2040,23 +2026,21 @@ class GraphView extends EventSource { * * @param cell {@link mxCell} for which a new {@link CellState} should be created. */ - createState(cell: Cell): CellState { - return new CellState(this, cell, (this.graph).getCellStyle(cell)); + createState(cell: Cell) { + return new CellState(this, cell, this.graph.getCellStyle(cell)); } /** * Returns true if the event origin is one of the drawing panes or * containers of the view. */ - isContainerEvent(evt: Event | MouseEvent): boolean { + isContainerEvent(evt: Event | MouseEvent) { const source = getSource(evt); return ( - source === (this.graph).container || + source === this.graph.container || source.parentNode === this.backgroundPane || - (source.parentNode != null && - source.parentNode.parentNode === this.backgroundPane) || - // @ts-ignore + (source.parentNode && source.parentNode.parentNode === this.backgroundPane) || source === this.canvas.parentNode || source === this.canvas || source === this.backgroundPane || @@ -2070,11 +2054,11 @@ class GraphView extends EventSource { * Returns true if the event origin is one of the scrollbars of the * container in IE. Such events are ignored. */ - isScrollEvent(evt: MouseEvent): boolean { - const graph = this.graph; + isScrollEvent(evt: MouseEvent) { + const graph = this.graph; const offset = getOffset(graph.container); const pt = new Point(evt.clientX - offset.x, evt.clientY - offset.y); - const container = graph.container; + const container = graph.container; const outWidth = container.offsetWidth; const inWidth = container.clientWidth; @@ -2093,168 +2077,169 @@ class GraphView extends EventSource { * Initializes the graph event dispatch loop for the specified container * and invokes {@link create} to create the required DOM nodes for the display. */ - init(): void { + init() { this.installListeners(); - - // Creates the DOM nodes for the respective display dialect - const { graph } = this; this.createSvg(); } /** * Installs the required listeners in the container. */ - installListeners(): void { - const graph = this.graph; + installListeners() { + const graph = this.graph; const { container } = graph; - if (container != null) { - // Support for touch device gestures (eg. pinch to zoom) - // Double-tap handling is implemented in mxGraph.fireMouseEvent - if (mxClient.IS_TOUCH) { - InternalEvent.addListener(container, 'gesturestart', ((evt: MouseEvent) => { - graph.fireGestureEvent(evt); - InternalEvent.consume(evt); - }) as EventListener); - - InternalEvent.addListener(container, 'gesturechange', ((evt: MouseEvent) => { - graph.fireGestureEvent(evt); - InternalEvent.consume(evt); - }) as EventListener); - - InternalEvent.addListener(container, 'gestureend', ((evt: MouseEvent) => { - graph.fireGestureEvent(evt); - InternalEvent.consume(evt); - }) as EventListener); - } - - // Fires event only for one pointer per gesture - let pointerId: number | null = null; - - // Adds basic listeners for graph event dispatching - InternalEvent.addGestureListeners( - container, - ((evt: MouseEvent) => { - // Condition to avoid scrollbar events starting a rubberband selection - if ( - this.isContainerEvent(evt) && - ((!mxClient.IS_GC && !mxClient.IS_SF) || !this.isScrollEvent(evt)) - ) { - graph.fireMouseEvent(InternalEvent.MOUSE_DOWN, new InternalMouseEvent(evt)); - // @ts-ignore - pointerId = evt.pointerId; - } - }) as EventListener, - (evt: Event) => { - if ( - this.isContainerEvent(evt) && - // @ts-ignore - (pointerId == null || evt.pointerId === pointerId) - ) { - graph.fireMouseEvent(InternalEvent.MOUSE_MOVE, new InternalMouseEvent(evt)); - } - }, - (evt: Event) => { - if (this.isContainerEvent(evt)) { - graph.fireMouseEvent(InternalEvent.MOUSE_UP, new InternalMouseEvent(evt)); - } - - pointerId = null; - } - ); - - // Adds listener for double click handling on background, this does always - // use native event handler, we assume that the DOM of the background - // does not change during the double click - InternalEvent.addListener(container, 'dblclick', ((evt: MouseEvent) => { - if (this.isContainerEvent(evt)) { - graph.dblClick(evt); - } + // Support for touch device gestures (eg. pinch to zoom) + // Double-tap handling is implemented in mxGraph.fireMouseEvent + if (mxClient.IS_TOUCH) { + InternalEvent.addListener(container, 'gesturestart', ((evt: MouseEvent) => { + graph.fireGestureEvent(evt); + InternalEvent.consume(evt); }) as EventListener); + InternalEvent.addListener(container, 'gesturechange', ((evt: MouseEvent) => { + graph.fireGestureEvent(evt); + InternalEvent.consume(evt); + }) as EventListener); + + InternalEvent.addListener(container, 'gestureend', ((evt: MouseEvent) => { + graph.fireGestureEvent(evt); + InternalEvent.consume(evt); + }) as EventListener); + } + + // Fires event only for one pointer per gesture + let pointerId: number | null = null; + + // Adds basic listeners for graph event dispatching + InternalEvent.addGestureListeners( + container, + ((evt: MouseEvent) => { + // Condition to avoid scrollbar events starting a rubberband selection + if ( + this.isContainerEvent(evt) && + ((!mxClient.IS_GC && !mxClient.IS_SF) || !this.isScrollEvent(evt)) + ) { + graph.fireMouseEvent(InternalEvent.MOUSE_DOWN, new InternalMouseEvent(evt)); + // @ts-ignore + pointerId = evt.pointerId; + } + }) as EventListener, + (evt: Event) => { + if ( + this.isContainerEvent(evt) && + // @ts-ignore + (pointerId === null || evt.pointerId === pointerId) + ) { + graph.fireMouseEvent( + InternalEvent.MOUSE_MOVE, + new InternalMouseEvent(evt as MouseEvent) + ); + } + }, + (evt: Event) => { + if (this.isContainerEvent(evt)) { + graph.fireMouseEvent( + InternalEvent.MOUSE_UP, + new InternalMouseEvent(evt as MouseEvent) + ); + } + + pointerId = null; + } + ); + + // Adds listener for double click handling on background, this does always + // use native event handler, we assume that the DOM of the background + // does not change during the double click + InternalEvent.addListener(container, 'dblclick', ((evt: MouseEvent) => { + if (this.isContainerEvent(evt)) { + graph.dblClick(evt); + } + }) as EventListener); + + // Workaround for touch events which started on some DOM node + // on top of the container, in which case the cells under the + // mouse for the move and up events are not detected. + const getState = (evt: Event) => { + let state = null; + // Workaround for touch events which started on some DOM node // on top of the container, in which case the cells under the // mouse for the move and up events are not detected. - const getState = (evt: Event) => { - let state = null; + if (mxClient.IS_TOUCH) { + const x = getClientX(evt); + const y = getClientY(evt); - // Workaround for touch events which started on some DOM node - // on top of the container, in which case the cells under the - // mouse for the move and up events are not detected. - if (mxClient.IS_TOUCH) { - const x = getClientX(evt); - const y = getClientY(evt); + // Dispatches the drop event to the graph which + // consumes and executes the source function + const pt = convertPoint(container, x, y); + const cell = graph.getCellAt(pt.x, pt.y); - // Dispatches the drop event to the graph which - // consumes and executes the source function - const pt = convertPoint(container, x, y); - state = (graph.view).getState(graph.getCellAt(pt.x, pt.y)); - } + if (cell) state = graph.view.getState(cell); + } - return state; - }; + return state; + }; - // Adds basic listeners for graph event dispatching outside of the - // container and finishing the handling of a single gesture - // Implemented via graph event dispatch loop to avoid duplicate events - // in Firefox and Chrome - graph.addMouseListener({ - mouseDown: (sender: any, me: InternalMouseEvent) => { - (graph.popupMenuHandler).hideMenu(); - }, - mouseMove: () => {}, - mouseUp: () => {}, - }); + // Adds basic listeners for graph event dispatching outside of the + // container and finishing the handling of a single gesture + // Implemented via graph event dispatch loop to avoid duplicate events + // in Firefox and Chrome + graph.addMouseListener({ + mouseDown: (sender: any, me: InternalMouseEvent) => { + (graph.popupMenuHandler).hideMenu(); + }, + mouseMove: () => {}, + mouseUp: () => {}, + }); - this.moveHandler = (evt: Event) => { - // Hides the tooltip if mouse is outside container - if (graph.tooltipHandler != null && graph.tooltipHandler.isHideOnHover()) { - graph.tooltipHandler.hide(); - } + this.moveHandler = (evt: Event) => { + // Hides the tooltip if mouse is outside container + if (graph.tooltipHandler != null && graph.tooltipHandler.isHideOnHover()) { + graph.tooltipHandler.hide(); + } - if ( - this.captureDocumentGesture && - graph.isMouseDown && - graph.container != null && - !this.isContainerEvent(evt) && - graph.container.style.display !== 'none' && - graph.container.style.visibility !== 'hidden' && - !isConsumed(evt) - ) { - graph.fireMouseEvent( - InternalEvent.MOUSE_MOVE, - new InternalMouseEvent(evt, getState(evt)) - ); - } - }; + if ( + this.captureDocumentGesture && + graph.isMouseDown && + graph.container != null && + !this.isContainerEvent(evt) && + graph.container.style.display !== 'none' && + graph.container.style.visibility !== 'hidden' && + !isConsumed(evt) + ) { + graph.fireMouseEvent( + InternalEvent.MOUSE_MOVE, + new InternalMouseEvent(evt as MouseEvent, getState(evt)) + ); + } + }; - this.endHandler = (evt: Event) => { - if ( - this.captureDocumentGesture && - graph.isMouseDown && - graph.container != null && - !this.isContainerEvent(evt) && - graph.container.style.display !== 'none' && - graph.container.style.visibility !== 'hidden' - ) { - graph.fireMouseEvent(InternalEvent.MOUSE_UP, new InternalMouseEvent(evt)); - } - }; + this.endHandler = (evt: Event) => { + if ( + this.captureDocumentGesture && + graph.isMouseDown && + graph.container != null && + !this.isContainerEvent(evt) && + graph.container.style.display !== 'none' && + graph.container.style.visibility !== 'hidden' + ) { + graph.fireMouseEvent( + InternalEvent.MOUSE_UP, + new InternalMouseEvent(evt as MouseEvent) + ); + } + }; - InternalEvent.addGestureListeners( - document, - null, - this.moveHandler, - this.endHandler - ); - } + InternalEvent.addGestureListeners(document, null, this.moveHandler, this.endHandler); } /** * Creates and returns the DOM nodes for the SVG display. */ createSvg(): void { - const { container } = this.graph; + const { container } = this.graph; const canvas = (this.canvas = document.createElementNS( 'http://www.w3.org/2000/svg', 'g' @@ -2311,7 +2296,7 @@ class GraphView extends EventSource { /** * Destroys the view and all its resources. */ - destroy(): void { + destroy() { let root: SVGElement | null = this.canvas != null ? this.canvas.ownerSVGElement : null; @@ -2332,11 +2317,6 @@ class GraphView extends EventSource { this.moveHandler = null; this.endHandler = null; - this.canvas = null; - this.backgroundPane = null; - this.drawPane = null; - this.overlayPane = null; - this.decoratorPane = null; } } @@ -2345,4 +2325,3 @@ class GraphView extends EventSource { } export default GraphView; -// import('../../serialization/mxGraphViewCodec');