Files
stripstream/src/components/reader/BookReader.tsx
2025-02-16 17:37:47 +01:00

214 lines
6.5 KiB
TypeScript

"use client";
import { BookReaderProps } from "./types";
import { useOrientation } from "./hooks/useOrientation";
import { usePageNavigation } from "./hooks/usePageNavigation";
import { usePageCache } from "./hooks/usePageCache";
import { useState, useEffect, useCallback } from "react";
import { NavigationBar } from "./components/NavigationBar";
import { ControlButtons } from "./components/ControlButtons";
import { ImageLoader } from "@/components/ui/image-loader";
import { cn } from "@/lib/utils";
export function BookReader({ book, pages, onClose }: BookReaderProps) {
const [isDoublePage, setIsDoublePage] = useState(false);
const [showControls, setShowControls] = useState(false);
const isLandscape = useOrientation();
const {
currentPage,
navigateToPage,
isLoading,
setIsLoading,
secondPageLoading,
setSecondPageLoading,
imageError,
setImageError,
handlePreviousPage,
handleNextPage,
shouldShowDoublePage,
syncReadProgress,
} = usePageNavigation({
book,
pages,
isDoublePage,
});
const { preloadPage, getPageUrl, cleanCache } = usePageCache({
book,
pages,
});
// État pour stocker les URLs des images
const [currentPageUrl, setCurrentPageUrl] = useState<string>("");
const [nextPageUrl, setNextPageUrl] = useState<string>("");
// Effet pour charger les URLs des images
useEffect(() => {
let isMounted = true;
const loadPageUrls = async () => {
try {
const url = await getPageUrl(currentPage);
if (isMounted) {
setCurrentPageUrl(url);
setIsLoading(false);
}
if (isDoublePage && shouldShowDoublePage(currentPage)) {
const nextUrl = await getPageUrl(currentPage + 1);
if (isMounted) {
setNextPageUrl(nextUrl);
setSecondPageLoading(false);
}
}
} catch (error) {
console.error("Erreur lors du chargement des URLs:", error);
setImageError(true);
} finally {
// On s'assure que le chargement est terminé même en cas d'erreur
if (isMounted) {
setIsLoading(false);
setSecondPageLoading(false);
}
}
};
setIsLoading(true);
setSecondPageLoading(true);
loadPageUrls();
return () => {
isMounted = false;
};
}, [currentPage, isDoublePage, shouldShowDoublePage, getPageUrl]);
// Effet pour précharger la page courante et les pages adjacentes
useEffect(() => {
let isMounted = true;
const preloadCurrentPages = async () => {
if (!isMounted) return;
await preloadPage(currentPage);
if (!isMounted) return;
if (isDoublePage && shouldShowDoublePage(currentPage)) {
await preloadPage(currentPage + 1);
}
if (!isMounted) return;
const pagesToPreload = [];
for (let i = 1; i <= 4 && currentPage + i <= pages.length; i++) {
pagesToPreload.push(currentPage + i);
}
for (let i = 1; i <= 2 && currentPage - i >= 1; i++) {
pagesToPreload.push(currentPage - i);
}
for (const page of pagesToPreload) {
if (!isMounted) break;
await preloadPage(page);
}
};
preloadCurrentPages();
cleanCache(currentPage);
return () => {
isMounted = false;
};
}, [currentPage, isDoublePage, shouldShowDoublePage, preloadPage, cleanCache, pages.length]);
// Effet pour gérer le mode double page automatiquement en paysage
useEffect(() => {
setIsDoublePage(isLandscape);
}, [isLandscape]);
const handleThumbnailLoad = useCallback(
(pageNumber: number) => {
if (pageNumber === currentPage) {
setIsLoading(false);
} else if (pageNumber === currentPage + 1) {
setSecondPageLoading(false);
}
},
[currentPage]
);
return (
<div className="fixed inset-0 bg-background/95 backdrop-blur-sm z-50">
<div
className="relative h-full flex flex-col items-center justify-center"
onClick={() => setShowControls(!showControls)}
>
{/* Contenu principal */}
<div className="relative h-full w-full flex items-center justify-center">
<ControlButtons
showControls={showControls}
onToggleControls={() => setShowControls(!showControls)}
onPreviousPage={handlePreviousPage}
onNextPage={handleNextPage}
onClose={onClose}
currentPage={currentPage}
totalPages={pages.length}
isDoublePage={isDoublePage}
onToggleDoublePage={() => setIsDoublePage(!isDoublePage)}
/>
{/* Pages */}
<div className="relative flex-1 flex items-center justify-center overflow-hidden p-1">
<div className="relative w-full h-[calc(100vh-2rem)] flex items-center justify-center">
{/* Page courante */}
<div className="relative h-full w-full flex items-center justify-center">
<ImageLoader isLoading={isLoading} />
{currentPageUrl && (
<img
src={currentPageUrl}
alt={`Page ${currentPage}`}
className={cn(
"max-h-full w-auto object-contain transition-opacity duration-300",
isLoading ? "opacity-0" : "opacity-100"
)}
onLoad={() => handleThumbnailLoad(currentPage)}
/>
)}
</div>
{/* Deuxième page en mode double page */}
{isDoublePage && shouldShowDoublePage(currentPage) && (
<div className="relative h-full w-full flex items-center justify-center">
<ImageLoader isLoading={secondPageLoading} />
{nextPageUrl && (
<img
src={nextPageUrl}
alt={`Page ${currentPage + 1}`}
className={cn(
"max-h-full w-auto object-contain transition-opacity duration-300",
secondPageLoading ? "opacity-0" : "opacity-100"
)}
onLoad={() => handleThumbnailLoad(currentPage + 1)}
/>
)}
</div>
)}
</div>
</div>
</div>
<NavigationBar
currentPage={currentPage}
pages={pages}
onPageChange={navigateToPage}
showControls={showControls}
book={book}
/>
</div>
</div>
);
}