Move core files into new folder
This commit is contained in:
@@ -17,11 +17,11 @@ import DialogContentText from '@mui/material/DialogContentText';
|
||||
import DialogActions from '@mui/material/DialogActions';
|
||||
import Button from '@mui/material/Button';
|
||||
import { t as translate } from 'i18next';
|
||||
import { ThreeStateCheckboxInput } from '@/components/atoms/ThreeStateCheckboxInput.tsx';
|
||||
import { makeToast } from '@/components/util/Toast.tsx';
|
||||
import { ThreeStateCheckboxInput } from '@/modules/core/components/inputs/ThreeStateCheckboxInput.tsx';
|
||||
import { makeToast } from '@/lib/ui/Toast.ts';
|
||||
import { IncludeOrExclude } from '@/lib/graphql/generated/graphql.ts';
|
||||
import { requestManager } from '@/lib/requests/RequestManager.ts';
|
||||
import { CheckboxContainer } from '@/components/settings/globalUpdate/CheckboxContainer.ts';
|
||||
import { requestManager } from '@/lib/requests/requests/RequestManager.ts';
|
||||
import { CheckboxContainer } from '@/modules/core/components/inputs/CheckboxContainer.ts';
|
||||
import {
|
||||
CategoryDownloadInclusionInfo,
|
||||
CategoryIdInfo,
|
||||
|
||||
@@ -1,153 +0,0 @@
|
||||
/*
|
||||
* 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 Button from '@mui/material/Button';
|
||||
import Dialog from '@mui/material/Dialog';
|
||||
import DialogTitle from '@mui/material/DialogTitle';
|
||||
import ListItemText from '@mui/material/ListItemText';
|
||||
import Stack from '@mui/material/Stack';
|
||||
import DialogContent from '@mui/material/DialogContent';
|
||||
import DialogActions from '@mui/material/DialogActions';
|
||||
import ListItemButton from '@mui/material/ListItemButton';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider';
|
||||
import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs';
|
||||
import dayjs from 'dayjs';
|
||||
import { DatePicker } from '@mui/x-date-pickers/DatePicker';
|
||||
|
||||
export const DateSetting = ({
|
||||
settingName,
|
||||
value,
|
||||
defaultValue,
|
||||
handleChange,
|
||||
remove,
|
||||
}: {
|
||||
settingName: string;
|
||||
value?: string;
|
||||
defaultValue?: string;
|
||||
handleChange: (path?: string | null) => void;
|
||||
remove?: boolean;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [isDialogOpen, setIsDialogOpen] = useState(false);
|
||||
const [dialogValue, setDialogValue] = useState(value ?? defaultValue);
|
||||
|
||||
useEffect(() => {
|
||||
if (!value) {
|
||||
return;
|
||||
}
|
||||
|
||||
setDialogValue(value);
|
||||
}, [value]);
|
||||
|
||||
const closeDialog = useCallback(
|
||||
(resetValue: boolean) => {
|
||||
setIsDialogOpen(false);
|
||||
|
||||
if (resetValue) {
|
||||
setDialogValue(value ?? defaultValue);
|
||||
}
|
||||
},
|
||||
[value],
|
||||
);
|
||||
|
||||
const closeDialogWithReset = useCallback(() => closeDialog(true), [closeDialog]);
|
||||
|
||||
const updateSetting = useCallback(
|
||||
(newValue?: string, shouldCloseDialog: boolean = true) => {
|
||||
if (shouldCloseDialog) {
|
||||
closeDialog(false);
|
||||
}
|
||||
|
||||
const didValueChange = value !== newValue;
|
||||
if (!didValueChange) {
|
||||
return;
|
||||
}
|
||||
|
||||
handleChange(newValue);
|
||||
},
|
||||
[value, handleChange, closeDialog],
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<ListItemButton onClick={() => setIsDialogOpen(true)}>
|
||||
<ListItemText
|
||||
primary={settingName}
|
||||
secondary={value ? dayjs(Number(value)).format('L') : '-'}
|
||||
secondaryTypographyProps={{ style: { display: 'flex', flexDirection: 'column' } }}
|
||||
/>
|
||||
</ListItemButton>
|
||||
|
||||
<Dialog open={isDialogOpen} onClose={closeDialog}>
|
||||
<DialogContent>
|
||||
<DialogTitle sx={{ paddingLeft: 0 }}>{settingName}</DialogTitle>
|
||||
<LocalizationProvider dateAdapter={AdapterDayjs} adapterLocale={dayjs.locale()}>
|
||||
<DatePicker
|
||||
value={dialogValue ? dayjs(Number(dialogValue)) : null}
|
||||
onChange={(date) => {
|
||||
if (!date) return;
|
||||
setDialogValue(date.valueOf().toString());
|
||||
}}
|
||||
/>
|
||||
</LocalizationProvider>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Stack
|
||||
direction="row"
|
||||
sx={{
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'end',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
<Stack>
|
||||
{defaultValue !== undefined && (
|
||||
<Button
|
||||
onClick={() => {
|
||||
setDialogValue(defaultValue);
|
||||
updateSetting(defaultValue, false);
|
||||
}}
|
||||
color="primary"
|
||||
>
|
||||
{t('global.button.reset_to_default')}
|
||||
</Button>
|
||||
)}
|
||||
{remove && (
|
||||
<Button
|
||||
onClick={() => {
|
||||
setDialogValue(undefined);
|
||||
updateSetting(undefined, false);
|
||||
}}
|
||||
color="primary"
|
||||
>
|
||||
{t('global.button.remove')}
|
||||
</Button>
|
||||
)}
|
||||
</Stack>
|
||||
<Stack direction="row">
|
||||
<Button onClick={closeDialogWithReset} color="primary">
|
||||
{t('global.button.cancel')}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
updateSetting(dialogValue);
|
||||
}}
|
||||
color="primary"
|
||||
>
|
||||
{t('global.button.ok')}
|
||||
</Button>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -14,14 +14,14 @@ import { useTranslation } from 'react-i18next';
|
||||
import { useContext, useLayoutEffect } from 'react';
|
||||
import { updateMetadataServerSettings, useMetadataServerSettings } from '@/lib/metadata/metadataServerSettings.ts';
|
||||
import { MetadataServerSettingKeys, MetadataServerSettings } from '@/typings.ts';
|
||||
import { makeToast } from '@/components/util/Toast.tsx';
|
||||
import { MutableListSetting } from '@/components/settings/MutableListSetting.tsx';
|
||||
import { makeToast } from '@/lib/ui/Toast.ts';
|
||||
import { MutableListSetting } from '@/modules/core/components/settings/MutableListSetting.tsx';
|
||||
import { NavBarContext } from '@/components/context/NavbarContext.tsx';
|
||||
import { ActiveDevice, DEFAULT_DEVICE } from '@/util/device.ts';
|
||||
import { Select } from '@/components/atoms/Select.tsx';
|
||||
import { LoadingPlaceholder } from '@/components/util/LoadingPlaceholder.tsx';
|
||||
import { EmptyViewAbsoluteCentered } from '@/components/util/EmptyViewAbsoluteCentered.tsx';
|
||||
import { defaultPromiseErrorHandler } from '@/util/defaultPromiseErrorHandler.ts';
|
||||
import { Select } from '@/modules/core/components/inputs/Select.tsx';
|
||||
import { LoadingPlaceholder } from '@/modules/core/components/placeholder/LoadingPlaceholder.tsx';
|
||||
import { EmptyViewAbsoluteCentered } from '@/modules/core/components/placeholder/EmptyViewAbsoluteCentered.tsx';
|
||||
import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.ts';
|
||||
|
||||
export const DeviceSetting = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
@@ -1,237 +0,0 @@
|
||||
/*
|
||||
* 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 Button from '@mui/material/Button';
|
||||
import Dialog from '@mui/material/Dialog';
|
||||
import DialogTitle from '@mui/material/DialogTitle';
|
||||
import ListItem from '@mui/material/ListItem';
|
||||
import ListItemButton from '@mui/material/ListItemButton';
|
||||
import ListItemText from '@mui/material/ListItemText';
|
||||
import Stack from '@mui/material/Stack';
|
||||
import Tooltip from '@mui/material/Tooltip';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import { useEffect, useState } from 'react';
|
||||
import DialogContent from '@mui/material/DialogContent';
|
||||
import DialogActions from '@mui/material/DialogActions';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import List from '@mui/material/List';
|
||||
import DeleteIcon from '@mui/icons-material/Delete';
|
||||
import IconButton from '@mui/material/IconButton';
|
||||
import DialogContentText from '@mui/material/DialogContentText';
|
||||
import InfoIcon from '@mui/icons-material/Info';
|
||||
import { TextSetting, TextSettingProps } from '@/components/settings/text/TextSetting.tsx';
|
||||
import { TextSettingDialog } from '@/components/settings/text/TextSettingDialog.tsx';
|
||||
import { makeToast } from '@/components/util/Toast.tsx';
|
||||
|
||||
const MutableListItem = ({
|
||||
handleDelete,
|
||||
mutable = true,
|
||||
deletable = true,
|
||||
...textSettingProps
|
||||
}: Omit<TextSettingProps, 'isPassword' | 'disabled'> & {
|
||||
handleDelete: () => void;
|
||||
mutable?: boolean;
|
||||
deletable?: boolean;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<Stack direction="row">
|
||||
{mutable ? (
|
||||
<TextSetting {...textSettingProps} dialogTitle="" />
|
||||
) : (
|
||||
<ListItem>
|
||||
<ListItemText secondary={textSettingProps.value} />
|
||||
</ListItem>
|
||||
)}
|
||||
<Tooltip title={t('chapter.action.download.delete.label.action')}>
|
||||
<IconButton disabled={!deletable} size="large" onClick={handleDelete}>
|
||||
<DeleteIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
type MutableListSettingProps = Pick<TextSettingProps, 'settingName' | 'placeholder'> & {
|
||||
valueInfos?: (
|
||||
| [value: string]
|
||||
| [value: string, Pick<React.ComponentProps<typeof MutableListItem>, 'mutable' | 'deletable'>]
|
||||
)[];
|
||||
description?: string;
|
||||
dialogDisclaimer?: JSX.Element | string;
|
||||
addItemButtonTitle?: string;
|
||||
handleChange: (values: string[]) => void;
|
||||
allowDuplicates?: boolean;
|
||||
validateItem?: (value: string) => boolean;
|
||||
invalidItemError?: string;
|
||||
};
|
||||
|
||||
const getValues = (valueInfos: MutableListSettingProps['valueInfos']): string[] =>
|
||||
valueInfos?.map((valueInfo) => valueInfo[0]) ?? [];
|
||||
|
||||
export const MutableListSetting = ({
|
||||
settingName,
|
||||
description,
|
||||
dialogDisclaimer,
|
||||
valueInfos,
|
||||
handleChange,
|
||||
addItemButtonTitle,
|
||||
placeholder,
|
||||
allowDuplicates = false,
|
||||
validateItem = () => true,
|
||||
invalidItemError,
|
||||
}: MutableListSettingProps) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const values = getValues(valueInfos);
|
||||
|
||||
const [isDialogOpen, setIsDialogOpen] = useState(false);
|
||||
const [dialogValues, setDialogValues] = useState(values);
|
||||
|
||||
const [isAddItemDialogOpen, setIsAddItemDialogOpen] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (!valueInfos) {
|
||||
return;
|
||||
}
|
||||
|
||||
setDialogValues(values);
|
||||
}, [valueInfos]);
|
||||
|
||||
const closeDialog = (resetValue: boolean = true) => {
|
||||
if (resetValue) {
|
||||
setDialogValues(values);
|
||||
}
|
||||
|
||||
setIsDialogOpen(false);
|
||||
};
|
||||
|
||||
const updateSetting = (index: number, newValue: string | undefined) => {
|
||||
const deleteValue = newValue === undefined;
|
||||
if (deleteValue) {
|
||||
setDialogValues(dialogValues.toSpliced(index, 1));
|
||||
return;
|
||||
}
|
||||
|
||||
const isDuplicate = !allowDuplicates && dialogValues.includes(newValue);
|
||||
if (isDuplicate) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (newValue === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!validateItem(newValue)) {
|
||||
makeToast(invalidItemError ?? t('global.error.label.invalid_input'), 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
setDialogValues(dialogValues.toSpliced(index, 1, newValue.trim()));
|
||||
};
|
||||
|
||||
const saveChanges = () => {
|
||||
closeDialog(true);
|
||||
handleChange(dialogValues.filter((dialogValue) => dialogValue !== ''));
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<ListItemButton onClick={() => setIsDialogOpen(true)}>
|
||||
<ListItemText
|
||||
primary={settingName}
|
||||
secondary={values?.length ? values?.join(', ') : description}
|
||||
secondaryTypographyProps={{ style: { display: 'flex', flexDirection: 'column' } }}
|
||||
/>
|
||||
</ListItemButton>
|
||||
|
||||
<Dialog open={isDialogOpen} onClose={() => closeDialog()} fullWidth>
|
||||
<DialogTitle>{settingName}</DialogTitle>
|
||||
{(!!description || !!dialogDisclaimer) && (
|
||||
<DialogContent>
|
||||
<DialogContentText sx={{ paddingBottom: '10px' }} component="div">
|
||||
{description && (
|
||||
<Typography
|
||||
variant="body1"
|
||||
sx={{
|
||||
whiteSpace: 'pre-line',
|
||||
}}
|
||||
>
|
||||
{description}
|
||||
</Typography>
|
||||
)}
|
||||
{dialogDisclaimer && (
|
||||
<Stack
|
||||
direction="row"
|
||||
sx={{
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
<InfoIcon color="warning" />
|
||||
<Typography
|
||||
variant="body1"
|
||||
sx={{
|
||||
marginLeft: '10px',
|
||||
marginTop: '5px',
|
||||
whiteSpace: 'pre-line',
|
||||
}}
|
||||
>
|
||||
{dialogDisclaimer}
|
||||
</Typography>
|
||||
</Stack>
|
||||
)}
|
||||
</DialogContentText>
|
||||
</DialogContent>
|
||||
)}
|
||||
<DialogContent dividers sx={{ maxHeight: '300px' }}>
|
||||
<List>
|
||||
{dialogValues.map((dialogValue, index) => (
|
||||
<MutableListItem
|
||||
settingName=""
|
||||
placeholder={placeholder}
|
||||
handleChange={(newValue: string) => updateSetting(index, newValue)}
|
||||
handleDelete={() => updateSetting(index, undefined)}
|
||||
value={dialogValue}
|
||||
mutable={valueInfos?.find(([value]) => value === dialogValue)?.[1]?.mutable}
|
||||
deletable={valueInfos?.find(([value]) => value === dialogValue)?.[1]?.deletable}
|
||||
/>
|
||||
))}
|
||||
</List>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Stack
|
||||
direction="row"
|
||||
sx={{
|
||||
justifyContent: 'space-between',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
<Button onClick={() => setIsAddItemDialogOpen(true)}>
|
||||
{addItemButtonTitle ?? t('global.button.add')}
|
||||
</Button>
|
||||
<Stack direction="row">
|
||||
<Button onClick={() => closeDialog()}>{t('global.button.cancel')}</Button>
|
||||
<Button onClick={() => saveChanges()}>{t('global.button.ok')}</Button>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
|
||||
{isAddItemDialogOpen && (
|
||||
<TextSettingDialog
|
||||
settingName=""
|
||||
placeholder={placeholder}
|
||||
handleChange={(newValue: string) => updateSetting(dialogValues.length, newValue)}
|
||||
isDialogOpen={isAddItemDialogOpen}
|
||||
setIsDialogOpen={setIsAddItemDialogOpen}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -1,212 +0,0 @@
|
||||
/*
|
||||
* 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 DialogTitle from '@mui/material/DialogTitle';
|
||||
import TextField from '@mui/material/TextField';
|
||||
import InputAdornment from '@mui/material/InputAdornment';
|
||||
import ListItemText from '@mui/material/ListItemText';
|
||||
import Stack from '@mui/material/Stack';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import DialogActions from '@mui/material/DialogActions';
|
||||
import Button from '@mui/material/Button';
|
||||
import { useCallback, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import ListItemButton from '@mui/material/ListItemButton';
|
||||
import * as React from 'react';
|
||||
import ListItemIcon from '@mui/material/ListItemIcon';
|
||||
import Slider from '@mui/material/Slider';
|
||||
import DialogContentText from '@mui/material/DialogContentText';
|
||||
import InfoIcon from '@mui/icons-material/Info';
|
||||
import { SxProps, Theme } from '@mui/material/styles';
|
||||
|
||||
type BaseProps = {
|
||||
settingTitle: string;
|
||||
settingValue: string;
|
||||
settingIcon?: React.ReactNode;
|
||||
value: number;
|
||||
defaultValue?: number;
|
||||
minValue?: number;
|
||||
maxValue?: number;
|
||||
stepSize?: number;
|
||||
dialogTitle?: string;
|
||||
dialogDescription?: string;
|
||||
dialogDisclaimer?: string;
|
||||
valueUnit: string;
|
||||
handleUpdate: (value: number) => void;
|
||||
showSlider?: never;
|
||||
disabled?: boolean;
|
||||
listItemTextSx?: SxProps<Theme>;
|
||||
handleLiveUpdate?: (value: number) => void;
|
||||
};
|
||||
|
||||
type PropsWithSlider = Omit<BaseProps, 'defaultValue' | 'minValue' | 'maxValue' | 'showSlider'> &
|
||||
Required<Pick<BaseProps, 'defaultValue' | 'minValue' | 'maxValue'>> & { showSlider: true };
|
||||
|
||||
type Props = BaseProps | PropsWithSlider;
|
||||
|
||||
export const NumberSetting = ({
|
||||
settingTitle,
|
||||
settingValue,
|
||||
settingIcon,
|
||||
dialogDescription,
|
||||
dialogDisclaimer,
|
||||
value,
|
||||
defaultValue,
|
||||
minValue,
|
||||
maxValue,
|
||||
stepSize,
|
||||
dialogTitle = settingTitle,
|
||||
valueUnit,
|
||||
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 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]);
|
||||
|
||||
const submit = () => {
|
||||
updateValue(dialogValue, true);
|
||||
setOriginalValue(dialogValue);
|
||||
setIsDialogOpen(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<ListItemButton disabled={disabled} onClick={() => setIsDialogOpen(true)}>
|
||||
{settingIcon ? <ListItemIcon>{settingIcon}</ListItemIcon> : null}
|
||||
<ListItemText
|
||||
primary={settingTitle}
|
||||
secondary={settingValue}
|
||||
sx={sx}
|
||||
secondaryTypographyProps={{ style: { display: 'flex', flexDirection: 'column' } }}
|
||||
/>
|
||||
</ListItemButton>
|
||||
|
||||
<Dialog open={isDialogOpen} onClose={cancel}>
|
||||
<DialogContent>
|
||||
<DialogTitle sx={{ paddingLeft: 0 }}>{dialogTitle}</DialogTitle>
|
||||
{(!!dialogDescription || !!dialogDisclaimer) && (
|
||||
<DialogContentText sx={{ paddingBottom: '10px' }} component="div">
|
||||
{dialogDescription && (
|
||||
<Typography
|
||||
variant="body1"
|
||||
sx={{
|
||||
whiteSpace: 'pre-line',
|
||||
}}
|
||||
>
|
||||
{dialogDescription}
|
||||
</Typography>
|
||||
)}
|
||||
{dialogDisclaimer && (
|
||||
<Stack
|
||||
direction="row"
|
||||
sx={{
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
<InfoIcon color="warning" />
|
||||
<Typography
|
||||
variant="body1"
|
||||
sx={{
|
||||
marginLeft: '10px',
|
||||
marginTop: '5px',
|
||||
whiteSpace: 'pre-line',
|
||||
}}
|
||||
>
|
||||
{dialogDisclaimer}
|
||||
</Typography>
|
||||
</Stack>
|
||||
)}
|
||||
</DialogContentText>
|
||||
)}
|
||||
<TextField
|
||||
sx={{
|
||||
width: '100%',
|
||||
margin: 'auto',
|
||||
}}
|
||||
autoFocus
|
||||
value={dialogValue}
|
||||
type="number"
|
||||
onChange={(e) => {
|
||||
const newValue = Number(e.target.value);
|
||||
updateValue(newValue, false);
|
||||
}}
|
||||
slotProps={{
|
||||
input: {
|
||||
inputProps: { min: minValue, max: maxValue, step: stepSize },
|
||||
endAdornment: <InputAdornment position="end">{valueUnit}</InputAdornment>,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
{showSlider ? (
|
||||
<Slider
|
||||
aria-label="number-setting-slider"
|
||||
defaultValue={defaultValue}
|
||||
value={dialogValue}
|
||||
step={stepSize}
|
||||
min={minValue}
|
||||
max={maxValue}
|
||||
onChange={(_, newValue) => {
|
||||
updateValue(newValue as number, false);
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
{defaultValue !== undefined ? (
|
||||
<Button onClick={resetToDefault} color="primary">
|
||||
{t('global.button.reset_to_default')}
|
||||
</Button>
|
||||
) : null}
|
||||
<Button onClick={cancel} color="primary">
|
||||
{t('global.button.cancel')}
|
||||
</Button>
|
||||
<Button onClick={submit} color="primary">
|
||||
{t('global.button.ok')}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -1,154 +0,0 @@
|
||||
/*
|
||||
* 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 Button from '@mui/material/Button';
|
||||
import Dialog from '@mui/material/Dialog';
|
||||
import DialogTitle from '@mui/material/DialogTitle';
|
||||
import FormControl from '@mui/material/FormControl';
|
||||
import ListItemText from '@mui/material/ListItemText';
|
||||
import MenuItem from '@mui/material/MenuItem';
|
||||
import Stack from '@mui/material/Stack';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import DialogContent from '@mui/material/DialogContent';
|
||||
import DialogActions from '@mui/material/DialogActions';
|
||||
import ListItemButton from '@mui/material/ListItemButton';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import DialogContentText from '@mui/material/DialogContentText';
|
||||
import InfoIcon from '@mui/icons-material/Info';
|
||||
import { TranslationKey } from '@/typings.ts';
|
||||
import { Select } from '@/components/atoms/Select.tsx';
|
||||
|
||||
export type SelectSettingValueDisplayInfo = {
|
||||
text: TranslationKey | string;
|
||||
description?: TranslationKey | string;
|
||||
disclaimer?: TranslationKey | string;
|
||||
};
|
||||
|
||||
export type SelectSettingValue<Value> = [Value: Value, DisplayInfo: SelectSettingValueDisplayInfo];
|
||||
|
||||
export const SelectSetting = <SettingValue extends string | number>({
|
||||
settingName,
|
||||
dialogDescription,
|
||||
value,
|
||||
values,
|
||||
handleChange,
|
||||
disabled = false,
|
||||
}: {
|
||||
settingName: string;
|
||||
dialogDescription?: string;
|
||||
value: SettingValue;
|
||||
values: SelectSettingValue<SettingValue>[];
|
||||
handleChange: (value: SettingValue) => void;
|
||||
disabled?: boolean;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [isDialogOpen, setIsDialogOpen] = useState(false);
|
||||
const [dialogValue, setDialogValue] = useState(value);
|
||||
|
||||
const valueDisplayText = useMemo(() => values.find(([key]) => key === value)?.[1]?.text, [value]);
|
||||
const dialogValueDisplayInfo = useMemo(() => values.find(([key]) => key === dialogValue)![1], [dialogValue]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!value) {
|
||||
return;
|
||||
}
|
||||
|
||||
setDialogValue(value);
|
||||
}, [value]);
|
||||
|
||||
const closeDialog = (resetValue: boolean = true) => {
|
||||
if (resetValue) {
|
||||
setDialogValue(value);
|
||||
}
|
||||
|
||||
setIsDialogOpen(false);
|
||||
};
|
||||
|
||||
const updateSetting = () => {
|
||||
closeDialog(false);
|
||||
handleChange(dialogValue);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<ListItemButton disabled={disabled} onClick={() => setIsDialogOpen(true)}>
|
||||
<ListItemText
|
||||
primary={settingName}
|
||||
secondary={valueDisplayText ? t(valueDisplayText as TranslationKey) : t('global.label.loading')}
|
||||
secondaryTypographyProps={{ style: { display: 'flex', flexDirection: 'column' } }}
|
||||
/>
|
||||
</ListItemButton>
|
||||
|
||||
<Dialog open={isDialogOpen} onClose={() => closeDialog()} fullWidth>
|
||||
<DialogContent>
|
||||
<DialogTitle sx={{ paddingLeft: 0 }}>{settingName}</DialogTitle>
|
||||
{!!dialogDescription && (
|
||||
<DialogContentText sx={{ paddingBottom: '10px' }}>{dialogDescription}</DialogContentText>
|
||||
)}
|
||||
{(!!dialogValueDisplayInfo.description || !!dialogValueDisplayInfo.disclaimer) && (
|
||||
<DialogContentText sx={{ paddingBottom: '10px' }} component="div">
|
||||
{dialogValueDisplayInfo.description && (
|
||||
<Typography
|
||||
variant="body1"
|
||||
sx={{
|
||||
whiteSpace: 'pre-line',
|
||||
}}
|
||||
>
|
||||
{t(dialogValueDisplayInfo.description as TranslationKey)}
|
||||
</Typography>
|
||||
)}
|
||||
{dialogValueDisplayInfo.disclaimer && (
|
||||
<Stack
|
||||
direction="row"
|
||||
sx={{
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
<InfoIcon color="warning" />
|
||||
<Typography
|
||||
variant="body1"
|
||||
sx={{
|
||||
marginLeft: '10px',
|
||||
marginTop: '5px',
|
||||
whiteSpace: 'pre-line',
|
||||
}}
|
||||
>
|
||||
{t(dialogValueDisplayInfo.disclaimer as TranslationKey)}
|
||||
</Typography>
|
||||
</Stack>
|
||||
)}
|
||||
</DialogContentText>
|
||||
)}
|
||||
<FormControl fullWidth>
|
||||
<Select
|
||||
id="dialog-select"
|
||||
value={dialogValue}
|
||||
onChange={(e) => setDialogValue(e.target.value as SettingValue)}
|
||||
>
|
||||
{values.map(([selectValue, { text: selectText }]) => (
|
||||
<MenuItem key={selectValue} value={selectValue}>
|
||||
{t(selectText as TranslationKey)}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={() => closeDialog()} color="primary">
|
||||
{t('global.button.cancel')}
|
||||
</Button>
|
||||
<Button onClick={() => updateSetting()} color="primary">
|
||||
{t('global.button.ok')}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -1,125 +0,0 @@
|
||||
/*
|
||||
* 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 Button from '@mui/material/Button';
|
||||
import Dialog from '@mui/material/Dialog';
|
||||
import DialogTitle from '@mui/material/DialogTitle';
|
||||
import ListItemText from '@mui/material/ListItemText';
|
||||
import DialogContent from '@mui/material/DialogContent';
|
||||
import DialogActions from '@mui/material/DialogActions';
|
||||
import ListItemButton from '@mui/material/ListItemButton';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider';
|
||||
import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs';
|
||||
import { TimePicker } from '@mui/x-date-pickers/TimePicker';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
export const TimeSetting = ({
|
||||
settingName,
|
||||
value,
|
||||
defaultValue,
|
||||
handleChange,
|
||||
}: {
|
||||
settingName: string;
|
||||
value: string;
|
||||
defaultValue: string;
|
||||
handleChange: (path: string) => void;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [isDialogOpen, setIsDialogOpen] = useState(false);
|
||||
const [dialogValue, setDialogValue] = useState(value);
|
||||
|
||||
useEffect(() => {
|
||||
if (!value) {
|
||||
return;
|
||||
}
|
||||
|
||||
setDialogValue(value);
|
||||
}, [value]);
|
||||
|
||||
const closeDialog = useCallback(
|
||||
(resetValue: boolean) => {
|
||||
setIsDialogOpen(false);
|
||||
|
||||
if (resetValue) {
|
||||
setDialogValue(value);
|
||||
}
|
||||
},
|
||||
[value],
|
||||
);
|
||||
|
||||
const closeDialogWithReset = useCallback(() => closeDialog(true), [closeDialog]);
|
||||
|
||||
const updateSetting = useCallback(
|
||||
(newValue: string, shouldCloseDialog: boolean = true) => {
|
||||
if (shouldCloseDialog) {
|
||||
closeDialog(false);
|
||||
}
|
||||
|
||||
const didValueChange = value !== newValue;
|
||||
if (!didValueChange) {
|
||||
return;
|
||||
}
|
||||
|
||||
handleChange(newValue);
|
||||
},
|
||||
[value, handleChange, closeDialog],
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<ListItemButton onClick={() => setIsDialogOpen(true)}>
|
||||
<ListItemText
|
||||
primary={settingName}
|
||||
secondary={dayjs(value, 'HH:mm').format('LT')}
|
||||
secondaryTypographyProps={{ style: { display: 'flex', flexDirection: 'column' } }}
|
||||
/>
|
||||
</ListItemButton>
|
||||
|
||||
<Dialog open={isDialogOpen} onClose={closeDialog}>
|
||||
<DialogContent>
|
||||
<DialogTitle sx={{ paddingLeft: 0 }}>{settingName}</DialogTitle>
|
||||
<LocalizationProvider dateAdapter={AdapterDayjs} adapterLocale={dayjs.locale()}>
|
||||
<TimePicker
|
||||
value={dayjs(dialogValue, 'HH:mm')}
|
||||
defaultValue={dayjs(defaultValue, 'HH:mm')}
|
||||
format="LT"
|
||||
onChange={(time) => setDialogValue(time?.format('HH:mm') ?? '00:00')}
|
||||
/>
|
||||
</LocalizationProvider>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
{defaultValue !== undefined ? (
|
||||
<Button
|
||||
onClick={() => {
|
||||
setDialogValue(defaultValue);
|
||||
updateSetting(defaultValue, false);
|
||||
}}
|
||||
color="primary"
|
||||
>
|
||||
{t('global.button.reset_to_default')}
|
||||
</Button>
|
||||
) : null}
|
||||
<Button onClick={closeDialogWithReset} color="primary">
|
||||
{t('global.button.cancel')}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
updateSetting(dialogValue);
|
||||
}}
|
||||
color="primary"
|
||||
>
|
||||
{t('global.button.ok')}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
SelectSetting,
|
||||
SelectSettingValue,
|
||||
SelectSettingValueDisplayInfo,
|
||||
} from '@/components/settings/SelectSetting.tsx';
|
||||
} from '@/modules/core/components/settings/SelectSetting.tsx';
|
||||
|
||||
const CHAPTERS_TO_DELETE = [0, 1, 2, 3, 4, 5] as const;
|
||||
const CHAPTERS_TO_DELETE_TO_TRANSLATION_KEY: {
|
||||
|
||||
@@ -11,11 +11,11 @@ import List from '@mui/material/List';
|
||||
import ListItem from '@mui/material/ListItem';
|
||||
import ListItemText from '@mui/material/ListItemText';
|
||||
import Switch from '@mui/material/Switch';
|
||||
import { NumberSetting } from '@/components/settings/NumberSetting.tsx';
|
||||
import { getPersistedServerSetting, usePersistedValue } from '@/util/usePersistedValue.tsx';
|
||||
import { NumberSetting } from '@/modules/core/components/settings/NumberSetting.tsx';
|
||||
import { getPersistedServerSetting, usePersistedValue } from '@/modules/core/hooks/usePersistedValue.tsx';
|
||||
import { updateMetadataServerSettings } from '@/lib/metadata/metadataServerSettings.ts';
|
||||
import { MetadataDownloadSettings, MetadataServerSettings } from '@/typings.ts';
|
||||
import { makeToast } from '@/components/util/Toast.tsx';
|
||||
import { makeToast } from '@/lib/ui/Toast.ts';
|
||||
|
||||
const MIN_LIMIT = 2;
|
||||
const MAX_LIMIT = 10;
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
/*
|
||||
* 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 { styled } from '@mui/material/styles';
|
||||
|
||||
export const CheckboxContainer = styled('div')({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
maxHeight: '170px',
|
||||
overflow: 'auto',
|
||||
});
|
||||
@@ -18,8 +18,8 @@ import {
|
||||
} from '@/components/settings/CategoriesInclusionSetting.tsx';
|
||||
import { GlobalUpdateSettingsEntries } from '@/components/settings/globalUpdate/GlobalUpdateSettingsEntries.tsx';
|
||||
import { GlobalUpdateSettingsInterval } from '@/components/settings/globalUpdate/GlobalUpdateSettingsInterval.tsx';
|
||||
import { requestManager } from '@/lib/requests/RequestManager.ts';
|
||||
import { makeToast } from '@/components/util/Toast.tsx';
|
||||
import { requestManager } from '@/lib/requests/requests/RequestManager.ts';
|
||||
import { makeToast } from '@/lib/ui/Toast.ts';
|
||||
import { ServerSettings } from '@/typings.ts';
|
||||
|
||||
type LibrarySettingsType = Pick<ServerSettings, 'updateMangas'>;
|
||||
|
||||
@@ -16,11 +16,12 @@ import Dialog from '@mui/material/Dialog';
|
||||
import DialogActions from '@mui/material/DialogActions';
|
||||
import DialogContent from '@mui/material/DialogContent';
|
||||
import DialogTitle from '@mui/material/DialogTitle';
|
||||
import { ServerSettings, TranslationKey } from '@/typings.ts';
|
||||
import { requestManager } from '@/lib/requests/RequestManager.ts';
|
||||
import { makeToast } from '@/components/util/Toast.tsx';
|
||||
import { CheckboxContainer } from '@/components/settings/globalUpdate/CheckboxContainer.ts';
|
||||
import { CheckboxInput } from '@/components/atoms/CheckboxInput.tsx';
|
||||
import { ServerSettings } from '@/typings.ts';
|
||||
import { requestManager } from '@/lib/requests/requests/RequestManager.ts';
|
||||
import { makeToast } from '@/lib/ui/Toast.ts';
|
||||
import { CheckboxContainer } from '@/modules/core/components/inputs/CheckboxContainer.ts';
|
||||
import { CheckboxInput } from '@/modules/core/components/inputs/CheckboxInput.tsx';
|
||||
import { TranslationKey } from '@/Base.types.ts';
|
||||
|
||||
type GlobalUpdateSkipEntriesSettings = Pick<
|
||||
ServerSettings,
|
||||
|
||||
@@ -12,9 +12,9 @@ import ListItem from '@mui/material/ListItem';
|
||||
import ListItemText from '@mui/material/ListItemText';
|
||||
import Switch from '@mui/material/Switch';
|
||||
import { useCallback } from 'react';
|
||||
import { requestManager } from '@/lib/requests/RequestManager.ts';
|
||||
import { NumberSetting } from '@/components/settings/NumberSetting.tsx';
|
||||
import { getPersistedServerSetting, usePersistedValue } from '@/util/usePersistedValue.tsx';
|
||||
import { requestManager } from '@/lib/requests/requests/RequestManager.ts';
|
||||
import { NumberSetting } from '@/modules/core/components/settings/NumberSetting.tsx';
|
||||
import { getPersistedServerSetting, usePersistedValue } from '@/modules/core/hooks/usePersistedValue.tsx';
|
||||
import { ServerSettings } from '@/typings.ts';
|
||||
|
||||
const DEFAULT_INTERVAL_HOURS = 12;
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
/*
|
||||
* 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 ListItemText from '@mui/material/ListItemText';
|
||||
import ListItemButton from '@mui/material/ListItemButton';
|
||||
import { useState } from 'react';
|
||||
import { TextSettingDialog, TextSettingDialogProps } from '@/components/settings/text/TextSettingDialog.tsx';
|
||||
|
||||
export type TextSettingProps = Omit<TextSettingDialogProps, 'isDialogOpen' | 'setIsDialogOpen' | 'value'> &
|
||||
Required<Pick<TextSettingDialogProps, 'value'>> & {
|
||||
disabled?: boolean;
|
||||
settingDescription?: string;
|
||||
};
|
||||
|
||||
export const TextSetting = (props: TextSettingProps) => {
|
||||
const [isDialogOpen, setIsDialogOpen] = useState(false);
|
||||
|
||||
const { settingName, settingDescription, value, isPassword = false, disabled = false } = props;
|
||||
|
||||
return (
|
||||
<>
|
||||
<ListItemButton disabled={disabled} onClick={() => setIsDialogOpen(true)}>
|
||||
<ListItemText
|
||||
primary={settingName}
|
||||
secondary={settingDescription ?? (isPassword ? value.replace(/./g, '*') : value)}
|
||||
secondaryTypographyProps={{
|
||||
sx: { display: 'flex', flexDirection: 'column', wordWrap: 'break-word' },
|
||||
}}
|
||||
/>
|
||||
</ListItemButton>
|
||||
|
||||
<TextSettingDialog {...props} isDialogOpen={isDialogOpen} setIsDialogOpen={setIsDialogOpen} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -1,108 +0,0 @@
|
||||
/*
|
||||
* 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 Button from '@mui/material/Button';
|
||||
import Dialog from '@mui/material/Dialog';
|
||||
import DialogTitle from '@mui/material/DialogTitle';
|
||||
import DialogContent from '@mui/material/DialogContent';
|
||||
import DialogContentText from '@mui/material/DialogContentText';
|
||||
import TextField from '@mui/material/TextField';
|
||||
import DialogActions from '@mui/material/DialogActions';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { PasswordTextField } from '@/components/atoms/PasswordTextField.tsx';
|
||||
|
||||
export type TextSettingDialogProps = {
|
||||
settingName: string;
|
||||
dialogTitle?: string;
|
||||
dialogDescription?: string;
|
||||
value?: string;
|
||||
handleChange: (value: string) => void;
|
||||
isPassword?: boolean;
|
||||
placeholder?: string;
|
||||
isDialogOpen: boolean;
|
||||
setIsDialogOpen: (open: boolean) => void;
|
||||
validate?: (value: string) => boolean;
|
||||
};
|
||||
|
||||
export const TextSettingDialog = ({
|
||||
settingName,
|
||||
dialogTitle = settingName,
|
||||
dialogDescription,
|
||||
value,
|
||||
handleChange,
|
||||
isPassword = false,
|
||||
placeholder = '',
|
||||
isDialogOpen,
|
||||
setIsDialogOpen,
|
||||
validate = () => true,
|
||||
}: TextSettingDialogProps) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [dialogValue, setDialogValue] = useState(value ?? '');
|
||||
const [isValidValue, setIsValidValue] = useState(true);
|
||||
|
||||
const TextFieldComponent = useMemo(() => (isPassword ? PasswordTextField : TextField), [isPassword]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!value) {
|
||||
return;
|
||||
}
|
||||
|
||||
setDialogValue(value);
|
||||
}, [value]);
|
||||
|
||||
const closeDialog = (resetValue: boolean = true) => {
|
||||
if (resetValue) {
|
||||
setDialogValue(value ?? '');
|
||||
}
|
||||
|
||||
setIsDialogOpen(false);
|
||||
};
|
||||
|
||||
const updateSetting = () => {
|
||||
closeDialog(false);
|
||||
handleChange(dialogValue);
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={isDialogOpen} onClose={() => closeDialog()} fullWidth>
|
||||
<DialogContent>
|
||||
<DialogTitle sx={{ paddingLeft: 0 }}>{dialogTitle}</DialogTitle>
|
||||
{!!dialogDescription && (
|
||||
<DialogContentText sx={{ paddingBottom: '10px' }}>{dialogDescription}</DialogContentText>
|
||||
)}
|
||||
<TextFieldComponent
|
||||
sx={{
|
||||
width: '100%',
|
||||
margin: 'auto',
|
||||
}}
|
||||
autoFocus
|
||||
placeholder={placeholder}
|
||||
value={dialogValue}
|
||||
error={!isValidValue}
|
||||
helperText={!isValidValue ? t('global.error.label.invalid_input') : ''}
|
||||
onChange={(e) => {
|
||||
const newValue = e.target.value;
|
||||
|
||||
setIsValidValue(validate(newValue));
|
||||
setDialogValue(newValue);
|
||||
}}
|
||||
/>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={() => closeDialog()} color="primary">
|
||||
{t('global.button.cancel')}
|
||||
</Button>
|
||||
<Button onClick={() => updateSetting()} disabled={!isValidValue} color="primary">
|
||||
{t('global.button.ok')}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
@@ -12,11 +12,11 @@ import ListItem from '@mui/material/ListItem';
|
||||
import ListItemText from '@mui/material/ListItemText';
|
||||
import Switch from '@mui/material/Switch';
|
||||
import { useCallback } from 'react';
|
||||
import { requestManager } from '@/lib/requests/RequestManager.ts';
|
||||
import { NumberSetting } from '@/components/settings/NumberSetting.tsx';
|
||||
import { getPersistedServerSetting, usePersistedValue } from '@/util/usePersistedValue.tsx';
|
||||
import { requestManager } from '@/lib/requests/requests/RequestManager.ts';
|
||||
import { NumberSetting } from '@/modules/core/components/settings/NumberSetting.tsx';
|
||||
import { getPersistedServerSetting, usePersistedValue } from '@/modules/core/hooks/usePersistedValue.tsx';
|
||||
import { ServerSettings } from '@/typings.ts';
|
||||
import { makeToast } from '@/components/util/Toast.tsx';
|
||||
import { makeToast } from '@/lib/ui/Toast.ts';
|
||||
|
||||
const DEFAULT_VALUE = 23;
|
||||
const MIN_VALUE = 1;
|
||||
|
||||
Reference in New Issue
Block a user