Files
paint-ui/lib/canvas-operations.ts
Sebastian Krüger 63a6801155 feat: implement comprehensive canvas context menu system
Adds right-click context menu for canvas with full operation support:

**Clipboard Operations:**
- Cut/Copy/Paste with selection mask support
- Browser clipboard API integration for external images
- Internal clipboard buffer for canvas selections
- Toast notifications for user feedback

**Selection Operations:**
- Select All - creates full canvas selection with proper mask
- Deselect - clears active selection
- Selection state properly integrated with canvas operations

**Layer Operations:**
- New Layer - creates layer with history support
- Duplicate Layer - clones active layer
- Merge Down - merges layer with one below

**Transform Operations:**
- Rotate 90° CW - rotates active layer clockwise
- Flip Horizontal - mirrors layer horizontally
- Flip Vertical - mirrors layer vertically
- All transforms preserve image quality and support undo/redo

**Edit Operations:**
- Undo/Redo - integrated with history system
- Disabled states for unavailable operations
- Context-aware menu items

**New Files Created:**
- lib/clipboard-operations.ts - Cut/copy/paste implementation
- lib/canvas-operations.ts - Rotate/flip canvas functions

**Modified Files:**
- components/canvas/canvas-with-tools.tsx - Context menu integration
- store/selection-store.ts - Added selectAll() method
- core/commands/index.ts - Export all command types

**Technical Improvements:**
- Proper Selection type structure with mask/bounds
- History command integration for all operations
- Lazy-loaded operations for performance
- Toast feedback for all user actions
- Full TypeScript type safety

All operations work with undo/redo and maintain app state consistency.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-21 17:16:06 +01:00

136 lines
3.6 KiB
TypeScript

/**
* Canvas transformation operations
* Rotate, flip, and other canvas manipulations
*/
import { useHistoryStore } from '@/store/history-store';
import { useLayerStore } from '@/store';
import { DrawCommand } from '@/core/commands';
/**
* Rotate a canvas by the specified degrees
*/
export function rotateCanvas(canvas: HTMLCanvasElement, degrees: 90 | 180 | 270): HTMLCanvasElement {
const tempCanvas = document.createElement('canvas');
const ctx = tempCanvas.getContext('2d');
if (!ctx) return canvas;
// For 90° and 270° rotations, swap width and height
if (degrees === 90 || degrees === 270) {
tempCanvas.width = canvas.height;
tempCanvas.height = canvas.width;
} else {
tempCanvas.width = canvas.width;
tempCanvas.height = canvas.height;
}
ctx.save();
// Translate and rotate based on degrees
switch (degrees) {
case 90:
ctx.translate(tempCanvas.width, 0);
ctx.rotate(Math.PI / 2);
break;
case 180:
ctx.translate(tempCanvas.width, tempCanvas.height);
ctx.rotate(Math.PI);
break;
case 270:
ctx.translate(0, tempCanvas.height);
ctx.rotate(-Math.PI / 2);
break;
}
ctx.drawImage(canvas, 0, 0);
ctx.restore();
return tempCanvas;
}
/**
* Flip a canvas horizontally or vertically
*/
export function flipCanvas(canvas: HTMLCanvasElement, direction: 'horizontal' | 'vertical'): HTMLCanvasElement {
const tempCanvas = document.createElement('canvas');
tempCanvas.width = canvas.width;
tempCanvas.height = canvas.height;
const ctx = tempCanvas.getContext('2d');
if (!ctx) return canvas;
ctx.save();
if (direction === 'horizontal') {
ctx.translate(tempCanvas.width, 0);
ctx.scale(-1, 1);
} else {
ctx.translate(0, tempCanvas.height);
ctx.scale(1, -1);
}
ctx.drawImage(canvas, 0, 0);
ctx.restore();
return tempCanvas;
}
/**
* Rotate the active layer with history support
*/
export function rotateLayerWithHistory(layerId: string, degrees: 90 | 180 | 270): void {
const { getActiveLayer } = useLayerStore.getState();
const layer = getActiveLayer();
if (!layer || !layer.canvas || layer.id !== layerId) return;
// Create command for history (capture before state)
const command = new DrawCommand(layerId, `Rotate ${degrees}°`);
const rotatedCanvas = rotateCanvas(layer.canvas, degrees);
// Replace the layer's canvas
const ctx = layer.canvas.getContext('2d');
if (!ctx) return;
// Update canvas dimensions if rotated 90° or 270°
if (degrees === 90 || degrees === 270) {
layer.canvas.width = rotatedCanvas.width;
layer.canvas.height = rotatedCanvas.height;
}
ctx.clearRect(0, 0, layer.canvas.width, layer.canvas.height);
ctx.drawImage(rotatedCanvas, 0, 0);
// Capture after state and execute command
command.captureAfterState();
useHistoryStore.getState().executeCommand(command);
}
/**
* Flip the active layer with history support
*/
export function flipLayerWithHistory(layerId: string, direction: 'horizontal' | 'vertical'): void {
const { getActiveLayer } = useLayerStore.getState();
const layer = getActiveLayer();
if (!layer || !layer.canvas || layer.id !== layerId) return;
// Create command for history (capture before state)
const command = new DrawCommand(layerId, `Flip ${direction}`);
const flippedCanvas = flipCanvas(layer.canvas, direction);
// Replace the layer's canvas
const ctx = layer.canvas.getContext('2d');
if (!ctx) return;
ctx.clearRect(0, 0, layer.canvas.width, layer.canvas.height);
ctx.drawImage(flippedCanvas, 0, 0);
// Capture after state and execute command
command.captureAfterState();
useHistoryStore.getState().executeCommand(command);
}