"use client"; import { KomgaBook } from "@/types/komga"; import { ChevronLeft, ChevronRight, ImageOff, Loader2, LayoutTemplate, SplitSquareVertical, } from "lucide-react"; import Image from "next/image"; import { useEffect, useState, useCallback, useRef } from "react"; interface PageCache { [pageNumber: number]: { blob: Blob; url: string; timestamp: number; }; } interface BookReaderProps { book: KomgaBook; pages: number[]; onClose?: () => void; } export function BookReader({ book, pages, onClose }: BookReaderProps) { const [currentPage, setCurrentPage] = useState(1); const [isLoading, setIsLoading] = useState(true); const [imageError, setImageError] = useState(false); const [isDoublePage, setIsDoublePage] = useState(false); const pageCache = useRef({}); // Fonction pour synchroniser la progression const syncReadProgress = useCallback( async (page: number) => { try { const completed = page === pages.length; await fetch(`/api/komga/books/${book.id}/read-progress`, { method: "PATCH", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ page, completed }), }); } catch (error) { console.error("Erreur lors de la synchronisation de la progression:", error); } }, [book.id, pages.length] ); // Fonction pour déterminer si on doit afficher une ou deux pages const shouldShowDoublePage = useCallback( (pageNumber: number) => { if (!isDoublePage) return false; // Toujours afficher la première page seule (couverture) if (pageNumber === 1) return false; // Vérifier si on a une page suivante disponible return pageNumber < pages.length; }, [isDoublePage, pages.length] ); const handlePreviousPage = useCallback(() => { if (currentPage > 1) { // En mode double page, reculer de 2 pages sauf si on est sur la page 2 const newPage = isDoublePage && currentPage > 2 ? currentPage - 2 : currentPage - 1; setCurrentPage(newPage); setIsLoading(true); setImageError(false); syncReadProgress(newPage); } }, [currentPage, isDoublePage, syncReadProgress]); const handleNextPage = useCallback(() => { if (currentPage < pages.length) { // En mode double page, avancer de 2 pages sauf si c'est la dernière paire const newPage = isDoublePage ? Math.min(currentPage + 2, pages.length) : currentPage + 1; setCurrentPage(newPage); setIsLoading(true); setImageError(false); syncReadProgress(newPage); } }, [currentPage, pages.length, isDoublePage, syncReadProgress]); // Fonction pour précharger une page const preloadPage = useCallback( async (pageNumber: number) => { if (pageNumber > pages.length || pageNumber < 1 || pageCache.current[pageNumber]) return; try { const response = await fetch(`/api/komga/books/${book.id}/pages/${pageNumber}`); const blob = await response.blob(); const url = URL.createObjectURL(blob); pageCache.current[pageNumber] = { blob, url, timestamp: Date.now(), }; } catch (error) { console.error(`Erreur lors du préchargement de la page ${pageNumber}:`, error); } }, [book.id, pages.length] ); // Fonction pour précharger les prochaines pages const preloadNextPages = useCallback( async (currentPageNumber: number, count: number = 2) => { const pagesToPreload = Array.from( { length: count }, (_, i) => currentPageNumber + i + 1 ).filter((page) => page <= pages.length); await Promise.all(pagesToPreload.map(preloadPage)); }, [pages.length, preloadPage] ); // Nettoyer le cache des pages trop anciennes const cleanCache = useCallback( (currentPageNumber: number, maxDistance: number = 5) => { const minPage = Math.max(1, currentPageNumber - maxDistance); const maxPage = Math.min(pages.length, currentPageNumber + maxDistance); Object.entries(pageCache.current).forEach(([pageNum, cache]) => { const page = parseInt(pageNum); if (page < minPage || page > maxPage) { URL.revokeObjectURL(cache.url); delete pageCache.current[page]; } }); }, [pages.length] ); // Fonction pour obtenir l'URL d'une page (depuis le cache ou générée) const getPageUrl = useCallback( (pageNumber: number) => { const cached = pageCache.current[pageNumber]; if (cached) return cached.url; return `/api/komga/books/${book.id}/pages/${pageNumber}`; }, [book.id] ); // Effet pour précharger les pages au changement de page useEffect(() => { preloadNextPages(currentPage); cleanCache(currentPage); }, [currentPage, preloadNextPages, cleanCache]); useEffect(() => { const handleKeyDown = (event: KeyboardEvent) => { if (event.key === "ArrowLeft") { handlePreviousPage(); } else if (event.key === "ArrowRight") { handleNextPage(); } else if (event.key === "Escape" && onClose) { onClose(); } }; window.addEventListener("keydown", handleKeyDown); return () => window.removeEventListener("keydown", handleKeyDown); }, [handlePreviousPage, handleNextPage, onClose]); return (
{/* Bouton mode double page */} {/* Bouton précédent */} {currentPage > 1 && ( )} {/* Pages */}
{/* Page courante */}
{isLoading && (
)} {!imageError ? ( {`Page setIsLoading(false)} onError={() => { setIsLoading(false); setImageError(true); }} /> ) : (
)}
{/* Deuxième page en mode double page */} {shouldShowDoublePage(currentPage) && (
{`Page setImageError(true)} />
)}
{/* Bouton suivant */} {currentPage < pages.length && ( )} {/* Indicateur de page */}
Page {currentPage} {shouldShowDoublePage(currentPage) ? `-${currentPage + 1}` : ""} / {pages.length}
{/* Bouton fermer */} {onClose && ( )}
); }