mirror of
https://github.com/OpenCut-app/OpenCut.git
synced 2026-07-13 21:52:53 +02:00
104 lines
2.6 KiB
TypeScript
104 lines
2.6 KiB
TypeScript
import type { EditorCore } from "@/core";
|
|
import type { Command, CommandResult } from "@/lib/commands";
|
|
import type { ElementRef } from "@/lib/timeline/types";
|
|
|
|
interface CommandHistoryEntry {
|
|
command: Command;
|
|
previousSelection: ElementRef[];
|
|
selectionOverride?: ElementRef[];
|
|
}
|
|
|
|
export class CommandManager {
|
|
private history: CommandHistoryEntry[] = [];
|
|
private redoStack: CommandHistoryEntry[] = [];
|
|
|
|
constructor(private editor: EditorCore) {}
|
|
|
|
execute({ command }: { command: Command }): Command {
|
|
const previousSelection = this.getSelectionSnapshot();
|
|
const result = command.execute();
|
|
const selectionOverride = this.applySelectionOverride(result);
|
|
this.history.push({
|
|
command,
|
|
previousSelection,
|
|
selectionOverride,
|
|
});
|
|
this.redoStack = [];
|
|
return command;
|
|
}
|
|
|
|
push({ command }: { command: Command }): void {
|
|
this.history.push({
|
|
command,
|
|
previousSelection: this.getSelectionSnapshot(),
|
|
});
|
|
this.redoStack = [];
|
|
}
|
|
|
|
undo(): void {
|
|
if (this.history.length === 0) return;
|
|
const entry = this.history.pop();
|
|
entry?.command.undo();
|
|
if (entry) {
|
|
// Only restore selection for commands that explicitly changed it.
|
|
// Commands without selection intent leave selection untouched,
|
|
// preserving any UI-driven selection changes (clicks, box select)
|
|
// that happened between commands. Commands that remove elements
|
|
// must declare { select: [] } to clear stale refs.
|
|
if (entry.selectionOverride !== undefined) {
|
|
this.editor.selection.setSelectedElements({
|
|
elements: [...entry.previousSelection],
|
|
});
|
|
}
|
|
this.redoStack.push(entry);
|
|
}
|
|
}
|
|
|
|
redo(): void {
|
|
if (this.redoStack.length === 0) return;
|
|
const entry = this.redoStack.pop();
|
|
if (!entry) {
|
|
return;
|
|
}
|
|
|
|
const previousSelection = this.getSelectionSnapshot();
|
|
const result = entry.command.redo();
|
|
const selectionOverride = this.applySelectionOverride(result);
|
|
|
|
this.history.push({
|
|
command: entry.command,
|
|
previousSelection,
|
|
selectionOverride,
|
|
});
|
|
}
|
|
|
|
canUndo(): boolean {
|
|
return this.history.length > 0;
|
|
}
|
|
|
|
canRedo(): boolean {
|
|
return this.redoStack.length > 0;
|
|
}
|
|
|
|
clear(): void {
|
|
this.history = [];
|
|
this.redoStack = [];
|
|
}
|
|
|
|
private getSelectionSnapshot(): ElementRef[] {
|
|
return [...this.editor.selection.getSelectedElements()];
|
|
}
|
|
|
|
private applySelectionOverride(
|
|
result: CommandResult | undefined,
|
|
): ElementRef[] | undefined {
|
|
if (result?.select === undefined) {
|
|
return undefined;
|
|
}
|
|
|
|
const selectionOverride = [...result.select];
|
|
this.editor.selection.setSelectedElements({ elements: selectionOverride });
|
|
return selectionOverride;
|
|
}
|
|
}
|