UNPKG

@antv/x6

Version:

JavaScript diagramming library that uses SVG and HTML for rendering

213 lines (182 loc) 5.05 kB
import { Basecoat, CssLoader, disposable, type KeyValue } from '../../common' import type { EventArgs, Graph, GraphPlugin } from '../../graph' import type { Node } from '../../model' import { content } from './style/raw' import type { TransformImplOptions, TransformImplEventArgs } from './transform' import { TransformImpl } from './transform' import './api' type OptionItem<T, S> = S | ((this: Graph, arg: T) => S) interface ResizingRaw { enabled?: boolean minWidth?: number maxWidth?: number minHeight?: number maxHeight?: number orthogonal?: boolean restrict?: boolean | number autoScroll?: boolean preserveAspectRatio?: boolean allowReverse?: boolean } interface RotatingRaw { enabled?: boolean grid?: number } type Resizing = { [K in keyof ResizingRaw]?: OptionItem<Node, ResizingRaw[K]> } type Rotating = { [K in keyof RotatingRaw]?: OptionItem<Node, RotatingRaw[K]> } type Options = { rotating?: boolean | Partial<Rotating> resizing?: boolean | Partial<Resizing> } export class Transform extends Basecoat<TransformImplEventArgs> implements GraphPlugin { public name = 'transform' public options: Options private graph: Graph protected widgets: Map<Node, TransformImpl> = new Map() private disabled = false constructor(options: Options = {}) { super() this.options = options CssLoader.ensure(this.name, content) } init(graph: Graph) { this.graph = graph if (this.disabled) { return } this.startListening() } protected startListening() { this.graph.on('node:click', this.onNodeClick, this) this.graph.on('blank:mousedown', this.onBlankMouseDown, this) } protected stopListening() { this.graph.off('node:click', this.onNodeClick, this) this.graph.off('blank:mousedown', this.onBlankMouseDown, this) } enable() { if (this.disabled) { this.disabled = false this.startListening() } } disable() { if (!this.disabled) { this.disabled = true this.stopListening() } } isEnabled() { return !this.disabled } createWidget(node: Node) { this.clearWidgets() const widget = this.createTransform(node) if (widget) { this.widgets.set(node, widget) widget.on('*', (name, args) => { this.trigger(name, args) this.graph.trigger(name, args) }) } } protected onNodeClick({ node }: EventArgs['node:click']) { this.createWidget(node) } protected onBlankMouseDown() { this.clearWidgets() } protected createTransform(node: Node) { const options = this.getTransformOptions(node) if (options.resizable || options.rotatable) { return new TransformImpl(options, node, this.graph) } return null } protected parseOptionGroup< K extends KeyValue, S extends KeyValue = KeyValue, T = any, >(graph: Graph, arg: T, options: S): K { const result: any = {} Object.keys(options || {}).forEach((key) => { const val = options[key] result[key] = typeof val === 'function' ? val.call(graph, arg) : val }) return result } protected getTransformOptions(node: Node) { if (!this.options.resizing) { this.options.resizing = { enabled: false, } } if (!this.options.rotating) { this.options.rotating = { enabled: false, } } if (typeof this.options.resizing === 'boolean') { this.options.resizing = { enabled: this.options.resizing, } } if (typeof this.options.rotating === 'boolean') { this.options.rotating = { enabled: this.options.rotating, } } const resizing = this.parseOptionGroup<ResizingRaw>( this.graph, node, this.options.resizing, ) const rotating = this.parseOptionGroup<RotatingRaw>( this.graph, node, this.options.rotating, ) const options: TransformImplOptions = { resizable: !!resizing.enabled, minWidth: resizing.minWidth || 0, maxWidth: resizing.maxWidth || Number.MAX_SAFE_INTEGER, minHeight: resizing.minHeight || 0, maxHeight: resizing.maxHeight || Number.MAX_SAFE_INTEGER, orthogonalResizing: typeof resizing.orthogonal === 'boolean' ? resizing.orthogonal : true, restrictedResizing: !!resizing.restrict, autoScrollOnResizing: typeof resizing.autoScroll === 'boolean' ? resizing.autoScroll : true, preserveAspectRatio: !!resizing.preserveAspectRatio, allowReverse: typeof resizing.allowReverse === 'boolean' ? resizing.allowReverse : true, rotatable: !!rotating.enabled, rotateGrid: rotating.grid || 15, } return options } clearWidgets() { this.widgets.forEach((widget, node) => { if (this.graph.getCellById(node.id)) { widget.dispose() } }) this.widgets.clear() } @disposable() dispose() { this.clearWidgets() this.stopListening() this.off() CssLoader.clean(this.name) } }