This commit is contained in:
@@ -92,7 +92,7 @@ const ControlPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
|
||||
/>
|
||||
</div>
|
||||
<NumberInput
|
||||
label={_('Scrolling Overlap (px)')}
|
||||
label={_('Overlap Margin')}
|
||||
value={scrollingOverlap}
|
||||
onChange={setScrollingOverlap}
|
||||
disabled={!viewSettings.scrolled}
|
||||
|
||||
@@ -8,17 +8,30 @@ interface DropDownProps {
|
||||
selected: { option: string; label: string };
|
||||
options: { option: string; label: string }[];
|
||||
onSelect: (option: string) => void;
|
||||
disabled?: boolean;
|
||||
className?: string;
|
||||
listClassName?: string;
|
||||
}
|
||||
|
||||
const DropDown: React.FC<DropDownProps> = ({ selected, options, onSelect }) => {
|
||||
const DropDown: React.FC<DropDownProps> = ({
|
||||
selected,
|
||||
options,
|
||||
onSelect,
|
||||
className,
|
||||
listClassName,
|
||||
disabled = false,
|
||||
}) => {
|
||||
const iconSize16 = useResponsiveSize(16);
|
||||
const defaultIconSize = useDefaultIconSize();
|
||||
|
||||
return (
|
||||
<div className='dropdown dropdown-bottom'>
|
||||
<div className={clsx('dropdown dropdown-bottom', className)}>
|
||||
<button
|
||||
tabIndex={0}
|
||||
className='btn btn-sm flex items-center gap-1 px-[20px] font-normal normal-case'
|
||||
className={clsx(
|
||||
'btn btn-sm flex items-center gap-1 px-[20px] font-normal normal-case',
|
||||
disabled && 'btn-disabled',
|
||||
)}
|
||||
onClick={(e) => e.currentTarget.focus()}
|
||||
>
|
||||
<span>{selected.label}</span>
|
||||
@@ -29,6 +42,7 @@ const DropDown: React.FC<DropDownProps> = ({ selected, options, onSelect }) => {
|
||||
className={clsx(
|
||||
'dropdown-content bgcolor-base-200 no-triangle menu rounded-box absolute z-[1] shadow',
|
||||
'menu-vertical right-[-32px] mt-2 inline max-h-80 w-44 overflow-y-scroll sm:right-0',
|
||||
listClassName,
|
||||
)}
|
||||
>
|
||||
{options.map(({ option, label }) => (
|
||||
|
||||
@@ -10,6 +10,7 @@ import { useTranslation } from '@/hooks/useTranslation';
|
||||
import { getStyles } from '@/utils/style';
|
||||
import { lockScreenOrientation } from '@/utils/bridge';
|
||||
import { saveViewSettings } from '../../utils/viewSettingsHelper';
|
||||
import { getTranslators } from '@/services/translators';
|
||||
import { TRANSLATED_LANGS } from '@/services/constants';
|
||||
import cssbeautify from 'cssbeautify';
|
||||
import cssValidate from '@/utils/css';
|
||||
@@ -26,8 +27,11 @@ const MiscPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
|
||||
const [draftStylesheet, setDraftStylesheet] = useState(viewSettings.userStylesheet!);
|
||||
const [draftStylesheetSaved, setDraftStylesheetSaved] = useState(true);
|
||||
const [screenOrientation, setScreenOrientation] = useState(viewSettings.screenOrientation!);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [translationEnabled, setTranslationEnabled] = useState(viewSettings.translationEnabled!);
|
||||
const [translationProvider, setTranslationProvider] = useState(viewSettings.translationProvider!);
|
||||
const [translateTargetLang, setTranslateTargetLang] = useState(viewSettings.translateTargetLang!);
|
||||
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [inputFocusInAndroid, setInputFocusInAndroid] = useState(false);
|
||||
const textareaRef = useRef<HTMLTextAreaElement | null>(null);
|
||||
|
||||
@@ -108,7 +112,7 @@ const MiscPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
|
||||
};
|
||||
};
|
||||
|
||||
const getUILangOptions = () => {
|
||||
const getLangOptions = () => {
|
||||
const langs = TRANSLATED_LANGS as Record<string, string>;
|
||||
const options = Object.entries(langs).map(([option, label]) => ({ option, label }));
|
||||
options.sort((a, b) => a.label.localeCompare(b.label));
|
||||
@@ -121,6 +125,40 @@ const MiscPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
|
||||
i18n.changeLanguage(option ? option : navigator.language);
|
||||
};
|
||||
|
||||
const getTranslationProviderOptions = () => {
|
||||
const translators = getTranslators();
|
||||
const availableProviders = translators.map((t) => {
|
||||
return { option: t.name, label: t.label };
|
||||
});
|
||||
return availableProviders;
|
||||
};
|
||||
|
||||
const getCurrentTranslationProviderOption = () => {
|
||||
const option = translationProvider;
|
||||
const availableProviders = getTranslationProviderOptions();
|
||||
return availableProviders.find((p) => p.option === option) || availableProviders[0]!;
|
||||
};
|
||||
|
||||
const handleSelectTranslationProvider = (option: string) => {
|
||||
setTranslationProvider(option);
|
||||
saveViewSettings(envConfig, bookKey, 'translationProvider', option, false, false);
|
||||
viewSettings.translationProvider = option;
|
||||
setViewSettings(bookKey, { ...viewSettings });
|
||||
};
|
||||
|
||||
const getCurrentTargetLangOption = () => {
|
||||
const option = translateTargetLang;
|
||||
const availableOptions = getLangOptions();
|
||||
return availableOptions.find((o) => o.option === option) || availableOptions[0]!;
|
||||
};
|
||||
|
||||
const handleSelectTargetLang = (option: string) => {
|
||||
setTranslateTargetLang(option);
|
||||
saveViewSettings(envConfig, bookKey, 'translateTargetLang', option, false, false);
|
||||
viewSettings.translateTargetLang = option;
|
||||
setViewSettings(bookKey, { ...viewSettings });
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
saveViewSettings(envConfig, bookKey, 'animated', animated, false, false);
|
||||
if (animated) {
|
||||
@@ -139,6 +177,14 @@ const MiscPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [screenOrientation]);
|
||||
|
||||
useEffect(() => {
|
||||
if (translationEnabled === viewSettings.translationEnabled) return;
|
||||
saveViewSettings(envConfig, bookKey, 'translationEnabled', translationEnabled, true, false);
|
||||
viewSettings.translationEnabled = translationEnabled;
|
||||
setViewSettings(bookKey, { ...viewSettings });
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [translationEnabled]);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
@@ -151,10 +197,10 @@ const MiscPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
|
||||
<div className='card border-base-200 bg-base-100 border shadow'>
|
||||
<div className='divide-base-200 divide-y'>
|
||||
<div className='config-item'>
|
||||
<span className=''>{_('Language')}</span>
|
||||
<span className=''>{_('Interface Language')}</span>
|
||||
<DropDown
|
||||
options={getLangOptions()}
|
||||
selected={getCurrentUILangOption()}
|
||||
options={getUILangOptions()}
|
||||
onSelect={handleSelectUILang}
|
||||
/>
|
||||
</div>
|
||||
@@ -162,6 +208,46 @@ const MiscPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='w-full'>
|
||||
<h2 className='mb-2 font-medium'>{_('Translation')}</h2>
|
||||
<div className='card border-base-200 bg-base-100 border shadow'>
|
||||
<div className='divide-base-200'>
|
||||
<div className='config-item'>
|
||||
<span className=''>{_('Enable Translation')}</span>
|
||||
<input
|
||||
type='checkbox'
|
||||
className='toggle'
|
||||
checked={translationEnabled}
|
||||
onChange={() => setTranslationEnabled(!translationEnabled)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='config-item'>
|
||||
<span className=''>{_('Translation Service')}</span>
|
||||
<DropDown
|
||||
selected={getCurrentTranslationProviderOption()}
|
||||
options={getTranslationProviderOptions()}
|
||||
onSelect={handleSelectTranslationProvider}
|
||||
disabled={!translationEnabled}
|
||||
className='dropdown-top'
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='config-item'>
|
||||
<span className=''>{_('Translate To')}</span>
|
||||
<DropDown
|
||||
options={getLangOptions()}
|
||||
selected={getCurrentTargetLangOption()}
|
||||
onSelect={handleSelectTargetLang}
|
||||
disabled={!translationEnabled}
|
||||
className='dropdown-top'
|
||||
listClassName='!max-h-60'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='w-full'>
|
||||
<h2 className='mb-2 font-medium'>{_('Animation')}</h2>
|
||||
<div className='card border-base-200 bg-base-100 border shadow'>
|
||||
|
||||
@@ -141,8 +141,8 @@ const SettingsDialog: React.FC<{ bookKey: string; config: BookConfig }> = ({ boo
|
||||
<div
|
||||
ref={tabsRef}
|
||||
className={clsx(
|
||||
'dialog-tabs flex h-10 w-full items-center',
|
||||
showTabLabels ? 'gap-4 sm:gap-2' : 'gap-4',
|
||||
'dialog-tabs ms-2 flex h-10 w-full items-center',
|
||||
showTabLabels ? 'gap-2' : 'gap-4',
|
||||
)}
|
||||
>
|
||||
{tabConfig.map(({ tab, icon: Icon, label }) => (
|
||||
|
||||
Reference in New Issue
Block a user