Skip to content

Commit e8008b6

Browse files
committed
Save last EPUB location
1 parent 0259514 commit e8008b6

File tree

7 files changed

+149
-39
lines changed

7 files changed

+149
-39
lines changed

src/app/epub/[id]/page.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,21 @@ import { EPUBViewer } from '@/components/EPUBViewer';
99
import { Button } from '@headlessui/react';
1010
import { DocumentSettings } from '@/components/DocumentSettings';
1111
import { SettingsIcon } from '@/components/icons/Icons';
12+
import { useTTS } from "@/contexts/TTSContext";
1213

1314
export default function EPUBPage() {
1415
const { id } = useParams();
1516
const { setCurrentDocument, currDocName, clearCurrDoc } = useEPUB();
17+
const { stop } = useTTS();
1618
const [error, setError] = useState<string | null>(null);
1719
const [isLoading, setIsLoading] = useState(true);
1820
const [isSettingsOpen, setIsSettingsOpen] = useState(false);
1921

2022
const loadDocument = useCallback(async () => {
2123
if (!isLoading) return;
24+
console.log('Loading new epub (from page.tsx)');
25+
stop(); // Reset TTS when loading new document
26+
2227
try {
2328
if (!id) {
2429
setError('Document not found');
@@ -31,7 +36,7 @@ export default function EPUBPage() {
3136
} finally {
3237
setIsLoading(false);
3338
}
34-
}, [isLoading, id, setCurrentDocument]);
39+
}, [isLoading, id, setCurrentDocument, stop]);
3540

3641
useEffect(() => {
3742
loadDocument();

src/app/pdf/[id]/page.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ export default function PDFViewerPage() {
3232
const loadDocument = useCallback(async () => {
3333
if (!isLoading) return; // Prevent calls when not loading new doc
3434
console.log('Loading new document (from page.tsx)');
35+
stop(); // Reset TTS when loading new document
3536
try {
3637
if (!id) {
3738
setError('Document not found');
@@ -44,7 +45,7 @@ export default function PDFViewerPage() {
4445
} finally {
4546
setIsLoading(false);
4647
}
47-
}, [isLoading, id, setCurrentDocument]);
48+
}, [isLoading, id, setCurrentDocument, stop]);
4849

4950
useEffect(() => {
5051
loadDocument();
@@ -59,7 +60,7 @@ export default function PDFViewerPage() {
5960
<p className="text-red-500 mb-4">{error}</p>
6061
<Link
6162
href="/"
62-
onClick={() => {clearCurrDoc(); stop();}}
63+
onClick={() => {clearCurrDoc();}}
6364
className="inline-flex items-center px-3 py-1 bg-base text-foreground rounded-lg hover:bg-offbase transition-colors"
6465
>
6566
<svg className="w-4 h-4 mr-2 text-muted" fill="none" stroke="currentColor" viewBox="0 0 24 24">
@@ -78,7 +79,7 @@ export default function PDFViewerPage() {
7879
<div className="flex items-center gap-2">
7980
<Link
8081
href="/"
81-
onClick={() => {clearCurrDoc(); stop();}}
82+
onClick={() => {clearCurrDoc();}}
8283
className="inline-flex items-center px-3 py-1 bg-base text-foreground rounded-lg hover:bg-offbase transform transition-transform duration-200 ease-in-out hover:scale-[1.02]"
8384
>
8485
<svg className="w-4 h-4 mr-2" fill="currentColor" stroke="currentColor" viewBox="0 0 24 24">

src/components/EPUBViewer.tsx

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
'use client';
22

33
import { useEffect, useRef, useCallback } from 'react';
4+
import { useParams } from 'next/navigation';
45
import dynamic from 'next/dynamic';
56
import { useEPUB } from '@/contexts/EPUBContext';
67
import { useTTS } from '@/contexts/TTSContext';
78
import { DocumentSkeleton } from '@/components/DocumentSkeleton';
89
import TTSPlayer from '@/components/player/TTSPlayer';
10+
import { setLastDocumentLocation } from '@/utils/indexedDB';
911
import type { Rendition, Book, NavItem } from 'epubjs';
1012

1113
const ReactReader = dynamic(() => import('react-reader').then(mod => mod.ReactReader), {
@@ -18,13 +20,27 @@ interface EPUBViewerProps {
1820
}
1921

2022
export function EPUBViewer({ className = '' }: EPUBViewerProps) {
23+
const { id } = useParams();
2124
const { currDocData, currDocName, currDocPage, extractPageText } = useEPUB();
2225
const { setEPUBPageInChapter, registerLocationChangeHandler } = useTTS();
2326
const bookRef = useRef<Book | null>(null);
2427
const rendition = useRef<Rendition | undefined>(undefined);
2528
const toc = useRef<NavItem[]>([]);
2629
const locationRef = useRef<string | number>(currDocPage);
2730

31+
// Load the last location when component mounts
32+
// useEffect(() => {
33+
// const loadLastLocation = async () => {
34+
// if (id) {
35+
// const lastLocation = await getLastDocumentLocation(id as string);
36+
// if (lastLocation) {
37+
// locationRef.current = lastLocation;
38+
// }
39+
// }
40+
// };
41+
// loadLastLocation();
42+
// }, [id]);
43+
2844
const handleLocationChanged = useCallback((location: string | number) => {
2945
// Handle special 'next' and 'prev' cases
3046
if (location === 'next' && rendition.current) {
@@ -42,12 +58,21 @@ export function EPUBViewer({ className = '' }: EPUBViewerProps) {
4258

4359
console.log('Displayed:', displayed, 'Chapter:', chapter);
4460

61+
62+
if (locationRef.current !== 1) {
63+
// Save the location to IndexedDB
64+
if (id) {
65+
console.log('Saving location:', location);
66+
setLastDocumentLocation(id as string, location.toString());
67+
}
68+
}
69+
4570
locationRef.current = location;
71+
4672
setEPUBPageInChapter(displayed.page, displayed.total, chapter?.label || '');
47-
4873
extractPageText(bookRef.current, rendition.current);
4974
}
50-
}, [setEPUBPageInChapter, extractPageText]);
75+
}, [id, setEPUBPageInChapter, extractPageText]);
5176

5277
// Register the location change handler
5378
useEffect(() => {

src/contexts/EPUBContext.tsx

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ interface EPUBContextType {
1717
currDocData: ArrayBuffer | undefined;
1818
currDocName: string | undefined;
1919
currDocPages: number | undefined;
20-
currDocPage: number;
20+
currDocPage: number | string;
2121
currDocText: string | undefined;
2222
setCurrentDocument: (id: string) => Promise<void>;
2323
clearCurrDoc: () => void;
@@ -28,7 +28,7 @@ interface EPUBContextType {
2828
const EPUBContext = createContext<EPUBContextType | undefined>(undefined);
2929

3030
export function EPUBProvider({ children }: { children: ReactNode }) {
31-
const { setText: setTTSText, stop, currDocPage, currDocPages, setCurrDocPages } = useTTS();
31+
const { setText: setTTSText, currDocPage, currDocPages, setCurrDocPages } = useTTS();
3232

3333
// Current document state
3434
const [currDocData, setCurrDocData] = useState<ArrayBuffer>();
@@ -51,8 +51,7 @@ export function EPUBProvider({ children }: { children: ReactNode }) {
5151
setCurrDocName(undefined);
5252
setCurrDocText(undefined);
5353
setCurrDocPages(undefined);
54-
stop();
55-
}, [setCurrDocPages, stop]);
54+
}, [setCurrDocPages]);
5655

5756
/**
5857
* Sets the current document based on its ID

src/contexts/PDFContext.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ const PDFContext = createContext<PDFContextType | undefined>(undefined);
6969
* Handles document loading, text processing, and integration with TTS.
7070
*/
7171
export function PDFProvider({ children }: { children: ReactNode }) {
72-
const { setText: setTTSText, currDocPage, currDocPages, setCurrDocPages } = useTTS();
72+
const { setText: setTTSText, stop, currDocPageNumber: currDocPage, currDocPages, setCurrDocPages } = useTTS();
7373

7474
// Current document state
7575
const [currDocURL, setCurrDocURL] = useState<string>();
@@ -136,8 +136,8 @@ export function PDFProvider({ children }: { children: ReactNode }) {
136136
setCurrDocURL(undefined);
137137
setCurrDocText(undefined);
138138
setCurrDocPages(undefined);
139-
setTTSText('');
140-
}, [setCurrDocPages, setTTSText]);
139+
stop();
140+
}, [setCurrDocPages, stop]);
141141

142142
// Context value memoization
143143
const contextValue = useMemo(

src/contexts/TTSContext.tsx

Lines changed: 36 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import React, {
2626
import OpenAI from 'openai';
2727
import { Howl } from 'howler';
2828
import toast from 'react-hot-toast';
29+
import { useParams } from 'next/navigation';
2930

3031
import { useConfig } from '@/contexts/ConfigContext';
3132
import { splitIntoSentences, preprocessSentenceForAudio } from '@/utils/nlp';
@@ -34,6 +35,7 @@ import { useAudioCache } from '@/hooks/audio/useAudioCache';
3435
import { useVoiceManagement } from '@/hooks/audio/useVoiceManagement';
3536
import { useMediaSession } from '@/hooks/audio/useMediaSession';
3637
import { useAudioContext } from '@/hooks/audio/useAudioContext';
38+
import { getLastDocumentLocation } from '@/utils/indexedDB';
3739

3840
// Media globals
3941
declare global {
@@ -52,7 +54,8 @@ interface TTSContextType {
5254
currentSentence: string;
5355

5456
// Navigation
55-
currDocPage: number;
57+
currDocPage: string | number; // Change this to allow both types
58+
currDocPageNumber: number; // For PDF
5659
currDocPages: number | undefined;
5760

5861
// Voice settings
@@ -110,6 +113,9 @@ export function TTSProvider({ children }: { children: React.ReactNode }) {
110113
locationChangeHandlerRef.current = handler;
111114
}, []);
112115

116+
// Get document ID from URL params
117+
const { id } = useParams();
118+
113119
/**
114120
* State Management
115121
*/
@@ -120,13 +126,15 @@ export function TTSProvider({ children }: { children: React.ReactNode }) {
120126
const [isProcessing, setIsProcessing] = useState(false);
121127
const [speed, setSpeed] = useState(voiceSpeed);
122128
const [voice, setVoice] = useState(configVoice);
123-
const [currDocPage, setCurrDocPage] = useState<number>(1);
129+
const [currDocPage, setCurrDocPage] = useState<string | number>(1);
124130
const [currDocPages, setCurrDocPages] = useState<number>();
125131
const [nextPageLoading, setNextPageLoading] = useState(false);
126132

127133
// Add this state to track if we're in EPUB mode
128134
const [isEPUB, setIsEPUB] = useState(false);
129135

136+
const currDocPageNumber = (!isEPUB ? parseInt(currDocPage.toString()) : 1);
137+
130138
console.log('page:', currDocPage, 'pages:', currDocPages);
131139

132140
/**
@@ -137,8 +145,8 @@ export function TTSProvider({ children }: { children: React.ReactNode }) {
137145
*/
138146
const incrementPage = useCallback((num = 1) => {
139147
setNextPageLoading(true);
140-
setCurrDocPage(currDocPage + num);
141-
}, [currDocPage]);
148+
setCurrDocPage(currDocPageNumber + num);
149+
}, [currDocPageNumber]);
142150

143151
/**
144152
* Sets the current text and splits it into sentences
@@ -167,12 +175,12 @@ export function TTSProvider({ children }: { children: React.ReactNode }) {
167175
position: 'top-center',
168176
});
169177
return;
170-
} else if (currDocPage < currDocPages!) {
178+
} else if (currDocPageNumber < currDocPages!) {
171179
// For PDF, increment the page
172180
incrementPage();
173181

174-
toast.success(`Skipping blank page ${currDocPage}`, {
175-
id: `page-${currDocPage}`,
182+
toast.success(`Skipping blank page ${currDocPageNumber}`, {
183+
id: `page-${currDocPageNumber}`,
176184
iconTheme: {
177185
primary: 'var(--accent)',
178186
secondary: 'var(--background)',
@@ -190,7 +198,7 @@ export function TTSProvider({ children }: { children: React.ReactNode }) {
190198

191199
setSentences(newSentences);
192200
setNextPageLoading(false);
193-
}, [isPlaying, skipBlank, currDocPage, currDocPages, incrementPage, isEPUB]);
201+
}, [isPlaying, skipBlank, currDocPageNumber, currDocPages, incrementPage, isEPUB]);
194202

195203
/**
196204
* Stops the current audio playback and clears the active Howl instance
@@ -299,8 +307,8 @@ export function TTSProvider({ children }: { children: React.ReactNode }) {
299307
// For PDFs and other documents, check page bounds
300308
if (!isEPUB) {
301309
// Handle next/previous page transitions
302-
if ((nextIndex >= sentences.length && currDocPage < currDocPages!) ||
303-
(nextIndex < 0 && currDocPage > 1)) {
310+
if ((nextIndex >= sentences.length && currDocPageNumber < currDocPages!) ||
311+
(nextIndex < 0 && currDocPageNumber > 1)) {
304312
console.log('PDF: Advancing to next/prev page');
305313
setCurrentIndex(0);
306314
setSentences([]);
@@ -309,12 +317,12 @@ export function TTSProvider({ children }: { children: React.ReactNode }) {
309317
}
310318

311319
// Handle end of document (PDF only)
312-
if (nextIndex >= sentences.length && currDocPage >= currDocPages!) {
320+
if (nextIndex >= sentences.length && currDocPageNumber >= currDocPages!) {
313321
console.log('PDF: Reached end of document');
314322
setIsPlaying(false);
315323
}
316324
}
317-
}, [currentIndex, incrementPage, sentences, currDocPage, currDocPages, isEPUB]);
325+
}, [currentIndex, incrementPage, sentences, currDocPageNumber, currDocPages, isEPUB]);
318326

319327
/**
320328
* Moves forward one sentence in the text
@@ -637,6 +645,7 @@ export function TTSProvider({ children }: { children: React.ReactNode }) {
637645
isProcessing,
638646
currentSentence: sentences[currentIndex] || '',
639647
currDocPage,
648+
currDocPageNumber,
640649
currDocPages,
641650
availableVoices,
642651
togglePlay,
@@ -657,6 +666,7 @@ export function TTSProvider({ children }: { children: React.ReactNode }) {
657666
sentences,
658667
currentIndex,
659668
currDocPage,
669+
currDocPageNumber,
660670
currDocPages,
661671
availableVoices,
662672
togglePlay,
@@ -680,6 +690,20 @@ export function TTSProvider({ children }: { children: React.ReactNode }) {
680690
skipBackward,
681691
});
682692

693+
// Load last location on mount for EPUB only
694+
useEffect(() => {
695+
if (id && isEPUB) {
696+
getLastDocumentLocation(id as string).then(lastLocation => {
697+
if (lastLocation) {
698+
setCurrDocPage(lastLocation);
699+
if (locationChangeHandlerRef.current) {
700+
locationChangeHandlerRef.current(lastLocation);
701+
}
702+
}
703+
});
704+
}
705+
}, [id, isEPUB]);
706+
683707
/**
684708
* Renders the TTS context provider with its children
685709
*

0 commit comments

Comments
 (0)