diff --git a/src/components/reader/BookReader.tsx b/src/components/reader/BookReader.tsx index aea53ac..565037c 100644 --- a/src/components/reader/BookReader.tsx +++ b/src/components/reader/BookReader.tsx @@ -5,20 +5,22 @@ import { BookReaderProps } from "./types"; import { useOrientation } from "./hooks/useOrientation"; import { usePageNavigation } from "./hooks/usePageNavigation"; import { usePageCache } from "./hooks/usePageCache"; +import { usePageUrls } from "./hooks/usePageUrls"; +import { usePreloadPages } from "./hooks/usePreloadPages"; +import { useFullscreen } from "./hooks/useFullscreen"; import { useState, useEffect, useCallback, useRef } from "react"; import { NavigationBar } from "./components/NavigationBar"; import { ControlButtons } from "./components/ControlButtons"; -import { ImageLoader } from "@/components/ui/image-loader"; -import { cn } from "@/lib/utils"; +import { ReaderContent } from "./components/ReaderContent"; import { useReadingDirection } from "./hooks/useReadingDirection"; export function BookReader({ book, pages, onClose }: BookReaderProps) { const [isDoublePage, setIsDoublePage] = useState(false); const [showControls, setShowControls] = useState(false); - const [isFullscreen, setIsFullscreen] = useState(false); const readerRef = useRef(null); const isLandscape = useOrientation(); const { direction, toggleDirection, isRTL } = useReadingDirection(); + const { isFullscreen, toggleFullscreen } = useFullscreen(); const { currentPage, @@ -43,130 +45,29 @@ export function BookReader({ book, pages, onClose }: BookReaderProps) { pages, }); - // État pour stocker les URLs des images - const [currentPageUrl, setCurrentPageUrl] = useState(""); - const [nextPageUrl, setNextPageUrl] = useState(""); - - // 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) { - if (error instanceof Error) { - console.error( - `Erreur de chargement des URLs pour la page ${currentPage}:`, - error.message - ); - } - // 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; - }; - }, [ + const { currentPageUrl, nextPageUrl } = usePageUrls({ currentPage, isDoublePage, shouldShowDoublePage, getPageUrl, setIsLoading, setSecondPageLoading, - ]); + }); - // 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; - }; - }, [ + usePreloadPages({ currentPage, + totalPages: pages.length, isDoublePage, shouldShowDoublePage, preloadPage, cleanCache, - pages.length, - isRTL, - ]); + }); // Effet pour gérer le mode double page automatiquement en paysage useEffect(() => { setIsDoublePage(isLandscape); }, [isLandscape]); - // Effet pour gérer le fullscreen - useEffect(() => { - const handleFullscreenChange = () => { - setIsFullscreen(!!document.fullscreenElement); - }; - - document.addEventListener("fullscreenchange", handleFullscreenChange); - - return () => { - document.removeEventListener("fullscreenchange", handleFullscreenChange); - if (document.fullscreenElement) { - document.exitFullscreen().catch(console.error); - } - }; - }, []); - const handleThumbnailLoad = useCallback( (pageNumber: number) => { if (pageNumber === currentPage) { @@ -187,7 +88,6 @@ export function BookReader({ book, pages, onClose }: BookReaderProps) { className="relative h-full flex flex-col items-center justify-center" onClick={() => setShowControls(!showControls)} > - {/* Contenu principal */}
setIsDoublePage(!isDoublePage)} isFullscreen={isFullscreen} - onToggleFullscreen={async () => { - try { - if (isFullscreen) { - await document.exitFullscreen(); - } else if (readerRef.current) { - await readerRef.current.requestFullscreen(); - } - } catch (error) { - console.error("Erreur lors du changement de mode plein écran:", error); - } - }} + onToggleFullscreen={() => toggleFullscreen(readerRef.current)} direction={direction} onToggleDirection={toggleDirection} /> - {/* Pages */} -
-
- {/* - Note: Nous utilisons intentionnellement des balises natives au lieu de next/image pour : - 1. Avoir un contrôle précis sur le chargement et le préchargement des pages - 2. Gérer efficacement le mode double page et les transitions - 3. Les images sont déjà optimisées côté serveur - 4. La performance est critique pour une lecture fluide - */} -
- - {currentPageUrl && ( - {`Page handleThumbnailLoad(currentPage)} - /> - )} -
+ - {/* Deuxième page en mode double page */} - {isDoublePage && shouldShowDoublePage(currentPage) && ( -
- - {nextPageUrl && ( - {`Page handleThumbnailLoad(currentPage + 1)} - /> - )} -
- )} -
-
- - {/* Barre de navigation */} boolean; + isRTL: boolean; + onThumbnailLoad: (pageNumber: number) => void; +} + +export const ReaderContent = ({ + currentPage, + currentPageUrl, + nextPageUrl, + isLoading, + secondPageLoading, + isDoublePage, + shouldShowDoublePage, + isRTL, + onThumbnailLoad, +}: ReaderContentProps) => { + return ( +
+
+ + + {isDoublePage && shouldShowDoublePage(currentPage) && ( + + )} +
+
+ ); +}; diff --git a/src/components/reader/components/SinglePage.tsx b/src/components/reader/components/SinglePage.tsx new file mode 100644 index 0000000..bcb24f4 --- /dev/null +++ b/src/components/reader/components/SinglePage.tsx @@ -0,0 +1,49 @@ +import { cn } from "@/lib/utils"; +import { ImageLoader } from "@/components/ui/image-loader"; + +interface SinglePageProps { + pageUrl: string; + pageNumber: number; + isLoading: boolean; + onLoad: (pageNumber: number) => void; + isDoublePage?: boolean; + isRTL?: boolean; + order?: "first" | "second"; +} + +export const SinglePage = ({ + pageUrl, + pageNumber, + isLoading, + onLoad, + isDoublePage = false, + isRTL = false, + order = "first", +}: SinglePageProps) => { + return ( +
+ + {pageUrl && ( + {`Page onLoad(pageNumber)} + /> + )} +
+ ); +}; diff --git a/src/components/reader/hooks/useFullscreen.ts b/src/components/reader/hooks/useFullscreen.ts new file mode 100644 index 0000000..a803e72 --- /dev/null +++ b/src/components/reader/hooks/useFullscreen.ts @@ -0,0 +1,37 @@ +import { useState, useEffect } from "react"; + +export const useFullscreen = () => { + const [isFullscreen, setIsFullscreen] = useState(false); + + useEffect(() => { + const handleFullscreenChange = () => { + setIsFullscreen(!!document.fullscreenElement); + }; + + document.addEventListener("fullscreenchange", handleFullscreenChange); + + return () => { + document.removeEventListener("fullscreenchange", handleFullscreenChange); + if (document.fullscreenElement) { + document.exitFullscreen().catch(console.error); + } + }; + }, []); + + const toggleFullscreen = async (element: HTMLElement | null) => { + try { + if (isFullscreen) { + await document.exitFullscreen(); + } else if (element) { + await element.requestFullscreen(); + } + } catch (error) { + console.error("Erreur lors du changement de mode plein écran:", error); + } + }; + + return { + isFullscreen, + toggleFullscreen, + }; +}; diff --git a/src/components/reader/hooks/usePageUrls.ts b/src/components/reader/hooks/usePageUrls.ts new file mode 100644 index 0000000..41c8c3c --- /dev/null +++ b/src/components/reader/hooks/usePageUrls.ts @@ -0,0 +1,75 @@ +import { useState, useEffect } from "react"; + +interface UsePageUrlsProps { + currentPage: number; + isDoublePage: boolean; + shouldShowDoublePage: (page: number) => boolean; + getPageUrl: (page: number) => Promise; + setIsLoading: (loading: boolean) => void; + setSecondPageLoading: (loading: boolean) => void; +} + +export const usePageUrls = ({ + currentPage, + isDoublePage, + shouldShowDoublePage, + getPageUrl, + setIsLoading, + setSecondPageLoading, +}: UsePageUrlsProps) => { + const [currentPageUrl, setCurrentPageUrl] = useState(""); + const [nextPageUrl, setNextPageUrl] = useState(""); + + 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) { + if (error instanceof Error) { + console.error( + `Erreur de chargement des URLs pour la page ${currentPage}:`, + error.message + ); + } + if (isMounted) { + setIsLoading(false); + setSecondPageLoading(false); + } + } + }; + + setIsLoading(true); + setSecondPageLoading(true); + loadPageUrls(); + + return () => { + isMounted = false; + }; + }, [ + currentPage, + isDoublePage, + shouldShowDoublePage, + getPageUrl, + setIsLoading, + setSecondPageLoading, + ]); + + return { + currentPageUrl, + nextPageUrl, + }; +}; diff --git a/src/components/reader/hooks/usePreloadPages.ts b/src/components/reader/hooks/usePreloadPages.ts new file mode 100644 index 0000000..bb7d687 --- /dev/null +++ b/src/components/reader/hooks/usePreloadPages.ts @@ -0,0 +1,61 @@ +import { useEffect } from "react"; + +interface UsePreloadPagesProps { + currentPage: number; + totalPages: number; + isDoublePage: boolean; + shouldShowDoublePage: (page: number) => boolean; + preloadPage: (page: number) => Promise; + cleanCache: (currentPage: number) => void; +} + +export const usePreloadPages = ({ + currentPage, + totalPages, + isDoublePage, + shouldShowDoublePage, + preloadPage, + cleanCache, +}: UsePreloadPagesProps) => { + 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 = []; + + // Précharger les 4 pages suivantes + for (let i = 1; i <= 4 && currentPage + i <= totalPages; i++) { + pagesToPreload.push(currentPage + i); + } + + // Précharger les 2 pages précédentes + 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, totalPages]); +};