@flowgram-vue/history 0.2.0

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.
@@ -0,0 +1,166 @@
1
+ /**
2
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
3
+ * SPDX-License-Identifier: MIT
4
+ */
5
+
6
+ import { cloneDeep } from 'lodash-es';
7
+ import { injectable, inject } from 'inversify';
8
+ import { DisposableCollection, Emitter } from '@flowgram-vue/utils';
9
+
10
+ import { HistoryOperation, Operation, OperationWithId } from '../operation';
11
+ import { HistoryConfig } from '../history-config';
12
+ import {
13
+ type HistoryItem,
14
+ HistoryStackChangeType,
15
+ type HistoryStackItem,
16
+ HistoryStackChangeEvent,
17
+ UndoRedoChangeType,
18
+ } from './types';
19
+ import { type HistoryService } from './history-service';
20
+
21
+ /**
22
+ * 历史栈,聚合所有历史操作
23
+ */
24
+ @injectable()
25
+ export class HistoryStack {
26
+ @inject(HistoryConfig)
27
+ historyConfig: HistoryConfig;
28
+
29
+ private _items: HistoryItem[] = [];
30
+
31
+ readonly onChangeEmitter = new Emitter<HistoryStackChangeEvent>();
32
+
33
+ readonly onChange = this.onChangeEmitter.event;
34
+
35
+ private _toDispose: DisposableCollection = new DisposableCollection();
36
+
37
+ limit = 100;
38
+
39
+ constructor() {
40
+ this._toDispose.push(this.onChangeEmitter);
41
+ }
42
+
43
+ get items(): HistoryItem[] {
44
+ return this._items;
45
+ }
46
+
47
+ add(service: HistoryService, item: HistoryStackItem) {
48
+ const historyItem = this._getHistoryItem(service, item);
49
+ this._items.unshift(historyItem);
50
+ if (this._items.length > this.limit) {
51
+ this._items.pop();
52
+ }
53
+ this.onChangeEmitter.fire({
54
+ type: HistoryStackChangeType.ADD,
55
+ value: historyItem,
56
+ service,
57
+ });
58
+ return historyItem;
59
+ }
60
+
61
+ findById(id: string): HistoryItem | undefined {
62
+ return this._items.find((item) => item.id === id);
63
+ }
64
+
65
+ changeByIndex(index: number, service: HistoryService, item: HistoryStackItem) {
66
+ const historyItem = this._getHistoryItem(service, item);
67
+ this._items[index] = historyItem;
68
+ this.onChangeEmitter.fire({
69
+ type: HistoryStackChangeType.UPDATE,
70
+ value: historyItem,
71
+ service,
72
+ });
73
+ }
74
+
75
+ addOperation(service: HistoryService, id: string, op: OperationWithId) {
76
+ const historyItem = this._items.find((item) => item.id === id);
77
+ if (!historyItem) {
78
+ console.warn('no history item found');
79
+ return;
80
+ }
81
+
82
+ const newOperatopn = this._getHistoryOperation(service, op);
83
+ historyItem.operations.push(newOperatopn);
84
+
85
+ this.onChangeEmitter.fire({
86
+ type: HistoryStackChangeType.ADD_OPERATION,
87
+ value: {
88
+ historyItem,
89
+ operation: newOperatopn,
90
+ },
91
+ service,
92
+ });
93
+ }
94
+
95
+ updateOperation(service: HistoryService, id: string, op: OperationWithId) {
96
+ const historyItem = this._items.find((item) => item.id === id);
97
+ if (!historyItem) {
98
+ console.warn('no history item found');
99
+ return;
100
+ }
101
+ const index = historyItem.operations.findIndex((op) => op.id === op.id);
102
+ if (index < 0) {
103
+ console.warn('no operation found');
104
+ return;
105
+ }
106
+ const newOperatopn = this._getHistoryOperation(service, op);
107
+ historyItem.operations.splice(index, 1, newOperatopn);
108
+ this.onChangeEmitter.fire({
109
+ type: HistoryStackChangeType.UPDATE_OPERATION,
110
+ value: {
111
+ historyItem,
112
+ operation: newOperatopn,
113
+ },
114
+ service,
115
+ });
116
+ }
117
+
118
+ clear() {
119
+ this._items = [];
120
+ }
121
+
122
+ dispose() {
123
+ this._items = [];
124
+ this._toDispose.dispose();
125
+ }
126
+
127
+ private _getHistoryItem(service: HistoryService, item: HistoryStackItem): HistoryItem {
128
+ return {
129
+ ...item,
130
+ uri: service.context.uri,
131
+ time: HistoryStack.dateFormat(item.timestamp),
132
+ operations: item.operations.map((op) =>
133
+ this._getHistoryOperation(service, op, item.type !== UndoRedoChangeType.PUSH)
134
+ ),
135
+ };
136
+ }
137
+
138
+ private _getHistoryOperation(
139
+ service: HistoryService,
140
+ op: Operation,
141
+ generateId: boolean = false
142
+ ): HistoryOperation {
143
+ let id;
144
+ if (generateId) {
145
+ id = this.historyConfig.generateId();
146
+ } else {
147
+ const oldId = (op as OperationWithId).id;
148
+ if (!oldId) {
149
+ throw new Error('no operation id found');
150
+ }
151
+ id = oldId;
152
+ }
153
+
154
+ return {
155
+ ...cloneDeep(op),
156
+ id,
157
+ label: service.operationService.getOperationLabel(op),
158
+ description: service.operationService.getOperationDescription(op),
159
+ timestamp: Date.now(),
160
+ };
161
+ }
162
+
163
+ static dateFormat(timestamp: number) {
164
+ return new Date(timestamp).toLocaleString();
165
+ }
166
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
3
+ * SPDX-License-Identifier: MIT
4
+ */
5
+
6
+ export * from './undo-redo-service';
7
+ export * from './types';
8
+ export * from './history-service';
9
+ export * from './stack-operation';
10
+ export * from './history-manager';
11
+ export * from './history-stack';
12
+ export * from '../history-config';
@@ -0,0 +1,112 @@
1
+ /**
2
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
3
+ * SPDX-License-Identifier: MIT
4
+ */
5
+
6
+ import { cloneDeep } from 'lodash-es';
7
+ import { DisposableCollection } from '@flowgram-vue/utils';
8
+
9
+ import { OperationService } from '../operation/operation-service';
10
+ import { Operation, OperationWithId } from '../operation';
11
+ import { IUndoRedoElement, UndoRedoChangeType } from './types';
12
+
13
+ export class StackOperation implements IUndoRedoElement {
14
+ label?: string | undefined;
15
+
16
+ description?: string | undefined;
17
+
18
+ private _operations: OperationWithId[];
19
+
20
+ private _toDispose = new DisposableCollection();
21
+
22
+ private _timestamp: number = Date.now();
23
+
24
+ private _operationService: OperationService;
25
+
26
+ private _id: string;
27
+
28
+ get id() {
29
+ return this._id;
30
+ }
31
+
32
+ constructor(operationService: OperationService, operations: Operation[] = []) {
33
+ this._operationService = operationService;
34
+ this._operations = operations.map((op) => this._operation(op));
35
+ this._id = operationService.config.generateId();
36
+ }
37
+
38
+ getTimestamp(): number {
39
+ return this._timestamp;
40
+ }
41
+
42
+ pushOperation(operation: Operation): OperationWithId {
43
+ const op = this._operation(operation);
44
+ this._operations.push(op);
45
+ return op;
46
+ }
47
+
48
+ getOperations(): Operation[] {
49
+ return this._operations;
50
+ }
51
+
52
+ getChangeOperations(type: UndoRedoChangeType): Operation[] {
53
+ if (type === UndoRedoChangeType.UNDO) {
54
+ return this._operationService.inverseOperations(this._operations);
55
+ }
56
+ return this._operations;
57
+ }
58
+
59
+ getFirstOperation(): Operation {
60
+ return this._operations[0];
61
+ }
62
+
63
+ getLastOperation(): Operation<unknown> {
64
+ return this._operations[this._operations.length - 1];
65
+ }
66
+
67
+ async undo(): Promise<void> {
68
+ const inverseOps = this._operationService.inverseOperations(this._operations);
69
+
70
+ for (const op of inverseOps) {
71
+ await this._apply(op);
72
+ }
73
+ }
74
+
75
+ async redo(): Promise<void> {
76
+ for (const op of this._operations) {
77
+ await this._apply(op);
78
+ }
79
+ }
80
+
81
+ revert(type: UndoRedoChangeType): void | Promise<void> {
82
+ let operations: Operation[] = this._operations;
83
+
84
+ if (type !== UndoRedoChangeType.UNDO) {
85
+ operations = this._operations.map((op) => this._inverse(op)).reverse();
86
+ }
87
+
88
+ for (const op of operations) {
89
+ this._apply(op);
90
+ }
91
+ }
92
+
93
+ private _inverse(op: Operation): Operation {
94
+ return this._operationService.inverseOperation(op);
95
+ }
96
+
97
+ private async _apply(op: Operation) {
98
+ await this._operationService.applyOperation(op);
99
+ }
100
+
101
+ private _operation(op: Operation) {
102
+ return {
103
+ ...op,
104
+ value: cloneDeep(op.value),
105
+ id: this._operationService.config.generateId(),
106
+ };
107
+ }
108
+
109
+ dispose(): void {
110
+ this._toDispose.dispose();
111
+ }
112
+ }
@@ -0,0 +1,325 @@
1
+ /**
2
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
3
+ * SPDX-License-Identifier: MIT
4
+ */
5
+
6
+ import { Disposable } from '@flowgram-vue/utils';
7
+
8
+ import { HistoryOperation, Operation } from '../operation';
9
+ import { HistoryService } from './history-service';
10
+
11
+ export interface HistoryRecord {
12
+ snapshot: any;
13
+ stack: any[];
14
+ }
15
+
16
+ export interface HistoryItem extends HistoryStackItem {
17
+ id: string;
18
+ time: string;
19
+ operations: HistoryOperation[];
20
+ }
21
+
22
+ /**
23
+ * 历史服务管理
24
+ */
25
+ export interface IHistoryManager {
26
+ /**
27
+ * 注册历史服务
28
+ * @param service 历史服务示例
29
+ */
30
+ registerHistoryService(service: IHistoryService): void;
31
+ /**
32
+ * 取消注册历史服务
33
+ * @param service 历史服务示例
34
+ */
35
+ unregisterHistoryService(service: HistoryService): void;
36
+ }
37
+
38
+ /**
39
+ * 历史服务
40
+ */
41
+ export interface IHistoryService extends Disposable {
42
+ /**
43
+ * 添加操作
44
+ * @param operation 操作
45
+ */
46
+ pushOperation(operation: Operation): void | Promise<void>;
47
+ /**
48
+ * 获取所有历史操作
49
+ */
50
+ getHistoryOperations(): Operation[];
51
+ /**
52
+ * 撤回
53
+ */
54
+ undo(): void | Promise<void>;
55
+ /**
56
+ * 重做
57
+ */
58
+ redo(): void | Promise<void>;
59
+ /**
60
+ * 是否有可撤销的操作
61
+ */
62
+ canUndo(): boolean;
63
+ /**
64
+ * 是否有可重做的操作
65
+ */
66
+ canRedo(): boolean;
67
+ /**
68
+ * 获取历史记录
69
+ */
70
+ getRecords(): Promise<HistoryRecord[]>;
71
+ /**
72
+ * 根据历史版本重新存储历史记录
73
+ * @param historyRecord 历史记录
74
+ */
75
+ restore(historyRecord: HistoryRecord): Promise<void>;
76
+ /**
77
+ * 清空undo/redo
78
+ */
79
+ clear(): void;
80
+ /**
81
+ * 最大数量限制
82
+ * @param num 数量
83
+ */
84
+ limit(num: number): void;
85
+ /**
86
+ * 返回快照
87
+ */
88
+ getSnapshot(): unknown;
89
+ }
90
+
91
+ export interface IOperationService {
92
+ pushOperation(operation: Operation): void;
93
+ }
94
+
95
+ /**
96
+ * UndoRedo服务
97
+ */
98
+ export interface IUndoRedoService extends Disposable {
99
+ /**
100
+ * 添加一个undo/redo元素
101
+ * @param element 可undo/redo的元素
102
+ */
103
+ pushElement(element: IUndoRedoElement): void;
104
+ /**
105
+ * 获取最后一个可undo的元素
106
+ */
107
+ getLastElement(): IUndoRedoElement;
108
+ /**
109
+ * 获取undo栈
110
+ */
111
+ getUndoStack(): IUndoRedoElement[];
112
+ /**
113
+ * 获取redo栈
114
+ */
115
+ getRedoStack(): IUndoRedoElement[];
116
+ /**
117
+ * 清空redo栈
118
+ */
119
+ clearRedoStack(): void;
120
+ /**
121
+ * 是否可undo
122
+ */
123
+ canUndo(): boolean;
124
+ /**
125
+ * 执行undo
126
+ */
127
+ undo(): Promise<void> | void;
128
+ /**
129
+ * 是否可redo
130
+ */
131
+ canRedo(): boolean;
132
+ /**
133
+ * 执行redo
134
+ */
135
+ redo(): Promise<void> | void;
136
+ /**
137
+ * 清空 undo和redo栈
138
+ */
139
+ clear(): void;
140
+ }
141
+
142
+ /**
143
+ * UndoRedo元素
144
+ */
145
+ export interface IUndoRedoElement extends Disposable {
146
+ /**
147
+ * 操作标题
148
+ */
149
+ readonly label?: string;
150
+ /**
151
+ * 操作描述
152
+ */
153
+ readonly description?: string;
154
+ /**
155
+ * 撤销
156
+ */
157
+ undo(): Promise<void> | void;
158
+ /**
159
+ * 重做
160
+ */
161
+ redo(): Promise<void> | void;
162
+ /**
163
+ * 添加一个操作
164
+ * @param operation 操作
165
+ */
166
+ pushOperation(operation: Operation): Operation;
167
+ /**
168
+ * 获取所有操作
169
+ */
170
+ getOperations(): Operation[];
171
+ /**
172
+ * 获取第一个操作
173
+ */
174
+ getFirstOperation(): Operation;
175
+ /**
176
+ * 获取最后一个操作
177
+ */
178
+ getLastOperation(): Operation;
179
+ /**
180
+ * 获取修改的操作
181
+ */
182
+ getChangeOperations(type: UndoRedoChangeType): Operation[];
183
+ }
184
+
185
+ /**
186
+ * 操作注册
187
+ */
188
+ export interface IOperationRegistry {
189
+ register(type: string, factory: IUndoRedoElementFactory<unknown>): void;
190
+ }
191
+
192
+ /**
193
+ * 操作工厂
194
+ */
195
+ export type IUndoRedoElementFactory<OperationValue> = (
196
+ operation: Operation<OperationValue>
197
+ ) => IUndoRedoElement;
198
+
199
+ /**
200
+ * undo redo 类型
201
+ */
202
+ export enum UndoRedoChangeType {
203
+ UNDO = 'undo',
204
+ REDO = 'redo',
205
+ PUSH = 'push',
206
+ CLEAR = 'clear',
207
+ }
208
+
209
+ /**
210
+ * 带element的事件
211
+ */
212
+ export interface UndoRedoChangeElementEvent {
213
+ type: UndoRedoChangeType.PUSH | UndoRedoChangeType.UNDO | UndoRedoChangeType.REDO;
214
+ element: IUndoRedoElement;
215
+ }
216
+ /**
217
+ * 清空事件
218
+ */
219
+ export interface UndoRedoClearEvent {
220
+ type: UndoRedoChangeType.CLEAR;
221
+ }
222
+ /**
223
+ * undo redo变化事件
224
+ */
225
+ export type UndoRedoChangeEvent = UndoRedoChangeElementEvent | UndoRedoClearEvent;
226
+
227
+ export interface HistoryStackItem {
228
+ id: string;
229
+ type: UndoRedoChangeType;
230
+ timestamp: number;
231
+ operations: Operation[];
232
+ uri?: string;
233
+ }
234
+
235
+ /**
236
+ * 历史栈变化类型
237
+ */
238
+ export enum HistoryStackChangeType {
239
+ ADD = 'add',
240
+ UPDATE = 'update',
241
+ CLEAR = 'clear',
242
+ ADD_OPERATION = 'add_operation',
243
+ UPDATE_OPERATION = 'update_operation',
244
+ }
245
+
246
+ /**
247
+ * 历史栈变化事件基础
248
+ */
249
+ export interface HistoryStackBaseEvent {
250
+ type: HistoryStackChangeType;
251
+ value?: any;
252
+ service: HistoryService;
253
+ }
254
+
255
+ /**
256
+ * 添加历史事件
257
+ */
258
+ export interface HistoryStackAddEvent extends HistoryStackBaseEvent {
259
+ type: HistoryStackChangeType.ADD;
260
+ value: HistoryItem;
261
+ }
262
+
263
+ /**
264
+ * 更新历史事件
265
+ */
266
+ export interface HistoryStackUpdateEvent extends HistoryStackBaseEvent {
267
+ type: HistoryStackChangeType.UPDATE;
268
+ value: HistoryItem;
269
+ }
270
+
271
+ /**
272
+ * 添加操作事件
273
+ */
274
+ export interface HistoryStackAddOperationEvent extends HistoryStackBaseEvent {
275
+ type: HistoryStackChangeType.ADD_OPERATION;
276
+ value: {
277
+ historyItem: HistoryItem;
278
+ operation: HistoryOperation;
279
+ };
280
+ }
281
+
282
+ /**
283
+ * 更新操作事件
284
+ */
285
+ export interface HistoryStackUpdateOperationEvent extends HistoryStackBaseEvent {
286
+ type: HistoryStackChangeType.UPDATE_OPERATION;
287
+ value: {
288
+ historyItem: HistoryItem;
289
+ operation: HistoryOperation;
290
+ };
291
+ }
292
+
293
+ /**
294
+ * 历史记录变化事件
295
+ */
296
+ export type HistoryStackChangeEvent =
297
+ | HistoryStackAddEvent
298
+ | HistoryStackUpdateEvent
299
+ | HistoryStackAddOperationEvent
300
+ | HistoryStackUpdateOperationEvent;
301
+
302
+ export enum HistoryMergeEventType {
303
+ ADD = 'ADD',
304
+ UPDATE = 'UPDATE',
305
+ }
306
+
307
+ /**
308
+ * 历史合并事件
309
+ */
310
+ export type HistoryMergeEvent =
311
+ | {
312
+ type: HistoryMergeEventType.ADD;
313
+ value: {
314
+ element: IUndoRedoElement;
315
+ operation: Operation;
316
+ };
317
+ }
318
+ | {
319
+ type: HistoryMergeEventType.UPDATE;
320
+ value: {
321
+ element: IUndoRedoElement;
322
+ operation: Operation;
323
+ value: any;
324
+ };
325
+ };