diff --git a/frontend/src/App/State/AppState.ts b/frontend/src/App/State/AppState.ts index 4a6951aa3..33638f91f 100644 --- a/frontend/src/App/State/AppState.ts +++ b/frontend/src/App/State/AppState.ts @@ -8,6 +8,7 @@ import InteractiveImportAppState from './InteractiveImportAppState'; import ParseAppState from './ParseAppState'; import PathsAppState from './PathsAppState'; import QueueAppState from './QueueAppState'; +import ReleasesAppState from './ReleasesAppState'; import RootFolderAppState from './RootFolderAppState'; import SeriesAppState, { SeriesIndexAppState } from './SeriesAppState'; import SettingsAppState from './SettingsAppState'; @@ -72,6 +73,7 @@ interface AppState { parse: ParseAppState; paths: PathsAppState; queue: QueueAppState; + releases: ReleasesAppState; rootFolders: RootFolderAppState; series: SeriesAppState; seriesIndex: SeriesIndexAppState; diff --git a/frontend/src/App/State/ReleasesAppState.ts b/frontend/src/App/State/ReleasesAppState.ts new file mode 100644 index 000000000..350f6eac8 --- /dev/null +++ b/frontend/src/App/State/ReleasesAppState.ts @@ -0,0 +1,10 @@ +import AppSectionState, { + AppSectionFilterState, +} from 'App/State/AppSectionState'; +import Release from 'typings/Release'; + +interface ReleasesAppState + extends AppSectionState, + AppSectionFilterState {} + +export default ReleasesAppState; diff --git a/frontend/src/Components/Table/Column.ts b/frontend/src/Components/Table/Column.ts index 24674c3fc..22d22e963 100644 --- a/frontend/src/Components/Table/Column.ts +++ b/frontend/src/Components/Table/Column.ts @@ -1,4 +1,5 @@ import React from 'react'; +import { SortDirection } from 'Helpers/Props/sortDirections'; type PropertyFunction = () => T; @@ -9,6 +10,7 @@ interface Column { className?: string; columnLabel?: string; isSortable?: boolean; + fixedSortDirection?: SortDirection; isVisible: boolean; isModifiable?: boolean; } diff --git a/frontend/src/Episode/Search/EpisodeSearchConnector.js b/frontend/src/Episode/Search/EpisodeSearchConnector.js index 36e8e667f..9b41dd9c4 100644 --- a/frontend/src/Episode/Search/EpisodeSearchConnector.js +++ b/frontend/src/Episode/Search/EpisodeSearchConnector.js @@ -3,7 +3,7 @@ import React, { Component } from 'react'; import { connect } from 'react-redux'; import { createSelector } from 'reselect'; import * as commandNames from 'Commands/commandNames'; -import InteractiveSearchConnector from 'InteractiveSearch/InteractiveSearchConnector'; +import InteractiveSearch from 'InteractiveSearch/InteractiveSearch'; import { executeCommand } from 'Store/Actions/commandActions'; import EpisodeSearch from './EpisodeSearch'; @@ -65,7 +65,7 @@ class EpisodeSearchConnector extends Component { if (this.state.isInteractiveSearchOpen) { return ( - diff --git a/frontend/src/InteractiveSearch/InteractiveSearch.js b/frontend/src/InteractiveSearch/InteractiveSearch.js deleted file mode 100644 index bea804902..000000000 --- a/frontend/src/InteractiveSearch/InteractiveSearch.js +++ /dev/null @@ -1,234 +0,0 @@ -import PropTypes from 'prop-types'; -import React, { Fragment } from 'react'; -import Alert from 'Components/Alert'; -import Icon from 'Components/Icon'; -import LoadingIndicator from 'Components/Loading/LoadingIndicator'; -import FilterMenu from 'Components/Menu/FilterMenu'; -import PageMenuButton from 'Components/Menu/PageMenuButton'; -import Table from 'Components/Table/Table'; -import TableBody from 'Components/Table/TableBody'; -import { align, icons, kinds, sortDirections } from 'Helpers/Props'; -import getErrorMessage from 'Utilities/Object/getErrorMessage'; -import translate from 'Utilities/String/translate'; -import InteractiveSearchFilterModalConnector from './InteractiveSearchFilterModalConnector'; -import InteractiveSearchRow from './InteractiveSearchRow'; -import styles from './InteractiveSearch.css'; - -const columns = [ - { - name: 'protocol', - label: () => translate('Source'), - isSortable: true, - isVisible: true - }, - { - name: 'age', - label: () => translate('Age'), - isSortable: true, - isVisible: true - }, - { - name: 'title', - label: () => translate('Title'), - isSortable: true, - isVisible: true - }, - { - name: 'indexer', - label: () => translate('Indexer'), - isSortable: true, - isVisible: true - }, - { - name: 'size', - label: () => translate('Size'), - isSortable: true, - isVisible: true - }, - { - name: 'peers', - label: () => translate('Peers'), - isSortable: true, - isVisible: true - }, - { - name: 'languageWeight', - label: () => translate('Languages'), - isSortable: true, - isVisible: true - }, - { - name: 'qualityWeight', - label: () => translate('Quality'), - isSortable: true, - isVisible: true - }, - { - name: 'customFormatScore', - label: React.createElement(Icon, { - name: icons.SCORE, - title: () => translate('CustomFormatScore') - }), - isSortable: true, - isVisible: true - }, - { - name: 'indexerFlags', - label: React.createElement(Icon, { - name: icons.FLAG, - title: () => translate('IndexerFlags') - }), - isSortable: true, - isVisible: true - }, - { - name: 'rejections', - label: React.createElement(Icon, { - name: icons.DANGER, - title: () => translate('Rejections') - }), - isSortable: true, - fixedSortDirection: sortDirections.ASCENDING, - isVisible: true - }, - { - name: 'releaseWeight', - label: React.createElement(Icon, { name: icons.DOWNLOAD }), - isSortable: true, - fixedSortDirection: sortDirections.ASCENDING, - isVisible: true - } -]; - -function InteractiveSearch(props) { - const { - searchPayload, - isFetching, - isPopulated, - error, - totalReleasesCount, - items, - selectedFilterKey, - filters, - customFilters, - sortKey, - sortDirection, - type, - longDateFormat, - timeFormat, - onSortPress, - onFilterSelect, - onGrabPress - } = props; - - const errorMessage = getErrorMessage(error); - - return ( -
-
- -
- - { - isFetching ? : null - } - - { - !isFetching && error ? -
- { - errorMessage ? - - {translate('InteractiveSearchResultsSeriesFailedErrorMessage', { message: errorMessage.charAt(0).toLowerCase() + errorMessage.slice(1) })} - : - translate('EpisodeSearchResultsLoadError') - } -
: - null - } - - { - !isFetching && isPopulated && !totalReleasesCount ? - - {translate('NoResultsFound')} - : - null - } - - { - !!totalReleasesCount && isPopulated && !items.length ? - - {translate('AllResultsAreHiddenByTheAppliedFilter')} - : - null - } - - { - isPopulated && !!items.length ? - - - { - items.map((item) => { - return ( - - ); - }) - } - -
: - null - } - - { - totalReleasesCount !== items.length && !!items.length ? -
- {translate('SomeResultsAreHiddenByTheAppliedFilter')} -
: - null - } -
- ); -} - -InteractiveSearch.propTypes = { - searchPayload: PropTypes.object.isRequired, - isFetching: PropTypes.bool.isRequired, - isPopulated: PropTypes.bool.isRequired, - error: PropTypes.object, - totalReleasesCount: PropTypes.number.isRequired, - items: PropTypes.arrayOf(PropTypes.object).isRequired, - selectedFilterKey: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired, - filters: PropTypes.arrayOf(PropTypes.object).isRequired, - customFilters: PropTypes.arrayOf(PropTypes.object).isRequired, - sortKey: PropTypes.string, - sortDirection: PropTypes.string, - type: PropTypes.string.isRequired, - longDateFormat: PropTypes.string.isRequired, - timeFormat: PropTypes.string.isRequired, - onSortPress: PropTypes.func.isRequired, - onFilterSelect: PropTypes.func.isRequired, - onGrabPress: PropTypes.func.isRequired -}; - -export default InteractiveSearch; diff --git a/frontend/src/InteractiveSearch/InteractiveSearch.tsx b/frontend/src/InteractiveSearch/InteractiveSearch.tsx new file mode 100644 index 000000000..92fc06dbc --- /dev/null +++ b/frontend/src/InteractiveSearch/InteractiveSearch.tsx @@ -0,0 +1,247 @@ +import React, { useCallback, useEffect } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import ClientSideCollectionAppState from 'App/State/ClientSideCollectionAppState'; +import ReleasesAppState from 'App/State/ReleasesAppState'; +import Alert from 'Components/Alert'; +import Icon from 'Components/Icon'; +import LoadingIndicator from 'Components/Loading/LoadingIndicator'; +import FilterMenu from 'Components/Menu/FilterMenu'; +import PageMenuButton from 'Components/Menu/PageMenuButton'; +import Column from 'Components/Table/Column'; +import Table from 'Components/Table/Table'; +import TableBody from 'Components/Table/TableBody'; +import { align, icons, kinds, sortDirections } from 'Helpers/Props'; +import { SortDirection } from 'Helpers/Props/sortDirections'; +import InteractiveSearchType from 'InteractiveSearch/InteractiveSearchType'; +import { + fetchReleases, + grabRelease, + setEpisodeReleasesFilter, + setReleasesSort, + setSeasonReleasesFilter, +} from 'Store/Actions/releaseActions'; +import createClientSideCollectionSelector from 'Store/Selectors/createClientSideCollectionSelector'; +import getErrorMessage from 'Utilities/Object/getErrorMessage'; +import translate from 'Utilities/String/translate'; +import InteractiveSearchFilterModal from './InteractiveSearchFilterModal'; +import InteractiveSearchRow from './InteractiveSearchRow'; +import styles from './InteractiveSearch.css'; + +const columns: Column[] = [ + { + name: 'protocol', + label: () => translate('Source'), + isSortable: true, + isVisible: true, + }, + { + name: 'age', + label: () => translate('Age'), + isSortable: true, + isVisible: true, + }, + { + name: 'title', + label: () => translate('Title'), + isSortable: true, + isVisible: true, + }, + { + name: 'indexer', + label: () => translate('Indexer'), + isSortable: true, + isVisible: true, + }, + { + name: 'size', + label: () => translate('Size'), + isSortable: true, + isVisible: true, + }, + { + name: 'peers', + label: () => translate('Peers'), + isSortable: true, + isVisible: true, + }, + { + name: 'languageWeight', + label: () => translate('Languages'), + isSortable: true, + isVisible: true, + }, + { + name: 'qualityWeight', + label: () => translate('Quality'), + isSortable: true, + isVisible: true, + }, + { + name: 'customFormatScore', + label: React.createElement(Icon, { + name: icons.SCORE, + title: () => translate('CustomFormatScore'), + }), + isSortable: true, + isVisible: true, + }, + { + name: 'indexerFlags', + label: React.createElement(Icon, { + name: icons.FLAG, + title: () => translate('IndexerFlags'), + }), + isSortable: true, + isVisible: true, + }, + { + name: 'rejections', + label: React.createElement(Icon, { + name: icons.DANGER, + title: () => translate('Rejections'), + }), + isSortable: true, + fixedSortDirection: sortDirections.ASCENDING, + isVisible: true, + }, + { + name: 'releaseWeight', + label: React.createElement(Icon, { name: icons.DOWNLOAD }), + isSortable: true, + fixedSortDirection: sortDirections.ASCENDING, + isVisible: true, + }, +]; + +interface InteractiveSearchProps { + type: InteractiveSearchType; + searchPayload: object; +} + +function InteractiveSearch({ type, searchPayload }: InteractiveSearchProps) { + const { + isFetching, + isPopulated, + error, + items, + totalItems, + selectedFilterKey, + filters, + customFilters, + sortKey, + sortDirection, + }: ReleasesAppState & ClientSideCollectionAppState = useSelector( + createClientSideCollectionSelector('releases', `releases.${type}`) + ); + + const dispatch = useDispatch(); + + const handleFilterSelect = useCallback( + (selectedFilterKey: string) => { + const action = + type === 'episode' ? setEpisodeReleasesFilter : setSeasonReleasesFilter; + + dispatch(action({ selectedFilterKey })); + }, + [type, dispatch] + ); + + const handleSortPress = useCallback( + (sortKey: string, sortDirection: SortDirection) => { + dispatch(setReleasesSort({ sortKey, sortDirection })); + }, + [dispatch] + ); + + const handleGrabPress = useCallback( + (payload: object) => { + dispatch(grabRelease(payload)); + }, + [dispatch] + ); + + useEffect(() => { + // If search results are not yet isPopulated fetch them, + // otherwise re-show the existing props. + + if (!isPopulated) { + dispatch(fetchReleases(searchPayload)); + } + }, [isPopulated, searchPayload, dispatch]); + + const errorMessage = getErrorMessage(error); + + return ( +
+
+ +
+ + {isFetching ? : null} + + {!isFetching && error ? ( +
+ {errorMessage ? ( + <> + {translate('InteractiveSearchResultsSeriesFailedErrorMessage', { + message: + errorMessage.charAt(0).toLowerCase() + errorMessage.slice(1), + })} + + ) : ( + translate('EpisodeSearchResultsLoadError') + )} +
+ ) : null} + + {!isFetching && isPopulated && !totalItems ? ( + {translate('NoResultsFound')} + ) : null} + + {!!totalItems && isPopulated && !items.length ? ( + + {translate('AllResultsAreHiddenByTheAppliedFilter')} + + ) : null} + + {isPopulated && !!items.length ? ( + + + {items.map((item) => { + return ( + + ); + })} + +
+ ) : null} + + {totalItems !== items.length && !!items.length ? ( +
+ {translate('SomeResultsAreHiddenByTheAppliedFilter')} +
+ ) : null} +
+ ); +} + +export default InteractiveSearch; diff --git a/frontend/src/InteractiveSearch/InteractiveSearchConnector.js b/frontend/src/InteractiveSearch/InteractiveSearchConnector.js deleted file mode 100644 index 10cad7224..000000000 --- a/frontend/src/InteractiveSearch/InteractiveSearchConnector.js +++ /dev/null @@ -1,95 +0,0 @@ -import PropTypes from 'prop-types'; -import React, { Component } from 'react'; -import { connect } from 'react-redux'; -import { createSelector } from 'reselect'; -import * as releaseActions from 'Store/Actions/releaseActions'; -import createClientSideCollectionSelector from 'Store/Selectors/createClientSideCollectionSelector'; -import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector'; -import InteractiveSearch from './InteractiveSearch'; - -function createMapStateToProps(appState, { type }) { - return createSelector( - (state) => state.releases.items.length, - createClientSideCollectionSelector('releases', `releases.${type}`), - createUISettingsSelector(), - (totalReleasesCount, releases, uiSettings) => { - return { - totalReleasesCount, - longDateFormat: uiSettings.longDateFormat, - timeFormat: uiSettings.timeFormat, - ...releases - }; - } - ); -} - -function createMapDispatchToProps(dispatch, props) { - return { - dispatchFetchReleases(payload) { - dispatch(releaseActions.fetchReleases(payload)); - }, - - onSortPress(sortKey, sortDirection) { - dispatch(releaseActions.setReleasesSort({ sortKey, sortDirection })); - }, - - onFilterSelect(selectedFilterKey) { - const action = props.type === 'episode' ? - releaseActions.setEpisodeReleasesFilter : - releaseActions.setSeasonReleasesFilter; - - dispatch(action({ selectedFilterKey })); - }, - - onGrabPress(payload) { - dispatch(releaseActions.grabRelease(payload)); - } - }; -} - -class InteractiveSearchConnector extends Component { - - // - // Lifecycle - - componentDidMount() { - const { - searchPayload, - isPopulated, - dispatchFetchReleases - } = this.props; - - // If search results are not yet isPopulated fetch them, - // otherwise re-show the existing props. - - if (!isPopulated) { - dispatchFetchReleases(searchPayload); - } - } - - // - // Render - - render() { - const { - dispatchFetchReleases, - ...otherProps - } = this.props; - - return ( - - - ); - } -} - -InteractiveSearchConnector.propTypes = { - type: PropTypes.string.isRequired, - searchPayload: PropTypes.object.isRequired, - isPopulated: PropTypes.bool, - dispatchFetchReleases: PropTypes.func -}; - -export default connect(createMapStateToProps, createMapDispatchToProps)(InteractiveSearchConnector); diff --git a/frontend/src/InteractiveSearch/InteractiveSearchFilterModal.tsx b/frontend/src/InteractiveSearch/InteractiveSearchFilterModal.tsx new file mode 100644 index 000000000..d24615554 --- /dev/null +++ b/frontend/src/InteractiveSearch/InteractiveSearchFilterModal.tsx @@ -0,0 +1,65 @@ +import React, { useCallback } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import { createSelector } from 'reselect'; +import AppState from 'App/State/AppState'; +import FilterModal from 'Components/Filter/FilterModal'; +import InteractiveSearchType from 'InteractiveSearch/InteractiveSearchType'; +import { + setEpisodeReleasesFilter, + setSeasonReleasesFilter, +} from 'Store/Actions/releaseActions'; + +function createReleasesSelector() { + return createSelector( + (state: AppState) => state.releases.items, + (releases) => { + return releases; + } + ); +} + +function createFilterBuilderPropsSelector() { + return createSelector( + (state: AppState) => state.releases.filterBuilderProps, + (filterBuilderProps) => { + return filterBuilderProps; + } + ); +} + +interface InteractiveSearchFilterModalProps { + isOpen: boolean; + type: InteractiveSearchType; +} + +export default function InteractiveSearchFilterModal({ + type, + ...otherProps +}: InteractiveSearchFilterModalProps) { + const sectionItems = useSelector(createReleasesSelector()); + const filterBuilderProps = useSelector(createFilterBuilderPropsSelector()); + const customFilterType = 'releases'; + + const dispatch = useDispatch(); + + const dispatchSetFilter = useCallback( + (payload: unknown) => { + const action = + type === 'episode' ? setEpisodeReleasesFilter : setSeasonReleasesFilter; + + dispatch(action(payload)); + }, + [type, dispatch] + ); + + return ( + + ); +} diff --git a/frontend/src/InteractiveSearch/InteractiveSearchFilterModalConnector.js b/frontend/src/InteractiveSearch/InteractiveSearchFilterModalConnector.js deleted file mode 100644 index c0ac11a46..000000000 --- a/frontend/src/InteractiveSearch/InteractiveSearchFilterModalConnector.js +++ /dev/null @@ -1,32 +0,0 @@ -import { connect } from 'react-redux'; -import { createSelector } from 'reselect'; -import FilterModal from 'Components/Filter/FilterModal'; -import { setEpisodeReleasesFilter, setSeasonReleasesFilter } from 'Store/Actions/releaseActions'; - -function createMapStateToProps() { - return createSelector( - (state) => state.releases.items, - (state) => state.releases.filterBuilderProps, - (sectionItems, filterBuilderProps) => { - return { - sectionItems, - filterBuilderProps, - customFilterType: 'releases' - }; - } - ); -} - -function createMapDispatchToProps(dispatch, props) { - return { - dispatchSetFilter(payload) { - const action = props.type === 'episode' ? - setEpisodeReleasesFilter: - setSeasonReleasesFilter; - - dispatch(action(payload)); - } - }; -} - -export default connect(createMapStateToProps, createMapDispatchToProps)(FilterModal); diff --git a/frontend/src/InteractiveSearch/InteractiveSearchRow.tsx b/frontend/src/InteractiveSearch/InteractiveSearchRow.tsx index d860b7fb9..0baf66f57 100644 --- a/frontend/src/InteractiveSearch/InteractiveSearchRow.tsx +++ b/frontend/src/InteractiveSearch/InteractiveSearchRow.tsx @@ -1,4 +1,5 @@ import React, { useCallback, useState } from 'react'; +import { useSelector } from 'react-redux'; import ProtocolLabel from 'Activity/Queue/ProtocolLabel'; import Icon from 'Components/Icon'; import Link from 'Components/Link/Link'; @@ -8,15 +9,13 @@ import TableRowCell from 'Components/Table/Cells/TableRowCell'; import TableRow from 'Components/Table/TableRow'; import Popover from 'Components/Tooltip/Popover'; import Tooltip from 'Components/Tooltip/Tooltip'; -import type DownloadProtocol from 'DownloadClient/DownloadProtocol'; import EpisodeFormats from 'Episode/EpisodeFormats'; import EpisodeLanguages from 'Episode/EpisodeLanguages'; import EpisodeQuality from 'Episode/EpisodeQuality'; import IndexerFlags from 'Episode/IndexerFlags'; import { icons, kinds, tooltipPositions } from 'Helpers/Props'; -import Language from 'Language/Language'; -import { QualityModel } from 'Quality/Quality'; -import CustomFormat from 'typings/CustomFormat'; +import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector'; +import Release from 'typings/Release'; import formatDateTime from 'Utilities/Date/formatDateTime'; import formatAge from 'Utilities/Number/formatAge'; import formatBytes from 'Utilities/Number/formatBytes'; @@ -24,7 +23,6 @@ import formatCustomFormatScore from 'Utilities/Number/formatCustomFormatScore'; import translate from 'Utilities/String/translate'; import OverrideMatchModal from './OverrideMatch/OverrideMatchModal'; import Peers from './Peers'; -import ReleaseEpisode from './ReleaseEpisode'; import ReleaseSceneIndicator from './ReleaseSceneIndicator'; import styles from './InteractiveSearchRow.css'; @@ -72,43 +70,7 @@ function getDownloadTooltip( return translate('AddToDownloadQueue'); } -interface InteractiveSearchRowProps { - guid: string; - protocol: DownloadProtocol; - age: number; - ageHours: number; - ageMinutes: number; - publishDate: string; - title: string; - infoUrl: string; - indexerId: number; - indexer: string; - size: number; - seeders?: number; - leechers?: number; - quality: QualityModel; - languages: Language[]; - customFormats: CustomFormat[]; - customFormatScore: number; - sceneMapping?: object; - seasonNumber?: number; - episodeNumbers?: number[]; - absoluteEpisodeNumbers?: number[]; - mappedSeriesId?: number; - mappedSeasonNumber?: number; - mappedEpisodeNumbers?: number[]; - mappedAbsoluteEpisodeNumbers?: number[]; - mappedEpisodeInfo: ReleaseEpisode[]; - indexerFlags: number; - rejections: string[]; - episodeRequested: boolean; - downloadAllowed: boolean; - isDaily: boolean; - isGrabbing: boolean; - isGrabbed: boolean; - grabError?: string; - longDateFormat: string; - timeFormat: string; +interface InteractiveSearchRowProps extends Release { searchPayload: object; onGrabPress(...args: unknown[]): void; } @@ -148,13 +110,15 @@ function InteractiveSearchRow(props: InteractiveSearchRowProps) { isDaily, isGrabbing = false, isGrabbed = false, - longDateFormat, - timeFormat, grabError, searchPayload, onGrabPress, } = props; + const { longDateFormat, timeFormat } = useSelector( + createUISettingsSelector() + ); + const [isConfirmGrabModalOpen, setIsConfirmGrabModalOpen] = useState(false); const [isOverrideModalOpen, setIsOverrideModalOpen] = useState(false); diff --git a/frontend/src/InteractiveSearch/InteractiveSearchType.ts b/frontend/src/InteractiveSearch/InteractiveSearchType.ts new file mode 100644 index 000000000..2ae6733a6 --- /dev/null +++ b/frontend/src/InteractiveSearch/InteractiveSearchType.ts @@ -0,0 +1,3 @@ +type InteractiveSearchType = 'episode' | 'season'; + +export default InteractiveSearchType; diff --git a/frontend/src/InteractiveSearch/OverrideMatch/OverrideMatchModal.tsx b/frontend/src/InteractiveSearch/OverrideMatch/OverrideMatchModal.tsx index 1a2e6514b..e15b5c66d 100644 --- a/frontend/src/InteractiveSearch/OverrideMatch/OverrideMatchModal.tsx +++ b/frontend/src/InteractiveSearch/OverrideMatch/OverrideMatchModal.tsx @@ -2,9 +2,9 @@ import React from 'react'; import Modal from 'Components/Modal/Modal'; import DownloadProtocol from 'DownloadClient/DownloadProtocol'; import { sizes } from 'Helpers/Props'; -import ReleaseEpisode from 'InteractiveSearch/ReleaseEpisode'; import Language from 'Language/Language'; import { QualityModel } from 'Quality/Quality'; +import { ReleaseEpisode } from 'typings/Release'; import OverrideMatchModalContent from './OverrideMatchModalContent'; interface OverrideMatchModalProps { diff --git a/frontend/src/InteractiveSearch/OverrideMatch/OverrideMatchModalContent.tsx b/frontend/src/InteractiveSearch/OverrideMatch/OverrideMatchModalContent.tsx index 9d6ab9253..8e41a93de 100644 --- a/frontend/src/InteractiveSearch/OverrideMatch/OverrideMatchModalContent.tsx +++ b/frontend/src/InteractiveSearch/OverrideMatch/OverrideMatchModalContent.tsx @@ -18,7 +18,6 @@ import SelectLanguageModal from 'InteractiveImport/Language/SelectLanguageModal' import SelectQualityModal from 'InteractiveImport/Quality/SelectQualityModal'; import SelectSeasonModal from 'InteractiveImport/Season/SelectSeasonModal'; import SelectSeriesModal from 'InteractiveImport/Series/SelectSeriesModal'; -import ReleaseEpisode from 'InteractiveSearch/ReleaseEpisode'; import Language from 'Language/Language'; import { QualityModel } from 'Quality/Quality'; import Series from 'Series/Series'; @@ -26,6 +25,7 @@ import { grabRelease } from 'Store/Actions/releaseActions'; import { fetchDownloadClients } from 'Store/Actions/settingsActions'; import createEnabledDownloadClientsSelector from 'Store/Selectors/createEnabledDownloadClientsSelector'; import { createSeriesSelectorForHook } from 'Store/Selectors/createSeriesSelector'; +import { ReleaseEpisode } from 'typings/Release'; import translate from 'Utilities/String/translate'; import SelectDownloadClientModal from './DownloadClient/SelectDownloadClientModal'; import OverrideMatchData from './OverrideMatchData'; diff --git a/frontend/src/InteractiveSearch/Peers.js b/frontend/src/InteractiveSearch/Peers.tsx similarity index 63% rename from frontend/src/InteractiveSearch/Peers.js rename to frontend/src/InteractiveSearch/Peers.tsx index a55e75c09..b23391210 100644 --- a/frontend/src/InteractiveSearch/Peers.js +++ b/frontend/src/InteractiveSearch/Peers.tsx @@ -1,9 +1,8 @@ -import PropTypes from 'prop-types'; import React from 'react'; import Label from 'Components/Label'; import { kinds } from 'Helpers/Props'; -function getKind(seeders) { +function getKind(seeders: number = 0) { if (seeders > 50) { return kinds.PRIMARY; } @@ -19,7 +18,7 @@ function getKind(seeders) { return kinds.DANGER; } -function getPeersTooltipPart(peers, peersUnit) { +function getPeersTooltipPart(peersUnit: string, peers?: number) { if (peers == null) { return `Unknown ${peersUnit}s`; } @@ -31,27 +30,27 @@ function getPeersTooltipPart(peers, peersUnit) { return `${peers} ${peersUnit}s`; } -function Peers(props) { - const { - seeders, - leechers - } = props; +interface PeersProps { + seeders?: number; + leechers?: number; +} + +function Peers(props: PeersProps) { + const { seeders, leechers } = props; const kind = getKind(seeders); return ( ); } -Peers.propTypes = { - seeders: PropTypes.number, - leechers: PropTypes.number -}; - export default Peers; diff --git a/frontend/src/InteractiveSearch/ReleaseEpisode.ts b/frontend/src/InteractiveSearch/ReleaseEpisode.ts deleted file mode 100644 index 91ab5b7b5..000000000 --- a/frontend/src/InteractiveSearch/ReleaseEpisode.ts +++ /dev/null @@ -1,10 +0,0 @@ -interface ReleaseEpisode { - id: number; - episodeFileId: number; - seasonNumber: number; - episodeNumber: number; - absoluteEpisodeNumber?: number; - title: string; -} - -export default ReleaseEpisode; diff --git a/frontend/src/Series/Search/SeasonInteractiveSearchModalContent.tsx b/frontend/src/Series/Search/SeasonInteractiveSearchModalContent.tsx index 362972c89..f3644e13b 100644 --- a/frontend/src/Series/Search/SeasonInteractiveSearchModalContent.tsx +++ b/frontend/src/Series/Search/SeasonInteractiveSearchModalContent.tsx @@ -5,7 +5,7 @@ import ModalContent from 'Components/Modal/ModalContent'; import ModalFooter from 'Components/Modal/ModalFooter'; import ModalHeader from 'Components/Modal/ModalHeader'; import { scrollDirections } from 'Helpers/Props'; -import InteractiveSearchConnector from 'InteractiveSearch/InteractiveSearchConnector'; +import InteractiveSearch from 'InteractiveSearch/InteractiveSearch'; import formatSeason from 'Season/formatSeason'; import translate from 'Utilities/String/translate'; @@ -31,7 +31,7 @@ function SeasonInteractiveSearchModalContent( -