mirror of
https://github.com/lidarr/lidarr.git
synced 2025-07-05 20:42:19 -07:00
Fixed: Sorting by title and release dates in Select Album modal
Fixes #5145 Closes #5125 Co-authored-by: Mark McDowall <mark@mcdowall.ca>
This commit is contained in:
parent
3a5012655e
commit
bd7d25f963
5 changed files with 143 additions and 70 deletions
|
@ -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 {
|
||||||
items,
|
|
||||||
onAlbumSelect,
|
|
||||||
onModalClose,
|
|
||||||
isFetching,
|
isFetching,
|
||||||
...otherProps
|
isPopulated,
|
||||||
|
error,
|
||||||
|
items,
|
||||||
|
sortKey,
|
||||||
|
sortDirection,
|
||||||
|
onSortPress,
|
||||||
|
onAlbumSelect,
|
||||||
|
onModalClose
|
||||||
} = 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
|
||||||
|
|
86
frontend/src/Store/Actions/albumSelectionActions.js
Normal file
86
frontend/src/Store/Actions/albumSelectionActions.js
Normal file
|
@ -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…
Add table
Add a link
Reference in a new issue