fix: show spinner instead of broken image icon while loading reader pages
All checks were successful
Build, Push & Deploy / deploy (push) Successful in 4m25s
All checks were successful
Build, Push & Deploy / deploy (push) Successful in 4m25s
Only render <img> when blob URL is available from prefetch, preventing
broken image icons from src={undefined} or failed direct URL fallbacks.
Reset error state when blob URL arrives to recover from transient failures.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -265,9 +265,7 @@ export function PhotoswipeReader({ book, pages, onClose, nextBook }: BookReaderP
|
|||||||
isDoublePage={isDoublePage}
|
isDoublePage={isDoublePage}
|
||||||
shouldShowDoublePage={(page) => shouldShowDoublePage(page, pages.length)}
|
shouldShowDoublePage={(page) => shouldShowDoublePage(page, pages.length)}
|
||||||
imageBlobUrls={imageBlobUrls}
|
imageBlobUrls={imageBlobUrls}
|
||||||
getPageUrl={getPageUrl}
|
|
||||||
isRTL={isRTL}
|
isRTL={isRTL}
|
||||||
isPageLoading={isPageLoading}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<NavigationBar
|
<NavigationBar
|
||||||
|
|||||||
@@ -7,9 +7,7 @@ interface PageDisplayProps {
|
|||||||
isDoublePage: boolean;
|
isDoublePage: boolean;
|
||||||
shouldShowDoublePage: (page: number) => boolean;
|
shouldShowDoublePage: (page: number) => boolean;
|
||||||
imageBlobUrls: Record<number, string>;
|
imageBlobUrls: Record<number, string>;
|
||||||
getPageUrl: (pageNum: number) => string;
|
|
||||||
isRTL: boolean;
|
isRTL: boolean;
|
||||||
isPageLoading?: (pageNum: number) => boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function PageDisplay({
|
export function PageDisplay({
|
||||||
@@ -18,9 +16,7 @@ export function PageDisplay({
|
|||||||
isDoublePage,
|
isDoublePage,
|
||||||
shouldShowDoublePage,
|
shouldShowDoublePage,
|
||||||
imageBlobUrls,
|
imageBlobUrls,
|
||||||
getPageUrl,
|
|
||||||
isRTL,
|
isRTL,
|
||||||
isPageLoading,
|
|
||||||
}: PageDisplayProps) {
|
}: PageDisplayProps) {
|
||||||
const [isLoading, setIsLoading] = useState(true);
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
const [hasError, setHasError] = useState(false);
|
const [hasError, setHasError] = useState(false);
|
||||||
@@ -53,6 +49,21 @@ export function PageDisplay({
|
|||||||
setSecondPageHasError(false);
|
setSecondPageHasError(false);
|
||||||
}, [currentPage, isDoublePage]);
|
}, [currentPage, isDoublePage]);
|
||||||
|
|
||||||
|
// Reset error state when blob URL becomes available
|
||||||
|
useEffect(() => {
|
||||||
|
if (imageBlobUrls[currentPage] && hasError) {
|
||||||
|
setHasError(false);
|
||||||
|
setIsLoading(true);
|
||||||
|
}
|
||||||
|
}, [imageBlobUrls[currentPage], currentPage, hasError]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (imageBlobUrls[currentPage + 1] && secondPageHasError) {
|
||||||
|
setSecondPageHasError(false);
|
||||||
|
setSecondPageLoading(true);
|
||||||
|
}
|
||||||
|
}, [imageBlobUrls[currentPage + 1], currentPage, secondPageHasError]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative flex w-full flex-1 items-center justify-center overflow-hidden">
|
<div className="relative flex w-full flex-1 items-center justify-center overflow-hidden">
|
||||||
<div className="relative flex h-[calc(100vh-2.5rem)] w-full items-center justify-center px-2 sm:px-4">
|
<div className="relative flex h-[calc(100vh-2.5rem)] w-full items-center justify-center px-2 sm:px-4">
|
||||||
@@ -99,15 +110,12 @@ export function PageDisplay({
|
|||||||
</svg>
|
</svg>
|
||||||
<span className="text-sm opacity-60">Image non disponible</span>
|
<span className="text-sm opacity-60">Image non disponible</span>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : imageBlobUrls[currentPage] ? (
|
||||||
<>
|
<>
|
||||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||||
<img
|
<img
|
||||||
key={`page-${currentPage}-${imageBlobUrls[currentPage] || ""}`}
|
key={`page-${currentPage}-${imageBlobUrls[currentPage]}`}
|
||||||
src={
|
src={imageBlobUrls[currentPage]}
|
||||||
imageBlobUrls[currentPage] ||
|
|
||||||
(isPageLoading && isPageLoading(currentPage) ? undefined : getPageUrl(currentPage))
|
|
||||||
}
|
|
||||||
alt={`Page ${currentPage}`}
|
alt={`Page ${currentPage}`}
|
||||||
className={cn(
|
className={cn(
|
||||||
"max-h-full max-w-full cursor-pointer object-contain transition-opacity",
|
"max-h-full max-w-full cursor-pointer object-contain transition-opacity",
|
||||||
@@ -124,7 +132,7 @@ export function PageDisplay({
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
)}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Page 2 (double page) */}
|
{/* Page 2 (double page) */}
|
||||||
@@ -166,17 +174,12 @@ export function PageDisplay({
|
|||||||
</svg>
|
</svg>
|
||||||
<span className="text-sm opacity-60">Image non disponible</span>
|
<span className="text-sm opacity-60">Image non disponible</span>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : imageBlobUrls[currentPage + 1] ? (
|
||||||
<>
|
<>
|
||||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||||
<img
|
<img
|
||||||
key={`page-${currentPage + 1}-${imageBlobUrls[currentPage + 1] || ""}`}
|
key={`page-${currentPage + 1}-${imageBlobUrls[currentPage + 1]}`}
|
||||||
src={
|
src={imageBlobUrls[currentPage + 1]}
|
||||||
imageBlobUrls[currentPage + 1] ||
|
|
||||||
(isPageLoading && isPageLoading(currentPage + 1)
|
|
||||||
? undefined
|
|
||||||
: getPageUrl(currentPage + 1))
|
|
||||||
}
|
|
||||||
alt={`Page ${currentPage + 1}`}
|
alt={`Page ${currentPage + 1}`}
|
||||||
className={cn(
|
className={cn(
|
||||||
"max-h-full max-w-full cursor-pointer object-contain transition-opacity",
|
"max-h-full max-w-full cursor-pointer object-contain transition-opacity",
|
||||||
@@ -193,7 +196,7 @@ export function PageDisplay({
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
)}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user