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

This commit is contained in:
Qstick 2020-09-11 22:17:36 -04:00
parent 2ee77aa0a4
commit 4ea9ded0de
8 changed files with 146 additions and 40 deletions

View File

@ -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;

View File

@ -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 (
<InteractiveSearchRow
<InteractiveSearchRowConnector
key={item.guid}
{...item}
searchPayload={searchPayload}

View File

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

View File

@ -124,7 +124,10 @@ class InteractiveSearchRow extends Component {
isGrabbed,
longDateFormat,
timeFormat,
grabError
grabError,
historyGrabbedData,
historyFailedData,
blacklistData
} = this.props;
return (
@ -157,6 +160,37 @@ class InteractiveSearchRow extends Component {
{indexer}
</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}>
{formatBytes(size)}
</TableRowCell>
@ -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 = {

View File

@ -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);

View File

@ -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
};

View File

@ -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
};

View File

@ -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",