UNPKG

@reactodia/workspace

Version:

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

221 lines (198 loc) 6.82 kB
import * as React from 'react'; import { Events, EventSource, EventObserver, PropertyChange } from '../coreUtils/events'; import { TemplateProperties } from '../data/schema'; import type { CanvasApi, CanvasDropEvent } from './canvasApi'; import type { ElementTemplate, LinkTemplate, RenameLinkProvider } from './customization'; import { Element, Link } from './elements'; import type { LayoutFunction } from './layout'; /** * Event data for {@link SharedCanvasState} events. * * @see {@link SharedCanvasState} */ export interface SharedCanvasStateEvents { /** * Triggered on {@link SharedCanvasState.highlighter} property change. */ changeHighlight: PropertyChange< SharedCanvasState, CellHighlighter | undefined >; /** * Triggered on a request to find all canvases using this state. */ findCanvas: FindCanvasEvent; /** * Triggered when all rendering-related state should be disposed. */ dispose: { /** * Event source (shared canvas state). */ readonly source: SharedCanvasState; }; } /** * Event data for a request to find all canvases using this state. * * @see {@link SharedCanvasStateEvents.findCanvas} */ export interface FindCanvasEvent { /** * Collects found canvas instances. */ readonly canvases: CanvasApi[]; } /** * For a each diagram cell tells whether it should be highlighted or blurred. */ export type CellHighlighter = (item: Element | Link) => boolean; /** @hidden */ export interface SharedCanvasStateOptions { defaultElementResolver: (element: Element) => ElementTemplate; defaultLinkResolver: (link: Link) => LinkTemplate; defaultLayout: LayoutFunction; renameLinkProvider?: RenameLinkProvider; } /** * Stores common state and settings for multiple canvases. * * @category Core */ export class SharedCanvasState { private readonly listener = new EventObserver(); private readonly source = new EventSource<SharedCanvasStateEvents>(); /** * Event for the shared canvas state. */ readonly events: Events<SharedCanvasStateEvents> = this.source; private disposed = false; private dropOnPaperHandler: ((e: CanvasDropEvent) => void) | undefined; private _highlighter: CellHighlighter | undefined; /** * Default element template resolver to use as a fallback * (returns a default template for any element). */ readonly defaultElementResolver: (element: Element) => ElementTemplate; /** * Default link template resolver to use as a fallback * (returns a default template for any link). */ readonly defaultLinkResolver: (link: Link) => LinkTemplate; /** * Default layout algorithm function to use if it's not specified explicitly. */ readonly defaultLayout: LayoutFunction; /** * A strategy to rename diagram links (change labels). */ readonly renameLinkProvider: RenameLinkProvider | undefined; /** @hidden */ constructor(options: SharedCanvasStateOptions) { const { defaultElementResolver, defaultLinkResolver, defaultLayout, renameLinkProvider, } = options; this.defaultElementResolver = defaultElementResolver; this.defaultLinkResolver = defaultLinkResolver; this.defaultLayout = defaultLayout; this.renameLinkProvider = renameLinkProvider; } /** @hidden */ dispose(): void { if (this.disposed) { return; } this.source.trigger('dispose', {source: this}); this.listener.stopListening(); this.disposed = true; } /** * Returns all canvases that use this shared state. */ findAllCanvases(): CanvasApi[] { const event: FindCanvasEvent = {canvases: []}; this.source.trigger('findCanvas', event); return event.canvases; } /** * Returns any canvas that uses this shared state or `undefined` if none found. */ findAnyCanvas(): CanvasApi | undefined { const canvases = this.findAllCanvases(); return canvases.length > 0 ? canvases[0] : undefined; } /** * Sets the handler for the next drop event from drag-and-drop operation on a canvas. * * **Experimental**: this feature will likely change in the future. */ setHandlerForNextDropOnPaper(handler: ((e: CanvasDropEvent) => void) | undefined): void { this.dropOnPaperHandler = handler; } /** * Returns `true` if there is a previously set drop handler on a canvas, * otherwise `false`. * * **Experimental**: this feature will likely change in the future. */ hasHandlerForNextDropOnPaper(): boolean { return Boolean(this.dropOnPaperHandler); } /** * Tries to run previously set drop handler on a canvas, * then removes the handler if it was set. * * **Experimental**: this feature will likely change in the future. * * @returns `true` if a handler was set, otherwise `false` */ tryHandleDropOnPaper(e: CanvasDropEvent): boolean { const {dropOnPaperHandler} = this; if (dropOnPaperHandler) { this.dropOnPaperHandler = undefined; e.sourceEvent.preventDefault(); dropOnPaperHandler(e); return true; } return false; } /** * Returns active highlight for the diagram cells. * * **Experimental**: this feature will likely change in the future. */ get highlighter(): CellHighlighter | undefined { return this._highlighter; } /** * Sets or removes an active highlight for the diagram cells. * * **Experimental**: this feature will likely change in the future. */ setHighlighter(value: CellHighlighter | undefined): void { const previous = this._highlighter; if (previous === value) { return; } this._highlighter = value; this.source.trigger('changeHighlight', {source: this, previous}); } } /** * A strategy to rename diagram links which stores changed link label * in the link template state. * * @see {@link TemplateProperties.CustomLabel} */ export class RenameLinkToLinkStateProvider implements RenameLinkProvider { canRename(link: Link): boolean { return true; } getLabel(link: Link): string | undefined { const {linkState} = link; const customLabel = linkState.get(TemplateProperties.CustomLabel); return typeof customLabel === 'string' ? customLabel : undefined; } setLabel(link: Link, label: string): void { link.setLinkState( link.linkState.set( TemplateProperties.CustomLabel, label.length === 0 ? undefined : label ) ); } }