From 4ea9ded0de484f922ff313f84350cfea9ff44bd9 Mon Sep 17 00:00:00 2001 From: Qstick Date: Fri, 11 Sep 2020 22:17:36 -0400 Subject: [PATCH] New: Add icons for search results to indicate if it has been previously grabbed, failed or is in the blacklist. --- frontend/src/Helpers/Props/icons.js | 2 + .../InteractiveSearchContent.js | 11 +++- .../InteractiveSearchRow.css | 10 +++ .../InteractiveSearch/InteractiveSearchRow.js | 41 +++++++++++- .../InteractiveSearchRowConnector.js | 62 +++++++++++++++++++ .../Movie/Details/MovieDetailsConnector.js | 22 +++++++ .../MovieHistoryTableContentConnector.js | 37 +---------- src/NzbDrone.Core/Localization/Core/en.json | 1 + 8 files changed, 146 insertions(+), 40 deletions(-) create mode 100644 frontend/src/InteractiveSearch/InteractiveSearchRowConnector.js diff --git a/frontend/src/Helpers/Props/icons.js b/frontend/src/Helpers/Props/icons.js index 8cea4ebc3..3c57c7a6b 100644 --- a/frontend/src/Helpers/Props/icons.js +++ b/frontend/src/Helpers/Props/icons.js @@ -23,6 +23,7 @@ import { faArrowCircleLeft as fasArrowCircleLeft, faArrowCircleRight as fasArrowCircleRight, faBackward as fasBackward, + faBan as fasBan, faBars as fasBars, faBolt as fasBolt, faBookmark as fasBookmark, @@ -223,3 +224,4 @@ export const UNSAVED_SETTING = farDotCircle; export const VIEW = fasEye; export const WARNING = fasExclamationTriangle; export const WIKI = fasBookReader; +export const BLACKLIST = fasBan; diff --git a/frontend/src/InteractiveSearch/InteractiveSearchContent.js b/frontend/src/InteractiveSearch/InteractiveSearchContent.js index 9b9703d30..181677660 100644 --- a/frontend/src/InteractiveSearch/InteractiveSearchContent.js +++ b/frontend/src/InteractiveSearch/InteractiveSearchContent.js @@ -6,7 +6,7 @@ import Table from 'Components/Table/Table'; import TableBody from 'Components/Table/TableBody'; import { icons, sortDirections } from 'Helpers/Props'; import translate from 'Utilities/String/translate'; -import InteractiveSearchRow from './InteractiveSearchRow'; +import InteractiveSearchRowConnector from './InteractiveSearchRowConnector'; import styles from './InteractiveSearchContent.css'; const columns = [ @@ -34,6 +34,13 @@ const columns = [ isSortable: true, isVisible: true }, + { + name: 'history', + label: translate('History'), + isSortable: true, + fixedSortDirection: sortDirections.ASCENDING, + isVisible: true + }, { name: 'size', label: translate('Size'), @@ -151,7 +158,7 @@ function InteractiveSearchContent(props) { { items.map((item) => { return ( - + + { + historyGrabbedData?.date && !historyFailedData?.date && + + } + + { + historyFailedData?.date && + + } + + { + blacklistData?.date && + + } + + {formatBytes(size)} @@ -304,7 +338,10 @@ InteractiveSearchRow.propTypes = { longDateFormat: PropTypes.string.isRequired, timeFormat: PropTypes.string.isRequired, searchPayload: PropTypes.object.isRequired, - onGrabPress: PropTypes.func.isRequired + onGrabPress: PropTypes.func.isRequired, + historyFailedData: PropTypes.object, + historyGrabbedData: PropTypes.object, + blacklistData: PropTypes.object }; InteractiveSearchRow.defaultProps = { diff --git a/frontend/src/InteractiveSearch/InteractiveSearchRowConnector.js b/frontend/src/InteractiveSearch/InteractiveSearchRowConnector.js new file mode 100644 index 000000000..d8c816957 --- /dev/null +++ b/frontend/src/InteractiveSearch/InteractiveSearchRowConnector.js @@ -0,0 +1,62 @@ +import PropTypes from 'prop-types'; +import React, { Component } from 'react'; +import { connect } from 'react-redux'; +import { createSelector } from 'reselect'; +import InteractiveSearchRow from './InteractiveSearchRow'; + +function createMapStateToProps() { + return createSelector( + (state, { guid }) => guid, + (state) => state.movieHistory.items, + (state) => state.blacklist.items, + (guid, movieHistory, blacklist) => { + + let blacklistData = {}; + let historyFailedData = {}; + + const historyGrabbedData = movieHistory.find((movie) => movie.eventType === 'grabbed' && movie.data.guid === guid); + if (historyGrabbedData) { + historyFailedData = movieHistory.find((movie) => movie.eventType === 'downloadFailed' && movie.sourceTitle === historyGrabbedData.sourceTitle); + blacklistData = blacklist.find((item) => item.sourceTitle === historyGrabbedData.sourceTitle); + } + + return { + historyGrabbedData, + historyFailedData, + blacklistData + }; + } + ); +} + +class InteractiveSearchRowConnector extends Component { + + // + // Render + + render() { + const { + historyGrabbedData, + historyFailedData, + blacklistData, + ...otherProps + } = this.props; + + return ( + + ); + } +} + +InteractiveSearchRowConnector.propTypes = { + historyGrabbedData: PropTypes.object, + historyFailedData: PropTypes.object, + blacklistData: PropTypes.object +}; + +export default connect(createMapStateToProps)(InteractiveSearchRowConnector); diff --git a/frontend/src/Movie/Details/MovieDetailsConnector.js b/frontend/src/Movie/Details/MovieDetailsConnector.js index 247736e7b..fdbc65af8 100644 --- a/frontend/src/Movie/Details/MovieDetailsConnector.js +++ b/frontend/src/Movie/Details/MovieDetailsConnector.js @@ -5,11 +5,13 @@ import React, { Component } from 'react'; import { connect } from 'react-redux'; import { createSelector } from 'reselect'; import * as commandNames from 'Commands/commandNames'; +import { fetchBlacklist } from 'Store/Actions/blacklistActions'; import { executeCommand } from 'Store/Actions/commandActions'; import { clearExtraFiles, fetchExtraFiles } from 'Store/Actions/extraFileActions'; import { toggleMovieMonitored } from 'Store/Actions/movieActions'; import { clearMovieCredits, fetchMovieCredits } from 'Store/Actions/movieCreditsActions'; import { clearMovieFiles, fetchMovieFiles } from 'Store/Actions/movieFileActions'; +import { clearMovieHistory, fetchMovieHistory } from 'Store/Actions/movieHistoryActions'; import { clearQueueDetails, fetchQueueDetails } from 'Store/Actions/queueActions'; import { cancelFetchReleases, clearReleases } from 'Store/Actions/releaseActions'; import { fetchImportListSchema } from 'Store/Actions/settingsActions'; @@ -178,6 +180,12 @@ function createMapDispatchToProps(dispatch, props) { dispatchClearMovieFiles() { dispatch(clearMovieFiles()); }, + dispatchFetchMovieHistory({ movieId }) { + dispatch(fetchMovieHistory({ movieId })); + }, + dispatchClearMovieHistory() { + dispatch(clearMovieHistory()); + }, dispatchFetchMovieCredits({ movieId }) { dispatch(fetchMovieCredits({ movieId })); }, @@ -213,6 +221,10 @@ function createMapDispatchToProps(dispatch, props) { }, onGoToMovie(titleSlug) { dispatch(push(`${window.Radarr.urlBase}/movie/${titleSlug}`)); + }, + dispatchFetchBlacklist() { + // TODO: Allow for passing a movie id to fetch a single movie's blacklist data + dispatch(fetchBlacklist()); } }; } @@ -266,15 +278,22 @@ class MovieDetailsConnector extends Component { const movieId = this.props.id; this.props.dispatchFetchMovieFiles({ movieId }); + this.props.dispatchFetchMovieHistory({ movieId }); this.props.dispatchFetchExtraFiles({ movieId }); this.props.dispatchFetchMovieCredits({ movieId }); this.props.dispatchFetchQueueDetails({ movieId }); this.props.dispatchFetchImportListSchema(); + this.props.dispatchFetchBlacklist(); + } + + repopulate = () => { + this.props.dispatchFetchBlacklist(); } unpopulate = () => { this.props.dispatchCancelFetchReleases(); this.props.dispatchClearMovieFiles(); + this.props.dispatchClearMovieHistory(); this.props.dispatchClearExtraFiles(); this.props.dispatchClearMovieCredits(); this.props.dispatchClearQueueDetails(); @@ -331,6 +350,8 @@ MovieDetailsConnector.propTypes = { isSmallScreen: PropTypes.bool.isRequired, dispatchFetchMovieFiles: PropTypes.func.isRequired, dispatchClearMovieFiles: PropTypes.func.isRequired, + dispatchFetchMovieHistory: PropTypes.func.isRequired, + dispatchClearMovieHistory: PropTypes.func.isRequired, dispatchFetchExtraFiles: PropTypes.func.isRequired, dispatchClearExtraFiles: PropTypes.func.isRequired, dispatchFetchMovieCredits: PropTypes.func.isRequired, @@ -342,6 +363,7 @@ MovieDetailsConnector.propTypes = { dispatchClearQueueDetails: PropTypes.func.isRequired, dispatchFetchImportListSchema: PropTypes.func.isRequired, dispatchExecuteCommand: PropTypes.func.isRequired, + dispatchFetchBlacklist: PropTypes.func.isRequired, onGoToMovie: PropTypes.func.isRequired }; diff --git a/frontend/src/Movie/History/MovieHistoryTableContentConnector.js b/frontend/src/Movie/History/MovieHistoryTableContentConnector.js index 96b0ac520..a59626693 100644 --- a/frontend/src/Movie/History/MovieHistoryTableContentConnector.js +++ b/frontend/src/Movie/History/MovieHistoryTableContentConnector.js @@ -2,7 +2,7 @@ import PropTypes from 'prop-types'; import React, { Component } from 'react'; import { connect } from 'react-redux'; import { createSelector } from 'reselect'; -import { clearMovieHistory, fetchMovieHistory, movieHistoryMarkAsFailed } from 'Store/Actions/movieHistoryActions'; +import { movieHistoryMarkAsFailed } from 'Store/Actions/movieHistoryActions'; import MovieHistoryTableContent from './MovieHistoryTableContent'; function createMapStateToProps() { @@ -15,44 +15,11 @@ function createMapStateToProps() { } const mapDispatchToProps = { - fetchMovieHistory, - clearMovieHistory, movieHistoryMarkAsFailed }; class MovieHistoryTableContentConnector extends Component { - // - // Lifecycle - - componentDidMount() { - const { - movieId - } = this.props; - - this.props.fetchMovieHistory({ - movieId - }); - } - - componentDidUpdate(prevProps) { - const { - movieId - } = this.props; - - // If the id has changed we need to clear the history - if (prevProps.movieId !== movieId) { - this.props.clearMovieHistory(); - this.props.fetchMovieHistory({ - movieId - }); - } - } - - componentWillUnmount() { - this.props.clearMovieHistory(); - } - // // Listeners @@ -82,8 +49,6 @@ class MovieHistoryTableContentConnector extends Component { MovieHistoryTableContentConnector.propTypes = { movieId: PropTypes.number.isRequired, - fetchMovieHistory: PropTypes.func.isRequired, - clearMovieHistory: PropTypes.func.isRequired, movieHistoryMarkAsFailed: PropTypes.func.isRequired }; diff --git a/src/NzbDrone.Core/Localization/Core/en.json b/src/NzbDrone.Core/Localization/Core/en.json index 55ad1800b..a6114d260 100644 --- a/src/NzbDrone.Core/Localization/Core/en.json +++ b/src/NzbDrone.Core/Localization/Core/en.json @@ -63,6 +63,7 @@ "BindAddress": "Bind Address", "BindAddressHelpText": "Valid IP4 address or '*' for all interfaces", "Blacklist": "Blacklist", + "Blacklisted": "Blacklisted", "BlacklistHelpText": "Prevents Radarr from automatically grabbing this movie again", "BlacklistRelease": "Blacklist Release", "Branch": "Branch",