'use client' import {fetch} from "@tauri-apps/plugin-http"; import React, {useCallback, useContext, useEffect, useMemo, useState} from 'react'; import {Check} from 'lucide-react'; import Button from '@/components/ui/Button'; import PulseLoader from '@/components/ui/PulseLoader'; import {useTranslations} from '@/lib/i18n'; import {BookContext, BookContextProps} from '@/context/BookContext'; import {SessionContext, SessionContextProps} from '@/context/SessionContext'; import {AlertContext, AlertContextProps} from '@/context/AlertContext'; import {configs, isDesktop} from '@/lib/configs'; import {apiGet} from '@/lib/api/client'; import * as tauri from '@/lib/tauri'; import OfflineContext, {OfflineContextType} from '@/context/OfflineContext'; import {LangContext, LangContextProps} from '@/context/LangContext'; import {ChapterExportInfo, ChapterExportSelection, ExportFormat,} from '@/lib/types/chapter'; import {chapterVersions} from '@/lib/constants/chapter'; import {SelectBoxProps} from '@/components/form/SelectBox'; const formats: ExportFormat[] = ['epub', 'pdf', 'docx']; const formatExtensions: Record = { epub: '.epub', pdf: '.pdf', docx: '.docx', }; export default function ExportSetting(): React.JSX.Element { const t = useTranslations(); const {book}: BookContextProps = useContext(BookContext); const {session}: SessionContextProps = useContext(SessionContext); const {successMessage, errorMessage}: AlertContextProps = useContext(AlertContext); const {lang}: LangContextProps = useContext(LangContext); const {isCurrentlyOffline}: OfflineContextType = useContext(OfflineContext); const [selectedFormat, setSelectedFormat] = useState('epub'); const [chaptersExportInfo, setChaptersExportInfo] = useState([]); const [chapterSelections, setChapterSelections] = useState([]); const [isLoadingChapters, setIsLoadingChapters] = useState(true); const [isExporting, setIsExporting] = useState(false); const selectedCount: number = useMemo( () => chapterSelections.filter((selection: ChapterExportSelection): boolean => selection.selected).length, [chapterSelections], ); const allSelected: boolean = useMemo( () => chapterSelections.length > 0 && chapterSelections.every((selection: ChapterExportSelection): boolean => selection.selected), [chapterSelections], ); const canExport: boolean = useMemo( () => !isExporting && selectedCount > 0, [isExporting, selectedCount], ); const fetchChaptersExportInfo = useCallback(async (): Promise => { if (!book?.bookId) { setIsLoadingChapters(false); return; } setIsLoadingChapters(true); try { let data: ChapterExportInfo[]; if (isDesktop && (isCurrentlyOffline() || book?.localBook)) { data = await tauri.getBookExportInfo(book.bookId) as ChapterExportInfo[]; } else { data = await apiGet( 'book/chapters/export-info', session.accessToken, lang, {id: book.bookId} ); } setChaptersExportInfo(data); setChapterSelections( data.map((chapter: ChapterExportInfo): ChapterExportSelection => ({ chapterId: chapter.chapterId, version: chapter.availableVersions.length > 0 ? chapter.availableVersions[chapter.availableVersions.length - 1] : 1, selected: true, })), ); } catch { errorMessage(t('exportOption.serverError')); } finally { setIsLoadingChapters(false); } }, [book?.bookId, session.accessToken, lang, errorMessage, t]); useEffect((): void => { fetchChaptersExportInfo(); }, [fetchChaptersExportInfo]); function toggleChapterSelection(chapterId: string): void { setChapterSelections((previous: ChapterExportSelection[]): ChapterExportSelection[] => previous.map((selection: ChapterExportSelection): ChapterExportSelection => selection.chapterId === chapterId ? {...selection, selected: !selection.selected} : selection, ), ); } function toggleAllChapters(): void { const newSelected: boolean = !allSelected; setChapterSelections((previous: ChapterExportSelection[]): ChapterExportSelection[] => previous.map((selection: ChapterExportSelection): ChapterExportSelection => ({ ...selection, selected: newSelected, })), ); } function setChapterVersion(chapterId: string, version: number): void { setChapterSelections((previous: ChapterExportSelection[]): ChapterExportSelection[] => previous.map((selection: ChapterExportSelection): ChapterExportSelection => selection.chapterId === chapterId ? {...selection, version} : selection, ), ); } function getVersionLabel(version: number): string { const match: SelectBoxProps | undefined = chapterVersions.find( (chapterVersion: SelectBoxProps): boolean => chapterVersion.value === String(version), ); return match ? t(match.label) : `V${version}`; } async function handleExport(): Promise { if (!book?.bookId) { errorMessage(t('exportOption.noBookSelected')); return; } if (selectedCount === 0) { errorMessage(t('exportOption.noChaptersSelected')); return; } setIsExporting(true); const selectedChapters: { chapterId: string; version: number }[] = chapterSelections .filter((selection: ChapterExportSelection): boolean => selection.selected) .map((selection: ChapterExportSelection): { chapterId: string; version: number } => ({ chapterId: selection.chapterId, version: selection.version, })); try { const response: Response = await fetch(`${configs.apiUrl}book/export`, { method: 'POST', headers: { Authorization: `Bearer ${session.accessToken}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ bookId: book.bookId, format: selectedFormat, chapters: selectedChapters, }), }); if (!response.ok) { errorMessage(t('exportOption.downloadError')); return; } const blob: Blob = await response.blob(); const bookName: string = book.subTitle ? `${book.title} - ${book.subTitle}` : book.title; const fileName: string = `${bookName}${formatExtensions[selectedFormat]}`; const virtualUrl: string = window.URL.createObjectURL(blob); const downloadLink: HTMLAnchorElement = document.createElement('a'); downloadLink.href = virtualUrl; downloadLink.download = fileName; document.body.appendChild(downloadLink); downloadLink.click(); downloadLink.remove(); window.URL.revokeObjectURL(virtualUrl); successMessage(t('exportOption.downloadSuccess', {format: selectedFormat.toUpperCase()})); } catch { errorMessage(t('exportOption.unknownError')); } finally { setIsExporting(false); } } return (

{t('exportOption.formatLabel')}

{formats.map((format: ExportFormat) => ( ))}

{t('exportOption.chapters')}

{isLoadingChapters ? ( ) : chaptersExportInfo.length === 0 ? (

{t('exportOption.noChaptersAvailable')}

) : ( <>
{chaptersExportInfo.map((chapter: ChapterExportInfo, index: number) => { const selection: ChapterExportSelection = chapterSelections[index]; return (
{selection.selected && chapter.availableVersions.length > 1 && (
{chapter.availableVersions.map((version: number) => ( ))}
)}
); })}
)}
); }