'use client'; import { useEffect, useRef, useState } from 'react'; import { useCanvasStore, useLayerStore } from '@/store'; import { getContext, drawGrid, drawCheckerboard } from '@/lib/canvas-utils'; import { cn } from '@/lib/utils'; export function CanvasWrapper() { const canvasRef = useRef(null); const containerRef = useRef(null); const { width, height, zoom, offsetX, offsetY, showGrid, gridSize, backgroundColor, selection, } = useCanvasStore(); const { layers } = useLayerStore(); const [isPanning, setIsPanning] = useState(false); const [panStart, setPanStart] = useState({ x: 0, y: 0 }); // Render canvas useEffect(() => { const canvas = canvasRef.current; if (!canvas) return; const ctx = getContext(canvas); const container = containerRef.current; if (!container) return; // Set canvas size to match container const rect = container.getBoundingClientRect(); canvas.width = rect.width; canvas.height = rect.height; // Clear canvas ctx.clearRect(0, 0, canvas.width, canvas.height); // Save context state ctx.save(); // Apply transformations ctx.translate(offsetX + canvas.width / 2, offsetY + canvas.height / 2); ctx.scale(zoom, zoom); ctx.translate(-width / 2, -height / 2); // Draw checkerboard background drawCheckerboard(ctx, 10, '#ffffff', '#e0e0e0'); // Draw background color if not transparent if (backgroundColor && backgroundColor !== 'transparent') { ctx.fillStyle = backgroundColor; ctx.fillRect(0, 0, width, height); } // Draw all visible layers layers .filter((layer) => layer.visible && layer.canvas) .sort((a, b) => a.order - b.order) .forEach((layer) => { if (!layer.canvas) return; ctx.globalAlpha = layer.opacity; ctx.globalCompositeOperation = layer.blendMode as GlobalCompositeOperation; ctx.drawImage(layer.canvas, layer.x, layer.y); }); // Reset composite operation ctx.globalAlpha = 1; ctx.globalCompositeOperation = 'source-over'; // Draw grid if enabled if (showGrid) { drawGrid(ctx, gridSize, 'rgba(0, 0, 0, 0.15)'); } // Draw selection if active if (selection.active) { ctx.strokeStyle = '#0066ff'; ctx.lineWidth = 1 / zoom; ctx.setLineDash([4 / zoom, 4 / zoom]); ctx.strokeRect(selection.x, selection.y, selection.width, selection.height); ctx.setLineDash([]); } // Restore context state ctx.restore(); }, [layers, width, height, zoom, offsetX, offsetY, showGrid, gridSize, backgroundColor, selection]); // Handle mouse wheel for zooming const handleWheel = (e: React.WheelEvent) => { if (e.ctrlKey || e.metaKey) { e.preventDefault(); const { zoomIn, zoomOut } = useCanvasStore.getState(); if (e.deltaY < 0) { zoomIn(); } else { zoomOut(); } } }; // Handle panning const handleMouseDown = (e: React.MouseEvent) => { if (e.button === 1 || (e.button === 0 && e.shiftKey)) { // Middle mouse or Shift + Left mouse for panning setIsPanning(true); setPanStart({ x: e.clientX - offsetX, y: e.clientY - offsetY }); e.preventDefault(); } }; const handleMouseMove = (e: React.MouseEvent) => { if (isPanning) { const { setPanOffset } = useCanvasStore.getState(); setPanOffset(e.clientX - panStart.x, e.clientY - panStart.y); } }; const handleMouseUp = () => { setIsPanning(false); }; return (
); }