'use client'; import { Fragment, useState, useCallback, useEffect } from 'react'; import { Dialog, DialogPanel, Transition, TransitionChild, Listbox, ListboxButton, ListboxOptions, ListboxOption, Button } from '@headlessui/react'; import { useConfig, ViewType } from '@/contexts/ConfigContext'; import { ChevronUpDownIcon, CheckIcon } from '@/components/icons/Icons'; import { useEPUB } from '@/contexts/EPUBContext'; import { usePDF } from '@/contexts/PDFContext'; import { AudiobookExportModal } from '@/components/AudiobookExportModal'; import { useParams } from 'next/navigation'; import type { TTSAudiobookChapter } from '@/types/tts'; import type { AudiobookGenerationSettings } from '@/types/client'; const isDev = process.env.NEXT_PUBLIC_NODE_ENV !== 'production' || process.env.NODE_ENV == null; const canExportAudiobook = process.env.NEXT_PUBLIC_ENABLE_AUDIOBOOK_EXPORT !== 'false'; const canWordHighlight = isDev || process.env.NEXT_PUBLIC_ENABLE_WORD_HIGHLIGHT === 'true'; const viewTypeTextMapping = [ { id: 'single', name: 'Single Page' }, { id: 'dual', name: 'Two Pages' }, { id: 'scroll', name: 'Continuous Scroll' }, ]; export function DocumentSettings({ isOpen, setIsOpen, epub, html }: { isOpen: boolean, setIsOpen: (isOpen: boolean) => void, epub?: boolean, html?: boolean }) { const { viewType, skipBlank, epubTheme, smartSentenceSplitting, headerMargin, footerMargin, leftMargin, rightMargin, updateConfigKey, pdfHighlightEnabled, epubHighlightEnabled, pdfWordHighlightEnabled, epubWordHighlightEnabled, } = useConfig(); const { createFullAudioBook: createEPUBAudioBook, regenerateChapter: regenerateEPUBChapter } = useEPUB(); const { createFullAudioBook: createPDFAudioBook, regenerateChapter: regeneratePDFChapter } = usePDF(); const { id } = useParams(); const [localMargins, setLocalMargins] = useState({ header: headerMargin, footer: footerMargin, left: leftMargin, right: rightMargin }); const [isAudiobookModalOpen, setIsAudiobookModalOpen] = useState(false); const selectedView = viewTypeTextMapping.find(v => v.id === viewType) || viewTypeTextMapping[0]; // Sync local margins with global state useEffect(() => { setLocalMargins({ header: headerMargin, footer: footerMargin, left: leftMargin, right: rightMargin }); }, [headerMargin, footerMargin, leftMargin, rightMargin]); // Handler for slider change (updates local state only) const handleMarginChange = (margin: keyof typeof localMargins) => (event: React.ChangeEvent) => { setLocalMargins(prev => ({ ...prev, [margin]: Number(event.target.value) })); }; // Handler for slider release const handleMarginChangeComplete = (margin: keyof typeof localMargins) => () => { const value = localMargins[margin]; const configKey = `${margin}Margin`; if (value !== (useConfig)[configKey as keyof typeof useConfig]) { updateConfigKey(configKey as 'headerMargin' | 'footerMargin' | 'leftMargin' | 'rightMargin', value); } }; const handleGenerateAudiobook = useCallback(async ( onProgress: (progress: number) => void, signal: AbortSignal, onChapterComplete: (chapter: TTSAudiobookChapter) => void, settings: AudiobookGenerationSettings ) => { if (epub) { return createEPUBAudioBook(onProgress, signal, onChapterComplete, id as string, settings.format, settings); } else { return createPDFAudioBook(onProgress, signal, onChapterComplete, id as string, settings.format, settings); } }, [epub, createEPUBAudioBook, createPDFAudioBook, id]); const handleRegenerateChapter = useCallback(async ( chapterIndex: number, bookId: string, settings: AudiobookGenerationSettings, signal: AbortSignal ) => { if (epub) { return regenerateEPUBChapter(chapterIndex, bookId, settings.format, signal, settings); } else { return regeneratePDFChapter(chapterIndex, bookId, settings.format, signal, settings); } }, [epub, regenerateEPUBChapter, regeneratePDFChapter]); return ( <> setIsOpen(false)}>
{!html &&
}
{!epub && !html &&
{/* Header Margin */}
Header {Math.round(localMargins.header * 100)}%
{/* Footer Margin */}
Footer {Math.round(localMargins.footer * 100)}%
{/* Left Margin */}
Left {Math.round(localMargins.left * 100)}%
{/* Right Margin */}
Right {Math.round(localMargins.right * 100)}%

Adjust margins to exclude content from edges of the page during text extraction (experimental)

updateConfigKey('viewType', newView.id as ViewType)} >
{selectedView.name} {viewTypeTextMapping.map((view) => ( `relative cursor-pointer select-none py-1.5 pl-10 pr-4 ${active ? 'bg-offbase text-accent' : 'text-foreground' }` } value={view} > {({ selected }) => ( <> {view.name} {selected ? ( ) : null} )} ))} {selectedView.id === 'scroll' && (

Note: Continuous scroll may perform poorly for larger documents.

)}
} {!html &&

Automatically skip pages with no text content

} {!html && (

Merge sentences across page or section breaks

)} {!epub && !html && (

Visual text playback highlighting in the PDF viewer

Highlight individual words using audio timestamps generated by whisper.cpp {!canWordHighlight && '(disabled by configuration)'}

)} {epub && (

Visual text playback highlighting in the EPUB viewer

Highlight individual words using audio timestamps generated by whisper.cpp {!canWordHighlight && '(disabled by configuration)'}

)} {epub && (

Apply the current app theme to the EPUB viewer background and text colors

)}
); }