'use client'; import { useRef, useEffect, useState, useCallback } from 'react'; import { useCanvasStore, useLayerStore } from '@/store'; import { cn } from '@/lib/utils'; import { Maximize2, Minimize2 } from 'lucide-react'; const MINIMAP_MAX_SIZE = 200; // Maximum width/height in pixels export function MiniMap() { const canvasRef = useRef(null); const containerRef = useRef(null); const [isExpanded, setIsExpanded] = useState(true); const [isDragging, setIsDragging] = useState(false); const { width: canvasWidth, height: canvasHeight, zoom, offsetX, offsetY, setZoom, setPanOffset } = useCanvasStore(); const { layers } = useLayerStore(); // Calculate minimap dimensions maintaining aspect ratio const aspectRatio = canvasWidth / canvasHeight; let minimapWidth = MINIMAP_MAX_SIZE; let minimapHeight = MINIMAP_MAX_SIZE; if (aspectRatio > 1) { // Landscape minimapHeight = MINIMAP_MAX_SIZE / aspectRatio; } else { // Portrait minimapWidth = MINIMAP_MAX_SIZE * aspectRatio; } const scale = minimapWidth / canvasWidth; // Render minimap useEffect(() => { if (!canvasRef.current || !isExpanded) return; const canvas = canvasRef.current; const ctx = canvas.getContext('2d'); if (!ctx) return; // Set canvas size canvas.width = minimapWidth; canvas.height = minimapHeight; // Clear canvas ctx.clearRect(0, 0, minimapWidth, minimapHeight); // Draw checkerboard background const checkSize = 4; ctx.fillStyle = '#ffffff'; ctx.fillRect(0, 0, minimapWidth, minimapHeight); ctx.fillStyle = '#e0e0e0'; for (let y = 0; y < minimapHeight; y += checkSize) { for (let x = 0; x < minimapWidth; x += checkSize) { if ((x / checkSize + y / checkSize) % 2 === 0) { ctx.fillRect(x, y, checkSize, checkSize); } } } // Draw all visible layers (scaled down) ctx.save(); ctx.scale(scale, scale); layers .filter((layer) => layer.visible && layer.canvas) .sort((a, b) => a.order - b.order) // Bottom to top .forEach((layer) => { if (!layer.canvas) return; ctx.globalAlpha = layer.opacity; ctx.drawImage(layer.canvas, layer.x, layer.y); }); ctx.restore(); // Draw viewport indicator const viewportWidth = window.innerWidth - 344; // Approximate viewport width const viewportHeight = window.innerHeight - 138; // Approximate viewport height const visibleCanvasWidth = viewportWidth / zoom; const visibleCanvasHeight = viewportHeight / zoom; const viewportX = (-offsetX / zoom) * scale; const viewportY = (-offsetY / zoom) * scale; const viewportW = visibleCanvasWidth * scale; const viewportH = visibleCanvasHeight * scale; // Draw viewport rectangle ctx.strokeStyle = '#3b82f6'; // Primary blue ctx.lineWidth = 2; ctx.strokeRect(viewportX, viewportY, viewportW, viewportH); // Draw semi-transparent fill ctx.fillStyle = 'rgba(59, 130, 246, 0.1)'; ctx.fillRect(viewportX, viewportY, viewportW, viewportH); }, [layers, zoom, offsetX, offsetY, minimapWidth, minimapHeight, scale, isExpanded]); // Handle click/drag to change viewport const handlePointerDown = useCallback((e: React.PointerEvent) => { if (!canvasRef.current) return; setIsDragging(true); const rect = canvasRef.current.getBoundingClientRect(); const x = e.clientX - rect.left; const y = e.clientY - rect.top; // Convert minimap coordinates to canvas coordinates const canvasX = x / scale; const canvasY = y / scale; // Center the viewport on the clicked point const newOffsetX = -canvasX * zoom + (window.innerWidth - 344) / 2; const newOffsetY = -canvasY * zoom + (window.innerHeight - 138) / 2; setPanOffset(newOffsetX, newOffsetY); }, [scale, zoom, setPanOffset]); const handlePointerMove = useCallback((e: React.PointerEvent) => { if (!isDragging || !canvasRef.current) return; const rect = canvasRef.current.getBoundingClientRect(); const x = e.clientX - rect.left; const y = e.clientY - rect.top; const canvasX = x / scale; const canvasY = y / scale; const newOffsetX = -canvasX * zoom + (window.innerWidth - 344) / 2; const newOffsetY = -canvasY * zoom + (window.innerHeight - 138) / 2; setPanOffset(newOffsetX, newOffsetY); }, [isDragging, scale, zoom, setPanOffset]); const handlePointerUp = useCallback(() => { setIsDragging(false); }, []); if (!isExpanded) { return (
); } return (
{/* Header */}
Navigator
{/* Mini-map canvas */} {/* Zoom indicator */}
{Math.round(zoom * 100)}%
); }