'use client';
import { useState, useEffect } 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 { IconButton, RangeInput, Section, ToggleRow, CheckItem, SegmentedControl } from '@/components/ui';
import { RefreshIcon, SparkleIcon } from '@/components/icons/Icons';
import type { ParsedPdfBlockKind, PdfParseStatus } from '@/types/parsed-pdf';
import { isForceReparseDisabled } from '@/lib/client/pdf/force-reparse';
const PDF_SKIP_KIND_OPTIONS: Array<{ kind: ParsedPdfBlockKind; label: string }> = [
{ kind: 'header', label: 'Header' },
{ kind: 'footer', label: 'Footer' },
{ kind: 'footnote', label: 'Footnote' },
{ kind: 'vision_footnote', label: 'Vision footnote' },
{ kind: 'figure_title', label: 'Figure title' },
{ kind: 'doc_title', label: 'Document title' },
{ kind: 'paragraph_title', label: 'Paragraph title' },
{ kind: 'abstract', label: 'Abstract' },
{ kind: 'algorithm', label: 'Algorithm' },
{ kind: 'aside_text', label: 'Aside text' },
{ kind: 'content', label: 'Content' },
{ kind: 'reference', label: 'Reference' },
{ kind: 'reference_content', label: 'Reference content' },
{ kind: 'text', label: 'Text' },
{ kind: 'number', label: 'Number' },
{ kind: 'formula', label: 'Formula' },
{ kind: 'formula_number', label: 'Formula number' },
{ kind: 'table', label: 'Table' },
{ kind: 'chart', label: 'Chart' },
{ kind: 'image', label: 'Image' },
{ kind: 'seal', label: 'Seal' },
];
const viewTypeTextMapping = [
{ id: 'single', name: 'Single Page' },
{ id: 'dual', name: 'Two Pages' },
{ id: 'scroll', name: 'Continuous Scroll' },
];
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"
/>
{formatter(value)}
{description}
);
}
export function DocumentSettings({ isOpen, setIsOpen, epub, html, pdf }: {
isOpen: boolean,
setIsOpen: (isOpen: boolean) => void,
epub?: boolean,
html?: boolean,
pdf?: {
parseStatus: PdfParseStatus | null;
parsedOverlayEnabled: boolean;
skipBlockKinds: ParsedPdfBlockKind[];
onToggleOverlay: (enabled: boolean) => void;
onToggleSkipKind: (kind: ParsedPdfBlockKind, enabled: boolean) => void;
onForceReparse: () => void;
}
}) {
const canWordHighlight = true;
const {
viewType,
skipBlank,
epubTheme,
segmentPreloadDepthPages,
segmentPreloadSentenceLookahead,
ttsSegmentMaxBlockLength,
updateConfigKey,
pdfHighlightEnabled,
epubHighlightEnabled,
pdfWordHighlightEnabled,
epubWordHighlightEnabled,
htmlHighlightEnabled,
htmlWordHighlightEnabled,
} = useConfig();
const selectedView = viewTypeTextMapping.find(v => v.id === viewType) || viewTypeTextMapping[0];
const isPdfMode = !epub && !html && !!pdf;
const [localPreloadDepth, setLocalPreloadDepth] = useState(segmentPreloadDepthPages);
const [localSentenceLookahead, setLocalSentenceLookahead] = useState(segmentPreloadSentenceLookahead);
const [localMaxBlockLength, setLocalMaxBlockLength] = useState(ttsSegmentMaxBlockLength);
useEffect(() => {
setLocalPreloadDepth(segmentPreloadDepthPages);
}, [segmentPreloadDepthPages]);
useEffect(() => {
setLocalSentenceLookahead(segmentPreloadSentenceLookahead);
}, [segmentPreloadSentenceLookahead]);
useEffect(() => {
setLocalMaxBlockLength(ttsSegmentMaxBlockLength);
}, [ttsSegmentMaxBlockLength]);
return (
setIsOpen(false)}
ariaLabel="Document settings"
title="Reader Settings"
subtitle="Tune layout, preloading, and playback."
bodyClassName="flex-1 overflow-y-auto px-4 py-4 bg-[radial-gradient(circle_at_top_right,color-mix(in_srgb,var(--accent),transparent_92%),transparent_35%)]"
panelClassName="w-full sm:w-[30rem]"
>
{isPdfMode && pdf && (
({ value: view.id as ViewType, label: view.name }))}
onChange={(nextViewType) => updateConfigKey('viewType', nextViewType)}
ariaLabel="Page mode"
className="grid-cols-3"
/>
{selectedView.id === 'scroll' ? (
Scroll mode may be slower on large PDFs.
) : null}
updateConfigKey('pdfHighlightEnabled', checked)}
variant="flat"
/>
updateConfigKey('pdfWordHighlightEnabled', checked)}
variant="flat"
/>
)}
{isPdfMode && pdf && (
PP-DocLayout-V3
{pdf.parseStatus ?? 'pending'}
}
>
Skip Block Kinds While Reading Aloud
{PDF_SKIP_KIND_OPTIONS.map((option) => (
pdf.onToggleSkipKind(option.kind, enabled)}
/>
))}
)}
{!html && (
updateConfigKey('skipBlank', checked)}
variant="flat"
/>
)}
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 && (
updateConfigKey('epubTheme', checked)}
variant="flat"
/>
updateConfigKey('epubHighlightEnabled', checked)}
variant="flat"
/>
updateConfigKey('epubWordHighlightEnabled', checked)}
variant="flat"
/>
)}
{html && (
updateConfigKey('htmlHighlightEnabled', checked)}
variant="flat"
/>
updateConfigKey('htmlWordHighlightEnabled', checked)}
variant="flat"
/>
)}
);
}