Skip to content

Commit 0ce14f3

Browse files
committed
Refactors + epub location fixes
1 parent 9ae6dd9 commit 0ce14f3

File tree

9 files changed

+237
-203
lines changed

9 files changed

+237
-203
lines changed

src/app/api/nlp/route.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { NextRequest, NextResponse } from 'next/server';
22
import nlp from 'compromise';
33

4-
const MAX_BLOCK_LENGTH = 350;
4+
const MAX_BLOCK_LENGTH = 300;
55

66
const preprocessSentenceForAudio = (text: string): string => {
77
return text

src/components/EPUBViewer.tsx

Lines changed: 42 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'use client';
22

3-
import { useEffect, useRef, useCallback, useState, useMemo } from 'react';
3+
import { useEffect, useRef, useCallback, useMemo } from 'react';
44
import { useParams } from 'next/navigation';
55
import dynamic from 'next/dynamic';
66
import { useEPUB } from '@/contexts/EPUBContext';
@@ -24,18 +24,29 @@ interface EPUBViewerProps {
2424
export function EPUBViewer({ className = '' }: EPUBViewerProps) {
2525
const { id } = useParams();
2626
const { currDocData, currDocName, currDocPage, extractPageText } = useEPUB();
27-
const { setEPUBPageInChapter, registerLocationChangeHandler } = useTTS();
27+
const { skipToLocation, registerLocationChangeHandler, setIsEPUB } = useTTS();
2828
const { epubTheme } = useConfig();
2929
const bookRef = useRef<Book | null>(null);
3030
const rendition = useRef<Rendition | undefined>(undefined);
3131
const toc = useRef<NavItem[]>([]);
3232
const locationRef = useRef<string | number>(currDocPage);
33-
const [initialPrevLocLoad, setInitialPrevLocLoad] = useState(false);
3433
const { updateTheme } = useEPUBTheme(epubTheme, rendition.current);
3534

36-
const handleLocationChanged = useCallback((location: string | number, initial = false) => {
35+
const isEPUBSetOnce = useRef(false);
36+
const handleLocationChanged = useCallback((location: string | number) => {
37+
// Set the EPUB flag once the location changes
38+
if (!isEPUBSetOnce.current) {
39+
setIsEPUB(true);
40+
isEPUBSetOnce.current = true;
41+
42+
rendition.current?.display(location.toString());
43+
44+
return;
45+
}
46+
3747
if (!bookRef.current?.isOpen || !rendition.current) return;
38-
// Handle special 'next' and 'prev' cases, which
48+
49+
// Handle special 'next' and 'prev' cases
3950
if (location === 'next' && rendition.current) {
4051
rendition.current.next();
4152
return;
@@ -45,33 +56,18 @@ export function EPUBViewer({ className = '' }: EPUBViewerProps) {
4556
return;
4657
}
4758

48-
49-
const { displayed, href } = rendition.current.location.start;
50-
const chapter = toc.current.find((item) => item.href === href);
51-
52-
console.log('Displayed:', displayed, 'Chapter:', chapter);
53-
54-
if (locationRef.current !== 1) {
55-
// Save the location to IndexedDB
56-
if (id) {
57-
console.log('Saving location:', location);
58-
setLastDocumentLocation(id as string, location.toString());
59-
}
59+
// Save the location to IndexedDB if not initial
60+
if (id && locationRef.current !== 1) {
61+
console.log('Saving location:', location);
62+
setLastDocumentLocation(id as string, location.toString());
6063
}
6164

62-
setEPUBPageInChapter(displayed.page, displayed.total, chapter?.label || '');
65+
skipToLocation(location);
6366

64-
// Add a small delay for initial load to ensure rendition is ready
65-
if (initial) {
66-
rendition.current.display(location.toString()).then(() => {
67-
setInitialPrevLocLoad(true);
68-
});
69-
} else {
70-
locationRef.current = location;
71-
extractPageText(bookRef.current, rendition.current);
72-
}
67+
locationRef.current = location;
68+
extractPageText(bookRef.current, rendition.current);
7369

74-
}, [id, setEPUBPageInChapter, extractPageText]);
70+
}, [id, skipToLocation, extractPageText, setIsEPUB]);
7571

7672
// Replace the debounced text extraction with a proper implementation using useMemo
7773
const debouncedExtractText = useMemo(() => {
@@ -86,27 +82,27 @@ export function EPUBViewer({ className = '' }: EPUBViewerProps) {
8682

8783
// Load the initial location and setup resize handler
8884
useEffect(() => {
89-
if (bookRef.current && rendition.current) {
90-
extractPageText(bookRef.current, rendition.current);
91-
92-
// Add resize observer
93-
const resizeObserver = new ResizeObserver(() => {
94-
if (bookRef.current && rendition.current) {
95-
debouncedExtractText(bookRef.current, rendition.current);
96-
}
97-
});
98-
99-
// Observe the container element
100-
const container = document.querySelector('.epub-container');
101-
if (container) {
102-
resizeObserver.observe(container);
85+
if (!bookRef.current || !rendition.current || isEPUBSetOnce.current) return;
86+
87+
extractPageText(bookRef.current, rendition.current);
88+
89+
// Add resize observer
90+
const resizeObserver = new ResizeObserver(() => {
91+
if (bookRef.current && rendition.current) {
92+
debouncedExtractText(bookRef.current, rendition.current);
10393
}
94+
});
10495

105-
return () => {
106-
resizeObserver.disconnect();
107-
};
96+
// Observe the container element
97+
const container = document.querySelector('.epub-container');
98+
if (container) {
99+
resizeObserver.observe(container);
108100
}
109-
}, [extractPageText, debouncedExtractText, initialPrevLocLoad]);
101+
102+
return () => {
103+
resizeObserver.disconnect();
104+
};
105+
}, [extractPageText, debouncedExtractText]);
110106

111107
// Register the location change handler
112108
useEffect(() => {

src/components/PDFViewer.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ export function PDFViewer({ zoomLevel }: PDFViewerProps) {
2727
const {
2828
currentSentence,
2929
stopAndPlayFromIndex,
30-
isProcessing
30+
isProcessing,
3131
} = useTTS();
3232

3333
// PDF context

src/components/player/Navigator.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,16 @@
22

33
import { Button } from '@headlessui/react';
44

5-
export const Navigator = ({ currentPage, numPages, skipToPage }: {
5+
export const Navigator = ({ currentPage, numPages, skipToLocation }: {
66
currentPage: number;
77
numPages: number | undefined;
8-
skipToPage: (page: number) => void;
8+
skipToLocation: (location: string | number) => void;
99
}) => {
1010
return (
1111
<div className="flex items-center space-x-1">
1212
{/* Page back */}
1313
<Button
14-
onClick={() => skipToPage(currentPage - 1)}
14+
onClick={() => skipToLocation(currentPage - 1)}
1515
disabled={currentPage <= 1}
1616
className="relative p-2 rounded-full text-foreground hover:bg-offbase data-[hover]:bg-offbase data-[active]:bg-offbase/80 transition-colors duration-200 focus:outline-none disabled:opacity-50"
1717
aria-label="Previous page"
@@ -30,7 +30,7 @@ export const Navigator = ({ currentPage, numPages, skipToPage }: {
3030

3131
{/* Page forward */}
3232
<Button
33-
onClick={() => skipToPage(currentPage + 1)}
33+
onClick={() => skipToLocation(currentPage + 1)}
3434
disabled={currentPage >= (numPages || 1)}
3535
className="relative p-2 rounded-full text-foreground hover:bg-offbase data-[hover]:bg-offbase data-[active]:bg-offbase/80 transition-colors duration-200 focus:outline-none disabled:opacity-50"
3636
aria-label="Next page"

src/components/player/TTSPlayer.tsx

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ export default function TTSPlayer({ currentPage, numPages }: {
2626
setSpeedAndRestart,
2727
setVoiceAndRestart,
2828
availableVoices,
29-
skipToPage,
29+
skipToLocation,
3030
} = useTTS();
3131

3232
return (
@@ -36,8 +36,13 @@ export default function TTSPlayer({ currentPage, numPages }: {
3636
<SpeedControl setSpeedAndRestart={setSpeedAndRestart} />
3737

3838
{/* Page Navigation */}
39-
{currentPage && numPages
40-
&& <Navigator currentPage={currentPage} numPages={numPages} skipToPage={skipToPage} />}
39+
{currentPage && numPages && (
40+
<Navigator
41+
currentPage={currentPage}
42+
numPages={numPages}
43+
skipToLocation={skipToLocation}
44+
/>
45+
)}
4146

4247
{/* Playback Controls */}
4348
<Button

src/contexts/ConfigContext.tsx

Lines changed: 34 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,34 +3,43 @@
33
import { createContext, useContext, useEffect, useState, ReactNode } from 'react';
44
import { getItem, indexedDBService, setItem } from '@/utils/indexedDB';
55

6+
/** Represents the possible view types for document display */
67
export type ViewType = 'single' | 'dual' | 'scroll';
7-
interface ConfigContextType {
8+
9+
/** Configuration values for the application */
10+
type ConfigValues = {
811
apiKey: string;
912
baseUrl: string;
1013
viewType: ViewType;
1114
voiceSpeed: number;
1215
voice: string;
1316
skipBlank: boolean;
14-
epubTheme: boolean; // Add this line
15-
updateConfig: (newConfig: Partial<{ apiKey: string; baseUrl: string; viewType: ViewType }>) => Promise<void>;
16-
updateConfigKey: <K extends keyof ConfigValues>(key: K, value: ConfigValues[K]) => Promise<void>;
17-
isLoading: boolean;
18-
isDBReady: boolean;
19-
}
17+
epubTheme: boolean;
18+
};
2019

21-
// Add this type to help with type safety
22-
type ConfigValues = {
20+
/** Interface defining the configuration context shape and functionality */
21+
interface ConfigContextType {
2322
apiKey: string;
2423
baseUrl: string;
2524
viewType: ViewType;
2625
voiceSpeed: number;
2726
voice: string;
2827
skipBlank: boolean;
29-
epubTheme: boolean; // Add this line
30-
};
28+
epubTheme: boolean;
29+
updateConfig: (newConfig: Partial<{ apiKey: string; baseUrl: string; viewType: ViewType }>) => Promise<void>;
30+
updateConfigKey: <K extends keyof ConfigValues>(key: K, value: ConfigValues[K]) => Promise<void>;
31+
isLoading: boolean;
32+
isDBReady: boolean;
33+
}
3134

3235
const ConfigContext = createContext<ConfigContextType | undefined>(undefined);
3336

37+
/**
38+
* Provider component for application configuration
39+
* Manages global configuration state and persistence
40+
* @param {Object} props - Component props
41+
* @param {ReactNode} props.children - Child components to be wrapped by the provider
42+
*/
3443
export function ConfigProvider({ children }: { children: ReactNode }) {
3544
// Config state
3645
const [apiKey, setApiKey] = useState<string>('');
@@ -108,6 +117,10 @@ export function ConfigProvider({ children }: { children: ReactNode }) {
108117
initializeDB();
109118
}, []);
110119

120+
/**
121+
* Updates multiple configuration values simultaneously
122+
* @param {Partial<{apiKey: string; baseUrl: string}>} newConfig - Object containing new config values
123+
*/
111124
const updateConfig = async (newConfig: Partial<{ apiKey: string; baseUrl: string }>) => {
112125
try {
113126
if (newConfig.apiKey !== undefined) {
@@ -124,6 +137,11 @@ export function ConfigProvider({ children }: { children: ReactNode }) {
124137
}
125138
};
126139

140+
/**
141+
* Updates a single configuration value by key
142+
* @param {K} key - The configuration key to update
143+
* @param {ConfigValues[K]} value - The new value for the configuration
144+
*/
127145
const updateConfigKey = async <K extends keyof ConfigValues>(key: K, value: ConfigValues[K]) => {
128146
try {
129147
await setItem(key, value.toString());
@@ -175,6 +193,11 @@ export function ConfigProvider({ children }: { children: ReactNode }) {
175193
);
176194
}
177195

196+
/**
197+
* Custom hook to consume the configuration context
198+
* @returns {ConfigContextType} The configuration context value
199+
* @throws {Error} When used outside of ConfigProvider
200+
*/
178201
export function useConfig() {
179202
const context = useContext(ConfigContext);
180203
if (context === undefined) {

src/contexts/EPUBContext.tsx

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,12 @@ interface EPUBContextType {
2626

2727
const EPUBContext = createContext<EPUBContextType | undefined>(undefined);
2828

29+
/**
30+
* Provider component for EPUB functionality
31+
* Manages the state and operations for EPUB document handling
32+
* @param {Object} props - Component props
33+
* @param {ReactNode} props.children - Child components to be wrapped by the provider
34+
*/
2935
export function EPUBProvider({ children }: { children: ReactNode }) {
3036
const { setText: setTTSText, currDocPage, currDocPages, setCurrDocPages, stop } = useTTS();
3137

@@ -35,7 +41,7 @@ export function EPUBProvider({ children }: { children: ReactNode }) {
3541
const [currDocText, setCurrDocText] = useState<string>();
3642

3743
/**
38-
* Clears the current document state
44+
* Clears all current document state and stops any active TTS
3945
*/
4046
const clearCurrDoc = useCallback(() => {
4147
setCurrDocData(undefined);
@@ -46,7 +52,9 @@ export function EPUBProvider({ children }: { children: ReactNode }) {
4652
}, [setCurrDocPages, stop]);
4753

4854
/**
49-
* Sets the current document based on its ID
55+
* Sets the current document based on its ID by fetching from IndexedDB
56+
* @param {string} id - The unique identifier of the document
57+
* @throws {Error} When document data is empty or retrieval fails
5058
*/
5159
const setCurrentDocument = useCallback(async (id: string): Promise<void> => {
5260
try {
@@ -73,6 +81,9 @@ export function EPUBProvider({ children }: { children: ReactNode }) {
7381

7482
/**
7583
* Extracts text content from the current EPUB page/location
84+
* @param {Book} book - The EPUB.js Book instance
85+
* @param {Rendition} rendition - The EPUB.js Rendition instance
86+
* @returns {Promise<string>} The extracted text content
7687
*/
7788
const extractPageText = useCallback(async (book: Book, rendition: Rendition): Promise<string> => {
7889
try {
@@ -127,7 +138,8 @@ export function EPUBProvider({ children }: { children: ReactNode }) {
127138

128139
/**
129140
* Custom hook to consume the EPUB context
130-
* Ensures the context is used within a provider
141+
* @returns {EPUBContextType} The EPUB context value
142+
* @throws {Error} When used outside of EPUBProvider
131143
*/
132144
export function useEPUB() {
133145
const context = useContext(EPUBContext);

0 commit comments

Comments
 (0)