From 64a8d02f77ff7917b1078461dafb122387ed05fc Mon Sep 17 00:00:00 2001 From: Qstick Date: Sat, 22 Sep 2018 23:10:50 -0400 Subject: [PATCH] New: Server Side UI Filtering, Error Boundaries (#501) Co-Authored-By: Mark McDowall --- frontend/gulp/start.js | 2 +- .../src/Activity/Blacklist/BlacklistRow.js | 4 +- frontend/src/Activity/History/HistoryRow.js | 4 +- frontend/src/Activity/Queue/QueueRow.js | 4 +- frontend/src/Album/AlbumSearchCell.js | 4 +- frontend/src/Album/Details/AlbumDetails.js | 4 +- frontend/src/Album/EpisodeStatus.js | 4 +- .../{EpisodeQuality.js => TrackQuality.js} | 8 +- frontend/src/AlbumStudio/AlbumStudio.js | 4 +- .../AlbumStudioFilterModalConnector.js | 24 +++++ .../src/Artist/Details/AlbumRowConnector.js | 2 +- frontend/src/Artist/Details/ArtistDetails.js | 1 + frontend/src/Artist/Editor/ArtistEditor.js | 2 +- .../ArtistEditorFilterModalConnector.js | 21 ++-- frontend/src/Artist/Editor/ArtistEditorRow.js | 4 + .../src/Artist/History/ArtistHistoryRow.js | 4 +- frontend/src/Artist/Index/ArtistIndex.js | 4 +- .../Index/ArtistIndexFilterModalConnector.js | 21 ++-- .../Index/Banners/ArtistIndexBanners.js | 2 +- .../Index/Menus/ArtistIndexFilterMenu.js | 2 +- .../Index/Overview/ArtistIndexOverviews.js | 2 +- .../Index/Posters/ArtistIndexPosters.js | 2 +- frontend/src/Calendar/CalendarPage.js | 2 +- frontend/src/Calendar/Events/CalendarEvent.js | 4 + .../src/Components/Error/ErrorBoundary.js | 62 +++++++++++ .../Components/Error/ErrorBoundaryError.css | 38 +++++++ .../Components/Error/ErrorBoundaryError.js | 60 +++++++++++ .../FileBrowserModalContentConnector.js | 2 +- .../Builder/FilterBuilderModalContent.js | 60 ++++++++--- .../FilterBuilderModalContentConnector.js | 30 ++++-- .../Filter/Builder/FilterBuilderRowValue.js | 68 ++++++++++-- .../Filter/CustomFilters/CustomFilter.js | 62 +++++++++-- .../CustomFiltersModalContent.js | 20 +++- .../CustomFiltersModalContentConnector.js | 23 ++++ frontend/src/Components/Filter/FilterModal.js | 16 +-- .../Components/Form/EnhancedSelectInput.js | 2 +- frontend/src/Components/Form/PathInput.js | 16 +-- frontend/src/Components/Form/TagInput.js | 4 + frontend/src/Components/Measure.js | 38 +++++++ frontend/src/Components/Menu/FilterMenu.js | 5 +- .../src/Components/Menu/FilterMenuContent.js | 6 +- .../src/Components/Menu/FilterMenuItem.js | 4 +- frontend/src/Components/Modal/Modal.js | 12 ++- frontend/src/Components/Modal/ModalError.css | 15 +++ frontend/src/Components/Modal/ModalError.js | 46 ++++++++ frontend/src/Components/Page/ErrorPage.js | 14 ++- frontend/src/Components/Page/PageConnector.js | 12 ++- frontend/src/Components/Page/PageContent.js | 14 ++- .../src/Components/Page/PageContentError.css | 3 + .../src/Components/Page/PageContentError.js | 19 ++++ frontend/src/Components/Page/PageJumpBar.js | 2 +- .../Page/Toolbar/PageToolbarSection.js | 2 +- frontend/src/Components/Table/VirtualTable.js | 2 +- frontend/src/Content/Images/error.png | Bin 0 -> 163647 bytes .../Helpers/Props/filterBuilderValueTypes.js | 1 + .../Interactive/InteractiveImportRow.js | 4 +- .../InteractiveSearchFilterModalConnector.js | 13 ++- .../InteractiveSearchModal.js} | 10 +- .../InteractiveSearchModalContent.css} | 0 .../InteractiveSearchModalContent.js} | 14 +-- ...InteractiveSearchModalContentConnector.js} | 10 +- .../InteractiveSearchRow.css} | 0 .../InteractiveSearchRow.js} | 14 +-- .../Search => InteractiveSearch}/Peers.js | 0 .../Settings/Profiles/Delay/DelayProfiles.js | 2 +- .../Quality/EditQualityProfileModalContent.js | 2 +- .../Profiles/Quality/QualityProfileItems.js | 2 +- .../Reducers/createCustomFilterReducers.js | 65 ------------ .../Creators/createRemoveItemHandler.js | 6 +- .../Creators/createSaveProviderHandler.js | 5 +- .../src/Store/Actions/albumStudioActions.js | 52 ++++++++- frontend/src/Store/Actions/artistActions.js | 14 +++ .../src/Store/Actions/artistEditorActions.js | 16 +-- .../src/Store/Actions/artistIndexActions.js | 20 +--- .../src/Store/Actions/customFilterActions.js | 55 ++++++++++ frontend/src/Store/Actions/index.js | 2 + frontend/src/Store/Actions/oAuthActions.js | 27 ++++- frontend/src/Store/Actions/releaseActions.js | 21 +--- .../Middleware/createSentryMiddleware.js | 91 ++++++++++++++++ frontend/src/Store/Middleware/middlewares.js | 8 +- .../src/Store/Middleware/sentryMiddleware.js | 51 --------- .../createClientSideCollectionSelector.js | 17 ++- frontend/src/System/Status/About/About.js | 34 ++++-- .../src/System/Status/About/AboutConnector.js | 8 +- frontend/src/System/Status/About/StartTime.js | 93 ++++++++++++++++ .../Editor/TrackFileEditorModalContent.js | 12 ++- .../TrackFile/Editor/TrackFileEditorRow.js | 4 +- .../Utilities/Filter/findSelectedFilters.js | 6 +- .../src/Utilities/Number/convertToBytes.js | 15 +++ .../src/Utilities/State/getProviderState.js | 7 +- .../src/Utilities/customFilterHandlers.js | 6 -- frontend/src/Wanted/Missing/MissingRow.js | 4 + package.json | 9 +- .../CustomFilters/CustomFilterModule.cs | 49 +++++++++ .../CustomFilters/CustomFilterResource.cs | 49 +++++++++ src/Lidarr.Api.V1/Lidarr.Api.V1.csproj | 2 + src/NzbDrone.Common/NzbDrone.Common.csproj | 5 +- src/NzbDrone.Common/packages.config | 2 +- .../CustomFilters/CustomFilter.cs | 11 ++ .../CustomFilters/CustomFilterRepository.cs | 17 +++ .../CustomFilters/CustomFilterService.cs | 49 +++++++++ .../Migration/021_add_custom_filters.cs | 17 +++ src/NzbDrone.Core/Datastore/TableMapping.cs | 3 + .../Plex/PlexTv/PlexTvPinUrlResponse.cs | 11 ++ .../Notifications/Plex/PlexTv/PlexTvProxy.cs | 27 +++-- .../Plex/PlexTv/PlexTvService.cs | 65 +++++++++--- .../Notifications/Plex/Server/PlexServer.cs | 35 +++++- .../Plex/Server/PlexServerProxy.cs | 38 +++++++ src/NzbDrone.Core/NzbDrone.Core.csproj | 5 + yarn.lock | 100 ++++++++++++++---- 110 files changed, 1565 insertions(+), 432 deletions(-) rename frontend/src/Album/{EpisodeQuality.js => TrackQuality.js} (88%) create mode 100644 frontend/src/AlbumStudio/AlbumStudioFilterModalConnector.js create mode 100644 frontend/src/Components/Error/ErrorBoundary.js create mode 100644 frontend/src/Components/Error/ErrorBoundaryError.css create mode 100644 frontend/src/Components/Error/ErrorBoundaryError.js create mode 100644 frontend/src/Components/Filter/CustomFilters/CustomFiltersModalContentConnector.js create mode 100644 frontend/src/Components/Measure.js create mode 100644 frontend/src/Components/Modal/ModalError.css create mode 100644 frontend/src/Components/Modal/ModalError.js create mode 100644 frontend/src/Components/Page/PageContentError.css create mode 100644 frontend/src/Components/Page/PageContentError.js create mode 100644 frontend/src/Content/Images/error.png rename frontend/src/{Album/Search => InteractiveSearch}/InteractiveSearchFilterModalConnector.js (64%) rename frontend/src/{Album/Search/InteractiveAlbumSearchModal.js => InteractiveSearch/InteractiveSearchModal.js} (60%) rename frontend/src/{Album/Search/InteractiveAlbumSearchModalContent.css => InteractiveSearch/InteractiveSearchModalContent.css} (100%) rename frontend/src/{Album/Search/InteractiveAlbumSearchModalContent.js => InteractiveSearch/InteractiveSearchModalContent.js} (93%) rename frontend/src/{Album/Search/InteractiveAlbumSearchModalContentConnector.js => InteractiveSearch/InteractiveSearchModalContentConnector.js} (87%) rename frontend/src/{Album/Search/InteractiveAlbumSearchRow.css => InteractiveSearch/InteractiveSearchRow.css} (100%) rename frontend/src/{Album/Search/InteractiveAlbumSearchRow.js => InteractiveSearch/InteractiveSearchRow.js} (94%) rename frontend/src/{Album/Search => InteractiveSearch}/Peers.js (100%) delete mode 100644 frontend/src/Store/Actions/Creators/Reducers/createCustomFilterReducers.js create mode 100644 frontend/src/Store/Actions/customFilterActions.js create mode 100644 frontend/src/Store/Middleware/createSentryMiddleware.js delete mode 100644 frontend/src/Store/Middleware/sentryMiddleware.js create mode 100644 frontend/src/System/Status/About/StartTime.js create mode 100644 frontend/src/Utilities/Number/convertToBytes.js delete mode 100644 frontend/src/Utilities/customFilterHandlers.js create mode 100644 src/Lidarr.Api.V1/CustomFilters/CustomFilterModule.cs create mode 100644 src/Lidarr.Api.V1/CustomFilters/CustomFilterResource.cs create mode 100644 src/NzbDrone.Core/CustomFilters/CustomFilter.cs create mode 100644 src/NzbDrone.Core/CustomFilters/CustomFilterRepository.cs create mode 100644 src/NzbDrone.Core/CustomFilters/CustomFilterService.cs create mode 100644 src/NzbDrone.Core/Datastore/Migration/021_add_custom_filters.cs create mode 100644 src/NzbDrone.Core/Notifications/Plex/PlexTv/PlexTvPinUrlResponse.cs diff --git a/frontend/gulp/start.js b/frontend/gulp/start.js index e2f65660b..f6760bc25 100644 --- a/frontend/gulp/start.js +++ b/frontend/gulp/start.js @@ -1,4 +1,4 @@ -// will download and run sonarr (server) in a non-windows enviroment +// will download and run lidarr (server) in a non-windows enviroment // you can use this if you don't care about the server code and just want to work // with the web code. diff --git a/frontend/src/Activity/Blacklist/BlacklistRow.js b/frontend/src/Activity/Blacklist/BlacklistRow.js index f766ca1ad..366245825 100644 --- a/frontend/src/Activity/Blacklist/BlacklistRow.js +++ b/frontend/src/Activity/Blacklist/BlacklistRow.js @@ -6,7 +6,7 @@ import RelativeDateCellConnector from 'Components/Table/Cells/RelativeDateCellCo import TableRow from 'Components/Table/TableRow'; import TableRowCell from 'Components/Table/Cells/TableRowCell'; import EpisodeLanguage from 'Album/EpisodeLanguage'; -import EpisodeQuality from 'Album/EpisodeQuality'; +import TrackQuality from 'Album/TrackQuality'; import ArtistNameLink from 'Artist/ArtistNameLink'; import BlacklistDetailsModal from './BlacklistDetailsModal'; import styles from './BlacklistRow.css'; @@ -103,7 +103,7 @@ class BlacklistRow extends Component { key={name} className={styles.quality} > - diff --git a/frontend/src/Activity/History/HistoryRow.js b/frontend/src/Activity/History/HistoryRow.js index 5976d1da1..f09537c01 100644 --- a/frontend/src/Activity/History/HistoryRow.js +++ b/frontend/src/Activity/History/HistoryRow.js @@ -7,7 +7,7 @@ import TableRow from 'Components/Table/TableRow'; import TableRowCell from 'Components/Table/Cells/TableRowCell'; import AlbumTitleLink from 'Album/AlbumTitleLink'; import EpisodeLanguage from 'Album/EpisodeLanguage'; -import EpisodeQuality from 'Album/EpisodeQuality'; +import TrackQuality from 'Album/TrackQuality'; import ArtistNameLink from 'Artist/ArtistNameLink'; import HistoryEventTypeCell from './HistoryEventTypeCell'; import HistoryDetailsModal from './Details/HistoryDetailsModal'; @@ -142,7 +142,7 @@ class HistoryRow extends Component { if (name === 'quality') { return ( - diff --git a/frontend/src/Activity/Queue/QueueRow.js b/frontend/src/Activity/Queue/QueueRow.js index d7d116333..be7e7c911 100644 --- a/frontend/src/Activity/Queue/QueueRow.js +++ b/frontend/src/Activity/Queue/QueueRow.js @@ -10,7 +10,7 @@ import TableRowCell from 'Components/Table/Cells/TableRowCell'; import TableSelectCell from 'Components/Table/Cells/TableSelectCell'; import ProtocolLabel from 'Activity/Queue/ProtocolLabel'; import AlbumTitleLink from 'Album/AlbumTitleLink'; -import EpisodeQuality from 'Album/EpisodeQuality'; +import TrackQuality from 'Album/TrackQuality'; import InteractiveImportModal from 'InteractiveImport/InteractiveImportModal'; import ArtistNameLink from 'Artist/ArtistNameLink'; import QueueStatusCell from './QueueStatusCell'; @@ -177,7 +177,7 @@ class QueueRow extends Component { if (name === 'quality') { return ( - diff --git a/frontend/src/Album/AlbumSearchCell.js b/frontend/src/Album/AlbumSearchCell.js index 83b7c2f23..093924798 100644 --- a/frontend/src/Album/AlbumSearchCell.js +++ b/frontend/src/Album/AlbumSearchCell.js @@ -4,7 +4,7 @@ import { icons } from 'Helpers/Props'; import IconButton from 'Components/Link/IconButton'; import SpinnerIconButton from 'Components/Link/SpinnerIconButton'; import TableRowCell from 'Components/Table/Cells/TableRowCell'; -import InteractiveAlbumSearchModal from './Search/InteractiveAlbumSearchModal'; +import InteractiveSearchModal from 'InteractiveSearch/InteractiveSearchModal'; import styles from './AlbumSearchCell.css'; class AlbumSearchCell extends Component { @@ -55,7 +55,7 @@ class AlbumSearchCell extends Component { onPress={this.onManualSearchPress} /> - - - @@ -202,7 +204,7 @@ AlbumStudio.propTypes = { items: PropTypes.arrayOf(PropTypes.object).isRequired, sortKey: PropTypes.string, sortDirection: PropTypes.oneOf(sortDirections.all), - selectedFilterKey: PropTypes.string.isRequired, + selectedFilterKey: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired, filters: PropTypes.arrayOf(PropTypes.object).isRequired, customFilters: PropTypes.arrayOf(PropTypes.object).isRequired, isSaving: PropTypes.bool.isRequired, diff --git a/frontend/src/AlbumStudio/AlbumStudioFilterModalConnector.js b/frontend/src/AlbumStudio/AlbumStudioFilterModalConnector.js new file mode 100644 index 000000000..655601cca --- /dev/null +++ b/frontend/src/AlbumStudio/AlbumStudioFilterModalConnector.js @@ -0,0 +1,24 @@ +import { connect } from 'react-redux'; +import { createSelector } from 'reselect'; +import { setAlbumStudioFilter } from 'Store/Actions/albumStudioActions'; +import FilterModal from 'Components/Filter/FilterModal'; + +function createMapStateToProps() { + return createSelector( + (state) => state.artist.items, + (state) => state.albumStudio.filterBuilderProps, + (sectionItems, filterBuilderProps) => { + return { + sectionItems, + filterBuilderProps, + customFilterType: 'albumStudio' + }; + } + ); +} + +const mapDispatchToProps = { + dispatchSetFilter: setAlbumStudioFilter +}; + +export default connect(createMapStateToProps, mapDispatchToProps)(FilterModal); diff --git a/frontend/src/Artist/Details/AlbumRowConnector.js b/frontend/src/Artist/Details/AlbumRowConnector.js index 1c48af992..6e92fb1d4 100644 --- a/frontend/src/Artist/Details/AlbumRowConnector.js +++ b/frontend/src/Artist/Details/AlbumRowConnector.js @@ -9,7 +9,7 @@ function createMapStateToProps() { return createSelector( createArtistSelector(), createTrackFileSelector(), - (artist, trackFile) => { + (artist = {}, trackFile) => { return { foreignArtistId: artist.foreignArtistId, artistMonitored: artist.monitored, diff --git a/frontend/src/Artist/Details/ArtistDetails.js b/frontend/src/Artist/Details/ArtistDetails.js index 140f87e43..28e6d3eee 100644 --- a/frontend/src/Artist/Details/ArtistDetails.js +++ b/frontend/src/Artist/Details/ArtistDetails.js @@ -11,6 +11,7 @@ import HeartRating from 'Components/HeartRating'; import Icon from 'Components/Icon'; import IconButton from 'Components/Link/IconButton'; import Label from 'Components/Label'; +import Measure from 'Components/Measure'; import LoadingIndicator from 'Components/Loading/LoadingIndicator'; import PageContent from 'Components/Page/PageContent'; import PageContentBodyConnector from 'Components/Page/PageContentBodyConnector'; diff --git a/frontend/src/Artist/Editor/ArtistEditor.js b/frontend/src/Artist/Editor/ArtistEditor.js index 714041600..d61c42dec 100644 --- a/frontend/src/Artist/Editor/ArtistEditor.js +++ b/frontend/src/Artist/Editor/ArtistEditor.js @@ -273,7 +273,7 @@ ArtistEditor.propTypes = { items: PropTypes.arrayOf(PropTypes.object).isRequired, sortKey: PropTypes.string, sortDirection: PropTypes.oneOf(sortDirections.all), - selectedFilterKey: PropTypes.string.isRequired, + selectedFilterKey: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired, filters: PropTypes.arrayOf(PropTypes.object).isRequired, customFilters: PropTypes.arrayOf(PropTypes.object).isRequired, isSaving: PropTypes.bool.isRequired, diff --git a/frontend/src/Artist/Editor/ArtistEditorFilterModalConnector.js b/frontend/src/Artist/Editor/ArtistEditorFilterModalConnector.js index 8b36fa2d2..4aff2df06 100644 --- a/frontend/src/Artist/Editor/ArtistEditorFilterModalConnector.js +++ b/frontend/src/Artist/Editor/ArtistEditorFilterModalConnector.js @@ -1,6 +1,6 @@ import { connect } from 'react-redux'; import { createSelector } from 'reselect'; -import * as artistEditorActions from 'Store/Actions/artistEditorActions'; +import { setArtistEditorFilter } from 'Store/Actions/artistEditorActions'; import FilterModal from 'Components/Filter/FilterModal'; function createMapStateToProps() { @@ -10,22 +10,15 @@ function createMapStateToProps() { (sectionItems, filterBuilderProps) => { return { sectionItems, - filterBuilderProps + filterBuilderProps, + customFilterType: 'artistEditor' }; } ); } -function createMapDispatchToProps(dispatch, props) { - return { - onRemoveCustomFilterPress(payload) { - dispatch(artistEditorActions.removeArtistEditorCustomFilter(payload)); - }, +const mapDispatchToProps = { + dispatchSetFilter: setArtistEditorFilter +}; - onSaveCustomFilterPress(payload) { - dispatch(artistEditorActions.saveArtistEditorCustomFilter(payload)); - } - }; -} - -export default connect(createMapStateToProps, createMapDispatchToProps)(FilterModal); +export default connect(createMapStateToProps, mapDispatchToProps)(FilterModal); diff --git a/frontend/src/Artist/Editor/ArtistEditorRow.js b/frontend/src/Artist/Editor/ArtistEditorRow.js index 2126be3e8..ae95c465f 100644 --- a/frontend/src/Artist/Editor/ArtistEditorRow.js +++ b/frontend/src/Artist/Editor/ArtistEditorRow.js @@ -119,4 +119,8 @@ ArtistEditorRow.propTypes = { onSelectedChange: PropTypes.func.isRequired }; +ArtistEditorRow.defaultProps = { + tags: [] +}; + export default ArtistEditorRow; diff --git a/frontend/src/Artist/History/ArtistHistoryRow.js b/frontend/src/Artist/History/ArtistHistoryRow.js index 7c9544a49..38e7c75a6 100644 --- a/frontend/src/Artist/History/ArtistHistoryRow.js +++ b/frontend/src/Artist/History/ArtistHistoryRow.js @@ -9,7 +9,7 @@ import TableRow from 'Components/Table/TableRow'; import TableRowCell from 'Components/Table/Cells/TableRowCell'; import Popover from 'Components/Tooltip/Popover'; import EpisodeLanguage from 'Album/EpisodeLanguage'; -import EpisodeQuality from 'Album/EpisodeQuality'; +import TrackQuality from 'Album/TrackQuality'; import HistoryDetailsConnector from 'Activity/History/Details/HistoryDetailsConnector'; import HistoryEventTypeCell from 'Activity/History/HistoryEventTypeCell'; import styles from './ArtistHistoryRow.css'; @@ -100,7 +100,7 @@ class ArtistHistoryRow extends Component { - diff --git a/frontend/src/Artist/Index/ArtistIndex.js b/frontend/src/Artist/Index/ArtistIndex.js index 4bfe3996f..710d898e0 100644 --- a/frontend/src/Artist/Index/ArtistIndex.js +++ b/frontend/src/Artist/Index/ArtistIndex.js @@ -215,7 +215,7 @@ class ArtistIndex extends Component { } = this.state; const ViewComponent = getViewComponent(view); - const isLoaded = !error && isPopulated && !!items.length && contentBody; + const isLoaded = !!(!error && isPopulated && items.length && contentBody); const hasNoArtist = !totalItems; return ( @@ -382,7 +382,7 @@ ArtistIndex.propTypes = { error: PropTypes.object, totalItems: PropTypes.number.isRequired, items: PropTypes.arrayOf(PropTypes.object).isRequired, - selectedFilterKey: PropTypes.string.isRequired, + selectedFilterKey: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired, filters: PropTypes.arrayOf(PropTypes.object).isRequired, customFilters: PropTypes.arrayOf(PropTypes.object).isRequired, sortKey: PropTypes.string, diff --git a/frontend/src/Artist/Index/ArtistIndexFilterModalConnector.js b/frontend/src/Artist/Index/ArtistIndexFilterModalConnector.js index 0f77138b2..412f3df34 100644 --- a/frontend/src/Artist/Index/ArtistIndexFilterModalConnector.js +++ b/frontend/src/Artist/Index/ArtistIndexFilterModalConnector.js @@ -1,6 +1,6 @@ import { connect } from 'react-redux'; import { createSelector } from 'reselect'; -import * as artistIndexActions from 'Store/Actions/artistIndexActions'; +import { setArtistFilter } from 'Store/Actions/artistIndexActions'; import FilterModal from 'Components/Filter/FilterModal'; function createMapStateToProps() { @@ -10,22 +10,15 @@ function createMapStateToProps() { (sectionItems, filterBuilderProps) => { return { sectionItems, - filterBuilderProps + filterBuilderProps, + customFilterType: 'artistIndex' }; } ); } -function createMapDispatchToProps(dispatch, props) { - return { - onRemoveCustomFilterPress(payload) { - dispatch(artistIndexActions.removeArtistCustomFilter(payload)); - }, +const mapDispatchToProps = { + dispatchSetFilter: setArtistFilter +}; - onSaveCustomFilterPress(payload) { - dispatch(artistIndexActions.saveArtistCustomFilter(payload)); - } - }; -} - -export default connect(createMapStateToProps, createMapDispatchToProps)(FilterModal); +export default connect(createMapStateToProps, mapDispatchToProps)(FilterModal); diff --git a/frontend/src/Artist/Index/Banners/ArtistIndexBanners.js b/frontend/src/Artist/Index/Banners/ArtistIndexBanners.js index b05332b28..f1c02f88f 100644 --- a/frontend/src/Artist/Index/Banners/ArtistIndexBanners.js +++ b/frontend/src/Artist/Index/Banners/ArtistIndexBanners.js @@ -1,12 +1,12 @@ import PropTypes from 'prop-types'; import React, { Component } from 'react'; import ReactDOM from 'react-dom'; -import Measure from 'react-measure'; import { Grid, WindowScroller } from 'react-virtualized'; import getIndexOfFirstCharacter from 'Utilities/Array/getIndexOfFirstCharacter'; import hasDifferentItems from 'Utilities/Object/hasDifferentItems'; import dimensions from 'Styles/Variables/dimensions'; import { sortDirections } from 'Helpers/Props'; +import Measure from 'Components/Measure'; import ArtistIndexItemConnector from 'Artist/Index/ArtistIndexItemConnector'; import ArtistIndexBanner from './ArtistIndexBanner'; import styles from './ArtistIndexBanners.css'; diff --git a/frontend/src/Artist/Index/Menus/ArtistIndexFilterMenu.js b/frontend/src/Artist/Index/Menus/ArtistIndexFilterMenu.js index dc5540410..818e83311 100644 --- a/frontend/src/Artist/Index/Menus/ArtistIndexFilterMenu.js +++ b/frontend/src/Artist/Index/Menus/ArtistIndexFilterMenu.js @@ -27,7 +27,7 @@ function ArtistIndexFilterMenu(props) { } ArtistIndexFilterMenu.propTypes = { - selectedFilterKey: PropTypes.string.isRequired, + selectedFilterKey: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired, filters: PropTypes.arrayOf(PropTypes.object).isRequired, customFilters: PropTypes.arrayOf(PropTypes.object).isRequired, isDisabled: PropTypes.bool.isRequired, diff --git a/frontend/src/Artist/Index/Overview/ArtistIndexOverviews.js b/frontend/src/Artist/Index/Overview/ArtistIndexOverviews.js index 93c2c9764..de13a9de8 100644 --- a/frontend/src/Artist/Index/Overview/ArtistIndexOverviews.js +++ b/frontend/src/Artist/Index/Overview/ArtistIndexOverviews.js @@ -2,12 +2,12 @@ import _ from 'lodash'; import PropTypes from 'prop-types'; import React, { Component } from 'react'; import ReactDOM from 'react-dom'; -import Measure from 'react-measure'; import { Grid, WindowScroller } from 'react-virtualized'; import getIndexOfFirstCharacter from 'Utilities/Array/getIndexOfFirstCharacter'; import hasDifferentItems from 'Utilities/Object/hasDifferentItems'; import dimensions from 'Styles/Variables/dimensions'; import { sortDirections } from 'Helpers/Props'; +import Measure from 'Components/Measure'; import ArtistIndexItemConnector from 'Artist/Index/ArtistIndexItemConnector'; import ArtistIndexOverview from './ArtistIndexOverview'; import styles from './ArtistIndexOverviews.css'; diff --git a/frontend/src/Artist/Index/Posters/ArtistIndexPosters.js b/frontend/src/Artist/Index/Posters/ArtistIndexPosters.js index 000b14887..d02aeaefd 100644 --- a/frontend/src/Artist/Index/Posters/ArtistIndexPosters.js +++ b/frontend/src/Artist/Index/Posters/ArtistIndexPosters.js @@ -1,12 +1,12 @@ import PropTypes from 'prop-types'; import React, { Component } from 'react'; import ReactDOM from 'react-dom'; -import Measure from 'react-measure'; import { Grid, WindowScroller } from 'react-virtualized'; import getIndexOfFirstCharacter from 'Utilities/Array/getIndexOfFirstCharacter'; import hasDifferentItems from 'Utilities/Object/hasDifferentItems'; import dimensions from 'Styles/Variables/dimensions'; import { sortDirections } from 'Helpers/Props'; +import Measure from 'Components/Measure'; import ArtistIndexItemConnector from 'Artist/Index/ArtistIndexItemConnector'; import ArtistIndexPoster from './ArtistIndexPoster'; import styles from './ArtistIndexPosters.css'; diff --git a/frontend/src/Calendar/CalendarPage.js b/frontend/src/Calendar/CalendarPage.js index 5ce161d91..d33b1387b 100644 --- a/frontend/src/Calendar/CalendarPage.js +++ b/frontend/src/Calendar/CalendarPage.js @@ -1,8 +1,8 @@ import PropTypes from 'prop-types'; import React, { Component } from 'react'; -import Measure from 'react-measure'; import { align, icons } from 'Helpers/Props'; import PageContent from 'Components/Page/PageContent'; +import Measure from 'Components/Measure'; import PageContentBodyConnector from 'Components/Page/PageContentBodyConnector'; import PageToolbar from 'Components/Page/Toolbar/PageToolbar'; import PageToolbarSection from 'Components/Page/Toolbar/PageToolbarSection'; diff --git a/frontend/src/Calendar/Events/CalendarEvent.js b/frontend/src/Calendar/Events/CalendarEvent.js index 0189fce3d..e73b30c93 100644 --- a/frontend/src/Calendar/Events/CalendarEvent.js +++ b/frontend/src/Calendar/Events/CalendarEvent.js @@ -56,6 +56,10 @@ class CalendarEvent extends Component { colorImpairedMode } = this.props; + if (!artist) { + return null; + } + const startTime = moment(releaseDate); // const endTime = startTime.add(artist.runtime, 'minutes'); const downloading = !!(queueItem || grabbed); diff --git a/frontend/src/Components/Error/ErrorBoundary.js b/frontend/src/Components/Error/ErrorBoundary.js new file mode 100644 index 000000000..50fcf98b5 --- /dev/null +++ b/frontend/src/Components/Error/ErrorBoundary.js @@ -0,0 +1,62 @@ +import PropTypes from 'prop-types'; +import React, { Component } from 'react'; +import * as sentry from '@sentry/browser'; + +class ErrorBoundary extends Component { + + // + // Lifecycle + + constructor(props, context) { + super(props, context); + + this.state = { + error: null, + info: null + }; + } + + componentDidCatch(error, info) { + this.setState({ + error, + info + }); + + sentry.captureException(error); + } + + // + // Render + + render() { + const { + children, + errorComponent: ErrorComponent, + ...otherProps + } = this.props; + + const { + error, + info + } = this.state; + + if (error) { + return ( + + ); + } + + return children; + } +} + +ErrorBoundary.propTypes = { + children: PropTypes.node.isRequired, + errorComponent: PropTypes.func.isRequired +}; + +export default ErrorBoundary; diff --git a/frontend/src/Components/Error/ErrorBoundaryError.css b/frontend/src/Components/Error/ErrorBoundaryError.css new file mode 100644 index 000000000..b6d1f917e --- /dev/null +++ b/frontend/src/Components/Error/ErrorBoundaryError.css @@ -0,0 +1,38 @@ +.container { + text-align: center; +} + +.message { + margin: 50px 0; + text-align: center; + font-weight: 300; + font-size: 36px; +} + +.imageContainer { + display: flex; + justify-content: center; + flex: 0 0 auto; +} + +.image { + height: 350px; +} + +.details { + margin: 20px; + text-align: left; + white-space: pre-wrap; +} + +@media only screen and (max-width: $breakpointMedium) { + .image { + height: 250px; + } +} + +@media only screen and (max-width: $breakpointSmall) { + .image { + height: 150px; + } +} diff --git a/frontend/src/Components/Error/ErrorBoundaryError.js b/frontend/src/Components/Error/ErrorBoundaryError.js new file mode 100644 index 000000000..f99930437 --- /dev/null +++ b/frontend/src/Components/Error/ErrorBoundaryError.js @@ -0,0 +1,60 @@ +import PropTypes from 'prop-types'; +import React from 'react'; +import styles from './ErrorBoundaryError.css'; + +function ErrorBoundaryError(props) { + const { + className, + messageClassName, + detailsClassName, + message, + error, + info + } = props; + + return ( +
+
+ {message} +
+ +
+ +
+ +
+ { + error && +
+ {error.toString()} +
+ } + +
+ {info.componentStack} +
+
+
+ ); +} + +ErrorBoundaryError.propTypes = { + className: PropTypes.string.isRequired, + messageClassName: PropTypes.string.isRequired, + detailsClassName: PropTypes.string.isRequired, + message: PropTypes.string.isRequired, + error: PropTypes.object.isRequired, + info: PropTypes.object.isRequired +}; + +ErrorBoundaryError.defaultProps = { + className: styles.container, + messageClassName: styles.message, + detailsClassName: styles.details, + message: 'There was an error loading this content' +}; + +export default ErrorBoundaryError; diff --git a/frontend/src/Components/FileBrowser/FileBrowserModalContentConnector.js b/frontend/src/Components/FileBrowser/FileBrowserModalContentConnector.js index 71b463292..fe577b896 100644 --- a/frontend/src/Components/FileBrowser/FileBrowserModalContentConnector.js +++ b/frontend/src/Components/FileBrowser/FileBrowserModalContentConnector.js @@ -35,7 +35,7 @@ function createMapStateToProps() { directories, files, paths: filteredPaths, - isWindowsService: true || systemStatus.isWindows && systemStatus.mode === 'service' + isWindowsService: systemStatus.isWindows && systemStatus.mode === 'service' }; } ); diff --git a/frontend/src/Components/Filter/Builder/FilterBuilderModalContent.js b/frontend/src/Components/Filter/Builder/FilterBuilderModalContent.js index d2a72b67c..ed3bc2409 100644 --- a/frontend/src/Components/Filter/Builder/FilterBuilderModalContent.js +++ b/frontend/src/Components/Filter/Builder/FilterBuilderModalContent.js @@ -3,6 +3,7 @@ import React, { Component } from 'react'; import { inputTypes } from 'Helpers/Props'; import FormInputGroup from 'Components/Form/FormInputGroup'; import Button from 'Components/Link/Button'; +import SpinnerErrorButton from 'Components/Link/SpinnerErrorButton'; import ModalContent from 'Components/Modal/ModalContent'; import ModalHeader from 'Components/Modal/ModalHeader'; import ModalBody from 'Components/Modal/ModalBody'; @@ -34,6 +35,28 @@ class FilterBuilderModalContent extends Component { }; } + componentDidUpdate(prevProps) { + const { + id, + customFilters, + isSaving, + saveError, + dispatchSetFilter, + onModalClose + } = this.props; + + if (prevProps.isSaving && !isSaving && !saveError) { + if (id) { + dispatchSetFilter({ selectedFilterKey: id }); + } else { + const last = customFilters[customFilters.length -1]; + dispatchSetFilter({ selectedFilterKey: last.id }); + } + + onModalClose(); + } + } + // // Listeners @@ -70,9 +93,9 @@ class FilterBuilderModalContent extends Component { onSaveFilterPress = () => { const { - customFilterKey: key, - onSaveCustomFilterPress, - onModalClose + id, + customFilterType, + onSaveCustomFilterPress } = this.props; const { @@ -92,8 +115,12 @@ class FilterBuilderModalContent extends Component { return; } - onSaveCustomFilterPress({ key, label, filters }); - onModalClose(); + onSaveCustomFilterPress({ + id, + type: customFilterType, + label, + filters + }); } // @@ -103,6 +130,8 @@ class FilterBuilderModalContent extends Component { const { sectionItems, filterBuilderProps, + isSaving, + saveError, onModalClose } = this.props; @@ -161,17 +190,17 @@ class FilterBuilderModalContent extends Component { - - + Save + ); @@ -179,13 +208,18 @@ class FilterBuilderModalContent extends Component { } FilterBuilderModalContent.propTypes = { - customFilterKey: PropTypes.string, + id: PropTypes.number, label: PropTypes.string.isRequired, + customFilterType: PropTypes.string.isRequired, sectionItems: PropTypes.arrayOf(PropTypes.object).isRequired, filters: PropTypes.arrayOf(PropTypes.object).isRequired, filterBuilderProps: PropTypes.arrayOf(PropTypes.object).isRequired, - onRemoveCustomFilterPress: PropTypes.func.isRequired, + customFilters: PropTypes.arrayOf(PropTypes.object).isRequired, + isSaving: PropTypes.bool.isRequired, + saveError: PropTypes.object, + dispatchDeleteCustomFilter: PropTypes.func.isRequired, onSaveCustomFilterPress: PropTypes.func.isRequired, + dispatchSetFilter: PropTypes.func.isRequired, onModalClose: PropTypes.func.isRequired }; diff --git a/frontend/src/Components/Filter/Builder/FilterBuilderModalContentConnector.js b/frontend/src/Components/Filter/Builder/FilterBuilderModalContentConnector.js index e7f237793..c94db9925 100644 --- a/frontend/src/Components/Filter/Builder/FilterBuilderModalContentConnector.js +++ b/frontend/src/Components/Filter/Builder/FilterBuilderModalContentConnector.js @@ -1,28 +1,42 @@ import { connect } from 'react-redux'; import { createSelector } from 'reselect'; +import { saveCustomFilter, deleteCustomFilter } from 'Store/Actions/customFilterActions'; import FilterBuilderModalContent from './FilterBuilderModalContent'; function createMapStateToProps() { return createSelector( (state, { customFilters }) => customFilters, - (state, { customFilterKey }) => customFilterKey, - (customFilters, customFilterKey) => { - if (customFilterKey) { - const customFilter = customFilters.find((c) => c.key === customFilterKey); + (state, { id }) => id, + (state) => state.customFilters.isSaving, + (state) => state.customFilters.saveError, + (customFilters, id, isSaving, saveError) => { + if (id) { + const customFilter = customFilters.find((c) => c.id === id); return { - customFilterKey: customFilter.key, + id: customFilter.id, label: customFilter.label, - filters: customFilter.filters + filters: customFilter.filters, + customFilters, + isSaving, + saveError }; } return { label: '', - filters: [] + filters: [], + customFilters, + isSaving, + saveError }; } ); } -export default connect(createMapStateToProps)(FilterBuilderModalContent); +const mapDispatchToProps = { + onSaveCustomFilterPress: saveCustomFilter, + dispatchDeleteCustomFilter: deleteCustomFilter +}; + +export default connect(createMapStateToProps, mapDispatchToProps)(FilterBuilderModalContent); diff --git a/frontend/src/Components/Filter/Builder/FilterBuilderRowValue.js b/frontend/src/Components/Filter/Builder/FilterBuilderRowValue.js index 62b56da9f..758e12691 100644 --- a/frontend/src/Components/Filter/Builder/FilterBuilderRowValue.js +++ b/frontend/src/Components/Filter/Builder/FilterBuilderRowValue.js @@ -1,11 +1,64 @@ import PropTypes from 'prop-types'; import React, { Component } from 'react'; -import { kinds, filterBuilderTypes } from 'Helpers/Props'; +import convertToBytes from 'Utilities/Number/convertToBytes'; +import formatBytes from 'Utilities/Number/formatBytes'; +import { kinds, filterBuilderTypes, filterBuilderValueTypes } from 'Helpers/Props'; import TagInput, { tagShape } from 'Components/Form/TagInput'; import FilterBuilderRowValueTag from './FilterBuilderRowValueTag'; export const NAME = 'value'; +function getTagDisplayValue(value, selectedFilterBuilderProp) { + if (selectedFilterBuilderProp.valueType === filterBuilderValueTypes.BYTES) { + return formatBytes(value); + } + + return value; +} + +function getValue(input, selectedFilterBuilderProp) { + if (selectedFilterBuilderProp.valueType === filterBuilderValueTypes.BYTES) { + const match = input.match(/^(\d+)([kmgt](i?b)?)$/i); + if (match && match.length > 1) { + const [, value, unit] = input.match(/^(\d+)([kmgt](i?b)?)$/i); + switch (unit.toLowerCase()) { + case 'k': + return convertToBytes(value, 1, true); + case 'm': + return convertToBytes(value, 2, true); + case 'g': + return convertToBytes(value, 3, true); + case 't': + return convertToBytes(value, 4, true); + case 'kb': + return convertToBytes(value, 1, true); + case 'mb': + return convertToBytes(value, 2, true); + case 'gb': + return convertToBytes(value, 3, true); + case 'tb': + return convertToBytes(value, 4, true); + case 'kib': + return convertToBytes(value, 1, true); + case 'mib': + return convertToBytes(value, 2, true); + case 'gib': + return convertToBytes(value, 3, true); + case 'tib': + return convertToBytes(value, 4, true); + default: + return parseInt(value); + } + } + } + + if (selectedFilterBuilderProp.type === filterBuilderTypes.NUMBER) { + return parseInt(input); + } + + return input; +} + class FilterBuilderRowValue extends Component { // @@ -18,17 +71,15 @@ class FilterBuilderRowValue extends Component { onChange } = this.props; - let id = tag.id; + let value = tag.id; - if (id == null) { - id = selectedFilterBuilderProp.type === filterBuilderTypes.NUMBER ? - parseInt(tag.name) : - tag.name; + if (value == null) { + value = getValue(tag.name, selectedFilterBuilderProp); } onChange({ name: NAME, - value: [...filterValue, id] + value: [...filterValue, value] }); } @@ -52,6 +103,7 @@ class FilterBuilderRowValue extends Component { render() { const { filterValue, + selectedFilterBuilderProp, tagList } = this.props; @@ -68,7 +120,7 @@ class FilterBuilderRowValue extends Component { } return { id, - name: id + name: getTagDisplayValue(id, selectedFilterBuilderProp) }; }); diff --git a/frontend/src/Components/Filter/CustomFilters/CustomFilter.js b/frontend/src/Components/Filter/CustomFilters/CustomFilter.js index 62551978b..ec9ff7cf4 100644 --- a/frontend/src/Components/Filter/CustomFilters/CustomFilter.js +++ b/frontend/src/Components/Filter/CustomFilters/CustomFilter.js @@ -2,29 +2,70 @@ import PropTypes from 'prop-types'; import React, { Component } from 'react'; import { icons } from 'Helpers/Props'; import IconButton from 'Components/Link/IconButton'; +import SpinnerIconButton from 'Components/Link/SpinnerIconButton'; import styles from './CustomFilter.css'; class CustomFilter extends Component { + // + // Lifecycle + + constructor(props, context) { + super(props, context); + + this.state = { + isDeleting: false + }; + } + + componentDidUpdate(prevProps) { + const { + isDeleting, + deleteError + } = this.props; + + if (prevProps.isDeleting && !isDeleting && this.state.isDeleting && deleteError) { + this.setState({ isDeleting: false }); + } + } + + componentWillUnmount() { + const { + id, + selectedFilterKey, + dispatchSetFilter + } = this.props; + + // Assume that delete and then unmounting means the delete was successful. + // Moving this check to a ancestor would be more accurate, but would have + // more boilerplate. + if (this.state.isDeleting && id === selectedFilterKey) { + dispatchSetFilter({ selectedFilterKey: 'all' }); + } + } + // // Listeners onEditPress = () => { const { - customFilterKey, + id, onEditPress } = this.props; - onEditPress(customFilterKey); + onEditPress(id); } onRemovePress = () => { const { - customFilterKey, - onRemovePress + id, + dispatchDeleteCustomFilter } = this.props; - onRemovePress({ key: customFilterKey }); + this.setState({ isDeleting: true }, () => { + dispatchDeleteCustomFilter({ id }); + }); + } // @@ -47,8 +88,9 @@ class CustomFilter extends Component { onPress={this.onEditPress} /> - @@ -58,10 +100,14 @@ class CustomFilter extends Component { } CustomFilter.propTypes = { - customFilterKey: PropTypes.string.isRequired, + id: PropTypes.number.isRequired, label: PropTypes.string.isRequired, + selectedFilterKey: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired, + isDeleting: PropTypes.bool.isRequired, + deleteError: PropTypes.object, + dispatchSetFilter: PropTypes.func.isRequired, onEditPress: PropTypes.func.isRequired, - onRemovePress: PropTypes.func.isRequired + dispatchDeleteCustomFilter: PropTypes.func.isRequired }; export default CustomFilter; diff --git a/frontend/src/Components/Filter/CustomFilters/CustomFiltersModalContent.js b/frontend/src/Components/Filter/CustomFilters/CustomFiltersModalContent.js index ac27bdd23..1a7168fca 100644 --- a/frontend/src/Components/Filter/CustomFilters/CustomFiltersModalContent.js +++ b/frontend/src/Components/Filter/CustomFilters/CustomFiltersModalContent.js @@ -10,9 +10,13 @@ import styles from './CustomFiltersModalContent.css'; function CustomFiltersModalContent(props) { const { + selectedFilterKey, customFilters, + isDeleting, + deleteError, + dispatchDeleteCustomFilter, + dispatchSetFilter, onAddCustomFilter, - onRemoveCustomFilterPress, onEditCustomFilter, onModalClose } = props; @@ -29,10 +33,14 @@ function CustomFiltersModalContent(props) { return ( ); @@ -58,9 +66,13 @@ function CustomFiltersModalContent(props) { } CustomFiltersModalContent.propTypes = { + selectedFilterKey: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired, customFilters: PropTypes.arrayOf(PropTypes.object).isRequired, + isDeleting: PropTypes.bool.isRequired, + deleteError: PropTypes.object, + dispatchDeleteCustomFilter: PropTypes.func.isRequired, + dispatchSetFilter: PropTypes.func.isRequired, onAddCustomFilter: PropTypes.func.isRequired, - onRemoveCustomFilterPress: PropTypes.func.isRequired, onEditCustomFilter: PropTypes.func.isRequired, onModalClose: PropTypes.func.isRequired }; diff --git a/frontend/src/Components/Filter/CustomFilters/CustomFiltersModalContentConnector.js b/frontend/src/Components/Filter/CustomFilters/CustomFiltersModalContentConnector.js new file mode 100644 index 000000000..32425d766 --- /dev/null +++ b/frontend/src/Components/Filter/CustomFilters/CustomFiltersModalContentConnector.js @@ -0,0 +1,23 @@ +import { connect } from 'react-redux'; +import { createSelector } from 'reselect'; +import { deleteCustomFilter } from 'Store/Actions/customFilterActions'; +import CustomFiltersModalContent from './CustomFiltersModalContent'; + +function createMapStateToProps() { + return createSelector( + (state) => state.customFilters.isDeleting, + (state) => state.customFilters.deleteError, + (isDeleting, deleteError) => { + return { + isDeleting, + deleteError + }; + } + ); +} + +const mapDispatchToProps = { + dispatchDeleteCustomFilter: deleteCustomFilter +}; + +export default connect(createMapStateToProps, mapDispatchToProps)(CustomFiltersModalContent); diff --git a/frontend/src/Components/Filter/FilterModal.js b/frontend/src/Components/Filter/FilterModal.js index 944caf070..750d1ed48 100644 --- a/frontend/src/Components/Filter/FilterModal.js +++ b/frontend/src/Components/Filter/FilterModal.js @@ -2,7 +2,7 @@ import PropTypes from 'prop-types'; import React, { Component } from 'react'; import Modal from 'Components/Modal/Modal'; import FilterBuilderModalContentConnector from './Builder/FilterBuilderModalContentConnector'; -import CustomFiltersModalContent from './CustomFilters/CustomFiltersModalContent'; +import CustomFiltersModalContentConnector from './CustomFilters/CustomFiltersModalContentConnector'; class FilterModal extends Component { @@ -14,7 +14,7 @@ class FilterModal extends Component { this.state = { filterBuilder: !props.customFilters.length, - customFilterKey: null + id: null }; } @@ -27,17 +27,17 @@ class FilterModal extends Component { }); } - onEditCustomFilter = (customFilterKey) => { + onEditCustomFilter = (id) => { this.setState({ filterBuilder: true, - customFilterKey + id }); } onModalClose = () => { this.setState({ filterBuilder: false, - customFilterKey: null + id: null }, () => { this.props.onModalClose(); }); @@ -54,7 +54,7 @@ class FilterModal extends Component { const { filterBuilder, - customFilterKey + id } = this.state; return ( @@ -66,10 +66,10 @@ class FilterModal extends Component { filterBuilder ? : - { + this.props.onMeasure(payload); + }, 250, { leading: true, trailing: false }) + + // + // Render + + render() { + return ( + + ); + } +} + +Measure.propTypes = { + onMeasure: PropTypes.func.isRequired +}; + +export default Measure; diff --git a/frontend/src/Components/Menu/FilterMenu.js b/frontend/src/Components/Menu/FilterMenu.js index 765824479..d37876c22 100644 --- a/frontend/src/Components/Menu/FilterMenu.js +++ b/frontend/src/Components/Menu/FilterMenu.js @@ -42,6 +42,7 @@ class FilterMenu extends Component { customFilters, buttonComponent: ButtonComponent, filterModalConnectorComponent: FilterModalConnectorComponent, + filterModalConnectorComponentProps, onFilterSelect, ...otherProps } = this.props; @@ -74,6 +75,7 @@ class FilterMenu extends Component { { showCustomFilters && { return ( @@ -70,7 +70,7 @@ class FilterMenuContent extends Component { } FilterMenuContent.propTypes = { - selectedFilterKey: PropTypes.string.isRequired, + selectedFilterKey: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired, filters: PropTypes.arrayOf(PropTypes.object).isRequired, customFilters: PropTypes.arrayOf(PropTypes.object).isRequired, showCustomFilters: PropTypes.bool.isRequired, diff --git a/frontend/src/Components/Menu/FilterMenuItem.js b/frontend/src/Components/Menu/FilterMenuItem.js index f8afb8364..d2c495187 100644 --- a/frontend/src/Components/Menu/FilterMenuItem.js +++ b/frontend/src/Components/Menu/FilterMenuItem.js @@ -37,8 +37,8 @@ class FilterMenuItem extends Component { } FilterMenuItem.propTypes = { - filterKey: PropTypes.string.isRequired, - selectedFilterKey: PropTypes.string.isRequired, + filterKey: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired, + selectedFilterKey: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired, onPress: PropTypes.func.isRequired }; diff --git a/frontend/src/Components/Modal/Modal.js b/frontend/src/Components/Modal/Modal.js index bbca184ec..a9de82c6d 100644 --- a/frontend/src/Components/Modal/Modal.js +++ b/frontend/src/Components/Modal/Modal.js @@ -6,6 +6,8 @@ import elementClass from 'element-class'; import getUniqueElememtId from 'Utilities/getUniqueElementId'; import * as keyCodes from 'Utilities/Constants/keyCodes'; import { sizes } from 'Helpers/Props'; +import ErrorBoundary from 'Components/Error/ErrorBoundary'; +import ModalError from './ModalError'; import styles from './Modal.css'; const openModals = []; @@ -153,7 +155,8 @@ class Modal extends Component { backdropClassName, size, children, - isOpen + isOpen, + onModalClose } = this.props; if (!isOpen) { @@ -177,7 +180,12 @@ class Modal extends Component { )} style={style} > - {children} + + {children} + , diff --git a/frontend/src/Components/Modal/ModalError.css b/frontend/src/Components/Modal/ModalError.css new file mode 100644 index 000000000..54dbdbc63 --- /dev/null +++ b/frontend/src/Components/Modal/ModalError.css @@ -0,0 +1,15 @@ +.message { + composes: message from 'Components/Error/ErrorBoundaryError.css'; + + margin: 0; + margin-bottom: 30px; + font-weight: normal; + font-size: 26px; +} + +.details { + composes: details from 'Components/Error/ErrorBoundaryError.css'; + + margin: 0; + margin-top: 20px; +} diff --git a/frontend/src/Components/Modal/ModalError.js b/frontend/src/Components/Modal/ModalError.js new file mode 100644 index 000000000..df99a5b32 --- /dev/null +++ b/frontend/src/Components/Modal/ModalError.js @@ -0,0 +1,46 @@ +import PropTypes from 'prop-types'; +import React from 'react'; +import ErrorBoundaryError from 'Components/Error/ErrorBoundaryError'; +import Button from 'Components/Link/Button'; +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 styles from './ModalError.css'; + +function ModalError(props) { + const { + onModalClose, + ...otherProps + } = props; + + return ( + + + Error + + + + + + + + + + ); +} + +ModalError.propTypes = { + onModalClose: PropTypes.func.isRequired +}; + +export default ModalError; diff --git a/frontend/src/Components/Page/ErrorPage.js b/frontend/src/Components/Page/ErrorPage.js index 325575ff0..6cdd08af1 100644 --- a/frontend/src/Components/Page/ErrorPage.js +++ b/frontend/src/Components/Page/ErrorPage.js @@ -8,8 +8,11 @@ function ErrorPage(props) { version, isLocalStorageSupported, artistError, + customFiltersError, tagsError, qualityProfilesError, + languageProfilesError, + metadataProfilesError, uiSettingsError } = props; @@ -19,10 +22,16 @@ function ErrorPage(props) { errorMessage = 'Local Storage is not supported or disabled. A plugin or private browsing may have disabled it.'; } else if (artistError) { errorMessage = getErrorMessage(artistError, 'Failed to load artist from API'); + } else if (customFiltersError) { + errorMessage = getErrorMessage(customFiltersError, 'Failed to load custom filters from API'); } else if (tagsError) { - errorMessage = getErrorMessage(artistError, 'Failed to load artist from API'); + errorMessage = getErrorMessage(tagsError, 'Failed to load tags from API'); } else if (qualityProfilesError) { errorMessage = getErrorMessage(qualityProfilesError, 'Failed to load quality profiles from API'); + } else if (languageProfilesError) { + errorMessage = getErrorMessage(languageProfilesError, 'Failed to load language profiles from API'); + } else if (metadataProfilesError) { + errorMessage = getErrorMessage(metadataProfilesError, 'Failed to load metadata profiles from API'); } else if (uiSettingsError) { errorMessage = getErrorMessage(uiSettingsError, 'Failed to load UI settings from API'); } @@ -44,8 +53,11 @@ ErrorPage.propTypes = { version: PropTypes.string.isRequired, isLocalStorageSupported: PropTypes.bool.isRequired, artistError: PropTypes.object, + customFiltersError: PropTypes.object, tagsError: PropTypes.object, qualityProfilesError: PropTypes.object, + languageProfilesError: PropTypes.object, + metadataProfilesError: PropTypes.object, uiSettingsError: PropTypes.object }; diff --git a/frontend/src/Components/Page/PageConnector.js b/frontend/src/Components/Page/PageConnector.js index 2b3adfd48..bdb00ccec 100644 --- a/frontend/src/Components/Page/PageConnector.js +++ b/frontend/src/Components/Page/PageConnector.js @@ -6,6 +6,7 @@ import { withRouter } from 'react-router-dom'; import { createSelector } from 'reselect'; import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector'; import { saveDimensions, setIsSidebarVisible } from 'Store/Actions/appActions'; +import { fetchCustomFilters } from 'Store/Actions/customFilterActions'; import { fetchArtist } from 'Store/Actions/artistActions'; import { fetchTags } from 'Store/Actions/tagActions'; import { fetchQualityProfiles, fetchLanguageProfiles, fetchMetadataProfiles, fetchUISettings, fetchImportLists } from 'Store/Actions/settingsActions'; @@ -30,13 +31,15 @@ function testLocalStorage() { function createMapStateToProps() { return createSelector( (state) => state.artist, + (state) => state.customFilters, (state) => state.tags, (state) => state.settings, (state) => state.app, createDimensionsSelector(), - (artist, tags, settings, app, dimensions) => { + (artist, customFilters, tags, settings, app, dimensions) => { const isPopulated = ( artist.isPopulated && + customFilters.isPopulated && tags.isPopulated && settings.qualityProfiles.isPopulated && settings.languageProfiles.isPopulated && @@ -47,6 +50,7 @@ function createMapStateToProps() { const hasError = !!( artist.error || + customFilters.error || tags.error || settings.qualityProfiles.error || settings.languageProfiles.error || @@ -59,6 +63,7 @@ function createMapStateToProps() { isPopulated, hasError, artistError: artist.error, + customFiltersError: tags.error, tagsError: tags.error, qualityProfilesError: settings.qualityProfiles.error, languageProfilesError: settings.languageProfiles.error, @@ -80,6 +85,9 @@ function createMapDispatchToProps(dispatch, props) { dispatchFetchArtist() { dispatch(fetchArtist()); }, + dispatchFetchCustomFilters() { + dispatch(fetchCustomFilters()); + }, dispatchFetchTags() { dispatch(fetchTags()); }, @@ -126,6 +134,7 @@ class PageConnector extends Component { componentDidMount() { if (!this.props.isPopulated) { this.props.dispatchFetchArtist(); + this.props.dispatchFetchCustomFilters(); this.props.dispatchFetchTags(); this.props.dispatchFetchQualityProfiles(); this.props.dispatchFetchLanguageProfiles(); @@ -190,6 +199,7 @@ PageConnector.propTypes = { hasError: PropTypes.bool.isRequired, isSidebarVisible: PropTypes.bool.isRequired, dispatchFetchArtist: PropTypes.func.isRequired, + dispatchFetchCustomFilters: PropTypes.func.isRequired, dispatchFetchTags: PropTypes.func.isRequired, dispatchFetchQualityProfiles: PropTypes.func.isRequired, dispatchFetchLanguageProfiles: PropTypes.func.isRequired, diff --git a/frontend/src/Components/Page/PageContent.js b/frontend/src/Components/Page/PageContent.js index a416d268c..e7a650bb4 100644 --- a/frontend/src/Components/Page/PageContent.js +++ b/frontend/src/Components/Page/PageContent.js @@ -1,6 +1,8 @@ import PropTypes from 'prop-types'; import React from 'react'; import DocumentTitle from 'react-document-title'; +import ErrorBoundary from 'Components/Error/ErrorBoundary'; +import PageContentError from './PageContentError'; import styles from './PageContent.css'; function PageContent(props) { @@ -11,11 +13,13 @@ function PageContent(props) { } = props; return ( - -
- {children} -
-
+ + +
+ {children} +
+
+
); } diff --git a/frontend/src/Components/Page/PageContentError.css b/frontend/src/Components/Page/PageContentError.css new file mode 100644 index 000000000..7b1f7a6db --- /dev/null +++ b/frontend/src/Components/Page/PageContentError.css @@ -0,0 +1,3 @@ +.content { + composes: content from './PageContent.css'; +} diff --git a/frontend/src/Components/Page/PageContentError.js b/frontend/src/Components/Page/PageContentError.js new file mode 100644 index 000000000..5ae41a936 --- /dev/null +++ b/frontend/src/Components/Page/PageContentError.js @@ -0,0 +1,19 @@ +import React from 'react'; +import ErrorBoundaryError from 'Components/Error/ErrorBoundaryError'; +import PageContentBodyConnector from './PageContentBodyConnector'; +import styles from './PageContentError.css'; + +function PageContentError(props) { + return ( +
+ + + +
+ ); +} + +export default PageContentError; diff --git a/frontend/src/Components/Page/PageJumpBar.js b/frontend/src/Components/Page/PageJumpBar.js index 25844ec94..f7d44ae9a 100644 --- a/frontend/src/Components/Page/PageJumpBar.js +++ b/frontend/src/Components/Page/PageJumpBar.js @@ -1,8 +1,8 @@ import _ from 'lodash'; import PropTypes from 'prop-types'; import React, { Component } from 'react'; -import Measure from 'react-measure'; import dimensions from 'Styles/Variables/dimensions'; +import Measure from 'Components/Measure'; import PageJumpBarItem from './PageJumpBarItem'; import styles from './PageJumpBar.css'; diff --git a/frontend/src/Components/Page/Toolbar/PageToolbarSection.js b/frontend/src/Components/Page/Toolbar/PageToolbarSection.js index 82e30f6eb..57b53ff4e 100644 --- a/frontend/src/Components/Page/Toolbar/PageToolbarSection.js +++ b/frontend/src/Components/Page/Toolbar/PageToolbarSection.js @@ -1,11 +1,11 @@ import PropTypes from 'prop-types'; import React, { Component } from 'react'; -import Measure from 'react-measure'; import classNames from 'classnames'; import { forEach } from 'Helpers/elementChildren'; import { align, icons } from 'Helpers/Props'; import dimensions from 'Styles/Variables/dimensions'; import SpinnerIcon from 'Components/SpinnerIcon'; +import Measure from 'Components/Measure'; import Menu from 'Components/Menu/Menu'; import MenuContent from 'Components/Menu/MenuContent'; import MenuItem from 'Components/Menu/MenuItem'; diff --git a/frontend/src/Components/Table/VirtualTable.js b/frontend/src/Components/Table/VirtualTable.js index 6d9af2382..a13807647 100644 --- a/frontend/src/Components/Table/VirtualTable.js +++ b/frontend/src/Components/Table/VirtualTable.js @@ -1,9 +1,9 @@ import PropTypes from 'prop-types'; import React, { Component } from 'react'; import ReactDOM from 'react-dom'; -import Measure from 'react-measure'; import { WindowScroller } from 'react-virtualized'; import { scrollDirections } from 'Helpers/Props'; +import Measure from 'Components/Measure'; import Scroller from 'Components/Scroller/Scroller'; import VirtualTableBody from './VirtualTableBody'; import styles from './VirtualTable.css'; diff --git a/frontend/src/Content/Images/error.png b/frontend/src/Content/Images/error.png new file mode 100644 index 0000000000000000000000000000000000000000..9b1ae774675b362b5c58dd104403761ccf5797c7 GIT binary patch literal 163647 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

  • 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 literal 0 HcmV?d00001 diff --git a/frontend/src/Helpers/Props/filterBuilderValueTypes.js b/frontend/src/Helpers/Props/filterBuilderValueTypes.js index 3d097055a..37bc6d2ad 100644 --- a/frontend/src/Helpers/Props/filterBuilderValueTypes.js +++ b/frontend/src/Helpers/Props/filterBuilderValueTypes.js @@ -1,4 +1,5 @@ export const BOOL = 'bool'; +export const BYTES = 'bytes'; export const DATE = 'date'; export const DEFAULT = 'default'; export const INDEXER = 'indexer'; diff --git a/frontend/src/InteractiveImport/Interactive/InteractiveImportRow.js b/frontend/src/InteractiveImport/Interactive/InteractiveImportRow.js index ae8a76391..2d061e4c5 100644 --- a/frontend/src/InteractiveImport/Interactive/InteractiveImportRow.js +++ b/frontend/src/InteractiveImport/Interactive/InteractiveImportRow.js @@ -9,7 +9,7 @@ import TableRowCell from 'Components/Table/Cells/TableRowCell'; import TableRowCellButton from 'Components/Table/Cells/TableRowCellButton'; import TableSelectCell from 'Components/Table/Cells/TableSelectCell'; import Popover from 'Components/Tooltip/Popover'; -import EpisodeQuality from 'Album/EpisodeQuality'; +import TrackQuality from 'Album/TrackQuality'; import EpisodeLanguage from 'Album/EpisodeLanguage'; import SelectArtistModal from 'InteractiveImport/Artist/SelectArtistModal'; import SelectAlbumModal from 'InteractiveImport/Album/SelectAlbumModal'; @@ -248,7 +248,7 @@ class InteractiveImportRow extends Component { { !showQualityPlaceholder && !!quality && - diff --git a/frontend/src/Album/Search/InteractiveSearchFilterModalConnector.js b/frontend/src/InteractiveSearch/InteractiveSearchFilterModalConnector.js similarity index 64% rename from frontend/src/Album/Search/InteractiveSearchFilterModalConnector.js rename to frontend/src/InteractiveSearch/InteractiveSearchFilterModalConnector.js index 65f9a2d45..6c40023d1 100644 --- a/frontend/src/Album/Search/InteractiveSearchFilterModalConnector.js +++ b/frontend/src/InteractiveSearch/InteractiveSearchFilterModalConnector.js @@ -1,6 +1,6 @@ import { connect } from 'react-redux'; import { createSelector } from 'reselect'; -import * as releaseActions from 'Store/Actions/releaseActions'; +import { setReleasesFilter } from 'Store/Actions/releaseActions'; import FilterModal from 'Components/Filter/FilterModal'; function createMapStateToProps() { @@ -10,7 +10,8 @@ function createMapStateToProps() { (sectionItems, filterBuilderProps) => { return { sectionItems, - filterBuilderProps + filterBuilderProps, + customFilterType: 'releases' }; } ); @@ -18,12 +19,10 @@ function createMapStateToProps() { function createMapDispatchToProps(dispatch, props) { return { - onRemoveCustomFilterPress(payload) { - dispatch(releaseActions.removeReleasesCustomFilter(payload)); - }, + dispatchSetFilter(payload) { + const action = setReleasesFilter; - onSaveCustomFilterPress(payload) { - dispatch(releaseActions.saveReleasesCustomFilter(payload)); + dispatch(action(payload)); } }; } diff --git a/frontend/src/Album/Search/InteractiveAlbumSearchModal.js b/frontend/src/InteractiveSearch/InteractiveSearchModal.js similarity index 60% rename from frontend/src/Album/Search/InteractiveAlbumSearchModal.js rename to frontend/src/InteractiveSearch/InteractiveSearchModal.js index 7b4081fbf..7b4b9ffdb 100644 --- a/frontend/src/Album/Search/InteractiveAlbumSearchModal.js +++ b/frontend/src/InteractiveSearch/InteractiveSearchModal.js @@ -1,9 +1,9 @@ import PropTypes from 'prop-types'; import React from 'react'; import Modal from 'Components/Modal/Modal'; -import InteractiveAlbumSearchModalContentConnector from './InteractiveAlbumSearchModalContentConnector'; +import InteractiveSearchModalContentConnector from './InteractiveSearchModalContentConnector'; -function InteractiveAlbumSearchModal(props) { +function InteractiveSearchModal(props) { const { isOpen, onModalClose, @@ -15,7 +15,7 @@ function InteractiveAlbumSearchModal(props) { isOpen={isOpen} onModalClose={onModalClose} > - @@ -23,9 +23,9 @@ function InteractiveAlbumSearchModal(props) { ); } -InteractiveAlbumSearchModal.propTypes = { +InteractiveSearchModal.propTypes = { isOpen: PropTypes.bool.isRequired, onModalClose: PropTypes.func.isRequired }; -export default InteractiveAlbumSearchModal; +export default InteractiveSearchModal; diff --git a/frontend/src/Album/Search/InteractiveAlbumSearchModalContent.css b/frontend/src/InteractiveSearch/InteractiveSearchModalContent.css similarity index 100% rename from frontend/src/Album/Search/InteractiveAlbumSearchModalContent.css rename to frontend/src/InteractiveSearch/InteractiveSearchModalContent.css diff --git a/frontend/src/Album/Search/InteractiveAlbumSearchModalContent.js b/frontend/src/InteractiveSearch/InteractiveSearchModalContent.js similarity index 93% rename from frontend/src/Album/Search/InteractiveAlbumSearchModalContent.js rename to frontend/src/InteractiveSearch/InteractiveSearchModalContent.js index 10bb77556..36a6b670a 100644 --- a/frontend/src/Album/Search/InteractiveAlbumSearchModalContent.js +++ b/frontend/src/InteractiveSearch/InteractiveSearchModalContent.js @@ -13,8 +13,8 @@ import ModalFooter from 'Components/Modal/ModalFooter'; import Table from 'Components/Table/Table'; import TableBody from 'Components/Table/TableBody'; import InteractiveSearchFilterModalConnector from './InteractiveSearchFilterModalConnector'; -import InteractiveAlbumSearchRow from './InteractiveAlbumSearchRow'; -import styles from './InteractiveAlbumSearchModalContent.css'; +import InteractiveSearchRow from './InteractiveSearchRow'; +import styles from './InteractiveSearchModalContent.css'; const columns = [ { @@ -75,7 +75,7 @@ const columns = [ } ]; -class InteractiveAlbumSearchModalContent extends Component { +class InteractiveSearchModalContent extends Component { // // Render @@ -161,7 +161,7 @@ class InteractiveAlbumSearchModalContent extends Component { { items.map((item) => { return ( - ); } } -InteractiveAlbumSearchModalContentConnector.propTypes = { +InteractiveSearchModalContentConnector.propTypes = { albumId: PropTypes.number, dispatchFetchReleases: PropTypes.func.isRequired, dispatchClearReleases: PropTypes.func.isRequired, dispatchCancelFetchReleases: PropTypes.func.isRequired }; -export default connect(createMapStateToProps, createMapDispatchToProps)(InteractiveAlbumSearchModalContentConnector); +export default connect(createMapStateToProps, createMapDispatchToProps)(InteractiveSearchModalContentConnector); diff --git a/frontend/src/Album/Search/InteractiveAlbumSearchRow.css b/frontend/src/InteractiveSearch/InteractiveSearchRow.css similarity index 100% rename from frontend/src/Album/Search/InteractiveAlbumSearchRow.css rename to frontend/src/InteractiveSearch/InteractiveSearchRow.css diff --git a/frontend/src/Album/Search/InteractiveAlbumSearchRow.js b/frontend/src/InteractiveSearch/InteractiveSearchRow.js similarity index 94% rename from frontend/src/Album/Search/InteractiveAlbumSearchRow.js rename to frontend/src/InteractiveSearch/InteractiveSearchRow.js index 2aadc3207..d12fabd52 100644 --- a/frontend/src/Album/Search/InteractiveAlbumSearchRow.js +++ b/frontend/src/InteractiveSearch/InteractiveSearchRow.js @@ -10,10 +10,10 @@ import Link from 'Components/Link/Link'; import TableRow from 'Components/Table/TableRow'; import TableRowCell from 'Components/Table/Cells/TableRowCell'; import Popover from 'Components/Tooltip/Popover'; -import EpisodeQuality from 'Album/EpisodeQuality'; +import TrackQuality from 'Album/TrackQuality'; import ProtocolLabel from 'Activity/Queue/ProtocolLabel'; import Peers from './Peers'; -import styles from './InteractiveAlbumSearchRow.css'; +import styles from './InteractiveSearchRow.css'; function getDownloadIcon(isGrabbing, isGrabbed, grabError) { if (isGrabbing) { @@ -39,7 +39,7 @@ function getDownloadTooltip(isGrabbing, isGrabbed, grabError) { return 'Add to downloaded queue'; } -class InteractiveAlbumSearchRow extends Component { +class InteractiveSearchRow extends Component { // // Listeners @@ -120,7 +120,7 @@ class InteractiveAlbumSearchRow extends Component { - @@ -171,7 +171,7 @@ class InteractiveAlbumSearchRow extends Component { } } -InteractiveAlbumSearchRow.propTypes = { +InteractiveSearchRow.propTypes = { guid: PropTypes.string.isRequired, protocol: PropTypes.string.isRequired, age: PropTypes.number.isRequired, @@ -196,9 +196,9 @@ InteractiveAlbumSearchRow.propTypes = { onGrabPress: PropTypes.func.isRequired }; -InteractiveAlbumSearchRow.defaultProps = { +InteractiveSearchRow.defaultProps = { isGrabbing: false, isGrabbed: false }; -export default InteractiveAlbumSearchRow; +export default InteractiveSearchRow; diff --git a/frontend/src/Album/Search/Peers.js b/frontend/src/InteractiveSearch/Peers.js similarity index 100% rename from frontend/src/Album/Search/Peers.js rename to frontend/src/InteractiveSearch/Peers.js diff --git a/frontend/src/Settings/Profiles/Delay/DelayProfiles.js b/frontend/src/Settings/Profiles/Delay/DelayProfiles.js index a8ae63379..a745da9d4 100644 --- a/frontend/src/Settings/Profiles/Delay/DelayProfiles.js +++ b/frontend/src/Settings/Profiles/Delay/DelayProfiles.js @@ -1,10 +1,10 @@ import PropTypes from 'prop-types'; import React, { Component } from 'react'; -import Measure from 'react-measure'; import { icons } from 'Helpers/Props'; import FieldSet from 'Components/FieldSet'; import Icon from 'Components/Icon'; import Link from 'Components/Link/Link'; +import Measure from 'Components/Measure'; import PageSectionContent from 'Components/Page/PageSectionContent'; import DelayProfileDragSource from './DelayProfileDragSource'; import DelayProfileDragPreview from './DelayProfileDragPreview'; diff --git a/frontend/src/Settings/Profiles/Quality/EditQualityProfileModalContent.js b/frontend/src/Settings/Profiles/Quality/EditQualityProfileModalContent.js index c14359877..ece3df17c 100644 --- a/frontend/src/Settings/Profiles/Quality/EditQualityProfileModalContent.js +++ b/frontend/src/Settings/Profiles/Quality/EditQualityProfileModalContent.js @@ -1,11 +1,11 @@ import PropTypes from 'prop-types'; import React, { Component } from 'react'; -import Measure from 'react-measure'; import { inputTypes, kinds, sizes } from 'Helpers/Props'; import dimensions from 'Styles/Variables/dimensions'; import Button from 'Components/Link/Button'; import SpinnerErrorButton from 'Components/Link/SpinnerErrorButton'; import LoadingIndicator from 'Components/Loading/LoadingIndicator'; +import Measure from 'Components/Measure'; import ModalContent from 'Components/Modal/ModalContent'; import ModalHeader from 'Components/Modal/ModalHeader'; import ModalBody from 'Components/Modal/ModalBody'; diff --git a/frontend/src/Settings/Profiles/Quality/QualityProfileItems.js b/frontend/src/Settings/Profiles/Quality/QualityProfileItems.js index 66bd25e7f..c41d4b77d 100644 --- a/frontend/src/Settings/Profiles/Quality/QualityProfileItems.js +++ b/frontend/src/Settings/Profiles/Quality/QualityProfileItems.js @@ -1,12 +1,12 @@ import PropTypes from 'prop-types'; import React, { Component } from 'react'; -import Measure from 'react-measure'; import { icons, kinds, sizes } from 'Helpers/Props'; import Icon from 'Components/Icon'; import Button from 'Components/Link/Button'; import FormGroup from 'Components/Form/FormGroup'; import FormLabel from 'Components/Form/FormLabel'; import FormInputHelpText from 'Components/Form/FormInputHelpText'; +import Measure from 'Components/Measure'; import QualityProfileItemDragSource from './QualityProfileItemDragSource'; import QualityProfileItemDragPreview from './QualityProfileItemDragPreview'; import styles from './QualityProfileItems.css'; diff --git a/frontend/src/Store/Actions/Creators/Reducers/createCustomFilterReducers.js b/frontend/src/Store/Actions/Creators/Reducers/createCustomFilterReducers.js deleted file mode 100644 index 8bec20cfc..000000000 --- a/frontend/src/Store/Actions/Creators/Reducers/createCustomFilterReducers.js +++ /dev/null @@ -1,65 +0,0 @@ -import customFilterHandlers from 'Utilities/customFilterHandlers'; -import getSectionState from 'Utilities/State/getSectionState'; -import updateSectionState from 'Utilities/State/updateSectionState'; -import generateUUIDv4 from 'Utilities/String/generateUUIDv4'; - -function createRemoveCustomFilterReducer(section) { - return (state, { payload }) => { - const newState = getSectionState(state, section); - const index = newState.customFilters.findIndex((c) => c.key === payload.key); - - newState.customFilters = [...newState.customFilters]; - newState.customFilters.splice(index, 1); - - // Reset the selected filter to the first filter if the selected filter - // is being deleted. - // TODO: Server side collections need to have their collections refetched - - if (newState.selectedFilterKey === payload.key) { - newState.selectedFilterKey = newState.filters[0].key; - } - - return updateSectionState(state, section, newState); - }; -} - -function createSaveCustomFilterReducer(section) { - return (state, { payload }) => { - const newState = getSectionState(state, section); - - const { - label, - filters - } = payload; - - let key = payload.key; - - newState.customFilters = [...newState.customFilters]; - - if (key) { - const index = newState.customFilters.findIndex((c) => c.key === key); - - newState.customFilters.splice(index, 1, { key, label, filters }); - } else { - key = generateUUIDv4(); - - newState.customFilters.push({ - key, - label, - filters - }); - } - - // TODO: Server side collections need to have their collections refetched - newState.selectedFilterKey = key; - - return updateSectionState(state, section, newState); - }; -} - -export default function createCustomFilterReducers(section, handlers) { - return { - [handlers[customFilterHandlers.REMOVE]]: createRemoveCustomFilterReducer(section), - [handlers[customFilterHandlers.SAVE]]: createSaveCustomFilterReducer(section) - }; -} diff --git a/frontend/src/Store/Actions/Creators/createRemoveItemHandler.js b/frontend/src/Store/Actions/Creators/createRemoveItemHandler.js index 9190c9a62..6f353ed17 100644 --- a/frontend/src/Store/Actions/Creators/createRemoveItemHandler.js +++ b/frontend/src/Store/Actions/Creators/createRemoveItemHandler.js @@ -20,13 +20,13 @@ function createRemoveItemHandler(section, url) { promise.done((data) => { dispatch(batchActions([ - removeItem({ section, id }), - set({ section, isDeleting: false, deleteError: null - }) + }), + + removeItem({ section, id }) ])); }); diff --git a/frontend/src/Store/Actions/Creators/createSaveProviderHandler.js b/frontend/src/Store/Actions/Creators/createSaveProviderHandler.js index 6533b5b0c..6d555bf23 100644 --- a/frontend/src/Store/Actions/Creators/createSaveProviderHandler.js +++ b/frontend/src/Store/Actions/Creators/createSaveProviderHandler.js @@ -21,10 +21,11 @@ function createSaveProviderHandler(section, url, options = {}) { const { id, - queryParams = {} + queryParams = {}, + ...otherPayload } = payload; - const saveData = getProviderState(payload, getState, section); + const saveData = getProviderState({ id, ...otherPayload }, getState, section); const ajaxOptions = { url: `${url}?${$.param(queryParams, true)}`, diff --git a/frontend/src/Store/Actions/albumStudioActions.js b/frontend/src/Store/Actions/albumStudioActions.js index 9221e8ba5..11302ccbd 100644 --- a/frontend/src/Store/Actions/albumStudioActions.js +++ b/frontend/src/Store/Actions/albumStudioActions.js @@ -2,7 +2,7 @@ import _ from 'lodash'; import $ from 'jquery'; import { createAction } from 'redux-actions'; import getMonitoringOptions from 'Utilities/Artist/getMonitoringOptions'; -import { sortDirections } from 'Helpers/Props'; +import { filterBuilderTypes, filterBuilderValueTypes, sortDirections } from 'Helpers/Props'; import { createThunk, handleThunks } from 'Store/thunks'; import createSetClientSideCollectionSortReducer from './Creators/Reducers/createSetClientSideCollectionSortReducer'; import createSetClientSideCollectionFilterReducer from './Creators/Reducers/createSetClientSideCollectionFilterReducer'; @@ -29,7 +29,55 @@ export const defaultState = { selectedFilterKey: 'all', filters, filterPredicates, - customFilters: [] + + filterBuilderProps: [ + { + name: 'monitored', + label: 'Monitored', + type: filterBuilderTypes.EXACT, + valueType: filterBuilderValueTypes.BOOL + }, + { + name: 'status', + label: 'Status', + type: filterBuilderTypes.EXACT, + valueType: filterBuilderValueTypes.ARTIST_STATUS + }, + { + name: 'artistType', + label: 'Artist Type', + type: filterBuilderTypes.EXACT + }, + { + name: 'qualityProfileId', + label: 'Quality Profile', + type: filterBuilderTypes.EXACT, + valueType: filterBuilderValueTypes.QUALITY_PROFILE + }, + { + name: 'languageProfileId', + label: 'Language Profile', + type: filterBuilderTypes.EXACT, + valueType: filterBuilderValueTypes.LANGUAGE_PROFILE + }, + { + name: 'metadataProfileId', + label: 'Metadata Profile', + type: filterBuilderTypes.EXACT, + valueType: filterBuilderValueTypes.METADATA_PROFILE + }, + { + name: 'rootFolderPath', + label: 'Root Folder Path', + type: filterBuilderTypes.EXACT + }, + { + name: 'tags', + label: 'Tags', + type: filterBuilderTypes.ARRAY, + valueType: filterBuilderValueTypes.TAG + } + ] }; export const persistState = [ diff --git a/frontend/src/Store/Actions/artistActions.js b/frontend/src/Store/Actions/artistActions.js index d96fb6370..5b82ae622 100644 --- a/frontend/src/Store/Actions/artistActions.js +++ b/frontend/src/Store/Actions/artistActions.js @@ -103,6 +103,20 @@ export const filterPredicates = { const predicate = filterTypePredicates[type]; return predicate(item.ratings.value * 10, filterValue); + }, + + albumCount: function(item, filterValue, type) { + const predicate = filterTypePredicates[type]; + const albumCount = item.statistics ? item.statistics.albumCount : 0; + + return predicate(albumCount, filterValue); + }, + + sizeOnDisk: function(item, filterValue, type) { + const predicate = filterTypePredicates[type]; + const sizeOnDisk = item.statistics ? item.statistics.sizeOnDisk : 0; + + return predicate(sizeOnDisk, filterValue); } }; diff --git a/frontend/src/Store/Actions/artistEditorActions.js b/frontend/src/Store/Actions/artistEditorActions.js index af786b1b4..34cb0ef19 100644 --- a/frontend/src/Store/Actions/artistEditorActions.js +++ b/frontend/src/Store/Actions/artistEditorActions.js @@ -1,12 +1,10 @@ import $ from 'jquery'; import { createAction } from 'redux-actions'; import { batchActions } from 'redux-batched-actions'; -import customFilterHandlers from 'Utilities/customFilterHandlers'; import { filterBuilderTypes, filterBuilderValueTypes, sortDirections } from 'Helpers/Props'; import { createThunk, handleThunks } from 'Store/thunks'; import createSetClientSideCollectionSortReducer from './Creators/Reducers/createSetClientSideCollectionSortReducer'; import createSetClientSideCollectionFilterReducer from './Creators/Reducers/createSetClientSideCollectionFilterReducer'; -import createCustomFilterReducers from './Creators/Reducers/createCustomFilterReducers'; import createHandleActions from './Creators/createHandleActions'; import { set, updateItem } from './baseActions'; import { filters, filterPredicates } from './artistActions'; @@ -79,8 +77,7 @@ export const defaultState = { type: filterBuilderTypes.ARRAY, valueType: filterBuilderValueTypes.TAG } - ], - customFilters: [] + ] }; export const persistState = [ @@ -97,8 +94,6 @@ export const SET_ARTIST_EDITOR_SORT = 'artistEditor/setArtistEditorSort'; export const SET_ARTIST_EDITOR_FILTER = 'artistEditor/setArtistEditorFilter'; export const SAVE_ARTIST_EDITOR = 'artistEditor/saveArtistEditor'; export const BULK_DELETE_ARTIST = 'artistEditor/bulkDeleteArtist'; -export const REMOVE_ARTIST_EDITOR_CUSTOM_FILTER = 'artistEditor/removeArtistEditorCustomFilter'; -export const SAVE_ARTIST_EDITOR_CUSTOM_FILTER = 'artistEditor/saveArtistEditorCustomFilter'; // // Action Creators @@ -107,8 +102,6 @@ export const setArtistEditorSort = createAction(SET_ARTIST_EDITOR_SORT); export const setArtistEditorFilter = createAction(SET_ARTIST_EDITOR_FILTER); export const saveArtistEditor = createThunk(SAVE_ARTIST_EDITOR); export const bulkDeleteArtist = createThunk(BULK_DELETE_ARTIST); -export const removeArtistEditorCustomFilter = createAction(REMOVE_ARTIST_EDITOR_CUSTOM_FILTER); -export const saveArtistEditorCustomFilter = createAction(SAVE_ARTIST_EDITOR_CUSTOM_FILTER); // // Action Handlers @@ -193,11 +186,6 @@ export const actionHandlers = handleThunks({ export const reducers = createHandleActions({ [SET_ARTIST_EDITOR_SORT]: createSetClientSideCollectionSortReducer(section), - [SET_ARTIST_EDITOR_FILTER]: createSetClientSideCollectionFilterReducer(section), - - ...createCustomFilterReducers(section, { - [customFilterHandlers.REMOVE]: REMOVE_ARTIST_EDITOR_CUSTOM_FILTER, - [customFilterHandlers.SAVE]: SAVE_ARTIST_EDITOR_CUSTOM_FILTER - }) + [SET_ARTIST_EDITOR_FILTER]: createSetClientSideCollectionFilterReducer(section) }, defaultState, section); diff --git a/frontend/src/Store/Actions/artistIndexActions.js b/frontend/src/Store/Actions/artistIndexActions.js index 9c081f3ed..efb787601 100644 --- a/frontend/src/Store/Actions/artistIndexActions.js +++ b/frontend/src/Store/Actions/artistIndexActions.js @@ -1,12 +1,10 @@ import moment from 'moment'; import { createAction } from 'redux-actions'; -import customFilterHandlers from 'Utilities/customFilterHandlers'; import sortByName from 'Utilities/Array/sortByName'; import { filterBuilderTypes, filterBuilderValueTypes, sortDirections } from 'Helpers/Props'; import createSetTableOptionReducer from './Creators/Reducers/createSetTableOptionReducer'; import createSetClientSideCollectionSortReducer from './Creators/Reducers/createSetClientSideCollectionSortReducer'; import createSetClientSideCollectionFilterReducer from './Creators/Reducers/createSetClientSideCollectionFilterReducer'; -import createCustomFilterReducers from './Creators/Reducers/createCustomFilterReducers'; import createHandleActions from './Creators/createHandleActions'; import { filters, filterPredicates } from './artistActions'; @@ -292,7 +290,8 @@ export const defaultState = { { name: 'sizeOnDisk', label: 'Size on Disk', - type: filterBuilderTypes.NUMBER + type: filterBuilderTypes.NUMBER, + valueType: filterBuilderValueTypes.BYTES }, { name: 'genres', @@ -324,8 +323,7 @@ export const defaultState = { type: filterBuilderTypes.ARRAY, valueType: filterBuilderValueTypes.TAG } - ], - customFilters: [] + ] }; export const persistState = [ @@ -350,8 +348,6 @@ export const SET_ARTIST_TABLE_OPTION = 'artistIndex/setArtistTableOption'; export const SET_ARTIST_POSTER_OPTION = 'artistIndex/setArtistPosterOption'; export const SET_ARTIST_BANNER_OPTION = 'artistIndex/setArtistBannerOption'; export const SET_ARTIST_OVERVIEW_OPTION = 'artistIndex/setArtistOverviewOption'; -export const REMOVE_ARTIST_CUSTOM_FILTER = 'artistIndex/removeArtistCustomFilter'; -export const SAVE_ARTIST_CUSTOM_FILTER = 'artistIndex/saveArtistCustomFilter'; // // Action Creators @@ -363,8 +359,7 @@ export const setArtistTableOption = createAction(SET_ARTIST_TABLE_OPTION); export const setArtistPosterOption = createAction(SET_ARTIST_POSTER_OPTION); export const setArtistBannerOption = createAction(SET_ARTIST_BANNER_OPTION); export const setArtistOverviewOption = createAction(SET_ARTIST_OVERVIEW_OPTION); -export const removeArtistCustomFilter = createAction(REMOVE_ARTIST_CUSTOM_FILTER); -export const saveArtistCustomFilter = createAction(SAVE_ARTIST_CUSTOM_FILTER); + // // Reducers @@ -413,11 +408,6 @@ export const reducers = createHandleActions({ ...payload } }; - }, - - ...createCustomFilterReducers(section, { - [customFilterHandlers.REMOVE]: REMOVE_ARTIST_CUSTOM_FILTER, - [customFilterHandlers.SAVE]: SAVE_ARTIST_CUSTOM_FILTER - }) + } }, defaultState, section); diff --git a/frontend/src/Store/Actions/customFilterActions.js b/frontend/src/Store/Actions/customFilterActions.js new file mode 100644 index 000000000..750c3ef6f --- /dev/null +++ b/frontend/src/Store/Actions/customFilterActions.js @@ -0,0 +1,55 @@ +import { createThunk, handleThunks } from 'Store/thunks'; +import createFetchHandler from './Creators/createFetchHandler'; +import createRemoveItemHandler from './Creators/createRemoveItemHandler'; +import createSaveProviderHandler from './Creators/createSaveProviderHandler'; +import createHandleActions from './Creators/createHandleActions'; + +// +// Variables + +export const section = 'customFilters'; + +// +// State + +export const defaultState = { + isFetching: false, + isPopulated: false, + error: null, + isSaving: false, + saveError: null, + isDeleting: false, + deleteError: null, + items: [], + pendingChanges: {} +}; + +// +// Actions Types + +export const FETCH_CUSTOM_FILTERS = 'customFilters/fetchCustomFilters'; +export const SAVE_CUSTOM_FILTER = 'customFilters/saveCustomFilter'; +export const DELETE_CUSTOM_FILTER = 'customFilters/deleteCustomFilter'; + +// +// Action Creators + +export const fetchCustomFilters = createThunk(FETCH_CUSTOM_FILTERS); +export const saveCustomFilter = createThunk(SAVE_CUSTOM_FILTER); +export const deleteCustomFilter = createThunk(DELETE_CUSTOM_FILTER); + +// +// Action Handlers + +export const actionHandlers = handleThunks({ + [FETCH_CUSTOM_FILTERS]: createFetchHandler(section, '/customFilter'), + + [SAVE_CUSTOM_FILTER]: createSaveProviderHandler(section, '/customFilter'), + + [DELETE_CUSTOM_FILTER]: createRemoveItemHandler(section, '/customFilter') + +}); + +// +// Reducers +export const reducers = createHandleActions({}, defaultState, section); diff --git a/frontend/src/Store/Actions/index.js b/frontend/src/Store/Actions/index.js index 9f8e162bd..3a8e5ebc0 100644 --- a/frontend/src/Store/Actions/index.js +++ b/frontend/src/Store/Actions/index.js @@ -2,6 +2,7 @@ import * as addArtist from './addArtistActions'; import * as app from './appActions'; import * as blacklist from './blacklistActions'; import * as captcha from './captchaActions'; +import * as customFilters from './customFilterActions'; import * as devices from './deviceActions'; import * as calendar from './calendarActions'; import * as commands from './commandActions'; @@ -35,6 +36,7 @@ export default [ captcha, calendar, commands, + customFilters, devices, albums, trackFiles, diff --git a/frontend/src/Store/Actions/oAuthActions.js b/frontend/src/Store/Actions/oAuthActions.js index d0b3bb930..8a8d2c596 100644 --- a/frontend/src/Store/Actions/oAuthActions.js +++ b/frontend/src/Store/Actions/oAuthActions.js @@ -10,6 +10,7 @@ import createHandleActions from './Creators/createHandleActions'; // Variables export const section = 'oAuth'; +const callbackUrl = `${window.location.origin}${window.Lidarr.urlBase}/oauth.html`; // // State @@ -64,6 +65,19 @@ function showOAuthWindow(url) { return deferred.promise(); } +function executeIntermediateRequest(payload, ajaxOptions) { + return $.ajax(ajaxOptions).then((data) => { + return requestAction({ + action: 'continueOAuth', + queryParams: { + ...data, + callbackUrl + }, + ...payload + }); + }); +} + // // Action Handlers @@ -72,7 +86,7 @@ export const actionHandlers = handleThunks({ [START_OAUTH]: function(getState, payload, dispatch) { const actionPayload = { action: 'startOAuth', - queryParams: { callbackUrl: `${window.location.origin}${window.Lidarr.urlBase}/oauth.html` }, + queryParams: { callbackUrl }, ...payload }; @@ -85,7 +99,16 @@ export const actionHandlers = handleThunks({ const promise = requestAction(actionPayload) .then((response) => { startResponse = response; - return showOAuthWindow(response.oauthUrl); + + if (response.oauthUrl) { + return showOAuthWindow(response.oauthUrl); + } + + return executeIntermediateRequest(payload, response).then((intermediateResponse) => { + startResponse = intermediateResponse; + + return showOAuthWindow(intermediateResponse.oauthUrl); + }); }) .then((queryParams) => { return requestAction({ diff --git a/frontend/src/Store/Actions/releaseActions.js b/frontend/src/Store/Actions/releaseActions.js index 62a1bbc4b..9d9ec2161 100644 --- a/frontend/src/Store/Actions/releaseActions.js +++ b/frontend/src/Store/Actions/releaseActions.js @@ -1,11 +1,9 @@ import $ from 'jquery'; import { createAction } from 'redux-actions'; -import customFilterHandlers from 'Utilities/customFilterHandlers'; import { filterBuilderTypes, filterBuilderValueTypes, filterTypes, sortDirections } from 'Helpers/Props'; import { createThunk, handleThunks } from 'Store/thunks'; import createSetClientSideCollectionSortReducer from './Creators/Reducers/createSetClientSideCollectionSortReducer'; import createSetClientSideCollectionFilterReducer from './Creators/Reducers/createSetClientSideCollectionFilterReducer'; -import createCustomFilterReducers from './Creators/Reducers/createCustomFilterReducers'; import createFetchHandler from './Creators/createFetchHandler'; import createHandleActions from './Creators/createHandleActions'; @@ -45,8 +43,6 @@ export const defaultState = { } }, - selectedFilterKey: 'all', - filters: [ { key: 'all', @@ -143,9 +139,7 @@ export const defaultState = { label: 'Rejections', type: filterBuilderTypes.NUMBER } - ], - - customFilters: [] + ] }; export const persistState = [ @@ -163,9 +157,6 @@ export const CLEAR_RELEASES = 'releases/clearReleases'; export const GRAB_RELEASE = 'releases/grabRelease'; export const UPDATE_RELEASE = 'releases/updateRelease'; export const SET_RELEASES_FILTER = 'releases/setReleasesFilter'; -export const ADD_RELEASES_CUSTOM_FILTER = 'releases/addReleasesCustomFilter'; -export const REMOVE_RELEASES_CUSTOM_FILTER = 'releases/removeReleasesCustomFilter'; -export const SAVE_RELEASES_CUSTOM_FILTER = 'releases/saveReleasesCustomFilter'; // // Action Creators @@ -177,9 +168,6 @@ export const clearReleases = createAction(CLEAR_RELEASES); export const grabRelease = createThunk(GRAB_RELEASE); export const updateRelease = createAction(UPDATE_RELEASE); export const setReleasesFilter = createAction(SET_RELEASES_FILTER); -export const addReleasesCustomFilter = createAction(ADD_RELEASES_CUSTOM_FILTER); -export const removeReleasesCustomFilter = createAction(REMOVE_RELEASES_CUSTOM_FILTER); -export const saveReleasesCustomFilter = createAction(SAVE_RELEASES_CUSTOM_FILTER); // // Helpers @@ -266,11 +254,6 @@ export const reducers = createHandleActions({ }, [SET_RELEASES_SORT]: createSetClientSideCollectionSortReducer(section), - [SET_RELEASES_FILTER]: createSetClientSideCollectionFilterReducer(section), - - ...createCustomFilterReducers(section, { - [customFilterHandlers.REMOVE]: REMOVE_RELEASES_CUSTOM_FILTER, - [customFilterHandlers.SAVE]: SAVE_RELEASES_CUSTOM_FILTER - }) + [SET_RELEASES_FILTER]: createSetClientSideCollectionFilterReducer(section) }, defaultState, section); diff --git a/frontend/src/Store/Middleware/createSentryMiddleware.js b/frontend/src/Store/Middleware/createSentryMiddleware.js new file mode 100644 index 000000000..41109a0c7 --- /dev/null +++ b/frontend/src/Store/Middleware/createSentryMiddleware.js @@ -0,0 +1,91 @@ +import _ from 'lodash'; +import * as sentry from '@sentry/browser'; +import parseUrl from 'Utilities/String/parseUrl'; + +function cleanseUrl(url) { + const properties = parseUrl(url); + + return `${properties.pathname}${properties.search}`; +} + +function cleanseData(data) { + const result = _.cloneDeep(data); + + result.transaction = cleanseUrl(result.transaction); + + if (result.exception) { + result.exception.values.forEach((exception) => { + const stacktrace = exception.stacktrace; + + if (stacktrace) { + stacktrace.frames.forEach((frame) => { + frame.filename = cleanseUrl(frame.filename); + }); + } + }); + } + + result.request.url = cleanseUrl(result.request.url); + + return result; +} + +function identity(stuff) { + return stuff; +} + +function createMiddleware() { + return (store) => (next) => (action) => { + try { + // Adds a breadcrumb for reporting later (if necessary). + sentry.addBreadcrumb({ + category: 'redux', + message: action.type + }); + + return next(action); + } catch (err) { + console.error(`[sentry] Reporting error to Sentry: ${err}`); + + // Send the report including breadcrumbs. + sentry.captureException(err, { + extra: { + action: identity(action), + state: identity(store.getState()) + } + }); + } + }; +} + +export default function createSentryMiddleware() { + const { + analytics, + branch, + version, + release, + isProduction + } = window.Lidarr; + + if (!analytics) { + return; + } + + const dsn = isProduction ? 'https://c3a5b33e08de4e18b7d0505e942dbc95@sentry.io/216290' : + 'https://baede6f14da54cf48ff431479e400adf@sentry.io/1249427'; + + sentry.init({ + dsn, + environment: isProduction ? 'production' : 'development', + release, + sendDefaultPii: true, + beforeSend: cleanseData + }); + + sentry.configureScope((scope) => { + scope.setTag('branch', branch); + scope.setTag('version', version); + }); + + return createMiddleware(); +} diff --git a/frontend/src/Store/Middleware/middlewares.js b/frontend/src/Store/Middleware/middlewares.js index 5583c21ef..a1c400bfe 100644 --- a/frontend/src/Store/Middleware/middlewares.js +++ b/frontend/src/Store/Middleware/middlewares.js @@ -1,15 +1,15 @@ import { applyMiddleware, compose } from 'redux'; import thunk from 'redux-thunk'; import { routerMiddleware } from 'react-router-redux'; -import sentryMiddleware from './sentryMiddleware'; +import createSentryMiddleware from './createSentryMiddleware'; import persistState from './persistState'; export default function(history) { const middlewares = []; - const ravenMiddleware = sentryMiddleware(); + const sentryMiddleware = createSentryMiddleware(); - if (ravenMiddleware) { - middlewares.push(ravenMiddleware); + if (sentryMiddleware) { + middlewares.push(sentryMiddleware); } middlewares.push(routerMiddleware(history)); diff --git a/frontend/src/Store/Middleware/sentryMiddleware.js b/frontend/src/Store/Middleware/sentryMiddleware.js deleted file mode 100644 index 454e1fd24..000000000 --- a/frontend/src/Store/Middleware/sentryMiddleware.js +++ /dev/null @@ -1,51 +0,0 @@ -import _ from 'lodash'; -import Raven from 'raven-js'; -import createRavenMiddleware from 'raven-for-redux'; -import parseUrl from 'Utilities/String/parseUrl'; - -function cleanseUrl(url) { - const properties = parseUrl(url); - - return `${properties.pathname}${properties.search}`; -} - -function cleanseData(data) { - const result = _.cloneDeep(data); - - result.culprit = cleanseUrl(result.culprit); - result.request.url = cleanseUrl(result.request.url); - - return result; -} - -export default function sentryMiddleware() { - const { - analytics, - branch, - version, - release, - isProduction - } = window.Lidarr; - - if (!analytics) { - return; - } - - const dsn = isProduction ? 'https://c3a5b33e08de4e18b7d0505e942dbc95@sentry.io/216290' : - 'https://baede6f14da54cf48ff431479e400adf@sentry.io/1249427'; - - Raven.config( - dsn, - { - environment: isProduction ? 'production' : 'development', - release, - tags: { - branch, - version - }, - dataCallback: cleanseData - } - ).install(); - - return createRavenMiddleware(Raven); -} diff --git a/frontend/src/Store/Selectors/createClientSideCollectionSelector.js b/frontend/src/Store/Selectors/createClientSideCollectionSelector.js index c00adc159..929b0afe0 100644 --- a/frontend/src/Store/Selectors/createClientSideCollectionSelector.js +++ b/frontend/src/Store/Selectors/createClientSideCollectionSelector.js @@ -94,12 +94,24 @@ function sort(items, state) { return _.orderBy(items, clauses, orders); } +function createCustomFiltersSelector(type, alternateType) { + return createSelector( + (state) => state.customFilters.items, + (customFilters) => { + return customFilters.filter((customFilter) => { + return customFilter.type === type || customFilter.type === alternateType; + }); + } + ); +} + function createClientSideCollectionSelector(section, uiSection) { return createSelector( (state) => _.get(state, section), (state) => _.get(state, uiSection), - (sectionState, uiSectionState = {}) => { - const state = Object.assign({}, sectionState, uiSectionState); + createCustomFiltersSelector(section, uiSection), + (sectionState, uiSectionState = {}, customFilters) => { + const state = Object.assign({}, sectionState, uiSectionState, { customFilters }); const filtered = filter(state.items, state); const sorted = sort(filtered, state); @@ -107,6 +119,7 @@ function createClientSideCollectionSelector(section, uiSection) { return { ...sectionState, ...uiSectionState, + customFilters, items: sorted, totalItems: state.items.length }; diff --git a/frontend/src/System/Status/About/About.js b/frontend/src/System/Status/About/About.js index a177913c2..a48ef1fed 100644 --- a/frontend/src/System/Status/About/About.js +++ b/frontend/src/System/Status/About/About.js @@ -4,6 +4,7 @@ import titleCase from 'Utilities/String/titleCase'; import FieldSet from 'Components/FieldSet'; import DescriptionList from 'Components/DescriptionList/DescriptionList'; import DescriptionListItem from 'Components/DescriptionList/DescriptionListItem'; +import StartTime from './StartTime'; import styles from './About.css'; class About extends Component { @@ -19,7 +20,10 @@ class About extends Component { migrationVersion, appData, startupPath, - mode + mode, + startTime, + timeFormat, + longDateFormat } = this.props; return ( @@ -57,6 +61,17 @@ class About extends Component { title="Mode" data={titleCase(mode)} /> + + + } + /> ); @@ -65,13 +80,16 @@ class About extends Component { } About.propTypes = { - version: PropTypes.string, - isMonoRuntime: PropTypes.bool, - runtimeVersion: PropTypes.string, - migrationVersion: PropTypes.number, - appData: PropTypes.string, - startupPath: PropTypes.string, - mode: PropTypes.string + version: PropTypes.string.isRequired, + isMonoRuntime: PropTypes.bool.isRequired, + runtimeVersion: PropTypes.string.isRequired, + migrationVersion: PropTypes.number.isRequired, + appData: PropTypes.string.isRequired, + startupPath: PropTypes.string.isRequired, + mode: PropTypes.string.isRequired, + startTime: PropTypes.string.isRequired, + timeFormat: PropTypes.string.isRequired, + longDateFormat: PropTypes.string.isRequired }; export default About; diff --git a/frontend/src/System/Status/About/AboutConnector.js b/frontend/src/System/Status/About/AboutConnector.js index 8d5c2ce0f..475d9778b 100644 --- a/frontend/src/System/Status/About/AboutConnector.js +++ b/frontend/src/System/Status/About/AboutConnector.js @@ -3,14 +3,18 @@ import React, { Component } from 'react'; import { connect } from 'react-redux'; import { createSelector } from 'reselect'; import { fetchStatus } from 'Store/Actions/systemActions'; +import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector'; import About from './About'; function createMapStateToProps() { return createSelector( (state) => state.system.status, - (status) => { + createUISettingsSelector(), + (status, uiSettings) => { return { - ...status.item + ...status.item, + timeFormat: uiSettings.timeFormat, + longDateFormat: uiSettings.longDateFormat }; } ); diff --git a/frontend/src/System/Status/About/StartTime.js b/frontend/src/System/Status/About/StartTime.js new file mode 100644 index 000000000..94b4322d5 --- /dev/null +++ b/frontend/src/System/Status/About/StartTime.js @@ -0,0 +1,93 @@ +import moment from 'moment'; +import PropTypes from 'prop-types'; +import React, { Component } from 'react'; +import formatDateTime from 'Utilities/Date/formatDateTime'; +import formatTimeSpan from 'Utilities/Date/formatTimeSpan'; + +function getUptime(startTime) { + return formatTimeSpan(moment().diff(startTime)); +} + +class StartTime extends Component { + + // + // Lifecycle + + constructor(props, context) { + super(props, context); + + const { + startTime, + timeFormat, + longDateFormat + } = props; + + this._timeoutId = null; + + this.state = { + uptime: getUptime(startTime), + startTime: formatDateTime(startTime, longDateFormat, timeFormat, { includeSeconds: true }) + }; + } + + componentDidMount() { + this._timeoutId = setTimeout(this.onTimeout, 1000); + } + + componentDidUpdate(prevProps) { + const { + startTime, + timeFormat, + longDateFormat + } = this.props; + + if ( + startTime !== prevProps.startTime || + timeFormat !== prevProps.timeFormat || + longDateFormat !== prevProps.longDateFormat + ) { + this.setState({ + uptime: getUptime(startTime), + startTime: formatDateTime(startTime, longDateFormat, timeFormat, { includeSeconds: true }) + }); + } + } + + componentWillUnmount() { + if (this._timeoutId) { + this._timeoutId = clearTimeout(this._timeoutId); + } + } + + // + // Listeners + + onTimeout = () => { + this.setState({ uptime: getUptime(this.props.startTime) }); + this._timeoutId = setTimeout(this.onTimeout, 1000); + } + + // + // Render + + render() { + const { + uptime, + startTime + } = this.state; + + return ( + + {uptime} + + ); + } +} + +StartTime.propTypes = { + startTime: PropTypes.string.isRequired, + timeFormat: PropTypes.string.isRequired, + longDateFormat: PropTypes.string.isRequired +}; + +export default StartTime; diff --git a/frontend/src/TrackFile/Editor/TrackFileEditorModalContent.js b/frontend/src/TrackFile/Editor/TrackFileEditorModalContent.js index a1365dc99..ac34d70fd 100644 --- a/frontend/src/TrackFile/Editor/TrackFileEditorModalContent.js +++ b/frontend/src/TrackFile/Editor/TrackFileEditorModalContent.js @@ -70,9 +70,15 @@ class TrackFileEditorModalContent extends Component { getSelectedIds = () => { const selectedIds = getSelectedIds(this.state.selectedState); - return _.uniq(_.map(selectedIds, (id) => { - return _.find(this.props.items, { id }).trackFileId; - })); + return selectedIds.reduce((acc, id) => { + const matchingItem = this.props.items.find((item) => item.id === id); + + if (matchingItem && !acc.includes(matchingItem.trackFileID)) { + acc.push(matchingItem.trackFileID); + } + + return acc; + }, []); } // diff --git a/frontend/src/TrackFile/Editor/TrackFileEditorRow.js b/frontend/src/TrackFile/Editor/TrackFileEditorRow.js index 19628a2f8..2bfbaea57 100644 --- a/frontend/src/TrackFile/Editor/TrackFileEditorRow.js +++ b/frontend/src/TrackFile/Editor/TrackFileEditorRow.js @@ -5,7 +5,7 @@ import Label from 'Components/Label'; import TableRow from 'Components/Table/TableRow'; import TableRowCell from 'Components/Table/Cells/TableRowCell'; import TableSelectCell from 'Components/Table/Cells/TableSelectCell'; -import EpisodeQuality from 'Album/EpisodeQuality'; +import TrackQuality from 'Album/TrackQuality'; import styles from './TrackFileEditorRow'; function TrackFileEditorRow(props) { @@ -42,7 +42,7 @@ function TrackFileEditorRow(props) { - diff --git a/frontend/src/Utilities/Filter/findSelectedFilters.js b/frontend/src/Utilities/Filter/findSelectedFilters.js index 4306f9439..1c104073c 100644 --- a/frontend/src/Utilities/Filter/findSelectedFilters.js +++ b/frontend/src/Utilities/Filter/findSelectedFilters.js @@ -3,7 +3,11 @@ export default function findSelectedFilters(selectedFilterKey, filters = [], cus return []; } - const selectedFilter = [...filters, ...customFilters].find((f) => f.key === selectedFilterKey); + let selectedFilter = filters.find((f) => f.key === selectedFilterKey); + + if (!selectedFilter) { + selectedFilter = customFilters.find((f) => f.id === selectedFilterKey); + } if (!selectedFilter) { // TODO: throw in dev diff --git a/frontend/src/Utilities/Number/convertToBytes.js b/frontend/src/Utilities/Number/convertToBytes.js new file mode 100644 index 000000000..88357944f --- /dev/null +++ b/frontend/src/Utilities/Number/convertToBytes.js @@ -0,0 +1,15 @@ +function convertToBytes(input, power, binaryPrefix) { + const size = Number(input); + + if (isNaN(size)) { + return ''; + } + + const prefix = binaryPrefix ? 1024 : 1000; + const multiplier = Math.pow(prefix, power); + const result = size * multiplier; + + return Math.round(result); +} + +export default convertToBytes; diff --git a/frontend/src/Utilities/State/getProviderState.js b/frontend/src/Utilities/State/getProviderState.js index cc18af711..039429c10 100644 --- a/frontend/src/Utilities/State/getProviderState.js +++ b/frontend/src/Utilities/State/getProviderState.js @@ -2,9 +2,12 @@ import _ from 'lodash'; import getSectionState from 'Utilities/State/getSectionState'; function getProviderState(payload, getState, section) { - const id = payload.id; + const { + id, + ...otherPayload + } = payload; const state = getSectionState(getState(), section, true); - const pendingChanges = Object.assign({}, state.pendingChanges); + const pendingChanges = Object.assign({}, state.pendingChanges, otherPayload); const pendingFields = state.pendingChanges.fields || {}; delete pendingChanges.fields; diff --git a/frontend/src/Utilities/customFilterHandlers.js b/frontend/src/Utilities/customFilterHandlers.js deleted file mode 100644 index 19be120ac..000000000 --- a/frontend/src/Utilities/customFilterHandlers.js +++ /dev/null @@ -1,6 +0,0 @@ -const customFilterHandlers = { - REMOVE: 'remove', - SAVE: 'save' -}; - -export default customFilterHandlers; diff --git a/frontend/src/Wanted/Missing/MissingRow.js b/frontend/src/Wanted/Missing/MissingRow.js index f54a5c82b..25a400545 100644 --- a/frontend/src/Wanted/Missing/MissingRow.js +++ b/frontend/src/Wanted/Missing/MissingRow.js @@ -26,6 +26,10 @@ function MissingRow(props) { onSelectedChange } = props; + if (!artist) { + return null; + } + return ( + { + private readonly ICustomFilterService _customFilterService; + + public CustomFilterModule(ICustomFilterService customFilterService) + { + _customFilterService = customFilterService; + + GetResourceById = GetCustomFilter; + GetResourceAll = GetCustomFilters; + CreateResource = AddCustomFilter; + UpdateResource = UpdateCustomFilter; + DeleteResource = DeleteCustomResource; + } + + private CustomFilterResource GetCustomFilter(int id) + { + return _customFilterService.Get(id).ToResource(); + } + + private List GetCustomFilters() + { + return _customFilterService.All().ToResource(); + } + + private int AddCustomFilter(CustomFilterResource resource) + { + var customFilter = _customFilterService.Add(resource.ToModel()); + + return customFilter.Id; + } + + private void UpdateCustomFilter(CustomFilterResource resource) + { + _customFilterService.Update(resource.ToModel()); + } + + private void DeleteCustomResource(int id) + { + _customFilterService.Delete(id); + } + } +} diff --git a/src/Lidarr.Api.V1/CustomFilters/CustomFilterResource.cs b/src/Lidarr.Api.V1/CustomFilters/CustomFilterResource.cs new file mode 100644 index 000000000..ee3cc9ea0 --- /dev/null +++ b/src/Lidarr.Api.V1/CustomFilters/CustomFilterResource.cs @@ -0,0 +1,49 @@ +using System.Collections.Generic; +using System.Linq; +using NzbDrone.Common.Serializer; +using NzbDrone.Core.CustomFilters; +using Lidarr.Http.REST; + +namespace Lidarr.Api.V1.CustomFilters +{ + public class CustomFilterResource : RestResource + { + public string Type { get; set; } + public string Label { get; set; } + public List Filters { get; set; } + } + + public static class CustomFilterResourceMapper + { + public static CustomFilterResource ToResource(this CustomFilter model) + { + if (model == null) return null; + + return new CustomFilterResource + { + Id = model.Id, + Type = model.Type, + Label = model.Label, + Filters = Json.Deserialize>(model.Filters) + }; + } + + public static CustomFilter ToModel(this CustomFilterResource resource) + { + if (resource == null) return null; + + return new CustomFilter + { + Id = resource.Id, + Type = resource.Type, + Label = resource.Label, + Filters = Json.ToJson(resource.Filters) + }; + } + + public static List ToResource(this IEnumerable filters) + { + return filters.Select(ToResource).ToList(); + } + } +} diff --git a/src/Lidarr.Api.V1/Lidarr.Api.V1.csproj b/src/Lidarr.Api.V1/Lidarr.Api.V1.csproj index f20eb8a8b..1fae358bb 100644 --- a/src/Lidarr.Api.V1/Lidarr.Api.V1.csproj +++ b/src/Lidarr.Api.V1/Lidarr.Api.V1.csproj @@ -97,6 +97,8 @@ + + diff --git a/src/NzbDrone.Common/NzbDrone.Common.csproj b/src/NzbDrone.Common/NzbDrone.Common.csproj index dd53eb5e1..08fd5c764 100644 --- a/src/NzbDrone.Common/NzbDrone.Common.csproj +++ b/src/NzbDrone.Common/NzbDrone.Common.csproj @@ -54,8 +54,8 @@ ..\packages\DotNet4.SocksProxy.1.3.4.0\lib\net40\Org.Mentalis.dll True - - ..\packages\SharpRaven.2.2.0\lib\net45\SharpRaven.dll + + ..\packages\SharpRaven.2.4.0\lib\net45\SharpRaven.dll ..\packages\DotNet4.SocksProxy.1.3.4.0\lib\net40\SocksWebProxy.dll @@ -66,6 +66,7 @@ + diff --git a/src/NzbDrone.Common/packages.config b/src/NzbDrone.Common/packages.config index 4f26eba16..d2e511546 100644 --- a/src/NzbDrone.Common/packages.config +++ b/src/NzbDrone.Common/packages.config @@ -4,5 +4,5 @@ - + \ No newline at end of file diff --git a/src/NzbDrone.Core/CustomFilters/CustomFilter.cs b/src/NzbDrone.Core/CustomFilters/CustomFilter.cs new file mode 100644 index 000000000..1c6e3e9b9 --- /dev/null +++ b/src/NzbDrone.Core/CustomFilters/CustomFilter.cs @@ -0,0 +1,11 @@ +using NzbDrone.Core.Datastore; + +namespace NzbDrone.Core.CustomFilters +{ + public class CustomFilter : ModelBase + { + public string Type { get; set; } + public string Label { get; set; } + public string Filters { get; set; } + } +} diff --git a/src/NzbDrone.Core/CustomFilters/CustomFilterRepository.cs b/src/NzbDrone.Core/CustomFilters/CustomFilterRepository.cs new file mode 100644 index 000000000..9bdb8fd07 --- /dev/null +++ b/src/NzbDrone.Core/CustomFilters/CustomFilterRepository.cs @@ -0,0 +1,17 @@ +using NzbDrone.Core.Datastore; +using NzbDrone.Core.Messaging.Events; + +namespace NzbDrone.Core.CustomFilters +{ + public interface ICustomFilterRepository : IBasicRepository + { + } + + public class CustomFilterRepository : BasicRepository, ICustomFilterRepository + { + public CustomFilterRepository(IMainDatabase database, IEventAggregator eventAggregator) + : base(database, eventAggregator) + { + } + } +} diff --git a/src/NzbDrone.Core/CustomFilters/CustomFilterService.cs b/src/NzbDrone.Core/CustomFilters/CustomFilterService.cs new file mode 100644 index 000000000..9ef98f8be --- /dev/null +++ b/src/NzbDrone.Core/CustomFilters/CustomFilterService.cs @@ -0,0 +1,49 @@ +using System.Collections.Generic; +using System.Linq; + +namespace NzbDrone.Core.CustomFilters +{ + public interface ICustomFilterService + { + CustomFilter Add(CustomFilter customFilter); + List All(); + void Delete(int id); + CustomFilter Get(int id); + CustomFilter Update(CustomFilter customFilter); + } + + public class CustomFilterService : ICustomFilterService + { + private readonly ICustomFilterRepository _repo; + + public CustomFilterService(ICustomFilterRepository repo) + { + _repo = repo; + } + + public CustomFilter Add(CustomFilter customFilter) + { + return _repo.Insert(customFilter); + } + + public CustomFilter Update(CustomFilter customFilter) + { + return _repo.Update(customFilter); + } + + public void Delete(int id) + { + _repo.Delete(id); + } + + public CustomFilter Get(int id) + { + return _repo.Get(id); + } + + public List All() + { + return _repo.All().ToList(); + } + } +} diff --git a/src/NzbDrone.Core/Datastore/Migration/021_add_custom_filters.cs b/src/NzbDrone.Core/Datastore/Migration/021_add_custom_filters.cs new file mode 100644 index 000000000..15b9004eb --- /dev/null +++ b/src/NzbDrone.Core/Datastore/Migration/021_add_custom_filters.cs @@ -0,0 +1,17 @@ +using FluentMigrator; +using NzbDrone.Core.Datastore.Migration.Framework; + +namespace NzbDrone.Core.Datastore.Migration +{ + [Migration(021)] + public class add_custom_filters : NzbDroneMigrationBase + { + protected override void MainDbUpgrade() + { + Create.TableForModel("CustomFilters") + .WithColumn("Type").AsString().NotNullable() + .WithColumn("Label").AsString().NotNullable() + .WithColumn("Filters").AsString().NotNullable(); + } + } +} diff --git a/src/NzbDrone.Core/Datastore/TableMapping.cs b/src/NzbDrone.Core/Datastore/TableMapping.cs index 45b734219..d10f04896 100644 --- a/src/NzbDrone.Core/Datastore/TableMapping.cs +++ b/src/NzbDrone.Core/Datastore/TableMapping.cs @@ -31,6 +31,7 @@ using NzbDrone.Core.ThingiProvider; using NzbDrone.Common.Disk; using NzbDrone.Common.Serializer; using NzbDrone.Core.Authentication; +using NzbDrone.Core.CustomFilters; using NzbDrone.Core.Extras.Metadata; using NzbDrone.Core.Extras.Metadata.Files; using NzbDrone.Core.Extras.Others; @@ -141,6 +142,8 @@ namespace NzbDrone.Core.Datastore Mapper.Entity().RegisterModel("IndexerStatus"); Mapper.Entity().RegisterModel("DownloadClientStatus"); Mapper.Entity().RegisterModel("ImportListStatus"); + + Mapper.Entity().RegisterModel("CustomFilters"); } private static void RegisterMappers() diff --git a/src/NzbDrone.Core/Notifications/Plex/PlexTv/PlexTvPinUrlResponse.cs b/src/NzbDrone.Core/Notifications/Plex/PlexTv/PlexTvPinUrlResponse.cs new file mode 100644 index 000000000..4dace5645 --- /dev/null +++ b/src/NzbDrone.Core/Notifications/Plex/PlexTv/PlexTvPinUrlResponse.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; + +namespace NzbDrone.Core.Notifications.Plex.PlexTv +{ + public class PlexTvPinUrlResponse + { + public string Url { get; set; } + public string Method => "POST"; + public Dictionary Headers { get; set; } + } +} diff --git a/src/NzbDrone.Core/Notifications/Plex/PlexTv/PlexTvProxy.cs b/src/NzbDrone.Core/Notifications/Plex/PlexTv/PlexTvProxy.cs index 2179bf058..f9913613e 100644 --- a/src/NzbDrone.Core/Notifications/Plex/PlexTv/PlexTvProxy.cs +++ b/src/NzbDrone.Core/Notifications/Plex/PlexTv/PlexTvProxy.cs @@ -4,46 +4,39 @@ using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.Http; using NzbDrone.Common.Serializer; using NzbDrone.Core.Exceptions; + namespace NzbDrone.Core.Notifications.Plex.PlexTv { public interface IPlexTvProxy { - PlexTvPinResponse GetPinCode(string clientIdentifier); string GetAuthToken(string clientIdentifier, int pinId); } public class PlexTvProxy : IPlexTvProxy { private readonly IHttpClient _httpClient; private readonly Logger _logger; + public PlexTvProxy(IHttpClient httpClient, Logger logger) { _httpClient = httpClient; _logger = logger; } - public PlexTvPinResponse GetPinCode(string clientIdentifier) - { - var request = BuildRequest(clientIdentifier); - request.Method = HttpMethod.POST; - request.ResourceUrl = "/api/v2/pins"; - request.AddQueryParam("strong", true); - PlexTvPinResponse response; - if (!Json.TryDeserialize(ProcessRequest(request), out response)) - { - response = new PlexTvPinResponse(); - } - return response; - } + public string GetAuthToken(string clientIdentifier, int pinId) { var request = BuildRequest(clientIdentifier); request.ResourceUrl = $"/api/v2/pins/{pinId}"; + PlexTvPinResponse response; + if (!Json.TryDeserialize(ProcessRequest(request), out response)) { response = new PlexTvPinResponse(); } + return response.AuthToken; } + private HttpRequestBuilder BuildRequest(string clientIdentifier) { var requestBuilder = new HttpRequestBuilder("https://plex.tv") @@ -54,13 +47,18 @@ namespace NzbDrone.Core.Notifications.Plex.PlexTv .AddQueryParam("X-Plex-Platform-Version", "7") .AddQueryParam("X-Plex-Device-Name", "Lidarr") .AddQueryParam("X-Plex-Version", BuildInfo.Version.ToString()); + return requestBuilder; } + private string ProcessRequest(HttpRequestBuilder requestBuilder) { var httpRequest = requestBuilder.Build(); + HttpResponse response; + _logger.Debug("Url: {0}", httpRequest.Url); + try { response = _httpClient.Execute(httpRequest); @@ -73,6 +71,7 @@ namespace NzbDrone.Core.Notifications.Plex.PlexTv { throw new NzbDroneClientException(HttpStatusCode.BadRequest, "Unable to connect to plex.tv"); } + return response.Content; } } diff --git a/src/NzbDrone.Core/Notifications/Plex/PlexTv/PlexTvService.cs b/src/NzbDrone.Core/Notifications/Plex/PlexTv/PlexTvService.cs index 80f7b629c..51b9aece6 100644 --- a/src/NzbDrone.Core/Notifications/Plex/PlexTv/PlexTvService.cs +++ b/src/NzbDrone.Core/Notifications/Plex/PlexTv/PlexTvService.cs @@ -1,39 +1,76 @@ +using System.Linq; using System.Text; using NzbDrone.Common.EnvironmentInfo; +using NzbDrone.Common.Http; using NzbDrone.Core.Configuration; + namespace NzbDrone.Core.Notifications.Plex.PlexTv { public interface IPlexTvService { - PlexTvSignInUrlResponse GetSignInUrl(string callbackUrl); + PlexTvPinUrlResponse GetPinUrl(); + PlexTvSignInUrlResponse GetSignInUrl(string callbackUrl, int pinId, string pinCode); string GetAuthToken(int pinId); } + public class PlexTvService : IPlexTvService { private readonly IPlexTvProxy _proxy; private readonly IConfigService _configService; + public PlexTvService(IPlexTvProxy proxy, IConfigService configService) { _proxy = proxy; _configService = configService; } - public PlexTvSignInUrlResponse GetSignInUrl(string callbackUrl) + + public PlexTvPinUrlResponse GetPinUrl() { var clientIdentifier = _configService.PlexClientIdentifier; - var pin = _proxy.GetPinCode(clientIdentifier); - var url = new StringBuilder(); - url.Append("https://app.plex.tv/auth/#!"); - url.Append($"?clientID={clientIdentifier}"); - url.Append($"&forwardUrl={callbackUrl}"); - url.Append($"&code={pin.Code}"); - url.Append($"&context[device][version]=${BuildInfo.Version.ToString()}"); - url.Append("&context[device][product]=Lidarr"); - url.Append("&context[device][platform]=Windows"); - url.Append("&context[device][platformVersion]=7"); + + var requestBuilder = new HttpRequestBuilder("https://plex.tv/api/v2/pins") + .Accept(HttpAccept.Json) + .AddQueryParam("X-Plex-Client-Identifier", clientIdentifier) + .AddQueryParam("X-Plex-Product", "Lidarr") + .AddQueryParam("X-Plex-Platform", "Windows") + .AddQueryParam("X-Plex-Platform-Version", "7") + .AddQueryParam("X-Plex-Device-Name", "Lidarr") + .AddQueryParam("X-Plex-Version", BuildInfo.Version.ToString()) + .AddQueryParam("strong", true); + + requestBuilder.Method = HttpMethod.POST; + + var request = requestBuilder.Build(); + + return new PlexTvPinUrlResponse + { + Url = request.Url.ToString(), + Headers = request.Headers.ToDictionary(h => h.Key, h => h.Value) + }; + } + + public PlexTvSignInUrlResponse GetSignInUrl(string callbackUrl, int pinId, string pinCode) + { + var clientIdentifier = _configService.PlexClientIdentifier; + + var requestBuilder = new HttpRequestBuilder("https://app.plex.tv/auth/hashBang") + .AddQueryParam("clientID", clientIdentifier) + .AddQueryParam("forwardUrl", callbackUrl) + .AddQueryParam("code", pinCode) + .AddQueryParam("context[device][product]", "Lidarr") + .AddQueryParam("context[device][platform]", "Windows") + .AddQueryParam("context[device][platformVersion]", "7") + .AddQueryParam("context[device][version", BuildInfo.Version.ToString()); + + // #! is stripped out of the URL when building, this works around it. + requestBuilder.Segments.Add("hashBang", "#!"); + + var request = requestBuilder.Build(); + return new PlexTvSignInUrlResponse { - OauthUrl = url.ToString(), - PinId = pin.Id + OauthUrl = request.Url.ToString(), + PinId = pinId }; } public string GetAuthToken(int pinId) diff --git a/src/NzbDrone.Core/Notifications/Plex/Server/PlexServer.cs b/src/NzbDrone.Core/Notifications/Plex/Server/PlexServer.cs index e3e9ff5ac..3be451a7d 100644 --- a/src/NzbDrone.Core/Notifications/Plex/Server/PlexServer.cs +++ b/src/NzbDrone.Core/Notifications/Plex/Server/PlexServer.cs @@ -6,31 +6,38 @@ using NzbDrone.Core.Exceptions; using NzbDrone.Core.Notifications.Plex.PlexTv; using NzbDrone.Core.Music; using NzbDrone.Core.Validation; + namespace NzbDrone.Core.Notifications.Plex.Server { public class PlexServer : NotificationBase { private readonly IPlexServerService _plexServerService; private readonly IPlexTvService _plexTvService; + public PlexServer(IPlexServerService plexServerService, IPlexTvService plexTvService) { _plexServerService = plexServerService; _plexTvService = plexTvService; } + public override string Link => "https://www.plex.tv/"; public override string Name => "Plex Media Server"; + public override void OnDownload(TrackDownloadMessage message) { UpdateIfEnabled(message.Artist); } + public override void OnAlbumDownload(AlbumDownloadMessage message) { UpdateIfEnabled(message.Artist); } + public override void OnRename(Artist artist) { UpdateIfEnabled(artist); } + private void UpdateIfEnabled(Artist artist) { if (Settings.UpdateLibrary) @@ -38,36 +45,62 @@ namespace NzbDrone.Core.Notifications.Plex.Server _plexServerService.UpdateLibrary(artist, Settings); } } + public override ValidationResult Test() { var failures = new List(); + failures.AddIfNotNull(_plexServerService.Test(Settings)); + return new ValidationResult(failures); } + public override object RequestAction(string action, IDictionary query) { if (action == "startOAuth") { Settings.Validate().Filter("ConsumerKey", "ConsumerSecret").ThrowOnError(); + + return _plexTvService.GetPinUrl(); + } + else if (action == "continueOAuth") + { + Settings.Validate().Filter("ConsumerKey", "ConsumerSecret").ThrowOnError(); + if (query["callbackUrl"].IsNullOrWhiteSpace()) { throw new BadRequestException("QueryParam callbackUrl invalid."); } - return _plexTvService.GetSignInUrl(query["callbackUrl"]); + + if (query["id"].IsNullOrWhiteSpace()) + { + throw new BadRequestException("QueryParam id invalid."); + } + + if (query["code"].IsNullOrWhiteSpace()) + { + throw new BadRequestException("QueryParam code invalid."); + } + + return _plexTvService.GetSignInUrl(query["callbackUrl"], Convert.ToInt32(query["id"]), query["code"]); } else if (action == "getOAuthToken") { Settings.Validate().Filter("ConsumerKey", "ConsumerSecret").ThrowOnError(); + if (query["pinId"].IsNullOrWhiteSpace()) { throw new BadRequestException("QueryParam pinId invalid."); } + var authToken = _plexTvService.GetAuthToken(Convert.ToInt32(query["pinId"])); + return new { authToken }; } + return new { }; } } diff --git a/src/NzbDrone.Core/Notifications/Plex/Server/PlexServerProxy.cs b/src/NzbDrone.Core/Notifications/Plex/Server/PlexServerProxy.cs index 8e604c9bd..b82ba57a6 100644 --- a/src/NzbDrone.Core/Notifications/Plex/Server/PlexServerProxy.cs +++ b/src/NzbDrone.Core/Notifications/Plex/Server/PlexServerProxy.cs @@ -7,6 +7,7 @@ using NzbDrone.Common.Extensions; using NzbDrone.Common.Http; using NzbDrone.Common.Serializer; using NzbDrone.Core.Configuration; + namespace NzbDrone.Core.Notifications.Plex.Server { public interface IPlexServerProxy @@ -18,22 +19,27 @@ namespace NzbDrone.Core.Notifications.Plex.Server List Preferences(PlexServerSettings settings); int? GetMetadataId(int sectionId, string mbId, string language, PlexServerSettings settings); } + public class PlexServerProxy : IPlexServerProxy { private readonly IHttpClient _httpClient; private readonly IConfigService _configService; private readonly Logger _logger; + public PlexServerProxy(IHttpClient httpClient, IConfigService configService, Logger logger) { _httpClient = httpClient; _configService = configService; _logger = logger; } + public List GetArtistSections(PlexServerSettings settings) { var request = BuildRequest("library/sections", HttpMethod.GET, settings); var response = ProcessRequest(request); + CheckForError(response); + if (response.Contains("_children")) { return Json.Deserialize(response) @@ -48,36 +54,45 @@ namespace NzbDrone.Core.Notifications.Plex.Server }) .ToList(); } + return Json.Deserialize>(response) .MediaContainer .Sections .Where(d => d.Type == "artist") .ToList(); } + public void Update(int sectionId, PlexServerSettings settings) { var resource = $"library/sections/{sectionId}/refresh"; var request = BuildRequest(resource, HttpMethod.GET, settings); var response = ProcessRequest(request); + CheckForError(response); } + public void UpdateArtist(int metadataId, PlexServerSettings settings) { var resource = $"library/metadata/{metadataId}/refresh"; var request = BuildRequest(resource, HttpMethod.PUT, settings); var response = ProcessRequest(request); + CheckForError(response); } + public string Version(PlexServerSettings settings) { var request = BuildRequest("identity", HttpMethod.GET, settings); var response = ProcessRequest(request); + CheckForError(response); + if (response.Contains("_children")) { return Json.Deserialize(response) .Version; } + return Json.Deserialize>(response) .MediaContainer .Version; @@ -86,24 +101,31 @@ namespace NzbDrone.Core.Notifications.Plex.Server { var request = BuildRequest(":/prefs", HttpMethod.GET, settings); var response = ProcessRequest(request); + CheckForError(response); + if (response.Contains("_children")) { return Json.Deserialize(response) .Preferences; } + return Json.Deserialize>(response) .MediaContainer .Preferences; } + public int? GetMetadataId(int sectionId, string mbId, string language, PlexServerSettings settings) { var guid = string.Format("com.plexapp.agents.lastfm://{0}?lang={1}", mbId, language); // TODO Plex Route for MB? LastFM? var resource = $"library/sections/{sectionId}/all?guid={System.Web.HttpUtility.UrlEncode(guid)}"; var request = BuildRequest(resource, HttpMethod.GET, settings); var response = ProcessRequest(request); + CheckForError(response); + List items; + if (response.Contains("_children")) { items = Json.Deserialize(response) @@ -115,12 +137,15 @@ namespace NzbDrone.Core.Notifications.Plex.Server .MediaContainer .Items; } + if (items == null || items.Empty()) { return null; } + return items.First().Id; } + private HttpRequestBuilder BuildRequest(string resource, HttpMethod method, PlexServerSettings settings) { var scheme = settings.UseSsl ? "https" : "http"; @@ -132,19 +157,26 @@ namespace NzbDrone.Core.Notifications.Plex.Server .AddQueryParam("X-Plex-Platform-Version", "7") .AddQueryParam("X-Plex-Device-Name", "Lidarr") .AddQueryParam("X-Plex-Version", BuildInfo.Version.ToString()); + if (settings.AuthToken.IsNotNullOrWhiteSpace()) { requestBuilder.AddQueryParam("X-Plex-Token", settings.AuthToken); } + requestBuilder.ResourceUrl = resource; requestBuilder.Method = method; + return requestBuilder; } + private string ProcessRequest(HttpRequestBuilder requestBuilder) { var httpRequest = requestBuilder.Build(); + HttpResponse response; + _logger.Debug("Url: {0}", httpRequest.Url); + try { response = _httpClient.Execute(httpRequest); @@ -161,23 +193,29 @@ namespace NzbDrone.Core.Notifications.Plex.Server { throw new PlexException("Unable to connect to Plex Media Server"); } + return response.Content; } + private void CheckForError(string response) { _logger.Trace("Checking for error"); + if (response.IsNullOrWhiteSpace()) { _logger.Trace("No response body returned, no error detected"); return; } + var error = response.Contains("_children") ? Json.Deserialize(response) : Json.Deserialize>(response).MediaContainer; + if (error != null && !error.Error.IsNullOrWhiteSpace()) { throw new PlexException(error.Error); } + _logger.Trace("No error detected"); } } diff --git a/src/NzbDrone.Core/NzbDrone.Core.csproj b/src/NzbDrone.Core/NzbDrone.Core.csproj index 1fa799b4b..3d951e32f 100644 --- a/src/NzbDrone.Core/NzbDrone.Core.csproj +++ b/src/NzbDrone.Core/NzbDrone.Core.csproj @@ -143,6 +143,9 @@ + + + @@ -192,6 +195,7 @@ + @@ -891,6 +895,7 @@ + diff --git a/yarn.lock b/yarn.lock index 22ebe8566..4acdd8c73 100644 --- a/yarn.lock +++ b/yarn.lock @@ -168,6 +168,52 @@ version "1.1.0" resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-1.1.0.tgz#50c1e2260ac0ed9439a181de3725a0168d59c48a" +"@sentry/browser@4.0.4": + version "4.0.4" + resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-4.0.4.tgz#6dfe05099d9ce23d7e9becaa905af5406520d448" + dependencies: + "@sentry/core" "4.0.3" + "@sentry/types" "4.0.1" + "@sentry/utils" "4.0.1" + md5 "2.2.1" + +"@sentry/core@4.0.3": + version "4.0.3" + resolved "https://registry.yarnpkg.com/@sentry/core/-/core-4.0.3.tgz#4f8fd67888f1cf0f1a984c5fa362122b60e8bd08" + dependencies: + "@sentry/hub" "4.0.1" + "@sentry/minimal" "4.0.1" + "@sentry/types" "4.0.1" + "@sentry/utils" "4.0.1" + +"@sentry/hub@4.0.1": + version "4.0.1" + resolved "https://registry.yarnpkg.com/@sentry/hub/-/hub-4.0.1.tgz#01870cede195029ae32d763199ff6c3e4edf99d1" + dependencies: + "@sentry/types" "4.0.0" + "@sentry/utils" "4.0.1" + +"@sentry/minimal@4.0.1": + version "4.0.1" + resolved "https://registry.yarnpkg.com/@sentry/minimal/-/minimal-4.0.1.tgz#c51a2af81eba48977fb54ab187e0c0eb0ad12c15" + dependencies: + "@sentry/hub" "4.0.1" + "@sentry/types" "4.0.0" + +"@sentry/types@4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@sentry/types/-/types-4.0.0.tgz#9dd46a7b05004871fe0cea0b0423098d9d91a173" + +"@sentry/types@4.0.1": + version "4.0.1" + resolved "https://registry.yarnpkg.com/@sentry/types/-/types-4.0.1.tgz#f9342e905ce2aee71975574589d915b6fb691fb0" + +"@sentry/utils@4.0.1": + version "4.0.1" + resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-4.0.1.tgz#5690058fb030c23d46ea056aa3e8ebebb8105d45" + dependencies: + "@sentry/types" "4.0.0" + abbrev@1: version "1.1.0" resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.0.tgz#d0554c2256636e2f56e7c2e5ad183f859428d81f" @@ -1642,6 +1688,10 @@ chardet@^0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" +charenc@~0.0.1: + version "0.0.2" + resolved "https://registry.yarnpkg.com/charenc/-/charenc-0.0.2.tgz#c0a1d2f3a7092e03774bfa83f14c0fc5790a8667" + chokidar@^1.7.0: version "1.7.0" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-1.7.0.tgz#798e689778151c8076b4b360e5edd28cda2bb468" @@ -2032,6 +2082,10 @@ cross-spawn@^6.0.5: shebang-command "^1.2.0" which "^1.2.9" +crypt@~0.0.1: + version "0.0.2" + resolved "https://registry.yarnpkg.com/crypt/-/crypt-0.0.2.tgz#88d7ff7ec0dfb86f713dc87bbb42d044d3e6c41b" + cryptiles@3.x.x: version "3.1.2" resolved "https://registry.yarnpkg.com/cryptiles/-/cryptiles-3.1.2.tgz#a89fbb220f5ce25ec56e8c4aa8a4fd7b5b0d29fe" @@ -4067,7 +4121,7 @@ is-binary-path@^1.0.0: dependencies: binary-extensions "^1.0.0" -is-buffer@^1.1.4: +is-buffer@^1.1.4, is-buffer@~1.1.1: version "1.1.6" resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" @@ -4861,6 +4915,14 @@ md5.js@^1.3.4: hash-base "^3.0.0" inherits "^2.0.1" +md5@2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/md5/-/md5-2.2.1.tgz#53ab38d5fe3c8891ba465329ea23fac0540126f9" + dependencies: + charenc "~0.0.1" + crypt "~0.0.1" + is-buffer "~1.1.1" + mdast-util-compact@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/mdast-util-compact/-/mdast-util-compact-1.0.1.tgz#cdb5f84e2b6a2d3114df33bd05d9cb32e3c4083a" @@ -6292,14 +6354,6 @@ randombytes@^2.0.0, randombytes@^2.0.1: dependencies: safe-buffer "^5.1.0" -raven-for-redux@1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/raven-for-redux/-/raven-for-redux-1.3.1.tgz#865f0056ec1706073c1b3a33164640453ed4fed2" - -raven-js@3.27.0: - version "3.27.0" - resolved "https://registry.yarnpkg.com/raven-js/-/raven-js-3.27.0.tgz#9f47c03e17933ce756e189f3669d49c441c1ba6e" - raw-body@~1.1.0: version "1.1.7" resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-1.1.7.tgz#1d027c2bfa116acc6623bca8f00016572a87d425" @@ -6330,9 +6384,9 @@ react-async-script@1.0.0, react-async-script@^1.0.0: hoist-non-react-statics "^3.0.1" prop-types "^15.5.0" -react-autosuggest@9.4.1: - version "9.4.1" - resolved "https://registry.yarnpkg.com/react-autosuggest/-/react-autosuggest-9.4.1.tgz#fe636b196eaffaf1d29283c2fc55f8a93cf3666d" +react-autosuggest@9.4.2: + version "9.4.2" + resolved "https://registry.yarnpkg.com/react-autosuggest/-/react-autosuggest-9.4.2.tgz#18cc0bebeebda3d24328e3da301f061a444ae223" dependencies: prop-types "^15.5.10" react-autowhatever "^10.1.2" @@ -6381,14 +6435,14 @@ react-document-title@2.0.3: prop-types "^15.5.6" react-side-effect "^1.0.2" -react-dom@16.5.1: - version "16.5.1" - resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.5.1.tgz#29d0c5a01ed3b6b4c14309aa91af6ec4eb4f292c" +react-dom@16.5.2: + version "16.5.2" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.5.2.tgz#b69ee47aa20bab5327b2b9d7c1fe2a30f2cfa9d7" dependencies: loose-envify "^1.1.0" object-assign "^4.1.1" prop-types "^15.6.2" - schedule "^0.4.0" + schedule "^0.5.0" react-google-recaptcha@1.0.2: version "1.0.2" @@ -6519,14 +6573,14 @@ react-virtualized@9.20.1: prop-types "^15.6.0" react-lifecycles-compat "^3.0.4" -react@16.5.1: - version "16.5.1" - resolved "https://registry.yarnpkg.com/react/-/react-16.5.1.tgz#8cb8e9f8cdcb4bde41c9a138bfbf907e66132372" +react@16.5.2: + version "16.5.2" + resolved "https://registry.yarnpkg.com/react/-/react-16.5.2.tgz#19f6b444ed139baa45609eee6dc3d318b3895d42" dependencies: loose-envify "^1.1.0" object-assign "^4.1.1" prop-types "^15.6.2" - schedule "^0.4.0" + schedule "^0.5.0" read-pkg-up@^2.0.0: version "2.0.0" @@ -7048,9 +7102,9 @@ sax@~1.2.1: version "1.2.4" resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" -schedule@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/schedule/-/schedule-0.4.0.tgz#fa20cfd0bfbf91c47d02272fd7096780d3170bbb" +schedule@^0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/schedule/-/schedule-0.5.0.tgz#c128fffa0b402488b08b55ae74bb9df55cc29cc8" dependencies: object-assign "^4.1.1"