From cc654f5f1d9fc1561c2eb87376f1f98ce2a5b93f Mon Sep 17 00:00:00 2001 From: Richard Roberson Date: Wed, 12 Feb 2025 20:49:37 -0700 Subject: [PATCH] Save document list state to confid db + add unified document types --- src/components/DocumentList.tsx | 98 +++++++++++++++++++++----------- src/contexts/DocumentContext.tsx | 2 +- src/hooks/useEPUBDocuments.ts | 4 +- src/hooks/usePDFDocuments.ts | 4 +- src/types/documents.ts | 40 +++++++++++++ src/utils/indexedDB.ts | 43 ++++++++------ 6 files changed, 138 insertions(+), 53 deletions(-) create mode 100644 src/types/documents.ts diff --git a/src/components/DocumentList.tsx b/src/components/DocumentList.tsx index 2f29b08..65da8f1 100644 --- a/src/components/DocumentList.tsx +++ b/src/components/DocumentList.tsx @@ -1,7 +1,7 @@ 'use client'; import Link from 'next/link'; -import { useCallback, useState } from 'react'; +import { useCallback, useState, useEffect } from 'react'; import { useDocuments } from '@/contexts/DocumentContext'; import { PDFIcon, EPUBIcon, ChevronUpDownIcon } from '@/components/icons/Icons'; import { ConfirmDialog } from '@/components/ConfirmDialog'; @@ -16,32 +16,23 @@ import { PopoverPanel, Button, } from '@headlessui/react'; +import { getDocumentListState, saveDocumentListState } from '@/utils/indexedDB'; +import { + DocumentType, + DocumentListDocument, + Folder, + DocumentListState, + SortBy, + SortDirection +} from '@/types/documents'; type DocumentToDelete = { id: string; name: string; - type: 'pdf' | 'epub'; + type: DocumentType; }; -type Folder = { - id: string; - name: string; - documents: Array; -}; - -type Document = { - id: string; - name: string; - type: 'pdf' | 'epub'; - size: number; - lastModified: number; - folderId?: string; -}; - -type SortBy = 'name' | 'type' | 'date' | 'size'; -type SortDirection = 'asc' | 'desc'; - -const generateDefaultFolderName = (doc1: Document, doc2: Document) => { +const generateDefaultFolderName = (doc1: DocumentListDocument, doc2: DocumentListDocument) => { // Try to find common words between the two document names const words1 = doc1.name.toLowerCase().split(/[\s-_\.]+/); const words2 = doc2.name.toLowerCase().split(/[\s-_\.]+/); @@ -74,16 +65,55 @@ export function DocumentList() { const [sortDirection, setSortDirection] = useState('desc'); const [folders, setFolders] = useState([]); const [documentToDelete, setDocumentToDelete] = useState(null); - const [draggedDoc, setDraggedDoc] = useState(null); - const [dropTargetDoc, setDropTargetDoc] = useState(null); + const [draggedDoc, setDraggedDoc] = useState(null); + const [dropTargetDoc, setDropTargetDoc] = useState(null); const [newFolderName, setNewFolderName] = useState(''); - const [pendingFolderDocs, setPendingFolderDocs] = useState<{ source: Document, target: Document } | null>(null); + const [pendingFolderDocs, setPendingFolderDocs] = useState<{ source: DocumentListDocument, target: DocumentListDocument } | null>(null); const [collapsedFolders, setCollapsedFolders] = useState>(new Set()); const [mousePosition, setMousePosition] = useState({ x: 0, y: 0 }); - const allDocuments: Document[] = [ - ...pdfDocs.map(doc => ({ ...doc, type: 'pdf' as const })), - ...epubDocs.map(doc => ({ ...doc, type: 'epub' as const })), + useEffect(() => { + // Load saved state + const loadState = async () => { + const savedState = await getDocumentListState(); + if (savedState) { + setSortBy(savedState.sortBy); + setSortDirection(savedState.sortDirection); + setFolders(savedState.folders); + setCollapsedFolders(new Set(savedState.collapsedFolders)); + } + }; + loadState(); + }, []); + + useEffect(() => { + const saveState = async () => { + const state: DocumentListState = { + sortBy, + sortDirection, + folders, + collapsedFolders: Array.from(collapsedFolders) + }; + await saveDocumentListState(state); + }; + saveState(); + }, [sortBy, sortDirection, folders, collapsedFolders]); + + const allDocuments: DocumentListDocument[] = [ + ...pdfDocs.map(doc => ({ + id: doc.id, + name: doc.name, + size: doc.size, + lastModified: doc.lastModified, + type: 'pdf' as const, + })), + ...epubDocs.map(doc => ({ + id: doc.id, + name: doc.name, + size: doc.size, + lastModified: doc.lastModified, + type: 'epub' as const, + })), ]; const sortOptions: Array<{ value: SortBy; label: string }> = [ @@ -93,7 +123,7 @@ export function DocumentList() { { value: 'size', label: 'Size' }, ]; - const sortDocuments = useCallback((docs: Document[]) => { + const sortDocuments = useCallback((docs: DocumentListDocument[]) => { return [...docs].sort((a, b) => { switch (sortBy) { case 'name': @@ -116,7 +146,7 @@ export function DocumentList() { }); }, [sortBy, sortDirection]); - const handleDragStart = (doc: Document) => { + const handleDragStart = (doc: DocumentListDocument) => { // Only allow dragging documents that aren't in folders if (!doc.folderId) { setDraggedDoc(doc); @@ -128,7 +158,7 @@ export function DocumentList() { setDropTargetDoc(null); }; - const handleDragOver = (e: React.DragEvent, doc: Document) => { + const handleDragOver = (e: React.DragEvent, doc: DocumentListDocument) => { e.preventDefault(); if (draggedDoc && draggedDoc.id !== doc.id && !draggedDoc.folderId) { // Only highlight target if neither document is in a folder @@ -143,7 +173,7 @@ export function DocumentList() { setDropTargetDoc(null); }; - const handleDrop = useCallback((e: React.DragEvent, targetDoc: Document) => { + const handleDrop = useCallback((e: React.DragEvent, targetDoc: DocumentListDocument) => { e.preventDefault(); if (!draggedDoc || draggedDoc.id === targetDoc.id || draggedDoc.folderId) return; @@ -214,7 +244,7 @@ export function DocumentList() { }, [documentToDelete, removePDF, removeEPUB]); const toggleFolderCollapse = (folderId: string) => { - setCollapsedFolders(prev => { + setCollapsedFolders((prev: Set) => { const next = new Set(prev); if (next.has(folderId)) { next.delete(folderId); @@ -259,7 +289,7 @@ export function DocumentList() { ); - const renderDocument = (doc: Document) => ( + const renderDocument = (doc: DocumentListDocument) => (
{ + const calculateFolderSize = (documents: DocumentListDocument[]) => { return documents.reduce((total, doc) => total + doc.size, 0); }; diff --git a/src/contexts/DocumentContext.tsx b/src/contexts/DocumentContext.tsx index 8fa01e5..1e136e1 100644 --- a/src/contexts/DocumentContext.tsx +++ b/src/contexts/DocumentContext.tsx @@ -3,7 +3,7 @@ import { createContext, useContext, ReactNode } from 'react'; import { usePDFDocuments } from '@/hooks/usePDFDocuments'; import { useEPUBDocuments } from '@/hooks/useEPUBDocuments'; -import { PDFDocument, EPUBDocument } from '@/utils/indexedDB'; +import { PDFDocument, EPUBDocument } from '@/types/documents'; interface DocumentContextType { // PDF Documents diff --git a/src/hooks/useEPUBDocuments.ts b/src/hooks/useEPUBDocuments.ts index fe48243..c0cc993 100644 --- a/src/hooks/useEPUBDocuments.ts +++ b/src/hooks/useEPUBDocuments.ts @@ -2,7 +2,8 @@ import { useState, useCallback, useEffect } from 'react'; import { v4 as uuidv4 } from 'uuid'; -import { indexedDBService, type EPUBDocument } from '@/utils/indexedDB'; +import { indexedDBService } from '@/utils/indexedDB'; +import type { EPUBDocument } from '@/types/documents'; import { useConfig } from '@/contexts/ConfigContext'; export function useEPUBDocuments() { @@ -36,6 +37,7 @@ export function useEPUBDocuments() { const newDoc: EPUBDocument = { id, + type: 'epub', name: file.name, size: file.size, lastModified: file.lastModified, diff --git a/src/hooks/usePDFDocuments.ts b/src/hooks/usePDFDocuments.ts index c413d3a..45d6ec3 100644 --- a/src/hooks/usePDFDocuments.ts +++ b/src/hooks/usePDFDocuments.ts @@ -2,7 +2,8 @@ import { useState, useCallback, useEffect } from 'react'; import { v4 as uuidv4 } from 'uuid'; -import { indexedDBService, type PDFDocument } from '@/utils/indexedDB'; +import { indexedDBService } from '@/utils/indexedDB'; +import type { PDFDocument } from '@/types/documents'; import { useConfig } from '@/contexts/ConfigContext'; export function usePDFDocuments() { @@ -40,6 +41,7 @@ export function usePDFDocuments() { const id = uuidv4(); const newDoc: PDFDocument = { id, + type: 'pdf', name: file.name, size: file.size, lastModified: file.lastModified, diff --git a/src/types/documents.ts b/src/types/documents.ts new file mode 100644 index 0000000..7c3efed --- /dev/null +++ b/src/types/documents.ts @@ -0,0 +1,40 @@ +export type DocumentType = 'pdf' | 'epub'; + +export interface BaseDocument { + id: string; + name: string; + size: number; + lastModified: number; + type: DocumentType; + folderId?: string; +} + +export interface PDFDocument extends BaseDocument { + type: 'pdf'; + data: Blob; +} + +export interface EPUBDocument extends BaseDocument { + type: 'epub'; + data: ArrayBuffer; +} + +export interface DocumentListDocument extends BaseDocument { + type: DocumentType; +} + +export interface Folder { + id: string; + name: string; + documents: DocumentListDocument[]; +} + +export type SortBy = 'name' | 'type' | 'date' | 'size'; +export type SortDirection = 'asc' | 'desc'; + +export interface DocumentListState { + sortBy: SortBy; + sortDirection: SortDirection; + folders: Folder[]; + collapsedFolders: string[]; +} diff --git a/src/utils/indexedDB.ts b/src/utils/indexedDB.ts index fcfe8cc..20f0ed1 100644 --- a/src/utils/indexedDB.ts +++ b/src/utils/indexedDB.ts @@ -1,25 +1,11 @@ +import { PDFDocument, EPUBDocument, DocumentListState } from '@/types/documents'; + const DB_NAME = 'openreader-db'; const DB_VERSION = 2; // Increased version number const PDF_STORE_NAME = 'pdf-documents'; const EPUB_STORE_NAME = 'epub-documents'; const CONFIG_STORE_NAME = 'config'; -export interface PDFDocument { - id: string; - name: string; - size: number; - lastModified: number; - data: Blob; -} - -export interface EPUBDocument { - id: string; - name: string; - size: number; - lastModified: number; - data: ArrayBuffer; // Changed from Blob to ArrayBuffer -} - export interface Config { key: string; value: string; @@ -542,6 +528,7 @@ class IndexedDBService { const blob = new Blob([new Uint8Array(doc.data)], { type: 'application/pdf' }); await this.addDocument({ id: doc.id, + type: doc.type, name: doc.name, size: doc.size, lastModified: doc.lastModified, @@ -552,6 +539,7 @@ class IndexedDBService { const uint8Array = new Uint8Array(doc.data); await this.addEPUBDocument({ id: doc.id, + type: doc.type, name: doc.name, size: doc.size, lastModified: doc.lastModified, @@ -620,6 +608,21 @@ class IndexedDBService { } }); } + + async saveDocumentListState(state: DocumentListState): Promise { + return this.setConfigItem('documentListState', JSON.stringify(state)); + } + + async getDocumentListState(): Promise { + const stateStr = await this.getConfigItem('documentListState'); + if (!stateStr) return null; + try { + return JSON.parse(stateStr); + } catch (error) { + console.error('Error parsing document list state:', error); + return null; + } + } } // Make sure we export a singleton instance @@ -644,4 +647,12 @@ export async function getLastDocumentLocation(docId: string): Promise { const key = `lastLocation_${docId}`; return indexedDBService.setConfigItem(key, location); +} + +export async function getDocumentListState(): Promise { + return indexedDBService.getDocumentListState(); +} + +export async function saveDocumentListState(state: DocumentListState): Promise { + return indexedDBService.saveDocumentListState(state); } \ No newline at end of file