From 2c81f3be0f8f7a5982b16822fb88bf86e8514d03 Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Fri, 3 Jan 2025 09:47:17 -0800 Subject: [PATCH] Improve typings in FormInputGroup (cherry picked from commit 6838f068bcd04b770cd9c53873f160be97ea745f) --- .../AddNewMovie/AddNewMovieModalContent.js | 2 +- .../ImportMovie/Import/ImportMovieFooter.js | 2 +- .../ImportMovie/Import/ImportMovieRow.js | 2 +- .../AddNewCollectionMovieModalContent.js | 2 +- .../src/Components/Form/AutoCompleteInput.tsx | 3 +- frontend/src/Components/Form/CaptchaInput.tsx | 2 +- frontend/src/Components/Form/CheckInput.css | 11 +- .../src/Components/Form/CheckInput.css.d.ts | 8 +- frontend/src/Components/Form/CheckInput.tsx | 5 +- .../src/Components/Form/FormInputGroup.tsx | 289 ++++++++++-------- frontend/src/Components/Form/NumberInput.tsx | 5 +- frontend/src/Components/Form/OAuthInput.tsx | 2 +- .../src/Components/Form/PasswordInput.tsx | 2 +- frontend/src/Components/Form/PathInput.tsx | 2 +- .../Form/Select/AvailabilitySelectInput.tsx | 2 +- .../Form/Select/DownloadClientSelectInput.tsx | 2 +- .../Form/Select/IndexerFlagsSelectInput.tsx | 2 +- .../Form/Select/IndexerSelectInput.tsx | 2 +- .../Form/Select/LanguageSelectInput.tsx | 2 +- .../Form/Select/MonitorMoviesSelectInput.tsx | 2 +- .../Form/Select/ProviderOptionSelectInput.tsx | 2 +- .../Form/Select/QualityProfileSelectInput.tsx | 2 +- .../Form/Select/RootFolderSelectInput.tsx | 2 +- .../src/Components/Form/Select/UMaskInput.tsx | 2 +- .../src/Components/Form/Tag/DeviceInput.tsx | 2 +- .../src/Components/Form/Tag/MovieTagInput.tsx | 50 +-- .../Components/Form/Tag/TagSelectInput.tsx | 2 +- .../src/Components/Form/Tag/TextTagInput.tsx | 6 +- frontend/src/Components/Form/TextArea.tsx | 2 +- frontend/src/Components/Form/TextInput.tsx | 23 +- frontend/src/Helpers/Props/inputTypes.ts | 7 +- .../Quality/SelectQualityModalContent.tsx | 6 +- .../src/Movie/Edit/EditMovieModalContent.tsx | 1 + .../Select/Delete/DeleteMovieModalContent.tsx | 2 +- .../Select/Edit/EditMoviesModalContent.tsx | 11 +- .../ManageDownloadClientsEditModalContent.tsx | 46 ++- .../src/Settings/General/BackupSettings.js | 1 + .../EditImportListExclusionModalContent.tsx | 5 +- .../ImportLists/EditImportListModalContent.js | 2 +- .../ManageImportListsEditModalContent.tsx | 46 ++- .../Edit/ManageIndexersEditModalContent.tsx | 42 ++- .../MediaManagement/Naming/Naming.tsx | 5 +- .../EditReleaseProfileModalContent.tsx | 6 +- 43 files changed, 321 insertions(+), 301 deletions(-) diff --git a/frontend/src/AddMovie/AddNewMovie/AddNewMovieModalContent.js b/frontend/src/AddMovie/AddNewMovie/AddNewMovieModalContent.js index 7e13c4641..7cb5d973a 100644 --- a/frontend/src/AddMovie/AddNewMovie/AddNewMovieModalContent.js +++ b/frontend/src/AddMovie/AddNewMovie/AddNewMovieModalContent.js @@ -110,7 +110,7 @@ class AddNewMovieModalContent extends Component { ) => unknown; diff --git a/frontend/src/Components/Form/CaptchaInput.tsx b/frontend/src/Components/Form/CaptchaInput.tsx index d5a3f11f7..597b1ad4f 100644 --- a/frontend/src/Components/Form/CaptchaInput.tsx +++ b/frontend/src/Components/Form/CaptchaInput.tsx @@ -16,7 +16,7 @@ import FormInputButton from './FormInputButton'; import TextInput from './TextInput'; import styles from './CaptchaInput.css'; -interface CaptchaInputProps { +export interface CaptchaInputProps { className?: string; name: string; value?: string; diff --git a/frontend/src/Components/Form/CheckInput.css b/frontend/src/Components/Form/CheckInput.css index 171121482..6e44bd609 100644 --- a/frontend/src/Components/Form/CheckInput.css +++ b/frontend/src/Components/Form/CheckInput.css @@ -41,10 +41,11 @@ .checkbox:focus + .input { outline: 0; border-color: var(--inputFocusBorderColor); - box-shadow: inset 0 1px 1px var(--inputBoxShadowColor), 0 0 8px var(--inputFocusBoxShadowColor); + box-shadow: inset 0 1px 1px var(--inputBoxShadowColor), + 0 0 8px var(--inputFocusBoxShadowColor); } -.dangerIsChecked { +.danger { border-color: var(--dangerColor); background-color: var(--dangerColor); @@ -53,7 +54,7 @@ } } -.primaryIsChecked { +.primary { border-color: var(--primaryColor); background-color: var(--primaryColor); @@ -62,7 +63,7 @@ } } -.successIsChecked { +.success { border-color: var(--successColor); background-color: var(--successColor); @@ -71,7 +72,7 @@ } } -.warningIsChecked { +.warning { border-color: var(--warningColor); background-color: var(--warningColor); diff --git a/frontend/src/Components/Form/CheckInput.css.d.ts b/frontend/src/Components/Form/CheckInput.css.d.ts index bba6b63bb..850250b67 100644 --- a/frontend/src/Components/Form/CheckInput.css.d.ts +++ b/frontend/src/Components/Form/CheckInput.css.d.ts @@ -3,16 +3,16 @@ interface CssExports { 'checkbox': string; 'container': string; - 'dangerIsChecked': string; + 'danger': string; 'helpText': string; 'input': string; 'isDisabled': string; 'isIndeterminate': string; 'isNotChecked': string; 'label': string; - 'primaryIsChecked': string; - 'successIsChecked': string; - 'warningIsChecked': string; + 'primary': string; + 'success': string; + 'warning': string; } export const cssExports: CssExports; export default cssExports; diff --git a/frontend/src/Components/Form/CheckInput.tsx b/frontend/src/Components/Form/CheckInput.tsx index 6f5dd0242..107beaa58 100644 --- a/frontend/src/Components/Form/CheckInput.tsx +++ b/frontend/src/Components/Form/CheckInput.tsx @@ -11,7 +11,7 @@ interface ChangeEvent extends SyntheticEvent { target: EventTarget & T; } -interface CheckInputProps { +export interface CheckInputProps { className?: string; containerClassName?: string; name: string; @@ -45,7 +45,6 @@ function CheckInput(props: CheckInputProps) { const isChecked = value === checkedValue; const isUnchecked = value === uncheckedValue; const isIndeterminate = !isChecked && !isUnchecked; - const isCheckClass: keyof typeof styles = `${kind}IsChecked`; const toggleChecked = useCallback( (checked: boolean, shiftKey: boolean) => { @@ -112,7 +111,7 @@ function CheckInput(props: CheckInputProps) {
= { + autoComplete: AutoCompleteInput, + availabilitySelect: AvailabilitySelectInput, + captcha: CaptchaInput, + check: CheckInput, + date: TextInput, + device: DeviceInput, + downloadClientSelect: DownloadClientSelectInput, + dynamicSelect: ProviderDataSelectInput, + file: TextInput, + float: NumberInput, + indexerFlagsSelect: IndexerFlagsSelectInput, + indexerSelect: IndexerSelectInput, + keyValueList: KeyValueListInput, + languageSelect: LanguageSelectInput, + monitorMoviesSelect: MonitorMoviesSelectInput, + movieTag: MovieTagInput, + number: NumberInput, + oauth: OAuthInput, + password: PasswordInput, + path: PathInput, + qualityProfileSelect: QualityProfileSelectInput, + rootFolderSelect: RootFolderSelectInput, + select: EnhancedSelectInput, + tag: MovieTagInput, + tagSelect: TagSelectInput, + text: TextInput, + textArea: TextArea, + textTag: TextTagInput, + umask: UMaskInput, +} as const; - case inputTypes.AVAILABILITY_SELECT: - return AvailabilitySelectInput; +// type Components = typeof componentMap; - case inputTypes.CAPTCHA: - return CaptchaInput; +type PickProps = C extends 'text' + ? TextInputProps + : C extends 'autoComplete' + ? AutoCompleteInputProps + : C extends 'availabilitySelect' + ? AvailabilitySelectInputProps + : C extends 'captcha' + ? CaptchaInputProps + : C extends 'check' + ? CheckInputProps + : C extends 'date' + ? TextInputProps + : C extends 'device' + ? DeviceInputProps + : C extends 'downloadClientSelect' + ? DownloadClientSelectInputProps + : C extends 'dynamicSelect' + ? ProviderOptionSelectInputProps + : C extends 'file' + ? TextInputProps + : C extends 'float' + ? TextInputProps + : C extends 'indexerFlagsSelect' + ? IndexerFlagsSelectInputProps + : C extends 'indexerSelect' + ? IndexerSelectInputProps + : C extends 'keyValueList' + ? KeyValueListInputProps + : C extends 'languageSelect' + ? LanguageSelectInputProps + : C extends 'monitorMoviesSelect' + ? MonitorMoviesSelectInputProps + : C extends 'movieTag' + ? MovieTagInputProps + : C extends 'number' + ? NumberInputProps + : C extends 'oauth' + ? OAuthInputProps + : C extends 'password' + ? TextInputProps + : C extends 'path' + ? PathInputProps + : C extends 'qualityProfileSelect' + ? QualityProfileSelectInputProps + : C extends 'rootFolderSelect' + ? RootFolderSelectInputProps + : C extends 'select' + ? // eslint-disable-next-line @typescript-eslint/no-explicit-any + EnhancedSelectInputProps + : C extends 'tag' + ? MovieTagInputProps + : C extends 'tagSelect' + ? TagSelectInputProps + : C extends 'text' + ? TextInputProps + : C extends 'textArea' + ? TextAreaProps + : C extends 'textTag' + ? TextTagInputProps + : C extends 'umask' + ? UMaskInputProps + : never; - case inputTypes.CHECK: - return CheckInput; - - case inputTypes.DEVICE: - return DeviceInput; - - case inputTypes.KEY_VALUE_LIST: - return KeyValueListInput; - - case inputTypes.LANGUAGE_SELECT: - return LanguageSelectInput; - - case inputTypes.MOVIE_MONITORED_SELECT: - return MonitorMoviesSelectInput; - - case inputTypes.NUMBER: - return NumberInput; - - case inputTypes.OAUTH: - return OAuthInput; - - case inputTypes.PASSWORD: - return PasswordInput; - - case inputTypes.PATH: - return PathInput; - - case inputTypes.QUALITY_PROFILE_SELECT: - return QualityProfileSelectInput; - - case inputTypes.INDEXER_SELECT: - return IndexerSelectInput; - - case inputTypes.INDEXER_FLAGS_SELECT: - return IndexerFlagsSelectInput; - - case inputTypes.DOWNLOAD_CLIENT_SELECT: - return DownloadClientSelectInput; - - case inputTypes.ROOT_FOLDER_SELECT: - return RootFolderSelectInput; - - case inputTypes.SELECT: - return EnhancedSelectInput; - - case inputTypes.DYNAMIC_SELECT: - return ProviderDataSelectInput; - - case inputTypes.TAG: - case inputTypes.MOVIE_TAG: - return MovieTagInput; - - case inputTypes.TEXT_AREA: - return TextArea; - - case inputTypes.TEXT_TAG: - return TextTagInput; - - case inputTypes.TAG_SELECT: - return TagSelectInput; - - case inputTypes.UMASK: - return UMaskInput; - - default: - return TextInput; - } +export interface FormInputGroupValues { + key: T; + value: string; + hint?: string; } // TODO: Remove once all parent components are updated to TSX and we can refactor to a consistent type -interface ValidationMessage { +export interface ValidationMessage { message: string; } -interface FormInputGroupProps { +export type FormInputGroupProps = Omit< + PickProps, + 'className' +> & { + type: C; className?: string; containerClassName?: string; inputClassName?: string; + autoFocus?: boolean; + autocomplete?: string; name: string; - value?: unknown; - values?: unknown[]; - isDisabled?: boolean; - type?: InputType; - kind?: Kind; - min?: number; - max?: number; - unit?: string; buttons?: ReactNode | ReactNode[]; helpText?: string; helpTexts?: string[]; helpTextWarning?: string; helpLink?: string; - placeholder?: string; - autoFocus?: boolean; - includeNoChange?: boolean; - includeNoChangeDisabled?: boolean; - valueOptions?: object; - selectedValueOptions?: object; - indexerFlags?: number; pending?: boolean; - canEdit?: boolean; - includeAny?: boolean; - delimiters?: string[]; - readOnly?: boolean; + placeholder?: string; + unit?: string; errors?: (ValidationMessage | ValidationError)[]; warnings?: (ValidationMessage | ValidationWarning)[]; - onChange: (args: T) => void; - onFocus?: (event: FocusEvent) => void; -} +}; -function FormInputGroup(props: FormInputGroupProps) { +function FormInputGroup( + props: FormInputGroupProps +) { const { className = styles.inputGroup, containerClassName = styles.inputGroupContainer, inputClassName, - type = 'text', + type, unit, buttons = [], helpText, @@ -173,7 +204,7 @@ function FormInputGroup(props: FormInputGroupProps) { ...otherProps } = props; - const InputComponent = getComponent(type); + const InputComponent = componentMap[type]; const checkInput = type === inputTypes.CHECK; const hasError = !!errors.length; const hasWarning = !hasError && !!warnings.length; @@ -185,7 +216,7 @@ function FormInputGroup(props: FormInputGroupProps) {
- {/* @ts-expect-error - need to pass through all the expected options */} + {/* @ts-expect-error - types are validated already */} , 'value'> { +export interface NumberInputProps + extends Omit { value?: number | null; min?: number; max?: number; isFloat?: boolean; + onChange: (input: InputChanged) => void; } function NumberInput({ diff --git a/frontend/src/Components/Form/OAuthInput.tsx b/frontend/src/Components/Form/OAuthInput.tsx index 04d2a0caf..19bc9ba23 100644 --- a/frontend/src/Components/Form/OAuthInput.tsx +++ b/frontend/src/Components/Form/OAuthInput.tsx @@ -6,7 +6,7 @@ import { kinds } from 'Helpers/Props'; import { resetOAuth, startOAuth } from 'Store/Actions/oAuthActions'; import { InputOnChange } from 'typings/inputs'; -interface OAuthInputProps { +export interface OAuthInputProps { label?: string; name: string; provider: string; diff --git a/frontend/src/Components/Form/PasswordInput.tsx b/frontend/src/Components/Form/PasswordInput.tsx index 776c2b913..98da46e7e 100644 --- a/frontend/src/Components/Form/PasswordInput.tsx +++ b/frontend/src/Components/Form/PasswordInput.tsx @@ -7,7 +7,7 @@ function onCopy(e: SyntheticEvent) { e.nativeEvent.stopImmediatePropagation(); } -function PasswordInput(props: TextInputProps) { +function PasswordInput(props: TextInputProps) { return ; } diff --git a/frontend/src/Components/Form/PathInput.tsx b/frontend/src/Components/Form/PathInput.tsx index f353f1be4..86505b8d1 100644 --- a/frontend/src/Components/Form/PathInput.tsx +++ b/frontend/src/Components/Form/PathInput.tsx @@ -23,7 +23,7 @@ import AutoSuggestInput from './AutoSuggestInput'; import FormInputButton from './FormInputButton'; import styles from './PathInput.css'; -interface PathInputProps { +export interface PathInputProps { className?: string; name: string; value?: string; diff --git a/frontend/src/Components/Form/Select/AvailabilitySelectInput.tsx b/frontend/src/Components/Form/Select/AvailabilitySelectInput.tsx index c5d5cc506..cba94f5a6 100644 --- a/frontend/src/Components/Form/Select/AvailabilitySelectInput.tsx +++ b/frontend/src/Components/Form/Select/AvailabilitySelectInput.tsx @@ -5,7 +5,7 @@ import EnhancedSelectInput, { EnhancedSelectInputValue, } from './EnhancedSelectInput'; -interface AvailabilitySelectInputProps +export interface AvailabilitySelectInputProps extends Omit< EnhancedSelectInputProps, string>, 'values' diff --git a/frontend/src/Components/Form/Select/DownloadClientSelectInput.tsx b/frontend/src/Components/Form/Select/DownloadClientSelectInput.tsx index e385d95db..4dca13db7 100644 --- a/frontend/src/Components/Form/Select/DownloadClientSelectInput.tsx +++ b/frontend/src/Components/Form/Select/DownloadClientSelectInput.tsx @@ -51,7 +51,7 @@ function createDownloadClientsSelector( ); } -interface DownloadClientSelectInputProps +export interface DownloadClientSelectInputProps extends Omit< EnhancedSelectInputProps, number>, 'values' diff --git a/frontend/src/Components/Form/Select/IndexerFlagsSelectInput.tsx b/frontend/src/Components/Form/Select/IndexerFlagsSelectInput.tsx index a43044156..e4f149d3c 100644 --- a/frontend/src/Components/Form/Select/IndexerFlagsSelectInput.tsx +++ b/frontend/src/Components/Form/Select/IndexerFlagsSelectInput.tsx @@ -30,7 +30,7 @@ const selectIndexerFlagsValues = (selectedFlags: number) => } ); -interface IndexerFlagsSelectInputProps { +export interface IndexerFlagsSelectInputProps { name: string; indexerFlags: number; onChange(payload: EnhancedSelectInputChanged): void; diff --git a/frontend/src/Components/Form/Select/IndexerSelectInput.tsx b/frontend/src/Components/Form/Select/IndexerSelectInput.tsx index 7b4e05129..f4c7f4bb5 100644 --- a/frontend/src/Components/Form/Select/IndexerSelectInput.tsx +++ b/frontend/src/Components/Form/Select/IndexerSelectInput.tsx @@ -38,7 +38,7 @@ function createIndexersSelector(includeAny: boolean) { ); } -interface IndexerSelectInputProps { +export interface IndexerSelectInputProps { name: string; value: number; includeAny?: boolean; diff --git a/frontend/src/Components/Form/Select/LanguageSelectInput.tsx b/frontend/src/Components/Form/Select/LanguageSelectInput.tsx index 80efde065..179debb51 100644 --- a/frontend/src/Components/Form/Select/LanguageSelectInput.tsx +++ b/frontend/src/Components/Form/Select/LanguageSelectInput.tsx @@ -4,7 +4,7 @@ import EnhancedSelectInput, { EnhancedSelectInputValue, } from './EnhancedSelectInput'; -interface LanguageSelectInputProps { +export interface LanguageSelectInputProps { name: string; value: number; values: EnhancedSelectInputValue[]; diff --git a/frontend/src/Components/Form/Select/MonitorMoviesSelectInput.tsx b/frontend/src/Components/Form/Select/MonitorMoviesSelectInput.tsx index 724ac6e95..69d105d6b 100644 --- a/frontend/src/Components/Form/Select/MonitorMoviesSelectInput.tsx +++ b/frontend/src/Components/Form/Select/MonitorMoviesSelectInput.tsx @@ -6,7 +6,7 @@ import EnhancedSelectInput, { EnhancedSelectInputValue, } from './EnhancedSelectInput'; -interface MonitorMoviesSelectInputProps +export interface MonitorMoviesSelectInputProps extends Omit< EnhancedSelectInputProps, string>, 'values' diff --git a/frontend/src/Components/Form/Select/ProviderOptionSelectInput.tsx b/frontend/src/Components/Form/Select/ProviderOptionSelectInput.tsx index e4a8003eb..f85fcc8f3 100644 --- a/frontend/src/Components/Form/Select/ProviderOptionSelectInput.tsx +++ b/frontend/src/Components/Form/Select/ProviderOptionSelectInput.tsx @@ -69,7 +69,7 @@ function createProviderOptionsSelector( ); } -interface ProviderOptionSelectInputProps +export interface ProviderOptionSelectInputProps extends Omit< EnhancedSelectInputProps, unknown>, 'values' diff --git a/frontend/src/Components/Form/Select/QualityProfileSelectInput.tsx b/frontend/src/Components/Form/Select/QualityProfileSelectInput.tsx index 1bc913f0f..ac611481f 100644 --- a/frontend/src/Components/Form/Select/QualityProfileSelectInput.tsx +++ b/frontend/src/Components/Form/Select/QualityProfileSelectInput.tsx @@ -56,7 +56,7 @@ function createQualityProfilesSelector( ); } -interface QualityProfileSelectInputProps +export interface QualityProfileSelectInputProps extends Omit< EnhancedSelectInputProps< EnhancedSelectInputValue, diff --git a/frontend/src/Components/Form/Select/RootFolderSelectInput.tsx b/frontend/src/Components/Form/Select/RootFolderSelectInput.tsx index ad4329f9d..8ddbd20ea 100644 --- a/frontend/src/Components/Form/Select/RootFolderSelectInput.tsx +++ b/frontend/src/Components/Form/Select/RootFolderSelectInput.tsx @@ -24,7 +24,7 @@ export interface RootFolderSelectInputValue isMissing?: boolean; } -interface RootFolderSelectInputProps +export interface RootFolderSelectInputProps extends Omit< EnhancedSelectInputProps, string>, 'value' | 'values' diff --git a/frontend/src/Components/Form/Select/UMaskInput.tsx b/frontend/src/Components/Form/Select/UMaskInput.tsx index 7a6c49fec..2f528ba91 100644 --- a/frontend/src/Components/Form/Select/UMaskInput.tsx +++ b/frontend/src/Components/Form/Select/UMaskInput.tsx @@ -66,7 +66,7 @@ function formatPermissions(permissions: number) { return result; } -interface UMaskInputProps { +export interface UMaskInputProps { name: string; value: string; hasError?: boolean; diff --git a/frontend/src/Components/Form/Tag/DeviceInput.tsx b/frontend/src/Components/Form/Tag/DeviceInput.tsx index 3c483d1f2..229776059 100644 --- a/frontend/src/Components/Form/Tag/DeviceInput.tsx +++ b/frontend/src/Components/Form/Tag/DeviceInput.tsx @@ -19,7 +19,7 @@ interface DeviceTag { name: string; } -interface DeviceInputProps extends TagInputProps { +export interface DeviceInputProps extends TagInputProps { className?: string; name: string; value: string[]; diff --git a/frontend/src/Components/Form/Tag/MovieTagInput.tsx b/frontend/src/Components/Form/Tag/MovieTagInput.tsx index 051ac438a..e6f896537 100644 --- a/frontend/src/Components/Form/Tag/MovieTagInput.tsx +++ b/frontend/src/Components/Form/Tag/MovieTagInput.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useMemo } from 'react'; +import React, { useCallback } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { createSelector } from 'reselect'; import { addTag } from 'Store/Actions/tagActions'; @@ -12,10 +12,10 @@ interface MovieTag extends TagBase { name: string; } -interface MovieTagInputProps { +export interface MovieTagInputProps { name: string; - value: number | number[]; - onChange: (change: InputChanged) => void; + value: number[]; + onChange: (change: InputChanged) => void; } const VALID_TAG_REGEX = new RegExp('[^-_a-z0-9]', 'i'); @@ -65,42 +65,22 @@ export default function MovieTagInput({ onChange, }: MovieTagInputProps) { const dispatch = useDispatch(); - const isArray = Array.isArray(value); - - const arrayValue = useMemo(() => { - if (isArray) { - return value; - } - - return value === 0 ? [] : [value]; - }, [isArray, value]); const { tags, tagList, allTags } = useSelector( - createMovieTagsSelector(arrayValue) + createMovieTagsSelector(value) ); const handleTagCreated = useCallback( (tag: MovieTag) => { - if (isArray) { - onChange({ name, value: [...value, tag.id] }); - } else { - onChange({ - name, - value: tag.id, - }); - } + onChange({ name, value: [...value, tag.id] }); }, - [name, value, isArray, onChange] + [name, value, onChange] ); const handleTagAdd = useCallback( (newTag: MovieTag) => { if (newTag.id) { - if (isArray) { - onChange({ name, value: [...value, newTag.id] }); - } else { - onChange({ name, value: newTag.id }); - } + onChange({ name, value: [...value, newTag.id] }); return; } @@ -116,21 +96,17 @@ export default function MovieTagInput({ ); } }, - [name, value, isArray, allTags, handleTagCreated, onChange, dispatch] + [name, value, allTags, handleTagCreated, onChange, dispatch] ); const handleTagDelete = useCallback( ({ index }: { index: number }) => { - if (isArray) { - const newValue = value.slice(); - newValue.splice(index, 1); + const newValue = value.slice(); + newValue.splice(index, 1); - onChange({ name, value: newValue }); - } else { - onChange({ name, value: 0 }); - } + onChange({ name, value: newValue }); }, - [name, value, isArray, onChange] + [name, value, onChange] ); return ( diff --git a/frontend/src/Components/Form/Tag/TagSelectInput.tsx b/frontend/src/Components/Form/Tag/TagSelectInput.tsx index 21fde893c..139b7ba84 100644 --- a/frontend/src/Components/Form/Tag/TagSelectInput.tsx +++ b/frontend/src/Components/Form/Tag/TagSelectInput.tsx @@ -13,7 +13,7 @@ interface TagSelectValue { order: number; } -interface TagSelectInputProps extends TagInputProps { +export interface TagSelectInputProps extends TagInputProps { name: string; value: number[]; values: TagSelectValue[]; diff --git a/frontend/src/Components/Form/Tag/TextTagInput.tsx b/frontend/src/Components/Form/Tag/TextTagInput.tsx index 6e2082c50..6d2652030 100644 --- a/frontend/src/Components/Form/Tag/TextTagInput.tsx +++ b/frontend/src/Components/Form/Tag/TextTagInput.tsx @@ -8,7 +8,11 @@ interface TextTag extends TagBase { name: string; } -interface TextTagInputProps extends TagInputProps { +export interface TextTagInputProps + extends Omit< + TagInputProps, + 'tags' | 'tagList' | 'onTagAdd' | 'onTagDelete' + > { name: string; value: string | string[]; onChange: (change: InputChanged) => unknown; diff --git a/frontend/src/Components/Form/TextArea.tsx b/frontend/src/Components/Form/TextArea.tsx index f37d5cb5f..047817eb7 100644 --- a/frontend/src/Components/Form/TextArea.tsx +++ b/frontend/src/Components/Form/TextArea.tsx @@ -9,7 +9,7 @@ import React, { import { InputChanged } from 'typings/inputs'; import styles from './TextArea.css'; -interface TextAreaProps { +export interface TextAreaProps { className?: string; readOnly?: boolean; autoFocus?: boolean; diff --git a/frontend/src/Components/Form/TextInput.tsx b/frontend/src/Components/Form/TextInput.tsx index 647b9f2ac..9cc2666a3 100644 --- a/frontend/src/Components/Form/TextInput.tsx +++ b/frontend/src/Components/Form/TextInput.tsx @@ -7,13 +7,11 @@ import React, { useEffect, useRef, } from 'react'; -import { InputType } from 'Helpers/Props/inputTypes'; import { FileInputChanged, InputChanged } from 'typings/inputs'; import styles from './TextInput.css'; -export interface TextInputProps { +export interface CommonTextInputProps { className?: string; - type?: InputType; readOnly?: boolean; autoFocus?: boolean; placeholder?: string; @@ -25,14 +23,23 @@ export interface TextInputProps { step?: number; min?: number; max?: number; - onChange: (change: InputChanged | FileInputChanged) => void; - onFocus?: (event: FocusEvent) => void; + onFocus?: (event: FocusEvent) => void; onBlur?: (event: SyntheticEvent) => void; onCopy?: (event: SyntheticEvent) => void; onSelectionChange?: (start: number | null, end: number | null) => void; } -function TextInput({ +export interface TextInputProps extends CommonTextInputProps { + type?: 'date' | 'number' | 'password' | 'text'; + onChange: (change: InputChanged) => void; +} + +export interface FileInputProps extends CommonTextInputProps { + type: 'file'; + onChange: (change: FileInputChanged) => void; +} + +function TextInput({ className = styles.input, type = 'text', readOnly = false, @@ -51,7 +58,7 @@ function TextInput({ onCopy, onChange, onSelectionChange, -}: TextInputProps) { +}: TextInputProps | FileInputProps): JSX.Element { const inputRef = useRef(null); const selectionTimeout = useRef>(); const selectionStart = useRef(); @@ -95,7 +102,7 @@ function TextInput({ ); const handleFocus = useCallback( - (event: FocusEvent) => { + (event: FocusEvent) => { onFocus?.(event); selectionChanged(); diff --git a/frontend/src/Helpers/Props/inputTypes.ts b/frontend/src/Helpers/Props/inputTypes.ts index c7fbd1600..af94fc8c4 100644 --- a/frontend/src/Helpers/Props/inputTypes.ts +++ b/frontend/src/Helpers/Props/inputTypes.ts @@ -4,7 +4,7 @@ export const CAPTCHA = 'captcha'; export const CHECK = 'check'; export const DEVICE = 'device'; export const KEY_VALUE_LIST = 'keyValueList'; -export const MOVIE_MONITORED_SELECT = 'movieMonitoredSelect'; +export const MONITOR_MOVIES_SELECT = 'monitorMoviesSelect'; export const FLOAT = 'float'; export const NUMBER = 'number'; export const OAUTH = 'oauth'; @@ -33,7 +33,7 @@ export const all = [ CHECK, DEVICE, KEY_VALUE_LIST, - MOVIE_MONITORED_SELECT, + MONITOR_MOVIES_SELECT, FLOAT, NUMBER, OAUTH, @@ -61,9 +61,10 @@ export type InputType = | 'availabilitySelect' | 'captcha' | 'check' + | 'date' | 'device' | 'keyValueList' - | 'movieMonitoredSelect' + | 'monitorMoviesSelect' | 'file' | 'float' | 'number' diff --git a/frontend/src/InteractiveImport/Quality/SelectQualityModalContent.tsx b/frontend/src/InteractiveImport/Quality/SelectQualityModalContent.tsx index 64eea3bf0..f4e252734 100644 --- a/frontend/src/InteractiveImport/Quality/SelectQualityModalContent.tsx +++ b/frontend/src/InteractiveImport/Quality/SelectQualityModalContent.tsx @@ -86,8 +86,8 @@ function SelectQualityModalContent(props: SelectQualityModalContentProps) { }, [items]); const onQualityChange = useCallback( - ({ value }: { value: string }) => { - setQualityId(parseInt(value)); + ({ value }: { value: number }) => { + setQualityId(value); }, [setQualityId] ); @@ -128,7 +128,7 @@ function SelectQualityModalContent(props: SelectQualityModalContentProps) { - {isFetching && } + {isFetching ? : null} {!isFetching && error ? ( {translate('QualitiesLoadError')} diff --git a/frontend/src/Movie/Edit/EditMovieModalContent.tsx b/frontend/src/Movie/Edit/EditMovieModalContent.tsx index 8f2831377..4ee7a281b 100644 --- a/frontend/src/Movie/Edit/EditMovieModalContent.tsx +++ b/frontend/src/Movie/Edit/EditMovieModalContent.tsx @@ -183,6 +183,7 @@ function EditMovieModalContent({ type={inputTypes.PATH} name="path" {...settings.path} + includeFiles={false} onChange={handleInputChange} /> diff --git a/frontend/src/Movie/Index/Select/Delete/DeleteMovieModalContent.tsx b/frontend/src/Movie/Index/Select/Delete/DeleteMovieModalContent.tsx index 4a5ba463c..f8208f7cb 100644 --- a/frontend/src/Movie/Index/Select/Delete/DeleteMovieModalContent.tsx +++ b/frontend/src/Movie/Index/Select/Delete/DeleteMovieModalContent.tsx @@ -141,7 +141,7 @@ function DeleteMovieModalContent(props: DeleteMovieModalContentProps) { ? translate('DeleteMovieFoldersHelpText') : translate('DeleteMovieFolderHelpText') } - kind={kinds.DANGER} + kind="danger" onChange={onDeleteFilesChange} /> diff --git a/frontend/src/Movie/Index/Select/Edit/EditMoviesModalContent.tsx b/frontend/src/Movie/Index/Select/Edit/EditMoviesModalContent.tsx index cf8c3ab7e..09a6414e7 100644 --- a/frontend/src/Movie/Index/Select/Edit/EditMoviesModalContent.tsx +++ b/frontend/src/Movie/Index/Select/Edit/EditMoviesModalContent.tsx @@ -9,6 +9,7 @@ import ModalFooter from 'Components/Modal/ModalFooter'; import ModalHeader from 'Components/Modal/ModalHeader'; import { inputTypes } from 'Helpers/Props'; import MoveMovieModal from 'Movie/MoveMovie/MoveMovieModal'; +import { InputChanged } from 'typings/inputs'; import translate from 'Utilities/String/translate'; import styles from './EditMoviesModalContent.css'; @@ -104,19 +105,19 @@ function EditMoviesModalContent(props: EditMoviesModalContentProps) { ); const onInputChange = useCallback( - ({ name, value }: { name: string; value: string }) => { + ({ name, value }: InputChanged) => { switch (name) { case 'monitored': - setMonitored(value); + setMonitored(value as string); break; case 'qualityProfileId': - setQualityProfileId(value); + setQualityProfileId(value as string); break; case 'minimumAvailability': - setMinimumAvailability(value); + setMinimumAvailability(value as string); break; case 'rootFolderPath': - setRootFolderPath(value); + setRootFolderPath(value as string); break; default: console.warn('EditMoviesModalContent Unknown Input'); diff --git a/frontend/src/Settings/DownloadClients/DownloadClients/Manage/Edit/ManageDownloadClientsEditModalContent.tsx b/frontend/src/Settings/DownloadClients/DownloadClients/Manage/Edit/ManageDownloadClientsEditModalContent.tsx index b77576391..ebe335338 100644 --- a/frontend/src/Settings/DownloadClients/DownloadClients/Manage/Edit/ManageDownloadClientsEditModalContent.tsx +++ b/frontend/src/Settings/DownloadClients/DownloadClients/Manage/Edit/ManageDownloadClientsEditModalContent.tsx @@ -8,6 +8,7 @@ import ModalContent from 'Components/Modal/ModalContent'; import ModalFooter from 'Components/Modal/ModalFooter'; import ModalHeader from 'Components/Modal/ModalHeader'; import { inputTypes } from 'Helpers/Props'; +import { InputChanged } from 'typings/inputs'; import translate from 'Utilities/String/translate'; import styles from './ManageDownloadClientsEditModalContent.css'; @@ -57,7 +58,7 @@ function ManageDownloadClientsEditModalContent( const [removeCompletedDownloads, setRemoveCompletedDownloads] = useState(NO_CHANGE); const [removeFailedDownloads, setRemoveFailedDownloads] = useState(NO_CHANGE); - const [priority, setPriority] = useState(null); + const [priority, setPriority] = useState(null); const save = useCallback(() => { let hasChanges = false; @@ -97,29 +98,26 @@ function ManageDownloadClientsEditModalContent( onModalClose, ]); - const onInputChange = useCallback( - ({ name, value }: { name: string; value: string }) => { - switch (name) { - case 'enable': - setEnable(value); - break; - case 'priority': - setPriority(value); - break; - case 'removeCompletedDownloads': - setRemoveCompletedDownloads(value); - break; - case 'removeFailedDownloads': - setRemoveFailedDownloads(value); - break; - default: - console.warn( - `EditDownloadClientsModalContent Unknown Input: '${name}'` - ); - } - }, - [] - ); + const onInputChange = useCallback(({ name, value }: InputChanged) => { + switch (name) { + case 'enable': + setEnable(value as string); + break; + case 'priority': + setPriority(value as number); + break; + case 'removeCompletedDownloads': + setRemoveCompletedDownloads(value as string); + break; + case 'removeFailedDownloads': + setRemoveFailedDownloads(value as string); + break; + default: + console.warn( + `EditDownloadClientsModalContent Unknown Input: '${name}'` + ); + } + }, []); const selectedCount = downloadClientIds.length; diff --git a/frontend/src/Settings/General/BackupSettings.js b/frontend/src/Settings/General/BackupSettings.js index 5f0f79c27..4d7c95b71 100644 --- a/frontend/src/Settings/General/BackupSettings.js +++ b/frontend/src/Settings/General/BackupSettings.js @@ -36,6 +36,7 @@ function BackupSettings(props) { type={inputTypes.PATH} name="backupFolder" helpText={translate('BackupFolderHelpText')} + includeFiles={false} onChange={onInputChange} {...backupFolder} /> diff --git a/frontend/src/Settings/ImportLists/ImportListExclusions/EditImportListExclusionModalContent.tsx b/frontend/src/Settings/ImportLists/ImportListExclusions/EditImportListExclusionModalContent.tsx index 8ff410c71..72d768a0c 100644 --- a/frontend/src/Settings/ImportLists/ImportListExclusions/EditImportListExclusionModalContent.tsx +++ b/frontend/src/Settings/ImportLists/ImportListExclusions/EditImportListExclusionModalContent.tsx @@ -22,6 +22,7 @@ import { } from 'Store/Actions/settingsActions'; import selectSettings from 'Store/Selectors/selectSettings'; import ImportListExclusion from 'typings/ImportListExclusion'; +import { InputChanged } from 'typings/inputs'; import { PendingSection } from 'typings/pending'; import translate from 'Utilities/String/translate'; import styles from './EditImportListExclusionModalContent.css'; @@ -104,9 +105,9 @@ function EditImportListExclusionModalContent({ }, [dispatch, id]); const onInputChange = useCallback( - (payload: { name: string; value: string | number }) => { + (change: InputChanged) => { // @ts-expect-error 'setImportListExclusionValue' isn't typed yet - dispatch(setImportListExclusionValue(payload)); + dispatch(setImportListExclusionValue(change)); }, [dispatch] ); diff --git a/frontend/src/Settings/ImportLists/ImportLists/EditImportListModalContent.js b/frontend/src/Settings/ImportLists/ImportLists/EditImportListModalContent.js index d80427d48..cb7c933a6 100644 --- a/frontend/src/Settings/ImportLists/ImportLists/EditImportListModalContent.js +++ b/frontend/src/Settings/ImportLists/ImportLists/EditImportListModalContent.js @@ -142,7 +142,7 @@ function EditImportListModalContent(props) { {translate('Monitor')} { - switch (name) { - case 'enabled': - setEnabled(value); - break; - case 'enableAuto': - setEnableAuto(value); - break; - case 'qualityProfileId': - setQualityProfileId(value); - break; - case 'minimumAvailability': - setMinimumAvailability(value); - break; - case 'rootFolderPath': - setRootFolderPath(value); - break; - default: - console.warn(`EditImportListModalContent Unknown Input: '${name}'`); - } - }, - [] - ); + const onInputChange = useCallback(({ name, value }: InputChanged) => { + switch (name) { + case 'enabled': + setEnabled(value as string); + break; + case 'enableAuto': + setEnableAuto(value as string); + break; + case 'qualityProfileId': + setQualityProfileId(value as string); + break; + case 'minimumAvailability': + setMinimumAvailability(value as string); + break; + case 'rootFolderPath': + setRootFolderPath(value as string); + break; + default: + console.warn(`EditImportListModalContent Unknown Input: '${name}'`); + } + }, []); const selectedCount = importListIds.length; diff --git a/frontend/src/Settings/Indexers/Indexers/Manage/Edit/ManageIndexersEditModalContent.tsx b/frontend/src/Settings/Indexers/Indexers/Manage/Edit/ManageIndexersEditModalContent.tsx index bfef17d94..804f7629a 100644 --- a/frontend/src/Settings/Indexers/Indexers/Manage/Edit/ManageIndexersEditModalContent.tsx +++ b/frontend/src/Settings/Indexers/Indexers/Manage/Edit/ManageIndexersEditModalContent.tsx @@ -8,6 +8,7 @@ import ModalContent from 'Components/Modal/ModalContent'; import ModalFooter from 'Components/Modal/ModalFooter'; import ModalHeader from 'Components/Modal/ModalHeader'; import { inputTypes } from 'Helpers/Props'; +import { InputChanged } from 'typings/inputs'; import translate from 'Utilities/String/translate'; import styles from './ManageIndexersEditModalContent.css'; @@ -57,7 +58,7 @@ function ManageIndexersEditModalContent( const [enableAutomaticSearch, setEnableAutomaticSearch] = useState(NO_CHANGE); const [enableInteractiveSearch, setEnableInteractiveSearch] = useState(NO_CHANGE); - const [priority, setPriority] = useState(null); + const [priority, setPriority] = useState(null); const save = useCallback(() => { let hasChanges = false; @@ -97,27 +98,24 @@ function ManageIndexersEditModalContent( onModalClose, ]); - const onInputChange = useCallback( - ({ name, value }: { name: string; value: string }) => { - switch (name) { - case 'enableRss': - setEnableRss(value); - break; - case 'enableAutomaticSearch': - setEnableAutomaticSearch(value); - break; - case 'enableInteractiveSearch': - setEnableInteractiveSearch(value); - break; - case 'priority': - setPriority(value); - break; - default: - console.warn(`EditIndexersModalContent Unknown Input: '${name}'`); - } - }, - [] - ); + const onInputChange = useCallback(({ name, value }: InputChanged) => { + switch (name) { + case 'enableRss': + setEnableRss(value as string); + break; + case 'enableAutomaticSearch': + setEnableAutomaticSearch(value as string); + break; + case 'enableInteractiveSearch': + setEnableInteractiveSearch(value as string); + break; + case 'priority': + setPriority(value as number); + break; + default: + console.warn(`EditIndexersModalContent Unknown Input: '${name}'`); + } + }, []); const selectedCount = indexerIds.length; diff --git a/frontend/src/Settings/MediaManagement/Naming/Naming.tsx b/frontend/src/Settings/MediaManagement/Naming/Naming.tsx index 0a09fc4d0..771afd792 100644 --- a/frontend/src/Settings/MediaManagement/Naming/Naming.tsx +++ b/frontend/src/Settings/MediaManagement/Naming/Naming.tsx @@ -19,6 +19,7 @@ import { setNamingSettingsValue, } from 'Store/Actions/settingsActions'; import createSettingsSectionSelector from 'Store/Selectors/createSettingsSectionSelector'; +import { InputChanged } from 'typings/inputs'; import NamingConfig from 'typings/Settings/NamingConfig'; import translate from 'Utilities/String/translate'; import NamingModal from './NamingModal'; @@ -77,9 +78,9 @@ function Naming() { }, [dispatch]); const handleInputChange = useCallback( - ({ name, value }: { name: string; value: string }) => { + (change: InputChanged) => { // @ts-expect-error 'setNamingSettingsValue' isn't typed yet - dispatch(setNamingSettingsValue({ name, value })); + dispatch(setNamingSettingsValue(change)); if (namingExampleTimeout.current) { clearTimeout(namingExampleTimeout.current); diff --git a/frontend/src/Settings/Profiles/Release/EditReleaseProfileModalContent.tsx b/frontend/src/Settings/Profiles/Release/EditReleaseProfileModalContent.tsx index ceb1d6a43..b478e403e 100644 --- a/frontend/src/Settings/Profiles/Release/EditReleaseProfileModalContent.tsx +++ b/frontend/src/Settings/Profiles/Release/EditReleaseProfileModalContent.tsx @@ -19,6 +19,7 @@ import { setReleaseProfileValue, } from 'Store/Actions/Settings/releaseProfiles'; import selectSettings from 'Store/Selectors/selectSettings'; +import { InputChanged } from 'typings/inputs'; import ReleaseProfile from 'typings/Settings/ReleaseProfile'; import translate from 'Utilities/String/translate'; import styles from './EditReleaseProfileModalContent.css'; @@ -102,9 +103,9 @@ function EditReleaseProfileModalContent({ }, [dispatch, id]); const handleInputChange = useCallback( - (payload: { name: string; value: string | number }) => { + (change: InputChanged) => { // @ts-expect-error 'setReleaseProfileValue' isn't typed yet - dispatch(setReleaseProfileValue(payload)); + dispatch(setReleaseProfileValue(change)); }, [dispatch] ); @@ -125,7 +126,6 @@ function EditReleaseProfileModalContent({ name="name" {...name} placeholder={translate('OptionalName')} - canEdit={true} onChange={handleInputChange} />