From 3e5687441d0098607f54d86d005c3ccf639e7f15 Mon Sep 17 00:00:00 2001 From: Froidefond Julien Date: Mon, 2 Mar 2026 16:03:25 +0100 Subject: [PATCH] fix: improve reader image error handling and double page alignment - Show a clean placeholder (icon + label) instead of the browser's broken image icon when a page fails to load - Track error state per page (page 1 and page 2) and reset on page navigation - Center each page within its half in double page mode instead of pushing toward the spine Co-Authored-By: Claude Sonnet 4.6 --- .../reader/components/PageDisplay.tsx | 126 ++++++++++++------ 1 file changed, 83 insertions(+), 43 deletions(-) diff --git a/src/components/reader/components/PageDisplay.tsx b/src/components/reader/components/PageDisplay.tsx index d69539b..844dda6 100644 --- a/src/components/reader/components/PageDisplay.tsx +++ b/src/components/reader/components/PageDisplay.tsx @@ -20,21 +20,35 @@ export function PageDisplay({ getPageUrl, }: PageDisplayProps) { const [isLoading, setIsLoading] = useState(true); + const [hasError, setHasError] = useState(false); const [secondPageLoading, setSecondPageLoading] = useState(true); + const [secondPageHasError, setSecondPageHasError] = useState(false); const { isRTL } = useReadingDirection(); const handleImageLoad = useCallback(() => { setIsLoading(false); }, []); + const handleImageError = useCallback(() => { + setIsLoading(false); + setHasError(true); + }, []); + const handleSecondImageLoad = useCallback(() => { setSecondPageLoading(false); }, []); + const handleSecondImageError = useCallback(() => { + setSecondPageLoading(false); + setSecondPageHasError(true); + }, []); + // Reset loading when page changes useEffect(() => { setIsLoading(true); + setHasError(false); setSecondPageLoading(true); + setSecondPageHasError(false); }, [currentPage, isDoublePage]); return ( @@ -47,8 +61,8 @@ export function PageDisplay({ isDoublePage && shouldShowDoublePage(currentPage) ? "w-1/2" : "w-full justify-center", isDoublePage && shouldShowDoublePage(currentPage) && { - "order-2 justify-start": isRTL, - "order-1 justify-end": !isRTL, + "order-2 justify-center": isRTL, + "order-1 justify-center": !isRTL, } )} > @@ -63,33 +77,46 @@ export function PageDisplay({ )} - {/* eslint-disable-next-line @next/next/no-img-element */} - {`Page { - // Si l'image est déjà en cache, onLoad ne sera pas appelé - if (img?.complete && img?.naturalHeight !== 0) { - handleImageLoad(); - } - }} - /> + {hasError ? ( +
+ + + + + + Image non disponible +
+ ) : ( + <> + {/* eslint-disable-next-line @next/next/no-img-element */} + {`Page { + // Si l'image est déjà en cache, onLoad ne sera pas appelé + if (img?.complete && img?.naturalHeight !== 0) { + handleImageLoad(); + } + }} + /> + + )} {/* Page 2 (double page) */} {isDoublePage && shouldShowDoublePage(currentPage) && (
{secondPageLoading && ( @@ -103,25 +130,38 @@ export function PageDisplay({
)} - {/* eslint-disable-next-line @next/next/no-img-element */} - {`Page { - // Si l'image est déjà en cache, onLoad ne sera pas appelé - if (img?.complete && img?.naturalHeight !== 0) { - handleSecondImageLoad(); - } - }} - /> + {secondPageHasError ? ( +
+ + + + + + Image non disponible +
+ ) : ( + <> + {/* eslint-disable-next-line @next/next/no-img-element */} + {`Page { + // Si l'image est déjà en cache, onLoad ne sera pas appelé + if (img?.complete && img?.naturalHeight !== 0) { + handleSecondImageLoad(); + } + }} + /> + + )} )}