refactor(pdf): move reader state to route-local hook
This commit is contained in:
parent
41036e1341
commit
2f067c0a90
4 changed files with 38 additions and 60 deletions
|
|
@ -4,14 +4,11 @@ import type { ReactNode } from 'react';
|
||||||
|
|
||||||
import { ConfigProvider } from '@/contexts/ConfigContext';
|
import { ConfigProvider } from '@/contexts/ConfigContext';
|
||||||
import { TTSProvider } from '@/contexts/TTSContext';
|
import { TTSProvider } from '@/contexts/TTSContext';
|
||||||
import { PDFProvider } from '@/contexts/PDFContext';
|
|
||||||
|
|
||||||
export default function PdfReaderLayout({ children }: { children: ReactNode }) {
|
export default function PdfReaderLayout({ children }: { children: ReactNode }) {
|
||||||
return (
|
return (
|
||||||
<ConfigProvider>
|
<ConfigProvider>
|
||||||
<TTSProvider>
|
<TTSProvider>{children}</TTSProvider>
|
||||||
<PDFProvider>{children}</PDFProvider>
|
|
||||||
</TTSProvider>
|
|
||||||
</ConfigProvider>
|
</ConfigProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import dynamic from 'next/dynamic';
|
import dynamic from 'next/dynamic';
|
||||||
import { usePDF } from '@/contexts/PDFContext';
|
|
||||||
import { useParams, useRouter } from 'next/navigation';
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||||
|
|
@ -20,6 +19,7 @@ import { resolveDocumentId } from '@/lib/client/dexie';
|
||||||
import { RateLimitBanner } from '@/components/auth/RateLimitBanner';
|
import { RateLimitBanner } from '@/components/auth/RateLimitBanner';
|
||||||
import { useAuthRateLimit } from '@/contexts/AuthRateLimitContext';
|
import { useAuthRateLimit } from '@/contexts/AuthRateLimitContext';
|
||||||
import { useFeatureFlag } from '@/contexts/RuntimeConfigContext';
|
import { useFeatureFlag } from '@/contexts/RuntimeConfigContext';
|
||||||
|
import { usePdfDocument } from './usePdfDocument';
|
||||||
|
|
||||||
// Dynamic import for client-side rendering only
|
// Dynamic import for client-side rendering only
|
||||||
const PDFViewer = dynamic(
|
const PDFViewer = dynamic(
|
||||||
|
|
@ -34,7 +34,16 @@ export default function PDFViewerPage() {
|
||||||
const canExportAudiobook = useFeatureFlag('enableAudiobookExport');
|
const canExportAudiobook = useFeatureFlag('enableAudiobookExport');
|
||||||
const { id } = useParams();
|
const { id } = useParams();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { setCurrentDocument, currDocName, clearCurrDoc, currDocPage, currDocPages, createFullAudioBook: createPDFAudioBook, regenerateChapter: regeneratePDFChapter } = usePDF();
|
const pdfState = usePdfDocument();
|
||||||
|
const {
|
||||||
|
setCurrentDocument,
|
||||||
|
currDocName,
|
||||||
|
clearCurrDoc,
|
||||||
|
currDocPage,
|
||||||
|
currDocPages,
|
||||||
|
createFullAudioBook: createPDFAudioBook,
|
||||||
|
regenerateChapter: regeneratePDFChapter,
|
||||||
|
} = pdfState;
|
||||||
const { stop } = useTTS();
|
const { stop } = useTTS();
|
||||||
const { isAtLimit } = useAuthRateLimit();
|
const { isAtLimit } = useAuthRateLimit();
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
|
@ -197,7 +206,7 @@ export default function PDFViewerPage() {
|
||||||
<DocumentSkeleton />
|
<DocumentSkeleton />
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<PDFViewer zoomLevel={zoomLevel} />
|
<PDFViewer zoomLevel={zoomLevel} pdfState={pdfState} />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{canExportAudiobook && (
|
{canExportAudiobook && (
|
||||||
|
|
|
||||||
|
|
@ -1,23 +1,14 @@
|
||||||
/**
|
/**
|
||||||
* PDF Context Provider
|
* Route-local PDF document hook.
|
||||||
*
|
*
|
||||||
* This module provides a React context for managing PDF document functionality.
|
* This module owns PDF document loading, text extraction, highlighting, and
|
||||||
* It handles document loading, text extraction, highlighting, and integration with TTS.
|
* audiobook integration for the `/pdf/[id]` route.
|
||||||
*
|
|
||||||
* Key features:
|
|
||||||
* - PDF document management (add/remove/load)
|
|
||||||
* - Text extraction and processing
|
|
||||||
* - Text highlighting and navigation
|
|
||||||
* - Document state management
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
createContext,
|
|
||||||
useContext,
|
|
||||||
useState,
|
useState,
|
||||||
ReactNode,
|
|
||||||
useEffect,
|
useEffect,
|
||||||
useCallback,
|
useCallback,
|
||||||
useMemo,
|
useMemo,
|
||||||
|
|
@ -50,9 +41,9 @@ import type { AudiobookGenerationSettings } from '@/types/client';
|
||||||
import { clampSegmentPreloadDepth } from '@/types/config';
|
import { clampSegmentPreloadDepth } from '@/types/config';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interface defining all available methods and properties in the PDF context
|
* Interface defining all available methods and properties for the PDF route.
|
||||||
*/
|
*/
|
||||||
interface PDFContextType {
|
export interface PdfDocumentState {
|
||||||
// Current document state
|
// Current document state
|
||||||
currDocId: string | undefined;
|
currDocId: string | undefined;
|
||||||
currDocData: ArrayBuffer | undefined;
|
currDocData: ArrayBuffer | undefined;
|
||||||
|
|
@ -93,22 +84,13 @@ interface PDFContextType {
|
||||||
isAudioCombining: boolean;
|
isAudioCombining: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create the context
|
|
||||||
const PDFContext = createContext<PDFContextType | undefined>(undefined);
|
|
||||||
|
|
||||||
const EMPTY_TEXT_RETRY_DELAY_MS = 120;
|
const EMPTY_TEXT_RETRY_DELAY_MS = 120;
|
||||||
const EMPTY_TEXT_MAX_RETRIES = 6;
|
const EMPTY_TEXT_MAX_RETRIES = 6;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* PDFProvider Component
|
* Main PDF route hook.
|
||||||
*
|
|
||||||
* Main provider component that manages PDF state and functionality.
|
|
||||||
* Handles document loading, text processing, and integration with TTS.
|
|
||||||
*
|
|
||||||
* @param {Object} props - Component props
|
|
||||||
* @param {ReactNode} props.children - Child components to be wrapped by the provider
|
|
||||||
*/
|
*/
|
||||||
export function PDFProvider({ children }: { children: ReactNode }) {
|
export function usePdfDocument(): PdfDocumentState {
|
||||||
const {
|
const {
|
||||||
setText: setTTSText,
|
setText: setTTSText,
|
||||||
stop,
|
stop,
|
||||||
|
|
@ -507,8 +489,7 @@ export function PDFProvider({ children }: { children: ReactNode }) {
|
||||||
};
|
};
|
||||||
}, [registerVisualPageChangeHandler, currDocPages, pdfDocument]);
|
}, [registerVisualPageChangeHandler, currDocPages, pdfDocument]);
|
||||||
|
|
||||||
// Context value memoization
|
return useMemo(
|
||||||
const contextValue = useMemo(
|
|
||||||
() => ({
|
() => ({
|
||||||
onDocumentLoadSuccess,
|
onDocumentLoadSuccess,
|
||||||
setCurrentDocument,
|
setCurrentDocument,
|
||||||
|
|
@ -544,25 +525,4 @@ export function PDFProvider({ children }: { children: ReactNode }) {
|
||||||
isAudioCombining,
|
isAudioCombining,
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
|
||||||
<PDFContext.Provider value={contextValue}>
|
|
||||||
{children}
|
|
||||||
</PDFContext.Provider>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Custom hook to consume the PDF context
|
|
||||||
* Ensures the context is used within a provider
|
|
||||||
*
|
|
||||||
* @throws {Error} If used outside of PDFProvider
|
|
||||||
* @returns {PDFContextType} The PDF context value containing all PDF-related functionality
|
|
||||||
*/
|
|
||||||
export function usePDF() {
|
|
||||||
const context = useContext(PDFContext);
|
|
||||||
if (context === undefined) {
|
|
||||||
throw new Error('usePDF must be used within a PDFProvider');
|
|
||||||
}
|
|
||||||
return context;
|
|
||||||
}
|
}
|
||||||
|
|
@ -7,12 +7,25 @@ import 'react-pdf/dist/Page/AnnotationLayer.css';
|
||||||
import 'react-pdf/dist/Page/TextLayer.css';
|
import 'react-pdf/dist/Page/TextLayer.css';
|
||||||
import { DocumentSkeleton } from '@/components/documents/DocumentSkeleton';
|
import { DocumentSkeleton } from '@/components/documents/DocumentSkeleton';
|
||||||
import { useTTS } from '@/contexts/TTSContext';
|
import { useTTS } from '@/contexts/TTSContext';
|
||||||
import { usePDF } from '@/contexts/PDFContext';
|
|
||||||
import { useConfig } from '@/contexts/ConfigContext';
|
import { useConfig } from '@/contexts/ConfigContext';
|
||||||
import { usePDFResize } from '@/hooks/pdf/usePDFResize';
|
import { usePDFResize } from '@/hooks/pdf/usePDFResize';
|
||||||
|
import type { PdfDocumentState } from '@/app/(app)/pdf/[id]/usePdfDocument';
|
||||||
|
|
||||||
interface PDFViewerProps {
|
interface PDFViewerProps {
|
||||||
zoomLevel: number;
|
zoomLevel: number;
|
||||||
|
pdfState: Pick<
|
||||||
|
PdfDocumentState,
|
||||||
|
| 'highlightPattern'
|
||||||
|
| 'clearHighlights'
|
||||||
|
| 'clearWordHighlights'
|
||||||
|
| 'highlightWordIndex'
|
||||||
|
| 'onDocumentLoadSuccess'
|
||||||
|
| 'currDocId'
|
||||||
|
| 'currDocData'
|
||||||
|
| 'currDocPages'
|
||||||
|
| 'currDocText'
|
||||||
|
| 'currDocPage'
|
||||||
|
>;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface PDFOnLinkClickArgs {
|
interface PDFOnLinkClickArgs {
|
||||||
|
|
@ -20,7 +33,7 @@ interface PDFOnLinkClickArgs {
|
||||||
dest?: Dest;
|
dest?: Dest;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function PDFViewer({ zoomLevel }: PDFViewerProps) {
|
export function PDFViewer({ zoomLevel, pdfState }: PDFViewerProps) {
|
||||||
const containerRef = useRef<HTMLDivElement>(null);
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
const [isPageRendering, setIsPageRendering] = useState(false);
|
const [isPageRendering, setIsPageRendering] = useState(false);
|
||||||
const scaleRef = useRef<number>(1);
|
const scaleRef = useRef<number>(1);
|
||||||
|
|
@ -43,7 +56,6 @@ export function PDFViewer({ zoomLevel }: PDFViewerProps) {
|
||||||
skipToLocation,
|
skipToLocation,
|
||||||
} = useTTS();
|
} = useTTS();
|
||||||
|
|
||||||
// PDF context
|
|
||||||
const {
|
const {
|
||||||
highlightPattern,
|
highlightPattern,
|
||||||
clearHighlights,
|
clearHighlights,
|
||||||
|
|
@ -55,7 +67,7 @@ export function PDFViewer({ zoomLevel }: PDFViewerProps) {
|
||||||
currDocPages,
|
currDocPages,
|
||||||
currDocText,
|
currDocText,
|
||||||
currDocPage,
|
currDocPage,
|
||||||
} = usePDF();
|
} = pdfState;
|
||||||
|
|
||||||
// IMPORTANT:
|
// IMPORTANT:
|
||||||
// - pdf.js may transfer/detach ArrayBuffers when sending them to its worker, so we must clone.
|
// - pdf.js may transfer/detach ArrayBuffers when sending them to its worker, so we must clone.
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue