refacto : bookreader bump on images
This commit is contained in:
@@ -20,6 +20,7 @@ interface PageCache {
|
|||||||
blob: Blob;
|
blob: Blob;
|
||||||
url: string;
|
url: string;
|
||||||
timestamp: number;
|
timestamp: number;
|
||||||
|
loading?: Promise<void>;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -151,46 +152,118 @@ export function BookReader({ book, pages, onClose }: BookReaderProps) {
|
|||||||
// Fonction pour précharger une page
|
// Fonction pour précharger une page
|
||||||
const preloadPage = useCallback(
|
const preloadPage = useCallback(
|
||||||
async (pageNumber: number) => {
|
async (pageNumber: number) => {
|
||||||
if (pageNumber > pages.length || pageNumber < 1 || pageCache.current[pageNumber]) return;
|
if (pageNumber > pages.length || pageNumber < 1) return;
|
||||||
|
|
||||||
|
// Si la page est déjà en cache, on ne fait rien
|
||||||
|
if (pageCache.current[pageNumber]?.url) return;
|
||||||
|
|
||||||
|
// Si la page est en cours de chargement, on attend
|
||||||
|
if (pageCache.current[pageNumber]?.loading) {
|
||||||
|
await pageCache.current[pageNumber].loading;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// On crée une promesse pour le chargement
|
||||||
|
let resolveLoading: () => void;
|
||||||
|
const loadingPromise = new Promise<void>((resolve) => {
|
||||||
|
resolveLoading = resolve;
|
||||||
|
});
|
||||||
|
|
||||||
|
// On initialise l'entrée dans le cache avec la promesse de chargement
|
||||||
|
pageCache.current[pageNumber] = {
|
||||||
|
...pageCache.current[pageNumber],
|
||||||
|
loading: loadingPromise,
|
||||||
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`/api/komga/books/${book.id}/pages/${pageNumber}`);
|
const response = await fetch(`/api/komga/books/${book.id}/pages/${pageNumber}`);
|
||||||
const blob = await response.blob();
|
const blob = await response.blob();
|
||||||
const url = URL.createObjectURL(blob);
|
const url = URL.createObjectURL(blob);
|
||||||
|
|
||||||
console.log("PRELOAD", pageNumber);
|
|
||||||
pageCache.current[pageNumber] = {
|
pageCache.current[pageNumber] = {
|
||||||
blob,
|
blob,
|
||||||
url,
|
url,
|
||||||
timestamp: Date.now(),
|
timestamp: Date.now(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
resolveLoading!();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Erreur lors du préchargement de la page ${pageNumber}:`, error);
|
console.error(`Erreur lors du préchargement de la page ${pageNumber}:`, error);
|
||||||
|
delete pageCache.current[pageNumber];
|
||||||
|
resolveLoading!();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[book.id, pages.length]
|
[book.id, pages.length]
|
||||||
);
|
);
|
||||||
|
|
||||||
// Fonction pour précharger les prochaines pages
|
// Fonction pour obtenir l'URL d'une page
|
||||||
const preloadNextPages = useCallback(
|
const getPageUrl = useCallback(
|
||||||
async (currentPageNumber: number) => {
|
async (pageNumber: number) => {
|
||||||
// Préchargement des pages suivantes
|
// Si la page est dans le cache, utiliser l'URL du cache
|
||||||
const nextPages = Array.from({ length: 4 }, (_, i) => currentPageNumber + i + 1).filter(
|
if (pageCache.current[pageNumber]?.url) {
|
||||||
(page) => page <= pages.length
|
return pageCache.current[pageNumber].url;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Si la page est en cours de chargement, attendre
|
||||||
|
if (pageCache.current[pageNumber]?.loading) {
|
||||||
|
await pageCache.current[pageNumber].loading;
|
||||||
|
return pageCache.current[pageNumber].url;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sinon, lancer le préchargement et attendre
|
||||||
|
await preloadPage(pageNumber);
|
||||||
|
return (
|
||||||
|
pageCache.current[pageNumber]?.url ||
|
||||||
|
`/api/komga/images/books/${book.id}/pages/${pageNumber}`
|
||||||
);
|
);
|
||||||
|
|
||||||
// Préchargement des pages précédentes
|
|
||||||
const previousPages = Array.from({ length: 2 }, (_, i) => currentPageNumber - i - 1).filter(
|
|
||||||
(page) => page >= 1
|
|
||||||
);
|
|
||||||
|
|
||||||
// Combiner les pages à précharger
|
|
||||||
const pagesToPreload = [...nextPages, ...previousPages];
|
|
||||||
|
|
||||||
// Précharger toutes les pages en parallèle
|
|
||||||
await Promise.all(pagesToPreload.map(preloadPage));
|
|
||||||
},
|
},
|
||||||
[pages.length, preloadPage]
|
[book.id, preloadPage]
|
||||||
|
);
|
||||||
|
|
||||||
|
// É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);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
setIsLoading(true);
|
||||||
|
setSecondPageLoading(true);
|
||||||
|
loadPageUrls();
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
isMounted = false;
|
||||||
|
};
|
||||||
|
}, [currentPage, isDoublePage, shouldShowDoublePage, getPageUrl]);
|
||||||
|
|
||||||
|
// Fonction pour obtenir l'URL d'une miniature
|
||||||
|
const getThumbnailUrl = useCallback(
|
||||||
|
(pageNumber: number) => {
|
||||||
|
return `/api/komga/images/books/${book.id}/pages/${pageNumber}/thumbnail`;
|
||||||
|
},
|
||||||
|
[book.id]
|
||||||
);
|
);
|
||||||
|
|
||||||
// Nettoyer le cache des pages trop anciennes
|
// Nettoyer le cache des pages trop anciennes
|
||||||
@@ -211,53 +284,43 @@ export function BookReader({ book, pages, onClose }: BookReaderProps) {
|
|||||||
[pages.length]
|
[pages.length]
|
||||||
);
|
);
|
||||||
|
|
||||||
// Fonction pour obtenir l'URL d'une page
|
|
||||||
const getPageUrl = useCallback(
|
|
||||||
(pageNumber: number) => {
|
|
||||||
// Si la page est dans le cache, utiliser l'URL du cache
|
|
||||||
if (pageCache.current[pageNumber]) {
|
|
||||||
return pageCache.current[pageNumber].url;
|
|
||||||
}
|
|
||||||
// Sinon, retourner l'URL de l'API et lancer le préchargement en arrière-plan
|
|
||||||
preloadPage(pageNumber).catch(console.error);
|
|
||||||
return `/api/komga/images/books/${book.id}/pages/${pageNumber}`;
|
|
||||||
},
|
|
||||||
[book.id, preloadPage]
|
|
||||||
);
|
|
||||||
|
|
||||||
// Fonction pour obtenir l'URL d'une miniature
|
|
||||||
const getThumbnailUrl = useCallback(
|
|
||||||
(pageNumber: number) => {
|
|
||||||
return `/api/komga/images/books/${book.id}/pages/${pageNumber}/thumbnail`;
|
|
||||||
},
|
|
||||||
[book.id]
|
|
||||||
);
|
|
||||||
|
|
||||||
// Effet pour précharger la page courante et les pages adjacentes
|
// Effet pour précharger la page courante et les pages adjacentes
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let isMounted = true;
|
let isMounted = true;
|
||||||
|
|
||||||
const preloadCurrentPages = async () => {
|
const preloadCurrentPages = async () => {
|
||||||
// Précharger la page courante si elle n'est pas dans le cache
|
if (!isMounted) return;
|
||||||
if (!pageCache.current[currentPage]) {
|
|
||||||
await preloadPage(currentPage);
|
// Précharger la page courante
|
||||||
}
|
await preloadPage(currentPage);
|
||||||
|
|
||||||
if (!isMounted) return;
|
if (!isMounted) return;
|
||||||
|
|
||||||
// En mode double page, précharger aussi la page suivante si nécessaire
|
// En mode double page, précharger la page suivante si nécessaire
|
||||||
if (
|
if (isDoublePage && shouldShowDoublePage(currentPage)) {
|
||||||
isDoublePage &&
|
|
||||||
shouldShowDoublePage(currentPage) &&
|
|
||||||
!pageCache.current[currentPage + 1]
|
|
||||||
) {
|
|
||||||
await preloadPage(currentPage + 1);
|
await preloadPage(currentPage + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isMounted) return;
|
if (!isMounted) return;
|
||||||
|
|
||||||
// Lancer le préchargement des pages suivantes et précédentes en arrière-plan
|
// Précharger les pages suivantes et précédentes
|
||||||
preloadNextPages(currentPage).catch(console.error);
|
const pagesToPreload = [];
|
||||||
|
|
||||||
|
// Pages suivantes (max 4)
|
||||||
|
for (let i = 1; i <= 4 && currentPage + i <= pages.length; i++) {
|
||||||
|
pagesToPreload.push(currentPage + i);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pages précédentes (max 2)
|
||||||
|
for (let i = 1; i <= 2 && currentPage - i >= 1; i++) {
|
||||||
|
pagesToPreload.push(currentPage - i);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Précharger en séquence pour éviter de surcharger
|
||||||
|
for (const page of pagesToPreload) {
|
||||||
|
if (!isMounted) break;
|
||||||
|
await preloadPage(page);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
preloadCurrentPages();
|
preloadCurrentPages();
|
||||||
@@ -266,15 +329,7 @@ export function BookReader({ book, pages, onClose }: BookReaderProps) {
|
|||||||
return () => {
|
return () => {
|
||||||
isMounted = false;
|
isMounted = false;
|
||||||
};
|
};
|
||||||
}, [
|
}, [currentPage, isDoublePage, shouldShowDoublePage, preloadPage, cleanCache, pages.length]);
|
||||||
currentPage,
|
|
||||||
isDoublePage,
|
|
||||||
shouldShowDoublePage,
|
|
||||||
preloadPage,
|
|
||||||
preloadNextPages,
|
|
||||||
cleanCache,
|
|
||||||
pages.length,
|
|
||||||
]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleKeyDown = (event: KeyboardEvent) => {
|
const handleKeyDown = (event: KeyboardEvent) => {
|
||||||
@@ -486,36 +541,40 @@ export function BookReader({ book, pages, onClose }: BookReaderProps) {
|
|||||||
{/* Page courante */}
|
{/* Page courante */}
|
||||||
<div className="relative max-h-[calc(100vh-2rem)] flex items-center justify-center">
|
<div className="relative max-h-[calc(100vh-2rem)] flex items-center justify-center">
|
||||||
<ImageLoader isLoading={isLoading} />
|
<ImageLoader isLoading={isLoading} />
|
||||||
<img
|
{currentPageUrl && (
|
||||||
src={getPageUrl(currentPage)}
|
<img
|
||||||
alt={`Page ${currentPage}`}
|
src={currentPageUrl}
|
||||||
className={cn(
|
alt={`Page ${currentPage}`}
|
||||||
"max-h-[calc(100vh-2rem)] w-auto object-contain transition-opacity duration-300",
|
className={cn(
|
||||||
isLoading ? "opacity-0" : "opacity-100"
|
"max-h-[calc(100vh-2rem)] w-auto object-contain transition-opacity duration-300",
|
||||||
)}
|
isLoading ? "opacity-0" : "opacity-100"
|
||||||
onLoad={() => {
|
)}
|
||||||
setIsLoading(false);
|
onLoad={() => {
|
||||||
handleThumbnailLoad(currentPage);
|
setIsLoading(false);
|
||||||
}}
|
handleThumbnailLoad(currentPage);
|
||||||
/>
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Deuxième page en mode double page */}
|
{/* Deuxième page en mode double page */}
|
||||||
{isDoublePage && shouldShowDoublePage(currentPage) && (
|
{isDoublePage && shouldShowDoublePage(currentPage) && (
|
||||||
<div className="relative max-h-[calc(100vh-2rem)] flex items-center justify-center">
|
<div className="relative max-h-[calc(100vh-2rem)] flex items-center justify-center">
|
||||||
<ImageLoader isLoading={secondPageLoading} />
|
<ImageLoader isLoading={secondPageLoading} />
|
||||||
<img
|
{nextPageUrl && (
|
||||||
src={getPageUrl(currentPage + 1)}
|
<img
|
||||||
alt={`Page ${currentPage + 1}`}
|
src={nextPageUrl}
|
||||||
className={cn(
|
alt={`Page ${currentPage + 1}`}
|
||||||
"max-h-[calc(100vh-2rem)] w-auto object-contain transition-opacity duration-300",
|
className={cn(
|
||||||
secondPageLoading ? "opacity-0" : "opacity-100"
|
"max-h-[calc(100vh-2rem)] w-auto object-contain transition-opacity duration-300",
|
||||||
)}
|
secondPageLoading ? "opacity-0" : "opacity-100"
|
||||||
onLoad={() => {
|
)}
|
||||||
setSecondPageLoading(false);
|
onLoad={() => {
|
||||||
handleThumbnailLoad(currentPage + 1);
|
setSecondPageLoading(false);
|
||||||
}}
|
handleThumbnailLoad(currentPage + 1);
|
||||||
/>
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user