From 10e3a237ef972540abcf4348bb56973d7ee19bd7 Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Wed, 1 Jan 2025 15:52:17 -0800 Subject: [PATCH] Convert ImportLists to TypeScript --- frontend/src/App/AppRoutes.tsx | 7 +- frontend/src/App/State/SettingsAppState.ts | 5 +- .../src/Components/Form/FormInputGroup.tsx | 1 + .../ImportLists/ImportListSettings.js | 133 ------- .../ImportLists/ImportListSettings.tsx | 107 ++++++ .../ImportListSettingsConnector.js | 21 -- .../ImportLists/AddImportListItem.js | 117 ------ .../ImportLists/AddImportListItem.tsx | 91 +++++ .../ImportLists/AddImportListModal.js | 25 -- .../ImportLists/AddImportListModal.tsx | 23 ++ .../ImportLists/AddImportListModalContent.js | 115 ------ .../ImportLists/AddImportListModalContent.tsx | 108 ++++++ .../AddImportListModalContentConnector.js | 76 ---- .../AddImportListPresetMenuItem.js | 57 --- .../AddImportListPresetMenuItem.tsx | 44 +++ .../ImportLists/EditImportListModal.js | 25 -- .../ImportLists/EditImportListModal.tsx | 44 +++ .../EditImportListModalConnector.js | 65 ---- .../ImportLists/EditImportListModalContent.js | 339 ----------------- .../EditImportListModalContent.tsx | 350 ++++++++++++++++++ .../EditImportListModalContentConnector.js | 93 ----- .../ImportLists/ImportLists/ImportList.js | 128 ------- .../ImportLists/ImportLists/ImportList.tsx | 104 ++++++ .../ImportLists/ImportLists/ImportLists.js | 118 ------ .../ImportLists/ImportLists/ImportLists.tsx | 92 +++++ .../ImportLists/ImportListsConnector.js | 67 ---- .../ImportLists/Options/ImportListOptions.tsx | 58 ++- frontend/src/Tags/useTags.ts | 8 + frontend/src/typings/ImportList.ts | 10 + 29 files changed, 1009 insertions(+), 1422 deletions(-) delete mode 100644 frontend/src/Settings/ImportLists/ImportListSettings.js create mode 100644 frontend/src/Settings/ImportLists/ImportListSettings.tsx delete mode 100644 frontend/src/Settings/ImportLists/ImportListSettingsConnector.js delete mode 100644 frontend/src/Settings/ImportLists/ImportLists/AddImportListItem.js create mode 100644 frontend/src/Settings/ImportLists/ImportLists/AddImportListItem.tsx delete mode 100644 frontend/src/Settings/ImportLists/ImportLists/AddImportListModal.js create mode 100644 frontend/src/Settings/ImportLists/ImportLists/AddImportListModal.tsx delete mode 100644 frontend/src/Settings/ImportLists/ImportLists/AddImportListModalContent.js create mode 100644 frontend/src/Settings/ImportLists/ImportLists/AddImportListModalContent.tsx delete mode 100644 frontend/src/Settings/ImportLists/ImportLists/AddImportListModalContentConnector.js delete mode 100644 frontend/src/Settings/ImportLists/ImportLists/AddImportListPresetMenuItem.js create mode 100644 frontend/src/Settings/ImportLists/ImportLists/AddImportListPresetMenuItem.tsx delete mode 100644 frontend/src/Settings/ImportLists/ImportLists/EditImportListModal.js create mode 100644 frontend/src/Settings/ImportLists/ImportLists/EditImportListModal.tsx delete mode 100644 frontend/src/Settings/ImportLists/ImportLists/EditImportListModalConnector.js delete mode 100644 frontend/src/Settings/ImportLists/ImportLists/EditImportListModalContent.js create mode 100644 frontend/src/Settings/ImportLists/ImportLists/EditImportListModalContent.tsx delete mode 100644 frontend/src/Settings/ImportLists/ImportLists/EditImportListModalContentConnector.js delete mode 100644 frontend/src/Settings/ImportLists/ImportLists/ImportList.js create mode 100644 frontend/src/Settings/ImportLists/ImportLists/ImportList.tsx delete mode 100644 frontend/src/Settings/ImportLists/ImportLists/ImportLists.js create mode 100644 frontend/src/Settings/ImportLists/ImportLists/ImportLists.tsx delete mode 100644 frontend/src/Settings/ImportLists/ImportLists/ImportListsConnector.js create mode 100644 frontend/src/Tags/useTags.ts diff --git a/frontend/src/App/AppRoutes.tsx b/frontend/src/App/AppRoutes.tsx index eba2d60f1..91116c74b 100644 --- a/frontend/src/App/AppRoutes.tsx +++ b/frontend/src/App/AppRoutes.tsx @@ -13,7 +13,7 @@ import SeriesIndex from 'Series/Index/SeriesIndex'; import CustomFormatSettingsPage from 'Settings/CustomFormats/CustomFormatSettingsPage'; import DownloadClientSettingsConnector from 'Settings/DownloadClients/DownloadClientSettingsConnector'; import GeneralSettingsConnector from 'Settings/General/GeneralSettingsConnector'; -import ImportListSettingsConnector from 'Settings/ImportLists/ImportListSettingsConnector'; +import ImportListSettings from 'Settings/ImportLists/ImportListSettings'; import IndexerSettings from 'Settings/Indexers/IndexerSettings'; import MediaManagement from 'Settings/MediaManagement/MediaManagement'; import MetadataSettings from 'Settings/Metadata/MetadataSettings'; @@ -116,10 +116,7 @@ function AppRoutes() { component={DownloadClientSettingsConnector} /> - + diff --git a/frontend/src/App/State/SettingsAppState.ts b/frontend/src/App/State/SettingsAppState.ts index e287a32cd..4b4d68094 100644 --- a/frontend/src/App/State/SettingsAppState.ts +++ b/frontend/src/App/State/SettingsAppState.ts @@ -73,7 +73,10 @@ export type NamingExamplesAppState = AppSectionItemState; export interface ImportListAppState extends AppSectionState, AppSectionDeleteState, - AppSectionSaveState {} + AppSectionSaveState, + AppSectionSchemaState> { + isTestingAll: boolean; +} export interface IndexerOptionsAppState extends AppSectionItemState, diff --git a/frontend/src/Components/Form/FormInputGroup.tsx b/frontend/src/Components/Form/FormInputGroup.tsx index 24d338448..144ba101f 100644 --- a/frontend/src/Components/Form/FormInputGroup.tsx +++ b/frontend/src/Components/Form/FormInputGroup.tsx @@ -152,6 +152,7 @@ interface FormInputGroupProps { placeholder?: string; autoFocus?: boolean; includeFiles?: boolean; + includeMissingValue?: boolean; includeNoChange?: boolean; includeNoChangeDisabled?: boolean; valueOptions?: object; diff --git a/frontend/src/Settings/ImportLists/ImportListSettings.js b/frontend/src/Settings/ImportLists/ImportListSettings.js deleted file mode 100644 index a7fc843ce..000000000 --- a/frontend/src/Settings/ImportLists/ImportListSettings.js +++ /dev/null @@ -1,133 +0,0 @@ -import PropTypes from 'prop-types'; -import React, { Component, Fragment } from 'react'; -import PageContent from 'Components/Page/PageContent'; -import PageContentBody from 'Components/Page/PageContentBody'; -import PageToolbarButton from 'Components/Page/Toolbar/PageToolbarButton'; -import PageToolbarSeparator from 'Components/Page/Toolbar/PageToolbarSeparator'; -import { icons } from 'Helpers/Props'; -import SettingsToolbar from 'Settings/SettingsToolbar'; -import translate from 'Utilities/String/translate'; -import ImportListExclusions from './ImportListExclusions/ImportListExclusions'; -import ImportListsConnector from './ImportLists/ImportListsConnector'; -import ManageImportListsModal from './ImportLists/Manage/ManageImportListsModal'; -import ImportListOptions from './Options/ImportListOptions'; - -class ImportListSettings extends Component { - - // - // Lifecycle - - constructor(props, context) { - super(props, context); - - this._saveCallback = null; - - this.state = { - isSaving: false, - hasPendingChanges: false, - isManageImportListsOpen: false - }; - } - - // - // Listeners - - setChildSave = (saveCallback) => { - this._saveCallback = saveCallback; - }; - - onChildStateChange = (payload) => { - this.setState(payload); - }; - - setListOptionsRef = (ref) => { - this._listOptions = ref; - }; - - onManageImportListsPress = () => { - this.setState({ isManageImportListsOpen: true }); - }; - - onManageImportListsModalClose = () => { - this.setState({ isManageImportListsOpen: false }); - }; - - onHasPendingChange = (hasPendingChanges) => { - this.setState({ - hasPendingChanges - }); - }; - - onSavePress = () => { - if (this._saveCallback) { - this._saveCallback(); - } - }; - - // - // Render - - render() { - const { - isTestingAll, - dispatchTestAllImportLists - } = this.props; - - const { - isSaving, - hasPendingChanges, - isManageImportListsOpen - } = this.state; - - return ( - - - - - - - - - } - onSavePress={this.onSavePress} - /> - - - - - - - - - - - - ); - } -} - -ImportListSettings.propTypes = { - isTestingAll: PropTypes.bool.isRequired, - dispatchTestAllImportLists: PropTypes.func.isRequired -}; - -export default ImportListSettings; diff --git a/frontend/src/Settings/ImportLists/ImportListSettings.tsx b/frontend/src/Settings/ImportLists/ImportListSettings.tsx new file mode 100644 index 000000000..53a2a49a5 --- /dev/null +++ b/frontend/src/Settings/ImportLists/ImportListSettings.tsx @@ -0,0 +1,107 @@ +import React, { useCallback, useRef, useState } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import AppState from 'App/State/AppState'; +import PageContent from 'Components/Page/PageContent'; +import PageContentBody from 'Components/Page/PageContentBody'; +import PageToolbarButton from 'Components/Page/Toolbar/PageToolbarButton'; +import PageToolbarSeparator from 'Components/Page/Toolbar/PageToolbarSeparator'; +import { icons } from 'Helpers/Props'; +import SettingsToolbar from 'Settings/SettingsToolbar'; +import { testAllImportLists } from 'Store/Actions/settingsActions'; +import { + SaveCallback, + SettingsStateChange, +} from 'typings/Settings/SettingsState'; +import translate from 'Utilities/String/translate'; +import ImportListExclusions from './ImportListExclusions/ImportListExclusions'; +import ImportLists from './ImportLists/ImportLists'; +import ManageImportListsModal from './ImportLists/Manage/ManageImportListsModal'; +import ImportListOptions from './Options/ImportListOptions'; + +function ImportListSettings() { + const dispatch = useDispatch(); + const isTestingAll = useSelector( + (state: AppState) => state.settings.importLists.isTestingAll + ); + + const saveOptions = useRef<() => void>(); + + const [isSaving, setIsSaving] = useState(false); + const [hasPendingChanges, setHasPendingChanges] = useState(false); + const [isManageImportListsModalOpen, setIsManageImportListsModalOpen] = + useState(false); + + const handleSetChildSave = useCallback((saveCallback: SaveCallback) => { + saveOptions.current = saveCallback; + }, []); + + const handleChildStateChange = useCallback( + ({ isSaving, hasPendingChanges }: SettingsStateChange) => { + setIsSaving(isSaving); + setHasPendingChanges(hasPendingChanges); + }, + [] + ); + + const handleManageImportListsPress = useCallback(() => { + setIsManageImportListsModalOpen(true); + }, []); + + const handleManageImportListsModalClose = useCallback(() => { + setIsManageImportListsModalOpen(false); + }, []); + + const handleSavePress = useCallback(() => { + saveOptions.current?.(); + }, []); + + const handleTestAllIndexersPress = useCallback(() => { + dispatch(testAllImportLists()); + }, [dispatch]); + + return ( + + + + + + + + + } + onSavePress={handleSavePress} + /> + + + + + + + + + + + + ); +} + +export default ImportListSettings; diff --git a/frontend/src/Settings/ImportLists/ImportListSettingsConnector.js b/frontend/src/Settings/ImportLists/ImportListSettingsConnector.js deleted file mode 100644 index 7607faef7..000000000 --- a/frontend/src/Settings/ImportLists/ImportListSettingsConnector.js +++ /dev/null @@ -1,21 +0,0 @@ -import { connect } from 'react-redux'; -import { createSelector } from 'reselect'; -import { testAllImportLists } from 'Store/Actions/settingsActions'; -import ImportListSettings from './ImportListSettings'; - -function createMapStateToProps() { - return createSelector( - (state) => state.settings.importLists.isTestingAll, - (isTestingAll) => { - return { - isTestingAll - }; - } - ); -} - -const mapDispatchToProps = { - dispatchTestAllImportLists: testAllImportLists -}; - -export default connect(createMapStateToProps, mapDispatchToProps)(ImportListSettings); diff --git a/frontend/src/Settings/ImportLists/ImportLists/AddImportListItem.js b/frontend/src/Settings/ImportLists/ImportLists/AddImportListItem.js deleted file mode 100644 index f38fbfa8e..000000000 --- a/frontend/src/Settings/ImportLists/ImportLists/AddImportListItem.js +++ /dev/null @@ -1,117 +0,0 @@ -import PropTypes from 'prop-types'; -import React, { Component } 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 AddImportListPresetMenuItem from './AddImportListPresetMenuItem'; -import styles from './AddImportListItem.css'; - -class AddImportListItem extends Component { - - // - // Listeners - - onImportListSelect = () => { - const { - implementation, - implementationName, - minRefreshInterval - } = this.props; - - this.props.onImportListSelect({ implementation, implementationName, minRefreshInterval }); - }; - - // - // Render - - render() { - const { - implementation, - implementationName, - minRefreshInterval, - infoLink, - presets, - onImportListSelect - } = this.props; - - const hasPresets = !!(presets && presets.length); - - return ( -
- - -
-
- {implementationName} -
- -
- { - hasPresets && - - - - - - - - { - presets.map((preset) => { - return ( - - ); - }) - } - - - - } - - -
-
-
- ); - } -} - -AddImportListItem.propTypes = { - implementation: PropTypes.string.isRequired, - implementationName: PropTypes.string.isRequired, - minRefreshInterval: PropTypes.string.isRequired, - infoLink: PropTypes.string.isRequired, - presets: PropTypes.arrayOf(PropTypes.object), - onImportListSelect: PropTypes.func.isRequired -}; - -export default AddImportListItem; diff --git a/frontend/src/Settings/ImportLists/ImportLists/AddImportListItem.tsx b/frontend/src/Settings/ImportLists/ImportLists/AddImportListItem.tsx new file mode 100644 index 000000000..88a053fe8 --- /dev/null +++ b/frontend/src/Settings/ImportLists/ImportLists/AddImportListItem.tsx @@ -0,0 +1,91 @@ +import React, { useCallback } from 'react'; +import { useDispatch } from 'react-redux'; +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 { selectImportListSchema } from 'Store/Actions/settingsActions'; +import ImportList from 'typings/ImportList'; +import translate from 'Utilities/String/translate'; +import AddImportListPresetMenuItem from './AddImportListPresetMenuItem'; +import styles from './AddImportListItem.css'; + +interface AddImportListItemProps { + implementation: string; + implementationName: string; + minRefreshInterval: string; + infoLink: string; + presets?: ImportList[]; + onImportListSelect: () => void; +} + +function AddImportListItem({ + implementation, + implementationName, + minRefreshInterval, + infoLink, + presets, + onImportListSelect, +}: AddImportListItemProps) { + const dispatch = useDispatch(); + const hasPresets = !!(presets && presets.length); + + const handleImportListSelect = useCallback(() => { + dispatch( + selectImportListSchema({ + implementation, + implementationName, + }) + ); + + onImportListSelect(); + }, [implementation, implementationName, dispatch, onImportListSelect]); + + return ( +
+ + +
+
{implementationName}
+ +
+ {hasPresets && ( + + + + + + + + {presets.map((preset) => { + return ( + + ); + })} + + + + )} + + +
+
+
+ ); +} + +export default AddImportListItem; diff --git a/frontend/src/Settings/ImportLists/ImportLists/AddImportListModal.js b/frontend/src/Settings/ImportLists/ImportLists/AddImportListModal.js deleted file mode 100644 index a188d6b4a..000000000 --- a/frontend/src/Settings/ImportLists/ImportLists/AddImportListModal.js +++ /dev/null @@ -1,25 +0,0 @@ -import PropTypes from 'prop-types'; -import React from 'react'; -import Modal from 'Components/Modal/Modal'; -import AddImportListModalContentConnector from './AddImportListModalContentConnector'; - -function AddImportListModal({ isOpen, onModalClose, ...otherProps }) { - return ( - - - - ); -} - -AddImportListModal.propTypes = { - isOpen: PropTypes.bool.isRequired, - onModalClose: PropTypes.func.isRequired -}; - -export default AddImportListModal; diff --git a/frontend/src/Settings/ImportLists/ImportLists/AddImportListModal.tsx b/frontend/src/Settings/ImportLists/ImportLists/AddImportListModal.tsx new file mode 100644 index 000000000..067fb577b --- /dev/null +++ b/frontend/src/Settings/ImportLists/ImportLists/AddImportListModal.tsx @@ -0,0 +1,23 @@ +import React from 'react'; +import Modal from 'Components/Modal/Modal'; +import AddImportListModalContent, { + AddImportListModalContentProps, +} from './AddImportListModalContent'; + +interface AddImportListModalProps extends AddImportListModalContentProps { + isOpen: boolean; +} + +function AddImportListModal({ + isOpen, + onModalClose, + ...otherProps +}: AddImportListModalProps) { + return ( + + + + ); +} + +export default AddImportListModal; diff --git a/frontend/src/Settings/ImportLists/ImportLists/AddImportListModalContent.js b/frontend/src/Settings/ImportLists/ImportLists/AddImportListModalContent.js deleted file mode 100644 index 9ed22fad2..000000000 --- a/frontend/src/Settings/ImportLists/ImportLists/AddImportListModalContent.js +++ /dev/null @@ -1,115 +0,0 @@ -import PropTypes from 'prop-types'; -import React, { Component } from 'react'; -import Alert from 'Components/Alert'; -import FieldSet from 'Components/FieldSet'; -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 titleCase from 'Utilities/String/titleCase'; -import translate from 'Utilities/String/translate'; -import AddImportListItem from './AddImportListItem'; -import styles from './AddImportListModalContent.css'; - -class AddImportListModalContent extends Component { - - // - // Render - - render() { - const { - isSchemaFetching, - isSchemaPopulated, - schemaError, - listGroups, - onImportListSelect, - onModalClose - } = this.props; - - return ( - - - {translate('AddImportList')} - - - - { - isSchemaFetching ? - : - null - } - - { - !isSchemaFetching && !!schemaError ? - - {translate('AddListError')} - : - null - } - - { - isSchemaPopulated && !schemaError ? -
- - -
- {translate('SupportedListsSeries')} -
-
- {translate('SupportedListsMoreInfo')} -
-
- { - Object.keys(listGroups).map((key) => { - return ( -
-
- { - listGroups[key].map((list) => { - return ( - - ); - }) - } -
-
- ); - }) - } -
: - null - } -
- - - -
- ); - } -} - -AddImportListModalContent.propTypes = { - isSchemaFetching: PropTypes.bool.isRequired, - isSchemaPopulated: PropTypes.bool.isRequired, - schemaError: PropTypes.object, - listGroups: PropTypes.object.isRequired, - onImportListSelect: PropTypes.func.isRequired, - onModalClose: PropTypes.func.isRequired -}; - -export default AddImportListModalContent; diff --git a/frontend/src/Settings/ImportLists/ImportLists/AddImportListModalContent.tsx b/frontend/src/Settings/ImportLists/ImportLists/AddImportListModalContent.tsx new file mode 100644 index 000000000..02fa12005 --- /dev/null +++ b/frontend/src/Settings/ImportLists/ImportLists/AddImportListModalContent.tsx @@ -0,0 +1,108 @@ +import React, { useEffect, useMemo } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import AppState from 'App/State/AppState'; +import Alert from 'Components/Alert'; +import FieldSet from 'Components/FieldSet'; +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 { fetchImportListSchema } from 'Store/Actions/settingsActions'; +import ImportList from 'typings/ImportList'; +import titleCase from 'Utilities/String/titleCase'; +import translate from 'Utilities/String/translate'; +import AddImportListItem from './AddImportListItem'; +import styles from './AddImportListModalContent.css'; + +export interface AddImportListModalContentProps { + onImportListSelect: () => void; + onModalClose: () => void; +} + +function AddImportListModalContent({ + onImportListSelect, + onModalClose, +}: AddImportListModalContentProps) { + const dispatch = useDispatch(); + const { isSchemaFetching, isSchemaPopulated, schemaError, schema } = + useSelector((state: AppState) => state.settings.importLists); + + const listGroups = useMemo(() => { + const result = schema.reduce>((acc, item) => { + if (!acc[item.listType]) { + acc[item.listType] = []; + } + + acc[item.listType].push(item); + + return acc; + }, {}); + + // Sort the lists by listOrder after grouping them + Object.keys(result).forEach((key) => { + result[key].sort((a, b) => { + return a.listOrder - b.listOrder; + }); + }); + + return result; + }, [schema]); + + useEffect(() => { + dispatch(fetchImportListSchema()); + }, [dispatch]); + + return ( + + {translate('AddImportList')} + + + {isSchemaFetching ? : null} + + {!isSchemaFetching && !!schemaError ? ( + {translate('AddListError')} + ) : null} + + {isSchemaPopulated && !schemaError ? ( +
+ +
{translate('SupportedListsSeries')}
+
{translate('SupportedListsMoreInfo')}
+
+ {Object.keys(listGroups).map((key) => { + return ( +
+
+ {listGroups[key].map((list) => { + return ( + + ); + })} +
+
+ ); + })} +
+ ) : null} +
+ + + +
+ ); +} + +export default AddImportListModalContent; diff --git a/frontend/src/Settings/ImportLists/ImportLists/AddImportListModalContentConnector.js b/frontend/src/Settings/ImportLists/ImportLists/AddImportListModalContentConnector.js deleted file mode 100644 index 99e1fbead..000000000 --- a/frontend/src/Settings/ImportLists/ImportLists/AddImportListModalContentConnector.js +++ /dev/null @@ -1,76 +0,0 @@ -import _ from 'lodash'; -import PropTypes from 'prop-types'; -import React, { Component } from 'react'; -import { connect } from 'react-redux'; -import { createSelector } from 'reselect'; -import { fetchImportListSchema, selectImportListSchema } from 'Store/Actions/settingsActions'; -import AddImportListModalContent from './AddImportListModalContent'; - -function createMapStateToProps() { - return createSelector( - (state) => state.settings.importLists, - (importLists) => { - const { - isSchemaFetching, - isSchemaPopulated, - schemaError, - schema - } = importLists; - - const listGroups = _.chain(schema) - .sortBy((o) => o.listOrder) - .groupBy('listType') - .value(); - - return { - isSchemaFetching, - isSchemaPopulated, - schemaError, - listGroups - }; - } - ); -} - -const mapDispatchToProps = { - fetchImportListSchema, - selectImportListSchema -}; - -class AddImportListModalContentConnector extends Component { - - // - // Lifecycle - - componentDidMount() { - this.props.fetchImportListSchema(); - } - - // - // Listeners - - onImportListSelect = ({ implementation, implementationName, name, minRefreshInterval }) => { - this.props.selectImportListSchema({ implementation, implementationName, presetName: name, minRefreshInterval }); - this.props.onModalClose({ listSelected: true }); - }; - - // - // Render - - render() { - return ( - - ); - } -} - -AddImportListModalContentConnector.propTypes = { - fetchImportListSchema: PropTypes.func.isRequired, - selectImportListSchema: PropTypes.func.isRequired, - onModalClose: PropTypes.func.isRequired -}; - -export default connect(createMapStateToProps, mapDispatchToProps)(AddImportListModalContentConnector); diff --git a/frontend/src/Settings/ImportLists/ImportLists/AddImportListPresetMenuItem.js b/frontend/src/Settings/ImportLists/ImportLists/AddImportListPresetMenuItem.js deleted file mode 100644 index 31d16db29..000000000 --- a/frontend/src/Settings/ImportLists/ImportLists/AddImportListPresetMenuItem.js +++ /dev/null @@ -1,57 +0,0 @@ -import PropTypes from 'prop-types'; -import React, { Component } from 'react'; -import MenuItem from 'Components/Menu/MenuItem'; - -class AddImportListPresetMenuItem extends Component { - - // - // Listeners - - onPress = () => { - const { - name, - implementation, - implementationName, - minRefreshInterval - } = this.props; - - this.props.onPress({ - name, - implementation, - implementationName, - minRefreshInterval - }); - }; - - // - // Render - - render() { - const { - name, - implementation, - implementationName, - minRefreshInterval, - ...otherProps - } = this.props; - - return ( - - {name} - - ); - } -} - -AddImportListPresetMenuItem.propTypes = { - name: PropTypes.string.isRequired, - implementation: PropTypes.string.isRequired, - implementationName: PropTypes.string.isRequired, - minRefreshInterval: PropTypes.string.isRequired, - onPress: PropTypes.func.isRequired -}; - -export default AddImportListPresetMenuItem; diff --git a/frontend/src/Settings/ImportLists/ImportLists/AddImportListPresetMenuItem.tsx b/frontend/src/Settings/ImportLists/ImportLists/AddImportListPresetMenuItem.tsx new file mode 100644 index 000000000..b3d9aaaf0 --- /dev/null +++ b/frontend/src/Settings/ImportLists/ImportLists/AddImportListPresetMenuItem.tsx @@ -0,0 +1,44 @@ +import React, { useCallback } from 'react'; +import { useDispatch } from 'react-redux'; +import MenuItem, { MenuItemProps } from 'Components/Menu/MenuItem'; +import { selectImportListSchema } from 'Store/Actions/settingsActions'; + +interface AddImportListPresetMenuItemProps + extends Omit { + name: string; + implementation: string; + implementationName: string; + minRefreshInterval: string; + onPress: () => void; +} + +function AddImportListPresetMenuItem({ + name, + implementation, + implementationName, + minRefreshInterval, + onPress, + ...otherProps +}: AddImportListPresetMenuItemProps) { + const dispatch = useDispatch(); + + const handlePress = useCallback(() => { + dispatch( + selectImportListSchema({ + implementation, + implementationName, + presetName: name, + }) + ); + + onPress(); + }, [name, implementation, implementationName, dispatch, onPress]); + + return ( + + {name} + + ); +} + +export default AddImportListPresetMenuItem; diff --git a/frontend/src/Settings/ImportLists/ImportLists/EditImportListModal.js b/frontend/src/Settings/ImportLists/ImportLists/EditImportListModal.js deleted file mode 100644 index b673ae9a4..000000000 --- a/frontend/src/Settings/ImportLists/ImportLists/EditImportListModal.js +++ /dev/null @@ -1,25 +0,0 @@ -import PropTypes from 'prop-types'; -import React from 'react'; -import Modal from 'Components/Modal/Modal'; -import EditImportListModalContentConnector from './EditImportListModalContentConnector'; - -function EditImportListModal({ isOpen, onModalClose, ...otherProps }) { - return ( - - - - ); -} - -EditImportListModal.propTypes = { - isOpen: PropTypes.bool.isRequired, - onModalClose: PropTypes.func.isRequired -}; - -export default EditImportListModal; diff --git a/frontend/src/Settings/ImportLists/ImportLists/EditImportListModal.tsx b/frontend/src/Settings/ImportLists/ImportLists/EditImportListModal.tsx new file mode 100644 index 000000000..0b2ea6740 --- /dev/null +++ b/frontend/src/Settings/ImportLists/ImportLists/EditImportListModal.tsx @@ -0,0 +1,44 @@ +import React, { useCallback } from 'react'; +import { useDispatch } from 'react-redux'; +import Modal from 'Components/Modal/Modal'; +import { clearPendingChanges } from 'Store/Actions/baseActions'; +import { + cancelSaveImportList, + cancelTestImportList, +} from 'Store/Actions/settingsActions'; +import EditImportListModalContent, { + EditImportListModalContentProps, +} from './EditImportListModalContent'; + +const section = 'settings.importLists'; + +interface EditImportListModalProps extends EditImportListModalContentProps { + isOpen: boolean; +} + +function EditImportListModal({ + isOpen, + onModalClose, + ...otherProps +}: EditImportListModalProps) { + const dispatch = useDispatch(); + + const handleModalClose = useCallback(() => { + dispatch(clearPendingChanges({ section })); + dispatch(cancelTestImportList({ section })); + dispatch(cancelSaveImportList({ section })); + + onModalClose(); + }, [dispatch, onModalClose]); + + return ( + + + + ); +} + +export default EditImportListModal; diff --git a/frontend/src/Settings/ImportLists/ImportLists/EditImportListModalConnector.js b/frontend/src/Settings/ImportLists/ImportLists/EditImportListModalConnector.js deleted file mode 100644 index c24d26b67..000000000 --- a/frontend/src/Settings/ImportLists/ImportLists/EditImportListModalConnector.js +++ /dev/null @@ -1,65 +0,0 @@ -import PropTypes from 'prop-types'; -import React, { Component } from 'react'; -import { connect } from 'react-redux'; -import { clearPendingChanges } from 'Store/Actions/baseActions'; -import { cancelSaveImportList, cancelTestImportList } from 'Store/Actions/settingsActions'; -import EditImportListModal from './EditImportListModal'; - -function createMapDispatchToProps(dispatch, props) { - const section = 'settings.importLists'; - - return { - dispatchClearPendingChanges() { - dispatch(clearPendingChanges({ section })); - }, - - dispatchCancelTestImportList() { - dispatch(cancelTestImportList({ section })); - }, - - dispatchCancelSaveImportList() { - dispatch(cancelSaveImportList({ section })); - } - }; -} - -class EditImportListModalConnector extends Component { - - // - // Listeners - - onModalClose = () => { - this.props.dispatchClearPendingChanges(); - this.props.dispatchCancelTestImportList(); - this.props.dispatchCancelSaveImportList(); - this.props.onModalClose(); - }; - - // - // Render - - render() { - const { - dispatchClearPendingChanges, - dispatchCancelTestImportList, - dispatchCancelSaveImportList, - ...otherProps - } = this.props; - - return ( - - ); - } -} - -EditImportListModalConnector.propTypes = { - onModalClose: PropTypes.func.isRequired, - dispatchClearPendingChanges: PropTypes.func.isRequired, - dispatchCancelTestImportList: PropTypes.func.isRequired, - dispatchCancelSaveImportList: PropTypes.func.isRequired -}; - -export default connect(null, createMapDispatchToProps)(EditImportListModalConnector); diff --git a/frontend/src/Settings/ImportLists/ImportLists/EditImportListModalContent.js b/frontend/src/Settings/ImportLists/ImportLists/EditImportListModalContent.js deleted file mode 100644 index 1413fc60f..000000000 --- a/frontend/src/Settings/ImportLists/ImportLists/EditImportListModalContent.js +++ /dev/null @@ -1,339 +0,0 @@ -import PropTypes from 'prop-types'; -import React from 'react'; -import SeriesMonitoringOptionsPopoverContent from 'AddSeries/SeriesMonitoringOptionsPopoverContent'; -import SeriesMonitorNewItemsOptionsPopoverContent from 'AddSeries/SeriesMonitorNewItemsOptionsPopoverContent'; -import SeriesTypePopoverContent from 'AddSeries/SeriesTypePopoverContent'; -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 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 Popover from 'Components/Tooltip/Popover'; -import { icons, inputTypes, kinds, tooltipPositions } from 'Helpers/Props'; -import AdvancedSettingsButton from 'Settings/AdvancedSettingsButton'; -import formatShortTimeSpan from 'Utilities/Date/formatShortTimeSpan'; -import translate from 'Utilities/String/translate'; -import styles from './EditImportListModalContent.css'; - -function EditImportListModalContent(props) { - - const { - advancedSettings, - isFetching, - error, - isSaving, - isTesting, - saveError, - item, - onInputChange, - onFieldChange, - onModalClose, - onSavePress, - onTestPress, - onDeleteImportListPress, - ...otherProps - } = props; - - const { - id, - implementationName, - name, - enableAutomaticAdd, - searchForMissingEpisodes, - minRefreshInterval, - shouldMonitor, - rootFolderPath, - monitorNewItems, - qualityProfileId, - seriesType, - seasonFolder, - tags, - fields - } = item; - - return ( - - - {id ? translate('EditImportListImplementation', { implementationName }) : translate('AddImportListImplementation', { implementationName })} - - - - { - isFetching ? - : - null - } - - { - !isFetching && !!error ? - - {translate('AddListError')} - : - null - } - - { - !isFetching && !error ? -
- - - {translate('ListWillRefreshEveryInterval', { - refreshInterval: formatShortTimeSpan(minRefreshInterval.value) - })} - - - - {translate('Name')} - - - - - - {translate('EnableAutomaticAdd')} - - - - - - {translate('ImportListSearchForMissingEpisodes')} - - - - - - - {translate('Monitor')} - - - } - title={translate('MonitoringOptions')} - body={} - position={tooltipPositions.RIGHT} - /> - - - - - - - - {translate('MonitorNewSeasons')} - - } - title={translate('MonitorNewSeasons')} - body={} - position={tooltipPositions.RIGHT} - /> - - - - - - - {translate('RootFolder')} - - - - - - {translate('QualityProfile')} - - - - - - - {translate('SeriesType')} - - - } - title={translate('SeriesTypes')} - body={} - position={tooltipPositions.RIGHT} - /> - - - - - - - {translate('SeasonFolder')} - - - - - - {translate('SonarrTags')} - - - - - { - !!fields && !!fields.length && -
- { - fields.map((field) => { - return ( - - ); - }) - } -
- } - -
: - null - } -
- - { - id && - - } - - - - - {translate('Test')} - - - - - - {translate('Save')} - - -
- ); -} - -EditImportListModalContent.propTypes = { - advancedSettings: PropTypes.bool.isRequired, - isFetching: PropTypes.bool.isRequired, - error: PropTypes.object, - isSaving: PropTypes.bool.isRequired, - isTesting: PropTypes.bool.isRequired, - saveError: PropTypes.object, - item: PropTypes.object.isRequired, - onInputChange: PropTypes.func.isRequired, - onFieldChange: PropTypes.func.isRequired, - onModalClose: PropTypes.func.isRequired, - onSavePress: PropTypes.func.isRequired, - onTestPress: PropTypes.func.isRequired, - onDeleteImportListPress: PropTypes.func -}; - -export default EditImportListModalContent; diff --git a/frontend/src/Settings/ImportLists/ImportLists/EditImportListModalContent.tsx b/frontend/src/Settings/ImportLists/ImportLists/EditImportListModalContent.tsx new file mode 100644 index 000000000..d7e785938 --- /dev/null +++ b/frontend/src/Settings/ImportLists/ImportLists/EditImportListModalContent.tsx @@ -0,0 +1,350 @@ +import React, { useCallback, useEffect } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import SeriesMonitoringOptionsPopoverContent from 'AddSeries/SeriesMonitoringOptionsPopoverContent'; +import SeriesMonitorNewItemsOptionsPopoverContent from 'AddSeries/SeriesMonitorNewItemsOptionsPopoverContent'; +import SeriesTypePopoverContent from 'AddSeries/SeriesTypePopoverContent'; +import { ImportListAppState } 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 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 Popover from 'Components/Tooltip/Popover'; +import usePrevious from 'Helpers/Hooks/usePrevious'; +import useShowAdvancedSettings from 'Helpers/Hooks/useShowAdvancedSettings'; +import { icons, inputTypes, kinds, tooltipPositions } from 'Helpers/Props'; +import AdvancedSettingsButton from 'Settings/AdvancedSettingsButton'; +import { + saveImportList, + setImportListFieldValue, + setImportListValue, + testImportList, +} from 'Store/Actions/settingsActions'; +import { createProviderSettingsSelectorHook } from 'Store/Selectors/createProviderSettingsSelector'; +import ImportList from 'typings/ImportList'; +import { InputChanged } from 'typings/inputs'; +import formatShortTimeSpan from 'Utilities/Date/formatShortTimeSpan'; +import translate from 'Utilities/String/translate'; +import styles from './EditImportListModalContent.css'; + +export interface EditImportListModalContentProps { + id?: number; + onModalClose: () => void; + onDeleteImportListPress?: () => void; +} + +function EditImportListModalContent({ + id, + onModalClose, + onDeleteImportListPress, +}: EditImportListModalContentProps) { + const dispatch = useDispatch(); + const showAdvancedSettings = useShowAdvancedSettings(); + + const { + isFetching, + isSaving, + isTesting = false, + error, + saveError, + item, + validationErrors, + validationWarnings, + } = useSelector( + createProviderSettingsSelectorHook( + 'importLists', + id + ) + ); + + const wasSaving = usePrevious(isSaving); + + const { + implementationName, + name, + enableAutomaticAdd, + searchForMissingEpisodes, + minRefreshInterval, + shouldMonitor, + rootFolderPath, + monitorNewItems, + qualityProfileId, + seriesType, + seasonFolder, + tags, + fields, + } = item; + + const handleInputChange = useCallback( + (change: InputChanged) => { + // @ts-expect-error - actions are not typed + dispatch(setImportListValue(change)); + }, + [dispatch] + ); + + const handleFieldChange = useCallback( + (change: InputChanged) => { + // @ts-expect-error - actions are not typed + dispatch(setImportListFieldValue(change)); + }, + [dispatch] + ); + + const handleTestPress = useCallback(() => { + dispatch(testImportList({ id })); + }, [id, dispatch]); + + const handleSavePress = useCallback(() => { + dispatch(saveImportList({ id })); + }, [id, dispatch]); + + useEffect(() => { + if (wasSaving && !isSaving && !saveError) { + onModalClose(); + } + }, [isSaving, wasSaving, saveError, onModalClose]); + + return ( + + + {id + ? translate('EditImportListImplementation', { implementationName }) + : translate('AddImportListImplementation', { implementationName })} + + + + {isFetching ? : null} + + {!isFetching && !!error ? ( + {translate('AddListError')} + ) : null} + + {!isFetching && !error ? ( +
+ + {translate('ListWillRefreshEveryInterval', { + refreshInterval: formatShortTimeSpan(minRefreshInterval.value), + })} + + + + {translate('Name')} + + + + + + {translate('EnableAutomaticAdd')} + + + + + + + {translate('ImportListSearchForMissingEpisodes')} + + + + + + + + {translate('Monitor')} + + + } + title={translate('MonitoringOptions')} + body={} + position={tooltipPositions.RIGHT} + /> + + + + + + + + {translate('MonitorNewSeasons')} + + } + title={translate('MonitorNewSeasons')} + body={} + position={tooltipPositions.RIGHT} + /> + + + + + + + {translate('RootFolder')} + + + + + + {translate('QualityProfile')} + + + + + + + {translate('SeriesType')} + + + } + title={translate('SeriesTypes')} + body={} + position={tooltipPositions.RIGHT} + /> + + + + + + + {translate('SeasonFolder')} + + + + + + {translate('SonarrTags')} + + + + + {fields?.length ? ( +
+ {fields.map((field) => { + return ( + + ); + })} +
+ ) : null} +
+ ) : null} +
+ + {id && ( + + )} + + + + + {translate('Test')} + + + + + + {translate('Save')} + + +
+ ); +} + +export default EditImportListModalContent; diff --git a/frontend/src/Settings/ImportLists/ImportLists/EditImportListModalContentConnector.js b/frontend/src/Settings/ImportLists/ImportLists/EditImportListModalContentConnector.js deleted file mode 100644 index 7ffc89e48..000000000 --- a/frontend/src/Settings/ImportLists/ImportLists/EditImportListModalContentConnector.js +++ /dev/null @@ -1,93 +0,0 @@ -import PropTypes from 'prop-types'; -import React, { Component } from 'react'; -import { connect } from 'react-redux'; -import { createSelector } from 'reselect'; -import { - saveImportList, - setImportListFieldValue, - setImportListValue, - testImportList -} from 'Store/Actions/settingsActions'; -import createProviderSettingsSelector from 'Store/Selectors/createProviderSettingsSelector'; -import EditImportListModalContent from './EditImportListModalContent'; - -function createMapStateToProps() { - return createSelector( - (state) => state.settings.advancedSettings, - createProviderSettingsSelector('importLists'), - (advancedSettings, importList) => { - return { - advancedSettings, - ...importList - }; - } - ); -} - -const mapDispatchToProps = { - setImportListValue, - setImportListFieldValue, - saveImportList, - testImportList -}; - -class EditImportListModalContentConnector extends Component { - - // - // Lifecycle - - componentDidUpdate(prevProps, prevState) { - if (prevProps.isSaving && !this.props.isSaving && !this.props.saveError) { - this.props.onModalClose(); - } - } - - // - // Listeners - - onInputChange = ({ name, value }) => { - this.props.setImportListValue({ name, value }); - }; - - onFieldChange = ({ name, value }) => { - this.props.setImportListFieldValue({ name, value }); - }; - - onSavePress = () => { - this.props.saveImportList({ id: this.props.id }); - }; - - onTestPress = () => { - this.props.testImportList({ id: this.props.id }); - }; - - // - // Render - - render() { - return ( - - ); - } -} - -EditImportListModalContentConnector.propTypes = { - id: PropTypes.number, - isFetching: PropTypes.bool.isRequired, - isSaving: PropTypes.bool.isRequired, - saveError: PropTypes.object, - item: PropTypes.object.isRequired, - setImportListValue: PropTypes.func.isRequired, - setImportListFieldValue: PropTypes.func.isRequired, - saveImportList: PropTypes.func.isRequired, - testImportList: PropTypes.func.isRequired, - onModalClose: PropTypes.func.isRequired -}; - -export default connect(createMapStateToProps, mapDispatchToProps)(EditImportListModalContentConnector); diff --git a/frontend/src/Settings/ImportLists/ImportLists/ImportList.js b/frontend/src/Settings/ImportLists/ImportLists/ImportList.js deleted file mode 100644 index ba9f0761b..000000000 --- a/frontend/src/Settings/ImportLists/ImportLists/ImportList.js +++ /dev/null @@ -1,128 +0,0 @@ -import PropTypes from 'prop-types'; -import React, { Component } from 'react'; -import Card from 'Components/Card'; -import Label from 'Components/Label'; -import ConfirmModal from 'Components/Modal/ConfirmModal'; -import TagList from 'Components/TagList'; -import { kinds } from 'Helpers/Props'; -import formatShortTimeSpan from 'Utilities/Date/formatShortTimeSpan'; -import translate from 'Utilities/String/translate'; -import EditImportListModalConnector from './EditImportListModalConnector'; -import styles from './ImportList.css'; - -class ImportList extends Component { - - // - // Lifecycle - - constructor(props, context) { - super(props, context); - - this.state = { - isEditImportListModalOpen: false, - isDeleteImportListModalOpen: false - }; - } - - // - // Listeners - - onEditImportListPress = () => { - this.setState({ isEditImportListModalOpen: true }); - }; - - onEditImportListModalClose = () => { - this.setState({ isEditImportListModalOpen: false }); - }; - - onDeleteImportListPress = () => { - this.setState({ - isEditImportListModalOpen: false, - isDeleteImportListModalOpen: true - }); - }; - - onDeleteImportListModalClose = () => { - this.setState({ isDeleteImportListModalOpen: false }); - }; - - onConfirmDeleteImportList = () => { - this.props.onConfirmDeleteImportList(this.props.id); - }; - - // - // Render - - render() { - const { - id, - name, - enableAutomaticAdd, - tags, - tagList, - minRefreshInterval - } = this.props; - - return ( - -
- {name} -
- -
- { - enableAutomaticAdd ? - : - null - } -
- - - -
- -
- - - - -
- ); - } -} - -ImportList.propTypes = { - id: PropTypes.number.isRequired, - name: PropTypes.string.isRequired, - enableAutomaticAdd: PropTypes.bool.isRequired, - tags: PropTypes.arrayOf(PropTypes.number).isRequired, - tagList: PropTypes.arrayOf(PropTypes.object).isRequired, - minRefreshInterval: PropTypes.string.isRequired, - onConfirmDeleteImportList: PropTypes.func.isRequired -}; - -export default ImportList; diff --git a/frontend/src/Settings/ImportLists/ImportLists/ImportList.tsx b/frontend/src/Settings/ImportLists/ImportLists/ImportList.tsx new file mode 100644 index 000000000..debebdde0 --- /dev/null +++ b/frontend/src/Settings/ImportLists/ImportLists/ImportList.tsx @@ -0,0 +1,104 @@ +import React, { useCallback, useState } from 'react'; +import { useDispatch } from 'react-redux'; +import Card from 'Components/Card'; +import Label from 'Components/Label'; +import ConfirmModal from 'Components/Modal/ConfirmModal'; +import TagList from 'Components/TagList'; +import { kinds } from 'Helpers/Props'; +import { deleteImportList } from 'Store/Actions/settingsActions'; +import useTags from 'Tags/useTags'; +import formatShortTimeSpan from 'Utilities/Date/formatShortTimeSpan'; +import translate from 'Utilities/String/translate'; +import EditImportListModal from './EditImportListModal'; +import styles from './ImportList.css'; + +interface ImportListProps { + id: number; + name: string; + enableAutomaticAdd: boolean; + tags: number[]; + minRefreshInterval: string; +} + +function ImportList({ + id, + name, + enableAutomaticAdd, + tags, + minRefreshInterval, +}: ImportListProps) { + const dispatch = useDispatch(); + const tagList = useTags(); + + const [isEditImportListModalOpen, setIsEditImportListModalOpen] = + useState(false); + + const [isDeleteImportListModalOpen, setIsDeleteImportListModalOpen] = + useState(false); + + const handleEditImportListPress = useCallback(() => { + setIsEditImportListModalOpen(true); + }, []); + + const handleEditImportListModalClose = useCallback(() => { + setIsEditImportListModalOpen(false); + }, []); + + const handleDeleteImportListPress = useCallback(() => { + setIsEditImportListModalOpen(false); + setIsDeleteImportListModalOpen(true); + }, []); + + const handleDeleteImportListModalClose = useCallback(() => { + setIsDeleteImportListModalOpen(false); + }, []); + + const handleConfirmDeleteImportList = useCallback(() => { + dispatch(deleteImportList({ id })); + }, [id, dispatch]); + + return ( + +
{name}
+ +
+ {enableAutomaticAdd ? ( + + ) : null} +
+ + + +
+ +
+ + + + +
+ ); +} + +export default ImportList; diff --git a/frontend/src/Settings/ImportLists/ImportLists/ImportLists.js b/frontend/src/Settings/ImportLists/ImportLists/ImportLists.js deleted file mode 100644 index b6f6e5837..000000000 --- a/frontend/src/Settings/ImportLists/ImportLists/ImportLists.js +++ /dev/null @@ -1,118 +0,0 @@ -import PropTypes from 'prop-types'; -import React, { Component } from 'react'; -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 translate from 'Utilities/String/translate'; -import AddImportListModal from './AddImportListModal'; -import EditImportListModalConnector from './EditImportListModalConnector'; -import ImportList from './ImportList'; -import styles from './ImportLists.css'; - -class ImportLists extends Component { - - // - // Lifecycle - - constructor(props, context) { - super(props, context); - - this.state = { - isAddImportListModalOpen: false, - isEditImportListModalOpen: false - }; - } - - // - // Listeners - - onAddImportListPress = () => { - this.setState({ isAddImportListModalOpen: true }); - }; - - onAddImportListModalClose = ({ listSelected = false } = {}) => { - this.setState({ - isAddImportListModalOpen: false, - isEditImportListModalOpen: listSelected - }); - }; - - onEditImportListModalClose = () => { - this.setState({ isEditImportListModalOpen: false }); - }; - - // - // Render - - render() { - const { - items, - tagList, - onConfirmDeleteImportList, - ...otherProps - } = this.props; - - const { - isAddImportListModalOpen, - isEditImportListModalOpen - } = this.state; - - return ( -
- -
- { - items.map((item) => { - return ( - - ); - }) - } - - -
- -
-
-
- - - - -
-
- ); - } -} - -ImportLists.propTypes = { - isFetching: PropTypes.bool.isRequired, - error: PropTypes.object, - items: PropTypes.arrayOf(PropTypes.object).isRequired, - tagList: PropTypes.arrayOf(PropTypes.object).isRequired, - onConfirmDeleteImportList: PropTypes.func.isRequired -}; - -export default ImportLists; diff --git a/frontend/src/Settings/ImportLists/ImportLists/ImportLists.tsx b/frontend/src/Settings/ImportLists/ImportLists/ImportLists.tsx new file mode 100644 index 000000000..b43691ca1 --- /dev/null +++ b/frontend/src/Settings/ImportLists/ImportLists/ImportLists.tsx @@ -0,0 +1,92 @@ +import React, { useCallback, useEffect, useState } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import { ImportListAppState } 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 { fetchImportLists } from 'Store/Actions/settingsActions'; +import createSortedSectionSelector from 'Store/Selectors/createSortedSectionSelector'; +import ImportListModel from 'typings/ImportList'; +import sortByProp from 'Utilities/Array/sortByProp'; +import translate from 'Utilities/String/translate'; +import AddImportListModal from './AddImportListModal'; +import EditImportListModal from './EditImportListModal'; +import ImportList from './ImportList'; +import styles from './ImportLists.css'; + +function ImportLists() { + const dispatch = useDispatch(); + + const { isFetching, isPopulated, items, error } = useSelector( + createSortedSectionSelector( + 'settings.importLists', + sortByProp('name') + ) + ); + + const [isAddImportListModalOpen, setIsAddImportListModalOpen] = + useState(false); + const [isEditImportListModalOpen, setIsEditImportListModalOpen] = + useState(false); + + const handleAddImportListPress = useCallback(() => { + setIsAddImportListModalOpen(true); + }, []); + + const handleAddImportListModalClose = useCallback(() => { + setIsAddImportListModalOpen(false); + }, []); + + const handleImportListSelect = useCallback(() => { + setIsAddImportListModalOpen(false); + setIsEditImportListModalOpen(true); + }, []); + + const handleEditImportListModalClose = useCallback(() => { + setIsEditImportListModalOpen(false); + }, []); + + useEffect(() => { + dispatch(fetchImportLists()); + dispatch(fetchRootFolders()); + }, [dispatch]); + + return ( +
+ +
+ {items.map((item) => { + return ; + })} + + +
+ +
+
+
+ + + + +
+
+ ); +} + +export default ImportLists; diff --git a/frontend/src/Settings/ImportLists/ImportLists/ImportListsConnector.js b/frontend/src/Settings/ImportLists/ImportLists/ImportListsConnector.js deleted file mode 100644 index 633d4f2f7..000000000 --- a/frontend/src/Settings/ImportLists/ImportLists/ImportListsConnector.js +++ /dev/null @@ -1,67 +0,0 @@ -import PropTypes from 'prop-types'; -import React, { Component } from 'react'; -import { connect } from 'react-redux'; -import { createSelector } from 'reselect'; -import { fetchRootFolders } from 'Store/Actions/rootFolderActions'; -import { deleteImportList, fetchImportLists } from 'Store/Actions/settingsActions'; -import createSortedSectionSelector from 'Store/Selectors/createSortedSectionSelector'; -import createTagsSelector from 'Store/Selectors/createTagsSelector'; -import sortByProp from 'Utilities/Array/sortByProp'; -import ImportLists from './ImportLists'; - -function createMapStateToProps() { - return createSelector( - createSortedSectionSelector('settings.importLists', sortByProp('name')), - createTagsSelector(), - (importLists, tagList) => { - return { - ...importLists, - tagList - }; - } - ); -} - -const mapDispatchToProps = { - fetchImportLists, - deleteImportList, - fetchRootFolders -}; - -class ImportListsConnector extends Component { - - // - // Lifecycle - - componentDidMount() { - this.props.fetchImportLists(); - this.props.fetchRootFolders(); - } - - // - // Listeners - - onConfirmDeleteImportList = (id) => { - this.props.deleteImportList({ id }); - }; - - // - // Render - - render() { - return ( - - ); - } -} - -ImportListsConnector.propTypes = { - fetchImportLists: PropTypes.func.isRequired, - deleteImportList: PropTypes.func.isRequired, - fetchRootFolders: PropTypes.func.isRequired -}; - -export default connect(createMapStateToProps, mapDispatchToProps)(ImportListsConnector); diff --git a/frontend/src/Settings/ImportLists/Options/ImportListOptions.tsx b/frontend/src/Settings/ImportLists/Options/ImportListOptions.tsx index 365f90f58..bfac93f83 100644 --- a/frontend/src/Settings/ImportLists/Options/ImportListOptions.tsx +++ b/frontend/src/Settings/ImportLists/Options/ImportListOptions.tsx @@ -1,7 +1,5 @@ import React, { useCallback, useEffect } from 'react'; import { useDispatch, useSelector } from 'react-redux'; -import { createSelector } from 'reselect'; -import AppState from 'App/State/AppState'; import Alert from 'Components/Alert'; import FieldSet from 'Components/FieldSet'; import Form from 'Components/Form/Form'; @@ -9,6 +7,7 @@ import FormGroup from 'Components/Form/FormGroup'; import FormInputGroup from 'Components/Form/FormInputGroup'; import FormLabel from 'Components/Form/FormLabel'; import LoadingIndicator from 'Components/Loading/LoadingIndicator'; +import useShowAdvancedSettings from 'Helpers/Hooks/useShowAdvancedSettings'; import { inputTypes, kinds } from 'Helpers/Props'; import { clearPendingChanges } from 'Store/Actions/baseActions'; import { @@ -27,42 +26,29 @@ const cleanLibraryLevelOptions = [ { key: 'keepAndTag', value: () => translate('KeepAndTagSeries') }, ]; -function createImportListOptionsSelector() { - return createSelector( - (state: AppState) => state.settings.advancedSettings, - createSettingsSectionSelector(SECTION), - (advancedSettings, sectionSettings) => { - return { - advancedSettings, - save: sectionSettings.isSaving, - ...sectionSettings, - }; - } - ); -} - -interface ImportListOptionsPageProps { +interface ImportListOptionsProps { setChildSave(saveCallback: () => void): void; onChildStateChange(payload: unknown): void; } -function ImportListOptions(props: ImportListOptionsPageProps) { - const { setChildSave, onChildStateChange } = props; +function ImportListOptions({ + setChildSave, + onChildStateChange, +}: ImportListOptionsProps) { + const dispatch = useDispatch(); + const showAdvancedSettings = useShowAdvancedSettings(); const { isSaving, hasPendingChanges, - advancedSettings, isFetching, error, settings, hasSettings, - } = useSelector(createImportListOptionsSelector()); + } = useSelector(createSettingsSectionSelector(SECTION)); const { listSyncLevel, listSyncTag } = settings; - const dispatch = useDispatch(); - const onInputChange = useCallback( ({ name, value }: { name: string; value: unknown }) => { // @ts-expect-error 'setImportListOptionsValue' isn't typed yet @@ -85,7 +71,7 @@ function ImportListOptions(props: ImportListOptionsPageProps) { setChildSave(() => dispatch(saveImportListOptions())); return () => { - dispatch(clearPendingChanges({ section: SECTION })); + dispatch(clearPendingChanges({ section: `settings.${SECTION}` })); }; }, [dispatch, setChildSave]); @@ -96,16 +82,11 @@ function ImportListOptions(props: ImportListOptionsPageProps) { }); }, [onChildStateChange, isSaving, hasPendingChanges]); - const translatedLevelOptions = cleanLibraryLevelOptions.map( - ({ key, value }) => { - return { - key, - value: value(), - }; - } - ); + if (!showAdvancedSettings) { + return null; + } - return advancedSettings ? ( + return (
{isFetching ? : null} @@ -115,19 +96,22 @@ function ImportListOptions(props: ImportListOptionsPageProps) { {hasSettings && !isFetching && !error ? (
- + {translate('CleanLibraryLevel')} {listSyncLevel.value === 'keepAndTag' ? ( - + {translate('ListSyncTag')} ) : null}
- ) : null; + ); } export default ImportListOptions; diff --git a/frontend/src/Tags/useTags.ts b/frontend/src/Tags/useTags.ts new file mode 100644 index 000000000..5f2d58d34 --- /dev/null +++ b/frontend/src/Tags/useTags.ts @@ -0,0 +1,8 @@ +import { useSelector } from 'react-redux'; +import createTagsSelector from 'Store/Selectors/createTagsSelector'; + +const useTags = () => { + return useSelector(createTagsSelector()); +}; + +export default useTags; diff --git a/frontend/src/typings/ImportList.ts b/frontend/src/typings/ImportList.ts index 7e596b25d..fe8ae65f5 100644 --- a/frontend/src/typings/ImportList.ts +++ b/frontend/src/typings/ImportList.ts @@ -1,10 +1,20 @@ +import { MonitorNewItems, SeriesMonitor, SeriesType } from 'Series/Series'; import Provider from './Provider'; interface ImportList extends Provider { enable: boolean; enableAutomaticAdd: boolean; + searchForMissingEpisodes: boolean; qualityProfileId: number; rootFolderPath: string; + shouldMonitor: SeriesMonitor; + monitorNewItems: MonitorNewItems; + seriesType: SeriesType; + seasonFolder: boolean; + listType: string; + listOrder: number; + minRefreshInterval: string; + name: string; tags: number[]; }