import { useEffect, type FC } from 'react' import type OpenSeadragon from 'openseadragon' import type { Region } from '../lib/api.ts' const REGION_COLORS: Record = { text_block: '#3b82f6', // bleu miniature: '#f59e0b', // or decorated_initial: '#10b981', // vert margin: '#6b7280', // gris rubric: '#ef4444', // rouge other: '#8b5cf6', // violet } interface Props { viewer: OpenSeadragon.Viewer | null regions: Region[] onRegionClick: (region: Region) => void } const RegionOverlay: FC = ({ viewer, regions, onRegionClick }) => { useEffect(() => { if (!viewer) return // AbortController pour nettoyer proprement tous les event listeners // lors du démontage ou du re-rendu (évite les fuites mémoire). const controller = new AbortController() const { signal } = controller const addOverlays = () => { viewer.clearOverlays() const item = viewer.world.getItemAt(0) if (!item) return for (const region of regions) { const [x, y, w, h] = region.bbox const color = REGION_COLORS[region.type] ?? REGION_COLORS['other'] const el = document.createElement('div') el.style.border = `2px solid ${color}` el.style.boxSizing = 'border-box' el.style.cursor = 'pointer' el.title = `${region.type} · ${(region.confidence * 100).toFixed(0)} %` el.addEventListener('mouseenter', () => { el.style.backgroundColor = `${color}33` }, { signal }) el.addEventListener('mouseleave', () => { el.style.backgroundColor = '' }, { signal }) el.addEventListener('click', (e: MouseEvent) => { e.stopPropagation() onRegionClick(region) }, { signal }) const rect = item.imageToViewportRectangle(x, y, w, h) viewer.addOverlay(el, rect) } } if (viewer.isOpen()) { addOverlays() } else { viewer.addOnceHandler('open', addOverlays) } return () => { // Supprime tous les event listeners d'un coup, puis les overlays controller.abort() try { viewer.clearOverlays() } catch { // viewer peut avoir été détruit lors du démontage } } }, [viewer, regions, onRegionClick]) return null } export default RegionOverlay