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;
+ });
}
);
}