Extract "image processing" components
This commit is contained in:
201
src/features/settings/ImageProcessing.utils.ts
Normal file
201
src/features/settings/ImageProcessing.utils.ts
Normal file
@@ -0,0 +1,201 @@
|
||||
/*
|
||||
* 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 { d } from 'koration';
|
||||
import {
|
||||
ImageProcessingTargetMode,
|
||||
TSettingsDownloadConversion,
|
||||
TSettingsDownloadConversionHeader,
|
||||
} from '@/features/settings/Settings.types.ts';
|
||||
import {
|
||||
DEFAULT_MIME_TYPE,
|
||||
IMAGE_PROCESSING_CALL_TIMEOUT,
|
||||
IMAGE_PROCESSING_COMPRESSION,
|
||||
IMAGE_PROCESSING_CONNECT_TIMEOUT,
|
||||
MIME_TYPE_PREFIX,
|
||||
} from '@/features/settings/Settings.constants.ts';
|
||||
import {
|
||||
SettingsDownloadConversion,
|
||||
SettingsDownloadConversionHeader,
|
||||
SettingsDownloadConversionType,
|
||||
} from '@/lib/graphql/generated/graphql.ts';
|
||||
|
||||
let COUNTER = 0;
|
||||
|
||||
const normalizeMimeType = (mimeType: string): string => mimeType.replace(MIME_TYPE_PREFIX, '');
|
||||
|
||||
export const isDefaultMimeType = (mimeType: string): boolean =>
|
||||
normalizeMimeType(mimeType.toLowerCase().trim()) === DEFAULT_MIME_TYPE;
|
||||
|
||||
export const isDuplicateHeader = (
|
||||
header: string,
|
||||
index: number,
|
||||
headers: SettingsDownloadConversionType['headers'],
|
||||
): boolean => headers?.slice(0, index).some(({ name }) => name === header) ?? false;
|
||||
|
||||
const hasDuplicateHeaders = (headers: SettingsDownloadConversionType['headers']): boolean =>
|
||||
headers?.some((header, index) => isDuplicateHeader(header.name, index, headers)) ?? false;
|
||||
|
||||
export const isDuplicateConversion = (
|
||||
mimeType: string,
|
||||
index: number,
|
||||
conversions: SettingsDownloadConversion[],
|
||||
): boolean => conversions.slice(0, index).some((conversion) => conversion.mimeType === mimeType);
|
||||
|
||||
export const isUrlTargetMode = (target: string): boolean => target !== '' && !!target.match(/^https?:\/\/.+/);
|
||||
|
||||
const isValidNumberSetting = (value: number | null | undefined, min: number, max: number): boolean =>
|
||||
value == null || (value >= min && value <= max);
|
||||
|
||||
export const isValidCallTimeoutSetting = (timeout: string | null | undefined): boolean =>
|
||||
isValidNumberSetting(
|
||||
timeout ? d(timeout).seconds.inWholeSeconds : null,
|
||||
IMAGE_PROCESSING_CALL_TIMEOUT.min,
|
||||
IMAGE_PROCESSING_CALL_TIMEOUT.max,
|
||||
);
|
||||
|
||||
export const isValidConnectTimeoutSetting = (timeout: string | null | undefined): boolean =>
|
||||
isValidNumberSetting(
|
||||
timeout ? d(timeout).seconds.inWholeSeconds : null,
|
||||
IMAGE_PROCESSING_CONNECT_TIMEOUT.min,
|
||||
IMAGE_PROCESSING_CONNECT_TIMEOUT.max,
|
||||
);
|
||||
|
||||
export const isValidCompressionLevel = (compression: number | null | undefined): boolean =>
|
||||
isValidNumberSetting(compression, IMAGE_PROCESSING_COMPRESSION.min, IMAGE_PROCESSING_COMPRESSION.max);
|
||||
|
||||
const isUnsetConversion = (mimeType: string, target: string): boolean => mimeType === '' && target === '';
|
||||
|
||||
const isInvalidTarget = (target: string, mode: ImageProcessingTargetMode, mimeType: string): boolean => {
|
||||
if (isDefaultMimeType(mimeType) && !target) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return mode === ImageProcessingTargetMode.URL && !isUrlTargetMode(target);
|
||||
};
|
||||
|
||||
export const containsInvalidConversion = (conversions: TSettingsDownloadConversion[]): boolean =>
|
||||
conversions.some(
|
||||
({ mimeType, compressionLevel, target, callTimeout, connectTimeout, headers, mode }, index) =>
|
||||
isUnsetConversion(mimeType, target) ||
|
||||
isInvalidTarget(target, mode, mimeType) ||
|
||||
!isValidCompressionLevel(compressionLevel) ||
|
||||
!isValidCallTimeoutSetting(callTimeout) ||
|
||||
!isValidConnectTimeoutSetting(connectTimeout) ||
|
||||
isDuplicateConversion(mimeType, index, conversions) ||
|
||||
hasDuplicateHeaders(headers),
|
||||
);
|
||||
|
||||
export const getTargetMode = (target: string): ImageProcessingTargetMode => {
|
||||
if (isUrlTargetMode(target)) {
|
||||
return ImageProcessingTargetMode.URL;
|
||||
}
|
||||
|
||||
return ImageProcessingTargetMode.IMAGE;
|
||||
};
|
||||
|
||||
export const addStableIdToHeaders = (
|
||||
headers: (SettingsDownloadConversionHeader | TSettingsDownloadConversionHeader)[],
|
||||
): TSettingsDownloadConversionHeader[] =>
|
||||
headers.map((header) => ({
|
||||
// eslint-disable-next-line no-plusplus
|
||||
id: (header as TSettingsDownloadConversionHeader).id ?? COUNTER++,
|
||||
...header,
|
||||
}));
|
||||
|
||||
export const addStableIdToConversions = (
|
||||
conversions: (SettingsDownloadConversion | TSettingsDownloadConversion)[],
|
||||
): TSettingsDownloadConversion[] =>
|
||||
conversions.map((conversion) => ({
|
||||
// eslint-disable-next-line no-plusplus
|
||||
id: (conversion as TSettingsDownloadConversion).id ?? COUNTER++,
|
||||
...conversion,
|
||||
mode: getTargetMode(conversion.target),
|
||||
headers: conversion.headers ? addStableIdToHeaders(conversion.headers) : null,
|
||||
}));
|
||||
|
||||
export const normalizeConversions = (conversions: TSettingsDownloadConversion[]): TSettingsDownloadConversion[] =>
|
||||
conversions.map((conversion) => ({
|
||||
...conversion,
|
||||
mimeType: normalizeMimeType(conversion.mimeType),
|
||||
target: normalizeMimeType(conversion.target),
|
||||
}));
|
||||
|
||||
const toValidServerMimeType = (mimeType: string): string => {
|
||||
if (isDefaultMimeType(mimeType)) {
|
||||
return DEFAULT_MIME_TYPE;
|
||||
}
|
||||
|
||||
return `${MIME_TYPE_PREFIX}${mimeType}`;
|
||||
};
|
||||
|
||||
export const toValidServerConversions = (conversions: TSettingsDownloadConversion[]): SettingsDownloadConversion[] =>
|
||||
conversions
|
||||
.filter(({ mimeType, target }) => !!mimeType && !!target)
|
||||
.map(({ id, mode, ...conversion }) => ({
|
||||
...conversion,
|
||||
mimeType: toValidServerMimeType(conversion.mimeType),
|
||||
target: isUrlTargetMode(conversion.target) ? conversion.target : `${MIME_TYPE_PREFIX}${conversion.target}`,
|
||||
headers: conversion.headers?.map(({ id: headerId, ...header }) => header) ?? null,
|
||||
}));
|
||||
|
||||
export const maybeAddDefault = (conversions: TSettingsDownloadConversion[]): TSettingsDownloadConversion[] => {
|
||||
const isDefaultDefined = conversions.some(({ mimeType }) => isDefaultMimeType(mimeType));
|
||||
|
||||
return [
|
||||
...(isDefaultDefined
|
||||
? []
|
||||
: [
|
||||
{
|
||||
id: -1,
|
||||
mode: ImageProcessingTargetMode.IMAGE,
|
||||
mimeType: DEFAULT_MIME_TYPE,
|
||||
target: '',
|
||||
compressionLevel: null,
|
||||
headers: null,
|
||||
callTimeout: null,
|
||||
connectTimeout: null,
|
||||
},
|
||||
]),
|
||||
...conversions,
|
||||
];
|
||||
};
|
||||
|
||||
const normalizeHeaders = (
|
||||
headers: SettingsDownloadConversionType['headers'],
|
||||
): SettingsDownloadConversionType['headers'] => headers?.filter(({ name }) => name !== '');
|
||||
|
||||
export const didUpdateConversions = (
|
||||
conversions: TSettingsDownloadConversion[],
|
||||
tmpConversions: TSettingsDownloadConversion[],
|
||||
): boolean => {
|
||||
if (conversions.length !== tmpConversions.length) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return conversions.some(({ mimeType, target, compressionLevel, callTimeout, connectTimeout, headers }, index) => {
|
||||
const tmpConversion = tmpConversions[index];
|
||||
|
||||
const orgHeaders = normalizeHeaders(headers);
|
||||
const tmpHeaders = normalizeHeaders(tmpConversion.headers);
|
||||
|
||||
return (
|
||||
normalizeMimeType(mimeType) !== tmpConversion.mimeType ||
|
||||
normalizeMimeType(target) !== tmpConversion.target ||
|
||||
compressionLevel !== tmpConversion.compressionLevel ||
|
||||
callTimeout !== tmpConversion.callTimeout ||
|
||||
connectTimeout !== tmpConversion.connectTimeout ||
|
||||
(orgHeaders?.length ?? 0) !== (tmpHeaders?.length ?? 0) ||
|
||||
!!orgHeaders?.some(
|
||||
(header, headerIndex) =>
|
||||
header.name !== tmpHeaders?.[headerIndex]?.name ||
|
||||
header.value !== tmpHeaders?.[headerIndex]?.value,
|
||||
)
|
||||
);
|
||||
});
|
||||
};
|
||||
Reference in New Issue
Block a user