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 { useSettingsStore } from '@/store/settingsStore'; 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 NumberInput from './NumberInput'; const LayoutPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => { const _ = useTranslation(); const { settings, isFontLayoutSettingsGlobal, setSettings } = useSettingsStore(); 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(() => { viewSettings.paragraphMargin = paragraphMargin; setViewSettings(bookKey, viewSettings); if (isFontLayoutSettingsGlobal) { settings.globalViewSettings.paragraphMargin = paragraphMargin; setSettings(settings); } view?.renderer.setStyles?.(getStyles(viewSettings)); // eslint-disable-next-line react-hooks/exhaustive-deps }, [paragraphMargin]); useEffect(() => { viewSettings.lineHeight = lineHeight; setViewSettings(bookKey, viewSettings); if (isFontLayoutSettingsGlobal) { settings.globalViewSettings.lineHeight = lineHeight; setSettings(settings); } view?.renderer.setStyles?.(getStyles(viewSettings)); // eslint-disable-next-line react-hooks/exhaustive-deps }, [lineHeight]); useEffect(() => { viewSettings.wordSpacing = wordSpacing; setViewSettings(bookKey, viewSettings); if (isFontLayoutSettingsGlobal) { settings.globalViewSettings.wordSpacing = wordSpacing; setSettings(settings); } view?.renderer.setStyles?.(getStyles(viewSettings)); // eslint-disable-next-line react-hooks/exhaustive-deps }, [wordSpacing]); useEffect(() => { viewSettings.letterSpacing = letterSpacing; setViewSettings(bookKey, viewSettings); if (isFontLayoutSettingsGlobal) { settings.globalViewSettings.letterSpacing = letterSpacing; setSettings(settings); } view?.renderer.setStyles?.(getStyles(viewSettings)); // eslint-disable-next-line react-hooks/exhaustive-deps }, [letterSpacing]); useEffect(() => { viewSettings.textIndent = textIndent; setViewSettings(bookKey, viewSettings); if (isFontLayoutSettingsGlobal) { settings.globalViewSettings.textIndent = textIndent; setSettings(settings); } view?.renderer.setStyles?.(getStyles(viewSettings)); // eslint-disable-next-line react-hooks/exhaustive-deps }, [textIndent]); useEffect(() => { viewSettings.fullJustification = fullJustification; setViewSettings(bookKey, viewSettings); if (isFontLayoutSettingsGlobal) { settings.globalViewSettings.fullJustification = fullJustification; setSettings(settings); } view?.renderer.setStyles?.(getStyles(viewSettings)); // eslint-disable-next-line react-hooks/exhaustive-deps }, [fullJustification]); useEffect(() => { viewSettings.hyphenation = hyphenation; setViewSettings(bookKey, viewSettings); if (isFontLayoutSettingsGlobal) { settings.globalViewSettings.hyphenation = hyphenation; setSettings(settings); } view?.renderer.setStyles?.(getStyles(viewSettings)); // eslint-disable-next-line react-hooks/exhaustive-deps }, [hyphenation]); useEffect(() => { viewSettings.marginPx = marginPx; setViewSettings(bookKey, viewSettings); if (isFontLayoutSettingsGlobal) { settings.globalViewSettings.marginPx = marginPx; setSettings(settings); } view?.renderer.setAttribute('margin', `${marginPx}px`); // eslint-disable-next-line react-hooks/exhaustive-deps }, [marginPx]); useEffect(() => { viewSettings.gapPercent = gapPercent; setViewSettings(bookKey, viewSettings); if (isFontLayoutSettingsGlobal) { settings.globalViewSettings.gapPercent = gapPercent; setSettings(settings); } view?.renderer.setAttribute('gap', `${gapPercent}%`); if (viewSettings.scrolled) { view?.renderer.setAttribute('flow', 'scrolled'); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [gapPercent]); useEffect(() => { viewSettings.maxColumnCount = maxColumnCount; setViewSettings(bookKey, viewSettings); if (isFontLayoutSettingsGlobal) { settings.globalViewSettings.maxColumnCount = maxColumnCount; setSettings(settings); } 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(() => { viewSettings.maxInlineSize = maxInlineSize; setViewSettings(bookKey, viewSettings); if (isFontLayoutSettingsGlobal) { settings.globalViewSettings.maxInlineSize = maxInlineSize; setSettings(settings); } view?.renderer.setAttribute('max-inline-size', `${getMaxInlineSize(viewSettings)}px`); // eslint-disable-next-line react-hooks/exhaustive-deps }, [maxInlineSize]); useEffect(() => { viewSettings.maxBlockSize = maxBlockSize; setViewSettings(bookKey, viewSettings); if (isFontLayoutSettingsGlobal) { settings.globalViewSettings.maxBlockSize = maxBlockSize; setSettings(settings); } 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; viewSettings.writingMode = writingMode; if (writingMode.includes('vertical')) { viewSettings.vertical = true; } setViewSettings(bookKey, viewSettings); 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(() => { viewSettings.overrideLayout = overrideLayout; setViewSettings(bookKey, viewSettings); if (isFontLayoutSettingsGlobal) { settings.globalViewSettings.overrideLayout = overrideLayout; setSettings(settings); } view?.renderer.setStyles?.(getStyles(viewSettings)); // eslint-disable-next-line react-hooks/exhaustive-deps }, [overrideLayout]); useEffect(() => { viewSettings!.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!)); setViewSettings(bookKey, viewSettings!); if (isFontLayoutSettingsGlobal) { settings.globalViewSettings.scrolled = isScrolledMode; setSettings(settings); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [isScrolledMode]); useEffect(() => { viewSettings.doubleBorder = doubleBorder; setViewSettings(bookKey, viewSettings); if (isFontLayoutSettingsGlobal) { settings.globalViewSettings.doubleBorder = doubleBorder; setSettings(settings); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [doubleBorder]); useEffect(() => { viewSettings.borderColor = borderColor; setViewSettings(bookKey, viewSettings); if (isFontLayoutSettingsGlobal) { settings.globalViewSettings.borderColor = borderColor; setSettings(settings); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [borderColor]); useEffect(() => { viewSettings.showHeader = showHeader; setViewSettings(bookKey, viewSettings); if (isFontLayoutSettingsGlobal) { settings.globalViewSettings.showHeader = showHeader; setSettings(settings); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [showHeader]); useEffect(() => { viewSettings.showFooter = showFooter; setViewSettings(bookKey, viewSettings); if (isFontLayoutSettingsGlobal) { settings.globalViewSettings.showFooter = showFooter; setSettings(settings); } // 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 (