/** * Clipboard operations for cut/copy/paste */ import { useLayerStore, useCanvasStore } from '@/store'; import { useSelectionStore } from '@/store/selection-store'; import { useHistoryStore } from '@/store/history-store'; import { useToastStore } from '@/store/toast-store'; import { DrawCommand } from '@/core/commands'; import { createLayerWithHistory } from './layer-operations'; // In-memory clipboard for canvas data let clipboardCanvas: HTMLCanvasElement | null = null; /** * Copy the selected region to clipboard */ export function copySelection(): void { const { getActiveLayer } = useLayerStore.getState(); const { activeSelection } = useSelectionStore.getState(); const { addToast } = useToastStore.getState(); const layer = getActiveLayer(); if (!layer || !layer.canvas) { addToast('No active layer', 'error'); return; } if (!activeSelection) { addToast('No selection to copy', 'warning'); return; } const { mask } = activeSelection; const { bounds, data: maskData } = mask; const ctx = layer.canvas.getContext('2d'); if (!ctx) return; // Create a temporary canvas for the selection clipboardCanvas = document.createElement('canvas'); clipboardCanvas.width = bounds.width; clipboardCanvas.height = bounds.height; const clipCtx = clipboardCanvas.getContext('2d'); if (!clipCtx) return; // Get the image data from the layer const imageData = ctx.getImageData(bounds.x, bounds.y, bounds.width, bounds.height); // Apply mask to image data for (let i = 0; i < maskData.length; i++) { const alpha = maskData[i]; const pixelIndex = i * 4 + 3; imageData.data[pixelIndex] = Math.min(imageData.data[pixelIndex], alpha); } // Put the masked image data into clipboard canvas clipCtx.putImageData(imageData, 0, 0); addToast('Selection copied', 'success'); } /** * Cut the selected region (copy + delete) */ export function cutSelection(): void { const { getActiveLayer } = useLayerStore.getState(); const { activeSelection } = useSelectionStore.getState(); const { addToast } = useToastStore.getState(); const { executeCommand } = useHistoryStore.getState(); const layer = getActiveLayer(); if (!layer || !layer.canvas) { addToast('No active layer', 'error'); return; } if (!activeSelection) { addToast('No selection to cut', 'warning'); return; } // First, copy the selection copySelection(); // Then delete the selection const { mask } = activeSelection; const { bounds, data: maskData } = mask; const ctx = layer.canvas.getContext('2d'); if (!ctx) return; // Create command for history const command = new DrawCommand(layer.id, 'Cut'); // Get the image data const imageData = ctx.getImageData(bounds.x, bounds.y, bounds.width, bounds.height); // Apply mask to delete (set alpha to 0) for (let i = 0; i < maskData.length; i++) { const alpha = maskData[i]; if (alpha > 0) { const pixelIndex = i * 4 + 3; imageData.data[pixelIndex] = 0; } } // Put the modified image data back ctx.putImageData(imageData, bounds.x, bounds.y); // Capture after state and execute command command.captureAfterState(); executeCommand(command); addToast('Selection cut', 'success'); } /** * Paste from clipboard */ export async function pasteFromClipboard(): Promise { const { width, height } = useCanvasStore.getState(); const { addToast } = useToastStore.getState(); // Try to paste from browser clipboard first try { const items = await navigator.clipboard.read(); for (const item of items) { for (const type of item.types) { if (type.startsWith('image/')) { const blob = await item.getType(type); const img = new Image(); const url = URL.createObjectURL(blob); img.onload = () => { // Create new layer with pasted image createLayerWithHistory({ name: 'Pasted Layer', width: img.width, height: img.height, }); // Draw the image onto the new layer const { getActiveLayer } = useLayerStore.getState(); const layer = getActiveLayer(); if (layer && layer.canvas) { const ctx = layer.canvas.getContext('2d'); if (ctx) { ctx.drawImage(img, 0, 0); addToast('Image pasted', 'success'); } } URL.revokeObjectURL(url); }; img.src = url; return; } } } } catch (error) { // Clipboard API not available or no image in clipboard console.warn('Clipboard API failed:', error); } // Fallback to internal clipboard if (clipboardCanvas) { createLayerWithHistory({ name: 'Pasted Layer', width: clipboardCanvas.width, height: clipboardCanvas.height, }); // Draw the clipboard canvas onto the new layer const { getActiveLayer } = useLayerStore.getState(); const layer = getActiveLayer(); if (layer && layer.canvas) { const ctx = layer.canvas.getContext('2d'); if (ctx) { ctx.drawImage(clipboardCanvas, 0, 0); addToast('Selection pasted', 'success'); } } } else { addToast('Nothing to paste', 'warning'); } } /** * Check if clipboard has content */ export function hasClipboardContent(): boolean { return clipboardCanvas !== null; }