import React, { useState, useEffect, useRef } from 'react'; import '../styles/BookReader.scss'; import { loadBook, BookContent } from './loadBook'; import BookContentSections, { BookContentSectionsRef } from './BookContentSections'; interface BookReaderProps { bookId: string; } type Theme = 'light' | 'dark'; type FontSize = 'small' | 'medium' | 'large'; const fontSizeMap = { small: '16px', medium: '18px', large: '22px', }; const TOC_PLACEHOLDER = 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNjAiIGhlaWdodD0iODAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgPCEtLSBCb29rIGNvdmVyIC0tPgogIDxyZWN0IHg9IjgiIHk9IjUiIHdpZHRoPSI0NCIgaGVpZ2h0PSI3MCIgZmlsbD0iIzRhOTBlMiIgc3Ryb2tlPSIjMmM1YWEwIiBzdHJva2Utd2lkdGg9IjEuNSIgcng9IjIiLz4KICAKICA8IS0tIFBhZ2VzIC0tPgogIDxyZWN0IHg9IjEyIiB5PSI4IiB3aWR0aD0iMzYiIGhlaWdodD0iNjQiIGZpbGw9IiNmZmZmZmYiIHN0cm9rZT0iI2UwZTBlMCIgc3Ryb2tlLXdpZHRoPSIwLjUiLz4KICA8cmVjdCB4PSIxMCIgeT0iNiIgd2lkdGg9IjM2IiBoZWlnaHQ9IjY0IiBmaWxsPSIjZjhmOGY4IiBzdHJva2U9IiNlMGUwZTAiIHN0cm9rZS13aWR0aD0iMC41Ii8+CiAgCiAgPCEtLSBQYWdlIGxpbmVzIC0tPgogIDxsaW5lIHgxPSIxNCIgeTE9IjIwIiB4Mj0iNDIiIHkyPSIyMCIgc3Ryb2tlPSIjZDBkMGQwIiBzdHJva2Utd2lkdGg9IjAuNSIvPgogIDxsaW5lIHgxPSIxNCIgeTE9IjI4IiB4Mj0iNDIiIHkyPSIyOCIgc3Ryb2tlPSIjZDBkMGQwIiBzdHJva2Utd2lkdGg9IjAuNSIvPgogIDxsaW5lIHgxPSIxNCIgeTE9IjM2IiB4Mj0iNDIiIHkyPSIzNiIgc3Ryb2tlPSIjZDBkMGQwIiBzdHJva2Utd2lkdGg9IjAuNSIvPgogIDxsaW5lIHgxPSIxNCIgeTE9IjQ0IiB4Mj0iNDIiIHkyPSI0NCIgc3Ryb2tlPSIjZDBkMGQwIiBzdHJva2Utd2lkdGg9IjAuNSIvPgogIAogIDwhLS0gU3BpbmUgZGV0YWlsIC0tPgogIDxsaW5lIHgxPSI4IiB5MT0iMTUiIHgyPSI4IiB5Mj0iNjUiIHN0cm9rZT0iIzFhNDQ4MCIgc3Ryb2tlLXdpZHRoPSIyIi8+Cjwvc3ZnPg=='; const BookReader: React.FC = ({ bookId }) => { const [isLoading, setIsLoading] = useState(true); const [bookData, setBookData] = useState(null); const [error, setError] = useState(null); const [showSettings, setShowSettings] = useState(false); const [theme, setTheme] = useState('light'); const [fontSize, setFontSize] = useState('medium'); const [showTOC, setShowTOC] = useState(false); const [currentPage, setCurrentPage] = useState(0); const [totalPages, setTotalPages] = useState(0); const [currentSection, setCurrentSection] = useState(null); const [chapterBoundaries, setChapterBoundaries] = useState>([]); const containerRef = useRef(null); const contentSectionsRef = useRef(null); const isInitialRender = useRef(true); useEffect(() => { const fetchBook = async () => { try { setIsLoading(true); const data = await loadBook(bookId); setBookData(data); } catch (err) { setError('Failed to load book'); console.error('Error loading book:', err); } finally { setIsLoading(false); } }; fetchBook(); }, [bookId]); // Optionally, persist settings useEffect(() => { const savedTheme = localStorage.getItem('bookReaderTheme'); const savedFontSize = localStorage.getItem('bookReaderFontSize'); if (savedTheme === 'light' || savedTheme === 'dark') setTheme(savedTheme); if (savedFontSize === 'small' || savedFontSize === 'medium' || savedFontSize === 'large') setFontSize(savedFontSize as FontSize); }, []); useEffect(() => { localStorage.setItem('bookReaderTheme', theme); localStorage.setItem('bookReaderFontSize', fontSize); if (isInitialRender.current) { isInitialRender.current = false; return; } // When font size changes, re-paginating is necessary. // This is triggered by passing the new fontSize to BookContentSections. // We reset to page 0 to avoid being on an invalid page after re-pagination. setCurrentPage(0); }, [theme, fontSize]); // Update section when page changes useEffect(() => { if (contentSectionsRef.current) { const section = contentSectionsRef.current.getCurrentSection(); setCurrentSection(section); } }, [currentPage, chapterBoundaries]); const handleHeaderClick = () => { if (containerRef.current) { containerRef.current.scrollIntoView({ behavior: 'smooth', block: 'center' }); } }; const handlePageChange = (page: number, total: number, boundaries: Array<{ id: number; title: string; startPage: number; endPage: number; pageCount: number; }>) => { setCurrentPage(page); setTotalPages(total); setChapterBoundaries(boundaries); }; const goToNextPage = () => { if (currentPage < totalPages - 1) { setCurrentPage(currentPage + 1); } }; const goToPreviousPage = () => { if (currentPage > 0) { setCurrentPage(currentPage - 1); } }; // Helper function to get current chapter info const getCurrentChapter = () => { if (chapterBoundaries.length === 0) return null; return chapterBoundaries.find(chapter => currentPage + 1 >= chapter.startPage && currentPage + 1 <= chapter.endPage ); }; // Helper function to get the last page of a specific chapter const getChapterLastPage = (chapterId: number) => { const chapter = chapterBoundaries.find(ch => ch.id === chapterId); return chapter ? chapter.endPage : null; }; // Helper function to get the first page of a specific chapter const getChapterFirstPage = (chapterId: number) => { const chapter = chapterBoundaries.find(ch => ch.id === chapterId); return chapter ? chapter.startPage : null; }; // Helper function to go to a specific chapter const goToChapter = (chapterId: number) => { const firstPage = getChapterFirstPage(chapterId); if (firstPage !== null) { setCurrentPage(firstPage - 1); // Convert to 0-based index } }; if (isLoading) { return (

Loading book...

); } if (error) { return (

Error

{error}

); } if (!bookData) { return (

Error

No book data available

); } const coverUrl = bookData.image_url?.url || TOC_PLACEHOLDER; const currentChapter = getCurrentChapter(); // Helper function to get section type label const getSectionTypeLabel = (type: string) => { switch (type) { case 'front': return 'Front Matter'; case 'chapter': return 'Chapter'; case 'back': return 'Back Matter'; default: return 'Section'; } }; // Helper function to get section progress text const getSectionProgressText = (section: any) => { if (!section) return ''; const currentPageInSection = currentPage - section.startPage + 1; const totalPagesInSection = section.pageCount; if (section.type === 'chapter') { return `Page ${currentPageInSection} of ${totalPagesInSection} in this chapter`; } else { return `Page ${currentPageInSection} of ${totalPagesInSection} in this section`; } }; return (
{/* TOC Button */} {/* Settings Button */} {/* TOC Panel */} {showTOC && ( )}

{bookData.title}

{currentPage + 1} / {totalPages} {currentSection && ( {' '}• {currentSection.title} )}
{/* Navigation controls */}
{currentPage + 1}
{totalPages}
{showSettings && (
setShowSettings(false)}>
e.stopPropagation()}>

Reader Settings

)}
); }; export default BookReader;