From 6e35849f4e73404ab932dc26f9f5b8003ce504bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Kr=C3=BCger?= Date: Fri, 21 Nov 2025 09:19:09 +0100 Subject: [PATCH] feat: implement MoveCommand for undoable layer movement MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Critical Fix - Move Tool Undo/Redo: - Create MoveCommand class to track layer position changes - Capture initial and final positions during move operation - Only add to history if position actually changed - Real-time visual feedback during drag (via updateLayer) - Single undo point per move operation (not per pixel) Command Pattern: - MoveCommand extends BaseCommand - Implements execute() and undo() methods - captureAfterPosition() called on pointer up - hasChanged() prevents no-op entries in history Files: - core/commands/move-command.ts - New command class - tools/move-tool.ts - Updated to use MoveCommand - core/commands/index.ts - Export MoveCommand This fixes the critical issue where moving layers had no undo support. Users can now undo/redo layer movements as expected. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- core/commands/index.ts | 1 + core/commands/move-command.ts | 60 +++++++++++++++++++++++++++++++++++ tools/move-tool.ts | 22 +++++++++++++ 3 files changed, 83 insertions(+) create mode 100644 core/commands/move-command.ts diff --git a/core/commands/index.ts b/core/commands/index.ts index 8426d8a..01c79be 100644 --- a/core/commands/index.ts +++ b/core/commands/index.ts @@ -1,3 +1,4 @@ export * from './base-command'; export * from './layer-commands'; export * from './draw-command'; +export * from './move-command'; diff --git a/core/commands/move-command.ts b/core/commands/move-command.ts new file mode 100644 index 0000000..f799a0a --- /dev/null +++ b/core/commands/move-command.ts @@ -0,0 +1,60 @@ +import { BaseCommand } from './base-command'; +import { useLayerStore } from '@/store/layer-store'; + +/** + * Command for moving a layer + * Captures start and end positions for undo/redo + */ +export class MoveCommand extends BaseCommand { + private layerId: string; + private beforePosition: { x: number; y: number }; + private afterPosition: { x: number; y: number } | null = null; + + constructor(layerId: string, beforePosition: { x: number; y: number }) { + super('Move Layer'); + this.layerId = layerId; + this.beforePosition = beforePosition; + } + + /** + * Capture the final position after the move is complete + */ + captureAfterPosition(x: number, y: number): void { + this.afterPosition = { x, y }; + } + + execute(): void { + if (!this.afterPosition) { + // No-op on first execute (position already set during drag) + return; + } + + // Restore after position (for redo) + const { updateLayer } = useLayerStore.getState(); + updateLayer(this.layerId, { + x: this.afterPosition.x, + y: this.afterPosition.y, + updatedAt: Date.now(), + }); + } + + undo(): void { + const { updateLayer } = useLayerStore.getState(); + updateLayer(this.layerId, { + x: this.beforePosition.x, + y: this.beforePosition.y, + updatedAt: Date.now(), + }); + } + + /** + * Check if this move actually changed position + */ + hasChanged(): boolean { + if (!this.afterPosition) return false; + return ( + this.beforePosition.x !== this.afterPosition.x || + this.beforePosition.y !== this.afterPosition.y + ); + } +} diff --git a/tools/move-tool.ts b/tools/move-tool.ts index 2ae8014..466d9ae 100644 --- a/tools/move-tool.ts +++ b/tools/move-tool.ts @@ -1,12 +1,15 @@ import { BaseTool } from './base-tool'; import type { PointerState } from '@/types'; import { useLayerStore } from '@/store/layer-store'; +import { useHistoryStore } from '@/store/history-store'; +import { MoveCommand } from '@/core/commands'; export class MoveTool extends BaseTool { private startX = 0; private startY = 0; private layerStartX = 0; private layerStartY = 0; + private moveCommand: MoveCommand | null = null; constructor() { super('Move'); @@ -23,6 +26,9 @@ export class MoveTool extends BaseTool { this.startY = pointer.y; this.layerStartX = layer.x; this.layerStartY = layer.y; + + // Create move command with initial position + this.moveCommand = new MoveCommand(layer.id, { x: layer.x, y: layer.y }); } onPointerMove(pointer: PointerState): void { @@ -34,6 +40,7 @@ export class MoveTool extends BaseTool { const dx = pointer.x - this.startX; const dy = pointer.y - this.startY; + // Update position in real-time (for visual feedback) const { updateLayer } = useLayerStore.getState(); updateLayer(layer.id, { x: this.layerStartX + dx, @@ -42,6 +49,21 @@ export class MoveTool extends BaseTool { } onPointerUp(): void { + if (this.moveCommand) { + const layer = this.getActiveLayer(); + if (layer) { + // Capture final position + this.moveCommand.captureAfterPosition(layer.x, layer.y); + + // Only add to history if position actually changed + if (this.moveCommand.hasChanged()) { + const { executeCommand } = useHistoryStore.getState(); + executeCommand(this.moveCommand); + } + } + this.moveCommand = null; + } + this.isDrawing = false; this.isActive = false; }