UNPKG

@antv/x6

Version:

JavaScript diagramming library that uses SVG and HTML for rendering.

549 lines (474 loc) 17.3 kB
import { ArrayExt } from '../util' import { Basecoat } from '../common' import { Cell } from './cell' import { Node } from './node' import { Edge } from './edge' export class Collection extends Basecoat<Collection.EventArgs> { public length = 0 public comparator: Collection.Comparator | null private cells: Cell[] private map: { [id: string]: Cell } constructor(cells: Cell | Cell[], options: Collection.Options = {}) { super() this.comparator = options.comparator || 'zIndex' this.clean() if (cells) { this.reset(cells, { silent: true }) } } toJSON() { return this.cells.map((cell) => cell.toJSON()) } add(cells: Cell | Cell[], options?: Collection.AddOptions): this add( cells: Cell | Cell[], index: number, options?: Collection.AddOptions, ): this add( cells: Cell | Cell[], index?: number | Collection.AddOptions, options?: Collection.AddOptions, ) { let localIndex: number let localOptions: Collection.AddOptions if (typeof index === 'number') { localIndex = index localOptions = { merge: false, ...options } } else { localIndex = this.length localOptions = { merge: false, ...index } } if (localIndex > this.length) { localIndex = this.length } if (localIndex < 0) { localIndex += this.length + 1 } const entities = Array.isArray(cells) ? cells : [cells] const sortable = this.comparator && typeof index !== 'number' && localOptions.sort !== false const sortAttr = this.comparator || null let sort = false const added: Cell[] = [] const merged: Cell[] = [] entities.forEach((cell) => { const existing = this.get(cell) if (existing) { if (localOptions.merge && !cell.isSameStore(existing)) { existing.setProp(cell.getProp(), options) // merge merged.push(existing) if (sortable && !sort) { if (sortAttr == null || typeof sortAttr === 'function') { sort = existing.hasChanged() } else if (typeof sortAttr === 'string') { sort = existing.hasChanged(sortAttr) } else { sort = sortAttr.some((key) => existing.hasChanged(key)) } } } } else { added.push(cell) this.reference(cell) } }) if (added.length) { if (sortable) { sort = true } this.cells.splice(localIndex, 0, ...added) this.length = this.cells.length } if (sort) { this.sort({ silent: true }) } if (!localOptions.silent) { added.forEach((cell, i) => { const args = { cell, index: localIndex + i, options: localOptions, } this.trigger('added', args) if (!localOptions.dryrun) { cell.notify('added', { ...args }) } }) if (sort) { this.trigger('sorted') } if (added.length || merged.length) { this.trigger('updated', { added, merged, removed: [], options: localOptions, }) } } return this } remove(cell: Cell, options?: Collection.RemoveOptions): Cell remove(cells: Cell[], options?: Collection.RemoveOptions): Cell[] remove(cells: Cell | Cell[], options: Collection.RemoveOptions = {}) { const arr = Array.isArray(cells) ? cells : [cells] const removed = this.removeCells(arr, options) if (!options.silent && removed.length > 0) { this.trigger('updated', { options, removed, added: [], merged: [], }) } return Array.isArray(cells) ? removed : removed[0] } protected removeCells(cells: Cell[], options: Collection.RemoveOptions) { const removed = [] for (let i = 0; i < cells.length; i += 1) { const cell = this.get(cells[i]) if (cell == null) { continue } const index = this.cells.indexOf(cell) this.cells.splice(index, 1) this.length -= 1 delete this.map[cell.id] removed.push(cell) this.unreference(cell) if (!options.dryrun) { cell.remove() } if (!options.silent) { this.trigger('removed', { cell, index, options }) if (!options.dryrun) { cell.notify('removed', { cell, index, options }) } } } return removed } reset(cells: Cell | Cell[], options: Collection.SetOptions = {}) { const previous = this.cells.slice() previous.forEach((cell) => this.unreference(cell)) this.clean() this.add(cells, { silent: true, ...options }) if (!options.silent) { const current = this.cells.slice() this.trigger('reseted', { options, previous, current, }) const added: Cell[] = [] const removed: Cell[] = [] current.forEach((a) => { const exist = previous.some((b) => b.id === a.id) if (!exist) { added.push(a) } }) previous.forEach((a) => { const exist = current.some((b) => b.id === a.id) if (!exist) { removed.push(a) } }) this.trigger('updated', { options, added, removed, merged: [] }) } return this } push(cell: Cell, options?: Collection.SetOptions) { return this.add(cell, this.length, options) } pop(options?: Collection.SetOptions) { const cell = this.at(this.length - 1)! return this.remove(cell, options) } unshift(cell: Cell, options?: Collection.SetOptions) { return this.add(cell, 0, options) } shift(options?: Collection.SetOptions) { const cell = this.at(0)! return this.remove(cell, options) } get(cell?: string | number | Cell | null): Cell | null { if (cell == null) { return null } const id = typeof cell === 'string' || typeof cell === 'number' ? cell : cell.id return this.map[id] || null } has(cell: string | Cell): boolean { return this.get(cell as any) != null } at(index: number): Cell | null { if (index < 0) { index += this.length // eslint-disable-line } return this.cells[index] || null } first() { return this.at(0) } last() { return this.at(-1) } indexOf(cell: Cell) { return this.cells.indexOf(cell) } toArray() { return this.cells.slice() } sort(options: Collection.SetOptions = {}) { if (this.comparator != null) { this.cells = ArrayExt.sortBy(this.cells, this.comparator) if (!options.silent) { this.trigger('sorted') } } return this } clone() { const constructor = this.constructor as any return new constructor(this.cells.slice(), { comparator: this.comparator, }) as Collection } protected reference(cell: Cell) { this.map[cell.id] = cell cell.on('*', this.notifyCellEvent, this) } protected unreference(cell: Cell) { cell.off('*', this.notifyCellEvent, this) delete this.map[cell.id] } protected notifyCellEvent<K extends keyof Cell.EventArgs>( name: K, args: Cell.EventArgs[K], ) { const cell = args.cell this.trigger(`cell:${name}`, args) if (cell) { if (cell.isNode()) { this.trigger(`node:${name}`, { ...args, node: cell }) } else if (cell.isEdge()) { this.trigger(`edge:${name}`, { ...args, edge: cell }) } } } protected clean() { this.length = 0 this.cells = [] this.map = {} } } export namespace Collection { export type Comparator = string | string[] | ((cell: Cell) => number) export interface Options { comparator?: Comparator } export interface SetOptions extends Cell.SetOptions {} export interface RemoveOptions extends Cell.SetOptions { /** * The default is to remove all the associated links. * Set `disconnectEdges` option to `true` to disconnect edges * when a cell is removed. */ disconnectEdges?: boolean dryrun?: boolean } export interface AddOptions extends SetOptions { sort?: boolean merge?: boolean dryrun?: boolean } } export namespace Collection { export interface EventArgs extends CellEventArgs, NodeEventArgs, EdgeEventArgs { sorted?: null reseted: { current: Cell[] previous: Cell[] options: SetOptions } updated: { added: Cell[] merged: Cell[] removed: Cell[] options: SetOptions } added: { cell: Cell index: number options: AddOptions } removed: { cell: Cell index: number options: RemoveOptions } } interface NodeEventCommonArgs { node: Node } interface EdgeEventCommonArgs { edge: Edge } export interface CellEventArgs { 'cell:transition:start': Cell.EventArgs['transition:start'] 'cell:transition:progress': Cell.EventArgs['transition:progress'] 'cell:transition:complete': Cell.EventArgs['transition:complete'] 'cell:transition:stop': Cell.EventArgs['transition:stop'] 'cell:transition:finish': Cell.EventArgs['transition:finish'] 'cell:changed': Cell.EventArgs['changed'] 'cell:added': Cell.EventArgs['added'] 'cell:removed': Cell.EventArgs['removed'] 'cell:change:*': Cell.EventArgs['change:*'] 'cell:change:attrs': Cell.EventArgs['change:attrs'] 'cell:change:zIndex': Cell.EventArgs['change:zIndex'] 'cell:change:markup': Cell.EventArgs['change:markup'] 'cell:change:visible': Cell.EventArgs['change:visible'] 'cell:change:parent': Cell.EventArgs['change:parent'] 'cell:change:children': Cell.EventArgs['change:children'] 'cell:change:tools': Cell.EventArgs['change:tools'] 'cell:change:view': Cell.EventArgs['change:view'] 'cell:change:data': Cell.EventArgs['change:data'] 'cell:change:size': Cell.EventArgs['change:size'] 'cell:change:angle': Cell.EventArgs['change:angle'] 'cell:change:position': Cell.EventArgs['change:position'] 'cell:change:ports': Cell.EventArgs['change:ports'] 'cell:change:portMarkup': Cell.EventArgs['change:portMarkup'] 'cell:change:portLabelMarkup': Cell.EventArgs['change:portLabelMarkup'] 'cell:change:portContainerMarkup': Cell.EventArgs['change:portContainerMarkup'] 'cell:ports:added': Cell.EventArgs['ports:added'] 'cell:ports:removed': Cell.EventArgs['ports:removed'] 'cell:change:source': Cell.EventArgs['change:source'] 'cell:change:target': Cell.EventArgs['change:target'] 'cell:change:router': Cell.EventArgs['change:router'] 'cell:change:connector': Cell.EventArgs['change:connector'] 'cell:change:vertices': Cell.EventArgs['change:vertices'] 'cell:change:labels': Cell.EventArgs['change:labels'] 'cell:change:defaultLabel': Cell.EventArgs['change:defaultLabel'] 'cell:change:toolMarkup': Cell.EventArgs['change:toolMarkup'] 'cell:change:doubleToolMarkup': Cell.EventArgs['change:doubleToolMarkup'] 'cell:change:vertexMarkup': Cell.EventArgs['change:vertexMarkup'] 'cell:change:arrowheadMarkup': Cell.EventArgs['change:arrowheadMarkup'] 'cell:vertexs:added': Cell.EventArgs['vertexs:added'] 'cell:vertexs:removed': Cell.EventArgs['vertexs:removed'] 'cell:labels:added': Cell.EventArgs['labels:added'] 'cell:labels:removed': Cell.EventArgs['labels:removed'] 'cell:batch:start': Cell.EventArgs['batch:start'] 'cell:batch:stop': Cell.EventArgs['batch:stop'] } export interface NodeEventArgs { 'node:transition:start': NodeEventCommonArgs & Cell.EventArgs['transition:start'] 'node:transition:progress': NodeEventCommonArgs & Cell.EventArgs['transition:progress'] 'node:transition:complete': NodeEventCommonArgs & Cell.EventArgs['transition:complete'] 'node:transition:stop': NodeEventCommonArgs & Cell.EventArgs['transition:stop'] 'node:transition:finish': NodeEventCommonArgs & Cell.EventArgs['transition:finish'] 'node:changed': NodeEventCommonArgs & CellEventArgs['cell:changed'] 'node:added': NodeEventCommonArgs & CellEventArgs['cell:added'] 'node:removed': NodeEventCommonArgs & CellEventArgs['cell:removed'] 'node:change:*': NodeEventCommonArgs & Cell.EventArgs['change:*'] 'node:change:attrs': NodeEventCommonArgs & Cell.EventArgs['change:attrs'] 'node:change:zIndex': NodeEventCommonArgs & Cell.EventArgs['change:zIndex'] 'node:change:markup': NodeEventCommonArgs & Cell.EventArgs['change:markup'] 'node:change:visible': NodeEventCommonArgs & Cell.EventArgs['change:visible'] 'node:change:parent': NodeEventCommonArgs & Cell.EventArgs['change:parent'] 'node:change:children': NodeEventCommonArgs & Cell.EventArgs['change:children'] 'node:change:tools': NodeEventCommonArgs & Cell.EventArgs['change:tools'] 'node:change:view': NodeEventCommonArgs & Cell.EventArgs['change:view'] 'node:change:data': NodeEventCommonArgs & Cell.EventArgs['change:data'] 'node:change:size': NodeEventCommonArgs & Cell.EventArgs['change:size'] 'node:change:position': NodeEventCommonArgs & Cell.EventArgs['change:position'] 'node:change:angle': NodeEventCommonArgs & Cell.EventArgs['change:angle'] 'node:change:ports': NodeEventCommonArgs & Cell.EventArgs['change:ports'] 'node:change:portMarkup': NodeEventCommonArgs & Cell.EventArgs['change:portMarkup'] 'node:change:portLabelMarkup': NodeEventCommonArgs & Cell.EventArgs['change:portLabelMarkup'] 'node:change:portContainerMarkup': NodeEventCommonArgs & Cell.EventArgs['change:portContainerMarkup'] 'node:ports:added': NodeEventCommonArgs & Cell.EventArgs['ports:added'] 'node:ports:removed': NodeEventCommonArgs & Cell.EventArgs['ports:removed'] 'node:batch:start': NodeEventCommonArgs & Cell.EventArgs['batch:start'] 'node:batch:stop': NodeEventCommonArgs & Cell.EventArgs['batch:stop'] // 'node:translate': NodeEventCommonArgs // 'node:translating': NodeEventCommonArgs // 'node:translated': NodeEventCommonArgs // 'node:resize': NodeEventCommonArgs // 'node:resizing': NodeEventCommonArgs // 'node:resized': NodeEventCommonArgs // 'node:rotate': NodeEventCommonArgs // 'node:rotating': NodeEventCommonArgs // 'node:rotated': NodeEventCommonArgs } export interface EdgeEventArgs { 'edge:transition:start': EdgeEventCommonArgs & Cell.EventArgs['transition:start'] 'edge:transition:progress': EdgeEventCommonArgs & Cell.EventArgs['transition:progress'] 'edge:transition:complete': EdgeEventCommonArgs & Cell.EventArgs['transition:complete'] 'edge:transition:stop': EdgeEventCommonArgs & Cell.EventArgs['transition:stop'] 'edge:transition:finish': EdgeEventCommonArgs & Cell.EventArgs['transition:finish'] 'edge:changed': EdgeEventCommonArgs & CellEventArgs['cell:changed'] 'edge:added': EdgeEventCommonArgs & CellEventArgs['cell:added'] 'edge:removed': EdgeEventCommonArgs & CellEventArgs['cell:removed'] 'edge:change:*': EdgeEventCommonArgs & Cell.EventArgs['change:*'] 'edge:change:attrs': EdgeEventCommonArgs & Cell.EventArgs['change:attrs'] 'edge:change:zIndex': EdgeEventCommonArgs & Cell.EventArgs['change:zIndex'] 'edge:change:markup': EdgeEventCommonArgs & Cell.EventArgs['change:markup'] 'edge:change:visible': EdgeEventCommonArgs & Cell.EventArgs['change:visible'] 'edge:change:parent': EdgeEventCommonArgs & Cell.EventArgs['change:parent'] 'edge:change:children': EdgeEventCommonArgs & Cell.EventArgs['change:children'] 'edge:change:tools': EdgeEventCommonArgs & Cell.EventArgs['change:tools'] 'edge:change:data': EdgeEventCommonArgs & Cell.EventArgs['change:data'] 'edge:change:source': EdgeEventCommonArgs & Cell.EventArgs['change:source'] 'edge:change:target': EdgeEventCommonArgs & Cell.EventArgs['change:target'] 'edge:change:router': EdgeEventCommonArgs & Cell.EventArgs['change:router'] 'edge:change:connector': EdgeEventCommonArgs & Cell.EventArgs['change:connector'] 'edge:change:vertices': EdgeEventCommonArgs & Cell.EventArgs['change:vertices'] 'edge:change:labels': EdgeEventCommonArgs & Cell.EventArgs['change:labels'] 'edge:change:defaultLabel': EdgeEventCommonArgs & Cell.EventArgs['change:defaultLabel'] 'edge:change:toolMarkup': EdgeEventCommonArgs & Cell.EventArgs['change:toolMarkup'] 'edge:change:doubleToolMarkup': EdgeEventCommonArgs & Cell.EventArgs['change:doubleToolMarkup'] 'edge:change:vertexMarkup': EdgeEventCommonArgs & Cell.EventArgs['change:vertexMarkup'] 'edge:change:arrowheadMarkup': EdgeEventCommonArgs & Cell.EventArgs['change:arrowheadMarkup'] 'edge:vertexs:added': EdgeEventCommonArgs & Cell.EventArgs['vertexs:added'] 'edge:vertexs:removed': EdgeEventCommonArgs & Cell.EventArgs['vertexs:removed'] 'edge:labels:added': EdgeEventCommonArgs & Cell.EventArgs['labels:added'] 'edge:labels:removed': EdgeEventCommonArgs & Cell.EventArgs['labels:removed'] 'edge:batch:start': EdgeEventCommonArgs & Cell.EventArgs['batch:start'] 'edge:batch:stop': EdgeEventCommonArgs & Cell.EventArgs['batch:stop'] } }