Remove ExportBook component and integrate new export workflows
- Deleted `ExportBook` component and its usage in `BookCard.tsx`. - Integrated improved book export workflows in `BookSettingOption` for better user experience. - Updated database models and repositories to support export options with chapter/version selection. - Added localization support for export-related messages and tooltips. - Upgraded dependencies to include libraries required for export formats (e.g., DOCX, PDF, EPUB). - Bumped app version to 0.4.1.
This commit is contained in:
@@ -1,10 +1,10 @@
|
||||
import {useContext, useEffect, useState} from "react";
|
||||
import {useContext, useEffect, useRef, useState} from "react";
|
||||
import System from "@/lib/models/System";
|
||||
import {AlertContext} from "@/context/AlertContext";
|
||||
import {BookContext} from "@/context/BookContext";
|
||||
import SearchBook from "./SearchBook";
|
||||
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
|
||||
import {faBook, faDownload, faGear, faTrash} from "@fortawesome/free-solid-svg-icons";
|
||||
import {faBook, faChevronLeft, faChevronRight, faDownload, faGear, faTrash} from "@fortawesome/free-solid-svg-icons";
|
||||
import {SessionContext} from "@/context/SessionContext";
|
||||
import Book, {BookProps} from "@/lib/models/Book";
|
||||
import BookCard from "@/components/book/BookCard";
|
||||
@@ -55,6 +55,7 @@ export default function BookList() {
|
||||
const [isLoadingBooks, setIsLoadingBooks] = useState<boolean>(true);
|
||||
const [showSeriesSettingId, setShowSeriesSettingId] = useState<string | null>(null);
|
||||
const [isLocalSeries, setIsLocalSeries] = useState<boolean>(false);
|
||||
const carouselRefs = useRef<Record<string, HTMLDivElement | null>>({});
|
||||
|
||||
const [bookGuide, setBookGuide] = useState<boolean>(false);
|
||||
|
||||
@@ -447,6 +448,17 @@ export default function BookList() {
|
||||
}
|
||||
}
|
||||
|
||||
function scrollCarousel(category: string, direction: 'left' | 'right'): void {
|
||||
const container: HTMLDivElement | null = carouselRefs.current[category];
|
||||
if (!container) return;
|
||||
const cardWidth: number = container.querySelector<HTMLDivElement>(':scope > div')?.offsetWidth || 250;
|
||||
const scrollAmount: number = cardWidth * 2;
|
||||
container.scrollBy({
|
||||
left: direction === 'left' ? -scrollAmount : scrollAmount,
|
||||
behavior: 'smooth'
|
||||
});
|
||||
}
|
||||
|
||||
function handleSeriesSettingsClick(seriesId: string): void {
|
||||
const isLocal: boolean = isCurrentlyOffline() ||
|
||||
Boolean(localOnlySeries.find((s: SyncedSeries): boolean => s.id === seriesId));
|
||||
@@ -464,7 +476,7 @@ export default function BookList() {
|
||||
<SearchBook searchQuery={searchQuery} setSearchQuery={setSearchQuery}/>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex flex-col w-full overflow-y-auto h-full min-h-0 flex-grow">
|
||||
<div className="flex flex-col w-full overflow-y-auto overflow-x-hidden h-full min-h-0 flex-grow">
|
||||
{
|
||||
isLoadingBooks ? (
|
||||
<>
|
||||
@@ -511,36 +523,55 @@ export default function BookList() {
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="flex items-start justify-center w-full px-4 overflow-x-auto pb-4">
|
||||
{items.map((item, idx) => {
|
||||
if (item.type === 'book' && item.book) {
|
||||
return (
|
||||
<div key={item.book.bookId}
|
||||
{...(idx === 0 && {'data-guide': 'book-card'})}
|
||||
className={`flex-shrink-0 w-64 sm:w-52 md:w-48 lg:w-56 xl:w-64 p-2 box-border ${User.guideTourDone(session.user?.guideTour || [], 'new-first-book') && 'mb-[200px]'}`}>
|
||||
<BookCard
|
||||
book={item.book}
|
||||
syncStatus={detectBookSyncStatus(item.book.bookId)}
|
||||
onClickCallback={handleBookClick}
|
||||
index={idx}
|
||||
<div className="group relative w-full">
|
||||
<button
|
||||
onClick={() => scrollCarousel(category, 'left')}
|
||||
className="absolute left-3 top-1/2 -translate-y-1/2 z-10 bg-primary/80 backdrop-blur-sm hover:bg-primary text-white rounded-2xl w-12 h-12 flex items-center justify-center shadow-xl border border-primary-light/30 transition-all duration-200 opacity-0 group-hover:opacity-100 hover:scale-110"
|
||||
>
|
||||
<FontAwesomeIcon icon={faChevronLeft} className="w-5 h-5"/>
|
||||
</button>
|
||||
|
||||
<div
|
||||
ref={(el: HTMLDivElement | null) => { carouselRefs.current[category] = el; }}
|
||||
className="flex items-start w-full overflow-hidden px-4 gap-2 scroll-smooth"
|
||||
>
|
||||
{items.map((item, idx) => {
|
||||
if (item.type === 'book' && item.book) {
|
||||
return (
|
||||
<div key={item.book.bookId}
|
||||
{...(idx === 0 && {'data-guide': 'book-card'})}
|
||||
className={`flex-shrink-0 w-64 sm:w-52 md:w-48 lg:w-56 xl:w-64 p-2 box-border ${User.guideTourDone(session.user?.guideTour || [], 'new-first-book') && 'mb-[200px]'}`}>
|
||||
<BookCard
|
||||
book={item.book}
|
||||
syncStatus={detectBookSyncStatus(item.book.bookId)}
|
||||
onClickCallback={handleBookClick}
|
||||
index={idx}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
if (item.type === 'series' && item.series) {
|
||||
return (
|
||||
<SeriesCard
|
||||
key={item.series.id}
|
||||
series={item.series}
|
||||
onBookClick={handleBookClick}
|
||||
onSettingsClick={handleSeriesSettingsClick}
|
||||
getSyncStatus={detectBookSyncStatus}
|
||||
seriesSyncStatus={detectSeriesSyncStatus(item.series.id)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
if (item.type === 'series' && item.series) {
|
||||
return (
|
||||
<SeriesCard
|
||||
key={item.series.id}
|
||||
series={item.series}
|
||||
onBookClick={handleBookClick}
|
||||
onSettingsClick={handleSeriesSettingsClick}
|
||||
getSyncStatus={detectBookSyncStatus}
|
||||
seriesSyncStatus={detectSeriesSyncStatus(item.series.id)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
})}
|
||||
);
|
||||
}
|
||||
return null;
|
||||
})}
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={() => scrollCarousel(category, 'right')}
|
||||
className="absolute right-3 top-1/2 -translate-y-1/2 z-10 bg-primary/80 backdrop-blur-sm hover:bg-primary text-white rounded-2xl w-12 h-12 flex items-center justify-center shadow-xl border border-primary-light/30 transition-all duration-200 opacity-0 group-hover:opacity-100 hover:scale-110"
|
||||
>
|
||||
<FontAwesomeIcon icon={faChevronRight} className="w-5 h-5"/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
||||
Reference in New Issue
Block a user