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:
@@ -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) => {
|
||||
|
||||
@@ -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' }}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user