import { useCallback, useEffect, useState } from 'react' import { useNavigate, useParams, useSearchParams } from 'react-router-dom' import type OpenSeadragon from 'openseadragon' import { fetchPages, fetchMasterJson, fetchProfile, ApiError, type Page, type PageMaster, type CorpusProfile, type Region, } from '../lib/api.ts' import Viewer from '../components/Viewer.tsx' import RegionOverlay from '../components/RegionOverlay.tsx' import LayerPanel from '../components/LayerPanel.tsx' import TranscriptionPanel from '../components/TranscriptionPanel.tsx' import TranslationPanel from '../components/TranslationPanel.tsx' import CommentaryPanel from '../components/CommentaryPanel.tsx' import { RetroMenuBar, RetroWindow, RetroButton, RetroBadge } from '../components/retro' export default function Reader() { const { manuscriptId = '' } = useParams() const [searchParams] = useSearchParams() const profileId = searchParams.get('profile') ?? '' const navigate = useNavigate() const [pages, setPages] = useState([]) const [currentIndex, setCurrentIndex] = useState(0) const [master, setMaster] = useState(null) const [profile, setProfile] = useState(null) const [visibleLayers, setVisibleLayers] = useState>(new Set()) const [osdViewer, setOsdViewer] = useState(null) const [selectedRegion, setSelectedRegion] = useState(null) const [loading, setLoading] = useState(true) const [error, setError] = useState(null) useEffect(() => { const loadPages = fetchPages(manuscriptId) const loadProfile = profileId ? fetchProfile(profileId).catch(() => null) : Promise.resolve(null) Promise.all([loadPages, loadProfile]) .then(([pgs, prof]) => { const sorted = [...pgs].sort((a, b) => a.sequence - b.sequence) setPages(sorted) if (prof) { setProfile(prof) setVisibleLayers(new Set(prof.active_layers)) } }) .catch((e: Error) => setError(e.message)) .finally(() => setLoading(false)) }, [manuscriptId, profileId]) const [masterError, setMasterError] = useState(null) useEffect(() => { if (pages.length === 0) return setMaster(null) setMasterError(null) setSelectedRegion(null) fetchMasterJson(pages[currentIndex].id) .then(setMaster) .catch((e: unknown) => { // 404 = page non analysée (normal), autres erreurs = problème réseau if (e instanceof ApiError && e.status === 404) { setMaster(null) } else { const msg = e instanceof Error ? e.message : '' setMasterError(msg || 'Erreur de chargement') } }) }, [pages, currentIndex]) const handleViewerReady = useCallback((v: OpenSeadragon.Viewer) => { setOsdViewer(v) }, []) const toggleLayer = useCallback((layer: string) => { setVisibleLayers((prev) => { const next = new Set(prev) if (next.has(layer)) next.delete(layer) else next.add(layer) return next }) }, []) // ── Loading ───────────────────────────────────────────────────────── if (loading) { return (
Chargement...
) } if (error) { return (
{error}
) } if (pages.length === 0) { return (
Aucune page dans ce manuscrit.
navigate('/')}>Retour
) } const currentPage = pages[currentIndex] const iiifServiceUrl = currentPage.iiif_service_url ?? null const fallbackImageUrl = currentPage.image_master_path ?? '' const regions: Region[] = master?.layout?.regions ?? [] return (
{/* ── Menu bar ───────────────────────────────────────────────── */} navigate('/') }, { label: profile?.label ?? profileId }, ]} right={
{currentPage.folio_label} — {currentIndex + 1}/{pages.length} setCurrentIndex((i) => i - 1)} > Prev setCurrentIndex((i) => i + 1)} > Next navigate(`/editor/${currentPage.id}`)}> Éditer
} /> {/* ── Main content ───────────────────────────────────────────── */}
{/* ── Viewer window (left, 70%) ──────────────────────────── */}
{/* Region info popup */} {selectedRegion && (
{selectedRegion.type.replace(/_/g, ' ')}
id: {selectedRegion.id}
confiance: {(selectedRegion.confidence * 100).toFixed(0)}%
bbox: [{selectedRegion.bbox.join(', ')}]
)} {/* Not analyzed / error badge */} {!master && !loading && (iiifServiceUrl || fallbackImageUrl) && (
{masterError ? Erreur: {masterError} : Non analysée }
)}
{/* ── Right panels (30%) ─────────────────────────────────── */}
{/* Layer toggles */} {profile && ( )} {/* Content panels */} {master ? (
) : (
{(iiifServiceUrl || fallbackImageUrl) ? 'Page non encore analysée par l\'IA.' : 'Aucune image associée à cette page.' }
)}
) }