Save document list state to confid db + add unified document types
This commit is contained in:
parent
a6915ee51d
commit
cc654f5f1d
6 changed files with 138 additions and 53 deletions
|
|
@ -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);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
40
src/types/documents.ts
Normal 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[];
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
Loading…
Reference in a new issue