11'use client' ;
22
3- import { RefObject } from 'react' ;
3+ import { RefObject , useCallback } from 'react' ;
44import { Document , Page } from 'react-pdf' ;
55import 'react-pdf/dist/Page/AnnotationLayer.css' ;
66import 'react-pdf/dist/Page/TextLayer.css' ;
77import { useState , useEffect , useRef } from 'react' ;
88import { PDFSkeleton } from './PDFSkeleton' ;
99import { useTTS } from '@/contexts/TTSContext' ;
1010import { usePDF } from '@/contexts/PDFContext' ;
11+ import TTSPlayer from '@/components/player/TTSPlayer' ;
1112
1213interface PDFViewerProps {
13- pdfData : Blob | undefined ;
1414 zoomLevel : number ;
1515}
1616
17- export function PDFViewer ( { pdfData, zoomLevel } : PDFViewerProps ) {
18- const [ numPages , setNumPages ] = useState < number > ( ) ;
17+ export function PDFViewer ( { zoomLevel } : PDFViewerProps ) {
1918 const [ containerWidth , setContainerWidth ] = useState < number > ( 0 ) ;
20- const { setText, currentSentence, stopAndPlayFromIndex, isProcessing } = useTTS ( ) ;
21- const [ pdfText , setPdfText ] = useState ( '' ) ;
22- const [ pdfDataUrl , setPdfDataUrl ] = useState < string > ( ) ;
23- const [ loadingError , setLoadingError ] = useState < string > ( ) ;
2419 const containerRef = useRef < HTMLDivElement > ( null ) ;
25- const { extractTextFromPDF, highlightPattern, clearHighlights, handleTextClick } = usePDF ( ) ;
20+
21+ // TTS context
22+ const {
23+ currentSentence,
24+ stopAndPlayFromIndex,
25+ isProcessing
26+ } = useTTS ( ) ;
27+
28+ // PDF context
29+ const {
30+ highlightPattern,
31+ clearHighlights,
32+ handleTextClick,
33+ onDocumentLoadSuccess,
34+ currDocURL,
35+ currDocPages,
36+ currDocText,
37+ currDocPage,
38+ } = usePDF ( ) ;
2639
2740 // Add static styles once during component initialization
2841 const styleElement = document . createElement ( 'style' ) ;
@@ -44,58 +57,6 @@ export function PDFViewer({ pdfData, zoomLevel }: PDFViewerProps) {
4457 } ;
4558 } , [ styleElement ] ) ;
4659
47- useEffect ( ( ) => {
48- /*
49- * Converts PDF blob to a data URL for display.
50- * Cleans up by clearing the data URL when component unmounts.
51- *
52- * Dependencies:
53- * - pdfData: Re-run when the PDF blob changes to convert it to a new data URL
54- */
55- if ( ! pdfData ) return ;
56-
57- const reader = new FileReader ( ) ;
58- reader . onload = ( ) => {
59- setPdfDataUrl ( reader . result as string ) ;
60- } ;
61- reader . onerror = ( ) => {
62- console . error ( 'Error reading file:' , reader . error ) ;
63- setLoadingError ( 'Failed to load PDF' ) ;
64- } ;
65- reader . readAsDataURL ( pdfData ) ;
66-
67- return ( ) => {
68- setPdfDataUrl ( undefined ) ;
69- } ;
70- } , [ pdfData ] ) ;
71-
72- useEffect ( ( ) => {
73- /*
74- * Extracts text content from the PDF once it's loaded.
75- * Sets the extracted text for both display and text-to-speech.
76- *
77- * Dependencies:
78- * - pdfDataUrl: Re-run when the data URL is ready
79- * - extractTextFromPDF: Function from context that could change
80- * - setText: Function from context that could change
81- * - pdfData: Source PDF blob that's being processed
82- */
83- if ( ! pdfDataUrl || ! pdfData ) return ;
84-
85- const loadPdfText = async ( ) => {
86- try {
87- const text = await extractTextFromPDF ( pdfData ) ;
88- setPdfText ( text ) ;
89- setText ( text ) ;
90- } catch ( error ) {
91- console . error ( 'Error loading PDF text:' , error ) ;
92- setLoadingError ( 'Failed to extract PDF text' ) ;
93- }
94- } ;
95-
96- loadPdfText ( ) ;
97- } , [ pdfDataUrl , extractTextFromPDF , setText , pdfData ] ) ;
98-
9960 useEffect ( ( ) => {
10061 /*
10162 * Sets up click event listeners for text selection in the PDF.
@@ -108,10 +69,11 @@ export function PDFViewer({ pdfData, zoomLevel }: PDFViewerProps) {
10869 */
10970 const container = containerRef . current ;
11071 if ( ! container ) return ;
72+ if ( ! currDocText ) return ;
11173
11274 const handleClick = ( event : MouseEvent ) => handleTextClick (
11375 event ,
114- pdfText ,
76+ currDocText ,
11577 containerRef as RefObject < HTMLDivElement > ,
11678 stopAndPlayFromIndex ,
11779 isProcessing
@@ -120,7 +82,7 @@ export function PDFViewer({ pdfData, zoomLevel }: PDFViewerProps) {
12082 return ( ) => {
12183 container . removeEventListener ( 'click' , handleClick ) ;
12284 } ;
123- } , [ pdfText , handleTextClick , stopAndPlayFromIndex , isProcessing ] ) ;
85+ } , [ currDocText , handleTextClick , stopAndPlayFromIndex , isProcessing ] ) ;
12486
12587 useEffect ( ( ) => {
12688 /*
@@ -133,25 +95,27 @@ export function PDFViewer({ pdfData, zoomLevel }: PDFViewerProps) {
13395 * - highlightPattern: Function from context that could change
13496 * - clearHighlights: Function from context that could change
13597 */
98+ if ( ! currDocText ) return ;
99+
136100 const highlightTimeout = setTimeout ( ( ) => {
137101 if ( containerRef . current ) {
138- highlightPattern ( pdfText , currentSentence || '' , containerRef as RefObject < HTMLDivElement > ) ;
102+ highlightPattern ( currDocText , currentSentence || '' , containerRef as RefObject < HTMLDivElement > ) ;
139103 }
140- } , 100 ) ;
104+ } , 200 ) ;
141105
142106 return ( ) => {
143107 clearTimeout ( highlightTimeout ) ;
144108 clearHighlights ( ) ;
145109 } ;
146- } , [ pdfText , currentSentence , highlightPattern , clearHighlights ] ) ;
110+ } , [ currDocText , currentSentence , highlightPattern , clearHighlights ] ) ;
147111
148112 // Add scale calculation function
149- const calculateScale = ( pageWidth : number = 595 ) => { // 595 is default PDF width in points
113+ const calculateScale = useCallback ( ( pageWidth = 595 ) : number => {
150114 const margin = 24 ; // 24px padding on each side
151115 const targetWidth = containerWidth - margin ;
152116 const baseScale = targetWidth / pageWidth ;
153117 return baseScale * ( zoomLevel / 100 ) ;
154- } ;
118+ } , [ containerWidth , zoomLevel ] ) ;
155119
156120 // Add resize observer effect
157121 useEffect ( ( ) => {
@@ -168,48 +132,34 @@ export function PDFViewer({ pdfData, zoomLevel }: PDFViewerProps) {
168132 return ( ) => observer . disconnect ( ) ;
169133 } , [ ] ) ;
170134
171- function onDocumentLoadSuccess ( { numPages } : { numPages : number } ) : void {
172- setNumPages ( numPages ) ;
173- }
174-
175135 return (
176- < div
177- ref = { containerRef }
178- className = "flex flex-col items-center overflow-auto max-h-[calc(100vh-100px)] w-full px-6"
179- style = { { WebkitTapHighlightColor : 'transparent' } }
180- >
181- { loadingError ? (
182- < div className = "text-red-500 mb-4" > { loadingError } </ div >
183- ) : null }
136+ < div ref = { containerRef } className = "flex flex-col items-center overflow-auto max-h-[calc(100vh-100px)] w-full px-6" >
184137 < Document
185138 loading = { < PDFSkeleton /> }
186139 noData = { < PDFSkeleton /> }
187- file = { pdfDataUrl }
188- onLoadSuccess = { onDocumentLoadSuccess }
140+ file = { currDocURL }
141+ onLoadSuccess = { ( pdf ) => {
142+ onDocumentLoadSuccess ( pdf ) ;
143+ //handlePageChange(1); // Load first page text
144+ } }
189145 className = "flex flex-col items-center m-0"
190146 >
191- { Array . from (
192- new Array ( numPages ) ,
193- ( el , index ) => (
194- < div key = { `page_${ index + 1 } ` } >
195- < div className = "bg-offbase my-4 px-2 py-0.5 rounded-full w-fit" >
196- < p className = "text-xs" >
197- { index + 1 } / { numPages }
198- </ p >
199- </ div >
200- < div className = "flex justify-center" >
201- < Page
202- pageNumber = { index + 1 }
203- renderAnnotationLayer = { true }
204- renderTextLayer = { true }
205- className = "shadow-lg"
206- scale = { calculateScale ( ) }
207- />
208- </ div >
209- </ div >
210- ) ,
211- ) }
147+ < div >
148+ < div className = "flex justify-center" >
149+ < Page
150+ pageNumber = { currDocPage }
151+ renderAnnotationLayer = { true }
152+ renderTextLayer = { true }
153+ className = "shadow-lg"
154+ scale = { calculateScale ( ) }
155+ />
156+ </ div >
157+ </ div >
212158 </ Document >
159+ < TTSPlayer
160+ currentPage = { currDocPage }
161+ numPages = { currDocPages }
162+ />
213163 </ div >
214164 ) ;
215165}
0 commit comments