From 90d9741056c20126dea13eb0c40119f054da2632 Mon Sep 17 00:00:00 2001 From: Qstick Date: Sun, 24 Sep 2017 01:10:24 -0400 Subject: [PATCH] Fix Interactive Import, Add Track Actions and Reducers --- frontend/src/Components/SignalRConnector.js | 14 ++++ .../Album/SelectAlbumModalContent.js | 9 ++- .../Album/SelectAlbumModalContentConnector.js | 43 ++++++++++-- .../InteractiveImportModalContent.js | 2 +- .../Interactive/InteractiveImportRow.js | 2 +- .../Track/SelectTrackModalContentConnector.js | 34 ++++----- frontend/src/Store/Actions/actionTypes.js | 8 +++ .../src/Store/Actions/trackActionHandlers.js | 11 +++ frontend/src/Store/Actions/trackActions.js | 8 +++ frontend/src/Store/Middleware/persistState.js | 2 + frontend/src/Store/Reducers/index.js | 3 + frontend/src/Store/Reducers/trackReducers.js | 70 +++++++++++++++++++ .../Store/Selectors/createTrackSelector.js | 14 ++++ 13 files changed, 193 insertions(+), 27 deletions(-) create mode 100644 frontend/src/Store/Actions/trackActionHandlers.js create mode 100644 frontend/src/Store/Actions/trackActions.js create mode 100644 frontend/src/Store/Reducers/trackReducers.js create mode 100644 frontend/src/Store/Selectors/createTrackSelector.js diff --git a/frontend/src/Components/SignalRConnector.js b/frontend/src/Components/SignalRConnector.js index 7d84bdab2..873001e16 100644 --- a/frontend/src/Components/SignalRConnector.js +++ b/frontend/src/Components/SignalRConnector.js @@ -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({ diff --git a/frontend/src/InteractiveImport/Album/SelectAlbumModalContent.js b/frontend/src/InteractiveImport/Album/SelectAlbumModalContent.js index f5965fde2..c10abb588 100644 --- a/frontend/src/InteractiveImport/Album/SelectAlbumModalContent.js +++ b/frontend/src/InteractiveImport/Album/SelectAlbumModalContent.js @@ -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 { + { + isFetching && + + } { 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 }; diff --git a/frontend/src/InteractiveImport/Album/SelectAlbumModalContentConnector.js b/frontend/src/InteractiveImport/Album/SelectAlbumModalContentConnector.js index 7adb8a1f7..81593379c 100644 --- a/frontend/src/InteractiveImport/Album/SelectAlbumModalContentConnector.js +++ b/frontend/src/InteractiveImport/Album/SelectAlbumModalContentConnector.js @@ -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); diff --git a/frontend/src/InteractiveImport/Interactive/InteractiveImportModalContent.js b/frontend/src/InteractiveImport/Interactive/InteractiveImportModalContent.js index 346222373..5997053f1 100644 --- a/frontend/src/InteractiveImport/Interactive/InteractiveImportModalContent.js +++ b/frontend/src/InteractiveImport/Interactive/InteractiveImportModalContent.js @@ -293,7 +293,7 @@ class InteractiveImportModalContent extends Component { diff --git a/frontend/src/InteractiveImport/Interactive/InteractiveImportRow.js b/frontend/src/InteractiveImport/Interactive/InteractiveImportRow.js index f28fe503a..e19d588de 100644 --- a/frontend/src/InteractiveImport/Interactive/InteractiveImportRow.js +++ b/frontend/src/InteractiveImport/Interactive/InteractiveImportRow.js @@ -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; diff --git a/frontend/src/InteractiveImport/Track/SelectTrackModalContentConnector.js b/frontend/src/InteractiveImport/Track/SelectTrackModalContentConnector.js index 368b7b9fd..52cf7c705 100644 --- a/frontend/src/InteractiveImport/Track/SelectTrackModalContentConnector.js +++ b/frontend/src/InteractiveImport/Track/SelectTrackModalContentConnector.js @@ -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); diff --git a/frontend/src/Store/Actions/actionTypes.js b/frontend/src/Store/Actions/actionTypes.js index a88c1b8b8..39edf059e 100644 --- a/frontend/src/Store/Actions/actionTypes.js +++ b/frontend/src/Store/Actions/actionTypes.js @@ -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 diff --git a/frontend/src/Store/Actions/trackActionHandlers.js b/frontend/src/Store/Actions/trackActionHandlers.js new file mode 100644 index 000000000..efda3ae40 --- /dev/null +++ b/frontend/src/Store/Actions/trackActionHandlers.js @@ -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; diff --git a/frontend/src/Store/Actions/trackActions.js b/frontend/src/Store/Actions/trackActions.js new file mode 100644 index 000000000..57d503c12 --- /dev/null +++ b/frontend/src/Store/Actions/trackActions.js @@ -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); diff --git a/frontend/src/Store/Middleware/persistState.js b/frontend/src/Store/Middleware/persistState.js index 19bdf87b8..456efc470 100644 --- a/frontend/src/Store/Middleware/persistState.js +++ b/frontend/src/Store/Middleware/persistState.js @@ -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, diff --git a/frontend/src/Store/Reducers/index.js b/frontend/src/Store/Reducers/index.js index ca9d51903..66d5ce808 100644 --- a/frontend/src/Store/Reducers/index.js +++ b/frontend/src/Store/Reducers/index.js @@ -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, diff --git a/frontend/src/Store/Reducers/trackReducers.js b/frontend/src/Store/Reducers/trackReducers.js new file mode 100644 index 000000000..bf17b4d5d --- /dev/null +++ b/frontend/src/Store/Reducers/trackReducers.js @@ -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; diff --git a/frontend/src/Store/Selectors/createTrackSelector.js b/frontend/src/Store/Selectors/createTrackSelector.js new file mode 100644 index 000000000..be57e6ca0 --- /dev/null +++ b/frontend/src/Store/Selectors/createTrackSelector.js @@ -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;