UNPKG

@antv/x6

Version:

JavaScript diagramming library that uses SVG and HTML for rendering.

542 lines (447 loc) 13 kB
import { KeyValue } from '../types' import { Dom, ObjectExt, StringExt } from '../util' import { NodeTool, EdgeTool } from '../registry/tool' import { View } from './view' import { CellView } from './cell' import { Markup } from './markup' export class ToolsView extends View { public tools: ToolsView.ToolItem[] | null public options: ToolsView.Options public cellView: CellView public svgContainer: SVGGElement public htmlContainer: HTMLDivElement public get name() { return this.options.name } public get graph() { return this.cellView.graph } public get cell() { return this.cellView.cell } protected get [Symbol.toStringTag]() { return ToolsView.toStringTag } constructor(options: ToolsView.Options = {}) { super() this.svgContainer = this.createContainer(true, options) as SVGGElement this.htmlContainer = this.createContainer(false, options) as HTMLDivElement this.config(options) } protected createContainer(svg: boolean, options: ToolsView.Options) { const container = svg ? View.createElement('g', true) : View.createElement('div', false) Dom.addClass(container, this.prefixClassName('cell-tools')) if (options.className) { Dom.addClass(container, options.className) } return container } config(options: ToolsView.ConfigOptions) { this.options = { ...this.options, ...options, } if (!CellView.isCellView(options.view) || options.view === this.cellView) { return this } this.cellView = options.view if (this.cell.isEdge()) { Dom.addClass(this.svgContainer, this.prefixClassName('edge-tools')) Dom.addClass(this.htmlContainer, this.prefixClassName('edge-tools')) } else if (this.cell.isNode()) { Dom.addClass(this.svgContainer, this.prefixClassName('node-tools')) Dom.addClass(this.htmlContainer, this.prefixClassName('node-tools')) } this.svgContainer.setAttribute('data-cell-id', this.cell.id) this.htmlContainer.setAttribute('data-cell-id', this.cell.id) if (this.name) { this.svgContainer.setAttribute('data-tools-name', this.name) this.htmlContainer.setAttribute('data-tools-name', this.name) } const tools = this.options.items if (!Array.isArray(tools)) { return this } this.tools = [] const normalizedTools: typeof tools = [] tools.forEach((meta) => { if (ToolsView.ToolItem.isToolItem(meta)) { if (meta.name === 'vertices') { normalizedTools.unshift(meta) } else { normalizedTools.push(meta) } } else { const name = typeof meta === 'object' ? meta.name : meta if (name === 'vertices') { normalizedTools.unshift(meta) } else { normalizedTools.push(meta) } } }) for (let i = 0; i < normalizedTools.length; i += 1) { const meta = normalizedTools[i] let tool: ToolsView.ToolItem | undefined if (ToolsView.ToolItem.isToolItem(meta)) { tool = meta } else { const name = typeof meta === 'object' ? meta.name : meta const args = typeof meta === 'object' ? meta.args || {} : {} if (name) { if (this.cell.isNode()) { const ctor = NodeTool.registry.get(name) if (ctor) { tool = new ctor(args) // eslint-disable-line } else { return NodeTool.registry.onNotFound(name) } } else if (this.cell.isEdge()) { const ctor = EdgeTool.registry.get(name) if (ctor) { tool = new ctor(args) // eslint-disable-line } else { return EdgeTool.registry.onNotFound(name) } } } } if (tool) { tool.config(this.cellView, this) tool.render() const container = tool.options.isSVGElement !== false ? this.svgContainer : this.htmlContainer container.appendChild(tool.container) this.tools.push(tool) } } return this } update(options: ToolsView.UpdateOptions = {}) { const tools = this.tools if (tools) { tools.forEach((tool) => { if (options.toolId !== tool.cid && tool.isVisible()) { tool.update() } }) } return this } focus(focusedTool: ToolsView.ToolItem | null) { const tools = this.tools if (tools) { tools.forEach((tool) => { if (focusedTool === tool) { tool.show() } else { tool.hide() } }) } return this } blur(blurredTool: ToolsView.ToolItem | null) { const tools = this.tools if (tools) { tools.forEach((tool) => { if (tool !== blurredTool && !tool.isVisible()) { tool.show() tool.update() } }) } return this } hide() { return this.focus(null) } show() { return this.blur(null) } remove() { const tools = this.tools if (tools) { tools.forEach((tool) => tool.remove()) this.tools = null } Dom.remove(this.svgContainer) Dom.remove(this.htmlContainer) return super.remove() } mount() { const tools = this.tools const cellView = this.cellView if (cellView && tools) { const hasSVG = tools.some((tool) => tool.options.isSVGElement !== false) const hasHTML = tools.some((tool) => tool.options.isSVGElement === false) if (hasSVG) { const parent = this.options.local ? cellView.container : cellView.graph.view.decorator parent.appendChild(this.svgContainer) } if (hasHTML) { this.graph.container.appendChild(this.htmlContainer) } } return this } } export namespace ToolsView { export interface Options extends ConfigOptions { className?: string } export interface ConfigOptions { view?: CellView name?: string local?: boolean items?: | ( | ToolItem | string | NodeTool.NativeNames | NodeTool.NativeItem | NodeTool.ManaualItem | EdgeTool.NativeNames | EdgeTool.NativeItem | EdgeTool.ManaualItem )[] | null } export interface UpdateOptions { toolId?: string } } export namespace ToolsView { export const toStringTag = `X6.${ToolsView.name}` export function isToolsView(instance: any): instance is ToolsView { if (instance == null) { return false } if (instance instanceof ToolsView) { return true } const tag = instance[Symbol.toStringTag] const view = instance as ToolsView if ( (tag == null || tag === toStringTag) && view.graph != null && view.cell != null && typeof view.config === 'function' && typeof view.update === 'function' && typeof view.focus === 'function' && typeof view.blur === 'function' && typeof view.show === 'function' && typeof view.hide === 'function' ) { return true } return false } } export namespace ToolsView { export class ToolItem< TargetView extends CellView = CellView, Options extends ToolItem.Options = ToolItem.Options, > extends View { // #region static protected static defaults: ToolItem.Options = { isSVGElement: true, tagName: 'g', } public static getDefaults<T extends ToolItem.Options>() { return this.defaults as T } public static config<T extends ToolItem.Options = ToolItem.Options>( options: Partial<T>, ) { this.defaults = this.getOptions(options) } public static getOptions<T extends ToolItem.Options = ToolItem.Options>( options: Partial<T>, ): T { return ObjectExt.merge( ObjectExt.cloneDeep(this.getDefaults()), options, ) as T } // #endregion public readonly options: Options public container: HTMLElement | SVGElement public parent: ToolsView protected cellView: TargetView protected visible: boolean protected childNodes: KeyValue<Element> public get graph() { return this.cellView.graph } public get cell() { return this.cellView.cell } public get name() { return this.options.name } protected get [Symbol.toStringTag]() { return ToolItem.toStringTag } constructor(options: Partial<Options> = {}) { super() this.options = this.getOptions(options) this.container = View.createElement( this.options.tagName || 'g', this.options.isSVGElement !== false, ) Dom.addClass(this.container, this.prefixClassName('cell-tool')) if (typeof this.options.className === 'string') { Dom.addClass(this.container, this.options.className) } this.init() } protected init() {} protected getOptions(options: Partial<Options>): Options { const ctor = this.constructor as any as ToolItem return ctor.getOptions(options) as Options } delegateEvents() { if (this.options.events) { super.delegateEvents(this.options.events) } return this } config(view: CellView, toolsView: ToolsView) { this.cellView = view as TargetView this.parent = toolsView this.stamp(this.container) if (this.cell.isEdge()) { Dom.addClass(this.container, this.prefixClassName('edge-tool')) } else if (this.cell.isNode()) { Dom.addClass(this.container, this.prefixClassName('node-tool')) } if (this.name) { this.container.setAttribute('data-tool-name', this.name) } this.delegateEvents() return this } render() { this.empty() const markup = this.options.markup if (markup) { const meta = Markup.isStringMarkup(markup) ? Markup.parseStringMarkup(markup) : Markup.parseJSONMarkup(markup) this.container.appendChild(meta.fragment) this.childNodes = meta.selectors as KeyValue<Element> } this.onRender() return this } protected onRender() {} update() { return this } protected stamp(elem: Element = this.container) { if (elem) { elem.setAttribute('data-cell-id', this.cellView.cell.id) } } show() { this.container.style.display = '' this.visible = true return this } hide() { this.container.style.display = 'none' this.visible = false return this } isVisible() { return this.visible } focus() { const opacity = this.options.focusOpacity if (opacity != null && Number.isFinite(opacity)) { this.container.style.opacity = `${opacity}` } this.parent.focus(this) return this } blur() { this.container.style.opacity = '' this.parent.blur(this) return this } protected guard(evt: JQuery.TriggeredEvent) { if (this.graph == null || this.cellView == null) { return true } return this.graph.view.guard(evt, this.cellView) } } export namespace ToolItem { export interface Options { name?: string tagName?: string isSVGElement?: boolean className?: string markup?: Markup events?: View.Events | null documentEvents?: View.Events | null focusOpacity?: number } } export namespace ToolItem { export type Definition = | typeof ToolItem | (new (options: ToolItem.Options) => ToolItem) let counter = 0 function getClassName(name?: string) { if (name) { return StringExt.pascalCase(name) } counter += 1 return `CustomTool${counter}` } export function define<T extends Options>(options: T) { const tool = ObjectExt.createClass<Definition>( getClassName(options.name), this as Definition, ) as typeof ToolItem tool.config(options) return tool } } export namespace ToolItem { export const toStringTag = `X6.${ToolItem.name}` export function isToolItem(instance: any): instance is ToolItem { if (instance == null) { return false } if (instance instanceof ToolItem) { return true } const tag = instance[Symbol.toStringTag] const view = instance as ToolItem if ( (tag == null || tag === toStringTag) && view.graph != null && view.cell != null && typeof view.config === 'function' && typeof view.update === 'function' && typeof view.focus === 'function' && typeof view.blur === 'function' && typeof view.show === 'function' && typeof view.hide === 'function' && typeof view.isVisible === 'function' ) { return true } return false } } }