mirror of
https://github.com/lidarr/Lidarr
synced 2025-03-15 08:19:05 +00:00
Fix Interactive Import, Add Track Actions and Reducers
This commit is contained in:
parent
8f45fe0afe
commit
90d9741056
13 changed files with 193 additions and 27 deletions
|
@ -114,6 +114,11 @@ class SignalRConnector extends Component {
|
|||
return;
|
||||
}
|
||||
|
||||
if (name === 'track') {
|
||||
this.handleTrack(body);
|
||||
return;
|
||||
}
|
||||
|
||||
if (name === 'episodefile') {
|
||||
this.handleEpisodeFile(body);
|
||||
return;
|
||||
|
@ -192,6 +197,15 @@ class SignalRConnector extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
handleTrack = (body) => {
|
||||
if (body.action === 'updated') {
|
||||
this.props.updateItem({
|
||||
section: 'tracks',
|
||||
updateOnly: true,
|
||||
...body.resource });
|
||||
}
|
||||
}
|
||||
|
||||
handleEpisodeFile = (body) => {
|
||||
if (body.action === 'updated') {
|
||||
this.props.updateItem({
|
||||
|
|
|
@ -5,6 +5,7 @@ import ModalContent from 'Components/Modal/ModalContent';
|
|||
import ModalHeader from 'Components/Modal/ModalHeader';
|
||||
import ModalBody from 'Components/Modal/ModalBody';
|
||||
import ModalFooter from 'Components/Modal/ModalFooter';
|
||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||
import SelectAlbumRow from './SelectAlbumRow';
|
||||
|
||||
class SelectAlbumModalContent extends Component {
|
||||
|
@ -16,7 +17,8 @@ class SelectAlbumModalContent extends Component {
|
|||
const {
|
||||
items,
|
||||
onAlbumSelect,
|
||||
onModalClose
|
||||
onModalClose,
|
||||
isFetching
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
|
@ -26,6 +28,10 @@ class SelectAlbumModalContent extends Component {
|
|||
</ModalHeader>
|
||||
|
||||
<ModalBody>
|
||||
{
|
||||
isFetching &&
|
||||
<LoadingIndicator />
|
||||
}
|
||||
{
|
||||
items.map((item) => {
|
||||
return (
|
||||
|
@ -52,6 +58,7 @@ class SelectAlbumModalContent extends Component {
|
|||
|
||||
SelectAlbumModalContent.propTypes = {
|
||||
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
isFetching: PropTypes.bool.isRequired,
|
||||
onAlbumSelect: PropTypes.func.isRequired,
|
||||
onModalClose: PropTypes.func.isRequired
|
||||
};
|
||||
|
|
|
@ -2,28 +2,48 @@ import _ from 'lodash';
|
|||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import connectSection from 'Store/connectSection';
|
||||
import { createSelector } from 'reselect';
|
||||
import { updateInteractiveImportItem } from 'Store/Actions/interactiveImportActions';
|
||||
import createArtistSelector from 'Store/Selectors/createArtistSelector';
|
||||
import { fetchEpisodes, setEpisodesSort, clearEpisodes } from 'Store/Actions/episodeActions';
|
||||
import createClientSideCollectionSelector from 'Store/Selectors/createClientSideCollectionSelector';
|
||||
import SelectAlbumModalContent from './SelectAlbumModalContent';
|
||||
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
createArtistSelector(),
|
||||
(series) => {
|
||||
return {
|
||||
items: series.albums
|
||||
};
|
||||
createClientSideCollectionSelector(),
|
||||
(episodes) => {
|
||||
return episodes;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
const mapDispatchToProps = {
|
||||
fetchEpisodes,
|
||||
setEpisodesSort,
|
||||
clearEpisodes,
|
||||
updateInteractiveImportItem
|
||||
};
|
||||
|
||||
class SelectAlbumModalContentConnector extends Component {
|
||||
|
||||
//
|
||||
// Lifecycle
|
||||
|
||||
componentDidMount() {
|
||||
const {
|
||||
artistId
|
||||
} = this.props;
|
||||
|
||||
this.props.fetchEpisodes({ 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.clearEpisodes();
|
||||
}
|
||||
|
||||
//
|
||||
// Listeners
|
||||
|
||||
|
@ -58,8 +78,17 @@ SelectAlbumModalContentConnector.propTypes = {
|
|||
ids: PropTypes.arrayOf(PropTypes.number).isRequired,
|
||||
artistId: PropTypes.number.isRequired,
|
||||
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
fetchEpisodes: PropTypes.func.isRequired,
|
||||
setEpisodesSort: PropTypes.func.isRequired,
|
||||
clearEpisodes: PropTypes.func.isRequired,
|
||||
updateInteractiveImportItem: PropTypes.func.isRequired,
|
||||
onModalClose: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default connect(createMapStateToProps, mapDispatchToProps)(SelectAlbumModalContentConnector);
|
||||
export default connectSection(
|
||||
createMapStateToProps,
|
||||
mapDispatchToProps,
|
||||
undefined,
|
||||
undefined,
|
||||
{ section: 'episodes' }
|
||||
)(SelectAlbumModalContentConnector);
|
||||
|
|
|
@ -293,7 +293,7 @@ class InteractiveImportModalContent extends Component {
|
|||
<SelectAlbumModal
|
||||
isOpen={isSelectAlbumModalOpen}
|
||||
ids={selectedIds}
|
||||
artistId={selectedItem && selectedItem.series && selectedItem.series.id}
|
||||
artistId={selectedItem && selectedItem.artist && selectedItem.artist.id}
|
||||
onModalClose={this.onSelectAlbumModalClose}
|
||||
/>
|
||||
</ModalContent>
|
||||
|
|
|
@ -177,7 +177,7 @@ class InteractiveImportRow extends Component {
|
|||
|
||||
const artistName = artist ? artist.artistName : '';
|
||||
const albumTitle = album ? album.title : '';
|
||||
const trackNumbers = tracks.map((episode) => episode.trackNumber)
|
||||
const trackNumbers = tracks.map((track) => track.trackNumber)
|
||||
.join(', ');
|
||||
|
||||
const showArtistPlaceholder = isSelected && !artist;
|
||||
|
|
|
@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
|
|||
import React, { Component } from 'react';
|
||||
import { createSelector } from 'reselect';
|
||||
import connectSection from 'Store/connectSection';
|
||||
import { fetchEpisodes, setEpisodesSort, clearEpisodes } from 'Store/Actions/episodeActions';
|
||||
import { fetchTracks, setTracksSort, clearTracks } from 'Store/Actions/trackActions';
|
||||
import { updateInteractiveImportItem } from 'Store/Actions/interactiveImportActions';
|
||||
import createClientSideCollectionSelector from 'Store/Selectors/createClientSideCollectionSelector';
|
||||
import SelectTrackModalContent from './SelectTrackModalContent';
|
||||
|
@ -11,16 +11,16 @@ import SelectTrackModalContent from './SelectTrackModalContent';
|
|||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
createClientSideCollectionSelector(),
|
||||
(episodes) => {
|
||||
return episodes;
|
||||
(tracks) => {
|
||||
return tracks;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
const mapDispatchToProps = {
|
||||
fetchEpisodes,
|
||||
setEpisodesSort,
|
||||
clearEpisodes,
|
||||
fetchTracks,
|
||||
setTracksSort,
|
||||
clearTracks,
|
||||
updateInteractiveImportItem
|
||||
};
|
||||
|
||||
|
@ -35,25 +35,25 @@ class SelectTrackModalContentConnector extends Component {
|
|||
albumId
|
||||
} = this.props;
|
||||
|
||||
this.props.fetchEpisodes({ artistId, albumId });
|
||||
this.props.fetchTracks({ artistId, albumId });
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
// This clears the episodes for the queue and hides the queue
|
||||
// We'll need another place to store episodes for manual import
|
||||
this.props.clearEpisodes();
|
||||
// This clears the tracks for the queue and hides the queue
|
||||
// We'll need another place to store tracks for manual import
|
||||
this.props.clearTracks();
|
||||
}
|
||||
|
||||
//
|
||||
// Listeners
|
||||
|
||||
onSortPress = (sortKey, sortDirection) => {
|
||||
this.props.setEpisodesSort({ sortKey, sortDirection });
|
||||
this.props.setTracksSort({ sortKey, sortDirection });
|
||||
}
|
||||
|
||||
onTracksSelect = (episodeIds) => {
|
||||
onTracksSelect = (trackIds) => {
|
||||
const tracks = _.reduce(this.props.items, (acc, item) => {
|
||||
if (episodeIds.indexOf(item.id) > -1) {
|
||||
if (trackIds.indexOf(item.id) > -1) {
|
||||
acc.push(item);
|
||||
}
|
||||
|
||||
|
@ -87,9 +87,9 @@ SelectTrackModalContentConnector.propTypes = {
|
|||
artistId: PropTypes.number.isRequired,
|
||||
albumId: PropTypes.number.isRequired,
|
||||
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
fetchEpisodes: PropTypes.func.isRequired,
|
||||
setEpisodesSort: PropTypes.func.isRequired,
|
||||
clearEpisodes: PropTypes.func.isRequired,
|
||||
fetchTracks: PropTypes.func.isRequired,
|
||||
setTracksSort: PropTypes.func.isRequired,
|
||||
clearTracks: PropTypes.func.isRequired,
|
||||
updateInteractiveImportItem: PropTypes.func.isRequired,
|
||||
onModalClose: PropTypes.func.isRequired
|
||||
};
|
||||
|
@ -99,5 +99,5 @@ export default connectSection(
|
|||
mapDispatchToProps,
|
||||
undefined,
|
||||
undefined,
|
||||
{ section: 'episodes' }
|
||||
{ section: 'tracks' }
|
||||
)(SelectTrackModalContentConnector);
|
||||
|
|
|
@ -84,6 +84,14 @@ export const CLEAR_EPISODES = 'CLEAR_EPISODES';
|
|||
export const TOGGLE_EPISODE_MONITORED = 'TOGGLE_EPISODE_MONITORED';
|
||||
export const TOGGLE_EPISODES_MONITORED = 'TOGGLE_EPISODES_MONITORED';
|
||||
|
||||
//
|
||||
// Tracks
|
||||
|
||||
export const FETCH_TRACKS = 'FETCH_TRACKS';
|
||||
export const SET_TRACKS_SORT = 'SET_TRACKS_SORT';
|
||||
export const SET_TRACKS_TABLE_OPTION = 'SET_TRACKS_TABLE_OPTION';
|
||||
export const CLEAR_TRACKS = 'CLEAR_TRACKS';
|
||||
|
||||
//
|
||||
// Episode Files
|
||||
|
||||
|
|
11
frontend/src/Store/Actions/trackActionHandlers.js
Normal file
11
frontend/src/Store/Actions/trackActionHandlers.js
Normal file
|
@ -0,0 +1,11 @@
|
|||
import createFetchHandler from './Creators/createFetchHandler';
|
||||
import * as types from './actionTypes';
|
||||
|
||||
const section = 'tracks';
|
||||
|
||||
const trackActionHandlers = {
|
||||
[types.FETCH_TRACKS]: createFetchHandler(section, '/track')
|
||||
|
||||
};
|
||||
|
||||
export default trackActionHandlers;
|
8
frontend/src/Store/Actions/trackActions.js
Normal file
8
frontend/src/Store/Actions/trackActions.js
Normal file
|
@ -0,0 +1,8 @@
|
|||
import { createAction } from 'redux-actions';
|
||||
import * as types from './actionTypes';
|
||||
import trackActionHandlers from './trackActionHandlers';
|
||||
|
||||
export const fetchTracks = trackActionHandlers[types.FETCH_TRACKS];
|
||||
export const setTracksSort = createAction(types.SET_TRACKS_SORT);
|
||||
export const setTracksTableOption = createAction(types.SET_TRACKS_TABLE_OPTION);
|
||||
export const clearTracks = createAction(types.CLEAR_TRACKS);
|
|
@ -2,6 +2,7 @@ import _ from 'lodash';
|
|||
import persistState from 'redux-localstorage';
|
||||
import * as addArtistReducers from 'Store/Reducers/addArtistReducers';
|
||||
import * as episodeReducers from 'Store/Reducers/episodeReducers';
|
||||
import * as trackReducers from 'Store/Reducers/trackReducers';
|
||||
import * as artistIndexReducers from 'Store/Reducers/artistIndexReducers';
|
||||
import * as artistEditorReducers from 'Store/Reducers/artistEditorReducers';
|
||||
import * as albumStudioReducers from 'Store/Reducers/albumStudioReducers';
|
||||
|
@ -17,6 +18,7 @@ import * as queueReducers from 'Store/Reducers/queueReducers';
|
|||
const reducers = [
|
||||
addArtistReducers,
|
||||
episodeReducers,
|
||||
trackReducers,
|
||||
artistIndexReducers,
|
||||
artistEditorReducers,
|
||||
albumStudioReducers,
|
||||
|
|
|
@ -13,6 +13,7 @@ import history, { defaultState as defaultHistoryState } from './historyReducers'
|
|||
import queue, { defaultState as defaultQueueState } from './queueReducers';
|
||||
import blacklist, { defaultState as defaultBlacklistState } from './blacklistReducers';
|
||||
import episodes, { defaultState as defaultEpisodesState } from './episodeReducers';
|
||||
import tracks, { defaultState as defaultTracksState } from './trackReducers';
|
||||
import episodeFiles, { defaultState as defaultEpisodeFilesState } from './episodeFileReducers';
|
||||
import albumHistory, { defaultState as defaultAlbumHistoryState } from './albumHistoryReducers';
|
||||
import releases, { defaultState as defaultReleasesState } from './releaseReducers';
|
||||
|
@ -41,6 +42,7 @@ export const defaultState = {
|
|||
queue: defaultQueueState,
|
||||
blacklist: defaultBlacklistState,
|
||||
episodes: defaultEpisodesState,
|
||||
tracks: defaultTracksState,
|
||||
episodeFiles: defaultEpisodeFilesState,
|
||||
albumHistory: defaultAlbumHistoryState,
|
||||
releases: defaultReleasesState,
|
||||
|
@ -70,6 +72,7 @@ export default enableBatching(combineReducers({
|
|||
queue,
|
||||
blacklist,
|
||||
episodes,
|
||||
tracks,
|
||||
episodeFiles,
|
||||
albumHistory,
|
||||
releases,
|
||||
|
|
70
frontend/src/Store/Reducers/trackReducers.js
Normal file
70
frontend/src/Store/Reducers/trackReducers.js
Normal file
|
@ -0,0 +1,70 @@
|
|||
import { handleActions } from 'redux-actions';
|
||||
import * as types from 'Store/Actions/actionTypes';
|
||||
import { sortDirections } from 'Helpers/Props';
|
||||
import createSetReducer from './Creators/createSetReducer';
|
||||
import createSetTableOptionReducer from './Creators/createSetTableOptionReducer';
|
||||
import createUpdateReducer from './Creators/createUpdateReducer';
|
||||
import createUpdateItemReducer from './Creators/createUpdateItemReducer';
|
||||
import createSetClientSideCollectionSortReducer from './Creators/createSetClientSideCollectionSortReducer';
|
||||
|
||||
export const defaultState = {
|
||||
isFetching: false,
|
||||
isPopulated: false,
|
||||
error: null,
|
||||
sortKey: 'trackNumber',
|
||||
sortDirection: sortDirections.DESCENDING,
|
||||
items: [],
|
||||
|
||||
columns: [
|
||||
{
|
||||
name: 'trackNumber',
|
||||
label: 'Track Number',
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'title',
|
||||
label: 'Title',
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'duration',
|
||||
label: 'Duration',
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'actions',
|
||||
columnLabel: 'Actions',
|
||||
isVisible: true,
|
||||
isModifiable: false
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
export const persistState = [
|
||||
'tracks.columns'
|
||||
];
|
||||
|
||||
const reducerSection = 'tracks';
|
||||
|
||||
const trackReducers = handleActions({
|
||||
|
||||
[types.SET]: createSetReducer(reducerSection),
|
||||
[types.UPDATE]: createUpdateReducer(reducerSection),
|
||||
[types.UPDATE_ITEM]: createUpdateItemReducer(reducerSection),
|
||||
|
||||
[types.SET_TRACKS_TABLE_OPTION]: createSetTableOptionReducer(reducerSection),
|
||||
|
||||
[types.CLEAR_TRACKS]: (state) => {
|
||||
return Object.assign({}, state, {
|
||||
isFetching: false,
|
||||
isPopulated: false,
|
||||
error: null,
|
||||
items: []
|
||||
});
|
||||
},
|
||||
|
||||
[types.SET_TRACKS_SORT]: createSetClientSideCollectionSortReducer(reducerSection)
|
||||
|
||||
}, defaultState);
|
||||
|
||||
export default trackReducers;
|
14
frontend/src/Store/Selectors/createTrackSelector.js
Normal file
14
frontend/src/Store/Selectors/createTrackSelector.js
Normal file
|
@ -0,0 +1,14 @@
|
|||
import _ from 'lodash';
|
||||
import { createSelector } from 'reselect';
|
||||
|
||||
function createTrackSelector() {
|
||||
return createSelector(
|
||||
(state, { trackId }) => trackId,
|
||||
(state) => state.tracks,
|
||||
(trackId, tracks) => {
|
||||
return _.find(tracks.items, { id: trackId });
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
export default createTrackSelector;
|
Loading…
Add table
Reference in a new issue