From 66f467c66bb6dd8a51663d0717ca0b819523c660 Mon Sep 17 00:00:00 2001 From: Julien Froidefond Date: Fri, 7 Mar 2025 08:15:34 +0100 Subject: [PATCH] feat: nextbook on next page if last page --- src/app/books/[bookId]/page.tsx | 4 +-- src/components/reader/BookReader.tsx | 21 ++++++++++++++- src/components/reader/ClientBookWrapper.tsx | 8 +++--- .../reader/hooks/usePageNavigation.ts | 26 +++++++++++++++++-- src/components/reader/types.ts | 1 + src/i18n/messages/en/common.json | 5 +++- src/i18n/messages/fr/common.json | 5 +++- src/lib/services/book.service.ts | 6 +++++ 8 files changed, 66 insertions(+), 10 deletions(-) diff --git a/src/app/books/[bookId]/page.tsx b/src/app/books/[bookId]/page.tsx index 5dcfe7b..c05cfca 100644 --- a/src/app/books/[bookId]/page.tsx +++ b/src/app/books/[bookId]/page.tsx @@ -11,10 +11,10 @@ import { AppError } from "@/utils/errors"; async function BookPage({ params }: { params: { bookId: string } }) { try { const data: KomgaBookWithPages = await BookService.getBook(params.bookId); - + const nextBook = await BookService.getNextBook(params.bookId, data.book.seriesId); return ( }> - + ); } catch (error) { diff --git a/src/components/reader/BookReader.tsx b/src/components/reader/BookReader.tsx index 8d03362..9920570 100644 --- a/src/components/reader/BookReader.tsx +++ b/src/components/reader/BookReader.tsx @@ -13,14 +13,16 @@ import { NavigationBar } from "./components/NavigationBar"; import { ControlButtons } from "./components/ControlButtons"; import { ReaderContent } from "./components/ReaderContent"; import { useReadingDirection } from "./hooks/useReadingDirection"; +import { useTranslate } from "@/hooks/useTranslate"; -export function BookReader({ book, pages, onClose }: BookReaderProps) { +export function BookReader({ book, pages, onClose, nextBook }: BookReaderProps) { const [isDoublePage, setIsDoublePage] = useState(false); const [showControls, setShowControls] = useState(false); const readerRef = useRef(null); const isLandscape = useOrientation(); const { direction, toggleDirection, isRTL } = useReadingDirection(); const { isFullscreen, toggleFullscreen } = useFullscreen(); + const { t } = useTranslate(); const { currentPage, @@ -35,12 +37,14 @@ export function BookReader({ book, pages, onClose }: BookReaderProps) { zoomLevel, panPosition, handleDoubleClick, + showEndMessage, } = usePageNavigation({ book, pages, isDoublePage, onClose, direction, + nextBook, }); const { preloadPage, getPageUrl, cleanCache } = usePageCache({ @@ -92,6 +96,21 @@ export function BookReader({ book, pages, onClose }: BookReaderProps) { onClick={() => setShowControls(!showControls)} >
+ {showEndMessage && ( +
+
+

{t("reader.endOfSeries")}

+

{t("reader.endOfSeriesMessage")}

+ +
+
+ )} + setShowControls(!showControls)} diff --git a/src/components/reader/ClientBookWrapper.tsx b/src/components/reader/ClientBookWrapper.tsx index ce8125b..11e1b06 100644 --- a/src/components/reader/ClientBookWrapper.tsx +++ b/src/components/reader/ClientBookWrapper.tsx @@ -8,9 +8,10 @@ import { ClientOfflineBookService } from "@/lib/services/client-offlinebook.serv interface ClientBookWrapperProps { book: KomgaBook; pages: number[]; + nextBook: KomgaBook | null; } -export function ClientBookWrapper({ book, pages }: ClientBookWrapperProps) { +export function ClientBookWrapper({ book, pages, nextBook }: ClientBookWrapperProps) { const router = useRouter(); const handleCloseReader = (currentPage: number) => { @@ -18,8 +19,9 @@ export function ClientBookWrapper({ book, pages }: ClientBookWrapperProps) { method: "POST", }); ClientOfflineBookService.setCurrentPage(book, currentPage); - router.back(); + router.push(`/series/${book.seriesId}`); + //router.back(); }; - return ; + return ; } diff --git a/src/components/reader/hooks/usePageNavigation.ts b/src/components/reader/hooks/usePageNavigation.ts index 465b31c..b3419e7 100644 --- a/src/components/reader/hooks/usePageNavigation.ts +++ b/src/components/reader/hooks/usePageNavigation.ts @@ -1,6 +1,7 @@ import { useState, useCallback, useEffect, useRef } from "react"; import type { KomgaBook } from "@/types/komga"; import { ClientOfflineBookService } from "@/lib/services/client-offlinebook.service"; +import { useRouter } from "next/navigation"; interface UsePageNavigationProps { book: KomgaBook; @@ -8,6 +9,7 @@ interface UsePageNavigationProps { isDoublePage: boolean; onClose?: (currentPage: number) => void; direction: "ltr" | "rtl"; + nextBook?: KomgaBook | null; } export const usePageNavigation = ({ @@ -16,13 +18,16 @@ export const usePageNavigation = ({ isDoublePage, onClose, direction, + nextBook, }: UsePageNavigationProps) => { + const router = useRouter(); const cPage = ClientOfflineBookService.getCurrentPage(book); const [currentPage, setCurrentPage] = useState(cPage < 1 ? 1 : cPage); const [isLoading, setIsLoading] = useState(true); const [secondPageLoading, setSecondPageLoading] = useState(true); const [zoomLevel, setZoomLevel] = useState(1); const [panPosition, setPanPosition] = useState({ x: 0, y: 0 }); + const [showEndMessage, setShowEndMessage] = useState(false); const timeoutRef = useRef(null); const touchStartXRef = useRef(null); const touchStartYRef = useRef(null); @@ -107,13 +112,29 @@ export const usePageNavigation = ({ }, [currentPage, isDoublePage, navigateToPage, shouldShowDoublePage]); const handleNextPage = useCallback(() => { - if (currentPage === pages.length) return; + if (currentPage === pages.length) { + if (nextBook) { + router.push(`/books/${nextBook.id}`); + return; + } else { + setShowEndMessage(true); + return; + } + } if (isDoublePage && shouldShowDoublePage(currentPage)) { navigateToPage(Math.min(pages.length, currentPage + 2)); } else { navigateToPage(Math.min(pages.length, currentPage + 1)); } - }, [currentPage, isDoublePage, navigateToPage, pages.length, shouldShowDoublePage]); + }, [ + currentPage, + isDoublePage, + navigateToPage, + pages.length, + shouldShowDoublePage, + nextBook, + router, + ]); const calculateDistance = (touch1: Touch, touch2: Touch) => { const dx = touch2.clientX - touch1.clientX; @@ -301,5 +322,6 @@ export const usePageNavigation = ({ zoomLevel, panPosition, handleDoubleClick, + showEndMessage, }; }; diff --git a/src/components/reader/types.ts b/src/components/reader/types.ts index 7d296d2..3a896da 100644 --- a/src/components/reader/types.ts +++ b/src/components/reader/types.ts @@ -13,6 +13,7 @@ export interface BookReaderProps { book: KomgaBook; pages: number[]; onClose?: (currentPage: number) => void; + nextBook?: KomgaBook | null; } export interface ThumbnailProps { diff --git a/src/i18n/messages/en/common.json b/src/i18n/messages/en/common.json index a3e94e6..35ad838 100644 --- a/src/i18n/messages/en/common.json +++ b/src/i18n/messages/en/common.json @@ -381,7 +381,10 @@ "close": "Close", "previousPage": "Previous page", "nextPage": "Next page" - } + }, + "endOfSeries": "End of series", + "endOfSeriesMessage": "You have finished all the books in this series!", + "backToSeries": "Back to series" }, "debug": { "title": "DEBUG", diff --git a/src/i18n/messages/fr/common.json b/src/i18n/messages/fr/common.json index 6cfa1e1..a5b122f 100644 --- a/src/i18n/messages/fr/common.json +++ b/src/i18n/messages/fr/common.json @@ -379,7 +379,10 @@ "close": "Fermer", "previousPage": "Page précédente", "nextPage": "Page suivante" - } + }, + "endOfSeries": "Fin de la série", + "endOfSeriesMessage": "Vous avez terminé tous les tomes de cette série !", + "backToSeries": "Retourner à la série" }, "header": { "toggleSidebar": "Afficher/masquer le menu latéral", diff --git a/src/lib/services/book.service.ts b/src/lib/services/book.service.ts index 0de27ce..22cf605 100644 --- a/src/lib/services/book.service.ts +++ b/src/lib/services/book.service.ts @@ -5,6 +5,7 @@ import { ImageService } from "./image.service"; import { PreferencesService } from "./preferences.service"; import { ERROR_CODES } from "../../constants/errorCodes"; import { AppError } from "../../utils/errors"; +import { SeriesService } from "./series.service"; export class BookService extends BaseApiService { static async getBook(bookId: string): Promise { @@ -31,6 +32,11 @@ export class BookService extends BaseApiService { throw new AppError(ERROR_CODES.BOOK.NOT_FOUND, {}, error); } } + public static async getNextBook(bookId: string, seriesId: string): Promise { + const books = await SeriesService.getAllSeriesBooks(seriesId); + const currentIndex = books.findIndex((book) => book.id === bookId); + return books[currentIndex + 1] || null; + } static async updateReadProgress( bookId: string,