forked from akai/readest
600d69fa50
* fix(reader): gate route View Transitions on the API, turns on groups (READEST-9) Reverts #4949, which opened books through the plain router to dodge the "Transition was aborted because of timeout in DOM update" TimeoutError (Sentry READEST-9). Rather than carve the transition out of one flow, gate it at the router: useAppRouter routes through the View Transition router only where the engine has the View Transitions API, and every into-reader path (including the reverted ones) goes back through useAppRouter. The base View Transitions API and nested view-transition groups reach very different browsers, so they become two separate appService capability flags, each backed by a probe in utils/viewTransition: * supportsViewTransitionsAPI (document.startViewTransition): the baseline a route crossfade needs, landing on Chrome 111+, Safari 18+, recent WebView. Gates the router. * supportsViewTransitionGroup (view-transition-group: nearest, Chrome/WebView 140+): the far narrower target the paginator's layered turns require. Gates the turn-style options and the captured-turn fallback. Both flags fold in the Linux WebKitGTK carve-out because it crashes on the snapshot, matching the supportsCanvasContext2DFilter precedent. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(tts): enlarge the Now Playing bar and scale its controls responsively Grow the collapsed bar to h-14 with a 10x10 cover and symmetric px-2 padding, drive the play/pause and close icons through useResponsiveSize instead of fixed pixel sizes, and cut the bottom safe-area contribution to a third so the bar sits closer to the screen edge. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
555 lines
21 KiB
TypeScript
555 lines
21 KiB
TypeScript
import React, { useEffect, useRef, useState } from 'react';
|
|
import { useEnv } from '@/context/EnvContext';
|
|
import { useReaderStore } from '@/store/readerStore';
|
|
import { useTranslation } from '@/hooks/useTranslation';
|
|
import { useBookDataStore } from '@/store/bookDataStore';
|
|
import { useSettingsStore } from '@/store/settingsStore';
|
|
import { useResetViewSettings } from '@/hooks/useResetSettings';
|
|
import { useEinkMode } from '@/hooks/useEinkMode';
|
|
import { getStyles } from '@/utils/style';
|
|
import { getMaxInlineSize } from '@/utils/config';
|
|
import { saveSysSettings, saveViewSettings } from '@/helpers/settings';
|
|
import { PageTurnStyle } from '@/types/book';
|
|
import { SettingsPanelPanelProp } from './SettingsDialog';
|
|
import { annotationToolQuickActions } from '@/app/reader/components/annotator/AnnotationTools';
|
|
import { applyPageTurnAttributes } from '@/app/reader/hooks/useCapturedTurn';
|
|
import { isTauriAppPlatform } from '@/services/environment';
|
|
import {
|
|
BoxedList,
|
|
NavigationRow,
|
|
SettingsRow,
|
|
SettingsSelect,
|
|
SettingsSwitchRow,
|
|
} from './primitives';
|
|
import NumberInput from './NumberInput';
|
|
import PageTurnerSettings from './PageTurnerSettings';
|
|
import AnnotationToolbarCustomizer from './AnnotationToolbarCustomizer';
|
|
import { DEFAULT_ANNOTATION_TOOLBAR_ITEMS } from '@/utils/annotationToolbar';
|
|
import { canShareText } from '@/utils/share';
|
|
import { optInTelemetry, optOutTelemetry } from '@/utils/telemetry';
|
|
|
|
const ControlPanel: React.FC<SettingsPanelPanelProp> = ({ bookKey, onRegisterReset }) => {
|
|
const _ = useTranslation();
|
|
const { envConfig, appService } = useEnv();
|
|
const { getView, getViewSettings, recreateViewer } = useReaderStore();
|
|
const { getBookData } = useBookDataStore();
|
|
const { settings } = useSettingsStore();
|
|
const { applyEinkMode } = useEinkMode();
|
|
const bookData = getBookData(bookKey);
|
|
const viewSettings = getViewSettings(bookKey) || settings.globalViewSettings;
|
|
|
|
const [isScrolledMode, setScrolledMode] = useState(viewSettings.scrolled);
|
|
const [noContinuousScroll, setNoContinuousScroll] = useState(viewSettings.noContinuousScroll);
|
|
const [scrollingOverlap, setScrollingOverlap] = useState(viewSettings.scrollingOverlap);
|
|
const [hideScrollbar, setHideScrollbar] = useState(viewSettings.hideScrollbar || false);
|
|
const [showPaginationButtons, setShowPaginationButtons] = useState(
|
|
viewSettings.showPaginationButtons,
|
|
);
|
|
const [isDisableClick, setIsDisableClick] = useState(viewSettings.disableClick);
|
|
const [isDisableSwipe, setIsDisableSwipe] = useState(viewSettings.disableSwipe);
|
|
const [fullscreenClickArea, setFullscreenClickArea] = useState(viewSettings.fullscreenClickArea);
|
|
const [swapClickArea, setSwapClickArea] = useState(viewSettings.swapClickArea);
|
|
const [isDisableDoubleClick, setIsDisableDoubleClick] = useState(viewSettings.disableDoubleClick);
|
|
const [enableAnnotationQuickActions, setEnableAnnotationQuickActions] = useState(
|
|
viewSettings.enableAnnotationQuickActions,
|
|
);
|
|
const [annotationQuickAction, setAnnotationQuickAction] = useState(
|
|
viewSettings.annotationQuickAction,
|
|
);
|
|
const [copyToNotebook, setCopyToNotebook] = useState(viewSettings.copyToNotebook);
|
|
const [showToolbarCustomizer, setShowToolbarCustomizer] = useState(false);
|
|
const [animated, setAnimated] = useState(viewSettings.animated);
|
|
const [pageTurnStyle, setPageTurnStyle] = useState(viewSettings.pageTurnStyle || 'push');
|
|
const [isEink, setIsEink] = useState(viewSettings.isEink);
|
|
const [isColorEink, setIsColorEink] = useState(viewSettings.isColorEink);
|
|
const [autoScreenBrightness, setAutoScreenBrightness] = useState(settings.autoScreenBrightness);
|
|
const [swipeBrightnessGesture, setSwipeBrightnessGesture] = useState(
|
|
settings.swipeBrightnessGesture,
|
|
);
|
|
const [screenWakeLock, setScreenWakeLock] = useState(settings.screenWakeLock);
|
|
const [allowScript, setAllowScript] = useState(viewSettings.allowScript);
|
|
const [isAutoCheckUpdates, setIsAutoCheckUpdates] = useState(settings.autoCheckUpdates);
|
|
const [isNightlyChannel, setIsNightlyChannel] = useState(settings.updateChannel === 'nightly');
|
|
const [isTelemetryEnabled, setIsTelemetryEnabled] = useState(settings.telemetryEnabled);
|
|
|
|
const resetToDefaults = useResetViewSettings();
|
|
const pageTurnerResetRef = useRef<() => void>(() => {});
|
|
const canShare = canShareText(appService);
|
|
|
|
// The layered styles need an engine with full View Transitions support or
|
|
// the Tauri captured-turn fallback; engines like iOS 18 WebKit crash on
|
|
// the VT turns, so on the web they only get Push (readest#555).
|
|
const turnStyleOptions = [
|
|
{ value: 'push', label: _('Push') },
|
|
...(appService?.supportsViewTransitionGroup || isTauriAppPlatform()
|
|
? [
|
|
{ value: 'slide', label: _('Slide') },
|
|
{ value: 'curl', label: _('Page Curl') },
|
|
]
|
|
: []),
|
|
];
|
|
|
|
const handleReset = () => {
|
|
resetToDefaults({
|
|
scrolled: setScrolledMode,
|
|
noContinuousScroll: setNoContinuousScroll,
|
|
scrollingOverlap: setScrollingOverlap,
|
|
hideScrollbar: setHideScrollbar,
|
|
showPaginationButtons: setShowPaginationButtons,
|
|
disableClick: setIsDisableClick,
|
|
disableSwipe: setIsDisableSwipe,
|
|
swapClickArea: setSwapClickArea,
|
|
animated: setAnimated,
|
|
isEink: setIsEink,
|
|
allowScript: setAllowScript,
|
|
fullscreenClickArea: setFullscreenClickArea,
|
|
disableDoubleClick: setIsDisableDoubleClick,
|
|
enableAnnotationQuickActions: setEnableAnnotationQuickActions,
|
|
copyToNotebook: setCopyToNotebook,
|
|
});
|
|
saveViewSettings(
|
|
envConfig,
|
|
bookKey,
|
|
'annotationToolbarItems',
|
|
DEFAULT_ANNOTATION_TOOLBAR_ITEMS,
|
|
false,
|
|
true,
|
|
);
|
|
pageTurnerResetRef.current();
|
|
};
|
|
|
|
useEffect(() => {
|
|
onRegisterReset(handleReset);
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, []);
|
|
|
|
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 (noContinuousScroll === viewSettings.noContinuousScroll) return;
|
|
saveViewSettings(envConfig, bookKey, 'noContinuousScroll', noContinuousScroll);
|
|
if (noContinuousScroll) {
|
|
getView(bookKey)?.renderer.setAttribute('no-continuous-scroll', '');
|
|
} else {
|
|
getView(bookKey)?.renderer.removeAttribute('no-continuous-scroll');
|
|
}
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, [noContinuousScroll]);
|
|
|
|
useEffect(() => {
|
|
saveViewSettings(envConfig, bookKey, 'hideScrollbar', hideScrollbar, false, false);
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, [hideScrollbar]);
|
|
|
|
useEffect(() => {
|
|
if (scrollingOverlap === viewSettings.scrollingOverlap) return;
|
|
saveViewSettings(envConfig, bookKey, 'scrollingOverlap', scrollingOverlap, false, false);
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, [scrollingOverlap]);
|
|
|
|
useEffect(() => {
|
|
saveViewSettings(
|
|
envConfig,
|
|
bookKey,
|
|
'showPaginationButtons',
|
|
showPaginationButtons,
|
|
false,
|
|
false,
|
|
);
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, [showPaginationButtons]);
|
|
|
|
useEffect(() => {
|
|
saveViewSettings(envConfig, bookKey, 'disableClick', isDisableClick, false, false);
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, [isDisableClick]);
|
|
|
|
// The renderer reads `turn-style`/`no-swipe` at touchmove/touchend time, so
|
|
// settings changes have to push the attributes through immediately rather
|
|
// than waiting for the next recreateViewer pass.
|
|
const applyTurnAttributes = () => {
|
|
const view = getView(bookKey);
|
|
const freshSettings = getViewSettings(bookKey);
|
|
if (view && freshSettings) {
|
|
applyPageTurnAttributes(view, freshSettings, !!bookData?.isFixedLayout);
|
|
}
|
|
};
|
|
|
|
useEffect(() => {
|
|
saveViewSettings(envConfig, bookKey, 'disableSwipe', isDisableSwipe, false, false);
|
|
applyTurnAttributes();
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, [isDisableSwipe]);
|
|
|
|
useEffect(() => {
|
|
saveViewSettings(envConfig, bookKey, 'disableDoubleClick', isDisableDoubleClick, false, false);
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, [isDisableDoubleClick]);
|
|
|
|
useEffect(() => {
|
|
saveViewSettings(envConfig, bookKey, 'fullscreenClickArea', fullscreenClickArea, false, false);
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, [fullscreenClickArea]);
|
|
|
|
useEffect(() => {
|
|
saveViewSettings(envConfig, bookKey, 'swapClickArea', swapClickArea, false, false);
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, [swapClickArea]);
|
|
|
|
useEffect(() => {
|
|
saveViewSettings(envConfig, bookKey, 'animated', animated, false, false);
|
|
if (animated) {
|
|
getView(bookKey)?.renderer.setAttribute('animated', '');
|
|
} else {
|
|
getView(bookKey)?.renderer.removeAttribute('animated');
|
|
}
|
|
// Mesh-curl eligibility depends on `animated`.
|
|
applyTurnAttributes();
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, [animated]);
|
|
|
|
useEffect(() => {
|
|
saveViewSettings(envConfig, bookKey, 'pageTurnStyle', pageTurnStyle, false, false);
|
|
applyTurnAttributes();
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, [pageTurnStyle]);
|
|
|
|
useEffect(() => {
|
|
saveViewSettings(envConfig, bookKey, 'isEink', isEink);
|
|
if (isEink) {
|
|
getView(bookKey)?.renderer.setAttribute('eink', '');
|
|
} else {
|
|
getView(bookKey)?.renderer.removeAttribute('eink');
|
|
}
|
|
applyEinkMode(isEink);
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, [isEink]);
|
|
|
|
useEffect(() => {
|
|
saveViewSettings(envConfig, bookKey, 'isColorEink', isColorEink);
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, [isColorEink]);
|
|
|
|
useEffect(() => {
|
|
if (autoScreenBrightness === settings.autoScreenBrightness) return;
|
|
saveSysSettings(envConfig, 'autoScreenBrightness', autoScreenBrightness);
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, [autoScreenBrightness]);
|
|
|
|
useEffect(() => {
|
|
if (swipeBrightnessGesture === settings.swipeBrightnessGesture) return;
|
|
saveSysSettings(envConfig, 'swipeBrightnessGesture', swipeBrightnessGesture);
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, [swipeBrightnessGesture]);
|
|
|
|
useEffect(() => {
|
|
if (screenWakeLock === settings.screenWakeLock) return;
|
|
saveSysSettings(envConfig, 'screenWakeLock', screenWakeLock);
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, [screenWakeLock]);
|
|
|
|
useEffect(() => {
|
|
if (viewSettings.allowScript === allowScript) return;
|
|
saveViewSettings(envConfig, bookKey, 'allowScript', allowScript, true, false).then(() => {
|
|
recreateViewer(envConfig, bookKey);
|
|
});
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, [allowScript]);
|
|
|
|
useEffect(() => {
|
|
saveViewSettings(
|
|
envConfig,
|
|
bookKey,
|
|
'enableAnnotationQuickActions',
|
|
enableAnnotationQuickActions,
|
|
false,
|
|
false,
|
|
);
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, [enableAnnotationQuickActions]);
|
|
|
|
useEffect(() => {
|
|
saveViewSettings(envConfig, bookKey, 'copyToNotebook', copyToNotebook, false, false);
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, [copyToNotebook]);
|
|
|
|
const toggleAutoCheckUpdates = () => {
|
|
const newValue = !isAutoCheckUpdates;
|
|
saveSysSettings(envConfig, 'autoCheckUpdates', newValue);
|
|
setIsAutoCheckUpdates(newValue);
|
|
};
|
|
|
|
const toggleNightlyChannel = () => {
|
|
const newValue = !isNightlyChannel;
|
|
saveSysSettings(envConfig, 'updateChannel', newValue ? 'nightly' : 'stable');
|
|
setIsNightlyChannel(newValue);
|
|
};
|
|
|
|
const toggleTelemetry = () => {
|
|
const newValue = !isTelemetryEnabled;
|
|
saveSysSettings(envConfig, 'telemetryEnabled', newValue);
|
|
setIsTelemetryEnabled(newValue);
|
|
if (newValue) {
|
|
optInTelemetry();
|
|
} else {
|
|
optOutTelemetry();
|
|
}
|
|
};
|
|
|
|
const getQuickActionOptions = () => {
|
|
return [
|
|
{
|
|
value: '',
|
|
label: _('None'),
|
|
},
|
|
...annotationToolQuickActions
|
|
.filter((button) => button.type !== 'share' || canShare)
|
|
.map((button) => ({
|
|
value: button.type,
|
|
label: _(button.label),
|
|
})),
|
|
];
|
|
};
|
|
|
|
const handleSelectAnnotationQuickAction = (event: React.ChangeEvent<HTMLSelectElement>) => {
|
|
const action = event.target.value as typeof annotationQuickAction;
|
|
setAnnotationQuickAction(action);
|
|
saveViewSettings(envConfig, bookKey, 'annotationQuickAction', action, false, true);
|
|
};
|
|
|
|
if (showToolbarCustomizer) {
|
|
return (
|
|
<AnnotationToolbarCustomizer
|
|
bookKey={bookKey}
|
|
onBack={() => setShowToolbarCustomizer(false)}
|
|
/>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className='my-4 w-full space-y-6'>
|
|
<BoxedList title={_('Scroll')} data-setting-id='settings.control.scrolledMode'>
|
|
<SettingsSwitchRow
|
|
label={_('Scrolled Mode')}
|
|
checked={isScrolledMode}
|
|
disabled={bookData?.isFixedLayout}
|
|
onChange={() => setScrolledMode(!isScrolledMode)}
|
|
/>
|
|
<SettingsSwitchRow
|
|
label={_('Single Section Scroll')}
|
|
checked={noContinuousScroll}
|
|
disabled={!viewSettings.scrolled}
|
|
onChange={() => setNoContinuousScroll(!noContinuousScroll)}
|
|
data-setting-id='settings.control.scroll.noContinuousScroll'
|
|
/>
|
|
<NumberInput
|
|
label={_('Overlap Pixels')}
|
|
value={scrollingOverlap}
|
|
onChange={setScrollingOverlap}
|
|
disabled={!viewSettings.scrolled}
|
|
min={0}
|
|
max={200}
|
|
step={10}
|
|
data-setting-id='settings.control.overlapPixels'
|
|
/>
|
|
<SettingsSwitchRow
|
|
label={_('Hide Scrollbar')}
|
|
checked={hideScrollbar}
|
|
disabled={!viewSettings.scrolled}
|
|
onChange={() => setHideScrollbar(!hideScrollbar)}
|
|
data-setting-id='settings.control.scroll.hideScrollbar'
|
|
/>
|
|
</BoxedList>
|
|
|
|
<BoxedList title={_('Pagination')} data-setting-id='settings.control.clickToPaginate'>
|
|
<SettingsSwitchRow
|
|
label={appService?.isMobileApp ? _('Tap to Paginate') : _('Click to Paginate')}
|
|
checked={!isDisableClick}
|
|
onChange={() => setIsDisableClick(!isDisableClick)}
|
|
/>
|
|
<SettingsSwitchRow
|
|
label={_('Swipe to Paginate')}
|
|
checked={!isDisableSwipe}
|
|
onChange={() => setIsDisableSwipe(!isDisableSwipe)}
|
|
data-setting-id='settings.control.swipeToPaginate'
|
|
/>
|
|
<SettingsSwitchRow
|
|
label={appService?.isMobileApp ? _('Tap Both Sides') : _('Click Both Sides')}
|
|
checked={fullscreenClickArea}
|
|
disabled={isDisableClick}
|
|
onChange={() => setFullscreenClickArea(!fullscreenClickArea)}
|
|
data-setting-id='settings.control.clickBothSides'
|
|
/>
|
|
<SettingsSwitchRow
|
|
label={appService?.isMobileApp ? _('Swap Tap Sides') : _('Swap Click Sides')}
|
|
checked={swapClickArea}
|
|
disabled={isDisableClick || fullscreenClickArea}
|
|
onChange={() => setSwapClickArea(!swapClickArea)}
|
|
data-setting-id='settings.control.swapClickSides'
|
|
/>
|
|
<SettingsSwitchRow
|
|
label={appService?.isMobileApp ? _('Disable Double Tap') : _('Disable Double Click')}
|
|
checked={isDisableDoubleClick}
|
|
onChange={() => setIsDisableDoubleClick(!isDisableDoubleClick)}
|
|
data-setting-id='settings.control.disableDoubleClick'
|
|
/>
|
|
<SettingsSwitchRow
|
|
label={_('Show Page Navigation Buttons')}
|
|
checked={showPaginationButtons}
|
|
onChange={() => setShowPaginationButtons(!showPaginationButtons)}
|
|
data-setting-id='settings.control.showPaginationButtons'
|
|
/>
|
|
</BoxedList>
|
|
|
|
<PageTurnerSettings
|
|
bookKey={bookKey}
|
|
onRegisterReset={(fn) => {
|
|
pageTurnerResetRef.current = fn;
|
|
}}
|
|
/>
|
|
|
|
<BoxedList
|
|
title={_('Annotation Tools')}
|
|
data-setting-id='settings.control.enableQuickActions'
|
|
>
|
|
<SettingsSwitchRow
|
|
label={_('Enable Quick Actions')}
|
|
checked={enableAnnotationQuickActions}
|
|
onChange={() => setEnableAnnotationQuickActions(!enableAnnotationQuickActions)}
|
|
/>
|
|
<SettingsRow label={_('Quick Action')} data-setting-id='settings.control.quickAction'>
|
|
<SettingsSelect
|
|
value={annotationQuickAction || ''}
|
|
onChange={handleSelectAnnotationQuickAction}
|
|
ariaLabel={_('Quick Action')}
|
|
options={getQuickActionOptions()}
|
|
disabled={!enableAnnotationQuickActions}
|
|
/>
|
|
</SettingsRow>
|
|
<SettingsSwitchRow
|
|
label={_('Copy to Notebook')}
|
|
checked={copyToNotebook}
|
|
onChange={() => setCopyToNotebook(!copyToNotebook)}
|
|
data-setting-id='settings.control.copyToNotebook'
|
|
/>
|
|
<NavigationRow
|
|
title={_('Customize Toolbar')}
|
|
onClick={() => setShowToolbarCustomizer(true)}
|
|
data-setting-id='settings.control.customizeToolbar'
|
|
/>
|
|
</BoxedList>
|
|
|
|
<BoxedList title={_('Animation')} data-setting-id='settings.control.pagingAnimation'>
|
|
<SettingsSwitchRow
|
|
label={_('Paging Animation')}
|
|
checked={animated}
|
|
onChange={() => setAnimated(!animated)}
|
|
/>
|
|
<SettingsRow label={_('Animation Style')} data-setting-id='settings.control.pageTurnStyle'>
|
|
<SettingsSelect
|
|
// A synced slide/curl setting from another device still reads as
|
|
// push here when this engine cannot animate it.
|
|
value={
|
|
turnStyleOptions.some((opt) => opt.value === pageTurnStyle) ? pageTurnStyle : 'push'
|
|
}
|
|
onChange={(e) => setPageTurnStyle(e.target.value as PageTurnStyle)}
|
|
ariaLabel={_('Animation Style')}
|
|
options={turnStyleOptions}
|
|
disabled={!animated}
|
|
/>
|
|
</SettingsRow>
|
|
</BoxedList>
|
|
|
|
<BoxedList title={_('Device')} data-setting-id='settings.control.device'>
|
|
{(appService?.isAndroidApp || appService?.appPlatform === 'web') && (
|
|
<SettingsSwitchRow
|
|
label={_('E-Ink Mode')}
|
|
checked={isEink}
|
|
onChange={() => setIsEink(!isEink)}
|
|
data-setting-id='settings.control.einkMode'
|
|
/>
|
|
)}
|
|
{(appService?.isAndroidApp || appService?.appPlatform === 'web') && (
|
|
<SettingsSwitchRow
|
|
label={_('Color E-Ink Mode')}
|
|
checked={isColorEink}
|
|
disabled={!isEink}
|
|
onChange={() => setIsColorEink(!isColorEink)}
|
|
data-setting-id='settings.control.colorEinkMode'
|
|
/>
|
|
)}
|
|
{appService?.isMobileApp && (
|
|
<SettingsSwitchRow
|
|
label={_('System Screen Brightness')}
|
|
checked={autoScreenBrightness}
|
|
onChange={() => setAutoScreenBrightness(!autoScreenBrightness)}
|
|
/>
|
|
)}
|
|
{appService?.hasScreenBrightness && (
|
|
<SettingsSwitchRow
|
|
label={_('Swipe for Brightness')}
|
|
description={_('Slide along the left edge')}
|
|
checked={swipeBrightnessGesture}
|
|
onChange={() => setSwipeBrightnessGesture(!swipeBrightnessGesture)}
|
|
data-setting-id='settings.control.swipeBrightnessGesture'
|
|
/>
|
|
)}
|
|
<SettingsSwitchRow
|
|
label={_('Keep Screen Awake')}
|
|
checked={screenWakeLock}
|
|
onChange={() => setScreenWakeLock(!screenWakeLock)}
|
|
data-setting-id='settings.control.screenWakeLock'
|
|
/>
|
|
</BoxedList>
|
|
|
|
{appService?.hasUpdater && (
|
|
<BoxedList title={_('Update')} data-setting-id='settings.control.checkUpdates'>
|
|
<SettingsSwitchRow
|
|
label={_('Check Updates on Start')}
|
|
checked={isAutoCheckUpdates}
|
|
onChange={toggleAutoCheckUpdates}
|
|
/>
|
|
<SettingsSwitchRow
|
|
label={_('Nightly Builds')}
|
|
description={isNightlyChannel ? _('Early daily builds') : ''}
|
|
checked={isNightlyChannel}
|
|
onChange={toggleNightlyChannel}
|
|
data-setting-id='settings.control.nightlyChannel'
|
|
/>
|
|
</BoxedList>
|
|
)}
|
|
|
|
<BoxedList title={_('Security')} data-setting-id='settings.control.allowJavascript'>
|
|
<SettingsSwitchRow
|
|
label={_('Allow JavaScript')}
|
|
description={_('Enable only if you trust the file.')}
|
|
checked={allowScript}
|
|
disabled={bookData?.book?.format !== 'EPUB'}
|
|
onChange={() => setAllowScript(!allowScript)}
|
|
/>
|
|
</BoxedList>
|
|
|
|
<BoxedList title={_('Privacy')} data-setting-id='settings.control.telemetry'>
|
|
<SettingsSwitchRow
|
|
label={_('Help improve Readest')}
|
|
description={isTelemetryEnabled ? _('Sharing anonymized statistics') : ''}
|
|
checked={isTelemetryEnabled}
|
|
onChange={toggleTelemetry}
|
|
/>
|
|
</BoxedList>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default ControlPanel;
|