From 48472eaaf7b27781fc91c96e97d7945654faadcf Mon Sep 17 00:00:00 2001 From: Julien Froidefond Date: Sun, 16 Feb 2025 15:13:04 +0100 Subject: [PATCH] refacto(bookreader): refacto read progress call in debounce --- src/components/reader/BookReader.tsx | 14 +-- .../reader/hooks/usePageNavigation.ts | 105 ++++++++++++++---- 2 files changed, 83 insertions(+), 36 deletions(-) diff --git a/src/components/reader/BookReader.tsx b/src/components/reader/BookReader.tsx index 9f7914c..4ed6136 100644 --- a/src/components/reader/BookReader.tsx +++ b/src/components/reader/BookReader.tsx @@ -17,7 +17,7 @@ export function BookReader({ book, pages, onClose }: BookReaderProps) { const { currentPage, - setCurrentPage, + navigateToPage, isLoading, setIsLoading, secondPageLoading, @@ -134,16 +134,6 @@ export function BookReader({ book, pages, onClose }: BookReaderProps) { [currentPage] ); - const handlePageChange = useCallback( - (page: number) => { - setCurrentPage(page); - setIsLoading(true); - setImageError(false); - syncReadProgress(page); - }, - [setCurrentPage, setIsLoading, setImageError, syncReadProgress] - ); - return (
diff --git a/src/components/reader/hooks/usePageNavigation.ts b/src/components/reader/hooks/usePageNavigation.ts index 095f38d..b8a018a 100644 --- a/src/components/reader/hooks/usePageNavigation.ts +++ b/src/components/reader/hooks/usePageNavigation.ts @@ -1,4 +1,4 @@ -import { useState, useCallback, useEffect } from "react"; +import { useState, useCallback, useEffect, useRef } from "react"; import { KomgaBook } from "@/types/komga"; interface UsePageNavigationProps { @@ -12,6 +12,13 @@ export const usePageNavigation = ({ book, pages, isDoublePage }: UsePageNavigati const [isLoading, setIsLoading] = useState(true); const [secondPageLoading, setSecondPageLoading] = useState(true); const [imageError, setImageError] = useState(false); + const timeoutRef = useRef(null); + const touchStartXRef = useRef(null); + const currentPageRef = useRef(currentPage); + + useEffect(() => { + currentPageRef.current = currentPage; + }, [currentPage]); const syncReadProgress = useCallback( async (page: number) => { @@ -31,6 +38,20 @@ export const usePageNavigation = ({ book, pages, isDoublePage }: UsePageNavigati [book.id, pages.length] ); + const debouncedSyncReadProgress = useCallback( + (page: number) => { + if (timeoutRef.current) { + clearTimeout(timeoutRef.current); + } + + timeoutRef.current = setTimeout(() => { + syncReadProgress(page); + timeoutRef.current = null; + }, 2000); + }, + [syncReadProgress] + ); + const shouldShowDoublePage = useCallback( (pageNumber: number) => { if (!isDoublePage) return false; @@ -40,35 +61,55 @@ export const usePageNavigation = ({ book, pages, isDoublePage }: UsePageNavigati [isDoublePage, pages.length] ); - const handlePreviousPage = useCallback(() => { - if (currentPage > 1) { - const newPage = isDoublePage && currentPage > 2 ? currentPage - 2 : currentPage - 1; - setCurrentPage(newPage); + const navigateToPage = useCallback( + (page: number) => { + setCurrentPage(page); setIsLoading(true); setSecondPageLoading(true); setImageError(false); + debouncedSyncReadProgress(page); + }, + [debouncedSyncReadProgress] + ); - const timer = setTimeout(() => { - syncReadProgress(newPage); - }, 300); - return () => clearTimeout(timer); + const handlePreviousPage = useCallback(() => { + if (currentPage > 1) { + const newPage = isDoublePage && currentPage > 2 ? currentPage - 2 : currentPage - 1; + navigateToPage(newPage); } - }, [currentPage, isDoublePage, syncReadProgress]); + }, [currentPage, isDoublePage, navigateToPage]); const handleNextPage = useCallback(() => { if (currentPage < pages.length) { const newPage = isDoublePage ? Math.min(currentPage + 2, pages.length) : currentPage + 1; - setCurrentPage(newPage); - setIsLoading(true); - setSecondPageLoading(true); - setImageError(false); - - const timer = setTimeout(() => { - syncReadProgress(newPage); - }, 300); - return () => clearTimeout(timer); + navigateToPage(newPage); } - }, [currentPage, pages.length, isDoublePage, syncReadProgress]); + }, [currentPage, pages.length, isDoublePage, navigateToPage]); + + const handleTouchStart = useCallback((event: TouchEvent) => { + touchStartXRef.current = event.touches[0].clientX; + }, []); + + const handleTouchEnd = useCallback( + (event: TouchEvent) => { + if (touchStartXRef.current === null) return; + + const touchEndX = event.changedTouches[0].clientX; + const deltaX = touchEndX - touchStartXRef.current; + const minSwipeDistance = 50; + + if (Math.abs(deltaX) > minSwipeDistance) { + if (deltaX > 0) { + handlePreviousPage(); + } else { + handleNextPage(); + } + } + + touchStartXRef.current = null; + }, + [handlePreviousPage, handleNextPage] + ); useEffect(() => { setIsLoading(true); @@ -85,12 +126,28 @@ export const usePageNavigation = ({ book, pages, isDoublePage }: UsePageNavigati }; window.addEventListener("keydown", handleKeyDown); - return () => window.removeEventListener("keydown", handleKeyDown); - }, [handlePreviousPage, handleNextPage]); + window.addEventListener("touchstart", handleTouchStart); + window.addEventListener("touchend", handleTouchEnd); + + return () => { + window.removeEventListener("keydown", handleKeyDown); + window.removeEventListener("touchstart", handleTouchStart); + window.removeEventListener("touchend", handleTouchEnd); + }; + }, [handlePreviousPage, handleNextPage, handleTouchStart, handleTouchEnd]); + + useEffect(() => { + return () => { + if (timeoutRef.current) { + clearTimeout(timeoutRef.current); + syncReadProgress(currentPageRef.current); + } + }; + }, [syncReadProgress]); return { currentPage, - setCurrentPage, + navigateToPage, isLoading, setIsLoading, secondPageLoading, @@ -100,6 +157,6 @@ export const usePageNavigation = ({ book, pages, isDoublePage }: UsePageNavigati handlePreviousPage, handleNextPage, shouldShowDoublePage, - syncReadProgress, + syncReadProgress: debouncedSyncReadProgress, }; };