From 35df710817a0d149047c6e12d6ee34d48a3c3e41 Mon Sep 17 00:00:00 2001 From: bubkoo Date: Mon, 9 Dec 2019 23:35:52 +0800 Subject: [PATCH] refactor(graph): split huge graph class to small modules finish #12 #12 --- .../x6-example-drawio/src/pages/editor.ts | 2 +- .../src/pages/graph/commands/util.ts | 2 +- .../src/pages/graph/graph.ts | 49 +- packages/x6/src/addon/clipboard/index.ts | 2 +- packages/x6/src/addon/dndsource/index.ts | 6 +- packages/x6/src/change/selection-change.ts | 6 +- packages/x6/src/common/dom-event/mouse.ts | 2 +- packages/x6/src/core/renderer.ts | 34 +- packages/x6/src/core/view.ts | 17 +- .../src/graph/{prop/base.ts => base-graph.ts} | 352 +- .../base.ts => graph/base-manager.ts} | 13 +- .../src/{manager => graph}/change-manager.ts | 4 +- .../{prop/folding.ts => collapse-accessor.ts} | 82 +- packages/x6/src/graph/collapse-manager.ts | 126 + packages/x6/src/graph/common-accessor.ts | 350 ++ packages/x6/src/graph/connection-accessor.ts | 115 + packages/x6/src/graph/connection-manager.ts | 286 ++ packages/x6/src/graph/creation-accessor.ts | 253 ++ packages/x6/src/graph/creation-manager.ts | 590 ++++ packages/x6/src/graph/decorator.ts | 10 +- packages/x6/src/graph/editing-accessor.ts | 64 + packages/x6/src/graph/editing-manager.ts | 24 + packages/x6/src/graph/eventloop-accessor.ts | 68 + .../eventloop-manager.ts} | 27 +- packages/x6/src/graph/events.ts | 46 + packages/x6/src/graph/graph.ts | 2542 +-------------- .../graph/{prop/grid.ts => grid-accessor.ts} | 15 +- packages/x6/src/graph/group-accessor.ts | 110 + packages/x6/src/graph/group-manager.ts | 290 ++ .../{prop/guide.ts => guide-accessor.ts} | 4 +- packages/x6/src/graph/hook.ts | 4 +- .../keyboard.ts => keyboard-accessor.ts} | 6 +- packages/x6/src/graph/moving-accessor.ts | 124 + packages/x6/src/graph/moving-manager.ts | 336 ++ packages/x6/src/graph/overlay-accessor.ts | 69 + packages/x6/src/graph/overlay-manager.ts | 88 + .../pagebreak.ts => pagebreak-accessor.ts} | 63 +- packages/x6/src/graph/pagebreak-manager.ts | 105 + packages/x6/src/graph/panning-accessor.ts | 148 + .../src/{manager => graph}/panning-manager.ts | 4 +- packages/x6/src/graph/prop/connection.ts | 26 - packages/x6/src/graph/prop/index.ts | 36 - packages/x6/src/graph/prop/panning.ts | 23 - packages/x6/src/graph/retrieval-accessor.ts | 315 ++ packages/x6/src/graph/retrieval-manager.ts | 442 +++ .../rubberband.ts => rubberband-accessor.ts} | 4 +- packages/x6/src/graph/selection-accessor.ts | 152 + .../{manager => graph}/selection-manager.ts | 4 +- .../x6/src/{manager => graph}/selection.ts | 4 +- packages/x6/src/graph/size-accessor.ts | 98 + packages/x6/src/graph/size-manager.ts | 526 +++ packages/x6/src/graph/style-accessor.ts | 163 + packages/x6/src/graph/style-manager.ts | 162 + .../{prop/tooltip.ts => tooltip-accessor.ts} | 11 +- packages/x6/src/graph/validation-accessor.ts | 50 + .../{manager => graph}/validation-manager.ts | 17 +- packages/x6/src/graph/viewport-accessor.ts | 66 + packages/x6/src/graph/viewport-manager.ts | 321 ++ packages/x6/src/graph/zoom-accessor.ts | 146 + packages/x6/src/graph/zoom-manager.ts | 519 +++ packages/x6/src/handler/connection/handler.ts | 6 +- packages/x6/src/handler/connection/marker.ts | 2 +- packages/x6/src/handler/connection/preview.ts | 2 +- packages/x6/src/handler/edge/handler.ts | 4 +- packages/x6/src/handler/edge/marker.ts | 2 +- packages/x6/src/handler/handler-mouse.ts | 2 +- packages/x6/src/handler/keyboard/handler.ts | 2 +- packages/x6/src/handler/mousewheel/handler.ts | 116 + packages/x6/src/handler/mousewheel/option.ts | 6 + packages/x6/src/handler/moving/preview.ts | 2 +- packages/x6/src/handler/node/preview.ts | 2 +- packages/x6/src/handler/tooltip/handler.ts | 50 +- packages/x6/src/manager/cell-manager.ts | 2868 ----------------- packages/x6/src/manager/index.ts | 9 - packages/x6/src/manager/overlay-manager.ts | 8 - packages/x6/src/manager/viewport-manager.ts | 1061 ------ packages/x6/src/option/preset.ts | 1 + packages/x6/src/option/rollup.ts | 8 +- packages/x6/src/util/function.ts | 2 +- yarn.lock | 735 ++--- 80 files changed, 7256 insertions(+), 7155 deletions(-) rename packages/x6/src/graph/{prop/base.ts => base-graph.ts} (87%) rename packages/x6/src/{manager/base.ts => graph/base-manager.ts} (60%) rename packages/x6/src/{manager => graph}/change-manager.ts (97%) rename packages/x6/src/graph/{prop/folding.ts => collapse-accessor.ts} (60%) create mode 100644 packages/x6/src/graph/collapse-manager.ts create mode 100644 packages/x6/src/graph/common-accessor.ts create mode 100644 packages/x6/src/graph/connection-accessor.ts create mode 100644 packages/x6/src/graph/connection-manager.ts create mode 100644 packages/x6/src/graph/creation-accessor.ts create mode 100644 packages/x6/src/graph/creation-manager.ts create mode 100644 packages/x6/src/graph/editing-accessor.ts create mode 100644 packages/x6/src/graph/editing-manager.ts create mode 100644 packages/x6/src/graph/eventloop-accessor.ts rename packages/x6/src/{manager/eventloop.ts => graph/eventloop-manager.ts} (96%) create mode 100644 packages/x6/src/graph/events.ts rename packages/x6/src/graph/{prop/grid.ts => grid-accessor.ts} (93%) create mode 100644 packages/x6/src/graph/group-accessor.ts create mode 100644 packages/x6/src/graph/group-manager.ts rename packages/x6/src/graph/{prop/guide.ts => guide-accessor.ts} (85%) rename packages/x6/src/graph/{prop/keyboard.ts => keyboard-accessor.ts} (87%) create mode 100644 packages/x6/src/graph/moving-accessor.ts create mode 100644 packages/x6/src/graph/moving-manager.ts create mode 100644 packages/x6/src/graph/overlay-accessor.ts create mode 100644 packages/x6/src/graph/overlay-manager.ts rename packages/x6/src/graph/{prop/pagebreak.ts => pagebreak-accessor.ts} (90%) create mode 100644 packages/x6/src/graph/pagebreak-manager.ts create mode 100644 packages/x6/src/graph/panning-accessor.ts rename packages/x6/src/{manager => graph}/panning-manager.ts (98%) delete mode 100644 packages/x6/src/graph/prop/connection.ts delete mode 100644 packages/x6/src/graph/prop/index.ts delete mode 100644 packages/x6/src/graph/prop/panning.ts create mode 100644 packages/x6/src/graph/retrieval-accessor.ts create mode 100644 packages/x6/src/graph/retrieval-manager.ts rename packages/x6/src/graph/{prop/rubberband.ts => rubberband-accessor.ts} (85%) create mode 100644 packages/x6/src/graph/selection-accessor.ts rename packages/x6/src/{manager => graph}/selection-manager.ts (98%) rename packages/x6/src/{manager => graph}/selection.ts (96%) create mode 100644 packages/x6/src/graph/size-accessor.ts create mode 100644 packages/x6/src/graph/size-manager.ts create mode 100644 packages/x6/src/graph/style-accessor.ts create mode 100644 packages/x6/src/graph/style-manager.ts rename packages/x6/src/graph/{prop/tooltip.ts => tooltip-accessor.ts} (58%) create mode 100644 packages/x6/src/graph/validation-accessor.ts rename packages/x6/src/{manager => graph}/validation-manager.ts (94%) create mode 100644 packages/x6/src/graph/viewport-accessor.ts create mode 100644 packages/x6/src/graph/viewport-manager.ts create mode 100644 packages/x6/src/graph/zoom-accessor.ts create mode 100644 packages/x6/src/graph/zoom-manager.ts create mode 100644 packages/x6/src/handler/mousewheel/handler.ts create mode 100644 packages/x6/src/handler/mousewheel/option.ts delete mode 100644 packages/x6/src/manager/cell-manager.ts delete mode 100644 packages/x6/src/manager/index.ts delete mode 100644 packages/x6/src/manager/overlay-manager.ts delete mode 100644 packages/x6/src/manager/viewport-manager.ts diff --git a/examples/x6-example-drawio/src/pages/editor.ts b/examples/x6-example-drawio/src/pages/editor.ts index 5196ea5f4de..1c7baf51570 100644 --- a/examples/x6-example-drawio/src/pages/editor.ts +++ b/examples/x6-example-drawio/src/pages/editor.ts @@ -41,7 +41,7 @@ export class Editor extends Primer { global: true, escape: true, }, - preferPageSize: true, + preferPageSize: false, rubberband: true, createView() { return new GraphView(this) diff --git a/examples/x6-example-drawio/src/pages/graph/commands/util.ts b/examples/x6-example-drawio/src/pages/graph/commands/util.ts index b5e0e43cd3b..8a5f71700c1 100644 --- a/examples/x6-example-drawio/src/pages/graph/commands/util.ts +++ b/examples/x6-example-drawio/src/pages/graph/commands/util.ts @@ -24,7 +24,7 @@ export function autosize(graph: Graph) { state.text.boundingBox!.height / graph.view.scale graph.model.setGeometry(cell, geo) } else { - graph.cellManager.updateCellSize(cell) + graph.sizeManager.updateCellSize(cell) } } }) diff --git a/examples/x6-example-drawio/src/pages/graph/graph.ts b/examples/x6-example-drawio/src/pages/graph/graph.ts index 63ea9e91e5b..afae7c27e44 100644 --- a/examples/x6-example-drawio/src/pages/graph/graph.ts +++ b/examples/x6-example-drawio/src/pages/graph/graph.ts @@ -13,12 +13,12 @@ export class EditorGraph extends Graph { view: GraphView autoTranslate: boolean cursorPosition: Point - lazyZoomDelay: number = 10 - updateZoomTimeout: number | null + wheelZoomDelay: number = 10 + wheelZoomTimer: number | null cumulativeZoomFactor: number = 1 initMouseWheel() { - DomEvent.addMouseWheelListener((e, up) => { + DomEvent.addWheelListener((e, up) => { if (this.isZoomWheelEvent(e)) { let source = DomEvent.getSource(e) while (source != null) { @@ -47,8 +47,8 @@ export class EditorGraph extends Graph { } lazyZoom(zoomIn: boolean) { - if (this.updateZoomTimeout != null) { - window.clearTimeout(this.updateZoomTimeout) + if (this.wheelZoomTimer != null) { + window.clearTimeout(this.wheelZoomTimer) } // Switches to 1% zoom steps below 15% @@ -84,7 +84,8 @@ export class EditorGraph extends Graph { Math.min(this.view.scale * this.cumulativeZoomFactor, 160) / this.view.scale, ) - this.updateZoomTimeout = window.setTimeout(() => { + + this.wheelZoomTimer = window.setTimeout(() => { var offset = util.getOffset(this.container) var dx = 0 var dy = 0 @@ -111,8 +112,8 @@ export class EditorGraph extends Graph { } this.cumulativeZoomFactor = 1 - this.updateZoomTimeout = null - }, this.lazyZoomDelay) + this.wheelZoomTimer = null + }, this.wheelZoomDelay) } sizeDidChange() { @@ -157,6 +158,22 @@ export class EditorGraph extends Graph { } } + getPageSize() { + return { + width: this.pageFormat.width * this.pageScale, + height: this.pageFormat.height * this.pageScale, + } + } + + getPagePadding() { + const scale = this.view.scale + const container = this.container + return [ + Math.max(0, Math.round((container.offsetWidth - 32) / scale)), + Math.max(0, Math.round((container.offsetHeight - 32) / scale)), + ] + } + getPreferredPageSize() { const size = this.getPageSize() const pages = this.getPageLayout() @@ -167,13 +184,6 @@ export class EditorGraph extends Graph { } } - getPageSize() { - return { - width: this.pageFormat.width * this.pageScale, - height: this.pageFormat.height * this.pageScale, - } - } - getPageLayout() { const size = this.getPageSize() const bounds = this.getGraphBounds() @@ -197,15 +207,6 @@ export class EditorGraph extends Graph { } } - getPagePadding() { - const scale = this.view.scale - const container = this.container - return [ - Math.max(0, Math.round((container.offsetWidth - 32) / scale)), - Math.max(0, Math.round((container.offsetHeight - 32) / scale)), - ] - } - updatePageBreaks(visible: boolean, width: number, height: number) { const s = this.view.scale const t = this.view.translate diff --git a/packages/x6/src/addon/clipboard/index.ts b/packages/x6/src/addon/clipboard/index.ts index bb40a805583..52754ed4563 100644 --- a/packages/x6/src/addon/clipboard/index.ts +++ b/packages/x6/src/addon/clipboard/index.ts @@ -97,7 +97,7 @@ export namespace Clipboard { const y = Math.round(graph.snap(triggerY / s - t.y)) const dx = x - p.x const dy = y - p.y - graph.cellManager.cellsMoved(cells, dx, dy, false, false) + graph.movingManager.cellsMoved(cells, dx, dy, false, false) } } }) diff --git a/packages/x6/src/addon/dndsource/index.ts b/packages/x6/src/addon/dndsource/index.ts index a46b8340888..2194bfdeb86 100644 --- a/packages/x6/src/addon/dndsource/index.ts +++ b/packages/x6/src/addon/dndsource/index.ts @@ -275,8 +275,8 @@ export class Dnd { } protected dragEnter(graph: Graph, e: MouseEvent) { - graph.eventloop.isMouseDown = true - graph.eventloop.isMouseTrigger = DomEvent.isMouseEvent(e) + graph.eventloopManager.isMouseDown = true + graph.eventloopManager.isMouseTrigger = DomEvent.isMouseEvent(e) this.previewElement = this.createPreviewElement(graph) @@ -302,7 +302,7 @@ export class Dnd { this.currentPoint = null this.currentDropTarget = null - graph.eventloop.isMouseDown = false + graph.eventloopManager.isMouseDown = false graph.off(Graph.events.fireMouseEvent, this.eventConsumer) this.removePreviewElement() diff --git a/packages/x6/src/change/selection-change.ts b/packages/x6/src/change/selection-change.ts index e101f510a29..1cca70a084e 100644 --- a/packages/x6/src/change/selection-change.ts +++ b/packages/x6/src/change/selection-change.ts @@ -1,7 +1,7 @@ -import { IChange } from './change' -import { Graph } from '../graph' import { Cell } from '../core/cell' -import { Selection } from '../manager' +import { Graph } from '../graph/graph' +import { Selection } from '../graph/selection' +import { IChange } from './change' export class SelectionChange implements IChange { public added: Cell[] | null diff --git a/packages/x6/src/common/dom-event/mouse.ts b/packages/x6/src/common/dom-event/mouse.ts index 3761f1a74e4..73e2c55ac86 100644 --- a/packages/x6/src/common/dom-event/mouse.ts +++ b/packages/x6/src/common/dom-event/mouse.ts @@ -162,7 +162,7 @@ export namespace MouseEventEx { onDblClick(e) } else if (!DomEvent.isConsumed(e)) { const state = getState(e) - graph.eventloop.dblClick(e, state ? state.cell : null) + graph.eventloopManager.dblClick(e, state ? state.cell : null) } }) } diff --git a/packages/x6/src/core/renderer.ts b/packages/x6/src/core/renderer.ts index c09c700533e..e46195ff3c9 100644 --- a/packages/x6/src/core/renderer.ts +++ b/packages/x6/src/core/renderer.ts @@ -181,9 +181,7 @@ export class Renderer { protected createIndicatorShape(state: State) { if (state != null && state.shape != null) { - state.shape.indicatorShape = Shape.getShape( - state.view.graph.cellManager.getIndicatorShape(state), - ) + state.shape.indicatorShape = Shape.getShape(state.style.indicatorShape) } } @@ -197,20 +195,14 @@ export class Renderer { protected configureShape(state: State) { if (state != null && state.shape != null) { - const graph = state.view.graph + const style = state.style state.shape.apply(state) - state.shape.image = graph.cellManager.getImage(state) - state.shape.indicatorImage = graph.cellManager.getIndicatorImage(state) - state.shape.indicatorColor = graph.cellManager.getIndicatorColor(state) - state.shape.indicatorDirection = graph.cellManager.getIndicatorDirection( - state, - ) - state.shape.indicatorStrokeColor = graph.cellManager.getIndicatorStrokeColor( - state, - ) - state.shape.indicatorGradientColor = graph.cellManager.getIndicatorGradientColor( - state, - ) + state.shape.image = style.image || null + state.shape.indicatorImage = style.indicatorImage || null + state.shape.indicatorColor = style.indicatorColor || null + state.shape.indicatorDirection = style.indicatorDirection || null + state.shape.indicatorStrokeColor = style.indicatorStrokeColor || null + state.shape.indicatorGradientColor = style.indicatorGradientColor || null this.postConfigureShape(state) } @@ -424,7 +416,7 @@ export class Renderer { if (graph.nativeDblClickEnabled) { DomEvent.addListener(elem, 'dblclick', (e: MouseEvent) => { if (this.isShapeEvent(state, e)) { - graph.eventloop.dblClick(e, state.cell) + graph.eventloopManager.dblClick(e, state.cell) DomEvent.consume(e) } }) @@ -599,7 +591,7 @@ export class Renderer { state.text.apply(state) // Special case where value is obtained via hook in graph - state.text.verticalAlign = graph.cellManager.getVerticalAlign(state) + state.text.verticalAlign = state.style.verticalAlign || 'middle' } const bounds = this.getLabelBounds(state) @@ -657,8 +649,8 @@ export class Renderer { (value != null && util.isHtmlElem(value)) state.text = new this.defaultTextShape(value, new Rectangle(), { - align: graph.cellManager.getAlign(state), - valign: graph.cellManager.getVerticalAlign(state), + align: state.style.align || 'center', + valign: state.style.verticalAlign || 'middle', color: state.style.fontColor, family: state.style.fontFamily, size: state.style.fontSize, @@ -746,7 +738,7 @@ export class Renderer { if (graph.nativeDblClickEnabled) { DomEvent.addListener(state.text.elem!, 'dblclick', (e: MouseEvent) => { if (this.isLabelEvent(state, e)) { - graph.eventloop.dblClick(e, state.cell) + graph.eventloopManager.dblClick(e, state.cell) DomEvent.consume(e) } }) diff --git a/packages/x6/src/core/view.ts b/packages/x6/src/core/view.ts index 2d78b1a6719..5ad304e7612 100644 --- a/packages/x6/src/core/view.ts +++ b/packages/x6/src/core/view.ts @@ -608,7 +608,7 @@ export class View extends Primer { point = this.getConnectionPoint( terminalState, anchor, - this.graph.cellManager.isOrthogonal(edgeState), + this.graph.connectionManager.isOrthogonal(edgeState), ) } @@ -897,7 +897,7 @@ export class View extends Primer { relateState = this.getTerminalPortState(edgeState, relateState, isSource) const rot = util.getRotation(relateState) - const orth = this.graph.cellManager.isOrthogonal(edgeState) + const orth = this.graph.connectionManager.isOrthogonal(edgeState) const center = relateState.bounds.getCenter() let nextPoint = this.getNextPoint(edgeState, opposeState, isSource) @@ -1546,7 +1546,7 @@ export class View extends Primer { this.backgroundPageShape.elem!, 'dblclick', (e: MouseEvent) => { - this.graph.eventloop.dblClick(e) + this.graph.eventloopManager.dblClick(e) }, ) } @@ -1567,7 +1567,10 @@ export class View extends Primer { this.graph.hideTooltip() } - if (this.graph.eventloop.isMouseDown && !DomEvent.isConsumed(e)) { + if ( + this.graph.eventloopManager.isMouseDown && + !DomEvent.isConsumed(e) + ) { this.graph.fireMouseEvent(DomEvent.MOUSE_MOVE, new MouseEventEx(e)) } }, @@ -1856,7 +1859,7 @@ export class View extends Primer { // background does not change during the double click DomEvent.addListener(container, 'dblclick', (e: MouseEvent) => { if (this.isContainerEvent(e)) { - graph.eventloop.dblClick(e) + graph.eventloopManager.dblClick(e) } }) @@ -1918,7 +1921,7 @@ export class View extends Primer { protected shouldHandleDocumentEvent(e: MouseEvent) { return ( this.captureDocumentGesture && - this.graph.eventloop.isMouseDown && + this.graph.eventloopManager.isMouseDown && this.isContainerVisible() && !this.isContainerEvent(e) ) @@ -2116,7 +2119,7 @@ export class View extends Primer { const edit = new UndoableEdit(this.graph.getModel()) edit.add(change) this.trigger(View.events.undo, edit) - this.graph.viewport.sizeDidChange() + this.graph.sizeDidChange() } return root diff --git a/packages/x6/src/graph/prop/base.ts b/packages/x6/src/graph/base-graph.ts similarity index 87% rename from packages/x6/src/graph/prop/base.ts rename to packages/x6/src/graph/base-graph.ts index b43c18123ff..e2e90af6cf7 100644 --- a/packages/x6/src/graph/prop/base.ts +++ b/packages/x6/src/graph/base-graph.ts @@ -1,37 +1,60 @@ -import { Route } from '../../route' -import { Model } from '../../core/model' -import { View } from '../../core/view' -import { Renderer } from '../../core/renderer' -import { FullOptions } from '../../option' -import { Disablable, Disposable } from '../../common' -import { Dialect, Style, Size } from '../../types' -import { Rectangle, Multiplicity, Image } from '../../struct' +import * as util from '../util' +import { Route } from '../route' +import { Model } from '../core/model' +import { Cell } from '../core/cell' +import { View } from '../core/view' +import { State } from '../core/state' +import { Renderer } from '../core/renderer' +import { FullOptions } from '../option' +import { Disablable, Disposable } from '../common' +import { Dialect, Style, Size } from '../types' +import { Rectangle, Multiplicity, Image } from '../struct' import { CellEditor, TooltipHandler, CursorHandler, - KeyboardHandler, - ContextMenuHandler, - PanningHandler, + SelectionHandler, + ConnectionHandler, GuideHandler, SelectHandler, MovingHandler, - SelectionHandler, - ConnectionHandler, + PanningHandler, + ContextMenuHandler, RubberbandHandler, -} from '../../handler' -import { - ChangeManager, - EventLoop, - Selection, - SelectionManager, - ValidationManager, - ViewportManager, - CellManager, -} from '../../manager' - -export class GraphBase extends Disablable + KeyboardHandler, + NodeHandler, + EdgeHandler, + EdgeElbowHandler, + EdgeSegmentHandler, +} from '../handler' +import { hook } from './decorator' +import { events as eventNames } from './events' +import { Selection } from './selection' +import { SelectionManager } from './selection-manager' +import { ChangeManager } from './change-manager' +import { EventLoopManager } from './eventloop-manager' +import { ViewportManager } from './viewport-manager' +import { ValidationManager } from './validation-manager' +import { PageBreakManager } from './pagebreak-manager' +import { CreationManager } from './creation-manager' +import { ConnectionManager } from './connection-manager' +import { StyleManager } from './style-manager' +import { RetrievalManager } from './retrieval-manager' +import { CollapseManager } from './collapse-manager' +import { OverlayManager } from './overlay-manager' +import { GroupManager } from './group-manager' +import { SizeManager } from './size-manager' +import { EditingManager } from './editing-manager' +import { MovingManager } from './moving-manager' +import { ZoomManager } from './zoom-manager' + +export class BaseGraph extends Disablable implements GraphProperties, CompositeOptions { + /** + * Custom event names + */ + public static events = eventNames + public options: FullOptions public container: HTMLElement public model: Model @@ -40,43 +63,240 @@ export class GraphBase extends Disablable public cellEditor: CellEditor public changeManager: ChangeManager - public eventloop: EventLoop + public eventloopManager: EventLoopManager + public viewportManager: ViewportManager + public pageBreakManager: PageBreakManager + public zoomManager: ZoomManager public selection: Selection public selectionManager: SelectionManager - public validator: ValidationManager - public viewport: ViewportManager - public cellManager: CellManager + public creationManager: CreationManager + public connectionManager: ConnectionManager + public retrievalManager: RetrievalManager + public styleManager: StyleManager + public validationManager: ValidationManager + public collapseManager: CollapseManager + public overlayManager: OverlayManager + public groupManager: GroupManager + public sizeManager: SizeManager + public editingManager: EditingManager + public movingManager: MovingManager + public panningManager: any - public keyboardHandler: KeyboardHandler public tooltipHandler: TooltipHandler public cursorHandler: CursorHandler - public contextMenuHandler: ContextMenuHandler - public guideHandler: GuideHandler public selectionHandler: SelectionHandler public connectionHandler: ConnectionHandler - public panningHandler: PanningHandler - public movingHandler: MovingHandler + public guideHandler: GuideHandler public selectHandler: SelectHandler + public movingHandler: MovingHandler + public panningHandler: PanningHandler + public contextMenuHandler: ContextMenuHandler public rubberbandHandler: RubberbandHandler - public panningManager: any + public keyboardHandler: KeyboardHandler + + /** + * Get the native value of hooked method. + */ + public getNativeValue: () => T | null + + public getModel() { + return this.model + } + + public getView() { + return this.view + } + + protected createManagers() { + this.changeManager = new ChangeManager(this as any) + this.eventloopManager = new EventLoopManager(this as any) + this.viewportManager = new ViewportManager(this as any) + this.pageBreakManager = new PageBreakManager(this as any) + this.zoomManager = new ZoomManager(this as any) + this.selectionManager = new SelectionManager(this as any) + this.creationManager = new CreationManager(this as any) + this.connectionManager = new ConnectionManager(this as any) + this.retrievalManager = new RetrievalManager(this as any) + this.styleManager = new StyleManager(this as any) + this.validationManager = new ValidationManager(this as any) + this.collapseManager = new CollapseManager(this as any) + this.overlayManager = new OverlayManager(this as any) + this.groupManager = new GroupManager(this as any) + this.sizeManager = new SizeManager(this as any) + this.editingManager = new EditingManager(this as any) + this.movingManager = new MovingManager(this as any) + } + + protected createHandlers() { + // The order of the following initializations should not be modified. + this.cellEditor = new CellEditor(this as any) + this.tooltipHandler = this.createTooltipHandler() + this.cursorHandler = this.createCursorHandler() + this.selectionHandler = this.createSelectionHandler() + this.connectionHandler = this.createConnectionHandler() + this.guideHandler = this.createGuideHandler() + this.selectHandler = this.createSelectHandler() + this.movingHandler = this.createMovingHandler() + this.panningHandler = this.createPanningHandler() + this.panningHandler.disablePanning() + this.contextMenuHandler = this.createContextMenuHandler() + this.rubberbandHandler = this.createRubberbandHandler() + this.keyboardHandler = this.createKeyboardHandler() + } + + @hook() + createKeyboardHandler() { + return new KeyboardHandler(this as any) + } + + @hook() + createTooltipHandler() { + return new TooltipHandler(this as any) + } + + @hook() + createCursorHandler() { + return new CursorHandler(this as any) + } + + @hook() + createGuideHandler() { + return new GuideHandler(this as any) + } + + @hook() + createSelectHandler() { + return new SelectHandler(this as any) + } + + @hook() + createConnectionHandler() { + return new ConnectionHandler(this as any) + } + + @hook() + createSelectionHandler() { + return new SelectionHandler(this as any) + } + + @hook() + createMovingHandler() { + return new MovingHandler(this as any) + } + + @hook() + createPanningHandler() { + return new PanningHandler(this as any) + } + + @hook() + createContextMenuHandler() { + return new ContextMenuHandler(this as any) + } + + @hook() + createRubberbandHandler() { + return new RubberbandHandler(this as any) + } + + @hook(null, true) + createCellHandler(state: State | null) { + if (state != null) { + if (this.model.isEdge(state.cell)) { + const sourceState = state.getVisibleTerminalState(true) + const targetState = state.getVisibleTerminalState(false) + const geo = state.cell.getGeometry() + const edgeFn = this.view.getRoute( + state, + geo != null ? geo.points : null, + sourceState!, + targetState!, + ) + return this.createEdgeHandler(state, edgeFn) + } + + return this.createNodeHandler(state) + } + + return null + } + + @hook() + createNodeHandler(state: State) { + return new NodeHandler(this as any, state) + } + + @hook() + createEdgeHandler(state: State, edgeFn: Route.Router | null) { + let result = null + + if ( + edgeFn === Route.loop || + edgeFn === Route.elbow || + edgeFn === Route.sideToSide || + edgeFn === Route.topToBottom + ) { + result = this.createElbowEdgeHandler(state) + } else if (edgeFn === Route.segment || edgeFn === Route.orth) { + result = this.createEdgeSegmentHandler(state) + } else { + return ( + util.call(this.options.createEdgeHandler, this, this, state) || + new EdgeHandler(this as any, state) + ) + } + + return result + } + + @hook() + createEdgeSegmentHandler(state: State) { + return new EdgeSegmentHandler(this as any, state) + } + + @hook() + createElbowEdgeHandler(state: State) { + return new EdgeElbowHandler(this as any, state) + } + + // #region IDisposable protected disposeManagers() { this.changeManager.dispose() - this.eventloop.dispose() + this.eventloopManager.dispose() + this.viewportManager.dispose() + this.pageBreakManager.dispose() + this.zoomManager.dispose() this.selection.dispose() this.selectionManager.dispose() - this.validator.dispose() - this.viewport.dispose() - this.cellManager.dispose() + this.creationManager.dispose() + this.connectionManager.dispose() + this.retrievalManager.dispose() + this.styleManager.dispose() + this.validationManager.dispose() + this.collapseManager.dispose() + this.overlayManager.dispose() + this.groupManager.dispose() + this.sizeManager.dispose() + this.editingManager.dispose() + this.movingManager.dispose() } protected disposeHandlers() { + if (this.cellEditor != null) { + this.cellEditor.dispose() + } this.tooltipHandler.dispose() - this.panningHandler.dispose() - this.contextMenuHandler.dispose() + this.cursorHandler.dispose() this.selectionHandler.dispose() - this.movingHandler.dispose() this.connectionHandler.dispose() + this.guideHandler.dispose() + this.selectHandler.dispose() + this.movingHandler.dispose() + this.panningHandler.dispose() + this.contextMenuHandler.dispose() + this.rubberbandHandler.dispose() + this.keyboardHandler.dispose() } @Disposable.aop() @@ -84,24 +304,14 @@ export class GraphBase extends Disablable this.disposeManagers() this.disposeHandlers() - if (this.cellEditor != null) { - this.cellEditor.dispose() - } - if (this.view != null) { this.view.dispose() } } - sizeDidChange() {} - - getModel() { - return this.model - } + // #endregion - getView() { - return this.view - } + // #region Properties get prefixCls() { return this.options.prefixCls @@ -111,6 +321,10 @@ export class GraphBase extends Disablable return this.options.dialect } + get infinite() { + return this.options.infinite + } + get antialiased() { return this.options.antialiased } @@ -2168,11 +2382,19 @@ export class GraphBase extends Disablable } // #endregion + + // #endregion } export interface GraphProperties { prefixCls: string dialect: Dialect + /** + * Specifies if the canvas is infinite. + * + * Default is `false`. + */ + infinite: boolean antialiased: boolean /** @@ -2813,3 +3035,25 @@ export interface CompositeOptions { */ cellsRotatable?: boolean } + +export interface BaseGraph extends IPreDependencies {} + +export interface IPreDependencies { + panDx: number + panDy: number + + snap(value: number): number + getGridSize(): number + sizeDidChange(): this + isCellLocked(cell: Cell | null): boolean + isCellCollapsed(cell: Cell | null): boolean + isCellConnectable(cell: Cell | null): boolean + getDefaultParent(): Cell + getSelectedCell(): Cell + getSelectedCells(): Cell[] + getDeletableCells(cells: Cell[]): Cell[] + getFoldableCells(cells: Cell[], collapse: boolean): Cell[] + dataToString(cell: Cell): string + putLabel(cell: Cell, label: string): string + getStyle(cell: Cell | null): Style +} diff --git a/packages/x6/src/manager/base.ts b/packages/x6/src/graph/base-manager.ts similarity index 60% rename from packages/x6/src/manager/base.ts rename to packages/x6/src/graph/base-manager.ts index e08bd1281f0..1dcc39ec393 100644 --- a/packages/x6/src/manager/base.ts +++ b/packages/x6/src/graph/base-manager.ts @@ -1,12 +1,9 @@ -import { Graph } from '../graph' +import { Graph } from './graph' import { Disposable } from '../common' -export class ManagerBase extends Disposable { - graph: Graph - - constructor(graph: Graph) { +export class BaseManager extends Disposable { + constructor(public graph: Graph) { super() - this.graph = graph } get model() { @@ -24,4 +21,8 @@ export class ManagerBase extends Disposable { get renderer() { return this.graph.renderer } + + get container() { + return this.graph.container + } } diff --git a/packages/x6/src/manager/change-manager.ts b/packages/x6/src/graph/change-manager.ts similarity index 97% rename from packages/x6/src/manager/change-manager.ts rename to packages/x6/src/graph/change-manager.ts index 15a7d40f592..90c882eea10 100644 --- a/packages/x6/src/manager/change-manager.ts +++ b/packages/x6/src/graph/change-manager.ts @@ -1,8 +1,8 @@ import { Cell } from '../core/cell' import { Model } from '../core/model' import { Graph } from '../graph' -import { ManagerBase } from './base' import { Disposable } from '../common' +import { BaseManager } from './base-manager' import { IChange, RootChange, @@ -13,7 +13,7 @@ import { GeometryChange, } from '../change' -export class ChangeManager extends ManagerBase { +export class ChangeManager extends BaseManager { constructor(graph: Graph) { super(graph) this.model.on(Model.events.change, this.onModelChanged, this) diff --git a/packages/x6/src/graph/prop/folding.ts b/packages/x6/src/graph/collapse-accessor.ts similarity index 60% rename from packages/x6/src/graph/prop/folding.ts rename to packages/x6/src/graph/collapse-accessor.ts index 1ec47330887..016dd17edf8 100644 --- a/packages/x6/src/graph/prop/folding.ts +++ b/packages/x6/src/graph/collapse-accessor.ts @@ -1,19 +1,10 @@ -import { GraphBase } from './base' -import { Image } from '../../struct' +import { Cell } from '../core/cell' +import { State } from '../core/state' +import { Image } from '../struct' +import { hook } from './decorator' +import { BaseGraph } from './base-graph' -export interface FoldingOptions { - /** - * Specifies if folding (collapse and expand) via an image icon - * in the graph should be enabled. - * - * Default is `true`. - */ - enabled: boolean - collapsedImage: Image - expandedImage: Image -} - -export class GraphFolding extends GraphBase { +export class CollapseAccessor extends BaseGraph { isCellsFoldable() { return this.options.folding.enabled } @@ -106,4 +97,65 @@ export class GraphFolding extends GraphBase { set collapsedImage(image: Image) { this.setCollapsedImage(image) } + + @hook() + isCellCollapsed(cell: Cell) { + return this.model.isCollapsed(cell) + } + + @hook() + isCellFoldable(cell: Cell, nextCollapseState: boolean) { + const style = this.getStyle(cell) + return this.model.getChildCount(cell) > 0 && style.foldable !== false + } + + /** + * Returns the cells which are movable in the given array of cells. + */ + getFoldableCells(cells: Cell[], collapse: boolean) { + return this.model.filterCells(cells, cell => + this.isCellFoldable(cell, collapse), + ) + } + + getFoldingImage(state: State) { + if ( + state != null && + this.cellsFoldable && + !this.getModel().isEdge(state.cell) + ) { + const collapsed = this.isCellCollapsed(state.cell) + if (this.isCellFoldable(state.cell, !collapsed)) { + return collapsed ? this.collapsedImage : this.expandedImage + } + } + + return null + } + + foldCells( + collapse: boolean, + recurse: boolean = false, + cells: Cell[] = this.getFoldableCells(this.getSelectedCells(), collapse), + checkFoldable: boolean = false, + ) { + return this.collapseManager.foldCells( + collapse, + recurse, + cells, + checkFoldable, + ) + } +} + +export interface FoldingOptions { + /** + * Specifies if folding (collapse and expand) via an image icon + * in the graph should be enabled. + * + * Default is `true`. + */ + enabled: boolean + collapsedImage: Image + expandedImage: Image } diff --git a/packages/x6/src/graph/collapse-manager.ts b/packages/x6/src/graph/collapse-manager.ts new file mode 100644 index 00000000000..254158d83ef --- /dev/null +++ b/packages/x6/src/graph/collapse-manager.ts @@ -0,0 +1,126 @@ +import * as util from '../util' +import { Cell } from '../core/cell' +import { Geometry } from '../core/geometry' +import { Rectangle } from '../struct' +import { Graph } from './graph' +import { BaseManager } from './base-manager' + +export class CollapseManager extends BaseManager { + foldCells( + collapse: boolean, + recurse: boolean, + cells: Cell[], + checkFoldable: boolean, + ) { + this.graph.stopEditing(false) + this.model.batchUpdate(() => { + this.graph.trigger(Graph.events.foldCells, { collapse, recurse, cells }) + this.cellsFolded(cells, collapse, recurse, checkFoldable) + }) + return cells + } + + cellsFolded( + cells: Cell[], + collapse: boolean, + recurse: boolean, + checkFoldable: boolean = false, + ) { + if (cells != null && cells.length > 0) { + this.model.batchUpdate(() => { + cells.forEach(cell => { + if ( + (!checkFoldable || this.graph.isCellFoldable(cell, collapse)) && + collapse !== this.graph.isCellCollapsed(cell) + ) { + this.model.setCollapsed(cell, collapse) + this.swapBounds(cell, collapse) + + if (this.graph.isExtendParent(cell)) { + this.graph.sizeManager.extendParent(cell) + } + + if (recurse) { + const children = this.model.getChildren(cell) + this.cellsFolded(children, collapse, recurse) + } + + this.graph.sizeManager.constrainChild(cell) + } + }) + }) + this.graph.trigger(Graph.events.cellsFolded, { cells, collapse, recurse }) + } + } + + swapBounds(cell: Cell, willCollapse: boolean) { + if (cell != null) { + let geo = this.model.getGeometry(cell) + if (geo != null) { + geo = geo.clone() + this.updateAlternateBounds(cell, geo, willCollapse) + geo.swap() + this.model.setGeometry(cell, geo) + } + } + } + + /** + * Specifies if the cell size should be changed to the preferred size when + * a cell is first collapsed. + * + * Default is `true`. + */ + collapseToPreferredSize: boolean = true + + /** + * Updates or sets the alternate bounds in the given geometry for the + * given cell depending on whether the cell is going to be collapsed. + * If no alternate bounds are defined in the geometry and + * `collapseToPreferredSize` is true, then the preferred size is used for + * the alternate bounds. The top, left corner is always kept at the same + * location. + */ + updateAlternateBounds(cell: Cell, geo: Geometry, willCollapse: boolean) { + if (cell != null && geo != null) { + const style = this.graph.getStyle(cell) + + if (geo.alternateBounds == null) { + let bounds = geo.bounds + + if (this.collapseToPreferredSize) { + const tmp = this.graph.sizeManager.getCellPreferredSize(cell) + if (tmp != null) { + bounds = tmp + const startSize = style.startSize || 0 + if (startSize > 0) { + bounds.height = Math.max(bounds.height, startSize) + } + } + } + + geo.alternateBounds = new Rectangle(0, 0, bounds.width, bounds.height) + } + + if (geo.alternateBounds != null) { + geo.alternateBounds.x = geo.bounds.x + geo.alternateBounds.y = geo.bounds.y + + const alpha = util.toRad(style.rotation || 0) + if (alpha !== 0) { + const dx = geo.alternateBounds.getCenterX() - geo.bounds.getCenterX() + const dy = geo.alternateBounds.getCenterY() - geo.bounds.getCenterY() + + const cos = Math.cos(alpha) + const sin = Math.sin(alpha) + + const dx2 = cos * dx - sin * dy + const dy2 = sin * dx + cos * dy + + geo.alternateBounds.x += dx2 - dx + geo.alternateBounds.y += dy2 - dy + } + } + } + } +} diff --git a/packages/x6/src/graph/common-accessor.ts b/packages/x6/src/graph/common-accessor.ts new file mode 100644 index 00000000000..3a5eafad91f --- /dev/null +++ b/packages/x6/src/graph/common-accessor.ts @@ -0,0 +1,350 @@ +import * as util from '../util' +import { globals } from '../option' +import { Cell } from '../core/cell' +import { Rectangle, Point } from '../struct' +import { hook } from './decorator' +import { BaseGraph } from './base-graph' +import { DomEvent } from '../common' + +export class CommonAccessor extends BaseGraph { + batchUpdate(update: () => void) { + this.model.batchUpdate(update) + return this + } + + getCellGeometry(cell: Cell) { + return this.model.getGeometry(cell) + } + + @hook() + getChildOffset(cell: Cell): Point | null { + return null + } + + @hook() + getTranslateForCurrentRoot(currentRoot: Cell | null): Point | null { + return null + } + + @hook() + isCellLocked(cell: Cell | null) { + if (this.isCellsLocked()) { + return true + } + + const style = this.getStyle(cell) + if (style.locked) { + return true + } + + const geometry = this.model.getGeometry(cell) + return geometry != null && this.model.isNode(cell) && geometry.relative + } + + @hook() + isCellSelectable(cell: Cell) { + return this.isCellsSelectable() + } + + @hook() + isCellRotatable(cell: Cell) { + const style = this.getStyle(cell) + return ( + this.isCellsRotatable() && + !this.isCellLocked(cell) && + style.rotatable !== false + ) + } + + @hook() + isLabelMovable(cell: Cell | null) { + return ( + !this.isCellLocked(cell) && + ((this.model.isEdge(cell) && this.edgeLabelsMovable) || + (this.model.isNode(cell) && this.nodeLabelsMovable)) + ) + } + + @hook() + isCellBendable(cell: Cell) { + const style = this.getStyle(cell) + return ( + this.isCellsBendable() && + !this.isCellLocked(cell) && + style.bendable !== false + ) + } + + @hook() + isTerminalPointMovable(cell: Cell, isSource: boolean) { + return true + } + + @hook() + isHtmlLabel(cell: Cell) { + const style = this.getStyle(cell) + if (style != null && style.htmlLabel != null) { + return style.htmlLabel + } + return this.isHtmlLabels() + } + + @hook() + isWrapping(cell: Cell) { + const style = this.getStyle(cell) + return style != null ? style.whiteSpace === 'wrap' : false + } + + @hook() + isLabelClipped(cell: Cell) { + const style = this.getStyle(cell) + return style != null ? style.overflow === 'hidden' : false + } + + @hook() + isSwimlane(cell: Cell | null) { + if (cell != null) { + if (this.model.getParent(cell) !== this.model.getRoot()) { + const style = this.getStyle(cell) + if (style != null && !this.model.isEdge(cell)) { + return style.shape === 'swimlane' + } + } + } + return false + } + + @hook() + dataToString(cell: Cell): string { + const data = this.model.getData(cell) + if (data != null) { + if (typeof data.toString === 'function') { + return data.toString() + } + } + + return '' + } + + @hook() + getHtml(cell: Cell): HTMLElement | string | null { + let result = '' + if (cell != null) { + result = this.dataToString(cell) + } + return result + } + + @hook() + getLabel(cell: Cell): HTMLElement | string | null { + let result = '' + + if (this.labelsVisible && cell != null) { + const style = this.getStyle(cell) + if (!style.noLabel) { + result = this.dataToString(cell) + } + } + + return result + } + + @hook() + putLabel(cell: Cell, label: string) { + const data = cell.getData() + if (typeof data === 'object') { + throw new Error('Method not implemented.') + } + + return label + } + + @hook() + getCellLink(cell: Cell) { + return null + } + + @hook() + getCellCursor(cell: Cell | null) { + return null + } + + @hook() + getStartSize(swimlane: Cell | null) { + const result = new Rectangle() + const style = this.getStyle(swimlane) + if (style != null) { + const size = style.startSize || globals.defaultStartSize + if (style.horizontal !== false) { + result.height = size + } else { + result.width = size + } + } + + return result + } + + @hook() + getCellClassName(cell: Cell) { + const style = this.getStyle(cell) + return style.className || null + } + + @hook() + getLabelClassName(cell: Cell) { + const style = this.getStyle(cell) + return style.labelClassName || null + } + + /** + * Returns the cells which may be exported in the given array of cells. + */ + getExportableCells(cells: Cell[]) { + return this.model.filterCells(cells, cell => this.canExportCell(cell)) + } + + canExportCell(cell: Cell) { + return this.isCellsExportable() + } + + getImportableCells(cells: Cell[]) { + return this.model.filterCells(cells, cell => this.canImportCell(cell)) + } + + /** + * Returns true if the given cell may be imported from the clipboard. + * + * Default is `true`. + */ + canImportCell(cell: Cell) { + return this.isCellsImportable() + } + + /** + * Returns a decimal number representing the amount of the width and height + * of the given cell that is allowed to overlap its parent. A value of 0 + * means all children must stay inside the parent, 1 means the child is + * allowed to be placed outside of the parent such that it touches one of + * the parents sides. + */ + getOverlap(cell: Cell) { + return this.isAllowOverlapParent(cell) ? this.defaultOverlap : 0 + } + + /** + * Returns true if the given cell is allowed to be placed outside of the + * parents area. + */ + isAllowOverlapParent(cell: Cell) { + return false + } + + @hook() + isValidDropTarget(target: Cell, cells: Cell[], e: MouseEvent) { + return ( + target != null && + ((this.isSplitEnabled() && this.isSplitTarget(target, cells, e)) || + (!this.model.isEdge(target) && + (this.isSwimlane(target) || + (this.model.getChildCount(target) > 0 && + !this.isCellCollapsed(target))))) + ) + } + + @hook() + isSplitTarget(target: Cell, cells: Cell[], e: MouseEvent) { + if ( + this.model.isEdge(target) && + cells != null && + cells.length === 1 && + this.isCellConnectable(cells[0]) && + this.validationManager.isEdgeValid( + target, + this.model.getTerminal(target, true), + cells[0], + ) + ) { + const src = this.model.getTerminal(target, true)! + const trg = this.model.getTerminal(target, false)! + + return ( + !this.model.isAncestor(cells[0], src) && + !this.model.isAncestor(cells[0], trg) + ) + } + + return false + } + + /** + * Returns the given cell if it is a drop target for the given cells or the + * nearest ancestor that may be used as a drop target for the given cells. + * + * @param cells Array of `Cell`s which are to be dropped onto the target. + * @param e Mouseevent for the drag and drop. + * @param cell `Cell` that is under the mousepointer. + * @param clone Optional boolean to indicate of cells will be cloned. + */ + getDropTarget( + cells: Cell[], + e: MouseEvent, + cell: Cell | null, + clone?: boolean, + ) { + if (!this.isSwimlaneNesting()) { + for (let i = 0; i < cells.length; i += 1) { + if (this.isSwimlane(cells[i])) { + return null + } + } + } + + const p = util.clientToGraph( + this.container, + DomEvent.getClientX(e), + DomEvent.getClientY(e), + ) + p.x -= this.panDx + p.y -= this.panDy + const swimlane = this.retrievalManager.getSwimlaneAt(p.x, p.y) + + if (cell == null) { + // tslint:disable-next-line + cell = swimlane! + } else if (swimlane != null) { + // Checks if the cell is an ancestor of the swimlane + // under the mouse and uses the swimlane in that case + let tmp = this.model.getParent(swimlane) + + while (tmp != null && this.isSwimlane(tmp) && tmp !== cell) { + tmp = this.model.getParent(tmp) + } + + if (tmp === cell) { + // tslint:disable-next-line + cell = swimlane + } + } + + while ( + cell != null && + !this.isValidDropTarget(cell, cells, e) && + !this.model.isLayer(cell) + ) { + // tslint:disable-next-line + cell = this.model.getParent(cell)! + } + + // Checks if parent is dropped into child if not cloning + if (clone == null || !clone) { + let parent = cell + + while (parent != null && util.indexOf(cells, parent) < 0) { + parent = this.model.getParent(parent)! + } + } + + return !this.model.isLayer(cell) && parent == null ? cell : null + } +} diff --git a/packages/x6/src/graph/connection-accessor.ts b/packages/x6/src/graph/connection-accessor.ts new file mode 100644 index 00000000000..54a4a2cd569 --- /dev/null +++ b/packages/x6/src/graph/connection-accessor.ts @@ -0,0 +1,115 @@ +import { Cell } from '../core/cell' +import { State } from '../core/state' +import { Anchor } from '../struct' +import { hook } from './decorator' +import { BaseGraph } from './base-graph' + +export class ConnectionAccessor extends BaseGraph { + setConnectable(connectable: boolean) { + if (connectable) { + this.connectionHandler.enable() + } else { + this.connectionHandler.disable() + } + return this + } + + enableConnection() { + this.connectionHandler.enable() + return this + } + + disableConnection() { + this.connectionHandler.disable() + return this + } + + isConnectable() { + return this.connectionHandler.isEnabled() + } + + @hook() + isPort(cell: Cell) { + return false + } + + @hook() + getTerminalForPort(cell: Cell, isSource: boolean) { + return this.model.getParent(cell) + } + + @hook() + isCellDisconnectable(cell: Cell, terminal: Cell, isSource: boolean) { + return this.isCellsDisconnectable() && !this.isCellLocked(cell) + } + + @hook() + isCellConnectable(cell: Cell | null) { + const style = this.getStyle(cell) + return style.connectable !== false + } + + /** + * Connects the specified end of the given edge to the given terminal. + * + * @param edge - The edge will be updated. + * @param terminal - The new terminal to be used. + * @param isSource - Indicating if the new terminal is the source or target. + * @param anchor - Optional `Anchor` to be used for this connection. + */ + connectCell( + edge: Cell, + terminal: Cell | null, + isSource: boolean, + anchor?: Anchor, + ) { + return this.connectionManager.connectCell(edge, terminal, isSource, anchor) + } + + /** + * Disconnects the given edges from the terminals which are not in the + * given array. + */ + disconnectGraph(cells: Cell[]) { + return this.connectionManager.disconnectGraph(cells) + } + + /** + * Get an `Anchor` instance that describes the given connection point. + */ + getConnectionAnchor( + edgeState: State, + terminalState?: State | null, + isSource: boolean = false, + ) { + return this.connectionManager.getConnectionAnchor( + edgeState, + terminalState, + isSource, + ) + } + + setConnectionAnchor( + edge: Cell, + terminal: Cell | null, + isSource: boolean, + anchor?: Anchor | null, + ) { + return this.connectionManager.setConnectionAnchor( + edge, + terminal, + isSource, + anchor, + ) + } + + @hook() + getAnchors(terminal: Cell, isSource: boolean) { + const state = this.view.getState(terminal) + if (state != null && state.shape != null && state.shape.stencil != null) { + return state.shape.stencil.anchors + } + + return null + } +} diff --git a/packages/x6/src/graph/connection-manager.ts b/packages/x6/src/graph/connection-manager.ts new file mode 100644 index 00000000000..a647478e145 --- /dev/null +++ b/packages/x6/src/graph/connection-manager.ts @@ -0,0 +1,286 @@ +import { Cell } from '../core/cell' +import { State } from '../core/state' +import { Route } from '../route' +import { Point, Anchor } from '../struct' +import { Graph } from './graph' +import { BaseManager } from './base-manager' + +export class ConnectionManager extends BaseManager { + connectCell( + edge: Cell, + terminal: Cell | null, + isSource: boolean, + anchor?: Anchor, + ) { + this.model.batchUpdate(() => { + const previous = this.model.getTerminal(edge, isSource) + this.graph.trigger(Graph.events.connectCell, { + edge, + terminal, + isSource, + anchor, + previous, + }) + + this.cellConnected(edge, terminal, isSource, anchor) + }) + return edge + } + + cellConnected( + edge: Cell, + terminal: Cell | null, + isSource: boolean, + anchor?: Anchor, + ) { + if (edge != null) { + this.model.batchUpdate(() => { + const previous = this.model.getTerminal(edge, isSource) + + // Updates the anchor + this.setConnectionAnchor(edge, terminal, isSource, anchor) + + // Checks if the new terminal is a port, uses the ID of the port + // in the style and the parent of the port as the actual terminal + // of the edge. + if (this.graph.isPortsEnabled()) { + let id = null + + if (terminal != null && this.graph.isPort(terminal)) { + id = terminal.getId() + // tslint:disable-next-line + terminal = this.graph.getTerminalForPort(terminal, isSource)! + } + + if (id != null) { + const key = isSource ? 'sourcePort' : 'targetPort' + this.graph.updateCellsStyle(key, id, [edge]) + } + } + + this.model.setTerminal(edge, terminal, isSource) + + if (this.graph.resetEdgesOnConnect) { + this.graph.resetEdge(edge) + } + + this.graph.trigger(Graph.events.cellConnected, { + edge, + terminal, + isSource, + previous, + anchor, + }) + }) + } + } + + getConnectionAnchor( + edgeState: State, + terminalState?: State | null, + isSource: boolean = false, + ) { + let point: Point | null = null + const style = edgeState.style + + // connection point specified in style + const x = isSource ? style.exitX : style.entryX + if (x != null) { + const y = isSource ? style.exitY : style.entryY + if (y != null) { + point = new Point(x, y) + } + } + + let dx = 0 + let dy = 0 + let perimeter = true + + if (point != null) { + perimeter = + (isSource ? style.exitPerimeter : style.entryPerimeter) !== false + + // Add entry/exit offset + dx = (isSource ? style.exitDx : style.entryDx) as number + dy = (isSource ? style.exitDy : style.entryDy) as number + + dx = isFinite(dx) ? dx : 0 + dy = isFinite(dy) ? dy : 0 + } + + return new Anchor({ point, perimeter, dx, dy }) + } + + setConnectionAnchor( + edge: Cell, + terminal: Cell | null, + isSource: boolean, + anchor?: Anchor | null, + ) { + if (anchor != null) { + this.model.batchUpdate(() => { + if (anchor == null || anchor.point == null) { + this.graph.updateCellsStyle(isSource ? 'exitX' : 'entryX', null, [ + edge, + ]) + this.graph.updateCellsStyle(isSource ? 'exitY' : 'entryY', null, [ + edge, + ]) + this.graph.updateCellsStyle(isSource ? 'exitDx' : 'entryDx', null, [ + edge, + ]) + this.graph.updateCellsStyle(isSource ? 'exitDy' : 'entryDy', null, [ + edge, + ]) + this.graph.updateCellsStyle( + isSource ? 'exitPerimeter' : 'entryPerimeter', + null, + [edge], + ) + } else if (anchor.point != null) { + this.graph.updateCellsStyle( + isSource ? 'exitX' : 'entryX', + `${anchor.point.x}`, + [edge], + ) + this.graph.updateCellsStyle( + isSource ? 'exitY' : 'entryY', + `${anchor.point.y}`, + [edge], + ) + this.graph.updateCellsStyle( + isSource ? 'exitDx' : 'entryDx', + `${anchor.dx}`, + [edge], + ) + this.graph.updateCellsStyle( + isSource ? 'exitDy' : 'entryDy', + `${anchor.dy}`, + [edge], + ) + + // Only writes `false` since `true` is default + if (!anchor.perimeter) { + this.graph.updateCellsStyle( + isSource ? 'exitPerimeter' : 'entryPerimeter', + false, + [edge], + ) + } else { + this.graph.updateCellsStyle( + isSource ? 'exitPerimeter' : 'entryPerimeter', + null, + [edge], + ) + } + } + }) + } + } + + disconnectGraph(cells: Cell[]) { + if (cells != null) { + this.model.batchUpdate(() => { + const s = this.view.scale + const t = this.view.translate + + const dict = new WeakMap() + cells.forEach(c => dict.set(c, true)) + + cells.forEach(edge => { + if (this.model.isEdge(edge)) { + let geo = this.model.getGeometry(edge) + if (geo != null) { + const state = this.view.getState(edge) + const pstate = this.view.getState(this.model.getParent(edge)) + + if (state != null && pstate != null) { + geo = geo.clone() + + const dx = -pstate.origin.x + const dy = -pstate.origin.y + const pts = state.absolutePoints + + let src = this.model.getTerminal(edge, true) + if ( + src != null && + this.graph.isCellDisconnectable(edge, src, true) + ) { + while (src != null && !dict.get(src)) { + src = this.model.getParent(src) + } + + if (src == null) { + geo.setTerminalPoint( + new Point( + pts[0]!.x / s - t.x + dx, + pts[0]!.y / s - t.y + dy, + ), + true, + ) + this.model.setTerminal(edge, null, true) + } + } + + let trg = this.model.getTerminal(edge, false) + if ( + trg != null && + this.graph.isCellDisconnectable(edge, trg, false) + ) { + while (trg != null && !dict.get(trg)) { + trg = this.model.getParent(trg) + } + + if (trg == null) { + const n = pts.length - 1 + geo.setTerminalPoint( + new Point( + pts[n]!.x / s - t.x + dx, + pts[n]!.y / s - t.y + dy, + ), + false, + ) + this.model.setTerminal(edge, null, false) + } + } + + this.model.setGeometry(edge, geo) + } + } + } + }) + }) + } + } + + /** + * Returns true if perimeter points should be computed such that the + * resulting edge has only horizontal or vertical segments. + */ + isOrthogonal(state: State) { + const orthogonal = state.style.orthogonal + if (orthogonal != null) { + return orthogonal + } + + const route = this.view.getRoute(state) + return ( + route === Route.segment || + route === Route.elbow || + route === Route.sideToSide || + route === Route.topToBottom || + route === Route.er || + route === Route.orth + ) + } + + /** + * Returns true if the given cell state is a loop. + */ + isLoop(state: State) { + const src = state.getVisibleTerminalState(true) + const trg = state.getVisibleTerminalState(false) + + return src != null && src === trg + } +} diff --git a/packages/x6/src/graph/creation-accessor.ts b/packages/x6/src/graph/creation-accessor.ts new file mode 100644 index 00000000000..6363d3a8777 --- /dev/null +++ b/packages/x6/src/graph/creation-accessor.ts @@ -0,0 +1,253 @@ +import { Cell } from '../core/cell' +import { afterCreate, hook } from './decorator' +import { BaseGraph } from './base-graph' + +export class CreationAccessor extends BaseGraph { + @afterCreate() + createNode(options: Cell.CreateNodeOptions = {}): Cell { + return Cell.createNode(options) + } + + @afterCreate() + createEdge(options: Cell.CreateEdgeOptions = {}): Cell { + return Cell.createEdge(options) + } + + addNode(options: CreationManager.AddNodeOptions): Cell + addNode(node: Cell, parent?: Cell, index?: number): Cell + addNode( + node?: Cell | CreationManager.AddNodeOptions, + parent?: Cell, + index?: number, + ): Cell { + if (node instanceof Cell) { + return this.addNodes([node], parent, index)[0] + } + + const options = node != null ? node : {} + const { parent: p, index: i, ...others } = options + const cell = this.createNode(others) + return this.addNodes([cell], p, i)[0] + } + + addNodes(nodes: Cell[], parent?: Cell, index?: number): Cell[] { + return this.addCells(nodes, parent, index) + } + + addEdge(options: CreationManager.AddEdgeOptions): Cell + addEdge( + edge: Cell, + parent?: Cell, + source?: Cell, + target?: Cell, + index?: number, + ): Cell + addEdge( + edge?: Cell | CreationManager.AddEdgeOptions, + parent?: Cell, + source?: Cell, + target?: Cell, + index?: number, + ) { + if (edge instanceof Cell) { + return this.addCell(edge, parent, index, source, target) + } + const options = edge != null ? edge : {} + const cell = this.createEdge(options) + + this.addCell( + cell, + options.parent, + options.index, + options.source, + options.target, + ) + + if (this.resetEdgesOnConnect && options.points != null) { + options.points.forEach(p => cell.geometry!.addPoint(p)) + } + + return cell + } + + /** + * Adds the cell to the parent and connects it to the given source and + * target terminals. + * + * @param cell - `Cell` to be inserted into the given parent. + * @param parent - `Cell` that represents the new parent. If no parent is + * given then the default parent is used. + * @param index - Optional index to insert the cells at. + * @param source - Optional `Cell` that represents the source terminal. + * @param target - Optional `Cell` that represents the target terminal. + */ + addCell( + cell: Cell, + parent?: Cell, + index?: number, + source?: Cell, + target?: Cell, + ) { + return this.addCells([cell], parent, index, source, target)[0] + } + + /** + * Adds the cells to the parent at the given index, connecting each cell to + * the optional source and target terminal. + * + * @param cells - Array of `Cell`s to be inserted. + * @param parent - `Cell` that represents the new parent. If no parent is + * given then the default parent is used. + * @param index - Optional index to insert the cells at. + * @param source - Optional source `Cell` for all inserted cells. + * @param target - Optional target `Cell` for all inserted cells. + */ + addCells( + cells: Cell[], + parent: Cell = this.getDefaultParent()!, + index: number = this.model.getChildCount(parent), + source?: Cell, + target?: Cell, + ) { + return this.creationManager.addCells(cells, parent, index, source, target) + } + + duplicateCells( + cells: Cell[] = this.getSelectedCells(), + append: boolean = true, + ) { + return this.creationManager.duplicateCells(cells, append) + } + + turnCells(cells: Cell[] = this.getSelectedCells()) { + return this.creationManager.turnCells(cells) + } + + @hook() + isCellDeletable(cell: Cell) { + const style = this.getStyle(cell) + return this.isCellsDeletable() && style.deletable !== false + } + + getDeletableCells(cells: Cell[]) { + return this.model.filterCells(cells, cell => this.isCellDeletable(cell)) + } + + deleteCells( + cells: Cell[] = this.getDeletableCells(this.getSelectedCells()), + includeEdges: boolean = true, + selectParentAfterDelete: boolean = true, + ) { + return this.creationManager.deleteCells( + cells, + includeEdges, + selectParentAfterDelete, + ) + } + + /** + * Removes the given cells from the graph including all connected + * edges if `includeEdges` is `true`. + */ + removeCells( + cells: Cell[] = this.getDeletableCells(this.getSelectedCells()), + includeEdges: boolean = true, + ) { + return this.creationManager.removeCells(cells, includeEdges) + } + + /** + * Returns the cells which may be exported in the given array of cells. + */ + getCloneableCells(cells: Cell[]) { + return this.model.filterCells(cells, cell => this.isCellCloneable(cell)) + } + + @hook() + isCellCloneable(cell: Cell) { + const style = this.getStyle(cell) + return this.isCellsCloneable() && style.cloneable !== false + } + + /** + * Returns the clone for the given cell. + * + * @param cell `Cell` to be cloned. + * @param allowInvalidEdges Optional boolean that specifies if invalid + * edges should be cloned. Default is `true`. + * @param mapping Optional mapping for existing clones. + * @param keepPosition Optional boolean indicating if the position + * of the cells should be updated to reflect the lost parent cell. + * Default is `false`. + */ + cloneCell( + cell: Cell, + allowInvalidEdges: boolean = true, + mapping: WeakMap = new WeakMap(), + keepPosition: boolean = false, + ) { + return this.cloneCells([cell], allowInvalidEdges, mapping, keepPosition)[0] + } + + /** + * Returns the clones for the given cells. If the terminal of an edge is + * not in the given array, then the respective end is assigned a terminal + * point and the terminal is removed. + * + * @param cells - Array of `Cell`s to be cloned. + * @param allowInvalidEdges - Optional boolean that specifies if + * invalid edges should be cloned. Default is `true`. + * @param mapping - Optional mapping for existing clones. + * @param keepPosition - Optional boolean indicating if the position + * of the cells should be updated to reflect the lost parent cell. + * Default is `false`. + */ + cloneCells( + cells: Cell[], + allowInvalidEdges: boolean = true, + mapping: WeakMap = new WeakMap(), + keepPosition: boolean = false, + ) { + return this.creationManager.cloneCells( + cells, + allowInvalidEdges, + mapping, + keepPosition, + ) + } + + /** + * Splits the given edge by adding the newEdge between the previous source + * and the given cell and reconnecting the source of the given edge to the + * given cell. + * + * @param edge The edge to be splitted. + * @param cells The cells to insert into the edge. + * @param newEdge The edge to be inserted. + * @param dx The vector to move the cells. + * @param dy The vector to move the cells. + */ + splitEdge( + edge: Cell, + cells: Cell[], + newEdge: Cell | null, + dx: number = 0, + dy: number = 0, + ) { + return this.creationManager.splitEdge(edge, cells, newEdge, dx, dy) + } +} + +export namespace CreationManager { + export interface AddNodeOptions extends Cell.CreateNodeOptions { + parent?: Cell + index?: number + } + + export interface AddEdgeOptions extends Cell.CreateEdgeOptions { + parent?: Cell + index?: number + source?: Cell + target?: Cell + } +} diff --git a/packages/x6/src/graph/creation-manager.ts b/packages/x6/src/graph/creation-manager.ts new file mode 100644 index 00000000000..ff9046ec89e --- /dev/null +++ b/packages/x6/src/graph/creation-manager.ts @@ -0,0 +1,590 @@ +import * as util from '../util' +import { Cell } from '../core/cell' +import { Point } from '../struct' +import { Graph } from './graph' +import { BaseManager } from './base-manager' + +export class CreationManager extends BaseManager { + addCells( + cells: Cell[], + parent: Cell, + index: number, + sourceNode?: Cell, + targetNode?: Cell, + ) { + this.model.batchUpdate(() => { + this.graph.trigger(Graph.events.addCells, { + cells, + parent, + index, + sourceNode, + targetNode, + }) + + this.cellsAdded(cells, parent, index, sourceNode, targetNode, false, true) + }) + + return cells + } + + cellsAdded( + cells: Cell[], + parent: Cell, + index: number, + sourceNode?: Cell | null, + targetNode?: Cell | null, + absolute?: boolean, + constrain?: boolean, + extend?: boolean, + ) { + if (cells != null && parent != null && index != null) { + this.model.batchUpdate(() => { + const pState = absolute ? this.view.getState(parent) : null + const o1 = pState != null ? pState.origin : null + const zero = new Point(0, 0) + + for (let i = 0, ii = cells.length; i < ii; i += 1) { + if (cells[i] == null) { + index -= 1 // tslint:disable-line + continue + } + + const oldParent = this.model.getParent(cells[i]) + + // Keeps the cell at it's absolute location. + if (o1 != null && cells[i] !== parent && parent !== oldParent) { + const oldState = this.view.getState(oldParent) + const o2 = oldState != null ? oldState.origin : zero + let geo = this.model.getGeometry(cells[i]) + + if (geo != null) { + const dx = o2.x - o1.x + const dy = o2.y - o1.y + + geo = geo.clone() + geo.translate(dx, dy) + + if ( + !geo.relative && + !this.graph.isNegativeCoordinatesAllowed() && + this.model.isNode(cells[i]) + ) { + geo.bounds.x = Math.max(0, geo.bounds.x) + geo.bounds.y = Math.max(0, geo.bounds.y) + } + + this.model.setGeometry(cells[i], geo) + } + } + + // Decrements all following indices if cell is already in parent + if ( + parent === oldParent && + index + i > this.model.getChildCount(parent) + ) { + index -= 1 // tslint:disable-line + } + + this.model.add(parent, cells[i], index + i) + + if (this.graph.autoSizeOnAdded) { + this.graph.sizeManager.autoSizeCell(cells[i], true) + } + + // Extends the parent or constrains the child + if ( + (extend == null || extend) && + this.graph.isExtendParentsOnAdd() && + this.graph.isExtendParent(cells[i]) + ) { + this.graph.sizeManager.extendParent(cells[i]) + } + + // Additionally constrains the child after extending the parent + if (constrain == null || constrain) { + this.graph.sizeManager.constrainChild(cells[i]) + } + + // Sets the source terminal + if (sourceNode != null) { + this.graph.connectionManager.cellConnected( + cells[i], + sourceNode, + true, + ) + } + + // Sets the target terminal + if (targetNode != null) { + this.graph.connectionManager.cellConnected( + cells[i], + targetNode, + false, + ) + } + } + + this.graph.trigger(Graph.events.cellsAdded, { + cells, + parent, + index, + sourceNode, + targetNode, + absolute, + }) + }) + } + } + + duplicateCells(cells: Cell[], append: boolean) { + const model = this.model + const diff = this.graph.getGridSize() + const sources = model.getTopmostCells(cells) + const select: Cell[] = [] + + model.batchUpdate(() => { + const clones = this.cloneCells(sources, false, undefined, true) + for (let i = 0, ii = sources.length; i < ii; i += 1) { + const parent = model.getParent(sources[i]) + const child = this.graph.movingManager.moveCells( + [clones[i]], + diff, + diff, + false, + )[0] + + select.push(child) + + if (append) { + model.add(parent, clones[i]) + } else { + const index = parent!.getChildIndex(sources[i]) + model.add(parent, clones[i], index + 1) + } + } + }) + + this.graph.selectCells(select) + + return select + } + + deleteCells( + cells: Cell[], + includeEdges: boolean, + selectParentAfterDelete: boolean, + ) { + if (cells != null && cells.length > 0) { + const graph = this.graph + this.graph.eventloopManager.escape(new KeyboardEvent('')) + const parents = selectParentAfterDelete + ? graph.model.getParents(cells) + : null + + graph.removeCells(cells, includeEdges) + + // Selects parents for easier editing of groups + if (parents != null) { + const select = parents.filter( + cell => + graph.model.contains(cell) && + (graph.model.isNode(cell) || graph.model.isEdge(cell)), + ) + + graph.selectCells(select) + } + } + } + + removeCells(cells: Cell[], includeEdges: boolean) { + let removing: Cell[] + + if (includeEdges) { + removing = this.graph.getDeletableCells(this.addAllEdges(cells)) + } else { + removing = cells.slice() + + // Removes edges that are currently not + // visible as those cannot be updated + const edges = this.graph.getDeletableCells(this.getAllEdges(cells)) + const dict = new WeakMap() + + cells.forEach(cell => dict.set(cell, true)) + edges.forEach(edge => { + if (this.view.getState(edge) == null && !dict.get(edge)) { + dict.set(edge, true) + removing.push(edge) + } + }) + } + + this.model.batchUpdate(() => { + this.graph.trigger(Graph.events.removeCells, { + includeEdges, + cells: removing, + }) + this.cellsRemoved(removing) + }) + + return removing + } + + /** + * Returns an array with the given cells and all edges that are connected + * to a cell or one of its descendants. + */ + addAllEdges(cells: Cell[]) { + const merged = [...cells, ...this.getAllEdges(cells)] + return util.uniq(merged) + } + + getAllEdges(cells: Cell[]) { + const edges: Cell[] = [] + if (cells != null) { + cells.forEach(cell => { + cell.eachEdge(edge => edges.push(edge)) + const children = this.model.getChildren(cell) + edges.push(...this.getAllEdges(children)) + }) + } + + return edges + } + + cellsRemoved(cells: Cell[]) { + if (cells != null && cells.length > 0) { + this.model.batchUpdate(() => { + const dict = new WeakMap() + cells.forEach(cell => dict.set(cell, true)) + cells.forEach(cell => { + const edges = this.getAllEdges([cell]) + edges.forEach(edge => { + if (!dict.get(edge)) { + dict.set(edge, true) + this.disconnectTerminal(cell, edge, true) + this.disconnectTerminal(cell, edge, false) + } + }) + + this.model.remove(cell) + }) + + this.graph.trigger(Graph.events.cellsRemoved, { cells }) + }) + } + } + + disconnectTerminal(cell: Cell, edge: Cell, isSource: boolean) { + const scale = this.view.scale + const trans = this.view.translate + + let geo = this.model.getGeometry(edge) + if (geo != null) { + // Checks if terminal is being removed + const terminal = this.model.getTerminal(edge, isSource) + let connected = false + let tmp = terminal + + while (tmp != null) { + if (cell === tmp) { + connected = true + break + } + + tmp = this.model.getParent(tmp) + } + + if (connected) { + geo = geo.clone() + const state = this.view.getState(edge) + + if (state != null && state.absolutePoints != null) { + const pts = state.absolutePoints + const n = isSource ? 0 : pts.length - 1 + + geo.setTerminalPoint( + new Point( + pts[n].x / scale - trans.x - state.origin.x, + pts[n].y / scale - trans.y - state.origin.y, + ), + isSource, + ) + } else { + // fallback + const state = this.view.getState(terminal) + if (state != null) { + geo.setTerminalPoint( + new Point( + state.bounds.getCenterX() / scale - trans.x, + state.bounds.getCenterY() / scale - trans.y, + ), + isSource, + ) + } + } + + this.model.setGeometry(edge, geo) + this.model.setTerminal(edge, null, isSource) + } + } + } + + splitEdge( + edge: Cell, + cells: Cell[], + newEdge: Cell | null, + dx: number, + dy: number, + ) { + this.model.batchUpdate(() => { + const parent = this.model.getParent(edge) + const source = this.model.getTerminal(edge, true) + + if (newEdge == null) { + newEdge = this.graph.cloneCell(edge) // tslint:disable-line + + // Removes waypoints before/after new cell + const state = this.view.getState(edge) + let geo = this.graph.getCellGeometry(newEdge) + + if (geo != null && geo.points != null && state != null) { + const t = this.view.translate + const s = this.view.scale + const idx = util.findNearestSegment( + state, + (dx + t.x) * s, + (dy + t.y) * s, + ) + + geo.points = geo.points.slice(0, idx) + + geo = this.graph.getCellGeometry(edge) + + if (geo != null && geo.points != null) { + geo = geo.clone() + geo.points = geo.points.slice(idx) + this.model.setGeometry(edge, geo) + } + } + } + + this.graph.movingManager.cellsMoved(cells, dx, dy, false, false) + + let index = this.model.getChildCount(parent) + this.cellsAdded(cells, parent!, index, null, null, true) + + index = this.model.getChildCount(parent) + this.cellsAdded([newEdge], parent!, index, source, cells[0], false) + this.graph.connectionManager.cellConnected(edge, cells[0], true) + + this.graph.trigger(Graph.events.splitEdge, { + edge, + cells, + newEdge, + dx, + dy, + }) + }) + + return newEdge + } + + cloneCells( + cells: Cell[], + allowInvalidEdges: boolean = true, + mapping: WeakMap = new WeakMap(), + keepPosition: boolean = false, + ) { + let clones: Cell[] = [] + if (cells != null) { + // Creates a dictionary for fast lookups + const dict = new WeakMap() + const tmp = [] + + cells.forEach(cell => { + dict.set(cell, true) + tmp.push(cell) + }) + + if (tmp.length > 0) { + const scale = this.view.scale + const trans = this.view.translate + + clones = this.model.cloneCells(cells, true, mapping) as Cell[] + + for (let i = 0, ii = cells.length; i < ii; i += 1) { + if ( + !allowInvalidEdges && + this.model.isEdge(clones[i]!) && + !this.graph.isEdgeValid( + clones[i], + this.model.getTerminal(clones[i], true), + this.model.getTerminal(clones[i], false), + ) + ) { + const tmp = clones as any + tmp[i] = null + } else { + const geom = this.model.getGeometry(clones[i]!) + if (geom != null) { + const state = this.view.getState(cells[i]) + const pstate = this.view.getState(this.model.getParent(cells[i])!) + + if (state != null && pstate != null) { + const dx = keepPosition ? 0 : pstate.origin.x + const dy = keepPosition ? 0 : pstate.origin.y + + if (this.model.isEdge(clones[i])) { + const pts = state.absolutePoints + if (pts != null) { + // Checks if the source is cloned or sets the terminal point + let source = this.model.getTerminal(cells[i], true) + while (source != null && !dict.get(source)) { + source = this.model.getParent(source) + } + + if (source == null && pts[0] != null) { + geom.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 target = this.model.getTerminal(cells[i], false) + while (target != null && !dict.get(target)) { + target = this.model.getParent(target) + } + + const n = pts.length - 1 + + if (target == null && pts[n] != null) { + geom.setTerminalPoint( + new Point( + pts[n]!.x / scale - trans.x, + pts[n]!.y / scale - trans.y, + ), + false, + ) + } + + // Translates the control points + geom.points && + geom.points.forEach(p => { + p.x += dx + p.y += dy + }) + } + } else { + geom.translate(dx, dy) + } + } + } + } + } + } + } + + return clones + } + + turnCells(cells: Cell[]) { + const model = this.model + const select: Cell[] = [] + + model.batchUpdate(() => { + for (let i = 0, ii = cells.length; i < ii; i += 1) { + const cell = cells[i] + + if (model.isEdge(cell)) { + const src = model.getTerminal(cell, true) + const trg = model.getTerminal(cell, false) + + model.setTerminal(cell, trg, true) + model.setTerminal(cell, src, false) + + let geo = model.getGeometry(cell) + if (geo != null) { + geo = geo.clone() + + if (geo.points != null) { + geo.points.reverse() + } + + const sp = geo.getTerminalPoint(true) + const tp = geo.getTerminalPoint(false) + + geo.setTerminalPoint(sp, false) + geo.setTerminalPoint(tp, true) + model.setGeometry(cell, geo) + + // Inverts anchors + const edgeState = this.view.getState(cell) + const sourceState = this.view.getState(src) + const targetState = this.view.getState(trg) + + if (edgeState != null) { + const sc = + sourceState != null + ? this.graph.getConnectionAnchor(edgeState, sourceState, true) + : null + const tc = + targetState != null + ? this.graph.getConnectionAnchor( + edgeState, + targetState, + false, + ) + : null + + this.graph.setConnectionAnchor(cell, src, true, tc) + this.graph.setConnectionAnchor(cell, trg, false, sc) + } + + select.push(cell) + } + } else if (model.isNode(cell)) { + let geo = model.getGeometry(cell) + if (geo != null) { + // Rotates the size and position in the geometry + geo = geo.clone() + const bounds = geo.bounds + bounds.x += bounds.width / 2 - bounds.height / 2 + bounds.y += bounds.height / 2 - bounds.width / 2 + const tmp = bounds.width + bounds.width = bounds.height + bounds.height = tmp + model.setGeometry(cell, geo) + + // Reads the current direction and advances by 90 degrees + const state = this.view.getState(cell) + if (state != null) { + const style = state.style + let direction = style.direction || 'east' + if (direction === 'east') { + direction = 'south' + } else if (direction === 'south') { + direction = 'west' + } else if (direction === 'west') { + direction = 'north' + } else if (direction === 'north') { + direction = 'east' + } + + this.graph.setCellStyle({ ...style, direction }, [cell]) + } + + select.push(cell) + } + } + } + }) + + this.graph.selectCells(select) + + return select + } +} diff --git a/packages/x6/src/graph/decorator.ts b/packages/x6/src/graph/decorator.ts index da0f8abba81..db3832d3f33 100644 --- a/packages/x6/src/graph/decorator.ts +++ b/packages/x6/src/graph/decorator.ts @@ -1,19 +1,19 @@ import * as util from '../util' -import { Graph } from './graph' +import { BaseGraph } from './base-graph' export function hook( hookName?: string | null, ignoreNullResult: boolean = false, ) { return ( - target: Graph, + target: BaseGraph, methodName: string, descriptor: PropertyDescriptor, ) => { const raw = descriptor.value const name = hookName || methodName - descriptor.value = function(this: Graph, ...args: any[]) { + descriptor.value = function(this: BaseGraph, ...args: any[]) { const hook = (this.options as any)[name] if (hook != null) { this.getNativeValue = raw.bind(this, ...args) @@ -32,14 +32,14 @@ export function hook( export function afterCreate(aopName?: string | null) { return ( - target: Graph, + target: BaseGraph, methodName: string, descriptor: PropertyDescriptor, ) => { const raw = descriptor.value const name = aopName || `on${util.ucFirst(methodName)}` - descriptor.value = function(this: Graph, ...args: any[]) { + descriptor.value = function(this: BaseGraph, ...args: any[]) { const instance = raw.call(this, ...args) const aop = (this.options as any)[name] if (aop != null) { diff --git a/packages/x6/src/graph/editing-accessor.ts b/packages/x6/src/graph/editing-accessor.ts new file mode 100644 index 00000000000..0b89d06331a --- /dev/null +++ b/packages/x6/src/graph/editing-accessor.ts @@ -0,0 +1,64 @@ +import { Cell } from '../core/cell' +import { DomEvent } from '../common' +import { hook } from './decorator' +import { events } from './events' +import { BaseGraph } from './base-graph' + +export class EditingAccessor extends BaseGraph { + @hook() + isCellEditable(cell: Cell) { + const style = this.getStyle(cell) + return ( + this.isCellsEditable() && + !this.isCellLocked(cell) && + style.editable !== false + ) + } + + @hook() + getEditingContent(cell: Cell, e?: Event) { + return this.dataToString(cell) + } + + startEditing(e?: MouseEvent) { + this.startEditingAtCell(null, e) + return this + } + + stopEditing(cancel: boolean = false) { + this.cellEditor.stopEditing(cancel) + this.trigger(events.editingStopped, { cancel }) + return this + } + + startEditingAtCell( + cell: Cell | null = this.getSelectedCell(), + e?: MouseEvent, + ) { + if (e == null || !DomEvent.isMultiTouchEvent(e)) { + if (cell != null && this.isCellEditable(cell)) { + this.trigger(events.startEditing, { cell, e }) + this.cellEditor.startEditing(cell, e) + this.trigger(events.editingStarted, { cell, e }) + } + } + return this + } + + /** + * Returns true if the given cell is currently being edited. + */ + isEditing(cell?: Cell) { + if (this.cellEditor != null) { + const editingCell = this.cellEditor.getEditingCell() + return cell == null ? editingCell != null : cell === editingCell + } + + return false + } + + updateLabel(cell: Cell, label: string, e?: Event) { + this.editingManager.updateLabel(cell, label, e) + return this + } +} diff --git a/packages/x6/src/graph/editing-manager.ts b/packages/x6/src/graph/editing-manager.ts new file mode 100644 index 00000000000..7322313249d --- /dev/null +++ b/packages/x6/src/graph/editing-manager.ts @@ -0,0 +1,24 @@ +import { Cell } from '../core/cell' +import { events } from './events' +import { BaseManager } from './base-manager' + +export class EditingManager extends BaseManager { + updateLabel(cell: Cell, label: string, e?: Event) { + this.model.batchUpdate(() => { + const old = cell.data + const data = this.graph.putLabel(cell, label) + this.dataChanged(cell, data, this.graph.isAutoSizeCell(cell)) + this.graph.trigger(events.labelChanged, { cell, data, old, e }) + }) + return cell + } + + protected dataChanged(cell: Cell, data: any, autoSize: boolean) { + this.model.batchUpdate(() => { + this.model.setData(cell, data) + if (autoSize) { + this.graph.sizeManager.cellSizeUpdated(cell, false) + } + }) + } +} diff --git a/packages/x6/src/graph/eventloop-accessor.ts b/packages/x6/src/graph/eventloop-accessor.ts new file mode 100644 index 00000000000..f285999a41a --- /dev/null +++ b/packages/x6/src/graph/eventloop-accessor.ts @@ -0,0 +1,68 @@ +import { Cell } from '../core/cell' +import { IMouseHandler } from '../handler' +import { DomEvent, MouseEventEx, detector } from '../common' +import { hook } from './decorator' +import { BaseGraph } from './base-graph' + +export class EventLoopAccessor extends BaseGraph { + /** + * Adds a listener to the graph event dispatch loop. The listener + * must implement the mouseDown, mouseMove and mouseUp + */ + addMouseListener(handler: IMouseHandler) { + this.eventloopManager.addMouseListener(handler) + return this + } + + removeMouseListener(handler: IMouseHandler) { + this.eventloopManager.removeMouseListener(handler) + return this + } + + getPointForEvent(e: MouseEvent, addOffset: boolean = true) { + return this.eventloopManager.getPointForEvent(e, addOffset) + } + + /** + * Dispatches the given event to the graph event dispatch loop. + */ + fireMouseEvent(eventName: string, e: MouseEventEx, sender: any = this) { + this.eventloopManager.fireMouseEvent(eventName, e, sender) + return this + } + + fireGestureEvent(e: MouseEvent, cell?: Cell) { + this.eventloopManager.fireGestureEvent(e, cell) + return this + } + + @hook() + isCloneEvent(e: MouseEvent) { + return DomEvent.isControlDown(e) + } + + @hook() + isToggleEvent(e: MouseEvent) { + return detector.IS_MAC ? DomEvent.isMetaDown(e) : DomEvent.isControlDown(e) + } + + @hook() + isConstrainedEvent(e: MouseEvent) { + return DomEvent.isShiftDown(e) + } + + @hook() + isGridEnabledForEvent(e: MouseEvent) { + return e != null && !DomEvent.isAltDown(e) + } + + @hook() + isConnectionIgnored(e: MouseEvent) { + return false + } + + @hook() + isTransparentClickEvent(e: MouseEvent) { + return false + } +} diff --git a/packages/x6/src/manager/eventloop.ts b/packages/x6/src/graph/eventloop-manager.ts similarity index 96% rename from packages/x6/src/manager/eventloop.ts rename to packages/x6/src/graph/eventloop-manager.ts index cf7f38e260b..5dfae3007b1 100644 --- a/packages/x6/src/manager/eventloop.ts +++ b/packages/x6/src/graph/eventloop-manager.ts @@ -1,15 +1,15 @@ import * as util from '../util' -import { Graph } from '../graph' import { Cell } from '../core/cell' import { State } from '../core/state' import { Point } from '../struct' import { RectangleShape } from '../shape' -import { DomEvent, MouseEventEx, detector } from '../common' +import { detector, DomEvent, MouseEventEx } from '../common' import { IMouseHandler, ConnectionHandler } from '../handler' -import { ManagerBase } from './base' +import { events } from './events' +import { BaseManager } from './base-manager' -export class EventLoop extends ManagerBase { - protected mouseListeners: IMouseHandler[] +export class EventLoopManager extends BaseManager { + protected mouseListeners: IMouseHandler[] = [] protected lastMouseX: number protected lastMouseY: number @@ -38,11 +38,6 @@ export class EventLoop extends ManagerBase { protected initialTouchX: number = 0 protected initialTouchY: number = 0 - constructor(graph: Graph) { - super(graph) - this.mouseListeners = [] - } - addMouseListener(handler: IMouseHandler) { if (!this.mouseListeners.includes(handler)) { this.mouseListeners.push(handler) @@ -369,7 +364,7 @@ export class EventLoop extends ManagerBase { // Updates the event state via getEventState e.state = this.getEventState(e.getState()) - this.graph.trigger(Graph.events.fireMouseEvent, { eventName, e, sender }) + this.graph.trigger(events.fireMouseEvent, { eventName, e, sender }) if ( detector.IS_OPERA || @@ -471,11 +466,11 @@ export class EventLoop extends ManagerBase { fireGestureEvent(e: MouseEvent, cell?: Cell) { // Resets double tap event handling when gestures take place this.lastTouchTime = 0 - this.graph.trigger(Graph.events.gesture, { e, cell }) + this.graph.trigger(events.gesture, { e, cell }) } escape(e: KeyboardEvent) { - this.graph.trigger(Graph.events.escape, { e }) + this.graph.trigger(events.escape, { e }) } click(e: MouseEventEx) { @@ -483,7 +478,7 @@ export class EventLoop extends ManagerBase { const consumed = e.isConsumed() let cell = e.getCell() - this.graph.trigger(Graph.events.click, { cell, e: evt }) + this.graph.trigger(events.click, { cell, e: evt }) // Handles the event if it has not been consumed if (this.graph.isEnabled() && !DomEvent.isConsumed(evt) && !consumed) { @@ -531,7 +526,7 @@ export class EventLoop extends ManagerBase { } dblClick(e: MouseEvent, cell?: Cell | null) { - this.graph.trigger(Graph.events.dblclick, { e, cell }) + this.graph.trigger(events.dblclick, { e, cell }) // Handles the event if it has not been consumed if ( @@ -548,7 +543,7 @@ export class EventLoop extends ManagerBase { tapAndHold(e: MouseEventEx) { const evt = e.getEvent() - this.graph.trigger(Graph.events.tapAndHold, { e }) + this.graph.trigger(events.tapAndHold, { e }) if (DomEvent.isConsumed(evt)) { // Resets the state of the panning handler diff --git a/packages/x6/src/graph/events.ts b/packages/x6/src/graph/events.ts new file mode 100644 index 00000000000..31062bba971 --- /dev/null +++ b/packages/x6/src/graph/events.ts @@ -0,0 +1,46 @@ +export const events = { + refresh: 'refresh', + root: 'root', + + addCells: 'addCells', + cellsAdded: 'cellsAdded', + removeCells: 'removeCells', + cellsRemoved: 'cellsRemoved', + removeCellsFromParent: 'removeCellsFromParent', + connectCell: 'connectCell', + cellConnected: 'cellConnected', + groupCells: 'groupCells', + ungroupCells: 'ungroupCells', + splitEdge: 'splitEdge', + updateCellSize: 'updateCellSize', + resizeCells: 'resizeCells', + cellsResized: 'cellsResized', + addOverlay: 'addOverlay', + removeOverlay: 'removeOverlay', + removeOverlays: 'removeOverlays', + foldCells: 'foldCells', + cellsFolded: 'cellsFolded', + orderCells: 'orderCells', + cellsOrdered: 'cellsOrdered', + toggleCells: 'toggleCells', + flipEdge: 'flipEdge', + alignCells: 'alignCells', + moveCells: 'moveCells', + cellsMoved: 'cellsMoved', + + startEditing: 'startEditing', + editingStarted: 'editingStarted', + editingStopped: 'editingStopped', + labelChanged: 'labelChanged', + size: 'size', + + click: 'click', + dblclick: 'dblclick', + tapAndHold: 'tapAndHold', + escape: 'escape', + + pan: 'pan', + gesture: 'gesture', + fireMouseEvent: 'fireMouseEvent', + selectionChanged: 'selectionChanged', +} diff --git a/packages/x6/src/graph/graph.ts b/packages/x6/src/graph/graph.ts index 8b02ba0c2c8..8d614dc29a1 100644 --- a/packages/x6/src/graph/graph.ts +++ b/packages/x6/src/graph/graph.ts @@ -1,58 +1,41 @@ import * as util from '../util' -import { Route } from '../route' -import { Cell } from '../core/cell' import { View } from '../core/view' import { Model } from '../core/model' -import { State } from '../core/state' -import { Geometry } from '../core/geometry' import { Renderer } from '../core/renderer' -import { Align, VAlign, Style, Size } from '../types' -import { detector, DomEvent, MouseEventEx } from '../common' -import { GraphOptions, getOptions, globals } from '../option' -import { Rectangle, Point, Anchor, Image, Overlay } from '../struct' -import { - CellEditor, - IMouseHandler, - TooltipHandler, - CursorHandler, - KeyboardHandler, - ContextMenuHandler, - PanningHandler, - GuideHandler, - SelectHandler, - MovingHandler, - SelectionHandler, - ConnectionHandler, - RubberbandHandler, - NodeHandler, - EdgeHandler, - EdgeElbowHandler, - EdgeSegmentHandler, -} from '../handler' -import { - ChangeManager, - EventLoop, - Selection, - SelectionManager, - ValidationManager, - ViewportManager, - CellManager, -} from '../manager' +import { detector, DomEvent } from '../common' +import { GraphOptions, getOptions } from '../option' +import { hook } from './decorator' import { IHooks } from './hook' -import { GraphProp } from './prop' -import { hook, afterCreate } from './decorator' - -export class Graph extends GraphProp implements IHooks { - public panDx: number = 0 - public panDy: number = 0 - - /** - * Get the native value of hooked method. - */ - public getNativeValue: () => T | null - +import { Selection } from './selection' +import { BaseGraph } from './base-graph' +import { RetrievalAccessor } from './retrieval-accessor' +import { OverlayAccessor } from './overlay-accessor' +import { ConnectionAccessor } from './connection-accessor' +import { TooltipAccessor } from './tooltip-accessor' +import { RubberbandAccessor } from './rubberband-accessor' +import { CollapseAccessor } from './collapse-accessor' +import { KeyboardAccessor } from './keyboard-accessor' +import { GuideAccessor } from './guide-accessor' +import { GridAccessor } from './grid-accessor' +import { PageBreakAccessor } from './pagebreak-accessor' +import { PanningAccessor } from './panning-accessor' +import { SelectionAccessor } from './selection-accessor' +import { CreationAccessor } from './creation-accessor' +import { GroupAccessor } from './group-accessor' +import { ValidationAccessor } from './validation-accessor' +import { StyleAccessor } from './style-accessor' +import { EventLoopAccessor } from './eventloop-accessor' +import { ZoomAccessor } from './zoom-accessor' +import { ViewportAccessor } from './viewport-accessor' +import { SizeAccessor } from './size-accessor' +import { EditingAccessor } from './editing-accessor' +import { CommonAccessor } from './common-accessor' +import { MovingAccessor } from './moving-accessor' + +export class Graph extends BaseGraph implements IHooks { constructor(container: HTMLElement, options: Graph.Options = {}) { super() + this.options = getOptions(options) this.container = container this.model = options.model || this.createModel() @@ -60,27 +43,8 @@ export class Graph extends GraphProp implements IHooks { this.renderer = this.createRenderer() this.selection = this.createSelection() - this.cellEditor = new CellEditor(this) - this.changeManager = new ChangeManager(this) - this.eventloop = new EventLoop(this) - this.cellManager = new CellManager(this) - this.selectionManager = new SelectionManager(this) - this.validator = new ValidationManager(this) - this.viewport = new ViewportManager(this) - - // The order of the following initializations should not be modified. - this.tooltipHandler = this.createTooltipHandler() - this.cursorHandler = this.createCursorHandler() - this.selectionHandler = this.createSelectionHandler() - this.connectionHandler = this.createConnectionHandler() - this.guideHandler = this.createGuideHandler() - this.selectHandler = this.createSelectHandler() - this.movingHandler = this.createMovingHandler() - this.panningHandler = this.createPanningHandler() - this.panningHandler.disablePanning() - this.contextMenuHandler = this.createContextMenuHandler() - this.rubberbandHandler = this.createRubberbandHandler() - this.keyboardHandler = this.createKeyboardHandler() + super.createManagers() + super.createHandlers() if (container != null) { this.init(container) @@ -89,8 +53,6 @@ export class Graph extends GraphProp implements IHooks { this.view.revalidate() } - // #region :::::::::::: init - protected init(container: HTMLElement) { this.view.init() this.sizeDidChange() @@ -99,7 +61,7 @@ export class Graph extends GraphProp implements IHooks { DomEvent.addListener(container, 'selectstart', (e: MouseEvent) => { return ( this.isEditing() || - (!this.eventloop.isMouseDown && !DomEvent.isShiftDown(e)) + (!this.eventloopManager.isMouseDown && !DomEvent.isShiftDown(e)) ) }) } @@ -126,2386 +88,62 @@ export class Graph extends GraphProp implements IHooks { createSelection() { return new Selection(this) } +} - @hook() - createKeyboardHandler() { - return new KeyboardHandler(this) - } - - @hook() - createTooltipHandler() { - return new TooltipHandler(this) - } - - @hook() - createCursorHandler() { - return new CursorHandler(this) - } - - @hook() - createGuideHandler() { - return new GuideHandler(this) - } - - @hook() - createSelectHandler() { - return new SelectHandler(this) - } - - @hook() - createConnectionHandler() { - return new ConnectionHandler(this) - } - - @hook() - createSelectionHandler() { - return new SelectionHandler(this) - } - - @hook() - createMovingHandler() { - return new MovingHandler(this) - } - - @hook() - createPanningHandler() { - return new PanningHandler(this) - } - - @hook() - createContextMenuHandler() { - return new ContextMenuHandler(this) - } - - @hook() - createRubberbandHandler() { - return new RubberbandHandler(this) - } - - @hook(null, true) - createCellHandler(state: State | null) { - if (state != null) { - if (this.model.isEdge(state.cell)) { - const sourceState = state.getVisibleTerminalState(true) - const targetState = state.getVisibleTerminalState(false) - const geo = this.getCellGeometry(state.cell) - - const edgeFn = this.view.getRoute( - state, - geo != null ? geo.points : null, - sourceState!, - targetState!, - ) - return this.createEdgeHandler(state, edgeFn) - } - - return this.createNodeHandler(state) - } - - return null - } - - @hook() - createNodeHandler(state: State) { - return new NodeHandler(this, state) - } - - @hook() - createEdgeHandler(state: State, edgeFn: Route.Router | null) { - let result = null - - if ( - edgeFn === Route.loop || - edgeFn === Route.elbow || - edgeFn === Route.sideToSide || - edgeFn === Route.topToBottom - ) { - result = this.createElbowEdgeHandler(state) - } else if (edgeFn === Route.segment || edgeFn === Route.orth) { - result = this.createEdgeSegmentHandler(state) - } else { - return ( - util.call(this.options.createEdgeHandler, this, this, state) || - new EdgeHandler(this, state) - ) - } - - return result - } - - @hook() - createEdgeSegmentHandler(state: State) { - return new EdgeSegmentHandler(this, state) - } - - @hook() - createElbowEdgeHandler(state: State) { - return new EdgeElbowHandler(this, state) - } - - // #endregion - - // #region :::::::::::: Cell Creation - - @afterCreate() - createNode(options: Cell.CreateNodeOptions = {}): Cell { - return Cell.createNode(options) - } - - @afterCreate() - createEdge(options: Cell.CreateEdgeOptions = {}): Cell { - return Cell.createEdge(options) - } - - addNode(options: Graph.AddNodeOptions): Cell - addNode(node: Cell, parent?: Cell, index?: number): Cell - addNode( - node?: Cell | Graph.AddNodeOptions, - parent?: Cell, - index?: number, - ): Cell { - if (node instanceof Cell) { - return this.addNodes([node], parent, index)[0] - } - - const options = node != null ? node : {} - const { parent: p, index: i, ...others } = options - const cell = this.createNode(others) - return this.addNodes([cell], p, i)[0] - } - - addNodes(nodes: Cell[], parent?: Cell, index?: number): Cell[] { - return this.addCells(nodes, parent, index) - } - - addEdge(options: Graph.AddEdgeOptions): Cell - addEdge( - edge: Cell, - parent?: Cell, - source?: Cell, - target?: Cell, - index?: number, - ): Cell - addEdge( - edge?: Cell | Graph.AddEdgeOptions, - parent?: Cell, - source?: Cell, - target?: Cell, - index?: number, - ) { - if (edge instanceof Cell) { - return this.addCell(edge, parent, index, source, target) - } - const options = edge != null ? edge : {} - const cell = this.createEdge(options) - - this.addCell( - cell, - options.parent, - options.index, - options.source, - options.target, - ) - - if (this.resetEdgesOnConnect && options.points != null) { - options.points.forEach(p => cell.geometry!.addPoint(p)) - } - - return cell - } - - /** - * Adds the cell to the parent and connects it to the given source and - * target terminals. - * - * @param cell - `Cell` to be inserted into the given parent. - * @param parent - `Cell` that represents the new parent. If no parent is - * given then the default parent is used. - * @param index - Optional index to insert the cells at. - * @param source - Optional `Cell` that represents the source terminal. - * @param target - Optional `Cell` that represents the target terminal. - */ - addCell( - cell: Cell, - parent?: Cell, - index?: number, - source?: Cell, - target?: Cell, - ) { - return this.addCells([cell], parent, index, source, target)[0] - } - - /** - * Adds the cells to the parent at the given index, connecting each cell to - * the optional source and target terminal. - * - * @param cells - Array of `Cell`s to be inserted. - * @param parent - `Cell` that represents the new parent. If no parent is - * given then the default parent is used. - * @param index - Optional index to insert the cells at. - * @param source - Optional source `Cell` for all inserted cells. - * @param target - Optional target `Cell` for all inserted cells. - */ - addCells( - cells: Cell[], - parent: Cell = this.getDefaultParent()!, - index: number = this.model.getChildCount(parent), - source?: Cell, - target?: Cell, - ) { - return this.cellManager.addCells(cells, parent, index, source, target) - } - - duplicateCells( - cells: Cell[] = this.getSelectedCells(), - append: boolean = true, - ) { - return this.cellManager.duplicateCells(cells, append) - } - - turnCells(cells: Cell[] = this.getSelectedCells()) { - return this.cellManager.turnCells(cells) - } - - deleteCells( - cells: Cell[] = this.getDeletableCells(this.getSelectedCells()), - includeEdges: boolean = true, - selectParentAfterDelete: boolean = true, - ) { - return this.cellManager.deleteCells( - cells, - includeEdges, - selectParentAfterDelete, - ) - } - - /** - * Removes the given cells from the graph including all connected - * edges if `includeEdges` is `true`. - */ - removeCells( - cells: Cell[] = this.getDeletableCells(this.getSelectedCells()), - includeEdges: boolean = true, - ) { - return this.cellManager.removeCells(cells, includeEdges) - } - - /** - * Splits the given edge by adding the newEdge between the previous source - * and the given cell and reconnecting the source of the given edge to the - * given cell. - * - * @param edge The edge to be splitted. - * @param cells The cells to insert into the edge. - * @param newEdge The edge to be inserted. - * @param dx The vector to move the cells. - * @param dy The vector to move the cells. - */ - splitEdge( - edge: Cell, - cells: Cell[], - newEdge: Cell | null, - dx: number = 0, - dy: number = 0, - ) { - return this.cellManager.splitEdge(edge, cells, newEdge, dx, dy) - } - - /** - * Returns the clone for the given cell. - * - * @param cell `Cell` to be cloned. - * @param allowInvalidEdges Optional boolean that specifies if invalid - * edges should be cloned. Default is `true`. - * @param mapping Optional mapping for existing clones. - * @param keepPosition Optional boolean indicating if the position - * of the cells should be updated to reflect the lost parent cell. - * Default is `false`. - */ - cloneCell( - cell: Cell, - allowInvalidEdges: boolean = true, - mapping: WeakMap = new WeakMap(), - keepPosition: boolean = false, - ) { - return this.cloneCells([cell], allowInvalidEdges, mapping, keepPosition)[0] - } - - /** - * Returns the clones for the given cells. If the terminal of an edge is - * not in the given array, then the respective end is assigned a terminal - * point and the terminal is removed. - * - * @param cells - Array of `Cell`s to be cloned. - * @param allowInvalidEdges - Optional boolean that specifies if - * invalid edges should be cloned. Default is `true`. - * @param mapping - Optional mapping for existing clones. - * @param keepPosition - Optional boolean indicating if the position - * of the cells should be updated to reflect the lost parent cell. - * Default is `false`. - */ - cloneCells( - cells: Cell[], - allowInvalidEdges: boolean = true, - mapping: WeakMap = new WeakMap(), - keepPosition: boolean = false, - ) { - return this.cellManager.cloneCells( - cells, - allowInvalidEdges, - mapping, - keepPosition, - ) - } - - // #endregion - - // #region :::::::::::: Cell Connecting - - /** - * Connects the specified end of the given edge to the given terminal. - * - * @param edge - The edge will be updated. - * @param terminal - The new terminal to be used. - * @param isSource - Indicating if the new terminal is the source or target. - * @param anchor - Optional `Anchor` to be used for this connection. - */ - connectCell( - edge: Cell, - terminal: Cell | null, - isSource: boolean, - anchor?: Anchor, - ) { - return this.cellManager.connectCell(edge, terminal, isSource, anchor) - } - - /** - * Disconnects the given edges from the terminals which are not in the - * given array. - */ - disconnectGraph(cells: Cell[]) { - return this.cellManager.disconnectGraph(cells) - } - - @hook() - getAnchors(terminal: Cell, isSource: boolean) { - const state = this.view.getState(terminal) - if (state != null && state.shape != null && state.shape.stencil != null) { - return state.shape.stencil.anchors - } - - return null - } - - /** - * Returns an `Anchor` that describes the given connection point. - */ - getConnectionAnchor( - edgeState: State, - terminalState?: State | null, - isSource: boolean = false, - ) { - return this.cellManager.getConnectionAnchor( - edgeState, - terminalState, - isSource, - ) - } - - setConnectionAnchor( - edge: Cell, - terminal: Cell | null, - isSource: boolean, - anchor?: Anchor | null, - ) { - return this.cellManager.setConnectionAnchor( - edge, - terminal, - isSource, - anchor, - ) - } - - // #endregion - - // #region :::::::::::: Cell Moving - - moveCell( - cell: Cell, - dx: number = 0, - dy: number = 0, - clone: boolean = false, - target?: Cell | null, - e?: MouseEvent, - cache?: WeakMap, - ) { - return this.moveCells([cell], dx, dy, clone, target, e, cache) - } - - /** - * Moves or clones the specified cells and moves the cells or clones by the - * given amount, adding them to the optional target cell. - * - * @param cells Array of `Cell`s to be moved, cloned or added to the target. - * @param dx Specifies the x-coordinate of the vector. Default is `0`. - * @param dy Specifies the y-coordinate of the vector. Default is `0`. - * @param clone Indicating if the cells should be cloned. Default is `false`. - * @param target The new parent of the cells. - * @param e Mouseevent that triggered the invocation. - * @param cache Optional mapping for existing clones. - */ - moveCells( - cells: Cell[], - dx: number = 0, - dy: number = 0, - clone: boolean = false, - target?: Cell | null, - e?: MouseEvent, - cache?: WeakMap, - ) { - return this.cellManager.moveCells(cells, dx, dy, clone, target, e, cache) - } - - /** - * Clones and inserts the given cells into the graph. - * - * @param cells Array of `Cell`s to be cloned. - * @param dx Specifies the x-coordinate of the vector. Default is `0`. - * @param dy Specifies the y-coordinate of the vector. Default is `0`. - * @param target The new parent of the cells. - * @param e Mouseevent that triggered the invocation. - * @param cache Optional mapping for existing clones. - */ - importCells( - cells: Cell[], - dx: number, - dy: number, - target?: Cell, - e?: MouseEvent, - cache?: WeakMap, - ) { - return this.moveCells(cells, dx, dy, true, target, e, cache) - } - - /** - * 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) { - this.cellManager.translateCell(cell, dx, dy) - } - - /** - * Resets the control points of the edges that are connected to the given - * cells if not both ends of the edge are in the given cells array. - */ - resetOtherEdges(cells: Cell[]) { - this.cellManager.resetOtherEdges(cells) - return this - } - - /** - * Resets the control points of the given edge. - */ - resetEdge(edge: Cell) { - this.cellManager.resetEdge(edge) - return this - } - - // #endregion - - // #region :::::::::::: Cell Style - - getStyle(cell: Cell | null) { - const state = this.view.getState(cell) - return state != null ? state.style : this.getCellStyle(cell) - } - - /** - * Returns a key-value pair object representing the cell style for - * the given cell. - * - * Note: You should try to use the cached style in the state before - * using this method. - */ - getCellStyle(cell: Cell | null) { - return this.cellManager.getCellStyle(cell) - } - - /** - * Sets the style of the specified cells. If no cells are given, then - * the current selected cells are changed. - */ - setCellStyle(style: Style, cells: Cell[] = this.getSelectedCells()) { - this.cellManager.setCellStyle(style, cells) - return this - } - - /** - * Toggles the boolean value for the given key in the style of the given - * cell and returns the new value. Optional boolean default value if no - * value is defined. If no cell is specified then the current selected - * cell is used. - */ - toggleCellStyle( - key: string, - defaultValue: boolean = false, - cell: Cell = this.getSelectedCell(), - ) { - return this.toggleCellsStyle(key, defaultValue, [cell]) - } - - /** - * Toggles the boolean value for the given key in the style of the given - * cells and returns the new value. If no cells are specified, then the - * current selected cells are used. - */ - toggleCellsStyle( - key: string, - defaultValue: boolean = false, - cells: Cell[] = this.getSelectedCells(), - ) { - return this.cellManager.toggleCellsStyle(key, defaultValue, cells) - } - - updateStyle(style: Style, cell?: Cell): this - updateStyle(style: Style, cells?: Cell[]): this - updateStyle( - key: string, - value?: string | number | boolean | null, - cell?: Cell, - ): this - updateStyle( - key: string, - value?: string | number | boolean | null, - cells?: Cell[], - ): this - updateStyle( - key: string | Style, - value?: (string | number | boolean | null) | Cell | Cell[], - cells?: Cell | Cell[], - ) { - const style: Style = typeof key === 'string' ? { [key]: value } : key - let targets = (typeof key === 'string' ? cells : value) as Cell | Cell[] - if (targets == null) { - targets = this.getSelectedCells() - } - if (!util.isArray(targets)) { - targets = [targets as Cell] - } - - Object.keys(style).forEach(name => { - this.updateCellsStyle(name, (style as any)[name], targets as Cell[]) - }) - - return this - } - - updateCellStyle( - key: string, - value?: string | number | boolean | null, - cell: Cell = this.getSelectedCell(), - ) { - this.updateCellsStyle(key, value, [cell]) - return this - } - - /** - * Sets the key to value in the styles of the given cells. This will modify - * the existing cell styles in-place and override any existing assignment - * for the given key. If no cells are specified, then the selection cells - * are changed. If no value is specified, then the respective key is - * removed from the styles. - */ - updateCellsStyle( - key: string, - value?: string | number | boolean | null, - cells: Cell[] = this.getSelectedCells(), - ) { - this.cellManager.updateCellsStyle(key, value, cells) - return this - } - - /** - * Toggles the given bit for the given key in the styles of the specified - * cells. - */ - toggleCellsStyleFlag( - key: string, - flag: number, - cells: Cell[] = this.getSelectedCells(), - ) { - this.setCellsStyleFlag(key, flag, null, cells) - return this - } - - /** - * Sets or toggles the given bit for the given key in the styles of the - * specified cells. - */ - setCellsStyleFlag( - key: string, - flag: number, - value: boolean | null, - cells: Cell[] = this.getSelectedCells(), - ) { - this.cellManager.setCellsStyleFlag(key, flag, value, cells) - return this - } - - toggleCellsLocked(cells: Cell[] = this.getSelectedCells()) { - this.cellManager.toggleCellsLocked(cells) - return this - } - - // #endregion - - // #region :::::::::::: Cell Visibility - - toggleCells( - show: boolean, - cells: Cell[] = this.getSelectedCells(), - includeEdges: boolean = true, - ) { - return this.cellManager.toggleCells(show, cells, includeEdges) - } - - // #endregion - - // #region :::::::::::: Cell Grouping - - @afterCreate() - createGroup(cells: Cell[]) { - const group = new Cell({ connectable: false }) - group.asNode(true) - return group - } - - /** - * Adds the cells into the given group. - * - * @param group The target group. If null is specified then a new group - * is created. - * @param border Optional integer that specifies the border between the - * child area and the group bounds. Default is `0`. - * @param cells Optional array of `Cell`s to be grouped. If null is - * specified then the selection cells are used. - */ - groupCells( - group: Cell | null = null, - border: number = 0, - cells: Cell[] = Cell.sortCells(this.getSelectedCells(), true), - ) { - return this.cellManager.groupCells(group!, border, cells) - } - - ungroup(group: Cell) { - return this.ungroups([group]) - } - - /** - * Ungroups the given cells by moving the children to their parents parent - * and removing the empty groups. Returns the children that have been - * removed from the groups. - * - * @param cells Array of cells to be ungrouped. If null is specified then - * the selection cells are used. - */ - ungroups(cells?: Cell[]) { - return this.cellManager.ungroupCells(cells) - } - - /** - * Updates the bounds of the given groups to include all children and - * returns the passed-in cells. Call this with the groups in parent to child order, - * top-most group first, the cells are processed in reverse order and cells - * with no children are ignored. - * - * @param cells - The groups whose bounds should be updated. If this is - * null, then the selection cells are used. - * @param border - Optional border to be added in the group. Default is `0`. - * @param moveGroup - Optional boolean that allows the group to be moved. - * Default is `false`. - * @param topBorder - Optional top border to be added in the group. - * Default is `0`. - * @param rightBorder - Optional top border to be added in the group. - * Default is `0`. - * @param bottomBorder - Optional top border to be added in the group. - * Default is `0`. - * @param leftBorder - Optional top border to be added in the group. - * Default is `0`. - */ - updateGroupBounds( - cells: Cell[] = this.getSelectedCells(), - border: number = 0, - moveGroup: boolean = false, - topBorder: number = 0, - rightBorder: number = 0, - bottomBorder: number = 0, - leftBorder: number = 0, - ) { - return this.cellManager.updateGroupBounds( - cells, - border, - moveGroup, - topBorder, - rightBorder, - bottomBorder, - leftBorder, - ) - } - - /** - * Removes the specified cells from their parents and adds them to the - * default parent. Returns the cells that were removed from their parents. - */ - removeCellsFromParent(cells: Cell[] = this.getSelectedCells()) { - return this.cellManager.removeCellsFromParent(cells) - } - - // #endregion - - // #region :::::::::::: Cell Sizing - - /** - * Resizes the specified cell to just fit around the its label - * and/or children. - * - * @param cell `Cells` to be resized. - * @param recurse Optional boolean which specifies if all descendants - * should be autosized. Default is `true`. - */ - autoSizeCell(cell: Cell, recurse: boolean = true) { - return this.cellManager.autoSizeCell(cell, recurse) - } - - /** - * Sets the bounds of the given cell. - */ - resizeCell(cell: Cell, bounds: Rectangle, recurse?: boolean) { - return this.resizeCells([cell], [bounds], recurse)[0] - } - - /** - * Sets the bounds of the given cells. - */ - resizeCells( - cells: Cell[], - bounds: Rectangle[], - recurse: boolean = this.isRecursiveResize(), - ) { - return this.cellManager.resizeCells(cells, bounds, recurse) - } - - /** - * Resizes the child cells of the given cell for the given new geometry with - * respect to the current geometry of the cell. - */ - resizeChildCells(cell: Cell, newGeo: Geometry) { - const geo = this.model.getGeometry(cell)! - const dx = newGeo.bounds.width / geo.bounds.width - const dy = newGeo.bounds.height / geo.bounds.height - cell.eachChild(child => this.scaleCell(child, dx, dy, true)) - return cell - } - - /** - * Scales the points, position and size of the given cell according to the - * given vertical and horizontal scaling factors. - * - * @param cell - The cell to be scaled. - * @param sx - Horizontal scaling factor. - * @param sy - Vertical scaling factor. - * @param recurse - Boolean indicating if the child cells should be scaled. - */ - scaleCell(cell: Cell, sx: number, sy: number, recurse: boolean) { - return this.cellManager.scaleCell(cell, sx, sy, recurse) - } - - /** - * Returns the bounding box for the given array of `Cell`s. - */ - getBoundingBox(cells: Cell[]) { - return this.cellManager.getBoundingBox(cells) - } - - // #endregion - - // #region :::::::::::: Cell Overlay - - /** - * Returns the array of `Overlay` for the given cell - * or null if no overlays are defined. - */ - getOverlays(cell: Cell | null) { - return cell != null ? cell.getOverlays() : null - } - - /** - * Adds an `Overlay` for the specified cell. - */ - addOverlay(cell: Cell, overlay: Overlay) { - return this.cellManager.addOverlay(cell, overlay) - } - - /** - * Removes and returns the given `Overlay` from the given cell. - * If no overlay is given, then all overlays are removed. - */ - removeOverlay(cell: Cell, overlay?: Overlay | null) { - return this.cellManager.removeOverlay(cell, overlay) - } - - removeOverlays(cell: Cell) { - return this.cellManager.removeOverlays(cell) - } - - /** - * Removes all `Overlays` in the graph for the given cell and all its - * descendants. If no cell is specified then all overlays are removed - * from the graph. - */ - clearOverlays(cell: Cell = this.model.getRoot()) { - this.removeOverlays(cell) - cell.eachChild(child => this.clearOverlays(child)) - return cell - } - - /** - * Creates an overlay for the given cell using the `warning` string and - * image `warningImage`, then returns the new `Overlay`. The `warning` - * string is displayed as a tooltip in a red font and may contain HTML - * markup. If the `warning` string is null or a zero length string, then - * all overlays are removed from the cell. - */ - addWarningOverlay( - cell: Cell, - warning?: string | null, - img: Image = this.warningImage, - selectOnClick: boolean = false, - ) { - if (warning != null && warning.length > 0) { - return this.cellManager.addWarningOverlay( - cell, - warning, - img, - selectOnClick, - ) - } - - this.removeOverlays(cell) - return null - } - - // #endregion - - // #region :::::::::::: Cell Editing - - startEditing(e?: MouseEvent) { - this.startEditingAtCell(null, e) - return this - } - - startEditingAtCell( - cell: Cell | null = this.getSelectedCell(), - e?: MouseEvent, - ) { - if (e == null || !DomEvent.isMultiTouchEvent(e)) { - if (cell != null && this.isCellEditable(cell)) { - this.trigger(Graph.events.startEditing, { cell, e }) - this.cellEditor.startEditing(cell, e) - this.trigger(Graph.events.editingStarted, { cell, e }) - } - } - return this - } - - @hook() - getEditingContent(cell: Cell, e?: Event) { - return this.convertDataToString(cell) - } - - stopEditing(cancel: boolean = false) { - this.cellEditor.stopEditing(cancel) - this.trigger(Graph.events.editingStopped, { cancel }) - return this - } - - updateLabel(cell: Cell, label: string, e?: Event) { - this.model.batchUpdate(() => { - const old = cell.data - const data = this.putLabel(cell, label) - this.dataChanged(cell, data, this.isAutoSizeCell(cell)) - this.trigger(Graph.events.labelChanged, { cell, data, old, e }) - }) - return cell - } - - protected dataChanged(cell: Cell, data: any, autoSize: boolean) { - this.model.batchUpdate(() => { - this.model.setData(cell, data) - if (autoSize) { - this.cellManager.cellSizeUpdated(cell, false) - } - }) - } - - // #endregion - - // #region :::::::::::: Cell Align - - /** - * Aligns the given cells vertically or horizontally according to the - * given alignment using the optional parameter as the coordinate. - */ - alignCells( - align: Align | VAlign, - cells: Cell[] = this.getSelectedCells(), - param?: number, - ) { - return this.cellManager.alignCells(align, cells, param) - } - - /** - * Toggles the style of the given edge between null (or empty) and - * `alternateEdgeStyle`. - */ - flipEdge(edge: Cell) { - return this.cellManager.flipEdge(edge) - } - - // #endregion - - // #region :::::::::::: Cell Order - - /** - * Specifies if the cell size should be changed to the preferred size when - * a cell is first collapsed. - * - * Default is `true`. - */ - collapseToPreferredSize: boolean = true - - foldCells( - collapse: boolean, - recurse: boolean = false, - cells: Cell[] = this.getFoldableCells(this.getSelectedCells(), collapse), - checkFoldable: boolean = false, - ) { - return this.cellManager.foldCells(collapse, recurse, cells, checkFoldable) - } - - /** - * Moves the given cells to the front or back. - */ - orderCells( - toBack: boolean = false, - cells: Cell[] = Cell.sortCells(this.getSelectedCells(), true), - ) { - return this.cellManager.orderCells(toBack, cells) - } - - // #endregion - - // #region :::::::::::: Drilldown - - @hook() - isValidRoot(cell: Cell | null) { - return cell != null - } - - @hook() - isPort(cell: Cell) { - return false +export namespace Graph { + export interface Options extends GraphOptions { + model?: Model } +} - @hook() - getTerminalForPort(cell: Cell, isSource: boolean) { - return this.model.getParent(cell) - } - - @hook() - getChildOffset(cell: Cell): Point | null { - return null - } - - /** - * Uses the given cell as the root of the displayed cell hierarchy. - */ - enterGroup(cell: Cell = this.getSelectedCell()) { - this.cellManager.enterGroup(cell) - return this - } - - /** - * Changes the current root to the next valid root. - */ - exitGroup() { - this.cellManager.exitGroup() - return this - } - - /** - * Uses the root of the model as the root of the displayed - * cell hierarchy and selects the previous root. - */ - home() { - this.cellManager.home() - return this - } - - @hook() - getTranslateForCurrentRoot(currentRoot: Cell | null): Point | null { - return null - } - - // #endregion - - // #region :::::::::::: Retrieval - - getCurrentRoot() { - return this.view.currentRoot - } - - /** - * Specifies the default parent to be used to insert new cells. - */ - defaultParent: Cell | null - - setDefaultParent(cell: Cell | null) { - this.defaultParent = cell - return this - } - - getDefaultParent(): Cell { - return this.cellManager.getDefaultParent() - } - - /** - * Returns the nearest ancestor of the given cell which is a - * swimlane, or the given cell, if it is itself a swimlane. - */ - getSwimlane(cell: Cell | null): Cell | null { - return this.cellManager.getSwimlane(cell) - } - - /** - * Returns the bottom-most swimlane that intersects the given - * point in the cell hierarchy that starts at the given parent. - */ - getSwimlaneAt( - x: number, - y: number, - parent: Cell = this.getDefaultParent(), - ): Cell | null { - return this.cellManager.getSwimlaneAt(x, y, parent) - } - - /** - * Returns the bottom-most cell that intersects the given point in - * the cell hierarchy starting at the given parent. - * - * @param x X-coordinate of the location to be checked. - * @param y Y-coordinate of the location to be checked. - * @param parent The root of the recursion. Default is current root of the - * view or the root of the model. - * @param includeNodes Optional boolean indicating if nodes should be - * returned. Default is `true`. - * @param includeEdges Optional boolean indicating if edges should be - * returned. Default is `true`. - * @param ignoreFn Optional function that returns true if cell should be - * ignored. - */ - getCellAt( - x: number, - y: number, - parent?: Cell | null, - includeNodes: boolean = true, - includeEdges: boolean = true, - ignoreFn?: (state: State, x?: number, y?: number) => boolean, - ): Cell | null { - return this.cellManager.getCellAt( - x, - y, - parent, - includeNodes, - includeEdges, - ignoreFn, - ) - } - - /** - * Returns the visible child nodes of the given parent. - */ - getChildNodes(parent: Cell) { - return this.getChildren(parent, true, false) - } - - /** - * Returns the visible child edges of the given parent. - */ - getChildEdges(parent: Cell) { - return this.getChildren(parent, false, true) - } - - /** - * Returns the visible child nodes or edges of the given parent. - */ - getChildren( - parent: Cell = this.getDefaultParent(), - includeNodes: boolean = false, - includeEdges: boolean = false, - ) { - const cells = this.model.getChildren(parent, includeNodes, includeEdges) - return cells.filter(cell => this.isCellVisible(cell)) - } - - /** - * Returns all visible edges connected to the given cell without loops. - */ - getConnections(node: Cell, parent?: Cell | null) { - return this.getEdges(node, parent, true, true, false) - } - - /** - * Returns the visible incoming edges for the given cell. If the optional - * parent argument is specified, then only child edges of the given parent - * are returned. - */ - getIncomingEdges(node: Cell, parent?: Cell | null) { - return this.getEdges(node, parent, true, false, false) - } - - /** - * Returns the visible outgoing edges for the given cell. If the optional - * parent argument is specified, then only child edges of the given parent - * are returned. - */ - getOutgoingEdges(node: Cell, parent?: Cell | null) { - return this.getEdges(node, parent, false, true, false) - } - - /** - * Returns the incoming and/or outgoing edges for the given cell. - * - * If the optional parent argument is specified, then only edges are returned - * where the opposite terminal is in the given parent cell. If at least one - * of incoming or outgoing is true, then loops are ignored, if both are false, - * then all edges connected to the given cell are returned including loops. - * - * Parameters: - * - * @param node `Cell` whose edges should be returned. - * @param parent Optional parent of the opposite end for an edge to be - * returned. - * @param incoming Specifies if incoming edges should be included in the - * result. Default is `true`. - * @param outgoing Specifies if outgoing edges should be included in the - * result. Default is `true`. - * @param includeLoops - Specifies if loops should be included in the - * result. Default is `true`. - * @param recurse - Specifies if the parent specified only need be - * an ancestral parent, true, or the direct parent, false. - */ - getEdges( - node: Cell, - parent?: Cell | null, - incoming: boolean = true, - outgoing: boolean = true, - includeLoops: boolean = true, - recurse: boolean = false, - ) { - return this.cellManager.getEdges( - node, - parent, - incoming, - outgoing, - includeLoops, - recurse, - ) - } - - /** - * Returns all distinct visible opposite cells for the specified terminal - * on the given edges. - * - * @param edges Array of `Cell`s that contains the edges whose opposite - * terminals should be returned. - * @param terminal - Specifies the end whose opposite should be returned. - * @param includeSources - Optional boolean that specifies if source - * terminals should be included in the result. Default is `true`. - * @param includeTargets - Optional boolean that specifies if target - * terminals should be included in the result. Default is `true`. - */ - getOppositeNodes( - edges: Cell[], - terminal: Cell, - includeSources: boolean = true, - includeTargets: boolean = true, - ) { - return this.cellManager.getOppositeNodes( - edges, - terminal, - includeSources, - includeTargets, - ) - } - - /** - * Returns the edges between the given source and target. This takes into - * account collapsed and invisible cells and returns the connected edges - * as displayed on the screen. - */ - getEdgesBetween(source: Cell, target: Cell, directed: boolean = false) { - return this.cellManager.getEdgesBetween(source, target, directed) - } - - /** - * Returns the child nodes and edges of the given parent that are contained - * in the given rectangle. - * - * @param x X-coordinate of the rectangle. - * @param y Y-coordinate of the rectangle. - * @param w Width of the rectangle. - * @param h Height of the rectangle. - * @param parent `Cell` that should be used as the root of the recursion. - * Default is current root of the view or the root of the model. - * @param result Optional array to store the result in. - */ - getCellsInRegion( - x: number, - y: number, - w: number, - h: number, - parent: Cell = this.getCurrentRoot() || this.model.getRoot(), - result: Cell[] = [], - ) { - return this.cellManager.getCellsInRegion(x, y, w, h, parent, result) - } - - /** - * Returns the children of the given parent that are contained in the - * canvas from the given point (x0, y0) rightwards or downwards - * depending on rightHalfpane and bottomHalfpane. - * - * @param x X-coordinate of the origin. - * @param y Y-coordinate of the origin. - * @param parent Optional `Cell` whose children should be checked. - * @param isRight - Boolean indicating if the cells in the right - * canvas from the origin should be returned. - * @param isBottom - Boolean indicating if the cells in the bottom - * canvas from the origin should be returned. - */ - getCellsBeyond( - x: number, - y: number, - parent: Cell = this.getDefaultParent(), - isRight: boolean = false, - isBottom: boolean = false, - ) { - return this.cellManager.getCellsBeyond(x, y, parent, isRight, isBottom) - } - - /** - * Returns all children in the given parent which do not have incoming - * edges. If the result is empty then the with the greatest difference - * between incoming and outgoing edges is returned. - * - * @param parent `Cell` whose children should be checked. - * @param isolate Optional boolean that specifies if edges should be ignored - * if the opposite end is not a child of the given parent cell. - * Default is `false`. - * @param invert - Optional boolean that specifies if outgoing or incoming - * edges should be counted for a tree root. If `false` then outgoing edges - * will be counted. - * Default is `false`. - */ - findTreeRoots( - parent: Cell | null, - isolate: boolean = false, - invert: boolean = false, - ) { - return this.cellManager.findTreeRoots(parent, isolate, invert) - } - - /** - * Traverses the (directed) graph invoking the given function for each - * visited node and edge. The function is invoked with the current node - * and the incoming edge as a parameter. This implementation makes sure - * each node is only visited once. The function may return false if the - * traversal should stop at the given node. - * - * @param node The node where the traversal starts. - * @param directed Optional boolean indicating if edges should only be - * traversed from source to target. Default is `true`. - * @param func - Visitor function that takes the current node and the - * incoming edge as arguments. The traversal stops if the function - * returns `false`. - * @param edge - Optional `Cell` that represents the incoming edge. This is - * `null` for the first step of the traversal. - * @param visited - Optional `WeakMap` for the visited cells. - * @param inverse - Optional boolean to traverse in inverse direction. - * Default is `false`. This is ignored if directed is `false`. - */ - traverse( - node: Cell, - directed: boolean = true, - func: (node: Cell, edge: Cell | null) => any, - edge?: Cell, - visited: WeakMap = new WeakMap(), - inverse: boolean = false, - ) { - this.cellManager.traverse(node, directed, func, edge, visited, inverse) - } - - // #endregion - - // #region :::::::::::: Validation - - @hook() - validateEdge(edge: Cell | null, source: Cell | null, target: Cell | null) { - return null - } - - @hook() - validateCell(cell: Cell, context: any) { - return null - } - - /** - * Validates the graph by validating each descendant of the given cell or - * the root of the model. - */ - validateGraph( - cell: Cell = this.model.getRoot(), - context: any = {}, - ): string | null { - return this.validator.validateGraph(cell, context) - } - - isEdgeValid(edge: Cell | null, source: Cell | null, target: Cell | null) { - return this.validator.getEdgeValidationError(edge, source, target) == null - } - - validationWarn(message: string) { - console.warn(message) - } - - // #endregion - - // #region :::::::::::: Selection - - isCellSelected(cell: Cell | null) { - return this.selection.isSelected(cell) - } - - isSelectionEmpty() { - return this.selection.isEmpty() - } - - clearSelection() { - return this.selection.clear() - } - - getSelecedCellCount() { - return this.selection.cells.length - } - - getSelectedCell() { - return this.selection.cells[0] - } - - getSelectedCells() { - return this.selection.cells.slice() - } - - hasSelectedCell() { - return this.selection.cells.length > 0 - } - - /** - * Replace selection cells with the given cell - */ - setSelectedCell(cell: Cell | null) { - this.selection.setCell(cell) - return this - } - - /** - * Replace selection cells with the given cells - */ - setSelectedCells(cells: Cell[]) { - this.selection.setCells(cells) - return this - } - - /** - * Adds the given cell to the selection. - */ - selectCell(cell: Cell | null) { - this.selection.addCell(cell) - return this - } - - /** - * Adds the given cells to the selection. - */ - selectCells(cells: Cell[]) { - this.selection.addCells(cells) - return this - } - - /** - * Removes the given cell from the selection. - */ - unSelectCell(cell: Cell | null) { - this.selection.removeCell(cell) - return this - } - - /** - * Removes the given cells from the selection. - */ - unSelectCells(cells: Cell[]) { - this.selection.removeCells(cells) - return this - } - - /** - * Removes selected cells that are not in the model from the selection. - */ - updateSelection() { - this.selectionManager.updateSelection() - return this - } - - /** - * Selects and returns the cells inside the given rectangle for the - * specified event. - */ - selectCellsInRegion( - rect: Rectangle | Rectangle.RectangleLike, - e: MouseEvent, - ) { - return this.selectionManager.selectCellsInRegion(rect, e) - } - - selectNextCell() { - this.selectionManager.selectCell(true) - return this - } - - selectPreviousCell() { - this.selectionManager.selectCell() - return this - } - - selectParentCell() { - this.selectionManager.selectCell(false, true) - return this - } - - selectChildCell() { - this.selectionManager.selectCell(false, false, true) - return this - } - - /** - * Selects all children of the given parent or the children of the - * default parent if no parent is specified. - * - * @param parent Optional parent `Cell` whose children should be selected. - * @param includeDescendants Optional boolean specifying whether all - * descendants should be selected. - */ - selectAll( - parent: Cell = this.getDefaultParent()!, - includeDescendants: boolean = false, - ) { - this.selectionManager.selectAll(parent, includeDescendants) - return this - } - - /** - * Select all nodes inside the given parent or the default parent. - */ - selectNodes(parent: Cell = this.getDefaultParent()!) { - this.selectionManager.selectCells(true, false, parent) - return this - } - - /** - * Select all edges inside the given parent or the default parent. - */ - selectEdges(parent: Cell = this.getDefaultParent()!) { - this.selectionManager.selectCells(false, true, parent) - return this - } - - // #endregion - - // #region :::::::::::: Eventloop - - /** - * Adds a listener to the graph event dispatch loop. The listener - * must implement the mouseDown, mouseMove and mouseUp - */ - addMouseListener(handler: IMouseHandler) { - this.eventloop.addMouseListener(handler) - return this - } - - removeMouseListener(handler: IMouseHandler) { - this.eventloop.removeMouseListener(handler) - return this - } - - getPointForEvent(e: MouseEvent, addOffset: boolean = true) { - return this.eventloop.getPointForEvent(e, addOffset) - } - - /** - * Dispatches the given event to the graph event dispatch loop. - */ - fireMouseEvent(eventName: string, e: MouseEventEx, sender: any = this) { - this.eventloop.fireMouseEvent(eventName, e, sender) - return this - } - - fireGestureEvent(e: MouseEvent, cell?: Cell) { - this.eventloop.fireGestureEvent(e, cell) - return this - } - - @hook() - isCloneEvent(e: MouseEvent) { - return DomEvent.isControlDown(e) - } - - @hook() - isToggleEvent(e: MouseEvent) { - return detector.IS_MAC ? DomEvent.isMetaDown(e) : DomEvent.isControlDown(e) - } - - @hook() - isConstrainedEvent(e: MouseEvent) { - return DomEvent.isShiftDown(e) - } - - @hook() - isGridEnabledForEvent(e: MouseEvent) { - return e != null && !DomEvent.isAltDown(e) - } - - @hook() - isConnectionIgnored(e: MouseEvent) { - return false - } - - @hook() - isTransparentClickEvent(e: MouseEvent) { - return false - } - - // #endregion - - // #region :::::::::::: Graph Viewport - - /** - * Returns the bounds of the visible graph. - */ - getGraphBounds() { - return this.view.getGraphBounds() - } - - /** - * Returns the scaled, translated bounds for the given cell. - * - * @param cell The `Cell` whose bounds should be returned. - * @param includeEdges Optional boolean that specifies if the bounds of - * the connected edges should be included. Default is `false`. - * @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, - ) { - return this.viewport.getCellBounds(cell, includeEdges, includeDescendants) - } - - /** - * Returns the bounding box for the geometries of the nodes in the - * given array of cells. - */ - getBoundingBoxFromGeometry(cells: Cell[], includeEdges: boolean = false) { - return this.viewport.getBoundingBoxFromGeometry(cells, includeEdges) - } - - /** - * Clears all cell states or the states for the hierarchy starting at the - * given cell and validates the graph. - */ - refresh(cell: Cell) { - this.view.clear(cell, cell == null) - this.view.validate() - this.sizeDidChange() - this.trigger(Graph.events.refresh) - return this - } - - sizeDidChange() { - this.viewport.sizeDidChange() - return this - } - - updatePageBreaks(visible: boolean, width: number, height: number) { - this.viewport.updatePageBreaks(visible, width, height) - return this - } - - getPreferredPageSize(bounds: Rectangle, width: number, height: number): Size { - return this.viewport.getPreferredPageSize(bounds, width, height) - } - - /** - * Snaps the given numeric value to the grid. - */ - snap(value: number) { - if (this.isGridEnabled()) { - const gridSize = this.getGridSize() - return Math.round(value / gridSize) * gridSize - } - return value - } - - pan(x: number, y: number, relative: boolean = false) { - const tx = relative ? this.panDx + x : x - const ty = relative ? this.panDy + y : y - this.viewport.pan(tx, ty) - return this - } - - panTo(x: number, y: number) { - this.pan(x, y, false) - return this - } - - panBy(x: number, y: number) { - this.pan(x, y, true) - return this - } - - zoomIn() { - this.zoom(this.scaleFactor) - return this - } - - zoomOut() { - this.zoom(1 / this.scaleFactor) - return this - } - - /** - * Resets the zoom and panning in the view. - */ - zoomActual() { - if (this.view.scale === 1) { - this.view.setTranslate(0, 0) - } else { - this.view.translate.x = 0 - this.view.translate.y = 0 - - this.view.setScale(1) - } - return this - } - - zoomTo(scale: number, center?: boolean) { - this.zoom(scale / this.view.scale, center) - return this - } - - /** - * Zooms the graph using the given factor. Center is an optional boolean - * argument that keeps the graph scrolled to the center. - */ - zoom(factor: number, center: boolean = this.centerZoom) { - this.viewport.zoom(factor, center) - return this - } - - /** - * Centers the graph in the container. - * - * @param horizontal Optional boolean that specifies if the graph should be - * centered horizontally. Default is `true`. - * @param vertical Optional boolean that specifies if the graph should be - * centered vertically. Default is `true`. - * @param cx Optional float that specifies the horizontal center. - * Default is `0.5`. - * @param cy Optional float that specifies the vertical center. - * Default is `0.5`. - */ - center( - horizontal: boolean = true, - vertical: boolean = true, - cx: number = 0.5, - cy: number = 0.5, - ) { - this.viewport.center(horizontal, vertical, cx, cy) - return this - } - - /** - * Scales the graph such that the complete diagram fits into container. - * - * @param border Optional number that specifies the border. - * @param keepOrigin Optional boolean that specifies if the translate - * should be changed. Default is `false`. - * @param margin Optional margin in pixels. Default is `0`. - * @param enabled Optional boolean that specifies if the scale should - * be set or just returned. Default is `true`. - * @param ignoreWidth Optional boolean that specifies if the width should - * be ignored. Default is `false`. - * @param ignoreHeight Optional boolean that specifies if the height should - * be ignored. Default is `false`. - * @param maxHeight Optional maximum height. - */ - fit( - border: number = this.getBorder(), - keepOrigin: boolean = false, - margin: number = 0, - enabled: boolean = true, - ignoreWidth: boolean = false, - ignoreHeight: boolean = false, - maxHeight?: number, - ) { - return this.viewport.fit( - border, - keepOrigin, - margin, - enabled, - ignoreWidth, - ignoreHeight, - maxHeight, - ) - } - - /** - * Zooms the graph to the specified rectangle. If the rectangle does not have - * same aspect ratio as the display container, it is increased in the smaller - * relative dimension only until the aspect match. The original rectangle is - * centralised within this expanded one. - * - * Note that the input rectangle must be un-scaled and un-translated. - */ - zoomToRect(rect: Rectangle) { - this.viewport.zoomToRect(rect) - return this - } - - /** - * Pans the graph so that it shows the given cell. Optionally the cell may - * be centered in the container. - */ - scrollCellToVisible(cell: Cell, center: boolean = false) { - this.viewport.scrollCellToVisible(cell, center) - return this - } - - /** - * Pans the graph so that it shows the given rectangle. - */ - scrollRectToVisible(rect: Rectangle) { - return this.viewport.scrollRectToVisible(rect) - } - - /** - * Scrolls the graph to the given point, extending - * the graph container if specified. - */ - scrollPointToVisible( - x: number, - y: number, - extend: boolean = false, - border: number = 20, - ) { - this.viewport.scrollPointToVisible(x, y, extend, border) - return this - } - - // #endregion - - // #region :::::::::::: Soft Links - - batchUpdate(update: () => void) { - this.model.batchUpdate(update) - return this - } - - /** - * Returns the `Geometry` for the given cell. - */ - getCellGeometry(cell: Cell) { - return this.model.getGeometry(cell) - } - - // #endregion - - // #region :::::::::::: Graph Appearance - - @hook() - isCellVisible(cell: Cell | null) { - return cell != null ? this.model.isVisible(cell) : false - } - - @hook() - isCellLocked(cell: Cell | null) { - if (this.isCellsLocked()) { - return true - } - - const style = this.getStyle(cell) - if (style.locked) { - return true - } - - const geometry = this.model.getGeometry(cell) - return geometry != null && this.model.isNode(cell) && geometry.relative - } - - /** - * Returns the cells which may be exported in the given array of cells. - */ - getCloneableCells(cells: Cell[]) { - return this.model.filterCells(cells, cell => this.isCellCloneable(cell)) - } - - @hook() - isCellCloneable(cell: Cell) { - const style = this.getStyle(cell) - return this.isCellsCloneable() && style.cloneable !== false - } - - @hook() - isCellSelectable(cell: Cell) { - return this.isCellsSelectable() - } - - getDeletableCells(cells: Cell[]) { - return this.model.filterCells(cells, cell => this.isCellDeletable(cell)) - } - - @hook() - isCellDeletable(cell: Cell) { - const style = this.getStyle(cell) - return this.isCellsDeletable() && style.deletable !== false - } - - @hook() - isCellRotatable(cell: Cell) { - const style = this.getStyle(cell) - return ( - this.isCellsRotatable() && - !this.isCellLocked(cell) && - style.rotatable !== false - ) - } - - getMovableCells(cells: Cell[]) { - return this.model.filterCells(cells, cell => this.isCellMovable(cell)) - } - - @hook() - isLabelMovable(cell: Cell | null) { - return ( - !this.isCellLocked(cell) && - ((this.model.isEdge(cell) && this.edgeLabelsMovable) || - (this.model.isNode(cell) && this.nodeLabelsMovable)) - ) - } - - @hook() - isCellMovable(cell: Cell | null) { - const style = this.getStyle(cell) - return ( - this.isCellsMovable() && - !this.isCellLocked(cell) && - style.movable !== false - ) - } - - @hook() - isCellResizable(cell: Cell) { - const style = this.getStyle(cell) - return ( - this.isCellsResizable() && - !this.isCellLocked(cell) && - style.resizable !== false - ) - } - - @hook() - isCellBendable(cell: Cell) { - const style = this.getStyle(cell) - return ( - this.isCellsBendable() && - !this.isCellLocked(cell) && - style.bendable !== false - ) - } - - @hook() - isCellEditable(cell: Cell) { - const style = this.getStyle(cell) - return ( - this.isCellsEditable() && - !this.isCellLocked(cell) && - style.editable !== false - ) - } - - @hook() - isCellDisconnectable(cell: Cell, terminal: Cell, isSource: boolean) { - return this.isCellsDisconnectable() && !this.isCellLocked(cell) - } - - @hook() - isCellConnectable(cell: Cell | null) { - const style = this.getStyle(cell) - return style.connectable !== false - } - - @hook() - isCellCollapsed(cell: Cell) { - return this.model.isCollapsed(cell) - } - - @hook() - isCellFoldable(cell: Cell, nextCollapseState: boolean) { - const style = this.getStyle(cell) - return this.model.getChildCount(cell) > 0 && style.foldable !== false - } - - /** - * Returns the cells which are movable in the given array of cells. - */ - getFoldableCells(cells: Cell[], collapse: boolean) { - return this.model.filterCells(cells, cell => - this.isCellFoldable(cell, collapse), - ) - } - - getFoldingImage(state: State) { - if ( - state != null && - this.cellsFoldable && - !this.getModel().isEdge(state.cell) - ) { - const collapsed = this.isCellCollapsed(state.cell) - if (this.isCellFoldable(state.cell, !collapsed)) { - return collapsed ? this.collapsedImage : this.expandedImage - } - } - - return null - } - - @hook() - isTerminalPointMovable(cell: Cell, isSource: boolean) { - return true - } - - @hook() - isHtmlLabel(cell: Cell) { - const style = this.getStyle(cell) - if (style != null && style.htmlLabel != null) { - return style.htmlLabel - } - return this.isHtmlLabels() - } - - @hook() - isWrapping(cell: Cell) { - const style = this.getStyle(cell) - return style != null ? style.whiteSpace === 'wrap' : false - } - - @hook() - isLabelClipped(cell: Cell) { - const style = this.getStyle(cell) - return style != null ? style.overflow === 'hidden' : false - } - - @hook() - isSwimlane(cell: Cell | null) { - if (cell != null) { - if (this.model.getParent(cell) !== this.model.getRoot()) { - const style = this.getStyle(cell) - if (style != null && !this.model.isEdge(cell)) { - return style.shape === 'swimlane' - } - } - } - return false - } - - @hook() - convertDataToString(cell: Cell): string { - const data = this.model.getData(cell) - if (data != null) { - if (typeof data.toString === 'function') { - return data.toString() - } - } - - return '' - } - - @hook() - getHtml(cell: Cell): HTMLElement | string | null { - let result = '' - if (cell != null) { - result = this.convertDataToString(cell) - } - return result - } - - @hook() - getLabel(cell: Cell): HTMLElement | string | null { - let result = '' - - if (this.labelsVisible && cell != null) { - const style = this.getStyle(cell) - if (!style.noLabel) { - result = this.convertDataToString(cell) - } - } - - return result - } - - @hook() - putLabel(cell: Cell, label: string) { - const data = cell.getData() - if (typeof data === 'object') { - throw new Error('Method not implemented.') - } - - return label - } - - @hook() - getTooltip(cell: Cell) { - return this.convertDataToString(cell) - } - - @hook() - getCellLink(cell: Cell) { - return null - } - - @hook() - getCellCursor(cell: Cell | null) { - return null - } - - @hook() - getStartSize(swimlane: Cell | null) { - const result = new Rectangle() - const style = this.getStyle(swimlane) - if (style != null) { - const size = style.startSize || globals.defaultStartSize - if (style.horizontal !== false) { - result.height = size - } else { - result.width = size - } - } - - return result - } - - @hook() - getCellClassName(cell: Cell) { - const style = this.getStyle(cell) - return style.className || null - } - - @hook() - getLabelClassName(cell: Cell) { - const style = this.getStyle(cell) - return style.labelClassName || null - } - - // #endregion - - // #region :::::::::::: Graph Behaviour - - /** - * Returns the cells which may be exported in the given array of cells. - */ - getExportableCells(cells: Cell[]) { - return this.model.filterCells(cells, cell => this.canExportCell(cell)) - } - - canExportCell(cell: Cell) { - return this.isCellsExportable() - } - - getImportableCells(cells: Cell[]) { - return this.model.filterCells(cells, cell => this.canImportCell(cell)) - } - - /** - * Returns true if the given cell may be imported from the clipboard. - * - * Default is `true`. - */ - canImportCell(cell: Cell) { - return this.isCellsImportable() - } - - @hook() - isValidSource(cell: Cell | null) { - return ( - (cell == null && this.allowDanglingEdges) || - (cell != null && - (!this.model.isEdge(cell) || this.edgesConnectable) && - this.isCellConnectable(cell)) - ) - } - - @hook() - isValidTarget(cell: Cell | null) { - return this.isValidSource(cell) - } - - @hook() - isValidConnection(source: Cell | null, target: Cell | null) { - return this.isValidSource(source) && this.isValidTarget(target) - } - - /** - * Returns true if the given cell is currently being edited. - */ - isEditing(cell?: Cell) { - if (this.cellEditor != null) { - const editingCell = this.cellEditor.getEditingCell() - return cell == null ? editingCell != null : cell === editingCell - } - - return false - } - - @hook() - isAutoSizeCell(cell: Cell) { - const style = this.getStyle(cell) - return this.isAutoSizeOnEdited() || style.autosize === true - } - - @hook() - isExtendParent(cell: Cell) { - return !this.model.isEdge(cell) && this.isExtendParents() - } - - @hook() - isConstrainChild(cell: Cell) { - return ( - this.isConstrainChildren() && - !this.model.isEdge(this.model.getParent(cell)!) - ) - } - - /** - * Returns a decimal number representing the amount of the width and height - * of the given cell that is allowed to overlap its parent. A value of 0 - * means all children must stay inside the parent, 1 means the child is - * allowed to be placed outside of the parent such that it touches one of - * the parents sides. - */ - getOverlap(cell: Cell) { - return this.isAllowOverlapParent(cell) ? this.defaultOverlap : 0 - } - - /** - * Returns true if the given cell is allowed to be placed outside of the - * parents area. - */ - isAllowOverlapParent(cell: Cell) { - return false - } - - @hook() - isValidDropTarget(target: Cell, cells: Cell[], e: MouseEvent) { - return ( - target != null && - ((this.isSplitEnabled() && this.isSplitTarget(target, cells, e)) || - (!this.model.isEdge(target) && - (this.isSwimlane(target) || - (this.model.getChildCount(target) > 0 && - !this.isCellCollapsed(target))))) - ) - } - - @hook() - isSplitTarget(target: Cell, cells: Cell[], e: MouseEvent) { - if ( - this.model.isEdge(target) && - cells != null && - cells.length === 1 && - this.isCellConnectable(cells[0]) && - this.isEdgeValid(target, this.model.getTerminal(target, true), cells[0]) - ) { - const src = this.model.getTerminal(target, true)! - const trg = this.model.getTerminal(target, false)! - - return ( - !this.model.isAncestor(cells[0], src) && - !this.model.isAncestor(cells[0], trg) - ) - } - - return false - } - - /** - * Returns the given cell if it is a drop target for the given cells or the - * nearest ancestor that may be used as a drop target for the given cells. - * - * @param cells Array of `Cell`s which are to be dropped onto the target. - * @param e Mouseevent for the drag and drop. - * @param cell `Cell` that is under the mousepointer. - * @param clone Optional boolean to indicate of cells will be cloned. - */ - getDropTarget( - cells: Cell[], - e: MouseEvent, - cell: Cell | null, - clone?: boolean, - ) { - if (!this.isSwimlaneNesting()) { - for (let i = 0; i < cells.length; i += 1) { - if (this.isSwimlane(cells[i])) { - return null - } - } - } - - const p = util.clientToGraph( - this.container, - DomEvent.getClientX(e), - DomEvent.getClientY(e), - ) - p.x -= this.panDx - p.y -= this.panDy - const swimlane = this.getSwimlaneAt(p.x, p.y) - - if (cell == null) { - // tslint:disable-next-line - cell = swimlane! - } else if (swimlane != null) { - // Checks if the cell is an ancestor of the swimlane - // under the mouse and uses the swimlane in that case - let tmp = this.model.getParent(swimlane) - - while (tmp != null && this.isSwimlane(tmp) && tmp !== cell) { - tmp = this.model.getParent(tmp) - } - - if (tmp === cell) { - // tslint:disable-next-line - cell = swimlane - } - } - - while ( - cell != null && - !this.isValidDropTarget(cell, cells, e) && - !this.model.isLayer(cell) - ) { - // tslint:disable-next-line - cell = this.model.getParent(cell)! - } - - // Checks if parent is dropped into child if not cloning - if (clone == null || !clone) { - let parent = cell - - while (parent != null && util.indexOf(cells, parent) < 0) { - parent = this.model.getParent(parent)! - } - } - - return !this.model.isLayer(cell) && parent == null ? cell : null - } - - // #endregion -} - -export namespace Graph { - export interface Options extends GraphOptions { - model?: Model - } - - export interface AddNodeOptions extends Cell.CreateNodeOptions { - parent?: Cell - index?: number - } - - export interface AddEdgeOptions extends Cell.CreateEdgeOptions { - parent?: Cell - index?: number - source?: Cell - target?: Cell - } - - export const events = { - refresh: 'refresh', - root: 'root', - - addCells: 'addCells', - cellsAdded: 'cellsAdded', - removeCells: 'removeCells', - cellsRemoved: 'cellsRemoved', - removeCellsFromParent: 'removeCellsFromParent', - connectCell: 'connectCell', - cellConnected: 'cellConnected', - groupCells: 'groupCells', - ungroupCells: 'ungroupCells', - splitEdge: 'splitEdge', - updateCellSize: 'updateCellSize', - resizeCells: 'resizeCells', - cellsResized: 'cellsResized', - addOverlay: 'addOverlay', - removeOverlay: 'removeOverlay', - removeOverlays: 'removeOverlays', - foldCells: 'foldCells', - cellsFolded: 'cellsFolded', - orderCells: 'orderCells', - cellsOrdered: 'cellsOrdered', - toggleCells: 'toggleCells', - flipEdge: 'flipEdge', - alignCells: 'alignCells', - moveCells: 'moveCells', - cellsMoved: 'cellsMoved', - - startEditing: 'startEditing', - editingStarted: 'editingStarted', - editingStopped: 'editingStopped', - labelChanged: 'labelChanged', - size: 'size', - - click: 'click', - dblclick: 'dblclick', - tapAndHold: 'tapAndHold', - escape: 'escape', - - pan: 'pan', - gesture: 'gesture', - fireMouseEvent: 'fireMouseEvent', - selectionChanged: 'selectionChanged', - } -} +export interface Graph + extends CommonAccessor, + SizeAccessor, + ZoomAccessor, + GridAccessor, + GuideAccessor, + GroupAccessor, + StyleAccessor, + MovingAccessor, + PanningAccessor, + EditingAccessor, + OverlayAccessor, + TooltipAccessor, + CreationAccessor, + CollapseAccessor, + KeyboardAccessor, + ViewportAccessor, + EventLoopAccessor, + PageBreakAccessor, + SelectionAccessor, + RetrievalAccessor, + ConnectionAccessor, + ValidationAccessor, + RubberbandAccessor {} + +util.applyMixins( + Graph, + CommonAccessor, + SizeAccessor, + ZoomAccessor, + GridAccessor, + GuideAccessor, + GroupAccessor, + StyleAccessor, + MovingAccessor, + PanningAccessor, + EditingAccessor, + OverlayAccessor, + TooltipAccessor, + CreationAccessor, + CollapseAccessor, + KeyboardAccessor, + ViewportAccessor, + EventLoopAccessor, + PageBreakAccessor, + SelectionAccessor, + RetrievalAccessor, + ConnectionAccessor, + ValidationAccessor, + RubberbandAccessor, +) diff --git a/packages/x6/src/graph/prop/grid.ts b/packages/x6/src/graph/grid-accessor.ts similarity index 93% rename from packages/x6/src/graph/prop/grid.ts rename to packages/x6/src/graph/grid-accessor.ts index ec489bdd745..9e6ee12aa6a 100644 --- a/packages/x6/src/graph/prop/grid.ts +++ b/packages/x6/src/graph/grid-accessor.ts @@ -1,6 +1,6 @@ -import { GraphBase } from './base' +import { BaseGraph } from './base-graph' -export class GraphGrid extends GraphBase { +export class GridAccessor extends BaseGraph { isGridEnabled() { return this.options.grid.enabled } @@ -210,6 +210,17 @@ export class GraphGrid extends GraphBase { set backgroundColor(color: string | null) { this.setBackgroundColor(color) } + + /** + * Snaps the given numeric value to the grid. + */ + snap(value: number) { + if (this.isGridEnabled()) { + const gridSize = this.getGridSize() + return Math.round(value / gridSize) * gridSize + } + return value + } } export type GridType = 'line' | 'dot' diff --git a/packages/x6/src/graph/group-accessor.ts b/packages/x6/src/graph/group-accessor.ts new file mode 100644 index 00000000000..20ddfcaf36c --- /dev/null +++ b/packages/x6/src/graph/group-accessor.ts @@ -0,0 +1,110 @@ +import { afterCreate } from './decorator' +import { Cell } from '../core/cell' +import { BaseGraph } from './base-graph' + +export class GroupAccessor extends BaseGraph { + @afterCreate() + createGroup(cells: Cell[]) { + const group = new Cell({ connectable: false }) + group.asNode(true) + return group + } + + /** + * Adds the cells into the given group. + * + * @param group The target group. If null is specified then a new group + * is created. + * @param border Optional integer that specifies the border between the + * child area and the group bounds. Default is `0`. + * @param cells Optional array of `Cell`s to be grouped. If null is + * specified then the selection cells are used. + */ + groupCells( + group: Cell | null = null, + border: number = 0, + cells: Cell[] = Cell.sortCells(this.getSelectedCells(), true), + ) { + return this.groupManager.groupCells(group!, border, cells) + } + + ungroup(group: Cell) { + return this.ungroups([group]) + } + + /** + * Ungroups the given cells by moving the children to their parents parent + * and removing the empty groups. Returns the children that have been + * removed from the groups. + * + * @param cells Array of cells to be ungrouped. If null is specified then + * the selection cells are used. + */ + ungroups(cells?: Cell[]) { + return this.groupManager.ungroupCells(cells) + } + + /** + * Updates the bounds of the given groups to include all children and + * returns the passed-in cells. Call this with the groups in parent to + * child order, top-most group first, the cells are processed in reverse + * order and cells with no children are ignored. + * + * @param cells - The groups whose bounds should be updated. If this is + * null, then the selection cells are used. + * @param border - Optional border to be added in the group. Default is `0`. + * @param moveGroup - Optional boolean that allows the group to be moved. + * Default is `false`. + * @param topBorder - Optional top border to be added in the group. + * Default is `0`. + * @param rightBorder - Optional top border to be added in the group. + * Default is `0`. + * @param bottomBorder - Optional top border to be added in the group. + * Default is `0`. + * @param leftBorder - Optional top border to be added in the group. + * Default is `0`. + */ + updateGroupBounds( + cells: Cell[] = this.getSelectedCells(), + border: number = 0, + moveGroup: boolean = false, + topBorder: number = 0, + rightBorder: number = 0, + bottomBorder: number = 0, + leftBorder: number = 0, + ) { + return this.groupManager.updateGroupBounds( + cells, + border, + moveGroup, + topBorder, + rightBorder, + bottomBorder, + leftBorder, + ) + } + + /** + * Uses the given cell as the root of the displayed cell hierarchy. + */ + enterGroup(cell: Cell = this.getSelectedCell()) { + this.groupManager.enterGroup(cell) + return this + } + + /** + * Changes the current root to the next valid root. + */ + exitGroup() { + this.groupManager.exitGroup() + return this + } + + /** + * Removes the specified cells from their parents and adds them to the + * default parent. Returns the cells that were removed from their parents. + */ + removeCellsFromParent(cells: Cell[] = this.getSelectedCells()) { + return this.groupManager.removeCellsFromParent(cells) + } +} diff --git a/packages/x6/src/graph/group-manager.ts b/packages/x6/src/graph/group-manager.ts new file mode 100644 index 00000000000..e722cd53b84 --- /dev/null +++ b/packages/x6/src/graph/group-manager.ts @@ -0,0 +1,290 @@ +import { Cell } from '../core/cell' +import { Geometry } from '../core/geometry' +import { events } from './events' +import { BaseManager } from './base-manager' + +export class GroupManager extends BaseManager { + groupCells(group: Cell, border: number, cells: Cell[]) { + cells = this.getCellsForGroup(cells) // tslint:disable-line + + if (group == null) { + group = this.graph.createGroup(cells) // tslint:disable-line + } + + if (cells.length > 0) { + const bounds = this.getBoundsForGroup(group, cells, border) + if (bounds != null) { + // Uses parent of group or previous parent of first child + let parent = this.model.getParent(group) + if (parent == null) { + parent = this.model.getParent(cells[0]) + } + + this.model.batchUpdate(() => { + // Ensure the group's geometry + if (this.graph.getCellGeometry(group) == null) { + this.model.setGeometry(group, new Geometry()) + } + + // Adds the group into the parent + let index = this.model.getChildCount(parent!) + this.graph.creationManager.cellsAdded( + [group], + parent!, + index, + null, + null, + false, + false, + false, + ) + + // Adds the children into the group and moves + index = this.model.getChildCount(group) + + this.graph.creationManager.cellsAdded( + cells, + group, + index, + null, + null, + false, + false, + false, + ) + + this.graph.movingManager.cellsMoved( + cells, + -bounds.x, + -bounds.y, + false, + false, + false, + ) + + // Resizes the group + this.graph.sizeManager.cellsResized([group], [bounds], false) + + this.graph.trigger(events.groupCells, { group, cells, border }) + }) + } + } + + return group + } + + /** + * Returns the cells with the same parent as the first cell + * in the given array. + */ + protected getCellsForGroup(cells: Cell[]) { + const result = [] + + if (cells != null && cells.length > 0) { + const parent = this.model.getParent(cells[0]) + result.push(cells[0]) + + // Filters selection cells with the same parent + for (let i = 1, ii = cells.length; i < ii; i += 1) { + if (this.model.getParent(cells[i]) === parent) { + result.push(cells[i]) + } + } + } + + return result + } + + /** + * Returns the bounds to be used for the given group and children. + */ + protected getBoundsForGroup(group: Cell, children: Cell[], border: number) { + const result = this.graph.getBoundingBoxFromGeometry(children, true) + if (result != null) { + if (this.graph.isSwimlane(group)) { + const size = this.graph.getStartSize(group) + + result.x -= size.width + result.y -= size.height + result.width += size.width + result.height += size.height + } + + // Adds the border + if (border != null) { + result.x -= border + result.y -= border + result.width += 2 * border + result.height += 2 * border + } + } + + return result + } + + ungroupCells(cells?: Cell[]) { + let result: Cell[] = [] + + if (cells == null) { + const selected = this.graph.getSelectedCells() + // Finds the cells with children + // tslint:disable-next-line + cells = selected.filter(cell => this.model.getChildCount(cell) > 0) + } + + if (cells != null && cells.length > 0) { + this.model.batchUpdate(() => { + cells!.forEach(cell => { + let children = this.model.getChildren(cell) + if (children != null && children.length > 0) { + children = children.slice() + const parent = this.model.getParent(cell)! + const index = this.model.getChildCount(parent) + + this.graph.creationManager.cellsAdded( + children, + parent, + index, + null, + null, + true, + ) + result = result.concat(children) + } + }) + + this.removeGroupsAfterUngroup(cells!) + this.graph.trigger(events.ungroupCells, { cells }) + }) + } + + return result + } + + removeGroupsAfterUngroup(cells: Cell[]) { + this.graph.creationManager.cellsRemoved( + this.graph.creationManager.addAllEdges(cells), + ) + } + + updateGroupBounds( + cells: Cell[], + border: number = 0, + moveGroup: boolean = false, + topBorder: number = 0, + rightBorder: number = 0, + bottomBorder: number = 0, + leftBorder: number = 0, + ) { + this.model.batchUpdate(() => { + for (let i = cells.length - 1; i >= 0; i -= 1) { + let geo = this.graph.getCellGeometry(cells[i]) + if (geo != null) { + const children = this.graph.getChildren(cells[i]) + if (children != null && children.length > 0) { + const bounds = this.graph.getBoundingBoxFromGeometry(children, true) + if (bounds != null && bounds.width > 0 && bounds.height > 0) { + let left = 0 + let top = 0 + + // Adds the size of the title area for swimlanes + if (this.graph.isSwimlane(cells[i])) { + const size = this.graph.getStartSize(cells[i]) + left = size.width + top = size.height + } + + geo = geo.clone() + + if (moveGroup) { + geo.bounds.x = Math.round( + geo.bounds.x + bounds.x - border - left - leftBorder, + ) + + geo.bounds.y = Math.round( + geo.bounds.y + bounds.y - border - top - topBorder, + ) + } + + geo.bounds.width = Math.round( + bounds.width + 2 * border + left + leftBorder + rightBorder, + ) + + geo.bounds.height = Math.round( + bounds.height + 2 * border + top + topBorder + bottomBorder, + ) + + this.model.setGeometry(cells[i], geo) + + this.graph.movingManager.moveCells( + children, + border + left - bounds.x + leftBorder, + border + top - bounds.y + topBorder, + ) + } + } + } + } + }) + + return cells + } + + enterGroup(cell: Cell) { + if (cell != null && this.graph.isValidRoot(cell)) { + this.view.setCurrentRoot(cell) + this.graph.clearSelection() + } + } + + exitGroup() { + const root = this.model.getRoot() + const current = this.graph.getCurrentRoot() + if (current != null) { + let next = this.model.getParent(current) + + // Finds the next valid root in the hierarchy + while ( + next !== root && + !this.graph.isValidRoot(next) && + this.model.getParent(next) !== root + ) { + next = this.model.getParent(next)! + } + + // Clears the current root if the new root is + // the model's root or one of the layers. + if (next === root || this.model.getParent(next) === root) { + this.view.setCurrentRoot(null) + } else { + this.view.setCurrentRoot(next) + } + + // Selects the previous root in the graph + const state = this.view.getState(current) + if (state != null) { + this.graph.setSelectedCell(current) + } + } + } + + removeCellsFromParent(cells: Cell[]) { + this.model.batchUpdate(() => { + const parent = this.graph.getDefaultParent()! + const index = this.model.getChildCount(parent) + + this.graph.creationManager.cellsAdded( + cells, + parent, + index, + null, + null, + true, + ) + + this.graph.trigger(events.removeCellsFromParent, { cells }) + }) + + return cells + } +} diff --git a/packages/x6/src/graph/prop/guide.ts b/packages/x6/src/graph/guide-accessor.ts similarity index 85% rename from packages/x6/src/graph/prop/guide.ts rename to packages/x6/src/graph/guide-accessor.ts index b89c2588e66..7dade3e5477 100644 --- a/packages/x6/src/graph/prop/guide.ts +++ b/packages/x6/src/graph/guide-accessor.ts @@ -1,6 +1,6 @@ -import { GraphBase } from './base' +import { BaseGraph } from './base-graph' -export class GuidBehaviour extends GraphBase { +export class GuideAccessor extends BaseGraph { isGuideEnabled() { return this.guideHandler.isEnabled() } diff --git a/packages/x6/src/graph/hook.ts b/packages/x6/src/graph/hook.ts index 6e866d99394..98cc18610c5 100644 --- a/packages/x6/src/graph/hook.ts +++ b/packages/x6/src/graph/hook.ts @@ -4,7 +4,7 @@ import { Model } from '../core/model' import { State } from '../core/state' import { Graph } from '.' import { Renderer } from '../core/renderer' -import { Selection } from '../manager' +import { Selection } from './selection' import { Route } from '../route' import { Point, Anchor, Rectangle } from '../struct' import { @@ -327,7 +327,7 @@ export interface IHooks { /** * Returns the textual representation for the given cell. */ - convertDataToString: CellHook + dataToString: CellHook /** * Returns the terminal to be used for a given port. diff --git a/packages/x6/src/graph/prop/keyboard.ts b/packages/x6/src/graph/keyboard-accessor.ts similarity index 87% rename from packages/x6/src/graph/prop/keyboard.ts rename to packages/x6/src/graph/keyboard-accessor.ts index 946b343b2f1..12b8c186429 100644 --- a/packages/x6/src/graph/prop/keyboard.ts +++ b/packages/x6/src/graph/keyboard-accessor.ts @@ -1,7 +1,7 @@ -import { GraphBase } from './base' -import { KeyboardHandler } from '../../handler' +import { BaseGraph } from './base-graph' +import { KeyboardHandler } from '../handler' -export class KeyboardBehaviour extends GraphBase { +export class KeyboardAccessor extends BaseGraph { isKeyboardEnabled() { return this.keyboardHandler.isEnabled() } diff --git a/packages/x6/src/graph/moving-accessor.ts b/packages/x6/src/graph/moving-accessor.ts new file mode 100644 index 00000000000..34d7eddea1b --- /dev/null +++ b/packages/x6/src/graph/moving-accessor.ts @@ -0,0 +1,124 @@ +import { Cell } from '../core/cell' +import { Align, VAlign } from '../types' +import { hook } from './decorator' +import { BaseGraph } from './base-graph' + +export class MovingAccessor extends BaseGraph { + @hook() + isCellMovable(cell: Cell | null) { + const style = this.getStyle(cell) + return ( + this.isCellsMovable() && + !this.isCellLocked(cell) && + style.movable !== false + ) + } + + getMovableCells(cells: Cell[]) { + return this.model.filterCells(cells, cell => this.isCellMovable(cell)) + } + + moveCell( + cell: Cell, + dx: number = 0, + dy: number = 0, + clone: boolean = false, + target?: Cell | null, + e?: MouseEvent, + cache?: WeakMap, + ) { + return this.moveCells([cell], dx, dy, clone, target, e, cache) + } + + /** + * Moves or clones the specified cells and moves the cells or clones by the + * given amount, adding them to the optional target cell. + * + * @param cells Array of `Cell`s to be moved, cloned or added to the target. + * @param dx Specifies the x-coordinate of the vector. Default is `0`. + * @param dy Specifies the y-coordinate of the vector. Default is `0`. + * @param clone Indicating if the cells should be cloned. Default is `false`. + * @param target The new parent of the cells. + * @param e Mouseevent that triggered the invocation. + * @param cache Optional mapping for existing clones. + */ + moveCells( + cells: Cell[], + dx: number = 0, + dy: number = 0, + clone: boolean = false, + target?: Cell | null, + e?: MouseEvent, + cache?: WeakMap, + ) { + return this.movingManager.moveCells(cells, dx, dy, clone, target, e, cache) + } + + /** + * Clones and inserts the given cells into the graph. + * + * @param cells Array of `Cell`s to be cloned. + * @param dx Specifies the x-coordinate of the vector. Default is `0`. + * @param dy Specifies the y-coordinate of the vector. Default is `0`. + * @param target The new parent of the cells. + * @param e Mouseevent that triggered the invocation. + * @param cache Optional mapping for existing clones. + */ + importCells( + cells: Cell[], + dx: number, + dy: number, + target?: Cell, + e?: MouseEvent, + cache?: WeakMap, + ) { + return this.moveCells(cells, dx, dy, true, target, e, cache) + } + + /** + * Moves the given cells to the front or back. + */ + orderCells( + toBack: boolean = false, + cells: Cell[] = Cell.sortCells(this.getSelectedCells(), true), + ) { + return this.movingManager.orderCells(toBack, cells) + } + + /** + * Aligns the given cells vertically or horizontally according to the + * given alignment using the optional parameter as the coordinate. + */ + alignCells( + align: Align | VAlign, + cells: Cell[] = this.getSelectedCells(), + param?: number, + ) { + return this.movingManager.alignCells(align, cells, param) + } + + /** + * 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) { + this.movingManager.translateCell(cell, dx, dy) + } + + /** + * Resets the control points of the edges that are connected to the given + * cells if not both ends of the edge are in the given cells array. + */ + resetOtherEdges(cells: Cell[]) { + this.movingManager.resetOtherEdges(cells) + return this + } + + /** + * Resets the control points of the given edge. + */ + resetEdge(edge: Cell) { + this.movingManager.resetEdge(edge) + return this + } +} diff --git a/packages/x6/src/graph/moving-manager.ts b/packages/x6/src/graph/moving-manager.ts new file mode 100644 index 00000000000..c1bb2cc4216 --- /dev/null +++ b/packages/x6/src/graph/moving-manager.ts @@ -0,0 +1,336 @@ +import * as util from '../util' +import { Cell } from '../core/cell' +import { Point } from '../struct' +import { events } from './events' +import { BaseManager } from './base-manager' +import { Align, VAlign } from '../types' + +export class MovingManager extends BaseManager { + moveCells( + cells: Cell[], + dx: number = 0, + dy: number = 0, + clone: boolean = false, + target?: Cell | null, + e?: MouseEvent, + cache?: WeakMap, + ) { + if (cells != null && (dx !== 0 || dy !== 0 || clone || target != null)) { + // Removes descendants with ancestors in cells to avoid multiple moving + cells = this.model.getTopmostCells(cells) // tslint:disable-line + + this.model.batchUpdate(() => { + const dict = new WeakMap() + cells.forEach(c => dict.set(c, true)) + + const isSelected = (cell: Cell | null) => { + let node = cell + while (node != null) { + if (dict.get(node)) { + return true + } + + node = this.model.getParent(node)! + } + + return false + } + + // Removes relative edge labels with selected terminals + const checked = [] + + for (let i = 0; i < cells.length; i += 1) { + const geo = this.graph.getCellGeometry(cells[i]) + const parent = this.model.getParent(cells[i])! + + if ( + geo == null || + !geo.relative || + !this.model.isEdge(parent) || + (!isSelected(this.model.getTerminal(parent, true)) && + !isSelected(this.model.getTerminal(parent, false))) + ) { + checked.push(cells[i]) + } + } + + // tslint:disable-next-line + cells = checked + + if (clone) { + // tslint:disable-next-line + cells = this.graph.creationManager.cloneCells( + cells, + this.graph.isInvalidEdgesClonable(), + cache, + )! + + if (target == null) { + // tslint:disable-next-line + target = this.graph.getDefaultParent()! + } + } + + const previous = this.graph.isNegativeCoordinatesAllowed() + + if (target != null) { + this.graph.setNegativeCoordinatesAllowed(true) + } + + this.graph.trigger(events.moveCells, { + cells, + dx, + dy, + clone, + target, + e, + }) + + this.cellsMoved( + cells, + dx, + dy, + !clone && + this.graph.isDisconnectOnMove() && + this.graph.isDanglingEdgesEnabled(), + target == null, + this.graph.isExtendParentsOnMove() && target == null, + ) + + this.graph.setNegativeCoordinatesAllowed(previous) + + if (target != null) { + const index = this.model.getChildCount(target) + this.graph.creationManager.cellsAdded( + cells, + target, + index, + null, + null, + true, + ) + } + }) + } + + return cells + } + + cellsMoved( + cells: Cell[], + dx: number, + dy: number, + disconnect: boolean, + constrain: boolean, + extend: boolean = false, + ) { + if (cells != null && (dx !== 0 || dy !== 0)) { + this.model.batchUpdate(() => { + if (disconnect) { + this.graph.disconnectGraph(cells) + } + + cells.forEach(cell => { + this.translateCell(cell, dx, dy) + if (extend && this.graph.isExtendParent(cell)) { + this.graph.sizeManager.extendParent(cell) + } else if (constrain) { + this.graph.sizeManager.constrainChild(cell) + } + }) + + if (this.graph.resetEdgesOnMove) { + this.resetOtherEdges(cells) + } + + this.graph.trigger(events.cellsMoved, { + cells, + dx, + dy, + disconnect, + }) + }) + } + } + + translateCell(cell: Cell, dx: number, dy: number) { + let geo = this.model.getGeometry(cell) + if (geo != null) { + geo = geo.clone() + geo.translate(dx, dy) + + if ( + !geo.relative && + this.model.isNode(cell) && + !this.graph.isNegativeCoordinatesAllowed() + ) { + geo.bounds.x = Math.max(0, geo.bounds.x) + geo.bounds.y = Math.max(0, geo.bounds.y) + } + + if (geo.relative && !this.model.isEdge(cell)) { + const parent = this.model.getParent(cell)! + let rot = 0 + + if (this.model.isNode(parent)) { + const style = this.graph.getStyle(parent) + rot = style.rotation || 0 + } + + if (rot !== 0) { + const pt = util.rotatePoint(new Point(dx, dy), -rot, new Point(0, 0)) + dx = pt.x // tslint:disable-line + dy = pt.y // tslint:disable-line + } + + if (geo.offset == null) { + geo.offset = new Point(dx, dy) + } else { + geo.offset.x = geo.offset.x + dx + geo.offset.y = geo.offset.y + dy + } + } + + this.model.setGeometry(cell, geo) + } + } + + resetOtherEdges(cells: Cell[]) { + if (cells != null) { + // Prepares faster cells lookup + const dict = new WeakMap() + cells.forEach(c => dict.set(c, true)) + + this.model.batchUpdate(() => { + cells.forEach(cell => { + const edges = this.model.getEdges(cell) + if (edges != null) { + edges.forEach(edge => { + const [ + source, + target, + ] = this.graph.retrievalManager.getVisibleTerminals(edge) + + // Checks if one of the terminals is not in the given array + if (!dict.get(source!) || !dict.get(target!)) { + this.resetEdge(edge) + } + }) + } + + this.resetOtherEdges(this.model.getChildren(cell)) + }) + }) + } + } + + resetEdge(edge: Cell) { + let geo = this.model.getGeometry(edge) + if (geo != null && geo.points != null && geo.points.length > 0) { + geo = geo.clone() + geo.points = [] + this.model.setGeometry(edge, geo) + } + } + + alignCells(align: Align | VAlign, cells: Cell[], param?: number) { + if (cells != null && cells.length > 1) { + let coord = param + + // Finds the required coordinate for the alignment + if (coord == null) { + for (let i = 0, ii = cells.length; i < ii; i += 1) { + const state = this.view.getState(cells[i]) + if (state != null && !this.model.isEdge(cells[i])) { + if (coord == null) { + if (align === 'center') { + coord = state.bounds.x + state.bounds.width / 2 + } else if (align === 'right') { + coord = state.bounds.x + state.bounds.width + } else if (align === 'top') { + coord = state.bounds.y + } else if (align === 'middle') { + coord = state.bounds.y + state.bounds.height / 2 + } else if (align === 'bottom') { + coord = state.bounds.y + state.bounds.height + } else { + coord = state.bounds.x + } + } else { + if (align === 'right') { + coord = Math.max(coord, state.bounds.x + state.bounds.width) + } else if (align === 'top') { + coord = Math.min(coord, state.bounds.y) + } else if (align === 'bottom') { + coord = Math.max(coord, state.bounds.y + state.bounds.height) + } else { + coord = Math.min(coord, state.bounds.x) + } + } + } + } + } + + // Aligns the cells to the coordinate + this.model.batchUpdate(() => { + const s = this.view.scale + cells.forEach(cell => { + const state = this.view.getState(cell) + if (state != null && coord != null) { + const bounds = state.bounds + let geo = this.graph.getCellGeometry(cell) + if (geo != null && !this.model.isEdge(cell)) { + geo = geo.clone() + + if (align === 'center') { + geo.bounds.x += (coord - bounds.x - bounds.width / 2) / s + } else if (align === 'right') { + geo.bounds.x += (coord - bounds.x - bounds.width) / s + } else if (align === 'top') { + geo.bounds.y += (coord - bounds.y) / s + } else if (align === 'middle') { + geo.bounds.y += (coord - bounds.y - bounds.height / 2) / s + } else if (align === 'bottom') { + geo.bounds.y += (coord - bounds.y - bounds.height) / s + } else { + geo.bounds.x += (coord - bounds.x) / s + } + + this.graph.resizeCell(cell, geo.bounds) + } + } + }) + }) + + this.graph.trigger(events.alignCells, { align, cells }) + } + + return cells + } + + orderCells(toBack: boolean, cells: Cell[]) { + this.model.batchUpdate(() => { + this.graph.trigger(events.orderCells, { cells, toBack }) + this.cellsOrdered(cells, toBack) + }) + return cells + } + + cellsOrdered(cells: Cell[], toBack: boolean) { + if (cells != null) { + this.model.batchUpdate(() => { + cells.forEach((cell, i) => { + const parent = this.model.getParent(cell)! + + if (toBack) { + this.model.add(parent, cell, i) + } else { + this.model.add(parent, cell, this.model.getChildCount(parent) - 1) + } + }) + + this.graph.trigger(events.cellsOrdered, { cells, toBack }) + }) + } + } +} diff --git a/packages/x6/src/graph/overlay-accessor.ts b/packages/x6/src/graph/overlay-accessor.ts new file mode 100644 index 00000000000..1632ef12954 --- /dev/null +++ b/packages/x6/src/graph/overlay-accessor.ts @@ -0,0 +1,69 @@ +import { Cell } from '../core/cell' +import { Image, Overlay } from '../struct' +import { BaseGraph } from './base-graph' + +export class OverlayAccessor extends BaseGraph { + /** + * Returns the array of `Overlay` for the given cell + * or null if no overlays are defined. + */ + getOverlays(cell: Cell | null) { + return cell != null ? cell.getOverlays() : null + } + + /** + * Adds an `Overlay` for the specified cell. + */ + addOverlay(cell: Cell, overlay: Overlay) { + return this.overlayManager.addOverlay(cell, overlay) + } + + /** + * Removes and returns the given `Overlay` from the given cell. + * If no overlay is given, then all overlays are removed. + */ + removeOverlay(cell: Cell, overlay?: Overlay | null) { + return this.overlayManager.removeOverlay(cell, overlay) + } + + removeOverlays(cell: Cell) { + return this.overlayManager.removeOverlays(cell) + } + + /** + * Removes all `Overlays` in the graph for the given cell and all its + * descendants. If no cell is specified then all overlays are removed + * from the graph. + */ + clearOverlays(cell: Cell = this.model.getRoot()) { + this.removeOverlays(cell) + cell.eachChild(child => this.clearOverlays(child)) + return cell + } + + /** + * Creates an overlay for the given cell using the `warning` string and + * image `warningImage`, then returns the new `Overlay`. The `warning` + * string is displayed as a tooltip in a red font and may contain HTML + * markup. If the `warning` string is null or a zero length string, then + * all overlays are removed from the cell. + */ + addWarningOverlay( + cell: Cell, + warning?: string | null, + img: Image = this.warningImage, + selectOnClick: boolean = false, + ) { + if (warning != null && warning.length > 0) { + return this.overlayManager.addWarningOverlay( + cell, + warning, + img, + selectOnClick, + ) + } + + this.removeOverlays(cell) + return null + } +} diff --git a/packages/x6/src/graph/overlay-manager.ts b/packages/x6/src/graph/overlay-manager.ts new file mode 100644 index 00000000000..0bd5cee64ed --- /dev/null +++ b/packages/x6/src/graph/overlay-manager.ts @@ -0,0 +1,88 @@ +import { Cell } from '../core/cell' +import { Overlay, Image } from '../struct' +import { events } from './events' +import { BaseManager } from './base-manager' + +export class OverlayManager extends BaseManager { + addOverlay(cell: Cell, overlay: Overlay) { + if (cell.overlays == null) { + cell.overlays = [] + } + + cell.overlays.push(overlay) + + const state = this.view.getState(cell) + if (state != null) { + // Immediately updates the cell display if the state exists + this.renderer.redraw(state) + } + + this.graph.trigger(events.addOverlay, { cell, overlay }) + + return overlay + } + + removeOverlay(cell: Cell, overlay?: Overlay | null) { + if (overlay == null) { + return this.removeOverlays(cell) + } + + const idx = cell.overlays != null ? cell.overlays.indexOf(overlay) : -1 + if (idx >= 0 && cell.overlays != null) { + cell.overlays.splice(idx, 1) + if (cell.overlays.length === 0) { + delete cell.overlays + } + + const state = this.view.getState(cell) + if (state != null) { + this.renderer.redraw(state) + } + + this.graph.trigger(events.removeOverlay, { cell, overlay }) + + return overlay + } + + return null + } + + removeOverlays(cell: Cell) { + const overlays = cell.overlays + if (overlays != null) { + delete cell.overlays + const state = this.view.getState(cell) + if (state != null) { + this.renderer.redraw(state) + } + + this.graph.trigger(events.removeOverlays, { cell, overlays }) + } + + return overlays + } + + addWarningOverlay( + cell: Cell, + warning: string | null, + image: Image, + selectOnClick: boolean, + ) { + const overlay = new Overlay({ + image, + tooltip: `${warning}`, + }) + + // Adds a handler for single mouseclicks to select the cell + if (selectOnClick) { + // TODO: + // overlay.addListener('click', () => { + // if (this.isEnabled()) { + // this.setSelectionCell(cell) + // } + // }) + } + + return this.addOverlay(cell, overlay) + } +} diff --git a/packages/x6/src/graph/prop/pagebreak.ts b/packages/x6/src/graph/pagebreak-accessor.ts similarity index 90% rename from packages/x6/src/graph/prop/pagebreak.ts rename to packages/x6/src/graph/pagebreak-accessor.ts index 9807a58850f..67a16fbe45c 100644 --- a/packages/x6/src/graph/prop/pagebreak.ts +++ b/packages/x6/src/graph/pagebreak-accessor.ts @@ -1,33 +1,6 @@ -import { GraphBase } from './base' +import { BaseGraph } from './base-graph' -export interface PageBreakOptions { - /** - * Specifies if a dashed line should be drawn between multiple pages. - * - * Default is `false`. - */ - enabled: boolean - /** - * Specifies the stroke color for page breaks. - * - * Default is `#808080`. - */ - stroke: string - /** - * Specifies the page breaks should be dashed. - * - * Default is `true`. - */ - dsahed: boolean - /** - * Specifies the minimum distance for page breaks to be visible. - * - * Default is `20`. - */ - minDist: number -} - -export class GraphPageBreak extends GraphBase { +export class PageBreakAccessor extends BaseGraph { isPageBreakEnabled() { return this.options.pageBreak.enabled } @@ -123,4 +96,36 @@ export class GraphPageBreak extends GraphBase { set pageBreakMinDist(minDist: number) { this.setPageBreakMinDist(minDist) } + + updatePageBreaks(visible: boolean, width: number, height: number) { + this.pageBreakManager.updatePageBreaks(visible, width, height) + return this + } +} + +export interface PageBreakOptions { + /** + * Specifies if a dashed line should be drawn between multiple pages. + * + * Default is `false`. + */ + enabled: boolean + /** + * Specifies the stroke color for page breaks. + * + * Default is `#808080`. + */ + stroke: string + /** + * Specifies the page breaks should be dashed. + * + * Default is `true`. + */ + dsahed: boolean + /** + * Specifies the minimum distance for page breaks to be visible. + * + * Default is `20`. + */ + minDist: number } diff --git a/packages/x6/src/graph/pagebreak-manager.ts b/packages/x6/src/graph/pagebreak-manager.ts new file mode 100644 index 00000000000..9a2a37a1c96 --- /dev/null +++ b/packages/x6/src/graph/pagebreak-manager.ts @@ -0,0 +1,105 @@ +import { Polyline } from '../shape' +import { Rectangle, Point } from '../struct' +import { BaseManager } from './base-manager' + +export class PageBreakManager extends BaseManager { + protected verticalPageBreaks: Polyline[] + protected horizontalPageBreaks: Polyline[] + + updatePageBreaks(visible: boolean, width: number, height: number) { + const s = this.view.scale + const t = this.view.translate + const fmt = this.graph.pageFormat + const ps = s * this.graph.pageScale + const bounds = new Rectangle(0, 0, fmt.width * ps, fmt.height * ps) + + const gb = this.graph.getGraphBounds().clone() + gb.width = Math.max(1, gb.width) + gb.height = Math.max(1, gb.height) + + bounds.x = + Math.floor((gb.x - t.x * s) / bounds.width) * bounds.width + t.x * s + bounds.y = + Math.floor((gb.y - t.y * s) / bounds.height) * bounds.height + t.y * s + + gb.width = + Math.ceil((gb.width + (gb.x - bounds.x)) / bounds.width) * bounds.width + gb.height = + Math.ceil((gb.height + (gb.y - bounds.y)) / bounds.height) * bounds.height + + // Does not show page breaks if the scale is too small + // tslint:disable-next-line + visible = + visible && + Math.min(bounds.width, bounds.height) > this.graph.pageBreakMinDist + + const hCount = visible ? Math.ceil(gb.height / bounds.height) + 1 : 0 + const vCount = visible ? Math.ceil(gb.width / bounds.width) + 1 : 0 + const right = (vCount - 1) * bounds.width + const bottom = (hCount - 1) * bounds.height + + if (this.horizontalPageBreaks == null && hCount > 0) { + this.horizontalPageBreaks = [] + } + + if (this.verticalPageBreaks == null && vCount > 0) { + this.verticalPageBreaks = [] + } + + const drawPageBreaks = (breaks: Polyline[]) => { + if (breaks != null) { + const count = breaks === this.horizontalPageBreaks ? hCount : vCount + + for (let i = 0; i <= count; i += 1) { + const pts = + breaks === this.horizontalPageBreaks + ? [ + new Point( + Math.round(bounds.x), + Math.round(bounds.y + i * bounds.height), + ), + new Point( + Math.round(bounds.x + right), + Math.round(bounds.y + i * bounds.height), + ), + ] + : [ + new Point( + Math.round(bounds.x + i * bounds.width), + Math.round(bounds.y), + ), + new Point( + Math.round(bounds.x + i * bounds.width), + Math.round(bounds.y + bottom), + ), + ] + + if (breaks[i] != null) { + breaks[i].points = pts + breaks[i].redraw() + } else { + const pageBreak = new Polyline(pts, this.graph.pageBreakColor) + pageBreak.dialect = this.graph.dialect + pageBreak.dashed = this.graph.pageBreakDashed + pageBreak.pointerEvents = false + pageBreak.init(this.view.getBackgroundPane()) + pageBreak.redraw() + + breaks[i] = pageBreak + } + } + + for (let i = count; i < breaks.length; i += 1) { + breaks[i].dispose() + } + + breaks.splice(count, breaks.length - count) + } + } + + drawPageBreaks(this.horizontalPageBreaks) + drawPageBreaks(this.verticalPageBreaks) + + return this + } +} diff --git a/packages/x6/src/graph/panning-accessor.ts b/packages/x6/src/graph/panning-accessor.ts new file mode 100644 index 00000000000..0aa6d3de29e --- /dev/null +++ b/packages/x6/src/graph/panning-accessor.ts @@ -0,0 +1,148 @@ +import * as util from '../util' +import { events } from './events' +import { BaseGraph } from './base-graph' + +export class PanningAccessor extends BaseGraph { + public panDx: number = 0 + public panDy: number = 0 + protected shiftPreview1: HTMLElement | null + protected shiftPreview2: HTMLElement | null + + enablePanning() { + this.panningHandler.enablePanning() + return this + } + + disablePanning() { + this.panningHandler.disablePanning() + return this + } + + enablePinch() { + this.panningHandler.enablePinch() + return this + } + + disablePinch() { + this.panningHandler.disablePinch() + return this + } + + /** + * Shifts the graph display by the given amount. This is used to preview + * panning operations, use `view.setTranslate` to set a persistent + * translation of the view. + * + * @param dx Amount to shift the graph along the x-axis. + * @param dy Amount to shift the graph along the y-axis. + */ + pan(x: number, y: number, relative: boolean = false) { + const dx = relative ? this.panDx + x : x + const dy = relative ? this.panDy + y : y + + if (this.useScrollbarsForPanning && util.hasScrollbars(this.container)) { + const container = this.container + const maxScrollLeft = container.scrollWidth - container.clientWidth + const maxScrollTop = container.scrollHeight - container.clientHeight + const scrollLeft = util.clamp(dx, 0, maxScrollLeft) + const scrollTop = util.clamp(dy, 0, maxScrollTop) + container.scrollLeft = scrollLeft + container.scrollTop = scrollTop + } else { + const stage = this.view.getStage()! + if (this.dialect === 'svg') { + // Puts everything inside the container in a DIV so that it + // can be moved without changing the state of the container + if (dx === 0 && dy === 0) { + stage.removeAttribute('transform') + + if (this.shiftPreview1 != null && this.shiftPreview2 != null) { + let child = this.shiftPreview1.firstChild + while (child != null) { + const next = child.nextSibling + this.container.appendChild(child) + child = next + } + + util.removeElement(this.shiftPreview1) + this.shiftPreview1 = null + + this.container.appendChild(stage.parentNode!) + + child = this.shiftPreview2.firstChild + while (child != null) { + const next = child.nextSibling + this.container.appendChild(child) + child = next + } + + util.removeElement(this.shiftPreview2) + this.shiftPreview2 = null + } + } else { + stage.setAttribute('transform', `translate(${dx},${dy})`) + + if (this.shiftPreview1 == null) { + // Needs two divs for stuff before and after the SVG element + this.shiftPreview1 = document.createElement('div') + this.shiftPreview1.style.position = 'absolute' + this.shiftPreview1.style.overflow = 'visible' + + this.shiftPreview2 = document.createElement('div') + this.shiftPreview2.style.position = 'absolute' + this.shiftPreview2.style.overflow = 'visible' + + let current = this.shiftPreview1 + let child = this.container.firstChild as HTMLElement + + while (child != null) { + const next = child.nextSibling as HTMLElement + // SVG element is moved via transform attribute + if (child !== stage.parentNode) { + current.appendChild(child) + } else { + current = this.shiftPreview2 + } + + child = next + } + + // Inserts elements only if not empty + if (this.shiftPreview1.firstChild != null) { + this.container.insertBefore(this.shiftPreview1, stage.parentNode) + } + + if (this.shiftPreview2.firstChild != null) { + this.container.appendChild(this.shiftPreview2) + } + } + + this.shiftPreview1.style.left = `${dx}px` + this.shiftPreview1.style.top = `${dy}px` + this.shiftPreview2!.style.left = util.toPx(dx) + this.shiftPreview2!.style.top = util.toPx(dy) + } + } else { + stage.style.left = util.toPx(dx) + stage.style.top = util.toPx(dy) + } + + this.panDx = dx + this.panDy = dy + } + + this.trigger(events.pan, { dx, dy }) + + return this + } + + panTo(x: number, y: number) { + this.pan(x, y, false) + return this + } + + panBy(x: number, y: number) { + this.pan(x, y, true) + return this + } +} diff --git a/packages/x6/src/manager/panning-manager.ts b/packages/x6/src/graph/panning-manager.ts similarity index 98% rename from packages/x6/src/manager/panning-manager.ts rename to packages/x6/src/graph/panning-manager.ts index 1d486e8bf53..7200652037c 100644 --- a/packages/x6/src/manager/panning-manager.ts +++ b/packages/x6/src/graph/panning-manager.ts @@ -2,9 +2,9 @@ import * as util from '../util' import { Graph } from '../graph' import { MouseEventEx, DomEvent } from '../common' import { IMouseHandler } from '../handler' -import { ManagerBase } from './base' +import { BaseManager } from './base-manager' -export class PanningManager extends ManagerBase { +export class PanningManager extends BaseManager { /** * Damper value for the panning. * diff --git a/packages/x6/src/graph/prop/connection.ts b/packages/x6/src/graph/prop/connection.ts deleted file mode 100644 index 8369141bb8b..00000000000 --- a/packages/x6/src/graph/prop/connection.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { GraphBase } from './base' - -export class ConnectionBehaviour extends GraphBase { - setConnectable(connectable: boolean) { - if (connectable) { - this.connectionHandler.enable() - } else { - this.connectionHandler.disable() - } - return this - } - - enableConnection() { - this.connectionHandler.enable() - return this - } - - disableConnection() { - this.connectionHandler.disable() - return this - } - - isConnectable() { - return this.connectionHandler.isEnabled() - } -} diff --git a/packages/x6/src/graph/prop/index.ts b/packages/x6/src/graph/prop/index.ts deleted file mode 100644 index 10b167ff1d8..00000000000 --- a/packages/x6/src/graph/prop/index.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { applyMixins } from '../../util' -import { GraphBase } from './base' -import { GraphGrid } from './grid' -import { GraphFolding } from './folding' -import { GuidBehaviour } from './guide' -import { GraphPageBreak } from './pagebreak' -import { TooltipBehaviour } from './tooltip' -import { PanningBehaviour } from './panning' -import { KeyboardBehaviour } from './keyboard' -import { ConnectionBehaviour } from './connection' -import { RubberbandBehaviour } from './rubberband' - -export class GraphProp extends GraphBase {} - -export interface GraphProp - extends GraphGrid, - GraphPageBreak, - GraphFolding, - GuidBehaviour, - TooltipBehaviour, - PanningBehaviour, - KeyboardBehaviour, - RubberbandBehaviour, - ConnectionBehaviour {} - -applyMixins(GraphProp, [ - GraphGrid, - GraphPageBreak, - GraphFolding, - GuidBehaviour, - TooltipBehaviour, - PanningBehaviour, - KeyboardBehaviour, - RubberbandBehaviour, - ConnectionBehaviour, -]) diff --git a/packages/x6/src/graph/prop/panning.ts b/packages/x6/src/graph/prop/panning.ts deleted file mode 100644 index d89742dae12..00000000000 --- a/packages/x6/src/graph/prop/panning.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { GraphBase } from './base' - -export class PanningBehaviour extends GraphBase { - enablePanning() { - this.panningHandler.enablePanning() - return this - } - - disablePanning() { - this.panningHandler.disablePanning() - return this - } - - enablePinch() { - this.panningHandler.enablePinch() - return this - } - - disablePinch() { - this.panningHandler.disablePinch() - return this - } -} diff --git a/packages/x6/src/graph/retrieval-accessor.ts b/packages/x6/src/graph/retrieval-accessor.ts new file mode 100644 index 00000000000..9f244daa2b4 --- /dev/null +++ b/packages/x6/src/graph/retrieval-accessor.ts @@ -0,0 +1,315 @@ +import { Cell } from '../core/cell' +import { State } from '../core/state' +import { hook } from './decorator' +import { BaseGraph } from './base-graph' + +export class RetrievalAccessor extends BaseGraph { + @hook() + isValidRoot(cell: Cell | null) { + return cell != null + } + + @hook() + isCellVisible(cell: Cell | null) { + return cell != null ? this.model.isVisible(cell) : false + } + + getCurrentRoot() { + return this.view.currentRoot + } + + setDefaultParent(cell: Cell | null) { + this.retrievalManager.setDefaultParent(cell) + return this + } + + getDefaultParent(): Cell { + return this.retrievalManager.getDefaultParent() + } + + /** + * Uses the root of the model as the root of the displayed + * cell hierarchy and selects the previous root. + */ + home() { + this.retrievalManager.home() + return this + } + + /** + * Returns the nearest ancestor of the given cell which is a + * swimlane, or the given cell, if it is itself a swimlane. + */ + getSwimlane(cell: Cell | null): Cell | null { + return this.retrievalManager.getSwimlane(cell) + } + + /** + * Returns the bottom-most swimlane that intersects the given + * point in the cell hierarchy that starts at the given parent. + */ + getSwimlaneAt( + x: number, + y: number, + parent: Cell = this.getDefaultParent(), + ): Cell | null { + return this.retrievalManager.getSwimlaneAt(x, y, parent) + } + + /** + * Returns the bottom-most cell that intersects the given point in + * the cell hierarchy starting at the given parent. + * + * @param x X-coordinate of the location to be checked. + * @param y Y-coordinate of the location to be checked. + * @param parent The root of the recursion. Default is current root of the + * view or the root of the model. + * @param includeNodes Optional boolean indicating if nodes should be + * returned. Default is `true`. + * @param includeEdges Optional boolean indicating if edges should be + * returned. Default is `true`. + * @param ignoreFn Optional function that returns true if cell should be + * ignored. + */ + getCellAt( + x: number, + y: number, + parent?: Cell | null, + includeNodes: boolean = true, + includeEdges: boolean = true, + ignoreFn?: (state: State, x?: number, y?: number) => boolean, + ): Cell | null { + return this.retrievalManager.getCellAt( + x, + y, + parent, + includeNodes, + includeEdges, + ignoreFn, + ) + } + + /** + * Returns the visible child nodes of the given parent. + */ + getChildNodes(parent: Cell) { + return this.getChildren(parent, true, false) + } + + /** + * Returns the visible child edges of the given parent. + */ + getChildEdges(parent: Cell) { + return this.getChildren(parent, false, true) + } + + /** + * Returns the visible child nodes or edges of the given parent. + */ + getChildren( + parent: Cell = this.getDefaultParent(), + includeNodes: boolean = false, + includeEdges: boolean = false, + ) { + const cells = this.model.getChildren(parent, includeNodes, includeEdges) + return cells.filter(cell => this.isCellVisible(cell)) + } + + /** + * Returns all visible edges connected to the given cell without loops. + */ + getConnections(node: Cell, parent?: Cell | null) { + return this.getEdges(node, parent, true, true, false) + } + + /** + * Returns the visible incoming edges for the given cell. If the optional + * parent argument is specified, then only child edges of the given parent + * are returned. + */ + getIncomingEdges(node: Cell, parent?: Cell | null) { + return this.getEdges(node, parent, true, false, false) + } + + /** + * Returns the visible outgoing edges for the given cell. If the optional + * parent argument is specified, then only child edges of the given parent + * are returned. + */ + getOutgoingEdges(node: Cell, parent?: Cell | null) { + return this.getEdges(node, parent, false, true, false) + } + + /** + * Returns the incoming and/or outgoing edges for the given cell. + * + * If the optional parent argument is specified, then only edges are returned + * where the opposite terminal is in the given parent cell. If at least one + * of incoming or outgoing is true, then loops are ignored, if both are false, + * then all edges connected to the given cell are returned including loops. + * + * Parameters: + * + * @param node `Cell` whose edges should be returned. + * @param parent Optional parent of the opposite end for an edge to be + * returned. + * @param incoming Specifies if incoming edges should be included in the + * result. Default is `true`. + * @param outgoing Specifies if outgoing edges should be included in the + * result. Default is `true`. + * @param includeLoops - Specifies if loops should be included in the + * result. Default is `true`. + * @param recurse - Specifies if the parent specified only need be + * an ancestral parent, true, or the direct parent, false. + */ + getEdges( + node: Cell, + parent?: Cell | null, + incoming: boolean = true, + outgoing: boolean = true, + includeLoops: boolean = true, + recurse: boolean = false, + ) { + return this.retrievalManager.getEdges( + node, + parent, + incoming, + outgoing, + includeLoops, + recurse, + ) + } + + /** + * Returns all distinct visible opposite cells for the specified terminal + * on the given edges. + * + * @param edges Array of `Cell`s that contains the edges whose opposite + * terminals should be returned. + * @param terminal - Specifies the end whose opposite should be returned. + * @param includeSources - Optional boolean that specifies if source + * terminals should be included in the result. Default is `true`. + * @param includeTargets - Optional boolean that specifies if target + * terminals should be included in the result. Default is `true`. + */ + getOppositeNodes( + edges: Cell[], + terminal: Cell, + includeSources: boolean = true, + includeTargets: boolean = true, + ) { + return this.retrievalManager.getOppositeNodes( + edges, + terminal, + includeSources, + includeTargets, + ) + } + + /** + * Returns the edges between the given source and target. This takes into + * account collapsed and invisible cells and returns the connected edges + * as displayed on the screen. + */ + getEdgesBetween(source: Cell, target: Cell, directed: boolean = false) { + return this.retrievalManager.getEdgesBetween(source, target, directed) + } + + /** + * Returns the child nodes and edges of the given parent that are contained + * in the given rectangle. + * + * @param x X-coordinate of the rectangle. + * @param y Y-coordinate of the rectangle. + * @param w Width of the rectangle. + * @param h Height of the rectangle. + * @param parent `Cell` that should be used as the root of the recursion. + * Default is current root of the view or the root of the model. + * @param result Optional array to store the result in. + */ + getCellsInRegion( + x: number, + y: number, + w: number, + h: number, + parent: Cell = this.getCurrentRoot() || this.model.getRoot(), + result: Cell[] = [], + ) { + return this.retrievalManager.getCellsInRegion(x, y, w, h, parent, result) + } + + /** + * Returns the children of the given parent that are contained in the + * canvas from the given point (x0, y0) rightwards or downwards + * depending on rightHalfpane and bottomHalfpane. + * + * @param x X-coordinate of the origin. + * @param y Y-coordinate of the origin. + * @param parent Optional `Cell` whose children should be checked. + * @param isRight - Boolean indicating if the cells in the right + * canvas from the origin should be returned. + * @param isBottom - Boolean indicating if the cells in the bottom + * canvas from the origin should be returned. + */ + getCellsBeyond( + x: number, + y: number, + parent: Cell = this.getDefaultParent(), + isRight: boolean = false, + isBottom: boolean = false, + ) { + return this.retrievalManager.getCellsBeyond(x, y, parent, isRight, isBottom) + } + + /** + * Returns all children in the given parent which do not have incoming + * edges. If the result is empty then the with the greatest difference + * between incoming and outgoing edges is returned. + * + * @param parent `Cell` whose children should be checked. + * @param isolate Optional boolean that specifies if edges should be ignored + * if the opposite end is not a child of the given parent cell. + * Default is `false`. + * @param invert - Optional boolean that specifies if outgoing or incoming + * edges should be counted for a tree root. If `false` then outgoing edges + * will be counted. + * Default is `false`. + */ + findTreeRoots( + parent: Cell | null, + isolate: boolean = false, + invert: boolean = false, + ) { + return this.retrievalManager.findTreeRoots(parent, isolate, invert) + } + + /** + * Traverses the (directed) graph invoking the given function for each + * visited node and edge. The function is invoked with the current node + * and the incoming edge as a parameter. This implementation makes sure + * each node is only visited once. The function may return false if the + * traversal should stop at the given node. + * + * @param node The node where the traversal starts. + * @param directed Optional boolean indicating if edges should only be + * traversed from source to target. Default is `true`. + * @param func - Visitor function that takes the current node and the + * incoming edge as arguments. The traversal stops if the function + * returns `false`. + * @param edge - Optional `Cell` that represents the incoming edge. This is + * `null` for the first step of the traversal. + * @param visited - Optional `WeakMap` for the visited cells. + * @param inverse - Optional boolean to traverse in inverse direction. + * Default is `false`. This is ignored if directed is `false`. + */ + traverse( + node: Cell, + directed: boolean = true, + func: (node: Cell, edge: Cell | null) => any, + edge?: Cell, + visited: WeakMap = new WeakMap(), + inverse: boolean = false, + ) { + this.retrievalManager.traverse(node, directed, func, edge, visited, inverse) + } +} diff --git a/packages/x6/src/graph/retrieval-manager.ts b/packages/x6/src/graph/retrieval-manager.ts new file mode 100644 index 00000000000..9b03b1c3e90 --- /dev/null +++ b/packages/x6/src/graph/retrieval-manager.ts @@ -0,0 +1,442 @@ +import * as util from '../util' +import { Cell } from '../core/cell' +import { State } from '../core/state' +import { Rectangle, Point } from '../struct' +import { BaseManager } from './base-manager' + +export class RetrievalManager extends BaseManager { + /** + * Specifies the default parent to be used to insert new cells. + */ + defaultParent: Cell | null + + setDefaultParent(cell: Cell | null) { + this.defaultParent = cell + } + + getDefaultParent(): Cell { + let parent = this.graph.getCurrentRoot() + if (parent == null) { + parent = this.defaultParent + if (parent == null) { + const root = this.model.getRoot() + parent = this.model.getChildAt(root, 0) + } + } + + return parent! + } + + home() { + const current = this.graph.getCurrentRoot() + if (current != null) { + this.view.setCurrentRoot(null) + const state = this.view.getState(current) + if (state != null) { + this.graph.setSelectedCell(current) + } + } + } + + getSwimlane(cell: Cell | null) { + let result = cell + while (result != null && !this.graph.isSwimlane(result)) { + result = this.model.getParent(result) + } + return result + } + + getSwimlaneAt( + x: number, + y: number, + parent: Cell = this.getDefaultParent(), + ): Cell | null { + const count = this.model.getChildCount(parent) + for (let i = 0; i < count; i += 1) { + const child = this.model.getChildAt(parent, i)! + const result = this.getSwimlaneAt(x, y, child) + + if (result != null) { + return result + } + + if (this.graph.isSwimlane(child)) { + const state = this.view.getState(child) + if (this.isIntersected(state, x, y)) { + return child + } + } + } + + return null + } + + getCellAt( + x: number, + y: number, + parent?: Cell | null, + includeNodes: boolean = true, + includeEdges: boolean = true, + ignoreFn?: (state: State, x?: number, y?: number) => boolean, + ): Cell | null { + if (parent == null) { + // tslint:disable-next-line + parent = this.graph.getCurrentRoot() || this.model.getRoot() + } + + if (parent != null) { + const c = this.model.getChildCount(parent) + for (let i = c - 1; i >= 0; i -= 1) { + const cell = this.model.getChildAt(parent, i)! + const result = this.getCellAt( + x, + y, + cell, + includeNodes, + includeEdges, + ignoreFn, + ) + + if (result != null) { + return result + } + + if ( + this.graph.isCellVisible(cell) && + ((includeEdges && this.model.isEdge(cell)) || + (includeNodes && this.model.isNode(cell))) + ) { + const state = this.view.getState(cell) + if ( + state != null && + (ignoreFn == null || !ignoreFn(state, x, y)) && + this.isIntersected(state, x, y) + ) { + return cell + } + } + } + } + + return null + } + + protected isIntersected(state: State | null, x: number, y: number) { + if (state != null) { + const points = state.absolutePoints + if (points != null) { + const hotspot = this.graph.tolerance * this.graph.tolerance + let pt = points[0]! + for (let i = 1, ii = points.length; i < ii; i += 1) { + const next = points[i]! + const dist = util.ptSegmentDist(pt.x, pt.y, next.x, next.y, x, y) + if (dist <= hotspot) { + return true + } + + pt = next + } + } else { + const rot = util.getRotation(state) + if (rot !== 0) { + const cx = state.bounds.getCenter() + const pt = util.rotatePoint(new Point(x, y), -rot, cx) + if (state.bounds.containsPoint(pt)) { + return true + } + } + + if (state.bounds.containsPoint({ x, y })) { + return true + } + } + } + + return false + } + + /** + * Returns true if the given point is inside the content of the + * given swimlane. + */ + hitsSwimlaneContent(swimlane: Cell | null, x: number, y: number) { + const state = this.view.getState(swimlane) + const size = this.graph.getStartSize(swimlane) + + if (state != null) { + const scale = this.view.scale + const dx = x - state.bounds.x + const dy = y - state.bounds.y + + if (size.width > 0 && dx > 0 && dx > size.width * scale) { + return true + } + + if (size.height > 0 && dy > 0 && dy > size.height * scale) { + return true + } + } + + return false + } + + getEdges( + node: Cell, + parent?: Cell | null, + incoming: boolean = true, + outgoing: boolean = true, + includeLoops: boolean = true, + recurse: boolean = false, + ) { + const result: Cell[] = [] + const edges: Cell[] = [] + const isCollapsed = this.graph.isCellCollapsed(node) + + node.eachChild(child => { + if (isCollapsed || !this.graph.isCellVisible(child)) { + edges.push(...this.model.getEdges(child, incoming, outgoing)) + } + }) + + edges.push(...this.model.getEdges(node, incoming, outgoing)) + + edges.forEach(edge => { + const [source, target] = this.getVisibleTerminals(edge) + + if (source === target) { + if (includeLoops) { + result.push(edge) + } + } else { + if ( + (incoming && + target === node && + (parent == null || + this.isValidAncestor(source!, parent, recurse))) || + (outgoing && + source === node && + (parent == null || this.isValidAncestor(target!, parent, recurse))) + ) { + result.push(edge) + } + } + }) + + return result + } + + getVisibleTerminals(edge: Cell) { + const state = this.view.getState(edge) + + const source = + state != null + ? state.getVisibleTerminal(true) + : this.view.getVisibleTerminal(edge, true) + + const target = + state != null + ? state.getVisibleTerminal(false) + : this.view.getVisibleTerminal(edge, false) + + return [source, target] + } + + isValidAncestor(cell: Cell, parent: Cell, recurse?: boolean) { + return recurse + ? this.model.isAncestor(parent, cell) + : this.model.getParent(cell) === parent + } + + getOppositeNodes( + edges: Cell[], + terminal: Cell, + includeSources: boolean = true, + includeTargets: boolean = true, + ) { + const result: Cell[] = [] + const map = new WeakMap() + const add = (cell: Cell) => { + if (!map.get(cell)) { + map.set(cell, true) + result.push(cell) + } + } + + edges && + edges.forEach(edge => { + const [source, target] = this.getVisibleTerminals(edge) + + if ( + source === terminal && + target != null && + target !== terminal && + includeTargets + ) { + add(target) + } else if ( + target === terminal && + source != null && + source !== terminal && + includeSources + ) { + add(source) + } + }) + + return result + } + + getEdgesBetween(source: Cell, target: Cell, directed: boolean = false) { + const edges = this.getEdges(source) + const result: Cell[] = [] + + edges && + edges.forEach(edge => { + const [s, t] = this.getVisibleTerminals(edge) + if ( + (s === source && t === target) || + (!directed && s === target && t === source) + ) { + result.push(edge) + } + }) + + return result + } + + getCellsInRegion( + x: number, + y: number, + width: number, + height: number, + parent: Cell, + result: Cell[] = [], + ) { + if (width > 0 || height > 0) { + const rect = new Rectangle(x, y, width, height) + + parent && + parent.eachChild(cell => { + const state = this.view.getState(cell) + if (state != null && this.graph.isCellVisible(cell)) { + const rot = util.getRotation(state) + const bounds = + rot === 0 ? state.bounds : util.rotateRectangle(state.bounds, rot) + + if ( + (this.model.isEdge(cell) || this.model.isNode(cell)) && + rect.containsRect(bounds) + ) { + result.push(cell) + } else { + this.getCellsInRegion(x, y, width, height, cell, result) + } + } + }) + } + + return result + } + + getCellsBeyond( + x: number, + y: number, + parent: Cell, + isRight: boolean = false, + isBottom: boolean = false, + ) { + const result: Cell[] = [] + + if (isRight || isBottom) { + parent && + parent.eachChild(child => { + const state = this.view.getState(child) + if (this.graph.isCellVisible(child) && state != null) { + if ( + (!isRight || state.bounds.x >= x) && + (!isBottom || state.bounds.y >= y) + ) { + result.push(child) + } + } + }) + } + + return result + } + + findTreeRoots( + parent: Cell | null, + isolate: boolean = false, + invert: boolean = false, + ) { + const roots: Cell[] = [] + + let best = null + let maxDiff = 0 + + parent && + parent.eachChild(cell => { + if (this.model.isNode(cell) && this.graph.isCellVisible(cell)) { + const conns = this.graph.getConnections(cell, isolate ? parent : null) + let fanOut = 0 + let fanIn = 0 + + conns.forEach(conn => { + const src = this.view.getVisibleTerminal(conn, true) + if (src === cell) { + fanOut += 1 + } else { + fanIn += 1 + } + }) + + if ( + (invert && fanOut === 0 && fanIn > 0) || + (!invert && fanIn === 0 && fanOut > 0) + ) { + roots.push(cell) + } + + const diff = invert ? fanIn - fanOut : fanOut - fanIn + + if (diff > maxDiff) { + maxDiff = diff + best = cell + } + } + }) + + if (roots.length === 0 && best != null) { + roots.push(best) + } + + return roots + } + + traverse( + node: Cell, + directed: boolean = true, + func: (node: Cell, edge: Cell | null) => any, + edge?: Cell, + visited: WeakMap = new WeakMap(), + inverse: boolean = false, + ) { + if (func != null && node != null) { + if (!visited.get(node)) { + visited.set(node, true) + + const result = func(node, edge || null) + if (result !== false) { + node.eachEdge(edge => { + const isSource = this.model.getTerminal(edge, true) === node + if (!directed || !inverse === isSource) { + const next = this.model.getTerminal(edge, !isSource)! + this.traverse(next, directed, func, edge, visited, inverse) + } + }) + } + } + } + } +} diff --git a/packages/x6/src/graph/prop/rubberband.ts b/packages/x6/src/graph/rubberband-accessor.ts similarity index 85% rename from packages/x6/src/graph/prop/rubberband.ts rename to packages/x6/src/graph/rubberband-accessor.ts index 9f0d86fccfd..1aad3bce4a9 100644 --- a/packages/x6/src/graph/prop/rubberband.ts +++ b/packages/x6/src/graph/rubberband-accessor.ts @@ -1,6 +1,6 @@ -import { GraphBase } from './base' +import { BaseGraph } from './base-graph' -export class RubberbandBehaviour extends GraphBase { +export class RubberbandAccessor extends BaseGraph { isRubberbandEnabled() { return this.rubberbandHandler.isEnabled() } diff --git a/packages/x6/src/graph/selection-accessor.ts b/packages/x6/src/graph/selection-accessor.ts new file mode 100644 index 00000000000..22b78b64839 --- /dev/null +++ b/packages/x6/src/graph/selection-accessor.ts @@ -0,0 +1,152 @@ +import { Cell } from '../core/cell' +import { Rectangle } from '../struct' +import { BaseGraph } from './base-graph' + +export class SelectionAccessor extends BaseGraph { + isCellSelected(cell: Cell | null) { + return this.selection.isSelected(cell) + } + + isSelectionEmpty() { + return this.selection.isEmpty() + } + + clearSelection() { + return this.selection.clear() + } + + getSelecedCellCount() { + return this.selection.cells.length + } + + getSelectedCell() { + return this.selection.cells[0] + } + + getSelectedCells() { + return this.selection.cells.slice() + } + + hasSelectedCell() { + return this.selection.cells.length > 0 + } + + /** + * Replace selection cells with the given cell + */ + setSelectedCell(cell: Cell | null) { + this.selection.setCell(cell) + return this + } + + /** + * Replace selection cells with the given cells + */ + setSelectedCells(cells: Cell[]) { + this.selection.setCells(cells) + return this + } + + /** + * Adds the given cell to the selection. + */ + selectCell(cell: Cell | null) { + this.selection.addCell(cell) + return this + } + + /** + * Adds the given cells to the selection. + */ + selectCells(cells: Cell[]) { + this.selection.addCells(cells) + return this + } + + /** + * Removes the given cell from the selection. + */ + unSelectCell(cell: Cell | null) { + this.selection.removeCell(cell) + return this + } + + /** + * Removes the given cells from the selection. + */ + unSelectCells(cells: Cell[]) { + this.selection.removeCells(cells) + return this + } + + /** + * Removes selected cells that are not in the model from the selection. + */ + updateSelection() { + this.selectionManager.updateSelection() + return this + } + + /** + * Selects and returns the cells inside the given rectangle for the + * specified event. + */ + selectCellsInRegion( + rect: Rectangle | Rectangle.RectangleLike, + e: MouseEvent, + ) { + return this.selectionManager.selectCellsInRegion(rect, e) + } + + selectNextCell() { + this.selectionManager.selectCell(true) + return this + } + + selectPreviousCell() { + this.selectionManager.selectCell() + return this + } + + selectParentCell() { + this.selectionManager.selectCell(false, true) + return this + } + + selectChildCell() { + this.selectionManager.selectCell(false, false, true) + return this + } + + /** + * Selects all children of the given parent or the children of the + * default parent if no parent is specified. + * + * @param parent Optional parent `Cell` whose children should be selected. + * @param includeDescendants Optional boolean specifying whether all + * descendants should be selected. + */ + selectAll( + parent: Cell = this.getDefaultParent()!, + includeDescendants: boolean = false, + ) { + this.selectionManager.selectAll(parent, includeDescendants) + return this + } + + /** + * Select all nodes inside the given parent or the default parent. + */ + selectNodes(parent: Cell = this.getDefaultParent()!) { + this.selectionManager.selectCells(true, false, parent) + return this + } + + /** + * Select all edges inside the given parent or the default parent. + */ + selectEdges(parent: Cell = this.getDefaultParent()!) { + this.selectionManager.selectCells(false, true, parent) + return this + } +} diff --git a/packages/x6/src/manager/selection-manager.ts b/packages/x6/src/graph/selection-manager.ts similarity index 98% rename from packages/x6/src/manager/selection-manager.ts rename to packages/x6/src/graph/selection-manager.ts index b0e1e39126e..fb357db2f35 100644 --- a/packages/x6/src/manager/selection-manager.ts +++ b/packages/x6/src/graph/selection-manager.ts @@ -1,9 +1,9 @@ import { Cell } from '../core/cell' import { Graph } from '../graph' import { Rectangle } from '../struct' -import { ManagerBase } from './base' +import { BaseManager } from './base-manager' -export class SelectionManager extends ManagerBase { +export class SelectionManager extends BaseManager { constructor(graph: Graph) { super(graph) } diff --git a/packages/x6/src/manager/selection.ts b/packages/x6/src/graph/selection.ts similarity index 96% rename from packages/x6/src/manager/selection.ts rename to packages/x6/src/graph/selection.ts index 2206c57be50..213275f2a8f 100644 --- a/packages/x6/src/manager/selection.ts +++ b/packages/x6/src/graph/selection.ts @@ -1,9 +1,9 @@ import { Cell } from '../core/cell' import { Graph } from '../graph' -import { ManagerBase } from './base' import { SelectionChange, UndoableEdit } from '../change' +import { BaseManager } from './base-manager' -export class Selection extends ManagerBase { +export class Selection extends BaseManager { cells: Cell[] private single: boolean diff --git a/packages/x6/src/graph/size-accessor.ts b/packages/x6/src/graph/size-accessor.ts new file mode 100644 index 00000000000..db79ad76a4a --- /dev/null +++ b/packages/x6/src/graph/size-accessor.ts @@ -0,0 +1,98 @@ +import { Cell } from '../core/cell' +import { Geometry } from '../core/geometry' +import { Rectangle } from '../struct' +import { hook } from './decorator' +import { BaseGraph } from './base-graph' + +export class SizeAccessor extends BaseGraph { + @hook() + isCellResizable(cell: Cell) { + const style = this.getStyle(cell) + return ( + this.isCellsResizable() && + !this.isCellLocked(cell) && + style.resizable !== false + ) + } + + @hook() + isAutoSizeCell(cell: Cell) { + const style = this.getStyle(cell) + return this.isAutoSizeOnEdited() || style.autosize === true + } + + @hook() + isExtendParent(cell: Cell) { + return !this.model.isEdge(cell) && this.isExtendParents() + } + + @hook() + isConstrainChild(cell: Cell) { + return ( + this.isConstrainChildren() && + !this.model.isEdge(this.model.getParent(cell)!) + ) + } + + /** + * Returns the bounding box for the given array of `Cell`s. + */ + getBoundingBox(cells: Cell[]) { + return this.sizeManager.getBoundingBox(cells) + } + + /** + * Resizes the specified cell to just fit around the its label + * and/or children. + * + * @param cell `Cells` to be resized. + * @param recurse Optional boolean which specifies if all descendants + * should be autosized. Default is `true`. + */ + autoSizeCell(cell: Cell, recurse: boolean = true) { + return this.sizeManager.autoSizeCell(cell, recurse) + } + + /** + * Sets the bounds of the given cell. + */ + resizeCell(cell: Cell, bounds: Rectangle, recurse?: boolean) { + return this.resizeCells([cell], [bounds], recurse)[0] + } + + /** + * Sets the bounds of the given cells. + */ + resizeCells( + cells: Cell[], + bounds: Rectangle[], + recurse: boolean = this.isRecursiveResize(), + ) { + return this.sizeManager.resizeCells(cells, bounds, recurse) + } + + /** + * Resizes the child cells of the given cell for the given new geometry with + * respect to the current geometry of the cell. + */ + resizeChildCells(cell: Cell, newGeo: Geometry) { + const geo = this.model.getGeometry(cell)! + const dx = newGeo.bounds.width / geo.bounds.width + const dy = newGeo.bounds.height / geo.bounds.height + cell.eachChild(child => this.scaleCell(child, dx, dy, true)) + return cell + } + + /** + * Scales the points, position and size of the given cell according to the + * given vertical and horizontal scaling factors. + * + * @param cell - The cell to be scaled. + * @param sx - Horizontal scaling factor. + * @param sy - Vertical scaling factor. + * @param recurse - Boolean indicating if the child cells should be scaled. + */ + scaleCell(cell: Cell, sx: number, sy: number, recurse: boolean) { + return this.sizeManager.scaleCell(cell, sx, sy, recurse) + } +} diff --git a/packages/x6/src/graph/size-manager.ts b/packages/x6/src/graph/size-manager.ts new file mode 100644 index 00000000000..0c63654ac1a --- /dev/null +++ b/packages/x6/src/graph/size-manager.ts @@ -0,0 +1,526 @@ +import * as util from '../util' +import { Cell } from '../core/cell' +import { Point, Rectangle } from '../struct' +import { events } from './events' +import { BaseManager } from './base-manager' + +export class SizeManager extends BaseManager { + autoSizeCell(cell: Cell, recurse: boolean) { + if (recurse) { + cell.eachChild(child => this.autoSizeCell(child, recurse)) + } + + if (this.model.isNode(cell) && this.graph.isAutoSizeCell(cell)) { + this.updateCellSize(cell) + } + + return cell + } + + updateCellSize(cell: Cell, ignoreChildren: boolean = false) { + this.model.batchUpdate(() => { + this.graph.trigger(events.updateCellSize, { cell, ignoreChildren }) + this.cellSizeUpdated(cell, ignoreChildren) + }) + + return cell + } + + cellSizeUpdated(cell: Cell, ignoreChildren: boolean) { + if (cell != null) { + this.model.batchUpdate(() => { + const size = this.getCellPreferredSize(cell) + let geo = this.model.getGeometry(cell) + if (size != null && geo != null) { + const collapsed = this.graph.isCellCollapsed(cell) + geo = geo.clone() + + if (this.graph.isSwimlane(cell)) { + const style = this.graph.getStyle(cell) + const cellStyle = this.model.getStyle(cell) || {} + + if (style.horizontal !== false) { + cellStyle.startSize = size.height + 8 + + if (collapsed) { + geo.bounds.height = size.height + 8 + } + + geo.bounds.width = size.width + } else { + cellStyle.startSize = size.width + 8 + + if (collapsed) { + geo.bounds.width = size.width + 8 + } + + geo.bounds.height = size.height + } + + this.model.setStyle(cell, cellStyle) + } else { + geo.bounds.width = size.width + geo.bounds.height = size.height + } + + if (!ignoreChildren && !collapsed) { + const bounds = this.view.getBounds(this.model.getChildren(cell)) + if (bounds != null) { + const t = this.view.translate + const s = this.view.scale + + const width = (bounds.x + bounds.width) / s - geo.bounds.x - t.x + const height = (bounds.y + bounds.height) / s - geo.bounds.y - t.y + + geo.bounds.width = Math.max(geo.bounds.width, width) + geo.bounds.height = Math.max(geo.bounds.height, height) + } + } + + this.cellsResized([cell], [geo.bounds], false) + } + }) + } + } + + getCellPreferredSize(cell: Cell) { + let result = null + + if (cell != null && !this.model.isEdge(cell)) { + const state = this.view.getState(cell, true)! + const style = state.style + const fontSize = style.fontSize || 12 + + let dx = 0 + let dy = 0 + + // Adds dimension of image if shape is a label + if (style.image != null) { + if (style.shape === 'label') { + if (style.verticalAlign === 'middle') { + dx += style.imageWidth || 0 + } + + if (style.align !== 'center') { + dy += style.imageHeight || 0 + } + } + } + + // Adds spacings + dx += 2 * (style.spacing || 0) + dx += style.spacingLeft || 0 + dx += style.spacingRight || 0 + + dy += 2 * (style.spacing || 0) + dy += style.spacingTop || 0 + dy += style.spacingBottom || 0 + + // Add spacing for collapse/expand icon + const image = this.graph.getFoldingImage(state) + if (image != null) { + dx += image.width + 8 + } + + // Adds space for label + let value = this.renderer.getLabelValue(state) + if (value != null && typeof value === 'string' && value.length > 0) { + if (!this.graph.isHtmlLabel(state.cell)) { + value = util.escape(value) + } + + value = value.replace(/\n/g, '
') + + const size = util.getSizeForString(value, fontSize, style.fontFamily) + let width = size.width + dx + let height = size.height + dy + + if (style.horizontal === false) { + const tmp = height + + height = width + width = tmp + } + + if (this.graph.isGridEnabled()) { + width = this.graph.snap(width + this.graph.getGridSize() / 2) + height = this.graph.snap(height + this.graph.getGridSize() / 2) + } + + result = new Rectangle(0, 0, width, height) + } else { + const gs2 = 4 * this.graph.getGridSize() + result = new Rectangle(0, 0, gs2, gs2) + } + } + + return result + } + + resizeCells(cells: Cell[], bounds: Rectangle[], recurse: boolean) { + this.model.batchUpdate(() => { + this.graph.trigger(events.resizeCells, { cells, bounds }) + this.cellsResized(cells, bounds, recurse) + }) + + return cells + } + + cellsResized(cells: Cell[], bounds: Rectangle[], recurse: boolean = false) { + if (cells != null && bounds != null && cells.length === bounds.length) { + this.model.batchUpdate(() => { + for (let i = 0, ii = cells.length; i < ii; i += 1) { + this.cellResized(cells[i], bounds[i], false, recurse) + + if (this.graph.isExtendParent(cells[i])) { + this.extendParent(cells[i]) + } + + this.constrainChild(cells[i]) + } + + if (this.graph.resetEdgesOnResize) { + this.graph.movingManager.resetOtherEdges(cells) + } + }) + + this.graph.trigger(events.cellsResized, { cells, bounds }) + } + } + + protected cellResized( + cell: Cell, + bounds: Rectangle, + ignoreRelative: boolean, + recurse: boolean, + ) { + let geo = this.model.getGeometry(cell) + if ( + geo != null && + (geo.bounds.x !== bounds.x || + geo.bounds.y !== bounds.y || + geo.bounds.width !== bounds.width || + geo.bounds.height !== bounds.height) + ) { + geo = geo.clone() + + if (!ignoreRelative && geo.relative) { + const offset = geo.offset + + if (offset != null) { + offset.x += bounds.x - geo.bounds.x + offset.y += bounds.y - geo.bounds.y + } + } else { + geo.bounds.x = bounds.x + geo.bounds.y = bounds.y + } + + geo.bounds.width = bounds.width + geo.bounds.height = bounds.height + + if ( + !geo.relative && + this.model.isNode(cell) && + !this.graph.isNegativeCoordinatesAllowed() + ) { + geo.bounds.x = Math.max(0, geo.bounds.x) + geo.bounds.y = Math.max(0, geo.bounds.y) + } + + this.model.batchUpdate(() => { + if (recurse) { + this.graph.resizeChildCells(cell, geo!) + } + + this.model.setGeometry(cell, geo!) + this.constrainChildCells(cell) + }) + } + } + + scaleCell(cell: Cell, sx: number, sy: number, recurse: boolean) { + let geo = this.model.getGeometry(cell) + if (geo != null) { + geo = geo.clone() + + // Stores values for restoring based on style + const x = geo.bounds.x + const y = geo.bounds.y + const w = geo.bounds.width + const h = geo.bounds.height + + const style = this.graph.getStyle(cell) + geo.scale(sx, sy, style.aspect) + + if (style.resizeWidth === true) { + geo.bounds.width = w * sx + } else if (style.resizeWidth === false) { + geo.bounds.width = w + } + + if (style.resizeHeight === true) { + geo.bounds.height = h * sy + } else if (style.resizeHeight === false) { + geo.bounds.height = h + } + + if (!this.graph.isCellMovable(cell)) { + geo.bounds.x = x + geo.bounds.y = y + } + + if (!this.graph.isCellResizable(cell)) { + geo.bounds.width = w + geo.bounds.height = h + } + + if (this.model.isNode(cell)) { + this.cellResized(cell, geo.bounds, true, recurse) + } else { + this.model.setGeometry(cell, geo) + } + } + return cell + } + + /** + * Resizes the parents recursively so that they contain the complete + * area of the resized child cell. + */ + extendParent(cell: Cell | null) { + if (cell != null) { + const parent = this.model.getParent(cell) + let pgeo = this.graph.getCellGeometry(parent!) + + if ( + parent != null && + pgeo != null && + !this.graph.isCellCollapsed(parent) + ) { + const geo = this.graph.getCellGeometry(cell) + + if ( + geo != null && + !geo.relative && + (pgeo.bounds.width < geo.bounds.x + geo.bounds.width || + pgeo.bounds.height < geo.bounds.y + geo.bounds.height) + ) { + pgeo = pgeo.clone() + + pgeo.bounds.width = Math.max( + pgeo.bounds.width, + geo.bounds.x + geo.bounds.width, + ) + + pgeo.bounds.height = Math.max( + pgeo.bounds.height, + geo.bounds.y + geo.bounds.height, + ) + + this.cellsResized([parent], [pgeo.bounds], false) + } + } + } + } + + /** + * Keeps the given cell inside the bounds. + * + * @param cell - The cell will be constrained. + * @param sizeFirst - Specifies if the size should be changed first. + * Default is `true`. + */ + constrainChild(cell: Cell, sizeFirst: boolean = true) { + if (cell != null) { + let geo = this.graph.getCellGeometry(cell) + if ( + geo != null && + (this.graph.isConstrainRelativeChildren() || !geo.relative) + ) { + const parent = this.model.getParent(cell)! + // const pgeo = this.getCellGeometry(parent) + let max = this.graph.getMaxGraphBounds() + // Finds parent offset + if (max != null) { + const off = this.graph.getBoundingBoxFromGeometry([parent], false) + if (off != null) { + max = max.clone() + max.x -= off.x + max.y -= off.y + } + } + + if (this.graph.isConstrainChild(cell)) { + let area = this.getCellContainmentArea(cell) + if (area != null) { + const overlap = this.graph.getOverlap(cell) + + if (overlap > 0) { + area = Rectangle.clone(area) + + area.x -= area.width * overlap + area.y -= area.height * overlap + area.width += 2 * area.width * overlap + area.height += 2 * area.height * overlap + } + + // Find the intersection between max and tmp + if (max == null) { + max = area + } else { + max = Rectangle.clone(max) + max.intersect(area) + } + } + } + + if (max != null) { + const cells = [cell] + + if (!this.graph.isCellCollapsed(cell)) { + const desc = this.model.getDescendants(cell) + + for (let i = 0; i < desc.length; i += 1) { + if (this.graph.isCellVisible(desc[i])) { + cells.push(desc[i]) + } + } + } + + const bbox = this.graph.getBoundingBoxFromGeometry(cells, false) + + if (bbox != null) { + geo = geo.clone() + + // Cumulative horizontal movement + let dx = 0 + + if (geo.bounds.width > max.width) { + dx = geo.bounds.width - max.width + geo.bounds.width -= dx + } + + if (bbox.x + bbox.width > max.x + max.width) { + dx -= bbox.x + bbox.width - max.x - max.width - dx + } + + // Cumulative vertical movement + let dy = 0 + + if (geo.bounds.height > max.height) { + dy = geo.bounds.height - max.height + geo.bounds.height -= dy + } + + if (bbox.y + bbox.height > max.y + max.height) { + dy -= bbox.y + bbox.height - max.y - max.height - dy + } + + if (bbox.x < max.x) { + dx -= bbox.x - max.x + } + + if (bbox.y < max.y) { + dy -= bbox.y - max.y + } + + if (dx !== 0 || dy !== 0) { + if (geo.relative) { + // Relative geometries are moved via absolute offset + if (geo.offset == null) { + geo.offset = new Point() + } + + geo.offset.x += dx + geo.offset.y += dy + } else { + geo.bounds.x += dx + geo.bounds.y += dy + } + } + + this.model.setGeometry(cell, geo) + } + } + } + } + } + + /** + * Constrains the children of the given cell. + */ + constrainChildCells(cell: Cell) { + cell.eachChild(child => this.constrainChild(child)) + } + + getCellContainmentArea(cell: Cell) { + if (cell != null && !this.model.isEdge(cell)) { + const parent = this.model.getParent(cell) + if (parent != null && parent !== this.graph.getDefaultParent()) { + const geo = this.model.getGeometry(parent) + if (geo != null) { + let x = 0 + let y = 0 + let w = geo.bounds.width + let h = geo.bounds.height + + if (this.graph.isSwimlane(parent)) { + const size = this.graph.getStartSize(parent) + const style = this.graph.getStyle(parent) + + const dir = style.direction || 'east' + const flipH = style.flipH === true + const flipV = style.flipV === true + + if (dir === 'south' || dir === 'north') { + const tmp = size.width + size.width = size.height + size.height = tmp + } + + if ( + (dir === 'east' && !flipV) || + (dir === 'north' && !flipH) || + (dir === 'west' && flipV) || + (dir === 'south' && flipH) + ) { + x = size.width + y = size.height + } + + w -= size.width + h -= size.height + } + + return new Rectangle(x, y, w, h) + } + } + } + + return null + } + + getBoundingBox(cells: Cell[]) { + let result: Rectangle | null = null + + if (cells == null || cells.length <= 0) { + return result + } + + cells.forEach(cell => { + if (this.model.isNode(cell) || this.model.isEdge(cell)) { + const bbox = this.view.getBoundingBox(this.view.getState(cell), true) + if (bbox != null) { + if (result == null) { + result = Rectangle.clone(bbox) + } else { + result.add(bbox) + } + } + } + }) + + return result + } +} diff --git a/packages/x6/src/graph/style-accessor.ts b/packages/x6/src/graph/style-accessor.ts new file mode 100644 index 00000000000..f19712c80d0 --- /dev/null +++ b/packages/x6/src/graph/style-accessor.ts @@ -0,0 +1,163 @@ +import { Cell } from '../core/cell' +import { Style } from '../types' +import { BaseGraph } from './base-graph' + +export class StyleAccessor extends BaseGraph { + getStyle(cell: Cell | null) { + const state = this.view.getState(cell) + return state != null ? state.style : this.getCellStyle(cell) + } + + /** + * Returns a key-value pair object representing the cell style for + * the given cell. + * + * Note: You should try to use the cached style in the state before + * using this method. + */ + getCellStyle(cell: Cell | null) { + return this.styleManager.getCellStyle(cell) + } + + /** + * Sets the style of the specified cells. If no cells are given, then + * the current selected cells are changed. + */ + setCellStyle(style: Style, cells: Cell[] = this.getSelectedCells()) { + this.styleManager.setCellStyle(style, cells) + return this + } + + /** + * Toggles the boolean value for the given key in the style of the given + * cell and returns the new value. Optional boolean default value if no + * value is defined. If no cell is specified then the current selected + * cell is used. + */ + toggleCellStyle( + key: string, + defaultValue: boolean = false, + cell: Cell = this.getSelectedCell(), + ) { + return this.toggleCellsStyle(key, defaultValue, [cell]) + } + + /** + * Toggles the boolean value for the given key in the style of the given + * cells and returns the new value. If no cells are specified, then the + * current selected cells are used. + */ + toggleCellsStyle( + key: string, + defaultValue: boolean = false, + cells: Cell[] = this.getSelectedCells(), + ) { + return this.styleManager.toggleCellsStyle(key, defaultValue, cells) + } + + updateStyle(style: Style, cell?: Cell): this + updateStyle(style: Style, cells?: Cell[]): this + updateStyle( + key: string, + value?: string | number | boolean | null, + cell?: Cell, + ): this + updateStyle( + key: string, + value?: string | number | boolean | null, + cells?: Cell[], + ): this + updateStyle( + key: string | Style, + value?: (string | number | boolean | null) | Cell | Cell[], + cells?: Cell | Cell[], + ) { + const style: Style = typeof key === 'string' ? { [key]: value } : key + let targets = (typeof key === 'string' ? cells : value) as Cell | Cell[] + if (targets == null) { + targets = this.getSelectedCells() + } + if (!Array.isArray(targets)) { + targets = [targets as Cell] + } + + Object.keys(style).forEach(name => { + this.updateCellsStyle(name, (style as any)[name], targets as Cell[]) + }) + + return this + } + + updateCellStyle( + key: string, + value?: string | number | boolean | null, + cell: Cell = this.getSelectedCell(), + ) { + this.updateCellsStyle(key, value, [cell]) + return this + } + + /** + * Sets the key to value in the styles of the given cells. This will modify + * the existing cell styles in-place and override any existing assignment + * for the given key. If no cells are specified, then the selection cells + * are changed. If no value is specified, then the respective key is + * removed from the styles. + */ + updateCellsStyle( + key: string, + value?: string | number | boolean | null, + cells: Cell[] = this.getSelectedCells(), + ) { + this.styleManager.updateCellsStyle(key, value, cells) + return this + } + + /** + * Toggles the given bit for the given key in the styles of the specified + * cells. + */ + toggleCellsStyleFlag( + key: string, + flag: number, + cells: Cell[] = this.getSelectedCells(), + ) { + this.setCellsStyleFlag(key, flag, null, cells) + return this + } + + /** + * Sets or toggles the given bit for the given key in the styles of the + * specified cells. + */ + setCellsStyleFlag( + key: string, + flag: number, + value: boolean | null, + cells: Cell[] = this.getSelectedCells(), + ) { + this.styleManager.setCellsStyleFlag(key, flag, value, cells) + return this + } + + toggleCellsLocked(cells: Cell[] = this.getSelectedCells()) { + this.styleManager.toggleCellsLocked(cells) + return this + } + + toggleCells( + show: boolean, + cells: Cell[] = this.getSelectedCells(), + includeEdges: boolean = true, + ) { + return this.styleManager.toggleCells(show, cells, includeEdges) + } + + /** + * Toggles the style of the given edge between null (or empty) and + * `alternateEdgeStyle`. + */ + flipEdge(edge: Cell) { + return this.styleManager.flipEdge(edge) + } +} diff --git a/packages/x6/src/graph/style-manager.ts b/packages/x6/src/graph/style-manager.ts new file mode 100644 index 00000000000..b6a76533fcf --- /dev/null +++ b/packages/x6/src/graph/style-manager.ts @@ -0,0 +1,162 @@ +import { Cell } from '../core/cell' +import { Style } from '../types' +import { BaseManager } from './base-manager' +import { events } from './events' + +export class StyleManager extends BaseManager { + getCellStyle(cell: Cell | null): Style { + if (cell != null) { + const preset = this.model.isEdge(cell) + ? this.graph.options.edgeStyle + : this.graph.options.nodeStyle + const style = this.model.getStyle(cell) || {} + return { + ...preset, + ...style, + } + } + + return {} + } + + setCellStyle(style: Style, cells: Cell[]) { + if (cells != null) { + this.model.batchUpdate(() => { + cells.forEach(cell => this.model.setStyle(cell, style)) + }) + } + } + + updateCellsStyle( + key: string, + value?: string | number | boolean | null, + cells?: Cell[], + ) { + if (cells != null && cells.length > 0) { + this.model.batchUpdate(() => { + cells.forEach(cell => { + if (cell != null) { + const raw = this.model.getStyle(cell) + const style = raw != null ? { ...raw } : {} + if (value == null) { + delete (style as any)[key] + } else { + const tmp = style as any + tmp[key] = value + } + this.model.setStyle(cell, style) + } + }) + }) + } + } + + toggleCellsStyle(key: string, defaultValue: boolean = false, cells: Cell[]) { + let value = null + if (cells != null && cells.length > 0) { + const state = this.view.getState(cells[0]) + const style = state != null ? state.style : this.getCellStyle(cells[0]) + if (style != null) { + const cur = (style as any)[key] + if (cur == null) { + value = defaultValue + } else { + value = cur ? false : true + } + this.updateCellsStyle(key, value, cells) + } + } + + return value + } + + setCellsStyleFlag( + key: string, + flag: number, + add: boolean | null, + cells: Cell[], + ) { + if (cells != null && cells.length > 0) { + if (add == null) { + const style = this.graph.getStyle(cells[0]) + const current = parseInt((style as any)[key], 10) || 0 + add = !((current & flag) === flag) // tslint:disable-line + } + + this.model.batchUpdate(() => { + cells.forEach(cell => { + if (cell != null) { + this.setCellStyleFlag(key, flag, add!, cell) + } + }) + }) + } + } + + setCellStyleFlag(key: string, flag: number, add: boolean, cell: Cell) { + const style = this.graph.getStyle(cell) + const current = parseInt((style as any)[key], 10) || 0 + const exists = (current & flag) === flag + let target = current + if (add && !exists) { + target += flag + } else if (!add && exists) { + target -= flag + } + + if (target !== current) { + this.updateCellsStyle(key, target, [cell]) + } + } + + toggleCellsLocked(cells: Cell[]) { + if (cells.length > 0) { + const style = this.graph.getStyle(cells[0]) + this.toggleCellsStyle('locked', !!style.locked, cells) + } + } + + flipEdge(edge: Cell) { + if (edge != null && this.graph.alternateEdgeStyle != null) { + this.model.batchUpdate(() => { + const style = this.model.getStyle(edge) + if (style == null) { + this.model.setStyle(edge, this.graph.alternateEdgeStyle!) + } else { + this.model.setStyle(edge, {}) + } + + // Removes all control points + this.graph.resetEdge(edge) + this.graph.trigger(events.flipEdge, { edge }) + }) + } + + return edge + } + + toggleCells(show: boolean, cells: Cell[], includeEdges: boolean) { + const arr = includeEdges + ? this.graph.creationManager.addAllEdges(cells) + : cells + + this.model.batchUpdate(() => { + this.graph.trigger(events.toggleCells, { + show, + includeEdges, + cells: arr, + }) + this.setCellsVisibleImpl(arr, show) + }) + + return arr + } + + protected setCellsVisibleImpl(cells: Cell[], show: boolean) { + if (cells != null && cells.length > 0) { + this.model.batchUpdate(() => { + cells.forEach(cell => this.model.setVisible(cell, show)) + }) + } + } +} diff --git a/packages/x6/src/graph/prop/tooltip.ts b/packages/x6/src/graph/tooltip-accessor.ts similarity index 58% rename from packages/x6/src/graph/prop/tooltip.ts rename to packages/x6/src/graph/tooltip-accessor.ts index 02c2de6352c..666728c0b4c 100644 --- a/packages/x6/src/graph/prop/tooltip.ts +++ b/packages/x6/src/graph/tooltip-accessor.ts @@ -1,6 +1,8 @@ -import { GraphBase } from './base' +import { Cell } from '../core/cell' +import { hook } from './decorator' +import { BaseGraph } from './base-graph' -export class TooltipBehaviour extends GraphBase { +export class TooltipAccessor extends BaseGraph { hideTooltip() { if (this.tooltipHandler) { this.tooltipHandler.hide() @@ -21,4 +23,9 @@ export class TooltipBehaviour extends GraphBase { isTooltipEnabled() { return this.tooltipHandler.isEnabled() } + + @hook() + getTooltip(cell: Cell) { + return this.dataToString(cell) + } } diff --git a/packages/x6/src/graph/validation-accessor.ts b/packages/x6/src/graph/validation-accessor.ts new file mode 100644 index 00000000000..f20a2093aa0 --- /dev/null +++ b/packages/x6/src/graph/validation-accessor.ts @@ -0,0 +1,50 @@ +import { hook } from './decorator' +import { Cell } from '../core/cell' +import { BaseGraph } from './base-graph' + +export class ValidationAccessor extends BaseGraph { + @hook() + isValidSource(cell: Cell | null) { + return ( + (cell == null && this.allowDanglingEdges) || + (cell != null && + (!this.model.isEdge(cell) || this.edgesConnectable) && + this.isCellConnectable(cell)) + ) + } + + @hook() + isValidTarget(cell: Cell | null) { + return this.isValidSource(cell) + } + + @hook() + isValidConnection(source: Cell | null, target: Cell | null) { + return this.isValidSource(source) && this.isValidTarget(target) + } + + @hook() + validateEdge(edge: Cell | null, source: Cell | null, target: Cell | null) { + return null + } + + @hook() + validateCell(cell: Cell, context: any) { + return null + } + + isEdgeValid(edge: Cell | null, source: Cell | null, target: Cell | null) { + return this.validationManager.isEdgeValid(edge, source, target) + } + + /** + * Validates the graph by validating each descendant of the given cell or + * the root of the model. + */ + validateGraph( + cell: Cell = this.model.getRoot(), + context: any = {}, + ): string | null { + return this.validationManager.validateGraph(cell, context) + } +} diff --git a/packages/x6/src/manager/validation-manager.ts b/packages/x6/src/graph/validation-manager.ts similarity index 94% rename from packages/x6/src/manager/validation-manager.ts rename to packages/x6/src/graph/validation-manager.ts index 03792d6bd00..fde53f6865e 100644 --- a/packages/x6/src/manager/validation-manager.ts +++ b/packages/x6/src/graph/validation-manager.ts @@ -1,13 +1,8 @@ import { Cell } from '../core/cell' -import { Graph } from '../graph' import { isHtmlElem } from '../util' -import { ManagerBase } from './base' - -export class ValidationManager extends ManagerBase { - constructor(graph: Graph) { - super(graph) - } +import { BaseManager } from './base-manager' +export class ValidationManager extends BaseManager { validateGraph(cell: Cell, context: any): string | null { let isValid = true @@ -63,6 +58,10 @@ export class ValidationManager extends ManagerBase { return warning.length > 0 || !isValid ? (warning as string) : null } + isEdgeValid(edge: Cell | null, source: Cell | null, target: Cell | null) { + return this.getEdgeValidationError(edge, source, target) == null + } + /** * Returns the validation error message to be displayed when inserting or * changing an edges' connectivity. @@ -181,4 +180,8 @@ export class ValidationManager extends ManagerBase { return error.length > 0 ? error : null } + + warning(message: string) { + console.warn(message) + } } diff --git a/packages/x6/src/graph/viewport-accessor.ts b/packages/x6/src/graph/viewport-accessor.ts new file mode 100644 index 00000000000..cfe12018d19 --- /dev/null +++ b/packages/x6/src/graph/viewport-accessor.ts @@ -0,0 +1,66 @@ +import { Cell } from '../core/cell' +import { Rectangle } from '../struct' +import { events } from './events' +import { BaseGraph } from './base-graph' + +export class ViewportAccessor extends BaseGraph { + /** + * Clears all cell states or the states for the hierarchy starting + * at the given cell and validates the graph. + */ + refresh(cell: Cell) { + this.view.clear(cell, cell == null) + this.view.validate() + this.sizeDidChange() + this.trigger(events.refresh) + return this + } + + sizeDidChange() { + this.viewportManager.sizeDidChange() + return this + } + + resetScrollbar() { + this.viewportManager.resetScrollbar() + return this + } + + /** + * Get the bounds of the visible graph. + */ + getGraphBounds() { + return this.viewportManager.getGraphBounds() + } + + /** + * Get the scaled, translated bounds for the given cell. + * + * @param cell The `Cell` whose bounds should be returned. + * @param includeEdges Optional boolean that specifies if the bounds of + * the connected edges should be included. Default is `false`. + * @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, + ) { + return this.viewportManager.getCellBounds( + cell, + includeEdges, + includeDescendants, + ) + } + + /** + * Get the bounding box from the geometries of the cells. + */ + getBoundingBoxFromGeometry( + cells: Cell[], + includeEdges: boolean = false, + ): Rectangle | null { + return this.viewportManager.getBoundingBox(cells, includeEdges) + } +} diff --git a/packages/x6/src/graph/viewport-manager.ts b/packages/x6/src/graph/viewport-manager.ts new file mode 100644 index 00000000000..c7506ad8a72 --- /dev/null +++ b/packages/x6/src/graph/viewport-manager.ts @@ -0,0 +1,321 @@ +import * as util from '../util' +import { Cell } from '../core/cell' +import { Rectangle, Point } from '../struct' +import { BaseManager } from './base-manager' +import { Size } from '../types' +import { detector } from '../common' +import { events } from './events' + +export class ViewportManager extends BaseManager { + getPageSize() { + const pageScale = this.graph.pageScale + const pageFormat = this.graph.pageFormat + return { + width: pageFormat.width * pageScale, + height: pageFormat.height * pageScale, + } + } + + getPagePadding() { + const scale = this.view.scale + const container = this.container + return [ + Math.max(0, Math.round((container.offsetWidth - 32) / scale)), + Math.max(0, Math.round((container.offsetHeight - 32) / scale)), + ] + } + + getPageLayout() { + const size = this.getPageSize() + const bounds = this.getGraphBounds() + + if (bounds.width === 0 || bounds.height === 0) { + return new Rectangle(0, 0, 1, 1) + } + + const s = this.view.scale + const t = this.view.translate + const x = Math.ceil(bounds.x / s - t.x) + const y = Math.ceil(bounds.y / s - t.y) + const w = Math.floor(bounds.width / s) + const h = Math.floor(bounds.height / s) + + const x0 = Math.floor(x / size.width) + const y0 = Math.floor(y / size.height) + const w0 = Math.ceil((x + w) / size.width) - x0 + const h0 = Math.ceil((y + h) / size.height) - y0 + + return new Rectangle(x0, y0, w0, h0) + } + + getPreferredPageSize(bounds: Rectangle, width: number, height: number): Size { + if (this.graph.infinite) { + const size = this.getPageSize() + const pages = this.getPageLayout() + + return { + width: pages.width * size.width, + height: pages.height * size.height, + } + } + + const format = this.graph.pageFormat + const pageScale = this.graph.pageScale + const translate = this.view.translate + const pageWidth = Math.ceil(format.width * pageScale) + const pageHeight = Math.ceil(format.height * pageScale) + const pageBreakEnabled = this.graph.isPageBreakEnabled() + const hCount = pageBreakEnabled ? Math.ceil(width / pageWidth) : 1 + const vCount = pageBreakEnabled ? Math.ceil(height / pageHeight) : 1 + + return { + width: hCount * pageWidth + 2 + translate.x, + height: vCount * pageHeight + 2 + translate.y, + } + } + + getGraphBounds() { + return this.view.getGraphBounds() + } + + getCellBounds( + cell: Cell, + includeEdges: boolean, + includeDescendants: boolean, + ) { + const cells = [cell] + + // Includes all connected edges + if (includeEdges) { + cells.push(...this.model.getEdges(cell)) + } + + let result = this.view.getBounds(cells) + + if (includeDescendants) { + cell.eachChild(child => { + const tmp = this.getCellBounds(child, includeEdges, true) + if (tmp != null) { + if (result != null) { + result.add(tmp) + } else { + result = tmp + } + } + }) + } + + return result + } + + getBoundingBox(cells: Cell[], includeEdges: boolean): Rectangle | null { + let result: Rectangle | null = null + if (cells != null && cells.length > 0) { + cells.forEach(cell => { + if (includeEdges || this.model.isNode(cell)) { + // Computes the bounding box for the points in the geometry + const geo = cell.getGeometry() + if (geo != null) { + let bbox = null + if (this.model.isEdge(cell)) { + const pts = geo.points + let tmp = new Rectangle(pts[0].x, pts[0].y, 0, 0) + + const addPoint = (pt: Point | null) => { + if (pt != null) { + const rect = new Rectangle(pt.x, pt.y, 0, 0) + if (tmp == null) { + tmp = rect + } else { + tmp.add(rect) + } + } + } + + if (this.model.getTerminal(cell, true) == null) { + addPoint(geo.getTerminalPoint(true)) + } + + if (this.model.getTerminal(cell, false) == null) { + addPoint(geo.getTerminalPoint(false)) + } + + if (pts != null && pts.length > 0) { + for (let j = 1; j < pts.length; j += 1) { + addPoint(pts[j]) + } + } + + bbox = tmp + } else { + const parent = this.model.getParent(cell)! + + if (geo.relative) { + if ( + this.model.isNode(parent) && + parent !== this.view.currentRoot + ) { + const tmp = this.getBoundingBox([parent], false) + if (tmp != null) { + bbox = new Rectangle( + geo.bounds.x * tmp.width, + geo.bounds.y * tmp.height, + geo.bounds.width, + geo.bounds.height, + ) + + if (cells.includes(parent)) { + bbox.x += tmp.x + bbox.y += tmp.y + } + } + } + } else { + bbox = Rectangle.clone(geo.bounds) + if (this.model.isNode(parent) && cells.includes(parent)) { + const tmp = this.getBoundingBox([parent], false) + if (tmp != null) { + bbox.x += tmp.x + bbox.y += tmp.y + } + } + } + + if (bbox != null && geo.offset != null) { + bbox.x += geo.offset.x + bbox.y += geo.offset.y + } + } + + if (bbox != null) { + if (result == null) { + result = bbox.clone() + } else { + result.add(bbox) + } + } + } + } + }) + } + + return result != null ? result : null + } + + sizeDidChange() { + const bounds = this.getGraphBounds() + + if (this.container != null) { + const scale = this.view.scale + const border = this.graph.getBorder() + + let width = Math.max(0, bounds.x + bounds.width + 2 * border * scale) + let height = Math.max(0, bounds.y + bounds.height + 2 * border * scale) + + if (this.graph.minContainerSize != null) { + width = Math.max(width, this.graph.minContainerSize.width) + height = Math.max(height, this.graph.minContainerSize.height) + } + + if (this.graph.isAutoResizeContainer()) { + this.resizeContainer(width, height) + } + + if ( + this.graph.preferPageSize || + (!detector.IS_IE && this.graph.pageVisible) + ) { + const size = this.getPreferredPageSize( + bounds, + Math.max(1, width), + Math.max(1, height), + ) + + if (size != null) { + width = size.width * scale + height = size.height * scale + } + } + + if (this.graph.minGraphSize != null) { + width = Math.max(width, this.graph.minGraphSize.width * scale) + height = Math.max(height, this.graph.minGraphSize.height * scale) + } + + width = Math.ceil(width) + height = Math.ceil(height) + + if (this.graph.dialect === 'svg') { + const svg = (this.view.getDrawPane() as SVGGElement).ownerSVGElement + if (svg != null) { + svg.style.minWidth = `${Math.max(1, width)}px` + svg.style.minHeight = `${Math.max(1, height)}px` + svg.style.width = '100%' + svg.style.height = '100%' + } + } else { + const stage = this.view.getStage()! + stage.style.minWidth = `${Math.max(1, width)}px` + stage.style.minHeight = `${Math.max(1, height)}px` + } + + this.graph.updatePageBreaks( + this.graph.isPageBreakEnabled(), + width, + height, + ) + } + + this.graph.trigger(events.size, bounds) + + return this + } + + resizeContainer(width: number, height: number) { + const w = + this.graph.maxContainerSize != null + ? Math.min(this.graph.maxContainerSize.width, width) + : width + + const h = + this.graph.maxContainerSize != null + ? Math.min(this.graph.maxContainerSize.height, height) + : height + + this.container.style.width = util.toPx(Math.ceil(w)) + this.container.style.height = util.toPx(Math.ceil(h)) + } + + resetScrollbar() { + const container = this.container + + if (this.graph.pageVisible && util.hasScrollbars(container)) { + const padding = this.getPagePadding() + container.scrollLeft = Math.floor( + Math.min( + padding[0], + (container.scrollWidth - container.clientWidth) / 2, + ), + ) + container.scrollTop = padding[1] + + // Scrolls graph to visible area + const bounds = this.getGraphBounds() + if (bounds.width > 0 && bounds.height > 0) { + if (bounds.x > container.scrollLeft + container.clientWidth * 0.9) { + container.scrollLeft = Math.min( + bounds.x + bounds.width - container.clientWidth, + bounds.x - 10, + ) + } + + if (bounds.y > container.scrollTop + container.clientHeight * 0.9) { + container.scrollTop = Math.min( + bounds.y + bounds.height - container.clientHeight, + bounds.y - 10, + ) + } + } + } + } +} diff --git a/packages/x6/src/graph/zoom-accessor.ts b/packages/x6/src/graph/zoom-accessor.ts new file mode 100644 index 00000000000..9dc4ebca32d --- /dev/null +++ b/packages/x6/src/graph/zoom-accessor.ts @@ -0,0 +1,146 @@ +import { Cell } from '../core/cell' +import { BaseGraph } from './base-graph' +import { Rectangle } from '../struct' + +export class ZoomAccessor extends BaseGraph { + zoomIn() { + this.zoom(this.scaleFactor) + return this + } + + zoomOut() { + this.zoom(1 / this.scaleFactor) + return this + } + + zoomTo(scale: number, center?: boolean) { + this.zoom(scale / this.view.scale, center) + return this + } + + /** + * Resets the zoom and panning in the view. + */ + zoomActual() { + if (this.view.scale === 1) { + this.view.setTranslate(0, 0) + } else { + this.view.translate.x = 0 + this.view.translate.y = 0 + + this.view.setScale(1) + } + return this + } + + /** + * Zooms the graph using the given factor. Center is an optional boolean + * argument that keeps the graph scrolled to the center. + */ + zoom(factor: number, center: boolean = this.centerZoom) { + this.zoomManager.zoom(factor, center) + return this + } + + /** + * Centers the graph in the container. + * + * @param horizontal Optional boolean that specifies if the graph should be + * centered horizontally. Default is `true`. + * @param vertical Optional boolean that specifies if the graph should be + * centered vertically. Default is `true`. + * @param cx Optional float that specifies the horizontal center. + * Default is `0.5`. + * @param cy Optional float that specifies the vertical center. + * Default is `0.5`. + */ + center( + horizontal: boolean = true, + vertical: boolean = true, + cx: number = 0.5, + cy: number = 0.5, + ) { + this.zoomManager.center(horizontal, vertical, cx, cy) + return this + } + + /** + * Scales the graph such that the complete diagram fits into container. + * + * @param border Optional number that specifies the border. + * @param keepOrigin Optional boolean that specifies if the translate + * should be changed. Default is `false`. + * @param margin Optional margin in pixels. Default is `0`. + * @param enabled Optional boolean that specifies if the scale should + * be set or just returned. Default is `true`. + * @param ignoreWidth Optional boolean that specifies if the width should + * be ignored. Default is `false`. + * @param ignoreHeight Optional boolean that specifies if the height should + * be ignored. Default is `false`. + * @param maxHeight Optional maximum height. + */ + fit( + border: number = this.getBorder(), + keepOrigin: boolean = false, + margin: number = 0, + enabled: boolean = true, + ignoreWidth: boolean = false, + ignoreHeight: boolean = false, + maxHeight?: number, + ) { + this.zoomManager.fit( + border, + keepOrigin, + margin, + enabled, + ignoreWidth, + ignoreHeight, + maxHeight, + ) + + return this.view.scale + } + + /** + * Zooms the graph to the specified rectangle. If the rectangle does not have + * same aspect ratio as the display container, it is increased in the smaller + * relative dimension only until the aspect match. The original rectangle is + * centralised within this expanded one. + * + * Note that the input rectangle must be un-scaled and un-translated. + */ + zoomToRect(rect: Rectangle) { + this.zoomManager.zoomToRect(rect) + return this + } + + /** + * Pans the graph so that it shows the given cell. Optionally the cell may + * be centered in the container. + */ + scrollCellToVisible(cell: Cell, center: boolean = false) { + this.zoomManager.scrollCellToVisible(cell, center) + return this + } + + /** + * Pans the graph so that it shows the given rectangle. + */ + scrollRectToVisible(rect: Rectangle) { + return this.zoomManager.scrollRectToVisible(rect) + } + + /** + * Scrolls the graph to the given point, extending + * the graph container if specified. + */ + scrollPointToVisible( + x: number, + y: number, + extend: boolean = false, + border: number = 20, + ) { + this.zoomManager.scrollPointToVisible(x, y, extend, border) + return this + } +} diff --git a/packages/x6/src/graph/zoom-manager.ts b/packages/x6/src/graph/zoom-manager.ts new file mode 100644 index 00000000000..8467922ab05 --- /dev/null +++ b/packages/x6/src/graph/zoom-manager.ts @@ -0,0 +1,519 @@ +import * as util from '../util' +import { Cell } from '../core/cell' +import { Rectangle, Point } from '../struct' +import { BaseManager } from './base-manager' + +export class ZoomManager extends BaseManager { + center(horizontal: boolean, vertical: boolean, cx: number, cy: number) { + const hasScrollbars = util.hasScrollbars(this.container) + const bounds = this.graph.getGraphBounds() + const cw = this.container.clientWidth + const ch = this.container.clientHeight + + const t = this.view.translate + const s = this.view.scale + + let dx = horizontal ? cw - bounds.width : 0 + let dy = vertical ? ch - bounds.height : 0 + + if (!hasScrollbars) { + const tx = horizontal + ? Math.floor(t.x - bounds.x * s + (dx * cx) / s) + : t.x + const ty = vertical ? Math.floor(t.y - bounds.y * s + (dy * cy) / s) : t.y + this.view.setTranslate(tx, ty) + } else { + const sw = this.container.scrollWidth + const sh = this.container.scrollHeight + + if (sw > cw) { + dx = 0 + } + + if (sh > ch) { + dy = 0 + } + + this.view.setTranslate( + Math.floor(dx / 2 - (bounds.x - t.x)), + Math.floor(dy / 2 - (bounds.y - t.y)), + ) + + this.container.scrollLeft = (sw - cw) / 2 + this.container.scrollTop = (sh - ch) / 2 + } + } + + fit( + border: number, + keepOrigin: boolean, + margin: number, + enabled: boolean, + ignoreWidth: boolean, + ignoreHeight: boolean, + maxHeight?: number, + ) { + if (this.container != null) { + // Adds spacing and border from css + const cssBorder = this.getBorderSizes() + let w1 = this.container.offsetWidth - cssBorder.x - cssBorder.width - 1 + let h1 = + maxHeight != null + ? maxHeight + : this.container.offsetHeight - cssBorder.y - cssBorder.height - 1 + let bounds = this.view.getGraphBounds() + + if (bounds.width > 0 && bounds.height > 0) { + if (keepOrigin && bounds.x != null && bounds.y != null) { + bounds = bounds.clone() + bounds.width += bounds.x + bounds.height += bounds.y + bounds.x = 0 + bounds.y = 0 + } + + const s = this.view.scale + const w2 = bounds.width / s + const h2 = bounds.height / s + const b = (keepOrigin ? border : 2 * border) + margin + 1 + + w1 -= b + h1 -= b + + let s2 = ignoreWidth + ? h1 / h2 + : ignoreHeight + ? w1 / w2 + : Math.min(w1 / w2, h1 / h2) + + if (this.graph.minFitScale != null) { + s2 = Math.max(s2, this.graph.minFitScale) + } + + if (this.graph.maxFitScale != null) { + s2 = Math.min(s2, this.graph.maxFitScale) + } + + if (enabled) { + if (!keepOrigin) { + if (!util.hasScrollbars(this.container)) { + const x0 = + bounds.x != null + ? Math.floor( + this.view.translate.x - + bounds.x / s + + border / s2 + + margin / 2, + ) + : border + + const y0 = + bounds.y != null + ? Math.floor( + this.view.translate.y - + bounds.y / s + + border / s2 + + margin / 2, + ) + : border + + this.view.scaleAndTranslate(s2, x0, y0) + } else { + this.view.setScale(s2) + const b2 = this.graph.getGraphBounds() + + if (b2.x != null) { + this.container.scrollLeft = b2.x + } + + if (b2.y != null) { + this.container.scrollTop = b2.y + } + } + } else if (this.view.scale !== s2) { + this.view.setScale(s2) + } + } else { + return s2 + } + } + } + + return this.view.scale + } + + zoom(factor: number, center: boolean) { + let scale = Math.round(this.view.scale * factor * 100) / 100 + scale = util.clamp(scale, this.graph.minScale, this.graph.maxScale) + factor = scale / this.view.scale // tslint:disable-line + + const state = this.view.getState(this.graph.getSelectedCell()) + if (this.graph.keepSelectionVisibleOnZoom && state != null) { + const rect = new Rectangle( + state.bounds.x * factor, + state.bounds.y * factor, + state.bounds.width * factor, + state.bounds.height * factor, + ) + + // Refreshes the display only once if a scroll is carried out + this.view.scale = scale + if (!this.scrollRectToVisible(rect)) { + this.view.revalidate() + // Forces an event to be fired but does not revalidate again + this.view.setScale(scale) + } + } else { + const hasScrollbars = util.hasScrollbars(this.container) + if (center && !hasScrollbars) { + let dx = this.container.offsetWidth + let dy = this.container.offsetHeight + + if (factor > 1) { + const f = (factor - 1) / (scale * 2) + dx *= -f + dy *= -f + } else { + const f = (1 / factor - 1) / (this.view.scale * 2) + dx *= f + dy *= f + } + + this.view.scaleAndTranslate( + scale, + this.view.translate.x + dx, + this.view.translate.y + dy, + ) + } else { + // Allows for changes of translate and scrollbars during setscale + const tx = this.view.translate.x + const ty = this.view.translate.y + const sl = this.container.scrollLeft + const st = this.container.scrollTop + + this.view.setScale(scale) + + if (hasScrollbars) { + let dx = 0 + let dy = 0 + + if (center) { + dx = (this.container.offsetWidth * (factor - 1)) / 2 + dy = (this.container.offsetHeight * (factor - 1)) / 2 + } + + const t = this.view.translate + const s = this.view.scale + this.container.scrollLeft = + (t.x - tx) * s + Math.round(sl * factor + dx) + this.container.scrollTop = + (t.y - ty) * s + Math.round(st * factor + dy) + } + } + } + } + + zoomToRect(rect: Rectangle) { + const container = this.container + const scaleX = container.clientWidth / rect.width + const scaleY = container.clientHeight / rect.height + const aspectFactor = scaleX / scaleY + + // Remove any overlap of the rect outside the client area + rect.x = Math.max(0, rect.x) + rect.y = Math.max(0, rect.y) + let rectRight = Math.min(container.scrollWidth, rect.x + rect.width) + let rectBottom = Math.min(container.scrollHeight, rect.y + rect.height) + rect.width = rectRight - rect.x + rect.height = rectBottom - rect.y + + // The selection area has to be increased to the same aspect + // ratio as the container, centred around the centre point of the + // original rect passed in. + if (aspectFactor < 1.0) { + // Height needs increasing + const newHeight = rect.height / aspectFactor + const deltaHeightBuffer = (newHeight - rect.height) / 2.0 + rect.height = newHeight + + // Assign up to half the buffer to the upper part of the rect, + // not crossing 0 put the rest on the bottom + const upperBuffer = Math.min(rect.y, deltaHeightBuffer) + rect.y = rect.y - upperBuffer + + // Check if the bottom has extended too far + rectBottom = Math.min(container.scrollHeight, rect.y + rect.height) + rect.height = rectBottom - rect.y + } else { + // Width needs increasing + const newWidth = rect.width * aspectFactor + const deltaWidthBuffer = (newWidth - rect.width) / 2.0 + rect.width = newWidth + + // Assign up to half the buffer to the upper part of the rect, + // not crossing 0 put the rest on the bottom + const leftBuffer = Math.min(rect.x, deltaWidthBuffer) + rect.x = rect.x - leftBuffer + + // Check if the right hand side has extended too far + rectRight = Math.min(container.scrollWidth, rect.x + rect.width) + rect.width = rectRight - rect.x + } + + const scale = container.clientWidth / rect.width + const newScale = this.view.scale * scale + + if (!util.hasScrollbars(container)) { + this.view.scaleAndTranslate( + newScale, + this.view.translate.x - rect.x / this.view.scale, + this.view.translate.y - rect.y / this.view.scale, + ) + } else { + this.view.setScale(newScale) + container.scrollLeft = Math.round(rect.x * scale) + container.scrollTop = Math.round(rect.y * scale) + } + } + + scrollCellToVisible(cell: Cell, center: boolean = false) { + const x = -this.view.translate.x + const y = -this.view.translate.y + + const state = this.view.getState(cell) + if (state != null) { + const bounds = new Rectangle( + x + state.bounds.x, + y + state.bounds.y, + state.bounds.width, + state.bounds.height, + ) + + if (center && this.container != null) { + const w = this.container.clientWidth + const h = this.container.clientHeight + + bounds.x = bounds.getCenterX() - w / 2 + bounds.width = w + bounds.y = bounds.getCenterY() - h / 2 + bounds.height = h + } + + const tr = new Point(this.view.translate.x, this.view.translate.y) + + if (this.scrollRectToVisible(bounds)) { + // Triggers an update via the view's event source + const tr2 = new Point(this.view.translate.x, this.view.translate.y) + this.view.translate.x = tr.x + this.view.translate.y = tr.y + this.view.setTranslate(tr2.x, tr2.y) + } + } + } + + scrollRectToVisible(rect: Rectangle) { + let isChanged = false + + if (rect != null) { + const w = this.container.offsetWidth + const h = this.container.offsetHeight + + const widthLimit = Math.min(w, rect.width) + const heightLimit = Math.min(h, rect.height) + + if (util.hasScrollbars(this.container)) { + const c = this.container + rect.x += this.view.translate.x + rect.y += this.view.translate.y + let dx = c.scrollLeft - rect.x + const ddx = Math.max(dx - c.scrollLeft, 0) + + if (dx > 0) { + c.scrollLeft -= dx + 2 + } else { + dx = rect.x + widthLimit - c.scrollLeft - c.clientWidth + + if (dx > 0) { + c.scrollLeft += dx + 2 + } + } + + let dy = c.scrollTop - rect.y + const ddy = Math.max(0, dy - c.scrollTop) + + if (dy > 0) { + c.scrollTop -= dy + 2 + } else { + dy = rect.y + heightLimit - c.scrollTop - c.clientHeight + + if (dy > 0) { + c.scrollTop += dy + 2 + } + } + + if (!this.graph.useScrollbarsForPanning && (ddx !== 0 || ddy !== 0)) { + this.view.setTranslate(ddx, ddy) + } + } else { + const x = -this.view.translate.x + const y = -this.view.translate.y + + const s = this.view.scale + + if (rect.x + widthLimit > x + w) { + this.view.translate.x -= (rect.x + widthLimit - w - x) / s + isChanged = true + } + + if (rect.y + heightLimit > y + h) { + this.view.translate.y -= (rect.y + heightLimit - h - y) / s + isChanged = true + } + + if (rect.x < x) { + this.view.translate.x += (x - rect.x) / s + isChanged = true + } + + if (rect.y < y) { + this.view.translate.y += (y - rect.y) / s + isChanged = true + } + + if (isChanged) { + this.view.refresh() + // Repaints selection marker (ticket 18) + this.graph.selectionHandler.refresh() + } + } + } + + return isChanged + } + + scrollPointToVisible(x: number, y: number, extend: boolean, border: number) { + if ( + !this.graph.timerAutoScroll && + (this.graph.ignoreScrollbars || util.hasScrollbars(this.container)) + ) { + const c = this.container + + if ( + x >= c.scrollLeft && + y >= c.scrollTop && + x <= c.scrollLeft + c.clientWidth && + y <= c.scrollTop + c.clientHeight + ) { + let dx = c.scrollLeft + c.clientWidth - x + + if (dx < border) { + const old = c.scrollLeft + c.scrollLeft += border - dx + + // Automatically extends the canvas size to the bottom, right + // if the event is outside of the canvas and the edge of the + // canvas has been reached. Notes: Needs fix for IE. + if (extend && old === c.scrollLeft) { + if (this.graph.dialect === 'svg') { + const root = (this.view.getDrawPane() as SVGElement) + .ownerSVGElement! + const width = this.container.scrollWidth + border - dx + + // Updates the clipping region. This is an expensive + // operation that should not be executed too often. + root.style.width = util.toPx(width) + } else { + const width = Math.max(c.clientWidth, c.scrollWidth) + border - dx + const stage = this.view.getStage()! + stage.style.width = util.toPx(width) + } + + c.scrollLeft += border - dx + } + } else { + dx = x - c.scrollLeft + + if (dx < border) { + c.scrollLeft -= border - dx + } + } + + let dy = c.scrollTop + c.clientHeight - y + + if (dy < border) { + const old = c.scrollTop + c.scrollTop += border - dy + + if (old === c.scrollTop && extend) { + if (this.graph.dialect === 'svg') { + const root = (this.view.getDrawPane() as SVGElement) + .ownerSVGElement! + const height = this.container.scrollHeight + border - dy + + // Updates the clipping region. This is an expensive + // operation that should not be executed too often. + root.style.height = util.toPx(height) + } else { + const height = + Math.max(c.clientHeight, c.scrollHeight) + border - dy + const canvas = this.view.getStage()! + canvas.style.height = util.toPx(height) + } + + c.scrollTop += border - dy + } + } else { + dy = y - c.scrollTop + + if (dy < border) { + c.scrollTop -= border - dy + } + } + } + } else if ( + this.graph.allowAutoPanning && + !this.graph.panningHandler.isActive() + ) { + if (this.graph.panningManager == null) { + this.graph.panningManager = this.createPanningManager() + } + + this.graph.panningManager.panTo( + x + this.graph.panDx, + y + this.graph.panDy, + ) + } + } + + createPanningManager() { + // TODO: xx + // return new PanningManager(this) + } + + /** + * Returns the size of the border and padding on all four sides of the + * container. The left, top, right and bottom borders are stored in the x, y, + * width and height of the returned , respectively. + */ + getBorderSizes() { + const css = util.getComputedStyle(this.container) + return new Rectangle( + util.parseCssNumber(css.paddingLeft) + + (css.borderLeftStyle !== 'none' + ? util.parseCssNumber(css.borderLeftWidth) + : 0), + util.parseCssNumber(css.paddingTop) + + (css.borderTopStyle !== 'none' + ? util.parseCssNumber(css.borderTopWidth) + : 0), + util.parseCssNumber(css.paddingRight) + + (css.borderRightStyle !== 'none' + ? util.parseCssNumber(css.borderRightWidth) + : 0), + util.parseCssNumber(css.paddingBottom) + + (css.borderBottomStyle !== 'none' + ? util.parseCssNumber(css.borderBottomWidth) + : 0), + ) + } +} diff --git a/packages/x6/src/handler/connection/handler.ts b/packages/x6/src/handler/connection/handler.ts index 0c7547bf589..198465a823e 100644 --- a/packages/x6/src/handler/connection/handler.ts +++ b/packages/x6/src/handler/connection/handler.ts @@ -130,7 +130,11 @@ export class ConnectionHandler extends MouseHandler { return '' } - return this.graph.validator.getEdgeValidationError(null, source, target) + return this.graph.validationManager.getEdgeValidationError( + null, + source, + target, + ) } protected isInsertBefore( diff --git a/packages/x6/src/handler/connection/marker.ts b/packages/x6/src/handler/connection/marker.ts index ae612a9eb72..7c7b3b8042b 100644 --- a/packages/x6/src/handler/connection/marker.ts +++ b/packages/x6/src/handler/connection/marker.ts @@ -49,7 +49,7 @@ export class ConnectionMarker extends CellMarker { if ( currentPoint != null && this.graph.isSwimlane(cell) && - this.graph.cellManager.hitsSwimlaneContent( + this.graph.retrievalManager.hitsSwimlaneContent( cell, currentPoint.x, currentPoint.y, diff --git a/packages/x6/src/handler/connection/preview.ts b/packages/x6/src/handler/connection/preview.ts index 56f3ee8d714..e3450a2335d 100644 --- a/packages/x6/src/handler/connection/preview.ts +++ b/packages/x6/src/handler/connection/preview.ts @@ -537,7 +537,7 @@ export class Preview extends Disposable { } if (this.error != null && this.error.length > 0) { - this.graph.validationWarn(this.error) + this.graph.validationManager.warning(this.error) } } } diff --git a/packages/x6/src/handler/edge/handler.ts b/packages/x6/src/handler/edge/handler.ts index bddabba16c5..02bbfb81f1f 100644 --- a/packages/x6/src/handler/edge/handler.ts +++ b/packages/x6/src/handler/edge/handler.ts @@ -286,7 +286,7 @@ export class EdgeHandler extends MouseHandler { } validateConnection(source: Cell | null, target: Cell | null) { - return this.graph.validator.getEdgeValidationError( + return this.graph.validationManager.getEdgeValidationError( this.state.cell, source, target, @@ -1110,7 +1110,7 @@ export class EdgeHandler extends MouseHandler { // if there is an error message with non-zero length if (this.error != null) { if (this.error.length > 0) { - this.graph.validationWarn(this.error) + this.graph.validationManager.warning(this.error) } } else if (Handle.isCustomHandle(index)) { if (this.customHandles != null) { diff --git a/packages/x6/src/handler/edge/marker.ts b/packages/x6/src/handler/edge/marker.ts index 83abd9130b9..3048a6310fc 100644 --- a/packages/x6/src/handler/edge/marker.ts +++ b/packages/x6/src/handler/edge/marker.ts @@ -52,7 +52,7 @@ export class EdgeMarker extends CellMarker { if ( (this.graph.isSwimlane(cell) && this.currentPoint != null && - this.graph.cellManager.hitsSwimlaneContent( + this.graph.retrievalManager.hitsSwimlaneContent( cell, this.currentPoint.x, this.currentPoint.y, diff --git a/packages/x6/src/handler/handler-mouse.ts b/packages/x6/src/handler/handler-mouse.ts index 757abbb4762..d8990f47963 100644 --- a/packages/x6/src/handler/handler-mouse.ts +++ b/packages/x6/src/handler/handler-mouse.ts @@ -31,7 +31,7 @@ export abstract class MouseHandler extends BaseHandler } isMouseDown() { - return this.graph.eventloop.isMouseDown + return this.graph.eventloopManager.isMouseDown } isConsumed(e: MouseEventEx) { diff --git a/packages/x6/src/handler/keyboard/handler.ts b/packages/x6/src/handler/keyboard/handler.ts index 9e67d91f146..2665b575b49 100644 --- a/packages/x6/src/handler/keyboard/handler.ts +++ b/packages/x6/src/handler/keyboard/handler.ts @@ -73,7 +73,7 @@ export class KeyboardHandler extends BaseHandler { escape(e: KeyboardEvent) { if (this.graph.isEscapeEnabled()) { - this.graph.eventloop.escape(e) + this.graph.eventloopManager.escape(e) } } diff --git a/packages/x6/src/handler/mousewheel/handler.ts b/packages/x6/src/handler/mousewheel/handler.ts new file mode 100644 index 00000000000..da3723a3ce7 --- /dev/null +++ b/packages/x6/src/handler/mousewheel/handler.ts @@ -0,0 +1,116 @@ +import * as util from '../../util' +import { Point } from '../../struct' +import { DomEvent, detector } from '../../common' +import { BaseHandler } from '../handler-base' + +export class MouseWheelHandler extends BaseHandler { + private cursorPosition: Point + private wheelZoomDelay: number = 10 + private wheelZoomTimer: number | null + private cumulativeZoomFactor: number = 1 + private handler = (e: WheelEvent, up: boolean) => { + if (this.isZoomWheelEvent(e)) { + let source = DomEvent.getSource(e) + while (source != null) { + if (source === this.graph.container) { + this.cursorPosition = new Point( + DomEvent.getClientX(e), + DomEvent.getClientY(e), + ) + this.lazyZoom(up) + DomEvent.consume(e) + return false + } + + source = source.parentNode as Element + } + } + } + + enable() { + DomEvent.addWheelListener(this.handler, this.graph.container) + super.enable() + } + + disable() { + DomEvent.removeWheelListener(this.handler, this.graph.container) + super.disable() + } + + isZoomWheelEvent(e: MouseEvent) { + return ( + DomEvent.isAltDown(e) || + (detector.IS_MAC && DomEvent.isMetaDown(e)) || + (!detector.IS_MAC && DomEvent.isControlDown(e)) + ) + } + + lazyZoom(zoomIn: boolean) { + if (this.wheelZoomTimer != null) { + window.clearTimeout(this.wheelZoomTimer) + } + + const scale = this.view.scale + const scaleFactor = this.graph.scaleFactor + const container = this.graph.container + + // Switches to 1% zoom steps below 15% + // Lower bound depdends on rounding below + if (zoomIn) { + if (scale * this.cumulativeZoomFactor < 0.15) { + this.cumulativeZoomFactor = (scale + 0.01) / scale + } else { + // Uses to 5% zoom steps for better grid rendering in webkit + // and to avoid rounding errors for zoom steps + this.cumulativeZoomFactor *= scaleFactor + this.cumulativeZoomFactor = + Math.round(scale * this.cumulativeZoomFactor * 20) / 20 / scale + } + } else { + if (scale * this.cumulativeZoomFactor <= 0.15) { + this.cumulativeZoomFactor = (scale - 0.01) / scale + } else { + // Uses to 5% zoom steps for better grid rendering in webkit + // and to avoid rounding errors for zoom steps + this.cumulativeZoomFactor /= scaleFactor + this.cumulativeZoomFactor = + Math.round(scale * this.cumulativeZoomFactor * 20) / 20 / scale + } + } + + this.cumulativeZoomFactor = Math.max( + 0.01, + Math.min(scale * this.cumulativeZoomFactor, 160) / scale, + ) + + this.wheelZoomTimer = window.setTimeout(() => { + const offset = util.getOffset(container) + let dx = 0 + let dy = 0 + + if (this.cursorPosition != null) { + dx = container.offsetWidth / 2 - this.cursorPosition.x + offset.x + dy = container.offsetHeight / 2 - this.cursorPosition.y + offset.y + } + + const prev = this.view.scale + this.graph.zoom(this.cumulativeZoomFactor) + const s = this.view.scale + + if (s !== prev) { + // if (resize != null) { + // ui.chromelessResize(false, null, dx * (this.cumulativeZoomFactor - 1), + // dy * (this.cumulativeZoomFactor - 1)) + // } + + if (util.hasScrollbars(container) && (dx !== 0 || dy !== 0)) { + container.scrollLeft -= dx * (this.cumulativeZoomFactor - 1) + container.scrollTop -= dy * (this.cumulativeZoomFactor - 1) + } + } + + this.cumulativeZoomFactor = 1 + this.wheelZoomTimer = null + }, this.wheelZoomDelay) + } +} diff --git a/packages/x6/src/handler/mousewheel/option.ts b/packages/x6/src/handler/mousewheel/option.ts new file mode 100644 index 00000000000..c2dffeffa2c --- /dev/null +++ b/packages/x6/src/handler/mousewheel/option.ts @@ -0,0 +1,6 @@ +type Modifiers = 'alt' | 'ctrl' | 'meta' + +export interface MouseWheelOptions { + enabled: boolean + modifiers: Modifiers | Modifiers[] | null +} diff --git a/packages/x6/src/handler/moving/preview.ts b/packages/x6/src/handler/moving/preview.ts index bd1a9bea970..1252f6a25c4 100644 --- a/packages/x6/src/handler/moving/preview.ts +++ b/packages/x6/src/handler/moving/preview.ts @@ -246,7 +246,7 @@ export class Preview extends Disposable { ) { state = graph.view.getState(cell) if (state != null) { - const error = graph.validator.getEdgeValidationError( + const error = graph.validationManager.getEdgeValidationError( null, this.cell, cell, diff --git a/packages/x6/src/handler/node/preview.ts b/packages/x6/src/handler/node/preview.ts index e5cae8c57ac..8819f331c61 100644 --- a/packages/x6/src/handler/node/preview.ts +++ b/packages/x6/src/handler/node/preview.ts @@ -623,7 +623,7 @@ export class Preview extends Disposable { } if (this.graph.isConstrainChild(this.state.cell)) { - let tmp = this.graph.cellManager.getCellContainmentArea(this.state.cell) + let tmp = this.graph.sizeManager.getCellContainmentArea(this.state.cell) if (tmp != null) { const overlap = this.graph.getOverlap(this.state.cell) diff --git a/packages/x6/src/handler/tooltip/handler.ts b/packages/x6/src/handler/tooltip/handler.ts index d434c1bc738..edd757b105b 100644 --- a/packages/x6/src/handler/tooltip/handler.ts +++ b/packages/x6/src/handler/tooltip/handler.ts @@ -109,10 +109,10 @@ export class TooltipHandler extends MouseHandler { } protected canShow(tip: string | HTMLElement | null) { - return !this.disposed && this.enabled && this.validateTip(tip) + return !this.disposed && this.enabled && this.validateTooltip(tip) } - protected validateTip(tip: string | HTMLElement | null) { + protected validateTooltip(tip: string | HTMLElement | null) { return ( tip != null && ((util.isString(tip) && (tip as string).length > 0) || @@ -120,6 +120,50 @@ export class TooltipHandler extends MouseHandler { ) } + protected getTooltip( + state: State | null, + trigger: HTMLElement, + x: number, + y: number, + ) { + let tooltip: string | null = null + if (state != null) { + // Checks if the mouse is over the folding icon + if ( + state.control != null && + (trigger === state.control.elem || + trigger.parentNode === state.control.elem) + ) { + tooltip = 'Collapse/Expand' + } + + if (tooltip == null && state.overlays != null) { + state.overlays.each(shape => { + if ( + tooltip == null && + (trigger === shape.elem || trigger.parentNode === shape.elem) + ) { + tooltip = shape.overlay!.toString() + } + }) + } + + if (tooltip == null) { + const handler = this.graph.selectionHandler.getHandler(state.cell) + const getTooltipForNode = handler && (handler as any).getTooltipForNode + if (getTooltipForNode && typeof getTooltipForNode === 'function') { + tooltip = getTooltipForNode(trigger) + } + } + + if (tooltip == null) { + tooltip = this.graph.getTooltip(state.cell) + } + } + + return tooltip + } + protected reset( e: MouseEventEx, restart: boolean = false, @@ -138,7 +182,7 @@ export class TooltipHandler extends MouseHandler { this.timer = window.setTimeout(() => { if (this.willShow()) { - const tip = this.graph.cellManager.getTooltip(state!, elem, x, y) + const tip = this.getTooltip(state!, elem, x, y) this.show(state!.cell, elem, tip, x, y) this.state = state! this.sourceElem = elem diff --git a/packages/x6/src/manager/cell-manager.ts b/packages/x6/src/manager/cell-manager.ts deleted file mode 100644 index 9ba75b7b968..00000000000 --- a/packages/x6/src/manager/cell-manager.ts +++ /dev/null @@ -1,2868 +0,0 @@ -import * as util from '../util' -import { Route } from '../route' -import { Graph } from '../graph' -import { Cell } from '../core/cell' -import { State } from '../core/state' -import { Geometry } from '../core/geometry' -import { Style, Align, VAlign } from '../types' -import { Point, Rectangle, Overlay, Image, Anchor } from '../struct' -import { ManagerBase } from './base' - -export class CellManager extends ManagerBase { - // #region :::::::::::: Creating - - addCells( - cells: Cell[], - parent: Cell, - index: number, - sourceNode?: Cell, - targetNode?: Cell, - ) { - this.model.batchUpdate(() => { - this.graph.trigger(Graph.events.addCells, { - cells, - parent, - index, - sourceNode, - targetNode, - }) - - this.cellsAdded(cells, parent, index, sourceNode, targetNode, false, true) - }) - - return cells - } - - protected cellsAdded( - cells: Cell[], - parent: Cell, - index: number, - sourceNode?: Cell | null, - targetNode?: Cell | null, - absolute?: boolean, - constrain?: boolean, - extend?: boolean, - ) { - if (cells != null && parent != null && index != null) { - this.model.batchUpdate(() => { - const pState = absolute ? this.view.getState(parent) : null - const o1 = pState != null ? pState.origin : null - const zero = new Point(0, 0) - - for (let i = 0, ii = cells.length; i < ii; i += 1) { - if (cells[i] == null) { - index -= 1 // tslint:disable-line - continue - } - - const oldParent = this.model.getParent(cells[i]) - - // Keeps the cell at it's absolute location. - if (o1 != null && cells[i] !== parent && parent !== oldParent) { - const oldState = this.view.getState(oldParent) - const o2 = oldState != null ? oldState.origin : zero - let geo = this.model.getGeometry(cells[i]) - - if (geo != null) { - const dx = o2.x - o1.x - const dy = o2.y - o1.y - - geo = geo.clone() - geo.translate(dx, dy) - - if ( - !geo.relative && - !this.graph.isNegativeCoordinatesAllowed() && - this.model.isNode(cells[i]) - ) { - geo.bounds.x = Math.max(0, geo.bounds.x) - geo.bounds.y = Math.max(0, geo.bounds.y) - } - - this.model.setGeometry(cells[i], geo) - } - } - - // Decrements all following indices if cell is already in parent - if ( - parent === oldParent && - index + i > this.model.getChildCount(parent) - ) { - index -= 1 // tslint:disable-line - } - - this.model.add(parent, cells[i], index + i) - - if (this.graph.autoSizeOnAdded) { - this.autoSizeCell(cells[i], true) - } - - // Extends the parent or constrains the child - if ( - (extend == null || extend) && - this.graph.isExtendParentsOnAdd() && - this.graph.isExtendParent(cells[i]) - ) { - this.extendParent(cells[i]) - } - - // Additionally constrains the child after extending the parent - if (constrain == null || constrain) { - this.constrainChild(cells[i]) - } - - // Sets the source terminal - if (sourceNode != null) { - this.cellConnected(cells[i], sourceNode, true) - } - - // Sets the target terminal - if (targetNode != null) { - this.cellConnected(cells[i], targetNode, false) - } - } - - this.graph.trigger(Graph.events.cellsAdded, { - cells, - parent, - index, - sourceNode, - targetNode, - absolute, - }) - }) - } - } - - duplicateCells(cells: Cell[], append: boolean) { - const model = this.model - const diff = this.graph.getGridSize() - const sources = model.getTopmostCells(cells) - const select: Cell[] = [] - - model.batchUpdate(() => { - const clones = this.cloneCells(sources, false, undefined, true) - for (let i = 0, ii = sources.length; i < ii; i += 1) { - const parent = model.getParent(sources[i]) - const child = this.moveCells([clones[i]], diff, diff, false)[0] - select.push(child) - - if (append) { - model.add(parent, clones[i]) - } else { - const index = parent!.getChildIndex(sources[i]) - model.add(parent, clones[i], index + 1) - } - } - }) - - this.graph.selectCells(select) - - return select - } - - deleteCells( - cells: Cell[], - includeEdges: boolean, - selectParentAfterDelete: boolean, - ) { - if (cells != null && cells.length > 0) { - const graph = this.graph - this.graph.eventloop.escape(new KeyboardEvent('')) - const parents = selectParentAfterDelete - ? graph.model.getParents(cells) - : null - - graph.removeCells(cells, includeEdges) - - // Selects parents for easier editing of groups - if (parents != null) { - const select = parents.filter( - cell => - graph.model.contains(cell) && - (graph.model.isNode(cell) || graph.model.isEdge(cell)), - ) - - graph.selectCells(select) - } - } - } - - removeCells(cells: Cell[], includeEdges: boolean) { - let removing: Cell[] - - if (includeEdges) { - removing = this.graph.getDeletableCells(this.addAllEdges(cells)) - } else { - removing = cells.slice() - - // Removes edges that are currently not - // visible as those cannot be updated - const edges = this.graph.getDeletableCells(this.getAllEdges(cells)) - const dict = new WeakMap() - - cells.forEach(cell => dict.set(cell, true)) - edges.forEach(edge => { - if (this.view.getState(edge) == null && !dict.get(edge)) { - dict.set(edge, true) - removing.push(edge) - } - }) - } - - this.model.batchUpdate(() => { - this.graph.trigger(Graph.events.removeCells, { - includeEdges, - cells: removing, - }) - this.cellsRemoved(removing) - }) - - return removing - } - - /** - * Returns an array with the given cells and all edges that are connected - * to a cell or one of its descendants. - */ - protected addAllEdges(cells: Cell[]) { - const merged = [...cells, ...this.getAllEdges(cells)] - return util.uniq(merged) - } - - getAllEdges(cells: Cell[]) { - const edges: Cell[] = [] - if (cells != null) { - cells.forEach(cell => { - cell.eachEdge(edge => edges.push(edge)) - const children = this.model.getChildren(cell) - edges.push(...this.getAllEdges(children)) - }) - } - - return edges - } - - protected cellsRemoved(cells: Cell[]) { - if (cells != null && cells.length > 0) { - this.model.batchUpdate(() => { - const dict = new WeakMap() - cells.forEach(cell => dict.set(cell, true)) - cells.forEach(cell => { - const edges = this.getAllEdges([cell]) - edges.forEach(edge => { - if (!dict.get(edge)) { - dict.set(edge, true) - this.disconnectTerminal(cell, edge, true) - this.disconnectTerminal(cell, edge, false) - } - }) - - this.model.remove(cell) - }) - - this.graph.trigger(Graph.events.cellsRemoved, { cells }) - }) - } - } - - protected disconnectTerminal(cell: Cell, edge: Cell, isSource: boolean) { - const scale = this.view.scale - const trans = this.view.translate - - let geo = this.model.getGeometry(edge) - if (geo != null) { - // Checks if terminal is being removed - const terminal = this.model.getTerminal(edge, isSource) - let connected = false - let tmp = terminal - - while (tmp != null) { - if (cell === tmp) { - connected = true - break - } - - tmp = this.model.getParent(tmp) - } - - if (connected) { - geo = geo.clone() - const state = this.view.getState(edge) - - if (state != null && state.absolutePoints != null) { - const pts = state.absolutePoints - const n = isSource ? 0 : pts.length - 1 - - geo.setTerminalPoint( - new Point( - pts[n].x / scale - trans.x - state.origin.x, - pts[n].y / scale - trans.y - state.origin.y, - ), - isSource, - ) - } else { - // fallback - const state = this.view.getState(terminal) - if (state != null) { - geo.setTerminalPoint( - new Point( - state.bounds.getCenterX() / scale - trans.x, - state.bounds.getCenterY() / scale - trans.y, - ), - isSource, - ) - } - } - - this.model.setGeometry(edge, geo) - this.model.setTerminal(edge, null, isSource) - } - } - } - - splitEdge( - edge: Cell, - cells: Cell[], - newEdge: Cell | null, - dx: number, - dy: number, - ) { - this.model.batchUpdate(() => { - const parent = this.model.getParent(edge) - const source = this.model.getTerminal(edge, true) - - if (newEdge == null) { - newEdge = this.graph.cloneCell(edge) // tslint:disable-line - - // Removes waypoints before/after new cell - const state = this.view.getState(edge) - let geo = this.graph.getCellGeometry(newEdge) - - if (geo != null && geo.points != null && state != null) { - const t = this.view.translate - const s = this.view.scale - const idx = util.findNearestSegment( - state, - (dx + t.x) * s, - (dy + t.y) * s, - ) - - geo.points = geo.points.slice(0, idx) - - geo = this.graph.getCellGeometry(edge) - - if (geo != null && geo.points != null) { - geo = geo.clone() - geo.points = geo.points.slice(idx) - this.model.setGeometry(edge, geo) - } - } - } - - this.cellsMoved(cells, dx, dy, false, false) - - let index = this.model.getChildCount(parent) - this.cellsAdded(cells, parent!, index, null, null, true) - - index = this.model.getChildCount(parent) - this.cellsAdded([newEdge], parent!, index, source, cells[0], false) - this.cellConnected(edge, cells[0], true) - - this.graph.trigger(Graph.events.splitEdge, { - edge, - cells, - newEdge, - dx, - dy, - }) - }) - - return newEdge - } - - cloneCells( - cells: Cell[], - allowInvalidEdges: boolean = true, - mapping: WeakMap = new WeakMap(), - keepPosition: boolean = false, - ) { - let clones: Cell[] = [] - if (cells != null) { - // Creates a dictionary for fast lookups - const dict = new WeakMap() - const tmp = [] - - cells.forEach(cell => { - dict.set(cell, true) - tmp.push(cell) - }) - - if (tmp.length > 0) { - const scale = this.view.scale - const trans = this.view.translate - - clones = this.model.cloneCells(cells, true, mapping) as Cell[] - - for (let i = 0, ii = cells.length; i < ii; i += 1) { - if ( - !allowInvalidEdges && - this.model.isEdge(clones[i]!) && - !this.graph.isEdgeValid( - clones[i], - this.model.getTerminal(clones[i], true), - this.model.getTerminal(clones[i], false), - ) - ) { - const tmp = clones as any - tmp[i] = null - } else { - const geom = this.model.getGeometry(clones[i]!) - if (geom != null) { - const state = this.view.getState(cells[i]) - const pstate = this.view.getState(this.model.getParent(cells[i])!) - - if (state != null && pstate != null) { - const dx = keepPosition ? 0 : pstate.origin.x - const dy = keepPosition ? 0 : pstate.origin.y - - if (this.model.isEdge(clones[i])) { - const pts = state.absolutePoints - if (pts != null) { - // Checks if the source is cloned or sets the terminal point - let source = this.model.getTerminal(cells[i], true) - while (source != null && !dict.get(source)) { - source = this.model.getParent(source) - } - - if (source == null && pts[0] != null) { - geom.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 target = this.model.getTerminal(cells[i], false) - while (target != null && !dict.get(target)) { - target = this.model.getParent(target) - } - - const n = pts.length - 1 - - if (target == null && pts[n] != null) { - geom.setTerminalPoint( - new Point( - pts[n]!.x / scale - trans.x, - pts[n]!.y / scale - trans.y, - ), - false, - ) - } - - // Translates the control points - geom.points && - geom.points.forEach(p => { - p.x += dx - p.y += dy - }) - } - } else { - geom.translate(dx, dy) - } - } - } - } - } - } - } - - return clones - } - - turnCells(cells: Cell[]) { - const model = this.model - const select: Cell[] = [] - - model.batchUpdate(() => { - for (let i = 0, ii = cells.length; i < ii; i += 1) { - const cell = cells[i] - - if (model.isEdge(cell)) { - const src = model.getTerminal(cell, true) - const trg = model.getTerminal(cell, false) - - model.setTerminal(cell, trg, true) - model.setTerminal(cell, src, false) - - let geo = model.getGeometry(cell) - if (geo != null) { - geo = geo.clone() - - if (geo.points != null) { - geo.points.reverse() - } - - const sp = geo.getTerminalPoint(true) - const tp = geo.getTerminalPoint(false) - - geo.setTerminalPoint(sp, false) - geo.setTerminalPoint(tp, true) - model.setGeometry(cell, geo) - - // Inverts anchors - const edgeState = this.view.getState(cell) - const sourceState = this.view.getState(src) - const targetState = this.view.getState(trg) - - if (edgeState != null) { - const sc = - sourceState != null - ? this.getConnectionAnchor(edgeState, sourceState, true) - : null - const tc = - targetState != null - ? this.getConnectionAnchor(edgeState, targetState, false) - : null - - this.setConnectionAnchor(cell, src, true, tc) - this.setConnectionAnchor(cell, trg, false, sc) - } - - select.push(cell) - } - } else if (model.isNode(cell)) { - let geo = model.getGeometry(cell) - if (geo != null) { - // Rotates the size and position in the geometry - geo = geo.clone() - const bounds = geo.bounds - bounds.x += bounds.width / 2 - bounds.height / 2 - bounds.y += bounds.height / 2 - bounds.width / 2 - const tmp = bounds.width - bounds.width = bounds.height - bounds.height = tmp - model.setGeometry(cell, geo) - - // Reads the current direction and advances by 90 degrees - const state = this.view.getState(cell) - if (state != null) { - const style = state.style - let direction = style.direction || 'east' - if (direction === 'east') { - direction = 'south' - } else if (direction === 'south') { - direction = 'west' - } else if (direction === 'west') { - direction = 'north' - } else if (direction === 'north') { - direction = 'east' - } - - this.setCellStyle({ ...style, direction }, [cell]) - } - - select.push(cell) - } - } - } - }) - - this.graph.selectCells(select) - - return select - } - - // #endregion - - // #region :::::::::::: Grouping - - groupCells(group: Cell, border: number, cells: Cell[]) { - cells = this.getCellsForGroup(cells) // tslint:disable-line - - if (group == null) { - group = this.graph.createGroup(cells) // tslint:disable-line - } - - if (cells.length > 0) { - const bounds = this.getBoundsForGroup(group, cells, border) - if (bounds != null) { - // Uses parent of group or previous parent of first child - let parent = this.model.getParent(group) - if (parent == null) { - parent = this.model.getParent(cells[0]) - } - - this.model.batchUpdate(() => { - // Ensure the group's geometry - if (this.graph.getCellGeometry(group) == null) { - this.model.setGeometry(group, new Geometry()) - } - - // Adds the group into the parent - let index = this.model.getChildCount(parent!) - this.cellsAdded( - [group], - parent!, - index, - null, - null, - false, - false, - false, - ) - - // Adds the children into the group and moves - index = this.model.getChildCount(group) - this.cellsAdded(cells, group, index, null, null, false, false, false) - this.cellsMoved(cells, -bounds.x, -bounds.y, false, false, false) - - // Resizes the group - this.cellsResized([group], [bounds], false) - - this.graph.trigger(Graph.events.groupCells, { group, cells, border }) - }) - } - } - - return group - } - - /** - * Returns the cells with the same parent as the first cell - * in the given array. - */ - protected getCellsForGroup(cells: Cell[]) { - const result = [] - - if (cells != null && cells.length > 0) { - const parent = this.model.getParent(cells[0]) - result.push(cells[0]) - - // Filters selection cells with the same parent - for (let i = 1, ii = cells.length; i < ii; i += 1) { - if (this.model.getParent(cells[i]) === parent) { - result.push(cells[i]) - } - } - } - - return result - } - - /** - * Returns the bounds to be used for the given group and children. - */ - protected getBoundsForGroup(group: Cell, children: Cell[], border: number) { - const result = this.graph.getBoundingBoxFromGeometry(children, true) - if (result != null) { - if (this.graph.isSwimlane(group)) { - const size = this.graph.getStartSize(group) - - result.x -= size.width - result.y -= size.height - result.width += size.width - result.height += size.height - } - - // Adds the border - if (border != null) { - result.x -= border - result.y -= border - result.width += 2 * border - result.height += 2 * border - } - } - - return result - } - - ungroupCells(cells?: Cell[]) { - let result: Cell[] = [] - - if (cells == null) { - const selected = this.graph.getSelectedCells() - // Finds the cells with children - // tslint:disable-next-line - cells = selected.filter(cell => this.model.getChildCount(cell) > 0) - } - - if (cells != null && cells.length > 0) { - this.model.batchUpdate(() => { - cells!.forEach(cell => { - let children = this.model.getChildren(cell) - if (children != null && children.length > 0) { - children = children.slice() - const parent = this.model.getParent(cell)! - const index = this.model.getChildCount(parent) - - this.cellsAdded(children, parent, index, null, null, true) - result = result.concat(children) - } - }) - - this.removeGroupsAfterUngroup(cells!) - this.graph.trigger(Graph.events.ungroupCells, { cells }) - }) - } - - return result - } - - removeGroupsAfterUngroup(cells: Cell[]) { - this.cellsRemoved(this.addAllEdges(cells)) - } - - removeCellsFromParent(cells: Cell[]) { - this.model.batchUpdate(() => { - const parent = this.graph.getDefaultParent()! - const index = this.model.getChildCount(parent) - - this.cellsAdded(cells, parent, index, null, null, true) - this.graph.trigger(Graph.events.removeCellsFromParent, { cells }) - }) - - return cells - } - - updateGroupBounds( - cells: Cell[], - border: number = 0, - moveGroup: boolean = false, - topBorder: number = 0, - rightBorder: number = 0, - bottomBorder: number = 0, - leftBorder: number = 0, - ) { - this.model.batchUpdate(() => { - for (let i = cells.length - 1; i >= 0; i -= 1) { - let geo = this.graph.getCellGeometry(cells[i]) - if (geo != null) { - const children = this.graph.getChildren(cells[i]) - if (children != null && children.length > 0) { - const bounds = this.graph.getBoundingBoxFromGeometry(children, true) - if (bounds != null && bounds.width > 0 && bounds.height > 0) { - let left = 0 - let top = 0 - - // Adds the size of the title area for swimlanes - if (this.graph.isSwimlane(cells[i])) { - const size = this.graph.getStartSize(cells[i]) - left = size.width - top = size.height - } - - geo = geo.clone() - - if (moveGroup) { - geo.bounds.x = Math.round( - geo.bounds.x + bounds.x - border - left - leftBorder, - ) - - geo.bounds.y = Math.round( - geo.bounds.y + bounds.y - border - top - topBorder, - ) - } - - geo.bounds.width = Math.round( - bounds.width + 2 * border + left + leftBorder + rightBorder, - ) - - geo.bounds.height = Math.round( - bounds.height + 2 * border + top + topBorder + bottomBorder, - ) - - this.model.setGeometry(cells[i], geo) - - this.moveCells( - children, - border + left - bounds.x + leftBorder, - border + top - bounds.y + topBorder, - ) - } - } - } - } - }) - - return cells - } - - // #endregion - - // #region :::::::::::: Connection - - connectCell( - edge: Cell, - terminal: Cell | null, - isSource: boolean, - anchor?: Anchor, - ) { - this.model.batchUpdate(() => { - const previous = this.model.getTerminal(edge, isSource) - this.graph.trigger(Graph.events.connectCell, { - edge, - terminal, - isSource, - anchor, - previous, - }) - - this.cellConnected(edge, terminal, isSource, anchor) - }) - return edge - } - - protected cellConnected( - edge: Cell, - terminal: Cell | null, - isSource: boolean, - anchor?: Anchor, - ) { - if (edge != null) { - this.model.batchUpdate(() => { - const previous = this.model.getTerminal(edge, isSource) - - // Updates the anchor - this.setConnectionAnchor(edge, terminal, isSource, anchor) - - // Checks if the new terminal is a port, uses the ID of the port - // in the style and the parent of the port as the actual terminal - // of the edge. - if (this.graph.isPortsEnabled()) { - let id = null - - if (terminal != null && this.graph.isPort(terminal)) { - id = terminal.getId() - // tslint:disable-next-line - terminal = this.graph.getTerminalForPort(terminal, isSource)! - } - - if (id != null) { - const key = isSource ? 'sourcePort' : 'targetPort' - this.updateCellsStyle(key, id, [edge]) - } - } - - this.model.setTerminal(edge, terminal, isSource) - - if (this.graph.resetEdgesOnConnect) { - this.resetEdge(edge) - } - - this.graph.trigger(Graph.events.cellConnected, { - edge, - terminal, - isSource, - previous, - anchor, - }) - }) - } - } - - getConnectionAnchor( - edgeState: State, - terminalState?: State | null, - isSource: boolean = false, - ) { - let point: Point | null = null - const style = edgeState.style - - // connection point specified in style - const x = isSource ? style.exitX : style.entryX - if (x != null) { - const y = isSource ? style.exitY : style.entryY - if (y != null) { - point = new Point(x, y) - } - } - - let dx = 0 - let dy = 0 - let perimeter = true - - if (point != null) { - perimeter = - (isSource ? style.exitPerimeter : style.entryPerimeter) !== false - - // Add entry/exit offset - dx = (isSource ? style.exitDx : style.entryDx) as number - dy = (isSource ? style.exitDy : style.entryDy) as number - - dx = isFinite(dx) ? dx : 0 - dy = isFinite(dy) ? dy : 0 - } - - return new Anchor({ point, perimeter, dx, dy }) - } - - setConnectionAnchor( - edge: Cell, - terminal: Cell | null, - isSource: boolean, - anchor?: Anchor | null, - ) { - if (anchor != null) { - this.model.batchUpdate(() => { - if (anchor == null || anchor.point == null) { - this.updateCellsStyle(isSource ? 'exitX' : 'entryX', null, [edge]) - this.updateCellsStyle(isSource ? 'exitY' : 'entryY', null, [edge]) - this.updateCellsStyle(isSource ? 'exitDx' : 'entryDx', null, [edge]) - this.updateCellsStyle(isSource ? 'exitDy' : 'entryDy', null, [edge]) - this.updateCellsStyle( - isSource ? 'exitPerimeter' : 'entryPerimeter', - null, - [edge], - ) - } else if (anchor.point != null) { - this.updateCellsStyle( - isSource ? 'exitX' : 'entryX', - `${anchor.point.x}`, - [edge], - ) - this.updateCellsStyle( - isSource ? 'exitY' : 'entryY', - `${anchor.point.y}`, - [edge], - ) - this.updateCellsStyle( - isSource ? 'exitDx' : 'entryDx', - `${anchor.dx}`, - [edge], - ) - this.updateCellsStyle( - isSource ? 'exitDy' : 'entryDy', - `${anchor.dy}`, - [edge], - ) - - // Only writes `false` since `true` is default - if (!anchor.perimeter) { - this.updateCellsStyle( - isSource ? 'exitPerimeter' : 'entryPerimeter', - false, - [edge], - ) - } else { - this.updateCellsStyle( - isSource ? 'exitPerimeter' : 'entryPerimeter', - null, - [edge], - ) - } - } - }) - } - } - - disconnectGraph(cells: Cell[]) { - if (cells != null) { - this.model.batchUpdate(() => { - const s = this.view.scale - const t = this.view.translate - - const dict = new WeakMap() - cells.forEach(c => dict.set(c, true)) - - cells.forEach(edge => { - if (this.model.isEdge(edge)) { - let geo = this.model.getGeometry(edge) - if (geo != null) { - const state = this.view.getState(edge) - const pstate = this.view.getState(this.model.getParent(edge)) - - if (state != null && pstate != null) { - geo = geo.clone() - - const dx = -pstate.origin.x - const dy = -pstate.origin.y - const pts = state.absolutePoints - - let src = this.model.getTerminal(edge, true) - if ( - src != null && - this.graph.isCellDisconnectable(edge, src, true) - ) { - while (src != null && !dict.get(src)) { - src = this.model.getParent(src) - } - - if (src == null) { - geo.setTerminalPoint( - new Point( - pts[0]!.x / s - t.x + dx, - pts[0]!.y / s - t.y + dy, - ), - true, - ) - this.model.setTerminal(edge, null, true) - } - } - - let trg = this.model.getTerminal(edge, false) - if ( - trg != null && - this.graph.isCellDisconnectable(edge, trg, false) - ) { - while (trg != null && !dict.get(trg)) { - trg = this.model.getParent(trg) - } - - if (trg == null) { - const n = pts.length - 1 - geo.setTerminalPoint( - new Point( - pts[n]!.x / s - t.x + dx, - pts[n]!.y / s - t.y + dy, - ), - false, - ) - this.model.setTerminal(edge, null, false) - } - } - - this.model.setGeometry(edge, geo) - } - } - } - }) - }) - } - } - - /** - * Returns true if perimeter points should be computed such that the - * resulting edge has only horizontal or vertical segments. - */ - isOrthogonal(state: State) { - const orthogonal = state.style.orthogonal - if (orthogonal != null) { - return orthogonal - } - - const route = this.view.getRoute(state) - return ( - route === Route.segment || - route === Route.elbow || - route === Route.sideToSide || - route === Route.topToBottom || - route === Route.er || - route === Route.orth - ) - } - - /** - * Returns true if the given cell state is a loop. - */ - isLoop(state: State) { - const src = state.getVisibleTerminalState(true) - const trg = state.getVisibleTerminalState(false) - - return src != null && src === trg - } - - // #endregion - - // #region :::::::::::: Sizing - - autoSizeCell(cell: Cell, recurse: boolean) { - if (recurse) { - cell.eachChild(child => this.autoSizeCell(child, recurse)) - } - - if (this.model.isNode(cell) && this.graph.isAutoSizeCell(cell)) { - this.updateCellSize(cell) - } - return cell - } - - updateCellSize(cell: Cell, ignoreChildren: boolean = false) { - this.model.batchUpdate(() => { - this.graph.trigger(Graph.events.updateCellSize, { cell, ignoreChildren }) - this.cellSizeUpdated(cell, ignoreChildren) - }) - return cell - } - - cellSizeUpdated(cell: Cell, ignoreChildren: boolean) { - if (cell != null) { - this.model.batchUpdate(() => { - const size = this.getCellPreferredSize(cell) - let geo = this.model.getGeometry(cell) - if (size != null && geo != null) { - const collapsed = this.graph.isCellCollapsed(cell) - geo = geo.clone() - - if (this.graph.isSwimlane(cell)) { - const style = this.graph.getStyle(cell) - const cellStyle = this.model.getStyle(cell) || {} - - if (style.horizontal !== false) { - cellStyle.startSize = size.height + 8 - - if (collapsed) { - geo.bounds.height = size.height + 8 - } - - geo.bounds.width = size.width - } else { - cellStyle.startSize = size.width + 8 - - if (collapsed) { - geo.bounds.width = size.width + 8 - } - - geo.bounds.height = size.height - } - - this.model.setStyle(cell, cellStyle) - } else { - geo.bounds.width = size.width - geo.bounds.height = size.height - } - - if (!ignoreChildren && !collapsed) { - const bounds = this.view.getBounds(this.model.getChildren(cell)) - if (bounds != null) { - const t = this.view.translate - const s = this.view.scale - - const width = (bounds.x + bounds.width) / s - geo.bounds.x - t.x - const height = (bounds.y + bounds.height) / s - geo.bounds.y - t.y - - geo.bounds.width = Math.max(geo.bounds.width, width) - geo.bounds.height = Math.max(geo.bounds.height, height) - } - } - - this.cellsResized([cell], [geo.bounds], false) - } - }) - } - } - - protected getCellPreferredSize(cell: Cell) { - let result = null - - if (cell != null && !this.model.isEdge(cell)) { - const state = this.view.getState(cell, true)! - const style = state.style - const fontSize = style.fontSize || 12 - - let dx = 0 - let dy = 0 - - // Adds dimension of image if shape is a label - if (this.getImage(state) != null || style.image != null) { - if (style.shape === 'label') { - if (style.verticalAlign === 'middle') { - dx += style.imageWidth || 0 - } - - if (style.align !== 'center') { - dy += style.imageHeight || 0 - } - } - } - - // Adds spacings - dx += 2 * (style.spacing || 0) - dx += style.spacingLeft || 0 - dx += style.spacingRight || 0 - - dy += 2 * (style.spacing || 0) - dy += style.spacingTop || 0 - dy += style.spacingBottom || 0 - - // Add spacing for collapse/expand icon - const image = this.graph.getFoldingImage(state) - if (image != null) { - dx += image.width + 8 - } - - // Adds space for label - let value = this.renderer.getLabelValue(state) - if (value != null && typeof value === 'string' && value.length > 0) { - if (!this.graph.isHtmlLabel(state.cell)) { - value = util.escape(value) - } - - value = value.replace(/\n/g, '
') - - const size = util.getSizeForString(value, fontSize, style.fontFamily) - let width = size.width + dx - let height = size.height + dy - - if (style.horizontal === false) { - const tmp = height - - height = width - width = tmp - } - - if (this.graph.isGridEnabled()) { - width = this.graph.snap(width + this.graph.getGridSize() / 2) - height = this.graph.snap(height + this.graph.getGridSize() / 2) - } - - result = new Rectangle(0, 0, width, height) - } else { - const gs2 = 4 * this.graph.getGridSize() - result = new Rectangle(0, 0, gs2, gs2) - } - } - - return result - } - - resizeCells(cells: Cell[], bounds: Rectangle[], recurse: boolean) { - this.model.batchUpdate(() => { - this.graph.trigger(Graph.events.resizeCells, { cells, bounds }) - this.cellsResized(cells, bounds, recurse) - }) - return cells - } - - protected cellsResized( - cells: Cell[], - bounds: Rectangle[], - recurse: boolean = false, - ) { - if (cells != null && bounds != null && cells.length === bounds.length) { - this.model.batchUpdate(() => { - for (let i = 0, ii = cells.length; i < ii; i += 1) { - this.cellResized(cells[i], bounds[i], false, recurse) - - if (this.graph.isExtendParent(cells[i])) { - this.extendParent(cells[i]) - } - - this.constrainChild(cells[i]) - } - - if (this.graph.resetEdgesOnResize) { - this.resetOtherEdges(cells) - } - }) - - this.graph.trigger(Graph.events.cellsResized, { cells, bounds }) - } - } - - protected cellResized( - cell: Cell, - bounds: Rectangle, - ignoreRelative: boolean, - recurse: boolean, - ) { - let geo = this.model.getGeometry(cell) - if ( - geo != null && - (geo.bounds.x !== bounds.x || - geo.bounds.y !== bounds.y || - geo.bounds.width !== bounds.width || - geo.bounds.height !== bounds.height) - ) { - geo = geo.clone() - - if (!ignoreRelative && geo.relative) { - const offset = geo.offset - - if (offset != null) { - offset.x += bounds.x - geo.bounds.x - offset.y += bounds.y - geo.bounds.y - } - } else { - geo.bounds.x = bounds.x - geo.bounds.y = bounds.y - } - - geo.bounds.width = bounds.width - geo.bounds.height = bounds.height - - if ( - !geo.relative && - this.model.isNode(cell) && - !this.graph.isNegativeCoordinatesAllowed() - ) { - geo.bounds.x = Math.max(0, geo.bounds.x) - geo.bounds.y = Math.max(0, geo.bounds.y) - } - - this.model.batchUpdate(() => { - if (recurse) { - this.graph.resizeChildCells(cell, geo!) - } - - this.model.setGeometry(cell, geo!) - this.constrainChildCells(cell) - }) - } - } - - scaleCell(cell: Cell, sx: number, sy: number, recurse: boolean) { - let geo = this.model.getGeometry(cell) - if (geo != null) { - geo = geo.clone() - - // Stores values for restoring based on style - const x = geo.bounds.x - const y = geo.bounds.y - const w = geo.bounds.width - const h = geo.bounds.height - - const style = this.graph.getStyle(cell) - geo.scale(sx, sy, style.aspect) - - if (style.resizeWidth === true) { - geo.bounds.width = w * sx - } else if (style.resizeWidth === false) { - geo.bounds.width = w - } - - if (style.resizeHeight === true) { - geo.bounds.height = h * sy - } else if (style.resizeHeight === false) { - geo.bounds.height = h - } - - if (!this.graph.isCellMovable(cell)) { - geo.bounds.x = x - geo.bounds.y = y - } - - if (!this.graph.isCellResizable(cell)) { - geo.bounds.width = w - geo.bounds.height = h - } - - if (this.model.isNode(cell)) { - this.cellResized(cell, geo.bounds, true, recurse) - } else { - this.model.setGeometry(cell, geo) - } - } - return cell - } - - /** - * Resizes the parents recursively so that they contain the complete - * area of the resized child cell. - */ - protected extendParent(cell: Cell | null) { - if (cell != null) { - const parent = this.model.getParent(cell) - let pgeo = this.graph.getCellGeometry(parent!) - - if ( - parent != null && - pgeo != null && - !this.graph.isCellCollapsed(parent) - ) { - const geo = this.graph.getCellGeometry(cell) - - if ( - geo != null && - !geo.relative && - (pgeo.bounds.width < geo.bounds.x + geo.bounds.width || - pgeo.bounds.height < geo.bounds.y + geo.bounds.height) - ) { - pgeo = pgeo.clone() - - pgeo.bounds.width = Math.max( - pgeo.bounds.width, - geo.bounds.x + geo.bounds.width, - ) - - pgeo.bounds.height = Math.max( - pgeo.bounds.height, - geo.bounds.y + geo.bounds.height, - ) - - this.cellsResized([parent], [pgeo.bounds], false) - } - } - } - } - - /** - * Keeps the given cell inside the bounds. - * - * @param cell - The cell will be constrained. - * @param sizeFirst - Specifies if the size should be changed first. - * Default is `true`. - */ - constrainChild(cell: Cell, sizeFirst: boolean = true) { - if (cell != null) { - let geo = this.graph.getCellGeometry(cell) - if ( - geo != null && - (this.graph.isConstrainRelativeChildren() || !geo.relative) - ) { - const parent = this.model.getParent(cell)! - // const pgeo = this.getCellGeometry(parent) - let max = this.graph.getMaxGraphBounds() - // Finds parent offset - if (max != null) { - const off = this.graph.getBoundingBoxFromGeometry([parent], false) - if (off != null) { - max = max.clone() - max.x -= off.x - max.y -= off.y - } - } - - if (this.graph.isConstrainChild(cell)) { - let area = this.getCellContainmentArea(cell) - if (area != null) { - const overlap = this.graph.getOverlap(cell) - - if (overlap > 0) { - area = Rectangle.clone(area) - - area.x -= area.width * overlap - area.y -= area.height * overlap - area.width += 2 * area.width * overlap - area.height += 2 * area.height * overlap - } - - // Find the intersection between max and tmp - if (max == null) { - max = area - } else { - max = Rectangle.clone(max) - max.intersect(area) - } - } - } - - if (max != null) { - const cells = [cell] - - if (!this.graph.isCellCollapsed(cell)) { - const desc = this.model.getDescendants(cell) - - for (let i = 0; i < desc.length; i += 1) { - if (this.graph.isCellVisible(desc[i])) { - cells.push(desc[i]) - } - } - } - - const bbox = this.graph.getBoundingBoxFromGeometry(cells, false) - - if (bbox != null) { - geo = geo.clone() - - // Cumulative horizontal movement - let dx = 0 - - if (geo.bounds.width > max.width) { - dx = geo.bounds.width - max.width - geo.bounds.width -= dx - } - - if (bbox.x + bbox.width > max.x + max.width) { - dx -= bbox.x + bbox.width - max.x - max.width - dx - } - - // Cumulative vertical movement - let dy = 0 - - if (geo.bounds.height > max.height) { - dy = geo.bounds.height - max.height - geo.bounds.height -= dy - } - - if (bbox.y + bbox.height > max.y + max.height) { - dy -= bbox.y + bbox.height - max.y - max.height - dy - } - - if (bbox.x < max.x) { - dx -= bbox.x - max.x - } - - if (bbox.y < max.y) { - dy -= bbox.y - max.y - } - - if (dx !== 0 || dy !== 0) { - if (geo.relative) { - // Relative geometries are moved via absolute offset - if (geo.offset == null) { - geo.offset = new Point() - } - - geo.offset.x += dx - geo.offset.y += dy - } else { - geo.bounds.x += dx - geo.bounds.y += dy - } - } - - this.model.setGeometry(cell, geo) - } - } - } - } - } - - /** - * Constrains the children of the given cell. - */ - constrainChildCells(cell: Cell) { - cell.eachChild(child => this.constrainChild(child)) - } - - getCellContainmentArea(cell: Cell) { - if (cell != null && !this.model.isEdge(cell)) { - const parent = this.model.getParent(cell) - if (parent != null && parent !== this.graph.getDefaultParent()) { - const geo = this.model.getGeometry(parent) - if (geo != null) { - let x = 0 - let y = 0 - let w = geo.bounds.width - let h = geo.bounds.height - - if (this.graph.isSwimlane(parent)) { - const size = this.graph.getStartSize(parent) - const style = this.graph.getStyle(parent) - - const dir = style.direction || 'east' - const flipH = style.flipH === true - const flipV = style.flipV === true - - if (dir === 'south' || dir === 'north') { - const tmp = size.width - size.width = size.height - size.height = tmp - } - - if ( - (dir === 'east' && !flipV) || - (dir === 'north' && !flipH) || - (dir === 'west' && flipV) || - (dir === 'south' && flipH) - ) { - x = size.width - y = size.height - } - - w -= size.width - h -= size.height - } - - return new Rectangle(x, y, w, h) - } - } - } - - return null - } - - getBoundingBox(cells: Cell[]) { - let result: Rectangle | null = null - - if (cells == null || cells.length <= 0) { - return result - } - - cells.forEach(cell => { - if (this.model.isNode(cell) || this.model.isEdge(cell)) { - const bbox = this.view.getBoundingBox(this.view.getState(cell), true) - if (bbox != null) { - if (result == null) { - result = Rectangle.clone(bbox) - } else { - result.add(bbox) - } - } - } - }) - - return result - } - - // #endregion - - // #region :::::::::::: Moving - - moveCells( - cells: Cell[], - dx: number = 0, - dy: number = 0, - clone: boolean = false, - target?: Cell | null, - e?: MouseEvent, - cache?: WeakMap, - ) { - if (cells != null && (dx !== 0 || dy !== 0 || clone || target != null)) { - // Removes descendants with ancestors in cells to avoid multiple moving - cells = this.model.getTopmostCells(cells) // tslint:disable-line - - this.model.batchUpdate(() => { - const dict = new WeakMap() - cells.forEach(c => dict.set(c, true)) - - const isSelected = (cell: Cell | null) => { - let node = cell - while (node != null) { - if (dict.get(node)) { - return true - } - - node = this.model.getParent(node)! - } - - return false - } - - // Removes relative edge labels with selected terminals - const checked = [] - - for (let i = 0; i < cells.length; i += 1) { - const geo = this.graph.getCellGeometry(cells[i]) - const parent = this.model.getParent(cells[i])! - - if ( - geo == null || - !geo.relative || - !this.model.isEdge(parent) || - (!isSelected(this.model.getTerminal(parent, true)) && - !isSelected(this.model.getTerminal(parent, false))) - ) { - checked.push(cells[i]) - } - } - - // tslint:disable-next-line - cells = checked - - if (clone) { - // tslint:disable-next-line - cells = this.cloneCells( - cells, - this.graph.isInvalidEdgesClonable(), - cache, - )! - - if (target == null) { - // tslint:disable-next-line - target = this.graph.getDefaultParent()! - } - } - - const previous = this.graph.isNegativeCoordinatesAllowed() - - if (target != null) { - this.graph.setNegativeCoordinatesAllowed(true) - } - - this.graph.trigger(Graph.events.moveCells, { - cells, - dx, - dy, - clone, - target, - e, - }) - - this.cellsMoved( - cells, - dx, - dy, - !clone && - this.graph.isDisconnectOnMove() && - this.graph.isDanglingEdgesEnabled(), - target == null, - this.graph.isExtendParentsOnMove() && target == null, - ) - - this.graph.setNegativeCoordinatesAllowed(previous) - - if (target != null) { - const index = this.model.getChildCount(target) - this.cellsAdded(cells, target, index, null, null, true) - } - }) - } - - return cells - } - - cellsMoved( - cells: Cell[], - dx: number, - dy: number, - disconnect: boolean, - constrain: boolean, - extend: boolean = false, - ) { - if (cells != null && (dx !== 0 || dy !== 0)) { - this.model.batchUpdate(() => { - if (disconnect) { - this.disconnectGraph(cells) - } - - cells.forEach(cell => { - this.translateCell(cell, dx, dy) - if (extend && this.graph.isExtendParent(cell)) { - this.extendParent(cell) - } else if (constrain) { - this.constrainChild(cell) - } - }) - - if (this.graph.resetEdgesOnMove) { - this.resetOtherEdges(cells) - } - - this.graph.trigger(Graph.events.cellsMoved, { - cells, - dx, - dy, - disconnect, - }) - }) - } - } - - translateCell(cell: Cell, dx: number, dy: number) { - let geo = this.model.getGeometry(cell) - if (geo != null) { - geo = geo.clone() - geo.translate(dx, dy) - - if ( - !geo.relative && - this.model.isNode(cell) && - !this.graph.isNegativeCoordinatesAllowed() - ) { - geo.bounds.x = Math.max(0, geo.bounds.x) - geo.bounds.y = Math.max(0, geo.bounds.y) - } - - if (geo.relative && !this.model.isEdge(cell)) { - const parent = this.model.getParent(cell)! - let rot = 0 - - if (this.model.isNode(parent)) { - const style = this.graph.getStyle(parent) - rot = style.rotation || 0 - } - - if (rot !== 0) { - const pt = util.rotatePoint(new Point(dx, dy), -rot, new Point(0, 0)) - dx = pt.x // tslint:disable-line - dy = pt.y // tslint:disable-line - } - - if (geo.offset == null) { - geo.offset = new Point(dx, dy) - } else { - geo.offset.x = geo.offset.x + dx - geo.offset.y = geo.offset.y + dy - } - } - - this.model.setGeometry(cell, geo) - } - } - - resetOtherEdges(cells: Cell[]) { - if (cells != null) { - // Prepares faster cells lookup - const dict = new WeakMap() - cells.forEach(c => dict.set(c, true)) - - this.model.batchUpdate(() => { - cells.forEach(cell => { - const edges = this.model.getEdges(cell) - if (edges != null) { - edges.forEach(edge => { - const [source, target] = this.getVisibleTerminals(edge) - // Checks if one of the terminals is not in the given array - if (!dict.get(source!) || !dict.get(target!)) { - this.resetEdge(edge) - } - }) - } - - this.resetOtherEdges(this.model.getChildren(cell)) - }) - }) - } - } - - /** - * Resets the control points of the given edge. - */ - resetEdge(edge: Cell) { - let geo = this.model.getGeometry(edge) - if (geo != null && geo.points != null && geo.points.length > 0) { - geo = geo.clone() - geo.points = [] - this.model.setGeometry(edge, geo) - } - } - - // #endregion - - // #region :::::::::::: Align - - alignCells(align: Align | VAlign, cells: Cell[], param?: number) { - if (cells != null && cells.length > 1) { - let coord = param - - // Finds the required coordinate for the alignment - if (coord == null) { - for (let i = 0, ii = cells.length; i < ii; i += 1) { - const state = this.view.getState(cells[i]) - if (state != null && !this.model.isEdge(cells[i])) { - if (coord == null) { - if (align === 'center') { - coord = state.bounds.x + state.bounds.width / 2 - } else if (align === 'right') { - coord = state.bounds.x + state.bounds.width - } else if (align === 'top') { - coord = state.bounds.y - } else if (align === 'middle') { - coord = state.bounds.y + state.bounds.height / 2 - } else if (align === 'bottom') { - coord = state.bounds.y + state.bounds.height - } else { - coord = state.bounds.x - } - } else { - if (align === 'right') { - coord = Math.max(coord, state.bounds.x + state.bounds.width) - } else if (align === 'top') { - coord = Math.min(coord, state.bounds.y) - } else if (align === 'bottom') { - coord = Math.max(coord, state.bounds.y + state.bounds.height) - } else { - coord = Math.min(coord, state.bounds.x) - } - } - } - } - } - - // Aligns the cells to the coordinate - this.model.batchUpdate(() => { - const s = this.view.scale - cells.forEach(cell => { - const state = this.view.getState(cell) - if (state != null && coord != null) { - const bounds = state.bounds - let geo = this.graph.getCellGeometry(cell) - if (geo != null && !this.model.isEdge(cell)) { - geo = geo.clone() - - if (align === 'center') { - geo.bounds.x += (coord - bounds.x - bounds.width / 2) / s - } else if (align === 'right') { - geo.bounds.x += (coord - bounds.x - bounds.width) / s - } else if (align === 'top') { - geo.bounds.y += (coord - bounds.y) / s - } else if (align === 'middle') { - geo.bounds.y += (coord - bounds.y - bounds.height / 2) / s - } else if (align === 'bottom') { - geo.bounds.y += (coord - bounds.y - bounds.height) / s - } else { - geo.bounds.x += (coord - bounds.x) / s - } - - this.graph.resizeCell(cell, geo.bounds) - } - } - }) - }) - - this.graph.trigger(Graph.events.alignCells, { align, cells }) - } - - return cells - } - - //#endregion - - // #region :::::::::::: Order - - orderCells(toBack: boolean, cells: Cell[]) { - this.model.batchUpdate(() => { - this.graph.trigger(Graph.events.orderCells, { cells, toBack }) - this.cellsOrdered(cells, toBack) - }) - return cells - } - - protected cellsOrdered(cells: Cell[], toBack: boolean) { - if (cells != null) { - this.model.batchUpdate(() => { - cells.forEach((cell, i) => { - const parent = this.model.getParent(cell)! - - if (toBack) { - this.model.add(parent, cell, i) - } else { - this.model.add(parent, cell, this.model.getChildCount(parent) - 1) - } - }) - - this.graph.trigger(Graph.events.cellsOrdered, { cells, toBack }) - }) - } - } - - //#endregion - - // #region :::::::::::: Visibility - - toggleCells(show: boolean, cells: Cell[], includeEdges: boolean) { - const arr = includeEdges ? this.addAllEdges(cells) : cells - this.model.batchUpdate(() => { - this.graph.trigger(Graph.events.toggleCells, { - show, - includeEdges, - cells: arr, - }) - this.setCellsVisibleImpl(arr, show) - }) - - return arr - } - - protected setCellsVisibleImpl(cells: Cell[], show: boolean) { - if (cells != null && cells.length > 0) { - this.model.batchUpdate(() => { - cells.forEach(cell => this.model.setVisible(cell, show)) - }) - } - } - - // #endregion - - // #region :::::::::::: Folding - - foldCells( - collapse: boolean, - recurse: boolean, - cells: Cell[], - checkFoldable: boolean, - ) { - this.graph.stopEditing(false) - this.model.batchUpdate(() => { - this.graph.trigger(Graph.events.foldCells, { collapse, recurse, cells }) - this.cellsFolded(cells, collapse, recurse, checkFoldable) - }) - return cells - } - - protected cellsFolded( - cells: Cell[], - collapse: boolean, - recurse: boolean, - checkFoldable: boolean = false, - ) { - if (cells != null && cells.length > 0) { - this.model.batchUpdate(() => { - cells.forEach(cell => { - if ( - (!checkFoldable || this.graph.isCellFoldable(cell, collapse)) && - collapse !== this.graph.isCellCollapsed(cell) - ) { - this.model.setCollapsed(cell, collapse) - this.swapBounds(cell, collapse) - - if (this.graph.isExtendParent(cell)) { - this.extendParent(cell) - } - - if (recurse) { - const children = this.model.getChildren(cell) - this.cellsFolded(children, collapse, recurse) - } - - this.constrainChild(cell) - } - }) - }) - this.graph.trigger(Graph.events.cellsFolded, { cells, collapse, recurse }) - } - } - - protected swapBounds(cell: Cell, willCollapse: boolean) { - if (cell != null) { - let geo = this.model.getGeometry(cell) - if (geo != null) { - geo = geo.clone() - this.updateAlternateBounds(cell, geo, willCollapse) - geo.swap() - this.model.setGeometry(cell, geo) - } - } - } - - /** - * Updates or sets the alternate bounds in the given geometry for the - * given cell depending on whether the cell is going to be collapsed. - * If no alternate bounds are defined in the geometry and - * `collapseToPreferredSize` is true, then the preferred size is used for - * the alternate bounds. The top, left corner is always kept at the same - * location. - */ - protected updateAlternateBounds( - cell: Cell, - geo: Geometry, - willCollapse: boolean, - ) { - if (cell != null && geo != null) { - const style = this.graph.getStyle(cell) - - if (geo.alternateBounds == null) { - let bounds = geo.bounds - - if (this.graph.collapseToPreferredSize) { - const tmp = this.getCellPreferredSize(cell) - if (tmp != null) { - bounds = tmp - const startSize = style.startSize || 0 - if (startSize > 0) { - bounds.height = Math.max(bounds.height, startSize) - } - } - } - - geo.alternateBounds = new Rectangle(0, 0, bounds.width, bounds.height) - } - - if (geo.alternateBounds != null) { - geo.alternateBounds.x = geo.bounds.x - geo.alternateBounds.y = geo.bounds.y - - const alpha = util.toRad(style.rotation || 0) - if (alpha !== 0) { - const dx = geo.alternateBounds.getCenterX() - geo.bounds.getCenterX() - const dy = geo.alternateBounds.getCenterY() - geo.bounds.getCenterY() - - const cos = Math.cos(alpha) - const sin = Math.sin(alpha) - - const dx2 = cos * dx - sin * dy - const dy2 = sin * dx + cos * dy - - geo.alternateBounds.x += dx2 - dx - geo.alternateBounds.y += dy2 - dy - } - } - } - } - - // #endregion - - // #region :::::::::::: Overlay - - addOverlay(cell: Cell, overlay: Overlay) { - if (cell.overlays == null) { - cell.overlays = [] - } - - cell.overlays.push(overlay) - - const state = this.view.getState(cell) - if (state != null) { - // Immediately updates the cell display if the state exists - this.renderer.redraw(state) - } - - this.graph.trigger(Graph.events.addOverlay, { cell, overlay }) - - return overlay - } - - removeOverlay(cell: Cell, overlay?: Overlay | null) { - if (overlay == null) { - return this.removeOverlays(cell) - } - - const idx = cell.overlays != null ? cell.overlays.indexOf(overlay) : -1 - if (idx >= 0 && cell.overlays != null) { - cell.overlays.splice(idx, 1) - if (cell.overlays.length === 0) { - delete cell.overlays - } - - const state = this.view.getState(cell) - if (state != null) { - this.renderer.redraw(state) - } - - this.graph.trigger(Graph.events.removeOverlay, { cell, overlay }) - - return overlay - } - - return null - } - - removeOverlays(cell: Cell) { - const overlays = cell.overlays - if (overlays != null) { - delete cell.overlays - const state = this.view.getState(cell) - if (state != null) { - this.renderer.redraw(state) - } - - this.graph.trigger(Graph.events.removeOverlays, { cell, overlays }) - } - - return overlays - } - - addWarningOverlay( - cell: Cell, - warning: string | null, - image: Image, - selectOnClick: boolean, - ) { - const overlay = new Overlay({ - image, - tooltip: `${warning}`, - }) - - // Adds a handler for single mouseclicks to select the cell - if (selectOnClick) { - // TODO: - // overlay.addListener('click', () => { - // if (this.isEnabled()) { - // this.setSelectionCell(cell) - // } - // }) - } - - return this.addOverlay(cell, overlay) - } - - // #endregion - - // #region :::::::::::: Style - - getCellStyle(cell: Cell | null): Style { - if (cell != null) { - const preset = this.model.isEdge(cell) - ? this.graph.options.edgeStyle - : this.graph.options.nodeStyle - const style = this.model.getStyle(cell) || {} - return { - ...preset, - ...style, - } - } - - return {} - } - - setCellStyle(style: Style, cells: Cell[]) { - if (cells != null) { - this.model.batchUpdate(() => { - cells.forEach(cell => this.model.setStyle(cell, style)) - }) - } - } - - updateCellsStyle( - key: string, - value?: string | number | boolean | null, - cells?: Cell[], - ) { - if (cells != null && cells.length > 0) { - this.model.batchUpdate(() => { - cells.forEach(cell => { - if (cell != null) { - const raw = this.model.getStyle(cell) - const style = raw != null ? { ...raw } : {} - if (value == null) { - delete (style as any)[key] - } else { - const tmp = style as any - tmp[key] = value - } - this.model.setStyle(cell, style) - } - }) - }) - } - } - - toggleCellsStyle(key: string, defaultValue: boolean = false, cells: Cell[]) { - let value = null - if (cells != null && cells.length > 0) { - const state = this.view.getState(cells[0]) - const style = state != null ? state.style : this.getCellStyle(cells[0]) - if (style != null) { - const cur = (style as any)[key] - if (cur == null) { - value = defaultValue - } else { - value = cur ? false : true - } - this.updateCellsStyle(key, value, cells) - } - } - - return value - } - - setCellsStyleFlag( - key: string, - flag: number, - add: boolean | null, - cells: Cell[], - ) { - if (cells != null && cells.length > 0) { - if (add == null) { - const style = this.graph.getStyle(cells[0]) - const current = parseInt((style as any)[key], 10) || 0 - add = !((current & flag) === flag) // tslint:disable-line - } - - this.model.batchUpdate(() => { - cells.forEach(cell => { - if (cell != null) { - this.setCellStyleFlag(key, flag, add!, cell) - } - }) - }) - } - } - - setCellStyleFlag(key: string, flag: number, add: boolean, cell: Cell) { - const style = this.graph.getStyle(cell) - const current = parseInt((style as any)[key], 10) || 0 - const exists = (current & flag) === flag - let target = current - if (add && !exists) { - target += flag - } else if (!add && exists) { - target -= flag - } - - if (target !== current) { - this.updateCellsStyle(key, target, [cell]) - } - } - - toggleCellsLocked(cells: Cell[]) { - if (cells.length > 0) { - const style = this.graph.getStyle(cells[0]) - this.toggleCellsStyle('locked', !!style.locked, cells) - } - } - - // #endregion - - // #region :::::::::::: Flip - - flipEdge(edge: Cell) { - if (edge != null && this.graph.alternateEdgeStyle != null) { - this.model.batchUpdate(() => { - const style = this.model.getStyle(edge) - if (style == null) { - this.model.setStyle(edge, this.graph.alternateEdgeStyle!) - } else { - this.model.setStyle(edge, {}) - } - - // Removes all control points - this.resetEdge(edge) - this.graph.trigger(Graph.events.flipEdge, { edge }) - }) - } - - return edge - } - - //#endregion - - // #region :::::::::::: Drilldown - - enterGroup(cell: Cell) { - if (cell != null && this.graph.isValidRoot(cell)) { - this.view.setCurrentRoot(cell) - this.graph.clearSelection() - } - } - - exitGroup() { - const root = this.model.getRoot() - const current = this.graph.getCurrentRoot() - if (current != null) { - let next = this.model.getParent(current) - - // Finds the next valid root in the hierarchy - while ( - next !== root && - !this.graph.isValidRoot(next) && - this.model.getParent(next) !== root - ) { - next = this.model.getParent(next)! - } - - // Clears the current root if the new root is - // the model's root or one of the layers. - if (next === root || this.model.getParent(next) === root) { - this.view.setCurrentRoot(null) - } else { - this.view.setCurrentRoot(next) - } - - // Selects the previous root in the graph - const state = this.view.getState(current) - if (state != null) { - this.graph.setSelectedCell(current) - } - } - } - - home() { - const current = this.graph.getCurrentRoot() - if (current != null) { - this.view.setCurrentRoot(null) - const state = this.view.getState(current) - if (state != null) { - this.graph.setSelectedCell(current) - } - } - } - - //#endregion - - // #region :::::::::::: Retrieval - - getDefaultParent(): Cell { - let parent = this.graph.getCurrentRoot() - if (parent == null) { - parent = this.graph.defaultParent - if (parent == null) { - const root = this.model.getRoot() - parent = this.model.getChildAt(root, 0) - } - } - - return parent! - } - - getSwimlane(cell: Cell | null) { - let result = cell - while (result != null && !this.graph.isSwimlane(result)) { - result = this.model.getParent(result) - } - return result - } - - getSwimlaneAt(x: number, y: number, parent: Cell): Cell | null { - const count = this.model.getChildCount(parent) - for (let i = 0; i < count; i += 1) { - const child = this.model.getChildAt(parent, i)! - const result = this.getSwimlaneAt(x, y, child) - - if (result != null) { - return result - } - - if (this.graph.isSwimlane(child)) { - const state = this.view.getState(child) - if (this.isIntersected(state, x, y)) { - return child - } - } - } - - return null - } - - getCellAt( - x: number, - y: number, - parent?: Cell | null, - includeNodes: boolean = true, - includeEdges: boolean = true, - ignoreFn?: (state: State, x?: number, y?: number) => boolean, - ): Cell | null { - if (parent == null) { - // tslint:disable-next-line - parent = this.graph.getCurrentRoot() || this.model.getRoot() - } - - if (parent != null) { - const c = this.model.getChildCount(parent) - for (let i = c - 1; i >= 0; i -= 1) { - const cell = this.model.getChildAt(parent, i)! - const result = this.getCellAt( - x, - y, - cell, - includeNodes, - includeEdges, - ignoreFn, - ) - - if (result != null) { - return result - } - - if ( - this.graph.isCellVisible(cell) && - ((includeEdges && this.model.isEdge(cell)) || - (includeNodes && this.model.isNode(cell))) - ) { - const state = this.view.getState(cell) - if ( - state != null && - (ignoreFn == null || !ignoreFn(state, x, y)) && - this.isIntersected(state, x, y) - ) { - return cell - } - } - } - } - - return null - } - - protected isIntersected(state: State | null, x: number, y: number) { - if (state != null) { - const points = state.absolutePoints - if (points != null) { - const hotspot = this.graph.tolerance * this.graph.tolerance - let pt = points[0]! - for (let i = 1, ii = points.length; i < ii; i += 1) { - const next = points[i]! - const dist = util.ptSegmentDist(pt.x, pt.y, next.x, next.y, x, y) - if (dist <= hotspot) { - return true - } - - pt = next - } - } else { - const rot = util.getRotation(state) - if (rot !== 0) { - const cx = state.bounds.getCenter() - const pt = util.rotatePoint(new Point(x, y), -rot, cx) - if (state.bounds.containsPoint(pt)) { - return true - } - } - - if (state.bounds.containsPoint({ x, y })) { - return true - } - } - } - - return false - } - - /** - * Returns true if the given point is inside the content of the - * given swimlane. - */ - hitsSwimlaneContent(swimlane: Cell | null, x: number, y: number) { - const state = this.view.getState(swimlane) - const size = this.graph.getStartSize(swimlane) - - if (state != null) { - const scale = this.view.scale - const dx = x - state.bounds.x - const dy = y - state.bounds.y - - if (size.width > 0 && dx > 0 && dx > size.width * scale) { - return true - } - - if (size.height > 0 && dy > 0 && dy > size.height * scale) { - return true - } - } - - return false - } - - getEdges( - node: Cell, - parent?: Cell | null, - incoming: boolean = true, - outgoing: boolean = true, - includeLoops: boolean = true, - recurse: boolean = false, - ) { - const result: Cell[] = [] - const edges: Cell[] = [] - const isCollapsed = this.graph.isCellCollapsed(node) - - node.eachChild(child => { - if (isCollapsed || !this.graph.isCellVisible(child)) { - edges.push(...this.model.getEdges(child, incoming, outgoing)) - } - }) - - edges.push(...this.model.getEdges(node, incoming, outgoing)) - - edges.forEach(edge => { - const [source, target] = this.getVisibleTerminals(edge) - - if (source === target) { - if (includeLoops) { - result.push(edge) - } - } else { - if ( - (incoming && - target === node && - (parent == null || - this.isValidAncestor(source!, parent, recurse))) || - (outgoing && - source === node && - (parent == null || this.isValidAncestor(target!, parent, recurse))) - ) { - result.push(edge) - } - } - }) - - return result - } - - protected getVisibleTerminals(edge: Cell) { - const state = this.view.getState(edge) - - const source = - state != null - ? state.getVisibleTerminal(true) - : this.view.getVisibleTerminal(edge, true) - - const target = - state != null - ? state.getVisibleTerminal(false) - : this.view.getVisibleTerminal(edge, false) - - return [source, target] - } - - protected isValidAncestor(cell: Cell, parent: Cell, recurse?: boolean) { - return recurse - ? this.model.isAncestor(parent, cell) - : this.model.getParent(cell) === parent - } - - getOppositeNodes( - edges: Cell[], - terminal: Cell, - includeSources: boolean = true, - includeTargets: boolean = true, - ) { - const result: Cell[] = [] - const map = new WeakMap() - const add = (cell: Cell) => { - if (!map.get(cell)) { - map.set(cell, true) - result.push(cell) - } - } - - edges && - edges.forEach(edge => { - const [source, target] = this.getVisibleTerminals(edge) - - if ( - source === terminal && - target != null && - target !== terminal && - includeTargets - ) { - add(target) - } else if ( - target === terminal && - source != null && - source !== terminal && - includeSources - ) { - add(source) - } - }) - - return result - } - - getEdgesBetween(source: Cell, target: Cell, directed: boolean = false) { - const edges = this.getEdges(source) - const result: Cell[] = [] - - edges && - edges.forEach(edge => { - const [s, t] = this.getVisibleTerminals(edge) - if ( - (s === source && t === target) || - (!directed && s === target && t === source) - ) { - result.push(edge) - } - }) - - return result - } - - getCellsInRegion( - x: number, - y: number, - width: number, - height: number, - parent: Cell, - result: Cell[] = [], - ) { - if (width > 0 || height > 0) { - const rect = new Rectangle(x, y, width, height) - - parent && - parent.eachChild(cell => { - const state = this.view.getState(cell) - if (state != null && this.graph.isCellVisible(cell)) { - const rot = util.getRotation(state) - const bounds = - rot === 0 ? state.bounds : util.rotateRectangle(state.bounds, rot) - - if ( - (this.model.isEdge(cell) || this.model.isNode(cell)) && - rect.containsRect(bounds) - ) { - result.push(cell) - } else { - this.getCellsInRegion(x, y, width, height, cell, result) - } - } - }) - } - - return result - } - - getCellsBeyond( - x: number, - y: number, - parent: Cell, - isRight: boolean = false, - isBottom: boolean = false, - ) { - const result: Cell[] = [] - - if (isRight || isBottom) { - parent && - parent.eachChild(child => { - const state = this.view.getState(child) - if (this.graph.isCellVisible(child) && state != null) { - if ( - (!isRight || state.bounds.x >= x) && - (!isBottom || state.bounds.y >= y) - ) { - result.push(child) - } - } - }) - } - - return result - } - - findTreeRoots( - parent: Cell | null, - isolate: boolean = false, - invert: boolean = false, - ) { - const roots: Cell[] = [] - - let best = null - let maxDiff = 0 - - parent && - parent.eachChild(cell => { - if (this.model.isNode(cell) && this.graph.isCellVisible(cell)) { - const conns = this.graph.getConnections(cell, isolate ? parent : null) - let fanOut = 0 - let fanIn = 0 - - conns.forEach(conn => { - const src = this.view.getVisibleTerminal(conn, true) - if (src === cell) { - fanOut += 1 - } else { - fanIn += 1 - } - }) - - if ( - (invert && fanOut === 0 && fanIn > 0) || - (!invert && fanIn === 0 && fanOut > 0) - ) { - roots.push(cell) - } - - const diff = invert ? fanIn - fanOut : fanOut - fanIn - - if (diff > maxDiff) { - maxDiff = diff - best = cell - } - } - }) - - if (roots.length === 0 && best != null) { - roots.push(best) - } - - return roots - } - - traverse( - node: Cell, - directed: boolean = true, - func: (node: Cell, edge: Cell | null) => any, - edge?: Cell, - visited: WeakMap = new WeakMap(), - inverse: boolean = false, - ) { - if (func != null && node != null) { - if (!visited.get(node)) { - visited.set(node, true) - - const result = func(node, edge || null) - if (result !== false) { - node.eachEdge(edge => { - const isSource = this.model.getTerminal(edge, true) === node - if (!directed || !inverse === isSource) { - const next = this.model.getTerminal(edge, !isSource)! - this.traverse(next, directed, func, edge, visited, inverse) - } - }) - } - } - } - } - - // #endregion - - // #region :::::::::::: State - - /** - * Returns the string or DOM node that represents the tooltip for - * the given state, node and coordinate pair. - * - * @param state The cell whose tooltip should be returned. - * @param trigger DOM node that is currently under the mouse. - * @param x X-coordinate of the mouse. - * @param y Y-coordinate of the mouse. - */ - getTooltip(state: State | null, trigger: HTMLElement, x: number, y: number) { - let tip: string | null = null - if (state != null) { - // Checks if the mouse is over the folding icon - if ( - state.control != null && - (trigger === state.control.elem || - trigger.parentNode === state.control.elem) - ) { - tip = 'Collapse/Expand' - } - - if (tip == null && state.overlays != null) { - state.overlays.each(shape => { - if ( - tip == null && - (trigger === shape.elem || trigger.parentNode === shape.elem) - ) { - tip = shape.overlay!.toString() - } - }) - } - - if (tip == null) { - const handler = this.graph.selectionHandler.getHandler(state.cell) - const getTooltipForNode = handler && (handler as any).getTooltipForNode - if (getTooltipForNode && typeof getTooltipForNode === 'function') { - tip = getTooltipForNode(trigger) - } - } - - if (tip == null) { - tip = this.graph.getTooltip(state.cell) - } - } - - return tip - } - - /** - * Returns the image URL for the given cell state. - */ - getImage(state: State): string | null { - return (state != null && state.style.image) || null - } - - /** - * Returns the vertical alignment for the given cell state. - */ - getVerticalAlign(state: State): VAlign { - return (state && state.style.verticalAlign) || 'middle' - } - - getAlign(state: State): Align { - return (state && state.style.align) || 'center' - } - - /** - * Returns the indicator color for the given cell state. - */ - getIndicatorColor(state: State) { - return (state && state.style.indicatorColor) || null - } - - getIndicatorDirection(state: State) { - return (state && state.style.indicatorDirection) || null - } - - getIndicatorStrokeColor(state: State) { - return (state && state.style.indicatorStrokeColor) || null - } - - /** - * Returns the indicator gradient color for the given cell state. - */ - getIndicatorGradientColor(state: State) { - return (state && state.style.indicatorGradientColor) || null - } - - /** - * Returns the indicator shape for the given cell state. - */ - getIndicatorShape(state: State) { - return (state && state.style.indicatorShape) || null - } - - /** - * Returns the indicator image for the given cell state. - */ - getIndicatorImage(state: State) { - return (state && state.style.indicatorImage) || null - } - - // #endregion -} diff --git a/packages/x6/src/manager/index.ts b/packages/x6/src/manager/index.ts deleted file mode 100644 index f7bae6ac82c..00000000000 --- a/packages/x6/src/manager/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -export * from './base' -export * from './change-manager' -export * from './eventloop' -export * from './selection' -export * from './selection-manager' -export * from './panning-manager' -export * from './validation-manager' -export * from './viewport-manager' -export * from './cell-manager' diff --git a/packages/x6/src/manager/overlay-manager.ts b/packages/x6/src/manager/overlay-manager.ts deleted file mode 100644 index 386f3d5f9fc..00000000000 --- a/packages/x6/src/manager/overlay-manager.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { Graph } from '../graph' -import { ManagerBase } from './base' - -export class OverlayManager extends ManagerBase { - constructor(graph: Graph) { - super(graph) - } -} diff --git a/packages/x6/src/manager/viewport-manager.ts b/packages/x6/src/manager/viewport-manager.ts deleted file mode 100644 index 1642d1cc543..00000000000 --- a/packages/x6/src/manager/viewport-manager.ts +++ /dev/null @@ -1,1061 +0,0 @@ -import * as util from '../util' -import { Size } from '../types' -import { Cell } from '../core/cell' -import { Graph } from '../graph' -import { detector } from '../common' -import { Polyline } from '../shape' -import { Rectangle, Point } from '../struct' -import { ManagerBase } from './base' - -export class ViewportManager extends ManagerBase { - constructor(graph: Graph) { - super(graph) - } - - get container() { - return this.graph.container - } - - getCellBounds( - cell: Cell, - includeEdges: boolean, - includeDescendants: boolean, - ) { - const cells = [cell] - - // Includes all connected edges - if (includeEdges) { - cells.push(...this.model.getEdges(cell)) - } - - let result = this.view.getBounds(cells) - - // Recursively includes the bounds of the children - if (includeDescendants) { - cell.eachChild(child => { - const tmp = this.getCellBounds(child, includeEdges, true) - if (tmp != null) { - if (result != null) { - result.add(tmp) - } else { - result = tmp - } - } - }) - } - - return result - } - - getBoundingBoxFromGeometry(cells: Cell[], includeEdges: boolean) { - let result: Rectangle | null = null - - cells && - cells.forEach(cell => { - if (includeEdges || this.model.isNode(cell)) { - // Computes the bounding box for the points in the geometry - const geo = this.graph.getCellGeometry(cell) - if (geo != null) { - let bbox = null - - if (this.model.isEdge(cell)) { - const pts = geo.points - let tmp = new Rectangle(pts[0].x, pts[0].y, 0, 0) - - const addPoint = (pt: Point | null) => { - if (pt != null) { - const rect = new Rectangle(pt.x, pt.y, 0, 0) - if (tmp == null) { - tmp = rect - } else { - tmp.add(rect) - } - } - } - - if (this.model.getTerminal(cell, true) == null) { - addPoint(geo.getTerminalPoint(true)) - } - - if (this.model.getTerminal(cell, false) == null) { - addPoint(geo.getTerminalPoint(false)) - } - - if (pts != null && pts.length > 0) { - for (let j = 1; j < pts.length; j += 1) { - addPoint(pts[j]) - } - } - - bbox = tmp - } else { - const parent = this.model.getParent(cell)! - - if (geo.relative) { - if ( - this.model.isNode(parent) && - parent !== this.view.currentRoot - ) { - const tmp = this.getBoundingBoxFromGeometry([parent], false) - - if (tmp != null) { - bbox = new Rectangle( - geo.bounds.x * tmp.width, - geo.bounds.y * tmp.height, - geo.bounds.width, - geo.bounds.height, - ) - - if (util.indexOf(cells, parent) >= 0) { - bbox.x += tmp.x - bbox.y += tmp.y - } - } - } - } else { - bbox = Rectangle.clone(geo.bounds) - - if ( - this.model.isNode(parent) && - util.indexOf(cells, parent) >= 0 - ) { - const tmp = this.getBoundingBoxFromGeometry([parent], false) - - if (tmp != null) { - bbox.x += tmp.x - bbox.y += tmp.y - } - } - } - - if (bbox != null && geo.offset != null) { - bbox.x += geo.offset.x - bbox.y += geo.offset.y - } - } - - if (bbox != null) { - if (result == null) { - result = bbox.clone() - } else { - result.add(bbox) - } - } - } - } - }) - - return result != null ? result! : null - } - - protected shiftPreview1: HTMLElement | null - protected shiftPreview2: HTMLElement | null - - /** - * Shifts the graph display by the given amount. This is used to preview - * panning operations, use `view.setTranslate` to set a persistent - * translation of the view. - * - * @param dx Amount to shift the graph along the x-axis. - * @param dy Amount to shift the graph along the y-axis. - */ - pan(dx: number, dy: number) { - if ( - this.graph.useScrollbarsForPanning && - util.hasScrollbars(this.container) - ) { - const container = this.container - const maxScrollLeft = container.scrollWidth - container.clientWidth - const maxScrollTop = container.scrollHeight - container.clientHeight - const scrollLeft = util.clamp(dx, 0, maxScrollLeft) - const scrollTop = util.clamp(dy, 0, maxScrollTop) - container.scrollLeft = scrollLeft - container.scrollTop = scrollTop - } else { - const stage = this.view.getStage()! - if (this.graph.dialect === 'svg') { - // Puts everything inside the container in a DIV so that it - // can be moved without changing the state of the container - if (dx === 0 && dy === 0) { - stage.removeAttribute('transform') - - if (this.shiftPreview1 != null && this.shiftPreview2 != null) { - let child = this.shiftPreview1.firstChild - while (child != null) { - const next = child.nextSibling - this.container.appendChild(child) - child = next - } - - util.removeElement(this.shiftPreview1) - this.shiftPreview1 = null - - this.container.appendChild(stage.parentNode!) - - child = this.shiftPreview2.firstChild - while (child != null) { - const next = child.nextSibling - this.container.appendChild(child) - child = next - } - - util.removeElement(this.shiftPreview2) - this.shiftPreview2 = null - } - } else { - stage.setAttribute('transform', `translate(${dx},${dy})`) - - if (this.shiftPreview1 == null) { - // Needs two divs for stuff before and after the SVG element - this.shiftPreview1 = document.createElement('div') - this.shiftPreview1.style.position = 'absolute' - this.shiftPreview1.style.overflow = 'visible' - - this.shiftPreview2 = document.createElement('div') - this.shiftPreview2.style.position = 'absolute' - this.shiftPreview2.style.overflow = 'visible' - - let current = this.shiftPreview1 - let child = this.container.firstChild as HTMLElement - - while (child != null) { - const next = child.nextSibling as HTMLElement - // SVG element is moved via transform attribute - if (child !== stage.parentNode) { - current.appendChild(child) - } else { - current = this.shiftPreview2 - } - - child = next - } - - // Inserts elements only if not empty - if (this.shiftPreview1.firstChild != null) { - this.container.insertBefore(this.shiftPreview1, stage.parentNode) - } - - if (this.shiftPreview2.firstChild != null) { - this.container.appendChild(this.shiftPreview2) - } - } - - this.shiftPreview1.style.left = `${dx}px` - this.shiftPreview1.style.top = `${dy}px` - this.shiftPreview2!.style.left = util.toPx(dx) - this.shiftPreview2!.style.top = util.toPx(dy) - } - } else { - stage.style.left = util.toPx(dx) - stage.style.top = util.toPx(dy) - } - - this.graph.panDx = dx - this.graph.panDy = dy - } - - this.graph.trigger(Graph.events.pan, { dx, dy }) - } - - center(horizontal: boolean, vertical: boolean, cx: number, cy: number) { - const hasScrollbars = util.hasScrollbars(this.container) - const bounds = this.graph.getGraphBounds() - const cw = this.container.clientWidth - const ch = this.container.clientHeight - - const t = this.view.translate - const s = this.view.scale - - let dx = horizontal ? cw - bounds.width : 0 - let dy = vertical ? ch - bounds.height : 0 - - if (!hasScrollbars) { - const tx = horizontal - ? Math.floor(t.x - bounds.x * s + (dx * cx) / s) - : t.x - const ty = vertical ? Math.floor(t.y - bounds.y * s + (dy * cy) / s) : t.y - this.view.setTranslate(tx, ty) - } else { - const sw = this.container.scrollWidth - const sh = this.container.scrollHeight - - if (sw > cw) { - dx = 0 - } - - if (sh > ch) { - dy = 0 - } - - this.view.setTranslate( - Math.floor(dx / 2 - (bounds.x - t.x)), - Math.floor(dy / 2 - (bounds.y - t.y)), - ) - - this.container.scrollLeft = (sw - cw) / 2 - this.container.scrollTop = (sh - ch) / 2 - } - } - - zoom(factor: number, center: boolean) { - let scale = Math.round(this.view.scale * factor * 100) / 100 - scale = util.clamp(scale, this.graph.minScale, this.graph.maxScale) - factor = scale / this.view.scale // tslint:disable-line - - const state = this.view.getState(this.graph.getSelectedCell()) - if (this.graph.keepSelectionVisibleOnZoom && state != null) { - const rect = new Rectangle( - state.bounds.x * factor, - state.bounds.y * factor, - state.bounds.width * factor, - state.bounds.height * factor, - ) - - // Refreshes the display only once if a scroll is carried out - this.view.scale = scale - if (!this.scrollRectToVisible(rect)) { - this.view.revalidate() - // Forces an event to be fired but does not revalidate again - this.view.setScale(scale) - } - } else { - const hasScrollbars = util.hasScrollbars(this.container) - if (center && !hasScrollbars) { - let dx = this.container.offsetWidth - let dy = this.container.offsetHeight - - if (factor > 1) { - const f = (factor - 1) / (scale * 2) - dx *= -f - dy *= -f - } else { - const f = (1 / factor - 1) / (this.view.scale * 2) - dx *= f - dy *= f - } - - this.view.scaleAndTranslate( - scale, - this.view.translate.x + dx, - this.view.translate.y + dy, - ) - } else { - // Allows for changes of translate and scrollbars during setscale - const tx = this.view.translate.x - const ty = this.view.translate.y - const sl = this.container.scrollLeft - const st = this.container.scrollTop - - this.view.setScale(scale) - - if (hasScrollbars) { - let dx = 0 - let dy = 0 - - if (center) { - dx = (this.container.offsetWidth * (factor - 1)) / 2 - dy = (this.container.offsetHeight * (factor - 1)) / 2 - } - - const t = this.view.translate - const s = this.view.scale - this.container.scrollLeft = - (t.x - tx) * s + Math.round(sl * factor + dx) - this.container.scrollTop = - (t.y - ty) * s + Math.round(st * factor + dy) - } - } - } - } - - zoomToRect(rect: Rectangle) { - const container = this.container - const scaleX = container.clientWidth / rect.width - const scaleY = container.clientHeight / rect.height - const aspectFactor = scaleX / scaleY - - // Remove any overlap of the rect outside the client area - rect.x = Math.max(0, rect.x) - rect.y = Math.max(0, rect.y) - let rectRight = Math.min(container.scrollWidth, rect.x + rect.width) - let rectBottom = Math.min(container.scrollHeight, rect.y + rect.height) - rect.width = rectRight - rect.x - rect.height = rectBottom - rect.y - - // The selection area has to be increased to the same aspect - // ratio as the container, centred around the centre point of the - // original rect passed in. - if (aspectFactor < 1.0) { - // Height needs increasing - const newHeight = rect.height / aspectFactor - const deltaHeightBuffer = (newHeight - rect.height) / 2.0 - rect.height = newHeight - - // Assign up to half the buffer to the upper part of the rect, - // not crossing 0 put the rest on the bottom - const upperBuffer = Math.min(rect.y, deltaHeightBuffer) - rect.y = rect.y - upperBuffer - - // Check if the bottom has extended too far - rectBottom = Math.min(container.scrollHeight, rect.y + rect.height) - rect.height = rectBottom - rect.y - } else { - // Width needs increasing - const newWidth = rect.width * aspectFactor - const deltaWidthBuffer = (newWidth - rect.width) / 2.0 - rect.width = newWidth - - // Assign up to half the buffer to the upper part of the rect, - // not crossing 0 put the rest on the bottom - const leftBuffer = Math.min(rect.x, deltaWidthBuffer) - rect.x = rect.x - leftBuffer - - // Check if the right hand side has extended too far - rectRight = Math.min(container.scrollWidth, rect.x + rect.width) - rect.width = rectRight - rect.x - } - - const scale = container.clientWidth / rect.width - const newScale = this.view.scale * scale - - if (!util.hasScrollbars(container)) { - this.view.scaleAndTranslate( - newScale, - this.view.translate.x - rect.x / this.view.scale, - this.view.translate.y - rect.y / this.view.scale, - ) - } else { - this.view.setScale(newScale) - container.scrollLeft = Math.round(rect.x * scale) - container.scrollTop = Math.round(rect.y * scale) - } - } - - scrollCellToVisible(cell: Cell, center: boolean = false) { - const x = -this.view.translate.x - const y = -this.view.translate.y - - const state = this.view.getState(cell) - if (state != null) { - const bounds = new Rectangle( - x + state.bounds.x, - y + state.bounds.y, - state.bounds.width, - state.bounds.height, - ) - - if (center && this.container != null) { - const w = this.container.clientWidth - const h = this.container.clientHeight - - bounds.x = bounds.getCenterX() - w / 2 - bounds.width = w - bounds.y = bounds.getCenterY() - h / 2 - bounds.height = h - } - - const tr = new Point(this.view.translate.x, this.view.translate.y) - - if (this.scrollRectToVisible(bounds)) { - // Triggers an update via the view's event source - const tr2 = new Point(this.view.translate.x, this.view.translate.y) - this.view.translate.x = tr.x - this.view.translate.y = tr.y - this.view.setTranslate(tr2.x, tr2.y) - } - } - } - - scrollRectToVisible(rect: Rectangle) { - let isChanged = false - - if (rect != null) { - const w = this.container.offsetWidth - const h = this.container.offsetHeight - - const widthLimit = Math.min(w, rect.width) - const heightLimit = Math.min(h, rect.height) - - if (util.hasScrollbars(this.container)) { - const c = this.container - rect.x += this.view.translate.x - rect.y += this.view.translate.y - let dx = c.scrollLeft - rect.x - const ddx = Math.max(dx - c.scrollLeft, 0) - - if (dx > 0) { - c.scrollLeft -= dx + 2 - } else { - dx = rect.x + widthLimit - c.scrollLeft - c.clientWidth - - if (dx > 0) { - c.scrollLeft += dx + 2 - } - } - - let dy = c.scrollTop - rect.y - const ddy = Math.max(0, dy - c.scrollTop) - - if (dy > 0) { - c.scrollTop -= dy + 2 - } else { - dy = rect.y + heightLimit - c.scrollTop - c.clientHeight - - if (dy > 0) { - c.scrollTop += dy + 2 - } - } - - if (!this.graph.useScrollbarsForPanning && (ddx !== 0 || ddy !== 0)) { - this.view.setTranslate(ddx, ddy) - } - } else { - const x = -this.view.translate.x - const y = -this.view.translate.y - - const s = this.view.scale - - if (rect.x + widthLimit > x + w) { - this.view.translate.x -= (rect.x + widthLimit - w - x) / s - isChanged = true - } - - if (rect.y + heightLimit > y + h) { - this.view.translate.y -= (rect.y + heightLimit - h - y) / s - isChanged = true - } - - if (rect.x < x) { - this.view.translate.x += (x - rect.x) / s - isChanged = true - } - - if (rect.y < y) { - this.view.translate.y += (y - rect.y) / s - isChanged = true - } - - if (isChanged) { - this.view.refresh() - // Repaints selection marker (ticket 18) - this.graph.selectionHandler.refresh() - } - } - } - - return isChanged - } - - scrollPointToVisible(x: number, y: number, extend: boolean, border: number) { - if ( - !this.graph.timerAutoScroll && - (this.graph.ignoreScrollbars || util.hasScrollbars(this.container)) - ) { - const c = this.container - - if ( - x >= c.scrollLeft && - y >= c.scrollTop && - x <= c.scrollLeft + c.clientWidth && - y <= c.scrollTop + c.clientHeight - ) { - let dx = c.scrollLeft + c.clientWidth - x - - if (dx < border) { - const old = c.scrollLeft - c.scrollLeft += border - dx - - // Automatically extends the canvas size to the bottom, right - // if the event is outside of the canvas and the edge of the - // canvas has been reached. Notes: Needs fix for IE. - if (extend && old === c.scrollLeft) { - if (this.graph.dialect === 'svg') { - const root = (this.view.getDrawPane() as SVGElement) - .ownerSVGElement! - const width = this.container.scrollWidth + border - dx - - // Updates the clipping region. This is an expensive - // operation that should not be executed too often. - root.style.width = util.toPx(width) - } else { - const width = Math.max(c.clientWidth, c.scrollWidth) + border - dx - const stage = this.view.getStage()! - stage.style.width = util.toPx(width) - } - - c.scrollLeft += border - dx - } - } else { - dx = x - c.scrollLeft - - if (dx < border) { - c.scrollLeft -= border - dx - } - } - - let dy = c.scrollTop + c.clientHeight - y - - if (dy < border) { - const old = c.scrollTop - c.scrollTop += border - dy - - if (old === c.scrollTop && extend) { - if (this.graph.dialect === 'svg') { - const root = (this.view.getDrawPane() as SVGElement) - .ownerSVGElement! - const height = this.container.scrollHeight + border - dy - - // Updates the clipping region. This is an expensive - // operation that should not be executed too often. - root.style.height = util.toPx(height) - } else { - const height = - Math.max(c.clientHeight, c.scrollHeight) + border - dy - const canvas = this.view.getStage()! - canvas.style.height = util.toPx(height) - } - - c.scrollTop += border - dy - } - } else { - dy = y - c.scrollTop - - if (dy < border) { - c.scrollTop -= border - dy - } - } - } - } else if ( - this.graph.allowAutoPanning && - !this.graph.panningHandler.isActive() - ) { - if (this.graph.panningManager == null) { - this.graph.panningManager = this.createPanningManager() - } - - this.graph.panningManager.panTo( - x + this.graph.panDx, - y + this.graph.panDy, - ) - } - } - - createPanningManager() { - // TODO: xx - // return new PanningManager(this) - } - - /** - * Returns the size of the border and padding on all four sides of the - * container. The left, top, right and bottom borders are stored in the x, y, - * width and height of the returned , respectively. - */ - getBorderSizes() { - const css = util.getComputedStyle(this.container) - return new Rectangle( - util.parseCssNumber(css.paddingLeft) + - (css.borderLeftStyle !== 'none' - ? util.parseCssNumber(css.borderLeftWidth) - : 0), - util.parseCssNumber(css.paddingTop) + - (css.borderTopStyle !== 'none' - ? util.parseCssNumber(css.borderTopWidth) - : 0), - util.parseCssNumber(css.paddingRight) + - (css.borderRightStyle !== 'none' - ? util.parseCssNumber(css.borderRightWidth) - : 0), - util.parseCssNumber(css.paddingBottom) + - (css.borderBottomStyle !== 'none' - ? util.parseCssNumber(css.borderBottomWidth) - : 0), - ) - } - - fit( - border: number, - keepOrigin: boolean, - margin: number, - enabled: boolean, - ignoreWidth: boolean, - ignoreHeight: boolean, - maxHeight?: number, - ) { - if (this.container != null) { - // Adds spacing and border from css - const cssBorder = this.getBorderSizes() - let w1 = this.container.offsetWidth - cssBorder.x - cssBorder.width - 1 - let h1 = - maxHeight != null - ? maxHeight - : this.container.offsetHeight - cssBorder.y - cssBorder.height - 1 - let bounds = this.view.getGraphBounds() - - if (bounds.width > 0 && bounds.height > 0) { - if (keepOrigin && bounds.x != null && bounds.y != null) { - bounds = bounds.clone() - bounds.width += bounds.x - bounds.height += bounds.y - bounds.x = 0 - bounds.y = 0 - } - - // LATER: Use unscaled bounding boxes to fix rounding errors - const s = this.view.scale - const w2 = bounds.width / s - const h2 = bounds.height / s - - const b = (keepOrigin ? border : 2 * border) + margin + 1 - - w1 -= b - h1 -= b - - let s2 = ignoreWidth - ? h1 / h2 - : ignoreHeight - ? w1 / w2 - : Math.min(w1 / w2, h1 / h2) - - if (this.graph.minFitScale != null) { - s2 = Math.max(s2, this.graph.minFitScale) - } - - if (this.graph.maxFitScale != null) { - s2 = Math.min(s2, this.graph.maxFitScale) - } - - if (enabled) { - if (!keepOrigin) { - if (!util.hasScrollbars(this.container)) { - const x0 = - bounds.x != null - ? Math.floor( - this.view.translate.x - - bounds.x / s + - border / s2 + - margin / 2, - ) - : border - - const y0 = - bounds.y != null - ? Math.floor( - this.view.translate.y - - bounds.y / s + - border / s2 + - margin / 2, - ) - : border - - this.view.scaleAndTranslate(s2, x0, y0) - } else { - this.view.setScale(s2) - const b2 = this.graph.getGraphBounds() - - if (b2.x != null) { - this.container.scrollLeft = b2.x - } - - if (b2.y != null) { - this.container.scrollTop = b2.y - } - } - } else if (this.view.scale !== s2) { - this.view.setScale(s2) - } - } else { - return s2 - } - } - } - - return this.view.scale - } - - sizeDidChange() { - const bounds = this.graph.getGraphBounds() - - if (this.container != null) { - const scale = this.view.scale - const border = this.graph.getBorder() - - let width = Math.max(0, bounds.x + bounds.width + 2 * border * scale) - let height = Math.max(0, bounds.y + bounds.height + 2 * border * scale) - - if (this.graph.minContainerSize != null) { - width = Math.max(width, this.graph.minContainerSize.width) - height = Math.max(height, this.graph.minContainerSize.height) - } - - if (this.graph.isAutoResizeContainer()) { - this.resizeContainer(width, height) - } - - if ( - this.graph.preferPageSize || - (!detector.IS_IE && this.graph.pageVisible) - ) { - const size = this.graph.getPreferredPageSize( - bounds, - Math.max(1, width), - Math.max(1, height), - ) - - if (size != null) { - width = size.width * scale - height = size.height * scale - } - } - - if (this.graph.minGraphSize != null) { - width = Math.max(width, this.graph.minGraphSize.width * scale) - height = Math.max(height, this.graph.minGraphSize.height * scale) - } - - width = Math.ceil(width) - height = Math.ceil(height) - - if (this.graph.dialect === 'svg') { - const svg = (this.view.getDrawPane() as SVGGElement).ownerSVGElement - if (svg != null) { - svg.style.minWidth = `${Math.max(1, width)}px` - svg.style.minHeight = `${Math.max(1, height)}px` - svg.style.width = '100%' - svg.style.height = '100%' - } - } else { - const stage = this.view.getStage()! - stage.style.minWidth = `${Math.max(1, width)}px` - stage.style.minHeight = `${Math.max(1, height)}px` - } - - this.graph.updatePageBreaks(this.graph.pageBreakEnabled, width, height) - } - - this.graph.trigger(Graph.events.size, bounds) - } - - /** - * Returns the preferred size of the background page. - */ - getPreferredPageSize(bounds: Rectangle, width: number, height: number): Size { - // const scale = this.view.scale - const tr = this.view.translate - const fmt = this.graph.pageFormat - const ps = this.graph.pageScale - const pw = Math.ceil(fmt.width * ps) - const ph = Math.ceil(fmt.height * ps) - const pageBreakEnabled = this.graph.pageBreakEnabled - const hCount = pageBreakEnabled ? Math.ceil(width / pw) : 1 - const vCount = pageBreakEnabled ? Math.ceil(height / ph) : 1 - - return { - width: hCount * pw + 2 + tr.x, - height: vCount * ph + 2 + tr.y, - } - } - - /** - * Resizes the container for the given graph width and height. - */ - protected resizeContainer(width: number, height: number) { - const w = - this.graph.maxContainerSize != null - ? Math.min(this.graph.maxContainerSize.width, width) - : width - - const h = - this.graph.maxContainerSize != null - ? Math.min(this.graph.maxContainerSize.height, height) - : height - - this.container.style.width = util.toPx(Math.ceil(w)) - this.container.style.height = util.toPx(Math.ceil(h)) - } - - verticalPageBreaks: Polyline[] - horizontalPageBreaks: Polyline[] - updatePageBreaks(visible: boolean, width: number, height: number) { - const s = this.view.scale - const t = this.view.translate - const fmt = this.graph.pageFormat - const ps = s * this.graph.pageScale - const bounds = new Rectangle(0, 0, fmt.width * ps, fmt.height * ps) - - const gb = this.graph.getGraphBounds().clone() - gb.width = Math.max(1, gb.width) - gb.height = Math.max(1, gb.height) - - bounds.x = - Math.floor((gb.x - t.x * s) / bounds.width) * bounds.width + t.x * s - bounds.y = - Math.floor((gb.y - t.y * s) / bounds.height) * bounds.height + t.y * s - - gb.width = - Math.ceil((gb.width + (gb.x - bounds.x)) / bounds.width) * bounds.width - gb.height = - Math.ceil((gb.height + (gb.y - bounds.y)) / bounds.height) * bounds.height - - // Does not show page breaks if the scale is too small - // tslint:disable-next-line - visible = - visible && - Math.min(bounds.width, bounds.height) > this.graph.pageBreakMinDist - - const hCount = visible ? Math.ceil(gb.height / bounds.height) + 1 : 0 - const vCount = visible ? Math.ceil(gb.width / bounds.width) + 1 : 0 - const right = (vCount - 1) * bounds.width - const bottom = (hCount - 1) * bounds.height - - if (this.horizontalPageBreaks == null && hCount > 0) { - this.horizontalPageBreaks = [] - } - - if (this.verticalPageBreaks == null && vCount > 0) { - this.verticalPageBreaks = [] - } - - const drawPageBreaks = (breaks: Polyline[]) => { - if (breaks != null) { - const count = breaks === this.horizontalPageBreaks ? hCount : vCount - - for (let i = 0; i <= count; i += 1) { - const pts = - breaks === this.horizontalPageBreaks - ? [ - new Point( - Math.round(bounds.x), - Math.round(bounds.y + i * bounds.height), - ), - new Point( - Math.round(bounds.x + right), - Math.round(bounds.y + i * bounds.height), - ), - ] - : [ - new Point( - Math.round(bounds.x + i * bounds.width), - Math.round(bounds.y), - ), - new Point( - Math.round(bounds.x + i * bounds.width), - Math.round(bounds.y + bottom), - ), - ] - - if (breaks[i] != null) { - breaks[i].points = pts - breaks[i].redraw() - } else { - const pageBreak = new Polyline(pts, this.graph.pageBreakColor) - pageBreak.dialect = this.graph.dialect - pageBreak.dashed = this.graph.pageBreakDashed - pageBreak.pointerEvents = false - pageBreak.init(this.view.getBackgroundPane()) - pageBreak.redraw() - - breaks[i] = pageBreak - } - } - - for (let i = count; i < breaks.length; i += 1) { - breaks[i].dispose() - } - - breaks.splice(count, breaks.length - count) - } - } - - drawPageBreaks(this.horizontalPageBreaks) - drawPageBreaks(this.verticalPageBreaks) - } - - uu(visible: boolean, width: number, height: number) { - const s = this.view.scale - const t = this.view.translate - const fmt = this.graph.pageFormat - const ps = s * this.graph.pageScale - const bounds2 = this.view.getBackgroundPageBounds() - - width = bounds2.width // tslint:disable-line - height = bounds2.height // tslint:disable-line - const bounds = new Rectangle( - s * t.x, - s * t.y, - fmt.width * ps, - fmt.height * ps, - ) - - // tslint:disable-next-line - visible = - visible && - Math.min(bounds.width, bounds.height) > this.graph.pageBreakMinDist - - const hCount = visible ? Math.ceil(height / bounds.height) - 1 : 0 - const vCount = visible ? Math.ceil(width / bounds.width) - 1 : 0 - const right = bounds2.x + width - const bottom = bounds2.y + height - - if (this.horizontalPageBreaks == null && hCount > 0) { - this.horizontalPageBreaks = [] - } - - if (this.verticalPageBreaks == null && vCount > 0) { - this.verticalPageBreaks = [] - } - - const drawPageBreaks = (breaks: Polyline[]) => { - if (breaks != null) { - const count = breaks === this.horizontalPageBreaks ? hCount : vCount - - for (let i = 0; i <= count; i += 1) { - const pts = - breaks === this.horizontalPageBreaks - ? [ - new Point( - Math.round(bounds2.x), - Math.round(bounds2.y + (i + 1) * bounds.height), - ), - new Point( - Math.round(right), - Math.round(bounds2.y + (i + 1) * bounds.height), - ), - ] - : [ - new Point( - Math.round(bounds2.x + (i + 1) * bounds.width), - Math.round(bounds2.y), - ), - new Point( - Math.round(bounds2.x + (i + 1) * bounds.width), - Math.round(bottom), - ), - ] - - if (breaks[i] != null) { - breaks[i].points = pts - breaks[i].redraw() - } else { - const pageBreak = new Polyline(pts, this.graph.pageBreakColor) - pageBreak.dialect = this.graph.dialect - pageBreak.dashed = this.graph.pageBreakDashed - pageBreak.pointerEvents = false - pageBreak.init(this.view.getBackgroundPane()) - pageBreak.redraw() - - breaks[i] = pageBreak - } - } - - for (let i = count; i < breaks.length; i += 1) { - breaks[i].dispose() - } - - breaks.splice(count, breaks.length - count) - } - } - - drawPageBreaks(this.horizontalPageBreaks) - drawPageBreaks(this.verticalPageBreaks) - } -} diff --git a/packages/x6/src/option/preset.ts b/packages/x6/src/option/preset.ts index b2e10a1a9e0..e05d117f1f9 100644 --- a/packages/x6/src/option/preset.ts +++ b/packages/x6/src/option/preset.ts @@ -17,6 +17,7 @@ export const preset: FullOptions = { ...globals, prefixCls: 'x6', dialect: 'svg', + infinite: false, antialiased: true, border: 0, backgroundColor: '#ffffff', diff --git a/packages/x6/src/option/rollup.ts b/packages/x6/src/option/rollup.ts index 8f21b5e6cce..e4945775d63 100644 --- a/packages/x6/src/option/rollup.ts +++ b/packages/x6/src/option/rollup.ts @@ -1,8 +1,8 @@ import * as util from '../util' import { Style } from '../types' -import { GridOptions } from '../graph/prop/grid' -import { PageBreakOptions } from '../graph/prop/pagebreak' -import { FoldingOptions } from '../graph/prop/folding' +import { GridOptions } from '../graph/grid-accessor' +import { PageBreakOptions } from '../graph/pagebreak-accessor' +import { FoldingOptions } from '../graph/collapse-accessor' import { TooltipOptions } from '../handler/tooltip/option' import { ContextMenuOptions } from '../handler/contextmenu/option' import { KeyboardOptions } from '../handler/keyboard/option' @@ -35,7 +35,7 @@ import { import { preset } from './preset' import { IHooks } from '../graph/hook' import { GlobalConfig } from './global' -import { GraphProperties } from '../graph/prop/base' +import { GraphProperties } from '../graph/base-graph' export interface FullOptions extends GlobalConfig, diff --git a/packages/x6/src/util/function.ts b/packages/x6/src/util/function.ts index fd77cd79885..bbc9699ebb7 100644 --- a/packages/x6/src/util/function.ts +++ b/packages/x6/src/util/function.ts @@ -1,6 +1,6 @@ import { isFunction } from './lang' -export function applyMixins(derivedCtor: any, baseCtors: any[]) { +export function applyMixins(derivedCtor: any, ...baseCtors: any[]) { baseCtors.forEach(baseCtor => { Object.getOwnPropertyNames(baseCtor.prototype).forEach(name => { Object.defineProperty( diff --git a/yarn.lock b/yarn.lock index 6826f799fbd..88611398599 100644 --- a/yarn.lock +++ b/yarn.lock @@ -50,13 +50,6 @@ dependencies: "@babel/highlight" "^7.0.0" -"@babel/code-frame@7.0.0-beta.44": - version "7.0.0-beta.44" - resolved "https://registry.npm.taobao.org/@babel/code-frame/download/@babel/code-frame-7.0.0-beta.44.tgz#2a02643368de80916162be70865c97774f3adbd9" - integrity sha1-KgJkM2jegJFhYr5whlyXd08629k= - dependencies: - "@babel/highlight" "7.0.0-beta.44" - "@babel/code-frame@^7.0.0", "@babel/code-frame@^7.5.5": version "7.5.5" resolved "https://registry.npm.taobao.org/@babel/code-frame/download/@babel/code-frame-7.5.5.tgz#bc0782f6d69f7b7d49531219699b988f669a8f9d" @@ -66,7 +59,7 @@ "@babel/core@7.4.5": version "7.4.5" - resolved "https://registry.npm.taobao.org/@babel/core/download/@babel/core-7.4.5.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fcore%2Fdownload%2F%40babel%2Fcore-7.4.5.tgz#081f97e8ffca65a9b4b0fdc7e274e703f000c06a" + resolved "https://registry.npm.taobao.org/@babel/core/download/@babel/core-7.4.5.tgz#081f97e8ffca65a9b4b0fdc7e274e703f000c06a" integrity sha1-CB+X6P/KZam0sP3H4nTnA/AAwGo= dependencies: "@babel/code-frame" "^7.0.0" @@ -85,14 +78,14 @@ source-map "^0.5.0" "@babel/core@>=7.2.2", "@babel/core@^7.1.0", "@babel/core@^7.4.5", "@babel/core@^7.5.4": - version "7.7.4" - resolved "https://registry.npm.taobao.org/@babel/core/download/@babel/core-7.7.4.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fcore%2Fdownload%2F%40babel%2Fcore-7.7.4.tgz#37e864532200cb6b50ee9a4045f5f817840166ab" - integrity sha1-N+hkUyIAy2tQ7ppARfX4F4QBZqs= + version "7.7.5" + resolved "https://registry.npm.taobao.org/@babel/core/download/@babel/core-7.7.5.tgz#ae1323cd035b5160293307f50647e83f8ba62f7e" + integrity sha1-rhMjzQNbUWApMwf1BkfoP4umL34= dependencies: "@babel/code-frame" "^7.5.5" "@babel/generator" "^7.7.4" "@babel/helpers" "^7.7.4" - "@babel/parser" "^7.7.4" + "@babel/parser" "^7.7.5" "@babel/template" "^7.7.4" "@babel/traverse" "^7.7.4" "@babel/types" "^7.7.4" @@ -104,17 +97,6 @@ semver "^5.4.1" source-map "^0.5.0" -"@babel/generator@7.0.0-beta.44": - version "7.0.0-beta.44" - resolved "https://registry.npm.taobao.org/@babel/generator/download/@babel/generator-7.0.0-beta.44.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fgenerator%2Fdownload%2F%40babel%2Fgenerator-7.0.0-beta.44.tgz#c7e67b9b5284afcf69b309b50d7d37f3e5033d42" - integrity sha1-x+Z7m1KEr89pswm1DX038+UDPUI= - dependencies: - "@babel/types" "7.0.0-beta.44" - jsesc "^2.5.1" - lodash "^4.2.0" - source-map "^0.5.0" - trim-right "^1.0.1" - "@babel/generator@7.4.4": version "7.4.4" resolved "https://registry.npm.taobao.org/@babel/generator/download/@babel/generator-7.4.4.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fgenerator%2Fdownload%2F%40babel%2Fgenerator-7.4.4.tgz#174a215eb843fc392c7edcaabeaa873de6e8f041" @@ -205,15 +187,6 @@ "@babel/traverse" "^7.7.4" "@babel/types" "^7.7.4" -"@babel/helper-function-name@7.0.0-beta.44": - version "7.0.0-beta.44" - resolved "https://registry.npm.taobao.org/@babel/helper-function-name/download/@babel/helper-function-name-7.0.0-beta.44.tgz?cache=0&sync_timestamp=1574465949765&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fhelper-function-name%2Fdownload%2F%40babel%2Fhelper-function-name-7.0.0-beta.44.tgz#e18552aaae2231100a6e485e03854bc3532d44dd" - integrity sha1-4YVSqq4iMRAKbkheA4VLw1MtRN0= - dependencies: - "@babel/helper-get-function-arity" "7.0.0-beta.44" - "@babel/template" "7.0.0-beta.44" - "@babel/types" "7.0.0-beta.44" - "@babel/helper-function-name@^7.1.0", "@babel/helper-function-name@^7.7.4": version "7.7.4" resolved "https://registry.npm.taobao.org/@babel/helper-function-name/download/@babel/helper-function-name-7.7.4.tgz?cache=0&sync_timestamp=1574465949765&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fhelper-function-name%2Fdownload%2F%40babel%2Fhelper-function-name-7.7.4.tgz#ab6e041e7135d436d8f0a3eca15de5b67a341a2e" @@ -223,13 +196,6 @@ "@babel/template" "^7.7.4" "@babel/types" "^7.7.4" -"@babel/helper-get-function-arity@7.0.0-beta.44": - version "7.0.0-beta.44" - resolved "https://registry.npm.taobao.org/@babel/helper-get-function-arity/download/@babel/helper-get-function-arity-7.0.0-beta.44.tgz#d03ca6dd2b9f7b0b1e6b32c56c72836140db3a15" - integrity sha1-0Dym3SufewseazLFbHKDYUDbOhU= - dependencies: - "@babel/types" "7.0.0-beta.44" - "@babel/helper-get-function-arity@^7.7.4": version "7.7.4" resolved "https://registry.npm.taobao.org/@babel/helper-get-function-arity/download/@babel/helper-get-function-arity-7.7.4.tgz#cb46348d2f8808e632f0ab048172130e636005f0" @@ -258,10 +224,10 @@ dependencies: "@babel/types" "^7.7.4" -"@babel/helper-module-transforms@^7.7.4": - version "7.7.4" - resolved "https://registry.npm.taobao.org/@babel/helper-module-transforms/download/@babel/helper-module-transforms-7.7.4.tgz#8d7cdb1e1f8ea3d8c38b067345924ac4f8e0879a" - integrity sha1-jXzbHh+Oo9jDiwZzRZJKxPjgh5o= +"@babel/helper-module-transforms@^7.7.4", "@babel/helper-module-transforms@^7.7.5": + version "7.7.5" + resolved "https://registry.npm.taobao.org/@babel/helper-module-transforms/download/@babel/helper-module-transforms-7.7.5.tgz?cache=0&sync_timestamp=1575638289501&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fhelper-module-transforms%2Fdownload%2F%40babel%2Fhelper-module-transforms-7.7.5.tgz#d044da7ffd91ec967db25cd6748f704b6b244835" + integrity sha1-0ETaf/2R7JZ9slzWdI9wS2skSDU= dependencies: "@babel/helper-module-imports" "^7.7.4" "@babel/helper-simple-access" "^7.7.4" @@ -318,13 +284,6 @@ "@babel/template" "^7.7.4" "@babel/types" "^7.7.4" -"@babel/helper-split-export-declaration@7.0.0-beta.44": - version "7.0.0-beta.44" - resolved "https://registry.npm.taobao.org/@babel/helper-split-export-declaration/download/@babel/helper-split-export-declaration-7.0.0-beta.44.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fhelper-split-export-declaration%2Fdownload%2F%40babel%2Fhelper-split-export-declaration-7.0.0-beta.44.tgz#c0b351735e0fbcb3822c8ad8db4e583b05ebd9dc" - integrity sha1-wLNRc14PvLOCLIrY205YOwXr2dw= - dependencies: - "@babel/types" "7.0.0-beta.44" - "@babel/helper-split-export-declaration@^7.4.4", "@babel/helper-split-export-declaration@^7.7.4": version "7.7.4" resolved "https://registry.npm.taobao.org/@babel/helper-split-export-declaration/download/@babel/helper-split-export-declaration-7.7.4.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fhelper-split-export-declaration%2Fdownload%2F%40babel%2Fhelper-split-export-declaration-7.7.4.tgz#57292af60443c4a3622cf74040ddc28e68336fd8" @@ -351,15 +310,6 @@ "@babel/traverse" "^7.7.4" "@babel/types" "^7.7.4" -"@babel/highlight@7.0.0-beta.44": - version "7.0.0-beta.44" - resolved "https://registry.npm.taobao.org/@babel/highlight/download/@babel/highlight-7.0.0-beta.44.tgz#18c94ce543916a80553edcdcf681890b200747d5" - integrity sha1-GMlM5UORaoBVPtzc9oGJCyAHR9U= - dependencies: - chalk "^2.0.0" - esutils "^2.0.2" - js-tokens "^3.0.0" - "@babel/highlight@^7.0.0": version "7.5.0" resolved "https://registry.npm.taobao.org/@babel/highlight/download/@babel/highlight-7.5.0.tgz#56d11312bd9248fa619591d02472be6e8cb32540" @@ -374,10 +324,10 @@ resolved "https://registry.npm.taobao.org/@babel/parser/download/@babel/parser-7.4.5.tgz#04af8d5d5a2b044a2a1bffacc1e5e6673544e872" integrity sha1-BK+NXVorBEoqG/+sweXmZzVE6HI= -"@babel/parser@^7.0.0", "@babel/parser@^7.1.0", "@babel/parser@^7.4.3", "@babel/parser@^7.4.4", "@babel/parser@^7.4.5", "@babel/parser@^7.5.0", "@babel/parser@^7.7.4": - version "7.7.4" - resolved "https://registry.npm.taobao.org/@babel/parser/download/@babel/parser-7.7.4.tgz#75ab2d7110c2cf2fa949959afb05fa346d2231bb" - integrity sha1-dastcRDCzy+pSZWa+wX6NG0iMbs= +"@babel/parser@^7.0.0", "@babel/parser@^7.1.0", "@babel/parser@^7.4.3", "@babel/parser@^7.4.4", "@babel/parser@^7.4.5", "@babel/parser@^7.5.0", "@babel/parser@^7.7.4", "@babel/parser@^7.7.5": + version "7.7.5" + resolved "https://registry.npm.taobao.org/@babel/parser/download/@babel/parser-7.7.5.tgz#cbf45321619ac12d83363fcf9c94bb67fa646d71" + integrity sha1-y/RTIWGawS2DNj/PnJS7Z/pkbXE= "@babel/plugin-proposal-async-generator-functions@7.2.0": version "7.2.0" @@ -504,7 +454,7 @@ "@babel/plugin-proposal-optional-chaining@7.2.0": version "7.2.0" - resolved "https://registry.npm.taobao.org/@babel/plugin-proposal-optional-chaining/download/@babel/plugin-proposal-optional-chaining-7.2.0.tgz?cache=0&sync_timestamp=1574467524487&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fplugin-proposal-optional-chaining%2Fdownload%2F%40babel%2Fplugin-proposal-optional-chaining-7.2.0.tgz#ae454f4c21c6c2ce8cb2397dc332ae8b420c5441" + resolved "https://registry.npm.taobao.org/@babel/plugin-proposal-optional-chaining/download/@babel/plugin-proposal-optional-chaining-7.2.0.tgz?cache=0&sync_timestamp=1575638428840&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fplugin-proposal-optional-chaining%2Fdownload%2F%40babel%2Fplugin-proposal-optional-chaining-7.2.0.tgz#ae454f4c21c6c2ce8cb2397dc332ae8b420c5441" integrity sha1-rkVPTCHGws6Msjl9wzKui0IMVEE= dependencies: "@babel/helper-plugin-utils" "^7.0.0" @@ -763,21 +713,21 @@ dependencies: "@babel/helper-plugin-utils" "^7.0.0" -"@babel/plugin-transform-modules-amd@^7.2.0", "@babel/plugin-transform-modules-amd@^7.7.4": - version "7.7.4" - resolved "https://registry.npm.taobao.org/@babel/plugin-transform-modules-amd/download/@babel/plugin-transform-modules-amd-7.7.4.tgz?cache=0&sync_timestamp=1574466132586&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fplugin-transform-modules-amd%2Fdownload%2F%40babel%2Fplugin-transform-modules-amd-7.7.4.tgz#276b3845ca2b228f2995e453adc2e6f54d72fb71" - integrity sha1-J2s4RcorIo8pleRTrcLm9U1y+3E= +"@babel/plugin-transform-modules-amd@^7.2.0", "@babel/plugin-transform-modules-amd@^7.7.5": + version "7.7.5" + resolved "https://registry.npm.taobao.org/@babel/plugin-transform-modules-amd/download/@babel/plugin-transform-modules-amd-7.7.5.tgz#39e0fb717224b59475b306402bb8eedab01e729c" + integrity sha1-OeD7cXIktZR1swZAK7ju2rAecpw= dependencies: - "@babel/helper-module-transforms" "^7.7.4" + "@babel/helper-module-transforms" "^7.7.5" "@babel/helper-plugin-utils" "^7.0.0" babel-plugin-dynamic-import-node "^2.3.0" -"@babel/plugin-transform-modules-commonjs@^7.4.4", "@babel/plugin-transform-modules-commonjs@^7.7.4": - version "7.7.4" - resolved "https://registry.npm.taobao.org/@babel/plugin-transform-modules-commonjs/download/@babel/plugin-transform-modules-commonjs-7.7.4.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fplugin-transform-modules-commonjs%2Fdownload%2F%40babel%2Fplugin-transform-modules-commonjs-7.7.4.tgz#bee4386e550446343dd52a571eda47851ff857a3" - integrity sha1-vuQ4blUERjQ91SpXHtpHhR/4V6M= +"@babel/plugin-transform-modules-commonjs@^7.4.4", "@babel/plugin-transform-modules-commonjs@^7.7.5": + version "7.7.5" + resolved "https://registry.npm.taobao.org/@babel/plugin-transform-modules-commonjs/download/@babel/plugin-transform-modules-commonjs-7.7.5.tgz?cache=0&sync_timestamp=1575638294618&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fplugin-transform-modules-commonjs%2Fdownload%2F%40babel%2Fplugin-transform-modules-commonjs-7.7.5.tgz#1d27f5eb0bcf7543e774950e5b2fa782e637b345" + integrity sha1-HSf16wvPdUPndJUOWy+nguY3s0U= dependencies: - "@babel/helper-module-transforms" "^7.7.4" + "@babel/helper-module-transforms" "^7.7.5" "@babel/helper-plugin-utils" "^7.0.0" "@babel/helper-simple-access" "^7.7.4" babel-plugin-dynamic-import-node "^2.3.0" @@ -877,10 +827,10 @@ "@babel/helper-plugin-utils" "^7.0.0" "@babel/plugin-syntax-jsx" "^7.7.4" -"@babel/plugin-transform-regenerator@^7.4.5", "@babel/plugin-transform-regenerator@^7.7.4": - version "7.7.4" - resolved "https://registry.npm.taobao.org/@babel/plugin-transform-regenerator/download/@babel/plugin-transform-regenerator-7.7.4.tgz#d18eac0312a70152d7d914cbed2dc3999601cfc0" - integrity sha1-0Y6sAxKnAVLX2RTL7S3DmZYBz8A= +"@babel/plugin-transform-regenerator@^7.4.5", "@babel/plugin-transform-regenerator@^7.7.5": + version "7.7.5" + resolved "https://registry.npm.taobao.org/@babel/plugin-transform-regenerator/download/@babel/plugin-transform-regenerator-7.7.5.tgz?cache=0&sync_timestamp=1575638289451&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fplugin-transform-regenerator%2Fdownload%2F%40babel%2Fplugin-transform-regenerator-7.7.5.tgz#3a8757ee1a2780f390e89f246065ecf59c26fce9" + integrity sha1-OodX7hongPOQ6J8kYGXs9Zwm/Ok= dependencies: regenerator-transform "^0.14.0" @@ -893,7 +843,7 @@ "@babel/plugin-transform-runtime@7.4.4": version "7.4.4" - resolved "https://registry.npm.taobao.org/@babel/plugin-transform-runtime/download/@babel/plugin-transform-runtime-7.4.4.tgz#a50f5d16e9c3a4ac18a1a9f9803c107c380bce08" + resolved "https://registry.npm.taobao.org/@babel/plugin-transform-runtime/download/@babel/plugin-transform-runtime-7.4.4.tgz?cache=0&sync_timestamp=1575765084777&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fplugin-transform-runtime%2Fdownload%2F%40babel%2Fplugin-transform-runtime-7.4.4.tgz#a50f5d16e9c3a4ac18a1a9f9803c107c380bce08" integrity sha1-pQ9dFunDpKwYoan5gDwQfDgLzgg= dependencies: "@babel/helper-module-imports" "^7.0.0" @@ -957,7 +907,7 @@ "@babel/preset-env@7.4.5": version "7.4.5" - resolved "https://registry.npm.taobao.org/@babel/preset-env/download/@babel/preset-env-7.4.5.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fpreset-env%2Fdownload%2F%40babel%2Fpreset-env-7.4.5.tgz#2fad7f62983d5af563b5f3139242755884998a58" + resolved "https://registry.npm.taobao.org/@babel/preset-env/download/@babel/preset-env-7.4.5.tgz#2fad7f62983d5af563b5f3139242755884998a58" integrity sha1-L61/Ypg9WvVjtfMTkkJ1WISZilg= dependencies: "@babel/helper-module-imports" "^7.0.0" @@ -1010,9 +960,9 @@ semver "^5.5.0" "@babel/preset-env@^7.5.4": - version "7.7.4" - resolved "https://registry.npm.taobao.org/@babel/preset-env/download/@babel/preset-env-7.7.4.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fpreset-env%2Fdownload%2F%40babel%2Fpreset-env-7.7.4.tgz#ccaf309ae8d1ee2409c85a4e2b5e280ceee830f8" - integrity sha1-zK8wmujR7iQJyFpOK14oDO7oMPg= + version "7.7.6" + resolved "https://registry.npm.taobao.org/@babel/preset-env/download/@babel/preset-env-7.7.6.tgz#39ac600427bbb94eec6b27953f1dfa1d64d457b2" + integrity sha1-OaxgBCe7uU7sayeVPx36HWTUV7I= dependencies: "@babel/helper-module-imports" "^7.7.4" "@babel/helper-plugin-utils" "^7.0.0" @@ -1042,8 +992,8 @@ "@babel/plugin-transform-function-name" "^7.7.4" "@babel/plugin-transform-literals" "^7.7.4" "@babel/plugin-transform-member-expression-literals" "^7.7.4" - "@babel/plugin-transform-modules-amd" "^7.7.4" - "@babel/plugin-transform-modules-commonjs" "^7.7.4" + "@babel/plugin-transform-modules-amd" "^7.7.5" + "@babel/plugin-transform-modules-commonjs" "^7.7.5" "@babel/plugin-transform-modules-systemjs" "^7.7.4" "@babel/plugin-transform-modules-umd" "^7.7.4" "@babel/plugin-transform-named-capturing-groups-regex" "^7.7.4" @@ -1051,7 +1001,7 @@ "@babel/plugin-transform-object-super" "^7.7.4" "@babel/plugin-transform-parameters" "^7.7.4" "@babel/plugin-transform-property-literals" "^7.7.4" - "@babel/plugin-transform-regenerator" "^7.7.4" + "@babel/plugin-transform-regenerator" "^7.7.5" "@babel/plugin-transform-reserved-words" "^7.7.4" "@babel/plugin-transform-shorthand-properties" "^7.7.4" "@babel/plugin-transform-spread" "^7.7.4" @@ -1061,7 +1011,7 @@ "@babel/plugin-transform-unicode-regex" "^7.7.4" "@babel/types" "^7.7.4" browserslist "^4.6.0" - core-js-compat "^3.1.1" + core-js-compat "^3.4.7" invariant "^2.2.2" js-levenshtein "^1.1.3" semver "^5.5.0" @@ -1106,45 +1056,35 @@ source-map-support "^0.5.9" "@babel/runtime-corejs2@^7.2.0": - version "7.7.4" - resolved "https://registry.npm.taobao.org/@babel/runtime-corejs2/download/@babel/runtime-corejs2-7.7.4.tgz?cache=0&sync_timestamp=1574466340191&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fruntime-corejs2%2Fdownload%2F%40babel%2Fruntime-corejs2-7.7.4.tgz#b9c2b1b5882762005785bc47740195a0ac780888" - integrity sha1-ucKxtYgnYgBXhbxHdAGVoKx4CIg= + version "7.7.6" + resolved "https://registry.npm.taobao.org/@babel/runtime-corejs2/download/@babel/runtime-corejs2-7.7.6.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fruntime-corejs2%2Fdownload%2F%40babel%2Fruntime-corejs2-7.7.6.tgz#50b7cd4eab929b4cb66167c4972d35eaceaa124b" + integrity sha1-ULfNTquSm0y2YWfEly016s6qEks= dependencies: core-js "^2.6.5" regenerator-runtime "^0.13.2" "@babel/runtime-corejs3@^7.7.4": - version "7.7.4" - resolved "https://registry.npm.taobao.org/@babel/runtime-corejs3/download/@babel/runtime-corejs3-7.7.4.tgz#f861adc1cecb9903dfd66ea97917f02ff8d79888" - integrity sha1-+GGtwc7LmQPf1m6peRfwL/jXmIg= + version "7.7.6" + resolved "https://registry.npm.taobao.org/@babel/runtime-corejs3/download/@babel/runtime-corejs3-7.7.6.tgz#5b1044ea11b659d288f77190e19c62da959ed9a3" + integrity sha1-WxBE6hG2WdKI93GQ4Zxi2pWe2aM= dependencies: core-js-pure "^3.0.0" regenerator-runtime "^0.13.2" "@babel/runtime@7.4.5": version "7.4.5" - resolved "https://registry.npm.taobao.org/@babel/runtime/download/@babel/runtime-7.4.5.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fruntime%2Fdownload%2F%40babel%2Fruntime-7.4.5.tgz#582bb531f5f9dc67d2fcb682979894f75e253f12" + resolved "https://registry.npm.taobao.org/@babel/runtime/download/@babel/runtime-7.4.5.tgz#582bb531f5f9dc67d2fcb682979894f75e253f12" integrity sha1-WCu1MfX53GfS/LaCl5iU914lPxI= dependencies: regenerator-runtime "^0.13.2" "@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.4.0", "@babel/runtime@^7.4.2", "@babel/runtime@^7.4.5", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.3", "@babel/runtime@^7.7.4": - version "7.7.4" - resolved "https://registry.npm.taobao.org/@babel/runtime/download/@babel/runtime-7.7.4.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fruntime%2Fdownload%2F%40babel%2Fruntime-7.7.4.tgz#b23a856751e4bf099262f867767889c0e3fe175b" - integrity sha1-sjqFZ1HkvwmSYvhndniJwOP+F1s= + version "7.7.6" + resolved "https://registry.npm.taobao.org/@babel/runtime/download/@babel/runtime-7.7.6.tgz#d18c511121aff1b4f2cd1d452f1bac9601dd830f" + integrity sha1-0YxRESGv8bTyzR1FLxuslgHdgw8= dependencies: regenerator-runtime "^0.13.2" -"@babel/template@7.0.0-beta.44": - version "7.0.0-beta.44" - resolved "https://registry.npm.taobao.org/@babel/template/download/@babel/template-7.0.0-beta.44.tgz?cache=0&sync_timestamp=1574465948896&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Ftemplate%2Fdownload%2F%40babel%2Ftemplate-7.0.0-beta.44.tgz#f8832f4fdcee5d59bf515e595fc5106c529b394f" - integrity sha1-+IMvT9zuXVm/UV5ZX8UQbFKbOU8= - dependencies: - "@babel/code-frame" "7.0.0-beta.44" - "@babel/types" "7.0.0-beta.44" - babylon "7.0.0-beta.44" - lodash "^4.2.0" - "@babel/template@7.4.4": version "7.4.4" resolved "https://registry.npm.taobao.org/@babel/template/download/@babel/template-7.4.4.tgz?cache=0&sync_timestamp=1574465948896&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Ftemplate%2Fdownload%2F%40babel%2Ftemplate-7.4.4.tgz#f4b88d1225689a08f5bc3a17483545be9e4ed237" @@ -1163,22 +1103,6 @@ "@babel/parser" "^7.7.4" "@babel/types" "^7.7.4" -"@babel/traverse@7.0.0-beta.44": - version "7.0.0-beta.44" - resolved "https://registry.npm.taobao.org/@babel/traverse/download/@babel/traverse-7.0.0-beta.44.tgz#a970a2c45477ad18017e2e465a0606feee0d2966" - integrity sha1-qXCixFR3rRgBfi5GWgYG/u4NKWY= - dependencies: - "@babel/code-frame" "7.0.0-beta.44" - "@babel/generator" "7.0.0-beta.44" - "@babel/helper-function-name" "7.0.0-beta.44" - "@babel/helper-split-export-declaration" "7.0.0-beta.44" - "@babel/types" "7.0.0-beta.44" - babylon "7.0.0-beta.44" - debug "^3.1.0" - globals "^11.1.0" - invariant "^2.2.0" - lodash "^4.2.0" - "@babel/traverse@7.4.5": version "7.4.5" resolved "https://registry.npm.taobao.org/@babel/traverse/download/@babel/traverse-7.4.5.tgz#4e92d1728fd2f1897dafdd321efbff92156c3216" @@ -1209,15 +1133,6 @@ globals "^11.1.0" lodash "^4.17.13" -"@babel/types@7.0.0-beta.44": - version "7.0.0-beta.44" - resolved "https://registry.npm.taobao.org/@babel/types/download/@babel/types-7.0.0-beta.44.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Ftypes%2Fdownload%2F%40babel%2Ftypes-7.0.0-beta.44.tgz#6b1b164591f77dec0a0342aca995f2d046b3a757" - integrity sha1-axsWRZH3fewKA0KsqZXy0Eazp1c= - dependencies: - esutils "^2.0.2" - lodash "^4.2.0" - to-fast-properties "^2.0.0" - "@babel/types@7.4.4": version "7.4.4" resolved "https://registry.npm.taobao.org/@babel/types/download/@babel/types-7.4.4.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Ftypes%2Fdownload%2F%40babel%2Ftypes-7.4.4.tgz#8db9e9a629bb7c29370009b4b779ed93fe57d5f0" @@ -2614,9 +2529,9 @@ integrity sha1-PcoOPzOyAPx9ETnAzZbBJoyt/Z0= "@types/node@*", "@types/node@>= 8", "@types/node@^12.0.2", "@types/node@^12.6.2": - version "12.12.14" - resolved "https://registry.npm.taobao.org/@types/node/download/@types/node-12.12.14.tgz#1c1d6e3c75dba466e0326948d56e8bd72a1903d2" - integrity sha1-HB1uPHXbpGbgMmlI1W6L1yoZA9I= + version "12.12.15" + resolved "https://registry.npm.taobao.org/@types/node/download/@types/node-12.12.15.tgz?cache=0&sync_timestamp=1575895068860&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40types%2Fnode%2Fdownload%2F%40types%2Fnode-12.12.15.tgz#8dfb6ce22fedd469128137640a3aa8f17415422f" + integrity sha1-jfts4i/t1GkSgTdkCjqo8XQVQi8= "@types/normalize-package-data@^2.4.0": version "2.4.0" @@ -2715,9 +2630,9 @@ "@types/react" "*" "@types/react@*", "@types/react@^16.7.18", "@types/react@^16.9.13", "@types/react@~16.9.1": - version "16.9.15" - resolved "https://registry.npm.taobao.org/@types/react/download/@types/react-16.9.15.tgz#aeabb7a50f96c9e31a16079ada20ede9ed602977" - integrity sha1-rqu3pQ+WyeMaFgea2iDt6e1gKXc= + version "16.9.16" + resolved "https://registry.npm.taobao.org/@types/react/download/@types/react-16.9.16.tgz#4f12515707148b1f53a8eaa4341dae5dfefb066d" + integrity sha1-TxJRVwcUix9TqOqkNB2uXf77Bm0= dependencies: "@types/prop-types" "*" csstype "^2.2.0" @@ -2829,7 +2744,7 @@ "@typescript-eslint/eslint-plugin@^1.4.2": version "1.13.0" - resolved "https://registry.npm.taobao.org/@typescript-eslint/eslint-plugin/download/@typescript-eslint/eslint-plugin-1.13.0.tgz#22fed9b16ddfeb402fd7bcde56307820f6ebc49f" + resolved "https://registry.npm.taobao.org/@typescript-eslint/eslint-plugin/download/@typescript-eslint/eslint-plugin-1.13.0.tgz?cache=0&sync_timestamp=1575893692987&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40typescript-eslint%2Feslint-plugin%2Fdownload%2F%40typescript-eslint%2Feslint-plugin-1.13.0.tgz#22fed9b16ddfeb402fd7bcde56307820f6ebc49f" integrity sha1-Iv7ZsW3f60Av17zeVjB4IPbrxJ8= dependencies: "@typescript-eslint/experimental-utils" "1.13.0" @@ -2838,17 +2753,6 @@ regexpp "^2.0.1" tsutils "^3.7.0" -"@typescript-eslint/eslint-plugin@^2.0.0": - version "2.10.0" - resolved "https://registry.npm.taobao.org/@typescript-eslint/eslint-plugin/download/@typescript-eslint/eslint-plugin-2.10.0.tgz#c4cb103275e555e8a7e9b3d14c5951eb6d431e70" - integrity sha1-xMsQMnXlVein6bPRTFlR621DHnA= - dependencies: - "@typescript-eslint/experimental-utils" "2.10.0" - eslint-utils "^1.4.3" - functional-red-black-tree "^1.0.1" - regexpp "^3.0.0" - tsutils "^3.17.1" - "@typescript-eslint/experimental-utils@1.13.0", "@typescript-eslint/experimental-utils@^1.13.0": version "1.13.0" resolved "https://registry.npm.taobao.org/@typescript-eslint/experimental-utils/download/@typescript-eslint/experimental-utils-1.13.0.tgz#b08c60d780c0067de2fb44b04b432f540138301e" @@ -2858,27 +2762,18 @@ "@typescript-eslint/typescript-estree" "1.13.0" eslint-scope "^4.0.0" -"@typescript-eslint/experimental-utils@2.10.0": - version "2.10.0" - resolved "https://registry.npm.taobao.org/@typescript-eslint/experimental-utils/download/@typescript-eslint/experimental-utils-2.10.0.tgz#8db1656cdfd3d9dcbdbf360b8274dea76f0b2c2c" - integrity sha1-jbFlbN/T2dy9vzYLgnTep28LLCw= - dependencies: - "@types/json-schema" "^7.0.3" - "@typescript-eslint/typescript-estree" "2.10.0" - eslint-scope "^5.0.0" - -"@typescript-eslint/experimental-utils@2.10.1-alpha.3+3ddf1a2a": - version "2.10.1-alpha.3" - resolved "https://registry.npm.taobao.org/@typescript-eslint/experimental-utils/download/@typescript-eslint/experimental-utils-2.10.1-alpha.3.tgz#69a134a80e36ba21e4c5ce690bd1a91bf82431ad" - integrity sha1-aaE0qA42uiHkxc5pC9GpG/gkMa0= +"@typescript-eslint/experimental-utils@2.10.1-alpha.4+324f155f": + version "2.10.1-alpha.4" + resolved "https://registry.npm.taobao.org/@typescript-eslint/experimental-utils/download/@typescript-eslint/experimental-utils-2.10.1-alpha.4.tgz#93fe5d335143fab9aaeda6dd361ea39c0784bbd2" + integrity sha1-k/5dM1FD+rmq7abdNh6jnAeEu9I= dependencies: "@types/json-schema" "^7.0.3" - "@typescript-eslint/typescript-estree" "2.10.1-alpha.3+3ddf1a2a" + "@typescript-eslint/typescript-estree" "2.10.1-alpha.4+324f155f" eslint-scope "^5.0.0" "@typescript-eslint/parser@^1.11.0": version "1.13.0" - resolved "https://registry.npm.taobao.org/@typescript-eslint/parser/download/@typescript-eslint/parser-1.13.0.tgz?cache=0&sync_timestamp=1575516904267&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40typescript-eslint%2Fparser%2Fdownload%2F%40typescript-eslint%2Fparser-1.13.0.tgz#61ac7811ea52791c47dc9fd4dd4a184fae9ac355" + resolved "https://registry.npm.taobao.org/@typescript-eslint/parser/download/@typescript-eslint/parser-1.13.0.tgz?cache=0&sync_timestamp=1575893692836&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40typescript-eslint%2Fparser%2Fdownload%2F%40typescript-eslint%2Fparser-1.13.0.tgz#61ac7811ea52791c47dc9fd4dd4a184fae9ac355" integrity sha1-Yax4EepSeRxH3J/U3UoYT66aw1U= dependencies: "@types/eslint-visitor-keys" "^1.0.0" @@ -2886,24 +2781,14 @@ "@typescript-eslint/typescript-estree" "1.13.0" eslint-visitor-keys "^1.0.0" -"@typescript-eslint/parser@^2.0.0": - version "2.10.0" - resolved "https://registry.npm.taobao.org/@typescript-eslint/parser/download/@typescript-eslint/parser-2.10.0.tgz?cache=0&sync_timestamp=1575516904267&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40typescript-eslint%2Fparser%2Fdownload%2F%40typescript-eslint%2Fparser-2.10.0.tgz#24b2e48384ab6d5a6121e4c4faf8892c79657ad3" - integrity sha1-JLLkg4SrbVphIeTE+viJLHlletM= - dependencies: - "@types/eslint-visitor-keys" "^1.0.0" - "@typescript-eslint/experimental-utils" "2.10.0" - "@typescript-eslint/typescript-estree" "2.10.0" - eslint-visitor-keys "^1.1.0" - "@typescript-eslint/parser@canary": - version "2.10.1-alpha.3" - resolved "https://registry.npm.taobao.org/@typescript-eslint/parser/download/@typescript-eslint/parser-2.10.1-alpha.3.tgz?cache=0&sync_timestamp=1575516904267&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40typescript-eslint%2Fparser%2Fdownload%2F%40typescript-eslint%2Fparser-2.10.1-alpha.3.tgz#9305e1a0306e1ec5532f9b4b9b030a49c988dd56" - integrity sha1-kwXhoDBuHsVTL5tLmwMKScmI3VY= + version "2.10.1-alpha.4" + resolved "https://registry.npm.taobao.org/@typescript-eslint/parser/download/@typescript-eslint/parser-2.10.1-alpha.4.tgz?cache=0&sync_timestamp=1575893692836&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40typescript-eslint%2Fparser%2Fdownload%2F%40typescript-eslint%2Fparser-2.10.1-alpha.4.tgz#9999cdab5d0fdb8b0699e42fb6b9b6e9236e169b" + integrity sha1-mZnNq10P24sGmeQvtrm26SNuFps= dependencies: "@types/eslint-visitor-keys" "^1.0.0" - "@typescript-eslint/experimental-utils" "2.10.1-alpha.3+3ddf1a2a" - "@typescript-eslint/typescript-estree" "2.10.1-alpha.3+3ddf1a2a" + "@typescript-eslint/experimental-utils" "2.10.1-alpha.4+324f155f" + "@typescript-eslint/typescript-estree" "2.10.1-alpha.4+324f155f" eslint-visitor-keys "^1.1.0" "@typescript-eslint/typescript-estree@1.13.0": @@ -2914,23 +2799,10 @@ lodash.unescape "4.0.1" semver "5.5.0" -"@typescript-eslint/typescript-estree@2.10.0": - version "2.10.0" - resolved "https://registry.npm.taobao.org/@typescript-eslint/typescript-estree/download/@typescript-eslint/typescript-estree-2.10.0.tgz#89cdabd5e8c774e9d590588cb42fb9afd14dcbd9" - integrity sha1-ic2r1ejHdOnVkFiMtC+5r9FNy9k= - dependencies: - debug "^4.1.1" - eslint-visitor-keys "^1.1.0" - glob "^7.1.6" - is-glob "^4.0.1" - lodash.unescape "4.0.1" - semver "^6.3.0" - tsutils "^3.17.1" - -"@typescript-eslint/typescript-estree@2.10.1-alpha.3+3ddf1a2a": - version "2.10.1-alpha.3" - resolved "https://registry.npm.taobao.org/@typescript-eslint/typescript-estree/download/@typescript-eslint/typescript-estree-2.10.1-alpha.3.tgz#3faa5d978f150b3e86861751b91369f9959786fa" - integrity sha1-P6pdl48VCz6GhhdRuRNp+ZWXhvo= +"@typescript-eslint/typescript-estree@2.10.1-alpha.4+324f155f": + version "2.10.1-alpha.4" + resolved "https://registry.npm.taobao.org/@typescript-eslint/typescript-estree/download/@typescript-eslint/typescript-estree-2.10.1-alpha.4.tgz#c3394605327bfa285bb32bab9254301e4499d3b8" + integrity sha1-wzlGBTJ7+ihbsyurklQwHkSZ07g= dependencies: debug "^4.1.1" eslint-visitor-keys "^1.1.0" @@ -2946,9 +2818,9 @@ integrity sha1-Ki3XtLzRGGnpaCZP89q6z6vouHQ= "@umijs/fabric@^1.1.7", "@umijs/fabric@^1.1.9": - version "1.2.1" - resolved "https://registry.npm.taobao.org/@umijs/fabric/download/@umijs/fabric-1.2.1.tgz#7e1dc13eddfe86cf45ee4716b912b4ddd2d2610a" - integrity sha1-fh3BPt3+hs9F7kcWuRK03dLSYQo= + version "1.2.4" + resolved "https://registry.npm.taobao.org/@umijs/fabric/download/@umijs/fabric-1.2.4.tgz#2f5830f8c6af79ac0e9a68a75301cd4c2b6a795f" + integrity sha1-L1gw+MaveawOmminUwHNTCtqeV8= dependencies: "@typescript-eslint/eslint-plugin" "^1.4.2" "@typescript-eslint/parser" canary @@ -2956,13 +2828,12 @@ eslint-config-airbnb "^17.1.0" eslint-config-airbnb-base "^13.1.0" eslint-config-airbnb-typescript "^4.0.0" - eslint-config-egg "^7.1.0" eslint-config-prettier "^4.1.0" eslint-formatter-pretty "^2.1.1" eslint-plugin-babel "^5.3.0" eslint-plugin-compat "^3.1.1" eslint-plugin-eslint-comments "^3.1.1" - eslint-plugin-import "^2.17.3" + eslint-plugin-import "~2.18.2" eslint-plugin-jest "^22.4.1" eslint-plugin-jsx-a11y "^6.2.0" eslint-plugin-markdown "^1.0.0" @@ -3233,10 +3104,10 @@ address@^1.0.1, address@^1.0.3: resolved "https://registry.npm.taobao.org/address/download/address-1.1.2.tgz#bf1116c9c758c51b7a933d296b72c221ed9428b6" integrity sha1-vxEWycdYxRt6kz0pa3LCIe2UKLY= -af-webpack@1.14.3: - version "1.14.3" - resolved "https://registry.npm.taobao.org/af-webpack/download/af-webpack-1.14.3.tgz#3d03663bba804bde803a760e519cdfe52867f41a" - integrity sha1-PQNmO7qAS96AOnYOUZzf5Shn9Bo= +af-webpack@1.14.4: + version "1.14.4" + resolved "https://registry.npm.taobao.org/af-webpack/download/af-webpack-1.14.4.tgz#87fc4a17e3a8d0bd13f64f603a43f5b6bfbebdeb" + integrity sha1-h/xKF+Oo0L0T9k9gOkP1tr++ves= dependencies: "@babel/core" "7.4.5" "@babel/plugin-transform-react-constant-elements" "7.2.0" @@ -3275,7 +3146,7 @@ af-webpack@1.14.3: eslint-plugin-react "7.13.0" eslint-plugin-react-hooks "1.6.0" file-loader "2.0.0" - fork-ts-checker-webpack-plugin "1.3.7" + fork-ts-checker-webpack-plugin "3.1.1" friendly-errors-webpack-plugin "1.7.0" graphql "14.3.1" graphql-tag "2.10.1" @@ -3501,9 +3372,9 @@ antd-mobile@2.x: rmc-tooltip "~1.0.0" antd@*, antd@3.x, antd@^3.19.5, antd@^3.25.3: - version "3.26.0" - resolved "https://registry.npm.taobao.org/antd/download/antd-3.26.0.tgz#0a6a43fdd8577e61708932b7bc34ba0234d0f138" - integrity sha1-CmpD/dhXfmFwiTK3vDS6AjTQ8Tg= + version "3.26.1" + resolved "https://registry.npm.taobao.org/antd/download/antd-3.26.1.tgz?cache=0&sync_timestamp=1575885799357&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fantd%2Fdownload%2Fantd-3.26.1.tgz#ebeed1f658f82f47b1be94812c7d5297923e9260" + integrity sha1-6+7R9lj4L0exvpSBLH1Sl5I+kmA= dependencies: "@ant-design/create-react-context" "^0.2.4" "@ant-design/icons" "~2.1.1" @@ -3543,7 +3414,7 @@ antd@*, antd@3.x, antd@^3.19.5, antd@^3.25.3: rc-slider "~8.7.1" rc-steps "~3.5.0" rc-switch "~1.9.0" - rc-table "~6.9.4" + rc-table "~6.10.5" rc-tabs "~9.7.0" rc-time-picker "~3.7.1" rc-tooltip "~3.7.3" @@ -3577,7 +3448,7 @@ anymatch@^2.0.0: micromatch "^3.1.4" normalize-path "^2.1.1" -anymatch@^3.0.1: +anymatch@^3.0.1, anymatch@~3.1.1: version "3.1.1" resolved "https://registry.npm.taobao.org/anymatch/download/anymatch-3.1.1.tgz#c55ecf02185e2469259399310c173ce31233b142" integrity sha1-xV7PAhheJGklk5kxDBc84xIzsUI= @@ -3846,7 +3717,7 @@ async-limiter@^1.0.0, async-limiter@~1.0.0: async-validator@~1.11.3: version "1.11.5" - resolved "https://registry.npm.taobao.org/async-validator/download/async-validator-1.11.5.tgz?cache=0&sync_timestamp=1573494332941&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fasync-validator%2Fdownload%2Fasync-validator-1.11.5.tgz#9d43cf49ef6bb76be5442388d19fb9a6e47597ea" + resolved "https://registry.npm.taobao.org/async-validator/download/async-validator-1.11.5.tgz?cache=0&sync_timestamp=1575620599372&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fasync-validator%2Fdownload%2Fasync-validator-1.11.5.tgz#9d43cf49ef6bb76be5442388d19fb9a6e47597ea" integrity sha1-nUPPSe9rt2vlRCOI0Z+5puR1l+o= async@^1.5.2: @@ -3953,18 +3824,6 @@ babel-eslint@10.0.2: eslint-scope "3.7.1" eslint-visitor-keys "^1.0.0" -babel-eslint@^8.2.6: - version "8.2.6" - resolved "https://registry.npm.taobao.org/babel-eslint/download/babel-eslint-8.2.6.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fbabel-eslint%2Fdownload%2Fbabel-eslint-8.2.6.tgz#6270d0c73205628067c0f7ae1693a9e797acefd9" - integrity sha1-YnDQxzIFYoBnwPeuFpOp55es79k= - dependencies: - "@babel/code-frame" "7.0.0-beta.44" - "@babel/traverse" "7.0.0-beta.44" - "@babel/types" "7.0.0-beta.44" - babylon "7.0.0-beta.44" - eslint-scope "3.7.1" - eslint-visitor-keys "^1.0.0" - babel-eslint@^9.0.0: version "9.0.0" resolved "https://registry.npm.taobao.org/babel-eslint/download/babel-eslint-9.0.0.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fbabel-eslint%2Fdownload%2Fbabel-eslint-9.0.0.tgz#7d9445f81ed9f60aff38115f838970df9f2b6220" @@ -4036,7 +3895,7 @@ babel-plugin-import@1.x, babel-plugin-import@^1.13.0: babel-plugin-istanbul@^5.1.0: version "5.2.0" - resolved "https://registry.npm.taobao.org/babel-plugin-istanbul/download/babel-plugin-istanbul-5.2.0.tgz#df4ade83d897a92df069c4d9a25cf2671293c854" + resolved "https://registry.npm.taobao.org/babel-plugin-istanbul/download/babel-plugin-istanbul-5.2.0.tgz?cache=0&sync_timestamp=1575854266823&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fbabel-plugin-istanbul%2Fdownload%2Fbabel-plugin-istanbul-5.2.0.tgz#df4ade83d897a92df069c4d9a25cf2671293c854" integrity sha1-30reg9iXqS3wacTZolzyZxKTyFQ= dependencies: "@babel/helper-plugin-utils" "^7.0.0" @@ -4177,11 +4036,6 @@ babel-types@^6.26.0: lodash "^4.17.4" to-fast-properties "^1.0.3" -babylon@7.0.0-beta.44: - version "7.0.0-beta.44" - resolved "https://registry.npm.taobao.org/babylon/download/babylon-7.0.0-beta.44.tgz#89159e15e6e30c5096e22d738d8c0af8a0e8ca1d" - integrity sha1-iRWeFebjDFCW4i1zjYwK+KDoyh0= - babylon@^6.18.0: version "6.18.0" resolved "https://registry.npm.taobao.org/babylon/download/babylon-6.18.0.tgz#af2f3b88fa6f5c1e4c634d1a0f8eac4f55b395e3" @@ -4320,7 +4174,7 @@ boolbase@^1.0.0, boolbase@~1.0.0: boxen@^3.0.0: version "3.2.0" - resolved "https://registry.npm.taobao.org/boxen/download/boxen-3.2.0.tgz#fbdff0de93636ab4450886b6ff45b92d098f45eb" + resolved "https://registry.npm.taobao.org/boxen/download/boxen-3.2.0.tgz?cache=0&sync_timestamp=1575618288662&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fboxen%2Fdownload%2Fboxen-3.2.0.tgz#fbdff0de93636ab4450886b6ff45b92d098f45eb" integrity sha1-+9/w3pNjarRFCIa2/0W5LQmPRes= dependencies: ansi-align "^3.0.0" @@ -4356,7 +4210,7 @@ braces@^2.3.1, braces@^2.3.2: split-string "^3.0.2" to-regex "^3.0.1" -braces@^3.0.1, braces@^3.0.2: +braces@^3.0.1, braces@^3.0.2, braces@~3.0.2: version "3.0.2" resolved "https://registry.npm.taobao.org/braces/download/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" integrity sha1-NFThpGLujVmeI23zNs2epPiv4Qc= @@ -4441,21 +4295,21 @@ browserify-zlib@^0.2.0: browserslist@4.5.4: version "4.5.4" - resolved "https://registry.npm.taobao.org/browserslist/download/browserslist-4.5.4.tgz?cache=0&sync_timestamp=1575124487524&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fbrowserslist%2Fdownload%2Fbrowserslist-4.5.4.tgz#166c4ecef3b51737a42436ea8002aeea466ea2c7" + resolved "https://registry.npm.taobao.org/browserslist/download/browserslist-4.5.4.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fbrowserslist%2Fdownload%2Fbrowserslist-4.5.4.tgz#166c4ecef3b51737a42436ea8002aeea466ea2c7" integrity sha1-FmxOzvO1FzekJDbqgAKu6kZuosc= dependencies: caniuse-lite "^1.0.30000955" electron-to-chromium "^1.3.122" node-releases "^1.1.13" -browserslist@^4.0.0, browserslist@^4.6.0, browserslist@^4.6.1, browserslist@^4.6.3, browserslist@^4.8.0: - version "4.8.0" - resolved "https://registry.npm.taobao.org/browserslist/download/browserslist-4.8.0.tgz?cache=0&sync_timestamp=1575124487524&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fbrowserslist%2Fdownload%2Fbrowserslist-4.8.0.tgz#6f06b0f974a7cc3a84babc2ccc56493668e3c789" - integrity sha1-bwaw+XSnzDqEurwszFZJNmjjx4k= +browserslist@^4.0.0, browserslist@^4.6.0, browserslist@^4.6.1, browserslist@^4.6.3, browserslist@^4.8.0, browserslist@^4.8.2: + version "4.8.2" + resolved "https://registry.npm.taobao.org/browserslist/download/browserslist-4.8.2.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fbrowserslist%2Fdownload%2Fbrowserslist-4.8.2.tgz#b45720ad5fbc8713b7253c20766f701c9a694289" + integrity sha1-tFcgrV+8hxO3JTwgdm9wHJppQok= dependencies: - caniuse-lite "^1.0.30001012" - electron-to-chromium "^1.3.317" - node-releases "^1.1.41" + caniuse-lite "^1.0.30001015" + electron-to-chromium "^1.3.322" + node-releases "^1.1.42" bs-logger@0.x: version "0.2.6" @@ -4464,7 +4318,7 @@ bs-logger@0.x: dependencies: fast-json-stable-stringify "2.x" -bser@^2.0.0: +bser@2.1.1: version "2.1.1" resolved "https://registry.npm.taobao.org/bser/download/bser-2.1.1.tgz?cache=0&sync_timestamp=1571761384718&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fbser%2Fdownload%2Fbser-2.1.1.tgz#e6787da20ece9d07998533cfd9de6f5c38f4bc05" integrity sha1-5nh9og7OnQeZhTPP2d5vXDj0vAU= @@ -4662,7 +4516,7 @@ callsites@^3.0.0, callsites@^3.1.0: camel-case@^3.0.0: version "3.0.0" - resolved "https://registry.npm.taobao.org/camel-case/download/camel-case-3.0.0.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fcamel-case%2Fdownload%2Fcamel-case-3.0.0.tgz#ca3c3688a4e9cf3a4cda777dc4dcbc713249cf73" + resolved "https://registry.npm.taobao.org/camel-case/download/camel-case-3.0.0.tgz?cache=0&sync_timestamp=1575601570648&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fcamel-case%2Fdownload%2Fcamel-case-3.0.0.tgz#ca3c3688a4e9cf3a4cda777dc4dcbc713249cf73" integrity sha1-yjw2iKTpzzpM2nd9xNy8cTJJz3M= dependencies: no-case "^2.2.0" @@ -4720,7 +4574,7 @@ caniuse-db@^1.0.30000977: resolved "https://registry.npm.taobao.org/caniuse-db/download/caniuse-db-1.0.30001015.tgz#9f1b4fa158c747dcc04b15f9c5359ddcf0390d90" integrity sha1-nxtPoVjHR9zASxX5xTWd3PA5DZA= -caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000955, caniuse-lite@^1.0.30000971, caniuse-lite@^1.0.30001012: +caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000955, caniuse-lite@^1.0.30000971, caniuse-lite@^1.0.30001012, caniuse-lite@^1.0.30001015: version "1.0.30001015" resolved "https://registry.npm.taobao.org/caniuse-lite/download/caniuse-lite-1.0.30001015.tgz#15a7ddf66aba786a71d99626bc8f2b91c6f0f5f0" integrity sha1-Fafd9mq6eGpx2ZYmvI8rkcbw9fA= @@ -4859,6 +4713,21 @@ chokidar@^2.0.0, chokidar@^2.0.2, chokidar@^2.0.4: optionalDependencies: fsevents "^1.2.7" +chokidar@^3.3.0: + version "3.3.0" + resolved "https://registry.npm.taobao.org/chokidar/download/chokidar-3.3.0.tgz?cache=0&sync_timestamp=1572684960295&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fchokidar%2Fdownload%2Fchokidar-3.3.0.tgz#12c0714668c55800f659e262d4962a97faf554a6" + integrity sha1-EsBxRmjFWAD2WeJi1JYql/r1VKY= + dependencies: + anymatch "~3.1.1" + braces "~3.0.2" + glob-parent "~5.1.0" + is-binary-path "~2.1.0" + is-glob "~4.0.1" + normalize-path "~3.0.0" + readdirp "~3.2.0" + optionalDependencies: + fsevents "~2.1.1" + chownr@^1.0.1, chownr@^1.1.1, chownr@^1.1.2: version "1.1.3" resolved "https://registry.npm.taobao.org/chownr/download/chownr-1.1.3.tgz#42d837d5239688d55f303003a508230fa6727142" @@ -4957,7 +4826,7 @@ cli-cursor@^3.1.0: cli-highlight@^1.2.3: version "1.2.3" - resolved "https://registry.npm.taobao.org/cli-highlight/download/cli-highlight-1.2.3.tgz?cache=0&sync_timestamp=1573949240542&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fcli-highlight%2Fdownload%2Fcli-highlight-1.2.3.tgz#b200f97ed0e43d24633e89de0f489a48bb87d2bf" + resolved "https://registry.npm.taobao.org/cli-highlight/download/cli-highlight-1.2.3.tgz#b200f97ed0e43d24633e89de0f489a48bb87d2bf" integrity sha1-sgD5ftDkPSRjPoneD0iaSLuH0r8= dependencies: chalk "^2.3.0" @@ -5211,11 +5080,6 @@ commander@~2.13.0: resolved "https://registry.npm.taobao.org/commander/download/commander-2.13.0.tgz#6964bca67685df7c1f1430c584f07d7597885b9c" integrity sha1-aWS8pnaF33wfFDDFhPB9dZeIW5w= -comment-parser@^0.5.4: - version "0.5.5" - resolved "https://registry.npm.taobao.org/comment-parser/download/comment-parser-0.5.5.tgz?cache=0&sync_timestamp=1575437901726&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fcomment-parser%2Fdownload%2Fcomment-parser-0.5.5.tgz#c2584cae7c2f0afc773e96b2ee98f8c10cbd693d" - integrity sha1-wlhMrnwvCvx3Ppay7pj4wQy9aT0= - common-tags@^1.4.0: version "1.8.0" resolved "https://registry.npm.taobao.org/common-tags/download/common-tags-1.8.0.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fcommon-tags%2Fdownload%2Fcommon-tags-1.8.0.tgz#8e3153e542d4a39e9b10554434afaaf98956a937" @@ -5549,48 +5413,48 @@ copy-webpack-plugin@5.0.3: serialize-javascript "^1.7.0" webpack-log "^2.0.0" -core-js-compat@^3.1.1: - version "3.4.7" - resolved "https://registry.npm.taobao.org/core-js-compat/download/core-js-compat-3.4.7.tgz#39f8080b1d92a524d6d90505c42b9c5c1eb90611" - integrity sha1-OfgICx2SpSTW2QUFxCucXB65BhE= +core-js-compat@^3.1.1, core-js-compat@^3.4.7: + version "3.4.8" + resolved "https://registry.npm.taobao.org/core-js-compat/download/core-js-compat-3.4.8.tgz#f72e6a4ed76437ea710928f44615f926a81607d5" + integrity sha1-9y5qTtdkN+pxCSj0RhX5JqgWB9U= dependencies: - browserslist "^4.8.0" + browserslist "^4.8.2" semver "^6.3.0" core-js-pure@^3.0.0: - version "3.4.7" - resolved "https://registry.npm.taobao.org/core-js-pure/download/core-js-pure-3.4.7.tgz?cache=0&sync_timestamp=1575309805893&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fcore-js-pure%2Fdownload%2Fcore-js-pure-3.4.7.tgz#c998e1892da9949200c7452cbd33c0df95be9f54" - integrity sha1-yZjhiS2plJIAx0UsvTPA35W+n1Q= + version "3.4.8" + resolved "https://registry.npm.taobao.org/core-js-pure/download/core-js-pure-3.4.8.tgz#a4415834383784e81974cd34321daf36a6d2366e" + integrity sha1-pEFYNDg3hOgZdM00Mh2vNqbSNm4= core-js@2.6.0: version "2.6.0" - resolved "https://registry.npm.taobao.org/core-js/download/core-js-2.6.0.tgz?cache=0&sync_timestamp=1575309459245&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fcore-js%2Fdownload%2Fcore-js-2.6.0.tgz#1e30793e9ee5782b307e37ffa22da0eacddd84d4" + resolved "https://registry.npm.taobao.org/core-js/download/core-js-2.6.0.tgz?cache=0&sync_timestamp=1575839734912&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fcore-js%2Fdownload%2Fcore-js-2.6.0.tgz#1e30793e9ee5782b307e37ffa22da0eacddd84d4" integrity sha1-HjB5Pp7leCswfjf/oi2g6s3dhNQ= core-js@2.6.9: version "2.6.9" - resolved "https://registry.npm.taobao.org/core-js/download/core-js-2.6.9.tgz?cache=0&sync_timestamp=1575309459245&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fcore-js%2Fdownload%2Fcore-js-2.6.9.tgz#6b4b214620c834152e179323727fc19741b084f2" + resolved "https://registry.npm.taobao.org/core-js/download/core-js-2.6.9.tgz?cache=0&sync_timestamp=1575839734912&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fcore-js%2Fdownload%2Fcore-js-2.6.9.tgz#6b4b214620c834152e179323727fc19741b084f2" integrity sha1-a0shRiDINBUuF5Mjcn/Bl0GwhPI= core-js@3.1.4: version "3.1.4" - resolved "https://registry.npm.taobao.org/core-js/download/core-js-3.1.4.tgz?cache=0&sync_timestamp=1575309459245&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fcore-js%2Fdownload%2Fcore-js-3.1.4.tgz#3a2837fc48e582e1ae25907afcd6cf03b0cc7a07" + resolved "https://registry.npm.taobao.org/core-js/download/core-js-3.1.4.tgz?cache=0&sync_timestamp=1575839734912&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fcore-js%2Fdownload%2Fcore-js-3.1.4.tgz#3a2837fc48e582e1ae25907afcd6cf03b0cc7a07" integrity sha1-Oig3/EjlguGuJZB6/NbPA7DMegc= core-js@^1.0.0: version "1.2.7" - resolved "https://registry.npm.taobao.org/core-js/download/core-js-1.2.7.tgz?cache=0&sync_timestamp=1575309459245&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fcore-js%2Fdownload%2Fcore-js-1.2.7.tgz#652294c14651db28fa93bd2d5ff2983a4f08c636" + resolved "https://registry.npm.taobao.org/core-js/download/core-js-1.2.7.tgz?cache=0&sync_timestamp=1575839734912&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fcore-js%2Fdownload%2Fcore-js-1.2.7.tgz#652294c14651db28fa93bd2d5ff2983a4f08c636" integrity sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY= core-js@^2.4.0, core-js@^2.5.0, core-js@^2.6.5: - version "2.6.10" - resolved "https://registry.npm.taobao.org/core-js/download/core-js-2.6.10.tgz?cache=0&sync_timestamp=1575309459245&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fcore-js%2Fdownload%2Fcore-js-2.6.10.tgz#8a5b8391f8cc7013da703411ce5b585706300d7f" - integrity sha1-iluDkfjMcBPacDQRzltYVwYwDX8= + version "2.6.11" + resolved "https://registry.npm.taobao.org/core-js/download/core-js-2.6.11.tgz?cache=0&sync_timestamp=1575839734912&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fcore-js%2Fdownload%2Fcore-js-2.6.11.tgz#38831469f9922bded8ee21c9dc46985e0399308c" + integrity sha1-OIMUafmSK97Y7iHJ3EaYXgOZMIw= core-js@^3.0.0: - version "3.4.7" - resolved "https://registry.npm.taobao.org/core-js/download/core-js-3.4.7.tgz?cache=0&sync_timestamp=1575309459245&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fcore-js%2Fdownload%2Fcore-js-3.4.7.tgz#57c35937da80fe494fbc3adcf9cf3dc00eb86b34" - integrity sha1-V8NZN9qA/klPvDrc+c89wA64azQ= + version "3.4.8" + resolved "https://registry.npm.taobao.org/core-js/download/core-js-3.4.8.tgz?cache=0&sync_timestamp=1575839734912&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fcore-js%2Fdownload%2Fcore-js-3.4.8.tgz#e0fc0c61f2ef90cbc10c531dbffaa46dfb7152dd" + integrity sha1-4PwMYfLvkMvBDFMdv/qkbftxUt0= core-util-is@1.0.2, core-util-is@~1.0.0: version "1.0.2" @@ -5853,7 +5717,7 @@ css-to-react-native@^2.2.2: css-tree@1.0.0-alpha.37: version "1.0.0-alpha.37" - resolved "https://registry.npm.taobao.org/css-tree/download/css-tree-1.0.0-alpha.37.tgz#98bebd62c4c1d9f960ec340cf9f7522e30709a22" + resolved "https://registry.npm.taobao.org/css-tree/download/css-tree-1.0.0-alpha.37.tgz?cache=0&sync_timestamp=1575583542748&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fcss-tree%2Fdownload%2Fcss-tree-1.0.0-alpha.37.tgz#98bebd62c4c1d9f960ec340cf9f7522e30709a22" integrity sha1-mL69YsTB2flg7DQM+fdSLjBwmiI= dependencies: mdn-data "2.0.4" @@ -6048,7 +5912,7 @@ dateformat@^3.0.0, dateformat@^3.0.3: resolved "https://registry.npm.taobao.org/dateformat/download/dateformat-3.0.3.tgz#a6e37499a4d9a9cf85ef5872044d62901c9889ae" integrity sha1-puN0maTZqc+F71hyBE1ikByYia4= -debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.0, debug@^2.6.8, debug@^2.6.9: +debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.0, debug@^2.6.9: version "2.6.9" resolved "https://registry.npm.taobao.org/debug/download/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" integrity sha1-XRKFFd8TT/Mn6QpMk/Tgd6U2NB8= @@ -6127,7 +5991,7 @@ dedent@^0.7.0: deep-equal@^1.0.1: version "1.1.1" - resolved "https://registry.npm.taobao.org/deep-equal/download/deep-equal-1.1.1.tgz?cache=0&sync_timestamp=1575446437951&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fdeep-equal%2Fdownload%2Fdeep-equal-1.1.1.tgz#b5c98c942ceffaf7cb051e24e1434a25a2e6076a" + resolved "https://registry.npm.taobao.org/deep-equal/download/deep-equal-1.1.1.tgz?cache=0&sync_timestamp=1575872560040&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fdeep-equal%2Fdownload%2Fdeep-equal-1.1.1.tgz#b5c98c942ceffaf7cb051e24e1434a25a2e6076a" integrity sha1-tcmMlCzv+vfLBR4k4UNKJaLmB2o= dependencies: is-arguments "^1.0.4" @@ -6473,7 +6337,7 @@ dom-walk@^0.1.0: domain-browser@^1.1.1: version "1.2.0" - resolved "https://registry.npm.taobao.org/domain-browser/download/domain-browser-1.2.0.tgz?cache=0&sync_timestamp=1575171286896&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fdomain-browser%2Fdownload%2Fdomain-browser-1.2.0.tgz#3d31f50191a6749dd1375a7f522e823d42e54eda" + resolved "https://registry.npm.taobao.org/domain-browser/download/domain-browser-1.2.0.tgz?cache=0&sync_timestamp=1575879334171&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fdomain-browser%2Fdownload%2Fdomain-browser-1.2.0.tgz#3d31f50191a6749dd1375a7f522e823d42e54eda" integrity sha1-PTH1AZGmdJ3RN1p/Ui6CPULlTto= domelementtype@1, domelementtype@^1.3.0, domelementtype@^1.3.1: @@ -6488,7 +6352,7 @@ domelementtype@^2.0.1: domexception@^1.0.1: version "1.0.1" - resolved "https://registry.npm.taobao.org/domexception/download/domexception-1.0.1.tgz#937442644ca6a31261ef36e3ec677fe805582c90" + resolved "https://registry.npm.taobao.org/domexception/download/domexception-1.0.1.tgz?cache=0&sync_timestamp=1575743328502&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fdomexception%2Fdownload%2Fdomexception-1.0.1.tgz#937442644ca6a31261ef36e3ec677fe805582c90" integrity sha1-k3RCZEymoxJh7zbj7Gd/6AVYLJA= dependencies: webidl-conversions "^4.0.2" @@ -6657,7 +6521,7 @@ ejs@^2.6.1: resolved "https://registry.npm.taobao.org/ejs/download/ejs-2.7.4.tgz#48661287573dcc53e366c7a1ae52c3a120eec9ba" integrity sha1-SGYSh1c9zFPjZsehrlLDoSDuybo= -electron-to-chromium@^1.3.122, electron-to-chromium@^1.3.317: +electron-to-chromium@^1.3.122, electron-to-chromium@^1.3.322: version "1.3.322" resolved "https://registry.npm.taobao.org/electron-to-chromium/download/electron-to-chromium-1.3.322.tgz#a6f7e1c79025c2b05838e8e344f6e89eb83213a8" integrity sha1-pvfhx5AlwrBYOOjjRPbonrgyE6g= @@ -6821,9 +6685,9 @@ err-code@^1.0.0: integrity sha1-BuARbTAo9q70gGhJ6w6mp0iuaWA= errlop@^1.1.2: - version "1.6.0" - resolved "https://registry.npm.taobao.org/errlop/download/errlop-1.6.0.tgz#1d925f440b8727b6238b3972208823176d4e7c17" - integrity sha1-HZJfRAuHJ7YjizlyIIgjF21OfBc= + version "1.7.0" + resolved "https://registry.npm.taobao.org/errlop/download/errlop-1.7.0.tgz#6b1b35528f3056002ea560906617191fcf6d9fea" + integrity sha1-axs1Uo8wVgAupWCQZhcZH89tn+o= dependencies: editions "^2.2.0" @@ -6963,20 +6827,6 @@ eslint-config-airbnb@^17.1.0: object.assign "^4.1.0" object.entries "^1.1.0" -eslint-config-egg@^7.1.0: - version "7.5.1" - resolved "https://registry.npm.taobao.org/eslint-config-egg/download/eslint-config-egg-7.5.1.tgz#d62a345e1a541cce7601750941b5840c476e27cc" - integrity sha1-1io0XhpUHM52AXUJQbWEDEduJ8w= - dependencies: - "@typescript-eslint/eslint-plugin" "^2.0.0" - "@typescript-eslint/parser" "^2.0.0" - babel-eslint "^8.2.6" - eslint-plugin-eggache "^1.0.0" - eslint-plugin-import "^2.14.0" - eslint-plugin-jsdoc "^4.1.1" - eslint-plugin-jsx-a11y "^6.1.1" - eslint-plugin-react "^7.11.1" - eslint-config-prettier@^4.1.0: version "4.3.0" resolved "https://registry.npm.taobao.org/eslint-config-prettier/download/eslint-config-prettier-4.3.0.tgz?cache=0&sync_timestamp=1574144217269&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Feslint-config-prettier%2Fdownload%2Feslint-config-prettier-4.3.0.tgz#c55c1fcac8ce4518aeb77906984e134d9eb5a4f0" @@ -7037,12 +6887,12 @@ eslint-loader@2.1.2: object-hash "^1.1.4" rimraf "^2.6.1" -eslint-module-utils@^2.4.0: - version "2.4.1" - resolved "https://registry.npm.taobao.org/eslint-module-utils/download/eslint-module-utils-2.4.1.tgz#7b4675875bf96b0dbf1b21977456e5bb1f5e018c" - integrity sha1-e0Z1h1v5aw2/GyGXdFblux9eAYw= +eslint-module-utils@^2.4.0, eslint-module-utils@^2.4.1: + version "2.5.0" + resolved "https://registry.npm.taobao.org/eslint-module-utils/download/eslint-module-utils-2.5.0.tgz#cdf0b40d623032274ccd2abd7e64c4e524d6e19c" + integrity sha1-zfC0DWIwMidMzSq9fmTE5STW4Zw= dependencies: - debug "^2.6.8" + debug "^2.6.9" pkg-dir "^2.0.0" eslint-plugin-babel@^5.3.0: @@ -7065,11 +6915,6 @@ eslint-plugin-compat@^3.1.1: mdn-browser-compat-data "^0.0.84" semver "^6.1.2" -eslint-plugin-eggache@^1.0.0: - version "1.0.0" - resolved "https://registry.npm.taobao.org/eslint-plugin-eggache/download/eslint-plugin-eggache-1.0.0.tgz#1f8f98c698d2b511519fbdefbae78fe230487aa4" - integrity sha1-H4+YxpjStRFRn73vuueP4jBIeqQ= - eslint-plugin-eslint-comments@^3.1.1: version "3.1.2" resolved "https://registry.npm.taobao.org/eslint-plugin-eslint-comments/download/eslint-plugin-eslint-comments-3.1.2.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Feslint-plugin-eslint-comments%2Fdownload%2Feslint-plugin-eslint-comments-3.1.2.tgz#4ef6c488dbe06aa1627fea107b3e5d059fc8a395" @@ -7092,7 +6937,7 @@ eslint-plugin-flowtype@2.50.3, eslint-plugin-flowtype@^2.50.0: eslint-plugin-import@2.17.3: version "2.17.3" - resolved "https://registry.npm.taobao.org/eslint-plugin-import/download/eslint-plugin-import-2.17.3.tgz#00548b4434c18faebaba04b24ae6198f280de189" + resolved "https://registry.npm.taobao.org/eslint-plugin-import/download/eslint-plugin-import-2.17.3.tgz?cache=0&sync_timestamp=1575877019251&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Feslint-plugin-import%2Fdownload%2Feslint-plugin-import-2.17.3.tgz#00548b4434c18faebaba04b24ae6198f280de189" integrity sha1-AFSLRDTBj666ugSySuYZjygN4Yk= dependencies: array-includes "^3.0.3" @@ -7107,9 +6952,27 @@ eslint-plugin-import@2.17.3: read-pkg-up "^2.0.0" resolve "^1.11.0" -eslint-plugin-import@^2.14.0, eslint-plugin-import@^2.17.3, eslint-plugin-import@^2.18.2: +eslint-plugin-import@^2.14.0, eslint-plugin-import@^2.18.2: + version "2.19.1" + resolved "https://registry.npm.taobao.org/eslint-plugin-import/download/eslint-plugin-import-2.19.1.tgz?cache=0&sync_timestamp=1575877019251&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Feslint-plugin-import%2Fdownload%2Feslint-plugin-import-2.19.1.tgz#5654e10b7839d064dd0d46cd1b88ec2133a11448" + integrity sha1-VlThC3g50GTdDUbNG4jsITOhFEg= + dependencies: + array-includes "^3.0.3" + array.prototype.flat "^1.2.1" + contains-path "^0.1.0" + debug "^2.6.9" + doctrine "1.5.0" + eslint-import-resolver-node "^0.3.2" + eslint-module-utils "^2.4.1" + has "^1.0.3" + minimatch "^3.0.4" + object.values "^1.1.0" + read-pkg-up "^2.0.0" + resolve "^1.12.0" + +eslint-plugin-import@~2.18.2: version "2.18.2" - resolved "https://registry.npm.taobao.org/eslint-plugin-import/download/eslint-plugin-import-2.18.2.tgz#02f1180b90b077b33d447a17a2326ceb400aceb6" + resolved "https://registry.npm.taobao.org/eslint-plugin-import/download/eslint-plugin-import-2.18.2.tgz?cache=0&sync_timestamp=1575877019251&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Feslint-plugin-import%2Fdownload%2Feslint-plugin-import-2.18.2.tgz#02f1180b90b077b33d447a17a2326ceb400aceb6" integrity sha1-AvEYC5Cwd7M9RHoXojJs60AKzrY= dependencies: array-includes "^3.0.3" @@ -7131,15 +6994,6 @@ eslint-plugin-jest@^22.4.1: dependencies: "@typescript-eslint/experimental-utils" "^1.13.0" -eslint-plugin-jsdoc@^4.1.1: - version "4.8.4" - resolved "https://registry.npm.taobao.org/eslint-plugin-jsdoc/download/eslint-plugin-jsdoc-4.8.4.tgz?cache=0&sync_timestamp=1575441072311&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Feslint-plugin-jsdoc%2Fdownload%2Feslint-plugin-jsdoc-4.8.4.tgz#31f413c8a31fe656881398d8920b381bfdfac618" - integrity sha1-MfQTyKMf5laIE5jYkgs4G/36xhg= - dependencies: - comment-parser "^0.5.4" - jsdoctypeparser "3.1.0" - lodash "^4.17.11" - eslint-plugin-jsx-a11y@6.2.1: version "6.2.1" resolved "https://registry.npm.taobao.org/eslint-plugin-jsx-a11y/download/eslint-plugin-jsx-a11y-6.2.1.tgz#4ebba9f339b600ff415ae4166e3e2e008831cf0c" @@ -7167,7 +7021,7 @@ eslint-plugin-jsx-a11y@^5.1.1: emoji-regex "^6.1.0" jsx-ast-utils "^1.4.0" -eslint-plugin-jsx-a11y@^6.1.1, eslint-plugin-jsx-a11y@^6.2.0: +eslint-plugin-jsx-a11y@^6.2.0: version "6.2.3" resolved "https://registry.npm.taobao.org/eslint-plugin-jsx-a11y/download/eslint-plugin-jsx-a11y-6.2.3.tgz#b872a09d5de51af70a97db1eea7dc933043708aa" integrity sha1-uHKgnV3lGvcKl9se6n3JMwQ3CKo= @@ -7258,9 +7112,9 @@ eslint-rule-composer@^0.3.0: integrity sha1-eTIMknsMXA09PSt2yLSkiPJbuvk= eslint-rule-docs@^1.1.5: - version "1.1.168" - resolved "https://registry.npm.taobao.org/eslint-rule-docs/download/eslint-rule-docs-1.1.168.tgz#e74a1f656a60c73e81a88c98b127014a4e7dcc6a" - integrity sha1-50ofZWpgxz6BqIyYsScBSk59zGo= + version "1.1.169" + resolved "https://registry.npm.taobao.org/eslint-rule-docs/download/eslint-rule-docs-1.1.169.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Feslint-rule-docs%2Fdownload%2Feslint-rule-docs-1.1.169.tgz#12568ee08df4ca24d1f42071c0fe00fc18fbece9" + integrity sha1-ElaO4I30yiTR9CBxwP4A/Bj77Ok= eslint-scope@3.7.1: version "3.7.1" @@ -7766,11 +7620,11 @@ faye-websocket@~0.11.1: websocket-driver ">=0.5.1" fb-watchman@^2.0.0: - version "2.0.0" - resolved "https://registry.npm.taobao.org/fb-watchman/download/fb-watchman-2.0.0.tgz#54e9abf7dfa2f26cd9b1636c588c1afc05de5d58" - integrity sha1-VOmr99+i8mzZsWNsWIwa/AXeXVg= + version "2.0.1" + resolved "https://registry.npm.taobao.org/fb-watchman/download/fb-watchman-2.0.1.tgz#fc84fb39d2709cf3ff6d743706157bb5708a8a85" + integrity sha1-/IT7OdJwnPP/bXQ3BhV7tXCKioU= dependencies: - bser "^2.0.0" + bser "2.1.1" fbjs@^0.8.15, fbjs@^0.8.16, fbjs@^0.8.3, fbjs@^0.8.9: version "0.8.17" @@ -8044,14 +7898,14 @@ fork-ts-checker-webpack-plugin@1.1.1: tapable "^1.0.0" worker-rpc "^0.1.0" -fork-ts-checker-webpack-plugin@1.3.7: - version "1.3.7" - resolved "https://registry.npm.taobao.org/fork-ts-checker-webpack-plugin/download/fork-ts-checker-webpack-plugin-1.3.7.tgz?cache=0&sync_timestamp=1574695097241&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Ffork-ts-checker-webpack-plugin%2Fdownload%2Ffork-ts-checker-webpack-plugin-1.3.7.tgz#2b9d50f1abcafe2c0b9c6e8cce76ee384fa661a1" - integrity sha1-K51Q8avK/iwLnG6MznbuOE+mYaE= +fork-ts-checker-webpack-plugin@3.1.1: + version "3.1.1" + resolved "https://registry.npm.taobao.org/fork-ts-checker-webpack-plugin/download/fork-ts-checker-webpack-plugin-3.1.1.tgz?cache=0&sync_timestamp=1574695097241&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Ffork-ts-checker-webpack-plugin%2Fdownload%2Ffork-ts-checker-webpack-plugin-3.1.1.tgz#a1642c0d3e65f50c2cc1742e9c0a80f441f86b19" + integrity sha1-oWQsDT5l9QwswXQunAqA9EH4axk= dependencies: babel-code-frame "^6.22.0" chalk "^2.4.1" - chokidar "^2.0.4" + chokidar "^3.3.0" micromatch "^3.1.10" minimatch "^3.0.4" semver "^5.6.0" @@ -8172,7 +8026,7 @@ fsevents@^1.2.7: nan "^2.12.1" node-pre-gyp "^0.12.0" -fsevents@^2.0.6: +fsevents@^2.0.6, fsevents@~2.1.1: version "2.1.2" resolved "https://registry.npm.taobao.org/fsevents/download/fsevents-2.1.2.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Ffsevents%2Fdownload%2Ffsevents-2.1.2.tgz#4c0a1fb34bc68e543b4b82a9ec392bfbda840805" integrity sha1-TAofs0vGjlQ7S4Kp7Dkr+9qECAU= @@ -8399,7 +8253,7 @@ glob-parent@^3.1.0: is-glob "^3.1.0" path-dirname "^1.0.0" -glob-parent@^5.0.0, glob-parent@^5.1.0: +glob-parent@^5.0.0, glob-parent@^5.1.0, glob-parent@~5.1.0: version "5.1.0" resolved "https://registry.npm.taobao.org/glob-parent/download/glob-parent-5.1.0.tgz#5f4c1d1e748d30cd73ad2944b3577a81b081e8c2" integrity sha1-X0wdHnSNMM1zrSlEs1d6gbCB6MI= @@ -8556,7 +8410,7 @@ gonzales-pe@^4.2.3, gonzales-pe@^4.2.4: got@9.6.0, got@^9.6.0: version "9.6.0" - resolved "https://registry.npm.taobao.org/got/download/got-9.6.0.tgz#edf45e7d67f99545705de1f7bbeeeb121765ed85" + resolved "https://registry.npm.taobao.org/got/download/got-9.6.0.tgz?cache=0&sync_timestamp=1575745490283&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fgot%2Fdownload%2Fgot-9.6.0.tgz#edf45e7d67f99545705de1f7bbeeeb121765ed85" integrity sha1-7fRefWf5lUVwXeH3u+7rEhdl7YU= dependencies: "@sindresorhus/is" "^0.14.0" @@ -8573,7 +8427,7 @@ got@9.6.0, got@^9.6.0: got@^6.2.0: version "6.7.1" - resolved "https://registry.npm.taobao.org/got/download/got-6.7.1.tgz#240cd05785a9a18e561dc1b44b41c763ef1e8db0" + resolved "https://registry.npm.taobao.org/got/download/got-6.7.1.tgz?cache=0&sync_timestamp=1575745490283&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fgot%2Fdownload%2Fgot-6.7.1.tgz#240cd05785a9a18e561dc1b44b41c763ef1e8db0" integrity sha1-JAzQV4WpoY5WHcG0S0HHY+8ejbA= dependencies: create-error-class "^3.0.0" @@ -8833,7 +8687,7 @@ highlight.js@^9.6.0: history@^4.7.2, history@^4.9.0: version "4.10.1" - resolved "https://registry.npm.taobao.org/history/download/history-4.10.1.tgz#33371a65e3a83b267434e2b3f3b1b4c58aad4cf3" + resolved "https://registry.npm.taobao.org/history/download/history-4.10.1.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fhistory%2Fdownload%2Fhistory-4.10.1.tgz#33371a65e3a83b267434e2b3f3b1b4c58aad4cf3" integrity sha1-MzcaZeOoOyZ0NOKz87G0xYqtTPM= dependencies: "@babel/runtime" "^7.1.2" @@ -9567,7 +9421,7 @@ is-binary-path@^1.0.0: dependencies: binary-extensions "^1.0.0" -is-binary-path@^2.1.0: +is-binary-path@^2.1.0, is-binary-path@~2.1.0: version "2.1.0" resolved "https://registry.npm.taobao.org/is-binary-path/download/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" integrity sha1-6h9/O4DwZCNug0cPhsCcJU+0Wwk= @@ -9713,7 +9567,7 @@ is-glob@^3.1.0: dependencies: is-extglob "^2.1.0" -is-glob@^4.0.0, is-glob@^4.0.1: +is-glob@^4.0.0, is-glob@^4.0.1, is-glob@~4.0.1: version "4.0.1" resolved "https://registry.npm.taobao.org/is-glob/download/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc" integrity sha1-dWfb6fL14kZ7x3q4PEopSCQHpdw= @@ -10036,7 +9890,7 @@ istanbul-lib-coverage@^2.0.2, istanbul-lib-coverage@^2.0.5: istanbul-lib-instrument@^3.0.1, istanbul-lib-instrument@^3.3.0: version "3.3.0" - resolved "https://registry.npm.taobao.org/istanbul-lib-instrument/download/istanbul-lib-instrument-3.3.0.tgz?cache=0&sync_timestamp=1572639564775&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fistanbul-lib-instrument%2Fdownload%2Fistanbul-lib-instrument-3.3.0.tgz#a5f63d91f0bbc0c3e479ef4c5de027335ec6d630" + resolved "https://registry.npm.taobao.org/istanbul-lib-instrument/download/istanbul-lib-instrument-3.3.0.tgz#a5f63d91f0bbc0c3e479ef4c5de027335ec6d630" integrity sha1-pfY9kfC7wMPkee9MXeAnM17G1jA= dependencies: "@babel/generator" "^7.4.0" @@ -10069,14 +9923,14 @@ istanbul-lib-source-maps@^3.0.1: istanbul-reports@^2.2.6: version "2.2.6" - resolved "https://registry.npm.taobao.org/istanbul-reports/download/istanbul-reports-2.2.6.tgz?cache=0&sync_timestamp=1574447130657&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fistanbul-reports%2Fdownload%2Fistanbul-reports-2.2.6.tgz#7b4f2660d82b29303a8fe6091f8ca4bf058da1af" + resolved "https://registry.npm.taobao.org/istanbul-reports/download/istanbul-reports-2.2.6.tgz#7b4f2660d82b29303a8fe6091f8ca4bf058da1af" integrity sha1-e08mYNgrKTA6j+YJH4ykvwWNoa8= dependencies: handlebars "^4.1.2" istextorbinary@^2.5.1: version "2.6.0" - resolved "https://registry.npm.taobao.org/istextorbinary/download/istextorbinary-2.6.0.tgz?cache=0&sync_timestamp=1575216282897&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fistextorbinary%2Fdownload%2Fistextorbinary-2.6.0.tgz#60776315fb0fa3999add276c02c69557b9ca28ab" + resolved "https://registry.npm.taobao.org/istextorbinary/download/istextorbinary-2.6.0.tgz?cache=0&sync_timestamp=1575884001558&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fistextorbinary%2Fdownload%2Fistextorbinary-2.6.0.tgz#60776315fb0fa3999add276c02c69557b9ca28ab" integrity sha1-YHdjFfsPo5ma3SdsAsaVV7nKKKs= dependencies: binaryextensions "^2.1.2" @@ -10481,16 +10335,16 @@ js-levenshtein@^1.1.3: resolved "https://registry.npm.taobao.org/js-levenshtein/download/js-levenshtein-1.1.6.tgz#c6cee58eb3550372df8deb85fad5ce66ce01d59d" integrity sha1-xs7ljrNVA3LfjeuF+tXOZs4B1Z0= -js-tokens@^3.0.0, js-tokens@^3.0.2: - version "3.0.2" - resolved "https://registry.npm.taobao.org/js-tokens/download/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b" - integrity sha1-mGbfOVECEw449/mWvOtlRDIJwls= - "js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.npm.taobao.org/js-tokens/download/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" integrity sha1-GSA/tZmR35jjoocFDUZHzerzJJk= +js-tokens@^3.0.2: + version "3.0.2" + resolved "https://registry.npm.taobao.org/js-tokens/download/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b" + integrity sha1-mGbfOVECEw449/mWvOtlRDIJwls= + js-yaml@3.13.1, js-yaml@^3.13.0, js-yaml@^3.13.1: version "3.13.1" resolved "https://registry.npm.taobao.org/js-yaml/download/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847" @@ -10504,11 +10358,6 @@ jsbn@~0.1.0: resolved "https://registry.npm.taobao.org/jsbn/download/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM= -jsdoctypeparser@3.1.0: - version "3.1.0" - resolved "https://registry.npm.taobao.org/jsdoctypeparser/download/jsdoctypeparser-3.1.0.tgz#2f65f75165c4d9c632bb4fda13ed36b78321a43b" - integrity sha1-L2X3UWXE2cYyu0/aE+02t4MhpDs= - jsdom@>=11.0.0: version "15.2.1" resolved "https://registry.npm.taobao.org/jsdom/download/jsdom-15.2.1.tgz?cache=0&sync_timestamp=1572837968320&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fjsdom%2Fdownload%2Fjsdom-15.2.1.tgz#d2feb1aef7183f86be521b8c6833ff5296d07ec5" @@ -11251,7 +11100,7 @@ lodash@4.17.14: resolved "https://registry.npm.taobao.org/lodash/download/lodash-4.17.14.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Flodash%2Fdownload%2Flodash-4.17.14.tgz#9ce487ae66c96254fe20b599f21b6816028078ba" integrity sha1-nOSHrmbJYlT+ILWZ8htoFgKAeLo= -"lodash@>=3.5 <5", lodash@^4.0.1, lodash@^4.15.0, lodash@^4.16.5, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.2, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.2.0, lodash@^4.2.1: +"lodash@>=3.5 <5", lodash@^4.0.1, lodash@^4.15.0, lodash@^4.16.5, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.2, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.2.1: version "4.17.15" resolved "https://registry.npm.taobao.org/lodash/download/lodash-4.17.15.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Flodash%2Fdownload%2Flodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" integrity sha1-tEf2ZwoEVbv+7dETku/zMOoJdUg= @@ -11544,7 +11393,7 @@ mem-fs@^1.1.0: mem@^1.1.0: version "1.1.0" - resolved "https://registry.npm.taobao.org/mem/download/mem-1.1.0.tgz#5edd52b485ca1d900fe64895505399a0dfa45f76" + resolved "https://registry.npm.taobao.org/mem/download/mem-1.1.0.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fmem%2Fdownload%2Fmem-1.1.0.tgz#5edd52b485ca1d900fe64895505399a0dfa45f76" integrity sha1-Xt1StIXKHZAP5kiVUFOZoN+kX3Y= dependencies: mimic-fn "^1.0.0" @@ -11595,7 +11444,7 @@ memorystream@^0.3.1: meow@5.0.0, meow@^5.0.0: version "5.0.0" - resolved "https://registry.npm.taobao.org/meow/download/meow-5.0.0.tgz#dfc73d63a9afc714a5e371760eb5c88b91078aa4" + resolved "https://registry.npm.taobao.org/meow/download/meow-5.0.0.tgz?cache=0&sync_timestamp=1575730464003&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fmeow%2Fdownload%2Fmeow-5.0.0.tgz#dfc73d63a9afc714a5e371760eb5c88b91078aa4" integrity sha1-38c9Y6mvxxSl43F2DrXIi5EHiqQ= dependencies: camelcase-keys "^4.0.0" @@ -11610,7 +11459,7 @@ meow@5.0.0, meow@^5.0.0: meow@^3.3.0: version "3.7.0" - resolved "https://registry.npm.taobao.org/meow/download/meow-3.7.0.tgz#72cb668b425228290abbfa856892587308a801fb" + resolved "https://registry.npm.taobao.org/meow/download/meow-3.7.0.tgz?cache=0&sync_timestamp=1575730464003&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fmeow%2Fdownload%2Fmeow-3.7.0.tgz#72cb668b425228290abbfa856892587308a801fb" integrity sha1-cstmi0JSKCkKu/qFaJJYcwioAfs= dependencies: camelcase-keys "^2.0.0" @@ -11626,7 +11475,7 @@ meow@^3.3.0: meow@^4.0.0: version "4.0.1" - resolved "https://registry.npm.taobao.org/meow/download/meow-4.0.1.tgz#d48598f6f4b1472f35bf6317a95945ace347f975" + resolved "https://registry.npm.taobao.org/meow/download/meow-4.0.1.tgz?cache=0&sync_timestamp=1575730464003&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fmeow%2Fdownload%2Fmeow-4.0.1.tgz#d48598f6f4b1472f35bf6317a95945ace347f975" integrity sha1-1IWY9vSxRy81v2MXqVlFrONH+XU= dependencies: camelcase-keys "^4.0.0" @@ -12124,7 +11973,7 @@ nise@^1.5.2: no-case@^2.2.0: version "2.3.2" - resolved "https://registry.npm.taobao.org/no-case/download/no-case-2.3.2.tgz#60b813396be39b3f1288a4c1ed5d1e7d28b464ac" + resolved "https://registry.npm.taobao.org/no-case/download/no-case-2.3.2.tgz?cache=0&sync_timestamp=1575601561695&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fno-case%2Fdownload%2Fno-case-2.3.2.tgz#60b813396be39b3f1288a4c1ed5d1e7d28b464ac" integrity sha1-YLgTOWvjmz8SiKTB7V0efSi0ZKw= dependencies: lower-case "^1.1.1" @@ -12269,7 +12118,7 @@ node-pty@^0.10.0-beta3: dependencies: nan "^2.14.0" -node-releases@^1.1.13, node-releases@^1.1.41: +node-releases@^1.1.13, node-releases@^1.1.42: version "1.1.42" resolved "https://registry.npm.taobao.org/node-releases/download/node-releases-1.1.42.tgz#a999f6a62f8746981f6da90627a8d2fc090bbad7" integrity sha1-qZn2pi+HRpgfbakGJ6jS/AkLutc= @@ -12313,7 +12162,7 @@ normalize-path@^2.1.1: dependencies: remove-trailing-separator "^1.0.1" -normalize-path@^3.0.0: +normalize-path@^3.0.0, normalize-path@~3.0.0: version "3.0.0" resolved "https://registry.npm.taobao.org/normalize-path/download/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" integrity sha1-Dc1p/yOhybEf0JeDFmRKA4ghamU= @@ -12354,9 +12203,9 @@ normalize.css@^7.0.0: integrity sha1-q/sd2CRwZ04DIrU86xqvQSk45L8= npm-bundled@^1.0.1: - version "1.0.6" - resolved "https://registry.npm.taobao.org/npm-bundled/download/npm-bundled-1.0.6.tgz#e7ba9aadcef962bb61248f91721cd932b3fe6bdd" - integrity sha1-57qarc75YrthJI+RchzZMrP+a90= + version "1.1.0" + resolved "https://registry.npm.taobao.org/npm-bundled/download/npm-bundled-1.1.0.tgz?cache=0&sync_timestamp=1575764664545&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fnpm-bundled%2Fdownload%2Fnpm-bundled-1.1.0.tgz#2e8fdb7e69eff2df963937b696243316537c284b" + integrity sha1-Lo/bfmnv8t+WOTe2liQzFlN8KEs= npm-lifecycle@^3.1.2: version "3.1.4" @@ -12702,7 +12551,7 @@ os-homedir@^1.0.0: os-locale@^2.0.0: version "2.1.0" - resolved "https://registry.npm.taobao.org/os-locale/download/os-locale-2.1.0.tgz#42bc2900a6b5b8bd17376c8e882b65afccf24bf2" + resolved "https://registry.npm.taobao.org/os-locale/download/os-locale-2.1.0.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fos-locale%2Fdownload%2Fos-locale-2.1.0.tgz#42bc2900a6b5b8bd17376c8e882b65afccf24bf2" integrity sha1-QrwpAKa1uL0XN2yOiCtlr8zyS/I= dependencies: execa "^0.7.0" @@ -13137,7 +12986,7 @@ performance-now@^2.1.0: resolved "https://registry.npm.taobao.org/performance-now/download/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns= -picomatch@^2.0.4, picomatch@^2.0.5: +picomatch@^2.0.4, picomatch@^2.0.5, picomatch@^2.0.7: version "2.1.1" resolved "https://registry.npm.taobao.org/picomatch/download/picomatch-2.1.1.tgz#ecdfbea7704adb5fe6fb47f9866c4c0e15e905c5" integrity sha1-7N++p3BK21/m+0f5hmxMDhXpBcU= @@ -13755,7 +13604,7 @@ postcss-value-parser@^4.0.2: postcss@7.0.17: version "7.0.17" - resolved "https://registry.npm.taobao.org/postcss/download/postcss-7.0.17.tgz#4da1bdff5322d4a0acaab4d87f3e782436bad31f" + resolved "https://registry.npm.taobao.org/postcss/download/postcss-7.0.17.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fpostcss%2Fdownload%2Fpostcss-7.0.17.tgz#4da1bdff5322d4a0acaab4d87f3e782436bad31f" integrity sha1-TaG9/1Mi1KCsqrTYfz54JDa60x8= dependencies: chalk "^2.4.2" @@ -13764,7 +13613,7 @@ postcss@7.0.17: postcss@^5.0.21: version "5.2.18" - resolved "https://registry.npm.taobao.org/postcss/download/postcss-5.2.18.tgz#badfa1497d46244f6390f58b319830d9107853c5" + resolved "https://registry.npm.taobao.org/postcss/download/postcss-5.2.18.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fpostcss%2Fdownload%2Fpostcss-5.2.18.tgz#badfa1497d46244f6390f58b319830d9107853c5" integrity sha1-ut+hSX1GJE9jkPWLMZgw2RB4U8U= dependencies: chalk "^1.1.3" @@ -13774,7 +13623,7 @@ postcss@^5.0.21: postcss@^6.0.1, postcss@^6.0.23: version "6.0.23" - resolved "https://registry.npm.taobao.org/postcss/download/postcss-6.0.23.tgz#61c82cc328ac60e677645f979054eb98bc0e3324" + resolved "https://registry.npm.taobao.org/postcss/download/postcss-6.0.23.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fpostcss%2Fdownload%2Fpostcss-6.0.23.tgz#61c82cc328ac60e677645f979054eb98bc0e3324" integrity sha1-YcgswyisYOZ3ZF+XkFTrmLwOMyQ= dependencies: chalk "^2.4.1" @@ -13782,9 +13631,9 @@ postcss@^6.0.1, postcss@^6.0.23: supports-color "^5.4.0" postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.13, postcss@^7.0.14, postcss@^7.0.16, postcss@^7.0.17, postcss@^7.0.2, postcss@^7.0.21, postcss@^7.0.23, postcss@^7.0.5, postcss@^7.0.6, postcss@^7.0.7: - version "7.0.23" - resolved "https://registry.npm.taobao.org/postcss/download/postcss-7.0.23.tgz#9f9759fad661b15964f3cfc3140f66f1e05eadc1" - integrity sha1-n5dZ+tZhsVlk88/DFA9m8eBercE= + version "7.0.24" + resolved "https://registry.npm.taobao.org/postcss/download/postcss-7.0.24.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fpostcss%2Fdownload%2Fpostcss-7.0.24.tgz#972c3c5be431b32e40caefe6c81b5a19117704c2" + integrity sha1-lyw8W+Qxsy5Ayu/myBtaGRF3BMI= dependencies: chalk "^2.4.2" source-map "^0.6.1" @@ -14035,9 +13884,9 @@ pseudomap@^1.0.2: integrity sha1-8FKijacOYYkX7wqKw0wa5aaChrM= psl@^1.1.24, psl@^1.1.28: - version "1.5.0" - resolved "https://registry.npm.taobao.org/psl/download/psl-1.5.0.tgz#47fd1292def7fdb1e138cd78afa8814cebcf7b13" - integrity sha1-R/0Skt73/bHhOM14r6iBTOvPexM= + version "1.6.0" + resolved "https://registry.npm.taobao.org/psl/download/psl-1.6.0.tgz#60557582ee23b6c43719d9890fb4170ecd91e110" + integrity sha1-YFV1gu4jtsQ3GdmJD7QXDs2R4RA= public-encrypt@^4.0.0: version "4.0.3" @@ -14582,10 +14431,10 @@ rc-switch@~1.9.0: prop-types "^15.5.6" react-lifecycles-compat "^3.0.4" -rc-table@~6.9.4: - version "6.9.5" - resolved "https://registry.npm.taobao.org/rc-table/download/rc-table-6.9.5.tgz#b7394c3780d4ad2398d08d2059aba454f195273f" - integrity sha1-tzlMN4DUrSOY0I0gWaukVPGVJz8= +rc-table@~6.10.5: + version "6.10.5" + resolved "https://registry.npm.taobao.org/rc-table/download/rc-table-6.10.5.tgz#5adb55839f90f7fcc17f08eb703d8f1feedec921" + integrity sha1-WttVg5+Q9/zBfwjrcD2PH+7eySE= dependencies: classnames "^2.2.5" component-classes "^1.2.6" @@ -14703,9 +14552,9 @@ rc-trigger@^4.0.0-alpha.4: rc-util "^4.13.0" rc-upload@~2.9.1: - version "2.9.3" - resolved "https://registry.npm.taobao.org/rc-upload/download/rc-upload-2.9.3.tgz?cache=0&sync_timestamp=1574828561031&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Frc-upload%2Fdownload%2Frc-upload-2.9.3.tgz#2dc1c39250159d348ec7afc7c88bff7164d9c128" - integrity sha1-LcHDklAVnTSOx6/HyIv/cWTZwSg= + version "2.9.4" + resolved "https://registry.npm.taobao.org/rc-upload/download/rc-upload-2.9.4.tgz#8e34a73a468d7907fe31982c38100e4593857d32" + integrity sha1-jjSnOkaNeQf+MZgsOBAORZOFfTI= dependencies: babel-runtime "6.x" classnames "^2.2.5" @@ -14980,7 +14829,7 @@ read-package-tree@^5.1.6: read-pkg-up@^1.0.1: version "1.0.1" - resolved "https://registry.npm.taobao.org/read-pkg-up/download/read-pkg-up-1.0.1.tgz#9d63c13276c065918d57f002a57f40a1b643fb02" + resolved "https://registry.npm.taobao.org/read-pkg-up/download/read-pkg-up-1.0.1.tgz?cache=0&sync_timestamp=1575620436254&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fread-pkg-up%2Fdownload%2Fread-pkg-up-1.0.1.tgz#9d63c13276c065918d57f002a57f40a1b643fb02" integrity sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI= dependencies: find-up "^1.0.0" @@ -14988,7 +14837,7 @@ read-pkg-up@^1.0.1: read-pkg-up@^2.0.0: version "2.0.0" - resolved "https://registry.npm.taobao.org/read-pkg-up/download/read-pkg-up-2.0.0.tgz#6b72a8048984e0c41e79510fd5e9fa99b3b549be" + resolved "https://registry.npm.taobao.org/read-pkg-up/download/read-pkg-up-2.0.0.tgz?cache=0&sync_timestamp=1575620436254&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fread-pkg-up%2Fdownload%2Fread-pkg-up-2.0.0.tgz#6b72a8048984e0c41e79510fd5e9fa99b3b549be" integrity sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4= dependencies: find-up "^2.0.0" @@ -14996,7 +14845,7 @@ read-pkg-up@^2.0.0: read-pkg-up@^3.0.0: version "3.0.0" - resolved "https://registry.npm.taobao.org/read-pkg-up/download/read-pkg-up-3.0.0.tgz#3ed496685dba0f8fe118d0691dc51f4a1ff96f07" + resolved "https://registry.npm.taobao.org/read-pkg-up/download/read-pkg-up-3.0.0.tgz?cache=0&sync_timestamp=1575620436254&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fread-pkg-up%2Fdownload%2Fread-pkg-up-3.0.0.tgz#3ed496685dba0f8fe118d0691dc51f4a1ff96f07" integrity sha1-PtSWaF26D4/hGNBpHcUfSh/5bwc= dependencies: find-up "^2.0.0" @@ -15004,7 +14853,7 @@ read-pkg-up@^3.0.0: read-pkg-up@^4.0.0: version "4.0.0" - resolved "https://registry.npm.taobao.org/read-pkg-up/download/read-pkg-up-4.0.0.tgz#1b221c6088ba7799601c808f91161c66e58f8978" + resolved "https://registry.npm.taobao.org/read-pkg-up/download/read-pkg-up-4.0.0.tgz?cache=0&sync_timestamp=1575620436254&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fread-pkg-up%2Fdownload%2Fread-pkg-up-4.0.0.tgz#1b221c6088ba7799601c808f91161c66e58f8978" integrity sha1-GyIcYIi6d5lgHICPkRYcZuWPiXg= dependencies: find-up "^3.0.0" @@ -15012,7 +14861,7 @@ read-pkg-up@^4.0.0: read-pkg-up@^5.0.0: version "5.0.0" - resolved "https://registry.npm.taobao.org/read-pkg-up/download/read-pkg-up-5.0.0.tgz#b6a6741cb144ed3610554f40162aa07a6db621b8" + resolved "https://registry.npm.taobao.org/read-pkg-up/download/read-pkg-up-5.0.0.tgz?cache=0&sync_timestamp=1575620436254&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fread-pkg-up%2Fdownload%2Fread-pkg-up-5.0.0.tgz#b6a6741cb144ed3610554f40162aa07a6db621b8" integrity sha1-tqZ0HLFE7TYQVU9AFiqgem22Ibg= dependencies: find-up "^3.0.0" @@ -15114,6 +14963,13 @@ readdirp@^2.2.1: readable-stream "^2.0.2" readdirp@^3.1.1: + version "3.3.0" + resolved "https://registry.npm.taobao.org/readdirp/download/readdirp-3.3.0.tgz#984458d13a1e42e2e9f5841b129e162f369aff17" + integrity sha1-mERY0ToeQuLp9YQbEp4WLzaa/xc= + dependencies: + picomatch "^2.0.7" + +readdirp@~3.2.0: version "3.2.0" resolved "https://registry.npm.taobao.org/readdirp/download/readdirp-3.2.0.tgz#c30c33352b12c96dfb4b895421a49fd5a9593839" integrity sha1-wwwzNSsSyW37S4lUIaSf1alZODk= @@ -15251,11 +15107,6 @@ regexpp@^2.0.1: resolved "https://registry.npm.taobao.org/regexpp/download/regexpp-2.0.1.tgz?cache=0&sync_timestamp=1567160849322&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fregexpp%2Fdownload%2Fregexpp-2.0.1.tgz#8d19d31cf632482b589049f8281f93dbcba4d07f" integrity sha1-jRnTHPYySCtYkEn4KB+T28uk0H8= -regexpp@^3.0.0: - version "3.0.0" - resolved "https://registry.npm.taobao.org/regexpp/download/regexpp-3.0.0.tgz?cache=0&sync_timestamp=1567160849322&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fregexpp%2Fdownload%2Fregexpp-3.0.0.tgz#dd63982ee3300e67b41c1956f850aa680d9d330e" - integrity sha1-3WOYLuMwDme0HBlW+FCqaA2dMw4= - regexpu-core@^1.0.0: version "1.0.0" resolved "https://registry.npm.taobao.org/regexpu-core/download/regexpu-core-1.0.0.tgz?cache=0&sync_timestamp=1568375270709&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fregexpu-core%2Fdownload%2Fregexpu-core-1.0.0.tgz#86a763f58ee4d7c2f6b102e4764050de7ed90c6b" @@ -15584,7 +15435,7 @@ resolve@1.11.0: dependencies: path-parse "^1.0.6" -resolve@1.x, resolve@^1.1.6, resolve@^1.10.0, resolve@^1.10.1, resolve@^1.11.0, resolve@^1.13.1, resolve@^1.3.2, resolve@^1.4.0, resolve@^1.5.0, resolve@^1.8.1: +resolve@1.x, resolve@^1.1.6, resolve@^1.10.0, resolve@^1.10.1, resolve@^1.11.0, resolve@^1.12.0, resolve@^1.13.1, resolve@^1.3.2, resolve@^1.4.0, resolve@^1.5.0, resolve@^1.8.1: version "1.13.1" resolved "https://registry.npm.taobao.org/resolve/download/resolve-1.13.1.tgz#be0aa4c06acd53083505abb35f4d66932ab35d16" integrity sha1-vgqkwGrNUwg1BauzX01mkyqzXRY= @@ -16028,16 +15879,21 @@ send@0.17.1: range-parser "~1.2.1" statuses "~1.5.0" -serialize-javascript@1.7.0: - version "1.7.0" - resolved "https://registry.npm.taobao.org/serialize-javascript/download/serialize-javascript-1.7.0.tgz#d6e0dfb2a3832a8c94468e6eb1db97e55a192a65" - integrity sha1-1uDfsqODKoyURo5usduX5VoZKmU= +serialize-javascript@2.1.1: + version "2.1.1" + resolved "https://registry.npm.taobao.org/serialize-javascript/download/serialize-javascript-2.1.1.tgz#952907a04a3e3a75af7f73d92d15e233862048b2" + integrity sha1-lSkHoEo+OnWvf3PZLRXiM4YgSLI= serialize-javascript@^1.4.0, serialize-javascript@^1.7.0: version "1.9.1" resolved "https://registry.npm.taobao.org/serialize-javascript/download/serialize-javascript-1.9.1.tgz#cfc200aef77b600c47da9bb8149c943e798c2fdb" integrity sha1-z8IArvd7YAxH2pu4FJyUPnmML9s= +serialize-javascript@^2.1.1: + version "2.1.2" + resolved "https://registry.npm.taobao.org/serialize-javascript/download/serialize-javascript-2.1.2.tgz#ecec53b0e0317bdc95ef76ab7074b7384785fa61" + integrity sha1-7OxTsOAxe9yV73arcHS3OEeF+mE= + serve-index@^1.7.2: version "1.9.1" resolved "https://registry.npm.taobao.org/serve-index/download/serve-index-1.9.1.tgz#d3768d69b1e7d82e5ce050fff5b453bea12a9239" @@ -16117,9 +15973,9 @@ shallow-clone@^3.0.0: kind-of "^6.0.2" shallow-equal@^1.0.0: - version "1.2.0" - resolved "https://registry.npm.taobao.org/shallow-equal/download/shallow-equal-1.2.0.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fshallow-equal%2Fdownload%2Fshallow-equal-1.2.0.tgz#fd828d2029ff4e19569db7e19e535e94e2d1f5cc" - integrity sha1-/YKNICn/ThlWnbfhnlNelOLR9cw= + version "1.2.1" + resolved "https://registry.npm.taobao.org/shallow-equal/download/shallow-equal-1.2.1.tgz?cache=0&sync_timestamp=1575626600353&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fshallow-equal%2Fdownload%2Fshallow-equal-1.2.1.tgz#4c16abfa56043aa20d050324efa68940b0da79da" + integrity sha1-TBar+lYEOqINBQMk76aJQLDaedo= shallowequal@^1.0.1, shallowequal@^1.0.2, shallowequal@^1.1.0: version "1.1.0" @@ -17314,7 +17170,7 @@ terminal-link@1.3.0: terser-webpack-plugin@1.3.0: version "1.3.0" - resolved "https://registry.npm.taobao.org/terser-webpack-plugin/download/terser-webpack-plugin-1.3.0.tgz?cache=0&sync_timestamp=1571751801699&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fterser-webpack-plugin%2Fdownload%2Fterser-webpack-plugin-1.3.0.tgz#69aa22426299f4b5b3775cbed8cb2c5d419aa1d4" + resolved "https://registry.npm.taobao.org/terser-webpack-plugin/download/terser-webpack-plugin-1.3.0.tgz#69aa22426299f4b5b3775cbed8cb2c5d419aa1d4" integrity sha1-aaoiQmKZ9LWzd1y+2MssXUGaodQ= dependencies: cacache "^11.3.2" @@ -17329,15 +17185,15 @@ terser-webpack-plugin@1.3.0: worker-farm "^1.7.0" terser-webpack-plugin@^1.4.1: - version "1.4.1" - resolved "https://registry.npm.taobao.org/terser-webpack-plugin/download/terser-webpack-plugin-1.4.1.tgz?cache=0&sync_timestamp=1571751801699&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fterser-webpack-plugin%2Fdownload%2Fterser-webpack-plugin-1.4.1.tgz#61b18e40eaee5be97e771cdbb10ed1280888c2b4" - integrity sha1-YbGOQOruW+l+dxzbsQ7RKAiIwrQ= + version "1.4.2" + resolved "https://registry.npm.taobao.org/terser-webpack-plugin/download/terser-webpack-plugin-1.4.2.tgz#e23c0d554587d1f473bd0cf68627720e733890a4" + integrity sha1-4jwNVUWH0fRzvQz2hidyDnM4kKQ= dependencies: cacache "^12.0.2" find-cache-dir "^2.1.0" is-wsl "^1.1.0" schema-utils "^1.0.0" - serialize-javascript "^1.7.0" + serialize-javascript "^2.1.1" source-map "^0.6.1" terser "^4.1.2" webpack-sources "^1.4.0" @@ -17354,7 +17210,7 @@ terser@^4.0.0, terser@^4.1.2: test-exclude@^5.2.3: version "5.2.3" - resolved "https://registry.npm.taobao.org/test-exclude/download/test-exclude-5.2.3.tgz#c3d3e1e311eb7ee405e092dac10aefd09091eac0" + resolved "https://registry.npm.taobao.org/test-exclude/download/test-exclude-5.2.3.tgz?cache=0&sync_timestamp=1575851928582&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Ftest-exclude%2Fdownload%2Ftest-exclude-5.2.3.tgz#c3d3e1e311eb7ee405e092dac10aefd09091eac0" integrity sha1-w9Ph4xHrfuQF4JLawQrv0JCR6sA= dependencies: glob "^7.1.3" @@ -17837,12 +17693,12 @@ typedarray@^0.0.6: typescript@3.7.2: version "3.7.2" - resolved "https://registry.npm.taobao.org/typescript/download/typescript-3.7.2.tgz?cache=0&sync_timestamp=1575557703512&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Ftypescript%2Fdownload%2Ftypescript-3.7.2.tgz#27e489b95fa5909445e9fef5ee48d81697ad18fb" + resolved "https://registry.npm.taobao.org/typescript/download/typescript-3.7.2.tgz#27e489b95fa5909445e9fef5ee48d81697ad18fb" integrity sha1-J+SJuV+lkJRF6f717kjYFpetGPs= typescript@^3.5.3, typescript@^3.7.2: version "3.7.3" - resolved "https://registry.npm.taobao.org/typescript/download/typescript-3.7.3.tgz?cache=0&sync_timestamp=1575557703512&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Ftypescript%2Fdownload%2Ftypescript-3.7.3.tgz#b36840668a16458a7025b9eabfad11b66ab85c69" + resolved "https://registry.npm.taobao.org/typescript/download/typescript-3.7.3.tgz#b36840668a16458a7025b9eabfad11b66ab85c69" integrity sha1-s2hAZooWRYpwJbnqv60Rtmq4XGk= ua-parser-js@^0.7.18, ua-parser-js@^0.7.20: @@ -17859,9 +17715,9 @@ uglify-es@^3.3.4: source-map "~0.6.1" uglify-js@^3.1.4, uglify-js@^3.5.1: - version "3.7.1" - resolved "https://registry.npm.taobao.org/uglify-js/download/uglify-js-3.7.1.tgz?cache=0&sync_timestamp=1575088300011&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fuglify-js%2Fdownload%2Fuglify-js-3.7.1.tgz#35c7de17971a4aa7689cd2eae0a5b39bb838c0c5" - integrity sha1-NcfeF5caSqdonNLq4KWzm7g4wMU= + version "3.7.2" + resolved "https://registry.npm.taobao.org/uglify-js/download/uglify-js-3.7.2.tgz?cache=0&sync_timestamp=1575828700598&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fuglify-js%2Fdownload%2Fuglify-js-3.7.2.tgz#cb1a601e67536e9ed094a92dd1e333459643d3f9" + integrity sha1-yxpgHmdTbp7QlKkt0eMzRZZD0/k= dependencies: commander "~2.20.3" source-map "~0.6.1" @@ -17890,10 +17746,10 @@ umask@^1.1.0: resolved "https://registry.npm.taobao.org/umask/download/umask-1.1.0.tgz#f29cebf01df517912bb58ff9c4e50fde8e33320d" integrity sha1-8pzr8B31F5ErtY/5xOUP3o4zMg0= -umi-build-dev@1.16.7: - version "1.16.7" - resolved "https://registry.npm.taobao.org/umi-build-dev/download/umi-build-dev-1.16.7.tgz#4d68eedecf5f16f3e33cd79108ec446a796491c9" - integrity sha1-TWju3s9fFvPjPNeRCOxEanlkkck= +umi-build-dev@1.16.8: + version "1.16.8" + resolved "https://registry.npm.taobao.org/umi-build-dev/download/umi-build-dev-1.16.8.tgz#c12776821c2c04adbaab7a004c5e938b7a904848" + integrity sha1-wSd2ghwsBK26q3oATF6Ti3qQSEg= dependencies: "@babel/code-frame" "7.0.0" "@babel/generator" "7.4.4" @@ -17902,7 +17758,7 @@ umi-build-dev@1.16.7: "@babel/template" "7.4.4" "@babel/traverse" "7.4.5" "@babel/types" "7.4.4" - af-webpack "1.14.3" + af-webpack "1.14.4" babel-plugin-module-resolver "3.2.0" babel-preset-umi "1.8.1" chalk "2.4.2" @@ -17945,7 +17801,7 @@ umi-build-dev@1.16.7: resolve "1.11.0" rimraf "2.6.3" semver "6.1.1" - serialize-javascript "1.7.0" + serialize-javascript "2.1.1" serve-static "1.14.1" signale "1.4.0" sockjs "0.3.19" @@ -17957,7 +17813,7 @@ umi-build-dev@1.16.7: umi-history "0.1.2" umi-mock "2.1.3" umi-notify "^0.1.1" - umi-plugin-ui "1.4.7" + umi-plugin-ui "1.4.8" umi-test "1.9.0" umi-uni18n "^1.1.6" umi-utils "1.7.2" @@ -18084,9 +17940,9 @@ umi-plugin-polyfills@1.4.2: url-polyfill "1.1.3" umi-plugin-react@^1.8.0: - version "1.14.7" - resolved "https://registry.npm.taobao.org/umi-plugin-react/download/umi-plugin-react-1.14.7.tgz#7f0c7cb2a66d0bf4243973b38fac5aadf62fb6d8" - integrity sha1-fwx8sqZtC/QkOXOzj6xarfYvttg= + version "1.14.8" + resolved "https://registry.npm.taobao.org/umi-plugin-react/download/umi-plugin-react-1.14.8.tgz?cache=0&sync_timestamp=1575631455020&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fumi-plugin-react%2Fdownload%2Fumi-plugin-react-1.14.8.tgz#e9e939b19818930ac262f29be7e92ea8ba526b29" + integrity sha1-6ek5sZgYkwrCYvKb5+kuqLpSayk= dependencies: antd "3.x" antd-mobile "2.x" @@ -18103,7 +17959,7 @@ umi-plugin-react@^1.8.0: umi-plugin-locale "2.11.3" umi-plugin-polyfills "1.4.2" umi-plugin-routes "1.8.3" - umi-plugin-ui "1.4.7" + umi-plugin-ui "1.4.8" umi-utils "1.7.2" webpack "4.41.1" workbox-webpack-plugin "3.6.3" @@ -18113,10 +17969,10 @@ umi-plugin-routes@1.8.3: resolved "https://registry.npm.taobao.org/umi-plugin-routes/download/umi-plugin-routes-1.8.3.tgz#9b136407a0087799a3e902e20d73247e81240ce1" integrity sha1-mxNkB6AId5mj6QLiDXMkfoEkDOE= -umi-plugin-ui@1.4.7: - version "1.4.7" - resolved "https://registry.npm.taobao.org/umi-plugin-ui/download/umi-plugin-ui-1.4.7.tgz#0f7d21aa3a864a03016cd66cec444b126eb0393e" - integrity sha1-D30hqjqGSgMBbNZs7ERLEm6wOT4= +umi-plugin-ui@1.4.8: + version "1.4.8" + resolved "https://registry.npm.taobao.org/umi-plugin-ui/download/umi-plugin-ui-1.4.8.tgz?cache=0&sync_timestamp=1575631456006&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fumi-plugin-ui%2Fdownload%2Fumi-plugin-ui-1.4.8.tgz#2adcfa46d5d1360f3acbfc6047eb31bdc3c522a9" + integrity sha1-Ktz6RtXRNg86y/xgR+sxvcPFIqk= dependencies: got "9.6.0" immer "^5.0.0" @@ -18124,7 +17980,7 @@ umi-plugin-ui@1.4.7: mkdirp "^0.5.1" sockjs-client "1.3.0" styled-components "^4.4.0" - umi-ui-tasks "1.3.4" + umi-ui-tasks "1.3.5" umi-test@1.9.0: version "1.9.0" @@ -18164,12 +18020,13 @@ umi-types@^0.3.0: "@types/webpack" "^4.4.23" "@types/webpack-chain" "^5.0.1" -umi-ui-tasks@1.3.4: - version "1.3.4" - resolved "https://registry.npm.taobao.org/umi-ui-tasks/download/umi-ui-tasks-1.3.4.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fumi-ui-tasks%2Fdownload%2Fumi-ui-tasks-1.3.4.tgz#825be55efaab8462d69bb9ce66f466e8bf9e30f2" - integrity sha1-glvlXvqrhGLWm7nOZvRm6L+eMPI= +umi-ui-tasks@1.3.5: + version "1.3.5" + resolved "https://registry.npm.taobao.org/umi-ui-tasks/download/umi-ui-tasks-1.3.5.tgz#c51d219f995cbf3a7d5d981f96ea5156a148fd3c" + integrity sha1-xR0hn5lcvzp9XZgflupRVqFI/Tw= dependencies: binary-mirror-config "1.20.3" + cross-spawn "^6.0.5" mkdirp "^0.5.1" portfinder "^1.0.24" rimraf "^3.0.0" @@ -18182,10 +18039,10 @@ umi-ui-theme@1.2.1: dependencies: less-vars-to-js "1.3.0" -umi-ui@1.3.7: - version "1.3.7" - resolved "https://registry.npm.taobao.org/umi-ui/download/umi-ui-1.3.7.tgz#da6e3da7720a9a024a3a2cb4fd1092125caf53c0" - integrity sha1-2m49p3IKmgJKOiy0/RCSElyvU8A= +umi-ui@1.3.8: + version "1.3.8" + resolved "https://registry.npm.taobao.org/umi-ui/download/umi-ui-1.3.8.tgz#08e881dd90082346824c9e7131ef30cc020cf3c3" + integrity sha1-COiB3ZAII0aCTJ5xMe8wzAIM88M= dependencies: "@umijs/launch-editor" "^1.0.0" binary-mirror-config "1.20.3" @@ -18209,7 +18066,7 @@ umi-ui@1.3.7: rimraf "3.0.0" semver "6.3.0" sockjs "0.3.19" - umi-build-dev "1.16.7" + umi-build-dev "1.16.8" umi-ui-theme "1.2.1" user-home "2.0.0" optionalDependencies: @@ -18292,9 +18149,9 @@ umi-webpack-bundle-analyzer@^3.5.0: ws "^6.0.0" umi@^2.9.0: - version "2.12.4" - resolved "https://registry.npm.taobao.org/umi/download/umi-2.12.4.tgz#dbd96ff50dd8fc007bb2996bde819fb098e6cb6c" - integrity sha1-29lv9Q3Y/AB7splr3oGfsJjmy2w= + version "2.12.5" + resolved "https://registry.npm.taobao.org/umi/download/umi-2.12.5.tgz#188e05e4e4f9194be3ede710b1b9c1f891a13ccf" + integrity sha1-GI4F5OT5GUvj7ecQsbnB+JGhPM8= dependencies: "@babel/core" "7.4.5" "@babel/runtime" "7.4.5" @@ -18309,9 +18166,9 @@ umi@^2.9.0: resolve-cwd "3.0.0" semver "6.1.1" signale "1.4.0" - umi-build-dev "1.16.7" + umi-build-dev "1.16.8" umi-core "1.9.3" - umi-ui "1.3.7" + umi-ui "1.3.8" umi-utils "1.7.2" update-notifier "3.0.0" yargs-parser "13.1.1" @@ -18416,7 +18273,7 @@ unique-string@^1.0.0: unist-util-find-all-after@^1.0.2: version "1.0.5" - resolved "https://registry.npm.taobao.org/unist-util-find-all-after/download/unist-util-find-all-after-1.0.5.tgz?cache=0&sync_timestamp=1573377180850&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Funist-util-find-all-after%2Fdownload%2Funist-util-find-all-after-1.0.5.tgz#5751a8608834f41d117ad9c577770c5f2f1b2899" + resolved "https://registry.npm.taobao.org/unist-util-find-all-after/download/unist-util-find-all-after-1.0.5.tgz#5751a8608834f41d117ad9c577770c5f2f1b2899" integrity sha1-V1GoYIg09B0RetnFd3cMXy8bKJk= dependencies: unist-util-is "^3.0.0" @@ -18844,7 +18701,7 @@ wcwidth@^1.0.0, wcwidth@^1.0.1: webidl-conversions@^4.0.2: version "4.0.2" - resolved "https://registry.npm.taobao.org/webidl-conversions/download/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad" + resolved "https://registry.npm.taobao.org/webidl-conversions/download/webidl-conversions-4.0.2.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fwebidl-conversions%2Fdownload%2Fwebidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad" integrity sha1-qFWYCx8LazWbodXZ+zmulB+qY60= webpack-chain@*, webpack-chain@6.0.0: @@ -18941,7 +18798,7 @@ webpack-sources@^1.1.0, webpack-sources@^1.3.0, webpack-sources@^1.4.0, webpack- webpack@4.41.1: version "4.41.1" - resolved "https://registry.npm.taobao.org/webpack/download/webpack-4.41.1.tgz?cache=0&sync_timestamp=1574287720943&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fwebpack%2Fdownload%2Fwebpack-4.41.1.tgz#5388dd3047d680d5d382a84249fd4750e87372fd" + resolved "https://registry.npm.taobao.org/webpack/download/webpack-4.41.1.tgz#5388dd3047d680d5d382a84249fd4750e87372fd" integrity sha1-U4jdMEfWgNXTgqhCSf1HUOhzcv0= dependencies: "@webassemblyjs/ast" "1.8.5" @@ -19500,7 +19357,7 @@ yargs@12.0.2: yargs@^10.0.3: version "10.1.2" - resolved "https://registry.npm.taobao.org/yargs/download/yargs-10.1.2.tgz#454d074c2b16a51a43e2fb7807e4f9de69ccb5c5" + resolved "https://registry.npm.taobao.org/yargs/download/yargs-10.1.2.tgz?cache=0&sync_timestamp=1574137569462&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fyargs%2Fdownload%2Fyargs-10.1.2.tgz#454d074c2b16a51a43e2fb7807e4f9de69ccb5c5" integrity sha1-RU0HTCsWpRpD4vt4B+T53mnMtcU= dependencies: cliui "^4.0.0" @@ -19518,7 +19375,7 @@ yargs@^10.0.3: yargs@^11.0.0: version "11.1.1" - resolved "https://registry.npm.taobao.org/yargs/download/yargs-11.1.1.tgz#5052efe3446a4df5ed669c995886cc0f13702766" + resolved "https://registry.npm.taobao.org/yargs/download/yargs-11.1.1.tgz?cache=0&sync_timestamp=1574137569462&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fyargs%2Fdownload%2Fyargs-11.1.1.tgz#5052efe3446a4df5ed669c995886cc0f13702766" integrity sha1-UFLv40RqTfXtZpyZWIbMDxNwJ2Y= dependencies: cliui "^4.0.0"