feat: refactor PhotoswipeReader to enhance modularity with new components and hooks for improved navigation, image loading, and touch handling

This commit is contained in:
Julien Froidefond
2025-10-22 21:05:10 +02:00
parent 66fbf98d54
commit 0ba027b625
11 changed files with 724 additions and 558 deletions

View File

@@ -0,0 +1,117 @@
import { useState, useCallback, useRef, useEffect } from "react";
import { useRouter } from "next/navigation";
import { ClientOfflineBookService } from "@/lib/services/client-offlinebook.service";
import type { KomgaBook } from "@/types/komga";
interface UsePageNavigationProps {
book: KomgaBook;
pages: number[];
isDoublePage: boolean;
shouldShowDoublePage: (page: number) => boolean;
onClose?: (currentPage: number) => void;
nextBook?: KomgaBook | null;
}
export function usePageNavigation({
book,
pages,
isDoublePage,
shouldShowDoublePage,
onClose: _onClose,
nextBook,
}: UsePageNavigationProps) {
const router = useRouter();
const [currentPage, setCurrentPage] = useState(() => {
const saved = ClientOfflineBookService.getCurrentPage(book);
return saved < 1 ? 1 : saved;
});
const [showEndMessage, setShowEndMessage] = useState(false);
const syncTimeoutRef = useRef<NodeJS.Timeout | null>(null);
const currentPageRef = useRef(currentPage);
// Garder currentPage à jour dans la ref pour le cleanup
useEffect(() => {
currentPageRef.current = currentPage;
}, [currentPage]);
// Sync progress
const syncReadProgress = useCallback(
async (page: number) => {
try {
ClientOfflineBookService.setCurrentPage(book, page);
const completed = page === pages.length;
await fetch(`/api/komga/books/${book.id}/read-progress`, {
method: "PATCH",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ page, completed }),
});
} catch (error) {
console.error("Sync error:", error);
}
},
[book, pages.length]
);
const debouncedSync = useCallback(
(page: number) => {
if (syncTimeoutRef.current) {
clearTimeout(syncTimeoutRef.current);
}
syncTimeoutRef.current = setTimeout(() => syncReadProgress(page), 500);
},
[syncReadProgress]
);
const navigateToPage = useCallback(
(page: number) => {
if (page >= 1 && page <= pages.length) {
setCurrentPage(page);
// Mettre à jour le localStorage immédiatement
ClientOfflineBookService.setCurrentPage(book, page);
// Débouncer seulement l'API Komga
debouncedSync(page);
}
},
[pages.length, debouncedSync, book]
);
const handlePreviousPage = useCallback(() => {
if (currentPage === 1) return;
const step = isDoublePage && shouldShowDoublePage(currentPage - 2) ? 2 : 1;
navigateToPage(Math.max(1, currentPage - step));
}, [currentPage, isDoublePage, navigateToPage, shouldShowDoublePage]);
const handleNextPage = useCallback(() => {
if (currentPage === pages.length) {
if (nextBook) {
router.push(`/books/${nextBook.id}`);
return;
}
setShowEndMessage(true);
return;
}
const step = isDoublePage && shouldShowDoublePage(currentPage) ? 2 : 1;
navigateToPage(Math.min(pages.length, currentPage + step));
}, [currentPage, pages.length, isDoublePage, shouldShowDoublePage, navigateToPage, nextBook, router]);
// Cleanup - Sync final sans debounce
useEffect(() => {
return () => {
if (syncTimeoutRef.current) {
clearTimeout(syncTimeoutRef.current);
}
// Sync immédiatement au cleanup avec la VRAIE valeur actuelle
syncReadProgress(currentPageRef.current);
};
}, [syncReadProgress]);
return {
currentPage,
setCurrentPage,
showEndMessage,
setShowEndMessage,
navigateToPage,
handlePreviousPage,
handleNextPage,
};
}