UNPKG

@reactodia/workspace

Version:

Reactodia Workspace -- library for visual interaction with graphs in a form of a diagram.

611 lines (585 loc) 18.6 kB
import * as React from 'react'; import { Events, PropertyChange } from '../coreUtils/events'; import type { RenderingState } from './renderingState'; import type { Cell } from './elements'; import type { Vector, Rect, Size } from './geometry'; import type { DiagramModel } from './model'; import type { PaperTransform } from './paper'; import type { ToDataURLOptions } from './toSvg'; /** * Describes an API to interact with a scrollable graph canvas. * * @category Core */ export interface CanvasApi { /** * Events for the scrollable graph canvas. */ readonly events: Events<CanvasEvents>; /** * Canvas-specific state for rendering graph content (elements, links, etc). */ readonly renderingState: RenderingState; /** * Live state for the current viewport size and transformation. * * Allows to convert between different canvas coordinate types. * * This state can be captured to provide freezed state via {@link CanvasMetrics.snapshot}. */ readonly metrics: CanvasMetrics; /** * Options for the scale-affecting operations on the canvas. */ readonly zoomOptions: Required<ZoomOptions>; /** * Default action on moving pointer with pressed main button. * * Initial mode is `panning`. */ readonly pointerMode: CanvasPointerMode; /** * Sets default action on moving pointer with pressed main button. */ setPointerMode(value: CanvasPointerMode): void; /** * Sets the focus on the graph canvas itself to be able to handle keyboard interaction * within its layers. */ focus(): void; /** * Changes the viewport such that its center is aligned to specified point * in paper coordinates. * * If no point is specified, aligns the viewport center with the canvas center instead. */ centerTo( paperPosition?: Vector, options?: CenterToOptions ): Promise<void>; /** * Changes the viewport such that center of the bounding box for the graph content * is aligned to the viewport center. */ centerContent(options?: ViewportOptions): Promise<void>; /** * Returns the current scale of the graph content in relation to the viewport. */ getScale(): number; /** * Changes the viewport to set specific scale of the graph content. * * If `pivot` is specified, the viewport is changed as if the canvas was * zoomed-in or zoomed-out at that point of the canvas * (e.g. by mouse wheel or pinch-zoom at the pivot). */ setScale(value: number, options?: ScaleOptions): Promise<void>; /** * Same as {@link CanvasApi.setScale setScale()} but relative to the current scale value. * * @see {@link CanvasApi.setScale} */ zoomBy(value: number, options?: ScaleOptions): Promise<void>; /** * Same as {@link CanvasApi.zoomBy zoomBy()} with a positive zoom step value. * * @see {@link CanvasApi.zoomBy} */ zoomIn(scaleOptions?: ScaleOptions): Promise<void>; /** * Same as {@link CanvasApi.zoomBy zoomBy()} with a negative zoom step value. * * @see {@link CanvasApi.zoomBy} */ zoomOut(scaleOptions?: ScaleOptions): Promise<void>; /** * Changes the viewport to fit the whole graph content if possible. * * If the diagram is empty, centers the viewport at the middle of the canvas. * * @see {@link CanvasApi.zoomToFitRect} */ zoomToFit(options?: ViewportOptions): Promise<void>; /** * Changes the viewport to fit specified rectangle area in paper coordinates if possible. * * @see {@link CanvasApi.zoomToFit} */ zoomToFitRect(paperRect: Rect, options?: ViewportOptions): Promise<void>; /** * Exports the diagram as a serialized into text SVG document * with `<foreignObject>` HTML layers inside. * * Exported SVG document would include all diagram content as well as every CSS rule * which applies to any DOM element from the diagram content. */ exportSvg(options?: ExportSvgOptions): Promise<string>; /** * Exports the diagram as a rendered raster image (e.g. PNG, JPEG, etc) * serialized into base64-encoded [data URL](https://developer.mozilla.org/en-US/docs/Web/URI/Schemes/data). */ exportRaster(options?: ExportRasterOptions): Promise<string>; /** * Returns `true` if there is an active animation for graph or links on the canvas; * otherwise `false`. * * @see {@link CanvasApi.animateGraph} */ isAnimatingGraph(): boolean; /** * Starts animation for graph elements and links. * * @param setupChanges immediately called function to perform animatable changes on graph * @param duration duration animation duration in milliseconds (default is `500`) * @returns promise which resolves when this animation ends * * **Example**: * ```js * // Animate element movement by 200px (in paper coordinates) on the x-axis * const target = model.getElement(...); * canvas.animateGraph(() => { * const {x, y} = target.position; * target.setPosition(x + 200, y); * }); * ``` */ animateGraph(setupChanges: () => void, duration?: number): Promise<void>; } /** * Event data for {@link CanvasApi} events. * * @see {@link CanvasApi} */ export interface CanvasEvents { /** * Triggered on [pointer down](https://developer.mozilla.org/en-US/docs/Web/API/Element/pointerdown_event) * event in the canvas. */ pointerDown: CanvasPointerEvent; /** * Triggered on [pointer move](https://developer.mozilla.org/en-US/docs/Web/API/Element/pointermove_event) * event in the canvas. */ pointerMove: CanvasPointerEvent; /** * Triggered on [pointer up](https://developer.mozilla.org/en-US/docs/Web/API/Element/pointerup_event) * event in the canvas. */ pointerUp: CanvasPointerUpEvent; /** * Triggered on [scroll](https://developer.mozilla.org/en-US/docs/Web/API/Element/scroll_event) * event in the canvas. */ scroll: CanvasScrollEvent; /** * Triggered on [drop](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/drop_event) * event from a drag and drop operation on the canvas. */ drop: CanvasDropEvent; /** * Triggered on [contextmenu](https://developer.mozilla.org/en-US/docs/Web/API/Element/contextmenu_event/) * event (opening a context menu) in the canvas. */ contextMenu: CanvasContextMenuEvent; /** * Triggered on canvas viewport resize, tracked by a * [ResizeObserver](https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver). */ resize: CanvasResizeEvent; /** * Triggered on [keydown](https://developer.mozilla.org/en-US/docs/Web/API/Element/keydown_event/) * event in the canvas. */ keydown: CanvasKeyboardEvent; /** * Triggered on [keyup](https://developer.mozilla.org/en-US/docs/Web/API/Element/keyup_event/) * event in the canvas. */ keyup: CanvasKeyboardEvent; /** * Triggered on {@link CanvasApi.isAnimatingGraph} property change. */ changeAnimatingGraph: PropertyChange<CanvasApi, boolean>; /** * Triggered on {@link CanvasApi.pointerMode} property change. */ changePointerMode: PropertyChange<CanvasApi, CanvasPointerMode>; /** * Triggered on {@link CanvasApi.getScale} property change. */ changeScale: PropertyChange<CanvasApi, number>; } /** * Event data for canvas pointer events. */ export interface CanvasPointerEvent { /** * Event source (canvas). */ readonly source: CanvasApi; /** * Original (raw) event data. */ readonly sourceEvent: React.MouseEvent<Element> | MouseEvent; /** * Pointer event target (element, link, link vertex). * * If `undefined` then the pointer event target is an empty canvas space. */ readonly target: Cell | undefined; /** * `true` if event triggered while viewport is being panned (moved); * otherwise `false`. */ readonly panning: boolean; } /** * Event data for canvas pointer up event. */ export interface CanvasPointerUpEvent extends CanvasPointerEvent { /** * `true` if the pointer up event should be considered as a "click" * on the target because it immediately follows pointer down event * without any pointer moves in-between. */ readonly triggerAsClick: boolean; } /** * Event data for canvas scroll event. */ export interface CanvasScrollEvent { /** * Event source (canvas). */ readonly source: CanvasApi; /** * Original (raw) event data. */ readonly sourceEvent: Event; } /** * Event data for canvas drop event from a drag and drop operation. */ export interface CanvasDropEvent { /** * Event source (canvas). */ readonly source: CanvasApi; /** * Original (raw) event data. */ readonly sourceEvent: DragEvent; /** * Position of the drop in paper coordinates. */ readonly position: Vector; } /** * Event data for canvas context menu open request event. */ export interface CanvasContextMenuEvent { /** * Event source (canvas). */ readonly source: CanvasApi; /** * Original (raw) event data. */ readonly sourceEvent: React.MouseEvent; /** * Pointer event target (element, link, link vertex). * * If `undefined` then the pointer event target is an empty canvas space. */ readonly target: Cell | undefined; } /** * Event data for canvas viewport resize event. */ export interface CanvasResizeEvent { /** * Event source (canvas). */ readonly source: CanvasApi; } export interface CanvasKeyboardEvent { /** * Event source (canvas). */ readonly source: CanvasApi; /** * Original (raw) event data. */ readonly sourceEvent: React.KeyboardEvent; } /** * Represents canvas viewport size and transformation. * * Allows to convert between different canvas coordinate types. */ export interface CanvasMetrics { /** * Sizes and offsets for the canvas area DOM element. */ readonly area: CanvasAreaMetrics; /** * Returns transformation data between paper and scrollable pane coordinates. */ getTransform(): PaperTransform; /** * Returns a immutable instance of this metrics which is guaranteed to * never change even if original canvas viewport changes. */ snapshot(): CanvasMetrics; /** * Returns paper size in paper coordinates. */ getPaperSize(): Size; /** * Returns viewport bounds in page coordinates. */ getViewportPageRect(): Rect; /** * Translates page to paper coordinates. */ pageToPaperCoords(pageX: number, pageY: number): Vector; /** * Translates paper to page coordinates. */ paperToPageCoords(paperX: number, paperY: number): Vector; /** * Translates client (viewport) to paper coordinates. */ clientToPaperCoords(areaClientX: number, areaClientY: number): Vector; /** * Translates client (viewport) to scrollable pane coordinates. */ clientToScrollablePaneCoords(areaClientX: number, areaClientY: number): Vector; /** * Translates scrollable pane to client (viewport) coordinates. */ scrollablePaneToClientCoords(paneX: number, paneY: number): Vector; /** * Translates scrollable pane to paper coordinates. */ scrollablePaneToPaperCoords(paneX: number, paneY: number): Vector; /** * Translates paper to scrollable pane coordinates. */ paperToScrollablePaneCoords(paperX: number, paperY: number): Vector; } /** * Contains sizes and offsets for the canvas area DOM element. */ export interface CanvasAreaMetrics { /** * Canvas area [client width](https://developer.mozilla.org/en-US/docs/Web/API/Element/clientWidth). */ readonly clientWidth: number; /** * Canvas area [client height](https://developer.mozilla.org/en-US/docs/Web/API/Element/clientHeight). */ readonly clientHeight: number; /** * Canvas area [offset width](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/offsetWidth). */ readonly offsetWidth: number; /** * Canvas area [offset height](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/offsetHeight). */ readonly offsetHeight: number; /** * Canvas area [scroll width](https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollWidth). */ readonly scrollLeft: number; /** * Canvas area [scroll height](https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollHeight). */ readonly scrollTop: number; } /** * Action on moving pointer with pressed main button: * - `panning` - pans the viewport over canvas; * - `selection` - starts selection of the cells on canvas. * * This mode may be changed to another while `Shift` button is being held * (this should be implemented separately when the property value is used * in other components). */ export type CanvasPointerMode = 'panning' | 'selection'; /** * Options for {@link CanvasApi} methods affecting the viewport. */ export interface ViewportOptions { /** * True if operation should be animated. * * If duration is provided and greater than zero then defaults to `true`, * otherwise it is set to `false`. */ animate?: boolean; /** * Animation duration in milliseconds. * * Implicitly sets `animate: true` if greater than zero. * * @default 500 */ duration?: number; } /** * Options for {@link CanvasApi.centerTo} method. */ export interface CenterToOptions extends ViewportOptions { /** * Scale to set when changing the viewport. */ scale?: number; } /** * Options for {@link CanvasApi} methods affecting canvas scale. */ export interface ScaleOptions extends ViewportOptions { /** * Scale pivot position in paper coordinates. */ pivot?: Vector; } /** * Options for the behavior of operation affecting scale on the canvas. */ export interface ZoomOptions { /** * Minimum scale factor. * * @default 0.2 */ min?: number; /** * Maximum scale factor. * * @default 2 */ max?: number; /** * Same as `max` but used only for zoom-to-fit to limit the scale factor * on small diagrams. * * @default 1 */ maxFit?: number; /** * Scale step for the zoom-in and zoom-out operations. * * @default 0.1 */ step?: number; /** * Padding from each viewport border for zoom-to-fit scaling. * * @default 20 */ fitPadding?: number; /** * Whether `Ctrl`/`Cmd` keyboard key should be held to zoom * with the mouse wheel. * * If `true`, the mouse wheel will be used to scroll viewport * horizontally or vertically if `Shift` is held; * otherwise the wheel action will be inverted. * * @default true */ requireCtrl?: boolean; } /** * Options for exporting diagram as SVG image. * * @see {@link CanvasApi.exportSvg} */ export interface ExportSvgOptions { /** * Padding size (in pixels) around the content for the exported diagram. * * @default {x: 100, y: 100} */ contentPadding?: Vector; /** * CSS selectors to exclude specific DOM elements from the exported diagram. * * By default, any element with `data-reactodia-no-export` is removed. * * @default ["[data-reactodia-no-export]"] */ removeByCssSelectors?: ReadonlyArray<string>; /** * Whether to prepend XML encoding header to the exported SVG string. * * Prepended header: * ```xml * <?xml version="1.0" encoding="UTF-8"?> * ``` * * @default false */ addXmlHeader?: boolean; } /** * Options for exporting diagram as raster image (e.g. JPEG, PNG, etc). * * @see {@link CanvasApi.exportRaster} */ export interface ExportRasterOptions extends ExportSvgOptions, ToDataURLOptions {} /** * Canvas widget layer to render widget: * - `viewport` - topmost layer, uses client (viewport) coordinates and * does not scale or scroll with the diagram; * - `overElements` - displayed over both elements and links, uses paper coordinates, * scales and scrolls with the diagram; * - `overLinks` - displayed under elements but over links, uses paper coordinates, * scales and scrolls with the diagram. */ export type CanvasWidgetAttachment = 'viewport' | 'overElements' | 'overLinks'; /** * Describes canvas widget element to render on the specific widget layer. */ export interface CanvasWidgetDescription { /** * Canvas widget element to render. */ element: React.ReactElement; /** * Canvas widget layer to render widget on. */ attachment: CanvasWidgetAttachment; } /** * Represents a context for everything rendered inside the canvas, * including diagram content and widgets. */ export interface CanvasContext { /** * The canvas API. */ readonly canvas: CanvasApi; /** * Model for the diagram displayed by the canvas. */ readonly model: DiagramModel; } /** @hidden */ export const CanvasContext = React.createContext<CanvasContext | null>(null); /** * React hook to get current canvas context. * * Throws an error if called from component which is outside the canvas. * * @category Hooks */ export function useCanvas(): CanvasContext { const context = React.useContext(CanvasContext); if (!context) { throw new Error('Missing Reactodia canvas context'); } return context; }