diff --git a/src/App.tsx b/src/App.tsx index fcaf9832..2cfaa842 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -60,6 +60,10 @@ const { DownloadSettings } = loadable( () => import('@/features/downloads/screens/DownloadSettings.tsx'), lazyLoadFallback, ); +const { DownloadConversionSettings } = loadable( + () => import('@/features/downloads/screens/DownloadConversionSettings.tsx'), + lazyLoadFallback, +); const { ServerSettings } = loadable(() => import('@/features/settings/screens/ServerSettings.tsx'), lazyLoadFallback); const { BrowseSettings } = loadable(() => import('@/features/browse/screens/BrowseSettings.tsx'), lazyLoadFallback); const { WebUISettings } = loadable(() => import('@/features/settings/screens/WebUISettings.tsx'), lazyLoadFallback); @@ -221,10 +225,13 @@ const MainApp = () => { element={} /> - } - /> + + } /> + } + /> + } /> } /> } /> diff --git a/src/base/AppRoute.constants.ts b/src/base/AppRoute.constants.ts index 9f3dccca..57a1a8e3 100644 --- a/src/base/AppRoute.constants.ts +++ b/src/base/AppRoute.constants.ts @@ -69,6 +69,12 @@ export const AppRoutes = { download: { match: 'download', path: '/settings/download', + childRoutes: { + conversions: { + match: 'conversions', + path: '/settings/download/conversions', + }, + }, }, backup: { match: 'backup', diff --git a/src/features/downloads/components/DownloadConversionSetting.tsx b/src/features/downloads/components/DownloadConversionSetting.tsx index a44626f1..1d00b98c 100644 --- a/src/features/downloads/components/DownloadConversionSetting.tsx +++ b/src/features/downloads/components/DownloadConversionSetting.tsx @@ -7,24 +7,21 @@ */ import { useTranslation } from 'react-i18next'; -import ListItemButton from '@mui/material/ListItemButton'; -import ListItemText from '@mui/material/ListItemText'; -import Dialog from '@mui/material/Dialog'; -import DialogTitle from '@mui/material/DialogTitle'; -import DialogContent from '@mui/material/DialogContent'; import { useState } from 'react'; import TextField from '@mui/material/TextField'; import Stack from '@mui/material/Stack'; import DeleteIcon from '@mui/icons-material/Delete'; import IconButton from '@mui/material/IconButton'; import InputAdornment from '@mui/material/InputAdornment'; +import Typography from '@mui/material/Typography'; import Button from '@mui/material/Button'; -import DialogActions from '@mui/material/DialogActions'; -import DialogContentText from '@mui/material/DialogContentText'; -import { SettingsDownloadConversion } from '@/lib/graphql/generated/graphql.ts'; -import { DOWNLOAD_CONVERSION_COMPRESSION } from '@/features/downloads/Downloads.constants.ts'; -import { CustomTooltip } from '@/base/components/CustomTooltip.tsx'; +import { useTheme } from '@mui/material/styles'; import { TypographyMaxLines } from '@/base/components/texts/TypographyMaxLines.tsx'; +import { CustomTooltip } from '@/base/components/CustomTooltip.tsx'; +import { DOWNLOAD_CONVERSION_COMPRESSION } from '@/features/downloads/Downloads.constants.ts'; +import { SettingsDownloadConversion } from '@/lib/graphql/generated/graphql.ts'; + +const INPUT_WIDTH = 250; const DEFAULT_MIME_TYPE = 'default'; const MIME_TYPE_PREFIX = 'image/'; @@ -127,7 +124,7 @@ const MimeTypeTextField = ({ return ( { const { t } = useTranslation(); + const theme = useTheme(); const isCompressionLevelValid = isValidCompressionLevel(compressionLevel); const isDefault = isDefaultMimeType(mimeType) && !isDuplicate; @@ -179,6 +177,10 @@ const Conversion = ({ flexDirection: 'row', alignItems: 'baseline', flexWrap: 'wrap', + [theme.breakpoints.down('md')]: { + flexDirection: 'column', + width: '100%', + }, }} > { const { t } = useTranslation(); - const [isDialogOpen, setIsDialogOpen] = useState(false); const [tmpConversions, setTmpConversions] = useState(normalizeConversions(maybeAddDefault(conversions))); const [focusedMimeTypeTextFieldIndex, setFocusedMimeTypeTextFieldIndex] = useState(DEFAULT_FOCUS_INDEX); @@ -264,98 +266,63 @@ export const DownloadConversionSetting = ({ const hasChanged = didUpdateConversions(normalizeConversions(maybeAddDefault(conversions)), tmpConversions); - const onClose = (newConversions: SettingsDownloadConversion[] = conversions) => { - setTmpConversions(normalizeConversions(maybeAddDefault(newConversions))); - setIsDialogOpen(false); - }; - - const onCancel = () => { - onClose(conversions); - }; - const onSubmit = async () => { try { await updateSetting(toValidServerConversions(tmpConversions)); - onClose(toValidServerConversions(tmpConversions)); } catch (e) { // ignore error } }; return ( - <> - setIsDialogOpen(true)}> - `${conversion.mimeType} → ${conversion.target}`) - .join('; ')} - secondaryTypographyProps={{ style: { display: 'flex', flexDirection: 'column' } }} - /> - - - {t('download.settings.conversion.title')} - - - {t('download.settings.conversion.description', { value: 'none' })} - - - {tmpConversions.map((conversion, index) => { - const { mimeType } = conversion; + + {t('download.settings.conversion.description', { value: 'none' })} + + {tmpConversions.map((conversion, index) => { + const { mimeType } = conversion; - const isDuplicate = isDuplicateConversion(mimeType, index, tmpConversions); - const shouldAutoFocusMimeTypeTextField = index === focusedMimeTypeTextFieldIndex; + const isDuplicate = isDuplicateConversion(mimeType, index, tmpConversions); + const shouldAutoFocusMimeTypeTextField = index === focusedMimeTypeTextFieldIndex; - return ( - - setFocusedMimeTypeTextFieldIndex(focus ? index : DEFAULT_FOCUS_INDEX) - } - shouldAutoFocusMimeTypeTextField={shouldAutoFocusMimeTypeTextField} - onChange={(newConversion) => { - setTmpConversions((prev) => - maybeAddDefault( - prev.toSpliced(index, 1, ...(newConversion ? [newConversion] : [])), - ), - ); - }} - /> - ); - })} - - - - - - - - - - - - - + /> + ); + })} + + + + + + ); }; diff --git a/src/features/downloads/screens/DownloadConversionSettings.tsx b/src/features/downloads/screens/DownloadConversionSettings.tsx new file mode 100644 index 00000000..9942bad1 --- /dev/null +++ b/src/features/downloads/screens/DownloadConversionSettings.tsx @@ -0,0 +1,52 @@ +/* + * 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 { useAppTitle } from '@/features/navigation-bar/hooks/useAppTitle.ts'; +import { requestManager } from '@/lib/requests/RequestManager.ts'; +import { getErrorMessage } from '@/lib/HelperFunctions.ts'; +import { LoadingPlaceholder } from '@/base/components/feedback/LoadingPlaceholder.tsx'; +import { EmptyViewAbsoluteCentered } from '@/base/components/feedback/EmptyViewAbsoluteCentered.tsx'; +import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.ts'; +import { DownloadConversionSetting } from '@/features/downloads/components/DownloadConversionSetting.tsx'; +import { makeToast } from '@/base/utils/Toast.ts'; +import { ServerSettings } from '@/features/settings/Settings.types.ts'; + +export const DownloadConversionSettings = () => { + const { t } = useTranslation(); + + useAppTitle(t('download.title.download')); + + const { data, loading, error, refetch } = requestManager.useGetServerSettings({ + notifyOnNetworkStatusChange: true, + }); + const [mutateSettings] = requestManager.useUpdateServerSettings(); + + const updateSetting = (value: ServerSettings['downloadConversions']): Promise => { + const mutation = mutateSettings({ variables: { input: { settings: { downloadConversions: value } } } }); + mutation.catch((e) => makeToast(t('global.error.label.failed_to_save_changes'), 'error', getErrorMessage(e))); + + return mutation; + }; + + if (loading) { + return ; + } + + if (error) { + return ( + refetch().catch(defaultPromiseErrorHandler('DownloadConversionSetting::refetch'))} + /> + ); + } + + return ; +}; diff --git a/src/features/downloads/screens/DownloadSettings.tsx b/src/features/downloads/screens/DownloadSettings.tsx index 4d639820..deab1eea 100644 --- a/src/features/downloads/screens/DownloadSettings.tsx +++ b/src/features/downloads/screens/DownloadSettings.tsx @@ -32,7 +32,8 @@ import { MetadataDownloadSettings } from '@/features/downloads/Downloads.types.t import { ServerSettings } from '@/features/settings/Settings.types.ts'; import { getErrorMessage } from '@/lib/HelperFunctions.ts'; import { useAppTitle } from '@/features/navigation-bar/hooks/useAppTitle.ts'; -import { DownloadConversionSetting } from '@/features/downloads/components/DownloadConversionSetting.tsx'; +import { ListItemLink } from '@/base/components/lists/ListItemLink.tsx'; +import { AppRoutes } from '@/base/AppRoute.constants.ts'; type DownloadSettingsType = Pick< ServerSettings, @@ -128,10 +129,9 @@ export const DownloadSettings = () => { onChange={(e) => updateSetting('downloadAsCbz', e.target.checked)} /> - updateSetting('downloadConversions', conversions)} - /> + + +