You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Radarr/frontend/src/Movie/Details/MovieDetailsConnector.js

395 lines
12 KiB

import { push } from 'connected-react-router';
import _ from 'lodash';
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import * as commandNames from 'Commands/commandNames';
import { executeCommand } from 'Store/Actions/commandActions';
import { clearExtraFiles, fetchExtraFiles } from 'Store/Actions/extraFileActions';
import { toggleMovieMonitored } from 'Store/Actions/movieActions';
import { clearMovieBlocklist, fetchMovieBlocklist } from 'Store/Actions/movieBlocklistActions';
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';
import createAllMoviesSelector from 'Store/Selectors/createAllMoviesSelector';
import createCommandsSelector from 'Store/Selectors/createCommandsSelector';
import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector';
import createMovieClientSideCollectionItemsSelector from 'Store/Selectors/createMovieClientSideCollectionItemsSelector';
import { findCommand, isCommandExecuting } from 'Utilities/Command';
import { registerPagePopulator, unregisterPagePopulator } from 'Utilities/pagePopulator';
import MovieDetails from './MovieDetails';
const selectMovieFiles = createSelector(
(state) => state.movieFiles,
(movieFiles) => {
const {
items,
isFetching,
isPopulated,
error
} = movieFiles;
const hasMovieFiles = !!items.length;
const sizeOnDisk = items.map((item) => item.size).reduce((prev, curr) => prev + curr, 0);
return {
isMovieFilesFetching: isFetching,
isMovieFilesPopulated: isPopulated,
movieFilesError: error,
hasMovieFiles,
sizeOnDisk
};
}
);
const selectMovieCredits = createSelector(
(state) => state.movieCredits,
(movieCredits) => {
const {
isFetching,
isPopulated,
error
} = movieCredits;
return {
isMovieCreditsFetching: isFetching,
isMovieCreditsPopulated: isPopulated,
movieCreditsError: error
};
}
);
const selectExtraFiles = createSelector(
(state) => state.extraFiles,
(extraFiles) => {
const {
isFetching,
isPopulated,
error
} = extraFiles;
return {
isExtraFilesFetching: isFetching,
isExtraFilesPopulated: isPopulated,
extraFilesError: error
};
}
);
function createMapStateToProps() {
return createSelector(
(state, { titleSlug }) => titleSlug,
selectMovieFiles,
selectMovieCredits,
selectExtraFiles,
createMovieClientSideCollectionItemsSelector('movieIndex'),
createAllMoviesSelector(),
createCommandsSelector(),
createDimensionsSelector(),
(state) => state.queue.details.items,
(state) => state.app.isSidebarVisible,
(state) => state.settings.ui.item.movieRuntimeFormat,
(state) => state.settings.ui.item.movieDetailsNextPrevBehavior,
(state) => state.movieIndex.selectedFilterKey,
(state) => state.customFilters.items.filter((s) => s.type === 'movieIndex'),
(titleSlug, movieFiles, movieCredits, extraFiles, collectionMovies, allMovies, commands, dimensions, queueItems, isSidebarVisible, movieRuntimeFormat, movieDetailsNextPrevBehavior, indexFilter, customFilters) => {
let sortedMovies = [];
if (movieDetailsNextPrevBehavior === 'filter') {
collectionMovies.items.forEach((c) => sortedMovies.push(allMovies.find((a) => a.id === c.id)));
} else {
sortedMovies = _.orderBy(allMovies, 'sortTitle');
}
let movieIndex = _.findIndex(sortedMovies, { titleSlug });
if (customFilters.length && Number.isInteger(indexFilter)) {
indexFilter = customFilters.find((filter) => (filter.id === indexFilter)).label;
}
if (movieIndex === -1) {
sortedMovies.push(allMovies.find((a) => a.titleSlug === titleSlug));
movieIndex = sortedMovies.length - 1;
}
const movie = sortedMovies[movieIndex];
const nextPrev = sortedMovies.length > 1;
if (!movie) {
return {};
}
const {
isMovieFilesFetching,
isMovieFilesPopulated,
movieFilesError,
hasMovieFiles,
sizeOnDisk
} = movieFiles;
const {
isMovieCreditsFetching,
isMovieCreditsPopulated,
movieCreditsError
} = movieCredits;
const {
isExtraFilesFetching,
isExtraFilesPopulated,
extraFilesError
} = extraFiles;
const previousMovie = sortedMovies[movieIndex - 1] || _.last(sortedMovies);
const nextMovie = sortedMovies[movieIndex + 1] || _.first(sortedMovies);
const isMovieRefreshing = isCommandExecuting(findCommand(commands, { name: commandNames.REFRESH_MOVIE, movieIds: [movie.id] }));
const movieRefreshingCommand = findCommand(commands, { name: commandNames.REFRESH_MOVIE });
const allMoviesRefreshing = (
isCommandExecuting(movieRefreshingCommand) &&
!movieRefreshingCommand.body.movieId
);
const isRefreshing = isMovieRefreshing || allMoviesRefreshing;
const isSearching = isCommandExecuting(findCommand(commands, { name: commandNames.MOVIE_SEARCH, movieIds: [movie.id] }));
const isRenamingFiles = isCommandExecuting(findCommand(commands, { name: commandNames.RENAME_FILES, movieId: movie.id }));
const isRenamingMovieCommand = findCommand(commands, { name: commandNames.RENAME_MOVIE });
const isRenamingMovie = (
isCommandExecuting(isRenamingMovieCommand) &&
isRenamingMovieCommand.body.movieIds.indexOf(movie.id) > -1
);
const isFetching = isMovieFilesFetching || isMovieCreditsFetching || isExtraFilesFetching;
const isPopulated = isMovieFilesPopulated && isMovieCreditsPopulated && isExtraFilesPopulated;
const alternateTitles = _.reduce(movie.alternateTitles, (acc, alternateTitle) => {
acc.push(alternateTitle.title);
return acc;
}, []);
return {
...movie,
alternateTitles,
isMovieRefreshing,
allMoviesRefreshing,
isRefreshing,
isSearching,
isRenamingFiles,
isRenamingMovie,
isFetching,
isPopulated,
movieFilesError,
movieCreditsError,
extraFilesError,
hasMovieFiles,
sizeOnDisk,
previousMovie,
nextMovie,
isSmallScreen: dimensions.isSmallScreen,
isSidebarVisible,
queueItems,
movieRuntimeFormat,
indexFilter,
nextPrev
};
}
);
}
function createMapDispatchToProps(dispatch, props) {
return {
dispatchFetchMovieFiles({ movieId }) {
dispatch(fetchMovieFiles({ movieId }));
},
dispatchClearMovieFiles() {
dispatch(clearMovieFiles());
},
dispatchFetchMovieHistory({ movieId }) {
dispatch(fetchMovieHistory({ movieId }));
},
dispatchClearMovieHistory() {
dispatch(clearMovieHistory());
},
dispatchFetchMovieCredits({ movieId }) {
dispatch(fetchMovieCredits({ movieId }));
},
dispatchClearMovieCredits() {
dispatch(clearMovieCredits());
},
dispatchFetchExtraFiles({ movieId }) {
dispatch(fetchExtraFiles({ movieId }));
},
dispatchClearExtraFiles() {
dispatch(clearExtraFiles());
},
dispatchClearReleases() {
dispatch(clearReleases());
},
dispatchCancelFetchReleases() {
dispatch(cancelFetchReleases());
},
dispatchFetchQueueDetails({ movieId }) {
dispatch(fetchQueueDetails({ movieId }));
},
dispatchClearQueueDetails() {
dispatch(clearQueueDetails());
},
dispatchFetchImportListSchema() {
dispatch(fetchImportListSchema());
},
dispatchToggleMovieMonitored(payload) {
dispatch(toggleMovieMonitored(payload));
},
dispatchExecuteCommand(payload) {
dispatch(executeCommand(payload));
},
onGoToMovie(titleSlug) {
dispatch(push(`${window.Radarr.urlBase}/movie/${titleSlug}`));
},
dispatchFetchMovieBlocklist({ movieId }) {
dispatch(fetchMovieBlocklist({ movieId }));
},
dispatchClearMovieBlocklist() {
dispatch(clearMovieBlocklist());
}
};
}
class MovieDetailsConnector extends Component {
//
// Lifecycle
componentDidMount() {
registerPagePopulator(this.populate);
this.populate();
}
componentDidUpdate(prevProps) {
const {
id,
isMovieRefreshing,
allMoviesRefreshing,
isRenamingFiles,
isRenamingMovie
} = this.props;
if (
(prevProps.isMovieRefreshing && !isMovieRefreshing) ||
(prevProps.allMoviesRefreshing && !allMoviesRefreshing) ||
(prevProps.isRenamingFiles && !isRenamingFiles) ||
(prevProps.isRenamingMovie && !isRenamingMovie)
) {
this.populate();
}
// If the id has changed we need to clear the episodes/episode
// files and fetch from the server.
if (prevProps.id !== id) {
this.unpopulate();
this.populate();
}
}
componentWillUnmount() {
unregisterPagePopulator(this.populate);
this.unpopulate();
}
//
// Control
populate = () => {
const movieId = this.props.id;
this.props.dispatchFetchMovieFiles({ movieId });
this.props.dispatchFetchMovieBlocklist({ movieId });
this.props.dispatchFetchMovieHistory({ movieId });
this.props.dispatchFetchExtraFiles({ movieId });
this.props.dispatchFetchMovieCredits({ movieId });
this.props.dispatchFetchQueueDetails({ movieId });
this.props.dispatchFetchImportListSchema();
};
unpopulate = () => {
this.props.dispatchCancelFetchReleases();
this.props.dispatchClearMovieBlocklist();
this.props.dispatchClearMovieFiles();
this.props.dispatchClearMovieHistory();
this.props.dispatchClearExtraFiles();
this.props.dispatchClearMovieCredits();
this.props.dispatchClearQueueDetails();
this.props.dispatchClearReleases();
};
//
// Listeners
onMonitorTogglePress = (monitored) => {
this.props.dispatchToggleMovieMonitored({
movieId: this.props.id,
monitored
});
};
onRefreshPress = () => {
this.props.dispatchExecuteCommand({
name: commandNames.REFRESH_MOVIE,
movieIds: [this.props.id]
});
};
onSearchPress = () => {
this.props.dispatchExecuteCommand({
name: commandNames.MOVIE_SEARCH,
movieIds: [this.props.id]
});
};
//
// Render
render() {
return (
<MovieDetails
{...this.props}
onMonitorTogglePress={this.onMonitorTogglePress}
onRefreshPress={this.onRefreshPress}
onSearchPress={this.onSearchPress}
/>
);
}
}
MovieDetailsConnector.propTypes = {
id: PropTypes.number.isRequired,
titleSlug: PropTypes.string.isRequired,
isMovieRefreshing: PropTypes.bool.isRequired,
allMoviesRefreshing: PropTypes.bool.isRequired,
isRefreshing: PropTypes.bool.isRequired,
isRenamingFiles: PropTypes.bool.isRequired,
isRenamingMovie: PropTypes.bool.isRequired,
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,
dispatchClearMovieCredits: PropTypes.func.isRequired,
dispatchClearReleases: PropTypes.func.isRequired,
dispatchCancelFetchReleases: PropTypes.func.isRequired,
dispatchToggleMovieMonitored: PropTypes.func.isRequired,
dispatchFetchQueueDetails: PropTypes.func.isRequired,
dispatchClearQueueDetails: PropTypes.func.isRequired,
dispatchFetchImportListSchema: PropTypes.func.isRequired,
dispatchExecuteCommand: PropTypes.func.isRequired,
dispatchFetchMovieBlocklist: PropTypes.func.isRequired,
dispatchClearMovieBlocklist: PropTypes.func.isRequired,
onGoToMovie: PropTypes.func.isRequired
};
export default connect(createMapStateToProps, createMapDispatchToProps)(MovieDetailsConnector);