mirror of
https://github.com/readest/readest.git
synced 2026-05-05 23:37:16 +00:00
Optimize book details modal (#47)
This commit is contained in:
parent
4612730474
commit
07b08ee568
1 changed files with 103 additions and 97 deletions
|
|
@ -1,23 +1,24 @@
|
|||
import clsx from 'clsx';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
|
||||
import { Book } from '@/types/book';
|
||||
import { EnvConfigType } from '@/services/environment';
|
||||
import { useSettingsStore } from '@/store/settingsStore';
|
||||
import Image from 'next/image';
|
||||
|
||||
import { Book } from '@/types/book';
|
||||
import { useEnv } from '@/context/EnvContext';
|
||||
import { useSettingsStore } from '@/store/settingsStore';
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
import { formatDate, formatSubject } from '@/utils/book';
|
||||
import WindowButtons from '@/components/WindowButtons';
|
||||
import Spinner from './Spinner';
|
||||
|
||||
const BookDetailModal = ({
|
||||
isOpen,
|
||||
onClose,
|
||||
book,
|
||||
envConfig,
|
||||
}: {
|
||||
interface BookDetailModalProps {
|
||||
book: Book;
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
book: Book;
|
||||
envConfig: EnvConfigType;
|
||||
}) => {
|
||||
}
|
||||
|
||||
const BookDetailModal = ({ book, isOpen, onClose }: BookDetailModalProps) => {
|
||||
const _ = useTranslation();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [bookMeta, setBookMeta] = useState<null | {
|
||||
title: string;
|
||||
language: string | string[];
|
||||
|
|
@ -28,120 +29,125 @@ const BookDetailModal = ({
|
|||
subject?: string[];
|
||||
identifier?: string;
|
||||
}>(null);
|
||||
|
||||
const { envConfig } = useEnv();
|
||||
const { settings } = useSettingsStore();
|
||||
|
||||
useEffect(() => {
|
||||
const loadingTimeout = setTimeout(() => setLoading(true), 300);
|
||||
const fetchBookDetails = async () => {
|
||||
const appService = await envConfig.getAppService();
|
||||
const details = await appService.fetchBookDetails(book, settings);
|
||||
setBookMeta(details);
|
||||
setLoading(false);
|
||||
if (loadingTimeout) clearTimeout(loadingTimeout);
|
||||
};
|
||||
fetchBookDetails();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [book]);
|
||||
|
||||
const handleClose = () => {
|
||||
setBookMeta(null);
|
||||
onClose();
|
||||
};
|
||||
|
||||
if (!isOpen) return null;
|
||||
|
||||
if (!bookMeta)
|
||||
return (
|
||||
<div className='fixed inset-0 z-50 flex items-center justify-center'>
|
||||
<div className='fixed inset-0 bg-gray-800 bg-opacity-70' onClick={onClose} />
|
||||
loading && (
|
||||
<div className='fixed inset-0 z-50 flex items-center justify-center'>
|
||||
<Spinner loading />
|
||||
</div>
|
||||
)
|
||||
);
|
||||
|
||||
<div className='bg-base-200 relative z-50 w-full max-w-md rounded-lg p-6 shadow-xl'>
|
||||
return (
|
||||
<div className='fixed inset-0 z-50 flex w-full select-text items-center justify-center'>
|
||||
<div className='min-w-[480px] max-w-md'>
|
||||
<div className='fixed inset-0 bg-gray-800 bg-opacity-70' onClick={handleClose} />
|
||||
|
||||
<div className='bg-base-200 relative z-50 w-full rounded-lg p-6 shadow-xl'>
|
||||
<div className='absolute right-4 top-4 flex space-x-2'>
|
||||
<WindowButtons
|
||||
className='window-buttons flex'
|
||||
showMinimize={false}
|
||||
showMaximize={false}
|
||||
onClose={onClose}
|
||||
onClose={handleClose}
|
||||
/>
|
||||
</div>
|
||||
<h2 className='text-base-content text-center text-2xl font-semibold'>
|
||||
Loading Book Details...
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className='fixed inset-0 z-50 flex items-center justify-center'>
|
||||
<div className='fixed inset-0 bg-gray-800 bg-opacity-70' onClick={onClose} />
|
||||
|
||||
<div className='bg-base-200 relative z-50 w-full max-w-md rounded-lg p-6 shadow-xl'>
|
||||
<div className='absolute right-4 top-4 flex space-x-2'>
|
||||
<WindowButtons
|
||||
className='window-buttons flex'
|
||||
showMinimize={false}
|
||||
showMaximize={false}
|
||||
onClose={onClose}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='mb-6 flex items-start'>
|
||||
{book.coverImageUrl ? (
|
||||
<Image
|
||||
src={book.coverImageUrl}
|
||||
alt={book.title}
|
||||
width={110}
|
||||
height={165}
|
||||
className='mr-4 h-40 w-30 rounded-lg object-contain shadow-md'
|
||||
/>
|
||||
) : (
|
||||
<div className='mr-4 flex h-40 w-30 items-center justify-center rounded-lg bg-gray-300'>
|
||||
<span className='text-gray-500'>No Image</span>
|
||||
<div className='mb-6 flex h-40 items-start'>
|
||||
<div className='book-cover relative mr-10 aspect-[28/41] h-40 items-end shadow-lg'>
|
||||
<Image
|
||||
src={book.coverImageUrl!}
|
||||
alt={book.title}
|
||||
fill={true}
|
||||
className='w-10 object-cover'
|
||||
onError={(e) => {
|
||||
(e.target as HTMLImageElement).style.display = 'none';
|
||||
(e.target as HTMLImageElement).nextElementSibling?.classList.remove('invisible');
|
||||
}}
|
||||
/>
|
||||
<div
|
||||
className={clsx(
|
||||
'invisible absolute inset-0 flex items-center justify-center p-1',
|
||||
'text-neutral-content rounded-none text-center font-serif text-base font-medium',
|
||||
)}
|
||||
>
|
||||
{book.title}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className='h-40 flex flex-col justify-between'>
|
||||
<h2 className='text-base-content mb-2 text-2xl font-bold'>
|
||||
{bookMeta.title || 'Untitled'}
|
||||
</h2>
|
||||
<p className='text-neutral-content mb-4'>{book.author || 'Unknown Author'}</p>
|
||||
<button className='mt-4 rounded bg-blue-500 px-4 py-2 text-white hover:bg-blue-600'>
|
||||
More Info
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='text-base-content mb-4'>
|
||||
<div className='mb-4 grid grid-cols-3 gap-4'>
|
||||
<div>
|
||||
<span className='font-bold'>Publisher:</span>
|
||||
<p className='text-neutral-content'>{bookMeta.publisher || 'Unknown'}</p>
|
||||
</div>
|
||||
<div>
|
||||
<span className='font-bold'>Published:</span>
|
||||
<p className='text-neutral-content'>{bookMeta.published || 'Unknown Date'}</p>
|
||||
</div>
|
||||
<div>
|
||||
<span className='font-bold'>Updated:</span>
|
||||
<p className='text-neutral-content'>
|
||||
{book.lastUpdated
|
||||
? new Date(book.lastUpdated).toLocaleDateString('en-US', {
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric',
|
||||
})
|
||||
: 'Unknown Date'}
|
||||
</p>
|
||||
<div className='title-author flex h-40 flex-col justify-between pr-4'>
|
||||
<div>
|
||||
<h2 className='text-base-content mb-2 line-clamp-2 text-2xl font-bold'>
|
||||
{bookMeta.title || _('Untitled')}
|
||||
</h2>
|
||||
<p className='text-neutral-content line-clamp-1'>{book.author || _('Unknown')}</p>
|
||||
</div>
|
||||
<button className='btn-disabled bg-primary/25 hover:bg-primary/85 w-36 rounded px-4 py-2 text-white'>
|
||||
{_('More Info')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='grid grid-cols-3 gap-4'>
|
||||
<div>
|
||||
<span className='font-bold'>Language:</span>
|
||||
<p className='text-neutral-content'>{bookMeta.language || 'Unknown'}</p>
|
||||
<div className='text-base-content mb-4'>
|
||||
<div className='mb-4 grid grid-cols-3 gap-4'>
|
||||
<div className='overflow-hidden'>
|
||||
<span className='font-bold'>{_('Publisher:')}</span>
|
||||
<p className='text-neutral-content line-clamp-1 text-sm'>
|
||||
{bookMeta.publisher || _('Unknown')}
|
||||
</p>
|
||||
</div>
|
||||
<div className='overflow-hidden'>
|
||||
<span className='font-bold'>{_('Published:')}</span>
|
||||
<p className='text-neutral-content max-w-28 text-ellipsis text-sm'>
|
||||
{formatDate(bookMeta.published) || _('Unknown')}
|
||||
</p>
|
||||
</div>
|
||||
<div className='overflow-hidden'>
|
||||
<span className='font-bold'>{_('Updated:')}</span>
|
||||
<p className='text-neutral-content text-sm'>{formatDate(book.lastUpdated) || ''}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<span className='font-bold'>Identifier:</span>
|
||||
<p className='text-neutral-content'>
|
||||
{bookMeta.identifier ? bookMeta.identifier.slice(-8) : 'N/A'}
|
||||
</p>{' '}
|
||||
{/* Show last 8 characters */}
|
||||
</div>
|
||||
<div>
|
||||
<span className='font-bold'>Subjects:</span>
|
||||
<p className='text-neutral-content'>{bookMeta.subject?.join(', ') || 'None'}</p>
|
||||
|
||||
<div className='grid grid-cols-3 gap-4'>
|
||||
<div className='overflow-hidden'>
|
||||
<span className='font-bold'>{_('Language:')}</span>
|
||||
<p className='text-neutral-content text-sm'>{bookMeta.language || _('Unknown')}</p>
|
||||
</div>
|
||||
<div className='overflow-hidden'>
|
||||
<span className='font-bold'>{_('Identifier:')}</span>
|
||||
<p className='text-neutral-content line-clamp-1 text-sm'>
|
||||
{bookMeta.identifier || 'N/A'}
|
||||
</p>
|
||||
</div>
|
||||
<div className='overflow-hidden'>
|
||||
<span className='font-bold'>{_('Subjects:')}</span>
|
||||
<p className='text-neutral-content line-clamp-1 text-sm'>
|
||||
{formatSubject(bookMeta.subject) || _('Unknown')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue