diff --git a/frontend/src/Activity/Queue/Queue.js b/frontend/src/Activity/Queue/Queue.js
index fefde01e3..5dc585c42 100644
--- a/frontend/src/Activity/Queue/Queue.js
+++ b/frontend/src/Activity/Queue/Queue.js
@@ -6,7 +6,6 @@ import getSelectedIds from 'Utilities/Table/getSelectedIds';
import selectAll from 'Utilities/Table/selectAll';
import toggleSelected from 'Utilities/Table/toggleSelected';
import { icons } from 'Helpers/Props';
-import episodeEntities from 'Episode/episodeEntities';
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import Table from 'Components/Table/Table';
import TableBody from 'Components/Table/TableBody';
@@ -100,7 +99,9 @@ class Queue extends Component {
isPopulated,
error,
items,
+ isAlbumsFetching,
isAlbumsPopulated,
+ episodesError,
columns,
totalRecords,
isGrabbing,
@@ -118,8 +119,9 @@ class Queue extends Component {
isPendingSelected
} = this.state;
- const isRefreshing = isFetching || isCheckForFinishedDownloadExecuting;
+ const isRefreshing = isFetching || isAlbumsFetching || isCheckForFinishedDownloadExecuting;
const isAllPopulated = isPopulated && (isAlbumsPopulated || !items.length);
+ const hasError = error || episodesError;
const selectedCount = this.getSelectedIds().length;
const disableSelectedActions = selectedCount === 0;
@@ -161,21 +163,21 @@ class Queue extends Component {
}
{
- !isRefreshing && error &&
+ !isRefreshing && hasError &&
Failed to load Queue
}
{
- isAllPopulated && !error && !items.length &&
+ isPopulated && !hasError && !items.length &&
Queue is empty
}
{
- isAllPopulated && !error && !!items.length &&
+ isAllPopulated && !hasError && !!items.length &&
state.episodes,
(state) => state.queue.paged,
- (state) => state.queue.queueEpisodes,
createCommandsSelector(),
- (queue, queueEpisodes, commands) => {
+ (episodes, queue, commands) => {
const isCheckForFinishedDownloadExecuting = _.some(commands, { name: commandNames.CHECK_FOR_FINISHED_DOWNLOAD });
return {
+ isAlbumsFetching: episodes.isFetching,
+ isAlbumsPopulated: episodes.isPopulated,
+ episodesError: episodes.error,
isCheckForFinishedDownloadExecuting,
- isAlbumsPopulated: queueEpisodes.isPopulated,
...queue
};
}
@@ -30,6 +33,7 @@ function createMapStateToProps() {
const mapDispatchToProps = {
...queueActions,
+ fetchEpisodes,
clearEpisodes,
executeCommand
};
@@ -45,14 +49,9 @@ class QueueConnector extends Component {
componentDidUpdate(prevProps) {
if (hasDifferentItems(prevProps.items, this.props.items)) {
- const episodes = _.uniqBy(_.reduce(this.props.items, (result, item) => {
- result.push(item.album);
+ const albumIds = selectUniqueIds(this.props.items, 'albumId');
+ this.props.fetchEpisodes({ albumIds });
- return result;
- }, []), ({ id }) => id);
-
- this.props.clearEpisodes();
- this.props.setQueueEpisodes({ episodes });
}
}
@@ -143,9 +142,9 @@ QueueConnector.propTypes = {
setQueueSort: PropTypes.func.isRequired,
setQueueTableOption: PropTypes.func.isRequired,
clearQueue: PropTypes.func.isRequired,
- setQueueEpisodes: PropTypes.func.isRequired,
grabQueueItems: PropTypes.func.isRequired,
removeQueueItems: PropTypes.func.isRequired,
+ fetchEpisodes: PropTypes.func.isRequired,
clearEpisodes: PropTypes.func.isRequired,
executeCommand: PropTypes.func.isRequired
};
diff --git a/frontend/src/Activity/Queue/QueueRow.js b/frontend/src/Activity/Queue/QueueRow.js
index 41840f735..fb2af3518 100644
--- a/frontend/src/Activity/Queue/QueueRow.js
+++ b/frontend/src/Activity/Queue/QueueRow.js
@@ -63,7 +63,6 @@ class QueueRow extends Component {
const {
id,
downloadId,
- episodeEntity,
title,
status,
trackedDownloadStatus,
@@ -161,7 +160,6 @@ class QueueRow extends Component {
episodeId={episode.id}
artistId={series.id}
trackFileId={episode.trackFileId}
- episodeEntity={episodeEntity}
episodeTitle={episode.title}
showOpenArtistButton={true}
/>
@@ -295,7 +293,6 @@ class QueueRow extends Component {
QueueRow.propTypes = {
id: PropTypes.number.isRequired,
downloadId: PropTypes.string,
- episodeEntity: PropTypes.string.isRequired,
title: PropTypes.string.isRequired,
status: PropTypes.string.isRequired,
trackedDownloadStatus: PropTypes.string,
diff --git a/frontend/src/Activity/Queue/QueueRowConnector.js b/frontend/src/Activity/Queue/QueueRowConnector.js
index 0da6a1abc..d34d3af51 100644
--- a/frontend/src/Activity/Queue/QueueRowConnector.js
+++ b/frontend/src/Activity/Queue/QueueRowConnector.js
@@ -67,7 +67,6 @@ class QueueRowConnector extends Component {
QueueRowConnector.propTypes = {
id: PropTypes.number.isRequired,
- episodeEntity: PropTypes.string.isRequired,
episode: PropTypes.object,
grabQueueItem: PropTypes.func.isRequired,
removeQueueItem: PropTypes.func.isRequired
diff --git a/frontend/src/Components/Page/Sidebar/PageSidebar.js b/frontend/src/Components/Page/Sidebar/PageSidebar.js
index d805669df..d03938b2d 100644
--- a/frontend/src/Components/Page/Sidebar/PageSidebar.js
+++ b/frontend/src/Components/Page/Sidebar/PageSidebar.js
@@ -340,29 +340,6 @@ class PageSidebar extends Component {
this._touchStartY = touchStartY;
}
- onTouchEnd = (event) => {
- const touches = event.changedTouches;
- const currentTouch = touches[0].pageX;
-
- if (!this._touchStartX) {
- return;
- }
-
- if (currentTouch > this._touchStartX && currentTouch > 50) {
- this._setSidebarTransform(true, 'none');
- } else if (currentTouch < this._touchStartX && currentTouch < 80) {
- this._setSidebarTransform(false, 'transform 50ms ease-in-out');
- } else {
- this._setSidebarTransform(this.props.isSidebarVisible);
- }
-
- this._touchStartX = null;
- }
-
- onTouchCancel = (event) => {
- this._touchStartX = null;
- }
-
onTouchMove = (event) => {
const touches = event.touches;
const currentTouchX = touches[0].pageX;
@@ -393,6 +370,31 @@ class PageSidebar extends Component {
});
}
+ onTouchEnd = (event) => {
+ const touches = event.changedTouches;
+ const currentTouch = touches[0].pageX;
+
+ if (!this._touchStartX) {
+ return;
+ }
+
+ if (currentTouch > this._touchStartX && currentTouch > 50) {
+ this._setSidebarTransform(true, 'none');
+ } else if (currentTouch < this._touchStartX && currentTouch < 80) {
+ this._setSidebarTransform(false, 'transform 50ms ease-in-out');
+ } else {
+ this._setSidebarTransform(this.props.isSidebarVisible);
+ }
+
+ this._touchStartX = null;
+ this._touchStartY = null;
+ }
+
+ onTouchCancel = (event) => {
+ this._touchStartX = null;
+ this._touchStartY = null;
+ }
+
onItemPress = () => {
this.props.onSidebarVisibleChange(false);
}
diff --git a/frontend/src/Episode/episodeEntities.js b/frontend/src/Episode/episodeEntities.js
index 7e6ea91e9..175b6fc54 100644
--- a/frontend/src/Episode/episodeEntities.js
+++ b/frontend/src/Episode/episodeEntities.js
@@ -1,13 +1,13 @@
export const CALENDAR = 'calendar';
export const EPISODES = 'episodes';
-export const QUEUE_EPISODES = 'queue.queueEpisodes';
+export const INTERACTIVE_IMPORT = 'interactiveImport.interactiveImportAlbums';
export const WANTED_CUTOFF_UNMET = 'wanted.cutoffUnmet';
export const WANTED_MISSING = 'wanted.missing';
export default {
CALENDAR,
EPISODES,
- QUEUE_EPISODES,
+ INTERACTIVE_IMPORT,
WANTED_CUTOFF_UNMET,
WANTED_MISSING
};
diff --git a/frontend/src/InteractiveImport/Album/SelectAlbumModalContentConnector.js b/frontend/src/InteractiveImport/Album/SelectAlbumModalContentConnector.js
index 7e178ade8..8694a5485 100644
--- a/frontend/src/InteractiveImport/Album/SelectAlbumModalContentConnector.js
+++ b/frontend/src/InteractiveImport/Album/SelectAlbumModalContentConnector.js
@@ -1,11 +1,14 @@
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 { fetchEpisodes, setEpisodesSort, clearEpisodes } from 'Store/Actions/episodeActions';
+import connectSection from 'Store/connectSection';
+import {
+ updateInteractiveImportItem,
+ fetchInteractiveImportAlbums,
+ setInteractiveImportAlbumsSort,
+ clearInteractiveImportAlbums
+} from 'Store/Actions/interactiveImportActions';
import createClientSideCollectionSelector from 'Store/Selectors/createClientSideCollectionSelector';
import SelectAlbumModalContent from './SelectAlbumModalContent';
@@ -19,9 +22,9 @@ function createMapStateToProps() {
}
const mapDispatchToProps = {
- fetchEpisodes,
- setEpisodesSort,
- clearEpisodes,
+ fetchInteractiveImportAlbums,
+ setInteractiveImportAlbumsSort,
+ clearInteractiveImportAlbums,
updateInteractiveImportItem
};
@@ -35,18 +38,22 @@ class SelectAlbumModalContentConnector extends Component {
artistId
} = this.props;
- this.props.fetchEpisodes({ artistId });
+ this.props.fetchInteractiveImportAlbums({ 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();
+ this.props.clearInteractiveImportAlbums();
}
//
// Listeners
+ onSortPress = (sortKey, sortDirection) => {
+ this.props.setInteractiveImportAlbumsSort({ sortKey, sortDirection });
+ }
+
onAlbumSelect = (albumId) => {
const album = _.find(this.props.items, { id: albumId });
@@ -78,17 +85,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,
+ fetchInteractiveImportAlbums: PropTypes.func.isRequired,
+ setInteractiveImportAlbumsSort: PropTypes.func.isRequired,
+ clearInteractiveImportAlbums: PropTypes.func.isRequired,
updateInteractiveImportItem: PropTypes.func.isRequired,
onModalClose: PropTypes.func.isRequired
};
export default connectSection(
- createMapStateToProps,
- mapDispatchToProps,
- undefined,
- undefined,
- { section: 'episodes' }
- )(SelectAlbumModalContentConnector);
+ createMapStateToProps,
+ mapDispatchToProps,
+ undefined,
+ undefined,
+ { section: 'interactiveImport.interactiveImportAlbums' }
+)(SelectAlbumModalContentConnector);
diff --git a/frontend/src/Store/Actions/actionTypes.js b/frontend/src/Store/Actions/actionTypes.js
index 78a626b1e..7f25e29e4 100644
--- a/frontend/src/Store/Actions/actionTypes.js
+++ b/frontend/src/Store/Actions/actionTypes.js
@@ -163,7 +163,6 @@ export const SET_QUEUE_SORT = 'SET_QUEUE_SORT';
export const SET_QUEUE_TABLE_OPTION = 'SET_QUEUE_TABLE_OPTION';
export const CLEAR_QUEUE = 'CLEAR_QUEUE';
-export const SET_QUEUE_EPISODES = 'SET_QUEUE_EPISODES';
export const GRAB_QUEUE_ITEM = 'GRAB_QUEUE_ITEM';
export const GRAB_QUEUE_ITEMS = 'GRAB_QUEUE_ITEMS';
export const REMOVE_QUEUE_ITEM = 'REMOVE_QUEUE_ITEM';
@@ -398,6 +397,10 @@ export const ADD_RECENT_FOLDER = 'ADD_RECENT_FOLDER';
export const REMOVE_RECENT_FOLDER = 'REMOVE_RECENT_FOLDER';
export const SET_INTERACTIVE_IMPORT_MODE = 'SET_INTERACTIVE_IMPORT_MODE';
+export const FETCH_INTERACTIVE_IMPORT_ALBUMS = 'FETCH_INTERACTIVE_IMPORT_ALBUMS';
+export const SET_INTERACTIVE_IMPORT_ALBUMS_SORT = 'SET_INTERACTIVE_IMPORT_ALBUMS_SORT';
+export const CLEAR_INTERACTIVE_IMPORT_ALBUMS = 'CLEAR_INTERACTIVE_IMPORT_ALBUMS';
+
//
// Root Folders
diff --git a/frontend/src/Store/Actions/interactiveImportActionHandlers.js b/frontend/src/Store/Actions/interactiveImportActionHandlers.js
index a46ce5702..811916bc0 100644
--- a/frontend/src/Store/Actions/interactiveImportActionHandlers.js
+++ b/frontend/src/Store/Actions/interactiveImportActionHandlers.js
@@ -1,5 +1,6 @@
import $ from 'jquery';
import { batchActions } from 'redux-batched-actions';
+import createFetchHandler from './Creators/createFetchHandler';
import * as types from './actionTypes';
import { set, update } from './baseActions';
@@ -42,7 +43,9 @@ const interactiveImportActionHandlers = {
}));
});
};
- }
+ },
+
+ [types.FETCH_INTERACTIVE_IMPORT_ALBUMS]: createFetchHandler('interactiveImportAlbums', '/album')
};
export default interactiveImportActionHandlers;
diff --git a/frontend/src/Store/Actions/interactiveImportActions.js b/frontend/src/Store/Actions/interactiveImportActions.js
index 880cd0ade..2a8da6efb 100644
--- a/frontend/src/Store/Actions/interactiveImportActions.js
+++ b/frontend/src/Store/Actions/interactiveImportActions.js
@@ -9,3 +9,7 @@ export const clearInteractiveImport = createAction(types.CLEAR_INTERACTIVE_IMPOR
export const addRecentFolder = createAction(types.ADD_RECENT_FOLDER);
export const removeRecentFolder = createAction(types.REMOVE_RECENT_FOLDER);
export const setInteractiveImportMode = createAction(types.SET_INTERACTIVE_IMPORT_MODE);
+
+export const fetchInteractiveImportAlbums = interactiveImportActionHandlers[types.FETCH_INTERACTIVE_IMPORT_ALBUMS];
+export const setInteractiveImportAlbumsSort = createAction(types.SET_INTERACTIVE_IMPORT_ALBUMS_SORT);
+export const clearInteractiveImportAlbums = createAction(types.CLEAR_INTERACTIVE_IMPORT_ALBUMS);
diff --git a/frontend/src/Store/Actions/queueActions.js b/frontend/src/Store/Actions/queueActions.js
index 9ef9b6a32..51dda41df 100644
--- a/frontend/src/Store/Actions/queueActions.js
+++ b/frontend/src/Store/Actions/queueActions.js
@@ -17,7 +17,6 @@ export const setQueueSort = queueActionHandlers[types.SET_QUEUE_SORT];
export const setQueueTableOption = createAction(types.SET_QUEUE_TABLE_OPTION);
export const clearQueue = createAction(types.CLEAR_QUEUE);
-export const setQueueEpisodes = createAction(types.SET_QUEUE_EPISODES);
export const grabQueueItem = queueActionHandlers[types.GRAB_QUEUE_ITEM];
export const grabQueueItems = queueActionHandlers[types.GRAB_QUEUE_ITEMS];
export const removeQueueItem = queueActionHandlers[types.REMOVE_QUEUE_ITEM];
diff --git a/frontend/src/Store/Reducers/interactiveImportReducers.js b/frontend/src/Store/Reducers/interactiveImportReducers.js
index 11a4e2fbe..70d36f70e 100644
--- a/frontend/src/Store/Reducers/interactiveImportReducers.js
+++ b/frontend/src/Store/Reducers/interactiveImportReducers.js
@@ -1,10 +1,12 @@
import _ from 'lodash';
import moment from 'moment';
import { handleActions } from 'redux-actions';
+import updateSectionState from 'Utilities/State/updateSectionState';
import * as types from 'Store/Actions/actionTypes';
import { sortDirections } from 'Helpers/Props';
import createSetReducer from './Creators/createSetReducer';
import createUpdateReducer from './Creators/createUpdateReducer';
+import createReducers from './Creators/createReducers';
import createSetClientSideCollectionSortReducer from './Creators/createSetClientSideCollectionSortReducer';
export const defaultState = {
@@ -26,6 +28,15 @@ export const defaultState = {
quality: function(item, direction) {
return item.quality.qualityWeight;
}
+ },
+
+ interactiveImportAlbums: {
+ isFetching: false,
+ isPopulated: false,
+ error: null,
+ sortKey: 'albumTitle',
+ sortDirection: sortDirections.DESCENDING,
+ items: []
}
};
@@ -35,11 +46,12 @@ export const persistState = [
];
const reducerSection = 'interactiveImport';
+const episodesSection = 'interactiveImportAlbums';
const interactiveImportReducers = handleActions({
- [types.SET]: createSetReducer(reducerSection),
- [types.UPDATE]: createUpdateReducer(reducerSection),
+ [types.SET]: createReducers([reducerSection, episodesSection], createSetReducer),
+ [types.UPDATE]: createReducers([reducerSection, episodesSection], createUpdateReducer),
[types.UPDATE_INTERACTIVE_IMPORT_ITEM]: (state, { payload }) => {
const id = payload.id;
@@ -90,6 +102,16 @@ const interactiveImportReducers = handleActions({
[types.SET_INTERACTIVE_IMPORT_MODE]: function(state, { payload }) {
return Object.assign({}, state, { importMode: payload.importMode });
+ },
+
+ [types.SET_INTERACTIVE_IMPORT_ALBUMS_SORT]: createSetClientSideCollectionSortReducer(episodesSection),
+
+ [types.CLEAR_INTERACTIVE_IMPORT_ALBUMS]: (state) => {
+ const section = episodesSection;
+
+ return updateSectionState(state, section, {
+ ...defaultState.interactiveImportAlbums
+ });
}
}, defaultState);
diff --git a/frontend/src/Store/Reducers/queueReducers.js b/frontend/src/Store/Reducers/queueReducers.js
index 325518226..368e94ed8 100644
--- a/frontend/src/Store/Reducers/queueReducers.js
+++ b/frontend/src/Store/Reducers/queueReducers.js
@@ -135,25 +135,7 @@ const queueReducers = handleActions({
isPopulated: false,
error: null,
items: []
- }),
-
- [types.SET_QUEUE_EPISODES]: function(state, { payload }) {
- const section = 'queueEpisodes';
-
- return updateSectionState(state, section, {
- isPopulated: true,
- items: payload.episodes
- });
- },
-
- [types.CLEAR_EPISODES]: (state) => {
- const section = 'queueEpisodes';
-
- return updateSectionState(state, section, {
- isPopulated: false,
- items: []
- });
- }
+ })
}, defaultState);
diff --git a/frontend/src/Store/Selectors/createClientSideCollectionSelector.js b/frontend/src/Store/Selectors/createClientSideCollectionSelector.js
index e5b740afc..56b75c80c 100644
--- a/frontend/src/Store/Selectors/createClientSideCollectionSelector.js
+++ b/frontend/src/Store/Selectors/createClientSideCollectionSelector.js
@@ -97,8 +97,8 @@ function sort(items, state) {
function createClientSideCollectionSelector() {
return createSelector(
- (state, { section }) => state[section],
- (state, { uiSection }) => state[uiSection],
+ (state, { section }) => _.get(state, section),
+ (state, { uiSection }) => _.get(state, uiSection),
(sectionState, uiSectionState = {}) => {
const state = Object.assign({}, sectionState, uiSectionState);
diff --git a/frontend/src/System/Status/MoreInfo/MoreInfo.js b/frontend/src/System/Status/MoreInfo/MoreInfo.js
index b3afec021..e573f1bfa 100644
--- a/frontend/src/System/Status/MoreInfo/MoreInfo.js
+++ b/frontend/src/System/Status/MoreInfo/MoreInfo.js
@@ -23,7 +23,7 @@ class MoreInfo extends Component {
Wiki
- wiki.lidarr.audio
+ wiki.lidarr.audio
Reddit
@@ -48,12 +48,12 @@ class MoreInfo extends Component {
Source
- github.com/Lidarr/Lidarr
+ github.com/Lidarr/Lidarr
Feature Requests
- github.com/Lidarr/Lidarr/issues
+ github.com/Lidarr/Lidarr/issues
diff --git a/src/Lidarr.Api.V3/Queue/QueueModule.cs b/src/Lidarr.Api.V3/Queue/QueueModule.cs
index 0d6c0ae94..8ecfdccfc 100644
--- a/src/Lidarr.Api.V3/Queue/QueueModule.cs
+++ b/src/Lidarr.Api.V3/Queue/QueueModule.cs
@@ -29,10 +29,10 @@ namespace Lidarr.Api.V3.Queue
private PagingResource GetQueue(PagingResource pagingResource)
{
var pagingSpec = pagingResource.MapToPagingSpec("timeleft", SortDirection.Ascending);
- var includeSeries = Request.GetBooleanQueryParameter("includeSeries");
- var includeEpisode = Request.GetBooleanQueryParameter("includeEpisode");
+ var includeArtist = Request.GetBooleanQueryParameter("includeArtist");
+ var includeAlbum = Request.GetBooleanQueryParameter("includeAlbum");
- return ApplyToPage(GetQueue, pagingSpec, (q) => MapToResource(q, includeSeries, includeEpisode));
+ return ApplyToPage(GetQueue, pagingSpec, (q) => MapToResource(q, includeArtist, includeAlbum));
}
private PagingSpec GetQueue(PagingSpec pagingSpec)