Update reader width live when updating in reader view (#581)

Basically, instead of updating the width of the image after clicking
`ok`, the width is adjusted live, which makes it easier to figure out
the right value for the manga being viewed.

While implementing this, I realized we can't update the backend every
time we re-render when `liveUpdate` is true, so I've updated the logic
for persisting settings to support skipping the API call to save the
settings in the backend, and only perform the API it when the user
clicks ok on the number setting.

Signed-off-by: Chance Zibolski <chance.zibolski@gmail.com>
This commit is contained in:
Chance Zibolski
2024-03-18 10:35:35 -07:00
committed by GitHub
parent 0c22a9012e
commit 0bf88d3fd1
5 changed files with 64 additions and 60 deletions

View File

@@ -118,7 +118,7 @@ const OpenDrawerButton = styled(IconButton)(({ theme }) => ({
interface IProps {
settings: IReaderSettings;
setSettingValue: (key: keyof IReaderSettings, value: AllowedMetadataValueTypes) => void;
setSettingValue: (key: keyof IReaderSettings, value: AllowedMetadataValueTypes, persist?: boolean) => void;
manga: TManga;
chapter: TChapter;
curPage: number;
@@ -150,10 +150,10 @@ export function ReaderNavBar(props: IProps) {
const disableChapterNavButtons = retrievingNextChapter;
const updateSettingValue = (key: keyof IReaderSettings, value: AllowedMetadataValueTypes) => {
const updateSettingValue = (key: keyof IReaderSettings, value: AllowedMetadataValueTypes, persist?: boolean) => {
// prevent closing the navBar when updating the "staticNav" setting
setUpdateDrawerOnRender(key !== 'staticNav');
setSettingValue(key, value);
setSettingValue(key, value, persist);
};
const updateDrawer = (open: boolean) => {

View File

@@ -15,7 +15,7 @@ import { NumberSetting } from '@/components/settings/NumberSetting.tsx';
import { isHorizontalReaderType } from '@/components/reader/Page.tsx';
interface IProps extends IReaderSettings {
setSettingValue: (key: keyof IReaderSettings, value: AllowedMetadataValueTypes) => void;
setSettingValue: (key: keyof IReaderSettings, value: AllowedMetadataValueTypes, persist?: boolean) => void;
}
export function ReaderSettingsOptions({
@@ -107,6 +107,7 @@ export function ReaderSettingsOptions({
valueUnit="%"
showSlider
handleUpdate={(width: number) => setSettingValue('readerWidth', width)}
handleLiveUpdate={(width: number) => setSettingValue('readerWidth', width, false)}
listItemTextSx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}
/>
)}

View File

@@ -13,7 +13,7 @@ import TextField from '@mui/material/TextField';
import { InputAdornment, ListItemText, Stack, SxProps, Typography, Theme } from '@mui/material';
import DialogActions from '@mui/material/DialogActions';
import Button from '@mui/material/Button';
import { useCallback, useEffect, useState } from 'react';
import { useCallback, useState } from 'react';
import { useTranslation } from 'react-i18next';
import ListItemButton from '@mui/material/ListItemButton';
import * as React from 'react';
@@ -39,6 +39,7 @@ type BaseProps = {
showSlider?: never;
disabled?: boolean;
listItemTextSx?: SxProps<Theme>;
handleLiveUpdate?: (value: number) => void;
};
type PropsWithSlider = Omit<BaseProps, 'defaultValue' | 'minValue' | 'maxValue' | 'showSlider'> &
@@ -62,45 +63,49 @@ export const NumberSetting = ({
handleUpdate,
showSlider,
disabled = false,
handleLiveUpdate,
listItemTextSx: sx,
}: Props) => {
const { t } = useTranslation();
const [isDialogOpen, setIsDialogOpen] = useState(false);
const [dialogValue, setDialogValue] = useState(value);
const [originalValue, setOriginalValue] = useState(value);
const closeDialog = useCallback(
(resetValue: boolean) => {
const updateValue = useCallback(
(newValue: number, persist: boolean) => {
setDialogValue(newValue);
const didValueChange = newValue !== originalValue;
// Call handleUpdate if the value changed and 'persist' is true,
// otherwise call handleLiveUpdate if it's defined.
if (persist && didValueChange) {
handleUpdate(newValue);
} else if (handleLiveUpdate) {
handleLiveUpdate(newValue);
}
},
[originalValue, setDialogValue, handleLiveUpdate, handleUpdate],
);
const cancel = useCallback(() => {
updateValue(originalValue, true);
setOriginalValue(originalValue);
setIsDialogOpen(false);
}, [originalValue, handleUpdate]);
const resetToDefault = useCallback(() => {
if (defaultValue !== undefined) {
updateValue(defaultValue, true);
setOriginalValue(defaultValue);
setIsDialogOpen(false);
}
}, [defaultValue, handleUpdate]);
if (resetValue) {
setDialogValue(value);
}
},
[value],
);
const closeDialogWithReset = useCallback(() => closeDialog(true), [closeDialog]);
const updateSetting = useCallback(
(newValue: number, shouldCloseDialog: boolean = true) => {
if (shouldCloseDialog) {
closeDialog(false);
}
const didValueChange = value !== newValue;
if (!didValueChange) {
return;
}
handleUpdate(newValue);
},
[value, handleUpdate, closeDialog],
);
useEffect(() => {
setDialogValue(value);
}, [value]);
const submit = () => {
updateValue(dialogValue, true);
setOriginalValue(dialogValue);
setIsDialogOpen(false);
};
return (
<>
@@ -114,7 +119,7 @@ export const NumberSetting = ({
/>
</ListItemButton>
<Dialog open={isDialogOpen} onClose={closeDialogWithReset}>
<Dialog open={isDialogOpen} onClose={cancel}>
<DialogContent>
<DialogTitle sx={{ paddingLeft: 0 }}>{dialogTitle}</DialogTitle>
{(!!dialogDescription || !!dialogDisclaimer) && (
@@ -158,7 +163,10 @@ export const NumberSetting = ({
autoFocus
value={dialogValue}
type="number"
onChange={(e) => setDialogValue(Number(e.target.value))}
onChange={(e) => {
const newValue = Number(e.target.value);
updateValue(newValue, false);
}}
/>
{showSlider ? (
<Slider
@@ -168,31 +176,22 @@ export const NumberSetting = ({
step={stepSize}
min={minValue}
max={maxValue}
onChange={(_, newValue) => setDialogValue(newValue as number)}
onChange={(_, newValue) => {
updateValue(newValue as number, false);
}}
/>
) : null}
</DialogContent>
<DialogActions>
{defaultValue !== undefined ? (
<Button
onClick={() => {
setDialogValue(defaultValue);
updateSetting(defaultValue, false);
}}
color="primary"
>
<Button onClick={resetToDefault} color="primary">
{t('global.button.reset_to_default')}
</Button>
) : null}
<Button onClick={closeDialogWithReset} color="primary">
<Button onClick={cancel} color="primary">
{t('global.button.cancel')}
</Button>
<Button
onClick={() => {
updateSetting(dialogValue);
}}
color="primary"
>
<Button onClick={submit} color="primary">
{t('global.button.ok')}
</Button>
</DialogActions>

View File

@@ -257,11 +257,13 @@ export function Reader() {
.response.catch();
};
const setSettingValue = (key: keyof IReaderSettings, value: AllowedMetadataValueTypes) => {
const setSettingValue = (key: keyof IReaderSettings, value: AllowedMetadataValueTypes, persist: boolean = true) => {
setSettings({ ...settings, [key]: value });
requestUpdateMangaMetadata(manga, [[key, value]]).catch(() =>
makeToast(t('reader.settings.error.label.failed_to_save_settings'), 'warning'),
);
if (persist) {
requestUpdateMangaMetadata(manga, [[key, value]]).catch(() =>
makeToast(t('reader.settings.error.label.failed_to_save_settings'), 'warning'),
);
}
};
const openNextChapter = useCallback(

View File

@@ -39,10 +39,12 @@ export function DefaultReaderSettings() {
useSetDefaultBackTo('settings');
const setSettingValue = (key: keyof IReaderSettings, value: AllowedMetadataValueTypes) => {
requestUpdateServerMetadata([[key, value]]).catch(() =>
makeToast(t('reader.settings.error.label.failed_to_save_settings'), 'warning'),
);
const setSettingValue = (key: keyof IReaderSettings, value: AllowedMetadataValueTypes, persist: boolean = true) => {
if (persist) {
requestUpdateServerMetadata([[key, value]]).catch(() =>
makeToast(t('reader.settings.error.label.failed_to_save_settings'), 'warning'),
);
}
};
if (loading) {