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:
natreex
2026-03-05 16:31:56 -05:00
parent 94cac463fb
commit ceaecb19fc
16 changed files with 780 additions and 245 deletions

View File

@@ -1,11 +1,9 @@
// Removed Next.js Link import for Electron
import {BookProps} from "@/lib/models/Book";
import DeleteBook from "@/components/book/settings/DeleteBook";
import ExportBook from "@/components/ExportBook";
import {useTranslations} from "next-intl";
import SyncBook from "@/components/SyncBook";
import {SyncType} from "@/context/BooksSyncContext";
import {useEffect} from "react";
interface BookCardProps {
book: BookProps;
@@ -68,7 +66,6 @@ export default function BookCard({book, onClickCallback, index, syncStatus}: Boo
<div className="flex justify-between items-center pt-3 border-t border-secondary/30">
<SyncBook status={syncStatus} bookId={book.bookId}/>
<div className="flex items-center gap-1" {...index === 0 && {'data-guide': 'bottom-book-card'}}>
<ExportBook bookTitle={book.title} bookId={book.bookId}/>
<DeleteBook bookId={book.bookId}/>
</div>
</div>

View File

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

View File

@@ -33,6 +33,9 @@ const CharacterSettings = lazy(function () {
const SpellSettings = lazy(function () {
return import('./spells/settings/SpellSettings');
});
const ExportSetting = lazy(function () {
return import('./ExportSetting');
});
function LoadingSpinner(): React.JSX.Element {
return (
@@ -51,7 +54,7 @@ interface SettingRef {
}
// Settings qui gèrent leur propre save (pas de bouton save parent)
const selfManagedSettings: string[] = ['characters', 'spells', 'world', 'worlds', 'locations'];
const selfManagedSettings: string[] = ['characters', 'spells', 'world', 'worlds', 'locations', 'export'];
export default function BookSettingOption({setting}: BookSettingOptionProps): React.JSX.Element {
const t = useTranslations();
@@ -77,6 +80,8 @@ export default function BookSettingOption({setting}: BookSettingOptionProps): Re
return t("bookSettingOption.characters");
case 'spells':
return t("bookSettingOption.spells");
case 'export':
return t("bookSettingOption.export");
default:
return "";
}
@@ -119,7 +124,10 @@ export default function BookSettingOption({setting}: BookSettingOptionProps): Re
{setting === 'spells' && (
<SpellSettings entityType="book" showToggle={true}/>
)}
{!['basic-information', 'guide-line', 'story', 'world', 'worlds', 'locations', 'characters', 'spells', 'quillsense'].includes(setting) && (
{setting === 'export' && (
<ExportSetting/>
)}
{!['basic-information', 'guide-line', 'story', 'world', 'worlds', 'locations', 'characters', 'spells', 'quillsense', 'export'].includes(setting) && (
<div className="text-text-secondary py-4 text-center">
{t("bookSettingOption.notAvailable")}
</div>

View File

@@ -3,6 +3,7 @@
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
import {
faBook,
faDownload,
faGlobe,
faHatWizard,
faListAlt,
@@ -74,6 +75,11 @@ export default function BookSettingSidebar(
name: 'bookSetting.quillsense',
icon: faWandMagicSparkles
},
{
id: 'export',
name: 'bookSetting.export',
icon: faDownload
},
// {
// id: 'objects',
// name: t('bookSetting.objects'),