文件
mynode/packages/lib/services/UndoRedoService.ts
chen 301194963b
一些检查失败了
automerge / automerge (push) Has been cancelled
react-native-android-build-apk / AssembleRelease (push) Has been cancelled
Build macOS M1 / Main (push) Has been cancelled
Joplin Continuous Integration / Main (macos-15-intel) (push) Has been cancelled
Joplin Continuous Integration / Main (ubuntu-22.04) (push) Has been cancelled
Joplin Continuous Integration / Main (ubuntu-22.04-arm) (push) Has been cancelled
Joplin Continuous Integration / Main (windows-11-vs2026-arm) (push) Has been cancelled
Joplin Continuous Integration / Main (windows-2025) (push) Has been cancelled
Joplin Continuous Integration / ServerDockerImage (ubuntu-22.04) (push) Has been cancelled
Joplin Continuous Integration / ServerDockerImage (ubuntu-22.04-arm) (push) Has been cancelled
Joplin UI tests / Main (ubuntu-22.04) (push) Has been cancelled
Joplin UI tests / Main (windows-2025) (push) Has been cancelled
Joplin UI tests / Mobile (push) Has been cancelled
Close stale issues / ProcessStaleIssues (push) Has been cancelled
feat: Initial clean release of CHM
2026-11-04 11:29:39 +08:00

106 行
2.6 KiB
TypeScript

import AsyncActionQueue from '../AsyncActionQueue';
import { EventEmitter } from 'events';
class UndoQueue {
private inner_: unknown[] = [];
private size_ = 20;
public pop() {
return this.inner_.pop();
}
public push(e: unknown) {
this.inner_.push(e);
while (this.length > this.size_) {
this.inner_.splice(0, 1);
}
}
public get length(): number {
return this.inner_.length;
}
public at(index: number): unknown {
return this.inner_[index];
}
}
export default class UndoRedoService {
private pushAsyncQueue: AsyncActionQueue = new AsyncActionQueue(700);
private undoStates: UndoQueue = new UndoQueue();
private redoStates: UndoQueue = new UndoQueue();
private eventEmitter: EventEmitter = new EventEmitter();
private isUndoing = false;
public constructor() {
this.push = this.push.bind(this);
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- EventEmitter events carry heterogeneous payloads by name
public on(eventName: string, callback: (...args: any[])=> void) {
return this.eventEmitter.on(eventName, callback);
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- See on() above
public off(eventName: string, callback: (...args: any[])=> void) {
return this.eventEmitter.removeListener(eventName, callback);
}
public push(state: unknown) {
this.undoStates.push(state);
this.redoStates = new UndoQueue();
this.eventEmitter.emit('stackChange');
}
public schedulePush(state: unknown) {
this.pushAsyncQueue.push(async () => {
this.push(state);
});
}
public async undo(redoState: unknown) {
if (this.isUndoing) return undefined;
if (!this.canUndo) throw new Error('Nothing to undo');
this.isUndoing = true;
await this.pushAsyncQueue.processAllNow();
const state = this.undoStates.pop();
this.redoStates.push(redoState);
this.eventEmitter.emit('stackChange');
this.isUndoing = false;
return state;
}
public async redo(undoState: unknown) {
if (this.isUndoing) return undefined;
if (!this.canRedo) throw new Error('Nothing to redo');
this.isUndoing = true;
await this.pushAsyncQueue.processAllNow();
const state = this.redoStates.pop();
this.undoStates.push(undoState);
this.eventEmitter.emit('stackChange');
this.isUndoing = false;
return state;
}
public async reset() {
this.undoStates = new UndoQueue();
this.redoStates = new UndoQueue();
this.isUndoing = false;
const output = this.pushAsyncQueue.reset();
this.eventEmitter.emit('stackChange');
return output;
}
public get canUndo(): boolean {
return !!this.undoStates.length;
}
public get canRedo(): boolean {
return !!this.redoStates.length;
}
}