@knotx/decorators
v0.5.8
Published
Decorators for Knotx
Readme
@knotx/decorators
一个为 Knotx 图形编辑器提供装饰器支持的 TypeScript 包,用于简化插件开发过程。
安装
npm install @knotx/decorators或者使用 yarn:
yarn add @knotx/decorators或者使用 pnpm:
pnpm add @knotx/decorators基本概述
@knotx/decorators 提供了一套完整的 TypeScript 装饰器,帮助开发者更高效地创建 Knotx 插件。这些装饰器封装了常见的插件开发模式,包括节点渲染、边渲染、数据管理、生命周期管理等功能。
主要功能
- 节点管理:提供节点类型定义、操作和管道处理
- 边管理:提供边类型定义、操作和管道处理
- 插件管理:提供配置管理、生命周期控制和工具函数注册
- 数据管理:提供响应式数据绑定和依赖注入
- UI 管理:提供层级管理和面板渲染
API 文档
节点相关装饰器
@nodeType
用于声明一个方法作为节点渲染器。
import { nodeType } from '@knotx/decorators'
class MyPlugin extends BasePlugin {
@nodeType('my-node')
renderNode(node: Node) {
return (
<div className="my-node">
<h3>{node.data.title}</h3>
<p>{node.data.content}</p>
</div>
)
}
}参数:
type: string- 节点类型标识符
返回值:返回一个 React 组件或 JSX 元素
@nodeOperator
用于声明一个方法作为节点操作函数。
import { nodeOperator } from '@knotx/decorators'
class MyPlugin extends BasePlugin {
@nodeOperator()
addNode(x: number, y: number) {
return [{
type: 'add',
node: {
id: generateId(),
type: 'default',
position: { x, y },
data: { title: 'New Node' }
}
}]
}
@nodeOperator()
deleteNode(nodeId: string) {
return [{
type: 'remove',
nodeId
}]
}
}使用说明:
- 方法必须返回一个操作数组
- 操作会自动分发到引擎进行处理
- 支持批量操作
@nodePipe
用于声明一个方法作为节点操作管道,在节点操作执行前后进行处理。
import { nodePipe } from '@knotx/decorators'
import { filter, map } from 'rxjs/operators'
class MyPlugin extends BasePlugin {
@nodePipe()
validateNodeOperation() {
return {
preOperation: () => filter((operation) => {
if (operation.type === 'add') {
return this.validateNode(operation.node)
}
return true
}),
postOperation: () => map((operation) => {
console.log('Operation completed:', operation)
return operation
})
}
}
}返回值:返回一个包含 preOperation 和 postOperation 的对象
边相关装饰器
@edgeType
用于声明一个方法作为边渲染器。
import { edgeType } from '@knotx/decorators'
class MyPlugin extends BasePlugin {
@edgeType('my-edge', {
sourcePosition: 'right',
targetPosition: 'left',
sourceXOffset: 10,
targetXOffset: -10
})
renderEdge(edge: Edge) {
return (
<g>
<path
d={edge.path}
stroke="#333"
strokeWidth={2}
fill="none"
/>
<text x={edge.labelX} y={edge.labelY}>
{edge.label}
</text>
</g>
)
}
}参数:
type: string- 边类型标识符edgeConfig?: EdgeConfig- 边配置选项sourcePosition: Position- 源连接点位置targetPosition: Position- 目标连接点位置sourceXOffset: number- 源连接点 X 偏移sourceYOffset: number- 源连接点 Y 偏移targetXOffset: number- 目标连接点 X 偏移targetYOffset: number- 目标连接点 Y 偏移
@edgeOperator
用于声明一个方法作为边操作函数。
import { edgeOperator } from '@knotx/decorators'
class MyPlugin extends BasePlugin {
@edgeOperator()
addEdge(sourceId: string, targetId: string) {
return [{
type: 'add',
edge: {
id: generateId(),
source: sourceId,
target: targetId,
type: 'default'
}
}]
}
}@edgePipe
用于声明一个方法作为边操作管道。
import { edgePipe } from '@knotx/decorators'
class MyPlugin extends BasePlugin {
@edgePipe()
validateEdgeOperation() {
return {
preOperation: () => filter((operation) => {
if (operation.type === 'add') {
return this.validateEdge(operation.edge)
}
return true
})
}
}
}插件相关装饰器
@config
自动绑定配置管理和生命周期函数。
import { config } from '@knotx/decorators'
class MyPlugin extends BasePlugin {
@config()
config = {
theme: 'light',
showGrid: true,
gridSize: 20
}
// 配置会自动在 onInit 和 onConfigChange 时更新
}@register
用于将插件的属性注册到引擎中,使其他插件可以通过 inject 访问。
import { register } from '@knotx/decorators'
class HistoryPlugin extends BasePlugin {
@register('canUndo')
canUndo = false
@register('canRedo')
canRedo = false
@register('history')
get history() {
return this.historyStack
}
}@tool
用于装饰插件中的工具函数,使其能被引擎和其他插件调用。
import { tool } from '@knotx/decorators'
class MathPlugin extends BasePlugin {
@tool('计算两点距离', {
type: 'object',
properties: {
x1: { type: 'number', description: '第一个点的 X 坐标' },
y1: { type: 'number', description: '第一个点的 Y 坐标' },
x2: { type: 'number', description: '第二个点的 X 坐标' },
y2: { type: 'number', description: '第二个点的 Y 坐标' }
},
required: ['x1', 'y1', 'x2', 'y2']
})
calculateDistance(params: { x1: number, y1: number, x2: number, y2: number }) {
const dx = params.x2 - params.x1
const dy = params.y2 - params.y1
return Math.sqrt(dx * dx + dy * dy)
}
}参数:
description: string- 工具函数描述parameters: Schema- 参数的 JSON Schema 定义
@observable
创建响应式属性,支持数据绑定和变更监听。
import { observable } from '@knotx/decorators'
class MyPlugin extends BasePlugin {
@observable()
selectedNodes: string[] = []
@observable()
zoomLevel = 1
updateSelection(nodeIds: string[]) {
this.selectedNodes = nodeIds // 自动触发变更通知
}
}生命周期装饰器
@OnInit
在插件初始化时调用。
import { OnInit } from '@knotx/decorators'
class MyPlugin extends BasePlugin {
@OnInit
initialize(config: PluginConfig) {
console.log('Plugin initialized with config:', config)
this.setupEventListeners()
}
}@OnDestroy
在插件销毁时调用。
import { OnDestroy } from '@knotx/decorators'
class MyPlugin extends BasePlugin {
@OnDestroy
cleanup() {
console.log('Plugin destroyed')
this.clearEventListeners()
}
}@OnConfigChange
在插件配置变更时调用。
import { OnConfigChange } from '@knotx/decorators'
class MyPlugin extends BasePlugin {
@OnConfigChange
handleConfigChange(newConfig: PluginConfig) {
console.log('Config changed:', newConfig)
this.applyNewConfig(newConfig)
}
}引擎相关装饰器
@inject
用于注入引擎变量或其他插件的数据。
import { inject } from '@knotx/decorators'
class MyPlugin extends BasePlugin {
// 注入引擎变量
@inject.nodes()
nodes: Node[] = []
// 注入引擎方法
@inject.getNodeById()
getNodeById!: (id: string) => Node | undefined
// 注入其他插件的数据
@inject.history.canUndo()
canUndo = false
// 使用选择器注入特定数据
@inject.nodes((nodes: Node[]) => nodes.filter(n => n.selected))
selectedNodes: Node[] = []
}使用方式:
@inject.property()- 注入引擎变量@inject.property(selector)- 注入引擎变量并使用选择器@inject.pluginName.property()- 注入插件变量@inject.methodName()- 注入引擎方法
@subscribe
用于订阅引擎变量或其他插件的数据变化。
import { subscribe } from '@knotx/decorators'
class MyPlugin extends BasePlugin {
// 订阅引擎变量
@subscribe.nodes()
handleNodesChange(nodes: Node[]) {
console.log('Nodes changed:', nodes)
this.updateUI(nodes)
}
// 订阅其他插件的数据
@subscribe.history.canUndo()
handleUndoStateChange(canUndo: boolean) {
this.updateUndoButton(canUndo)
}
}层相关装饰器
@layer
用于声明方法所属的渲染层级。
import { Layer } from '@knotx/core'
import { layer } from '@knotx/decorators'
class MyPlugin extends BasePlugin {
@layer(Layer.BACKGROUND)
renderBackground() {
return (
<rect width="100%" height="100%" fill="#f0f0f0" />
)
}
@layer(Layer.FOREGROUND, 100)
renderOverlay() {
return (
<div className="overlay">
Overlay content
</div>
)
}
}参数:
layer: Layer- 层级枚举值offset?: number- 层级偏移值
@panel
用于创建面板组件。
import { panel } from '@knotx/decorators'
class MyPlugin extends BasePlugin {
@panel('top', { x: 10, y: 10 })
renderToolbar() {
return (
<div className="toolbar">
<button>Save</button>
<button>Load</button>
<button>Export</button>
</div>
)
}
@panel('bottom-right')
renderStatus() {
return (
<div className="status">
Ready
</div>
)
}
}参数:
position: Position- 面板位置('top' | 'bottom' | 'left' | 'right' | 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right')offset?: PanelOffset- 面板偏移x?: number- X 轴偏移y?: number- Y 轴偏移
文件目录结构
src/
├── index.ts # 主导出文件
├── internal.ts # 内部工具函数
├── edge/ # 边相关装饰器
│ ├── index.ts # 边模块导出
│ ├── edgeType.ts # 边类型装饰器
│ ├── edgeOperator.ts # 边操作装饰器
│ └── edgePipe.ts # 边管道装饰器
├── engine/ # 引擎相关装饰器
│ ├── index.ts # 引擎模块导出
│ ├── inject/ # 注入装饰器
│ │ ├── index.ts # 注入模块导出
│ │ ├── inject-data.ts # 数据注入实现
│ │ ├── inject-decorator.ts # 注入装饰器实现
│ │ ├── inject-method.ts # 方法注入实现
│ │ ├── inject-plugin.ts # 插件注入实现
│ │ ├── injectable.ts # 可注入接口
│ │ ├── injectable-proxy.ts # 注入代理实现
│ │ ├── proxy.ts # 代理对象
│ │ └── types.ts # 类型定义
│ └── subscribe/ # 订阅装饰器
│ ├── index.ts # 订阅模块导出
│ ├── proxy.ts # 订阅代理对象
│ ├── subscribe-data.ts # 数据订阅实现
│ ├── subscribe-decorator.ts # 订阅装饰器实现
│ ├── subscribe-plugin.ts # 插件订阅实现
│ └── types.ts # 类型定义
├── layer/ # 层相关装饰器
│ ├── index.ts # 层模块导出
│ ├── layer.ts # 层装饰器
│ └── panel.tsx # 面板装饰器
├── node/ # 节点相关装饰器
│ ├── index.ts # 节点模块导出
│ ├── nodeType.ts # 节点类型装饰器
│ ├── nodeOperator.ts # 节点操作装饰器
│ └── nodePipe.ts # 节点管道装饰器
└── plugin/ # 插件相关装饰器
├── index.ts # 插件模块导出
├── config.ts # 配置装饰器
├── lifecycle.ts # 生命周期装饰器
├── observable.ts # 响应式装饰器
├── register.ts # 注册装饰器
└── tool.ts # 工具函数装饰器依赖关系
对等依赖 (Peer Dependencies)
@knotx/jsx- Knotx JSX 运行时支持
主要依赖 (Dependencies)
@knotx/core- Knotx 核心库jsonschema- JSON Schema 验证库lodash-es- 实用工具函数库rxjs- 响应式编程库
许可证
MIT License
贡献指南
欢迎提交 Issue 和 Pull Request。在提交代码前,请确保:
- 代码符合项目的 ESLint 规则
- 通过 TypeScript 类型检查
- 添加必要的测试用例
- 更新相关文档
