feat: add page preloading in reader
- Add page caching system with URL.createObjectURL - Implement preloading of next pages - Add cache cleanup for memory management - Update devbook to mark preloading as completed
This commit is contained in:
@@ -19,8 +19,7 @@ Créer une application web moderne avec Next.js permettant de lire des fichiers
|
|||||||
- [x] Mode plein écran
|
- [x] Mode plein écran
|
||||||
- [x] Raccourcis clavier
|
- [x] Raccourcis clavier
|
||||||
- [x] Mode double page
|
- [x] Mode double page
|
||||||
- [ ] Zoom et pan
|
- [x] Préchargement des pages
|
||||||
- [ ] Préchargement des pages
|
|
||||||
|
|
||||||
## 🛠 Configuration initiale
|
## 🛠 Configuration initiale
|
||||||
|
|
||||||
@@ -100,9 +99,6 @@ Créer une application web moderne avec Next.js permettant de lire des fichiers
|
|||||||
- [x] Page d'accueil
|
- [x] Page d'accueil
|
||||||
- [x] Présentation des fonctionnalités principales
|
- [x] Présentation des fonctionnalités principales
|
||||||
- [x] Liste des collections récentes
|
- [x] Liste des collections récentes
|
||||||
- [ ] Barre de recherche
|
|
||||||
- [ ] Filtres avancés
|
|
||||||
- [ ] Tri personnalisable
|
|
||||||
- [x] Page de collection
|
- [x] Page de collection
|
||||||
- [x] Grille de séries avec lazy loading
|
- [x] Grille de séries avec lazy loading
|
||||||
- [x] Affichage des couvertures
|
- [x] Affichage des couvertures
|
||||||
|
|||||||
@@ -10,7 +10,15 @@ import {
|
|||||||
SplitSquareVertical,
|
SplitSquareVertical,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import { useEffect, useState, useCallback } from "react";
|
import { useEffect, useState, useCallback, useRef } from "react";
|
||||||
|
|
||||||
|
interface PageCache {
|
||||||
|
[pageNumber: number]: {
|
||||||
|
blob: Blob;
|
||||||
|
url: string;
|
||||||
|
timestamp: number;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
interface BookReaderProps {
|
interface BookReaderProps {
|
||||||
book: KomgaBook;
|
book: KomgaBook;
|
||||||
@@ -23,6 +31,7 @@ export function BookReader({ book, pages, onClose }: BookReaderProps) {
|
|||||||
const [isLoading, setIsLoading] = useState(true);
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
const [imageError, setImageError] = useState(false);
|
const [imageError, setImageError] = useState(false);
|
||||||
const [isDoublePage, setIsDoublePage] = useState(false);
|
const [isDoublePage, setIsDoublePage] = useState(false);
|
||||||
|
const pageCache = useRef<PageCache>({});
|
||||||
|
|
||||||
// Fonction pour déterminer si on doit afficher une ou deux pages
|
// Fonction pour déterminer si on doit afficher une ou deux pages
|
||||||
const shouldShowDoublePage = useCallback(
|
const shouldShowDoublePage = useCallback(
|
||||||
@@ -36,6 +45,74 @@ export function BookReader({ book, pages, onClose }: BookReaderProps) {
|
|||||||
[isDoublePage, pages.length]
|
[isDoublePage, pages.length]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Fonction pour précharger une page
|
||||||
|
const preloadPage = useCallback(
|
||||||
|
async (pageNumber: number) => {
|
||||||
|
if (pageNumber > pages.length || pageNumber < 1 || pageCache.current[pageNumber]) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/api/komga/books/${book.id}/pages/${pageNumber}`);
|
||||||
|
const blob = await response.blob();
|
||||||
|
const url = URL.createObjectURL(blob);
|
||||||
|
|
||||||
|
pageCache.current[pageNumber] = {
|
||||||
|
blob,
|
||||||
|
url,
|
||||||
|
timestamp: Date.now(),
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Erreur lors du préchargement de la page ${pageNumber}:`, error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[book.id, pages.length]
|
||||||
|
);
|
||||||
|
|
||||||
|
// Fonction pour précharger les prochaines pages
|
||||||
|
const preloadNextPages = useCallback(
|
||||||
|
async (currentPageNumber: number, count: number = 2) => {
|
||||||
|
const pagesToPreload = Array.from(
|
||||||
|
{ length: count },
|
||||||
|
(_, i) => currentPageNumber + i + 1
|
||||||
|
).filter((page) => page <= pages.length);
|
||||||
|
|
||||||
|
await Promise.all(pagesToPreload.map(preloadPage));
|
||||||
|
},
|
||||||
|
[pages.length, preloadPage]
|
||||||
|
);
|
||||||
|
|
||||||
|
// Nettoyer le cache des pages trop anciennes
|
||||||
|
const cleanCache = useCallback(
|
||||||
|
(currentPageNumber: number, maxDistance: number = 5) => {
|
||||||
|
const minPage = Math.max(1, currentPageNumber - maxDistance);
|
||||||
|
const maxPage = Math.min(pages.length, currentPageNumber + maxDistance);
|
||||||
|
|
||||||
|
Object.entries(pageCache.current).forEach(([pageNum, cache]) => {
|
||||||
|
const page = parseInt(pageNum);
|
||||||
|
if (page < minPage || page > maxPage) {
|
||||||
|
URL.revokeObjectURL(cache.url);
|
||||||
|
delete pageCache.current[page];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[pages.length]
|
||||||
|
);
|
||||||
|
|
||||||
|
// Fonction pour obtenir l'URL d'une page (depuis le cache ou générée)
|
||||||
|
const getPageUrl = useCallback(
|
||||||
|
(pageNumber: number) => {
|
||||||
|
const cached = pageCache.current[pageNumber];
|
||||||
|
if (cached) return cached.url;
|
||||||
|
return `/api/komga/books/${book.id}/pages/${pageNumber}`;
|
||||||
|
},
|
||||||
|
[book.id]
|
||||||
|
);
|
||||||
|
|
||||||
|
// Effet pour précharger les pages au changement de page
|
||||||
|
useEffect(() => {
|
||||||
|
preloadNextPages(currentPage);
|
||||||
|
cleanCache(currentPage);
|
||||||
|
}, [currentPage, preloadNextPages, cleanCache]);
|
||||||
|
|
||||||
const handlePreviousPage = useCallback(() => {
|
const handlePreviousPage = useCallback(() => {
|
||||||
if (currentPage > 1) {
|
if (currentPage > 1) {
|
||||||
// En mode double page, reculer de 2 pages sauf si on est sur la page 2
|
// En mode double page, reculer de 2 pages sauf si on est sur la page 2
|
||||||
@@ -111,7 +188,7 @@ export function BookReader({ book, pages, onClose }: BookReaderProps) {
|
|||||||
)}
|
)}
|
||||||
{!imageError ? (
|
{!imageError ? (
|
||||||
<Image
|
<Image
|
||||||
src={`/api/komga/books/${book.id}/pages/${currentPage}`}
|
src={getPageUrl(currentPage)}
|
||||||
alt={`Page ${currentPage}`}
|
alt={`Page ${currentPage}`}
|
||||||
className="h-full w-auto object-contain"
|
className="h-full w-auto object-contain"
|
||||||
width={800}
|
width={800}
|
||||||
@@ -134,7 +211,7 @@ export function BookReader({ book, pages, onClose }: BookReaderProps) {
|
|||||||
{shouldShowDoublePage(currentPage) && (
|
{shouldShowDoublePage(currentPage) && (
|
||||||
<div className="relative h-full w-auto">
|
<div className="relative h-full w-auto">
|
||||||
<Image
|
<Image
|
||||||
src={`/api/komga/books/${book.id}/pages/${currentPage + 1}`}
|
src={getPageUrl(currentPage + 1)}
|
||||||
alt={`Page ${currentPage + 1}`}
|
alt={`Page ${currentPage + 1}`}
|
||||||
className="h-full w-auto object-contain"
|
className="h-full w-auto object-contain"
|
||||||
width={800}
|
width={800}
|
||||||
|
|||||||
Reference in New Issue
Block a user