'use client'; import { useState, useEffect, type ChangeEvent } from 'react'; import { useConfig, ViewType } from '@/contexts/ConfigContext'; import { ReaderSidebarShell } from '@/components/reader/ReaderSidebarShell'; import { SEGMENT_PRELOAD_DEPTH_MIN, SEGMENT_PRELOAD_DEPTH_MAX, SEGMENT_PRELOAD_SENTENCE_LOOKAHEAD_MIN, SEGMENT_PRELOAD_SENTENCE_LOOKAHEAD_MAX, TTS_SEGMENT_MAX_BLOCK_LENGTH_MIN, TTS_SEGMENT_MAX_BLOCK_LENGTH_MAX, TTS_SEGMENT_MAX_BLOCK_LENGTH_STEP, clampSegmentPreloadDepth, clampSegmentPreloadSentenceLookahead, clampTtsSegmentMaxBlockLength, } from '@/types/config'; import { useFeatureFlag } from '@/contexts/RuntimeConfigContext'; const viewTypeTextMapping = [ { id: 'single', name: 'Single Page' }, { id: 'dual', name: 'Two Pages' }, { id: 'scroll', name: 'Continuous Scroll' }, ]; const rangeInputClassName = 'w-full bg-offbase rounded-lg appearance-none cursor-pointer accent-accent [&::-webkit-slider-runnable-track]:bg-offbase [&::-webkit-slider-runnable-track]:rounded-lg [&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:h-4 [&::-webkit-slider-thumb]:w-4 [&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:bg-accent [&::-moz-range-track]:bg-offbase [&::-moz-range-track]:rounded-lg [&::-moz-range-thumb]:appearance-none [&::-moz-range-thumb]:h-4 [&::-moz-range-thumb]:w-4 [&::-moz-range-thumb]:rounded-full [&::-moz-range-thumb]:bg-accent'; type MarginKey = 'header' | 'footer' | 'left' | 'right'; type RangeSettingProps = { label: string; value: number; min: number; max: number; step: number; description: string; valueWidth?: string; formatter?: (value: number) => string; onChange: (value: number) => void; }; function RangeSetting({ label, value, min, max, step, description, valueWidth = 'w-10', formatter = (next) => String(next), onChange, }: RangeSettingProps) { return (
onChange(Number(event.target.value))} className={`flex-1 ${rangeInputClassName}`} /> {formatter(value)}

{description}

); } type ToggleRowProps = { label: string; description: string; checked: boolean; onChange: (checked: boolean) => void; disabled?: boolean; }; function ToggleRow({ label, description, checked, onChange, disabled = false }: ToggleRowProps) { return (
); } export function DocumentSettings({ isOpen, setIsOpen, epub, html }: { isOpen: boolean, setIsOpen: (isOpen: boolean) => void, epub?: boolean, html?: boolean }) { const canWordHighlight = useFeatureFlag('enableWordHighlight'); const { viewType, skipBlank, epubTheme, smartSentenceSplitting, segmentPreloadDepthPages, segmentPreloadSentenceLookahead, ttsSegmentMaxBlockLength, headerMargin, footerMargin, leftMargin, rightMargin, updateConfigKey, pdfHighlightEnabled, epubHighlightEnabled, pdfWordHighlightEnabled, epubWordHighlightEnabled, } = useConfig(); const [localMargins, setLocalMargins] = useState({ header: headerMargin, footer: footerMargin, left: leftMargin, right: rightMargin }); const selectedView = viewTypeTextMapping.find(v => v.id === viewType) || viewTypeTextMapping[0]; const [localPreloadDepth, setLocalPreloadDepth] = useState(segmentPreloadDepthPages); const [localSentenceLookahead, setLocalSentenceLookahead] = useState(segmentPreloadSentenceLookahead); const [localMaxBlockLength, setLocalMaxBlockLength] = useState(ttsSegmentMaxBlockLength); const marginValues: Record = { header: headerMargin, footer: footerMargin, left: leftMargin, right: rightMargin, }; useEffect(() => { setLocalMargins({ header: headerMargin, footer: footerMargin, left: leftMargin, right: rightMargin }); }, [headerMargin, footerMargin, leftMargin, rightMargin]); useEffect(() => { setLocalPreloadDepth(segmentPreloadDepthPages); }, [segmentPreloadDepthPages]); useEffect(() => { setLocalSentenceLookahead(segmentPreloadSentenceLookahead); }, [segmentPreloadSentenceLookahead]); useEffect(() => { setLocalMaxBlockLength(ttsSegmentMaxBlockLength); }, [ttsSegmentMaxBlockLength]); // Handler for slider release const handleMarginChangeComplete = (margin: MarginKey) => () => { const value = localMargins[margin]; const configKey = `${margin}Margin`; if (value !== marginValues[margin]) { updateConfigKey(configKey as 'headerMargin' | 'footerMargin' | 'leftMargin' | 'rightMargin', value); } }; const handleMarginSliderChange = (margin: MarginKey) => (event: ChangeEvent) => { setLocalMargins((previous) => ({ ...previous, [margin]: Number(event.target.value), })); }; return ( setIsOpen(false)} ariaLabel="Document settings" title="Reader Settings" subtitle="Configure layout, preloading, and playback behavior for this document." bodyClassName="flex-1 overflow-y-auto px-4 py-4 bg-[radial-gradient(circle_at_top_right,rgba(239,68,68,0.08),transparent_35%)]" panelClassName="w-full sm:w-[30rem]" >
{!html && (

Playback Flow

Control segment generation and lookahead while audio is active.

updateConfigKey('skipBlank', checked)} /> updateConfigKey('smartSentenceSplitting', checked)} />
String(value)} onChange={(value) => { const next = clampSegmentPreloadDepth(value); setLocalPreloadDepth(next); void updateConfigKey('segmentPreloadDepthPages', next); }} /> String(value)} onChange={(value) => { const next = clampSegmentPreloadSentenceLookahead(value); setLocalSentenceLookahead(next); void updateConfigKey('segmentPreloadSentenceLookahead', next); }} /> String(value)} onChange={(value) => { const next = clampTtsSegmentMaxBlockLength(value); setLocalMaxBlockLength(next); void updateConfigKey('ttsSegmentMaxBlockLength', next); }} />
)} {!epub && !html && (

PDF Layout & Extraction

Set viewer mode and trim page edges before extraction.

{viewTypeTextMapping.map((view) => { const active = selectedView.id === view.id; return ( ); })}
{selectedView.id === 'scroll' ? (

Continuous scroll may perform poorly for very large PDFs.

) : null}

Text extraction margins

Exclude content near edges before sentence extraction.

{(['header', 'footer', 'left', 'right'] as MarginKey[]).map((margin) => (
{margin} {Math.round(localMargins[margin] * 100)}%
))}
)} {!epub && !html && (

PDF Highlighting

Control playback highlighting behavior in PDF mode.

updateConfigKey('pdfHighlightEnabled', checked)} /> updateConfigKey('pdfWordHighlightEnabled', checked)} />
)} {epub && (

EPUB Appearance

Apply app styling and playback highlighting in EPUB mode.

updateConfigKey('epubTheme', checked)} /> updateConfigKey('epubHighlightEnabled', checked)} /> updateConfigKey('epubWordHighlightEnabled', checked)} />
)}
); }