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>
136 lines
3.6 KiB
TypeScript
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);
|
|
}
|