refacto(thumbnails): revew thumbnails and caching
This commit is contained in:
@@ -2,7 +2,7 @@ import { ThumbnailProps } from "../types";
|
|||||||
import { ImageLoader } from "@/components/ui/image-loader";
|
import { ImageLoader } from "@/components/ui/image-loader";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import { forwardRef, useEffect, useState } from "react";
|
import { forwardRef, useEffect, useState, useCallback, useRef } from "react";
|
||||||
|
|
||||||
export const Thumbnail = forwardRef<HTMLButtonElement, ThumbnailProps>(
|
export const Thumbnail = forwardRef<HTMLButtonElement, ThumbnailProps>(
|
||||||
(
|
(
|
||||||
@@ -19,23 +19,60 @@ export const Thumbnail = forwardRef<HTMLButtonElement, ThumbnailProps>(
|
|||||||
) => {
|
) => {
|
||||||
const [imageUrl, setImageUrl] = useState<string | null>(null);
|
const [imageUrl, setImageUrl] = useState<string | null>(null);
|
||||||
const [isLoading, setIsLoading] = useState(true);
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
|
const [hasError, setHasError] = useState(false);
|
||||||
|
const loadAttempts = useRef(0);
|
||||||
|
const maxAttempts = 3;
|
||||||
|
|
||||||
|
const resetLoadingState = useCallback(() => {
|
||||||
|
setIsLoading(true);
|
||||||
|
setHasError(false);
|
||||||
|
loadAttempts.current = 0;
|
||||||
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isVisible) {
|
try {
|
||||||
const url = getThumbnailUrl(pageNumber);
|
const url = getThumbnailUrl(pageNumber);
|
||||||
setImageUrl(url);
|
setImageUrl(url);
|
||||||
|
// On réinitialise le loading uniquement si la miniature n'est pas déjà chargée
|
||||||
if (!loadedThumbnails[pageNumber]) {
|
if (!loadedThumbnails[pageNumber]) {
|
||||||
setIsLoading(true);
|
resetLoadingState();
|
||||||
}
|
} else {
|
||||||
}
|
|
||||||
}, [isVisible, pageNumber, getThumbnailUrl, loadedThumbnails]);
|
|
||||||
|
|
||||||
const handleImageLoad = () => {
|
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Erreur lors du chargement de la miniature ${pageNumber}:`, error);
|
||||||
|
setHasError(true);
|
||||||
|
setIsLoading(false);
|
||||||
|
}
|
||||||
|
}, [pageNumber, getThumbnailUrl, loadedThumbnails, resetLoadingState]);
|
||||||
|
|
||||||
|
const handleImageLoad = useCallback(() => {
|
||||||
|
setIsLoading(false);
|
||||||
|
setHasError(false);
|
||||||
if (!loadedThumbnails[pageNumber]) {
|
if (!loadedThumbnails[pageNumber]) {
|
||||||
onThumbnailLoad(pageNumber);
|
onThumbnailLoad(pageNumber);
|
||||||
}
|
}
|
||||||
};
|
}, [loadedThumbnails, pageNumber, onThumbnailLoad]);
|
||||||
|
|
||||||
|
const handleImageError = useCallback(() => {
|
||||||
|
loadAttempts.current += 1;
|
||||||
|
if (loadAttempts.current < maxAttempts) {
|
||||||
|
// Réessayer avec un délai croissant
|
||||||
|
const delay = Math.min(1000 * Math.pow(2, loadAttempts.current - 1), 5000);
|
||||||
|
console.log(
|
||||||
|
`Réessai ${loadAttempts.current}/${maxAttempts} dans ${delay}ms pour la page ${pageNumber}`
|
||||||
|
);
|
||||||
|
setTimeout(() => {
|
||||||
|
setImageUrl((prev) => (prev ? `${prev}?retry=${loadAttempts.current}` : null));
|
||||||
|
}, delay);
|
||||||
|
} else {
|
||||||
|
console.error(
|
||||||
|
`Échec du chargement de l'image pour la page ${pageNumber} après ${maxAttempts} tentatives`
|
||||||
|
);
|
||||||
|
setHasError(true);
|
||||||
|
setIsLoading(false);
|
||||||
|
}
|
||||||
|
}, [pageNumber]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
@@ -43,14 +80,16 @@ export const Thumbnail = forwardRef<HTMLButtonElement, ThumbnailProps>(
|
|||||||
data-page={pageNumber}
|
data-page={pageNumber}
|
||||||
id={`thumbnail-${pageNumber}`}
|
id={`thumbnail-${pageNumber}`}
|
||||||
onClick={() => onPageChange(pageNumber)}
|
onClick={() => onPageChange(pageNumber)}
|
||||||
className={`relative h-56 w-40 flex-shrink-0 rounded-md overflow-hidden transition-all cursor-pointer snap-center ${
|
className={cn(
|
||||||
|
"relative h-56 w-40 flex-shrink-0 rounded-md overflow-hidden transition-all cursor-pointer snap-center",
|
||||||
currentPage === pageNumber
|
currentPage === pageNumber
|
||||||
? "ring-2 ring-primary scale-110 z-10"
|
? "ring-2 ring-primary scale-110 z-10"
|
||||||
: "opacity-80 hover:opacity-100 hover:scale-105"
|
: "opacity-80 hover:opacity-100 hover:scale-105",
|
||||||
}`}
|
hasError && "bg-muted"
|
||||||
|
)}
|
||||||
>
|
>
|
||||||
<ImageLoader isLoading={isLoading} />
|
<ImageLoader isLoading={isLoading} />
|
||||||
{isVisible && imageUrl && (
|
{imageUrl && !hasError && (
|
||||||
<Image
|
<Image
|
||||||
src={imageUrl}
|
src={imageUrl}
|
||||||
alt={`Miniature page ${pageNumber}`}
|
alt={`Miniature page ${pageNumber}`}
|
||||||
@@ -63,8 +102,14 @@ export const Thumbnail = forwardRef<HTMLButtonElement, ThumbnailProps>(
|
|||||||
loading="lazy"
|
loading="lazy"
|
||||||
quality={50}
|
quality={50}
|
||||||
onLoad={handleImageLoad}
|
onLoad={handleImageLoad}
|
||||||
|
onError={handleImageError}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
{hasError && (
|
||||||
|
<div className="absolute inset-0 flex items-center justify-center bg-muted">
|
||||||
|
<span className="text-sm text-muted-foreground">Erreur</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<div className="absolute bottom-0 inset-x-0 h-8 bg-gradient-to-t from-black/60 to-transparent flex items-center justify-center">
|
<div className="absolute bottom-0 inset-x-0 h-8 bg-gradient-to-t from-black/60 to-transparent flex items-center justify-center">
|
||||||
<span className="text-sm text-white font-medium">{pageNumber}</span>
|
<span className="text-sm text-white font-medium">{pageNumber}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ class ServerCacheService {
|
|||||||
private static readonly tenMinutes = 10 * 60;
|
private static readonly tenMinutes = 10 * 60;
|
||||||
private static readonly twentyFourHours = 24 * 60 * 60;
|
private static readonly twentyFourHours = 24 * 60 * 60;
|
||||||
private static readonly oneMinute = 1 * 60;
|
private static readonly oneMinute = 1 * 60;
|
||||||
|
private static readonly oneWeek = 7 * 24 * 60 * 60;
|
||||||
private static readonly noCache = 0;
|
private static readonly noCache = 0;
|
||||||
|
|
||||||
// Configuration des temps de cache en secondes
|
// Configuration des temps de cache en secondes
|
||||||
@@ -21,7 +22,7 @@ class ServerCacheService {
|
|||||||
LIBRARIES: ServerCacheService.twentyFourHours,
|
LIBRARIES: ServerCacheService.twentyFourHours,
|
||||||
SERIES: ServerCacheService.fiveMinutes,
|
SERIES: ServerCacheService.fiveMinutes,
|
||||||
BOOKS: ServerCacheService.fiveMinutes,
|
BOOKS: ServerCacheService.fiveMinutes,
|
||||||
IMAGES: ServerCacheService.twentyFourHours,
|
IMAGES: ServerCacheService.oneWeek,
|
||||||
};
|
};
|
||||||
|
|
||||||
private constructor() {
|
private constructor() {
|
||||||
|
|||||||
Reference in New Issue
Block a user