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.
Readarr/frontend/src/Author/Details/AuthorDetailsConnector.js

349 lines
9.3 KiB

/* eslint max-params: 0 */
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 { toggleAuthorMonitored } from 'Store/Actions/authorActions';
import { clearBookFiles, fetchBookFiles } from 'Store/Actions/bookFileActions';
import { saveBookEditor } from 'Store/Actions/bookIndexActions';
import { executeCommand } from 'Store/Actions/commandActions';
import { clearQueueDetails, fetchQueueDetails } from 'Store/Actions/queueActions';
import { cancelFetchReleases, clearReleases } from 'Store/Actions/releaseActions';
import { clearSeries, fetchSeries } from 'Store/Actions/seriesActions';
import createAllAuthorSelector from 'Store/Selectors/createAllAuthorsSelector';
import createCommandsSelector from 'Store/Selectors/createCommandsSelector';
import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector';
import createSortedSectionSelector from 'Store/Selectors/createSortedSectionSelector';
import { findCommand, isCommandExecuting } from 'Utilities/Command';
import { registerPagePopulator, unregisterPagePopulator } from 'Utilities/pagePopulator';
import AuthorDetails from './AuthorDetails';
const selectBooks = createSelector(
(state) => state.books,
(state) => state.bookIndex,
(books, index) => {
const {
items,
isFetching,
isPopulated,
error
} = books;
const {
isSaving,
saveError,
isDeleting,
deleteError
} = index;
const hasBooks = !!items.length;
const hasMonitoredBooks = items.some((e) => e.monitored);
return {
isBooksFetching: isFetching,
isBooksPopulated: isPopulated,
booksError: error,
hasBooks,
hasMonitoredBooks,
isSaving,
saveError,
isDeleting,
deleteError
};
}
);
const selectSeries = createSelector(
createSortedSectionSelector('series', (a, b) => a.title.localeCompare(b.title)),
(state) => state.series,
(series) => {
const {
items,
isFetching,
isPopulated,
error
} = series;
const hasSeries = !!items.length;
return {
isSeriesFetching: isFetching,
isSeriesPopulated: isPopulated,
seriesError: error,
hasSeries,
series: series.items
};
}
);
const selectBookFiles = createSelector(
(state) => state.bookFiles,
(bookFiles) => {
const {
items,
isFetching,
isPopulated,
error
} = bookFiles;
const hasBookFiles = !!items.length;
return {
isBookFilesFetching: isFetching,
isBookFilesPopulated: isPopulated,
bookFilesError: error,
hasBookFiles
};
}
);
function createMapStateToProps() {
return createSelector(
(state, { titleSlug }) => titleSlug,
selectBooks,
selectSeries,
selectBookFiles,
createAllAuthorSelector(),
createCommandsSelector(),
createDimensionsSelector(),
(titleSlug, books, series, bookFiles, allAuthors, commands, dimensions) => {
const sortedAuthor = _.orderBy(allAuthors, 'sortNameLastFirst');
const authorIndex = _.findIndex(sortedAuthor, { titleSlug });
const author = sortedAuthor[authorIndex];
if (!author) {
return {};
}
const {
isBooksFetching,
isBooksPopulated,
booksError,
hasBooks,
hasMonitoredBooks,
isSaving,
saveError,
isDeleting,
deleteError
} = books;
const {
isSeriesFetching,
isSeriesPopulated,
seriesError,
hasSeries,
series: seriesItems
} = series;
const {
isBookFilesFetching,
isBookFilesPopulated,
bookFilesError,
hasBookFiles
} = bookFiles;
const previousAuthor = sortedAuthor[authorIndex - 1] || _.last(sortedAuthor);
const nextAuthor = sortedAuthor[authorIndex + 1] || _.first(sortedAuthor);
const isAuthorRefreshing = isCommandExecuting(findCommand(commands, { name: commandNames.REFRESH_AUTHOR, authorId: author.id }));
const authorRefreshingCommand = findCommand(commands, { name: commandNames.REFRESH_AUTHOR });
const allAuthorRefreshing = (
isCommandExecuting(authorRefreshingCommand) &&
!authorRefreshingCommand.body.authorId
);
const isRefreshing = isAuthorRefreshing || allAuthorRefreshing;
const isSearching = isCommandExecuting(findCommand(commands, { name: commandNames.AUTHOR_SEARCH, authorId: author.id }));
const isRenamingFiles = isCommandExecuting(findCommand(commands, { name: commandNames.RENAME_FILES, authorId: author.id }));
const isRenamingAuthorCommand = findCommand(commands, { name: commandNames.RENAME_AUTHOR });
const isRenamingAuthor = (
isCommandExecuting(isRenamingAuthorCommand) &&
isRenamingAuthorCommand.body.authorIds.indexOf(author.id) > -1
);
const isFetching = isBooksFetching || isSeriesFetching || isBookFilesFetching;
const isPopulated = isBooksPopulated && isSeriesPopulated && isBookFilesPopulated;
const alternateTitles = _.reduce(author.alternateTitles, (acc, alternateTitle) => {
if ((alternateTitle.seasonNumber === -1 || alternateTitle.seasonNumber === undefined) &&
(alternateTitle.sceneSeasonNumber === -1 || alternateTitle.sceneSeasonNumber === undefined)) {
acc.push(alternateTitle.title);
}
return acc;
}, []);
return {
...author,
alternateTitles,
isAuthorRefreshing,
allAuthorRefreshing,
isRefreshing,
isSearching,
isRenamingFiles,
isRenamingAuthor,
isFetching,
isPopulated,
booksError,
isSaving,
saveError,
isDeleting,
deleteError,
seriesError,
bookFilesError,
hasBooks,
hasMonitoredBooks,
hasSeries,
series: seriesItems,
hasBookFiles,
previousAuthor,
nextAuthor,
isSmallScreen: dimensions.isSmallScreen
};
}
);
}
const mapDispatchToProps = {
fetchSeries,
clearSeries,
saveBookEditor,
fetchBookFiles,
clearBookFiles,
toggleAuthorMonitored,
fetchQueueDetails,
clearQueueDetails,
clearReleases,
cancelFetchReleases,
executeCommand
};
class AuthorDetailsConnector extends Component {
//
// Lifecycle
componentDidMount() {
registerPagePopulator(this.populate);
this.populate();
}
componentDidUpdate(prevProps) {
const {
id,
isAuthorRefreshing,
allAuthorRefreshing,
isRenamingFiles,
isRenamingAuthor
} = this.props;
if (
(prevProps.isAuthorRefreshing && !isAuthorRefreshing) ||
(prevProps.allAuthorRefreshing && !allAuthorRefreshing) ||
(prevProps.isRenamingFiles && !isRenamingFiles) ||
(prevProps.isRenamingAuthor && !isRenamingAuthor)
) {
this.populate();
}
// If the id has changed we need to clear the books
// files and fetch from the server.
if (prevProps.id !== id) {
this.unpopulate();
this.populate();
}
}
componentWillUnmount() {
unregisterPagePopulator(this.populate);
this.unpopulate();
}
//
// Control
populate = () => {
const authorId = this.props.id;
this.props.fetchSeries({ authorId });
this.props.fetchBookFiles({ authorId });
this.props.fetchQueueDetails({ authorId });
};
unpopulate = () => {
this.props.cancelFetchReleases();
this.props.clearSeries();
this.props.clearBookFiles();
this.props.clearQueueDetails();
this.props.clearReleases();
};
//
// Listeners
onMonitorTogglePress = (monitored) => {
this.props.toggleAuthorMonitored({
authorId: this.props.id,
monitored
});
};
onRefreshPress = () => {
this.props.executeCommand({
name: commandNames.REFRESH_AUTHOR,
authorId: this.props.id
});
};
onSearchPress = () => {
this.props.executeCommand({
name: commandNames.AUTHOR_SEARCH,
authorId: this.props.id
});
};
onSaveSelected = (payload) => {
this.props.saveBookEditor(payload);
};
//
// Render
render() {
return (
<AuthorDetails
{...this.props}
onMonitorTogglePress={this.onMonitorTogglePress}
onRefreshPress={this.onRefreshPress}
onSearchPress={this.onSearchPress}
onSaveSelected={this.onSaveSelected}
/>
);
}
}
AuthorDetailsConnector.propTypes = {
id: PropTypes.number.isRequired,
titleSlug: PropTypes.string.isRequired,
isAuthorRefreshing: PropTypes.bool.isRequired,
allAuthorRefreshing: PropTypes.bool.isRequired,
isRefreshing: PropTypes.bool.isRequired,
isRenamingFiles: PropTypes.bool.isRequired,
isRenamingAuthor: PropTypes.bool.isRequired,
fetchSeries: PropTypes.func.isRequired,
clearSeries: PropTypes.func.isRequired,
saveBookEditor: PropTypes.func.isRequired,
fetchBookFiles: PropTypes.func.isRequired,
clearBookFiles: PropTypes.func.isRequired,
toggleAuthorMonitored: PropTypes.func.isRequired,
fetchQueueDetails: PropTypes.func.isRequired,
clearQueueDetails: PropTypes.func.isRequired,
clearReleases: PropTypes.func.isRequired,
cancelFetchReleases: PropTypes.func.isRequired,
executeCommand: PropTypes.func.isRequired
};
export default connect(createMapStateToProps, mapDispatchToProps)(AuthorDetailsConnector);