@antv/x6
Version:
JavaScript diagramming library that uses SVG and HTML for rendering
213 lines (182 loc) • 5.05 kB
text/typescript
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()
}
dispose() {
this.clearWidgets()
this.stopListening()
this.off()
CssLoader.clean(this.name)
}
}