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!); useEffect(() => { saveViewSettings(envConfig, bookKey, 'paragraphMargin', paragraphMargin); view?.renderer.setStyles?.(getStyles(viewSettings)); // eslint-disable-next-line react-hooks/exhaustive-deps }, [paragraphMargin]); useEffect(() => { saveViewSettings(envConfig, bookKey, 'lineHeight', lineHeight); view?.renderer.setStyles?.(getStyles(viewSettings)); // eslint-disable-next-line react-hooks/exhaustive-deps }, [lineHeight]); useEffect(() => { saveViewSettings(envConfig, bookKey, 'wordSpacing', wordSpacing); view?.renderer.setStyles?.(getStyles(viewSettings)); // eslint-disable-next-line react-hooks/exhaustive-deps }, [wordSpacing]); useEffect(() => { saveViewSettings(envConfig, bookKey, 'letterSpacing', letterSpacing); view?.renderer.setStyles?.(getStyles(viewSettings)); // eslint-disable-next-line react-hooks/exhaustive-deps }, [letterSpacing]); useEffect(() => { saveViewSettings(envConfig, bookKey, 'textIndent', textIndent); view?.renderer.setStyles?.(getStyles(viewSettings)); // eslint-disable-next-line react-hooks/exhaustive-deps }, [textIndent]); useEffect(() => { saveViewSettings(envConfig, bookKey, 'fullJustification', fullJustification); view?.renderer.setStyles?.(getStyles(viewSettings)); // eslint-disable-next-line react-hooks/exhaustive-deps }, [fullJustification]); useEffect(() => { saveViewSettings(envConfig, bookKey, 'hyphenation', hyphenation); view?.renderer.setStyles?.(getStyles(viewSettings)); // eslint-disable-next-line react-hooks/exhaustive-deps }, [hyphenation]); useEffect(() => { saveViewSettings(envConfig, bookKey, 'marginPx', marginPx); view?.renderer.setAttribute('margin', `${marginPx}px`); // eslint-disable-next-line react-hooks/exhaustive-deps }, [marginPx]); useEffect(() => { saveViewSettings(envConfig, bookKey, 'gapPercent', gapPercent); view?.renderer.setAttribute('gap', `${gapPercent}%`); if (viewSettings.scrolled) { view?.renderer.setAttribute('flow', 'scrolled'); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [gapPercent]); useEffect(() => { saveViewSettings(envConfig, bookKey, 'maxColumnCount', maxColumnCount); 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(() => { saveViewSettings(envConfig, bookKey, 'maxInlineSize', maxInlineSize); view?.renderer.setAttribute('max-inline-size', `${getMaxInlineSize(viewSettings)}px`); // eslint-disable-next-line react-hooks/exhaustive-deps }, [maxInlineSize]); useEffect(() => { saveViewSettings(envConfig, bookKey, 'maxBlockSize', maxBlockSize); view?.renderer.setAttribute('max-block-size', `${maxBlockSize}px`); // eslint-disable-next-line react-hooks/exhaustive-deps }, [maxBlockSize]); useEffect(() => { // global settings are not supported for writing mode const prevWritingMode = viewSettings.writingMode; if (!writingMode.includes('vertical')) { 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); view?.renderer.setStyles?.(getStyles(viewSettings)); // eslint-disable-next-line react-hooks/exhaustive-deps }, [overrideLayout]); useEffect(() => { 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.vertical) { viewSettings.gapPercent = Math.max( viewSettings.gapPercent, Math.ceil(4800 / window.innerWidth), ); setGapPercent(viewSettings.gapPercent); setViewSettings(bookKey, viewSettings); } saveViewSettings(envConfig, bookKey, 'doubleBorder', doubleBorder); // eslint-disable-next-line react-hooks/exhaustive-deps }, [doubleBorder]); useEffect(() => { saveViewSettings(envConfig, bookKey, 'borderColor', borderColor); // eslint-disable-next-line react-hooks/exhaustive-deps }, [borderColor]); useEffect(() => { 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); // eslint-disable-next-line react-hooks/exhaustive-deps }, [showHeader]); useEffect(() => { 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); // 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 (