@knotx/plugins-history 0.4.12 → 0.4.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +378 -0
- package/README.md +378 -0
- package/package.json +6 -6
package/README.en.md
ADDED
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
# @knotx/plugins-history
|
|
2
|
+
|
|
3
|
+
History plugin that provides undo/redo functionality for KnotX.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @knotx/plugins-history
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Overview
|
|
12
|
+
|
|
13
|
+
The History plugin provides complete history management functionality for KnotX, including undo, redo, state tagging, and more. This plugin tracks all data changes and provides convenient history operation interfaces.
|
|
14
|
+
|
|
15
|
+
## Implementation Principle
|
|
16
|
+
|
|
17
|
+
The core implementation principles of the History plugin:
|
|
18
|
+
|
|
19
|
+
1. **Operation Interception**: Intercepts all data manager operations and records states before and after changes
|
|
20
|
+
2. **Buffering Mechanism**: Uses debouncing to batch operations and avoid frequent history record creation
|
|
21
|
+
3. **Reverse Operations**: Generates reverse operations for each operation to enable undo functionality
|
|
22
|
+
4. **State Tagging**: Supports tagging specific states for quick return to designated history points
|
|
23
|
+
|
|
24
|
+
## Dependencies
|
|
25
|
+
|
|
26
|
+
### Core Dependencies
|
|
27
|
+
- `@knotx/core`: Provides base plugin architecture and data management
|
|
28
|
+
- `@knotx/decorators`: Provides decorator support
|
|
29
|
+
- `rxjs`: Provides reactive programming support
|
|
30
|
+
|
|
31
|
+
### Automatic Dependencies
|
|
32
|
+
- `nodesManager` and `edgesManager` from `@knotx/core`: Automatically registered to the history system
|
|
33
|
+
|
|
34
|
+
## API Documentation
|
|
35
|
+
|
|
36
|
+
### Main Classes
|
|
37
|
+
|
|
38
|
+
#### History
|
|
39
|
+
|
|
40
|
+
The main class of the History plugin, extending `BasePlugin`.
|
|
41
|
+
|
|
42
|
+
```typescript
|
|
43
|
+
export class History<T extends Record<string, IData> = Record<string, any>> extends BasePlugin<'history', HistoryConfig> {
|
|
44
|
+
name = 'history' as const
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Configuration Options
|
|
49
|
+
|
|
50
|
+
#### HistoryConfig
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
export interface HistoryConfig {
|
|
54
|
+
/** Maximum number of history records */
|
|
55
|
+
maxHistory?: number
|
|
56
|
+
/** Debounce time (milliseconds) */
|
|
57
|
+
debounceTime?: number
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Plugin Data (PluginData)
|
|
62
|
+
|
|
63
|
+
The History plugin provides the following data:
|
|
64
|
+
|
|
65
|
+
```typescript
|
|
66
|
+
interface PluginData {
|
|
67
|
+
history: {
|
|
68
|
+
ref: IHistory
|
|
69
|
+
canUndo: boolean
|
|
70
|
+
canRedo: boolean
|
|
71
|
+
registerDataManager: (dataManager: DataManager<any>) => (() => void)
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Interface Definitions
|
|
77
|
+
|
|
78
|
+
#### IHistory
|
|
79
|
+
|
|
80
|
+
```typescript
|
|
81
|
+
export interface IHistory {
|
|
82
|
+
isUndoRedo: boolean
|
|
83
|
+
undo: () => void
|
|
84
|
+
redo: () => void
|
|
85
|
+
addTag: (tag: string) => void
|
|
86
|
+
removeTag: (tag: string) => void
|
|
87
|
+
toTag: (tag: string) => void
|
|
88
|
+
clearTags: () => void
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
#### HistoryState
|
|
93
|
+
|
|
94
|
+
```typescript
|
|
95
|
+
export interface HistoryState<T extends Record<string, IData> = Record<string, any>> {
|
|
96
|
+
value: {
|
|
97
|
+
[key: string]: HistoryOperation<T>[]
|
|
98
|
+
}
|
|
99
|
+
timestamp: number
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Usage Examples
|
|
104
|
+
|
|
105
|
+
### Basic Usage
|
|
106
|
+
|
|
107
|
+
```typescript
|
|
108
|
+
import { History } from '@knotx/plugins-history'
|
|
109
|
+
|
|
110
|
+
const engine = new Engine({
|
|
111
|
+
plugins: [History],
|
|
112
|
+
pluginConfig: {
|
|
113
|
+
history: {
|
|
114
|
+
maxHistory: 50,
|
|
115
|
+
debounceTime: 100,
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
})
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### Undo and Redo Operations
|
|
122
|
+
|
|
123
|
+
```typescript
|
|
124
|
+
const engine = new Engine({
|
|
125
|
+
plugins: [History],
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
const historyData = engine.getPluginData('history')
|
|
129
|
+
|
|
130
|
+
// Undo operation
|
|
131
|
+
if (historyData.canUndo) {
|
|
132
|
+
historyData.ref.undo()
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Redo operation
|
|
136
|
+
if (historyData.canRedo) {
|
|
137
|
+
historyData.ref.redo()
|
|
138
|
+
}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### Listening to History State
|
|
142
|
+
|
|
143
|
+
```tsx
|
|
144
|
+
class HistoryUIPlugin extends BasePlugin {
|
|
145
|
+
@inject.history.canUndo()
|
|
146
|
+
canUndo!: boolean
|
|
147
|
+
|
|
148
|
+
@inject.history.canRedo()
|
|
149
|
+
canRedo!: boolean
|
|
150
|
+
|
|
151
|
+
@inject.history.ref()
|
|
152
|
+
history!: IHistory
|
|
153
|
+
|
|
154
|
+
@panel('bottom')
|
|
155
|
+
render() {
|
|
156
|
+
return (
|
|
157
|
+
<div>
|
|
158
|
+
<button
|
|
159
|
+
disabled={!this.canUndo}
|
|
160
|
+
onClick={() => this.history.undo()}
|
|
161
|
+
>
|
|
162
|
+
Undo
|
|
163
|
+
</button>
|
|
164
|
+
<button
|
|
165
|
+
disabled={!this.canRedo}
|
|
166
|
+
onClick={() => this.history.redo()}
|
|
167
|
+
>
|
|
168
|
+
Redo
|
|
169
|
+
</button>
|
|
170
|
+
</div>
|
|
171
|
+
)
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### State Tagging Functionality
|
|
177
|
+
|
|
178
|
+
```typescript
|
|
179
|
+
const engine = new Engine({
|
|
180
|
+
plugins: [History],
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
const historyData = engine.getPluginData('history')
|
|
184
|
+
|
|
185
|
+
// Tag current state
|
|
186
|
+
historyData.ref.addTag('checkpoint-1')
|
|
187
|
+
|
|
188
|
+
// After performing a series of operations...
|
|
189
|
+
// Return to tagged state
|
|
190
|
+
historyData.ref.toTag('checkpoint-1')
|
|
191
|
+
|
|
192
|
+
// Remove tag
|
|
193
|
+
historyData.ref.removeTag('checkpoint-1')
|
|
194
|
+
|
|
195
|
+
// Clear all tags
|
|
196
|
+
historyData.ref.clearTags()
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
### Custom Data Manager Integration
|
|
200
|
+
|
|
201
|
+
```typescript
|
|
202
|
+
class CustomDataManager extends DataManager<MyData> {
|
|
203
|
+
constructor() {
|
|
204
|
+
super('custom')
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const engine = new Engine({
|
|
209
|
+
plugins: [History],
|
|
210
|
+
})
|
|
211
|
+
|
|
212
|
+
const historyData = engine.getPluginData('history')
|
|
213
|
+
const customManager = new CustomDataManager()
|
|
214
|
+
|
|
215
|
+
// Register custom data manager
|
|
216
|
+
const unregister = historyData.registerDataManager(customManager)
|
|
217
|
+
|
|
218
|
+
// Unregister
|
|
219
|
+
unregister()
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
## Advanced Features
|
|
223
|
+
|
|
224
|
+
### Batch Operation Optimization
|
|
225
|
+
|
|
226
|
+
```typescript
|
|
227
|
+
class BatchHistoryPlugin extends BasePlugin {
|
|
228
|
+
@inject.history.ref()
|
|
229
|
+
history!: IHistory
|
|
230
|
+
|
|
231
|
+
performBatchOperation() {
|
|
232
|
+
// Batch operations are automatically debounced
|
|
233
|
+
engine.dispatchNodeOperation({
|
|
234
|
+
type: 'add',
|
|
235
|
+
data: { id: 'node1', /* ... */ },
|
|
236
|
+
})
|
|
237
|
+
|
|
238
|
+
engine.dispatchNodeOperation({
|
|
239
|
+
type: 'add',
|
|
240
|
+
data: { id: 'node2', /* ... */ },
|
|
241
|
+
})
|
|
242
|
+
|
|
243
|
+
// These operations will be merged into a single history entry
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
### Conditional History Recording
|
|
249
|
+
|
|
250
|
+
```typescript
|
|
251
|
+
class ConditionalHistoryPlugin extends BasePlugin {
|
|
252
|
+
@inject.history.ref()
|
|
253
|
+
history!: IHistory
|
|
254
|
+
|
|
255
|
+
@inject.nodesManager()
|
|
256
|
+
nodesManager!: DataManager<Node>
|
|
257
|
+
|
|
258
|
+
performTemporaryOperation() {
|
|
259
|
+
// Check if in undo/redo process
|
|
260
|
+
if (this.history.isUndoRedo) {
|
|
261
|
+
// Avoid creating new history records during undo/redo
|
|
262
|
+
return
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Normal operation
|
|
266
|
+
this.nodesManager.dispatch({
|
|
267
|
+
type: 'update',
|
|
268
|
+
data: { id: 'node1', /* ... */ },
|
|
269
|
+
})
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
### History State Monitoring
|
|
275
|
+
|
|
276
|
+
```typescript
|
|
277
|
+
class HistoryMonitorPlugin extends BasePlugin {
|
|
278
|
+
@inject.history.ref()
|
|
279
|
+
history!: IHistory
|
|
280
|
+
|
|
281
|
+
@subscribe.history.canUndo()
|
|
282
|
+
onCanUndoChange(canUndo: boolean) {
|
|
283
|
+
console.log('Can undo:', canUndo)
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
@subscribe.history.canRedo()
|
|
287
|
+
onCanRedoChange(canRedo: boolean) {
|
|
288
|
+
console.log('Can redo:', canRedo)
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// Custom history record monitoring
|
|
292
|
+
@OnInit
|
|
293
|
+
init() {
|
|
294
|
+
// Listen to history changes
|
|
295
|
+
this.history.onHistoryChange?.subscribe((state) => {
|
|
296
|
+
console.log('History changed:', state)
|
|
297
|
+
})
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
## Performance Optimization
|
|
303
|
+
|
|
304
|
+
### Debounce Configuration
|
|
305
|
+
|
|
306
|
+
```typescript
|
|
307
|
+
const engine = new Engine({
|
|
308
|
+
plugins: [History],
|
|
309
|
+
pluginConfig: {
|
|
310
|
+
history: {
|
|
311
|
+
debounceTime: 200, // Increase debounce time to reduce history entries
|
|
312
|
+
},
|
|
313
|
+
},
|
|
314
|
+
})
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
### History Record Limits
|
|
318
|
+
|
|
319
|
+
```typescript
|
|
320
|
+
const engine = new Engine({
|
|
321
|
+
plugins: [History],
|
|
322
|
+
pluginConfig: {
|
|
323
|
+
history: {
|
|
324
|
+
maxHistory: 20, // Reduce maximum history records to save memory
|
|
325
|
+
},
|
|
326
|
+
},
|
|
327
|
+
})
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
## File Directory Structure
|
|
331
|
+
|
|
332
|
+
```
|
|
333
|
+
packages/plugins-history/
|
|
334
|
+
├── src/
|
|
335
|
+
│ ├── history.ts # Main implementation file
|
|
336
|
+
│ └── index.ts # Export file
|
|
337
|
+
├── dist/ # Build output directory
|
|
338
|
+
├── package.json # Package configuration
|
|
339
|
+
├── build.config.ts # Build configuration
|
|
340
|
+
├── tsconfig.json # TypeScript configuration
|
|
341
|
+
├── eslint.config.mjs # ESLint configuration
|
|
342
|
+
└── CHANGELOG.md # Changelog
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
### Core Files Description
|
|
346
|
+
|
|
347
|
+
- **history.ts**: Contains the main implementation of the History plugin, including operation interception, history management, and state tagging functionality
|
|
348
|
+
- **index.ts**: Exports the History class and related type definitions
|
|
349
|
+
|
|
350
|
+
## Best Practices
|
|
351
|
+
|
|
352
|
+
### Memory Management
|
|
353
|
+
|
|
354
|
+
1. **Set Reasonable History Count**: Configure appropriate `maxHistory` value based on application needs
|
|
355
|
+
2. **Clean Up Tags Timely**: Remove state tags that are no longer needed
|
|
356
|
+
3. **Avoid Excessive Debouncing**: Too long debounce time may affect user experience
|
|
357
|
+
|
|
358
|
+
### User Experience
|
|
359
|
+
|
|
360
|
+
1. **Provide Undo/Redo Buttons**: Offer clear undo/redo operation entry points in the UI
|
|
361
|
+
2. **Display Operation Status**: Enable/disable buttons based on `canUndo` and `canRedo` states
|
|
362
|
+
3. **Keyboard Shortcuts**: Support `Ctrl+Z` and `Ctrl+Y` shortcuts
|
|
363
|
+
|
|
364
|
+
### Development and Debugging
|
|
365
|
+
|
|
366
|
+
1. **Monitor History State**: Listen to history changes during development for debugging
|
|
367
|
+
2. **Use State Tags**: Set state tags at key operation points for testing and debugging
|
|
368
|
+
|
|
369
|
+
## Notes
|
|
370
|
+
|
|
371
|
+
1. **Performance with Large Data**: History records consume memory; consider performance optimization with large datasets
|
|
372
|
+
2. **Asynchronous Operations**: Async operations may cause history order confusion; requires special attention
|
|
373
|
+
3. **Data Consistency**: Ensure undo/redo operations don't break data consistency
|
|
374
|
+
4. **Circular References**: Avoid creating circular references during history record processing
|
|
375
|
+
|
|
376
|
+
## License
|
|
377
|
+
|
|
378
|
+
MIT
|
package/README.md
ADDED
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
# @knotx/plugins-history
|
|
2
|
+
|
|
3
|
+
历史记录插件,为 KnotX 提供撤销/重做功能。
|
|
4
|
+
|
|
5
|
+
## 安装
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @knotx/plugins-history
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## 概述
|
|
12
|
+
|
|
13
|
+
History 插件为 KnotX 提供了完整的历史记录管理功能,包括撤销、重做、标记状态等。该插件能够跟踪所有数据变化,并提供便捷的历史记录操作接口。
|
|
14
|
+
|
|
15
|
+
## 实现原理
|
|
16
|
+
|
|
17
|
+
History 插件的核心实现原理:
|
|
18
|
+
|
|
19
|
+
1. **操作拦截**:拦截所有数据管理器的操作,记录变化前后的状态
|
|
20
|
+
2. **缓冲机制**:使用防抖机制批量处理操作,避免频繁的历史记录创建
|
|
21
|
+
3. **反向操作**:为每个操作生成反向操作,实现撤销功能
|
|
22
|
+
4. **状态标记**:支持标记特定状态,快速回到指定的历史点
|
|
23
|
+
|
|
24
|
+
## 依赖关系
|
|
25
|
+
|
|
26
|
+
### 核心依赖
|
|
27
|
+
- `@knotx/core`:提供基础插件架构和数据管理
|
|
28
|
+
- `@knotx/decorators`:提供装饰器支持
|
|
29
|
+
- `rxjs`:提供响应式编程支持
|
|
30
|
+
|
|
31
|
+
### 自动依赖
|
|
32
|
+
- `@knotx/core` 中的 `nodesManager` 和 `edgesManager`:自动注册到历史记录系统
|
|
33
|
+
|
|
34
|
+
## API 文档
|
|
35
|
+
|
|
36
|
+
### 主要类
|
|
37
|
+
|
|
38
|
+
#### History
|
|
39
|
+
|
|
40
|
+
历史记录插件的主要类,继承自 `BasePlugin`。
|
|
41
|
+
|
|
42
|
+
```typescript
|
|
43
|
+
export class History<T extends Record<string, IData> = Record<string, any>> extends BasePlugin<'history', HistoryConfig> {
|
|
44
|
+
name = 'history' as const
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### 配置选项
|
|
49
|
+
|
|
50
|
+
#### HistoryConfig
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
export interface HistoryConfig {
|
|
54
|
+
/** 最大历史记录数量 */
|
|
55
|
+
maxHistory?: number
|
|
56
|
+
/** 防抖时间(毫秒) */
|
|
57
|
+
debounceTime?: number
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### 插件数据 (PluginData)
|
|
62
|
+
|
|
63
|
+
History 插件提供以下数据:
|
|
64
|
+
|
|
65
|
+
```typescript
|
|
66
|
+
interface PluginData {
|
|
67
|
+
history: {
|
|
68
|
+
ref: IHistory
|
|
69
|
+
canUndo: boolean
|
|
70
|
+
canRedo: boolean
|
|
71
|
+
registerDataManager: (dataManager: DataManager<any>) => (() => void)
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### 接口定义
|
|
77
|
+
|
|
78
|
+
#### IHistory
|
|
79
|
+
|
|
80
|
+
```typescript
|
|
81
|
+
export interface IHistory {
|
|
82
|
+
isUndoRedo: boolean
|
|
83
|
+
undo: () => void
|
|
84
|
+
redo: () => void
|
|
85
|
+
addTag: (tag: string) => void
|
|
86
|
+
removeTag: (tag: string) => void
|
|
87
|
+
toTag: (tag: string) => void
|
|
88
|
+
clearTags: () => void
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
#### HistoryState
|
|
93
|
+
|
|
94
|
+
```typescript
|
|
95
|
+
export interface HistoryState<T extends Record<string, IData> = Record<string, any>> {
|
|
96
|
+
value: {
|
|
97
|
+
[key: string]: HistoryOperation<T>[]
|
|
98
|
+
}
|
|
99
|
+
timestamp: number
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## 使用示例
|
|
104
|
+
|
|
105
|
+
### 基本用法
|
|
106
|
+
|
|
107
|
+
```typescript
|
|
108
|
+
import { History } from '@knotx/plugins-history'
|
|
109
|
+
|
|
110
|
+
const engine = new Engine({
|
|
111
|
+
plugins: [History],
|
|
112
|
+
pluginConfig: {
|
|
113
|
+
history: {
|
|
114
|
+
maxHistory: 50,
|
|
115
|
+
debounceTime: 100,
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
})
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### 撤销和重做操作
|
|
122
|
+
|
|
123
|
+
```typescript
|
|
124
|
+
const engine = new Engine({
|
|
125
|
+
plugins: [History],
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
const historyData = engine.getPluginData('history')
|
|
129
|
+
|
|
130
|
+
// 撤销操作
|
|
131
|
+
if (historyData.canUndo) {
|
|
132
|
+
historyData.ref.undo()
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// 重做操作
|
|
136
|
+
if (historyData.canRedo) {
|
|
137
|
+
historyData.ref.redo()
|
|
138
|
+
}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### 监听历史记录状态
|
|
142
|
+
|
|
143
|
+
```tsx
|
|
144
|
+
class HistoryUIPlugin extends BasePlugin {
|
|
145
|
+
@inject.history.canUndo()
|
|
146
|
+
canUndo!: boolean
|
|
147
|
+
|
|
148
|
+
@inject.history.canRedo()
|
|
149
|
+
canRedo!: boolean
|
|
150
|
+
|
|
151
|
+
@inject.history.ref()
|
|
152
|
+
history!: IHistory
|
|
153
|
+
|
|
154
|
+
@panel('bottom')
|
|
155
|
+
render() {
|
|
156
|
+
return (
|
|
157
|
+
<div>
|
|
158
|
+
<button
|
|
159
|
+
disabled={!this.canUndo}
|
|
160
|
+
onClick={() => this.history.undo()}
|
|
161
|
+
>
|
|
162
|
+
撤销
|
|
163
|
+
</button>
|
|
164
|
+
<button
|
|
165
|
+
disabled={!this.canRedo}
|
|
166
|
+
onClick={() => this.history.redo()}
|
|
167
|
+
>
|
|
168
|
+
重做
|
|
169
|
+
</button>
|
|
170
|
+
</div>
|
|
171
|
+
)
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### 状态标记功能
|
|
177
|
+
|
|
178
|
+
```typescript
|
|
179
|
+
const engine = new Engine({
|
|
180
|
+
plugins: [History],
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
const historyData = engine.getPluginData('history')
|
|
184
|
+
|
|
185
|
+
// 标记当前状态
|
|
186
|
+
historyData.ref.addTag('checkpoint-1')
|
|
187
|
+
|
|
188
|
+
// 在进行一系列操作后...
|
|
189
|
+
// 回到标记的状态
|
|
190
|
+
historyData.ref.toTag('checkpoint-1')
|
|
191
|
+
|
|
192
|
+
// 移除标记
|
|
193
|
+
historyData.ref.removeTag('checkpoint-1')
|
|
194
|
+
|
|
195
|
+
// 清空所有标记
|
|
196
|
+
historyData.ref.clearTags()
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
### 自定义数据管理器集成
|
|
200
|
+
|
|
201
|
+
```typescript
|
|
202
|
+
class CustomDataManager extends DataManager<MyData> {
|
|
203
|
+
constructor() {
|
|
204
|
+
super('custom')
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const engine = new Engine({
|
|
209
|
+
plugins: [History],
|
|
210
|
+
})
|
|
211
|
+
|
|
212
|
+
const historyData = engine.getPluginData('history')
|
|
213
|
+
const customManager = new CustomDataManager()
|
|
214
|
+
|
|
215
|
+
// 注册自定义数据管理器
|
|
216
|
+
const unregister = historyData.registerDataManager(customManager)
|
|
217
|
+
|
|
218
|
+
// 取消注册
|
|
219
|
+
unregister()
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
## 高级功能
|
|
223
|
+
|
|
224
|
+
### 批量操作优化
|
|
225
|
+
|
|
226
|
+
```typescript
|
|
227
|
+
class BatchHistoryPlugin extends BasePlugin {
|
|
228
|
+
@inject.history.ref()
|
|
229
|
+
history!: IHistory
|
|
230
|
+
|
|
231
|
+
performBatchOperation() {
|
|
232
|
+
// 批量操作会自动被防抖处理
|
|
233
|
+
engine.dispatchNodeOperation({
|
|
234
|
+
type: 'add',
|
|
235
|
+
data: { id: 'node1', /* ... */ },
|
|
236
|
+
})
|
|
237
|
+
|
|
238
|
+
engine.dispatchNodeOperation({
|
|
239
|
+
type: 'add',
|
|
240
|
+
data: { id: 'node2', /* ... */ },
|
|
241
|
+
})
|
|
242
|
+
|
|
243
|
+
// 这些操作会被合并为一个历史记录条目
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
### 条件性历史记录
|
|
249
|
+
|
|
250
|
+
```typescript
|
|
251
|
+
class ConditionalHistoryPlugin extends BasePlugin {
|
|
252
|
+
@inject.history.ref()
|
|
253
|
+
history!: IHistory
|
|
254
|
+
|
|
255
|
+
@inject.nodesManager()
|
|
256
|
+
nodesManager!: DataManager<Node>
|
|
257
|
+
|
|
258
|
+
performTemporaryOperation() {
|
|
259
|
+
// 检查是否在撤销/重做过程中
|
|
260
|
+
if (this.history.isUndoRedo) {
|
|
261
|
+
// 在撤销/重做过程中,避免创建新的历史记录
|
|
262
|
+
return
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// 正常操作
|
|
266
|
+
this.nodesManager.dispatch({
|
|
267
|
+
type: 'update',
|
|
268
|
+
data: { id: 'node1', /* ... */ },
|
|
269
|
+
})
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
### 历史记录状态监听
|
|
275
|
+
|
|
276
|
+
```typescript
|
|
277
|
+
class HistoryMonitorPlugin extends BasePlugin {
|
|
278
|
+
@inject.history.ref()
|
|
279
|
+
history!: IHistory
|
|
280
|
+
|
|
281
|
+
@subscribe.history.canUndo()
|
|
282
|
+
onCanUndoChange(canUndo: boolean) {
|
|
283
|
+
console.log('Can undo:', canUndo)
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
@subscribe.history.canRedo()
|
|
287
|
+
onCanRedoChange(canRedo: boolean) {
|
|
288
|
+
console.log('Can redo:', canRedo)
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// 自定义历史记录监听
|
|
292
|
+
@OnInit
|
|
293
|
+
init() {
|
|
294
|
+
// 监听历史记录变化
|
|
295
|
+
this.history.onHistoryChange?.subscribe((state) => {
|
|
296
|
+
console.log('History changed:', state)
|
|
297
|
+
})
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
## 性能优化
|
|
303
|
+
|
|
304
|
+
### 防抖配置
|
|
305
|
+
|
|
306
|
+
```typescript
|
|
307
|
+
const engine = new Engine({
|
|
308
|
+
plugins: [History],
|
|
309
|
+
pluginConfig: {
|
|
310
|
+
history: {
|
|
311
|
+
debounceTime: 200, // 增加防抖时间以减少历史记录条目
|
|
312
|
+
},
|
|
313
|
+
},
|
|
314
|
+
})
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
### 历史记录限制
|
|
318
|
+
|
|
319
|
+
```typescript
|
|
320
|
+
const engine = new Engine({
|
|
321
|
+
plugins: [History],
|
|
322
|
+
pluginConfig: {
|
|
323
|
+
history: {
|
|
324
|
+
maxHistory: 20, // 减少最大历史记录数量以节省内存
|
|
325
|
+
},
|
|
326
|
+
},
|
|
327
|
+
})
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
## 文件目录结构
|
|
331
|
+
|
|
332
|
+
```
|
|
333
|
+
packages/plugins-history/
|
|
334
|
+
├── src/
|
|
335
|
+
│ ├── history.ts # 主要实现文件
|
|
336
|
+
│ └── index.ts # 导出文件
|
|
337
|
+
├── dist/ # 构建输出目录
|
|
338
|
+
├── package.json # 包配置文件
|
|
339
|
+
├── build.config.ts # 构建配置
|
|
340
|
+
├── tsconfig.json # TypeScript 配置
|
|
341
|
+
├── eslint.config.mjs # ESLint 配置
|
|
342
|
+
└── CHANGELOG.md # 更新日志
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
### 核心文件说明
|
|
346
|
+
|
|
347
|
+
- **history.ts**:包含 History 插件的主要实现,包括操作拦截、历史记录管理和状态标记功能
|
|
348
|
+
- **index.ts**:导出 History 类和相关类型定义
|
|
349
|
+
|
|
350
|
+
## 最佳实践
|
|
351
|
+
|
|
352
|
+
### 内存管理
|
|
353
|
+
|
|
354
|
+
1. **合理设置历史记录数量**:根据应用需求设置合适的 `maxHistory` 值
|
|
355
|
+
2. **及时清理标记**:不再需要的状态标记应及时清理
|
|
356
|
+
3. **避免过度防抖**:过长的防抖时间可能影响用户体验
|
|
357
|
+
|
|
358
|
+
### 用户体验
|
|
359
|
+
|
|
360
|
+
1. **提供撤销/重做按钮**:在 UI 中提供清晰的撤销/重做操作入口
|
|
361
|
+
2. **显示操作状态**:根据 `canUndo` 和 `canRedo` 状态禁用/启用按钮
|
|
362
|
+
3. **键盘快捷键**:支持 `Ctrl+Z` 和 `Ctrl+Y` 快捷键
|
|
363
|
+
|
|
364
|
+
### 开发调试
|
|
365
|
+
|
|
366
|
+
1. **监听历史记录状态**:在开发阶段监听历史记录变化以便调试
|
|
367
|
+
2. **使用状态标记**:在关键操作点设置状态标记,方便测试和调试
|
|
368
|
+
|
|
369
|
+
## 注意事项
|
|
370
|
+
|
|
371
|
+
1. **大量数据性能**:历史记录会占用内存,大量数据时注意性能优化
|
|
372
|
+
2. **异步操作**:异步操作可能导致历史记录顺序混乱,需要特别注意
|
|
373
|
+
3. **数据一致性**:确保撤销/重做操作不会破坏数据的一致性
|
|
374
|
+
4. **循环引用**:避免在历史记录处理过程中创建循环引用
|
|
375
|
+
|
|
376
|
+
## 许可证
|
|
377
|
+
|
|
378
|
+
MIT
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@knotx/plugins-history",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.13",
|
|
4
4
|
"description": "History Plugin for Knotx",
|
|
5
5
|
"author": "boenfu",
|
|
6
6
|
"license": "MIT",
|
|
@@ -29,13 +29,13 @@
|
|
|
29
29
|
],
|
|
30
30
|
"dependencies": {
|
|
31
31
|
"rxjs": "^7.8.1",
|
|
32
|
-
"@knotx/core": "0.4.
|
|
33
|
-
"@knotx/decorators": "0.4.
|
|
32
|
+
"@knotx/core": "0.4.13",
|
|
33
|
+
"@knotx/decorators": "0.4.13"
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|
|
36
|
-
"@knotx/build-config": "0.4.
|
|
37
|
-
"@knotx/eslint-config": "0.4.
|
|
38
|
-
"@knotx/typescript-config": "0.4.
|
|
36
|
+
"@knotx/build-config": "0.4.13",
|
|
37
|
+
"@knotx/eslint-config": "0.4.13",
|
|
38
|
+
"@knotx/typescript-config": "0.4.13"
|
|
39
39
|
},
|
|
40
40
|
"scripts": {
|
|
41
41
|
"build": "unbuild",
|