/** * Clipboard module — copy canvas to system clipboard and paste images from it. * * Copy approach: snapshot the already-rendered canvas, then crop the visible * selection region in screen space. This preserves crop/flip/rotation/group * transforms exactly as the user sees them. */ import type { Viewport } from 'pixi-viewport'; import type { Application } from 'pixi.js'; import type { SceneManager, SceneItem } from './SceneManager'; import { uploadImage } from '../api'; import { renderCanvasSnapshot, renderNativeImageSelection, supportsNativeImageExport, } from './snapshotRenderer'; /** * Write selected items (or full viewport) to system clipboard as PNG. * Selected items are rendered at their native size, not affected by zoom. */ export async function writeCanvasToClipboard( app: Application | null, viewport: Viewport | null, items?: SceneItem[], ): Promise { if (!viewport) throw new Error('Viewport not available'); if (!app) throw new Error('Renderer not available'); let outputCanvas: HTMLCanvasElement; if (items && items.length > 0) { if (supportsNativeImageExport(items)) { outputCanvas = await renderNativeImageSelection(items, { background: null, paddingPx: 10 }); } else { outputCanvas = renderCanvasSnapshot(app, viewport, items, { background: null, paddingPx: 12 }); } } else { outputCanvas = renderCanvasSnapshot(app, viewport, undefined, { background: null }); } const blob = await new Promise((resolve, reject) => { outputCanvas.toBlob((b) => { if (b) resolve(b); else reject(new Error('toBlob returned null')); }, 'image/png'); }); await navigator.clipboard.write([ new ClipboardItem({ 'image/png': blob }), ]); } /** * Paste an image from the system clipboard into the scene. * Reads the clipboard, uploads the first image found, and adds it at the viewport center. */ export async function pasteFromSystemClipboard( scene: SceneManager, viewport: Viewport, boardId: string, onChange: () => void, ): Promise { const clipItems = await navigator.clipboard.read(); for (const clipItem of clipItems) { for (const type of clipItem.types) { if (!type.startsWith('image/')) continue; const blob = await clipItem.getType(type); const file = new File([blob], `paste.${type.split('/')[1]}`, { type }); const res = await uploadImage(boardId, file); const imgData = res.data.image || res.data; const assetKey = imgData.asset_key; const w = imgData.width || 400; const h = imgData.height || 300; // Display at native size — let user resize manually if needed const fw = w; const fh = h; if (assetKey) { const center = viewport.center; scene.addImageFromUpload(assetKey, fw, fh, center.x - fw / 2, center.y - fh / 2); onChange(); return 'Pasted image'; } } } return 'No image in clipboard'; }