Simplify disabling image processing for mime-type

This commit is contained in:
schroda
2025-12-23 02:31:25 +01:00
parent 5def0ea818
commit 8da12f7a6e
6 changed files with 128 additions and 99 deletions

View File

@@ -217,7 +217,7 @@
"call_timeout": "Call timeout",
"compression_level": "Compression level",
"connect_timeout": "Connect timeout",
"description": "In case no MIME-Type is defined, the \"default\" one will be used for the processing.\nSet \"{{value}}\" as the target type to disable processing for a MIME-Type",
"description": "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",
"headers": {
"button": "Headers ({{count}})",
"name": "Name",

View File

@@ -18,6 +18,7 @@ import {
IMAGE_PROCESSING_COMPRESSION,
IMAGE_PROCESSING_CONNECT_TIMEOUT,
MIME_TYPE_PREFIX,
TARGET_DISABLED,
} from '@/features/settings/Settings.constants.ts';
import {
SettingsDownloadConversion,
@@ -92,7 +93,13 @@ export const containsInvalidConversion = (conversions: TSettingsDownloadConversi
);
export const getTargetMode = (target: string): ImageProcessingTargetMode => {
if (isUrlTargetMode(target)) {
const normalizedTargetMode = normalizeMimeType(target);
if (normalizedTargetMode === TARGET_DISABLED) {
return ImageProcessingTargetMode.DISABLED;
}
if (isUrlTargetMode(normalizedTargetMode)) {
return ImageProcessingTargetMode.URL;
}
@@ -115,7 +122,7 @@ export const addStableIdToConversions = (
// eslint-disable-next-line no-plusplus
id: (conversion as TSettingsDownloadConversion).id ?? COUNTER++,
...conversion,
mode: getTargetMode(conversion.target),
mode: getTargetMode(normalizeMimeType(conversion.target)),
headers: conversion.headers ? addStableIdToHeaders(conversion.headers) : null,
}));

View File

@@ -283,6 +283,9 @@ const IMAGE_PROCESSING_TARGET_MODES = Object.values(ImageProcessingTargetMode);
const IMAGE_PROCESSING_TARGET_MODES_TO_TRANSLATION_KEY: {
[flavor in ImageProcessingTargetMode]: SelectSettingValueDisplayInfo;
} = {
[ImageProcessingTargetMode.DISABLED]: {
text: 'global.label.disabled',
},
[ImageProcessingTargetMode.IMAGE]: {
text: 'download.settings.conversion.target_modes.image.title',
description: 'download.settings.conversion.target_modes.image.description',
@@ -298,6 +301,7 @@ export const IMAGE_PROCESSING_TARGET_MODES_SELECT_VALUES: SelectSettingValue<Ima
export const IMAGE_PROCESSING_INPUT_WIDTH = 250;
export const DEFAULT_MIME_TYPE = 'default';
export const MIME_TYPE_PREFIX = 'image/';
export const TARGET_DISABLED = 'none';
export const IMAGE_PROCESSING_TYPE_TO_TRANSLATION: Record<ImageProcessingType, TranslationKey> = {
[ImageProcessingType.DOWNLOAD]: 'download.settings.conversion.title',

View File

@@ -60,6 +60,7 @@ export type GlobalUpdateSkipEntriesSettings = Pick<
export type LibrarySettingsType = Pick<ServerSettings, 'updateMangas'>;
export enum ImageProcessingTargetMode {
DISABLED = 'disabled',
IMAGE = 'image',
URL = 'url',
}

View File

@@ -29,6 +29,7 @@ import {
IMAGE_PROCESSING_CONNECT_TIMEOUT,
IMAGE_PROCESSING_INPUT_WIDTH,
IMAGE_PROCESSING_TARGET_MODES_SELECT_VALUES,
TARGET_DISABLED,
} from '@/features/settings/Settings.constants.ts';
import { Select } from '@/base/components/inputs/Select.tsx';
import { TypographyMaxLines } from '@/base/components/texts/TypographyMaxLines.tsx';
@@ -58,6 +59,7 @@ export const Processing = ({
const [areHeadersCollapsed, setAreHeadersCollapsed] = useState(true);
const isDisabledMode = mode === ImageProcessingTargetMode.DISABLED;
const isImageMode = mode === ImageProcessingTargetMode.IMAGE;
const isCallTimeoutValid = isValidCallTimeoutSetting(callTimeout);
@@ -124,6 +126,8 @@ export const Processing = ({
onChange({
...conversion,
mode: e.target.value,
target:
e.target.value === ImageProcessingTargetMode.DISABLED ? TARGET_DISABLED : '',
})
}
>
@@ -134,105 +138,117 @@ export const Processing = ({
))}
</Select>
</FormControl>
<MimeTypeTextField
mode={mode}
shouldAutoFocus={false}
isDefault={false}
isDuplicate={false}
label={t('download.settings.conversion.target')}
value={target}
onUpdate={(value) =>
onChange({
...conversion,
target: value,
})
}
/>
{isImageMode ? (
<TextField
sx={{ width: IMAGE_PROCESSING_INPUT_WIDTH }}
label={t('download.settings.conversion.compression_level')}
value={compressionLevel ?? ''}
type="number"
error={!isCompressionLevelValid}
helperText={!isCompressionLevelValid ? t('global.error.label.invalid_input') : ''}
slotProps={{
input: {
inputProps: IMAGE_PROCESSING_COMPRESSION,
},
}}
onChange={(e) => {
{!isDisabledMode && (
<MimeTypeTextField
mode={mode}
shouldAutoFocus={false}
isDefault={false}
isDuplicate={false}
label={t('download.settings.conversion.target')}
value={target}
onUpdate={(value) =>
onChange({
...conversion,
compressionLevel: e.target.value ? Number(e.target.value) : null,
});
}}
target: value,
})
}
/>
) : (
<>
<TextField
sx={{ width: IMAGE_PROCESSING_INPUT_WIDTH }}
label={t('download.settings.conversion.call_timeout')}
value={callTimeout ? d(callTimeout).seconds.inWholeSeconds : ''}
type="number"
error={!isCallTimeoutValid}
helperText={!isCallTimeoutValid ? t('global.error.label.invalid_input') : ''}
slotProps={{
input: {
inputProps: IMAGE_PROCESSING_CALL_TIMEOUT,
endAdornment: (
<InputAdornment position="end">
{t('global.date.label.second_other')}
</InputAdornment>
),
},
}}
onChange={(e) => {
onChange({
...conversion,
callTimeout: e.target.value
? d(Number(e.target.value)).seconds.toISOString()
: null,
});
}}
/>
<TextField
sx={{ width: IMAGE_PROCESSING_INPUT_WIDTH }}
label={t('download.settings.conversion.connect_timeout')}
value={connectTimeout ? d(connectTimeout).seconds.inWholeSeconds : ''}
type="number"
error={!isConnectTimeoutValid}
helperText={!isConnectTimeoutValid ? t('global.error.label.invalid_input') : ''}
slotProps={{
input: {
inputProps: IMAGE_PROCESSING_CONNECT_TIMEOUT,
endAdornment: (
<InputAdornment position="end">
{t('global.date.label.second_other')}
</InputAdornment>
),
},
}}
onChange={(e) => {
onChange({
...conversion,
connectTimeout: e.target.value
? d(Number(e.target.value)).seconds.toISOString()
: null,
});
}}
/>
<Button
onClick={() => setAreHeadersCollapsed(!areHeadersCollapsed)}
variant={areHeadersCollapsed ? 'outlined' : 'contained'}
sx={{ height: textFieldHeight }}
>
{t('download.settings.conversion.headers.button', {
count: headers?.length ?? 0,
})}
</Button>
</>
)}
{(() => {
if (isDisabledMode) {
return null;
}
if (isImageMode) {
return (
<TextField
sx={{ width: IMAGE_PROCESSING_INPUT_WIDTH }}
label={t('download.settings.conversion.compression_level')}
value={compressionLevel ?? ''}
type="number"
error={!isCompressionLevelValid}
helperText={!isCompressionLevelValid ? t('global.error.label.invalid_input') : ''}
slotProps={{
input: {
inputProps: IMAGE_PROCESSING_COMPRESSION,
},
}}
onChange={(e) => {
onChange({
...conversion,
compressionLevel: e.target.value ? Number(e.target.value) : null,
});
}}
/>
);
}
return (
<>
<TextField
sx={{ width: IMAGE_PROCESSING_INPUT_WIDTH }}
label={t('download.settings.conversion.call_timeout')}
value={callTimeout ? d(callTimeout).seconds.inWholeSeconds : ''}
type="number"
error={!isCallTimeoutValid}
helperText={!isCallTimeoutValid ? t('global.error.label.invalid_input') : ''}
slotProps={{
input: {
inputProps: IMAGE_PROCESSING_CALL_TIMEOUT,
endAdornment: (
<InputAdornment position="end">
{t('global.date.label.second_other')}
</InputAdornment>
),
},
}}
onChange={(e) => {
onChange({
...conversion,
callTimeout: e.target.value
? d(Number(e.target.value)).seconds.toISOString()
: null,
});
}}
/>
<TextField
sx={{ width: IMAGE_PROCESSING_INPUT_WIDTH }}
label={t('download.settings.conversion.connect_timeout')}
value={connectTimeout ? d(connectTimeout).seconds.inWholeSeconds : ''}
type="number"
error={!isConnectTimeoutValid}
helperText={!isConnectTimeoutValid ? t('global.error.label.invalid_input') : ''}
slotProps={{
input: {
inputProps: IMAGE_PROCESSING_CONNECT_TIMEOUT,
endAdornment: (
<InputAdornment position="end">
{t('global.date.label.second_other')}
</InputAdornment>
),
},
}}
onChange={(e) => {
onChange({
...conversion,
connectTimeout: e.target.value
? d(Number(e.target.value)).seconds.toISOString()
: null,
});
}}
/>
<Button
onClick={() => setAreHeadersCollapsed(!areHeadersCollapsed)}
variant={areHeadersCollapsed ? 'outlined' : 'contained'}
sx={{ height: textFieldHeight }}
>
{t('download.settings.conversion.headers.button', {
count: headers?.length ?? 0,
})}
</Button>
</>
);
})()}
</Stack>
<CustomTooltip disabled={isDisabled} title={t('chapter.action.download.delete.label.action')}>
<IconButton

View File

@@ -14,6 +14,7 @@ import Button from '@mui/material/Button';
import {
IMAGE_PROCESSING_TYPE_TO_SETTING,
IMAGE_PROCESSING_TYPE_TO_TRANSLATION,
TARGET_DISABLED,
} from '@/features/settings/Settings.constants.ts';
import { requestManager } from '@/lib/requests/RequestManager.ts';
import { ImageProcessingType, ServerSettings } from '@/features/settings/Settings.types.ts';
@@ -92,7 +93,7 @@ export const ImageProcessingSetting = ({ type }: { type: ImageProcessingType })
return (
<Stack sx={{ p: 2, gap: 3 }}>
<Typography>{t('download.settings.conversion.description', { value: 'none' })}</Typography>
<Typography>{t('download.settings.conversion.description', { value: TARGET_DISABLED })}</Typography>
<Stack sx={{ flexDirection: 'column', gap: 5 }}>
{tmpConversions.map((conversion, index) => {
const { mimeType } = conversion;