From 89f584d1b3c4005727863085c67e2130413000af Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Fri, 27 Dec 2024 10:43:10 -0800 Subject: [PATCH] Convert Tags to TypeScript --- frontend/src/App/State/AppSectionState.ts | 8 +- frontend/src/App/State/SettingsAppState.ts | 22 ++ .../Form/ProviderFieldFormGroup.tsx | 1 + .../Form/Select/QualityProfileSelectInput.tsx | 2 +- .../Components/Page/PageSectionContent.tsx | 5 +- .../Settings/Metadata/Metadata/Metadatas.tsx | 2 +- .../{AutoTagging.js => AutoTagging.tsx} | 99 +++---- .../{AutoTaggings.js => AutoTaggings.tsx} | 82 +++--- .../Tags/AutoTagging/EditAutoTaggingModal.js | 50 ---- .../Tags/AutoTagging/EditAutoTaggingModal.tsx | 35 +++ .../EditAutoTaggingModalContent.js | 270 ------------------ .../EditAutoTaggingModalContent.tsx | 258 +++++++++++++++++ .../Specifications/AddSpecificationItem.js | 101 ------- .../Specifications/AddSpecificationItem.tsx | 81 ++++++ .../Specifications/AddSpecificationModal.js | 25 -- .../Specifications/AddSpecificationModal.tsx | 21 ++ .../AddSpecificationModalContent.js | 106 ------- .../AddSpecificationModalContent.tsx | 90 ++++++ .../AddSpecificationPresetMenuItem.js | 34 --- .../AddSpecificationPresetMenuItem.tsx | 34 +++ ...ionModal.js => EditSpecificationModal.tsx} | 32 ++- .../EditSpecificationModalContent.js | 190 ------------ .../EditSpecificationModalContent.tsx | 192 +++++++++++++ .../EditSpecificationModalContentConnector.js | 78 ----- .../{Specification.js => Specification.tsx} | 72 ++--- .../Tags/Details/TagDetailsDelayProfile.js | 48 ---- .../Tags/Details/TagDetailsDelayProfile.tsx | 41 +++ .../Settings/Tags/Details/TagDetailsModal.js | 33 --- .../Settings/Tags/Details/TagDetailsModal.tsx | 25 ++ .../Tags/Details/TagDetailsModalContent.js | 257 ----------------- .../Tags/Details/TagDetailsModalContent.tsx | 269 +++++++++++++++++ .../TagDetailsModalContentConnector.js | 121 -------- frontend/src/Settings/Tags/Tag.js | 207 -------------- frontend/src/Settings/Tags/Tag.tsx | 151 ++++++++++ frontend/src/Settings/Tags/TagConnector.js | 22 -- .../Tags/{TagInUse.js => TagInUse.tsx} | 19 +- .../Tags/{TagSettings.js => TagSettings.tsx} | 8 +- frontend/src/Settings/Tags/Tags.js | 54 ---- frontend/src/Settings/Tags/Tags.tsx | 73 +++++ frontend/src/Settings/Tags/TagsConnector.js | 90 ------ .../createEnabledDownloadClientsSelector.ts | 2 +- .../createProviderSettingsSelector.js | 71 ----- .../createProviderSettingsSelector.ts | 100 +++++++ .../Selectors/createRootFoldersSelector.ts | 5 +- .../Selectors/createSortedSectionSelector.ts | 13 +- .../Selectors/createTagDetailsSelector.ts | 5 +- .../src/Store/Selectors/selectSettings.ts | 8 +- frontend/src/typings/AutoTagging.ts | 21 ++ frontend/src/typings/DelayProfile.ts | 17 ++ 49 files changed, 1601 insertions(+), 1949 deletions(-) rename frontend/src/Settings/Tags/AutoTagging/{AutoTagging.js => AutoTagging.tsx} (63%) rename frontend/src/Settings/Tags/AutoTagging/{AutoTaggings.js => AutoTaggings.tsx} (60%) delete mode 100644 frontend/src/Settings/Tags/AutoTagging/EditAutoTaggingModal.js create mode 100644 frontend/src/Settings/Tags/AutoTagging/EditAutoTaggingModal.tsx delete mode 100644 frontend/src/Settings/Tags/AutoTagging/EditAutoTaggingModalContent.js create mode 100644 frontend/src/Settings/Tags/AutoTagging/EditAutoTaggingModalContent.tsx delete mode 100644 frontend/src/Settings/Tags/AutoTagging/Specifications/AddSpecificationItem.js create mode 100644 frontend/src/Settings/Tags/AutoTagging/Specifications/AddSpecificationItem.tsx delete mode 100644 frontend/src/Settings/Tags/AutoTagging/Specifications/AddSpecificationModal.js create mode 100644 frontend/src/Settings/Tags/AutoTagging/Specifications/AddSpecificationModal.tsx delete mode 100644 frontend/src/Settings/Tags/AutoTagging/Specifications/AddSpecificationModalContent.js create mode 100644 frontend/src/Settings/Tags/AutoTagging/Specifications/AddSpecificationModalContent.tsx delete mode 100644 frontend/src/Settings/Tags/AutoTagging/Specifications/AddSpecificationPresetMenuItem.js create mode 100644 frontend/src/Settings/Tags/AutoTagging/Specifications/AddSpecificationPresetMenuItem.tsx rename frontend/src/Settings/Tags/AutoTagging/Specifications/{EditSpecificationModal.js => EditSpecificationModal.tsx} (50%) delete mode 100644 frontend/src/Settings/Tags/AutoTagging/Specifications/EditSpecificationModalContent.js create mode 100644 frontend/src/Settings/Tags/AutoTagging/Specifications/EditSpecificationModalContent.tsx delete mode 100644 frontend/src/Settings/Tags/AutoTagging/Specifications/EditSpecificationModalContentConnector.js rename frontend/src/Settings/Tags/AutoTagging/Specifications/{Specification.js => Specification.tsx} (65%) delete mode 100644 frontend/src/Settings/Tags/Details/TagDetailsDelayProfile.js create mode 100644 frontend/src/Settings/Tags/Details/TagDetailsDelayProfile.tsx delete mode 100644 frontend/src/Settings/Tags/Details/TagDetailsModal.js create mode 100644 frontend/src/Settings/Tags/Details/TagDetailsModal.tsx delete mode 100644 frontend/src/Settings/Tags/Details/TagDetailsModalContent.js create mode 100644 frontend/src/Settings/Tags/Details/TagDetailsModalContent.tsx delete mode 100644 frontend/src/Settings/Tags/Details/TagDetailsModalContentConnector.js delete mode 100644 frontend/src/Settings/Tags/Tag.js create mode 100644 frontend/src/Settings/Tags/Tag.tsx delete mode 100644 frontend/src/Settings/Tags/TagConnector.js rename frontend/src/Settings/Tags/{TagInUse.js => TagInUse.tsx} (50%) rename frontend/src/Settings/Tags/{TagSettings.js => TagSettings.tsx} (80%) delete mode 100644 frontend/src/Settings/Tags/Tags.js create mode 100644 frontend/src/Settings/Tags/Tags.tsx delete mode 100644 frontend/src/Settings/Tags/TagsConnector.js delete mode 100644 frontend/src/Store/Selectors/createProviderSettingsSelector.js create mode 100644 frontend/src/Store/Selectors/createProviderSettingsSelector.ts create mode 100644 frontend/src/typings/AutoTagging.ts create mode 100644 frontend/src/typings/DelayProfile.ts diff --git a/frontend/src/App/State/AppSectionState.ts b/frontend/src/App/State/AppSectionState.ts index 771ce2c19..34b5af597 100644 --- a/frontend/src/App/State/AppSectionState.ts +++ b/frontend/src/App/State/AppSectionState.ts @@ -43,9 +43,8 @@ export interface AppSectionSchemaState { isSchemaFetching: boolean; isSchemaPopulated: boolean; schemaError: Error; - schema: { - items: T[]; - }; + schema: T[]; + selectedSchema?: T; } export interface AppSectionItemSchemaState { @@ -68,9 +67,10 @@ export interface AppSectionProviderState AppSectionSaveState { isFetching: boolean; isPopulated: boolean; + isTesting?: boolean; error: Error; items: T[]; - pendingChanges: Partial; + pendingChanges?: Partial; } interface AppSectionState { diff --git a/frontend/src/App/State/SettingsAppState.ts b/frontend/src/App/State/SettingsAppState.ts index b8e6f4954..3c032c1d7 100644 --- a/frontend/src/App/State/SettingsAppState.ts +++ b/frontend/src/App/State/SettingsAppState.ts @@ -3,10 +3,13 @@ import AppSectionState, { AppSectionItemSchemaState, AppSectionItemState, AppSectionSaveState, + AppSectionSchemaState, PagedAppSectionState, } from 'App/State/AppSectionState'; import Language from 'Language/Language'; +import AutoTagging, { AutoTaggingSpecification } from 'typings/AutoTagging'; import CustomFormat from 'typings/CustomFormat'; +import DelayProfile from 'typings/DelayProfile'; import DownloadClient from 'typings/DownloadClient'; import ImportList from 'typings/ImportList'; import ImportListExclusion from 'typings/ImportListExclusion'; @@ -22,6 +25,22 @@ import ReleaseProfile from 'typings/Settings/ReleaseProfile'; import UiSettings from 'typings/Settings/UiSettings'; import MetadataAppState from './MetadataAppState'; +export interface AutoTaggingAppState + extends AppSectionState, + AppSectionDeleteState, + AppSectionSaveState {} + +export interface AutoTaggingSpecificationAppState + extends AppSectionState, + AppSectionDeleteState, + AppSectionSaveState, + AppSectionSchemaState {} + +export interface DelayProfileAppState + extends AppSectionState, + AppSectionDeleteState, + AppSectionSaveState {} + export interface DownloadClientAppState extends AppSectionState, AppSectionDeleteState, @@ -88,7 +107,10 @@ export type UiSettingsAppState = AppSectionItemState; interface SettingsAppState { advancedSettings: boolean; + autoTaggings: AutoTaggingAppState; + autoTaggingSpecifications: AutoTaggingSpecificationAppState; customFormats: CustomFormatAppState; + delayProfiles: DelayProfileAppState; downloadClients: DownloadClientAppState; general: GeneralAppState; importListExclusions: ImportListExclusionsSettingsAppState; diff --git a/frontend/src/Components/Form/ProviderFieldFormGroup.tsx b/frontend/src/Components/Form/ProviderFieldFormGroup.tsx index ece6d090b..ca6685134 100644 --- a/frontend/src/Components/Form/ProviderFieldFormGroup.tsx +++ b/frontend/src/Components/Form/ProviderFieldFormGroup.tsx @@ -20,6 +20,7 @@ interface ProviderFieldFormGroupProps { hidden?: string; isDisabled?: boolean; provider?: string; + providerData?: object; pending: boolean; errors: Failure[]; warnings: Failure[]; diff --git a/frontend/src/Components/Form/Select/QualityProfileSelectInput.tsx b/frontend/src/Components/Form/Select/QualityProfileSelectInput.tsx index 036f0f82c..485264349 100644 --- a/frontend/src/Components/Form/Select/QualityProfileSelectInput.tsx +++ b/frontend/src/Components/Form/Select/QualityProfileSelectInput.tsx @@ -18,7 +18,7 @@ function createQualityProfilesSelector( includeMixed: boolean ) { return createSelector( - createSortedSectionSelector( + createSortedSectionSelector( 'settings.qualityProfiles', sortByProp('name') ), diff --git a/frontend/src/Components/Page/PageSectionContent.tsx b/frontend/src/Components/Page/PageSectionContent.tsx index 808de3e38..f3470fe7e 100644 --- a/frontend/src/Components/Page/PageSectionContent.tsx +++ b/frontend/src/Components/Page/PageSectionContent.tsx @@ -1,4 +1,5 @@ import React from 'react'; +import { Error } from 'App/State/AppSectionState'; import Alert from 'Components/Alert'; import LoadingIndicator from 'Components/Loading/LoadingIndicator'; import { kinds } from 'Helpers/Props'; @@ -6,7 +7,7 @@ import { kinds } from 'Helpers/Props'; interface PageSectionContentProps { isFetching: boolean; isPopulated: boolean; - error?: object; + error?: Error; errorMessage: string; children: React.ReactNode; } @@ -18,7 +19,7 @@ function PageSectionContent({ errorMessage, children, }: PageSectionContentProps) { - if (isFetching) { + if (isFetching && !isPopulated) { return ; } diff --git a/frontend/src/Settings/Metadata/Metadata/Metadatas.tsx b/frontend/src/Settings/Metadata/Metadata/Metadatas.tsx index befe207d8..0f8a33f2c 100644 --- a/frontend/src/Settings/Metadata/Metadata/Metadatas.tsx +++ b/frontend/src/Settings/Metadata/Metadata/Metadatas.tsx @@ -14,7 +14,7 @@ import styles from './Metadatas.css'; function createMetadatasSelector() { return createSelector( - createSortedSectionSelector( + createSortedSectionSelector( 'settings.metadata', sortByProp('name') ), diff --git a/frontend/src/Settings/Tags/AutoTagging/AutoTagging.js b/frontend/src/Settings/Tags/AutoTagging/AutoTagging.tsx similarity index 63% rename from frontend/src/Settings/Tags/AutoTagging/AutoTagging.js rename to frontend/src/Settings/Tags/AutoTagging/AutoTagging.tsx index 760273cb3..cd484a33f 100644 --- a/frontend/src/Settings/Tags/AutoTagging/AutoTagging.js +++ b/frontend/src/Settings/Tags/AutoTagging/AutoTagging.tsx @@ -1,27 +1,38 @@ -import PropTypes from 'prop-types'; import React, { useCallback, useState } from 'react'; +import { Tag } from 'App/State/TagsAppState'; import Card from 'Components/Card'; import Label from 'Components/Label'; import IconButton from 'Components/Link/IconButton'; import ConfirmModal from 'Components/Modal/ConfirmModal'; import TagList from 'Components/TagList'; import { icons, kinds } from 'Helpers/Props'; +import { Kind } from 'Helpers/Props/kinds'; +import { AutoTaggingSpecification } from 'typings/AutoTagging'; import translate from 'Utilities/String/translate'; import EditAutoTaggingModal from './EditAutoTaggingModal'; import styles from './AutoTagging.css'; -export default function AutoTagging(props) { - const { - id, - name, - tags, - tagList, - specifications, - isDeleting, - onConfirmDeleteAutoTagging, - onCloneAutoTaggingPress - } = props; +interface AutoTaggingProps { + id: number; + name: string; + specifications: AutoTaggingSpecification[]; + tags: number[]; + tagList: Tag[]; + isDeleting: boolean; + onConfirmDeleteAutoTagging: (id: number) => void; + onCloneAutoTaggingPress: (id: number) => void; +} +export default function AutoTagging({ + id, + name, + tags, + tagList, + specifications, + isDeleting, + onConfirmDeleteAutoTagging, + onCloneAutoTaggingPress, +}: AutoTaggingProps) { const [isEditModalOpen, setIsEditModalOpen] = useState(false); const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false); @@ -57,9 +68,7 @@ export default function AutoTagging(props) { onPress={onEditPress} >
-
- {name} -
+
{name}
- +
- { - specifications.map((item, index) => { - if (!item) { - return null; - } - - let kind = kinds.DEFAULT; - if (item.required) { - kind = kinds.SUCCESS; - } - if (item.negate) { - kind = kinds.DANGER; - } - - return ( - - ); - }) - } + {specifications.map((item, index) => { + if (!item) { + return null; + } + + let kind: Kind = 'default'; + + if (item.required) { + kind = 'success'; + } + if (item.negate) { + kind = 'danger'; + } + + return ( + + ); + })}
); } - -AutoTagging.propTypes = { - id: PropTypes.number.isRequired, - name: PropTypes.string.isRequired, - specifications: PropTypes.arrayOf(PropTypes.object).isRequired, - tags: PropTypes.arrayOf(PropTypes.number).isRequired, - tagList: PropTypes.arrayOf(PropTypes.object).isRequired, - isDeleting: PropTypes.bool.isRequired, - onConfirmDeleteAutoTagging: PropTypes.func.isRequired, - onCloneAutoTaggingPress: PropTypes.func.isRequired -}; diff --git a/frontend/src/Settings/Tags/AutoTagging/AutoTaggings.js b/frontend/src/Settings/Tags/AutoTagging/AutoTaggings.tsx similarity index 60% rename from frontend/src/Settings/Tags/AutoTagging/AutoTaggings.js rename to frontend/src/Settings/Tags/AutoTagging/AutoTaggings.tsx index f27dc3b5a..01685af9c 100644 --- a/frontend/src/Settings/Tags/AutoTagging/AutoTaggings.js +++ b/frontend/src/Settings/Tags/AutoTagging/AutoTaggings.tsx @@ -1,14 +1,20 @@ import React, { useCallback, useEffect, useState } from 'react'; import { useDispatch, useSelector } from 'react-redux'; +import { AutoTaggingAppState } from 'App/State/SettingsAppState'; import Card from 'Components/Card'; import FieldSet from 'Components/FieldSet'; import Icon from 'Components/Icon'; import PageSectionContent from 'Components/Page/PageSectionContent'; import { icons } from 'Helpers/Props'; import { fetchRootFolders } from 'Store/Actions/rootFolderActions'; -import { cloneAutoTagging, deleteAutoTagging, fetchAutoTaggings } from 'Store/Actions/settingsActions'; +import { + cloneAutoTagging, + deleteAutoTagging, + fetchAutoTaggings, +} from 'Store/Actions/settingsActions'; import createSortedSectionSelector from 'Store/Selectors/createSortedSectionSelector'; import createTagsSelector from 'Store/Selectors/createTagsSelector'; +import AutoTaggingModel from 'typings/AutoTagging'; import sortByProp from 'Utilities/Array/sortByProp'; import translate from 'Utilities/String/translate'; import AutoTagging from './AutoTagging'; @@ -16,27 +22,27 @@ import EditAutoTaggingModal from './EditAutoTaggingModal'; import styles from './AutoTaggings.css'; export default function AutoTaggings() { - const { - error, - items, - isDeleting, - isFetching, - isPopulated - } = useSelector( - createSortedSectionSelector('settings.autoTaggings', sortByProp('name')) + const { error, items, isDeleting, isFetching, isPopulated } = useSelector( + createSortedSectionSelector( + 'settings.autoTaggings', + sortByProp('name') + ) ); const tagList = useSelector(createTagsSelector()); const dispatch = useDispatch(); const [isEditModalOpen, setIsEditModalOpen] = useState(false); - const [tagsFromId, setTagsFromId] = useState(undefined); + const [tagsFromId, setTagsFromId] = useState(); - const onClonePress = useCallback((id) => { - dispatch(cloneAutoTagging({ id })); + const onClonePress = useCallback( + (id: number) => { + dispatch(cloneAutoTagging({ id })); - setTagsFromId(id); - setIsEditModalOpen(true); - }, [dispatch, setIsEditModalOpen]); + setTagsFromId(id); + setIsEditModalOpen(true); + }, + [dispatch, setIsEditModalOpen] + ); const onEditPress = useCallback(() => { setIsEditModalOpen(true); @@ -46,9 +52,12 @@ export default function AutoTaggings() { setIsEditModalOpen(false); }, [setIsEditModalOpen]); - const onConfirmDelete = useCallback((id) => { - dispatch(deleteAutoTagging({ id })); - }, [dispatch]); + const onConfirmDelete = useCallback( + (id: number) => { + dispatch(deleteAutoTagging({ id })); + }, + [dispatch] + ); useEffect(() => { dispatch(fetchAutoTaggings()); @@ -64,30 +73,22 @@ export default function AutoTaggings() { isPopulated={isPopulated} >
- { - items.map((item) => { - return ( - - ); - }) - } + {items.map((item) => { + return ( + + ); + })} - +
- +
@@ -97,7 +98,6 @@ export default function AutoTaggings() { tagsFromId={tagsFromId} onModalClose={onEditModalClose} /> - ); diff --git a/frontend/src/Settings/Tags/AutoTagging/EditAutoTaggingModal.js b/frontend/src/Settings/Tags/AutoTagging/EditAutoTaggingModal.js deleted file mode 100644 index c6f810785..000000000 --- a/frontend/src/Settings/Tags/AutoTagging/EditAutoTaggingModal.js +++ /dev/null @@ -1,50 +0,0 @@ -import PropTypes from 'prop-types'; -import React, { useCallback, useState } from 'react'; -import { useDispatch } from 'react-redux'; -import Modal from 'Components/Modal/Modal'; -import { sizes } from 'Helpers/Props'; -import { clearPendingChanges } from 'Store/Actions/baseActions'; -import EditAutoTaggingModalContent from './EditAutoTaggingModalContent'; - -export default function EditAutoTaggingModal(props) { - const { - isOpen, - onModalClose: onOriginalModalClose, - ...otherProps - } = props; - - const dispatch = useDispatch(); - const [height, setHeight] = useState('auto'); - - const onContentHeightChange = useCallback((h) => { - if (height === 'auto' || h > height) { - setHeight(h); - } - }, [height, setHeight]); - - const onModalClose = useCallback(() => { - dispatch(clearPendingChanges({ section: 'settings.autoTaggings' })); - onOriginalModalClose(); - }, [dispatch, onOriginalModalClose]); - - return ( - - - - ); -} - -EditAutoTaggingModal.propTypes = { - isOpen: PropTypes.bool.isRequired, - onModalClose: PropTypes.func.isRequired -}; - diff --git a/frontend/src/Settings/Tags/AutoTagging/EditAutoTaggingModal.tsx b/frontend/src/Settings/Tags/AutoTagging/EditAutoTaggingModal.tsx new file mode 100644 index 000000000..96bd117d6 --- /dev/null +++ b/frontend/src/Settings/Tags/AutoTagging/EditAutoTaggingModal.tsx @@ -0,0 +1,35 @@ +import React, { useCallback } from 'react'; +import { useDispatch } from 'react-redux'; +import Modal from 'Components/Modal/Modal'; +import { sizes } from 'Helpers/Props'; +import { clearPendingChanges } from 'Store/Actions/baseActions'; +import EditAutoTaggingModalContent, { + EditAutoTaggingModalContentProps, +} from './EditAutoTaggingModalContent'; + +interface EditAutoTaggingModalProps extends EditAutoTaggingModalContentProps { + isOpen: boolean; + onModalClose: () => void; +} + +export default function EditAutoTaggingModal({ + isOpen, + onModalClose, + ...otherProps +}: EditAutoTaggingModalProps) { + const dispatch = useDispatch(); + + const handleModalClose = useCallback(() => { + dispatch(clearPendingChanges({ section: 'settings.autoTaggings' })); + onModalClose(); + }, [dispatch, onModalClose]); + + return ( + + + + ); +} diff --git a/frontend/src/Settings/Tags/AutoTagging/EditAutoTaggingModalContent.js b/frontend/src/Settings/Tags/AutoTagging/EditAutoTaggingModalContent.js deleted file mode 100644 index 811c98461..000000000 --- a/frontend/src/Settings/Tags/AutoTagging/EditAutoTaggingModalContent.js +++ /dev/null @@ -1,270 +0,0 @@ -import PropTypes from 'prop-types'; -import React, { useCallback, useEffect, useRef, useState } from 'react'; -import { useDispatch, useSelector } from 'react-redux'; -import Alert from 'Components/Alert'; -import Card from 'Components/Card'; -import FieldSet from 'Components/FieldSet'; -import Form from 'Components/Form/Form'; -import FormGroup from 'Components/Form/FormGroup'; -import FormInputGroup from 'Components/Form/FormInputGroup'; -import FormLabel from 'Components/Form/FormLabel'; -import Icon from 'Components/Icon'; -import Button from 'Components/Link/Button'; -import SpinnerErrorButton from 'Components/Link/SpinnerErrorButton'; -import LoadingIndicator from 'Components/Loading/LoadingIndicator'; -import ModalBody from 'Components/Modal/ModalBody'; -import ModalContent from 'Components/Modal/ModalContent'; -import ModalFooter from 'Components/Modal/ModalFooter'; -import ModalHeader from 'Components/Modal/ModalHeader'; -import { icons, inputTypes, kinds } from 'Helpers/Props'; -import { - cloneAutoTaggingSpecification, - deleteAutoTaggingSpecification, - fetchAutoTaggingSpecifications, - saveAutoTagging, - setAutoTaggingValue -} from 'Store/Actions/settingsActions'; -import { createProviderSettingsSelectorHook } from 'Store/Selectors/createProviderSettingsSelector'; -import translate from 'Utilities/String/translate'; -import AddSpecificationModal from './Specifications/AddSpecificationModal'; -import EditSpecificationModal from './Specifications/EditSpecificationModal'; -import Specification from './Specifications/Specification'; -import styles from './EditAutoTaggingModalContent.css'; - -export default function EditAutoTaggingModalContent(props) { - const { - id, - tagsFromId, - onModalClose, - onDeleteAutoTaggingPress - } = props; - - const { - error, - item, - isFetching, - isSaving, - saveError, - validationErrors, - validationWarnings - } = useSelector(createProviderSettingsSelectorHook('autoTaggings', id)); - - const { - isPopulated: specificationsPopulated, - items: specifications - } = useSelector((state) => state.settings.autoTaggingSpecifications); - - const dispatch = useDispatch(); - const [isAddSpecificationModalOpen, setIsAddSpecificationModalOpen] = useState(false); - const [isEditSpecificationModalOpen, setIsEditSpecificationModalOpen] = useState(false); - // const [isImportAutoTaggingModalOpen, setIsImportAutoTaggingModalOpen] = useState(false); - - const onAddSpecificationPress = useCallback(() => { - setIsAddSpecificationModalOpen(true); - }, [setIsAddSpecificationModalOpen]); - - const onAddSpecificationModalClose = useCallback(({ specificationSelected = false } = {}) => { - setIsAddSpecificationModalOpen(false); - setIsEditSpecificationModalOpen(specificationSelected); - }, [setIsAddSpecificationModalOpen]); - - const onEditSpecificationModalClose = useCallback(() => { - setIsEditSpecificationModalOpen(false); - }, [setIsEditSpecificationModalOpen]); - - const onInputChange = useCallback(({ name, value }) => { - dispatch(setAutoTaggingValue({ name, value })); - }, [dispatch]); - - const onSavePress = useCallback(() => { - dispatch(saveAutoTagging({ id })); - }, [dispatch, id]); - - const onCloneSpecificationPress = useCallback((specId) => { - dispatch(cloneAutoTaggingSpecification({ id: specId })); - }, [dispatch]); - - const onConfirmDeleteSpecification = useCallback((specId) => { - dispatch(deleteAutoTaggingSpecification({ id: specId })); - }, [dispatch]); - - useEffect(() => { - dispatch(fetchAutoTaggingSpecifications({ id: tagsFromId || id })); - }, [id, tagsFromId, dispatch]); - - const isSavingRef = useRef(); - - useEffect(() => { - if (isSavingRef.current && !isSaving && !saveError) { - onModalClose(); - } - - isSavingRef.current = isSaving; - }, [isSaving, saveError, onModalClose]); - - const { - name, - removeTagsAutomatically, - tags - } = item; - - return ( - - - - {id ? translate('EditAutoTag') : translate('AddAutoTag')} - - - -
- { - isFetching ? : null - } - - { - !isFetching && !!error ? - - {translate('AddAutoTagError')} - : - null - } - - { - !isFetching && !error && specificationsPopulated ? -
-
- - - {translate('Name')} - - - - - - - {translate('RemoveTagsAutomatically')} - - - - - - {translate('Tags')} - - - -
- -
-
- { - specifications.map((tag) => { - return ( - - ); - }) - } - - -
- -
-
-
-
- - - - - - {/* */} - -
: - null - } -
-
- -
- { - id ? - : - null - } - - {/* */} -
- - - - - {translate('Save')} - -
-
- ); -} - -EditAutoTaggingModalContent.propTypes = { - id: PropTypes.number, - tagsFromId: PropTypes.number, - onModalClose: PropTypes.func.isRequired, - onDeleteAutoTaggingPress: PropTypes.func -}; diff --git a/frontend/src/Settings/Tags/AutoTagging/EditAutoTaggingModalContent.tsx b/frontend/src/Settings/Tags/AutoTagging/EditAutoTaggingModalContent.tsx new file mode 100644 index 000000000..6317a5b61 --- /dev/null +++ b/frontend/src/Settings/Tags/AutoTagging/EditAutoTaggingModalContent.tsx @@ -0,0 +1,258 @@ +import React, { useCallback, useEffect, useRef, useState } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import AppState from 'App/State/AppState'; +import Alert from 'Components/Alert'; +import Card from 'Components/Card'; +import FieldSet from 'Components/FieldSet'; +import Form from 'Components/Form/Form'; +import FormGroup from 'Components/Form/FormGroup'; +import FormInputGroup from 'Components/Form/FormInputGroup'; +import FormLabel from 'Components/Form/FormLabel'; +import Icon from 'Components/Icon'; +import Button from 'Components/Link/Button'; +import SpinnerErrorButton from 'Components/Link/SpinnerErrorButton'; +import LoadingIndicator from 'Components/Loading/LoadingIndicator'; +import ModalBody from 'Components/Modal/ModalBody'; +import ModalContent from 'Components/Modal/ModalContent'; +import ModalFooter from 'Components/Modal/ModalFooter'; +import ModalHeader from 'Components/Modal/ModalHeader'; +import { icons, inputTypes, kinds } from 'Helpers/Props'; +import { + cloneAutoTaggingSpecification, + deleteAutoTaggingSpecification, + fetchAutoTaggingSpecifications, + saveAutoTagging, + setAutoTaggingValue, +} from 'Store/Actions/settingsActions'; +import { createProviderSettingsSelectorHook } from 'Store/Selectors/createProviderSettingsSelector'; +import { InputChanged } from 'typings/inputs'; +import translate from 'Utilities/String/translate'; +import AddSpecificationModal from './Specifications/AddSpecificationModal'; +import EditSpecificationModal from './Specifications/EditSpecificationModal'; +import Specification from './Specifications/Specification'; +import styles from './EditAutoTaggingModalContent.css'; + +export interface EditAutoTaggingModalContentProps { + id?: number; + tagsFromId?: number; + onModalClose: () => void; + onDeleteAutoTaggingPress?: () => void; +} + +export default function EditAutoTaggingModalContent({ + id, + tagsFromId, + onModalClose, + onDeleteAutoTaggingPress, +}: EditAutoTaggingModalContentProps) { + const { + error, + item, + isFetching, + isSaving, + saveError, + validationErrors, + validationWarnings, + } = useSelector(createProviderSettingsSelectorHook('autoTaggings', id)); + + const { isPopulated: specificationsPopulated, items: specifications } = + useSelector((state: AppState) => state.settings.autoTaggingSpecifications); + + const dispatch = useDispatch(); + const [isAddSpecificationModalOpen, setIsAddSpecificationModalOpen] = + useState(false); + const [isEditSpecificationModalOpen, setIsEditSpecificationModalOpen] = + useState(false); + + const handleAddSpecificationPress = useCallback(() => { + setIsAddSpecificationModalOpen(true); + }, [setIsAddSpecificationModalOpen]); + + const handleAddSpecificationModalClose = useCallback( + ({ specificationSelected = false } = {}) => { + setIsAddSpecificationModalOpen(false); + setIsEditSpecificationModalOpen(specificationSelected); + }, + [setIsAddSpecificationModalOpen] + ); + + const handleEditSpecificationModalClose = useCallback(() => { + setIsEditSpecificationModalOpen(false); + }, [setIsEditSpecificationModalOpen]); + + const handleInputChange = useCallback( + ({ name, value }: InputChanged) => { + // @ts-expect-error - actions are not typed + dispatch(setAutoTaggingValue({ name, value })); + }, + [dispatch] + ); + + const handleSavePress = useCallback(() => { + dispatch(saveAutoTagging({ id })); + }, [dispatch, id]); + + const handleCloneSpecificationPress = useCallback( + (specId: number) => { + dispatch(cloneAutoTaggingSpecification({ id: specId })); + }, + [dispatch] + ); + + const handleConfirmDeleteSpecification = useCallback( + (specId: number) => { + dispatch(deleteAutoTaggingSpecification({ id: specId })); + }, + [dispatch] + ); + + useEffect(() => { + dispatch(fetchAutoTaggingSpecifications({ id: tagsFromId || id })); + }, [id, tagsFromId, dispatch]); + + const isSavingRef = useRef(false); + + useEffect(() => { + if (isSavingRef.current && !isSaving && !saveError) { + onModalClose(); + } + + isSavingRef.current = isSaving; + }, [isSaving, saveError, onModalClose]); + + const { name, removeTagsAutomatically, tags } = item; + + return ( + + + {id ? translate('EditAutoTag') : translate('AddAutoTag')} + + + +
+ {isFetching ? : null} + + {!isFetching && !!error ? ( + {translate('AddAutoTagError')} + ) : null} + + {!isFetching && !error && specificationsPopulated ? ( +
+
+ + {translate('Name')} + + + + + + {translate('RemoveTagsAutomatically')} + + + + + + {translate('Tags')} + + + +
+ +
+
+ {specifications.map((specification) => { + return ( + + ); + })} + + +
+ +
+
+
+
+ + + + + + {/* */} +
+ ) : null} +
+
+ +
+ {id ? ( + + ) : null} + + {/* */} +
+ + + + + {translate('Save')} + +
+
+ ); +} diff --git a/frontend/src/Settings/Tags/AutoTagging/Specifications/AddSpecificationItem.js b/frontend/src/Settings/Tags/AutoTagging/Specifications/AddSpecificationItem.js deleted file mode 100644 index f6f2b134e..000000000 --- a/frontend/src/Settings/Tags/AutoTagging/Specifications/AddSpecificationItem.js +++ /dev/null @@ -1,101 +0,0 @@ -import PropTypes from 'prop-types'; -import React, { useCallback } from 'react'; -import Button from 'Components/Link/Button'; -import Link from 'Components/Link/Link'; -import Menu from 'Components/Menu/Menu'; -import MenuContent from 'Components/Menu/MenuContent'; -import { sizes } from 'Helpers/Props'; -import translate from 'Utilities/String/translate'; -import AddSpecificationPresetMenuItem from './AddSpecificationPresetMenuItem'; -import styles from './AddSpecificationItem.css'; - -export default function AddSpecificationItem(props) { - const { - implementation, - implementationName, - infoLink, - presets, - onSpecificationSelect - } = props; - - const onWrappedSpecificationSelect = useCallback(() => { - onSpecificationSelect({ implementation }); - }, [implementation, onSpecificationSelect]); - - const hasPresets = !!presets && !!presets.length; - - return ( -
- - -
-
- {implementationName} -
- -
- { - hasPresets ? - - - - - - - - { - presets.map((preset, index) => { - return ( - - ); - }) - } - - - : - null - } - - { - infoLink ? - : - null - } -
-
-
- ); -} - -AddSpecificationItem.propTypes = { - implementation: PropTypes.string.isRequired, - implementationName: PropTypes.string.isRequired, - infoLink: PropTypes.string, - presets: PropTypes.arrayOf(PropTypes.object), - onSpecificationSelect: PropTypes.func.isRequired -}; diff --git a/frontend/src/Settings/Tags/AutoTagging/Specifications/AddSpecificationItem.tsx b/frontend/src/Settings/Tags/AutoTagging/Specifications/AddSpecificationItem.tsx new file mode 100644 index 000000000..774f367e9 --- /dev/null +++ b/frontend/src/Settings/Tags/AutoTagging/Specifications/AddSpecificationItem.tsx @@ -0,0 +1,81 @@ +import React, { useCallback } from 'react'; +import Button from 'Components/Link/Button'; +import Link from 'Components/Link/Link'; +import Menu from 'Components/Menu/Menu'; +import MenuContent from 'Components/Menu/MenuContent'; +import { sizes } from 'Helpers/Props'; +import { AutoTaggingSpecification } from 'typings/AutoTagging'; +import translate from 'Utilities/String/translate'; +import AddSpecificationPresetMenuItem from './AddSpecificationPresetMenuItem'; +import styles from './AddSpecificationItem.css'; + +interface AddSpecificationItemProps { + implementation: string; + implementationName: string; + infoLink?: string; + presets?: AutoTaggingSpecification[]; + onSpecificationSelect: ({ + implementation, + }: { + implementation: string; + }) => void; +} + +export default function AddSpecificationItem({ + implementation, + implementationName, + infoLink, + presets, + onSpecificationSelect, +}: AddSpecificationItemProps) { + const handleSpecificationSelect = useCallback(() => { + onSpecificationSelect({ implementation }); + }, [implementation, onSpecificationSelect]); + + const hasPresets = !!presets && !!presets.length; + + return ( +
+ + +
+
{implementationName}
+ +
+ {hasPresets ? ( + + + + + + + + {presets.map((preset, index) => { + return ( + + ); + })} + + + + ) : null} + + {infoLink ? ( + + ) : null} +
+
+
+ ); +} diff --git a/frontend/src/Settings/Tags/AutoTagging/Specifications/AddSpecificationModal.js b/frontend/src/Settings/Tags/AutoTagging/Specifications/AddSpecificationModal.js deleted file mode 100644 index 1a8c115f0..000000000 --- a/frontend/src/Settings/Tags/AutoTagging/Specifications/AddSpecificationModal.js +++ /dev/null @@ -1,25 +0,0 @@ -import PropTypes from 'prop-types'; -import React from 'react'; -import Modal from 'Components/Modal/Modal'; -import AddSpecificationModalContent from './AddSpecificationModalContent'; - -function AddSpecificationModal({ isOpen, onModalClose, ...otherProps }) { - return ( - - - - ); -} - -AddSpecificationModal.propTypes = { - isOpen: PropTypes.bool.isRequired, - onModalClose: PropTypes.func.isRequired -}; - -export default AddSpecificationModal; diff --git a/frontend/src/Settings/Tags/AutoTagging/Specifications/AddSpecificationModal.tsx b/frontend/src/Settings/Tags/AutoTagging/Specifications/AddSpecificationModal.tsx new file mode 100644 index 000000000..e56486625 --- /dev/null +++ b/frontend/src/Settings/Tags/AutoTagging/Specifications/AddSpecificationModal.tsx @@ -0,0 +1,21 @@ +import React from 'react'; +import Modal from 'Components/Modal/Modal'; +import AddSpecificationModalContent from './AddSpecificationModalContent'; + +interface AddSpecificationModalProps { + isOpen: boolean; + onModalClose: (options?: { specificationSelected: boolean }) => void; +} + +function AddSpecificationModal({ + isOpen, + onModalClose, +}: AddSpecificationModalProps) { + return ( + + + + ); +} + +export default AddSpecificationModal; diff --git a/frontend/src/Settings/Tags/AutoTagging/Specifications/AddSpecificationModalContent.js b/frontend/src/Settings/Tags/AutoTagging/Specifications/AddSpecificationModalContent.js deleted file mode 100644 index 9e06e815b..000000000 --- a/frontend/src/Settings/Tags/AutoTagging/Specifications/AddSpecificationModalContent.js +++ /dev/null @@ -1,106 +0,0 @@ -import PropTypes from 'prop-types'; -import React, { useCallback, useEffect } from 'react'; -import { useDispatch, useSelector } from 'react-redux'; -import Alert from 'Components/Alert'; -import Button from 'Components/Link/Button'; -import LoadingIndicator from 'Components/Loading/LoadingIndicator'; -import ModalBody from 'Components/Modal/ModalBody'; -import ModalContent from 'Components/Modal/ModalContent'; -import ModalFooter from 'Components/Modal/ModalFooter'; -import ModalHeader from 'Components/Modal/ModalHeader'; -import { kinds } from 'Helpers/Props'; -import { - fetchAutoTaggingSpecificationSchema, - selectAutoTaggingSpecificationSchema -} from 'Store/Actions/settingsActions'; -import translate from 'Utilities/String/translate'; -import AddSpecificationItem from './AddSpecificationItem'; -import styles from './AddSpecificationModalContent.css'; - -export default function AddSpecificationModalContent(props) { - const { - onModalClose - } = props; - - const { - isSchemaFetching, - isSchemaPopulated, - schemaError, - schema - } = useSelector( - (state) => state.settings.autoTaggingSpecifications - ); - - const dispatch = useDispatch(); - - const onSpecificationSelect = useCallback(({ implementation, name }) => { - dispatch(selectAutoTaggingSpecificationSchema({ implementation, presetName: name })); - onModalClose({ specificationSelected: true }); - }, [dispatch, onModalClose]); - - useEffect(() => { - dispatch(fetchAutoTaggingSpecificationSchema()); - }, [dispatch]); - - return ( - - - {translate('AddCondition')} - - - - { - isSchemaFetching ? : null - } - - { - !isSchemaFetching && !!schemaError ? - - {translate('AddConditionError')} - : - null - } - - { - isSchemaPopulated && !schemaError ? -
- - -
- {translate('SupportedAutoTaggingProperties')} -
-
- -
- { - schema.map((specification) => { - return ( - - ); - }) - } -
- -
: - null - } -
- - - - -
- ); -} - -AddSpecificationModalContent.propTypes = { - onModalClose: PropTypes.func.isRequired -}; diff --git a/frontend/src/Settings/Tags/AutoTagging/Specifications/AddSpecificationModalContent.tsx b/frontend/src/Settings/Tags/AutoTagging/Specifications/AddSpecificationModalContent.tsx new file mode 100644 index 000000000..12b8373ff --- /dev/null +++ b/frontend/src/Settings/Tags/AutoTagging/Specifications/AddSpecificationModalContent.tsx @@ -0,0 +1,90 @@ +import React, { useCallback, useEffect } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import AppState from 'App/State/AppState'; +import Alert from 'Components/Alert'; +import Button from 'Components/Link/Button'; +import LoadingIndicator from 'Components/Loading/LoadingIndicator'; +import ModalBody from 'Components/Modal/ModalBody'; +import ModalContent from 'Components/Modal/ModalContent'; +import ModalFooter from 'Components/Modal/ModalFooter'; +import ModalHeader from 'Components/Modal/ModalHeader'; +import { kinds } from 'Helpers/Props'; +import { + fetchAutoTaggingSpecificationSchema, + selectAutoTaggingSpecificationSchema, +} from 'Store/Actions/settingsActions'; +import translate from 'Utilities/String/translate'; +import AddSpecificationItem from './AddSpecificationItem'; +import styles from './AddSpecificationModalContent.css'; + +interface AddSpecificationModalContentProps { + onModalClose: (options?: { specificationSelected: boolean }) => void; +} + +export default function AddSpecificationModalContent({ + onModalClose, +}: AddSpecificationModalContentProps) { + const { isSchemaFetching, isSchemaPopulated, schemaError, schema } = + useSelector((state: AppState) => state.settings.autoTaggingSpecifications); + + const dispatch = useDispatch(); + + const onSpecificationSelect = useCallback( + ({ implementation }: { implementation: string }) => { + dispatch( + selectAutoTaggingSpecificationSchema({ + implementation, + presetName: name, + }) + ); + onModalClose({ specificationSelected: true }); + }, + [dispatch, onModalClose] + ); + + const handleModalClose = useCallback(() => { + onModalClose(); + }, [onModalClose]); + + useEffect(() => { + dispatch(fetchAutoTaggingSpecificationSchema()); + }, [dispatch]); + + return ( + + {translate('AddCondition')} + + + {isSchemaFetching ? : null} + + {!isSchemaFetching && !!schemaError ? ( + {translate('AddConditionError')} + ) : null} + + {isSchemaPopulated && !schemaError ? ( +
+ +
{translate('SupportedAutoTaggingProperties')}
+
+ +
+ {schema.map((specification) => { + return ( + + ); + })} +
+
+ ) : null} +
+ + + + +
+ ); +} diff --git a/frontend/src/Settings/Tags/AutoTagging/Specifications/AddSpecificationPresetMenuItem.js b/frontend/src/Settings/Tags/AutoTagging/Specifications/AddSpecificationPresetMenuItem.js deleted file mode 100644 index b043ddf06..000000000 --- a/frontend/src/Settings/Tags/AutoTagging/Specifications/AddSpecificationPresetMenuItem.js +++ /dev/null @@ -1,34 +0,0 @@ -import PropTypes from 'prop-types'; -import React, { useCallback } from 'react'; -import MenuItem from 'Components/Menu/MenuItem'; - -export default function AddSpecificationPresetMenuItem(props) { - const { - name, - implementation, - onPress, - ...otherProps - } = props; - - const onWrappedPress = useCallback(() => { - onPress({ - name, - implementation - }); - }, [name, implementation, onPress]); - - return ( - - {name} - - ); -} - -AddSpecificationPresetMenuItem.propTypes = { - name: PropTypes.string.isRequired, - implementation: PropTypes.string.isRequired, - onPress: PropTypes.func.isRequired -}; diff --git a/frontend/src/Settings/Tags/AutoTagging/Specifications/AddSpecificationPresetMenuItem.tsx b/frontend/src/Settings/Tags/AutoTagging/Specifications/AddSpecificationPresetMenuItem.tsx new file mode 100644 index 000000000..06cd3b027 --- /dev/null +++ b/frontend/src/Settings/Tags/AutoTagging/Specifications/AddSpecificationPresetMenuItem.tsx @@ -0,0 +1,34 @@ +import React, { useCallback } from 'react'; +import MenuItem from 'Components/Menu/MenuItem'; + +interface AddSpecificationPresetMenuItemProps { + name: string; + implementation: string; + onPress: ({ + name, + implementation, + }: { + name: string; + implementation: string; + }) => void; +} + +export default function AddSpecificationPresetMenuItem({ + name, + implementation, + onPress, + ...otherProps +}: AddSpecificationPresetMenuItemProps) { + const handlePress = useCallback(() => { + onPress({ + name, + implementation, + }); + }, [name, implementation, onPress]); + + return ( + + {name} + + ); +} diff --git a/frontend/src/Settings/Tags/AutoTagging/Specifications/EditSpecificationModal.js b/frontend/src/Settings/Tags/AutoTagging/Specifications/EditSpecificationModal.tsx similarity index 50% rename from frontend/src/Settings/Tags/AutoTagging/Specifications/EditSpecificationModal.js rename to frontend/src/Settings/Tags/AutoTagging/Specifications/EditSpecificationModal.tsx index 16ed4daec..5b4041e62 100644 --- a/frontend/src/Settings/Tags/AutoTagging/Specifications/EditSpecificationModal.js +++ b/frontend/src/Settings/Tags/AutoTagging/Specifications/EditSpecificationModal.tsx @@ -1,25 +1,34 @@ -import PropTypes from 'prop-types'; import React, { useCallback } from 'react'; import { useDispatch } from 'react-redux'; import Modal from 'Components/Modal/Modal'; import { sizes } from 'Helpers/Props'; import { clearPendingChanges } from 'Store/Actions/baseActions'; -import EditSpecificationModalContent from './EditSpecificationModalContent'; +import EditSpecificationModalContent, { + EditSpecificationModalContentProps, +} from './EditSpecificationModalContent'; -function EditSpecificationModal({ isOpen, onModalClose, ...otherProps }) { +interface EditSpecificationModalProps + extends EditSpecificationModalContentProps { + isOpen: boolean; + onModalClose: () => void; +} + +function EditSpecificationModal({ + isOpen, + onModalClose, + ...otherProps +}: EditSpecificationModalProps) { const dispatch = useDispatch(); const onWrappedModalClose = useCallback(() => { - dispatch(clearPendingChanges({ section: 'settings.autoTaggingSpecifications' })); + dispatch( + clearPendingChanges({ section: 'settings.autoTaggingSpecifications' }) + ); onModalClose(); }, [onModalClose, dispatch]); return ( - + state.settings.advancedSettings); - - const { - item, - ...otherFormProps - } = useSelector( - createProviderSettingsSelectorHook('autoTaggingSpecifications', id) - ); - - const dispatch = useDispatch(); - - const onInputChange = useCallback(({ name, value }) => { - dispatch(setAutoTaggingSpecificationValue({ name, value })); - }, [dispatch]); - - const onFieldChange = useCallback(({ name, value }) => { - dispatch(setAutoTaggingSpecificationFieldValue({ name, value })); - }, [dispatch]); - - const onCancelPress = useCallback(({ name, value }) => { - dispatch(clearAutoTaggingSpecificationPending()); - onModalClose(); - }, [dispatch, onModalClose]); - - const onSavePress = useCallback(({ name, value }) => { - dispatch(saveAutoTaggingSpecification({ id })); - onModalClose(); - }, [dispatch, id, onModalClose]); - - const { - implementationName, - name, - negate, - required, - fields - } = item; - - return ( - - - {id ? translate('EditConditionImplementation', { implementationName }) : translate('AddConditionImplementation', { implementationName })} - - - -
- { - fields && fields.some((x) => x.label === 'Regular Expression') && - -
- -
-
- -
-
- -
-
- } - - - - {translate('Name')} - - - - - - { - fields && fields.map((field) => { - return ( - - ); - }) - } - - - - {translate('Negate')} - - - - - - - - {translate('Required')} - - - - - -
- - { - id ? - : - null - } - - - - - {translate('Save')} - - -
- ); -} - -EditSpecificationModalContent.propTypes = { - id: PropTypes.number, - onDeleteSpecificationPress: PropTypes.func, - onModalClose: PropTypes.func.isRequired -}; - -export default EditSpecificationModalContent; diff --git a/frontend/src/Settings/Tags/AutoTagging/Specifications/EditSpecificationModalContent.tsx b/frontend/src/Settings/Tags/AutoTagging/Specifications/EditSpecificationModalContent.tsx new file mode 100644 index 000000000..da316d0e2 --- /dev/null +++ b/frontend/src/Settings/Tags/AutoTagging/Specifications/EditSpecificationModalContent.tsx @@ -0,0 +1,192 @@ +import React, { useCallback } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import AppState from 'App/State/AppState'; +import { AutoTaggingSpecificationAppState } from 'App/State/SettingsAppState'; +import Alert from 'Components/Alert'; +import Form from 'Components/Form/Form'; +import FormGroup from 'Components/Form/FormGroup'; +import FormInputGroup from 'Components/Form/FormInputGroup'; +import FormLabel from 'Components/Form/FormLabel'; +import ProviderFieldFormGroup from 'Components/Form/ProviderFieldFormGroup'; +import Button from 'Components/Link/Button'; +import SpinnerErrorButton from 'Components/Link/SpinnerErrorButton'; +import InlineMarkdown from 'Components/Markdown/InlineMarkdown'; +import ModalBody from 'Components/Modal/ModalBody'; +import ModalContent from 'Components/Modal/ModalContent'; +import ModalFooter from 'Components/Modal/ModalFooter'; +import ModalHeader from 'Components/Modal/ModalHeader'; +import { inputTypes, kinds } from 'Helpers/Props'; +import { + clearAutoTaggingSpecificationPending, + saveAutoTaggingSpecification, + setAutoTaggingSpecificationFieldValue, + setAutoTaggingSpecificationValue, +} from 'Store/Actions/settingsActions'; +import { createProviderSettingsSelectorHook } from 'Store/Selectors/createProviderSettingsSelector'; +import { AutoTaggingSpecification } from 'typings/AutoTagging'; +import { InputChanged } from 'typings/inputs'; +import translate from 'Utilities/String/translate'; +import styles from './EditSpecificationModalContent.css'; + +export interface EditSpecificationModalContentProps { + id?: number; + onDeleteSpecificationPress?: () => void; + onModalClose: () => void; +} + +function EditSpecificationModalContent({ + id, + onDeleteSpecificationPress, + onModalClose, +}: EditSpecificationModalContentProps) { + const advancedSettings = useSelector( + (state: AppState) => state.settings.advancedSettings + ); + + const { item, ...otherFormProps } = useSelector( + createProviderSettingsSelectorHook< + AutoTaggingSpecification, + AutoTaggingSpecificationAppState + >('autoTaggingSpecifications', id) + ); + + const dispatch = useDispatch(); + + const onInputChange = useCallback( + ({ name, value }: InputChanged) => { + // @ts-expect-error - actions are not typed + dispatch(setAutoTaggingSpecificationValue({ name, value })); + }, + [dispatch] + ); + + const onFieldChange = useCallback( + ({ name, value }: InputChanged) => { + // @ts-expect-error - actions are not typed + dispatch(setAutoTaggingSpecificationFieldValue({ name, value })); + }, + [dispatch] + ); + + const onCancelPress = useCallback(() => { + dispatch(clearAutoTaggingSpecificationPending()); + onModalClose(); + }, [dispatch, onModalClose]); + + const onSavePress = useCallback(() => { + dispatch(saveAutoTaggingSpecification({ id })); + onModalClose(); + }, [dispatch, id, onModalClose]); + + const { implementationName, name, negate, required, fields } = item; + + return ( + + + {id + ? translate('EditConditionImplementation', { implementationName }) + : translate('AddConditionImplementation', { implementationName })} + + + +
+ {fields && fields.some((x) => x.label === 'Regular Expression') && ( + +
+ +
+
+ +
+
+ +
+
+ )} + + + {translate('Name')} + + + + + {fields && + fields.map((field) => { + return ( + + ); + })} + + + {translate('Negate')} + + + + + + {translate('Required')} + + + + +
+ + {id ? ( + + ) : null} + + + + + {translate('Save')} + + +
+ ); +} + +export default EditSpecificationModalContent; diff --git a/frontend/src/Settings/Tags/AutoTagging/Specifications/EditSpecificationModalContentConnector.js b/frontend/src/Settings/Tags/AutoTagging/Specifications/EditSpecificationModalContentConnector.js deleted file mode 100644 index 8f27b74e0..000000000 --- a/frontend/src/Settings/Tags/AutoTagging/Specifications/EditSpecificationModalContentConnector.js +++ /dev/null @@ -1,78 +0,0 @@ -import PropTypes from 'prop-types'; -import React, { Component } from 'react'; -import { connect } from 'react-redux'; -import { createSelector } from 'reselect'; -import { clearAutoTaggingSpecificationPending, saveAutoTaggingSpecification, setAutoTaggingSpecificationFieldValue, setAutoTaggingSpecificationValue } from 'Store/Actions/settingsActions'; -import createProviderSettingsSelector from 'Store/Selectors/createProviderSettingsSelector'; -import EditSpecificationModalContent from './EditSpecificationModalContent'; - -function createMapStateToProps() { - return createSelector( - (state) => state.settings.advancedSettings, - createProviderSettingsSelector('autoTaggingSpecifications'), - (advancedSettings, specification) => { - return { - advancedSettings, - ...specification - }; - } - ); -} - -const mapDispatchToProps = { - setAutoTaggingSpecificationValue, - setAutoTaggingSpecificationFieldValue, - saveAutoTaggingSpecification, - clearAutoTaggingSpecificationPending -}; - -class EditSpecificationModalContentConnector extends Component { - - // - // Listeners - - onInputChange = ({ name, value }) => { - this.props.setAutoTaggingSpecificationValue({ name, value }); - }; - - onFieldChange = ({ name, value }) => { - this.props.setAutoTaggingSpecificationFieldValue({ name, value }); - }; - - onCancelPress = () => { - this.props.clearAutoTaggingSpecificationPending(); - this.props.onModalClose(); - }; - - onSavePress = () => { - this.props.saveAutoTaggingSpecification({ id: this.props.id }); - this.props.onModalClose(); - }; - - // - // Render - - render() { - return ( - - ); - } -} - -EditSpecificationModalContentConnector.propTypes = { - id: PropTypes.number, - item: PropTypes.object.isRequired, - setAutoTaggingSpecificationValue: PropTypes.func.isRequired, - setAutoTaggingSpecificationFieldValue: PropTypes.func.isRequired, - clearAutoTaggingSpecificationPending: PropTypes.func.isRequired, - saveAutoTaggingSpecification: PropTypes.func.isRequired, - onModalClose: PropTypes.func.isRequired -}; - -export default connect(createMapStateToProps, mapDispatchToProps)(EditSpecificationModalContentConnector); diff --git a/frontend/src/Settings/Tags/AutoTagging/Specifications/Specification.js b/frontend/src/Settings/Tags/AutoTagging/Specifications/Specification.tsx similarity index 65% rename from frontend/src/Settings/Tags/AutoTagging/Specifications/Specification.js rename to frontend/src/Settings/Tags/AutoTagging/Specifications/Specification.tsx index 21977e160..099dfd5d8 100644 --- a/frontend/src/Settings/Tags/AutoTagging/Specifications/Specification.js +++ b/frontend/src/Settings/Tags/AutoTagging/Specifications/Specification.tsx @@ -1,25 +1,35 @@ -import PropTypes from 'prop-types'; import React, { useCallback, useState } from 'react'; import Card from 'Components/Card'; import Label from 'Components/Label'; import IconButton from 'Components/Link/IconButton'; import ConfirmModal from 'Components/Modal/ConfirmModal'; import { icons, kinds } from 'Helpers/Props'; +import Field from 'typings/Field'; import translate from 'Utilities/String/translate'; import EditSpecificationModal from './EditSpecificationModal'; import styles from './Specification.css'; -export default function Specification(props) { - const { - id, - implementationName, - name, - required, - negate, - onConfirmDeleteSpecification, - onCloneSpecificationPress - } = props; +interface SpecificationProps { + id: number; + implementation: string; + implementationName: string; + name: string; + negate: boolean; + required: boolean; + fields: Field[]; + onConfirmDeleteSpecification: (specId: number) => void; + onCloneSpecificationPress: (specId: number) => void; +} +export default function Specification({ + id, + implementationName, + name, + required, + negate, + onConfirmDeleteSpecification, + onCloneSpecificationPress, +}: SpecificationProps) { const [isEditModalOpen, setIsEditModalOpen] = useState(false); const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false); @@ -55,9 +65,7 @@ export default function Specification(props) { onPress={onEditPress} >
-
- {name} -
+
{name}
- + - { - negate ? - : - null - } + {negate ? ( + + ) : null} - { - required ? - : - null - } + {required ? ( + + ) : null}
); } - -Specification.propTypes = { - id: PropTypes.number.isRequired, - implementation: PropTypes.string.isRequired, - implementationName: PropTypes.string.isRequired, - name: PropTypes.string.isRequired, - negate: PropTypes.bool.isRequired, - required: PropTypes.bool.isRequired, - fields: PropTypes.arrayOf(PropTypes.object).isRequired, - onConfirmDeleteSpecification: PropTypes.func.isRequired, - onCloneSpecificationPress: PropTypes.func.isRequired -}; diff --git a/frontend/src/Settings/Tags/Details/TagDetailsDelayProfile.js b/frontend/src/Settings/Tags/Details/TagDetailsDelayProfile.js deleted file mode 100644 index d3482e94f..000000000 --- a/frontend/src/Settings/Tags/Details/TagDetailsDelayProfile.js +++ /dev/null @@ -1,48 +0,0 @@ -import PropTypes from 'prop-types'; -import React from 'react'; -import titleCase from 'Utilities/String/titleCase'; -import translate from 'Utilities/String/translate'; - -function TagDetailsDelayProfile(props) { - const { - preferredProtocol, - enableUsenet, - enableTorrent, - usenetDelay, - torrentDelay - } = props; - - return ( -
-
- {titleCase(translate('DelayProfileProtocol', { preferredProtocol }))} -
- -
- { - enableUsenet ? - translate('UsenetDelayTime', { usenetDelay }) : - translate('UsenetDisabled') - } -
- -
- { - enableTorrent ? - translate('TorrentDelayTime', { torrentDelay }) : - translate('TorrentsDisabled') - } -
-
- ); -} - -TagDetailsDelayProfile.propTypes = { - preferredProtocol: PropTypes.string.isRequired, - enableUsenet: PropTypes.bool.isRequired, - enableTorrent: PropTypes.bool.isRequired, - usenetDelay: PropTypes.number.isRequired, - torrentDelay: PropTypes.number.isRequired -}; - -export default TagDetailsDelayProfile; diff --git a/frontend/src/Settings/Tags/Details/TagDetailsDelayProfile.tsx b/frontend/src/Settings/Tags/Details/TagDetailsDelayProfile.tsx new file mode 100644 index 000000000..7aa5a9b8b --- /dev/null +++ b/frontend/src/Settings/Tags/Details/TagDetailsDelayProfile.tsx @@ -0,0 +1,41 @@ +import React from 'react'; +import titleCase from 'Utilities/String/titleCase'; +import translate from 'Utilities/String/translate'; + +interface TagDetailsDelayProfileProps { + preferredProtocol: string; + enableUsenet: boolean; + enableTorrent: boolean; + usenetDelay: number; + torrentDelay: number; +} + +function TagDetailsDelayProfile({ + preferredProtocol, + enableUsenet, + enableTorrent, + usenetDelay, + torrentDelay, +}: TagDetailsDelayProfileProps) { + return ( +
+
+ {titleCase(translate('DelayProfileProtocol', { preferredProtocol }))} +
+ +
+ {enableUsenet + ? translate('UsenetDelayTime', { usenetDelay }) + : translate('UsenetDisabled')} +
+ +
+ {enableTorrent + ? translate('TorrentDelayTime', { torrentDelay }) + : translate('TorrentsDisabled')} +
+
+ ); +} + +export default TagDetailsDelayProfile; diff --git a/frontend/src/Settings/Tags/Details/TagDetailsModal.js b/frontend/src/Settings/Tags/Details/TagDetailsModal.js deleted file mode 100644 index 4195c64db..000000000 --- a/frontend/src/Settings/Tags/Details/TagDetailsModal.js +++ /dev/null @@ -1,33 +0,0 @@ -import PropTypes from 'prop-types'; -import React from 'react'; -import Modal from 'Components/Modal/Modal'; -import { sizes } from 'Helpers/Props'; -import TagDetailsModalContentConnector from './TagDetailsModalContentConnector'; - -function TagDetailsModal(props) { - const { - isOpen, - onModalClose, - ...otherProps - } = props; - - return ( - - - - ); -} - -TagDetailsModal.propTypes = { - isOpen: PropTypes.bool.isRequired, - onModalClose: PropTypes.func.isRequired -}; - -export default TagDetailsModal; diff --git a/frontend/src/Settings/Tags/Details/TagDetailsModal.tsx b/frontend/src/Settings/Tags/Details/TagDetailsModal.tsx new file mode 100644 index 000000000..e398a1798 --- /dev/null +++ b/frontend/src/Settings/Tags/Details/TagDetailsModal.tsx @@ -0,0 +1,25 @@ +import React from 'react'; +import Modal from 'Components/Modal/Modal'; +import { sizes } from 'Helpers/Props'; +import TagDetailsModalContent, { + TagDetailsModalContentProps, +} from './TagDetailsModalContent'; + +interface TagDetailsModalProps extends TagDetailsModalContentProps { + isOpen: boolean; + onModalClose: () => void; +} + +function TagDetailsModal({ + isOpen, + onModalClose, + ...otherProps +}: TagDetailsModalProps) { + return ( + + + + ); +} + +export default TagDetailsModal; diff --git a/frontend/src/Settings/Tags/Details/TagDetailsModalContent.js b/frontend/src/Settings/Tags/Details/TagDetailsModalContent.js deleted file mode 100644 index 41a14807c..000000000 --- a/frontend/src/Settings/Tags/Details/TagDetailsModalContent.js +++ /dev/null @@ -1,257 +0,0 @@ -import PropTypes from 'prop-types'; -import React from 'react'; -import FieldSet from 'Components/FieldSet'; -import Label from 'Components/Label'; -import Button from 'Components/Link/Button'; -import ModalBody from 'Components/Modal/ModalBody'; -import ModalContent from 'Components/Modal/ModalContent'; -import ModalFooter from 'Components/Modal/ModalFooter'; -import ModalHeader from 'Components/Modal/ModalHeader'; -import { kinds } from 'Helpers/Props'; -import translate from 'Utilities/String/translate'; -import TagDetailsDelayProfile from './TagDetailsDelayProfile'; -import styles from './TagDetailsModalContent.css'; - -function TagDetailsModalContent(props) { - const { - label, - isTagUsed, - series, - delayProfiles, - importLists, - notifications, - releaseProfiles, - indexers, - downloadClients, - autoTags, - onModalClose, - onDeleteTagPress - } = props; - - return ( - - - {translate('TagDetails', { label })} - - - - { - !isTagUsed && -
- {translate('TagIsNotUsedAndCanBeDeleted')} -
- } - - { - series.length ? -
- { - series.map((item) => { - return ( -
- {item.title} -
- ); - }) - } -
: - null - } - - { - delayProfiles.length ? -
- { - delayProfiles.map((item) => { - const { - id, - preferredProtocol, - enableUsenet, - enableTorrent, - usenetDelay, - torrentDelay - } = item; - - return ( - - ); - }) - } -
: - null - } - - { - notifications.length ? -
- { - notifications.map((item) => { - return ( -
- {item.name} -
- ); - }) - } -
: - null - } - - { - importLists.length ? -
- { - importLists.map((item) => { - return ( -
- {item.name} -
- ); - }) - } -
: - null - } - - { - releaseProfiles.length ? -
- { - releaseProfiles.map((item) => { - return ( -
-
- { - item.required.map((r) => { - return ( - - ); - }) - } -
- -
- { - item.ignored.map((i) => { - return ( - - ); - }) - } -
-
- ); - }) - } -
: - null - } - - { - indexers.length ? -
- { - indexers.map((item) => { - return ( -
- {item.name} -
- ); - }) - } -
: - null - } - - { - downloadClients.length ? -
- { - downloadClients.map((item) => { - return ( -
- {item.name} -
- ); - }) - } -
: - null - } - - { - autoTags.length ? -
- { - autoTags.map((item) => { - return ( -
- {item.name} -
- ); - }) - } -
: - null - } -
- - - { - - } - - - -
- ); -} - -TagDetailsModalContent.propTypes = { - label: PropTypes.string.isRequired, - isTagUsed: PropTypes.bool.isRequired, - series: PropTypes.arrayOf(PropTypes.object).isRequired, - delayProfiles: PropTypes.arrayOf(PropTypes.object).isRequired, - importLists: PropTypes.arrayOf(PropTypes.object).isRequired, - notifications: PropTypes.arrayOf(PropTypes.object).isRequired, - releaseProfiles: PropTypes.arrayOf(PropTypes.object).isRequired, - indexers: PropTypes.arrayOf(PropTypes.object).isRequired, - downloadClients: PropTypes.arrayOf(PropTypes.object).isRequired, - autoTags: PropTypes.arrayOf(PropTypes.object).isRequired, - onModalClose: PropTypes.func.isRequired, - onDeleteTagPress: PropTypes.func.isRequired -}; - -export default TagDetailsModalContent; diff --git a/frontend/src/Settings/Tags/Details/TagDetailsModalContent.tsx b/frontend/src/Settings/Tags/Details/TagDetailsModalContent.tsx new file mode 100644 index 000000000..b3f91fd2e --- /dev/null +++ b/frontend/src/Settings/Tags/Details/TagDetailsModalContent.tsx @@ -0,0 +1,269 @@ +import React from 'react'; +import { useSelector } from 'react-redux'; +import { createSelector } from 'reselect'; +import ModelBase from 'App/ModelBase'; +import AppState from 'App/State/AppState'; +import FieldSet from 'Components/FieldSet'; +import Label from 'Components/Label'; +import Button from 'Components/Link/Button'; +import ModalBody from 'Components/Modal/ModalBody'; +import ModalContent from 'Components/Modal/ModalContent'; +import ModalFooter from 'Components/Modal/ModalFooter'; +import ModalHeader from 'Components/Modal/ModalHeader'; +import { kinds } from 'Helpers/Props'; +import createAllSeriesSelector from 'Store/Selectors/createAllSeriesSelector'; +import translate from 'Utilities/String/translate'; +import TagDetailsDelayProfile from './TagDetailsDelayProfile'; +import styles from './TagDetailsModalContent.css'; + +function findMatchingItems(ids: number[], items: T[]) { + return items.filter((s) => { + return ids.includes(s.id); + }); +} + +function createUnorderedMatchingSeriesSelector(seriesIds: number[]) { + return createSelector(createAllSeriesSelector(), (series) => + findMatchingItems(seriesIds, series) + ); +} + +function createMatchingSeriesSelector(seriesIds: number[]) { + return createSelector( + createUnorderedMatchingSeriesSelector(seriesIds), + (series) => { + return series.sort((seriesA, seriesB) => { + const sortTitleA = seriesA.sortTitle; + const sortTitleB = seriesB.sortTitle; + + if (sortTitleA > sortTitleB) { + return 1; + } else if (sortTitleA < sortTitleB) { + return -1; + } + + return 0; + }); + } + ); +} + +function createMatchingItemSelector( + ids: number[], + selector: (state: AppState) => T[] +) { + return createSelector(selector, (items) => findMatchingItems(ids, items)); +} + +export interface TagDetailsModalContentProps { + label: string; + isTagUsed: boolean; + delayProfileIds: number[]; + importListIds: number[]; + notificationIds: number[]; + restrictionIds: number[]; + indexerIds: number[]; + downloadClientIds: number[]; + autoTagIds: number[]; + seriesIds: number[]; + onModalClose: () => void; + onDeleteTagPress: () => void; +} + +function TagDetailsModalContent({ + label, + isTagUsed, + delayProfileIds = [], + importListIds = [], + notificationIds = [], + restrictionIds = [], + indexerIds = [], + downloadClientIds = [], + autoTagIds = [], + seriesIds = [], + onModalClose, + onDeleteTagPress, +}: TagDetailsModalContentProps) { + const series = useSelector(createMatchingSeriesSelector(seriesIds)); + + const delayProfiles = useSelector( + createMatchingItemSelector( + delayProfileIds, + (state: AppState) => state.settings.delayProfiles.items + ) + ); + + const importLists = useSelector( + createMatchingItemSelector( + importListIds, + (state: AppState) => state.settings.importLists.items + ) + ); + + const notifications = useSelector( + createMatchingItemSelector( + notificationIds, + (state: AppState) => state.settings.notifications.items + ) + ); + + const releaseProfiles = useSelector( + createMatchingItemSelector( + restrictionIds, + (state: AppState) => state.settings.releaseProfiles.items + ) + ); + + const indexers = useSelector( + createMatchingItemSelector( + indexerIds, + (state: AppState) => state.settings.indexers.items + ) + ); + + const downloadClients = useSelector( + createMatchingItemSelector( + downloadClientIds, + (state: AppState) => state.settings.downloadClients.items + ) + ); + + const autoTags = useSelector( + createMatchingItemSelector( + autoTagIds, + (state: AppState) => state.settings.autoTaggings.items + ) + ); + + return ( + + {translate('TagDetails', { label })} + + + {!isTagUsed &&
{translate('TagIsNotUsedAndCanBeDeleted')}
} + + {series.length ? ( +
+ {series.map((item) => { + return
{item.title}
; + })} +
+ ) : null} + + {delayProfiles.length ? ( +
+ {delayProfiles.map((item) => { + const { + id, + preferredProtocol, + enableUsenet, + enableTorrent, + usenetDelay, + torrentDelay, + } = item; + + return ( + + ); + })} +
+ ) : null} + + {notifications.length ? ( +
+ {notifications.map((item) => { + return
{item.name}
; + })} +
+ ) : null} + + {importLists.length ? ( +
+ {importLists.map((item) => { + return
{item.name}
; + })} +
+ ) : null} + + {releaseProfiles.length ? ( +
+ {releaseProfiles.map((item) => { + return ( +
+
+ {item.required.map((r) => { + return ( + + ); + })} +
+ +
+ {item.ignored.map((i) => { + return ( + + ); + })} +
+
+ ); + })} +
+ ) : null} + + {indexers.length ? ( +
+ {indexers.map((item) => { + return
{item.name}
; + })} +
+ ) : null} + + {downloadClients.length ? ( +
+ {downloadClients.map((item) => { + return
{item.name}
; + })} +
+ ) : null} + + {autoTags.length ? ( +
+ {autoTags.map((item) => { + return
{item.name}
; + })} +
+ ) : null} +
+ + + + + + +
+ ); +} + +export default TagDetailsModalContent; diff --git a/frontend/src/Settings/Tags/Details/TagDetailsModalContentConnector.js b/frontend/src/Settings/Tags/Details/TagDetailsModalContentConnector.js deleted file mode 100644 index e33a958f7..000000000 --- a/frontend/src/Settings/Tags/Details/TagDetailsModalContentConnector.js +++ /dev/null @@ -1,121 +0,0 @@ -import { connect } from 'react-redux'; -import { createSelector } from 'reselect'; -import createAllSeriesSelector from 'Store/Selectors/createAllSeriesSelector'; -import TagDetailsModalContent from './TagDetailsModalContent'; - -function findMatchingItems(ids, items) { - return items.filter((s) => { - return ids.includes(s.id); - }); -} - -function createUnorderedMatchingSeriesSelector() { - return createSelector( - (state, { seriesIds }) => seriesIds, - createAllSeriesSelector(), - findMatchingItems - ); -} - -function createMatchingSeriesSelector() { - return createSelector( - createUnorderedMatchingSeriesSelector(), - (series) => { - return series.sort((seriesA, seriesB) => { - const sortTitleA = seriesA.sortTitle; - const sortTitleB = seriesB.sortTitle; - - if (sortTitleA > sortTitleB) { - return 1; - } else if (sortTitleA < sortTitleB) { - return -1; - } - - return 0; - }); - } - ); -} - -function createMatchingDelayProfilesSelector() { - return createSelector( - (state, { delayProfileIds }) => delayProfileIds, - (state) => state.settings.delayProfiles.items, - findMatchingItems - ); -} - -function createMatchingImportListsSelector() { - return createSelector( - (state, { importListIds }) => importListIds, - (state) => state.settings.importLists.items, - findMatchingItems - ); -} - -function createMatchingNotificationsSelector() { - return createSelector( - (state, { notificationIds }) => notificationIds, - (state) => state.settings.notifications.items, - findMatchingItems - ); -} - -function createMatchingReleaseProfilesSelector() { - return createSelector( - (state, { restrictionIds }) => restrictionIds, - (state) => state.settings.releaseProfiles.items, - findMatchingItems - ); -} - -function createMatchingIndexersSelector() { - return createSelector( - (state, { indexerIds }) => indexerIds, - (state) => state.settings.indexers.items, - findMatchingItems - ); -} - -function createMatchingDownloadClientsSelector() { - return createSelector( - (state, { downloadClientIds }) => downloadClientIds, - (state) => state.settings.downloadClients.items, - findMatchingItems - ); -} - -function createMatchingAutoTagsSelector() { - return createSelector( - (state, { autoTagIds }) => autoTagIds, - (state) => state.settings.autoTaggings.items, - findMatchingItems - ); -} - -function createMapStateToProps() { - return createSelector( - createMatchingSeriesSelector(), - createMatchingDelayProfilesSelector(), - createMatchingImportListsSelector(), - createMatchingNotificationsSelector(), - createMatchingReleaseProfilesSelector(), - createMatchingIndexersSelector(), - createMatchingDownloadClientsSelector(), - createMatchingAutoTagsSelector(), - (series, delayProfiles, importLists, notifications, releaseProfiles, indexers, downloadClients, autoTags) => { - return { - series, - delayProfiles, - importLists, - notifications, - releaseProfiles, - indexers, - downloadClients, - autoTags - }; - } - ); -} - -export default connect(createMapStateToProps)(TagDetailsModalContent); diff --git a/frontend/src/Settings/Tags/Tag.js b/frontend/src/Settings/Tags/Tag.js deleted file mode 100644 index b74043fcd..000000000 --- a/frontend/src/Settings/Tags/Tag.js +++ /dev/null @@ -1,207 +0,0 @@ -import PropTypes from 'prop-types'; -import React, { Component } from 'react'; -import Card from 'Components/Card'; -import ConfirmModal from 'Components/Modal/ConfirmModal'; -import { kinds } from 'Helpers/Props'; -import translate from 'Utilities/String/translate'; -import TagDetailsModal from './Details/TagDetailsModal'; -import TagInUse from './TagInUse'; -import styles from './Tag.css'; - -class Tag extends Component { - - // - // Lifecycle - - constructor(props, context) { - super(props, context); - - this.state = { - isDetailsModalOpen: false, - isDeleteTagModalOpen: false - }; - } - - // - // Listeners - - onShowDetailsPress = () => { - this.setState({ isDetailsModalOpen: true }); - }; - - onDetailsModalClose = () => { - this.setState({ isDetailsModalOpen: false }); - }; - - onDeleteTagPress = () => { - this.setState({ - isDetailsModalOpen: false, - isDeleteTagModalOpen: true - }); - }; - - onDeleteTagModalClose = () => { - this.setState({ isDeleteTagModalOpen: false }); - }; - - onConfirmDeleteTag = () => { - this.props.onConfirmDeleteTag({ id: this.props.id }); - }; - - // - // Render - - render() { - const { - label, - delayProfileIds, - importListIds, - notificationIds, - restrictionIds, - indexerIds, - downloadClientIds, - autoTagIds, - seriesIds - } = this.props; - - const { - isDetailsModalOpen, - isDeleteTagModalOpen - } = this.state; - - const isTagUsed = !!( - delayProfileIds.length || - importListIds.length || - notificationIds.length || - restrictionIds.length || - indexerIds.length || - downloadClientIds.length || - autoTagIds.length || - seriesIds.length - ); - - return ( - -
- {label} -
- - { - isTagUsed ? -
- - - - - - - - - - - - - - - -
: - null - } - - { - !isTagUsed && -
- {translate('NoLinks')} -
- } - - - - -
- ); - } -} - -Tag.propTypes = { - id: PropTypes.number.isRequired, - label: PropTypes.string.isRequired, - delayProfileIds: PropTypes.arrayOf(PropTypes.number).isRequired, - importListIds: PropTypes.arrayOf(PropTypes.number).isRequired, - notificationIds: PropTypes.arrayOf(PropTypes.number).isRequired, - restrictionIds: PropTypes.arrayOf(PropTypes.number).isRequired, - indexerIds: PropTypes.arrayOf(PropTypes.number).isRequired, - downloadClientIds: PropTypes.arrayOf(PropTypes.number).isRequired, - autoTagIds: PropTypes.arrayOf(PropTypes.number).isRequired, - seriesIds: PropTypes.arrayOf(PropTypes.number).isRequired, - onConfirmDeleteTag: PropTypes.func.isRequired -}; - -Tag.defaultProps = { - delayProfileIds: [], - importListIds: [], - notificationIds: [], - restrictionIds: [], - indexerIds: [], - downloadClientIds: [], - autoTagIds: [], - seriesIds: [] -}; - -export default Tag; diff --git a/frontend/src/Settings/Tags/Tag.tsx b/frontend/src/Settings/Tags/Tag.tsx new file mode 100644 index 000000000..97990ac4e --- /dev/null +++ b/frontend/src/Settings/Tags/Tag.tsx @@ -0,0 +1,151 @@ +import React, { useCallback, useState } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import Card from 'Components/Card'; +import ConfirmModal from 'Components/Modal/ConfirmModal'; +import { kinds } from 'Helpers/Props'; +import { deleteTag } from 'Store/Actions/tagActions'; +import createTagDetailsSelector from 'Store/Selectors/createTagDetailsSelector'; +import translate from 'Utilities/String/translate'; +import TagDetailsModal from './Details/TagDetailsModal'; +import TagInUse from './TagInUse'; +import styles from './Tag.css'; + +interface TagProps { + id: number; + label: string; +} + +function Tag({ id, label }: TagProps) { + const dispatch = useDispatch(); + const { + delayProfileIds = [], + importListIds = [], + notificationIds = [], + restrictionIds = [], + indexerIds = [], + downloadClientIds = [], + autoTagIds = [], + seriesIds = [], + } = useSelector(createTagDetailsSelector(id)) ?? {}; + const [isDetailsModalOpen, setIsDetailsModalOpen] = useState(false); + const [isDeleteTagModalOpen, setIsDeleteTagModalOpen] = useState(false); + + const isTagUsed = !!( + delayProfileIds.length || + importListIds.length || + notificationIds.length || + restrictionIds.length || + indexerIds.length || + downloadClientIds.length || + autoTagIds.length || + seriesIds.length + ); + + const handleShowDetailsPress = useCallback(() => { + setIsDetailsModalOpen(true); + }, []); + + const handeDetailsModalClose = useCallback(() => { + setIsDetailsModalOpen(false); + }, []); + + const handleDeleteTagPress = useCallback(() => { + setIsDetailsModalOpen(false); + setIsDeleteTagModalOpen(true); + }, []); + + const handleConfirmDeleteTag = useCallback(() => { + setIsDeleteTagModalOpen(false); + }, []); + + const handleDeleteTagModalClose = useCallback(() => { + dispatch(deleteTag({ id })); + }, [id, dispatch]); + + return ( + +
{label}
+ + {isTagUsed ? ( +
+ + + + + + + + + + + + + + + +
+ ) : null} + + {!isTagUsed &&
{translate('NoLinks')}
} + + + + +
+ ); +} + +export default Tag; diff --git a/frontend/src/Settings/Tags/TagConnector.js b/frontend/src/Settings/Tags/TagConnector.js deleted file mode 100644 index 986acc8e8..000000000 --- a/frontend/src/Settings/Tags/TagConnector.js +++ /dev/null @@ -1,22 +0,0 @@ -import { connect } from 'react-redux'; -import { createSelector } from 'reselect'; -import { deleteTag } from 'Store/Actions/tagActions'; -import createTagDetailsSelector from 'Store/Selectors/createTagDetailsSelector'; -import Tag from './Tag'; - -function createMapStateToProps() { - return createSelector( - createTagDetailsSelector(), - (tagDetails) => { - return { - ...tagDetails - }; - } - ); -} - -const mapStateToProps = { - onConfirmDeleteTag: deleteTag -}; - -export default connect(createMapStateToProps, mapStateToProps)(Tag); diff --git a/frontend/src/Settings/Tags/TagInUse.js b/frontend/src/Settings/Tags/TagInUse.tsx similarity index 50% rename from frontend/src/Settings/Tags/TagInUse.js rename to frontend/src/Settings/Tags/TagInUse.tsx index 27228fa2e..0f8090a85 100644 --- a/frontend/src/Settings/Tags/TagInUse.js +++ b/frontend/src/Settings/Tags/TagInUse.tsx @@ -1,13 +1,12 @@ -import PropTypes from 'prop-types'; import React from 'react'; -export default function TagInUse(props) { - const { - label, - labelPlural, - count - } = props; +interface TagInUseProps { + label: string; + labelPlural?: string; + count: number; +} +export default function TagInUse({ label, labelPlural, count }: TagInUseProps) { if (count === 0) { return null; } @@ -26,9 +25,3 @@ export default function TagInUse(props) {
); } - -TagInUse.propTypes = { - label: PropTypes.string.isRequired, - labelPlural: PropTypes.string, - count: PropTypes.number.isRequired -}; diff --git a/frontend/src/Settings/Tags/TagSettings.js b/frontend/src/Settings/Tags/TagSettings.tsx similarity index 80% rename from frontend/src/Settings/Tags/TagSettings.js rename to frontend/src/Settings/Tags/TagSettings.tsx index b37b185f7..68edf765b 100644 --- a/frontend/src/Settings/Tags/TagSettings.js +++ b/frontend/src/Settings/Tags/TagSettings.tsx @@ -4,17 +4,15 @@ import PageContentBody from 'Components/Page/PageContentBody'; import SettingsToolbar from 'Settings/SettingsToolbar'; import translate from 'Utilities/String/translate'; import AutoTaggings from './AutoTagging/AutoTaggings'; -import TagsConnector from './TagsConnector'; +import Tags from './Tags'; function TagSettings() { return ( - + - + diff --git a/frontend/src/Settings/Tags/Tags.js b/frontend/src/Settings/Tags/Tags.js deleted file mode 100644 index 8f5a9918a..000000000 --- a/frontend/src/Settings/Tags/Tags.js +++ /dev/null @@ -1,54 +0,0 @@ -import PropTypes from 'prop-types'; -import React from 'react'; -import Alert from 'Components/Alert'; -import FieldSet from 'Components/FieldSet'; -import PageSectionContent from 'Components/Page/PageSectionContent'; -import { kinds } from 'Helpers/Props'; -import translate from 'Utilities/String/translate'; -import TagConnector from './TagConnector'; -import styles from './Tags.css'; - -function Tags(props) { - const { - items, - ...otherProps - } = props; - - if (!items.length) { - return ( - - {translate('NoTagsHaveBeenAddedYet')} - - ); - } - - return ( -
- -
- { - items.map((item) => { - return ( - - ); - }) - } -
-
-
- ); -} - -Tags.propTypes = { - items: PropTypes.arrayOf(PropTypes.object).isRequired -}; - -export default Tags; diff --git a/frontend/src/Settings/Tags/Tags.tsx b/frontend/src/Settings/Tags/Tags.tsx new file mode 100644 index 000000000..85768d565 --- /dev/null +++ b/frontend/src/Settings/Tags/Tags.tsx @@ -0,0 +1,73 @@ +import React, { useEffect } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import TagsAppState, { Tag as TagModel } from 'App/State/TagsAppState'; +import Alert from 'Components/Alert'; +import FieldSet from 'Components/FieldSet'; +import PageSectionContent from 'Components/Page/PageSectionContent'; +import { kinds } from 'Helpers/Props'; +import { + fetchDelayProfiles, + fetchDownloadClients, + fetchImportLists, + fetchIndexers, + fetchNotifications, + fetchReleaseProfiles, +} from 'Store/Actions/settingsActions'; +import { fetchTagDetails, fetchTags } from 'Store/Actions/tagActions'; +import createSortedSectionSelector from 'Store/Selectors/createSortedSectionSelector'; +import sortByProp from 'Utilities/Array/sortByProp'; +import translate from 'Utilities/String/translate'; +import Tag from './Tag'; +import styles from './Tags.css'; + +function Tags() { + const dispatch = useDispatch(); + const { items, isFetching, isPopulated, error, details } = useSelector( + createSortedSectionSelector( + 'tags', + sortByProp('label') + ) + ); + + const { + isFetching: isDetailsFetching, + isPopulated: isDetailsPopulated, + error: detailsError, + } = details; + + useEffect(() => { + dispatch(fetchTags()); + dispatch(fetchTagDetails()); + dispatch(fetchDelayProfiles()); + dispatch(fetchImportLists()); + dispatch(fetchNotifications()); + dispatch(fetchReleaseProfiles()); + dispatch(fetchIndexers()); + dispatch(fetchDownloadClients()); + }, [dispatch]); + + if (!items.length) { + return ( + {translate('NoTagsHaveBeenAddedYet')} + ); + } + + return ( +
+ +
+ {items.map((item) => { + return ; + })} +
+
+
+ ); +} + +export default Tags; diff --git a/frontend/src/Settings/Tags/TagsConnector.js b/frontend/src/Settings/Tags/TagsConnector.js deleted file mode 100644 index 15f31d3c5..000000000 --- a/frontend/src/Settings/Tags/TagsConnector.js +++ /dev/null @@ -1,90 +0,0 @@ -import PropTypes from 'prop-types'; -import React, { Component } from 'react'; -import { connect } from 'react-redux'; -import { createSelector } from 'reselect'; -import { fetchDelayProfiles, fetchDownloadClients, fetchImportLists, fetchIndexers, fetchNotifications, fetchReleaseProfiles } from 'Store/Actions/settingsActions'; -import { fetchTagDetails, fetchTags } from 'Store/Actions/tagActions'; -import createSortedSectionSelector from 'Store/Selectors/createSortedSectionSelector'; -import sortByProp from 'Utilities/Array/sortByProp'; -import Tags from './Tags'; - -function createMapStateToProps() { - return createSelector( - createSortedSectionSelector('tags', sortByProp('label')), - (tags) => { - const isFetching = tags.isFetching || tags.details.isFetching; - const error = tags.error || tags.details.error; - const isPopulated = tags.isPopulated && tags.details.isPopulated; - - return { - ...tags, - isFetching, - error, - isPopulated - }; - } - ); -} - -const mapDispatchToProps = { - dispatchFetchTags: fetchTags, - dispatchFetchTagDetails: fetchTagDetails, - dispatchFetchDelayProfiles: fetchDelayProfiles, - dispatchFetchImportLists: fetchImportLists, - dispatchFetchNotifications: fetchNotifications, - dispatchFetchReleaseProfiles: fetchReleaseProfiles, - dispatchFetchIndexers: fetchIndexers, - dispatchFetchDownloadClients: fetchDownloadClients -}; - -class MetadatasConnector extends Component { - - // - // Lifecycle - - componentDidMount() { - const { - dispatchFetchTags, - dispatchFetchTagDetails, - dispatchFetchDelayProfiles, - dispatchFetchImportLists, - dispatchFetchNotifications, - dispatchFetchReleaseProfiles, - dispatchFetchIndexers, - dispatchFetchDownloadClients - } = this.props; - - dispatchFetchTags(); - dispatchFetchTagDetails(); - dispatchFetchDelayProfiles(); - dispatchFetchImportLists(); - dispatchFetchNotifications(); - dispatchFetchReleaseProfiles(); - dispatchFetchIndexers(); - dispatchFetchDownloadClients(); - } - - // - // Render - - render() { - return ( - - ); - } -} - -MetadatasConnector.propTypes = { - dispatchFetchTags: PropTypes.func.isRequired, - dispatchFetchTagDetails: PropTypes.func.isRequired, - dispatchFetchDelayProfiles: PropTypes.func.isRequired, - dispatchFetchImportLists: PropTypes.func.isRequired, - dispatchFetchNotifications: PropTypes.func.isRequired, - dispatchFetchReleaseProfiles: PropTypes.func.isRequired, - dispatchFetchIndexers: PropTypes.func.isRequired, - dispatchFetchDownloadClients: PropTypes.func.isRequired -}; - -export default connect(createMapStateToProps, mapDispatchToProps)(MetadatasConnector); diff --git a/frontend/src/Store/Selectors/createEnabledDownloadClientsSelector.ts b/frontend/src/Store/Selectors/createEnabledDownloadClientsSelector.ts index 3a581587b..66b8a776d 100644 --- a/frontend/src/Store/Selectors/createEnabledDownloadClientsSelector.ts +++ b/frontend/src/Store/Selectors/createEnabledDownloadClientsSelector.ts @@ -9,7 +9,7 @@ export default function createEnabledDownloadClientsSelector( protocol: DownloadProtocol ) { return createSelector( - createSortedSectionSelector( + createSortedSectionSelector( 'settings.downloadClients', sortByProp('name') ), diff --git a/frontend/src/Store/Selectors/createProviderSettingsSelector.js b/frontend/src/Store/Selectors/createProviderSettingsSelector.js deleted file mode 100644 index f5ac9bad5..000000000 --- a/frontend/src/Store/Selectors/createProviderSettingsSelector.js +++ /dev/null @@ -1,71 +0,0 @@ -import _ from 'lodash'; -import { createSelector } from 'reselect'; -import selectSettings from 'Store/Selectors/selectSettings'; - -function selector(id, section) { - if (!id) { - const item = _.isArray(section.schema) ? section.selectedSchema : section.schema; - const settings = selectSettings(Object.assign({ name: '' }, item), section.pendingChanges, section.saveError); - - const { - isSchemaFetching: isFetching, - isSchemaPopulated: isPopulated, - schemaError: error, - isSaving, - saveError, - isTesting, - pendingChanges - } = section; - - return { - isFetching, - isPopulated, - error, - isSaving, - saveError, - isTesting, - pendingChanges, - ...settings, - item: settings.settings - }; - } - - const { - isFetching, - isPopulated, - error, - isSaving, - saveError, - isTesting, - pendingChanges - } = section; - - const settings = selectSettings(_.find(section.items, { id }), pendingChanges, saveError); - - return { - isFetching, - isPopulated, - error, - isSaving, - saveError, - isTesting, - ...settings, - item: settings.settings - }; -} - -export default function createProviderSettingsSelector(sectionName) { - return createSelector( - (state, { id }) => id, - (state) => state.settings[sectionName], - (id, section) => selector(id, section) - ); -} - -export function createProviderSettingsSelectorHook(sectionName, id) { - return createSelector( - (state) => state.settings[sectionName], - (section) => selector(id, section) - ); -} - diff --git a/frontend/src/Store/Selectors/createProviderSettingsSelector.ts b/frontend/src/Store/Selectors/createProviderSettingsSelector.ts new file mode 100644 index 000000000..161dc7df5 --- /dev/null +++ b/frontend/src/Store/Selectors/createProviderSettingsSelector.ts @@ -0,0 +1,100 @@ +import _ from 'lodash'; +import { createSelector } from 'reselect'; +import ModelBase from 'App/ModelBase'; +import { + AppSectionProviderState, + AppSectionSchemaState, +} from 'App/State/AppSectionState'; +import AppState from 'App/State/AppState'; +import selectSettings, { + ModelBaseSetting, +} from 'Store/Selectors/selectSettings'; +import getSectionState from 'Utilities/State/getSectionState'; + +function selector< + T extends ModelBaseSetting, + S extends AppSectionProviderState & AppSectionSchemaState +>(id: number | undefined, section: S) { + if (!id) { + const item = _.isArray(section.schema) + ? section.selectedSchema + : section.schema; + const settings = selectSettings( + Object.assign({ name: '' }, item), + section.pendingChanges ?? {}, + section.saveError + ); + + const { + isSchemaFetching: isFetching, + isSchemaPopulated: isPopulated, + schemaError: error, + isSaving, + saveError, + isTesting, + pendingChanges, + } = section; + + return { + isFetching, + isPopulated, + error, + isSaving, + saveError, + isTesting, + ...settings, + pendingChanges, + item: settings.settings, + }; + } + + const { + isFetching, + isPopulated, + error, + isSaving, + saveError, + isTesting, + pendingChanges, + } = section; + + const item = section.items.find((i) => i.id === id)!; + const settings = selectSettings(item, pendingChanges, saveError); + + return { + isFetching, + isPopulated, + error, + isSaving, + saveError, + isTesting, + ...settings, + item: settings.settings, + }; +} + +export default function createProviderSettingsSelector< + T extends ModelBase, + S extends AppSectionProviderState & AppSectionSchemaState +>(sectionName: string) { + // @ts-expect-error - This isn't fully typed + return createSelector( + (_state: AppState, { id }: { id: number }) => id, + (state) => state.settings[sectionName] as S, + (id: number, section: S) => selector(id, section) + ); +} + +export function createProviderSettingsSelectorHook< + T extends ModelBaseSetting, + S extends AppSectionProviderState & AppSectionSchemaState +>(sectionName: string, id: number | undefined) { + return createSelector( + (state: AppState) => state.settings, + (state) => { + const sectionState = getSectionState(state, sectionName, false) as S; + + return selector(id, sectionState); + } + ); +} diff --git a/frontend/src/Store/Selectors/createRootFoldersSelector.ts b/frontend/src/Store/Selectors/createRootFoldersSelector.ts index 3eb486191..df2600f78 100644 --- a/frontend/src/Store/Selectors/createRootFoldersSelector.ts +++ b/frontend/src/Store/Selectors/createRootFoldersSelector.ts @@ -6,7 +6,10 @@ import sortByProp from 'Utilities/Array/sortByProp'; export default function createRootFoldersSelector() { return createSelector( - createSortedSectionSelector('rootFolders', sortByProp('path')), + createSortedSectionSelector( + 'rootFolders', + sortByProp('path') + ), (rootFolders: RootFolderAppState) => rootFolders ); } diff --git a/frontend/src/Store/Selectors/createSortedSectionSelector.ts b/frontend/src/Store/Selectors/createSortedSectionSelector.ts index 7e13b8a31..88078531e 100644 --- a/frontend/src/Store/Selectors/createSortedSectionSelector.ts +++ b/frontend/src/Store/Selectors/createSortedSectionSelector.ts @@ -1,15 +1,18 @@ import { createSelector } from 'reselect'; +import AppSectionState, { + AppSectionProviderState, +} from 'App/State/AppSectionState'; import AppState from 'App/State/AppState'; import getSectionState from 'Utilities/State/getSectionState'; -function createSortedSectionSelector( - section: string, - comparer: (a: T, b: T) => number -) { +function createSortedSectionSelector< + T, + S extends AppSectionState | AppSectionProviderState +>(section: string, comparer: (a: T, b: T) => number) { return createSelector( (state: AppState) => state, (state) => { - const sectionState = getSectionState(state, section, true); + const sectionState = getSectionState(state, section, true) as S; return { ...sectionState, diff --git a/frontend/src/Store/Selectors/createTagDetailsSelector.ts b/frontend/src/Store/Selectors/createTagDetailsSelector.ts index 2a271cafe..aa330a657 100644 --- a/frontend/src/Store/Selectors/createTagDetailsSelector.ts +++ b/frontend/src/Store/Selectors/createTagDetailsSelector.ts @@ -1,11 +1,10 @@ import { createSelector } from 'reselect'; import AppState from 'App/State/AppState'; -function createTagDetailsSelector() { +function createTagDetailsSelector(id: number) { return createSelector( - (_: AppState, { id }: { id: number }) => id, (state: AppState) => state.tags.details.items, - (id, tagDetails) => { + (tagDetails) => { return tagDetails.find((t) => t.id === id); } ); diff --git a/frontend/src/Store/Selectors/selectSettings.ts b/frontend/src/Store/Selectors/selectSettings.ts index 75665d73b..c1e5b4de4 100644 --- a/frontend/src/Store/Selectors/selectSettings.ts +++ b/frontend/src/Store/Selectors/selectSettings.ts @@ -68,14 +68,14 @@ function mapFailure(failure: ValidationFailure): Failure { }; } -interface ModelBaseSetting { +export interface ModelBaseSetting { // eslint-disable-next-line @typescript-eslint/no-explicit-any [id: string]: any; } function selectSettings( item: T, - pendingChanges: Partial, + pendingChanges?: Partial, saveError?: Error ) { const { errors, warnings } = getValidationFailures(saveError); @@ -105,7 +105,7 @@ function selectSettings( warnings: getFailures(warnings, key), }; - if (pendingChanges.hasOwnProperty(key)) { + if (pendingChanges?.hasOwnProperty(key)) { setting.previousValue = setting.value; setting.value = pendingChanges[key]; setting.pending = true; @@ -126,7 +126,7 @@ function selectSettings( f ); - if ('fields' in pendingChanges) { + if (pendingChanges && 'fields' in pendingChanges) { const pendingChangesFields = pendingChanges.fields as Record< string, // eslint-disable-next-line @typescript-eslint/no-explicit-any diff --git a/frontend/src/typings/AutoTagging.ts b/frontend/src/typings/AutoTagging.ts new file mode 100644 index 000000000..fab9759d3 --- /dev/null +++ b/frontend/src/typings/AutoTagging.ts @@ -0,0 +1,21 @@ +import ModelBase from 'App/ModelBase'; +import Field from './Field'; + +export interface AutoTaggingSpecification { + id: number; + name: string; + implementation: string; + implementationName: string; + negate: boolean; + required: boolean; + fields: Field[]; +} + +interface AutoTagging extends ModelBase { + name: string; + removeTagsAutomatically: boolean; + tags: number[]; + specifications: AutoTaggingSpecification[]; +} + +export default AutoTagging; diff --git a/frontend/src/typings/DelayProfile.ts b/frontend/src/typings/DelayProfile.ts new file mode 100644 index 000000000..0e087b142 --- /dev/null +++ b/frontend/src/typings/DelayProfile.ts @@ -0,0 +1,17 @@ +import ModelBase from 'App/ModelBase'; + +interface DelayProfile extends ModelBase { + name: string; + enableUsenet: boolean; + enableTorrent: boolean; + preferredProtocol: string; + usenetDelay: number; + torrentDelay: number; + bypassIfHighestQuality: boolean; + bypassIfAboveCustomFormatScore: boolean; + minimumCustomFormatScore: number; + order: number; + tags: number[]; +} + +export default DelayProfile;