'use client'; import { Fragment, useState, useRef, 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'; const isDev = process.env.NEXT_PUBLIC_NODE_ENV !== 'production' || process.env.NODE_ENV == null; interface DocViewSettingsProps { isOpen: boolean; setIsOpen: (isOpen: boolean) => void; epub?: boolean; } const viewTypes = [ { id: 'single', name: 'Single Page' }, { id: 'dual', name: 'Two Pages' }, { id: 'scroll', name: 'Continuous Scroll' }, ]; export function DocumentSettings({ isOpen, setIsOpen, epub }: DocViewSettingsProps) { const { viewType, skipBlank, epubTheme, headerMargin, footerMargin, leftMargin, rightMargin, updateConfigKey } = useConfig(); const { createFullAudioBook } = useEPUB(); const [progress, setProgress] = useState(0); const [isGenerating, setIsGenerating] = useState(false); const [localMargins, setLocalMargins] = useState({ header: headerMargin, footer: footerMargin, left: leftMargin, right: rightMargin }); const abortControllerRef = useRef(null); const selectedView = viewTypes.find(v => v.id === viewType) || viewTypes[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 handleStartGeneration = async () => { setIsGenerating(true); setProgress(0); abortControllerRef.current = new AbortController(); try { const audioBuffer = await createFullAudioBook( (progress) => setProgress(progress), abortControllerRef.current.signal ); // Create and trigger download const blob = new Blob([audioBuffer], { type: 'audio/mp3' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'audiobook.mp3'; document.body.appendChild(a); a.click(); // Clean up setTimeout(() => { document.body.removeChild(a); URL.revokeObjectURL(url); }, 100); } catch (error) { console.error('Error generating audiobook:', error); } finally { setIsGenerating(false); setProgress(0); abortControllerRef.current = null; } }; const handleCancel = () => { if (abortControllerRef.current) { abortControllerRef.current.abort(); } }; return ( setIsOpen(false)}>
{isDev &&
{!isGenerating ? ( ) : (
{Math.round(progress)}% complete
)}
} {!epub &&
{/* 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

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

Note: Continuous scroll may perform poorly for larger documents.

)}
}

Automatically skip pages with no text content

{epub && (

Apply the current app theme to the EPUB viewer

)}
); }