mirror of
https://github.com/Radarr/Radarr.git
synced 2024-11-04 10:02:40 +01:00
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:
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…
Reference in New Issue
Block a user