Save document list state to confid db + add unified document types

This commit is contained in:
Richard Roberson 2025-02-12 20:49:37 -07:00
parent a6915ee51d
commit cc654f5f1d
6 changed files with 138 additions and 53 deletions

View file

@ -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<Document>;
};
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<SortDirection>('desc');
const [folders, setFolders] = useState<Folder[]>([]);
const [documentToDelete, setDocumentToDelete] = useState<DocumentToDelete | null>(null);
const [draggedDoc, setDraggedDoc] = useState<Document | null>(null);
const [dropTargetDoc, setDropTargetDoc] = useState<Document | null>(null);
const [draggedDoc, setDraggedDoc] = useState<DocumentListDocument | null>(null);
const [dropTargetDoc, setDropTargetDoc] = useState<DocumentListDocument | null>(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<Set<string>>(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<string>) => {
const next = new Set(prev);
if (next.has(folderId)) {
next.delete(folderId);
@ -259,7 +289,7 @@ export function DocumentList() {
</div>
);
const renderDocument = (doc: Document) => (
const renderDocument = (doc: DocumentListDocument) => (
<div
key={`${doc.type}-${doc.id}`}
draggable={!doc.folderId} // Only make unfoldered documents draggable
@ -314,7 +344,7 @@ export function DocumentList() {
);
// Add this helper function before renderFolder
const calculateFolderSize = (documents: Document[]) => {
const calculateFolderSize = (documents: DocumentListDocument[]) => {
return documents.reduce((total, doc) => total + doc.size, 0);
};

View file

@ -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

View file

@ -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,

View file

@ -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,

40
src/types/documents.ts Normal file
View file

@ -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[];
}

View file

@ -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<void> {
return this.setConfigItem('documentListState', JSON.stringify(state));
}
async getDocumentListState(): Promise<DocumentListState | null> {
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<string | n
export async function setLastDocumentLocation(docId: string, location: string): Promise<void> {
const key = `lastLocation_${docId}`;
return indexedDBService.setConfigItem(key, location);
}
export async function getDocumentListState(): Promise<DocumentListState | null> {
return indexedDBService.getDocumentListState();
}
export async function saveDocumentListState(state: DocumentListState): Promise<void> {
return indexedDBService.saveDocumentListState(state);
}