Files
readest/apps/readest-app/src/components/metadata/BookDetailModal.tsx
T

261 lines
7.8 KiB
TypeScript

import clsx from 'clsx';
import React, { useEffect, useState } from 'react';
import { Book } from '@/types/book';
import { BookMetadata } from '@/libs/document';
import { useEnv } from '@/context/EnvContext';
import { useSettingsStore } from '@/store/settingsStore';
import { useTranslation } from '@/hooks/useTranslation';
import { useMetadataEdit } from './useMetadataEdit';
import { DeleteAction } from '@/types/system';
import Alert from '@/components/Alert';
import Dialog from '@/components/Dialog';
import Spinner from '@/components/Spinner';
import BookDetailView from './BookDetailView';
import BookDetailEdit from './BookDetailEdit';
import SourceSelector from './SourceSelector';
interface BookDetailModalProps {
book: Book;
isOpen: boolean;
onClose: () => void;
handleBookDownload?: (book: Book, redownload?: boolean) => void;
handleBookUpload?: (book: Book) => void;
handleBookDelete?: (book: Book) => void;
handleBookDeleteCloudBackup?: (book: Book) => void;
handleBookDeleteLocalCopy?: (book: Book) => void;
handleBookMetadataUpdate?: (book: Book, updatedMetadata: BookMetadata) => void;
}
interface DeleteConfig {
title: string;
message: string;
handler?: (book: Book) => void;
}
const BookDetailModal: React.FC<BookDetailModalProps> = ({
book,
isOpen,
onClose,
handleBookDownload,
handleBookUpload,
handleBookDelete,
handleBookDeleteCloudBackup,
handleBookDeleteLocalCopy,
handleBookMetadataUpdate,
}) => {
const _ = useTranslation();
const [loading, setLoading] = useState(false);
const [activeDeleteAction, setActiveDeleteAction] = useState<DeleteAction | null>(null);
const [editMode, setEditMode] = useState(false);
const [bookMeta, setBookMeta] = useState<BookMetadata | null>(null);
const [fileSize, setFileSize] = useState<number | null>(null);
const { envConfig } = useEnv();
const { settings } = useSettingsStore();
// Initialize metadata edit hook
const {
editedMeta,
fieldSources,
lockedFields,
fieldErrors,
searchLoading,
showSourceSelection,
availableSources,
handleFieldChange,
handleToggleFieldLock,
handleLockAll,
handleUnlockAll,
handleAutoRetrieve,
handleSourceSelection,
handleCloseSourceSelection,
resetToOriginal,
} = useMetadataEdit(bookMeta);
const deleteConfigs: Record<DeleteAction, DeleteConfig> = {
both: {
title: _('Confirm Deletion'),
message: _('Are you sure to delete the selected book?'),
handler: handleBookDelete,
},
cloud: {
title: _('Confirm Deletion'),
message: _('Are you sure to delete the cloud backup of the selected book?'),
handler: handleBookDeleteCloudBackup,
},
local: {
title: _('Confirm Deletion'),
message: _('Are you sure to delete the local copy of the selected book?'),
handler: handleBookDeleteLocalCopy,
},
};
useEffect(() => {
const loadingTimeout = setTimeout(() => setLoading(true), 300);
const fetchBookDetails = async () => {
const appService = await envConfig.getAppService();
try {
const details = book.metadata || (await appService.fetchBookDetails(book, settings));
setBookMeta(details);
const size = await appService.getBookFileSize(book);
setFileSize(size);
} finally {
if (loadingTimeout) clearTimeout(loadingTimeout);
setLoading(false);
}
};
fetchBookDetails();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [book]);
const handleClose = () => {
setBookMeta(null);
setEditMode(false);
setActiveDeleteAction(null);
onClose();
};
const handleEditMetadata = () => {
setEditMode(true);
};
const handleCancelEdit = () => {
resetToOriginal();
setEditMode(false);
};
const handleSaveMetadata = () => {
if (editedMeta && handleBookMetadataUpdate) {
setBookMeta({ ...editedMeta });
handleBookMetadataUpdate(book, editedMeta);
setEditMode(false);
}
};
const handleDeleteAction = (action: DeleteAction) => {
setActiveDeleteAction(action);
};
const confirmDeleteAction = async () => {
if (!activeDeleteAction) return;
const config = deleteConfigs[activeDeleteAction];
handleClose();
if (config.handler) {
config.handler(book);
}
};
const cancelDeleteAction = () => {
setActiveDeleteAction(null);
};
const handleDelete = () => handleDeleteAction('both');
const handleDeleteCloudBackup = () => handleDeleteAction('cloud');
const handleDeleteLocalCopy = () => handleDeleteAction('local');
const handleRedownload = async () => {
handleClose();
if (handleBookDownload) {
handleBookDownload(book, true);
}
};
const handleReupload = async () => {
handleClose();
if (handleBookUpload) {
handleBookUpload(book);
}
};
const currentDeleteConfig = activeDeleteAction ? deleteConfigs[activeDeleteAction] : null;
if (!bookMeta)
return (
loading && (
<div className='fixed inset-0 z-50 flex items-center justify-center'>
<Spinner loading />
</div>
)
);
return (
<>
<div className='fixed inset-0 z-50 flex items-center justify-center'>
<Dialog
title={editMode ? _('Edit Metadata') : _('Book Details')}
isOpen={isOpen}
onClose={handleClose}
boxClassName={clsx(
editMode ? 'sm:min-w-[600px] sm:max-w-[600px]' : 'sm:min-w-[480px] sm:max-w-[480px]',
'sm:h-auto sm:max-h-[90%]',
)}
contentClassName='!px-6 !py-4'
>
<div className='flex w-full select-text items-start justify-center'>
{editMode ? (
<BookDetailEdit
book={book}
metadata={editedMeta}
fieldSources={fieldSources}
lockedFields={lockedFields}
fieldErrors={fieldErrors}
searchLoading={searchLoading}
onFieldChange={handleFieldChange}
onToggleFieldLock={handleToggleFieldLock}
onAutoRetrieve={handleAutoRetrieve}
onLockAll={handleLockAll}
onUnlockAll={handleUnlockAll}
onCancel={handleCancelEdit}
onReset={resetToOriginal}
onSave={handleSaveMetadata}
/>
) : (
<BookDetailView
book={book}
metadata={bookMeta}
fileSize={fileSize}
onEdit={handleBookMetadataUpdate ? handleEditMetadata : undefined}
onDelete={handleBookDelete ? handleDelete : undefined}
onDeleteCloudBackup={
handleBookDeleteCloudBackup ? handleDeleteCloudBackup : undefined
}
onDeleteLocalCopy={handleBookDeleteLocalCopy ? handleDeleteLocalCopy : undefined}
onDownload={handleBookDownload ? handleRedownload : undefined}
onUpload={handleBookUpload ? handleReupload : undefined}
/>
)}
</div>
</Dialog>
{/* Source Selection Modal */}
<SourceSelector
sources={availableSources}
isOpen={showSourceSelection}
onSelect={handleSourceSelection}
onClose={handleCloseSourceSelection}
/>
{activeDeleteAction && currentDeleteConfig && (
<div
className={clsx(
'fixed bottom-0 left-0 right-0 z-50 flex justify-center',
'pb-[calc(env(safe-area-inset-bottom)+16px)]',
)}
>
<Alert
title={currentDeleteConfig.title}
message={currentDeleteConfig.message}
onCancel={cancelDeleteAction}
onConfirm={confirmDeleteAction}
/>
</div>
)}
</div>
</>
);
};
export default BookDetailModal;