diff --git a/apps/readest-app/src/app/page.tsx b/apps/readest-app/src/app/page.tsx index 79fbbad2..fd20d656 100644 --- a/apps/readest-app/src/app/page.tsx +++ b/apps/readest-app/src/app/page.tsx @@ -2,8 +2,9 @@ import * as React from 'react'; import { useState } from 'react'; -import { Book, BooksGroup, LibraryItem } from '../types/book'; -import { useEnv } from '../context/EnvContext'; + +import { Book } from '@/types/book'; +import { useEnv } from '@/context/EnvContext'; import Navbar from '@/components/Navbar'; import Spinner from '@/components/Spinner'; @@ -11,16 +12,10 @@ import Bookshelf from '@/components/Bookshelf'; type AppState = 'Init' | 'Loading' | 'Library' | 'Reader'; -const generateLibraryItems = (groups: BooksGroup[]): LibraryItem[] => { - const ungroupedBooks: Book[] = groups.find((group) => group.id === 'ungrouped')?.books || []; - const groupedBooks: BooksGroup[] = groups.filter((group) => group.id !== 'ungrouped'); - return [...ungroupedBooks, ...groupedBooks].sort((a, b) => b.lastUpdated - a.lastUpdated); -}; - const LibraryPage = () => { const { envConfig } = useEnv(); const [appState, setAppState] = useState('Init'); - const [libraryItems, setLibraryItems] = useState([]); + const [libraryBooks, setLibraryBooks] = useState([]); const [loading, setLoading] = useState(false); React.useEffect(() => { @@ -33,9 +28,7 @@ const LibraryPage = () => { appService .loadLibraryBooks() .then((libraryBooks) => { - const libraryItems = generateLibraryItems(libraryBooks); - console.log('Library items:', libraryItems); - setLibraryItems(libraryItems); + setLibraryBooks(libraryBooks); setAppState('Library'); setLoading(false); }) @@ -59,7 +52,7 @@ const LibraryPage = () => {
- +
diff --git a/apps/readest-app/src/components/Bookshelf.tsx b/apps/readest-app/src/components/Bookshelf.tsx index 0a4fdca1..3c80b37e 100644 --- a/apps/readest-app/src/components/Bookshelf.tsx +++ b/apps/readest-app/src/components/Bookshelf.tsx @@ -1,19 +1,56 @@ import * as React from 'react'; import Image from 'next/image'; -import { Book, BooksGroup, LibraryItem } from '../types/book'; +import { Book, BooksGroup } from '../types/book'; import { FaPlus } from 'react-icons/fa'; interface BookshelfProps { - libraryItems: LibraryItem[]; + libraryBooks: Book[]; onImport: () => void; } -const Bookshelf: React.FC = ({ libraryItems, onImport }) => { +type BookshelfItem = Book | BooksGroup; + +const UNGROUPED_NAME = 'ungrouped'; + +const MOCK_BOOKS: Book[] = Array.from({ length: 14 }, (_v, k) => ({ + id: `book-${k}`, + format: 'EPUB', + title: `Book ${k}`, + author: `Author ${k}`, + lastUpdated: Date.now() - 1000000 * k, + coverImageUrl: `https://placehold.co/800?text=Book+${k}&font=roboto`, +})); + +const generateBookshelfItems = (books: Book[]): BookshelfItem[] => { + const groups: BooksGroup[] = books.reduce((acc: BooksGroup[], book: Book) => { + book.group = book.group || UNGROUPED_NAME; + const groupIndex = acc.findIndex((group) => group.name === book.group); + const booksGroup = acc[acc.findIndex((group) => group.name === book.group)]; + if (booksGroup) { + booksGroup.books.push(book); + booksGroup.lastUpdated = Math.max(acc[groupIndex]!.lastUpdated, book.lastUpdated); + } else { + acc.push({ + name: book.group, + books: [book], + lastUpdated: book.lastUpdated, + }); + } + return acc; + }, []); + const ungroupedBooks: Book[] = groups.find((group) => group.name === UNGROUPED_NAME)?.books || []; + const groupedBooks: BooksGroup[] = groups.filter((group) => group.name !== UNGROUPED_NAME); + return [...ungroupedBooks, ...groupedBooks].sort((a, b) => b.lastUpdated - a.lastUpdated); +}; + +const Bookshelf: React.FC = ({ libraryBooks, onImport }) => { + libraryBooks = [...libraryBooks, ...MOCK_BOOKS]; + const bookshelfItems = generateBookshelfItems(libraryBooks); return (
{/* Books Grid */}
- {libraryItems.map((item, index) => ( + {bookshelfItems.map((item, index) => (
{'format' in item ? ( @@ -53,7 +90,7 @@ const Bookshelf: React.FC = ({ libraryItems, onImport }) => {
))} - {libraryItems.length > 0 && ( + {bookshelfItems.length > 0 && (
({ - id: `book-${k}`, - format: 'EPUB', - title: `Book ${k}`, - author: `Author ${k}`, - lastUpdated: Date.now() - 1000000 * k, - coverImageUrl: `https://placehold.co/800?text=Book+${k}&font=roboto`, -})); - function resolvePath( fp: string, base: BaseDir, @@ -52,7 +43,7 @@ function resolvePath( case 'Books': return { baseDir: BaseDirectory.Document, - fp: `${BOOKS_SUBDIR}/${fp}`, + fp: `${LOCAL_BOOKS_SUBDIR}/${fp}`, base, dir: () => new Promise((r) => r(`${BOOKS_DIR}/`)), }; @@ -130,7 +121,7 @@ export const nativeAppService: AppService = { const txt = await nativeAppService.fs.readFile(fp, base, 'text'); settings = JSON.parse(txt as string); } catch { - const INIT_BOOKS_DIR = await join(await documentDir(), BOOKS_SUBDIR); + const INIT_BOOKS_DIR = await join(await documentDir(), LOCAL_BOOKS_SUBDIR); await nativeAppService.fs.createDir('', 'Books', true); settings = { localBooksDir: INIT_BOOKS_DIR, @@ -182,7 +173,6 @@ export const nativeAppService: AppService = { await message(msg, { kind, title, okLabel }); }, loadLibraryBooks: async () => { - // TODO: Burrently only ungrouped books are supported let books: Book[] = []; try { const txt = await nativeAppService.fs.readFile('books.json', 'Books', 'text'); @@ -194,16 +184,8 @@ export const nativeAppService: AppService = { books.forEach((book) => { book.coverImageUrl = nativeAppService.generateCoverUrl(book); }); - books = [...books, ...MOCK_BOOKS]; - const ungroupedBooks: BooksGroup[] = [ - { - id: 'ungrouped', - name: 'Ungrouped', - books, - lastUpdated: Date.now(), - }, - ]; - return ungroupedBooks; + + return books; }, generateCoverUrl: (book: Book) => { return convertFileSrc(`${BOOKS_DIR}/${book.id}/cover.png`); diff --git a/apps/readest-app/src/types/book.ts b/apps/readest-app/src/types/book.ts index 390aba50..d3fc05a7 100644 --- a/apps/readest-app/src/types/book.ts +++ b/apps/readest-app/src/types/book.ts @@ -5,6 +5,8 @@ export interface Book { format: BookFormat; title: string; author: string; + group?: string; + tags?: string[]; lastUpdated: number; isRemoved?: boolean; coverImageUrl?: string | null; @@ -31,10 +33,7 @@ export interface BookConfig { } export interface BooksGroup { - id: string; name: string; books: Book[]; lastUpdated: number; } - -export type LibraryItem = Book | BooksGroup; diff --git a/apps/readest-app/src/types/system.ts b/apps/readest-app/src/types/system.ts index 632868f7..460df23e 100644 --- a/apps/readest-app/src/types/system.ts +++ b/apps/readest-app/src/types/system.ts @@ -1,5 +1,5 @@ import { SystemSettings } from './settings'; -import { Book, BooksGroup } from './book'; +import { Book } from './book'; export type BaseDir = 'Books' | 'Settings' | 'Data' | 'Log' | 'Cache' | 'None'; export type ToastType = 'info' | 'warning' | 'error'; @@ -23,6 +23,6 @@ export interface AppService { selectFiles(name: string, extensions: string[]): Promise; showMessage(msg: string, kind?: ToastType, title?: string, okLabel?: string): Promise; - loadLibraryBooks(): Promise; + loadLibraryBooks(): Promise; generateCoverUrl(book: Book): string; }