From f3e55a236c170a3a7806b8f843ce2cade19e2304 Mon Sep 17 00:00:00 2001 From: Qstick Date: Thu, 5 Oct 2017 22:32:26 -0400 Subject: [PATCH] Misc UI Fixes --- frontend/src/Activity/Queue/Queue.js | 17 ++++--- frontend/src/Activity/Queue/QueueConnector.js | 23 +++++---- frontend/src/Activity/Queue/QueueRow.js | 3 -- .../src/Activity/Queue/QueueRowConnector.js | 1 - .../Components/Page/Sidebar/PageSidebar.js | 48 ++++++++++--------- frontend/src/Episode/episodeEntities.js | 4 +- .../Album/SelectAlbumModalContentConnector.js | 43 ++++++++++------- frontend/src/Store/Actions/actionTypes.js | 5 +- .../interactiveImportActionHandlers.js | 5 +- .../Store/Actions/interactiveImportActions.js | 4 ++ frontend/src/Store/Actions/queueActions.js | 1 - .../Reducers/interactiveImportReducers.js | 26 +++++++++- frontend/src/Store/Reducers/queueReducers.js | 20 +------- .../createClientSideCollectionSelector.js | 4 +- .../src/System/Status/MoreInfo/MoreInfo.js | 6 +-- src/Lidarr.Api.V3/Queue/QueueModule.cs | 6 +-- 16 files changed, 118 insertions(+), 98 deletions(-) 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)