Working epub viewer
This commit is contained in:
parent
7d667b2114
commit
2fa91a2448
12 changed files with 882 additions and 303 deletions
773
package-lock.json
generated
773
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -24,6 +24,7 @@
|
||||||
"react-dropzone": "^14.3.5",
|
"react-dropzone": "^14.3.5",
|
||||||
"react-hot-toast": "^2.5.1",
|
"react-hot-toast": "^2.5.1",
|
||||||
"react-pdf": "^9.2.1",
|
"react-pdf": "^9.2.1",
|
||||||
|
"react-reader": "^2.0.12",
|
||||||
"string-similarity": "^4.0.4",
|
"string-similarity": "^4.0.4",
|
||||||
"uuid": "^11.0.5"
|
"uuid": "^11.0.5"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,54 @@
|
||||||
|
|
||||||
import { useParams } from "next/navigation";
|
import { useParams } from "next/navigation";
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
|
import { useEPUB } from '@/contexts/EPUBContext';
|
||||||
|
import { DocumentSkeleton } from '@/components/DocumentSkeleton';
|
||||||
|
import { EPUBViewer } from '@/components/EPUBViewer';
|
||||||
|
|
||||||
export default function EPUBPage() {
|
export default function EPUBPage() {
|
||||||
const { id } = useParams();
|
const { id } = useParams();
|
||||||
|
const { setCurrentDocument, currDocName, clearCurrDoc } = useEPUB();
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
|
|
||||||
|
const loadDocument = useCallback(async () => {
|
||||||
|
if (!isLoading) return;
|
||||||
|
try {
|
||||||
|
if (!id) {
|
||||||
|
setError('Document not found');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await setCurrentDocument(id as string);
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error loading document:', err);
|
||||||
|
setError('Failed to load document');
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false);
|
||||||
|
}
|
||||||
|
}, [isLoading, id, setCurrentDocument]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
loadDocument();
|
||||||
|
}, [loadDocument]);
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col items-center justify-center min-h-screen">
|
||||||
|
<p className="text-red-500 mb-4">{error}</p>
|
||||||
|
<Link
|
||||||
|
href="/"
|
||||||
|
onClick={() => clearCurrDoc()}
|
||||||
|
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">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M10 19l-7-7m0 0l7-7m-7 7h18" />
|
||||||
|
</svg>
|
||||||
|
Back to Documents
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|
@ -13,7 +58,7 @@ export default function EPUBPage() {
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Link
|
<Link
|
||||||
href="/"
|
href="/"
|
||||||
onClick={() => { }}
|
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">
|
||||||
|
|
@ -23,11 +68,17 @@ export default function EPUBPage() {
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
<h1 className="ml-2 mr-2 text-md font-semibold text-foreground truncate">
|
<h1 className="ml-2 mr-2 text-md font-semibold text-foreground truncate">
|
||||||
{id}
|
{isLoading ? 'Loading...' : currDocName}
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{isLoading ? (
|
||||||
|
<div className="p-4">
|
||||||
|
<DocumentSkeleton />
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<EPUBViewer className="p-4" />
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -1,20 +1,27 @@
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
|
import { ReactNode } from 'react';
|
||||||
|
|
||||||
|
import { DocumentProvider } from '@/contexts/DocumentContext';
|
||||||
import { PDFProvider } from '@/contexts/PDFContext';
|
import { PDFProvider } from '@/contexts/PDFContext';
|
||||||
|
import { EPUBProvider } from '@/contexts/EPUBContext';
|
||||||
import { TTSProvider } from '@/contexts/TTSContext';
|
import { TTSProvider } from '@/contexts/TTSContext';
|
||||||
import { ThemeProvider } from '@/contexts/ThemeContext';
|
import { ThemeProvider } from '@/contexts/ThemeContext';
|
||||||
import { ConfigProvider } from '@/contexts/ConfigContext';
|
import { ConfigProvider } from '@/contexts/ConfigContext';
|
||||||
import { ReactNode } from 'react';
|
|
||||||
|
|
||||||
export function Providers({ children }: { children: ReactNode }) {
|
export function Providers({ children }: { children: ReactNode }) {
|
||||||
return (
|
return (
|
||||||
<ThemeProvider>
|
<ThemeProvider>
|
||||||
<ConfigProvider>
|
<ConfigProvider>
|
||||||
<TTSProvider>
|
<DocumentProvider>
|
||||||
<PDFProvider>
|
<TTSProvider>
|
||||||
{children}
|
<PDFProvider>
|
||||||
</PDFProvider>
|
<EPUBProvider>
|
||||||
</TTSProvider>
|
{children}
|
||||||
|
</EPUBProvider>
|
||||||
|
</PDFProvider>
|
||||||
|
</TTSProvider>
|
||||||
|
</DocumentProvider>
|
||||||
</ConfigProvider>
|
</ConfigProvider>
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,8 @@
|
||||||
import { usePDF } from '@/contexts/PDFContext';
|
|
||||||
import { useEpubDocuments } from '@/hooks/epub/useEpubDocuments';
|
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { Button, Dialog } from '@headlessui/react';
|
import { Button, Dialog } from '@headlessui/react';
|
||||||
import { Transition, TransitionChild, DialogPanel, DialogTitle } from '@headlessui/react';
|
import { Transition, TransitionChild, DialogPanel, DialogTitle } from '@headlessui/react';
|
||||||
import { Fragment, useState } from 'react';
|
import { Fragment, useState } from 'react';
|
||||||
|
import { useDocuments } from '@/contexts/DocumentContext';
|
||||||
|
|
||||||
type DocumentToDelete = {
|
type DocumentToDelete = {
|
||||||
id: string;
|
id: string;
|
||||||
|
|
@ -12,8 +11,15 @@ type DocumentToDelete = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export function DocumentList() {
|
export function DocumentList() {
|
||||||
const { documents: pdfDocs, removeDocument: removePDF, isDocsLoading: isPDFLoading } = usePDF();
|
const {
|
||||||
const { documents: epubDocs, removeDocument: removeEPUB, isLoading: isEPUBLoading } = useEpubDocuments();
|
pdfDocs,
|
||||||
|
removePDFDocument: removePDF,
|
||||||
|
isPDFLoading,
|
||||||
|
epubDocs,
|
||||||
|
removeEPUBDocument: removeEPUB,
|
||||||
|
isEPUBLoading,
|
||||||
|
} = useDocuments();
|
||||||
|
|
||||||
const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false);
|
const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false);
|
||||||
const [documentToDelete, setDocumentToDelete] = useState<DocumentToDelete | null>(null);
|
const [documentToDelete, setDocumentToDelete] = useState<DocumentToDelete | null>(null);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,17 +2,15 @@
|
||||||
|
|
||||||
import { useState, useCallback } from 'react';
|
import { useState, useCallback } from 'react';
|
||||||
import { useDropzone } from 'react-dropzone';
|
import { useDropzone } from 'react-dropzone';
|
||||||
import { usePDF } from '@/contexts/PDFContext';
|
|
||||||
import { UploadIcon } from '@/components/icons/Icons';
|
import { UploadIcon } from '@/components/icons/Icons';
|
||||||
import { useEpubDocuments } from '@/hooks/epub/useEpubDocuments';
|
import { useDocuments } from '@/contexts/DocumentContext';
|
||||||
|
|
||||||
interface DocumentUploaderProps {
|
interface DocumentUploaderProps {
|
||||||
className?: string;
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function DocumentUploader({ className = '' }: DocumentUploaderProps) {
|
export function DocumentUploader({ className = '' }: DocumentUploaderProps) {
|
||||||
const { addDocument: addPDF } = usePDF();
|
const { addPDFDocument: addPDF, addEPUBDocument: addEPUB } = useDocuments();
|
||||||
const { addDocument: addEPUB } = useEpubDocuments();
|
|
||||||
const [isUploading, setIsUploading] = useState(false);
|
const [isUploading, setIsUploading] = useState(false);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
|
||||||
|
|
|
||||||
36
src/components/EPUBViewer.tsx
Normal file
36
src/components/EPUBViewer.tsx
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useState } from 'react';
|
||||||
|
import dynamic from 'next/dynamic';
|
||||||
|
import { useEPUB } from '@/contexts/EPUBContext';
|
||||||
|
import { DocumentSkeleton } from '@/components/DocumentSkeleton';
|
||||||
|
|
||||||
|
const ReactReader = dynamic(() => import('react-reader').then(mod => mod.ReactReader), {
|
||||||
|
ssr: false,
|
||||||
|
loading: () => <DocumentSkeleton />
|
||||||
|
});
|
||||||
|
|
||||||
|
interface EPUBViewerProps {
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function EPUBViewer({ className = '' }: EPUBViewerProps) {
|
||||||
|
const { currDocData, currDocName } = useEPUB(); // Changed from currDocURL
|
||||||
|
const [location, setLocation] = useState<string | number>(0);
|
||||||
|
|
||||||
|
if (!currDocData) {
|
||||||
|
return <DocumentSkeleton />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{ height: '100vh' }} className={className}>
|
||||||
|
<ReactReader
|
||||||
|
location={location}
|
||||||
|
locationChanged={setLocation}
|
||||||
|
url={currDocData} // ReactReader can accept ArrayBuffer directly
|
||||||
|
title={currDocName}
|
||||||
|
showToc={true}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
61
src/contexts/DocumentContext.tsx
Normal file
61
src/contexts/DocumentContext.tsx
Normal file
|
|
@ -0,0 +1,61 @@
|
||||||
|
'use client';
|
||||||
|
|
||||||
|
import { createContext, useContext, ReactNode } from 'react';
|
||||||
|
import { usePDFDocuments } from '@/hooks/pdf/usePDFDocuments';
|
||||||
|
import { useEPUBDocuments } from '@/hooks/epub/useEPUBDocuments';
|
||||||
|
import { PDFDocument, EPUBDocument } from '@/utils/indexedDB';
|
||||||
|
|
||||||
|
interface DocumentContextType {
|
||||||
|
// PDF Documents
|
||||||
|
pdfDocs: PDFDocument[];
|
||||||
|
addPDFDocument: (file: File) => Promise<string>;
|
||||||
|
removePDFDocument: (id: string) => Promise<void>;
|
||||||
|
isPDFLoading: boolean;
|
||||||
|
|
||||||
|
// EPUB Documents
|
||||||
|
epubDocs: EPUBDocument[];
|
||||||
|
addEPUBDocument: (file: File) => Promise<string>;
|
||||||
|
removeEPUBDocument: (id: string) => Promise<void>;
|
||||||
|
isEPUBLoading: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const DocumentContext = createContext<DocumentContextType | undefined>(undefined);
|
||||||
|
|
||||||
|
export function DocumentProvider({ children }: { children: ReactNode }) {
|
||||||
|
const {
|
||||||
|
documents: pdfDocs,
|
||||||
|
addDocument: addPDFDocument,
|
||||||
|
removeDocument: removePDFDocument,
|
||||||
|
isLoading: isPDFLoading
|
||||||
|
} = usePDFDocuments();
|
||||||
|
|
||||||
|
const {
|
||||||
|
documents: epubDocs,
|
||||||
|
addDocument: addEPUBDocument,
|
||||||
|
removeDocument: removeEPUBDocument,
|
||||||
|
isLoading: isEPUBLoading
|
||||||
|
} = useEPUBDocuments();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DocumentContext.Provider value={{
|
||||||
|
pdfDocs,
|
||||||
|
addPDFDocument,
|
||||||
|
removePDFDocument,
|
||||||
|
isPDFLoading,
|
||||||
|
epubDocs,
|
||||||
|
addEPUBDocument,
|
||||||
|
removeEPUBDocument,
|
||||||
|
isEPUBLoading
|
||||||
|
}}>
|
||||||
|
{children}
|
||||||
|
</DocumentContext.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useDocuments() {
|
||||||
|
const context = useContext(DocumentContext);
|
||||||
|
if (context === undefined) {
|
||||||
|
throw new Error('useDocuments must be used within a DocumentProvider');
|
||||||
|
}
|
||||||
|
return context;
|
||||||
|
}
|
||||||
132
src/contexts/EPUBContext.tsx
Normal file
132
src/contexts/EPUBContext.tsx
Normal file
|
|
@ -0,0 +1,132 @@
|
||||||
|
'use client';
|
||||||
|
|
||||||
|
import {
|
||||||
|
createContext,
|
||||||
|
useContext,
|
||||||
|
useState,
|
||||||
|
ReactNode,
|
||||||
|
useCallback,
|
||||||
|
useMemo,
|
||||||
|
} from 'react';
|
||||||
|
import { indexedDBService } from '@/utils/indexedDB';
|
||||||
|
import { useTTS } from '@/contexts/TTSContext';
|
||||||
|
|
||||||
|
interface EPUBContextType {
|
||||||
|
// Current document state
|
||||||
|
currDocData: ArrayBuffer | undefined; // Changed back to currDocData
|
||||||
|
currDocName: string | undefined;
|
||||||
|
currDocPages: number | undefined;
|
||||||
|
currDocPage: number;
|
||||||
|
currDocText: string | undefined;
|
||||||
|
setCurrentDocument: (id: string) => Promise<void>;
|
||||||
|
clearCurrDoc: () => void;
|
||||||
|
|
||||||
|
// EPUB functionality
|
||||||
|
onDocumentLoadSuccess: ({ numPages }: { numPages: number }) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the context
|
||||||
|
const EPUBContext = createContext<EPUBContextType | undefined>(undefined);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* EPUBProvider Component
|
||||||
|
*
|
||||||
|
* Main provider component that manages EPUB state and functionality.
|
||||||
|
* Handles document loading, text processing, and integration with TTS.
|
||||||
|
*/
|
||||||
|
export function EPUBProvider({ children }: { children: ReactNode }) {
|
||||||
|
const { setText: setTTSText, currDocPage, currDocPages, setCurrDocPages } = useTTS();
|
||||||
|
|
||||||
|
// Current document state
|
||||||
|
const [currDocData, setCurrDocData] = useState<ArrayBuffer>(); // Changed back to currDocData
|
||||||
|
const [currDocName, setCurrDocName] = useState<string>();
|
||||||
|
const [currDocText, setCurrDocText] = useState<string>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles successful document load
|
||||||
|
*/
|
||||||
|
const onDocumentLoadSuccess = useCallback(({ numPages }: { numPages: number }) => {
|
||||||
|
console.log('EPUB loaded:', numPages);
|
||||||
|
setCurrDocPages(numPages);
|
||||||
|
}, [setCurrDocPages]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears the current document state
|
||||||
|
*/
|
||||||
|
const clearCurrDoc = useCallback(() => {
|
||||||
|
setCurrDocData(undefined);
|
||||||
|
setCurrDocName(undefined);
|
||||||
|
setCurrDocText(undefined);
|
||||||
|
setCurrDocPages(undefined);
|
||||||
|
setTTSText('');
|
||||||
|
}, [setCurrDocPages, setTTSText]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the current document based on its ID
|
||||||
|
*/
|
||||||
|
const setCurrentDocument = useCallback(async (id: string): Promise<void> => {
|
||||||
|
try {
|
||||||
|
const doc = await indexedDBService.getEPUBDocument(id);
|
||||||
|
if (doc) {
|
||||||
|
console.log('Retrieved document size:', doc.size);
|
||||||
|
console.log('Retrieved ArrayBuffer size:', doc.data.byteLength);
|
||||||
|
|
||||||
|
if (doc.data.byteLength === 0) {
|
||||||
|
console.error('Retrieved ArrayBuffer is empty');
|
||||||
|
throw new Error('Empty document data');
|
||||||
|
}
|
||||||
|
|
||||||
|
setCurrDocName(doc.name);
|
||||||
|
setCurrDocData(doc.data); // Store ArrayBuffer directly
|
||||||
|
} else {
|
||||||
|
console.error('Document not found in IndexedDB');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to get EPUB document:', error);
|
||||||
|
clearCurrDoc(); // Clean up on error
|
||||||
|
}
|
||||||
|
}, [clearCurrDoc]);
|
||||||
|
|
||||||
|
|
||||||
|
// Context value memoization
|
||||||
|
const contextValue = useMemo(
|
||||||
|
() => ({
|
||||||
|
onDocumentLoadSuccess,
|
||||||
|
setCurrentDocument,
|
||||||
|
currDocData, // Changed back to currDocData
|
||||||
|
currDocName,
|
||||||
|
currDocPages,
|
||||||
|
currDocPage,
|
||||||
|
currDocText,
|
||||||
|
clearCurrDoc,
|
||||||
|
}),
|
||||||
|
[
|
||||||
|
onDocumentLoadSuccess,
|
||||||
|
setCurrentDocument,
|
||||||
|
currDocData, // Changed back to currDocData
|
||||||
|
currDocName,
|
||||||
|
currDocPages,
|
||||||
|
currDocPage,
|
||||||
|
currDocText,
|
||||||
|
clearCurrDoc,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<EPUBContext.Provider value={contextValue}>
|
||||||
|
{children}
|
||||||
|
</EPUBContext.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom hook to consume the EPUB context
|
||||||
|
* Ensures the context is used within a provider
|
||||||
|
*/
|
||||||
|
export function useEPUB() {
|
||||||
|
const context = useContext(EPUBContext);
|
||||||
|
if (context === undefined) {
|
||||||
|
throw new Error('useEPUB must be used within an EPUBProvider');
|
||||||
|
}
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
@ -23,7 +23,7 @@ import {
|
||||||
useMemo,
|
useMemo,
|
||||||
} from 'react';
|
} from 'react';
|
||||||
|
|
||||||
import { indexedDBService, type PDFDocument } from '@/utils/indexedDB';
|
import { indexedDBService } from '@/utils/indexedDB';
|
||||||
import { useTTS } from '@/contexts/TTSContext';
|
import { useTTS } from '@/contexts/TTSContext';
|
||||||
import {
|
import {
|
||||||
extractTextFromPDF,
|
extractTextFromPDF,
|
||||||
|
|
@ -32,18 +32,11 @@ import {
|
||||||
clearHighlights,
|
clearHighlights,
|
||||||
handleTextClick,
|
handleTextClick,
|
||||||
} from '@/utils/pdf';
|
} from '@/utils/pdf';
|
||||||
import { usePDFDocuments } from '@/hooks/pdf/usePDFDocuments';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interface defining all available methods and properties in the PDF context
|
* Interface defining all available methods and properties in the PDF context
|
||||||
*/
|
*/
|
||||||
interface PDFContextType {
|
interface PDFContextType {
|
||||||
// PDF document store management
|
|
||||||
addDocument: (file: File) => Promise<string>;
|
|
||||||
documents: PDFDocument[];
|
|
||||||
removeDocument: (id: string) => Promise<void>;
|
|
||||||
isDocsLoading: boolean;
|
|
||||||
|
|
||||||
// Current document state
|
// Current document state
|
||||||
currDocURL: string | undefined;
|
currDocURL: string | undefined;
|
||||||
currDocName: string | undefined;
|
currDocName: string | undefined;
|
||||||
|
|
@ -76,15 +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 {
|
const { setText: setTTSText, currDocPage, currDocPages, setCurrDocPages } = useTTS();
|
||||||
setText: setTTSText,
|
|
||||||
currDocPage,
|
|
||||||
currDocPages,
|
|
||||||
setCurrDocPages,
|
|
||||||
} = useTTS();
|
|
||||||
|
|
||||||
// PDF Documents hook
|
|
||||||
const { addDocument, documents, removeDocument, isLoading: isDocsLoading } = usePDFDocuments();
|
|
||||||
|
|
||||||
// Current document state
|
// Current document state
|
||||||
const [currDocURL, setCurrDocURL] = useState<string>();
|
const [currDocURL, setCurrDocURL] = useState<string>();
|
||||||
|
|
@ -157,10 +142,6 @@ export function PDFProvider({ children }: { children: ReactNode }) {
|
||||||
// Context value memoization
|
// Context value memoization
|
||||||
const contextValue = useMemo(
|
const contextValue = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
addDocument,
|
|
||||||
documents,
|
|
||||||
removeDocument,
|
|
||||||
isDocsLoading,
|
|
||||||
onDocumentLoadSuccess,
|
onDocumentLoadSuccess,
|
||||||
setCurrentDocument,
|
setCurrentDocument,
|
||||||
currDocURL,
|
currDocURL,
|
||||||
|
|
@ -174,10 +155,6 @@ export function PDFProvider({ children }: { children: ReactNode }) {
|
||||||
handleTextClick,
|
handleTextClick,
|
||||||
}),
|
}),
|
||||||
[
|
[
|
||||||
addDocument,
|
|
||||||
documents,
|
|
||||||
removeDocument,
|
|
||||||
isDocsLoading,
|
|
||||||
onDocumentLoadSuccess,
|
onDocumentLoadSuccess,
|
||||||
setCurrentDocument,
|
setCurrentDocument,
|
||||||
currDocURL,
|
currDocURL,
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import { v4 as uuidv4 } from 'uuid';
|
||||||
import { indexedDBService, type EPUBDocument } from '@/utils/indexedDB';
|
import { indexedDBService, type EPUBDocument } from '@/utils/indexedDB';
|
||||||
import { useConfig } from '@/contexts/ConfigContext';
|
import { useConfig } from '@/contexts/ConfigContext';
|
||||||
|
|
||||||
export function useEpubDocuments() {
|
export function useEPUBDocuments() {
|
||||||
const { isDBReady } = useConfig();
|
const { isDBReady } = useConfig();
|
||||||
const [documents, setDocuments] = useState<EPUBDocument[]>([]);
|
const [documents, setDocuments] = useState<EPUBDocument[]>([]);
|
||||||
const [isLoading, setIsLoading] = useState(true);
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
|
|
@ -13,7 +13,7 @@ export function useEpubDocuments() {
|
||||||
const loadDocuments = useCallback(async () => {
|
const loadDocuments = useCallback(async () => {
|
||||||
if (isDBReady) {
|
if (isDBReady) {
|
||||||
try {
|
try {
|
||||||
const docs = await indexedDBService.getAllEpubDocuments();
|
const docs = await indexedDBService.getAllEPUBDocuments();
|
||||||
setDocuments(docs);
|
setDocuments(docs);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to load EPUB documents:', error);
|
console.error('Failed to load EPUB documents:', error);
|
||||||
|
|
@ -29,16 +29,21 @@ export function useEpubDocuments() {
|
||||||
|
|
||||||
const addDocument = useCallback(async (file: File): Promise<string> => {
|
const addDocument = useCallback(async (file: File): Promise<string> => {
|
||||||
const id = uuidv4();
|
const id = uuidv4();
|
||||||
|
const arrayBuffer = await file.arrayBuffer();
|
||||||
|
|
||||||
|
console.log('Original file size:', file.size);
|
||||||
|
console.log('ArrayBuffer size:', arrayBuffer.byteLength);
|
||||||
|
|
||||||
const newDoc: EPUBDocument = {
|
const newDoc: EPUBDocument = {
|
||||||
id,
|
id,
|
||||||
name: file.name,
|
name: file.name,
|
||||||
size: file.size,
|
size: file.size,
|
||||||
lastModified: file.lastModified,
|
lastModified: file.lastModified,
|
||||||
data: new Blob([file], { type: file.type }),
|
data: arrayBuffer,
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await indexedDBService.addEpubDocument(newDoc);
|
await indexedDBService.addEPUBDocument(newDoc);
|
||||||
setDocuments((prev) => [...prev, newDoc]);
|
setDocuments((prev) => [...prev, newDoc]);
|
||||||
return id;
|
return id;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
@ -49,7 +54,7 @@ export function useEpubDocuments() {
|
||||||
|
|
||||||
const removeDocument = useCallback(async (id: string): Promise<void> => {
|
const removeDocument = useCallback(async (id: string): Promise<void> => {
|
||||||
try {
|
try {
|
||||||
await indexedDBService.removeEpubDocument(id);
|
await indexedDBService.removeEPUBDocument(id);
|
||||||
setDocuments((prev) => prev.filter((doc) => doc.id !== id));
|
setDocuments((prev) => prev.filter((doc) => doc.id !== id));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to remove EPUB document:', error);
|
console.error('Failed to remove EPUB document:', error);
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ export interface EPUBDocument {
|
||||||
name: string;
|
name: string;
|
||||||
size: number;
|
size: number;
|
||||||
lastModified: number;
|
lastModified: number;
|
||||||
data: Blob;
|
data: ArrayBuffer; // Changed from Blob to ArrayBuffer
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Config {
|
export interface Config {
|
||||||
|
|
@ -196,31 +196,49 @@ class IndexedDBService {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add EPUB Document Methods
|
// Add EPUB Document Methods
|
||||||
async addEpubDocument(document: EPUBDocument): Promise<void> {
|
async addEPUBDocument(document: EPUBDocument): Promise<void> {
|
||||||
if (!this.db) {
|
if (!this.db) {
|
||||||
await this.init();
|
await this.init();
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
try {
|
try {
|
||||||
|
if (document.data.byteLength === 0) {
|
||||||
|
throw new Error('Cannot store empty ArrayBuffer');
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Storing document:', {
|
||||||
|
name: document.name,
|
||||||
|
size: document.size,
|
||||||
|
actualSize: document.data.byteLength
|
||||||
|
});
|
||||||
|
|
||||||
const transaction = this.db!.transaction([EPUB_STORE_NAME], 'readwrite');
|
const transaction = this.db!.transaction([EPUB_STORE_NAME], 'readwrite');
|
||||||
const store = transaction.objectStore(EPUB_STORE_NAME);
|
const store = transaction.objectStore(EPUB_STORE_NAME);
|
||||||
const request = store.put(document);
|
|
||||||
|
// Create a structured clone of the document to ensure proper storage
|
||||||
|
const request = store.put({
|
||||||
|
...document,
|
||||||
|
data: document.data.slice(0) // Create a copy of the ArrayBuffer
|
||||||
|
});
|
||||||
|
|
||||||
request.onerror = (event) => {
|
request.onerror = (event) => {
|
||||||
|
console.error('Error storing document:', (event.target as IDBRequest).error);
|
||||||
reject((event.target as IDBRequest).error);
|
reject((event.target as IDBRequest).error);
|
||||||
};
|
};
|
||||||
|
|
||||||
transaction.oncomplete = () => {
|
transaction.oncomplete = () => {
|
||||||
|
console.log('Document stored successfully');
|
||||||
resolve();
|
resolve();
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.error('Error in addEPUBDocument:', error);
|
||||||
reject(error);
|
reject(error);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async getEpubDocument(id: string): Promise<EPUBDocument | undefined> {
|
async getEPUBDocument(id: string): Promise<EPUBDocument | undefined> {
|
||||||
if (!this.db) {
|
if (!this.db) {
|
||||||
await this.init();
|
await this.init();
|
||||||
}
|
}
|
||||||
|
|
@ -232,19 +250,29 @@ class IndexedDBService {
|
||||||
const request = store.get(id);
|
const request = store.get(id);
|
||||||
|
|
||||||
request.onerror = (event) => {
|
request.onerror = (event) => {
|
||||||
|
console.error('Error fetching document:', (event.target as IDBRequest).error);
|
||||||
reject((event.target as IDBRequest).error);
|
reject((event.target as IDBRequest).error);
|
||||||
};
|
};
|
||||||
|
|
||||||
request.onsuccess = () => {
|
request.onsuccess = () => {
|
||||||
resolve(request.result);
|
const doc = request.result;
|
||||||
|
if (doc) {
|
||||||
|
console.log('Retrieved document from DB:', {
|
||||||
|
name: doc.name,
|
||||||
|
size: doc.size,
|
||||||
|
actualSize: doc.data.byteLength
|
||||||
|
});
|
||||||
|
}
|
||||||
|
resolve(doc);
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.error('Error in getEPUBDocument:', error);
|
||||||
reject(error);
|
reject(error);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async getAllEpubDocuments(): Promise<EPUBDocument[]> {
|
async getAllEPUBDocuments(): Promise<EPUBDocument[]> {
|
||||||
if (!this.db) {
|
if (!this.db) {
|
||||||
await this.init();
|
await this.init();
|
||||||
}
|
}
|
||||||
|
|
@ -268,7 +296,7 @@ class IndexedDBService {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async removeEpubDocument(id: string): Promise<void> {
|
async removeEPUBDocument(id: string): Promise<void> {
|
||||||
if (!this.db) {
|
if (!this.db) {
|
||||||
await this.init();
|
await this.init();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue