diff --git a/frontend/src/Collection/Collection.js b/frontend/src/Collection/Collection.js index c617d46676..25880733de 100644 --- a/frontend/src/Collection/Collection.js +++ b/frontend/src/Collection/Collection.js @@ -20,7 +20,7 @@ import toggleSelected from 'Utilities/Table/toggleSelected'; import CollectionFooter from './CollectionFooter'; import CollectionFilterMenu from './Menus/CollectionFilterMenu'; import CollectionSortMenu from './Menus/CollectionSortMenu'; -import NoCollection from './NoCollection'; +import NoCollections from './NoCollections'; import CollectionOverviewsConnector from './Overview/CollectionOverviewsConnector'; import CollectionOverviewOptionsModal from './Overview/Options/CollectionOverviewOptionsModal'; @@ -341,7 +341,7 @@ class Collection extends Component { { !error && isPopulated && !items.length && - + } diff --git a/frontend/src/Collection/CollectionFooter.js b/frontend/src/Collection/CollectionFooter.js deleted file mode 100644 index 89e9cff054..0000000000 --- a/frontend/src/Collection/CollectionFooter.js +++ /dev/null @@ -1,300 +0,0 @@ -import _ from 'lodash'; -import PropTypes from 'prop-types'; -import React, { Component } from 'react'; -import FormInputGroup from 'Components/Form/FormInputGroup'; -import SpinnerButton from 'Components/Link/SpinnerButton'; -import PageContentFooter from 'Components/Page/PageContentFooter'; -import { inputTypes, kinds } from 'Helpers/Props'; -import translate from 'Utilities/String/translate'; -import CollectionFooterLabel from './CollectionFooterLabel'; -import styles from './CollectionFooter.css'; - -const NO_CHANGE = 'noChange'; - -const monitoredOptions = [ - { - key: NO_CHANGE, - get value() { - return translate('NoChange'); - } - }, - { - key: 'monitored', - get value() { - return translate('Monitored'); - } - }, - { - key: 'unmonitored', - get value() { - return translate('Unmonitored'); - } - } -]; - -const searchOnAddOptions = [ - { - key: NO_CHANGE, - get value() { - return translate('NoChange'); - } - }, - { - key: 'yes', - get value() { - return translate('Yes'); - } - }, - { - key: 'no', - get value() { - return translate('No'); - } - } -]; - -class CollectionFooter extends Component { - - // - // Lifecycle - - constructor(props, context) { - super(props, context); - - this.state = { - monitored: NO_CHANGE, - monitor: NO_CHANGE, - qualityProfileId: NO_CHANGE, - minimumAvailability: NO_CHANGE, - rootFolderPath: NO_CHANGE, - searchOnAdd: NO_CHANGE - }; - } - - componentDidUpdate(prevProps) { - const { - isSaving, - saveError - } = this.props; - - const newState = {}; - if (prevProps.isSaving && !isSaving && !saveError) { - this.setState({ - monitored: NO_CHANGE, - monitor: NO_CHANGE, - qualityProfileId: NO_CHANGE, - minimumAvailability: NO_CHANGE, - rootFolderPath: NO_CHANGE, - searchOnAdd: NO_CHANGE - }); - } - - if (!_.isEmpty(newState)) { - this.setState(newState); - } - } - - // - // Listeners - - onInputChange = ({ name, value }) => { - this.setState({ [name]: value }); - }; - - onUpdateSelectedPress = () => { - const { - monitored, - monitor, - qualityProfileId, - minimumAvailability, - rootFolderPath, - searchOnAdd - } = this.state; - - const changes = {}; - - if (monitored !== NO_CHANGE) { - changes.monitored = monitored === 'monitored'; - } - - if (monitor !== NO_CHANGE) { - changes.monitor = monitor; - } - - if (qualityProfileId !== NO_CHANGE) { - changes.qualityProfileId = qualityProfileId; - } - - if (minimumAvailability !== NO_CHANGE) { - changes.minimumAvailability = minimumAvailability; - } - - if (rootFolderPath !== NO_CHANGE) { - changes.rootFolderPath = rootFolderPath; - } - - if (searchOnAdd !== NO_CHANGE) { - changes.searchOnAdd = searchOnAdd === 'yes'; - } - - this.props.onUpdateSelectedPress(changes); - }; - - // - // Render - - render() { - const { - selectedIds, - isSaving - } = this.props; - - const { - monitored, - monitor, - qualityProfileId, - minimumAvailability, - rootFolderPath, - searchOnAdd - } = this.state; - - const selectedCount = selectedIds.length; - - return ( - -
- - - -
- -
- - - -
- -
- - - -
- -
- - - -
- -
- - - -
- -
- - - -
- -
-
- - -
-
- - {translate('UpdateSelected')} - -
-
-
-
-
- ); - } -} - -CollectionFooter.propTypes = { - selectedIds: PropTypes.arrayOf(PropTypes.number).isRequired, - isAdding: PropTypes.bool.isRequired, - isSaving: PropTypes.bool.isRequired, - saveError: PropTypes.object, - onUpdateSelectedPress: PropTypes.func.isRequired -}; - -export default CollectionFooter; diff --git a/frontend/src/Collection/CollectionFooter.tsx b/frontend/src/Collection/CollectionFooter.tsx new file mode 100644 index 0000000000..ad3c86ac35 --- /dev/null +++ b/frontend/src/Collection/CollectionFooter.tsx @@ -0,0 +1,317 @@ +import React, { useCallback, useEffect, useState } from 'react'; +import { Error } from 'App/State/AppSectionState'; +import FormInputGroup from 'Components/Form/FormInputGroup'; +import { EnhancedSelectInputValue } from 'Components/Form/Select/EnhancedSelectInput'; +import SpinnerButton from 'Components/Link/SpinnerButton'; +import PageContentFooter from 'Components/Page/PageContentFooter'; +import usePrevious from 'Helpers/Hooks/usePrevious'; +import { inputTypes, kinds } from 'Helpers/Props'; +import { InputChanged } from 'typings/inputs'; +import translate from 'Utilities/String/translate'; +import CollectionFooterLabel from './CollectionFooterLabel'; +import styles from './CollectionFooter.css'; + +interface SavePayload { + monitored?: boolean; + monitor?: string; + qualityProfileId?: number; + minimumAvailability?: string; + rootFolderPath?: string; + searchOnAdd?: boolean; +} + +interface CollectionFooterProps { + selectedIds: number[]; + isAdding: boolean; + isSaving: boolean; + saveError: Error; + onUpdateSelectedPress(payload: object): void; +} + +const NO_CHANGE = 'noChange'; + +const monitoredOptions: EnhancedSelectInputValue[] = [ + { + key: NO_CHANGE, + get value() { + return translate('NoChange'); + }, + }, + { + key: 'monitored', + get value() { + return translate('Monitored'); + }, + }, + { + key: 'unmonitored', + get value() { + return translate('Unmonitored'); + }, + }, +]; + +const searchOnAddOptions: EnhancedSelectInputValue[] = [ + { + key: NO_CHANGE, + get value() { + return translate('NoChange'); + }, + }, + { + key: 'yes', + get value() { + return translate('Yes'); + }, + }, + { + key: 'no', + get value() { + return translate('No'); + }, + }, +]; + +function CollectionFooter({ + selectedIds, + isSaving, + saveError, + onUpdateSelectedPress, +}: CollectionFooterProps) { + const [monitored, setMonitored] = useState(NO_CHANGE); + const [monitor, setMonitor] = useState(NO_CHANGE); + const [qualityProfileId, setQualityProfileId] = useState( + NO_CHANGE + ); + const [minimumAvailability, setMinimumAvailability] = useState(NO_CHANGE); + const [rootFolderPath, setRootFolderPath] = useState(NO_CHANGE); + const [searchOnAdd, setSearchOnAdd] = useState(NO_CHANGE); + + const wasSaving = usePrevious(isSaving); + + const handleSavePress = useCallback(() => { + let hasChanges = false; + const payload: SavePayload = {}; + + if (monitored !== NO_CHANGE) { + hasChanges = true; + payload.monitored = monitored === 'monitored'; + } + + if (monitor !== NO_CHANGE) { + hasChanges = true; + payload.monitor = monitor; + } + + if (qualityProfileId !== NO_CHANGE) { + hasChanges = true; + payload.qualityProfileId = qualityProfileId as number; + } + + if (minimumAvailability !== NO_CHANGE) { + hasChanges = true; + payload.minimumAvailability = minimumAvailability as string; + } + + if (rootFolderPath !== NO_CHANGE) { + hasChanges = true; + payload.rootFolderPath = rootFolderPath; + } + + if (searchOnAdd !== NO_CHANGE) { + hasChanges = true; + payload.searchOnAdd = searchOnAdd === 'yes'; + } + + if (hasChanges) { + onUpdateSelectedPress(payload); + } + }, [ + monitor, + monitored, + qualityProfileId, + minimumAvailability, + rootFolderPath, + searchOnAdd, + onUpdateSelectedPress, + ]); + + const handleInputChange = useCallback(({ name, value }: InputChanged) => { + switch (name) { + case 'monitored': + setMonitored(value as string); + break; + case 'monitor': + setMonitor(value as string); + break; + case 'qualityProfileId': + setQualityProfileId(value as string); + break; + case 'minimumAvailability': + setMinimumAvailability(value as string); + break; + case 'rootFolderPath': + setRootFolderPath(value as string); + break; + case 'searchOnAdd': + setSearchOnAdd(value as string); + break; + default: + console.warn(`CollectionFooter Unknown Input: '${name}'`); + } + }, []); + + useEffect(() => { + if (!isSaving && wasSaving && !saveError) { + setMonitored(NO_CHANGE); + setMonitor(NO_CHANGE); + setQualityProfileId(NO_CHANGE); + setMinimumAvailability(NO_CHANGE); + setRootFolderPath(NO_CHANGE); + setSearchOnAdd(NO_CHANGE); + } + }, [ + isSaving, + wasSaving, + saveError, + setMonitored, + setMonitor, + setQualityProfileId, + setMinimumAvailability, + setRootFolderPath, + setSearchOnAdd, + ]); + + const selectedCount = selectedIds.length; + + return ( + +
+ + + +
+ +
+ + + +
+ +
+ + + +
+ +
+ + + +
+ +
+ + + +
+ +
+ + + +
+ +
+
+ + +
+
+ + {translate('UpdateSelected')} + +
+
+
+
+
+ ); +} + +export default CollectionFooter; diff --git a/frontend/src/Collection/CollectionFooterLabel.js b/frontend/src/Collection/CollectionFooterLabel.js deleted file mode 100644 index 6f8b578df1..0000000000 --- a/frontend/src/Collection/CollectionFooterLabel.js +++ /dev/null @@ -1,40 +0,0 @@ -import PropTypes from 'prop-types'; -import React from 'react'; -import SpinnerIcon from 'Components/SpinnerIcon'; -import { icons } from 'Helpers/Props'; -import styles from './CollectionFooterLabel.css'; - -function CollectionFooterLabel(props) { - const { - className, - label, - isSaving - } = props; - - return ( -
- {label} - - { - isSaving && - - } -
- ); -} - -CollectionFooterLabel.propTypes = { - className: PropTypes.string.isRequired, - label: PropTypes.string.isRequired, - isSaving: PropTypes.bool.isRequired -}; - -CollectionFooterLabel.defaultProps = { - className: styles.label -}; - -export default CollectionFooterLabel; diff --git a/frontend/src/Collection/CollectionFooterLabel.tsx b/frontend/src/Collection/CollectionFooterLabel.tsx new file mode 100644 index 0000000000..97c938fbd8 --- /dev/null +++ b/frontend/src/Collection/CollectionFooterLabel.tsx @@ -0,0 +1,32 @@ +import React from 'react'; +import SpinnerIcon from 'Components/SpinnerIcon'; +import { icons } from 'Helpers/Props'; +import styles from './CollectionFooterLabel.css'; + +interface CollectionFooterLabelProps { + className?: string; + label: string; + isSaving: boolean; +} + +function CollectionFooterLabel({ + className = styles.label, + label, + isSaving, +}: CollectionFooterLabelProps) { + return ( +
+ {label} + + {isSaving ? ( + + ) : null} +
+ ); +} + +export default CollectionFooterLabel; diff --git a/frontend/src/Collection/NoCollection.js b/frontend/src/Collection/NoCollections.tsx similarity index 59% rename from frontend/src/Collection/NoCollection.js rename to frontend/src/Collection/NoCollections.tsx index 1e76fd0142..7e677b9357 100644 --- a/frontend/src/Collection/NoCollection.js +++ b/frontend/src/Collection/NoCollections.tsx @@ -1,13 +1,14 @@ -import PropTypes from 'prop-types'; import React from 'react'; import Button from 'Components/Link/Button'; import { kinds } from 'Helpers/Props'; import translate from 'Utilities/String/translate'; import styles from './NoCollection.css'; -function NoCollection(props) { - const { totalItems } = props; +interface NoCollectionsProps { + totalItems: number; +} +function NoCollections({ totalItems }: NoCollectionsProps) { if (totalItems > 0) { return (
@@ -20,24 +21,16 @@ function NoCollection(props) { return (
-
- {translate('NoCollections')} -
+
{translate('NoCollections')}
-
-
@@ -45,8 +38,4 @@ function NoCollection(props) { ); } -NoCollection.propTypes = { - totalItems: PropTypes.number.isRequired -}; - -export default NoCollection; +export default NoCollections;