forked from akai/readest
This commit is contained in:
@@ -381,5 +381,7 @@
|
||||
"Enter CSS for reader interface styling...": "أدخل CSS لتنسيق واجهة القارئ...",
|
||||
"Crop": "قص",
|
||||
"Book Covers": "أغلفة الكتب",
|
||||
"Fit": "ملاءمة"
|
||||
"Fit": "ملاءمة",
|
||||
"Reset {{settings}}": "إعادة تعيين {{settings}}",
|
||||
"Reset Settings": "إعادة تعيين الإعدادات"
|
||||
}
|
||||
|
||||
@@ -369,5 +369,7 @@
|
||||
"Enter CSS for reader interface styling...": "CSS für die Leseroberfläche eingeben...",
|
||||
"Crop": "Zuschneiden",
|
||||
"Book Covers": "Buchcover",
|
||||
"Fit": "Anpassen"
|
||||
"Fit": "Anpassen",
|
||||
"Reset {{settings}}": "{{settings}} zurücksetzen",
|
||||
"Reset Settings": "Einstellungen zurücksetzen"
|
||||
}
|
||||
|
||||
@@ -369,5 +369,7 @@
|
||||
"Enter CSS for reader interface styling...": "Εισαγάγετε CSS για στυλ διεπαφής αναγνώστη...",
|
||||
"Crop": "Περικοπή",
|
||||
"Book Covers": "Εξώφυλλα βιβλίων",
|
||||
"Fit": "Προσαρμογή"
|
||||
"Fit": "Προσαρμογή",
|
||||
"Reset {{settings}}": "Επαναφορά {{settings}}",
|
||||
"Reset Settings": "Επαναφορά ρυθμίσεων"
|
||||
}
|
||||
|
||||
@@ -372,5 +372,7 @@
|
||||
"Enter CSS for reader interface styling...": "Introduce CSS para la interfaz de lectura...",
|
||||
"Crop": "Recortar",
|
||||
"Book Covers": "Portadas de libros",
|
||||
"Fit": "Encajar"
|
||||
"Fit": "Encajar",
|
||||
"Reset {{settings}}": "Restablecer {{settings}}",
|
||||
"Reset Settings": "Restablecer configuración"
|
||||
}
|
||||
|
||||
@@ -372,5 +372,7 @@
|
||||
"Enter CSS for reader interface styling...": "Saisissez le CSS pour l’interface de lecture...",
|
||||
"Crop": "Rogner",
|
||||
"Book Covers": "Couvertures de livres",
|
||||
"Fit": "Adapter"
|
||||
"Fit": "Adapter",
|
||||
"Reset {{settings}}": "Réinitialiser {{settings}}",
|
||||
"Reset Settings": "Réinitialiser les paramètres"
|
||||
}
|
||||
|
||||
@@ -369,5 +369,7 @@
|
||||
"Enter CSS for reader interface styling...": "रीडर इंटरफ़ेस के लिए CSS दर्ज करें...",
|
||||
"Crop": "काटें",
|
||||
"Book Covers": "पुस्तक कवर",
|
||||
"Fit": "फिट"
|
||||
"Fit": "फिट",
|
||||
"Reset {{settings}}": "रीसेट {{settings}}",
|
||||
"Reset Settings": "सेटिंग्स रीसेट करें"
|
||||
}
|
||||
|
||||
@@ -366,5 +366,7 @@
|
||||
"Enter CSS for reader interface styling...": "Masukkan CSS untuk gaya antarmuka pembaca...",
|
||||
"Crop": "Potong",
|
||||
"Book Covers": "Sampul Buku",
|
||||
"Fit": "Pas"
|
||||
"Fit": "Pas",
|
||||
"Reset {{settings}}": "Atur Ulang {{settings}}",
|
||||
"Reset Settings": "Atur Ulang Pengaturan"
|
||||
}
|
||||
|
||||
@@ -372,5 +372,7 @@
|
||||
"Enter CSS for reader interface styling...": "Inserisci il CSS per l'interfaccia di lettura...",
|
||||
"Crop": "Ritaglia",
|
||||
"Book Covers": "Copertine libri",
|
||||
"Fit": "Adatta"
|
||||
"Fit": "Adatta",
|
||||
"Reset {{settings}}": "Reimposta {{settings}}",
|
||||
"Reset Settings": "Reimposta impostazioni"
|
||||
}
|
||||
|
||||
@@ -366,5 +366,7 @@
|
||||
"Enter CSS for reader interface styling...": "リーダーインターフェース用CSSを入力...",
|
||||
"Crop": "トリミング",
|
||||
"Book Covers": "書籍の表紙",
|
||||
"Fit": "フィット"
|
||||
"Fit": "フィット",
|
||||
"Reset {{settings}}": "{{settings}}をリセット",
|
||||
"Reset Settings": "設定をリセット"
|
||||
}
|
||||
|
||||
@@ -366,5 +366,7 @@
|
||||
"Enter CSS for reader interface styling...": "리더 인터페이스 스타일용 CSS 입력...",
|
||||
"Crop": "자르기",
|
||||
"Book Covers": "책 표지",
|
||||
"Fit": "맞춤"
|
||||
"Fit": "맞춤",
|
||||
"Reset {{settings}}": "{{settings}} 재설정",
|
||||
"Reset Settings": "설정 재설정"
|
||||
}
|
||||
|
||||
@@ -369,5 +369,7 @@
|
||||
"Enter CSS for reader interface styling...": "Voer CSS in voor de opmaak van de leesinterface...",
|
||||
"Crop": "Bijsnijden",
|
||||
"Book Covers": "Boekomslagen",
|
||||
"Fit": "Passend maken"
|
||||
"Fit": "Passend maken",
|
||||
"Reset {{settings}}": "Reset {{settings}}",
|
||||
"Reset Settings": "Instellingen resetten"
|
||||
}
|
||||
|
||||
@@ -375,5 +375,7 @@
|
||||
"Enter CSS for reader interface styling...": "Wprowadź CSS dla stylu interfejsu czytnika...",
|
||||
"Crop": "Przytnij",
|
||||
"Book Covers": "Okładki książek",
|
||||
"Fit": "Pasuje"
|
||||
"Fit": "Pasuje",
|
||||
"Reset {{settings}}": "Zresetuj {{settings}}",
|
||||
"Reset Settings": "Zresetuj ustawienia"
|
||||
}
|
||||
|
||||
@@ -372,5 +372,7 @@
|
||||
"Enter CSS for reader interface styling...": "Insira o CSS para o estilo da interface de leitura...",
|
||||
"Crop": "Cortar",
|
||||
"Book Covers": "Capas de Livros",
|
||||
"Fit": "Encaixar"
|
||||
"Fit": "Encaixar",
|
||||
"Reset {{settings}}": "Redefinir {{settings}}",
|
||||
"Reset Settings": "Redefinir Configurações"
|
||||
}
|
||||
|
||||
@@ -375,5 +375,7 @@
|
||||
"Enter CSS for reader interface styling...": "Введите CSS для оформления интерфейса ридера...",
|
||||
"Crop": "Обрезать",
|
||||
"Book Covers": "Обложки книг",
|
||||
"Fit": "Подогнать"
|
||||
"Fit": "Подогнать",
|
||||
"Reset {{settings}}": "Сбросить {{settings}}",
|
||||
"Reset Settings": "Сбросить настройки"
|
||||
}
|
||||
|
||||
@@ -369,5 +369,7 @@
|
||||
"Enter CSS for reader interface styling...": "Okuyucu arayüzü stili için CSS girin...",
|
||||
"Crop": "Kes",
|
||||
"Book Covers": "Kitap Kapağı",
|
||||
"Fit": "Uygun"
|
||||
"Fit": "Uygun",
|
||||
"Reset {{settings}}": "{{settings}}'i Sıfırla",
|
||||
"Reset Settings": "Ayarları Sıfırla"
|
||||
}
|
||||
|
||||
@@ -375,5 +375,7 @@
|
||||
"Enter CSS for reader interface styling...": "Введіть CSS для стилізації інтерфейсу рідера...",
|
||||
"Crop": "Обрізати",
|
||||
"Book Covers": "Обкладинки книг",
|
||||
"Fit": "Вписати"
|
||||
"Fit": "Вписати",
|
||||
"Reset {{settings}}": "Скинути {{settings}}",
|
||||
"Reset Settings": "Скинути налаштування"
|
||||
}
|
||||
|
||||
@@ -366,5 +366,7 @@
|
||||
"Enter CSS for reader interface styling...": "Nhập CSS cho giao diện trình đọc...",
|
||||
"Crop": "Cắt",
|
||||
"Book Covers": "Bìa sách",
|
||||
"Fit": "Vừa vặn"
|
||||
"Fit": "Vừa vặn",
|
||||
"Reset {{settings}}": "Đặt lại {{settings}}",
|
||||
"Reset Settings": "Đặt lại cài đặt"
|
||||
}
|
||||
|
||||
@@ -366,5 +366,7 @@
|
||||
"Enter CSS for reader interface styling...": "输入阅读器界面样式的CSS…",
|
||||
"Crop": "裁剪",
|
||||
"Book Covers": "书籍封面",
|
||||
"Fit": "适应"
|
||||
"Fit": "适应",
|
||||
"Reset {{settings}}": "重置{{settings}}",
|
||||
"Reset Settings": "重置设置"
|
||||
}
|
||||
|
||||
@@ -366,5 +366,7 @@
|
||||
"Enter CSS for reader interface styling...": "輸入閱讀器介面樣式的CSS…",
|
||||
"Crop": "裁剪",
|
||||
"Book Covers": "書籍封面",
|
||||
"Fit": "適應"
|
||||
"Fit": "適應",
|
||||
"Reset {{settings}}": "重置{{settings}}",
|
||||
"Reset Settings": "重置設置"
|
||||
}
|
||||
|
||||
@@ -18,12 +18,14 @@ import { useReaderStore } from '@/store/readerStore';
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
import { useSettingsStore } from '@/store/settingsStore';
|
||||
import { useResponsiveSize } from '@/hooks/useResponsiveSize';
|
||||
import { useResetViewSettings } from '../../hooks/useResetSettings';
|
||||
import { saveViewSettings } from '../../utils/viewSettingsHelper';
|
||||
import { CODE_LANGUAGES, CodeLanguage, manageSyntaxHighlighting } from '@/utils/highlightjs';
|
||||
import { SettingsPanelPanelProp } from './SettingsDialog';
|
||||
import Select from '@/components/Select';
|
||||
import ThemeEditor from './ThemeEditor';
|
||||
|
||||
const ColorPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
|
||||
const ColorPanel: React.FC<SettingsPanelPanelProp> = ({ bookKey, onRegisterReset }) => {
|
||||
const _ = useTranslation();
|
||||
const { themeMode, themeColor, isDarkMode, setThemeMode, setThemeColor, saveCustomTheme } =
|
||||
useThemeStore();
|
||||
@@ -44,6 +46,24 @@ const ColorPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
|
||||
const [codeHighlighting, setcodeHighlighting] = useState(viewSettings.codeHighlighting!);
|
||||
const [codeLanguage, setCodeLanguage] = useState(viewSettings.codeLanguage!);
|
||||
|
||||
const resetToDefaults = useResetViewSettings();
|
||||
|
||||
const handleReset = () => {
|
||||
resetToDefaults({
|
||||
overrideColor: setOverrideColor,
|
||||
invertImgColorInDark: setInvertImgColorInDark,
|
||||
codeHighlighting: setcodeHighlighting,
|
||||
codeLanguage: setCodeLanguage,
|
||||
});
|
||||
setThemeColor('default');
|
||||
setThemeMode('auto');
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
onRegisterReset(handleReset);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (invertImgColorInDark === viewSettings.invertImgColorInDark) return;
|
||||
saveViewSettings(envConfig, bookKey, 'invertImgColorInDark', invertImgColorInDark);
|
||||
|
||||
@@ -8,9 +8,11 @@ import { getStyles } from '@/utils/style';
|
||||
import { getMaxInlineSize } from '@/utils/config';
|
||||
import { saveViewSettings } from '../../utils/viewSettingsHelper';
|
||||
import { RELOAD_BEFREE_SAVED_TIMEOUT_MS } from '@/services/constants';
|
||||
import { SettingsPanelPanelProp } from './SettingsDialog';
|
||||
import { useResetViewSettings } from '../../hooks/useResetSettings';
|
||||
import NumberInput from './NumberInput';
|
||||
|
||||
const ControlPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
|
||||
const ControlPanel: React.FC<SettingsPanelPanelProp> = ({ bookKey, onRegisterReset }) => {
|
||||
const _ = useTranslation();
|
||||
const { envConfig, appService } = useEnv();
|
||||
const { getView, getViewSettings } = useReaderStore();
|
||||
@@ -28,6 +30,26 @@ const ControlPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
|
||||
const [animated, setAnimated] = useState(viewSettings.animated!);
|
||||
const [allowScript, setAllowScript] = useState(viewSettings.allowScript!);
|
||||
|
||||
const resetToDefaults = useResetViewSettings();
|
||||
|
||||
const handleReset = () => {
|
||||
resetToDefaults({
|
||||
scrolled: setScrolledMode,
|
||||
continuousScroll: setIsContinuousScroll,
|
||||
scrollingOverlap: setScrollingOverlap,
|
||||
volumeKeysToFlip: setVolumeKeysToFlip,
|
||||
disableClick: setIsDisableClick,
|
||||
swapClickArea: setSwapClickArea,
|
||||
animated: setAnimated,
|
||||
allowScript: setAllowScript,
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
onRegisterReset(handleReset);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (isScrolledMode === viewSettings.scrolled) return;
|
||||
saveViewSettings(envConfig, bookKey, 'scrolled', isScrolledMode);
|
||||
|
||||
@@ -3,20 +3,30 @@ import React from 'react';
|
||||
import { MdCheck } from 'react-icons/md';
|
||||
import { useSettingsStore } from '@/store/settingsStore';
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
import { useDefaultIconSize } from '@/hooks/useResponsiveSize';
|
||||
import { useResponsiveSize } from '@/hooks/useResponsiveSize';
|
||||
import { SettingsPanelType } from './SettingsDialog';
|
||||
import MenuItem from '@/components/MenuItem';
|
||||
|
||||
interface DialogMenuProps {
|
||||
toggleDropdown?: () => void;
|
||||
activePanel: SettingsPanelType;
|
||||
setIsDropdownOpen?: (open: boolean) => void;
|
||||
onReset: () => void;
|
||||
resetLabel?: string;
|
||||
}
|
||||
|
||||
const DialogMenu: React.FC<DialogMenuProps> = ({ toggleDropdown }) => {
|
||||
const DialogMenu: React.FC<DialogMenuProps> = ({ setIsDropdownOpen, onReset, resetLabel }) => {
|
||||
const _ = useTranslation();
|
||||
const iconSize = useDefaultIconSize();
|
||||
const iconSize = useResponsiveSize(16);
|
||||
const { isFontLayoutSettingsGlobal, setFontLayoutSettingsGlobal } = useSettingsStore();
|
||||
|
||||
const handleToggleGlobal = () => {
|
||||
setFontLayoutSettingsGlobal(!isFontLayoutSettingsGlobal);
|
||||
toggleDropdown?.();
|
||||
setIsDropdownOpen?.(false);
|
||||
};
|
||||
|
||||
const handleResetToDefaults = () => {
|
||||
onReset();
|
||||
setIsDropdownOpen?.(false);
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -27,24 +37,18 @@ const DialogMenu: React.FC<DialogMenuProps> = ({ toggleDropdown }) => {
|
||||
'text-base sm:text-sm',
|
||||
)}
|
||||
>
|
||||
<button
|
||||
className='hover:bg-base-200 text-base-content flex w-full items-center justify-between rounded-md p-1'
|
||||
<MenuItem
|
||||
label={_('Global Settings')}
|
||||
tooltip={isFontLayoutSettingsGlobal ? _('Apply to All Books') : _('Apply to This Book')}
|
||||
buttonClass='lg:tooltip'
|
||||
Icon={
|
||||
isFontLayoutSettingsGlobal ? (
|
||||
<MdCheck size={iconSize} className='text-base-content' />
|
||||
) : null
|
||||
}
|
||||
onClick={handleToggleGlobal}
|
||||
>
|
||||
<div className='flex items-center'>
|
||||
<span style={{ minWidth: `${iconSize}px` }}>
|
||||
{isFontLayoutSettingsGlobal && <MdCheck className='text-base-content' />}
|
||||
</span>
|
||||
<div
|
||||
className='lg:tooltip'
|
||||
data-tip={
|
||||
isFontLayoutSettingsGlobal ? _('Apply to All Books') : _('Apply to This Book')
|
||||
}
|
||||
>
|
||||
<span className='ml-2 whitespace-nowrap'>{_('Global Settings')}</span>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
/>
|
||||
<MenuItem label={resetLabel || _('Reset Settings')} onClick={handleResetToDefaults} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -24,6 +24,8 @@ import { getOSPlatform, isCJKEnv } from '@/utils/misc';
|
||||
import { getSysFontsList } from '@/utils/bridge';
|
||||
import { isTauriAppPlatform } from '@/services/environment';
|
||||
import { saveViewSettings } from '../../utils/viewSettingsHelper';
|
||||
import { useResetViewSettings } from '../../hooks/useResetSettings';
|
||||
import { SettingsPanelPanelProp } from './SettingsDialog';
|
||||
import NumberInput from './NumberInput';
|
||||
import FontDropdown from './FontDropDown';
|
||||
|
||||
@@ -79,7 +81,7 @@ const FontFace = ({
|
||||
);
|
||||
};
|
||||
|
||||
const FontPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
|
||||
const FontPanel: React.FC<SettingsPanelPanelProp> = ({ bookKey, onRegisterReset }) => {
|
||||
const _ = useTranslation();
|
||||
const { envConfig } = useEnv();
|
||||
const { getView, getViewSettings } = useReaderStore();
|
||||
@@ -119,10 +121,10 @@ const FontPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
|
||||
break;
|
||||
}
|
||||
const [sysFonts, setSysFonts] = useState<string[]>(defaultSysFonts);
|
||||
const [defaultFont, setDefaultFont] = useState(viewSettings.defaultFont!);
|
||||
const [defaultFontSize, setDefaultFontSize] = useState(viewSettings.defaultFontSize!);
|
||||
const [minFontSize, setMinFontSize] = useState(viewSettings.minimumFontSize!);
|
||||
const [overrideFont, setOverrideFont] = useState(viewSettings.overrideFont!);
|
||||
const [defaultFont, setDefaultFont] = useState(viewSettings.defaultFont!);
|
||||
const [defaultCJKFont, setDefaultCJKFont] = useState(viewSettings.defaultCJKFont!);
|
||||
const [serifFont, setSerifFont] = useState(viewSettings.serifFont!);
|
||||
const [sansSerifFont, setSansSerifFont] = useState(viewSettings.sansSerifFont!);
|
||||
@@ -132,6 +134,27 @@ const FontPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
|
||||
return genCJKFontsList(sysFonts);
|
||||
});
|
||||
|
||||
const resetToDefaults = useResetViewSettings();
|
||||
|
||||
const handleReset = () => {
|
||||
resetToDefaults({
|
||||
defaultFont: setDefaultFont,
|
||||
defaultFontSize: setDefaultFontSize,
|
||||
minimumFontSize: setMinFontSize,
|
||||
overrideFont: setOverrideFont,
|
||||
defaultCJKFont: setDefaultCJKFont,
|
||||
serifFont: setSerifFont,
|
||||
sansSerifFont: setSansSerifFont,
|
||||
monospaceFont: setMonospaceFont,
|
||||
fontWeight: setFontWeight,
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
onRegisterReset(handleReset);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
setCJKFonts((prev) => {
|
||||
const newFonts = genCJKFontsList(sysFonts);
|
||||
|
||||
@@ -8,20 +8,39 @@ import { useTranslation } from '@/hooks/useTranslation';
|
||||
import { saveViewSettings } from '../../utils/viewSettingsHelper';
|
||||
import { getTranslators } from '@/services/translators';
|
||||
import { TRANSLATED_LANGS } from '@/services/constants';
|
||||
import { SettingsPanelPanelProp } from './SettingsDialog';
|
||||
import { useResetViewSettings } from '../../hooks/useResetSettings';
|
||||
import { initDayjs } from '@/utils/time';
|
||||
import Select from '@/components/Select';
|
||||
|
||||
const LangPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
|
||||
const LangPanel: React.FC<SettingsPanelPanelProp> = ({ bookKey, onRegisterReset }) => {
|
||||
const _ = useTranslation();
|
||||
const { token } = useAuth();
|
||||
const { envConfig } = useEnv();
|
||||
const { getViewSettings, setViewSettings } = useReaderStore();
|
||||
const viewSettings = getViewSettings(bookKey)!;
|
||||
|
||||
const [uiLanguage, setUILanguage] = useState(viewSettings.uiLanguage!);
|
||||
const [translationEnabled, setTranslationEnabled] = useState(viewSettings.translationEnabled!);
|
||||
const [translationProvider, setTranslationProvider] = useState(viewSettings.translationProvider!);
|
||||
const [translateTargetLang, setTranslateTargetLang] = useState(viewSettings.translateTargetLang!);
|
||||
|
||||
const resetToDefaults = useResetViewSettings();
|
||||
|
||||
const handleReset = () => {
|
||||
resetToDefaults({
|
||||
uiLanguage: setUILanguage,
|
||||
translationEnabled: setTranslationEnabled,
|
||||
translationProvider: setTranslationProvider,
|
||||
translateTargetLang: setTranslateTargetLang,
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
onRegisterReset(handleReset);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
const getCurrentUILangOption = () => {
|
||||
const uiLanguage = viewSettings.uiLanguage;
|
||||
return {
|
||||
@@ -43,10 +62,7 @@ const LangPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
|
||||
|
||||
const handleSelectUILang = (event: React.ChangeEvent<HTMLSelectElement>) => {
|
||||
const option = event.target.value;
|
||||
saveViewSettings(envConfig, bookKey, 'uiLanguage', option, false, false);
|
||||
const locale = option ? option : navigator.language;
|
||||
i18n.changeLanguage(locale);
|
||||
initDayjs(locale);
|
||||
setUILanguage(option);
|
||||
};
|
||||
|
||||
const getTranslationProviderOptions = () => {
|
||||
@@ -97,6 +113,15 @@ const LangPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
|
||||
setViewSettings(bookKey, { ...viewSettings });
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (uiLanguage === viewSettings.uiLanguage) return;
|
||||
saveViewSettings(envConfig, bookKey, 'uiLanguage', uiLanguage, false, false);
|
||||
const locale = uiLanguage ? uiLanguage : navigator.language;
|
||||
i18n.changeLanguage(locale);
|
||||
initDayjs(locale);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [uiLanguage]);
|
||||
|
||||
useEffect(() => {
|
||||
if (translationEnabled === viewSettings.translationEnabled) return;
|
||||
saveViewSettings(envConfig, bookKey, 'translationEnabled', translationEnabled, true, false);
|
||||
|
||||
@@ -8,6 +8,7 @@ import { useEnv } from '@/context/EnvContext';
|
||||
import { useReaderStore } from '@/store/readerStore';
|
||||
import { useBookDataStore } from '@/store/bookDataStore';
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
import { useResetViewSettings } from '../../hooks/useResetSettings';
|
||||
import { isCJKEnv } from '@/utils/misc';
|
||||
import { getStyles } from '@/utils/style';
|
||||
import { getMaxInlineSize } from '@/utils/config';
|
||||
@@ -15,9 +16,10 @@ import { lockScreenOrientation } from '@/utils/bridge';
|
||||
import { getBookDirFromWritingMode, getBookLangCode } from '@/utils/book';
|
||||
import { MIGHT_BE_RTL_LANGS, RELOAD_BEFREE_SAVED_TIMEOUT_MS } from '@/services/constants';
|
||||
import { saveViewSettings } from '../../utils/viewSettingsHelper';
|
||||
import { SettingsPanelPanelProp } from './SettingsDialog';
|
||||
import NumberInput from './NumberInput';
|
||||
|
||||
const LayoutPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
|
||||
const LayoutPanel: React.FC<SettingsPanelPanelProp> = ({ bookKey, onRegisterReset }) => {
|
||||
const _ = useTranslation();
|
||||
const { envConfig, appService } = useEnv();
|
||||
const { getView, getViewSettings, getGridInsets, setViewSettings } = useReaderStore();
|
||||
@@ -63,9 +65,47 @@ const LayoutPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
|
||||
const [showBarsOnScroll, setShowBarsOnScroll] = useState(viewSettings.showBarsOnScroll!);
|
||||
const [showRemainingTime, setShowRemainingTime] = useState(viewSettings.showRemainingTime!);
|
||||
const [showPageNumber, setShowPageNumber] = useState(viewSettings.showPageNumber!);
|
||||
|
||||
const [screenOrientation, setScreenOrientation] = useState(viewSettings.screenOrientation!);
|
||||
|
||||
const resetToDefaults = useResetViewSettings();
|
||||
|
||||
const handleReset = () => {
|
||||
resetToDefaults({
|
||||
paragraphMargin: setParagraphMargin,
|
||||
lineHeight: setLineHeight,
|
||||
wordSpacing: setWordSpacing,
|
||||
letterSpacing: setLetterSpacing,
|
||||
textIndent: setTextIndent,
|
||||
fullJustification: setFullJustification,
|
||||
hyphenation: setHyphenation,
|
||||
marginTopPx: setMarginTopPx,
|
||||
marginBottomPx: setMarginBottomPx,
|
||||
marginLeftPx: setMarginLeftPx,
|
||||
marginRightPx: setMarginRightPx,
|
||||
compactMarginTopPx: setCompactMarginTopPx,
|
||||
compactMarginBottomPx: setCompactMarginBottomPx,
|
||||
compactMarginLeftPx: setCompactMarginLeftPx,
|
||||
compactMarginRightPx: setCompactMarginRightPx,
|
||||
gapPercent: setGapPercent,
|
||||
maxColumnCount: setMaxColumnCount,
|
||||
maxInlineSize: setMaxInlineSize,
|
||||
maxBlockSize: setMaxBlockSize,
|
||||
overrideLayout: setOverrideLayout,
|
||||
doubleBorder: setDoubleBorder,
|
||||
borderColor: setBorderColor,
|
||||
showHeader: setShowHeader,
|
||||
showFooter: setShowFooter,
|
||||
showBarsOnScroll: setShowBarsOnScroll,
|
||||
showRemainingTime: setShowRemainingTime,
|
||||
showPageNumber: setShowPageNumber,
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
onRegisterReset(handleReset);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
saveViewSettings(envConfig, bookKey, 'paragraphMargin', paragraphMargin);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
|
||||
@@ -1,17 +1,19 @@
|
||||
import clsx from 'clsx';
|
||||
import cssbeautify from 'cssbeautify';
|
||||
import React, { useRef, useState } from 'react';
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
|
||||
import { useEnv } from '@/context/EnvContext';
|
||||
import { useReaderStore } from '@/store/readerStore';
|
||||
import { useSettingsStore } from '@/store/settingsStore';
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
import { useResetViewSettings } from '../../hooks/useResetSettings';
|
||||
import { SettingsPanelPanelProp } from './SettingsDialog';
|
||||
import { getStyles } from '@/utils/style';
|
||||
import cssValidate from '@/utils/css';
|
||||
|
||||
type CSSType = 'book' | 'reader';
|
||||
|
||||
const MiscPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
|
||||
const MiscPanel: React.FC<SettingsPanelPanelProp> = ({ bookKey, onRegisterReset }) => {
|
||||
const _ = useTranslation();
|
||||
const { appService } = useEnv();
|
||||
const { settings, isFontLayoutSettingsGlobal, setSettings } = useSettingsStore();
|
||||
@@ -29,6 +31,22 @@ const MiscPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
|
||||
const contentTextareaRef = useRef<HTMLTextAreaElement | null>(null);
|
||||
const uiTextareaRef = useRef<HTMLTextAreaElement | null>(null);
|
||||
|
||||
const resetToDefaults = useResetViewSettings();
|
||||
|
||||
const handleReset = () => {
|
||||
resetToDefaults({
|
||||
userStylesheet: setDraftContentStylesheet,
|
||||
userUIStylesheet: setDraftUIStylesheet,
|
||||
});
|
||||
applyStyles('book', true);
|
||||
applyStyles('reader', true);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
onRegisterReset(handleReset);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
const validateCSS = (cssInput: string): { isValid: boolean; error?: string } => {
|
||||
if (!cssInput.trim()) return { isValid: true };
|
||||
|
||||
@@ -64,9 +82,9 @@ const MiscPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
|
||||
}
|
||||
};
|
||||
|
||||
const applyStyles = (type: CSSType) => {
|
||||
const applyStyles = (type: CSSType, clear = false) => {
|
||||
const cssInput = type === 'book' ? draftContentStylesheet : draftUIStylesheet;
|
||||
const formattedCSS = cssbeautify(cssInput, {
|
||||
const formattedCSS = cssbeautify(clear ? '' : cssInput, {
|
||||
indent: ' ',
|
||||
openbrace: 'end-of-line',
|
||||
autosemicolon: true,
|
||||
|
||||
@@ -22,7 +22,11 @@ import ControlPanel from './ControlPanel';
|
||||
import LangPanel from './LangPanel';
|
||||
import MiscPanel from './MiscPanel';
|
||||
|
||||
type SettingsPanelType = 'Font' | 'Layout' | 'Color' | 'Control' | 'Language' | 'Custom';
|
||||
export type SettingsPanelType = 'Font' | 'Layout' | 'Color' | 'Control' | 'Language' | 'Custom';
|
||||
export type SettingsPanelPanelProp = {
|
||||
bookKey: string;
|
||||
onRegisterReset: (resetFn: () => void) => void;
|
||||
};
|
||||
|
||||
type TabConfig = {
|
||||
tab: SettingsPanelType;
|
||||
@@ -84,6 +88,28 @@ const SettingsDialog: React.FC<{ bookKey: string; config: BookConfig }> = ({ boo
|
||||
localStorage.setItem('lastConfigPanel', tab);
|
||||
};
|
||||
|
||||
const [resetFunctions, setResetFunctions] = useState<
|
||||
Record<SettingsPanelType, (() => void) | null>
|
||||
>({
|
||||
Font: null,
|
||||
Layout: null,
|
||||
Color: null,
|
||||
Control: null,
|
||||
Language: null,
|
||||
Custom: null,
|
||||
});
|
||||
|
||||
const registerResetFunction = (panel: SettingsPanelType, resetFn: () => void) => {
|
||||
setResetFunctions((prev) => ({ ...prev, [panel]: resetFn }));
|
||||
};
|
||||
|
||||
const handleResetCurrentPanel = () => {
|
||||
const resetFn = resetFunctions[activePanel];
|
||||
if (resetFn) {
|
||||
resetFn();
|
||||
}
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
setFontLayoutSettingsDialogOpen(false);
|
||||
};
|
||||
@@ -130,6 +156,8 @@ const SettingsDialog: React.FC<{ bookKey: string; config: BookConfig }> = ({ boo
|
||||
};
|
||||
}, []);
|
||||
|
||||
const currentPanel = tabConfig.find((tab) => tab.tab === activePanel);
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
isOpen={true}
|
||||
@@ -140,7 +168,7 @@ const SettingsDialog: React.FC<{ bookKey: string; config: BookConfig }> = ({ boo
|
||||
header={
|
||||
<div className='flex w-full flex-col items-center'>
|
||||
<div className='tab-title flex pb-2 text-base font-semibold sm:hidden'>
|
||||
{tabConfig.find((tab) => tab.tab === activePanel)?.label || ''}
|
||||
{currentPanel?.label || ''}
|
||||
</div>
|
||||
<div className='flex w-full flex-row items-center justify-between'>
|
||||
<button
|
||||
@@ -184,7 +212,15 @@ const SettingsDialog: React.FC<{ bookKey: string; config: BookConfig }> = ({ boo
|
||||
buttonClassName='btn btn-ghost h-8 min-h-8 w-8 p-0 flex items-center justify-center'
|
||||
toggleButton={<PiDotsThreeVerticalBold />}
|
||||
>
|
||||
<DialogMenu />
|
||||
<DialogMenu
|
||||
activePanel={activePanel}
|
||||
onReset={handleResetCurrentPanel}
|
||||
resetLabel={
|
||||
currentPanel
|
||||
? _('Reset {{settings}}', { settings: currentPanel.label })
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
</Dropdown>
|
||||
<button
|
||||
onClick={handleClose}
|
||||
@@ -209,12 +245,39 @@ const SettingsDialog: React.FC<{ bookKey: string; config: BookConfig }> = ({ boo
|
||||
</div>
|
||||
}
|
||||
>
|
||||
{activePanel === 'Font' && <FontPanel bookKey={bookKey} />}
|
||||
{activePanel === 'Layout' && <LayoutPanel bookKey={bookKey} />}
|
||||
{activePanel === 'Color' && <ColorPanel bookKey={bookKey} />}
|
||||
{activePanel === 'Control' && <ControlPanel bookKey={bookKey} />}
|
||||
{activePanel === 'Language' && <LangPanel bookKey={bookKey} />}
|
||||
{activePanel === 'Custom' && <MiscPanel bookKey={bookKey} />}
|
||||
{activePanel === 'Font' && (
|
||||
<FontPanel bookKey={bookKey} onRegisterReset={(fn) => registerResetFunction('Font', fn)} />
|
||||
)}
|
||||
{activePanel === 'Layout' && (
|
||||
<LayoutPanel
|
||||
bookKey={bookKey}
|
||||
onRegisterReset={(fn) => registerResetFunction('Layout', fn)}
|
||||
/>
|
||||
)}
|
||||
{activePanel === 'Color' && (
|
||||
<ColorPanel
|
||||
bookKey={bookKey}
|
||||
onRegisterReset={(fn) => registerResetFunction('Color', fn)}
|
||||
/>
|
||||
)}
|
||||
{activePanel === 'Control' && (
|
||||
<ControlPanel
|
||||
bookKey={bookKey}
|
||||
onRegisterReset={(fn) => registerResetFunction('Control', fn)}
|
||||
/>
|
||||
)}
|
||||
{activePanel === 'Language' && (
|
||||
<LangPanel
|
||||
bookKey={bookKey}
|
||||
onRegisterReset={(fn) => registerResetFunction('Language', fn)}
|
||||
/>
|
||||
)}
|
||||
{activePanel === 'Custom' && (
|
||||
<MiscPanel
|
||||
bookKey={bookKey}
|
||||
onRegisterReset={(fn) => registerResetFunction('Custom', fn)}
|
||||
/>
|
||||
)}
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import { useReaderStore } from '@/store/readerStore';
|
||||
import { useNotebookStore } from '@/store/notebookStore';
|
||||
import { isTauriAppPlatform } from '@/services/environment';
|
||||
import useShortcuts from '@/hooks/useShortcuts';
|
||||
import useBooksManager from './useBooksManager';
|
||||
import { useSidebarStore } from '@/store/sidebarStore';
|
||||
import { useSettingsStore } from '@/store/settingsStore';
|
||||
import { getStyles } from '@/utils/style';
|
||||
@@ -10,6 +8,8 @@ import { tauriHandleToggleFullScreen, tauriQuitApp } from '@/utils/window';
|
||||
import { eventDispatcher } from '@/utils/event';
|
||||
import { MAX_ZOOM_LEVEL, MIN_ZOOM_LEVEL, ZOOM_STEP } from '@/services/constants';
|
||||
import { viewPagination } from './usePagination';
|
||||
import useShortcuts from '@/hooks/useShortcuts';
|
||||
import useBooksManager from './useBooksManager';
|
||||
|
||||
interface UseBookShortcutsProps {
|
||||
sideBarBookKey: string | null;
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import { Dispatch, SetStateAction } from 'react';
|
||||
import { useEnv } from '@/context/EnvContext';
|
||||
import { ViewSettings } from '@/types/book';
|
||||
|
||||
type SetterKey = keyof ViewSettings;
|
||||
type SetterValue = SetStateAction<string> & SetStateAction<number> & SetStateAction<boolean>;
|
||||
|
||||
type StateSetters = Partial<{
|
||||
[Key in SetterKey]: Dispatch<SetterValue>;
|
||||
}>;
|
||||
|
||||
export const useResetViewSettings = () => {
|
||||
const { appService } = useEnv();
|
||||
|
||||
const resetToDefaults = (setters: StateSetters) => {
|
||||
if (!appService) return;
|
||||
const defaultSettings = appService.getDefaultViewSettings();
|
||||
|
||||
Object.entries(setters).forEach(([settingKey, setter]) => {
|
||||
const freshValue = defaultSettings[settingKey as SetterKey];
|
||||
if (freshValue !== undefined) {
|
||||
setter(freshValue as SetterValue);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
return resetToDefaults;
|
||||
};
|
||||
@@ -36,7 +36,7 @@ const Dropdown: React.FC<DropdownProps> = ({
|
||||
: children;
|
||||
|
||||
return (
|
||||
<div className='dropdown-container'>
|
||||
<div className='dropdown-container flex'>
|
||||
{isOpen && (
|
||||
<div className='fixed inset-0 bg-transparent' onClick={() => setIsDropdownOpen(false)} />
|
||||
)}
|
||||
|
||||
@@ -6,6 +6,7 @@ import { useResponsiveSize } from '@/hooks/useResponsiveSize';
|
||||
interface MenuItemProps {
|
||||
label: string;
|
||||
description?: string;
|
||||
tooltip?: string;
|
||||
buttonClass?: string;
|
||||
labelClass?: string;
|
||||
shortcut?: string;
|
||||
@@ -19,6 +20,7 @@ interface MenuItemProps {
|
||||
const MenuItem: React.FC<MenuItemProps> = ({
|
||||
label,
|
||||
description,
|
||||
tooltip,
|
||||
buttonClass,
|
||||
labelClass,
|
||||
shortcut,
|
||||
@@ -36,6 +38,7 @@ const MenuItem: React.FC<MenuItemProps> = ({
|
||||
disabled && 'btn-disabled text-gray-400',
|
||||
buttonClass,
|
||||
)}
|
||||
data-tip={tooltip ? tooltip : ''}
|
||||
onClick={onClick}
|
||||
disabled={disabled}
|
||||
>
|
||||
|
||||
@@ -2,7 +2,7 @@ import { AppPlatform, AppService, OsPlatform } from '@/types/system';
|
||||
|
||||
import { SystemSettings } from '@/types/settings';
|
||||
import { FileSystem, BaseDir } from '@/types/system';
|
||||
import { Book, BookConfig, BookContent, BookFormat } from '@/types/book';
|
||||
import { Book, BookConfig, BookContent, BookFormat, ViewSettings } from '@/types/book';
|
||||
import {
|
||||
getDir,
|
||||
getLocalBookFilename,
|
||||
@@ -74,6 +74,20 @@ export abstract class BaseAppService implements AppService {
|
||||
abstract selectDirectory(): Promise<string>;
|
||||
abstract selectFiles(name: string, extensions: string[]): Promise<string[]>;
|
||||
|
||||
getDefaultViewSettings(): ViewSettings {
|
||||
return {
|
||||
...DEFAULT_BOOK_LAYOUT,
|
||||
...DEFAULT_BOOK_STYLE,
|
||||
...DEFAULT_BOOK_FONT,
|
||||
...(this.isMobile ? DEFAULT_MOBILE_VIEW_SETTINGS : {}),
|
||||
...(isCJKEnv() ? DEFAULT_CJK_VIEW_SETTINGS : {}),
|
||||
...DEFAULT_VIEW_CONFIG,
|
||||
...DEFAULT_TTS_CONFIG,
|
||||
...DEFAULT_SCREEN_CONFIG,
|
||||
...{ ...DEFAULT_TRANSLATOR_CONFIG, translateTargetLang: getTargetLang() },
|
||||
};
|
||||
}
|
||||
|
||||
async loadSettings(): Promise<SystemSettings> {
|
||||
let settings: SystemSettings;
|
||||
const { fp, base } = this.resolvePath('settings.json', 'Settings');
|
||||
@@ -90,15 +104,7 @@ export abstract class BaseAppService implements AppService {
|
||||
settings = { ...DEFAULT_SYSTEM_SETTINGS, ...settings };
|
||||
settings.globalReadSettings = { ...DEFAULT_READSETTINGS, ...settings.globalReadSettings };
|
||||
settings.globalViewSettings = {
|
||||
...DEFAULT_BOOK_LAYOUT,
|
||||
...DEFAULT_BOOK_STYLE,
|
||||
...DEFAULT_BOOK_FONT,
|
||||
...(this.isMobile ? DEFAULT_MOBILE_VIEW_SETTINGS : {}),
|
||||
...(isCJKEnv() ? DEFAULT_CJK_VIEW_SETTINGS : {}),
|
||||
...DEFAULT_VIEW_CONFIG,
|
||||
...DEFAULT_TTS_CONFIG,
|
||||
...DEFAULT_SCREEN_CONFIG,
|
||||
...{ ...DEFAULT_TRANSLATOR_CONFIG, translateTargetLang: getTargetLang() },
|
||||
...this.getDefaultViewSettings(),
|
||||
...settings.globalViewSettings,
|
||||
};
|
||||
} catch {
|
||||
@@ -110,17 +116,7 @@ export abstract class BaseAppService implements AppService {
|
||||
...DEFAULT_READSETTINGS,
|
||||
...(this.isMobile ? DEFAULT_MOBILE_READSETTINGS : {}),
|
||||
},
|
||||
globalViewSettings: {
|
||||
...DEFAULT_BOOK_LAYOUT,
|
||||
...DEFAULT_BOOK_STYLE,
|
||||
...DEFAULT_BOOK_FONT,
|
||||
...(this.isMobile ? DEFAULT_MOBILE_VIEW_SETTINGS : {}),
|
||||
...(isCJKEnv() ? DEFAULT_CJK_VIEW_SETTINGS : {}),
|
||||
...DEFAULT_VIEW_CONFIG,
|
||||
...DEFAULT_TTS_CONFIG,
|
||||
...DEFAULT_SCREEN_CONFIG,
|
||||
...{ ...DEFAULT_TRANSLATOR_CONFIG, translateTargetLang: getTargetLang() },
|
||||
},
|
||||
globalViewSettings: this.getDefaultViewSettings(),
|
||||
} as SystemSettings;
|
||||
|
||||
await this.fs.createDir('', 'Books', true);
|
||||
|
||||
@@ -2,7 +2,6 @@ export type BookFormat = 'EPUB' | 'PDF' | 'MOBI' | 'CBZ' | 'FB2' | 'FBZ';
|
||||
export type BookNoteType = 'bookmark' | 'annotation' | 'excerpt';
|
||||
export type HighlightStyle = 'highlight' | 'underline' | 'squiggly';
|
||||
export type HighlightColor = 'red' | 'yellow' | 'green' | 'blue' | 'violet';
|
||||
import { CodeLanguage } from '@/utils/highlightjs';
|
||||
|
||||
export interface Book {
|
||||
// if Book is a remote book we just lazy load the book content via url
|
||||
@@ -115,7 +114,7 @@ export interface BookStyle {
|
||||
overrideLayout: boolean;
|
||||
overrideColor: boolean;
|
||||
codeHighlighting: boolean;
|
||||
codeLanguage: CodeLanguage;
|
||||
codeLanguage: string;
|
||||
userStylesheet: string;
|
||||
userUIStylesheet: string;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { SystemSettings } from './settings';
|
||||
import { Book, BookConfig, BookContent } from './book';
|
||||
import { Book, BookConfig, BookContent, ViewSettings } from './book';
|
||||
import { BookDoc } from '@/libs/document';
|
||||
import { ProgressHandler } from '@/utils/transfer';
|
||||
|
||||
@@ -44,6 +44,7 @@ export interface AppService {
|
||||
|
||||
selectDirectory(): Promise<string>;
|
||||
selectFiles(name: string, extensions: string[]): Promise<string[]>;
|
||||
getDefaultViewSettings(): ViewSettings;
|
||||
loadSettings(): Promise<SystemSettings>;
|
||||
saveSettings(settings: SystemSettings): Promise<void>;
|
||||
importBook(
|
||||
|
||||
Reference in New Issue
Block a user