New: Add icons for search results to indicate if it has been previously grabbed, failed or is in the blacklist.

pull/5070/head
Qstick 4 years ago
parent 2ee77aa0a4
commit 4ea9ded0de

@ -23,6 +23,7 @@ import {
faArrowCircleLeft as fasArrowCircleLeft, faArrowCircleLeft as fasArrowCircleLeft,
faArrowCircleRight as fasArrowCircleRight, faArrowCircleRight as fasArrowCircleRight,
faBackward as fasBackward, faBackward as fasBackward,
faBan as fasBan,
faBars as fasBars, faBars as fasBars,
faBolt as fasBolt, faBolt as fasBolt,
faBookmark as fasBookmark, faBookmark as fasBookmark,
@ -223,3 +224,4 @@ export const UNSAVED_SETTING = farDotCircle;
export const VIEW = fasEye; export const VIEW = fasEye;
export const WARNING = fasExclamationTriangle; export const WARNING = fasExclamationTriangle;
export const WIKI = fasBookReader; export const WIKI = fasBookReader;
export const BLACKLIST = fasBan;

@ -6,7 +6,7 @@ import Table from 'Components/Table/Table';
import TableBody from 'Components/Table/TableBody'; import TableBody from 'Components/Table/TableBody';
import { icons, sortDirections } from 'Helpers/Props'; import { icons, sortDirections } from 'Helpers/Props';
import translate from 'Utilities/String/translate'; import translate from 'Utilities/String/translate';
import InteractiveSearchRow from './InteractiveSearchRow'; import InteractiveSearchRowConnector from './InteractiveSearchRowConnector';
import styles from './InteractiveSearchContent.css'; import styles from './InteractiveSearchContent.css';
const columns = [ const columns = [
@ -34,6 +34,13 @@ const columns = [
isSortable: true, isSortable: true,
isVisible: true isVisible: true
}, },
{
name: 'history',
label: translate('History'),
isSortable: true,
fixedSortDirection: sortDirections.ASCENDING,
isVisible: true
},
{ {
name: 'size', name: 'size',
label: translate('Size'), label: translate('Size'),
@ -151,7 +158,7 @@ function InteractiveSearchContent(props) {
{ {
items.map((item) => { items.map((item) => {
return ( return (
<InteractiveSearchRow <InteractiveSearchRowConnector
key={item.guid} key={item.guid}
{...item} {...item}
searchPayload={searchPayload} searchPayload={searchPayload}

@ -62,3 +62,13 @@
.title div { .title div {
@add-mixin truncate; @add-mixin truncate;
} }
.history {
composes: cell;
width: 75px;
}
.blacklist {
margin-left: 5px;
}

@ -124,7 +124,10 @@ class InteractiveSearchRow extends Component {
isGrabbed, isGrabbed,
longDateFormat, longDateFormat,
timeFormat, timeFormat,
grabError grabError,
historyGrabbedData,
historyFailedData,
blacklistData
} = this.props; } = this.props;
return ( return (
@ -157,6 +160,37 @@ class InteractiveSearchRow extends Component {
{indexer} {indexer}
</TableRowCell> </TableRowCell>
<TableRowCell className={styles.history}>
{
historyGrabbedData?.date && !historyFailedData?.date &&
<Icon
name={icons.DOWNLOADING}
kind={kinds.DEFAULT}
title={`${translate('Grabbed')}: ${formatDateTime(historyGrabbedData.date, longDateFormat, timeFormat, { includeSeconds: true })}`}
/>
}
{
historyFailedData?.date &&
<Icon
className={styles.failed}
name={icons.DOWNLOADING}
kind={kinds.DANGER}
title={`${translate('Failed')}: ${formatDateTime(historyFailedData.date, longDateFormat, timeFormat, { includeSeconds: true })}`}
/>
}
{
blacklistData?.date &&
<Icon
className={historyGrabbedData || historyFailedData ? styles.blacklist : ''}
name={icons.BLACKLIST}
kind={kinds.DANGER}
title={`${translate('Blacklisted')}: ${formatDateTime(blacklistData.date, longDateFormat, timeFormat, { includeSeconds: true })}`}
/>
}
</TableRowCell>
<TableRowCell className={styles.size}> <TableRowCell className={styles.size}>
{formatBytes(size)} {formatBytes(size)}
</TableRowCell> </TableRowCell>
@ -304,7 +338,10 @@ InteractiveSearchRow.propTypes = {
longDateFormat: PropTypes.string.isRequired, longDateFormat: PropTypes.string.isRequired,
timeFormat: PropTypes.string.isRequired, timeFormat: PropTypes.string.isRequired,
searchPayload: PropTypes.object.isRequired, searchPayload: PropTypes.object.isRequired,
onGrabPress: PropTypes.func.isRequired onGrabPress: PropTypes.func.isRequired,
historyFailedData: PropTypes.object,
historyGrabbedData: PropTypes.object,
blacklistData: PropTypes.object
}; };
InteractiveSearchRow.defaultProps = { InteractiveSearchRow.defaultProps = {

@ -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 (
<InteractiveSearchRow
historyGrabbedData={historyGrabbedData}
historyFailedData={historyFailedData}
blacklistData={blacklistData}
{...otherProps}
/>
);
}
}
InteractiveSearchRowConnector.propTypes = {
historyGrabbedData: PropTypes.object,
historyFailedData: PropTypes.object,
blacklistData: PropTypes.object
};
export default connect(createMapStateToProps)(InteractiveSearchRowConnector);

@ -5,11 +5,13 @@ import React, { Component } from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { createSelector } from 'reselect'; import { createSelector } from 'reselect';
import * as commandNames from 'Commands/commandNames'; import * as commandNames from 'Commands/commandNames';
import { fetchBlacklist } from 'Store/Actions/blacklistActions';
import { executeCommand } from 'Store/Actions/commandActions'; import { executeCommand } from 'Store/Actions/commandActions';
import { clearExtraFiles, fetchExtraFiles } from 'Store/Actions/extraFileActions'; import { clearExtraFiles, fetchExtraFiles } from 'Store/Actions/extraFileActions';
import { toggleMovieMonitored } from 'Store/Actions/movieActions'; import { toggleMovieMonitored } from 'Store/Actions/movieActions';
import { clearMovieCredits, fetchMovieCredits } from 'Store/Actions/movieCreditsActions'; import { clearMovieCredits, fetchMovieCredits } from 'Store/Actions/movieCreditsActions';
import { clearMovieFiles, fetchMovieFiles } from 'Store/Actions/movieFileActions'; import { clearMovieFiles, fetchMovieFiles } from 'Store/Actions/movieFileActions';
import { clearMovieHistory, fetchMovieHistory } from 'Store/Actions/movieHistoryActions';
import { clearQueueDetails, fetchQueueDetails } from 'Store/Actions/queueActions'; import { clearQueueDetails, fetchQueueDetails } from 'Store/Actions/queueActions';
import { cancelFetchReleases, clearReleases } from 'Store/Actions/releaseActions'; import { cancelFetchReleases, clearReleases } from 'Store/Actions/releaseActions';
import { fetchImportListSchema } from 'Store/Actions/settingsActions'; import { fetchImportListSchema } from 'Store/Actions/settingsActions';
@ -178,6 +180,12 @@ function createMapDispatchToProps(dispatch, props) {
dispatchClearMovieFiles() { dispatchClearMovieFiles() {
dispatch(clearMovieFiles()); dispatch(clearMovieFiles());
}, },
dispatchFetchMovieHistory({ movieId }) {
dispatch(fetchMovieHistory({ movieId }));
},
dispatchClearMovieHistory() {
dispatch(clearMovieHistory());
},
dispatchFetchMovieCredits({ movieId }) { dispatchFetchMovieCredits({ movieId }) {
dispatch(fetchMovieCredits({ movieId })); dispatch(fetchMovieCredits({ movieId }));
}, },
@ -213,6 +221,10 @@ function createMapDispatchToProps(dispatch, props) {
}, },
onGoToMovie(titleSlug) { onGoToMovie(titleSlug) {
dispatch(push(`${window.Radarr.urlBase}/movie/${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; const movieId = this.props.id;
this.props.dispatchFetchMovieFiles({ movieId }); this.props.dispatchFetchMovieFiles({ movieId });
this.props.dispatchFetchMovieHistory({ movieId });
this.props.dispatchFetchExtraFiles({ movieId }); this.props.dispatchFetchExtraFiles({ movieId });
this.props.dispatchFetchMovieCredits({ movieId }); this.props.dispatchFetchMovieCredits({ movieId });
this.props.dispatchFetchQueueDetails({ movieId }); this.props.dispatchFetchQueueDetails({ movieId });
this.props.dispatchFetchImportListSchema(); this.props.dispatchFetchImportListSchema();
this.props.dispatchFetchBlacklist();
}
repopulate = () => {
this.props.dispatchFetchBlacklist();
} }
unpopulate = () => { unpopulate = () => {
this.props.dispatchCancelFetchReleases(); this.props.dispatchCancelFetchReleases();
this.props.dispatchClearMovieFiles(); this.props.dispatchClearMovieFiles();
this.props.dispatchClearMovieHistory();
this.props.dispatchClearExtraFiles(); this.props.dispatchClearExtraFiles();
this.props.dispatchClearMovieCredits(); this.props.dispatchClearMovieCredits();
this.props.dispatchClearQueueDetails(); this.props.dispatchClearQueueDetails();
@ -331,6 +350,8 @@ MovieDetailsConnector.propTypes = {
isSmallScreen: PropTypes.bool.isRequired, isSmallScreen: PropTypes.bool.isRequired,
dispatchFetchMovieFiles: PropTypes.func.isRequired, dispatchFetchMovieFiles: PropTypes.func.isRequired,
dispatchClearMovieFiles: PropTypes.func.isRequired, dispatchClearMovieFiles: PropTypes.func.isRequired,
dispatchFetchMovieHistory: PropTypes.func.isRequired,
dispatchClearMovieHistory: PropTypes.func.isRequired,
dispatchFetchExtraFiles: PropTypes.func.isRequired, dispatchFetchExtraFiles: PropTypes.func.isRequired,
dispatchClearExtraFiles: PropTypes.func.isRequired, dispatchClearExtraFiles: PropTypes.func.isRequired,
dispatchFetchMovieCredits: PropTypes.func.isRequired, dispatchFetchMovieCredits: PropTypes.func.isRequired,
@ -342,6 +363,7 @@ MovieDetailsConnector.propTypes = {
dispatchClearQueueDetails: PropTypes.func.isRequired, dispatchClearQueueDetails: PropTypes.func.isRequired,
dispatchFetchImportListSchema: PropTypes.func.isRequired, dispatchFetchImportListSchema: PropTypes.func.isRequired,
dispatchExecuteCommand: PropTypes.func.isRequired, dispatchExecuteCommand: PropTypes.func.isRequired,
dispatchFetchBlacklist: PropTypes.func.isRequired,
onGoToMovie: PropTypes.func.isRequired onGoToMovie: PropTypes.func.isRequired
}; };

@ -2,7 +2,7 @@ import PropTypes from 'prop-types';
import React, { Component } from 'react'; import React, { Component } from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { createSelector } from 'reselect'; import { createSelector } from 'reselect';
import { clearMovieHistory, fetchMovieHistory, movieHistoryMarkAsFailed } from 'Store/Actions/movieHistoryActions'; import { movieHistoryMarkAsFailed } from 'Store/Actions/movieHistoryActions';
import MovieHistoryTableContent from './MovieHistoryTableContent'; import MovieHistoryTableContent from './MovieHistoryTableContent';
function createMapStateToProps() { function createMapStateToProps() {
@ -15,44 +15,11 @@ function createMapStateToProps() {
} }
const mapDispatchToProps = { const mapDispatchToProps = {
fetchMovieHistory,
clearMovieHistory,
movieHistoryMarkAsFailed movieHistoryMarkAsFailed
}; };
class MovieHistoryTableContentConnector extends Component { 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 // Listeners
@ -82,8 +49,6 @@ class MovieHistoryTableContentConnector extends Component {
MovieHistoryTableContentConnector.propTypes = { MovieHistoryTableContentConnector.propTypes = {
movieId: PropTypes.number.isRequired, movieId: PropTypes.number.isRequired,
fetchMovieHistory: PropTypes.func.isRequired,
clearMovieHistory: PropTypes.func.isRequired,
movieHistoryMarkAsFailed: PropTypes.func.isRequired movieHistoryMarkAsFailed: PropTypes.func.isRequired
}; };

@ -63,6 +63,7 @@
"BindAddress": "Bind Address", "BindAddress": "Bind Address",
"BindAddressHelpText": "Valid IP4 address or '*' for all interfaces", "BindAddressHelpText": "Valid IP4 address or '*' for all interfaces",
"Blacklist": "Blacklist", "Blacklist": "Blacklist",
"Blacklisted": "Blacklisted",
"BlacklistHelpText": "Prevents Radarr from automatically grabbing this movie again", "BlacklistHelpText": "Prevents Radarr from automatically grabbing this movie again",
"BlacklistRelease": "Blacklist Release", "BlacklistRelease": "Blacklist Release",
"Branch": "Branch", "Branch": "Branch",

Loading…
Cancel
Save