diff options
Diffstat (limited to 'apps/web/src/core')
| -rw-r--r-- | apps/web/src/core/index.ts | 2 | ||||
| -rw-r--r-- | apps/web/src/core/managers/commands.ts | 152 | ||||
| -rw-r--r-- | apps/web/src/core/managers/timeline-manager.ts | 2 |
3 files changed, 105 insertions, 51 deletions
diff --git a/apps/web/src/core/index.ts b/apps/web/src/core/index.ts index ee1873b0..f1c2d032 100644 --- a/apps/web/src/core/index.ts +++ b/apps/web/src/core/index.ts @@ -27,7 +27,7 @@ export class EditorCore { private constructor() {
registerDefaultEffects();
registerDefaultMasks();
- this.command = new CommandManager();
+ this.command = new CommandManager(this);
this.timeline = new TimelineManager(this);
this.playback = new PlaybackManager(this);
this.scenes = new ScenesManager(this);
diff --git a/apps/web/src/core/managers/commands.ts b/apps/web/src/core/managers/commands.ts index d4d4c35c..4c53857b 100644 --- a/apps/web/src/core/managers/commands.ts +++ b/apps/web/src/core/managers/commands.ts @@ -1,49 +1,103 @@ -import type { Command } from "@/lib/commands";
-
-export class CommandManager {
- private history: Command[] = [];
- private redoStack: Command[] = [];
-
- execute({ command }: { command: Command }): Command {
- command.execute();
- this.history.push(command);
- this.redoStack = [];
- return command;
- }
-
- push({ command }: { command: Command }): void {
- this.history.push(command);
- this.redoStack = [];
- }
-
- undo(): void {
- if (this.history.length === 0) return;
- const command = this.history.pop();
- command?.undo();
- if (command) {
- this.redoStack.push(command);
- }
- }
-
- redo(): void {
- if (this.redoStack.length === 0) return;
- const command = this.redoStack.pop();
- command?.redo();
- if (command) {
- this.history.push(command);
- }
- }
-
- canUndo(): boolean {
- return this.history.length > 0;
- }
-
- canRedo(): boolean {
- return this.redoStack.length > 0;
- }
-
- clear(): void {
- this.history = [];
- this.redoStack = [];
- }
-}
+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; + } +} diff --git a/apps/web/src/core/managers/timeline-manager.ts b/apps/web/src/core/managers/timeline-manager.ts index 263de41f..7189761c 100644 --- a/apps/web/src/core/managers/timeline-manager.ts +++ b/apps/web/src/core/managers/timeline-manager.ts @@ -265,7 +265,7 @@ export class TimelineManager { time: number; clipboardItems: ClipboardItem[]; }): { trackId: string; elementId: string }[] { - const command = new PasteCommand(time, clipboardItems); + const command = new PasteCommand({ time, clipboardItems }); this.editor.command.execute({ command }); return command.getPastedElements(); } |
