From f33e9f2bbc816e9c8e33a3c3108af41a4c997892 Mon Sep 17 00:00:00 2001 From: ta264 Date: Sun, 7 Aug 2022 21:52:42 +0100 Subject: [PATCH] Fixed: Selecting edition for books in manual import --- .../Edition/SelectEditionModalContent.js | 28 +++++++++-- .../SelectEditionModalContentConnector.js | 50 ++++++++++++++++++- .../Edition/SelectEditionRowConnector.js | 32 ++++++++++++ .../Books/Repositories/EditionRepository.cs | 8 +-- .../Books/Services/EditionService.cs | 8 ++- src/NzbDrone.Core/Localization/Core/en.json | 2 + .../Editions/EditionController.cs | 2 +- 7 files changed, 118 insertions(+), 12 deletions(-) create mode 100644 frontend/src/InteractiveImport/Edition/SelectEditionRowConnector.js diff --git a/frontend/src/InteractiveImport/Edition/SelectEditionModalContent.js b/frontend/src/InteractiveImport/Edition/SelectEditionModalContent.js index dc9954fea..e85a30b61 100644 --- a/frontend/src/InteractiveImport/Edition/SelectEditionModalContent.js +++ b/frontend/src/InteractiveImport/Edition/SelectEditionModalContent.js @@ -2,6 +2,7 @@ import PropTypes from 'prop-types'; import React, { Component } from 'react'; import Alert from 'Components/Alert'; import Button from 'Components/Link/Button'; +import LoadingIndicator from 'Components/Loading/LoadingIndicator'; import ModalBody from 'Components/Modal/ModalBody'; import ModalContent from 'Components/Modal/ModalContent'; import ModalFooter from 'Components/Modal/ModalFooter'; @@ -9,7 +10,8 @@ import ModalHeader from 'Components/Modal/ModalHeader'; import Table from 'Components/Table/Table'; import TableBody from 'Components/Table/TableBody'; import { scrollDirections } from 'Helpers/Props'; -import SelectEditionRow from './SelectEditionRow'; +import translate from 'Utilities/String/translate'; +import SelectEditionRowConnector from './SelectEditionRowConnector'; import styles from './SelectEditionModalContent.css'; const columns = [ @@ -33,15 +35,30 @@ class SelectEditionModalContent extends Component { render() { const { books, + isPopulated, + isFetching, + error, onEditionSelect, onModalClose, ...otherProps } = this.props; + if (!isPopulated && !error) { + return (); + } + + if (!isFetching && error) { + return ( +
+ {translate('LoadingEditionsFailed')} +
+ ); + } + return ( - Manual Import - Select Edition + {translate('ManualImportSelectEdition')} { return ( - @@ -86,6 +103,9 @@ class SelectEditionModalContent extends Component { SelectEditionModalContent.propTypes = { books: PropTypes.arrayOf(PropTypes.object).isRequired, + isFetching: PropTypes.bool, + isPopulated: PropTypes.bool, + error: PropTypes.object, onEditionSelect: PropTypes.func.isRequired, onModalClose: PropTypes.func.isRequired }; diff --git a/frontend/src/InteractiveImport/Edition/SelectEditionModalContentConnector.js b/frontend/src/InteractiveImport/Edition/SelectEditionModalContentConnector.js index 564896141..7fb344c5c 100644 --- a/frontend/src/InteractiveImport/Edition/SelectEditionModalContentConnector.js +++ b/frontend/src/InteractiveImport/Edition/SelectEditionModalContentConnector.js @@ -1,27 +1,71 @@ import PropTypes from 'prop-types'; import React, { Component } from 'react'; import { connect } from 'react-redux'; +import { createSelector } from 'reselect'; +import { clearEditions, fetchEditions } from 'Store/Actions/editionActions'; import { saveInteractiveImportItem, updateInteractiveImportItem } from 'Store/Actions/interactiveImportActions'; +import { registerPagePopulator, unregisterPagePopulator } from 'Utilities/pagePopulator'; import SelectEditionModalContent from './SelectEditionModalContent'; function createMapStateToProps() { - return {}; + return createSelector( + (state) => state.editions, + (editions) => { + const { + isFetching, + isPopulated, + error + } = editions; + + return { + isFetching, + isPopulated, + error + }; + } + ); } const mapDispatchToProps = { + fetchEditions, + clearEditions, updateInteractiveImportItem, saveInteractiveImportItem }; class SelectEditionModalContentConnector extends Component { + // + // Lifecycle + + componentDidMount() { + registerPagePopulator(this.populate); + this.populate(); + } + + componentWillUnmount() { + unregisterPagePopulator(this.populate); + this.unpopulate(); + } + // + // Control + + populate = () => { + const bookId = this.props.books.map((b) => b.book.id); + + this.props.fetchEditions({ bookId }); + } + + unpopulate = () => { + this.props.clearEditions(); + } + // // Listeners onEditionSelect = (bookId, foreignEditionId) => { - console.log(`book: ${bookId} id: ${foreignEditionId} ${typeof foreignEditionId}`); const ids = this.props.importIdsByBook[bookId]; ids.forEach((id) => { @@ -55,6 +99,8 @@ class SelectEditionModalContentConnector extends Component { SelectEditionModalContentConnector.propTypes = { importIdsByBook: PropTypes.object.isRequired, books: PropTypes.arrayOf(PropTypes.object).isRequired, + fetchEditions: PropTypes.func.isRequired, + clearEditions: PropTypes.func.isRequired, updateInteractiveImportItem: PropTypes.func.isRequired, saveInteractiveImportItem: PropTypes.func.isRequired, onModalClose: PropTypes.func.isRequired diff --git a/frontend/src/InteractiveImport/Edition/SelectEditionRowConnector.js b/frontend/src/InteractiveImport/Edition/SelectEditionRowConnector.js new file mode 100644 index 000000000..4cdcda585 --- /dev/null +++ b/frontend/src/InteractiveImport/Edition/SelectEditionRowConnector.js @@ -0,0 +1,32 @@ +import PropTypes from 'prop-types'; +import React, { Component } from 'react'; +import { connect } from 'react-redux'; +import { createSelector } from 'reselect'; +import SelectEditionRow from './SelectEditionRow'; + +function createMapStateToProps() { + return createSelector( + (state, { id }) => id, + (state) => state.editions, + (id, editionState) => { + const editions = editionState.items.filter((e) => e.bookId === id); + return { editions }; + } + ); +} + +class SelectEditionRowConnector extends Component { + render() { + return ( + + ); + } +} + +SelectEditionRowConnector.PropTypes = { + editions: PropTypes.arrayOf(PropTypes.object).isRequired +}; + +export default connect(createMapStateToProps)(SelectEditionRowConnector); diff --git a/src/NzbDrone.Core/Books/Repositories/EditionRepository.cs b/src/NzbDrone.Core/Books/Repositories/EditionRepository.cs index 04e408ae6..bdd511d28 100644 --- a/src/NzbDrone.Core/Books/Repositories/EditionRepository.cs +++ b/src/NzbDrone.Core/Books/Repositories/EditionRepository.cs @@ -11,7 +11,7 @@ namespace NzbDrone.Core.Books { List GetAllMonitoredEditions(); Edition FindByForeignEditionId(string foreignEditionId); - List FindByBook(int id); + List FindByBook(IEnumerable ids); List FindByAuthor(int id); List FindByAuthorMetadataId(int id, bool onlyMonitored); Edition FindByTitle(int authorMetadataId, string title); @@ -43,14 +43,14 @@ namespace NzbDrone.Core.Books return Query(r => r.BookId == bookId || foreignEditionIds.Contains(r.ForeignEditionId)); } - public List FindByBook(int id) + public List FindByBook(IEnumerable ids) { // populate the books and author metadata also // this hopefully speeds up the track matching a lot var builder = new SqlBuilder(_database.DatabaseType) .LeftJoin((e, b) => e.BookId == b.Id) .LeftJoin((b, a) => b.AuthorMetadataId == a.Id) - .Where(r => r.BookId == id); + .Where(r => ids.Contains(r.BookId)); return _database.QueryJoined(builder, (edition, book, metadata) => { @@ -96,7 +96,7 @@ namespace NzbDrone.Core.Books public List SetMonitored(Edition edition) { - var allEditions = FindByBook(edition.BookId); + var allEditions = FindByBook(new[] { edition.BookId }); allEditions.ForEach(r => r.Monitored = r.Id == edition.Id); Ensure.That(allEditions.Count(x => x.Monitored) == 1).IsTrue(); UpdateMany(allEditions); diff --git a/src/NzbDrone.Core/Books/Services/EditionService.cs b/src/NzbDrone.Core/Books/Services/EditionService.cs index a7424fe0e..f2e289556 100644 --- a/src/NzbDrone.Core/Books/Services/EditionService.cs +++ b/src/NzbDrone.Core/Books/Services/EditionService.cs @@ -18,6 +18,7 @@ namespace NzbDrone.Core.Books void DeleteMany(List editions); List GetEditionsForRefresh(int bookId, List foreignEditionIds); List GetEditionsByBook(int bookId); + List GetEditionsByBook(IEnumerable bookIds); List GetEditionsByAuthor(int authorId); Edition FindByTitle(int authorMetadataId, string title); Edition FindByTitleInexact(int authorMetadataId, string title); @@ -79,7 +80,12 @@ namespace NzbDrone.Core.Books public List GetEditionsByBook(int bookId) { - return _editionRepository.FindByBook(bookId); + return _editionRepository.FindByBook(new[] { bookId }); + } + + public List GetEditionsByBook(IEnumerable bookIds) + { + return _editionRepository.FindByBook(bookIds); } public List GetEditionsByAuthor(int authorId) diff --git a/src/NzbDrone.Core/Localization/Core/en.json b/src/NzbDrone.Core/Localization/Core/en.json index 713dbb20a..3ff678e49 100644 --- a/src/NzbDrone.Core/Localization/Core/en.json +++ b/src/NzbDrone.Core/Localization/Core/en.json @@ -381,6 +381,7 @@ "ListsSettingsSummary": "Import Lists", "LoadingBookFilesFailed": "Loading book files failed", "LoadingBooksFailed": "Loading books failed", + "LoadingEditionsFailed": "Loading editions failed", "Local": "Local", "LogFiles": "Log Files", "Logging": "Logging", @@ -395,6 +396,7 @@ "MaintenanceRelease": "Maintenance Release: bug fixes and other improvements. See Github Commit History for more details", "ManualDownload": "Manual Download", "ManualImport": "Manual Import", + "ManualImportSelectEdition": "Manual Import - Select Edition", "MarkAsFailed": "Mark as Failed", "MarkAsFailedMessageText": "Are you sure you want to mark '{0}' as failed?", "MassBookSearch": "Mass Book Search", diff --git a/src/Readarr.Api.V1/Editions/EditionController.cs b/src/Readarr.Api.V1/Editions/EditionController.cs index 71aedc399..34d44a957 100644 --- a/src/Readarr.Api.V1/Editions/EditionController.cs +++ b/src/Readarr.Api.V1/Editions/EditionController.cs @@ -17,7 +17,7 @@ namespace NzbDrone.Api.V1.Editions } [HttpGet] - public List GetEditions(int bookId) + public List GetEditions([FromQuery]List bookId) { var editions = _editionService.GetEditionsByBook(bookId);