import React, { useEffect, useState } from 'react'; import { MdOutlineAutoMode } from 'react-icons/md'; import { MdOutlineTextRotationNone, MdTextRotateVertical } from 'react-icons/md'; import { TbTextDirectionRtl } from 'react-icons/tb'; import { useEnv } from '@/context/EnvContext'; import { useReaderStore } from '@/store/readerStore'; import { useBookDataStore } from '@/store/bookDataStore'; import { useTranslation } from '@/hooks/useTranslation'; import { isCJKEnv } from '@/utils/misc'; import { getStyles } from '@/utils/style'; import { getMaxInlineSize } from '@/utils/config'; import { getBookDirFromWritingMode, getBookLangCode } from '@/utils/book'; import { MIGHT_BE_RTL_LANGS } from '@/services/constants'; import { saveViewSettings } from '../../utils/viewSettingsHelper'; import NumberInput from './NumberInput'; const LayoutPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => { const _ = useTranslation(); const { envConfig } = useEnv(); const { getView, getViewSettings, setViewSettings } = useReaderStore(); const { getBookData } = useBookDataStore(); const view = getView(bookKey); const bookData = getBookData(bookKey)!; const viewSettings = getViewSettings(bookKey)!; const [paragraphMargin, setParagraphMargin] = useState(viewSettings.paragraphMargin!); const [lineHeight, setLineHeight] = useState(viewSettings.lineHeight!); const [wordSpacing, setWordSpacing] = useState(viewSettings.wordSpacing!); const [letterSpacing, setLetterSpacing] = useState(viewSettings.letterSpacing!); const [textIndent, setTextIndent] = useState(viewSettings.textIndent!); const [fullJustification, setFullJustification] = useState(viewSettings.fullJustification!); const [hyphenation, setHyphenation] = useState(viewSettings.hyphenation!); const [marginPx, setMarginPx] = useState(viewSettings.marginPx!); const [gapPercent, setGapPercent] = useState(viewSettings.gapPercent!); const [maxColumnCount, setMaxColumnCount] = useState(viewSettings.maxColumnCount!); const [maxInlineSize, setMaxInlineSize] = useState(viewSettings.maxInlineSize!); const [maxBlockSize, setMaxBlockSize] = useState(viewSettings.maxBlockSize!); const [writingMode, setWritingMode] = useState(viewSettings.writingMode!); const [overrideLayout, setOverrideLayout] = useState(viewSettings.overrideLayout!); const [isScrolledMode, setScrolledMode] = useState(viewSettings.scrolled!); const [doubleBorder, setDoubleBorder] = useState(viewSettings.doubleBorder!); const [borderColor, setBorderColor] = useState(viewSettings.borderColor!); const [showHeader, setShowHeader] = useState(viewSettings.showHeader!); const [showFooter, setShowFooter] = useState(viewSettings.showFooter!); const [showBarsOnScroll, setShowBarsOnScroll] = useState(viewSettings.showBarsOnScroll!); useEffect(() => { saveViewSettings(envConfig, bookKey, 'paragraphMargin', paragraphMargin); // eslint-disable-next-line react-hooks/exhaustive-deps }, [paragraphMargin]); useEffect(() => { saveViewSettings(envConfig, bookKey, 'lineHeight', lineHeight); // eslint-disable-next-line react-hooks/exhaustive-deps }, [lineHeight]); useEffect(() => { saveViewSettings(envConfig, bookKey, 'wordSpacing', wordSpacing); // eslint-disable-next-line react-hooks/exhaustive-deps }, [wordSpacing]); useEffect(() => { saveViewSettings(envConfig, bookKey, 'letterSpacing', letterSpacing); // eslint-disable-next-line react-hooks/exhaustive-deps }, [letterSpacing]); useEffect(() => { saveViewSettings(envConfig, bookKey, 'textIndent', textIndent); // eslint-disable-next-line react-hooks/exhaustive-deps }, [textIndent]); useEffect(() => { saveViewSettings(envConfig, bookKey, 'fullJustification', fullJustification); // eslint-disable-next-line react-hooks/exhaustive-deps }, [fullJustification]); useEffect(() => { saveViewSettings(envConfig, bookKey, 'hyphenation', hyphenation); // eslint-disable-next-line react-hooks/exhaustive-deps }, [hyphenation]); useEffect(() => { if (marginPx === viewSettings.marginPx) return; saveViewSettings(envConfig, bookKey, 'marginPx', marginPx, false, false); view?.renderer.setAttribute('margin', `${marginPx}px`); // eslint-disable-next-line react-hooks/exhaustive-deps }, [marginPx]); useEffect(() => { if (gapPercent === viewSettings.gapPercent) return; saveViewSettings(envConfig, bookKey, 'gapPercent', gapPercent, false, false); view?.renderer.setAttribute('gap', `${gapPercent}%`); if (viewSettings.scrolled) { view?.renderer.setAttribute('flow', 'scrolled'); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [gapPercent]); useEffect(() => { if (maxColumnCount === viewSettings.maxColumnCount) return; saveViewSettings(envConfig, bookKey, 'maxColumnCount', maxColumnCount, false, false); view?.renderer.setAttribute('max-column-count', maxColumnCount); view?.renderer.setAttribute('max-inline-size', `${getMaxInlineSize(viewSettings)}px`); // eslint-disable-next-line react-hooks/exhaustive-deps }, [maxColumnCount]); useEffect(() => { if (maxInlineSize === viewSettings.maxInlineSize) return; saveViewSettings(envConfig, bookKey, 'maxInlineSize', maxInlineSize, false, false); view?.renderer.setAttribute('max-inline-size', `${getMaxInlineSize(viewSettings)}px`); // eslint-disable-next-line react-hooks/exhaustive-deps }, [maxInlineSize]); useEffect(() => { if (maxBlockSize === viewSettings.maxBlockSize) return; saveViewSettings(envConfig, bookKey, 'maxBlockSize', maxBlockSize, false, false); view?.renderer.setAttribute('max-block-size', `${maxBlockSize}px`); // eslint-disable-next-line react-hooks/exhaustive-deps }, [maxBlockSize]); useEffect(() => { if (writingMode === viewSettings.writingMode) return; // global settings are not supported for writing mode const prevWritingMode = viewSettings.writingMode; if (writingMode.includes('vertical')) { viewSettings.vertical = true; } else { viewSettings.vertical = false; } saveViewSettings(envConfig, bookKey, 'writingMode', writingMode, true); if (view) { view.renderer.setStyles?.(getStyles(viewSettings)); view.book.dir = getBookDirFromWritingMode(writingMode); } if ( prevWritingMode !== writingMode && (['horizontal-rl', 'vertical-rl'].includes(writingMode) || ['horizontal-rl', 'vertical-rl'].includes(prevWritingMode)) ) { setTimeout(() => window.location.reload(), 100); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [writingMode]); useEffect(() => { saveViewSettings(envConfig, bookKey, 'overrideLayout', overrideLayout); // eslint-disable-next-line react-hooks/exhaustive-deps }, [overrideLayout]); useEffect(() => { if (isScrolledMode === viewSettings.scrolled) return; saveViewSettings(envConfig, bookKey, 'scrolled', isScrolledMode); getView(bookKey)?.renderer.setAttribute('flow', isScrolledMode ? 'scrolled' : 'paginated'); getView(bookKey)?.renderer.setAttribute( 'max-inline-size', `${getMaxInlineSize(viewSettings)}px`, ); getView(bookKey)?.renderer.setStyles?.(getStyles(viewSettings!)); // eslint-disable-next-line react-hooks/exhaustive-deps }, [isScrolledMode]); useEffect(() => { if (doubleBorder === viewSettings.doubleBorder) return; if (doubleBorder && viewSettings.vertical) { viewSettings.gapPercent = Math.max( viewSettings.gapPercent, Math.ceil(4800 / window.innerWidth), ); setGapPercent(viewSettings.gapPercent); setViewSettings(bookKey, viewSettings); } saveViewSettings(envConfig, bookKey, 'doubleBorder', doubleBorder, false, false); // eslint-disable-next-line react-hooks/exhaustive-deps }, [doubleBorder]); useEffect(() => { saveViewSettings(envConfig, bookKey, 'borderColor', borderColor, false, false); // eslint-disable-next-line react-hooks/exhaustive-deps }, [borderColor]); useEffect(() => { if (showHeader === viewSettings.showHeader) return; if (showHeader && !viewSettings.vertical) { viewSettings.marginPx = Math.max(viewSettings.marginPx, 44); setMarginPx(viewSettings.marginPx); setViewSettings(bookKey, viewSettings); } else if (showHeader && viewSettings.vertical) { viewSettings.gapPercent = Math.max( viewSettings.gapPercent, Math.ceil(4800 / window.innerWidth), ); setGapPercent(viewSettings.gapPercent); setViewSettings(bookKey, viewSettings); } saveViewSettings(envConfig, bookKey, 'showHeader', showHeader, false, false); // eslint-disable-next-line react-hooks/exhaustive-deps }, [showHeader]); useEffect(() => { saveViewSettings(envConfig, bookKey, 'showBarsOnScroll', showBarsOnScroll, false, false); // eslint-disable-next-line react-hooks/exhaustive-deps }, [showBarsOnScroll]); useEffect(() => { if (showFooter === viewSettings.showFooter) return; if (showFooter && !viewSettings.vertical) { viewSettings.marginPx = Math.max(viewSettings.marginPx, 44); setMarginPx(viewSettings.marginPx); setViewSettings(bookKey, viewSettings); } else if (showFooter && viewSettings.vertical) { viewSettings.gapPercent = Math.max( viewSettings.gapPercent, Math.ceil(4800 / window.innerWidth), ); setGapPercent(viewSettings.gapPercent); setViewSettings(bookKey, viewSettings); } saveViewSettings(envConfig, bookKey, 'showFooter', showFooter, false, false); // eslint-disable-next-line react-hooks/exhaustive-deps }, [showFooter]); const langCode = getBookLangCode(bookData.bookDoc?.metadata?.language); const mightBeRTLBook = MIGHT_BE_RTL_LANGS.includes(langCode) || isCJKEnv(); return (

{_('Scrolled Mode')}

setScrolledMode(!isScrolledMode)} />
{mightBeRTLBook && (

{_('Writing Mode')}

)} {viewSettings.vertical && (

{_('Border Frame')}

{_('Double Border')} setDoubleBorder(!doubleBorder)} />
{_('Border Color')}
)}

{_('Paragraph')}

{langCode !== 'zh' && ( )}
{_('Full Justification')} setFullJustification(!fullJustification)} />
{_('Hyphenation')} setHyphenation(!hyphenation)} />
{_('Override Book Layout')} setOverrideLayout(!overrideLayout)} />

{_('Page')}

{_('Header & Footer')}

{_('Show Header')} setShowHeader(!showHeader)} />
{_('Show Footer')} setShowFooter(!showFooter)} />
{_('Apply also in Scrolled Mode')} setShowBarsOnScroll(!showBarsOnScroll)} />
); }; export default LayoutPanel;