Files
paint-ui/components/canvas/mini-map.tsx

197 lines
6.1 KiB
TypeScript
Raw Normal View History

feat(phase-12): add professional UI polish with status bar, navigator, and shortcuts help Implements comprehensive quality-of-life improvements for professional editing experience: **1. Status Bar Component** (`components/editor/status-bar.tsx`): - Real-time canvas dimensions display (width × height) - Live zoom percentage indicator - Dynamic cursor position tracking in canvas coordinates - FPS counter for performance monitoring - Memory usage display (when browser supports performance.memory) - Icons for each metric (Maximize2, ZoomIn, MousePointer, Activity, HardDrive) - Fixed bottom position with clean UI - Updates at 60 FPS for smooth cursor tracking - Memory updates every 2 seconds to reduce overhead **2. Mini-Map / Navigator** (`components/canvas/mini-map.tsx`): - Live thumbnail preview of entire canvas - Renders all visible layers with proper stacking order - Checkerboard background for transparency visualization - Interactive viewport indicator (blue rectangle with semi-transparent fill) - Click or drag to pan viewport to different canvas areas - Collapsible with expand/minimize toggle button - Maintains canvas aspect ratio (max 200px) - Positioned in bottom-right corner as floating overlay - Zoom percentage display at bottom - Smart scaling for optimal thumbnail size - Cursor changes to pointer/grabbing during interaction **3. Keyboard Shortcuts Help Panel** (`components/editor/shortcuts-help-panel.tsx`): - Comprehensive list of 40+ keyboard shortcuts - 7 categories: File, Edit, View, Tools, Layers, Transform, Adjustments, Help - Real-time search filtering (searches action, category, keys, description) - Beautiful kbd element styling for shortcut keys - Modal overlay with backdrop blur - Opens with `?` or `F1` keys - Closes with `Esc` key or backdrop click - Fully responsive with scrollable content - Organized sections with category headers - Shows key combinations with proper separators (+) - Optional descriptions for special shortcuts (e.g., "Hold to pan") - Footer with helpful hints **Integration Changes:** **Canvas Component** (`canvas-with-tools.tsx`): - Added `onCursorMove` prop callback for cursor position reporting - Modified `handlePointerMove` to report canvas coordinates - Created `handlePointerLeave` to clear cursor when leaving canvas - Integrated MiniMap component as overlay **Editor Layout** (`editor-layout.tsx`): - Added cursor position state management - Integrated StatusBar at bottom of layout - Added ShortcutsHelpPanel with state management - Keyboard event handlers for `?` and `F1` to open shortcuts - Cursor position passed down to CanvasWithTools and up to StatusBar **Features:** - Non-intrusive overlays that don't block canvas interaction - All components optimized for performance - Responsive design adapts to different screen sizes - Professional appearance matching app theme - Smooth animations and transitions - Real-time updates without lag **User Experience Improvements:** - Quick access to all shortcuts via `?` or `F1` - Always-visible status information in bottom bar - Easy canvas navigation with mini-map - Performance monitoring at a glance - Professional editor feel with polished UI All features tested and working smoothly with no performance impact. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-21 17:51:32 +01:00
'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<HTMLCanvasElement>(null);
const containerRef = useRef<HTMLDivElement>(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)
feat(phase-12): add professional UI polish with status bar, navigator, and shortcuts help Implements comprehensive quality-of-life improvements for professional editing experience: **1. Status Bar Component** (`components/editor/status-bar.tsx`): - Real-time canvas dimensions display (width × height) - Live zoom percentage indicator - Dynamic cursor position tracking in canvas coordinates - FPS counter for performance monitoring - Memory usage display (when browser supports performance.memory) - Icons for each metric (Maximize2, ZoomIn, MousePointer, Activity, HardDrive) - Fixed bottom position with clean UI - Updates at 60 FPS for smooth cursor tracking - Memory updates every 2 seconds to reduce overhead **2. Mini-Map / Navigator** (`components/canvas/mini-map.tsx`): - Live thumbnail preview of entire canvas - Renders all visible layers with proper stacking order - Checkerboard background for transparency visualization - Interactive viewport indicator (blue rectangle with semi-transparent fill) - Click or drag to pan viewport to different canvas areas - Collapsible with expand/minimize toggle button - Maintains canvas aspect ratio (max 200px) - Positioned in bottom-right corner as floating overlay - Zoom percentage display at bottom - Smart scaling for optimal thumbnail size - Cursor changes to pointer/grabbing during interaction **3. Keyboard Shortcuts Help Panel** (`components/editor/shortcuts-help-panel.tsx`): - Comprehensive list of 40+ keyboard shortcuts - 7 categories: File, Edit, View, Tools, Layers, Transform, Adjustments, Help - Real-time search filtering (searches action, category, keys, description) - Beautiful kbd element styling for shortcut keys - Modal overlay with backdrop blur - Opens with `?` or `F1` keys - Closes with `Esc` key or backdrop click - Fully responsive with scrollable content - Organized sections with category headers - Shows key combinations with proper separators (+) - Optional descriptions for special shortcuts (e.g., "Hold to pan") - Footer with helpful hints **Integration Changes:** **Canvas Component** (`canvas-with-tools.tsx`): - Added `onCursorMove` prop callback for cursor position reporting - Modified `handlePointerMove` to report canvas coordinates - Created `handlePointerLeave` to clear cursor when leaving canvas - Integrated MiniMap component as overlay **Editor Layout** (`editor-layout.tsx`): - Added cursor position state management - Integrated StatusBar at bottom of layout - Added ShortcutsHelpPanel with state management - Keyboard event handlers for `?` and `F1` to open shortcuts - Cursor position passed down to CanvasWithTools and up to StatusBar **Features:** - Non-intrusive overlays that don't block canvas interaction - All components optimized for performance - Responsive design adapts to different screen sizes - Professional appearance matching app theme - Smooth animations and transitions - Real-time updates without lag **User Experience Improvements:** - Quick access to all shortcuts via `?` or `F1` - Always-visible status information in bottom bar - Easy canvas navigation with mini-map - Performance monitoring at a glance - Professional editor feel with polished UI All features tested and working smoothly with no performance impact. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-21 17:51:32 +01:00
.sort((a, b) => a.order - b.order) // Bottom to top
.forEach((layer) => {
if (!layer.canvas) return;
feat(phase-12): add professional UI polish with status bar, navigator, and shortcuts help Implements comprehensive quality-of-life improvements for professional editing experience: **1. Status Bar Component** (`components/editor/status-bar.tsx`): - Real-time canvas dimensions display (width × height) - Live zoom percentage indicator - Dynamic cursor position tracking in canvas coordinates - FPS counter for performance monitoring - Memory usage display (when browser supports performance.memory) - Icons for each metric (Maximize2, ZoomIn, MousePointer, Activity, HardDrive) - Fixed bottom position with clean UI - Updates at 60 FPS for smooth cursor tracking - Memory updates every 2 seconds to reduce overhead **2. Mini-Map / Navigator** (`components/canvas/mini-map.tsx`): - Live thumbnail preview of entire canvas - Renders all visible layers with proper stacking order - Checkerboard background for transparency visualization - Interactive viewport indicator (blue rectangle with semi-transparent fill) - Click or drag to pan viewport to different canvas areas - Collapsible with expand/minimize toggle button - Maintains canvas aspect ratio (max 200px) - Positioned in bottom-right corner as floating overlay - Zoom percentage display at bottom - Smart scaling for optimal thumbnail size - Cursor changes to pointer/grabbing during interaction **3. Keyboard Shortcuts Help Panel** (`components/editor/shortcuts-help-panel.tsx`): - Comprehensive list of 40+ keyboard shortcuts - 7 categories: File, Edit, View, Tools, Layers, Transform, Adjustments, Help - Real-time search filtering (searches action, category, keys, description) - Beautiful kbd element styling for shortcut keys - Modal overlay with backdrop blur - Opens with `?` or `F1` keys - Closes with `Esc` key or backdrop click - Fully responsive with scrollable content - Organized sections with category headers - Shows key combinations with proper separators (+) - Optional descriptions for special shortcuts (e.g., "Hold to pan") - Footer with helpful hints **Integration Changes:** **Canvas Component** (`canvas-with-tools.tsx`): - Added `onCursorMove` prop callback for cursor position reporting - Modified `handlePointerMove` to report canvas coordinates - Created `handlePointerLeave` to clear cursor when leaving canvas - Integrated MiniMap component as overlay **Editor Layout** (`editor-layout.tsx`): - Added cursor position state management - Integrated StatusBar at bottom of layout - Added ShortcutsHelpPanel with state management - Keyboard event handlers for `?` and `F1` to open shortcuts - Cursor position passed down to CanvasWithTools and up to StatusBar **Features:** - Non-intrusive overlays that don't block canvas interaction - All components optimized for performance - Responsive design adapts to different screen sizes - Professional appearance matching app theme - Smooth animations and transitions - Real-time updates without lag **User Experience Improvements:** - Quick access to all shortcuts via `?` or `F1` - Always-visible status information in bottom bar - Easy canvas navigation with mini-map - Performance monitoring at a glance - Professional editor feel with polished UI All features tested and working smoothly with no performance impact. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-21 17:51:32 +01:00
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 (
<div
ref={containerRef}
className="absolute bottom-4 right-4 z-10 bg-card border border-border rounded-md shadow-lg"
>
<button
onClick={() => setIsExpanded(true)}
className="p-2 hover:bg-accent rounded-md transition-colors"
title="Show Navigator"
>
<Maximize2 className="h-4 w-4" />
</button>
</div>
);
}
return (
<div
ref={containerRef}
className="absolute bottom-4 right-4 z-10 bg-card border border-border rounded-md shadow-lg p-2 flex flex-col gap-2"
>
{/* Header */}
<div className="flex items-center justify-between">
<span className="text-xs font-medium text-muted-foreground">Navigator</span>
<button
onClick={() => setIsExpanded(false)}
className="p-1 hover:bg-accent rounded transition-colors"
title="Hide Navigator"
>
<Minimize2 className="h-3 w-3" />
</button>
</div>
{/* Mini-map canvas */}
<canvas
ref={canvasRef}
className={cn(
'border border-border rounded cursor-pointer',
isDragging && 'cursor-grabbing'
)}
style={{
width: `${minimapWidth}px`,
height: `${minimapHeight}px`,
}}
onPointerDown={handlePointerDown}
onPointerMove={handlePointerMove}
onPointerUp={handlePointerUp}
onPointerLeave={handlePointerUp}
/>
{/* Zoom indicator */}
<div className="text-xs text-center text-muted-foreground font-mono">
{Math.round(zoom * 100)}%
</div>
</div>
);
}