Fixed: Sorting by title and release dates in Select Album modal

Fixes #5145
Closes #5125

Co-authored-by: Mark McDowall <mark@mcdowall.ca>
pull/5150/head
Bogdan 2 months ago
parent 3a5012655e
commit bd7d25f963

@ -11,6 +11,7 @@ import Scroller from 'Components/Scroller/Scroller';
import Table from 'Components/Table/Table'; import Table from 'Components/Table/Table';
import TableBody from 'Components/Table/TableBody'; import TableBody from 'Components/Table/TableBody';
import { scrollDirections } from 'Helpers/Props'; import { scrollDirections } from 'Helpers/Props';
import getErrorMessage from 'Utilities/Object/getErrorMessage';
import translate from 'Utilities/String/translate'; import translate from 'Utilities/String/translate';
import SelectAlbumRow from './SelectAlbumRow'; import SelectAlbumRow from './SelectAlbumRow';
import styles from './SelectAlbumModalContent.css'; import styles from './SelectAlbumModalContent.css';
@ -19,6 +20,7 @@ const columns = [
{ {
name: 'title', name: 'title',
label: () => translate('AlbumTitle'), label: () => translate('AlbumTitle'),
isSortable: true,
isVisible: true isVisible: true
}, },
{ {
@ -29,6 +31,7 @@ const columns = [
{ {
name: 'releaseDate', name: 'releaseDate',
label: () => translate('ReleaseDate'), label: () => translate('ReleaseDate'),
isSortable: true,
isVisible: true isVisible: true
}, },
{ {
@ -63,16 +66,22 @@ class SelectAlbumModalContent extends Component {
render() { render() {
const { const {
isFetching,
isPopulated,
error,
items, items,
sortKey,
sortDirection,
onSortPress,
onAlbumSelect, onAlbumSelect,
onModalClose, onModalClose
isFetching,
...otherProps
} = this.props; } = this.props;
const filter = this.state.filter; const filter = this.state.filter;
const filterLower = filter.toLowerCase(); const filterLower = filter.toLowerCase();
const errorMessage = getErrorMessage(error, 'Unable to load albums');
return ( return (
<ModalContent onModalClose={onModalClose}> <ModalContent onModalClose={onModalClose}>
<ModalHeader> <ModalHeader>
@ -83,27 +92,29 @@ class SelectAlbumModalContent extends Component {
className={styles.modalBody} className={styles.modalBody}
scrollDirection={scrollDirections.NONE} scrollDirection={scrollDirections.NONE}
> >
{
isFetching &&
<LoadingIndicator />
}
<TextInput
className={styles.filterInput}
placeholder={translate('FilterAlbumPlaceholder')}
name="filter"
value={filter}
autoFocus={true}
onChange={this.onFilterChange}
/>
<Scroller <Scroller
className={styles.scroller} className={styles.scroller}
autoFocus={false} autoFocus={false}
> >
{ {isFetching ? <LoadingIndicator /> : null}
{error ? <div>{errorMessage}</div> : null}
<TextInput
className={styles.filterInput}
placeholder={translate('FilterAlbumPlaceholder')}
name="filter"
value={filter}
autoFocus={true}
onChange={this.onFilterChange}
/>
{isPopulated && !!items.length ? (
<Table <Table
columns={columns} columns={columns}
{...otherProps} sortKey={sortKey}
sortDirection={sortDirection}
onSortPress={onSortPress}
> >
<TableBody> <TableBody>
{ {
@ -122,7 +133,7 @@ class SelectAlbumModalContent extends Component {
} }
</TableBody> </TableBody>
</Table> </Table>
} ) : null}
</Scroller> </Scroller>
</ModalBody> </ModalBody>
@ -137,8 +148,13 @@ class SelectAlbumModalContent extends Component {
} }
SelectAlbumModalContent.propTypes = { SelectAlbumModalContent.propTypes = {
items: PropTypes.arrayOf(PropTypes.object).isRequired,
isFetching: PropTypes.bool.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, onAlbumSelect: PropTypes.func.isRequired,
onModalClose: PropTypes.func.isRequired onModalClose: PropTypes.func.isRequired
}; };

@ -3,18 +3,14 @@ import PropTypes from 'prop-types';
import React, { Component } from 'react'; import React, { Component } from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { createSelector } from 'reselect'; import { createSelector } from 'reselect';
import { import { clearAlbums, fetchAlbums, setAlbumsSort } from 'Store/Actions/albumSelectionActions';
clearInteractiveImportAlbums, import { saveInteractiveImportItem, updateInteractiveImportItem } from 'Store/Actions/interactiveImportActions';
fetchInteractiveImportAlbums,
saveInteractiveImportItem,
setInteractiveImportAlbumsSort,
updateInteractiveImportItem } from 'Store/Actions/interactiveImportActions';
import createClientSideCollectionSelector from 'Store/Selectors/createClientSideCollectionSelector'; import createClientSideCollectionSelector from 'Store/Selectors/createClientSideCollectionSelector';
import SelectAlbumModalContent from './SelectAlbumModalContent'; import SelectAlbumModalContent from './SelectAlbumModalContent';
function createMapStateToProps() { function createMapStateToProps() {
return createSelector( return createSelector(
createClientSideCollectionSelector('interactiveImport.albums'), createClientSideCollectionSelector('albumSelection'),
(albums) => { (albums) => {
return albums; return albums;
} }
@ -22,9 +18,9 @@ function createMapStateToProps() {
} }
const mapDispatchToProps = { const mapDispatchToProps = {
fetchInteractiveImportAlbums, fetchAlbums,
setInteractiveImportAlbumsSort, setAlbumsSort,
clearInteractiveImportAlbums, clearAlbums,
updateInteractiveImportItem, updateInteractiveImportItem,
saveInteractiveImportItem saveInteractiveImportItem
}; };
@ -39,20 +35,20 @@ class SelectAlbumModalContentConnector extends Component {
artistId artistId
} = this.props; } = this.props;
this.props.fetchInteractiveImportAlbums({ artistId }); this.props.fetchAlbums({ artistId });
} }
componentWillUnmount() { componentWillUnmount() {
// This clears the albums for the queue and hides the queue // This clears the albums for the queue and hides the queue
// We'll need another place to store albums for manual import // We'll need another place to store albums for manual import
this.props.clearInteractiveImportAlbums(); this.props.clearAlbums();
} }
// //
// Listeners // Listeners
onSortPress = (sortKey, sortDirection) => { onSortPress = (sortKey, sortDirection) => {
this.props.setInteractiveImportAlbumsSort({ sortKey, sortDirection }); this.props.setAlbumsSort({ sortKey, sortDirection });
}; };
onAlbumSelect = (albumId) => { onAlbumSelect = (albumId) => {
@ -82,6 +78,7 @@ class SelectAlbumModalContentConnector extends Component {
return ( return (
<SelectAlbumModalContent <SelectAlbumModalContent
{...this.props} {...this.props}
onSortPress={this.onSortPress}
onAlbumSelect={this.onAlbumSelect} onAlbumSelect={this.onAlbumSelect}
/> />
); );
@ -92,9 +89,9 @@ SelectAlbumModalContentConnector.propTypes = {
ids: PropTypes.arrayOf(PropTypes.number).isRequired, ids: PropTypes.arrayOf(PropTypes.number).isRequired,
artistId: PropTypes.number.isRequired, artistId: PropTypes.number.isRequired,
items: PropTypes.arrayOf(PropTypes.object).isRequired, items: PropTypes.arrayOf(PropTypes.object).isRequired,
fetchInteractiveImportAlbums: PropTypes.func.isRequired, fetchAlbums: PropTypes.func.isRequired,
setInteractiveImportAlbumsSort: PropTypes.func.isRequired, setAlbumsSort: PropTypes.func.isRequired,
clearInteractiveImportAlbums: PropTypes.func.isRequired, clearAlbums: PropTypes.func.isRequired,
saveInteractiveImportItem: PropTypes.func.isRequired, saveInteractiveImportItem: PropTypes.func.isRequired,
updateInteractiveImportItem: PropTypes.func.isRequired, updateInteractiveImportItem: PropTypes.func.isRequired,
onModalClose: PropTypes.func.isRequired onModalClose: PropTypes.func.isRequired

@ -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);

@ -1,5 +1,6 @@
import * as albums from './albumActions'; import * as albums from './albumActions';
import * as albumHistory from './albumHistoryActions'; import * as albumHistory from './albumHistoryActions';
import * as albumSelection from './albumSelectionActions';
import * as app from './appActions'; import * as app from './appActions';
import * as artist from './artistActions'; import * as artist from './artistActions';
import * as artistHistory from './artistHistoryActions'; import * as artistHistory from './artistHistoryActions';
@ -29,14 +30,18 @@ import * as wanted from './wantedActions';
export default [ export default [
app, app,
albums,
albumHistory,
albumSelection,
artist,
artistHistory,
artistIndex,
blocklist, blocklist,
captcha, captcha,
calendar, calendar,
commands, commands,
customFilters, customFilters,
albums,
trackFiles, trackFiles,
albumHistory,
history, history,
interactiveImportActions, interactiveImportActions,
oAuth, oAuth,
@ -47,9 +52,6 @@ export default [
providerOptions, providerOptions,
queue, queue,
releases, releases,
artist,
artistHistory,
artistIndex,
search, search,
settings, settings,
system, system,

@ -16,7 +16,6 @@ import createSetClientSideCollectionSortReducer from './Creators/Reducers/create
export const section = 'interactiveImport'; export const section = 'interactiveImport';
const albumsSection = `${section}.albums`;
const trackFilesSection = `${section}.trackFiles`; const trackFilesSection = `${section}.trackFiles`;
let abortCurrentFetchRequest = null; let abortCurrentFetchRequest = null;
let abortCurrentRequest = 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: { trackFiles: {
isFetching: false, isFetching: false,
isPopulated: false, isPopulated: false,
@ -97,10 +87,6 @@ export const ADD_RECENT_FOLDER = 'interactiveImport/addRecentFolder';
export const REMOVE_RECENT_FOLDER = 'interactiveImport/removeRecentFolder'; export const REMOVE_RECENT_FOLDER = 'interactiveImport/removeRecentFolder';
export const SET_INTERACTIVE_IMPORT_MODE = 'interactiveImport/setInteractiveImportMode'; 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 FETCH_INTERACTIVE_IMPORT_TRACKFILES = 'interactiveImport/fetchInteractiveImportTrackFiles';
export const CLEAR_INTERACTIVE_IMPORT_TRACKFILES = 'interactiveImport/clearInteractiveImportTrackFiles'; 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 removeRecentFolder = createAction(REMOVE_RECENT_FOLDER);
export const setInteractiveImportMode = createAction(SET_INTERACTIVE_IMPORT_MODE); 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 fetchInteractiveImportTrackFiles = createThunk(FETCH_INTERACTIVE_IMPORT_TRACKFILES);
export const clearInteractiveImportTrackFiles = createAction(CLEAR_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') [FETCH_INTERACTIVE_IMPORT_TRACKFILES]: createFetchHandler(trackFilesSection, '/trackFile')
}); });
@ -336,14 +316,6 @@ export const reducers = createHandleActions({
return Object.assign({}, state, { importMode: payload.importMode }); 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) => { [CLEAR_INTERACTIVE_IMPORT_TRACKFILES]: (state) => {
return updateSectionState(state, trackFilesSection, { return updateSectionState(state, trackFilesSection, {
...defaultState.trackFiles ...defaultState.trackFiles

Loading…
Cancel
Save