Use indexed db

This commit is contained in:
Richard Roberson 2025-01-18 21:59:02 -07:00
parent 109d43f585
commit 355e4ebe29
9 changed files with 492 additions and 120 deletions

23
package-lock.json generated
View file

@ -10,6 +10,7 @@
"dependencies": {
"@headlessui/react": "^2.2.0",
"@types/string-similarity": "^4.0.2",
"@types/uuid": "^10.0.0",
"compromise": "^14.14.4",
"lru-cache": "^11.0.2",
"next": "15.1.5",
@ -19,7 +20,8 @@
"react-dom": "^19.0.0",
"react-dropzone": "^14.3.5",
"react-pdf": "^9.2.1",
"string-similarity": "^4.0.4"
"string-similarity": "^4.0.4",
"uuid": "^11.0.5"
},
"devDependencies": {
"@eslint/eslintrc": "^3",
@ -1165,6 +1167,12 @@
"integrity": "sha512-LkJQ/jsXtCVMK+sKYAmX/8zEq+/46f1PTQw7YtmQwb74jemS1SlNLmARM2Zml9DgdDTWKAtc5L13WorpHPDjDA==",
"license": "MIT"
},
"node_modules/@types/uuid": {
"version": "10.0.0",
"resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-10.0.0.tgz",
"integrity": "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==",
"license": "MIT"
},
"node_modules/@typescript-eslint/eslint-plugin": {
"version": "8.20.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.20.0.tgz",
@ -6751,6 +6759,19 @@
"devOptional": true,
"license": "MIT"
},
"node_modules/uuid": {
"version": "11.0.5",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-11.0.5.tgz",
"integrity": "sha512-508e6IcKLrhxKdBbcA2b4KQZlLVp2+J5UwQ6F7Drckkc5N9ZJwFa4TgWtsww9UG8fGHbm6gbV19TdM5pQ4GaIA==",
"funding": [
"https://github.com/sponsors/broofa",
"https://github.com/sponsors/ctavan"
],
"license": "MIT",
"bin": {
"uuid": "dist/esm/bin/uuid"
}
},
"node_modules/warning": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz",

View file

@ -11,6 +11,7 @@
"dependencies": {
"@headlessui/react": "^2.2.0",
"@types/string-similarity": "^4.0.2",
"@types/uuid": "^10.0.0",
"compromise": "^14.14.4",
"lru-cache": "^11.0.2",
"next": "15.1.5",
@ -20,7 +21,8 @@
"react-dom": "^19.0.0",
"react-dropzone": "^14.3.5",
"react-pdf": "^9.2.1",
"string-similarity": "^4.0.4"
"string-similarity": "^4.0.4",
"uuid": "^11.0.5"
},
"devDependencies": {
"@eslint/eslintrc": "^3",

View file

@ -4,13 +4,53 @@ import { PDFViewer } from '@/components/PDFViewer';
import { usePDF } from '@/context/PDFContext';
import { useParams, useRouter } from 'next/navigation';
import Link from 'next/link';
import { useEffect, useState } from 'react';
import { PDFSkeleton } from '@/components/PDFSkeleton';
export default function PDFViewerPage() {
const { id } = useParams();
const { getDocument } = usePDF();
const router = useRouter();
const [document, setDocument] = useState<{ name: string; data: Blob } | null>(null);
const [error, setError] = useState<string | null>(null);
const [isLoading, setIsLoading] = useState(true);
const document = getDocument(id as string);
useEffect(() => {
async function loadDocument() {
try {
const doc = await getDocument(id as string);
if (!doc) {
setError('Document not found');
return;
}
setDocument(doc);
} catch (err) {
console.error('Error loading document:', err);
setError('Failed to load document');
} finally {
setIsLoading(false);
}
}
loadDocument();
}, [id, getDocument]);
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="/"
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 (
<>
@ -25,10 +65,18 @@ export default function PDFViewerPage() {
</svg>
Documents
</Link>
<h1 className="mr-2 text-xl font-semibold text-foreground">{document?.name}</h1>
<h1 className="mr-2 text-xl font-semibold text-foreground">
{isLoading ? 'Loading...' : document?.name}
</h1>
</div>
</div>
<PDFViewer pdfFile={document?.data} />
{isLoading ? (
<div className="p-4">
<PDFSkeleton />
</div>
) : (
<PDFViewer pdfData={document?.data} />
)}
</>
);
);
}

View file

@ -2,7 +2,23 @@ import { usePDF } from '@/context/PDFContext';
import Link from 'next/link';
export function DocumentList() {
const { documents, removeDocument } = usePDF();
const { documents, removeDocument, isLoading, error } = usePDF();
if (isLoading) {
return (
<div className="w-full text-center text-muted">
Loading documents...
</div>
);
}
if (error) {
return (
<div className="w-full text-center text-red-500">
{error}
</div>
);
}
if (documents.length === 0) {
return (
@ -30,17 +46,23 @@ export function DocumentList() {
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M7 21h10a2 2 0 002-2V9.414a1 1 0 00-.293-.707l-5.414-5.414A1 1 0 0012.586 3H7a2 2 0 00-2 2v14a2 2 0 002 2z" />
</svg>
</div>
<div className="min-w-0">
<h3 className="font-medium text-foreground truncate">{doc.name}</h3>
<p className="text-sm text-muted">
<div className="flex-1 min-w-0">
<p className="text-foreground font-medium truncate">{doc.name}</p>
<p className="text-sm text-muted truncate">
{(doc.size / 1024 / 1024).toFixed(2)} MB
</p>
</div>
</Link>
<button
onClick={() => removeDocument(doc.id)}
className="p-2 text-muted hover:text-accent transition-colors flex-shrink-0 ml-2"
title="Remove document"
onClick={async () => {
try {
await removeDocument(doc.id);
} catch (err) {
console.error('Failed to remove document:', err);
}
}}
className="ml-4 p-2 text-muted hover:text-red-500 rounded-lg hover:bg-red-50 transition-colors"
aria-label="Delete document"
>
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />

View file

@ -9,22 +9,33 @@ interface PDFUploaderProps {
}
export function PDFUploader({ className = '' }: PDFUploaderProps) {
const { addDocument } = usePDF();
const [isDragging, setIsDragging] = useState(false);
const { addDocument, error: contextError } = usePDF();
const [isUploading, setIsUploading] = useState(false);
const [error, setError] = useState<string | null>(null);
const onDrop = useCallback(async (acceptedFiles: File[]) => {
const file = acceptedFiles[0];
if (file && file.type === 'application/pdf') {
await addDocument(file);
setIsUploading(true);
setError(null);
try {
await addDocument(file);
} catch (err) {
setError(contextError || 'Failed to upload PDF. Please try again.');
console.error('Upload error:', err);
} finally {
setIsUploading(false);
}
}
}, [addDocument]);
}, [addDocument, contextError]);
const { getRootProps, getInputProps, isDragActive } = useDropzone({
onDrop,
accept: {
'application/pdf': ['.pdf']
},
multiple: false
multiple: false,
disabled: isUploading
});
return (
@ -33,8 +44,8 @@ export function PDFUploader({ className = '' }: PDFUploaderProps) {
className={`
w-full p-8 border-2 border-dashed rounded-lg
${isDragActive ? 'border-accent bg-base' : 'border-muted'}
transition-colors duration-200 ease-in-out cursor-pointer
hover:border-accent hover:bg-base
transition-colors duration-200 ease-in-out
${isUploading ? 'cursor-not-allowed opacity-50' : 'cursor-pointer hover:border-accent hover:bg-base'}
${className}
`}
>
@ -55,12 +66,21 @@ export function PDFUploader({ className = '' }: PDFUploaderProps) {
/>
</svg>
<p className="mb-2 text-lg font-semibold text-foreground">
Drop your PDF here, or click to select
</p>
<p className="text-sm text-muted">
Only PDF files are accepted
</p>
{isUploading ? (
<p className="text-lg font-semibold text-foreground">
Uploading PDF...
</p>
) : (
<>
<p className="mb-2 text-lg font-semibold text-foreground">
{isDragActive ? 'Drop your PDF here' : 'Drop your PDF here, or click to select'}
</p>
<p className="text-sm text-muted">
Only PDF files are accepted
</p>
{error && <p className="mt-2 text-sm text-red-500">{error}</p>}
</>
)}
</div>
</div>
);

View file

@ -3,7 +3,7 @@
import { Document, Page, pdfjs } from 'react-pdf';
import 'react-pdf/dist/Page/AnnotationLayer.css';
import 'react-pdf/dist/Page/TextLayer.css';
import { useState, useEffect, useCallback } from 'react';
import { useState, useEffect, useCallback, useMemo } from 'react';
import { PDFSkeleton } from './PDFSkeleton';
import { useTTS } from '@/context/TTSContext';
import stringSimilarity from 'string-similarity';
@ -12,7 +12,7 @@ import stringSimilarity from 'string-similarity';
pdfjs.GlobalWorkerOptions.workerSrc = '/pdf.worker.mjs';
interface PDFViewerProps {
pdfFile: string | undefined;
pdfData: Blob | undefined;
}
interface TextHighlight {
@ -23,11 +23,98 @@ interface TextHighlight {
};
}
export function PDFViewer({ pdfFile }: PDFViewerProps) {
export function PDFViewer({ pdfData }: PDFViewerProps) {
const [numPages, setNumPages] = useState<number>();
const { setText, currentSentence } = useTTS();
const [pdfText, setPdfText] = useState('');
const [highlights, setHighlights] = useState<TextHighlight[]>([]);
const [pdfDataUrl, setPdfDataUrl] = useState<string>();
const [loadingError, setLoadingError] = useState<string>();
// Convert Blob to data URL when pdfData changes
useEffect(() => {
if (!pdfData) return;
const reader = new FileReader();
reader.onload = () => {
setPdfDataUrl(reader.result as string);
};
reader.onerror = () => {
console.error('Error reading file:', reader.error);
setLoadingError('Failed to load PDF');
};
reader.readAsDataURL(pdfData);
return () => {
setPdfDataUrl(undefined);
};
}, [pdfData]);
// Load PDF text content
useEffect(() => {
if (!pdfDataUrl) return;
let isCurrentPdf = true;
let currentLoadingTask: any = null;
setLoadingError(undefined);
const loadPdfText = async () => {
try {
// Create a typed array from the base64 data
const base64Data = pdfDataUrl.split(',')[1];
const binaryData = atob(base64Data);
const length = binaryData.length;
const bytes = new Uint8Array(length);
for (let i = 0; i < length; i++) {
bytes[i] = binaryData.charCodeAt(i);
}
const loadingTask = pdfjs.getDocument({
data: bytes,
disableAutoFetch: true,
disableStream: false,
});
currentLoadingTask = loadingTask;
const pdf = await loadingTask.promise;
if (!isCurrentPdf) return;
let fullText = '';
for (let i = 1; i <= pdf.numPages; i++) {
if (!isCurrentPdf) break;
const page = await pdf.getPage(i);
const textContent = await page.getTextContent();
const pageText = textContent.items
.map((item: any) => item.str)
.join(' ');
fullText += pageText + ' ';
}
if (!isCurrentPdf) return;
console.log('Loaded PDF text sample:', fullText.substring(0, 100));
setPdfText(fullText);
setText(fullText);
} catch (error) {
if (!isCurrentPdf) return;
console.error('Error loading PDF text:', error);
setLoadingError('Failed to extract PDF text');
}
};
loadPdfText();
return () => {
isCurrentPdf = false;
if (currentLoadingTask) {
currentLoadingTask.destroy();
}
setPdfText('');
};
}, [pdfDataUrl, setText]);
// Function to clear all highlights
const clearHighlights = useCallback(() => {
@ -137,12 +224,12 @@ export function PDFViewer({ pdfFile }: PDFViewerProps) {
const lengthPenalty = lengthDiff / patternLength; // Normalized length difference
const adjustedRating = similarity * (1 - lengthPenalty * 0.5); // Reduce score based on length difference
console.log('Comparing:', {
text: combinedText,
similarity,
lengthDiff,
adjustedRating
});
// console.log('Comparing:', {
// text: combinedText,
// similarity,
// lengthDiff,
// adjustedRating
// });
// Update best match if we have better adjusted rating
if (adjustedRating > bestMatch.rating) {
@ -181,37 +268,6 @@ export function PDFViewer({ pdfFile }: PDFViewerProps) {
}
}, [clearHighlights]);
useEffect(() => {
if (pdfFile) {
const loadPdfText = async () => {
try {
const pdf = await pdfjs.getDocument(pdfFile).promise;
let fullText = '';
for (let i = 1; i <= pdf.numPages; i++) {
const page = await pdf.getPage(i);
const textContent = await page.getTextContent();
const pageText = textContent.items
.map((item: any) => item.str)
.join(' ');
fullText += pageText + ' ';
}
console.log('Loaded PDF text sample:', fullText.substring(0, 100));
setPdfText(fullText);
setText(fullText);
} catch (error) {
console.error('Error loading PDF text:', error);
}
};
loadPdfText();
}
// Clear highlights when PDF changes
return () => clearHighlights();
}, [pdfFile, setText, clearHighlights]);
// Update highlights when current sentence changes
useEffect(() => {
console.log('Current sentence changed:', currentSentence);
@ -229,11 +285,14 @@ export function PDFViewer({ pdfFile }: PDFViewerProps) {
return (
<div className="flex flex-col items-center">
{loadingError ? (
<div className="text-red-500 mb-4">{loadingError}</div>
) : null}
<Document
file={pdfFile}
onLoadSuccess={onDocumentLoadSuccess}
loading={<PDFSkeleton />}
noData={<PDFSkeleton />}
file={pdfDataUrl}
onLoadSuccess={onDocumentLoadSuccess}
className="flex flex-col items-center"
>
{Array.from(

View file

@ -1,72 +1,90 @@
'use client';
import { createContext, useContext, useState, ReactNode, useEffect } from 'react';
interface PDFDocument {
id: string;
name: string;
size: number;
lastModified: number;
data: string; // Base64 encoded file data
}
import { indexedDBService, type PDFDocument } from '@/services/indexedDB';
import { v4 as uuidv4 } from 'uuid';
interface PDFContextType {
documents: PDFDocument[];
addDocument: (file: File) => Promise<string>;
getDocument: (id: string) => PDFDocument | undefined;
removeDocument: (id: string) => void;
getDocument: (id: string) => Promise<PDFDocument | undefined>;
removeDocument: (id: string) => Promise<void>;
isLoading: boolean;
error: string | null;
}
const PDFContext = createContext<PDFContextType | undefined>(undefined);
const LOCAL_STORAGE_KEY = 'pdf-documents';
export function PDFProvider({ children }: { children: ReactNode }) {
const [documents, setDocuments] = useState<PDFDocument[]>([]);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
// Load documents from local storage on mount
useEffect(() => {
const storedDocs = localStorage.getItem(LOCAL_STORAGE_KEY);
if (storedDocs) {
setDocuments(JSON.parse(storedDocs));
}
const loadDocuments = async () => {
try {
setError(null);
await indexedDBService.init();
const docs = await indexedDBService.getAllDocuments();
setDocuments(docs);
} catch (error) {
console.error('Failed to load documents:', error);
setError('Failed to initialize document storage. Please check if your browser supports IndexedDB.');
} finally {
setIsLoading(false);
}
};
loadDocuments();
}, []);
// Save documents to local storage whenever they change
useEffect(() => {
localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(documents));
}, [documents]);
const addDocument = async (file: File): Promise<string> => {
return new Promise((resolve) => {
const reader = new FileReader();
reader.onloadend = () => {
const base64Data = reader.result as string;
const newDoc: PDFDocument = {
id: `${file.name.replace(/[^a-zA-Z0-9]/g, '-').replace(/-+/g, '-')}-${Date.now()}`,
name: file.name,
size: file.size,
lastModified: file.lastModified,
data: base64Data
};
setDocuments(prev => [...prev, newDoc]);
resolve(newDoc.id);
};
reader.readAsDataURL(file);
});
setError(null);
const id = uuidv4();
const newDoc: PDFDocument = {
id,
name: file.name,
size: file.size,
lastModified: file.lastModified,
data: new Blob([file], { type: file.type })
};
try {
await indexedDBService.addDocument(newDoc);
setDocuments(prev => [...prev, newDoc]);
return id;
} catch (error) {
console.error('Failed to add document:', error);
setError('Failed to save the document. Please try again.');
throw error;
}
};
const getDocument = (id: string) => {
return documents.find(doc => doc.id === id);
const getDocument = async (id: string): Promise<PDFDocument | undefined> => {
setError(null);
try {
return await indexedDBService.getDocument(id);
} catch (error) {
console.error('Failed to get document:', error);
setError('Failed to retrieve the document. Please try again.');
return undefined;
}
};
const removeDocument = (id: string) => {
setDocuments(prev => prev.filter(doc => doc.id !== id));
const removeDocument = async (id: string): Promise<void> => {
setError(null);
try {
await indexedDBService.removeDocument(id);
setDocuments(prev => prev.filter(doc => doc.id !== id));
} catch (error) {
console.error('Failed to remove document:', error);
setError('Failed to remove the document. Please try again.');
throw error;
}
};
return (
<PDFContext.Provider value={{ documents, addDocument, getDocument, removeDocument }}>
<PDFContext.Provider value={{ documents, addDocument, getDocument, removeDocument, isLoading, error }}>
{children}
</PDFContext.Provider>
);

View file

@ -49,6 +49,8 @@ export function TTSProvider({ children }: { children: React.ReactNode }) {
const [currentAudioIndex, setCurrentAudioIndex] = useState(0);
const [isProcessing, setIsProcessing] = useState(false);
const skipTriggeredRef = useRef(false);
const skipTimeoutRef = useRef<NodeJS.Timeout | null>(null);
const isPausingRef = useRef(false);
// Create OpenAI instance
const [openai] = useState(
@ -151,9 +153,11 @@ export function TTSProvider({ children }: { children: React.ReactNode }) {
source.onended = () => {
setActiveSource(null);
setIsProcessing(false);
if (isPlaying && !skipTriggeredRef.current) {
// Only advance if we're playing and not pausing or skipping
if (isPlaying && !skipTriggeredRef.current && !isPausingRef.current) {
processNextSentence();
}
isPausingRef.current = false; // Reset pause flag
};
source.start(0);
@ -173,19 +177,24 @@ export function TTSProvider({ children }: { children: React.ReactNode }) {
const togglePlay = useCallback(() => {
setIsPlaying((prev) => {
if (!prev) {
return true; // Start playing
isPausingRef.current = false;
return true;
} else {
// Pause playback
if (activeSource) {
activeSource.stop(); // Stop the audio source
setActiveSource(null); // Clear the active source
isPausingRef.current = true;
activeSource.stop();
setActiveSource(null);
}
return false; // Set isPlaying to false
return false;
}
});
}, [activeSource]);
const skipForward = useCallback(() => {
if (skipTimeoutRef.current) {
clearTimeout(skipTimeoutRef.current);
}
skipTriggeredRef.current = true;
// Cancel any ongoing request
if (currentRequestRef.current) {
@ -206,12 +215,16 @@ export function TTSProvider({ children }: { children: React.ReactNode }) {
});
// Reset skip flag after a short delay
setTimeout(() => {
skipTimeoutRef.current = setTimeout(() => {
skipTriggeredRef.current = false;
}, 100);
}, [sentences, activeSource]);
const skipBackward = useCallback(() => {
if (skipTimeoutRef.current) {
clearTimeout(skipTimeoutRef.current);
}
skipTriggeredRef.current = true;
// Cancel any ongoing request
if (currentRequestRef.current) {
@ -232,7 +245,7 @@ export function TTSProvider({ children }: { children: React.ReactNode }) {
});
// Reset skip flag after a short delay
setTimeout(() => {
skipTimeoutRef.current = setTimeout(() => {
skipTriggeredRef.current = false;
}, 100);
}, [sentences, activeSource]);

169
src/services/indexedDB.ts Normal file
View file

@ -0,0 +1,169 @@
const DB_NAME = 'openreader-db';
const DB_VERSION = 1;
const STORE_NAME = 'pdf-documents';
export interface PDFDocument {
id: string;
name: string;
size: number;
lastModified: number;
data: Blob;
}
class IndexedDBService {
private db: IDBDatabase | null = null;
async init(): Promise<void> {
if (!window.indexedDB) {
throw new Error('IndexedDB is not supported in this browser');
}
return new Promise((resolve, reject) => {
console.log('Initializing IndexedDB...');
const request = indexedDB.open(DB_NAME, DB_VERSION);
request.onerror = (event) => {
const error = (event.target as IDBRequest).error;
console.error('IndexedDB initialization error:', error);
reject(error);
};
request.onsuccess = (event) => {
console.log('IndexedDB initialized successfully');
this.db = (event.target as IDBOpenDBRequest).result;
resolve();
};
request.onupgradeneeded = (event) => {
console.log('Upgrading IndexedDB schema...');
const db = (event.target as IDBOpenDBRequest).result;
if (!db.objectStoreNames.contains(STORE_NAME)) {
console.log('Creating PDF documents store...');
db.createObjectStore(STORE_NAME, { keyPath: 'id' });
}
};
});
}
async addDocument(document: PDFDocument): Promise<void> {
if (!this.db) {
console.log('Database not initialized, initializing now...');
await this.init();
}
return new Promise((resolve, reject) => {
try {
console.log('Adding document to IndexedDB:', document.name);
const transaction = this.db!.transaction([STORE_NAME], 'readwrite');
const store = transaction.objectStore(STORE_NAME);
const request = store.put(document);
request.onerror = (event) => {
const error = (event.target as IDBRequest).error;
console.error('Error adding document:', error);
reject(error);
};
transaction.oncomplete = () => {
console.log('Document added successfully:', document.name);
resolve();
};
} catch (error) {
console.error('Error in addDocument transaction:', error);
reject(error);
}
});
}
async getDocument(id: string): Promise<PDFDocument | undefined> {
if (!this.db) {
console.log('Database not initialized, initializing now...');
await this.init();
}
return new Promise((resolve, reject) => {
try {
console.log('Fetching document:', id);
const transaction = this.db!.transaction([STORE_NAME], 'readonly');
const store = transaction.objectStore(STORE_NAME);
const request = store.get(id);
request.onerror = (event) => {
const error = (event.target as IDBRequest).error;
console.error('Error fetching document:', error);
reject(error);
};
request.onsuccess = () => {
console.log('Document fetch result:', request.result ? 'found' : 'not found');
resolve(request.result);
};
} catch (error) {
console.error('Error in getDocument transaction:', error);
reject(error);
}
});
}
async getAllDocuments(): Promise<PDFDocument[]> {
if (!this.db) {
console.log('Database not initialized, initializing now...');
await this.init();
}
return new Promise((resolve, reject) => {
try {
console.log('Fetching all documents');
const transaction = this.db!.transaction([STORE_NAME], 'readonly');
const store = transaction.objectStore(STORE_NAME);
const request = store.getAll();
request.onerror = (event) => {
const error = (event.target as IDBRequest).error;
console.error('Error fetching all documents:', error);
reject(error);
};
request.onsuccess = () => {
console.log('Retrieved documents count:', request.result?.length || 0);
resolve(request.result || []);
};
} catch (error) {
console.error('Error in getAllDocuments transaction:', error);
reject(error);
}
});
}
async removeDocument(id: string): Promise<void> {
if (!this.db) {
console.log('Database not initialized, initializing now...');
await this.init();
}
return new Promise((resolve, reject) => {
try {
console.log('Removing document:', id);
const transaction = this.db!.transaction([STORE_NAME], 'readwrite');
const store = transaction.objectStore(STORE_NAME);
const request = store.delete(id);
request.onerror = (event) => {
const error = (event.target as IDBRequest).error;
console.error('Error removing document:', error);
reject(error);
};
transaction.oncomplete = () => {
console.log('Document removed successfully:', id);
resolve();
};
} catch (error) {
console.error('Error in removeDocument transaction:', error);
reject(error);
}
});
}
}
export const indexedDBService = new IndexedDBService();