2025-07-20 17:37:39 +02:00
/*
* 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 { useState } from 'react' ;
import Stack from '@mui/material/Stack' ;
2025-12-07 03:32:28 +01:00
import Typography from '@mui/material/Typography' ;
2025-07-20 17:37:39 +02:00
import Button from '@mui/material/Button' ;
2026-01-10 19:45:02 +01:00
import { useLingui } from '@lingui/react/macro' ;
2025-12-22 22:04:05 +01:00
import {
IMAGE_PROCESSING_TYPE_TO_SETTING ,
IMAGE_PROCESSING_TYPE_TO_TRANSLATION ,
} from '@/features/settings/Settings.constants.ts' ;
2025-12-22 21:11:12 +01:00
import { requestManager } from '@/lib/requests/RequestManager.ts' ;
2026-05-15 20:40:31 +02:00
import type { ImageProcessingType } from '@/features/settings/Settings.types.ts' ;
2025-12-22 21:11:12 +01:00
import { makeToast } from '@/base/utils/Toast.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 { useAppTitle } from '@/features/navigation-bar/hooks/useAppTitle.ts' ;
2025-12-22 21:33:23 +01:00
import {
addStableIdToConversions ,
containsInvalidConversion ,
didUpdateConversions ,
isDuplicateConversion ,
maybeAddDefault ,
normalizeConversions ,
toValidServerConversions ,
} from '@/features/settings/ImageProcessing.utils.ts' ;
import { Processing } from '@/features/settings/components/images/Processing.tsx' ;
2026-03-20 03:29:45 +01:00
import { STABLE_EMPTY_ARRAY } from '@/base/Base.constants.ts' ;
2026-05-15 20:40:31 +02:00
import type { PartialSettingsType } from '@/lib/graphql/generated/graphql-base.types.ts' ;
2025-12-22 16:49:10 +01:00
2025-12-22 22:04:05 +01:00
export const ImageProcessingSetting = ({ type } : { type : ImageProcessingType }) => {
2026-01-10 19:45:02 +01:00
const { t } = useLingui ();
2025-07-20 17:37:39 +02:00
2025-12-22 22:04:05 +01:00
useAppTitle ( t ( IMAGE_PROCESSING_TYPE_TO_TRANSLATION [ type ]));
2025-12-22 21:11:12 +01:00
2026-05-02 17:37:38 +02:00
const { data , loading , error , refetch } = requestManager . useGetServerSettings ();
2025-12-22 21:11:12 +01:00
const [ mutateSettings ] = requestManager . useUpdateServerSettings ();
2025-12-22 22:04:05 +01:00
const settingKey = IMAGE_PROCESSING_TYPE_TO_SETTING [ type ];
2025-12-22 21:11:12 +01:00
2026-03-20 03:29:45 +01:00
const conversions = data ? . settings ? .[ settingKey ] ?? STABLE_EMPTY_ARRAY ;
2025-12-22 21:11:12 +01:00
2025-12-22 16:49:10 +01:00
const [ tmpConversions , setTmpConversions ] = useState (
normalizeConversions ( maybeAddDefault ( addStableIdToConversions ( conversions ))),
);
2025-07-20 17:37:39 +02:00
const hasInvalidConversion = containsInvalidConversion ( tmpConversions );
2025-12-22 16:49:10 +01:00
const hasChanged = didUpdateConversions (
normalizeConversions ( maybeAddDefault ( addStableIdToConversions ( conversions ))),
tmpConversions ,
);
2025-07-20 17:37:39 +02:00
2026-05-15 20:40:31 +02:00
const updateSetting = ( value : PartialSettingsType [ typeof settingKey ]) : Promise < any > => {
2025-12-22 22:04:05 +01:00
const mutation = mutateSettings ({ variables : { input : { settings : { [ settingKey ] : value } } } });
2026-01-10 19:45:02 +01:00
mutation . catch (( e ) => makeToast ( t `Failed to save changes` , 'error' , getErrorMessage ( e )));
2025-12-22 21:11:12 +01:00
return mutation ;
};
2025-09-13 14:13:32 +02:00
const onSubmit = async () => {
try {
await updateSetting ( toValidServerConversions ( tmpConversions ));
} catch ( e ) {
// ignore error
}
};
2026-03-15 01:42:37 +01:00
if ( loading ) {
return < LoadingPlaceholder />;
}
if ( error ) {
return (
< EmptyViewAbsoluteCentered
message = { t `Unable to load data` }
messageExtra = { getErrorMessage ( error )}
retry = {() => refetch (). catch ( defaultPromiseErrorHandler ( 'ImageProcessingSetting::refetch' ))}
/>
);
}
2025-07-20 17:37:39 +02:00
return (
2025-12-28 02:07:52 +01:00
< Stack sx = {{ p : 2 , gap : 5 }}>
2026-01-10 19:45:02 +01:00
< Typography >
{ t `In case no MIME-Type is defined, the "default" one will be used for the processing. \ nSet the target mode to disabled to prevent processing for a MIME-Type` }
</ Typography >
2025-12-07 03:32:28 +01:00
< Stack sx = {{ flexDirection : 'column' , gap : 5 }}>
{ tmpConversions . map (( conversion , index ) => {
const { mimeType } = conversion ;
2025-07-20 17:37:39 +02:00
2025-12-07 03:32:28 +01:00
const isDuplicate = isDuplicateConversion ( mimeType , index , tmpConversions );
2025-07-20 17:37:39 +02:00
2025-12-07 03:32:28 +01:00
return (
2025-12-22 21:33:23 +01:00
< Processing
2025-12-22 16:49:10 +01:00
key = { conversion . id }
2025-12-07 03:32:28 +01:00
conversion = { conversion }
isDuplicate = { isDuplicate }
onChange = {( newConversion ) => {
setTmpConversions (( prev ) =>
maybeAddDefault (
prev . toSpliced ( index , 1 , ...( newConversion ? [ newConversion ] : [])),
),
);
2025-07-20 17:37:39 +02:00
}}
2025-12-07 03:32:28 +01:00
/>
);
})}
</ Stack >
< Stack
direction = "row"
sx = {{
2025-12-22 16:49:10 +01:00
gap : 1 ,
2025-12-07 03:32:28 +01:00
}}
>
< Button
variant = "outlined"
onClick = {() => {
2025-12-22 16:49:10 +01:00
setTmpConversions (( prev ) => [
... prev ,
2025-12-22 21:33:23 +01:00
... addStableIdToConversions ([
{
mimeType : '' ,
target : '' ,
compressionLevel : null ,
headers : null ,
callTimeout : null ,
connectTimeout : null ,
},
]),
2025-12-22 16:49:10 +01:00
]);
2025-12-07 03:32:28 +01:00
}}
>
2026-01-10 19:45:02 +01:00
{ t `Add` }
2025-12-07 03:32:28 +01:00
</ Button >
< Button variant = "contained" disabled = { hasInvalidConversion || ! hasChanged } onClick = { onSubmit }>
2026-01-10 19:45:02 +01:00
{ t `Save` }
2025-12-07 03:32:28 +01:00
</ Button >
</ Stack >
</ Stack >
2025-07-20 17:37:39 +02:00
);
};