Improve image processing settings url target adding search parameters
This commit is contained in:
@@ -225,6 +225,10 @@
|
|||||||
"value": "Value"
|
"value": "Value"
|
||||||
},
|
},
|
||||||
"mime_type": "MIME-Type",
|
"mime_type": "MIME-Type",
|
||||||
|
"search_params": {
|
||||||
|
"button": "Search parameters ({{count}})",
|
||||||
|
"title": "Search parameters"
|
||||||
|
},
|
||||||
"target": "MIME-Type target",
|
"target": "MIME-Type target",
|
||||||
"target_modes": {
|
"target_modes": {
|
||||||
"image": {
|
"image": {
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import { d } from 'koration';
|
|||||||
import {
|
import {
|
||||||
ImageProcessingTargetMode,
|
ImageProcessingTargetMode,
|
||||||
TSettingsDownloadConversion,
|
TSettingsDownloadConversion,
|
||||||
TSettingsDownloadConversionHeader,
|
TSettingsDownloadConversionKeyValueItem,
|
||||||
} from '@/features/settings/Settings.types.ts';
|
} from '@/features/settings/Settings.types.ts';
|
||||||
import {
|
import {
|
||||||
DEFAULT_MIME_TYPE,
|
DEFAULT_MIME_TYPE,
|
||||||
@@ -21,10 +21,13 @@ import {
|
|||||||
TARGET_DISABLED,
|
TARGET_DISABLED,
|
||||||
} from '@/features/settings/Settings.constants.ts';
|
} from '@/features/settings/Settings.constants.ts';
|
||||||
import {
|
import {
|
||||||
|
Maybe,
|
||||||
SettingsDownloadConversion,
|
SettingsDownloadConversion,
|
||||||
SettingsDownloadConversionHeader,
|
SettingsDownloadConversionHeader,
|
||||||
SettingsDownloadConversionType,
|
SettingsDownloadConversionType,
|
||||||
} from '@/lib/graphql/generated/graphql.ts';
|
} from '@/lib/graphql/generated/graphql.ts';
|
||||||
|
import { UrlUtil } from '@/lib/UrlUtil.ts';
|
||||||
|
import { jsonSaveParse } from '@/lib/HelperFunctions.ts';
|
||||||
|
|
||||||
let COUNTER = 0;
|
let COUNTER = 0;
|
||||||
|
|
||||||
@@ -33,14 +36,14 @@ const normalizeMimeType = (mimeType: string): string => mimeType.replace(MIME_TY
|
|||||||
export const isDefaultMimeType = (mimeType: string): boolean =>
|
export const isDefaultMimeType = (mimeType: string): boolean =>
|
||||||
normalizeMimeType(mimeType.toLowerCase().trim()) === DEFAULT_MIME_TYPE;
|
normalizeMimeType(mimeType.toLowerCase().trim()) === DEFAULT_MIME_TYPE;
|
||||||
|
|
||||||
export const isDuplicateHeader = (
|
export const isDuplicateKeyValueItem = (
|
||||||
header: string,
|
header: string,
|
||||||
index: number,
|
index: number,
|
||||||
headers: SettingsDownloadConversionType['headers'],
|
headers: SettingsDownloadConversionType['headers'],
|
||||||
): boolean => headers?.slice(0, index).some(({ name }) => name === header) ?? false;
|
): boolean => headers?.slice(0, index).some(({ name }) => name === header) ?? false;
|
||||||
|
|
||||||
const hasDuplicateHeaders = (headers: SettingsDownloadConversionType['headers']): boolean =>
|
const hasDuplicateKeyValueItems = (headers: SettingsDownloadConversionType['headers']): boolean =>
|
||||||
headers?.some((header, index) => isDuplicateHeader(header.name, index, headers)) ?? false;
|
headers?.some((header, index) => isDuplicateKeyValueItem(header.name, index, headers)) ?? false;
|
||||||
|
|
||||||
export const isDuplicateConversion = (
|
export const isDuplicateConversion = (
|
||||||
mimeType: string,
|
mimeType: string,
|
||||||
@@ -82,14 +85,15 @@ const isInvalidTarget = (target: string, mode: ImageProcessingTargetMode, mimeTy
|
|||||||
|
|
||||||
export const containsInvalidConversion = (conversions: TSettingsDownloadConversion[]): boolean =>
|
export const containsInvalidConversion = (conversions: TSettingsDownloadConversion[]): boolean =>
|
||||||
conversions.some(
|
conversions.some(
|
||||||
({ mimeType, compressionLevel, target, callTimeout, connectTimeout, headers, mode }, index) =>
|
({ mimeType, compressionLevel, target, callTimeout, connectTimeout, headers, searchParams, mode }, index) =>
|
||||||
isUnsetConversion(mimeType, target) ||
|
isUnsetConversion(mimeType, target) ||
|
||||||
isInvalidTarget(target, mode, mimeType) ||
|
isInvalidTarget(target, mode, mimeType) ||
|
||||||
!isValidCompressionLevel(compressionLevel) ||
|
!isValidCompressionLevel(compressionLevel) ||
|
||||||
!isValidCallTimeoutSetting(callTimeout) ||
|
!isValidCallTimeoutSetting(callTimeout) ||
|
||||||
!isValidConnectTimeoutSetting(connectTimeout) ||
|
!isValidConnectTimeoutSetting(connectTimeout) ||
|
||||||
isDuplicateConversion(mimeType, index, conversions) ||
|
isDuplicateConversion(mimeType, index, conversions) ||
|
||||||
hasDuplicateHeaders(headers),
|
hasDuplicateKeyValueItems(headers) ||
|
||||||
|
hasDuplicateKeyValueItems(searchParams),
|
||||||
);
|
);
|
||||||
|
|
||||||
export const getTargetMode = (target: string): ImageProcessingTargetMode => {
|
export const getTargetMode = (target: string): ImageProcessingTargetMode => {
|
||||||
@@ -106,13 +110,22 @@ export const getTargetMode = (target: string): ImageProcessingTargetMode => {
|
|||||||
return ImageProcessingTargetMode.IMAGE;
|
return ImageProcessingTargetMode.IMAGE;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const addStableIdToHeaders = (
|
export const extractSearchParams = (url: string): SettingsDownloadConversionHeader[] => {
|
||||||
headers: (SettingsDownloadConversionHeader | TSettingsDownloadConversionHeader)[],
|
const urlObject = UrlUtil.asUrl(url);
|
||||||
): TSettingsDownloadConversionHeader[] =>
|
|
||||||
headers.map((header) => ({
|
return [...(urlObject?.searchParams ?? []).entries()].map(([key, value]) => ({
|
||||||
|
name: String(key),
|
||||||
|
value: jsonSaveParse(value) ?? value,
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
export const addStableIdToKeyValueItems = (
|
||||||
|
items: (SettingsDownloadConversionHeader | TSettingsDownloadConversionKeyValueItem)[],
|
||||||
|
): TSettingsDownloadConversionKeyValueItem[] =>
|
||||||
|
items.map((item) => ({
|
||||||
// eslint-disable-next-line no-plusplus
|
// eslint-disable-next-line no-plusplus
|
||||||
id: (header as TSettingsDownloadConversionHeader).id ?? COUNTER++,
|
id: (item as TSettingsDownloadConversionKeyValueItem).id ?? COUNTER++,
|
||||||
...header,
|
...item,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export const addStableIdToConversions = (
|
export const addStableIdToConversions = (
|
||||||
@@ -123,9 +136,29 @@ export const addStableIdToConversions = (
|
|||||||
id: (conversion as TSettingsDownloadConversion).id ?? COUNTER++,
|
id: (conversion as TSettingsDownloadConversion).id ?? COUNTER++,
|
||||||
...conversion,
|
...conversion,
|
||||||
mode: getTargetMode(normalizeMimeType(conversion.target)),
|
mode: getTargetMode(normalizeMimeType(conversion.target)),
|
||||||
headers: conversion.headers ? addStableIdToHeaders(conversion.headers) : null,
|
headers: conversion.headers ? addStableIdToKeyValueItems(conversion.headers) : null,
|
||||||
|
searchParams: addStableIdToKeyValueItems(extractSearchParams(conversion.target)),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
export const getUpdatedSearchParams = (
|
||||||
|
url: string,
|
||||||
|
existingParams: Maybe<TSettingsDownloadConversionKeyValueItem[] | undefined>,
|
||||||
|
): TSettingsDownloadConversionKeyValueItem[] => {
|
||||||
|
const urlObject = UrlUtil.asUrl(url);
|
||||||
|
|
||||||
|
const params = [...(urlObject?.searchParams?.entries() ?? [])].map(([key, value]) => ({ name: key, value }));
|
||||||
|
const paramsWithRetainedStableId = params.map((param) => {
|
||||||
|
const existingParam = existingParams?.find(({ name }) => name === param.name);
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: existingParam?.id,
|
||||||
|
...param,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return addStableIdToKeyValueItems(paramsWithRetainedStableId);
|
||||||
|
};
|
||||||
|
|
||||||
export const normalizeConversions = (conversions: TSettingsDownloadConversion[]): TSettingsDownloadConversion[] =>
|
export const normalizeConversions = (conversions: TSettingsDownloadConversion[]): TSettingsDownloadConversion[] =>
|
||||||
conversions.map((conversion) => ({
|
conversions.map((conversion) => ({
|
||||||
...conversion,
|
...conversion,
|
||||||
@@ -144,7 +177,7 @@ const toValidServerMimeType = (mimeType: string): string => {
|
|||||||
export const toValidServerConversions = (conversions: TSettingsDownloadConversion[]): SettingsDownloadConversion[] =>
|
export const toValidServerConversions = (conversions: TSettingsDownloadConversion[]): SettingsDownloadConversion[] =>
|
||||||
conversions
|
conversions
|
||||||
.filter(({ mimeType, target }) => !!mimeType && !!target)
|
.filter(({ mimeType, target }) => !!mimeType && !!target)
|
||||||
.map(({ id, mode, ...conversion }) => ({
|
.map(({ id, mode, searchParams, ...conversion }) => ({
|
||||||
...conversion,
|
...conversion,
|
||||||
mimeType: toValidServerMimeType(conversion.mimeType),
|
mimeType: toValidServerMimeType(conversion.mimeType),
|
||||||
target: isUrlTargetMode(conversion.target) ? conversion.target : `${MIME_TYPE_PREFIX}${conversion.target}`,
|
target: isUrlTargetMode(conversion.target) ? conversion.target : `${MIME_TYPE_PREFIX}${conversion.target}`,
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ export enum ImageProcessingType {
|
|||||||
SERVE = 'serve',
|
SERVE = 'serve',
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TSettingsDownloadConversionHeader = SettingsDownloadConversionHeader & {
|
export type TSettingsDownloadConversionKeyValueItem = SettingsDownloadConversionHeader & {
|
||||||
/**
|
/**
|
||||||
* The conversion object does not have a stable key, which causes issues when editing the settings
|
* The conversion object does not have a stable key, which causes issues when editing the settings
|
||||||
*/
|
*/
|
||||||
@@ -83,5 +83,6 @@ export type TSettingsDownloadConversion = Omit<SettingsDownloadConversion, 'head
|
|||||||
*/
|
*/
|
||||||
id: number;
|
id: number;
|
||||||
mode: ImageProcessingTargetMode;
|
mode: ImageProcessingTargetMode;
|
||||||
headers?: Maybe<TSettingsDownloadConversionHeader[]>;
|
headers?: Maybe<TSettingsDownloadConversionKeyValueItem[]>;
|
||||||
|
searchParams?: Maybe<TSettingsDownloadConversionKeyValueItem[]>;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -14,9 +14,9 @@ import IconButton from '@mui/material/IconButton';
|
|||||||
import { useTheme } from '@mui/material/styles';
|
import { useTheme } from '@mui/material/styles';
|
||||||
import { CustomTooltip } from '@/base/components/CustomTooltip.tsx';
|
import { CustomTooltip } from '@/base/components/CustomTooltip.tsx';
|
||||||
import { IMAGE_PROCESSING_INPUT_WIDTH } from '@/features/settings/Settings.constants.ts';
|
import { IMAGE_PROCESSING_INPUT_WIDTH } from '@/features/settings/Settings.constants.ts';
|
||||||
import { TSettingsDownloadConversionHeader } from '@/features/settings/Settings.types';
|
import { TSettingsDownloadConversionKeyValueItem } from '@/features/settings/Settings.types';
|
||||||
|
|
||||||
export const Header = ({
|
export const KeyValueItem = ({
|
||||||
id,
|
id,
|
||||||
name,
|
name,
|
||||||
value,
|
value,
|
||||||
@@ -24,8 +24,8 @@ export const Header = ({
|
|||||||
isDuplicate,
|
isDuplicate,
|
||||||
}: {
|
}: {
|
||||||
isDuplicate: boolean;
|
isDuplicate: boolean;
|
||||||
onChange: (header: TSettingsDownloadConversionHeader | null) => void;
|
onChange: (header: TSettingsDownloadConversionKeyValueItem | null) => void;
|
||||||
} & TSettingsDownloadConversionHeader) => {
|
} & TSettingsDownloadConversionKeyValueItem) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
|
||||||
@@ -12,43 +12,47 @@ import Typography from '@mui/material/Typography';
|
|||||||
import Button from '@mui/material/Button';
|
import Button from '@mui/material/Button';
|
||||||
import Collapse from '@mui/material/Collapse';
|
import Collapse from '@mui/material/Collapse';
|
||||||
import { Maybe } from '@/lib/graphql/generated/graphql.ts';
|
import { Maybe } from '@/lib/graphql/generated/graphql.ts';
|
||||||
import { addStableIdToHeaders, isDuplicateHeader } from '@/features/settings/ImageProcessing.utils.ts';
|
import { addStableIdToKeyValueItems, isDuplicateKeyValueItem } from '@/features/settings/ImageProcessing.utils.ts';
|
||||||
import { Header } from '@/features/settings/components/images/Header.tsx';
|
import { KeyValueItem } from '@/features/settings/components/images/KeyValueItem.tsx';
|
||||||
import { TSettingsDownloadConversionHeader } from '@/features/settings/Settings.types.ts';
|
import { TSettingsDownloadConversionKeyValueItem } from '@/features/settings/Settings.types.ts';
|
||||||
|
|
||||||
export const Headers = ({
|
export const KeyValueItems = ({
|
||||||
|
title,
|
||||||
open,
|
open,
|
||||||
headers,
|
items,
|
||||||
onChange,
|
onChange,
|
||||||
}: {
|
}: {
|
||||||
|
title: string;
|
||||||
open: boolean;
|
open: boolean;
|
||||||
headers: Maybe<TSettingsDownloadConversionHeader[] | undefined>;
|
items: Maybe<TSettingsDownloadConversionKeyValueItem[] | undefined>;
|
||||||
onChange: (headers: Maybe<TSettingsDownloadConversionHeader[]>) => void;
|
onChange: (items: Maybe<TSettingsDownloadConversionKeyValueItem[]>) => void;
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Collapse in={open}>
|
<Collapse in={open}>
|
||||||
<Stack sx={{ justifyContent: 'start', gap: 2, pt: 2 }}>
|
<Stack sx={{ justifyContent: 'start', gap: 2, pt: 2 }}>
|
||||||
<Typography>{t('download.settings.conversion.headers.title')}</Typography>
|
<Typography>{title}</Typography>
|
||||||
{headers?.map((header, index) => (
|
{items?.map((header, index) => (
|
||||||
<Header
|
<KeyValueItem
|
||||||
key={header.id}
|
key={header.id}
|
||||||
{...header}
|
{...header}
|
||||||
isDuplicate={isDuplicateHeader(header.name, index, headers)}
|
isDuplicate={isDuplicateKeyValueItem(header.name, index, items)}
|
||||||
onChange={(updatedHeader) => {
|
onChange={(updatedHeader) => {
|
||||||
const isDeletion = updatedHeader == null;
|
const isDeletion = updatedHeader == null;
|
||||||
if (isDeletion) {
|
if (isDeletion) {
|
||||||
onChange(headers?.toSpliced(index, 1));
|
onChange(items?.toSpliced(index, 1));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
onChange((headers ?? []).toSpliced(index, 1, updatedHeader));
|
onChange((items ?? []).toSpliced(index, 1, updatedHeader));
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
<Button
|
<Button
|
||||||
onClick={() => onChange([...(headers ?? []), ...addStableIdToHeaders([{ name: '', value: '' }])])}
|
onClick={() =>
|
||||||
|
onChange([...(items ?? []), ...addStableIdToKeyValueItems([{ name: '', value: '' }])])
|
||||||
|
}
|
||||||
variant="contained"
|
variant="contained"
|
||||||
sx={{ width: 'fit-content' }}
|
sx={{ width: 'fit-content' }}
|
||||||
>
|
>
|
||||||
@@ -20,7 +20,7 @@ import InputLabel from '@mui/material/InputLabel';
|
|||||||
import FormControl from '@mui/material/FormControl';
|
import FormControl from '@mui/material/FormControl';
|
||||||
import { d } from 'koration';
|
import { d } from 'koration';
|
||||||
import { useElementSize } from '@mantine/hooks';
|
import { useElementSize } from '@mantine/hooks';
|
||||||
import { Headers } from '@/features/settings/components/images/Headers.tsx';
|
import { KeyValueItems } from '@/features/settings/components/images/KeyValueItems.tsx';
|
||||||
import { CustomTooltip } from '@/base/components/CustomTooltip.tsx';
|
import { CustomTooltip } from '@/base/components/CustomTooltip.tsx';
|
||||||
import { TranslationKey } from '@/base/Base.types.ts';
|
import { TranslationKey } from '@/base/Base.types.ts';
|
||||||
import {
|
import {
|
||||||
@@ -36,15 +36,18 @@ import { TypographyMaxLines } from '@/base/components/texts/TypographyMaxLines.t
|
|||||||
import { MimeTypeTextField } from '@/features/settings/components/images/MimeTypeTextField.tsx';
|
import { MimeTypeTextField } from '@/features/settings/components/images/MimeTypeTextField.tsx';
|
||||||
import { ImageProcessingTargetMode, TSettingsDownloadConversion } from '@/features/settings/Settings.types.ts';
|
import { ImageProcessingTargetMode, TSettingsDownloadConversion } from '@/features/settings/Settings.types.ts';
|
||||||
import {
|
import {
|
||||||
|
getUpdatedSearchParams,
|
||||||
isDefaultMimeType,
|
isDefaultMimeType,
|
||||||
|
isUrlTargetMode,
|
||||||
isValidCallTimeoutSetting,
|
isValidCallTimeoutSetting,
|
||||||
isValidCompressionLevel,
|
isValidCompressionLevel,
|
||||||
isValidConnectTimeoutSetting,
|
isValidConnectTimeoutSetting,
|
||||||
} from '@/features/settings/ImageProcessing.utils.ts';
|
} from '@/features/settings/ImageProcessing.utils.ts';
|
||||||
|
import { UrlUtil } from '@/lib/UrlUtil.ts';
|
||||||
|
|
||||||
export const Processing = ({
|
export const Processing = ({
|
||||||
conversion,
|
conversion,
|
||||||
conversion: { mode, mimeType, target, compressionLevel, headers, callTimeout, connectTimeout },
|
conversion: { mode, mimeType, target, compressionLevel, headers, searchParams, callTimeout, connectTimeout },
|
||||||
onChange,
|
onChange,
|
||||||
isDuplicate,
|
isDuplicate,
|
||||||
}: {
|
}: {
|
||||||
@@ -57,10 +60,12 @@ export const Processing = ({
|
|||||||
|
|
||||||
const { ref: textFieldRef, height: textFieldHeight } = useElementSize();
|
const { ref: textFieldRef, height: textFieldHeight } = useElementSize();
|
||||||
|
|
||||||
|
const [areSearchParamsCollapsed, setAreSearchParamsCollapsed] = useState(true);
|
||||||
const [areHeadersCollapsed, setAreHeadersCollapsed] = useState(true);
|
const [areHeadersCollapsed, setAreHeadersCollapsed] = useState(true);
|
||||||
|
|
||||||
const isDisabledMode = mode === ImageProcessingTargetMode.DISABLED;
|
const isDisabledMode = mode === ImageProcessingTargetMode.DISABLED;
|
||||||
const isImageMode = mode === ImageProcessingTargetMode.IMAGE;
|
const isImageMode = mode === ImageProcessingTargetMode.IMAGE;
|
||||||
|
const isUrlMode = mode === ImageProcessingTargetMode.URL;
|
||||||
|
|
||||||
const isCallTimeoutValid = isValidCallTimeoutSetting(callTimeout);
|
const isCallTimeoutValid = isValidCallTimeoutSetting(callTimeout);
|
||||||
const isConnectTimeoutValid = isValidConnectTimeoutSetting(connectTimeout);
|
const isConnectTimeoutValid = isValidConnectTimeoutSetting(connectTimeout);
|
||||||
@@ -122,14 +127,21 @@ export const Processing = ({
|
|||||||
labelId="image-conversion-target-mode-label"
|
labelId="image-conversion-target-mode-label"
|
||||||
label={t('download.settings.conversion.target_modes.title')}
|
label={t('download.settings.conversion.target_modes.title')}
|
||||||
value={mode}
|
value={mode}
|
||||||
onChange={(e) =>
|
onChange={(e) => {
|
||||||
|
if (!isUrlMode) {
|
||||||
|
setAreSearchParamsCollapsed(true);
|
||||||
|
setAreHeadersCollapsed(true);
|
||||||
|
}
|
||||||
|
|
||||||
onChange({
|
onChange({
|
||||||
...conversion,
|
...conversion,
|
||||||
mode: e.target.value,
|
mode: e.target.value,
|
||||||
target:
|
target:
|
||||||
e.target.value === ImageProcessingTargetMode.DISABLED ? TARGET_DISABLED : '',
|
e.target.value === ImageProcessingTargetMode.DISABLED ? TARGET_DISABLED : '',
|
||||||
})
|
headers: !isUrlMode ? null : headers,
|
||||||
}
|
searchParams: !isUrlMode ? null : searchParams,
|
||||||
|
});
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{IMAGE_PROCESSING_TARGET_MODES_SELECT_VALUES.map(([selectValue, { text: selectText }]) => (
|
{IMAGE_PROCESSING_TARGET_MODES_SELECT_VALUES.map(([selectValue, { text: selectText }]) => (
|
||||||
<MenuItem key={selectValue} value={selectValue}>
|
<MenuItem key={selectValue} value={selectValue}>
|
||||||
@@ -146,12 +158,13 @@ export const Processing = ({
|
|||||||
isDuplicate={false}
|
isDuplicate={false}
|
||||||
label={t('download.settings.conversion.target')}
|
label={t('download.settings.conversion.target')}
|
||||||
value={target}
|
value={target}
|
||||||
onUpdate={(value) =>
|
onUpdate={(value) => {
|
||||||
onChange({
|
onChange({
|
||||||
...conversion,
|
...conversion,
|
||||||
target: value,
|
target: value,
|
||||||
})
|
searchParams: isImageMode ? null : getUpdatedSearchParams(value, searchParams),
|
||||||
}
|
});
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{(() => {
|
{(() => {
|
||||||
@@ -237,6 +250,16 @@ export const Processing = ({
|
|||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
<Button
|
||||||
|
disabled={!isUrlTargetMode(target)}
|
||||||
|
onClick={() => setAreSearchParamsCollapsed(!areSearchParamsCollapsed)}
|
||||||
|
variant={areSearchParamsCollapsed ? 'outlined' : 'contained'}
|
||||||
|
sx={{ height: textFieldHeight }}
|
||||||
|
>
|
||||||
|
{t('download.settings.conversion.search_params.button', {
|
||||||
|
count: searchParams?.length ?? 0,
|
||||||
|
})}
|
||||||
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => setAreHeadersCollapsed(!areHeadersCollapsed)}
|
onClick={() => setAreHeadersCollapsed(!areHeadersCollapsed)}
|
||||||
variant={areHeadersCollapsed ? 'outlined' : 'contained'}
|
variant={areHeadersCollapsed ? 'outlined' : 'contained'}
|
||||||
@@ -261,9 +284,25 @@ export const Processing = ({
|
|||||||
</IconButton>
|
</IconButton>
|
||||||
</CustomTooltip>
|
</CustomTooltip>
|
||||||
</Stack>
|
</Stack>
|
||||||
<Headers
|
<KeyValueItems
|
||||||
open={!areHeadersCollapsed}
|
title={t('download.settings.conversion.search_params.title')}
|
||||||
headers={headers}
|
open={isUrlMode && !areSearchParamsCollapsed}
|
||||||
|
items={searchParams}
|
||||||
|
onChange={(params) => {
|
||||||
|
const baseUrl = target?.split('?')[0] ?? '';
|
||||||
|
const updatedParams = Object.fromEntries(params?.map(({ name, value }) => [name, value]) ?? []);
|
||||||
|
|
||||||
|
onChange({
|
||||||
|
...conversion,
|
||||||
|
target: UrlUtil.addParams(baseUrl, updatedParams),
|
||||||
|
searchParams: params,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<KeyValueItems
|
||||||
|
title={t('download.settings.conversion.headers.title')}
|
||||||
|
open={isUrlMode && !areHeadersCollapsed}
|
||||||
|
items={headers}
|
||||||
onChange={(updatedHeaders) =>
|
onChange={(updatedHeaders) =>
|
||||||
onChange({
|
onChange({
|
||||||
...conversion,
|
...conversion,
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ export const ImageProcessingSetting = ({ type }: { type: ImageProcessingType })
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack sx={{ p: 2, gap: 3 }}>
|
<Stack sx={{ p: 2, gap: 5 }}>
|
||||||
<Typography>{t('download.settings.conversion.description', { value: TARGET_DISABLED })}</Typography>
|
<Typography>{t('download.settings.conversion.description', { value: TARGET_DISABLED })}</Typography>
|
||||||
<Stack sx={{ flexDirection: 'column', gap: 5 }}>
|
<Stack sx={{ flexDirection: 'column', gap: 5 }}>
|
||||||
{tmpConversions.map((conversion, index) => {
|
{tmpConversions.map((conversion, index) => {
|
||||||
|
|||||||
@@ -9,6 +9,14 @@
|
|||||||
import { SearchParam } from '@/base/Base.types.ts';
|
import { SearchParam } from '@/base/Base.types.ts';
|
||||||
|
|
||||||
export class UrlUtil {
|
export class UrlUtil {
|
||||||
|
static asUrl(url: string): URL | null {
|
||||||
|
try {
|
||||||
|
return new URL(url);
|
||||||
|
} catch (e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static createParams(params: Record<SearchParam | string, string | null | undefined>): URLSearchParams {
|
static createParams(params: Record<SearchParam | string, string | null | undefined>): URLSearchParams {
|
||||||
const paramEntries = Object.entries(params).filter(
|
const paramEntries = Object.entries(params).filter(
|
||||||
(entry): entry is [string, string] => typeof entry[1] === 'string',
|
(entry): entry is [string, string] => typeof entry[1] === 'string',
|
||||||
|
|||||||
Reference in New Issue
Block a user