import { useState } from "react"; import { MousePointer, Square, Pentagon, Circle, Brush, ChevronLeft, ChevronRight } from "lucide-react"; import { Tool } from "./PathoraViewer"; import { type Annotation } from "./AnnotationCanvas"; interface ToolsSidebarProps { selectedTool: Tool; onToolChange: (tool: Tool) => void; annotations: Annotation[]; selectedAnnotationId: string | null; onSelectAnnotation: (annotationId: string | null) => void; uploadedSlides: Array<{ id: string; name: string; uploadedAt: string; levelCount: number; levelDimensions: number[][]; }>; tileServerUrl: string; onTileServerUrlChange: (value: string) => void; onSlideFileChange: (file: File | null) => void; slideFileName: string | null; onUploadSlide: () => void; isTileLoading: boolean; tileLoadError: string | null; onSelectUploadedSlide: (slideId: string) => void; activeLabel: string; onLabelChange: (label: string) => void; imageMeta: { stain: string; width: number | null; height: number | null; levelCount: number | null; mpp: number | null; slideId: string; }; channelVisibility: { original: boolean; hematoxylin: boolean; eosin: boolean; }; onChannelToggle: (channel: "original" | "hematoxylin" | "eosin", value: boolean) => void; isTileLoaded: boolean; isCollapsed: boolean; onToggleCollapsed: () => void; } export function ToolsSidebar({ selectedTool, onToolChange, annotations, selectedAnnotationId, onSelectAnnotation, uploadedSlides, tileServerUrl, onTileServerUrlChange, onSlideFileChange, slideFileName, onUploadSlide, isTileLoading, tileLoadError, onSelectUploadedSlide, activeLabel, onLabelChange, imageMeta, channelVisibility, onChannelToggle, isTileLoaded, isCollapsed, onToggleCollapsed, }: ToolsSidebarProps) { const [activeTab, setActiveTab] = useState<"tools" | "image" | "images" | "annotations">("tools"); const tools = [ { id: "select" as Tool, label: "Select", icon: MousePointer }, { id: "rectangle" as Tool, label: "Rectangle", icon: Square }, { id: "polygon" as Tool, label: "Polygon", icon: Pentagon }, { id: "ellipse" as Tool, label: "Ellipse", icon: Circle }, { id: "brush" as Tool, label: "Brush", icon: Brush }, ]; const annotationLabel = (annotation: Annotation, index: number) => { if (annotation.label && annotation.label.trim().length > 0) { return annotation.label; } return annotation.type === "rectangle" ? "Rectangle" : annotation.type === "polygon" ? "Polygon" : annotation.type === "ellipse" ? "Ellipse" : "Brush"; }; const normalizePoints = (points: Array<{ x: number; y: number }>, size = 24) => { if (points.length === 0) return []; const xs = points.map((p) => p.x); const ys = points.map((p) => p.y); const minX = Math.min(...xs); const maxX = Math.max(...xs); const minY = Math.min(...ys); const maxY = Math.max(...ys); const width = Math.max(maxX - minX, 1); const height = Math.max(maxY - minY, 1); const padding = 2; const scale = Math.min((size - padding * 2) / width, (size - padding * 2) / height); return points.map((p) => ({ x: (p.x - minX) * scale + padding, y: (p.y - minY) * scale + padding, })); }; const normalizeBaseUrl = (value: string) => value.replace(/\/$/, ""); const baseTileUrl = normalizeBaseUrl(tileServerUrl); const thumbnailSize = 192; const renderAnnotationPreview = (annotation: Annotation) => { const size = 24; const stroke = annotation.color || "#9CA3AF"; const normalized = normalizePoints(annotation.points, size); if (annotation.type === "rectangle" && normalized.length >= 2) { const [p1, p2] = normalized; const x = Math.min(p1.x, p2.x); const y = Math.min(p1.y, p2.y); const width = Math.abs(p2.x - p1.x); const height = Math.abs(p2.y - p1.y); return ( ); } if (annotation.type === "ellipse" && normalized.length >= 2) { const [p1, p2] = normalized; const cx = (p1.x + p2.x) / 2; const cy = (p1.y + p2.y) / 2; const rx = Math.abs(p2.x - p1.x) / 2; const ry = Math.abs(p2.y - p1.y) / 2; return ( ); } if ((annotation.type === "polygon" || annotation.type === "brush") && normalized.length >= 2) { const path = normalized.map((p, idx) => `${idx === 0 ? "M" : "L"}${p.x} ${p.y}`).join(" "); const closed = annotation.type === "polygon" ? " Z" : ""; return ( ); } return ( ); }; return ( ); }