From 329c907f808056a0f529105292ae7ddfa1dfa6ea Mon Sep 17 00:00:00 2001 From: "Jiucheng(Oliver)" Date: Thu, 26 Dec 2024 10:59:54 -0500 Subject: [PATCH] Add information modal for book details (#27) * Add BookDetailModal component and implement details view in Bookshelf * Enhance BookDetailModal to fetch and display detailed book metadata * Refactor Bookshelf and BookDetailModal for improved click handling and styling consistency * Enhance BookDetailModal with additional metadata fields and improved layout * Refactor BookDetailModal to use appService for fetching book details and remove deprecated bookService --- .../src/app/library/components/Bookshelf.tsx | 33 +++- .../src/components/BookDetailModal.tsx | 149 ++++++++++++++++++ apps/readest-app/src/services/appService.ts | 6 + apps/readest-app/src/types/system.ts | 2 + 4 files changed, 187 insertions(+), 3 deletions(-) create mode 100644 apps/readest-app/src/components/BookDetailModal.tsx diff --git a/apps/readest-app/src/app/library/components/Bookshelf.tsx b/apps/readest-app/src/app/library/components/Bookshelf.tsx index cf6a726b..7c7552bd 100644 --- a/apps/readest-app/src/app/library/components/Bookshelf.tsx +++ b/apps/readest-app/src/app/library/components/Bookshelf.tsx @@ -4,6 +4,7 @@ import { useEffect, useRef, useState } from 'react'; import { PiPlus } from 'react-icons/pi'; import { MdDelete, MdOpenInNew } from 'react-icons/md'; import { MdCheckCircle, MdCheckCircleOutline } from 'react-icons/md'; +import { CiCircleMore } from "react-icons/ci"; import { useRouter, useSearchParams } from 'next/navigation'; import Image from 'next/image'; @@ -22,6 +23,7 @@ import { FILE_REVEAL_LABELS, FILE_REVEAL_PLATFORMS } from '@/utils/os'; import Alert from '@/components/Alert'; import Spinner from '@/components/Spinner'; import { isTauriAppPlatform } from '@/services/environment'; +import BookDetailModal from '@/components/BookDetailModal'; type BookshelfItem = Book | BooksGroup; @@ -67,6 +69,17 @@ const Bookshelf: React.FC = ({ libraryBooks, isSelectMode, onImp const [clickedImage, setClickedImage] = useState(null); const [importBookUrl] = useState(searchParams?.get('url') || ''); const isImportingBook = useRef(false); + const [isModalOpen, setIsModalOpen] = useState(false); + const [selectedBook, setSelectedBook] = useState(null); + + const showMoreDetails = (book: Book) => { + setIsModalOpen(true); + setSelectedBook(book); + }; + + const closeModal = () => { + setIsModalOpen(false); + }; const { setLibrary } = useLibraryStore(); @@ -171,9 +184,11 @@ const Bookshelf: React.FC = ({ libraryBooks, isSelectMode, onImp
handleBookClick(item.hash)} > -
+
handleBookClick(item.hash)} + >
= ({ libraryBooks, isSelectMode, onImp )}
-
+

{(item as Book).title}

+
+ +
) : ( @@ -275,6 +297,11 @@ const Bookshelf: React.FC = ({ libraryBooks, isSelectMode, onImp onClickConfirm={confirmDelete} /> )} + + {/* Modal Component */} + {selectedBook && ( + + )}
); }; diff --git a/apps/readest-app/src/components/BookDetailModal.tsx b/apps/readest-app/src/components/BookDetailModal.tsx new file mode 100644 index 00000000..1989a38f --- /dev/null +++ b/apps/readest-app/src/components/BookDetailModal.tsx @@ -0,0 +1,149 @@ +import React, { useEffect, useState } from 'react'; + +import { Book } from '@/types/book'; +import { EnvConfigType } from '@/services/environment'; +import { useSettingsStore } from '@/store/settingsStore'; + +import WindowButtons from '@/components/WindowButtons'; + +const BookDetailModal = ({ + isOpen, + onClose, + book, + envConfig, +}: { + isOpen: boolean; + onClose: () => void; + book: Book; + envConfig: EnvConfigType; +}) => { + if (!isOpen) return null; + const [bookMeta, setBookMeta] = useState(null); + + const { settings } = useSettingsStore(); + + useEffect(() => { + const fetchBookDetails = async () => { + const appService = await envConfig.getAppService(); + const details = await appService.fetchBookDetails(book, settings); + setBookMeta(details); + }; + fetchBookDetails(); + }, [book]); + + if (!bookMeta) + return ( +
+
+ +
+
+ +
+

+ Loading Book Details... +

+
+
+ ); + + return ( +
+
+ +
+
+ +
+ +
+ {book.coverImageUrl ? ( + {book.title} + ) : ( +
+ No Image +
+ )} + +
+

+ {bookMeta.title || 'Untitled'} +

+

{book.author || 'Unknown Author'}

+ +
+
+ +
+
+
+ Publisher: +

{bookMeta.publisher || 'Unknown'}

+
+
+ Published: +

{bookMeta.published || 'Unknown Date'}

+
+
+ Updated: +

+ {book.lastUpdated + ? new Date(book.lastUpdated).toLocaleDateString('en-US', { + year: 'numeric', + month: 'long', + day: 'numeric', + }) + : 'Unknown Date'} +

+
+
+ +
+
+ Language: +

{bookMeta.language || 'Unknown'}

+
+
+ Identifier: +

+ {bookMeta.identifier ? bookMeta.identifier.slice(-8) : 'N/A'} +

{' '} + {/* Show last 8 characters */} +
+
+ Subjects: +

{bookMeta.subject?.join(', ') || 'None'}

+
+
+
+
+
+ ); +}; + +export default BookDetailModal; diff --git a/apps/readest-app/src/services/appService.ts b/apps/readest-app/src/services/appService.ts index 50adc86a..dede7e71 100644 --- a/apps/readest-app/src/services/appService.ts +++ b/apps/readest-app/src/services/appService.ts @@ -219,6 +219,12 @@ export abstract class BaseAppService implements AppService { } } + async fetchBookDetails(book: Book, settings: SystemSettings) { + const { file } = await this.loadBookContent(book, settings) as BookContent; + const bookDoc = (await new DocumentLoader(file).open()).book as BookDoc; + return bookDoc.metadata; + } + async saveBookConfig(book: Book, config: BookConfig, settings?: SystemSettings) { let serializedConfig: string; if (settings) { diff --git a/apps/readest-app/src/types/system.ts b/apps/readest-app/src/types/system.ts index bc2c603a..5decdb75 100644 --- a/apps/readest-app/src/types/system.ts +++ b/apps/readest-app/src/types/system.ts @@ -1,5 +1,6 @@ import { SystemSettings } from './settings'; import { Book, BookConfig, BookContent } from './book'; +import { BookDoc } from '@/libs/document'; export type AppPlatform = 'web' | 'tauri'; export type BaseDir = 'Books' | 'Settings' | 'Data' | 'Log' | 'Cache' | 'None'; @@ -38,6 +39,7 @@ export interface AppService { ): Promise; deleteBook(book: Book): Promise; loadBookConfig(book: Book, settings: SystemSettings): Promise; + fetchBookDetails(book: Book, settings: SystemSettings): Promise; saveBookConfig(book: Book, config: BookConfig, settings?: SystemSettings): Promise; loadBookContent(book: Book, settings: SystemSettings): Promise; loadLibraryBooks(): Promise;