From 7cf39e6a30aeec3a22b5506e78decfe0771585d4 Mon Sep 17 00:00:00 2001 From: Qstick Date: Sat, 27 Apr 2019 23:45:59 -0400 Subject: [PATCH] Fixed: UI Selector, Rendering Improvements --- frontend/src/Artist/Index/ArtistIndex.js | 6 +- .../src/Artist/Index/ArtistIndexConnector.js | 4 +- .../Index/ArtistIndexFooterConnector.js | 46 +++++ .../Artist/Index/ArtistIndexItemConnector.js | 20 +-- frontend/src/Components/Page/ErrorPage.js | 8 +- frontend/src/Components/Page/PageConnector.js | 161 ++++++++++++------ ...ArtistClientSideCollectionItemsSelector.js | 36 ++++ .../createArtistLanguageProfileSelector.js | 16 ++ .../createArtistMetadataProfileSelector.js | 16 ++ .../createArtistQualityProfileSelector.js | 16 ++ .../Selectors/createDeepEqualSelector.js | 9 + .../createLanguageProfileSelector.js | 5 +- .../createMetadataProfileSelector.js | 5 +- .../Selectors/createQualityProfileSelector.js | 5 +- 14 files changed, 276 insertions(+), 77 deletions(-) create mode 100644 frontend/src/Artist/Index/ArtistIndexFooterConnector.js create mode 100644 frontend/src/Store/Selectors/createArtistClientSideCollectionItemsSelector.js create mode 100644 frontend/src/Store/Selectors/createArtistLanguageProfileSelector.js create mode 100644 frontend/src/Store/Selectors/createArtistMetadataProfileSelector.js create mode 100644 frontend/src/Store/Selectors/createArtistQualityProfileSelector.js create mode 100644 frontend/src/Store/Selectors/createDeepEqualSelector.js diff --git a/frontend/src/Artist/Index/ArtistIndex.js b/frontend/src/Artist/Index/ArtistIndex.js index 40b0535ee..b4152a5e9 100644 --- a/frontend/src/Artist/Index/ArtistIndex.js +++ b/frontend/src/Artist/Index/ArtistIndex.js @@ -21,7 +21,7 @@ import ArtistIndexBannerOptionsModal from './Banners/Options/ArtistIndexBannerOp import ArtistIndexBannersConnector from './Banners/ArtistIndexBannersConnector'; import ArtistIndexOverviewOptionsModal from './Overview/Options/ArtistIndexOverviewOptionsModal'; import ArtistIndexOverviewsConnector from './Overview/ArtistIndexOverviewsConnector'; -import ArtistIndexFooter from './ArtistIndexFooter'; +import ArtistIndexFooterConnector from './ArtistIndexFooterConnector'; import ArtistIndexFilterMenu from './Menus/ArtistIndexFilterMenu'; import ArtistIndexSortMenu from './Menus/ArtistIndexSortMenu'; import ArtistIndexViewMenu from './Menus/ArtistIndexViewMenu'; @@ -358,9 +358,7 @@ class ArtistIndex extends Component { {...otherProps} /> - + } diff --git a/frontend/src/Artist/Index/ArtistIndexConnector.js b/frontend/src/Artist/Index/ArtistIndexConnector.js index daa52e435..ad7909957 100644 --- a/frontend/src/Artist/Index/ArtistIndexConnector.js +++ b/frontend/src/Artist/Index/ArtistIndexConnector.js @@ -3,7 +3,7 @@ import PropTypes from 'prop-types'; import React, { Component } from 'react'; import { connect } from 'react-redux'; import { createSelector } from 'reselect'; -import createClientSideCollectionSelector from 'Store/Selectors/createClientSideCollectionSelector'; +import createArtistClientSideCollectionItemsSelector from 'Store/Selectors/createArtistClientSideCollectionItemsSelector'; import dimensions from 'Styles/Variables/dimensions'; import createCommandExecutingSelector from 'Store/Selectors/createCommandExecutingSelector'; import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector'; @@ -46,7 +46,7 @@ function getScrollTop(view, scrollTop, isSmallScreen) { function createMapStateToProps() { return createSelector( - createClientSideCollectionSelector('artist', 'artistIndex'), + createArtistClientSideCollectionItemsSelector('artistIndex'), createCommandExecutingSelector(commandNames.REFRESH_ARTIST), createCommandExecutingSelector(commandNames.RSS_SYNC), createDimensionsSelector(), diff --git a/frontend/src/Artist/Index/ArtistIndexFooterConnector.js b/frontend/src/Artist/Index/ArtistIndexFooterConnector.js new file mode 100644 index 000000000..9d7afc298 --- /dev/null +++ b/frontend/src/Artist/Index/ArtistIndexFooterConnector.js @@ -0,0 +1,46 @@ +import { connect } from 'react-redux'; +import { createSelector } from 'reselect'; +import createDeepEqualSelector from 'Store/Selectors/createDeepEqualSelector'; +import createClientSideCollectionSelector from 'Store/Selectors/createClientSideCollectionSelector'; +import ArtistIndexFooter from './ArtistIndexFooter'; + +function createUnoptimizedSelector() { + return createSelector( + createClientSideCollectionSelector('artist', 'artistIndex'), + (artist) => { + return artist.items.map((s) => { + const { + monitored, + status, + statistics + } = s; + + return { + monitored, + status, + statistics + }; + }); + } + ); +} + +function createArtistSelector() { + return createDeepEqualSelector( + createUnoptimizedSelector(), + (artist) => artist + ); +} + +function createMapStateToProps() { + return createSelector( + createArtistSelector(), + (artist) => { + return { + artist + }; + } + ); +} + +export default connect(createMapStateToProps)(ArtistIndexFooter); diff --git a/frontend/src/Artist/Index/ArtistIndexItemConnector.js b/frontend/src/Artist/Index/ArtistIndexItemConnector.js index b4e3cd0ba..2f70f13a1 100644 --- a/frontend/src/Artist/Index/ArtistIndexItemConnector.js +++ b/frontend/src/Artist/Index/ArtistIndexItemConnector.js @@ -6,9 +6,9 @@ import { connect } from 'react-redux'; import { createSelector } from 'reselect'; import createArtistSelector from 'Store/Selectors/createArtistSelector'; import createExecutingCommandsSelector from 'Store/Selectors/createExecutingCommandsSelector'; -import createQualityProfileSelector from 'Store/Selectors/createQualityProfileSelector'; -import createLanguageProfileSelector from 'Store/Selectors/createLanguageProfileSelector'; -import createMetadataProfileSelector from 'Store/Selectors/createMetadataProfileSelector'; +import createArtistQualityProfileSelector from 'Store/Selectors/createArtistQualityProfileSelector'; +import createArtistLanguageProfileSelector from 'Store/Selectors/createArtistLanguageProfileSelector'; +import createArtistMetadataProfileSelector from 'Store/Selectors/createArtistMetadataProfileSelector'; import { executeCommand } from 'Store/Actions/commandActions'; import * as commandNames from 'Commands/commandNames'; @@ -35,9 +35,9 @@ function selectShowSearchAction() { function createMapStateToProps() { return createSelector( createArtistSelector(), - createQualityProfileSelector(), - createLanguageProfileSelector(), - createMetadataProfileSelector(), + createArtistQualityProfileSelector(), + createArtistLanguageProfileSelector(), + createArtistMetadataProfileSelector(), selectShowSearchAction(), createExecutingCommandsSelector(), ( @@ -89,7 +89,7 @@ function createMapStateToProps() { } const mapDispatchToProps = { - executeCommand + dispatchExecuteCommand: executeCommand }; class ArtistIndexItemConnector extends Component { @@ -98,14 +98,14 @@ class ArtistIndexItemConnector extends Component { // Listeners onRefreshArtistPress = () => { - this.props.executeCommand({ + this.props.dispatchExecuteCommand({ name: commandNames.REFRESH_ARTIST, artistId: this.props.id }); } onSearchPress = () => { - this.props.executeCommand({ + this.props.dispatchExecuteCommand({ name: commandNames.ARTIST_SEARCH, artistId: this.props.id }); @@ -139,7 +139,7 @@ class ArtistIndexItemConnector extends Component { ArtistIndexItemConnector.propTypes = { id: PropTypes.number, component: PropTypes.func.isRequired, - executeCommand: PropTypes.func.isRequired + dispatchExecuteCommand: PropTypes.func.isRequired }; export default connect(createMapStateToProps, mapDispatchToProps)(ArtistIndexItemConnector); diff --git a/frontend/src/Components/Page/ErrorPage.js b/frontend/src/Components/Page/ErrorPage.js index 6cdd08af1..f7261ed06 100644 --- a/frontend/src/Components/Page/ErrorPage.js +++ b/frontend/src/Components/Page/ErrorPage.js @@ -13,7 +13,8 @@ function ErrorPage(props) { qualityProfilesError, languageProfilesError, metadataProfilesError, - uiSettingsError + uiSettingsError, + systemStatusError } = props; let errorMessage = 'Failed to load Lidarr'; @@ -34,6 +35,8 @@ function ErrorPage(props) { errorMessage = getErrorMessage(metadataProfilesError, 'Failed to load metadata profiles from API'); } else if (uiSettingsError) { errorMessage = getErrorMessage(uiSettingsError, 'Failed to load UI settings from API'); + } else if (systemStatusError) { + errorMessage = getErrorMessage(uiSettingsError, 'Failed to load system status from API'); } return ( @@ -58,7 +61,8 @@ ErrorPage.propTypes = { qualityProfilesError: PropTypes.object, languageProfilesError: PropTypes.object, metadataProfilesError: PropTypes.object, - uiSettingsError: PropTypes.object + uiSettingsError: PropTypes.object, + systemStatusError: PropTypes.object }; export default ErrorPage; diff --git a/frontend/src/Components/Page/PageConnector.js b/frontend/src/Components/Page/PageConnector.js index 4fd993002..3a9e02869 100644 --- a/frontend/src/Components/Page/PageConnector.js +++ b/frontend/src/Components/Page/PageConnector.js @@ -27,69 +27,124 @@ function testLocalStorage() { } } +const selectAppProps = createSelector( + (state) => state.app.isSidebarVisible, + (state) => state.app.version, + (state) => state.app.isUpdated, + (state) => state.app.isDisconnected, + (isSidebarVisible, version, isUpdated, isDisconnected) => { + return { + isSidebarVisible, + version, + isUpdated, + isDisconnected + }; + } +); + +const selectIsPopulated = createSelector( + (state) => state.artist.isPopulated, + (state) => state.customFilters.isPopulated, + (state) => state.tags.isPopulated, + (state) => state.settings.ui.isPopulated, + (state) => state.settings.qualityProfiles.isPopulated, + (state) => state.settings.languageProfiles.isPopulated, + (state) => state.settings.metadataProfiles.isPopulated, + (state) => state.settings.importLists.isPopulated, + (state) => state.system.status.isPopulated, + ( + artistIsPopulated, + customFiltersIsPopulated, + tagsIsPopulated, + uiSettingsIsPopulated, + qualityProfilesIsPopulated, + languageProfilesIsPopulated, + metadataProfilesIsPopulated, + importListsIsPopulated, + systemStatusIsPopulated + ) => { + return ( + artistIsPopulated && + customFiltersIsPopulated && + tagsIsPopulated && + uiSettingsIsPopulated && + qualityProfilesIsPopulated && + languageProfilesIsPopulated && + metadataProfilesIsPopulated && + importListsIsPopulated && + systemStatusIsPopulated + ); + } +); + +const selectErrors = createSelector( + (state) => state.artist.error, + (state) => state.customFilters.error, + (state) => state.tags.error, + (state) => state.settings.ui.error, + (state) => state.settings.qualityProfiles.error, + (state) => state.settings.languageProfiles.error, + (state) => state.settings.metadataProfiles.error, + (state) => state.settings.importLists.error, + (state) => state.system.status.error, + ( + artistError, + customFiltersError, + tagsError, + uiSettingsError, + qualityProfilesError, + languageProfilesError, + metadataProfilesError, + importListsError, + systemStatusError + ) => { + const hasError = !!( + artistError || + customFiltersError || + tagsError || + uiSettingsError || + qualityProfilesError || + languageProfilesError || + metadataProfilesError || + importListsError || + systemStatusError + ); + + return { + hasError, + artistError, + customFiltersError, + tagsError, + uiSettingsError, + qualityProfilesError, + languageProfilesError, + metadataProfilesError, + importListsError, + systemStatusError + }; + } +); + function createMapStateToProps() { return createSelector( - (state) => state.artist, - (state) => state.customFilters, - (state) => state.tags, - (state) => state.settings.ui, - (state) => state.settings.qualityProfiles, - (state) => state.settings.languageProfiles, - (state) => state.settings.metadataProfiles, - (state) => state.settings.importLists, - (state) => state.app, + (state) => state.settings.ui.item.enableColorImpairedMode, + selectIsPopulated, + selectErrors, + selectAppProps, createDimensionsSelector(), ( - artist, - customFilters, - tags, - uiSettings, - qualityProfiles, - languageProfiles, - metadataProfiles, - importLists, + enableColorImpairedMode, + isPopulated, + errors, app, dimensions ) => { - const isPopulated = ( - artist.isPopulated && - customFilters.isPopulated && - tags.isPopulated && - qualityProfiles.isPopulated && - languageProfiles.isPopulated && - metadataProfiles.isPopulated && - importLists.isPopulated && - uiSettings.isPopulated - ); - - const hasError = !!( - artist.error || - customFilters.error || - tags.error || - qualityProfiles.error || - languageProfiles.error || - metadataProfiles.error || - importLists.error || - uiSettings.error - ); - return { + ...app, + ...errors, isPopulated, - hasError, - artistError: artist.error, - customFiltersError: tags.error, - tagsError: tags.error, - qualityProfilesError: qualityProfiles.error, - languageProfilesError: languageProfiles.error, - metadataProfilesError: metadataProfiles.error, - importListsError: importLists.error, - uiSettingsError: uiSettings.error, isSmallScreen: dimensions.isSmallScreen, - isSidebarVisible: app.isSidebarVisible, - enableColorImpairedMode: uiSettings.item.enableColorImpairedMode, - version: app.version, - isUpdated: app.isUpdated, - isDisconnected: app.isDisconnected + enableColorImpairedMode }; } ); diff --git a/frontend/src/Store/Selectors/createArtistClientSideCollectionItemsSelector.js b/frontend/src/Store/Selectors/createArtistClientSideCollectionItemsSelector.js new file mode 100644 index 000000000..38300bd9d --- /dev/null +++ b/frontend/src/Store/Selectors/createArtistClientSideCollectionItemsSelector.js @@ -0,0 +1,36 @@ +import { createSelector } from 'reselect'; +import createDeepEqualSelector from './createDeepEqualSelector'; +import createClientSideCollectionSelector from './createClientSideCollectionSelector'; + +function createUnoptimizedSelector(uiSection) { + return createSelector( + createClientSideCollectionSelector('artist', uiSection), + (artist) => { + const items = artist.items.map((s) => { + const { + id, + sortName + } = s; + + return { + id, + sortName + }; + }); + + return { + ...artist, + items + }; + } + ); +} + +function createArtistClientSideCollectionItemsSelector(uiSection) { + return createDeepEqualSelector( + createUnoptimizedSelector(uiSection), + (artist) => artist + ); +} + +export default createArtistClientSideCollectionItemsSelector; diff --git a/frontend/src/Store/Selectors/createArtistLanguageProfileSelector.js b/frontend/src/Store/Selectors/createArtistLanguageProfileSelector.js new file mode 100644 index 000000000..2cddd2056 --- /dev/null +++ b/frontend/src/Store/Selectors/createArtistLanguageProfileSelector.js @@ -0,0 +1,16 @@ +import { createSelector } from 'reselect'; +import createArtistSelector from './createArtistSelector'; + +function createArtistLanguageProfileSelector() { + return createSelector( + (state) => state.settings.languageProfiles.items, + createArtistSelector(), + (languageProfiles, artist) => { + return languageProfiles.find((profile) => { + return profile.id === artist.languageProfileId; + }); + } + ); +} + +export default createArtistLanguageProfileSelector; diff --git a/frontend/src/Store/Selectors/createArtistMetadataProfileSelector.js b/frontend/src/Store/Selectors/createArtistMetadataProfileSelector.js new file mode 100644 index 000000000..0bdec92d6 --- /dev/null +++ b/frontend/src/Store/Selectors/createArtistMetadataProfileSelector.js @@ -0,0 +1,16 @@ +import { createSelector } from 'reselect'; +import createArtistSelector from './createArtistSelector'; + +function createArtistMetadataProfileSelector() { + return createSelector( + (state) => state.settings.metadataProfiles.items, + createArtistSelector(), + (metadataProfiles, artist) => { + return metadataProfiles.find((profile) => { + return profile.id === artist.metadataProfileId; + }); + } + ); +} + +export default createArtistMetadataProfileSelector; diff --git a/frontend/src/Store/Selectors/createArtistQualityProfileSelector.js b/frontend/src/Store/Selectors/createArtistQualityProfileSelector.js new file mode 100644 index 000000000..bc413b161 --- /dev/null +++ b/frontend/src/Store/Selectors/createArtistQualityProfileSelector.js @@ -0,0 +1,16 @@ +import { createSelector } from 'reselect'; +import createArtistSelector from './createArtistSelector'; + +function createArtistQualityProfileSelector() { + return createSelector( + (state) => state.settings.qualityProfiles.items, + createArtistSelector(), + (qualityProfiles, artist) => { + return qualityProfiles.find((profile) => { + return profile.id === artist.qualityProfileId; + }); + } + ); +} + +export default createArtistQualityProfileSelector; diff --git a/frontend/src/Store/Selectors/createDeepEqualSelector.js b/frontend/src/Store/Selectors/createDeepEqualSelector.js new file mode 100644 index 000000000..c01d23875 --- /dev/null +++ b/frontend/src/Store/Selectors/createDeepEqualSelector.js @@ -0,0 +1,9 @@ +import { createSelectorCreator, defaultMemoize } from 'reselect'; +import _ from 'lodash'; + +const createDeepEqualSelector = createSelectorCreator( + defaultMemoize, + _.isEqual +); + +export default createDeepEqualSelector; diff --git a/frontend/src/Store/Selectors/createLanguageProfileSelector.js b/frontend/src/Store/Selectors/createLanguageProfileSelector.js index 2ad04d506..c32f85ada 100644 --- a/frontend/src/Store/Selectors/createLanguageProfileSelector.js +++ b/frontend/src/Store/Selectors/createLanguageProfileSelector.js @@ -1,4 +1,3 @@ -import _ from 'lodash'; import { createSelector } from 'reselect'; function createLanguageProfileSelector() { @@ -6,7 +5,9 @@ function createLanguageProfileSelector() { (state, { languageProfileId }) => languageProfileId, (state) => state.settings.languageProfiles.items, (languageProfileId, languageProfiles) => { - return _.find(languageProfiles, { id: languageProfileId }); + return languageProfiles.find((profile) => { + return profile.id === languageProfileId; + }); } ); } diff --git a/frontend/src/Store/Selectors/createMetadataProfileSelector.js b/frontend/src/Store/Selectors/createMetadataProfileSelector.js index 6cbb8685a..bdd0d0636 100644 --- a/frontend/src/Store/Selectors/createMetadataProfileSelector.js +++ b/frontend/src/Store/Selectors/createMetadataProfileSelector.js @@ -1,4 +1,3 @@ -import _ from 'lodash'; import { createSelector } from 'reselect'; function createMetadataProfileSelector() { @@ -6,7 +5,9 @@ function createMetadataProfileSelector() { (state, { metadataProfileId }) => metadataProfileId, (state) => state.settings.metadataProfiles.items, (metadataProfileId, metadataProfiles) => { - return _.find(metadataProfiles, { id: metadataProfileId }); + return metadataProfiles.find((profile) => { + return profile.id === metadataProfileId; + }); } ); } diff --git a/frontend/src/Store/Selectors/createQualityProfileSelector.js b/frontend/src/Store/Selectors/createQualityProfileSelector.js index 9308d63ac..451aacfd4 100644 --- a/frontend/src/Store/Selectors/createQualityProfileSelector.js +++ b/frontend/src/Store/Selectors/createQualityProfileSelector.js @@ -1,4 +1,3 @@ -import _ from 'lodash'; import { createSelector } from 'reselect'; function createQualityProfileSelector() { @@ -6,7 +5,9 @@ function createQualityProfileSelector() { (state, { qualityProfileId }) => qualityProfileId, (state) => state.settings.qualityProfiles.items, (qualityProfileId, qualityProfiles) => { - return _.find(qualityProfiles, { id: qualityProfileId }); + return qualityProfiles.find((profile) => { + return profile.id === qualityProfileId; + }); } ); }