From bd7d25f963b27153655bf5ea1258e02c78bc440e Mon Sep 17 00:00:00 2001 From: Bogdan Date: Mon, 7 Oct 2024 16:46:41 +0300 Subject: [PATCH] Fixed: Sorting by title and release dates in Select Album modal Fixes #5145 Closes #5125 Co-authored-by: Mark McDowall --- .../Album/SelectAlbumModalContent.js | 56 +++++++----- .../Album/SelectAlbumModalContentConnector.js | 29 +++---- .../Store/Actions/albumSelectionActions.js | 86 +++++++++++++++++++ frontend/src/Store/Actions/index.js | 12 +-- .../Store/Actions/interactiveImportActions.js | 28 ------ 5 files changed, 142 insertions(+), 69 deletions(-) create mode 100644 frontend/src/Store/Actions/albumSelectionActions.js diff --git a/frontend/src/InteractiveImport/Album/SelectAlbumModalContent.js b/frontend/src/InteractiveImport/Album/SelectAlbumModalContent.js index 5f2cc9696..f8c84e54b 100644 --- a/frontend/src/InteractiveImport/Album/SelectAlbumModalContent.js +++ b/frontend/src/InteractiveImport/Album/SelectAlbumModalContent.js @@ -11,6 +11,7 @@ import Scroller from 'Components/Scroller/Scroller'; import Table from 'Components/Table/Table'; import TableBody from 'Components/Table/TableBody'; import { scrollDirections } from 'Helpers/Props'; +import getErrorMessage from 'Utilities/Object/getErrorMessage'; import translate from 'Utilities/String/translate'; import SelectAlbumRow from './SelectAlbumRow'; import styles from './SelectAlbumModalContent.css'; @@ -19,6 +20,7 @@ const columns = [ { name: 'title', label: () => translate('AlbumTitle'), + isSortable: true, isVisible: true }, { @@ -29,6 +31,7 @@ const columns = [ { name: 'releaseDate', label: () => translate('ReleaseDate'), + isSortable: true, isVisible: true }, { @@ -63,16 +66,22 @@ class SelectAlbumModalContent extends Component { render() { const { + isFetching, + isPopulated, + error, items, + sortKey, + sortDirection, + onSortPress, onAlbumSelect, - onModalClose, - isFetching, - ...otherProps + onModalClose } = this.props; const filter = this.state.filter; const filterLower = filter.toLowerCase(); + const errorMessage = getErrorMessage(error, 'Unable to load albums'); + return ( @@ -83,27 +92,29 @@ class SelectAlbumModalContent extends Component { className={styles.modalBody} scrollDirection={scrollDirections.NONE} > - { - isFetching && - - } - - - { + {isFetching ? : null} + + {error ?
{errorMessage}
: null} + + + + {isPopulated && !!items.length ? ( { @@ -122,7 +133,7 @@ class SelectAlbumModalContent extends Component { }
- } + ) : null}
@@ -137,8 +148,13 @@ class SelectAlbumModalContent extends Component { } SelectAlbumModalContent.propTypes = { - items: PropTypes.arrayOf(PropTypes.object).isRequired, isFetching: PropTypes.bool.isRequired, + isPopulated: PropTypes.bool.isRequired, + error: PropTypes.object, + items: PropTypes.arrayOf(PropTypes.object).isRequired, + sortKey: PropTypes.string, + sortDirection: PropTypes.string, + onSortPress: PropTypes.func.isRequired, onAlbumSelect: PropTypes.func.isRequired, onModalClose: PropTypes.func.isRequired }; diff --git a/frontend/src/InteractiveImport/Album/SelectAlbumModalContentConnector.js b/frontend/src/InteractiveImport/Album/SelectAlbumModalContentConnector.js index 12cd88e53..d09da0fca 100644 --- a/frontend/src/InteractiveImport/Album/SelectAlbumModalContentConnector.js +++ b/frontend/src/InteractiveImport/Album/SelectAlbumModalContentConnector.js @@ -3,18 +3,14 @@ import PropTypes from 'prop-types'; import React, { Component } from 'react'; import { connect } from 'react-redux'; import { createSelector } from 'reselect'; -import { - clearInteractiveImportAlbums, - fetchInteractiveImportAlbums, - saveInteractiveImportItem, - setInteractiveImportAlbumsSort, - updateInteractiveImportItem } from 'Store/Actions/interactiveImportActions'; +import { clearAlbums, fetchAlbums, setAlbumsSort } from 'Store/Actions/albumSelectionActions'; +import { saveInteractiveImportItem, updateInteractiveImportItem } from 'Store/Actions/interactiveImportActions'; import createClientSideCollectionSelector from 'Store/Selectors/createClientSideCollectionSelector'; import SelectAlbumModalContent from './SelectAlbumModalContent'; function createMapStateToProps() { return createSelector( - createClientSideCollectionSelector('interactiveImport.albums'), + createClientSideCollectionSelector('albumSelection'), (albums) => { return albums; } @@ -22,9 +18,9 @@ function createMapStateToProps() { } const mapDispatchToProps = { - fetchInteractiveImportAlbums, - setInteractiveImportAlbumsSort, - clearInteractiveImportAlbums, + fetchAlbums, + setAlbumsSort, + clearAlbums, updateInteractiveImportItem, saveInteractiveImportItem }; @@ -39,20 +35,20 @@ class SelectAlbumModalContentConnector extends Component { artistId } = this.props; - this.props.fetchInteractiveImportAlbums({ artistId }); + this.props.fetchAlbums({ artistId }); } componentWillUnmount() { // This clears the albums for the queue and hides the queue // We'll need another place to store albums for manual import - this.props.clearInteractiveImportAlbums(); + this.props.clearAlbums(); } // // Listeners onSortPress = (sortKey, sortDirection) => { - this.props.setInteractiveImportAlbumsSort({ sortKey, sortDirection }); + this.props.setAlbumsSort({ sortKey, sortDirection }); }; onAlbumSelect = (albumId) => { @@ -82,6 +78,7 @@ class SelectAlbumModalContentConnector extends Component { return ( ); @@ -92,9 +89,9 @@ SelectAlbumModalContentConnector.propTypes = { ids: PropTypes.arrayOf(PropTypes.number).isRequired, artistId: PropTypes.number.isRequired, items: PropTypes.arrayOf(PropTypes.object).isRequired, - fetchInteractiveImportAlbums: PropTypes.func.isRequired, - setInteractiveImportAlbumsSort: PropTypes.func.isRequired, - clearInteractiveImportAlbums: PropTypes.func.isRequired, + fetchAlbums: PropTypes.func.isRequired, + setAlbumsSort: PropTypes.func.isRequired, + clearAlbums: PropTypes.func.isRequired, saveInteractiveImportItem: PropTypes.func.isRequired, updateInteractiveImportItem: PropTypes.func.isRequired, onModalClose: PropTypes.func.isRequired diff --git a/frontend/src/Store/Actions/albumSelectionActions.js b/frontend/src/Store/Actions/albumSelectionActions.js new file mode 100644 index 000000000..f19f5b691 --- /dev/null +++ b/frontend/src/Store/Actions/albumSelectionActions.js @@ -0,0 +1,86 @@ +import moment from 'moment'; +import { createAction } from 'redux-actions'; +import { sortDirections } from 'Helpers/Props'; +import { createThunk, handleThunks } from 'Store/thunks'; +import updateSectionState from 'Utilities/State/updateSectionState'; +import createFetchHandler from './Creators/createFetchHandler'; +import createHandleActions from './Creators/createHandleActions'; +import createSetClientSideCollectionSortReducer from './Creators/Reducers/createSetClientSideCollectionSortReducer'; + +// +// Variables + +export const section = 'albumSelection'; + +// +// State + +export const defaultState = { + isFetching: false, + isReprocessing: false, + isPopulated: false, + error: null, + sortKey: 'title', + sortDirection: sortDirections.ASCENDING, + items: [], + sortPredicates: { + title: ({ title }) => { + return title.toLocaleLowerCase(); + }, + + releaseDate: function({ releaseDate }, direction) { + if (releaseDate) { + return moment(releaseDate).unix(); + } + + if (direction === sortDirections.DESCENDING) { + return 0; + } + + return Number.MAX_VALUE; + } + } +}; + +export const persistState = [ + 'albumSelection.sortKey', + 'albumSelection.sortDirection' +]; + +// +// Actions Types + +export const FETCH_ALBUMS = 'albumSelection/fetchAlbums'; +export const SET_ALBUMS_SORT = 'albumSelection/setAlbumsSort'; +export const CLEAR_ALBUMS = 'albumSelection/clearAlbums'; + +// +// Action Creators + +export const fetchAlbums = createThunk(FETCH_ALBUMS); +export const setAlbumsSort = createAction(SET_ALBUMS_SORT); +export const clearAlbums = createAction(CLEAR_ALBUMS); + +// +// Action Handlers + +export const actionHandlers = handleThunks({ + [FETCH_ALBUMS]: createFetchHandler(section, '/album') +}); + +// +// Reducers + +export const reducers = createHandleActions({ + + [SET_ALBUMS_SORT]: createSetClientSideCollectionSortReducer(section), + + [CLEAR_ALBUMS]: (state) => { + return updateSectionState(state, section, { + ...defaultState, + sortKey: state.sortKey, + sortDirection: state.sortDirection + }); + } + +}, defaultState, section); diff --git a/frontend/src/Store/Actions/index.js b/frontend/src/Store/Actions/index.js index 85bcc2645..85fda482b 100644 --- a/frontend/src/Store/Actions/index.js +++ b/frontend/src/Store/Actions/index.js @@ -1,5 +1,6 @@ import * as albums from './albumActions'; import * as albumHistory from './albumHistoryActions'; +import * as albumSelection from './albumSelectionActions'; import * as app from './appActions'; import * as artist from './artistActions'; import * as artistHistory from './artistHistoryActions'; @@ -29,14 +30,18 @@ import * as wanted from './wantedActions'; export default [ app, + albums, + albumHistory, + albumSelection, + artist, + artistHistory, + artistIndex, blocklist, captcha, calendar, commands, customFilters, - albums, trackFiles, - albumHistory, history, interactiveImportActions, oAuth, @@ -47,9 +52,6 @@ export default [ providerOptions, queue, releases, - artist, - artistHistory, - artistIndex, search, settings, system, diff --git a/frontend/src/Store/Actions/interactiveImportActions.js b/frontend/src/Store/Actions/interactiveImportActions.js index d174f443b..a250292c5 100644 --- a/frontend/src/Store/Actions/interactiveImportActions.js +++ b/frontend/src/Store/Actions/interactiveImportActions.js @@ -16,7 +16,6 @@ import createSetClientSideCollectionSortReducer from './Creators/Reducers/create export const section = 'interactiveImport'; -const albumsSection = `${section}.albums`; const trackFilesSection = `${section}.trackFiles`; let abortCurrentFetchRequest = null; let abortCurrentRequest = null; @@ -58,15 +57,6 @@ export const defaultState = { } }, - albums: { - isFetching: false, - isPopulated: false, - error: null, - sortKey: 'albumTitle', - sortDirection: sortDirections.ASCENDING, - items: [] - }, - trackFiles: { isFetching: false, isPopulated: false, @@ -97,10 +87,6 @@ export const ADD_RECENT_FOLDER = 'interactiveImport/addRecentFolder'; export const REMOVE_RECENT_FOLDER = 'interactiveImport/removeRecentFolder'; export const SET_INTERACTIVE_IMPORT_MODE = 'interactiveImport/setInteractiveImportMode'; -export const FETCH_INTERACTIVE_IMPORT_ALBUMS = 'interactiveImport/fetchInteractiveImportAlbums'; -export const SET_INTERACTIVE_IMPORT_ALBUMS_SORT = 'interactiveImport/clearInteractiveImportAlbumsSort'; -export const CLEAR_INTERACTIVE_IMPORT_ALBUMS = 'interactiveImport/clearInteractiveImportAlbums'; - export const FETCH_INTERACTIVE_IMPORT_TRACKFILES = 'interactiveImport/fetchInteractiveImportTrackFiles'; export const CLEAR_INTERACTIVE_IMPORT_TRACKFILES = 'interactiveImport/clearInteractiveImportTrackFiles'; @@ -117,10 +103,6 @@ export const addRecentFolder = createAction(ADD_RECENT_FOLDER); export const removeRecentFolder = createAction(REMOVE_RECENT_FOLDER); export const setInteractiveImportMode = createAction(SET_INTERACTIVE_IMPORT_MODE); -export const fetchInteractiveImportAlbums = createThunk(FETCH_INTERACTIVE_IMPORT_ALBUMS); -export const setInteractiveImportAlbumsSort = createAction(SET_INTERACTIVE_IMPORT_ALBUMS_SORT); -export const clearInteractiveImportAlbums = createAction(CLEAR_INTERACTIVE_IMPORT_ALBUMS); - export const fetchInteractiveImportTrackFiles = createThunk(FETCH_INTERACTIVE_IMPORT_TRACKFILES); export const clearInteractiveImportTrackFiles = createAction(CLEAR_INTERACTIVE_IMPORT_TRACKFILES); @@ -253,8 +235,6 @@ export const actionHandlers = handleThunks({ }); }, - [FETCH_INTERACTIVE_IMPORT_ALBUMS]: createFetchHandler(albumsSection, '/album'), - [FETCH_INTERACTIVE_IMPORT_TRACKFILES]: createFetchHandler(trackFilesSection, '/trackFile') }); @@ -336,14 +316,6 @@ export const reducers = createHandleActions({ return Object.assign({}, state, { importMode: payload.importMode }); }, - [SET_INTERACTIVE_IMPORT_ALBUMS_SORT]: createSetClientSideCollectionSortReducer(albumsSection), - - [CLEAR_INTERACTIVE_IMPORT_ALBUMS]: (state) => { - return updateSectionState(state, albumsSection, { - ...defaultState.albums - }); - }, - [CLEAR_INTERACTIVE_IMPORT_TRACKFILES]: (state) => { return updateSectionState(state, trackFilesSection, { ...defaultState.trackFiles