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.
Sonarr/frontend/src/Series/Details/SeriesDetailsConnector.js

266 lines
7.4 KiB

import _ from 'lodash';
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { findCommand, isCommandExecuting } from 'Utilities/Command';
import { registerPagePopulator, unregisterPagePopulator } from 'Utilities/pagePopulator';
import filterAlternateTitles from 'Utilities/Series/filterAlternateTitles';
import createAllSeriesSelector from 'Store/Selectors/createAllSeriesSelector';
import createCommandsSelector from 'Store/Selectors/createCommandsSelector';
import { fetchEpisodes, clearEpisodes } from 'Store/Actions/episodeActions';
import { fetchEpisodeFiles, clearEpisodeFiles } from 'Store/Actions/episodeFileActions';
import { toggleSeriesMonitored } from 'Store/Actions/seriesActions';
import { fetchQueueDetails, clearQueueDetails } from 'Store/Actions/queueActions';
import { executeCommand } from 'Store/Actions/commandActions';
import * as commandNames from 'Commands/commandNames';
import SeriesDetails from './SeriesDetails';
const selectEpisodes = createSelector(
(state) => state.episodes,
(episodes) => {
const {
items,
isFetching,
isPopulated,
error
} = episodes;
const hasEpisodes = !!items.length;
const hasMonitoredEpisodes = items.some((e) => e.monitored);
return {
isEpisodesFetching: isFetching,
isEpisodesPopulated: isPopulated,
episodesError: error,
hasEpisodes,
hasMonitoredEpisodes
};
}
);
const selectEpisodeFiles = createSelector(
(state) => state.episodeFiles,
(episodeFiles) => {
const {
items,
isFetching,
isPopulated,
error
} = episodeFiles;
const hasEpisodeFiles = !!items.length;
return {
isEpisodeFilesFetching: isFetching,
isEpisodeFilesPopulated: isPopulated,
episodeFilesError: error,
hasEpisodeFiles
};
}
);
function createMapStateToProps() {
return createSelector(
(state, { titleSlug }) => titleSlug,
selectEpisodes,
selectEpisodeFiles,
createAllSeriesSelector(),
createCommandsSelector(),
(titleSlug, episodes, episodeFiles, allSeries, commands) => {
const sortedSeries = _.orderBy(allSeries, 'sortTitle');
const seriesIndex = _.findIndex(sortedSeries, { titleSlug });
const series = sortedSeries[seriesIndex];
if (!series) {
return {};
}
const {
isEpisodesFetching,
isEpisodesPopulated,
episodesError,
hasEpisodes,
hasMonitoredEpisodes
} = episodes;
const {
isEpisodeFilesFetching,
isEpisodeFilesPopulated,
episodeFilesError,
hasEpisodeFiles
} = episodeFiles;
const previousSeries = sortedSeries[seriesIndex - 1] || _.last(sortedSeries);
const nextSeries = sortedSeries[seriesIndex + 1] || _.first(sortedSeries);
const isSeriesRefreshing = isCommandExecuting(findCommand(commands, { name: commandNames.REFRESH_SERIES, seriesId: series.id }));
const seriesRefreshingCommand = findCommand(commands, { name: commandNames.REFRESH_SERIES });
const allSeriesRefreshing = (
isCommandExecuting(seriesRefreshingCommand) &&
!seriesRefreshingCommand.body.seriesId
);
const isRefreshing = isSeriesRefreshing || allSeriesRefreshing;
const isSearching = isCommandExecuting(findCommand(commands, { name: commandNames.SERIES_SEARCH, seriesId: series.id }));
const isRenamingFiles = isCommandExecuting(findCommand(commands, { name: commandNames.RENAME_FILES, seriesId: series.id }));
const isRenamingSeriesCommand = findCommand(commands, { name: commandNames.RENAME_SERIES });
const isRenamingSeries = (
isCommandExecuting(isRenamingSeriesCommand) &&
isRenamingSeriesCommand.body.seriesIds.indexOf(series.id) > -1
);
const isFetching = isEpisodesFetching || isEpisodeFilesFetching;
const isPopulated = isEpisodesPopulated && isEpisodeFilesPopulated;
const alternateTitles = filterAlternateTitles(series.alternateTitles, series.title, series.useSceneNumbering);
return {
...series,
alternateTitles,
isSeriesRefreshing,
allSeriesRefreshing,
isRefreshing,
isSearching,
isRenamingFiles,
isRenamingSeries,
isFetching,
isPopulated,
episodesError,
episodeFilesError,
hasEpisodes,
hasMonitoredEpisodes,
hasEpisodeFiles,
previousSeries,
nextSeries
};
}
);
}
const mapDispatchToProps = {
fetchEpisodes,
clearEpisodes,
fetchEpisodeFiles,
clearEpisodeFiles,
toggleSeriesMonitored,
fetchQueueDetails,
clearQueueDetails,
executeCommand
};
class SeriesDetailsConnector extends Component {
//
// Lifecycle
componentDidMount() {
registerPagePopulator(this.populate);
this.populate();
}
componentDidUpdate(prevProps) {
const {
id,
isSeriesRefreshing,
allSeriesRefreshing,
isRenamingFiles,
isRenamingSeries
} = this.props;
if (
(prevProps.isSeriesRefreshing && !isSeriesRefreshing) ||
(prevProps.allSeriesRefreshing && !allSeriesRefreshing) ||
(prevProps.isRenamingFiles && !isRenamingFiles) ||
(prevProps.isRenamingSeries && !isRenamingSeries)
) {
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 seriesId = this.props.id;
this.props.fetchEpisodes({ seriesId });
this.props.fetchEpisodeFiles({ seriesId });
this.props.fetchQueueDetails({ seriesId });
}
unpopulate = () => {
this.props.clearEpisodes();
this.props.clearEpisodeFiles();
this.props.clearQueueDetails();
}
//
// Listeners
onMonitorTogglePress = (monitored) => {
this.props.toggleSeriesMonitored({
seriesId: this.props.id,
monitored
});
}
onRefreshPress = () => {
this.props.executeCommand({
name: commandNames.REFRESH_SERIES,
seriesId: this.props.id
});
}
onSearchPress = () => {
this.props.executeCommand({
name: commandNames.SERIES_SEARCH,
seriesId: this.props.id
});
}
//
// Render
render() {
return (
<SeriesDetails
{...this.props}
onMonitorTogglePress={this.onMonitorTogglePress}
onRefreshPress={this.onRefreshPress}
onSearchPress={this.onSearchPress}
/>
);
}
}
SeriesDetailsConnector.propTypes = {
id: PropTypes.number.isRequired,
titleSlug: PropTypes.string.isRequired,
isSeriesRefreshing: PropTypes.bool.isRequired,
allSeriesRefreshing: PropTypes.bool.isRequired,
isRefreshing: PropTypes.bool.isRequired,
isRenamingFiles: PropTypes.bool.isRequired,
isRenamingSeries: PropTypes.bool.isRequired,
fetchEpisodes: PropTypes.func.isRequired,
clearEpisodes: PropTypes.func.isRequired,
fetchEpisodeFiles: PropTypes.func.isRequired,
clearEpisodeFiles: PropTypes.func.isRequired,
toggleSeriesMonitored: PropTypes.func.isRequired,
fetchQueueDetails: PropTypes.func.isRequired,
clearQueueDetails: PropTypes.func.isRequired,
executeCommand: PropTypes.func.isRequired
};
export default connect(createMapStateToProps, mapDispatchToProps)(SeriesDetailsConnector);