Save last EPUB location
This commit is contained in:
parent
02595147fa
commit
e8008b68ab
7 changed files with 150 additions and 40 deletions
|
|
@ -9,16 +9,21 @@ import { EPUBViewer } from '@/components/EPUBViewer';
|
||||||
import { Button } from '@headlessui/react';
|
import { Button } from '@headlessui/react';
|
||||||
import { DocumentSettings } from '@/components/DocumentSettings';
|
import { DocumentSettings } from '@/components/DocumentSettings';
|
||||||
import { SettingsIcon } from '@/components/icons/Icons';
|
import { SettingsIcon } from '@/components/icons/Icons';
|
||||||
|
import { useTTS } from "@/contexts/TTSContext";
|
||||||
|
|
||||||
export default function EPUBPage() {
|
export default function EPUBPage() {
|
||||||
const { id } = useParams();
|
const { id } = useParams();
|
||||||
const { setCurrentDocument, currDocName, clearCurrDoc } = useEPUB();
|
const { setCurrentDocument, currDocName, clearCurrDoc } = useEPUB();
|
||||||
|
const { stop } = useTTS();
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
const [isLoading, setIsLoading] = useState(true);
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
const [isSettingsOpen, setIsSettingsOpen] = useState(false);
|
const [isSettingsOpen, setIsSettingsOpen] = useState(false);
|
||||||
|
|
||||||
const loadDocument = useCallback(async () => {
|
const loadDocument = useCallback(async () => {
|
||||||
if (!isLoading) return;
|
if (!isLoading) return;
|
||||||
|
console.log('Loading new epub (from page.tsx)');
|
||||||
|
stop(); // Reset TTS when loading new document
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (!id) {
|
if (!id) {
|
||||||
setError('Document not found');
|
setError('Document not found');
|
||||||
|
|
@ -31,7 +36,7 @@ export default function EPUBPage() {
|
||||||
} finally {
|
} finally {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
}
|
}
|
||||||
}, [isLoading, id, setCurrentDocument]);
|
}, [isLoading, id, setCurrentDocument, stop]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadDocument();
|
loadDocument();
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ export default function PDFViewerPage() {
|
||||||
const loadDocument = useCallback(async () => {
|
const loadDocument = useCallback(async () => {
|
||||||
if (!isLoading) return; // Prevent calls when not loading new doc
|
if (!isLoading) return; // Prevent calls when not loading new doc
|
||||||
console.log('Loading new document (from page.tsx)');
|
console.log('Loading new document (from page.tsx)');
|
||||||
|
stop(); // Reset TTS when loading new document
|
||||||
try {
|
try {
|
||||||
if (!id) {
|
if (!id) {
|
||||||
setError('Document not found');
|
setError('Document not found');
|
||||||
|
|
@ -44,7 +45,7 @@ export default function PDFViewerPage() {
|
||||||
} finally {
|
} finally {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
}
|
}
|
||||||
}, [isLoading, id, setCurrentDocument]);
|
}, [isLoading, id, setCurrentDocument, stop]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadDocument();
|
loadDocument();
|
||||||
|
|
@ -59,7 +60,7 @@ export default function PDFViewerPage() {
|
||||||
<p className="text-red-500 mb-4">{error}</p>
|
<p className="text-red-500 mb-4">{error}</p>
|
||||||
<Link
|
<Link
|
||||||
href="/"
|
href="/"
|
||||||
onClick={() => {clearCurrDoc(); stop();}}
|
onClick={() => {clearCurrDoc();}}
|
||||||
className="inline-flex items-center px-3 py-1 bg-base text-foreground rounded-lg hover:bg-offbase transition-colors"
|
className="inline-flex items-center px-3 py-1 bg-base text-foreground rounded-lg hover:bg-offbase transition-colors"
|
||||||
>
|
>
|
||||||
<svg className="w-4 h-4 mr-2 text-muted" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<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() {
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Link
|
<Link
|
||||||
href="/"
|
href="/"
|
||||||
onClick={() => {clearCurrDoc(); stop();}}
|
onClick={() => {clearCurrDoc();}}
|
||||||
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]"
|
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]"
|
||||||
>
|
>
|
||||||
<svg className="w-4 h-4 mr-2" fill="currentColor" stroke="currentColor" viewBox="0 0 24 24">
|
<svg className="w-4 h-4 mr-2" fill="currentColor" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,13 @@
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { useEffect, useRef, useCallback } from 'react';
|
import { useEffect, useRef, useCallback } from 'react';
|
||||||
|
import { useParams } from 'next/navigation';
|
||||||
import dynamic from 'next/dynamic';
|
import dynamic from 'next/dynamic';
|
||||||
import { useEPUB } from '@/contexts/EPUBContext';
|
import { useEPUB } from '@/contexts/EPUBContext';
|
||||||
import { useTTS } from '@/contexts/TTSContext';
|
import { useTTS } from '@/contexts/TTSContext';
|
||||||
import { DocumentSkeleton } from '@/components/DocumentSkeleton';
|
import { DocumentSkeleton } from '@/components/DocumentSkeleton';
|
||||||
import TTSPlayer from '@/components/player/TTSPlayer';
|
import TTSPlayer from '@/components/player/TTSPlayer';
|
||||||
|
import { setLastDocumentLocation } from '@/utils/indexedDB';
|
||||||
import type { Rendition, Book, NavItem } from 'epubjs';
|
import type { Rendition, Book, NavItem } from 'epubjs';
|
||||||
|
|
||||||
const ReactReader = dynamic(() => import('react-reader').then(mod => mod.ReactReader), {
|
const ReactReader = dynamic(() => import('react-reader').then(mod => mod.ReactReader), {
|
||||||
|
|
@ -18,6 +20,7 @@ interface EPUBViewerProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function EPUBViewer({ className = '' }: EPUBViewerProps) {
|
export function EPUBViewer({ className = '' }: EPUBViewerProps) {
|
||||||
|
const { id } = useParams();
|
||||||
const { currDocData, currDocName, currDocPage, extractPageText } = useEPUB();
|
const { currDocData, currDocName, currDocPage, extractPageText } = useEPUB();
|
||||||
const { setEPUBPageInChapter, registerLocationChangeHandler } = useTTS();
|
const { setEPUBPageInChapter, registerLocationChangeHandler } = useTTS();
|
||||||
const bookRef = useRef<Book | null>(null);
|
const bookRef = useRef<Book | null>(null);
|
||||||
|
|
@ -25,6 +28,19 @@ export function EPUBViewer({ className = '' }: EPUBViewerProps) {
|
||||||
const toc = useRef<NavItem[]>([]);
|
const toc = useRef<NavItem[]>([]);
|
||||||
const locationRef = useRef<string | number>(currDocPage);
|
const locationRef = useRef<string | number>(currDocPage);
|
||||||
|
|
||||||
|
// Load the last location when component mounts
|
||||||
|
// useEffect(() => {
|
||||||
|
// const loadLastLocation = async () => {
|
||||||
|
// if (id) {
|
||||||
|
// const lastLocation = await getLastDocumentLocation(id as string);
|
||||||
|
// if (lastLocation) {
|
||||||
|
// locationRef.current = lastLocation;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
// loadLastLocation();
|
||||||
|
// }, [id]);
|
||||||
|
|
||||||
const handleLocationChanged = useCallback((location: string | number) => {
|
const handleLocationChanged = useCallback((location: string | number) => {
|
||||||
// Handle special 'next' and 'prev' cases
|
// Handle special 'next' and 'prev' cases
|
||||||
if (location === 'next' && rendition.current) {
|
if (location === 'next' && rendition.current) {
|
||||||
|
|
@ -42,12 +58,21 @@ export function EPUBViewer({ className = '' }: EPUBViewerProps) {
|
||||||
|
|
||||||
console.log('Displayed:', displayed, 'Chapter:', chapter);
|
console.log('Displayed:', displayed, 'Chapter:', chapter);
|
||||||
|
|
||||||
locationRef.current = location;
|
|
||||||
setEPUBPageInChapter(displayed.page, displayed.total, chapter?.label || '');
|
|
||||||
|
|
||||||
|
if (locationRef.current !== 1) {
|
||||||
|
// Save the location to IndexedDB
|
||||||
|
if (id) {
|
||||||
|
console.log('Saving location:', location);
|
||||||
|
setLastDocumentLocation(id as string, location.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
locationRef.current = location;
|
||||||
|
|
||||||
|
setEPUBPageInChapter(displayed.page, displayed.total, chapter?.label || '');
|
||||||
extractPageText(bookRef.current, rendition.current);
|
extractPageText(bookRef.current, rendition.current);
|
||||||
}
|
}
|
||||||
}, [setEPUBPageInChapter, extractPageText]);
|
}, [id, setEPUBPageInChapter, extractPageText]);
|
||||||
|
|
||||||
// Register the location change handler
|
// Register the location change handler
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ interface EPUBContextType {
|
||||||
currDocData: ArrayBuffer | undefined;
|
currDocData: ArrayBuffer | undefined;
|
||||||
currDocName: string | undefined;
|
currDocName: string | undefined;
|
||||||
currDocPages: number | undefined;
|
currDocPages: number | undefined;
|
||||||
currDocPage: number;
|
currDocPage: number | string;
|
||||||
currDocText: string | undefined;
|
currDocText: string | undefined;
|
||||||
setCurrentDocument: (id: string) => Promise<void>;
|
setCurrentDocument: (id: string) => Promise<void>;
|
||||||
clearCurrDoc: () => void;
|
clearCurrDoc: () => void;
|
||||||
|
|
@ -28,7 +28,7 @@ interface EPUBContextType {
|
||||||
const EPUBContext = createContext<EPUBContextType | undefined>(undefined);
|
const EPUBContext = createContext<EPUBContextType | undefined>(undefined);
|
||||||
|
|
||||||
export function EPUBProvider({ children }: { children: ReactNode }) {
|
export function EPUBProvider({ children }: { children: ReactNode }) {
|
||||||
const { setText: setTTSText, stop, currDocPage, currDocPages, setCurrDocPages } = useTTS();
|
const { setText: setTTSText, currDocPage, currDocPages, setCurrDocPages } = useTTS();
|
||||||
|
|
||||||
// Current document state
|
// Current document state
|
||||||
const [currDocData, setCurrDocData] = useState<ArrayBuffer>();
|
const [currDocData, setCurrDocData] = useState<ArrayBuffer>();
|
||||||
|
|
@ -51,8 +51,7 @@ export function EPUBProvider({ children }: { children: ReactNode }) {
|
||||||
setCurrDocName(undefined);
|
setCurrDocName(undefined);
|
||||||
setCurrDocText(undefined);
|
setCurrDocText(undefined);
|
||||||
setCurrDocPages(undefined);
|
setCurrDocPages(undefined);
|
||||||
stop();
|
}, [setCurrDocPages]);
|
||||||
}, [setCurrDocPages, stop]);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the current document based on its ID
|
* Sets the current document based on its ID
|
||||||
|
|
|
||||||
|
|
@ -69,7 +69,7 @@ const PDFContext = createContext<PDFContextType | undefined>(undefined);
|
||||||
* Handles document loading, text processing, and integration with TTS.
|
* Handles document loading, text processing, and integration with TTS.
|
||||||
*/
|
*/
|
||||||
export function PDFProvider({ children }: { children: ReactNode }) {
|
export function PDFProvider({ children }: { children: ReactNode }) {
|
||||||
const { setText: setTTSText, currDocPage, currDocPages, setCurrDocPages } = useTTS();
|
const { setText: setTTSText, stop, currDocPageNumber: currDocPage, currDocPages, setCurrDocPages } = useTTS();
|
||||||
|
|
||||||
// Current document state
|
// Current document state
|
||||||
const [currDocURL, setCurrDocURL] = useState<string>();
|
const [currDocURL, setCurrDocURL] = useState<string>();
|
||||||
|
|
@ -136,8 +136,8 @@ export function PDFProvider({ children }: { children: ReactNode }) {
|
||||||
setCurrDocURL(undefined);
|
setCurrDocURL(undefined);
|
||||||
setCurrDocText(undefined);
|
setCurrDocText(undefined);
|
||||||
setCurrDocPages(undefined);
|
setCurrDocPages(undefined);
|
||||||
setTTSText('');
|
stop();
|
||||||
}, [setCurrDocPages, setTTSText]);
|
}, [setCurrDocPages, stop]);
|
||||||
|
|
||||||
// Context value memoization
|
// Context value memoization
|
||||||
const contextValue = useMemo(
|
const contextValue = useMemo(
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ import React, {
|
||||||
import OpenAI from 'openai';
|
import OpenAI from 'openai';
|
||||||
import { Howl } from 'howler';
|
import { Howl } from 'howler';
|
||||||
import toast from 'react-hot-toast';
|
import toast from 'react-hot-toast';
|
||||||
|
import { useParams } from 'next/navigation';
|
||||||
|
|
||||||
import { useConfig } from '@/contexts/ConfigContext';
|
import { useConfig } from '@/contexts/ConfigContext';
|
||||||
import { splitIntoSentences, preprocessSentenceForAudio } from '@/utils/nlp';
|
import { splitIntoSentences, preprocessSentenceForAudio } from '@/utils/nlp';
|
||||||
|
|
@ -34,6 +35,7 @@ import { useAudioCache } from '@/hooks/audio/useAudioCache';
|
||||||
import { useVoiceManagement } from '@/hooks/audio/useVoiceManagement';
|
import { useVoiceManagement } from '@/hooks/audio/useVoiceManagement';
|
||||||
import { useMediaSession } from '@/hooks/audio/useMediaSession';
|
import { useMediaSession } from '@/hooks/audio/useMediaSession';
|
||||||
import { useAudioContext } from '@/hooks/audio/useAudioContext';
|
import { useAudioContext } from '@/hooks/audio/useAudioContext';
|
||||||
|
import { getLastDocumentLocation } from '@/utils/indexedDB';
|
||||||
|
|
||||||
// Media globals
|
// Media globals
|
||||||
declare global {
|
declare global {
|
||||||
|
|
@ -52,7 +54,8 @@ interface TTSContextType {
|
||||||
currentSentence: string;
|
currentSentence: string;
|
||||||
|
|
||||||
// Navigation
|
// Navigation
|
||||||
currDocPage: number;
|
currDocPage: string | number; // Change this to allow both types
|
||||||
|
currDocPageNumber: number; // For PDF
|
||||||
currDocPages: number | undefined;
|
currDocPages: number | undefined;
|
||||||
|
|
||||||
// Voice settings
|
// Voice settings
|
||||||
|
|
@ -110,6 +113,9 @@ export function TTSProvider({ children }: { children: React.ReactNode }) {
|
||||||
locationChangeHandlerRef.current = handler;
|
locationChangeHandlerRef.current = handler;
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
// Get document ID from URL params
|
||||||
|
const { id } = useParams();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* State Management
|
* State Management
|
||||||
*/
|
*/
|
||||||
|
|
@ -120,13 +126,15 @@ export function TTSProvider({ children }: { children: React.ReactNode }) {
|
||||||
const [isProcessing, setIsProcessing] = useState(false);
|
const [isProcessing, setIsProcessing] = useState(false);
|
||||||
const [speed, setSpeed] = useState(voiceSpeed);
|
const [speed, setSpeed] = useState(voiceSpeed);
|
||||||
const [voice, setVoice] = useState(configVoice);
|
const [voice, setVoice] = useState(configVoice);
|
||||||
const [currDocPage, setCurrDocPage] = useState<number>(1);
|
const [currDocPage, setCurrDocPage] = useState<string | number>(1);
|
||||||
const [currDocPages, setCurrDocPages] = useState<number>();
|
const [currDocPages, setCurrDocPages] = useState<number>();
|
||||||
const [nextPageLoading, setNextPageLoading] = useState(false);
|
const [nextPageLoading, setNextPageLoading] = useState(false);
|
||||||
|
|
||||||
// Add this state to track if we're in EPUB mode
|
// Add this state to track if we're in EPUB mode
|
||||||
const [isEPUB, setIsEPUB] = useState(false);
|
const [isEPUB, setIsEPUB] = useState(false);
|
||||||
|
|
||||||
|
const currDocPageNumber = (!isEPUB ? parseInt(currDocPage.toString()) : 1);
|
||||||
|
|
||||||
console.log('page:', currDocPage, 'pages:', currDocPages);
|
console.log('page:', currDocPage, 'pages:', currDocPages);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -137,8 +145,8 @@ export function TTSProvider({ children }: { children: React.ReactNode }) {
|
||||||
*/
|
*/
|
||||||
const incrementPage = useCallback((num = 1) => {
|
const incrementPage = useCallback((num = 1) => {
|
||||||
setNextPageLoading(true);
|
setNextPageLoading(true);
|
||||||
setCurrDocPage(currDocPage + num);
|
setCurrDocPage(currDocPageNumber + num);
|
||||||
}, [currDocPage]);
|
}, [currDocPageNumber]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the current text and splits it into sentences
|
* Sets the current text and splits it into sentences
|
||||||
|
|
@ -167,12 +175,12 @@ export function TTSProvider({ children }: { children: React.ReactNode }) {
|
||||||
position: 'top-center',
|
position: 'top-center',
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
} else if (currDocPage < currDocPages!) {
|
} else if (currDocPageNumber < currDocPages!) {
|
||||||
// For PDF, increment the page
|
// For PDF, increment the page
|
||||||
incrementPage();
|
incrementPage();
|
||||||
|
|
||||||
toast.success(`Skipping blank page ${currDocPage}`, {
|
toast.success(`Skipping blank page ${currDocPageNumber}`, {
|
||||||
id: `page-${currDocPage}`,
|
id: `page-${currDocPageNumber}`,
|
||||||
iconTheme: {
|
iconTheme: {
|
||||||
primary: 'var(--accent)',
|
primary: 'var(--accent)',
|
||||||
secondary: 'var(--background)',
|
secondary: 'var(--background)',
|
||||||
|
|
@ -190,7 +198,7 @@ export function TTSProvider({ children }: { children: React.ReactNode }) {
|
||||||
|
|
||||||
setSentences(newSentences);
|
setSentences(newSentences);
|
||||||
setNextPageLoading(false);
|
setNextPageLoading(false);
|
||||||
}, [isPlaying, skipBlank, currDocPage, currDocPages, incrementPage, isEPUB]);
|
}, [isPlaying, skipBlank, currDocPageNumber, currDocPages, incrementPage, isEPUB]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stops the current audio playback and clears the active Howl instance
|
* Stops the current audio playback and clears the active Howl instance
|
||||||
|
|
@ -299,8 +307,8 @@ export function TTSProvider({ children }: { children: React.ReactNode }) {
|
||||||
// For PDFs and other documents, check page bounds
|
// For PDFs and other documents, check page bounds
|
||||||
if (!isEPUB) {
|
if (!isEPUB) {
|
||||||
// Handle next/previous page transitions
|
// Handle next/previous page transitions
|
||||||
if ((nextIndex >= sentences.length && currDocPage < currDocPages!) ||
|
if ((nextIndex >= sentences.length && currDocPageNumber < currDocPages!) ||
|
||||||
(nextIndex < 0 && currDocPage > 1)) {
|
(nextIndex < 0 && currDocPageNumber > 1)) {
|
||||||
console.log('PDF: Advancing to next/prev page');
|
console.log('PDF: Advancing to next/prev page');
|
||||||
setCurrentIndex(0);
|
setCurrentIndex(0);
|
||||||
setSentences([]);
|
setSentences([]);
|
||||||
|
|
@ -309,12 +317,12 @@ export function TTSProvider({ children }: { children: React.ReactNode }) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle end of document (PDF only)
|
// Handle end of document (PDF only)
|
||||||
if (nextIndex >= sentences.length && currDocPage >= currDocPages!) {
|
if (nextIndex >= sentences.length && currDocPageNumber >= currDocPages!) {
|
||||||
console.log('PDF: Reached end of document');
|
console.log('PDF: Reached end of document');
|
||||||
setIsPlaying(false);
|
setIsPlaying(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [currentIndex, incrementPage, sentences, currDocPage, currDocPages, isEPUB]);
|
}, [currentIndex, incrementPage, sentences, currDocPageNumber, currDocPages, isEPUB]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Moves forward one sentence in the text
|
* Moves forward one sentence in the text
|
||||||
|
|
@ -637,6 +645,7 @@ export function TTSProvider({ children }: { children: React.ReactNode }) {
|
||||||
isProcessing,
|
isProcessing,
|
||||||
currentSentence: sentences[currentIndex] || '',
|
currentSentence: sentences[currentIndex] || '',
|
||||||
currDocPage,
|
currDocPage,
|
||||||
|
currDocPageNumber,
|
||||||
currDocPages,
|
currDocPages,
|
||||||
availableVoices,
|
availableVoices,
|
||||||
togglePlay,
|
togglePlay,
|
||||||
|
|
@ -657,6 +666,7 @@ export function TTSProvider({ children }: { children: React.ReactNode }) {
|
||||||
sentences,
|
sentences,
|
||||||
currentIndex,
|
currentIndex,
|
||||||
currDocPage,
|
currDocPage,
|
||||||
|
currDocPageNumber,
|
||||||
currDocPages,
|
currDocPages,
|
||||||
availableVoices,
|
availableVoices,
|
||||||
togglePlay,
|
togglePlay,
|
||||||
|
|
@ -680,6 +690,20 @@ export function TTSProvider({ children }: { children: React.ReactNode }) {
|
||||||
skipBackward,
|
skipBackward,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Load last location on mount for EPUB only
|
||||||
|
useEffect(() => {
|
||||||
|
if (id && isEPUB) {
|
||||||
|
getLastDocumentLocation(id as string).then(lastLocation => {
|
||||||
|
if (lastLocation) {
|
||||||
|
setCurrDocPage(lastLocation);
|
||||||
|
if (locationChangeHandlerRef.current) {
|
||||||
|
locationChangeHandlerRef.current(lastLocation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [id, isEPUB]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Renders the TTS context provider with its children
|
* Renders the TTS context provider with its children
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -174,20 +174,40 @@ class IndexedDBService {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
try {
|
try {
|
||||||
console.log('Removing document:', id);
|
console.log('Removing document:', id);
|
||||||
const transaction = this.db!.transaction([PDF_STORE_NAME], 'readwrite');
|
const locationKey = `lastLocation_${id}`;
|
||||||
const store = transaction.objectStore(PDF_STORE_NAME);
|
|
||||||
const request = store.delete(id);
|
|
||||||
|
|
||||||
request.onerror = (event) => {
|
// Create two transactions - one for document deletion, one for location cleanup
|
||||||
|
const docTransaction = this.db!.transaction([PDF_STORE_NAME], 'readwrite');
|
||||||
|
const configTransaction = this.db!.transaction([CONFIG_STORE_NAME], 'readwrite');
|
||||||
|
|
||||||
|
const docStore = docTransaction.objectStore(PDF_STORE_NAME);
|
||||||
|
const configStore = configTransaction.objectStore(CONFIG_STORE_NAME);
|
||||||
|
|
||||||
|
// Remove the document
|
||||||
|
const docRequest = docStore.delete(id);
|
||||||
|
// Remove any saved location
|
||||||
|
const locationRequest = configStore.delete(locationKey);
|
||||||
|
|
||||||
|
docRequest.onerror = (event) => {
|
||||||
const error = (event.target as IDBRequest).error;
|
const error = (event.target as IDBRequest).error;
|
||||||
console.error('Error removing document:', error);
|
console.error('Error removing document:', error);
|
||||||
reject(error);
|
reject(error);
|
||||||
};
|
};
|
||||||
|
|
||||||
transaction.oncomplete = () => {
|
locationRequest.onerror = (event) => {
|
||||||
console.log('Document removed successfully:', id);
|
console.warn('Error cleaning up location:', (event.target as IDBRequest).error);
|
||||||
resolve();
|
// Don't reject here, as document deletion is the primary operation
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Wait for both transactions to complete
|
||||||
|
Promise.all([
|
||||||
|
new Promise<void>((res) => { docTransaction.oncomplete = () => res(); }),
|
||||||
|
new Promise<void>((res) => { configTransaction.oncomplete = () => res(); })
|
||||||
|
]).then(() => {
|
||||||
|
console.log('Document and location data removed successfully:', id);
|
||||||
|
resolve();
|
||||||
|
}).catch(reject);
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error in removeDocument transaction:', error);
|
console.error('Error in removeDocument transaction:', error);
|
||||||
reject(error);
|
reject(error);
|
||||||
|
|
@ -303,18 +323,43 @@ class IndexedDBService {
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
try {
|
try {
|
||||||
const transaction = this.db!.transaction([EPUB_STORE_NAME], 'readwrite');
|
console.log('Removing EPUB document:', id);
|
||||||
const store = transaction.objectStore(EPUB_STORE_NAME);
|
const locationKey = `lastLocation_${id}`;
|
||||||
const request = store.delete(id);
|
|
||||||
|
|
||||||
request.onerror = (event) => {
|
// Create two transactions - one for document deletion, one for location cleanup
|
||||||
reject((event.target as IDBRequest).error);
|
const docTransaction = this.db!.transaction([EPUB_STORE_NAME], 'readwrite');
|
||||||
|
const configTransaction = this.db!.transaction([CONFIG_STORE_NAME], 'readwrite');
|
||||||
|
|
||||||
|
const docStore = docTransaction.objectStore(EPUB_STORE_NAME);
|
||||||
|
const configStore = configTransaction.objectStore(CONFIG_STORE_NAME);
|
||||||
|
|
||||||
|
// Remove the document
|
||||||
|
const docRequest = docStore.delete(id);
|
||||||
|
// Remove any saved location
|
||||||
|
const locationRequest = configStore.delete(locationKey);
|
||||||
|
|
||||||
|
docRequest.onerror = (event) => {
|
||||||
|
const error = (event.target as IDBRequest).error;
|
||||||
|
console.error('Error removing EPUB document:', error);
|
||||||
|
reject(error);
|
||||||
};
|
};
|
||||||
|
|
||||||
transaction.oncomplete = () => {
|
locationRequest.onerror = (event) => {
|
||||||
|
console.warn('Error cleaning up location:', (event.target as IDBRequest).error);
|
||||||
|
// Don't reject here, as document deletion is the primary operation
|
||||||
|
};
|
||||||
|
|
||||||
|
// Wait for both transactions to complete
|
||||||
|
Promise.all([
|
||||||
|
new Promise<void>((res) => { docTransaction.oncomplete = () => res(); }),
|
||||||
|
new Promise<void>((res) => { configTransaction.oncomplete = () => res(); })
|
||||||
|
]).then(() => {
|
||||||
|
console.log('EPUB document and location data removed successfully:', id);
|
||||||
resolve();
|
resolve();
|
||||||
};
|
}).catch(reject);
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.error('Error in removeEPUBDocument transaction:', error);
|
||||||
reject(error);
|
reject(error);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -456,3 +501,14 @@ export async function getItem(key: string): Promise<string | null> {
|
||||||
export async function setItem(key: string, value: string): Promise<void> {
|
export async function setItem(key: string, value: string): Promise<void> {
|
||||||
return indexedDBService.setConfigItem(key, value);
|
return indexedDBService.setConfigItem(key, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add these helper functions before the final export
|
||||||
|
export async function getLastDocumentLocation(docId: string): Promise<string | null> {
|
||||||
|
const key = `lastLocation_${docId}`;
|
||||||
|
return indexedDBService.getConfigItem(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function setLastDocumentLocation(docId: string, location: string): Promise<void> {
|
||||||
|
const key = `lastLocation_${docId}`;
|
||||||
|
return indexedDBService.setConfigItem(key, location);
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue