'use client'; import { useState, useEffect, useRef } from 'react'; import { Item } from '@/lib/db'; import { X } from 'lucide-react'; interface DebugRotationPanelProps { item: Item; imageUrl: string; originalPhotoPath?: string; originalCropBounds?: { x: number; y: number; width: number; height: number }; originalRotation?: number; imageProcessing?: any; onClose: () => void; } export function DebugRotationPanel({ item, imageUrl, originalPhotoPath, originalCropBounds, originalRotation = 0, imageProcessing, onClose, }: DebugRotationPanelProps) { const [rotation, setRotation] = useState(originalRotation); const canvasRef = useRef(null); const [log, setLog] = useState(''); const imageRef = useRef(null); const [imageLoaded, setImageLoaded] = useState(false); const [scaledDimensions, setScaledDimensions] = useState({ width: 0, height: 0, scale: 1 }); const containerRef = useRef(null); // Adjustable crop bounds (in original image coordinates) const [adjustedCropBounds, setAdjustedCropBounds] = useState<{ x: number; y: number; width: number; height: number } | null>(null); const [draggingState, setDraggingState] = useState<{ type: string; startX: number; startY: number; startBounds: any } | null>(null); // Use original photo path if available, otherwise fall back to processed image URL const displayImageUrl = originalPhotoPath ? originalPhotoPath : imageUrl; const commonAngles = [ { label: '0°', value: 0 }, { label: '+22°', value: 22 }, { label: '-22°', value: -22 }, { label: '+45°', value: 45 }, { label: '-45°', value: -45 }, { label: '+90°', value: 90 }, { label: '-90°', value: -90 }, { label: '±180°', value: 180 }, ]; const updateLog = (message: string) => { setLog(message); }; // Get the crop bounds to display (adjusted if user modified, otherwise original) const displayCropBounds = adjustedCropBounds || originalCropBounds; // Handle mouse down on canvas for dragging/resizing const handleCanvasMouseDown = (e: React.MouseEvent) => { if (!canvasRef.current || !displayCropBounds) return; const rect = canvasRef.current.getBoundingClientRect(); const mouseX = (e.clientX - rect.left) / (rect.width / scaledDimensions.width); const mouseY = (e.clientY - rect.top) / (rect.height / scaledDimensions.height); // Convert screen coords to original image coords const scale = scaledDimensions.scale; const origX = mouseX / scale; const origY = mouseY / scale; const cropX = displayCropBounds.x; const cropY = displayCropBounds.y; const cropW = displayCropBounds.width; const cropH = displayCropBounds.height; const margin = 15; // pixels in original coords for resize handles let dragType = ''; if (origX >= cropX - margin && origX <= cropX + margin && origY >= cropY - margin && origY <= cropY + margin) { dragType = 'nw'; // NW corner } else if (origX >= cropX + cropW - margin && origX <= cropX + cropW + margin && origY >= cropY - margin && origY <= cropY + margin) { dragType = 'ne'; // NE corner } else if (origX >= cropX - margin && origX <= cropX + margin && origY >= cropY + cropH - margin && origY <= cropY + cropH + margin) { dragType = 'sw'; // SW corner } else if (origX >= cropX + cropW - margin && origX <= cropX + cropW + margin && origY >= cropY + cropH - margin && origY <= cropY + cropH + margin) { dragType = 'se'; // SE corner } else if (origX >= cropX && origX <= cropX + cropW && origY >= cropY && origY <= cropY + cropH) { dragType = 'move'; // Inside the box } if (dragType) { setDraggingState({ type: dragType, startX: origX, startY: origY, startBounds: { ...displayCropBounds }, }); } }; const handleCanvasMouseMove = (e: React.MouseEvent) => { if (!draggingState || !canvasRef.current || !displayCropBounds) return; const rect = canvasRef.current.getBoundingClientRect(); const mouseX = (e.clientX - rect.left) / (rect.width / scaledDimensions.width); const mouseY = (e.clientY - rect.top) / (rect.height / scaledDimensions.height); const scale = scaledDimensions.scale; const origX = mouseX / scale; const origY = mouseY / scale; const dx = origX - draggingState.startX; const dy = origY - draggingState.startY; const startBounds = draggingState.startBounds; let newBounds = { ...startBounds }; if (draggingState.type === 'move') { newBounds.x = Math.max(0, startBounds.x + dx); newBounds.y = Math.max(0, startBounds.y + dy); } else if (draggingState.type === 'nw') { newBounds.x = Math.max(0, startBounds.x + dx); newBounds.y = Math.max(0, startBounds.y + dy); newBounds.width = Math.max(50, startBounds.width - dx); newBounds.height = Math.max(50, startBounds.height - dy); } else if (draggingState.type === 'ne') { newBounds.y = Math.max(0, startBounds.y + dy); newBounds.width = Math.max(50, startBounds.width + dx); newBounds.height = Math.max(50, startBounds.height - dy); } else if (draggingState.type === 'sw') { newBounds.x = Math.max(0, startBounds.x + dx); newBounds.width = Math.max(50, startBounds.width - dx); newBounds.height = Math.max(50, startBounds.height + dy); } else if (draggingState.type === 'se') { newBounds.width = Math.max(50, startBounds.width + dx); newBounds.height = Math.max(50, startBounds.height + dy); } setAdjustedCropBounds(newBounds); }; const handleCanvasMouseUp = () => { setDraggingState(null); }; useEffect(() => { const img = new Image(); img.crossOrigin = 'anonymous'; img.onload = () => { imageRef.current = img; setImageLoaded(true); // Calculate scaled dimensions to fit available space (much larger) const maxWidth = 900; const maxHeight = 600; const scale = Math.min(maxWidth / img.width, maxHeight / img.height); const scaledW = img.width * scale; const scaledH = img.height * scale; setScaledDimensions({ width: scaledW, height: scaledH, scale }); updateLog(`📸 ${img.width}×${img.height}px → scaled ${scaledW.toFixed(0)}×${scaledH.toFixed(0)}px (${scale.toFixed(2)}x)`); }; img.onerror = () => updateLog('❌ Failed to load image'); img.src = displayImageUrl; }, [displayImageUrl]); useEffect(() => { if (!imageLoaded || !imageRef.current || !canvasRef.current || !displayCropBounds) return; const canvas = canvasRef.current; const ctx = canvas.getContext('2d'); if (!ctx) return; const img = imageRef.current; const { scale } = scaledDimensions; // Set canvas size to match scaled image canvas.width = scaledDimensions.width; canvas.height = scaledDimensions.height; // Draw scaled image ctx.drawImage(img, 0, 0, canvas.width, canvas.height); // Calculate crop box in scaled coordinates const cropX = displayCropBounds.x * scale; const cropY = displayCropBounds.y * scale; const cropW = displayCropBounds.width * scale; const cropH = displayCropBounds.height * scale; // Save state ctx.save(); // Draw orientation indicators at corners ctx.fillStyle = 'rgba(255, 255, 255, 0.9)'; ctx.font = 'bold 14px monospace'; ctx.textAlign = 'left'; ctx.textBaseline = 'top'; ctx.fillText('⬆ UP', 10, 5); ctx.fillText('⬅ LEFT', 5, 20); ctx.textAlign = 'right'; ctx.textBaseline = 'top'; ctx.fillText('RIGHT ➡', canvas.width - 10, 20); ctx.textAlign = 'left'; ctx.textBaseline = 'bottom'; ctx.fillText('⬇ DOWN', 10, canvas.height - 5); // Draw crop bounds rectangle (bright green) ctx.strokeStyle = '#00FF00'; ctx.lineWidth = 4; ctx.shadowColor = 'rgba(0, 0, 0, 0.8)'; ctx.shadowBlur = 3; ctx.strokeRect(cropX, cropY, cropW, cropH); // Draw edge orientation labels on crop box ctx.fillStyle = '#00FF00'; ctx.font = 'bold 12px monospace'; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; ctx.shadowColor = 'rgba(0, 0, 0, 0.8)'; ctx.shadowBlur = 2; // Top edge label ctx.fillText('T', cropX + cropW / 2, cropY - 8); // Bottom edge label ctx.fillText('B', cropX + cropW / 2, cropY + cropH + 8); // Left edge label ctx.textAlign = 'right'; ctx.fillText('L', cropX - 8, cropY + cropH / 2); // Right edge label ctx.textAlign = 'left'; ctx.fillText('R', cropX + cropW + 8, cropY + cropH / 2); // Draw resize handles at corners const handleSize = 8; ctx.fillStyle = '#FFB800'; ctx.shadowColor = 'rgba(0, 0, 0, 0.8)'; ctx.shadowBlur = 2; // NW, NE, SW, SE corners ctx.fillRect(cropX - handleSize / 2, cropY - handleSize / 2, handleSize, handleSize); ctx.fillRect(cropX + cropW - handleSize / 2, cropY - handleSize / 2, handleSize, handleSize); ctx.fillRect(cropX - handleSize / 2, cropY + cropH - handleSize / 2, handleSize, handleSize); ctx.fillRect(cropX + cropW - handleSize / 2, cropY + cropH - handleSize / 2, handleSize, handleSize); // Draw rotation indicator (rotated rectangle inside crop area) if (Math.abs(rotation) > 0.5) { ctx.strokeStyle = '#FFB800'; ctx.lineWidth = 3; ctx.setLineDash([5, 5]); ctx.shadowColor = 'rgba(0, 0, 0, 0.8)'; ctx.shadowBlur = 2; // Draw a rotated rectangle showing where text will be after rotation ctx.save(); ctx.translate(cropX + cropW / 2, cropY + cropH / 2); ctx.rotate((rotation * Math.PI) / 180); ctx.strokeRect(-cropW / 2, -cropH / 2, cropW, cropH); // Add direction markers to rotated box ctx.setLineDash([]); ctx.fillStyle = '#FFB800'; ctx.font = 'bold 12px monospace'; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; ctx.fillText('↑ TOP', 0, -cropH / 2 - 10); ctx.fillText('↓ BTM', 0, cropH / 2 + 10); ctx.restore(); } ctx.restore(); // Update log with current state let logText = `Rotation: ${rotation}°`; if (adjustedCropBounds) { logText += ` | Adjusted: (${adjustedCropBounds.x}, ${adjustedCropBounds.y}) ${adjustedCropBounds.width}×${adjustedCropBounds.height}px`; } else { logText += ` | AI Crop: (${displayCropBounds.x}, ${displayCropBounds.y}) ${displayCropBounds.width}×${displayCropBounds.height}px`; } updateLog(logText); }, [imageLoaded, rotation, displayCropBounds, scaledDimensions, adjustedCropBounds]); return (
{/* Header */}

Debug Rotation & Crop

{/* Left: Controls */}
{/* Rotation Slider */}
setRotation(Number(e.target.value))} className="w-full" />
{/* Quick Presets */}
{commonAngles.map((angle) => ( ))}
{/* Reset Button */} {/* Original Values */} {originalRotation !== undefined && (

Original AI Values

Rotation: {originalRotation}°
{originalCropBounds && ( <>
Crop ({originalCropBounds.x}, {originalCropBounds.y})
Size {originalCropBounds.width}×{originalCropBounds.height}
)} {imageProcessing?.confidence && (
Confidence: {(imageProcessing.confidence * 100).toFixed(0)}%
)}
)}
{/* Right: Canvas + Log */}
{/* Canvas - takes most space */}

Preview (Green = Crop, Orange = Rotation)

{/* Log - single line */}
{log}
); }