1
0
mirror of https://github.com/Radarr/Radarr.git synced 2024-09-11 20:12:41 +02:00

Add movie status to the main search page if the movie is already in the db (label on small screen otherwise progress bar under poster)

Fix issues with yellow/grey movie status color not showing up properly
Refactor the getMovieStatus to be more generic
This commit is contained in:
nitsua 2020-10-05 14:50:03 -04:00 committed by Qstick
parent e263d066da
commit 553b8b1945
12 changed files with 205 additions and 68 deletions

View File

@ -81,7 +81,8 @@ class AddNewMovie extends Component {
const { const {
error, error,
items, items,
hasExistingMovies hasExistingMovies,
colorImpairedMode
} = this.props; } = this.props;
const term = this.state.term; const term = this.state.term;
@ -141,6 +142,7 @@ class AddNewMovie extends Component {
return ( return (
<AddNewMovieSearchResultConnector <AddNewMovieSearchResultConnector
key={item.tmdbId} key={item.tmdbId}
colorImpairedMode={colorImpairedMode}
{...item} {...item}
/> />
); );
@ -213,7 +215,8 @@ AddNewMovie.propTypes = {
items: PropTypes.arrayOf(PropTypes.object).isRequired, items: PropTypes.arrayOf(PropTypes.object).isRequired,
hasExistingMovies: PropTypes.bool.isRequired, hasExistingMovies: PropTypes.bool.isRequired,
onMovieLookupChange: PropTypes.func.isRequired, onMovieLookupChange: PropTypes.func.isRequired,
onClearMovieLookup: PropTypes.func.isRequired onClearMovieLookup: PropTypes.func.isRequired,
colorImpairedMode: PropTypes.bool.isRequired
}; };
export default AddNewMovie; export default AddNewMovie;

View File

@ -3,8 +3,10 @@ import React, { Component } from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { createSelector } from 'reselect'; import { createSelector } from 'reselect';
import { clearAddMovie, lookupMovie } from 'Store/Actions/addMovieActions'; import { clearAddMovie, lookupMovie } from 'Store/Actions/addMovieActions';
import { clearQueueDetails, fetchQueueDetails } from 'Store/Actions/queueActions';
import { fetchRootFolders } from 'Store/Actions/rootFolderActions'; import { fetchRootFolders } from 'Store/Actions/rootFolderActions';
import { fetchImportExclusions } from 'Store/Actions/Settings/importExclusions'; import { fetchImportExclusions } from 'Store/Actions/Settings/importExclusions';
import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector';
import parseUrl from 'Utilities/String/parseUrl'; import parseUrl from 'Utilities/String/parseUrl';
import AddNewMovie from './AddNewMovie'; import AddNewMovie from './AddNewMovie';
@ -13,13 +15,15 @@ function createMapStateToProps() {
(state) => state.addMovie, (state) => state.addMovie,
(state) => state.movies.items.length, (state) => state.movies.items.length,
(state) => state.router.location, (state) => state.router.location,
(addMovie, existingMoviesCount, location) => { createUISettingsSelector(),
(addMovie, existingMoviesCount, location, uiSettings) => {
const { params } = parseUrl(location.search); const { params } = parseUrl(location.search);
return { return {
...addMovie, ...addMovie,
term: params.term, term: params.term,
hasExistingMovies: existingMoviesCount > 0 hasExistingMovies: existingMoviesCount > 0,
colorImpairedMode: uiSettings.enableColorImpairedMode
}; };
} }
); );
@ -29,7 +33,9 @@ const mapDispatchToProps = {
lookupMovie, lookupMovie,
clearAddMovie, clearAddMovie,
fetchRootFolders, fetchRootFolders,
fetchImportExclusions fetchImportExclusions,
fetchQueueDetails,
clearQueueDetails
}; };
class AddNewMovieConnector extends Component { class AddNewMovieConnector extends Component {
@ -46,6 +52,7 @@ class AddNewMovieConnector extends Component {
componentDidMount() { componentDidMount() {
this.props.fetchRootFolders(); this.props.fetchRootFolders();
this.props.fetchImportExclusions(); this.props.fetchImportExclusions();
this.props.fetchQueueDetails();
} }
componentWillUnmount() { componentWillUnmount() {
@ -54,6 +61,7 @@ class AddNewMovieConnector extends Component {
} }
this.props.clearAddMovie(); this.props.clearAddMovie();
this.props.clearQueueDetails();
} }
// //
@ -102,7 +110,9 @@ AddNewMovieConnector.propTypes = {
lookupMovie: PropTypes.func.isRequired, lookupMovie: PropTypes.func.isRequired,
clearAddMovie: PropTypes.func.isRequired, clearAddMovie: PropTypes.func.isRequired,
fetchRootFolders: PropTypes.func.isRequired, fetchRootFolders: PropTypes.func.isRequired,
fetchImportExclusions: PropTypes.func.isRequired fetchImportExclusions: PropTypes.func.isRequired,
fetchQueueDetails: PropTypes.func.isRequired,
clearQueueDetails: PropTypes.func.isRequired
}; };
export default connect(createMapStateToProps, mapDispatchToProps)(AddNewMovieConnector); export default connect(createMapStateToProps, mapDispatchToProps)(AddNewMovieConnector);

View File

@ -27,9 +27,11 @@
} }
.poster { .poster {
flex: 0 0 170px; position: relative;
display: block;
margin-right: 20px; margin-right: 20px;
height: 250px; height: 250px;
background-color: $defaultColor;
} }
.content { .content {
@ -86,6 +88,15 @@
pointer-events: all; pointer-events: all;
} }
.posterContainer {
position: relative;
}
.statusContainer {
margin-right: 22px;
font-weight: bold;
}
@media only screen and (max-width: $breakpointMedium) { @media only screen and (max-width: $breakpointMedium) {
.titleRow { .titleRow {
justify-content: space-between; justify-content: space-between;

View File

@ -7,6 +7,8 @@ import Link from 'Components/Link/Link';
import Tooltip from 'Components/Tooltip/Tooltip'; import Tooltip from 'Components/Tooltip/Tooltip';
import { icons, kinds, sizes, tooltipPositions } from 'Helpers/Props'; import { icons, kinds, sizes, tooltipPositions } from 'Helpers/Props';
import MovieDetailsLinks from 'Movie/Details/MovieDetailsLinks'; import MovieDetailsLinks from 'Movie/Details/MovieDetailsLinks';
import MovieStatusLabel from 'Movie/Details/MovieStatusLabel';
import MovieIndexProgressBar from 'Movie/Index/ProgressBar/MovieIndexProgressBar';
import MoviePoster from 'Movie/MoviePoster'; import MoviePoster from 'Movie/MoviePoster';
import translate from 'Utilities/String/translate'; import translate from 'Utilities/String/translate';
import AddNewMovieModal from './AddNewMovieModal'; import AddNewMovieModal from './AddNewMovieModal';
@ -65,7 +67,14 @@ class AddNewMovieSearchResult extends Component {
images, images,
isExistingMovie, isExistingMovie,
isExclusionMovie, isExclusionMovie,
isSmallScreen isSmallScreen,
colorImpairedMode,
id,
monitored,
hasFile,
isAvailable,
queueStatus,
queueState
} = this.props; } = this.props;
const { const {
@ -85,12 +94,30 @@ class AddNewMovieSearchResult extends Component {
{ {
isSmallScreen ? isSmallScreen ?
null : null :
<MoviePoster <div>
className={styles.poster} <div className={styles.posterContainer}>
images={images} <MoviePoster
size={250} className={styles.poster}
overflow={true} images={images}
/> size={250}
overflow={true}
/>
</div>
{
isExistingMovie &&
<MovieIndexProgressBar
monitored={monitored}
hasFile={hasFile}
status={status}
posterWidth={167}
detailedProgressBar={true}
queueStatus={queueStatus}
queueState={queueState}
isAvailable={isAvailable}
/>
}
</div>
} }
<div className={styles.content}> <div className={styles.content}>
@ -176,13 +203,15 @@ class AddNewMovieSearchResult extends Component {
/> />
{ {
status === 'ended' && isExistingMovie && isSmallScreen &&
<Label <MovieStatusLabel
kind={kinds.DANGER} hasMovieFiles={hasFile}
size={sizes.LARGE} monitored={monitored}
> isAvailable={isAvailable}
Ended id={id}
</Label> useLabel={true}
colorImpairedMode={colorImpairedMode}
/>
} }
</div> </div>
@ -222,7 +251,15 @@ AddNewMovieSearchResult.propTypes = {
images: PropTypes.arrayOf(PropTypes.object).isRequired, images: PropTypes.arrayOf(PropTypes.object).isRequired,
isExistingMovie: PropTypes.bool.isRequired, isExistingMovie: PropTypes.bool.isRequired,
isExclusionMovie: PropTypes.bool.isRequired, isExclusionMovie: PropTypes.bool.isRequired,
isSmallScreen: PropTypes.bool.isRequired isSmallScreen: PropTypes.bool.isRequired,
id: PropTypes.number,
queueItems: PropTypes.arrayOf(PropTypes.object),
monitored: PropTypes.bool.isRequired,
hasFile: PropTypes.bool.isRequired,
isAvailable: PropTypes.bool.isRequired,
colorImpairedMode: PropTypes.bool,
queueStatus: PropTypes.string,
queueState: PropTypes.string
}; };
export default AddNewMovieSearchResult; export default AddNewMovieSearchResult;

View File

@ -10,11 +10,17 @@ function createMapStateToProps() {
createExistingMovieSelector(), createExistingMovieSelector(),
createExclusionMovieSelector(), createExclusionMovieSelector(),
createDimensionsSelector(), createDimensionsSelector(),
(isExistingMovie, isExclusionMovie, dimensions) => { (state) => state.queue.details.items,
(state) => state.tmdbId,
(isExistingMovie, isExclusionMovie, dimensions, queueItems, tmdbId) => {
const firstQueueItem = queueItems.find((q) => q.tmdbId === tmdbId);
return { return {
isExistingMovie, isExistingMovie,
isExclusionMovie, isExclusionMovie,
isSmallScreen: dimensions.isSmallScreen isSmallScreen: dimensions.isSmallScreen,
queueStatus: firstQueueItem ? firstQueueItem.status : null,
queueState: firstQueueItem ? firstQueueItem.trackedDownloadState : null
}; };
} }
); );

View File

@ -19,6 +19,10 @@
&.outline { &.outline {
color: $dangerColor; color: $dangerColor;
} }
&:global(.colorImpaired) {
background: repeating-linear-gradient(90deg, color($dangerColor shade(5%)), color($dangerColor shade(5%)) 5px, color($dangerColor shade(15%)) 5px, color($dangerColor shade(15%)) 10px);
}
} }
.default { .default {
@ -85,6 +89,10 @@
&.outline { &.outline {
color: $warningColor; color: $warningColor;
} }
&:global(.colorImpaired) {
background: repeating-linear-gradient(45deg, $warningColor, $warningColor 5px, color($warningColor tint(15%)) 5px, color($warningColor tint(15%)) 10px);
}
} }
.queue { .queue {

View File

@ -11,6 +11,7 @@ function Label(props) {
size, size,
outline, outline,
children, children,
colorImpairedMode,
...otherProps ...otherProps
} = props; } = props;
@ -20,7 +21,8 @@ function Label(props) {
className, className,
styles[kind], styles[kind],
styles[size], styles[size],
outline && styles.outline outline && styles.outline,
colorImpairedMode && 'colorImpaired'
)} )}
{...otherProps} {...otherProps}
> >
@ -34,14 +36,16 @@ Label.propTypes = {
kind: PropTypes.oneOf(kinds.all).isRequired, kind: PropTypes.oneOf(kinds.all).isRequired,
size: PropTypes.oneOf(sizes.all).isRequired, size: PropTypes.oneOf(sizes.all).isRequired,
outline: PropTypes.bool.isRequired, outline: PropTypes.bool.isRequired,
children: PropTypes.node.isRequired children: PropTypes.node.isRequired,
colorImpairedMode: PropTypes.bool
}; };
Label.defaultProps = { Label.defaultProps = {
className: styles.label, className: styles.label,
kind: kinds.DEFAULT, kind: kinds.DEFAULT,
size: sizes.SMALL, size: sizes.SMALL,
outline: false outline: false,
colorImpairedMode: false
}; };
export default Label; export default Label;

View File

@ -286,7 +286,7 @@ class MovieDetails extends Component {
onMonitorTogglePress, onMonitorTogglePress,
onRefreshPress, onRefreshPress,
onSearchPress, onSearchPress,
queueDetails, queueItems,
movieRuntimeFormat movieRuntimeFormat
} = this.props; } = this.props;
@ -523,7 +523,7 @@ class MovieDetails extends Component {
hasMovieFiles={hasMovieFiles} hasMovieFiles={hasMovieFiles}
monitored={monitored} monitored={monitored}
isAvailable={isAvailable} isAvailable={isAvailable}
queueDetails={queueDetails} queueItem={(queueItems.length > 0) ? queueItems[0] : null}
/> />
</span> </span>
</InfoLabel> </InfoLabel>
@ -794,7 +794,7 @@ MovieDetails.propTypes = {
onRefreshPress: PropTypes.func.isRequired, onRefreshPress: PropTypes.func.isRequired,
onSearchPress: PropTypes.func.isRequired, onSearchPress: PropTypes.func.isRequired,
onGoToMovie: PropTypes.func.isRequired, onGoToMovie: PropTypes.func.isRequired,
queueDetails: PropTypes.object, queueItems: PropTypes.arrayOf(PropTypes.object),
movieRuntimeFormat: PropTypes.string.isRequired movieRuntimeFormat: PropTypes.string.isRequired
}; };

View File

@ -89,10 +89,10 @@ function createMapStateToProps() {
createAllMoviesSelector(), createAllMoviesSelector(),
createCommandsSelector(), createCommandsSelector(),
createDimensionsSelector(), createDimensionsSelector(),
(state) => state.queue.details, (state) => state.queue.details.items,
(state) => state.app.isSidebarVisible, (state) => state.app.isSidebarVisible,
(state) => state.settings.ui.item.movieRuntimeFormat, (state) => state.settings.ui.item.movieRuntimeFormat,
(titleSlug, movieFiles, movieCredits, extraFiles, allMovies, commands, dimensions, queueDetails, isSidebarVisible, movieRuntimeFormat) => { (titleSlug, movieFiles, movieCredits, extraFiles, allMovies, commands, dimensions, queueItems, isSidebarVisible, movieRuntimeFormat) => {
const sortedMovies = _.orderBy(allMovies, 'sortTitle'); const sortedMovies = _.orderBy(allMovies, 'sortTitle');
const movieIndex = _.findIndex(sortedMovies, { titleSlug }); const movieIndex = _.findIndex(sortedMovies, { titleSlug });
const movie = sortedMovies[movieIndex]; const movie = sortedMovies[movieIndex];
@ -165,7 +165,7 @@ function createMapStateToProps() {
nextMovie, nextMovie,
isSmallScreen: dimensions.isSmallScreen, isSmallScreen: dimensions.isSmallScreen,
isSidebarVisible, isSidebarVisible,
queueDetails, queueItems,
movieRuntimeFormat movieRuntimeFormat
}; };
} }

View File

@ -1,24 +1,29 @@
.missing {
padding-left: 2px;
border-left: 4px solid $dangerColor;
}
.downloaded {
padding-left: 2px;
border-left: 4px solid $successColor;
}
.notAvailable {
padding-left: 2px;
border-left: 4px solid $primaryColor;
}
.unmonitored {
padding-left: 2px;
border-left: 4px solid $warningColor;
}
.queue { .queue {
padding-left: 2px; padding-left: 2px;
border-left: 4px solid $queueColor; border-left: 4px solid $queueColor;
} }
.continuing {
padding-left: 2px;
border-left: 4px solid $primaryColor;
}
.availNotMonitored {
padding-left: 2px;
border-left: 4px solid $darkGray;
}
.ended {
padding-left: 2px;
border-left: 4px solid $successColor;
}
.missingMonitored {
padding-left: 2px;
border-left: 4px solid $dangerColor;
}
.missingUnmonitored {
padding-left: 2px;
border-left: 4px solid $warningColor;
}

View File

@ -1,15 +1,17 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React from 'react'; import React from 'react';
import Label from 'Components/Label';
import { kinds, sizes } from 'Helpers/Props';
import getQueueStatusText from 'Utilities/Movie/getQueueStatusText'; import getQueueStatusText from 'Utilities/Movie/getQueueStatusText';
import firstCharToUpper from 'Utilities/String/firstCharToUpper'; import firstCharToUpper from 'Utilities/String/firstCharToUpper';
import translate from 'Utilities/String/translate'; import translate from 'Utilities/String/translate';
import styles from './MovieStatusLabel.css'; import styles from './MovieStatusLabel.css';
function getMovieStatus(hasFile, isMonitored, isAvailable, queueDetails = false) { function getMovieStatus(hasFile, isMonitored, isAvailable, queueItem = false) {
if (queueDetails.items[0]) { if (queueItem) {
const queueStatus = queueDetails.items[0].status; const queueStatus = queueItem.status;
const queueState = queueDetails.items[0].trackedDownloadStatus; const queueState = queueItem.trackedDownloadStatus;
const queueStatusText = getQueueStatusText(queueStatus, queueState); const queueStatusText = getQueueStatusText(queueStatus, queueState);
if (queueStatusText) { if (queueStatusText) {
@ -17,19 +19,23 @@ function getMovieStatus(hasFile, isMonitored, isAvailable, queueDetails = false)
} }
} }
if (hasFile) { if (hasFile && !isMonitored) {
return 'downloaded'; return 'availNotMonitored';
} }
if (!isMonitored) { if (hasFile) {
return 'unmonitored'; return 'ended';
}
if (isAvailable && !isMonitored && !hasFile) {
return 'missingUnmonitored';
} }
if (isAvailable && !hasFile) { if (isAvailable && !hasFile) {
return 'missing'; return 'missingMonitored';
} }
return 'notAvailable'; return 'continuing';
} }
function MovieStatusLabel(props) { function MovieStatusLabel(props) {
@ -37,16 +43,61 @@ function MovieStatusLabel(props) {
hasMovieFiles, hasMovieFiles,
monitored, monitored,
isAvailable, isAvailable,
queueDetails queueItem,
useLabel,
colorImpairedMode
} = props; } = props;
const status = getMovieStatus(hasMovieFiles, monitored, isAvailable, queueDetails); let status = getMovieStatus(hasMovieFiles, monitored, isAvailable, queueItem);
let statusClass = status; let statusClass = status;
if (queueDetails.items.length) { if (status === 'availNotMonitored' || status === 'ended') {
status = 'downloaded';
}
if (status === 'missingMonitored' || status === 'missingUnmonitored') {
status = 'missing';
}
if (status === 'continuing') {
status = 'notAvailable';
}
if (queueItem) {
statusClass = 'queue'; statusClass = 'queue';
} }
if (useLabel) {
let kind = kinds.SUCCESS;
switch (statusClass) {
case 'queue':
kind = kinds.QUEUE;
break;
case 'missingMonitored':
kind = kinds.DANGER;
break;
case 'continuing':
kind = kinds.INFO;
break;
case 'availNotMonitored':
kind = kinds.DEFAULT;
break;
case 'missingUnmonitored':
kind = kinds.WARNING;
break;
default:
}
return (
<Label
kind={kind}
size={sizes.LARGE}
colorImpairedMode={colorImpairedMode}
>
{translate(firstCharToUpper(status))}
</Label>
);
}
return ( return (
<span <span
className={styles[statusClass]} className={styles[statusClass]}
@ -60,7 +111,9 @@ MovieStatusLabel.propTypes = {
hasMovieFiles: PropTypes.bool.isRequired, hasMovieFiles: PropTypes.bool.isRequired,
monitored: PropTypes.bool.isRequired, monitored: PropTypes.bool.isRequired,
isAvailable: PropTypes.bool.isRequired, isAvailable: PropTypes.bool.isRequired,
queueDetails: PropTypes.object queueItem: PropTypes.object,
useLabel: PropTypes.bool,
colorImpairedMode: PropTypes.bool
}; };
MovieStatusLabel.defaultProps = { MovieStatusLabel.defaultProps = {

View File

@ -13,7 +13,7 @@ function getProgressBarKind(status, monitored, hasFile, isAvailable, queue = fal
return kinds.DEFAULT; return kinds.DEFAULT;
} }
if (isAvailable) { if (isAvailable && monitored) {
return kinds.DANGER; return kinds.DANGER;
} }