Make reader width configurable (#565)

Signed-off-by: Chance Zibolski <chance.zibolski@gmail.com>
This commit is contained in:
Chance Zibolski
2024-01-21 13:33:13 -08:00
committed by GitHub
parent dcbf5a1899
commit 5032a0ae94
9 changed files with 61 additions and 33 deletions

View File

@@ -23,7 +23,7 @@ import ListItem from '@mui/material/ListItem';
import ListItemText from '@mui/material/ListItemText'; import ListItemText from '@mui/material/ListItemText';
import Collapse from '@mui/material/Collapse'; import Collapse from '@mui/material/Collapse';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { ChapterOffset, IReaderSettings, TChapter, TManga } from '@/typings'; import { AllowedMetadataValueTypes, ChapterOffset, IReaderSettings, TChapter, TManga } from '@/typings';
import { ReaderSettingsOptions } from '@/components/reader/ReaderSettingsOptions'; import { ReaderSettingsOptions } from '@/components/reader/ReaderSettingsOptions';
const Root = styled('div')(({ theme }) => ({ const Root = styled('div')(({ theme }) => ({
@@ -112,7 +112,7 @@ const OpenDrawerButton = styled(IconButton)(({ theme }) => ({
interface IProps { interface IProps {
settings: IReaderSettings; settings: IReaderSettings;
setSettingValue: (key: keyof IReaderSettings, value: string | boolean) => void; setSettingValue: (key: keyof IReaderSettings, value: AllowedMetadataValueTypes) => void;
manga: TManga; manga: TManga;
chapter: TChapter; chapter: TChapter;
curPage: number; curPage: number;
@@ -141,7 +141,7 @@ export function ReaderNavBar(props: IProps) {
const disableChapterNavButtons = retrievingNextChapter; const disableChapterNavButtons = retrievingNextChapter;
const updateSettingValue = (key: keyof IReaderSettings, value: string | boolean) => { const updateSettingValue = (key: keyof IReaderSettings, value: AllowedMetadataValueTypes) => {
// prevent closing the navBar when updating the "staticNav" setting // prevent closing the navBar when updating the "staticNav" setting
setUpdateDrawerOnRender(key !== 'staticNav'); setUpdateDrawerOnRender(key !== 'staticNav');
setSettingValue(key, value); setSettingValue(key, value);
@@ -269,6 +269,7 @@ export function ReaderNavBar(props: IProps) {
fitPageToWindow={settings.fitPageToWindow} fitPageToWindow={settings.fitPageToWindow}
readerType={settings.readerType} readerType={settings.readerType}
offsetFirstPage={settings.offsetFirstPage} offsetFirstPage={settings.offsetFirstPage}
readerWidth={settings.readerWidth}
/> />
</Collapse> </Collapse>
<Divider sx={{ my: 1, mx: 2 }} /> <Divider sx={{ my: 1, mx: 2 }} />

View File

@@ -8,9 +8,12 @@
import { useState, useEffect, forwardRef, useRef } from 'react'; import { useState, useEffect, forwardRef, useRef } from 'react';
import Box from '@mui/material/Box'; import Box from '@mui/material/Box';
import { IReaderSettings } from '@/typings'; import { IReaderSettings, ReaderType } from '@/typings';
import { SpinnerImage } from '@/components/util/SpinnerImage'; import { SpinnerImage } from '@/components/util/SpinnerImage';
export const isFillsPageReaderType = (readerType: ReaderType): boolean =>
['DoubleRTL', 'DoubleLTR', 'ContinuesHorizontalLTR', 'ContinuesHorizontalRTL'].includes(readerType);
function imageStyle(settings: IReaderSettings): any { function imageStyle(settings: IReaderSettings): any {
const [dimensions, setDimensions] = useState({ const [dimensions, setDimensions] = useState({
height: window.innerHeight, height: window.innerHeight,
@@ -29,13 +32,7 @@ function imageStyle(settings: IReaderSettings): any {
window.removeEventListener('resize', handleResize); window.removeEventListener('resize', handleResize);
}; };
}, []); }, []);
if ( if (settings.fitPageToWindow || isFillsPageReaderType(settings.readerType)) {
settings.fitPageToWindow ||
settings.readerType === 'DoubleLTR' ||
settings.readerType === 'DoubleRTL' ||
settings.readerType === 'ContinuesHorizontalLTR' ||
settings.readerType === 'ContinuesHorizontalRTL'
) {
return { return {
display: 'block', display: 'block',
marginLeft: '7px', marginLeft: '7px',
@@ -51,9 +48,11 @@ function imageStyle(settings: IReaderSettings): any {
return { return {
display: 'block', display: 'block',
marginBottom: settings.readerType === 'ContinuesVertical' ? '15px' : 0, marginBottom: settings.readerType === 'ContinuesVertical' ? '15px' : 0,
minWidth: '50vw', minWidth: '10vw',
width: dimensions.width < dimensions.height ? '100vw' : '100%', width: dimensions.width < dimensions.height ? '100vw' : `${settings.readerWidth}%`,
maxWidth: '100%', maxWidth: '100%',
marginLeft: 'auto',
marginRight: 'auto',
}; };
} }

View File

@@ -10,10 +10,12 @@ import { List, ListItem, ListItemText, Switch } from '@mui/material';
import Select from '@mui/material/Select'; import Select from '@mui/material/Select';
import MenuItem from '@mui/material/MenuItem'; import MenuItem from '@mui/material/MenuItem';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { IReaderSettings } from '@/typings'; import { AllowedMetadataValueTypes, IReaderSettings } from '@/typings';
import { NumberSetting } from '@/components/settings/NumberSetting.tsx';
import { isFillsPageReaderType } from '@/components/reader/Page.tsx';
interface IProps extends IReaderSettings { interface IProps extends IReaderSettings {
setSettingValue: (key: keyof IReaderSettings, value: string | boolean) => void; setSettingValue: (key: keyof IReaderSettings, value: AllowedMetadataValueTypes) => void;
} }
export function ReaderSettingsOptions({ export function ReaderSettingsOptions({
@@ -25,15 +27,10 @@ export function ReaderSettingsOptions({
setSettingValue, setSettingValue,
fitPageToWindow, fitPageToWindow,
offsetFirstPage, offsetFirstPage,
readerWidth,
}: IProps) { }: IProps) {
const { t } = useTranslation(); const { t } = useTranslation();
const fitPageToWindowEligible = [ const fitPageToWindowEligible = !isFillsPageReaderType(readerType);
'ContinuesVertical',
'Webtoon',
'SingleVertical',
'SingleRTL',
'SingleLTR',
].includes(readerType);
return ( return (
<List> <List>
<ListItem> <ListItem>
@@ -68,7 +65,7 @@ export function ReaderSettingsOptions({
onChange={(e) => setSettingValue('skipDupChapters', e.target.checked)} onChange={(e) => setSettingValue('skipDupChapters', e.target.checked)}
/> />
</ListItem> </ListItem>
{fitPageToWindowEligible ? ( {fitPageToWindowEligible && (
<ListItem> <ListItem>
<ListItemText primary={t('reader.settings.label.fit_page_to_window')} /> <ListItemText primary={t('reader.settings.label.fit_page_to_window')} />
<Switch <Switch
@@ -77,8 +74,8 @@ export function ReaderSettingsOptions({
onChange={(e) => setSettingValue('fitPageToWindow', e.target.checked)} onChange={(e) => setSettingValue('fitPageToWindow', e.target.checked)}
/> />
</ListItem> </ListItem>
) : null} )}
{readerType === 'DoubleLTR' || readerType === 'DoubleRTL' ? ( {(readerType === 'DoubleLTR' || readerType === 'DoubleRTL') && (
<ListItem> <ListItem>
<ListItemText primary={t('reader.settings.label.offset_first_page')} /> <ListItemText primary={t('reader.settings.label.offset_first_page')} />
<Switch <Switch
@@ -87,7 +84,22 @@ export function ReaderSettingsOptions({
onChange={(e) => setSettingValue('offsetFirstPage', e.target.checked)} onChange={(e) => setSettingValue('offsetFirstPage', e.target.checked)}
/> />
</ListItem> </ListItem>
) : null} )}
{fitPageToWindowEligible && !fitPageToWindow && (
<NumberSetting
settingTitle={t('reader.settings.label.reader_width')}
dialogTitle={t('reader.settings.label.reader_width')}
settingValue={`${readerWidth}%`}
value={readerWidth}
minValue={10}
maxValue={100}
defaultValue={100}
valueUnit="%"
showSlider
handleUpdate={(width: number) => setSettingValue('readerWidth', width)}
listItemTextSx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}
/>
)}
<ListItem> <ListItem>
<ListItemText primary={t('reader.settings.label.reader_type')} /> <ListItemText primary={t('reader.settings.label.reader_type')} />
<Select <Select

View File

@@ -10,7 +10,7 @@ import Dialog from '@mui/material/Dialog';
import DialogContent from '@mui/material/DialogContent'; import DialogContent from '@mui/material/DialogContent';
import DialogTitle from '@mui/material/DialogTitle'; import DialogTitle from '@mui/material/DialogTitle';
import TextField from '@mui/material/TextField'; import TextField from '@mui/material/TextField';
import { InputAdornment, ListItemText, Stack, Typography } from '@mui/material'; import { InputAdornment, ListItemText, Stack, SxProps, Typography, Theme } from '@mui/material';
import DialogActions from '@mui/material/DialogActions'; import DialogActions from '@mui/material/DialogActions';
import Button from '@mui/material/Button'; import Button from '@mui/material/Button';
import { useCallback, useEffect, useState } from 'react'; import { useCallback, useEffect, useState } from 'react';
@@ -38,6 +38,7 @@ type BaseProps = {
handleUpdate: (value: number) => void; handleUpdate: (value: number) => void;
showSlider?: never; showSlider?: never;
disabled?: boolean; disabled?: boolean;
listItemTextSx?: SxProps<Theme>;
}; };
type PropsWithSlider = Omit<BaseProps, 'defaultValue' | 'minValue' | 'maxValue' | 'showSlider'> & type PropsWithSlider = Omit<BaseProps, 'defaultValue' | 'minValue' | 'maxValue' | 'showSlider'> &
@@ -61,6 +62,7 @@ export const NumberSetting = ({
handleUpdate, handleUpdate,
showSlider, showSlider,
disabled = false, disabled = false,
listItemTextSx: sx,
}: Props) => { }: Props) => {
const { t } = useTranslation(); const { t } = useTranslation();
@@ -107,6 +109,7 @@ export const NumberSetting = ({
<ListItemText <ListItemText
primary={settingTitle} primary={settingTitle}
secondary={settingValue ?? t('global.label.loading')} secondary={settingValue ?? t('global.label.loading')}
sx={sx}
secondaryTypographyProps={{ style: { display: 'flex', flexDirection: 'column' } }} secondaryTypographyProps={{ style: { display: 'flex', flexDirection: 'column' } }}
/> />
</ListItemButton> </ListItemButton>
@@ -150,7 +153,7 @@ export const NumberSetting = ({
}} }}
InputProps={{ InputProps={{
inputProps: { min: minValue, max: maxValue, step: stepSize }, inputProps: { min: minValue, max: maxValue, step: stepSize },
startAdornment: <InputAdornment position="start">{valueUnit}</InputAdornment>, endAdornment: <InputAdornment position="end">{valueUnit}</InputAdornment>,
}} }}
autoFocus autoFocus
value={dialogValue} value={dialogValue}

View File

@@ -607,7 +607,8 @@
"reader_type": "Reader type", "reader_type": "Reader type",
"show_page_number": "Show page number", "show_page_number": "Show page number",
"skip_dup_chapters": "Skip duplicate chapters", "skip_dup_chapters": "Skip duplicate chapters",
"static_navigation": "Static navigation" "static_navigation": "Static navigation",
"reader_width": "Reader width"
}, },
"reader_type": { "reader_type": {
"label": { "label": {

View File

@@ -11,7 +11,15 @@ import { useCallback, useContext, useEffect, useMemo, useRef, useState } from 'r
import { useLocation, useNavigate, useParams } from 'react-router-dom'; import { useLocation, useNavigate, useParams } from 'react-router-dom';
import { Box } from '@mui/material'; import { Box } from '@mui/material';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { ChapterOffset, IReaderSettings, ReaderType, TChapter, TManga, TranslationKey } from '@/typings'; import {
AllowedMetadataValueTypes,
ChapterOffset,
IReaderSettings,
ReaderType,
TChapter,
TManga,
TranslationKey,
} from '@/typings';
import { requestManager } from '@/lib/requests/RequestManager.ts'; import { requestManager } from '@/lib/requests/RequestManager.ts';
import { import {
checkAndHandleMissingStoredReaderSettings, checkAndHandleMissingStoredReaderSettings,
@@ -227,7 +235,7 @@ export function Reader() {
.response.catch(); .response.catch();
}; };
const setSettingValue = (key: keyof IReaderSettings, value: string | boolean) => { const setSettingValue = (key: keyof IReaderSettings, value: AllowedMetadataValueTypes) => {
setSettings({ ...settings, [key]: value }); setSettings({ ...settings, [key]: value });
requestUpdateMangaMetadata(manga, [[key, value]]).catch(() => requestUpdateMangaMetadata(manga, [[key, value]]).catch(() =>
makeToast(t('reader.settings.error.label.failed_to_save_settings'), 'warning'), makeToast(t('reader.settings.error.label.failed_to_save_settings'), 'warning'),

View File

@@ -10,7 +10,7 @@ import { useContext, useEffect } from 'react';
import { Box } from '@mui/material'; import { Box } from '@mui/material';
import CircularProgress from '@mui/material/CircularProgress'; import CircularProgress from '@mui/material/CircularProgress';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { IReaderSettings } from '@/typings'; import { AllowedMetadataValueTypes, IReaderSettings } from '@/typings';
import { convertToGqlMeta, requestUpdateServerMetadata } from '@/util/metadata'; import { convertToGqlMeta, requestUpdateServerMetadata } from '@/util/metadata';
import { import {
checkAndHandleMissingStoredReaderSettings, checkAndHandleMissingStoredReaderSettings,
@@ -34,7 +34,7 @@ export function DefaultReaderSettings() {
useSetDefaultBackTo('settings'); useSetDefaultBackTo('settings');
const setSettingValue = (key: keyof IReaderSettings, value: string | boolean) => { const setSettingValue = (key: keyof IReaderSettings, value: AllowedMetadataValueTypes) => {
requestUpdateServerMetadata(convertToGqlMeta(metadata)! ?? {}, [[key, value]]).catch(() => requestUpdateServerMetadata(convertToGqlMeta(metadata)! ?? {}, [[key, value]]).catch(() =>
makeToast(t('reader.settings.error.label.failed_to_save_settings'), 'warning'), makeToast(t('reader.settings.error.label.failed_to_save_settings'), 'warning'),
); );
@@ -71,6 +71,7 @@ export function DefaultReaderSettings() {
fitPageToWindow={settings.fitPageToWindow} fitPageToWindow={settings.fitPageToWindow}
readerType={settings.readerType} readerType={settings.readerType}
offsetFirstPage={settings.offsetFirstPage} offsetFirstPage={settings.offsetFirstPage}
readerWidth={settings.readerWidth}
/> />
); );
} }

View File

@@ -209,6 +209,7 @@ export interface IReaderSettings {
fitPageToWindow: boolean; fitPageToWindow: boolean;
readerType: ReaderType; readerType: ReaderType;
offsetFirstPage: boolean; offsetFirstPage: boolean;
readerWidth: number;
} }
export enum ChapterOffset { export enum ChapterOffset {

View File

@@ -28,6 +28,7 @@ export const getDefaultSettings = (): IReaderSettings => ({
fitPageToWindow: false, fitPageToWindow: false,
readerType: 'ContinuesVertical', readerType: 'ContinuesVertical',
offsetFirstPage: false, offsetFirstPage: false,
readerWidth: 100,
}); });
const getReaderSettingsWithDefaultValueFallback = <DefaultSettings extends IReaderSettings | UndefinedReaderSettings>( const getReaderSettingsWithDefaultValueFallback = <DefaultSettings extends IReaderSettings | UndefinedReaderSettings>(
@@ -84,6 +85,7 @@ export const checkAndHandleMissingStoredReaderSettings = async (
fitPageToWindow: undefined, fitPageToWindow: undefined,
readerType: undefined, readerType: undefined,
offsetFirstPage: undefined, offsetFirstPage: undefined,
readerWidth: undefined,
}, },
false, false,
); );