diff --git a/src/app/(app)/epub/[id]/layout.tsx b/src/app/(app)/epub/[id]/layout.tsx index 701e1e7..2f41494 100644 --- a/src/app/(app)/epub/[id]/layout.tsx +++ b/src/app/(app)/epub/[id]/layout.tsx @@ -4,13 +4,12 @@ import type { ReactNode } from 'react'; import { ConfigProvider } from '@/contexts/ConfigContext'; import { TTSProvider } from '@/contexts/TTSContext'; -import { EPUBProvider } from '@/contexts/EPUBContext'; export default function EpubReaderLayout({ children }: { children: ReactNode }) { return ( - {children} + {children} ); diff --git a/src/app/(app)/epub/[id]/page.tsx b/src/app/(app)/epub/[id]/page.tsx index b3ee043..5e6f784 100644 --- a/src/app/(app)/epub/[id]/page.tsx +++ b/src/app/(app)/epub/[id]/page.tsx @@ -3,7 +3,6 @@ import { useParams, useRouter } from "next/navigation"; import Link from 'next/link'; import { useCallback, useEffect, useRef, useState } from 'react'; -import { useEPUB } from '@/contexts/EPUBContext'; import { DocumentSkeleton } from '@/components/documents/DocumentSkeleton'; import { EPUBViewer } from '@/components/views/EPUBViewer'; import { DocumentSettings } from '@/components/documents/DocumentSettings'; @@ -20,12 +19,21 @@ import { resolveDocumentId } from '@/lib/client/dexie'; import { RateLimitBanner } from '@/components/auth/RateLimitBanner'; import { useAuthRateLimit } from '@/contexts/AuthRateLimitContext'; import { useFeatureFlag } from '@/contexts/RuntimeConfigContext'; +import { useEPUB } from './useEpubDocument'; export default function EPUBPage() { const canExportAudiobook = useFeatureFlag('enableAudiobookExport'); const { id } = useParams(); const router = useRouter(); - const { setCurrentDocument, currDocName, clearCurrDoc, createFullAudioBook: createEPUBAudioBook, regenerateChapter: regenerateEPUBChapter, bookRef } = useEPUB(); + const epubState = useEPUB(); + const { + setCurrentDocument, + currDocName, + clearCurrDoc, + createFullAudioBook: createEPUBAudioBook, + regenerateChapter: regenerateEPUBChapter, + bookRef, + } = epubState; const { stop } = useTTS(); const { isAtLimit } = useAuthRateLimit(); const [error, setError] = useState(null); @@ -209,7 +217,7 @@ export default function EPUBPage() { ) : (
- +
)} diff --git a/src/contexts/EPUBContext.tsx b/src/app/(app)/epub/[id]/useEpubDocument.ts similarity index 97% rename from src/contexts/EPUBContext.tsx rename to src/app/(app)/epub/[id]/useEpubDocument.ts index 660f644..e3e7546 100644 --- a/src/contexts/EPUBContext.tsx +++ b/src/app/(app)/epub/[id]/useEpubDocument.ts @@ -1,10 +1,7 @@ 'use client'; import { - createContext, - useContext, useState, - ReactNode, useCallback, useMemo, useRef, @@ -36,7 +33,7 @@ import { type EpubCanonicalWordToken, } from '@/lib/client/epub/epub-word-highlight'; import { useParams } from 'next/navigation'; -import { useConfig } from './ConfigContext'; +import { useConfig } from '@/contexts/ConfigContext'; import type { EpubRenderedLocationWalker, TTSSentenceAlignment, @@ -47,7 +44,7 @@ import type { AudiobookGenerationSettings, TTSSegmentLocator } from '@/types/cli import { isStableEpubLocator } from '@/types/client'; import type { CanonicalTtsSegment } from '@/lib/shared/tts-segment-plan'; -interface EPUBContextType { +export interface EPUBState { currDocData: ArrayBuffer | undefined; currDocName: string | undefined; currDocPages: number | undefined; @@ -90,8 +87,6 @@ interface EPUBContextType { clearWordHighlights: () => void; } -const EPUBContext = createContext(undefined); - const EPUB_CONTINUATION_CHARS = 5000; const stepToNextNode = (node: Node | null, root: Node): Node | null => { @@ -526,12 +521,9 @@ const resolveVisibleSegmentRange = ( /** - * Provider component for EPUB functionality - * Manages the state and operations for EPUB document handling - * @param {Object} props - Component props - * @param {ReactNode} props.children - Child components to be wrapped by the provider + * Route-local EPUB reader hook. */ -export function EPUBProvider({ children }: { children: ReactNode }) { +export function useEPUB(): EPUBState { const { setText: setTTSText, currDocPage, currDocPages, setCurrDocPages, stop, skipToLocation, setIsEPUB } = useTTS(); const { authEnabled } = useAuthConfig(); const { id } = useParams(); @@ -1141,8 +1133,7 @@ export function EPUBProvider({ children }: { children: ReactNode }) { - // Context value memoization - const contextValue = useMemo( + return useMemo( () => ({ setCurrentDocument, currDocData, @@ -1190,23 +1181,4 @@ export function EPUBProvider({ children }: { children: ReactNode }) { clearWordHighlights, ] ); - - return ( - - {children} - - ); -} - -/** - * Custom hook to consume the EPUB context - * @returns {EPUBContextType} The EPUB context value - * @throws {Error} When used outside of EPUBProvider - */ -export function useEPUB() { - const context = useContext(EPUBContext); - if (context === undefined) { - throw new Error('useEPUB must be used within an EPUBProvider'); - } - return context; } diff --git a/src/components/views/EPUBViewer.tsx b/src/components/views/EPUBViewer.tsx index f269eab..37c57c3 100644 --- a/src/components/views/EPUBViewer.tsx +++ b/src/components/views/EPUBViewer.tsx @@ -2,13 +2,13 @@ import { useEffect, useRef, useCallback, useState } from 'react'; import dynamic from 'next/dynamic'; -import { useEPUB } from '@/contexts/EPUBContext'; import { useTTS } from '@/contexts/TTSContext'; import { useConfig } from '@/contexts/ConfigContext'; import { DocumentSkeleton } from '@/components/documents/DocumentSkeleton'; import { useEPUBTheme, getThemeStyles } from '@/hooks/epub/useEPUBTheme'; import { useEPUBResize } from '@/hooks/epub/useEPUBResize'; import { DotsVerticalIcon, ChevronLeftIcon, ChevronRightIcon } from '@/components/icons/Icons'; +import type { EPUBState } from '@/app/(app)/epub/[id]/useEpubDocument'; const ReactReader = dynamic(() => import('react-reader').then(mod => mod.ReactReader), { ssr: false, @@ -17,9 +17,29 @@ const ReactReader = dynamic(() => import('react-reader').then(mod => mod.ReactRe interface EPUBViewerProps { className?: string; + epubState: Pick< + EPUBState, + | 'currDocData' + | 'currDocName' + | 'currDocPage' + | 'currDocPages' + | 'locationRef' + | 'handleLocationChanged' + | 'bookRef' + | 'renditionRef' + | 'tocRef' + | 'setRendition' + | 'extractPageText' + | 'highlightSegment' + | 'clearHighlights' + | 'highlightWordIndex' + | 'clearWordHighlights' + | 'walkUpcomingRenderedLocations' + | 'resolveEpubLocator' + >; } -export function EPUBViewer({ className = '' }: EPUBViewerProps) { +export function EPUBViewer({ className = '', epubState }: EPUBViewerProps) { const [isTocOpen, setIsTocOpen] = useState(false); const { currDocData, @@ -39,7 +59,7 @@ export function EPUBViewer({ className = '' }: EPUBViewerProps) { clearWordHighlights, walkUpcomingRenderedLocations, resolveEpubLocator, - } = useEPUB(); + } = epubState; const { registerLocationChangeHandler, registerEpubLocationWalker,