Reader settings

This commit is contained in:
schroda
2024-11-04 18:00:35 +01:00
parent 9c57ace973
commit a607f7f368
70 changed files with 2813 additions and 425 deletions

View File

@@ -0,0 +1,51 @@
/*
* Copyright (C) Contributors to the Suwayomi project
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
import Dialog from '@mui/material/Dialog';
import DialogContent from '@mui/material/DialogContent';
import { useState } from 'react';
import { useReaderStateMangaContext } from '@/modules/reader/contexts/state/ReaderStateMangaContext.tsx';
import { ReaderService } from '@/modules/reader/services/ReaderService.ts';
import { ReaderSettingsTabs } from '@/modules/reader/components/settings/ReaderSettingsTabs.tsx';
import { ReaderSettingTab } from '@/modules/reader/constants/ReaderSettings.constants.tsx';
export const ReaderSettings = ({ isOpen, close }: { isOpen: boolean; close: () => void }) => {
const { manga } = useReaderStateMangaContext();
const settings = ReaderService.useSettings();
const [activeTab, setActiveTab] = useState(0);
if (!manga) {
return null;
}
if (!isOpen) {
return null;
}
return (
<Dialog
open={isOpen}
maxWidth="md"
fullWidth
onClose={close}
hideBackdrop={activeTab === ReaderSettingTab.FILTER}
>
<DialogContent sx={{ p: 0 }}>
<ReaderSettingsTabs
activeTab={activeTab}
setActiveTab={setActiveTab}
settings={settings}
updateSetting={(...args) => ReaderService.updateSetting(manga, ...args)}
deleteSetting={(...args) => ReaderService.deleteSetting(manga, ...args)}
/>
</DialogContent>
</Dialog>
);
};

View File

@@ -0,0 +1,159 @@
/*
* Copyright (C) Contributors to the Suwayomi project
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
import Box from '@mui/material/Box';
import Tab from '@mui/material/Tab';
import { useTranslation } from 'react-i18next';
import { TabsMenu } from '@/modules/core/components/tabs/TabsMenu.tsx';
import { ReaderService } from '@/modules/reader/services/ReaderService.ts';
import { MediaQuery } from '@/modules/core/utils/MediaQuery.tsx';
import { IReaderSettings, IReaderSettingsWithDefaultFlag } from '@/modules/reader/types/Reader.types.ts';
import { useReaderTapZoneContext } from '@/modules/reader/contexts/ReaderTapZoneContext.tsx';
import { applyStyles } from '@/modules/core/utils/ApplyStyles.ts';
import { READER_SETTING_TABS, ReaderSettingTab } from '@/modules/reader/constants/ReaderSettings.constants.tsx';
import { TabPanel } from '@/modules/core/components/tabs/TabPanel.tsx';
import { ReaderLayoutSettings } from '@/modules/reader/components/settings/layout/ReaderLayoutSettings.tsx';
import { ReaderGeneralSettings } from '@/modules/reader/components/settings/general/ReaderGeneralSettings.tsx';
import { ReaderFilterSettings } from '@/modules/reader/components/settings/filters/ReaderFilterSettings.tsx';
import { ReaderBehaviourSettings } from '@/modules/reader/components/settings/behaviour/ReaderBehaviourSettings.tsx';
interface BaseProps {
activeTab: number;
setActiveTab: (tab: number) => void;
settings: IReaderSettingsWithDefaultFlag;
updateSetting: (...args: OmitFirst<Parameters<typeof ReaderService.updateSetting>>) => void;
}
interface DefaultSettingsProps extends BaseProps {
areDefaultSettings: true;
}
interface MangaSettingsProps extends BaseProps {
deleteSetting: (setting: keyof IReaderSettings) => void;
}
type Props =
| (DefaultSettingsProps & PropertiesNever<Omit<MangaSettingsProps, keyof BaseProps>>)
| (PropertiesNever<Omit<DefaultSettingsProps, keyof BaseProps>> & MangaSettingsProps);
export const ReaderSettingsTabs = ({
activeTab,
setActiveTab,
areDefaultSettings,
settings,
updateSetting,
deleteSetting,
}: Props) => {
const { t } = useTranslation();
const { setShowPreview } = useReaderTapZoneContext();
const isTouchDevice = MediaQuery.useIsTouchDevice();
const { mode: overlayMode } = ReaderService.useOverlayMode();
return (
<>
<TabsMenu
value={activeTab}
onChange={(_, newTab) => setActiveTab(newTab)}
sx={{
...applyStyles(!!areDefaultSettings, { zIndex: 2 }),
...applyStyles(!areDefaultSettings, {
backgroundColor: 'background.paper',
backgroundImage: 'var(--Paper-overlay)',
}),
}}
>
{Object.values(READER_SETTING_TABS).map(({ id, label, supportsTouchDevices }) => {
if (!supportsTouchDevices && isTouchDevice) {
return null;
}
return (
<Tab
key={id}
value={id}
label={t(label)}
sx={{ flexGrow: 1, maxWidth: 'unset', textTransform: 'none' }}
/>
);
})}
</TabsMenu>
<Box sx={{ p: areDefaultSettings ? undefined : 2, overflowX: 'hidden' }}>
{Object.values(READER_SETTING_TABS).map(({ id, supportsTouchDevices }) => {
if (!supportsTouchDevices && isTouchDevice) {
return null;
}
switch (id as ReaderSettingTab) {
case ReaderSettingTab.LAYOUT:
return (
<TabPanel key={id} index={id} currentIndex={activeTab}>
<ReaderLayoutSettings
settings={settings}
updateSetting={(...args) => updateSetting(...args)}
setShowPreview={setShowPreview!}
// @ts-expect-error - TS2322: Type boolean is not assignable to type true
isDefaultable={!areDefaultSettings}
onDefault={(...args) => deleteSetting?.(...args)}
/>
</TabPanel>
);
case ReaderSettingTab.GENERAL:
return (
<TabPanel
key={id}
index={id}
currentIndex={activeTab}
sx={{ p: areDefaultSettings ? 2 : undefined }}
>
<ReaderGeneralSettings
overlayMode={overlayMode}
settings={settings}
updateSetting={(...args) => updateSetting(...args)}
// @ts-expect-error - TS2322: Type boolean is not assignable to type true
isDefaultable={!areDefaultSettings}
onDefault={(...args) => deleteSetting?.(...args)}
/>
</TabPanel>
);
case ReaderSettingTab.FILTER:
return (
<TabPanel
key={id}
index={id}
currentIndex={activeTab}
sx={{ p: areDefaultSettings ? 2 : undefined }}
>
<ReaderFilterSettings
settings={settings}
updateSetting={(...args) => updateSetting(...args)}
/>
</TabPanel>
);
case ReaderSettingTab.BEHAVIOUR:
return (
<TabPanel
key={id}
index={id}
currentIndex={activeTab}
sx={{ p: areDefaultSettings ? 2 : undefined }}
>
<ReaderBehaviourSettings
settings={settings}
updateSetting={(...args) => updateSetting(...args)}
/>
</TabPanel>
);
default:
throw new Error(`Unexpected "ReaderSettingTab" (${id})`);
}
})}
</Box>
</>
);
};

View File

@@ -0,0 +1,48 @@
/*
* Copyright (C) Contributors to the Suwayomi project
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
import Stack from '@mui/material/Stack';
import { useTranslation } from 'react-i18next';
import { ReaderService } from '@/modules/reader/services/ReaderService.ts';
import { IReaderSettingsWithDefaultFlag } from '@/modules/reader/types/Reader.types.ts';
import { CheckboxInput } from '@/modules/core/components/inputs/CheckboxInput.tsx';
import { ReaderSettingExitMode } from '@/modules/reader/components/settings/behaviour/ReaderSettingExitMode.tsx';
import { isOffsetDoubleSpreadPagesEditable } from '@/modules/reader/utils/ReaderSettings.utils.tsx';
export const ReaderBehaviourSettings = ({
settings,
updateSetting,
}: {
settings: IReaderSettingsWithDefaultFlag;
updateSetting: (
...args: OmitFirst<Parameters<typeof ReaderService.updateSetting>>
) => ReturnType<typeof ReaderService.updateSetting>;
}) => {
const { t } = useTranslation();
return (
<Stack sx={{ gap: 2 }}>
<ReaderSettingExitMode
exitMode={settings.exitMode}
setExitMode={(value) => updateSetting('exitMode', value)}
/>
<CheckboxInput
label={t('reader.settings.label.skip_dup_chapters')}
checked={settings.shouldSkipDupChapters}
onChange={(_, checked) => updateSetting('shouldSkipDupChapters', checked)}
/>
{isOffsetDoubleSpreadPagesEditable(settings.readingMode.value) && (
<CheckboxInput
label={t('reader.settings.label.offset_double_spread')}
checked={settings.shouldOffsetDoubleSpreads.value}
onChange={(_, checked) => updateSetting('shouldOffsetDoubleSpreads', checked)}
/>
)}
</Stack>
);
};

View File

@@ -0,0 +1,44 @@
/*
* Copyright (C) Contributors to the Suwayomi project
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
import { useTranslation } from 'react-i18next';
import { IReaderSettings, ReaderExitMode } from '@/modules/reader/types/Reader.types.ts';
import { ValueToDisplayData } from '@/modules/core/Core.types.ts';
import { ButtonSelectInput } from '@/modules/core/components/inputs/ButtonSelectInput.tsx';
const VALUE_TO_DISPLAY_DATA: ValueToDisplayData<ReaderExitMode> = {
[ReaderExitMode.PREVIOUS]: {
title: 'global.label.previous',
icon: null,
},
[ReaderExitMode.MANGA]: {
title: 'manga.title_one',
icon: null,
},
};
const READER_EXIT_MODE_VALUES = Object.values(ReaderExitMode).filter((value) => typeof value === 'number');
export const ReaderSettingExitMode = ({
exitMode,
setExitMode,
}: Pick<IReaderSettings, 'exitMode'> & {
setExitMode: (mode: ReaderExitMode) => void;
}) => {
const { t } = useTranslation();
return (
<ButtonSelectInput
label={t('reader.settings.exit_mode')}
value={exitMode}
values={READER_EXIT_MODE_VALUES}
setValue={setExitMode}
valueToDisplayData={VALUE_TO_DISPLAY_DATA}
/>
);
};

View File

@@ -0,0 +1,115 @@
/*
* Copyright (C) Contributors to the Suwayomi project
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
import Stack from '@mui/material/Stack';
import { ReaderSettingBrightness } from '@/modules/reader/components/settings/filters/ReaderSettingBrightness.tsx';
import { ReaderSettingContrast } from '@/modules/reader/components/settings/filters/ReaderSettingContrast.tsx';
import { ReaderSettingSaturate } from '@/modules/reader/components/settings/filters/ReaderSettingSaturate.tsx';
import { ReaderSettingHue } from '@/modules/reader/components/settings/filters/ReaderSettingHue.tsx';
import { ReaderSettingRGBA } from '@/modules/reader/components/settings/filters/ReaderSettingRGBA.tsx';
import { ReaderSettingSepia } from '@/modules/reader/components/settings/filters/ReaderSettingSepia.tsx';
import { ReaderSettingGrayscale } from '@/modules/reader/components/settings/filters/ReaderSettingGrayscale.tsx';
import { ReaderSettingInvert } from '@/modules/reader/components/settings/filters/ReaderSettingInvert.tsx';
import { ReaderSettingsTypeProps } from '@/modules/reader/types/Reader.types.ts';
export const ReaderFilterSettings = ({ settings: { customFilter }, updateSetting }: ReaderSettingsTypeProps) => (
<Stack sx={{ gap: 2 }}>
<ReaderSettingBrightness
brightness={customFilter.brightness}
updateSetting={(key, value, commit) =>
updateSetting(
'customFilter',
{
...customFilter,
[key]: value,
},
commit,
)
}
/>
<ReaderSettingContrast
contrast={customFilter.contrast}
updateSetting={(key, value, commit) =>
updateSetting(
'customFilter',
{
...customFilter,
[key]: value,
},
commit,
)
}
/>
<ReaderSettingSaturate
saturate={customFilter.saturate}
updateSetting={(key, value, commit) =>
updateSetting(
'customFilter',
{
...customFilter,
[key]: value,
},
commit,
)
}
/>
<ReaderSettingHue
hue={customFilter.hue}
updateSetting={(key, value, commit) =>
updateSetting(
'customFilter',
{
...customFilter,
[key]: value,
},
commit,
)
}
/>
<ReaderSettingRGBA
rgba={customFilter.rgba}
updateSetting={(key, value, commit) =>
updateSetting(
'customFilter',
{
...customFilter,
[key]: value,
},
commit,
)
}
/>
<ReaderSettingSepia
sepia={customFilter.sepia}
updateSetting={(value) =>
updateSetting('customFilter', {
...customFilter,
sepia: value,
})
}
/>
<ReaderSettingGrayscale
grayscale={customFilter.grayscale}
updateSetting={(value) =>
updateSetting('customFilter', {
...customFilter,
grayscale: value,
})
}
/>
<ReaderSettingInvert
invert={customFilter.invert}
updateSetting={(value) =>
updateSetting('customFilter', {
...customFilter,
invert: value,
})
}
/>
</Stack>
);

View File

@@ -0,0 +1,59 @@
/*
* Copyright (C) Contributors to the Suwayomi project
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
import Stack from '@mui/material/Stack';
import { useTranslation } from 'react-i18next';
import { IReaderSettings } from '@/modules/reader/types/Reader.types.ts';
import { CheckboxInput } from '@/modules/core/components/inputs/CheckboxInput.tsx';
import { SliderInput } from '@/modules/core/components/inputs/SliderInput.tsx';
import { DEFAULT_READER_SETTINGS } from '@/modules/reader/constants/ReaderSettings.constants.tsx';
export const ReaderSettingBrightness = ({
brightness,
updateSetting,
}: Pick<IReaderSettings['customFilter'], 'brightness'> & {
updateSetting: <Filter extends keyof IReaderSettings['customFilter']>(
filter: Filter,
value: IReaderSettings['customFilter'][Filter],
commit: boolean,
) => void;
}) => {
const { t } = useTranslation();
return (
<Stack>
<CheckboxInput
label={t('reader.settings.custom_filter.brightness.title')}
checked={brightness.enabled}
onChange={(_, checked) => updateSetting('brightness', { ...brightness, enabled: checked }, true)}
/>
{brightness.enabled && (
<SliderInput
label={t('reader.settings.custom_filter.brightness.value')}
value={brightness.value}
slotProps={{
slider: {
defaultValue: DEFAULT_READER_SETTINGS.customFilter.brightness.value,
value: brightness.value,
step: 1,
min: 5,
max: 200,
onChange: (_, value) => {
updateSetting('brightness', { ...brightness, value: value as number }, false);
},
onChangeCommitted: (_, value) => {
updateSetting('brightness', { ...brightness, value: value as number }, true);
},
},
}}
/>
)}
</Stack>
);
};

View File

@@ -0,0 +1,58 @@
/*
* Copyright (C) Contributors to the Suwayomi project
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
import Stack from '@mui/material/Stack';
import { useTranslation } from 'react-i18next';
import { IReaderSettings } from '@/modules/reader/types/Reader.types.ts';
import { CheckboxInput } from '@/modules/core/components/inputs/CheckboxInput.tsx';
import { SliderInput } from '@/modules/core/components/inputs/SliderInput.tsx';
import { DEFAULT_READER_SETTINGS } from '@/modules/reader/constants/ReaderSettings.constants.tsx';
export const ReaderSettingContrast = ({
contrast,
updateSetting,
}: Pick<IReaderSettings['customFilter'], 'contrast'> & {
updateSetting: <Filter extends keyof IReaderSettings['customFilter']>(
filter: Filter,
value: IReaderSettings['customFilter'][Filter],
commit: boolean,
) => void;
}) => {
const { t } = useTranslation();
return (
<Stack>
<CheckboxInput
label={t('reader.settings.custom_filter.contrast.title')}
checked={contrast.enabled}
onChange={(_, checked) => updateSetting('contrast', { ...contrast, enabled: checked }, true)}
/>
{contrast.enabled && (
<SliderInput
label={t('reader.settings.custom_filter.contrast.value')}
value={contrast.value}
slotProps={{
slider: {
defaultValue: DEFAULT_READER_SETTINGS.customFilter.contrast.value,
value: contrast.value,
step: 1,
min: 5,
max: 200,
onChange: (_, newValue) => {
updateSetting('contrast', { ...contrast, value: newValue as number }, false);
},
onChangeCommitted: (_, newValue) => {
updateSetting('contrast', { ...contrast, value: newValue as number }, true);
},
},
}}
/>
)}
</Stack>
);
};

View File

@@ -0,0 +1,31 @@
/*
* Copyright (C) Contributors to the Suwayomi project
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
import Stack from '@mui/material/Stack';
import { useTranslation } from 'react-i18next';
import { IReaderSettings } from '@/modules/reader/types/Reader.types.ts';
import { CheckboxInput } from '@/modules/core/components/inputs/CheckboxInput.tsx';
export const ReaderSettingGrayscale = ({
grayscale,
updateSetting,
}: Pick<IReaderSettings['customFilter'], 'grayscale'> & {
updateSetting: (grayscale: boolean) => void;
}) => {
const { t } = useTranslation();
return (
<Stack>
<CheckboxInput
label={t('reader.settings.custom_filter.grayscale')}
checked={grayscale}
onChange={(_, checked) => updateSetting(checked)}
/>
</Stack>
);
};

View File

@@ -0,0 +1,58 @@
/*
* Copyright (C) Contributors to the Suwayomi project
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
import Stack from '@mui/material/Stack';
import { useTranslation } from 'react-i18next';
import { IReaderSettings } from '@/modules/reader/types/Reader.types.ts';
import { CheckboxInput } from '@/modules/core/components/inputs/CheckboxInput.tsx';
import { SliderInput } from '@/modules/core/components/inputs/SliderInput.tsx';
import { DEFAULT_READER_SETTINGS } from '@/modules/reader/constants/ReaderSettings.constants.tsx';
export const ReaderSettingHue = ({
hue,
updateSetting,
}: Pick<IReaderSettings['customFilter'], 'hue'> & {
updateSetting: <Filter extends keyof IReaderSettings['customFilter']>(
filter: Filter,
value: IReaderSettings['customFilter'][Filter],
commit: boolean,
) => void;
}) => {
const { t } = useTranslation();
return (
<Stack>
<CheckboxInput
label={t('reader.settings.custom_filter.hue.title')}
checked={hue.enabled}
onChange={(_, checked) => updateSetting('hue', { ...hue, enabled: checked }, true)}
/>
{hue.enabled && (
<SliderInput
label={t('reader.settings.custom_filter.hue.value')}
value={hue.value}
slotProps={{
slider: {
defaultValue: DEFAULT_READER_SETTINGS.customFilter.hue.value,
value: hue.value,
step: 1,
min: 0,
max: 200,
onChange: (_, newValue) => {
updateSetting('hue', { ...hue, value: newValue as number }, false);
},
onChangeCommitted: (_, newValue) => {
updateSetting('hue', { ...hue, value: newValue as number }, true);
},
},
}}
/>
)}
</Stack>
);
};

View File

@@ -0,0 +1,31 @@
/*
* Copyright (C) Contributors to the Suwayomi project
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
import Stack from '@mui/material/Stack';
import { useTranslation } from 'react-i18next';
import { IReaderSettings } from '@/modules/reader/types/Reader.types.ts';
import { CheckboxInput } from '@/modules/core/components/inputs/CheckboxInput.tsx';
export const ReaderSettingInvert = ({
invert,
updateSetting,
}: Pick<IReaderSettings['customFilter'], 'invert'> & {
updateSetting: (invert: boolean) => void;
}) => {
const { t } = useTranslation();
return (
<Stack>
<CheckboxInput
label={t('reader.settings.custom_filter.invert')}
checked={invert}
onChange={(_, checked) => updateSetting(checked)}
/>
</Stack>
);
};

View File

@@ -0,0 +1,89 @@
/*
* Copyright (C) Contributors to the Suwayomi project
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
import Stack from '@mui/material/Stack';
import { useTranslation } from 'react-i18next';
import { IReaderSettings } from '@/modules/reader/types/Reader.types.ts';
import { CheckboxInput } from '@/modules/core/components/inputs/CheckboxInput.tsx';
import { TranslationKey } from '@/Base.types.ts';
import { SliderInput } from '@/modules/core/components/inputs/SliderInput.tsx';
import { DEFAULT_READER_SETTINGS } from '@/modules/reader/constants/ReaderSettings.constants.tsx';
type RGBAType = keyof IReaderSettings['customFilter']['rgba']['value'];
const RGBA_TYPE_TO_TRANSLATION_KEY: Record<RGBAType, TranslationKey> = {
red: 'reader.settings.custom_filter.rgba.red',
green: 'reader.settings.custom_filter.rgba.green',
blue: 'reader.settings.custom_filter.rgba.blue',
alpha: 'reader.settings.custom_filter.rgba.alpha',
};
const RGBA_TYPE_TO_MAX_VALUE: Record<RGBAType, number> = {
red: 255,
green: 255,
blue: 255,
alpha: 100,
};
export const ReaderSettingRGBA = ({
rgba,
updateSetting,
}: Pick<IReaderSettings['customFilter'], 'rgba'> & {
updateSetting: <Filter extends keyof IReaderSettings['customFilter']>(
filter: Filter,
value: IReaderSettings['customFilter'][Filter],
commit: boolean,
) => void;
}) => {
const { t } = useTranslation();
return (
<Stack>
<CheckboxInput
label={t('reader.settings.custom_filter.rgba.title')}
checked={rgba.enabled}
onChange={(_, checked) => updateSetting('rgba', { ...rgba, enabled: checked }, true)}
/>
{rgba.enabled && (
<Stack sx={{ gap: 1 }}>
{Object.entries(rgba.value).map(([key, value]) => (
<SliderInput
key={key}
label={t(RGBA_TYPE_TO_TRANSLATION_KEY[key as RGBAType])}
value={value}
slotProps={{
slider: {
value,
defaultValue: DEFAULT_READER_SETTINGS.customFilter.rgba.value[key as RGBAType],
step: 1,
min: 0,
max: RGBA_TYPE_TO_MAX_VALUE[key as RGBAType],
onChange: (_, newValue) => {
updateSetting(
'rgba',
{ ...rgba, value: { ...rgba.value, [key]: newValue } },
false,
);
},
onChangeCommitted: (_, newValue) => {
updateSetting(
'rgba',
{ ...rgba, value: { ...rgba.value, [key]: newValue } },
true,
);
},
},
}}
/>
))}
</Stack>
)}
</Stack>
);
};

View File

@@ -0,0 +1,58 @@
/*
* Copyright (C) Contributors to the Suwayomi project
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
import Stack from '@mui/material/Stack';
import { useTranslation } from 'react-i18next';
import { IReaderSettings } from '@/modules/reader/types/Reader.types.ts';
import { CheckboxInput } from '@/modules/core/components/inputs/CheckboxInput.tsx';
import { SliderInput } from '@/modules/core/components/inputs/SliderInput.tsx';
import { DEFAULT_READER_SETTINGS } from '@/modules/reader/constants/ReaderSettings.constants.tsx';
export const ReaderSettingSaturate = ({
saturate,
updateSetting,
}: Pick<IReaderSettings['customFilter'], 'saturate'> & {
updateSetting: <Filter extends keyof IReaderSettings['customFilter']>(
filter: Filter,
value: IReaderSettings['customFilter'][Filter],
commit: boolean,
) => void;
}) => {
const { t } = useTranslation();
return (
<Stack>
<CheckboxInput
label={t('reader.settings.custom_filter.saturate.title')}
checked={saturate.enabled}
onChange={(_, checked) => updateSetting('saturate', { ...saturate, enabled: checked }, true)}
/>
{saturate.enabled && (
<SliderInput
label={t('reader.settings.custom_filter.saturate.value')}
value={saturate.value}
slotProps={{
slider: {
defaultValue: DEFAULT_READER_SETTINGS.customFilter.saturate.value,
value: saturate.value,
step: 1,
min: 0,
max: 200,
onChange: (_, value) => {
updateSetting('saturate', { ...saturate, value: value as number }, false);
},
onChangeCommitted: (_, value) => {
updateSetting('saturate', { ...saturate, value: value as number }, true);
},
},
}}
/>
)}
</Stack>
);
};

View File

@@ -0,0 +1,31 @@
/*
* Copyright (C) Contributors to the Suwayomi project
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
import Stack from '@mui/material/Stack';
import { useTranslation } from 'react-i18next';
import { IReaderSettings } from '@/modules/reader/types/Reader.types.ts';
import { CheckboxInput } from '@/modules/core/components/inputs/CheckboxInput.tsx';
export const ReaderSettingSepia = ({
sepia,
updateSetting,
}: Pick<IReaderSettings['customFilter'], 'sepia'> & {
updateSetting: (sepia: boolean) => void;
}) => {
const { t } = useTranslation();
return (
<Stack>
<CheckboxInput
label={t('reader.settings.custom_filter.sepia')}
checked={sepia}
onChange={(_, checked) => updateSetting(checked)}
/>
</Stack>
);
};

View File

@@ -0,0 +1,66 @@
/*
* Copyright (C) Contributors to the Suwayomi project
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
import Stack from '@mui/material/Stack';
import { useTranslation } from 'react-i18next';
import { ReaderSettingProgressBarType } from '@/modules/reader/components/settings/general/ReaderSettingProgressBarType.tsx';
import { ReaderSettingProgressBarSize } from '@/modules/reader/components/settings/general/ReaderSettingProgressBarSize.tsx';
import { ReaderSettingProgressBarPosition } from '@/modules/reader/components/settings/general/ReaderSettingProgressBarPosition.tsx';
import {
IReaderSettings,
ProgressBarType,
ReaderOverlayMode,
ReaderSettingsTypeProps,
} from '@/modules/reader/types/Reader.types.ts';
import { ReaderSettingOverlayMode } from '@/modules/reader/components/settings/general/ReaderSettingOverlayMode.tsx';
import { CheckboxInput } from '@/modules/core/components/inputs/CheckboxInput.tsx';
import { ReaderSettingBackgroundColor } from '@/modules/reader/components/settings/general/ReaderSettingBackgroundColor.tsx';
export const ReaderGeneralSettings = ({
overlayMode,
settings,
updateSetting,
}: Pick<IReaderSettings, 'overlayMode'> & ReaderSettingsTypeProps) => {
const { t } = useTranslation();
return (
<Stack sx={{ gap: 2 }}>
<ReaderSettingOverlayMode
overlayMode={settings.overlayMode}
setOverlayMode={(value) => updateSetting('overlayMode', value)}
/>
<ReaderSettingBackgroundColor
backgroundColor={settings.backgroundColor}
updateSetting={(value) => updateSetting('backgroundColor', value)}
/>
<ReaderSettingProgressBarType
overlayMode={overlayMode}
progressBarType={settings.progressBarType}
setProgressBarType={(value) => updateSetting('progressBarType', value)}
/>
<ReaderSettingProgressBarSize
overlayMode={overlayMode}
progressBarType={settings.progressBarType}
progressBarSize={settings.progressBarSize}
setProgressBarSize={(...args) => updateSetting('progressBarSize', ...args)}
/>
<ReaderSettingProgressBarPosition
overlayMode={overlayMode}
progressBarPosition={settings.progressBarPosition}
setProgressBarPosition={(value) => updateSetting('progressBarPosition', value)}
/>
{(settings.progressBarType === ProgressBarType.HIDDEN || overlayMode === ReaderOverlayMode.MOBILE) && (
<CheckboxInput
label={t('reader.settings.label.show_page_number')}
checked={settings.shouldShowPageNumber}
onChange={(_, checked) => updateSetting('shouldShowPageNumber', checked)}
/>
)}
</Stack>
);
};

View File

@@ -0,0 +1,54 @@
/*
* Copyright (C) Contributors to the Suwayomi project
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
import { useTranslation } from 'react-i18next';
import { ValueToDisplayData } from '@/modules/core/Core.types.ts';
import { ButtonSelectInput } from '@/modules/core/components/inputs/ButtonSelectInput.tsx';
import { IReaderSettingsWithDefaultFlag, ReaderBackgroundColor } from '@/modules/reader/types/Reader.types.ts';
const VALUE_TO_DISPLAY_DATA: ValueToDisplayData<ReaderBackgroundColor> = {
[ReaderBackgroundColor.THEME]: {
title: 'settings.appearance.theme.title',
icon: null,
},
[ReaderBackgroundColor.BLACK]: {
title: 'global.colors.black',
icon: null,
},
[ReaderBackgroundColor.GRAY]: {
title: 'global.colors.gray',
icon: null,
},
[ReaderBackgroundColor.WHITE]: {
title: 'global.colors.white',
icon: null,
},
};
const READER_BACKGROUND_COLOR_VALUES = Object.values(ReaderBackgroundColor).filter(
(value) => typeof value === 'number',
);
export const ReaderSettingBackgroundColor = ({
backgroundColor,
updateSetting,
}: Pick<IReaderSettingsWithDefaultFlag, 'backgroundColor'> & {
updateSetting: (color: ReaderBackgroundColor) => void;
}) => {
const { t } = useTranslation();
return (
<ButtonSelectInput
label={t('reader.settings.background_color')}
value={backgroundColor}
values={READER_BACKGROUND_COLOR_VALUES}
setValue={updateSetting}
valueToDisplayData={VALUE_TO_DISPLAY_DATA}
/>
);
};

View File

@@ -0,0 +1,51 @@
/*
* Copyright (C) Contributors to the Suwayomi project
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
import { useTranslation } from 'react-i18next';
import PhoneIphoneIcon from '@mui/icons-material/PhoneIphone';
import ComputerIcon from '@mui/icons-material/Computer';
import AutoModeIcon from '@mui/icons-material/AutoMode';
import { IReaderSettings, ReaderOverlayMode } from '@/modules/reader/types/Reader.types.ts';
import { ValueToDisplayData } from '@/modules/core/Core.types.ts';
import { ButtonSelectInput } from '@/modules/core/components/inputs/ButtonSelectInput.tsx';
const VALUE_TO_DISPLAY_DATA: ValueToDisplayData<ReaderOverlayMode> = {
[ReaderOverlayMode.AUTO]: {
title: 'global.label.auto',
icon: <AutoModeIcon />,
},
[ReaderOverlayMode.DESKTOP]: {
title: 'global.label.desktop',
icon: <ComputerIcon />,
},
[ReaderOverlayMode.MOBILE]: {
title: 'global.label.mobile',
icon: <PhoneIphoneIcon />,
},
};
const READING_MODE_VALUES = Object.values(ReaderOverlayMode).filter((value) => typeof value === 'number');
export const ReaderSettingOverlayMode = ({
overlayMode,
setOverlayMode,
}: Pick<IReaderSettings, 'overlayMode'> & {
setOverlayMode: (mode: ReaderOverlayMode) => void;
}) => {
const { t } = useTranslation();
return (
<ButtonSelectInput
label={t('reader.settings.overlay_mode')}
value={overlayMode}
values={READING_MODE_VALUES}
setValue={setOverlayMode}
valueToDisplayData={VALUE_TO_DISPLAY_DATA}
/>
);
};

View File

@@ -0,0 +1,55 @@
/*
* Copyright (C) Contributors to the Suwayomi project
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
import { useTranslation } from 'react-i18next';
import ArrowForwardIosIcon from '@mui/icons-material/ArrowForwardIos';
import ArrowBackIosNewIcon from '@mui/icons-material/ArrowBackIosNew';
import { IReaderSettings, ProgressBarPosition, ReaderOverlayMode } from '@/modules/reader/types/Reader.types.ts';
import { ValueToDisplayData } from '@/modules/core/Core.types.ts';
import { ButtonSelectInput } from '@/modules/core/components/inputs/ButtonSelectInput.tsx';
const VALUE_TO_DISPLAY_DATA: ValueToDisplayData<ProgressBarPosition> = {
[ProgressBarPosition.BOTTOM]: {
title: 'global.label.bottom',
icon: <ArrowBackIosNewIcon sx={{ transform: 'rotate(90deg)' }} />,
},
[ProgressBarPosition.LEFT]: {
title: 'global.label.left',
icon: <ArrowForwardIosIcon />,
},
[ProgressBarPosition.RIGHT]: {
title: 'global.label.right',
icon: <ArrowBackIosNewIcon />,
},
};
const PROGRESS_BAR_POSITION_VALUES = Object.values(ProgressBarPosition).filter((value) => typeof value === 'number');
export const ReaderSettingProgressBarPosition = ({
overlayMode,
progressBarPosition,
setProgressBarPosition,
}: Pick<IReaderSettings, 'progressBarPosition' | 'overlayMode'> & {
setProgressBarPosition: (position: ProgressBarPosition) => void;
}) => {
const { t } = useTranslation();
if (overlayMode !== ReaderOverlayMode.DESKTOP) {
return null;
}
return (
<ButtonSelectInput
label={t('reader.settings.progress_bar.position')}
value={progressBarPosition}
values={PROGRESS_BAR_POSITION_VALUES}
setValue={setProgressBarPosition}
valueToDisplayData={VALUE_TO_DISPLAY_DATA}
/>
);
};

View File

@@ -0,0 +1,50 @@
/*
* Copyright (C) Contributors to the Suwayomi project
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
import { useTranslation } from 'react-i18next';
import { IReaderSettings, ProgressBarType, ReaderOverlayMode } from '@/modules/reader/types/Reader.types.ts';
import { SliderInput } from '@/modules/core/components/inputs/SliderInput.tsx';
import { DEFAULT_READER_SETTINGS } from '@/modules/reader/constants/ReaderSettings.constants.tsx';
export const ReaderSettingProgressBarSize = ({
overlayMode,
progressBarType,
progressBarSize,
setProgressBarSize,
}: Pick<IReaderSettings, 'progressBarType' | 'progressBarSize' | 'overlayMode'> & {
setProgressBarSize: (size: number, commit: boolean) => void;
}) => {
const { t } = useTranslation();
const isChangeable = overlayMode === ReaderOverlayMode.DESKTOP && progressBarType === ProgressBarType.STANDARD;
if (!isChangeable) {
return null;
}
return (
<SliderInput
label={t('reader.settings.progress_bar.size')}
value={t('global.value', { value: progressBarSize, unit: t('global.unit.px') })}
slotProps={{
slider: {
defaultValue: DEFAULT_READER_SETTINGS.progressBarSize,
value: progressBarSize,
step: 1,
min: 2,
max: 20,
onChange: (_, value) => {
setProgressBarSize(value as number, false);
},
onChangeCommitted: (_, value) => {
setProgressBarSize(value as number, true);
},
},
}}
/>
);
};

View File

@@ -0,0 +1,51 @@
/*
* Copyright (C) Contributors to the Suwayomi project
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
import { useTranslation } from 'react-i18next';
import { IReaderSettings, ProgressBarType, ReaderOverlayMode } from '@/modules/reader/types/Reader.types.ts';
import { ValueToDisplayData } from '@/modules/core/Core.types.ts';
import { HiddenProgressBarIcon } from '@/assets/icons/svg/HiddenProgressBarIcon.tsx';
import { StandardProgressBarIcon } from '@/assets/icons/svg/StandardProgressBarIcon.tsx';
import { ButtonSelectInput } from '@/modules/core/components/inputs/ButtonSelectInput.tsx';
const VALUE_TO_DISPLAY_DATA: ValueToDisplayData<ProgressBarType> = {
[ProgressBarType.HIDDEN]: {
title: 'global.label.hidden',
icon: <HiddenProgressBarIcon />,
},
[ProgressBarType.STANDARD]: {
title: 'global.label.standard',
icon: <StandardProgressBarIcon />,
},
};
const PROGRESS_BAR_TYPE_VALUES = Object.values(ProgressBarType).filter((value) => typeof value === 'number');
export const ReaderSettingProgressBarType = ({
overlayMode,
progressBarType,
setProgressBarType,
}: Pick<IReaderSettings, 'progressBarType' | 'overlayMode'> & {
setProgressBarType: (progressBarType: ProgressBarType) => void;
}) => {
const { t } = useTranslation();
if (overlayMode !== ReaderOverlayMode.DESKTOP) {
return null;
}
return (
<ButtonSelectInput
label={t('reader.settings.progress_bar.style')}
value={progressBarType}
values={PROGRESS_BAR_TYPE_VALUES}
setValue={setProgressBarType}
valueToDisplayData={VALUE_TO_DISPLAY_DATA}
/>
);
};

View File

@@ -0,0 +1,90 @@
/*
* Copyright (C) Contributors to the Suwayomi project
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
import Stack from '@mui/material/Stack';
import { ContextType } from 'react';
import { ReaderSettingReadingMode } from '@/modules/reader/components/settings/layout/ReaderSettingReadingMode.tsx';
import { ReaderSettingReadingDirection } from '@/modules/reader/components/settings/layout/ReaderSettingReadingDirection.tsx';
import { ReaderSettingTapZoneLayout } from '@/modules/reader/components/settings/layout/ReaderSettingTapZoneLayout.tsx';
import { ReaderTapZoneContext } from '@/modules/reader/contexts/ReaderTapZoneContext.tsx';
import { ReaderSettingTapZoneInvertMode } from '@/modules/reader/components/settings/layout/ReaderSettingTapZoneInvertMode.tsx';
import { ReaderSettingPageScaleMode } from '@/modules/reader/components/settings/layout/ReaderSettingPageScaleMode.tsx';
import { ReaderSettingStretchPage } from '@/modules/reader/components/settings/layout/ReaderSettingStretchPage.tsx';
import { ReaderSettingsTypeProps } from '@/modules/reader/types/Reader.types.ts';
import { ReaderSettingPageGap } from '@/modules/reader/components/settings/layout/ReaderSettingPageGap.tsx';
import { ReaderSettingWidth } from '@/modules/reader/components/settings/layout/ReaderSettingWidth.tsx';
export const ReaderLayoutSettings = ({
setShowPreview,
settings,
updateSetting,
isDefaultable,
onDefault,
}: ReaderSettingsTypeProps & {
setShowPreview: ContextType<typeof ReaderTapZoneContext>['setShowPreview'];
}) => (
<Stack sx={{ gap: 2 }}>
<ReaderSettingReadingMode
readingMode={settings.readingMode}
setReadingMode={(value) => updateSetting('readingMode', value)}
isDefaultable={isDefaultable}
onDefault={() => onDefault?.('readingMode')}
/>
<ReaderSettingPageGap
pageGap={settings.pageGap}
readingMode={settings.readingMode}
updateSetting={(...args) => updateSetting('pageGap', ...args)}
/>
<ReaderSettingReadingDirection
readingDirection={settings.readingDirection}
setReadingDirection={(value) => updateSetting('readingDirection', value)}
isDefaultable={isDefaultable}
onDefault={() => onDefault?.('readingDirection')}
/>
<ReaderSettingTapZoneLayout
tapZoneLayout={settings.tapZoneLayout}
setTapZoneLayout={(value) => {
setShowPreview(true);
updateSetting('tapZoneLayout', value);
}}
isDefaultable={isDefaultable}
onDefault={() => {
setShowPreview(true);
onDefault?.('tapZoneLayout');
}}
/>
<ReaderSettingTapZoneInvertMode
tapZoneInvertMode={settings.tapZoneInvertMode}
setTapZoneInvertMode={(value) => {
setShowPreview(true);
updateSetting('tapZoneInvertMode', value);
}}
isDefaultable={isDefaultable}
onDefault={() => {
setShowPreview(true);
onDefault?.('tapZoneInvertMode');
}}
/>
<ReaderSettingPageScaleMode
pageScaleMode={settings.pageScaleMode}
setPageScaleMode={(value) => updateSetting('pageScaleMode', value)}
isDefaultable={isDefaultable}
onDefault={() => onDefault?.('pageScaleMode')}
/>
<ReaderSettingStretchPage
pageScaleMode={settings.pageScaleMode.value}
shouldStretchPage={settings.shouldStretchPage.value}
setShouldStretchPage={(value) => updateSetting('shouldStretchPage', value)}
/>
<ReaderSettingWidth
readerWidth={settings.readerWidth.value}
pageScaleMode={settings.pageScaleMode.value}
updateSetting={(...args) => updateSetting(...args)}
/>
</Stack>
);

View File

@@ -0,0 +1,49 @@
/*
* Copyright (C) Contributors to the Suwayomi project
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
import Stack from '@mui/material/Stack';
import { useTranslation } from 'react-i18next';
import { IReaderSettingsWithDefaultFlag, ReadingMode } from '@/modules/reader/types/Reader.types.ts';
import { SliderInput } from '@/modules/core/components/inputs/SliderInput.tsx';
import { DEFAULT_READER_SETTINGS } from '@/modules/reader/constants/ReaderSettings.constants.tsx';
export const ReaderSettingPageGap = ({
pageGap,
readingMode,
updateSetting,
}: Pick<IReaderSettingsWithDefaultFlag, 'pageGap' | 'readingMode'> & {
updateSetting: (gap: number, commit: boolean) => void;
}) => {
const { t } = useTranslation();
return (
<Stack>
{[ReadingMode.CONTINUOUS_HORIZONTAL, ReadingMode.CONTINUOUS_VERTICAL].includes(readingMode.value) && (
<SliderInput
label={t('reader.settings.label.page_gap')}
value={t('global.value', { value: pageGap.value, unit: t('global.unit.px') })}
slotProps={{
slider: {
defaultValue: DEFAULT_READER_SETTINGS.readerWidth.value,
value: pageGap.value,
step: 1,
min: 0,
max: 20,
onChange: (_, value) => {
updateSetting(value as number, false);
},
onChangeCommitted: (_, value) => {
updateSetting(value as number, true);
},
},
}}
/>
)}
</Stack>
);
};

View File

@@ -0,0 +1,42 @@
/*
* Copyright (C) Contributors to the Suwayomi project
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
import { useTranslation } from 'react-i18next';
import {
IReaderSettings,
IReaderSettingsWithDefaultFlag,
ReadingDirection,
} from '@/modules/reader/types/Reader.types.ts';
import {
PAGE_SCALE_VALUE_TO_DISPLAY_DATA,
READER_PAGE_SCALE_MODE_VALUES,
} from '@/modules/reader/constants/ReaderSettings.constants.tsx';
import { ButtonSelectInput } from '@/modules/core/components/inputs/ButtonSelectInput.tsx';
import { MultiValueButtonDefaultableProps } from '@/modules/core/Core.types.ts';
export const ReaderSettingPageScaleMode = ({
pageScaleMode,
setPageScaleMode,
...buttonSelectInputProps
}: Pick<IReaderSettingsWithDefaultFlag, 'pageScaleMode'> &
Pick<MultiValueButtonDefaultableProps<ReadingDirection>, 'isDefaultable' | 'onDefault'> & {
setPageScaleMode: (mode: IReaderSettings['pageScaleMode']) => void;
}) => {
const { t } = useTranslation();
return (
<ButtonSelectInput
{...buttonSelectInputProps}
label={t('reader.settings.page_scale.title')}
value={pageScaleMode.isDefault ? undefined : pageScaleMode.value}
values={READER_PAGE_SCALE_MODE_VALUES}
setValue={setPageScaleMode}
valueToDisplayData={PAGE_SCALE_VALUE_TO_DISPLAY_DATA}
/>
);
};

View File

@@ -0,0 +1,49 @@
/*
* Copyright (C) Contributors to the Suwayomi project
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
import ArrowCircleLeftIcon from '@mui/icons-material/ArrowCircleLeft';
import ArrowCircleRightIcon from '@mui/icons-material/ArrowCircleRight';
import { useTranslation } from 'react-i18next';
import { IReaderSettingsWithDefaultFlag, ReadingDirection } from '@/modules/reader/types/Reader.types.ts';
import { MultiValueButtonDefaultableProps, ValueToDisplayData } from '@/modules/core/Core.types.ts';
import { ButtonSelectInput } from '@/modules/core/components/inputs/ButtonSelectInput.tsx';
const VALUE_TO_DISPLAY_DATA: ValueToDisplayData<ReadingDirection> = {
[ReadingDirection.LTR]: {
title: 'reader.settings.reading_direction.ltr',
icon: <ArrowCircleRightIcon />,
},
[ReadingDirection.RTL]: {
title: 'reader.settings.reading_direction.rtl',
icon: <ArrowCircleLeftIcon />,
},
};
const READING_DIRECTION_VALUES = Object.values(ReadingDirection).filter((value) => typeof value === 'number');
export const ReaderSettingReadingDirection = ({
readingDirection,
setReadingDirection,
...buttonSelectInputProps
}: Pick<IReaderSettingsWithDefaultFlag, 'readingDirection'> &
Pick<MultiValueButtonDefaultableProps<ReadingDirection>, 'isDefaultable' | 'onDefault'> & {
setReadingDirection: (readingDirection: ReadingDirection) => void;
}) => {
const { t } = useTranslation();
return (
<ButtonSelectInput
{...buttonSelectInputProps}
label={t('reader.settings.label.reading_direction')}
value={readingDirection.isDefault ? undefined : readingDirection.value}
values={READING_DIRECTION_VALUES}
setValue={setReadingDirection}
valueToDisplayData={VALUE_TO_DISPLAY_DATA}
/>
);
};

View File

@@ -0,0 +1,38 @@
/*
* Copyright (C) Contributors to the Suwayomi project
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
import { useTranslation } from 'react-i18next';
import { IReaderSettingsWithDefaultFlag, ReadingMode } from '@/modules/reader/types/Reader.types.ts';
import {
READING_MODE_VALUE_TO_DISPLAY_DATA,
READING_MODE_VALUES,
} from '@/modules/reader/constants/ReaderSettings.constants.tsx';
import { ButtonSelectInput } from '@/modules/core/components/inputs/ButtonSelectInput.tsx';
import { MultiValueButtonDefaultableProps } from '@/modules/core/Core.types.ts';
export const ReaderSettingReadingMode = ({
readingMode,
setReadingMode,
...buttonSelectInputProps
}: Pick<IReaderSettingsWithDefaultFlag, 'readingMode'> &
Pick<MultiValueButtonDefaultableProps<ReadingMode>, 'isDefaultable' | 'onDefault'> & {
setReadingMode: (mode: ReadingMode) => void;
}) => {
const { t } = useTranslation();
return (
<ButtonSelectInput
{...buttonSelectInputProps}
label={t('reader.settings.label.reading_mode')}
value={readingMode.isDefault ? undefined : readingMode.value}
values={READING_MODE_VALUES}
setValue={setReadingMode}
valueToDisplayData={READING_MODE_VALUE_TO_DISPLAY_DATA}
/>
);
};

View File

@@ -0,0 +1,38 @@
/*
* Copyright (C) Contributors to the Suwayomi project
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
import { useTranslation } from 'react-i18next';
import Button from '@mui/material/Button';
import Box from '@mui/material/Box';
import { IReaderSettings } from '@/modules/reader/types/Reader.types.ts';
import { READER_PAGE_SCALE_MODE_TO_SCALING_ALLOWED } from '@/modules/reader/constants/ReaderSettings.constants.tsx';
export const ReaderSettingStretchPage = ({
pageScaleMode,
shouldStretchPage,
setShouldStretchPage,
}: Pick<IReaderSettings, 'pageScaleMode' | 'shouldStretchPage'> & {
setShouldStretchPage: (mode: IReaderSettings['shouldStretchPage']) => void;
}) => {
const { t } = useTranslation();
if (!READER_PAGE_SCALE_MODE_TO_SCALING_ALLOWED[pageScaleMode]) {
return null;
}
return (
<Box>
<Button
onClick={() => setShouldStretchPage(!shouldStretchPage)}
variant={shouldStretchPage ? 'contained' : 'outlined'}
>
{t('reader.settings.page_scale.stretch')}
</Button>
</Box>
);
};

View File

@@ -0,0 +1,102 @@
/*
* Copyright (C) Contributors to the Suwayomi project
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
import { useTranslation } from 'react-i18next';
import { MultiValueButtonDefaultableProps, ValueToDisplayData } from '@/modules/core/Core.types.ts';
import {
IReaderSettings,
IReaderSettingsWithDefaultFlag,
ReadingDirection,
} from '@/modules/reader/types/Reader.types.ts';
import { TapZoneInvertMode } from '@/modules/reader/types/TapZoneLayout.types.ts';
import { ButtonSelectInput } from '@/modules/core/components/inputs/ButtonSelectInput.tsx';
enum TapZonesInvertOption {
NONE,
HORIZONTAL,
VERTICAL,
BOTH,
}
const VALUE_TO_DISPLAY_DATA: ValueToDisplayData<TapZonesInvertOption> = {
[TapZonesInvertOption.NONE]: {
title: 'global.label.none',
icon: null,
},
[TapZonesInvertOption.HORIZONTAL]: {
title: 'global.label.horizontal',
icon: null,
},
[TapZonesInvertOption.VERTICAL]: {
title: 'global.label.vertical',
icon: null,
},
[TapZonesInvertOption.BOTH]: {
title: 'global.label.both',
icon: null,
},
};
const TAP_ZONES_INVERT_OPTION_VALUES = Object.values(TapZonesInvertOption).filter((value) => typeof value === 'number');
const TAP_ZONES_INVERT_OPTION_TO_SETTING: Record<TapZonesInvertOption, TapZoneInvertMode> = {
[TapZonesInvertOption.NONE]: {
vertical: false,
horizontal: false,
},
[TapZonesInvertOption.HORIZONTAL]: {
vertical: false,
horizontal: true,
},
[TapZonesInvertOption.VERTICAL]: {
vertical: true,
horizontal: false,
},
[TapZonesInvertOption.BOTH]: {
vertical: true,
horizontal: true,
},
};
const convertTapZoneInvertModeToOption = ({ vertical, horizontal }: TapZoneInvertMode): TapZonesInvertOption => {
if (vertical && horizontal) {
return TapZonesInvertOption.BOTH;
}
if (vertical) {
return TapZonesInvertOption.VERTICAL;
}
if (horizontal) {
return TapZonesInvertOption.HORIZONTAL;
}
return TapZonesInvertOption.NONE;
};
export const ReaderSettingTapZoneInvertMode = ({
tapZoneInvertMode,
setTapZoneInvertMode,
...buttonSelectInputProps
}: Pick<IReaderSettingsWithDefaultFlag, 'tapZoneInvertMode'> &
Pick<MultiValueButtonDefaultableProps<ReadingDirection>, 'isDefaultable' | 'onDefault'> & {
setTapZoneInvertMode: (invert: IReaderSettings['tapZoneInvertMode']) => void;
}) => {
const { t } = useTranslation();
return (
<ButtonSelectInput
{...buttonSelectInputProps}
label={t('reader.settings.tap_zones.invert')}
value={tapZoneInvertMode.isDefault ? undefined : convertTapZoneInvertModeToOption(tapZoneInvertMode.value)}
values={TAP_ZONES_INVERT_OPTION_VALUES}
setValue={(value) => setTapZoneInvertMode(TAP_ZONES_INVERT_OPTION_TO_SETTING[value])}
valueToDisplayData={VALUE_TO_DISPLAY_DATA}
/>
);
};

View File

@@ -0,0 +1,56 @@
/*
* Copyright (C) Contributors to the Suwayomi project
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
import { useTranslation } from 'react-i18next';
import { TapZoneLayouts } from '@/modules/reader/types/TapZoneLayout.types.ts';
import { MultiValueButtonDefaultableProps, ValueToDisplayData } from '@/modules/core/Core.types.ts';
import { IReaderSettingsWithDefaultFlag, ReadingDirection } from '@/modules/reader/types/Reader.types.ts';
import { ButtonSelectInput } from '@/modules/core/components/inputs/ButtonSelectInput.tsx';
const VALUE_TO_DISPLAY_DATA: ValueToDisplayData<TapZoneLayouts> = {
[TapZoneLayouts.EDGE]: {
title: 'reader.settings.tap_zones.edge',
icon: null,
},
[TapZoneLayouts.KINDLE]: {
title: 'reader.settings.tap_zones.kindle',
icon: null,
},
[TapZoneLayouts.L_SHAPE]: {
title: 'reader.settings.tap_zones.l_shape',
icon: null,
},
[TapZoneLayouts.RIGHT_LEFT]: {
title: 'reader.settings.tap_zones.right_left',
icon: null,
},
};
const READER_TAP_ZONE_LAYOUT_VALUES = Object.values(TapZoneLayouts).filter((value) => typeof value === 'number');
export const ReaderSettingTapZoneLayout = ({
tapZoneLayout,
setTapZoneLayout,
...buttonSelectInputProps
}: Pick<IReaderSettingsWithDefaultFlag, 'tapZoneLayout'> &
Pick<MultiValueButtonDefaultableProps<ReadingDirection>, 'isDefaultable' | 'onDefault'> & {
setTapZoneLayout: (layout: TapZoneLayouts) => void;
}) => {
const { t } = useTranslation();
return (
<ButtonSelectInput
{...buttonSelectInputProps}
label={t('reader.settings.tap_zones.title')}
value={tapZoneLayout.isDefault ? undefined : tapZoneLayout.value}
values={READER_TAP_ZONE_LAYOUT_VALUES}
setValue={setTapZoneLayout}
valueToDisplayData={VALUE_TO_DISPLAY_DATA}
/>
);
};

View File

@@ -0,0 +1,61 @@
/*
* Copyright (C) Contributors to the Suwayomi project
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
import Stack from '@mui/material/Stack';
import { useTranslation } from 'react-i18next';
import { IReaderSettings } from '@/modules/reader/types/Reader.types.ts';
import { CheckboxInput } from '@/modules/core/components/inputs/CheckboxInput.tsx';
import { ReaderService } from '@/modules/reader/services/ReaderService.ts';
import { SliderInput } from '@/modules/core/components/inputs/SliderInput.tsx';
import { DEFAULT_READER_SETTINGS } from '@/modules/reader/constants/ReaderSettings.constants.tsx';
import { isReaderWidthEditable } from '@/modules/reader/utils/ReaderSettings.utils.tsx';
export const ReaderSettingWidth = ({
readerWidth,
pageScaleMode,
updateSetting,
}: Pick<IReaderSettings, 'readerWidth' | 'pageScaleMode'> & {
updateSetting: (...args: OmitFirst<Parameters<typeof ReaderService.updateSetting>>) => void;
}) => {
const { t } = useTranslation();
if (!isReaderWidthEditable(pageScaleMode)) {
return null;
}
return (
<Stack>
<CheckboxInput
label={t('reader.settings.label.limit_reader_width')}
checked={readerWidth.enabled}
onChange={(_, checked) => updateSetting('readerWidth', { ...readerWidth, enabled: checked }, true)}
/>
{readerWidth.enabled && (
<SliderInput
label={t('reader.settings.label.reader_width')}
value={t('global.value', { value: readerWidth.value, unit: '%' })}
slotProps={{
slider: {
defaultValue: DEFAULT_READER_SETTINGS.readerWidth.value,
value: readerWidth.value,
step: 1,
min: 10,
max: 100,
onChange: (_, value) => {
updateSetting('readerWidth', { ...readerWidth, value: value as number }, false);
},
onChangeCommitted: (_, value) => {
updateSetting('readerWidth', { ...readerWidth, value: value as number }, true);
},
},
}}
/>
)}
</Stack>
);
};