From 6802bfc736310726e994ea7de41513f21ee9f835 Mon Sep 17 00:00:00 2001 From: Qstick Date: Tue, 9 Jun 2020 01:51:23 -0400 Subject: [PATCH] Fixed: List UI Revamp --- .../AddListMovie/AddListMovieConnector.js | 113 ---- .../Overview/AddListMovieOverviews.css | 3 - .../AddListMovie/Table/AddListMovieRow.css | 56 -- .../AddListMovie/Table/MovieStatusCell.js | 62 -- .../AddNewMovie/AddNewMovieSearchResult.css | 2 +- frontend/src/App/AppRoutes.js | 10 +- .../Components/Page/Sidebar/PageSidebar.js | 4 - .../AddListMovieFilterModalConnector.js | 8 +- .../AddListMovieItemConnector.js | 17 +- .../DiscoverMovie/AddNewDiscoverMovieModal.js | 31 + ...ddNewDiscoverMovieModalContentConnector.js | 105 ++++ .../DiscoverMovie.js} | 129 +++- .../DiscoverMovieConnector.js} | 50 +- .../src/DiscoverMovie/DiscoverMovieFooter.css | 56 ++ .../src/DiscoverMovie/DiscoverMovieFooter.js | 252 ++++++++ .../DiscoverMovieFooterConnector.js | 72 +++ .../DiscoverMovieFooterLabel.css | 8 + .../DiscoverMovie/DiscoverMovieFooterLabel.js | 40 ++ .../Exclusion/ExcludeMovieModal.js | 33 + .../Exclusion/ExcludeMovieModalContent.css | 12 + .../Exclusion/ExcludeMovieModalContent.js | 69 +++ .../ExcludeMovieModalContentConnector.js | 47 ++ .../Menus/AddListMovieFilterMenu.js | 2 +- .../Menus/AddListMovieSortMenu.js | 18 + .../Menus/AddListMovieViewMenu.js | 0 .../src/DiscoverMovie/NoDiscoverMovie.css | 11 + frontend/src/DiscoverMovie/NoDiscoverMovie.js | 60 ++ .../Overview/AddListMovieOverview.css | 32 +- .../Overview/AddListMovieOverview.js | 95 ++- .../Overview/AddListMovieOverviewConnector.js | 8 +- .../Overview/AddListMovieOverviews.css | 11 + .../Overview/AddListMovieOverviews.js | 22 +- .../AddListMovieOverviewsConnector.js | 2 +- .../AddListMovieOverviewOptionsModal.js | 0 ...AddListMovieOverviewOptionsModalContent.js | 0 ...vieOverviewOptionsModalContentConnector.js | 8 +- .../Posters/AddListMoviePoster.css | 14 +- .../Posters/AddListMoviePoster.js | 88 ++- .../Posters/AddListMoviePosterConnector.js | 8 +- .../Posters/AddListMoviePosterInfo.css | 0 .../Posters/AddListMoviePosterInfo.js | 0 .../Posters/AddListMoviePosters.css | 0 .../Posters/AddListMoviePosters.js | 54 +- .../Posters/AddListMoviePostersConnector.js | 2 +- .../Options/AddListMoviePosterOptionsModal.js | 0 .../AddListMoviePosterOptionsModalContent.js | 0 ...MoviePosterOptionsModalContentConnector.js | 8 +- .../Table/AddListMovieActionsCell.js | 0 .../Table/AddListMovieHeader.css | 0 .../Table/AddListMovieHeader.js | 16 +- .../Table/AddListMovieHeaderConnector.js | 2 +- .../DiscoverMovie/Table/AddListMovieRow.css | 65 ++ .../Table/AddListMovieRow.js | 76 ++- .../Table/AddListMovieRowConnector.js | 8 +- .../Table/AddListMovieTable.css | 0 .../Table/AddListMovieTable.js | 30 +- .../Table/AddListMovieTableConnector.js | 4 +- .../Table/ListMovieStatusCell.css} | 4 + .../Table/ListMovieStatusCell.js | 56 ++ .../Movie/Index/Posters/MovieIndexPoster.css | 1 + .../Movie/Index/Posters/MovieIndexPosters.css | 4 - .../Movie/Index/Posters/MovieIndexPosters.js | 12 +- ...NetImportExclusionModalContentConnector.js | 5 +- .../Actions/Settings/netImportExclusions.js | 2 + frontend/src/Store/Actions/addMovieActions.js | 288 +-------- .../src/Store/Actions/discoverMovieActions.js | 584 ++++++++++++++++++ frontend/src/Store/Actions/index.js | 2 + ...MovieClientSideCollectionItemsSelector.js} | 4 +- ...ctor.js => createDiscoverMovieSelector.js} | 6 +- frontend/src/Store/scrollPositions.js | 2 +- frontend/src/Styles/Variables/dimensions.js | 4 +- .../src/Utilities/Table/toggleSelected.js | 4 +- .../ImportExclusionsService.cs | 8 + .../NetImport/ImportExclusionsModule.cs | 10 +- .../NetImport/ImportExclusionsResource.cs | 14 +- 75 files changed, 2095 insertions(+), 738 deletions(-) delete mode 100644 frontend/src/AddMovie/AddListMovie/AddListMovieConnector.js delete mode 100644 frontend/src/AddMovie/AddListMovie/Overview/AddListMovieOverviews.css delete mode 100644 frontend/src/AddMovie/AddListMovie/Table/AddListMovieRow.css delete mode 100644 frontend/src/AddMovie/AddListMovie/Table/MovieStatusCell.js rename frontend/src/{AddMovie/AddListMovie => DiscoverMovie}/AddListMovieFilterModalConnector.js (69%) rename frontend/src/{AddMovie/AddListMovie => DiscoverMovie}/AddListMovieItemConnector.js (69%) create mode 100644 frontend/src/DiscoverMovie/AddNewDiscoverMovieModal.js create mode 100644 frontend/src/DiscoverMovie/AddNewDiscoverMovieModalContentConnector.js rename frontend/src/{AddMovie/AddListMovie/AddListMovie.js => DiscoverMovie/DiscoverMovie.js} (71%) rename frontend/src/{AddMovie/AddListMovie/AddDiscoverMovieConnector.js => DiscoverMovie/DiscoverMovieConnector.js} (57%) create mode 100644 frontend/src/DiscoverMovie/DiscoverMovieFooter.css create mode 100644 frontend/src/DiscoverMovie/DiscoverMovieFooter.js create mode 100644 frontend/src/DiscoverMovie/DiscoverMovieFooterConnector.js create mode 100644 frontend/src/DiscoverMovie/DiscoverMovieFooterLabel.css create mode 100644 frontend/src/DiscoverMovie/DiscoverMovieFooterLabel.js create mode 100644 frontend/src/DiscoverMovie/Exclusion/ExcludeMovieModal.js create mode 100644 frontend/src/DiscoverMovie/Exclusion/ExcludeMovieModalContent.css create mode 100644 frontend/src/DiscoverMovie/Exclusion/ExcludeMovieModalContent.js create mode 100644 frontend/src/DiscoverMovie/Exclusion/ExcludeMovieModalContentConnector.js rename frontend/src/{AddMovie/AddListMovie => DiscoverMovie}/Menus/AddListMovieFilterMenu.js (91%) rename frontend/src/{AddMovie/AddListMovie => DiscoverMovie}/Menus/AddListMovieSortMenu.js (81%) rename frontend/src/{AddMovie/AddListMovie => DiscoverMovie}/Menus/AddListMovieViewMenu.js (100%) create mode 100644 frontend/src/DiscoverMovie/NoDiscoverMovie.css create mode 100644 frontend/src/DiscoverMovie/NoDiscoverMovie.js rename frontend/src/{AddMovie/AddListMovie => DiscoverMovie}/Overview/AddListMovieOverview.css (74%) rename frontend/src/{AddMovie/AddListMovie => DiscoverMovie}/Overview/AddListMovieOverview.js (58%) rename frontend/src/{AddMovie/AddListMovie => DiscoverMovie}/Overview/AddListMovieOverviewConnector.js (56%) create mode 100644 frontend/src/DiscoverMovie/Overview/AddListMovieOverviews.css rename frontend/src/{AddMovie/AddListMovie => DiscoverMovie}/Overview/AddListMovieOverviews.js (91%) rename frontend/src/{AddMovie/AddListMovie => DiscoverMovie}/Overview/AddListMovieOverviewsConnector.js (94%) rename frontend/src/{AddMovie/AddListMovie => DiscoverMovie}/Overview/Options/AddListMovieOverviewOptionsModal.js (100%) rename frontend/src/{AddMovie/AddListMovie => DiscoverMovie}/Overview/Options/AddListMovieOverviewOptionsModalContent.js (100%) rename frontend/src/{AddMovie/AddListMovie => DiscoverMovie}/Overview/Options/AddListMovieOverviewOptionsModalContentConnector.js (74%) rename frontend/src/{AddMovie/AddListMovie => DiscoverMovie}/Posters/AddListMoviePoster.css (93%) rename frontend/src/{AddMovie/AddListMovie => DiscoverMovie}/Posters/AddListMoviePoster.js (58%) rename frontend/src/{AddMovie/AddListMovie => DiscoverMovie}/Posters/AddListMoviePosterConnector.js (56%) rename frontend/src/{AddMovie/AddListMovie => DiscoverMovie}/Posters/AddListMoviePosterInfo.css (100%) rename frontend/src/{AddMovie/AddListMovie => DiscoverMovie}/Posters/AddListMoviePosterInfo.js (100%) rename frontend/src/{AddMovie/AddListMovie => DiscoverMovie}/Posters/AddListMoviePosters.css (100%) rename frontend/src/{AddMovie/AddListMovie => DiscoverMovie}/Posters/AddListMoviePosters.js (86%) rename frontend/src/{AddMovie/AddListMovie => DiscoverMovie}/Posters/AddListMoviePostersConnector.js (94%) rename frontend/src/{AddMovie/AddListMovie => DiscoverMovie}/Posters/Options/AddListMoviePosterOptionsModal.js (100%) rename frontend/src/{AddMovie/AddListMovie => DiscoverMovie}/Posters/Options/AddListMoviePosterOptionsModalContent.js (100%) rename frontend/src/{AddMovie/AddListMovie => DiscoverMovie}/Posters/Options/AddListMoviePosterOptionsModalContentConnector.js (74%) rename frontend/src/{AddMovie/AddListMovie => DiscoverMovie}/Table/AddListMovieActionsCell.js (100%) rename frontend/src/{AddMovie/AddListMovie => DiscoverMovie}/Table/AddListMovieHeader.css (100%) rename frontend/src/{AddMovie/AddListMovie => DiscoverMovie}/Table/AddListMovieHeader.js (82%) rename frontend/src/{AddMovie/AddListMovie => DiscoverMovie}/Table/AddListMovieHeaderConnector.js (81%) create mode 100644 frontend/src/DiscoverMovie/Table/AddListMovieRow.css rename frontend/src/{AddMovie/AddListMovie => DiscoverMovie}/Table/AddListMovieRow.js (69%) rename frontend/src/{AddMovie/AddListMovie => DiscoverMovie}/Table/AddListMovieRowConnector.js (56%) rename frontend/src/{AddMovie/AddListMovie => DiscoverMovie}/Table/AddListMovieTable.css (100%) rename frontend/src/{AddMovie/AddListMovie => DiscoverMovie}/Table/AddListMovieTable.js (74%) rename frontend/src/{AddMovie/AddListMovie => DiscoverMovie}/Table/AddListMovieTableConnector.js (84%) rename frontend/src/{AddMovie/AddListMovie/Table/MovieStatusCell.css => DiscoverMovie/Table/ListMovieStatusCell.css} (76%) create mode 100644 frontend/src/DiscoverMovie/Table/ListMovieStatusCell.js create mode 100644 frontend/src/Store/Actions/discoverMovieActions.js rename frontend/src/Store/Selectors/{createAddMovieClientSideCollectionItemsSelector.js => createDiscoverMovieClientSideCollectionItemsSelector.js} (83%) rename frontend/src/Store/Selectors/{createAddListMovieSelector.js => createDiscoverMovieSelector.js} (64%) diff --git a/frontend/src/AddMovie/AddListMovie/AddListMovieConnector.js b/frontend/src/AddMovie/AddListMovie/AddListMovieConnector.js deleted file mode 100644 index a0e14e492..000000000 --- a/frontend/src/AddMovie/AddListMovie/AddListMovieConnector.js +++ /dev/null @@ -1,113 +0,0 @@ -import PropTypes from 'prop-types'; -import React, { Component } from 'react'; -import { connect } from 'react-redux'; -import { createSelector } from 'reselect'; -import createAddMovieClientSideCollectionItemsSelector from 'Store/Selectors/createAddMovieClientSideCollectionItemsSelector'; -import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector'; -import { fetchRootFolders } from 'Store/Actions/rootFolderActions'; -import { fetchListMovies, clearAddMovie, setListMovieSort, setListMovieFilter, setListMovieView, setListMovieTableOption } from 'Store/Actions/addMovieActions'; -import scrollPositions from 'Store/scrollPositions'; -import { registerPagePopulator, unregisterPagePopulator } from 'Utilities/pagePopulator'; -import withScrollPosition from 'Components/withScrollPosition'; -import AddListMovie from './AddListMovie'; - -function createMapStateToProps() { - return createSelector( - createAddMovieClientSideCollectionItemsSelector('addMovie'), - createDimensionsSelector(), - ( - movies, - dimensionsState - ) => { - return { - ...movies, - isSmallScreen: dimensionsState.isSmallScreen - }; - } - ); -} - -function createMapDispatchToProps(dispatch, props) { - return { - dispatchFetchRootFolders() { - dispatch(fetchRootFolders()); - }, - - dispatchFetchListMovies() { - dispatch(fetchListMovies()); - }, - - onTableOptionChange(payload) { - dispatch(setListMovieTableOption(payload)); - }, - - onSortSelect(sortKey) { - dispatch(setListMovieSort({ sortKey })); - }, - - onFilterSelect(selectedFilterKey) { - dispatch(setListMovieFilter({ selectedFilterKey })); - }, - - dispatchSetListMovieView(view) { - dispatch(setListMovieView({ view })); - }, - - dispatchClearListMovie() { - dispatch(clearAddMovie()); - } - }; -} - -class AddListMovieConnector extends Component { - - componentDidMount() { - registerPagePopulator(this.repopulate); - this.props.dispatchFetchRootFolders(); - this.props.dispatchFetchListMovies(); - } - - componentWillUnmount() { - this.props.dispatchClearListMovie(); - unregisterPagePopulator(this.repopulate); - } - - // - // Listeners - - onViewSelect = (view) => { - this.props.dispatchSetListMovieView(view); - } - - onScroll = ({ scrollTop }) => { - scrollPositions.addMovie = scrollTop; - } - - // - // Render - - render() { - return ( - - ); - } -} - -AddListMovieConnector.propTypes = { - isSmallScreen: PropTypes.bool.isRequired, - view: PropTypes.string.isRequired, - dispatchFetchRootFolders: PropTypes.func.isRequired, - dispatchFetchListMovies: PropTypes.func.isRequired, - dispatchClearListMovie: PropTypes.func.isRequired, - dispatchSetListMovieView: PropTypes.func.isRequired -}; - -export default withScrollPosition( - connect(createMapStateToProps, createMapDispatchToProps)(AddListMovieConnector), - 'addMovie' -); diff --git a/frontend/src/AddMovie/AddListMovie/Overview/AddListMovieOverviews.css b/frontend/src/AddMovie/AddListMovie/Overview/AddListMovieOverviews.css deleted file mode 100644 index 9c6520fb5..000000000 --- a/frontend/src/AddMovie/AddListMovie/Overview/AddListMovieOverviews.css +++ /dev/null @@ -1,3 +0,0 @@ -.grid { - flex: 1 0 auto; -} diff --git a/frontend/src/AddMovie/AddListMovie/Table/AddListMovieRow.css b/frontend/src/AddMovie/AddListMovie/Table/AddListMovieRow.css deleted file mode 100644 index f8eb16625..000000000 --- a/frontend/src/AddMovie/AddListMovie/Table/AddListMovieRow.css +++ /dev/null @@ -1,56 +0,0 @@ -.status { - composes: cell from '~Components/Table/Cells/VirtualTableRowCell.css'; - - flex: 0 0 60px; -} - -.sortTitle { - composes: cell from '~Components/Table/Cells/VirtualTableRowCell.css'; - - flex: 4 0 110px; -} - -.studio { - composes: cell from '~Components/Table/Cells/VirtualTableRowCell.css'; - - flex: 2 0 90px; -} - -.inCinemas, -.physicalRelease, -.genres { - composes: cell from '~Components/Table/Cells/VirtualTableRowCell.css'; - - flex: 0 0 180px; -} - -.movieStatus, -.certification { - composes: cell from '~Components/Table/Cells/VirtualTableRowCell.css'; - - flex: 0 0 100px; -} - -.ratings { - composes: headerCell from '~Components/Table/VirtualTableHeaderCell.css'; - - flex: 0 0 80px; -} - -.tags { - composes: cell from '~Components/Table/Cells/VirtualTableRowCell.css'; - - flex: 1 0 60px; -} - -.actions { - composes: cell from '~Components/Table/Cells/VirtualTableRowCell.css'; - - flex: 0 1 90px; -} - -.checkInput { - composes: input from '~Components/Form/CheckInput.css'; - - margin-top: 0; -} diff --git a/frontend/src/AddMovie/AddListMovie/Table/MovieStatusCell.js b/frontend/src/AddMovie/AddListMovie/Table/MovieStatusCell.js deleted file mode 100644 index e2b7f7bfe..000000000 --- a/frontend/src/AddMovie/AddListMovie/Table/MovieStatusCell.js +++ /dev/null @@ -1,62 +0,0 @@ -import PropTypes from 'prop-types'; -import React from 'react'; -import { icons } from 'Helpers/Props'; -import Icon from 'Components/Icon'; -import VirtualTableRowCell from 'Components/Table/Cells/TableRowCell'; -import styles from './MovieStatusCell.css'; - -function MovieStatusCell(props) { - const { - className, - status, - component: Component, - ...otherProps - } = props; - - return ( - - { - status === 'announced' ? - : null - } - - { - status === 'inCinemas' ? - : null - } - - { - status === 'released' ? - : null - } - - ); -} - -MovieStatusCell.propTypes = { - className: PropTypes.string.isRequired, - status: PropTypes.string.isRequired, - component: PropTypes.elementType -}; - -MovieStatusCell.defaultProps = { - className: styles.status, - component: VirtualTableRowCell -}; - -export default MovieStatusCell; diff --git a/frontend/src/AddMovie/AddNewMovie/AddNewMovieSearchResult.css b/frontend/src/AddMovie/AddNewMovie/AddNewMovieSearchResult.css index cc1f60343..b03fdd229 100644 --- a/frontend/src/AddMovie/AddNewMovie/AddNewMovieSearchResult.css +++ b/frontend/src/AddMovie/AddNewMovie/AddNewMovieSearchResult.css @@ -67,7 +67,7 @@ .exclusionIcon { margin-left: 10px; - color: #bc3737; + color: $dangerColor; pointer-events: all; } diff --git a/frontend/src/App/AppRoutes.js b/frontend/src/App/AppRoutes.js index d3052ed9e..2f1202af1 100644 --- a/frontend/src/App/AppRoutes.js +++ b/frontend/src/App/AppRoutes.js @@ -6,8 +6,7 @@ import NotFound from 'Components/NotFound'; import Switch from 'Components/Router/Switch'; import MovieIndexConnector from 'Movie/Index/MovieIndexConnector'; import AddNewMovieConnector from 'AddMovie/AddNewMovie/AddNewMovieConnector'; -import AddListMovieConnector from 'AddMovie/AddListMovie/AddListMovieConnector'; -import AddDiscoverMovieConnector from 'AddMovie/AddListMovie/AddDiscoverMovieConnector'; +import DiscoverMovieConnector from 'DiscoverMovie/DiscoverMovieConnector'; import ImportMovies from 'AddMovie/ImportMovie/ImportMovies'; import MovieDetailsPageConnector from 'Movie/Details/MovieDetailsPageConnector'; import CalendarPageConnector from 'Calendar/CalendarPageConnector'; @@ -78,14 +77,9 @@ function AppRoutes(props) { component={ImportMovies} /> - - state.addMovie.items, - (state) => state.addMovie.filterBuilderProps, + (state) => state.discoverMovie.items, + (state) => state.discoverMovie.filterBuilderProps, (sectionItems, filterBuilderProps) => { return { sectionItems, filterBuilderProps, - customFilterType: 'addMovie' + customFilterType: 'discoverMovie' }; } ); diff --git a/frontend/src/AddMovie/AddListMovie/AddListMovieItemConnector.js b/frontend/src/DiscoverMovie/AddListMovieItemConnector.js similarity index 69% rename from frontend/src/AddMovie/AddListMovie/AddListMovieItemConnector.js rename to frontend/src/DiscoverMovie/AddListMovieItemConnector.js index 2dde915cf..96a23ec9a 100644 --- a/frontend/src/AddMovie/AddListMovie/AddListMovieItemConnector.js +++ b/frontend/src/DiscoverMovie/AddListMovieItemConnector.js @@ -2,16 +2,13 @@ import PropTypes from 'prop-types'; import React, { Component } from 'react'; import { connect } from 'react-redux'; import { createSelector } from 'reselect'; -import createAddListMovieSelector from 'Store/Selectors/createAddListMovieSelector'; -import createMovieQualityProfileSelector from 'Store/Selectors/createMovieQualityProfileSelector'; +import createDiscoverMovieSelector from 'Store/Selectors/createDiscoverMovieSelector'; function createMapStateToProps() { return createSelector( - createAddListMovieSelector(), - createMovieQualityProfileSelector(), + createDiscoverMovieSelector(), ( - movie, - qualityProfile + movie ) => { // If a movie is deleted this selector may fire before the parent @@ -24,16 +21,12 @@ function createMapStateToProps() { } return { - ...movie, - qualityProfile + ...movie }; } ); } -const mapDispatchToProps = { -}; - class AddListMovieItemConnector extends Component { // @@ -64,4 +57,4 @@ AddListMovieItemConnector.propTypes = { component: PropTypes.elementType.isRequired }; -export default connect(createMapStateToProps, mapDispatchToProps)(AddListMovieItemConnector); +export default connect(createMapStateToProps)(AddListMovieItemConnector); diff --git a/frontend/src/DiscoverMovie/AddNewDiscoverMovieModal.js b/frontend/src/DiscoverMovie/AddNewDiscoverMovieModal.js new file mode 100644 index 000000000..87bb2bb50 --- /dev/null +++ b/frontend/src/DiscoverMovie/AddNewDiscoverMovieModal.js @@ -0,0 +1,31 @@ +import PropTypes from 'prop-types'; +import React from 'react'; +import Modal from 'Components/Modal/Modal'; +import AddNewDiscoverMovieModalContentConnector from './AddNewDiscoverMovieModalContentConnector'; + +function AddNewDiscoverMovieModal(props) { + const { + isOpen, + onModalClose, + ...otherProps + } = props; + + return ( + + + + ); +} + +AddNewDiscoverMovieModal.propTypes = { + isOpen: PropTypes.bool.isRequired, + onModalClose: PropTypes.func.isRequired +}; + +export default AddNewDiscoverMovieModal; diff --git a/frontend/src/DiscoverMovie/AddNewDiscoverMovieModalContentConnector.js b/frontend/src/DiscoverMovie/AddNewDiscoverMovieModalContentConnector.js new file mode 100644 index 000000000..d50307c03 --- /dev/null +++ b/frontend/src/DiscoverMovie/AddNewDiscoverMovieModalContentConnector.js @@ -0,0 +1,105 @@ +import PropTypes from 'prop-types'; +import React, { Component } from 'react'; +import { connect } from 'react-redux'; +import { createSelector } from 'reselect'; +import { setAddMovieDefault, addMovie } from 'Store/Actions/discoverMovieActions'; +import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector'; +import createSystemStatusSelector from 'Store/Selectors/createSystemStatusSelector'; +import selectSettings from 'Store/Selectors/selectSettings'; +import AddNewMovieModalContent from 'AddMovie/AddNewMovie/AddNewMovieModalContent'; + +function createMapStateToProps() { + return createSelector( + (state) => state.discoverMovie, + createDimensionsSelector(), + createSystemStatusSelector(), + (discoverMovieState, dimensions, systemStatus) => { + const { + isAdding, + addError, + defaults + } = discoverMovieState; + + const { + settings, + validationErrors, + validationWarnings + } = selectSettings(defaults, {}, addError); + + return { + isAdding, + addError, + isSmallScreen: dimensions.isSmallScreen, + validationErrors, + validationWarnings, + isWindows: systemStatus.isWindows, + ...settings + }; + } + ); +} + +const mapDispatchToProps = { + setAddMovieDefault, + addMovie +}; + +class AddNewDiscoverMovieModalContentConnector extends Component { + + // + // Listeners + + onInputChange = ({ name, value }) => { + this.props.setAddMovieDefault({ [name]: value }); + } + + onAddMoviePress = (searchForMovie) => { + const { + tmdbId, + rootFolderPath, + monitor, + qualityProfileId, + minimumAvailability, + tags + } = this.props; + + this.props.addMovie({ + tmdbId, + rootFolderPath: rootFolderPath.value, + monitor: monitor.value, + qualityProfileId: qualityProfileId.value, + minimumAvailability: minimumAvailability.value, + tags: tags.value, + searchForMovie + }); + + this.props.onModalClose(true); + } + + // + // Render + + render() { + return ( + + ); + } +} + +AddNewDiscoverMovieModalContentConnector.propTypes = { + tmdbId: PropTypes.number.isRequired, + rootFolderPath: PropTypes.object, + monitor: PropTypes.object.isRequired, + qualityProfileId: PropTypes.object, + minimumAvailability: PropTypes.object.isRequired, + tags: PropTypes.object.isRequired, + onModalClose: PropTypes.func.isRequired, + setAddMovieDefault: PropTypes.func.isRequired, + addMovie: PropTypes.func.isRequired +}; + +export default connect(createMapStateToProps, mapDispatchToProps)(AddNewDiscoverMovieModalContentConnector); diff --git a/frontend/src/AddMovie/AddListMovie/AddListMovie.js b/frontend/src/DiscoverMovie/DiscoverMovie.js similarity index 71% rename from frontend/src/AddMovie/AddListMovie/AddListMovie.js rename to frontend/src/DiscoverMovie/DiscoverMovie.js index 4a4aaa013..656a4ebcf 100644 --- a/frontend/src/AddMovie/AddListMovie/AddListMovie.js +++ b/frontend/src/DiscoverMovie/DiscoverMovie.js @@ -1,7 +1,10 @@ import _ from 'lodash'; import PropTypes from 'prop-types'; import React, { Component } from 'react'; -import hasDifferentItemsOrOrder from 'Utilities/Object/hasDifferentItems'; +import hasDifferentItemsOrOrder from 'Utilities/Object/hasDifferentItemsOrOrder'; +import getSelectedIds from 'Utilities/Table/getSelectedIds'; +import selectAll from 'Utilities/Table/selectAll'; +import toggleSelected from 'Utilities/Table/toggleSelected'; import { align, icons, sortDirections } from 'Helpers/Props'; import LoadingIndicator from 'Components/Loading/LoadingIndicator'; import PageContent from 'Components/Page/PageContent'; @@ -17,9 +20,11 @@ import AddListMoviePosterOptionsModal from './Posters/Options/AddListMoviePoster import AddListMoviePostersConnector from './Posters/AddListMoviePostersConnector'; import AddListMovieOverviewOptionsModal from './Overview/Options/AddListMovieOverviewOptionsModal'; import AddListMovieOverviewsConnector from './Overview/AddListMovieOverviewsConnector'; -import AddListMovieFilterMenu from 'AddMovie/AddListMovie/Menus/AddListMovieFilterMenu'; -import AddListMovieSortMenu from 'AddMovie/AddListMovie/Menus/AddListMovieSortMenu'; -import AddListMovieViewMenu from 'AddMovie/AddListMovie/Menus/AddListMovieViewMenu'; +import AddListMovieFilterMenu from './Menus/AddListMovieFilterMenu'; +import AddListMovieSortMenu from './Menus/AddListMovieSortMenu'; +import AddListMovieViewMenu from './Menus/AddListMovieViewMenu'; +import NoDiscoverMovie from './NoDiscoverMovie'; +import DiscoverMovieFooterConnector from './DiscoverMovieFooterConnector'; import styles from 'Movie/Index/MovieIndex.css'; function getViewComponent(view) { @@ -34,7 +39,7 @@ function getViewComponent(view) { return AddListMovieTableConnector; } -class AddListMovie extends Component { +class DiscoverMovie extends Component { // // Lifecycle @@ -50,12 +55,16 @@ class AddListMovie extends Component { isOverviewOptionsModalOpen: false, isConfirmSearchModalOpen: false, searchType: null, - lastToggled: null + allSelected: false, + allUnselected: false, + lastToggled: null, + selectedState: {} }; } componentDidMount() { this.setJumpBarItems(); + this.setSelectedState(); } componentDidUpdate(prevProps) { @@ -70,6 +79,7 @@ class AddListMovie extends Component { hasDifferentItemsOrOrder(prevProps.items, items) ) { this.setJumpBarItems(); + this.setSelectedState(); } if (this.state.jumpToCharacter != null) { @@ -84,6 +94,48 @@ class AddListMovie extends Component { this.setState({ scroller: ref }); } + getSelectedIds = () => { + if (this.state.allUnselected) { + return []; + } + return getSelectedIds(this.state.selectedState); + } + + setSelectedState() { + const { + items + } = this.props; + + const { + selectedState + } = this.state; + + const newSelectedState = {}; + + items.forEach((movie) => { + const isItemSelected = selectedState[movie.tmdbId]; + + if (isItemSelected) { + newSelectedState[movie.tmdbId] = isItemSelected; + } else { + newSelectedState[movie.tmdbId] = false; + } + }); + + const selectedCount = getSelectedIds(newSelectedState).length; + const newStateCount = Object.keys(newSelectedState).length; + let isAllSelected = false; + let isAllUnselected = false; + + if (selectedCount === 0) { + isAllUnselected = true; + } else if (selectedCount === newStateCount) { + isAllSelected = true; + } + + this.setState({ selectedState: newSelectedState, allSelected: isAllSelected, allUnselected: isAllUnselected }); + } + setJumpBarItems() { const { items, @@ -151,6 +203,28 @@ class AddListMovie extends Component { this.setState({ jumpToCharacter }); } + onSelectAllChange = ({ value }) => { + this.setState(selectAll(this.state.selectedState, value)); + } + + onSelectAllPress = () => { + this.onSelectAllChange({ value: !this.state.allSelected }); + } + + onSelectedChange = ({ id, value, shiftKey = false }) => { + this.setState((state) => { + return toggleSelected(state, this.props.items, id, value, shiftKey, 'tmdbId'); + }); + } + + onAddMoviesPress = ({ addOptions }) => { + this.props.onAddMoviesPress({ ids: this.getSelectedIds(), addOptions }); + } + + onExcludeMoviesPress = () => { + this.props.onExcludeMoviesPress({ ids: this.getSelectedIds() }); + } + // // Render @@ -172,6 +246,7 @@ class AddListMovie extends Component { onFilterSelect, onViewSelect, onScroll, + onAddMoviesPress, ...otherProps } = this.props; @@ -180,9 +255,14 @@ class AddListMovie extends Component { jumpBarItems, jumpToCharacter, isPosterOptionsModalOpen, - isOverviewOptionsModalOpen + isOverviewOptionsModalOpen, + selectedState, + allSelected, + allUnselected } = this.state; + const selectedMovieIds = this.getSelectedIds(); + const ViewComponent = getViewComponent(view); const isLoaded = !!(!error && isPopulated && items.length && scroller); const hasNoMovie = !totalItems; @@ -190,6 +270,15 @@ class AddListMovie extends Component { return ( + + + + @@ -292,9 +386,7 @@ class AddListMovie extends Component { { !error && isPopulated && !items.length && -
-
Couldn't find any results
-
+ } @@ -307,6 +399,15 @@ class AddListMovie extends Component { } + { + isLoaded && + + } + { - scrollPositions.addMovie = scrollTop; + scrollPositions.discoverMovie = scrollTop; + } + + onAddMoviesPress = ({ ids, addOptions }) => { + this.props.dispatchAddMovies(ids, addOptions); + } + + onExcludeMoviesPress =({ ids }) => { + this.props.dispatchAddNetImportExclusions({ ids }); } // @@ -91,26 +113,30 @@ class AddDiscoverMovieConnector extends Component { render() { return ( - ); } } -AddDiscoverMovieConnector.propTypes = { +DiscoverMovieConnector.propTypes = { isSmallScreen: PropTypes.bool.isRequired, view: PropTypes.string.isRequired, + dispatchFetchNetImportExclusions: PropTypes.func.isRequired, dispatchFetchRootFolders: PropTypes.func.isRequired, dispatchFetchListMovies: PropTypes.func.isRequired, dispatchClearListMovie: PropTypes.func.isRequired, - dispatchSetListMovieView: PropTypes.func.isRequired + dispatchSetListMovieView: PropTypes.func.isRequired, + dispatchAddMovies: PropTypes.func.isRequired, + dispatchAddNetImportExclusions: PropTypes.func.isRequired }; export default withScrollPosition( - connect(createMapStateToProps, createMapDispatchToProps)(AddDiscoverMovieConnector), - 'addMovie' + connect(createMapStateToProps, createMapDispatchToProps)(DiscoverMovieConnector), + 'discoverMovie' ); diff --git a/frontend/src/DiscoverMovie/DiscoverMovieFooter.css b/frontend/src/DiscoverMovie/DiscoverMovieFooter.css new file mode 100644 index 000000000..2dc21f388 --- /dev/null +++ b/frontend/src/DiscoverMovie/DiscoverMovieFooter.css @@ -0,0 +1,56 @@ +.inputContainer { + margin-right: 20px; + min-width: 150px; +} + +.buttonContainer { + display: flex; + justify-content: flex-end; + flex-grow: 1; +} + +.buttonContainerContent { + flex-grow: 0; +} + +.buttons { + display: flex; + justify-content: flex-end; + flex-grow: 1; +} + +.addSelectedButton { + composes: button from '~Components/Link/SpinnerButton.css'; + + margin-right: 10px; + height: 35px; +} + +.excludeSelectedButton { + composes: button from '~Components/Link/SpinnerButton.css'; + + margin-left: 50px; + height: 35px; +} + +@media only screen and (max-width: $breakpointSmall) { + .inputContainer { + margin-right: 0; + } + + .buttonContainer { + justify-content: flex-start; + } + + .buttonContainerContent { + flex-grow: 1; + } + + .buttons { + justify-content: space-between; + } + + .selectedMovieLabel { + text-align: left; + } +} diff --git a/frontend/src/DiscoverMovie/DiscoverMovieFooter.js b/frontend/src/DiscoverMovie/DiscoverMovieFooter.js new file mode 100644 index 000000000..67eb16a72 --- /dev/null +++ b/frontend/src/DiscoverMovie/DiscoverMovieFooter.js @@ -0,0 +1,252 @@ +import _ from 'lodash'; +import PropTypes from 'prop-types'; +import React, { Component } from 'react'; +import { kinds } from 'Helpers/Props'; +import SelectInput from 'Components/Form/SelectInput'; +import AvailabilitySelectInput from 'Components/Form/AvailabilitySelectInput'; +import QualityProfileSelectInputConnector from 'Components/Form/QualityProfileSelectInputConnector'; +import RootFolderSelectInputConnector from 'Components/Form/RootFolderSelectInputConnector'; +import SpinnerButton from 'Components/Link/SpinnerButton'; +import PageContentFooter from 'Components/Page/PageContentFooter'; +import ExcludeMovieModal from './Exclusion/ExcludeMovieModal'; +import DiscoverMovieFooterLabel from './DiscoverMovieFooterLabel'; +import styles from './DiscoverMovieFooter.css'; + +class DiscoverMovieFooter extends Component { + + // + // Lifecycle + + constructor(props, context) { + super(props, context); + + const { + defaultMonitor, + defaultQualityProfileId, + defaultMinimumAvailability, + defaultRootFolderPath + } = props; + + this.state = { + monitor: defaultMonitor, + qualityProfileId: defaultQualityProfileId, + minimumAvailability: defaultMinimumAvailability, + rootFolderPath: defaultRootFolderPath, + isExcludeMovieModalOpen: false, + destinationRootFolder: null + }; + } + + componentDidUpdate(prevProps) { + const { + defaultMonitor, + defaultQualityProfileId, + defaultMinimumAvailability, + defaultRootFolderPath + } = this.props; + + const { + monitor, + qualityProfileId, + minimumAvailability, + rootFolderPath + } = this.state; + + const newState = {}; + + if (monitor !== defaultMonitor) { + newState.monitor = defaultMonitor; + } + + if (qualityProfileId !== defaultQualityProfileId) { + newState.qualityProfileId = defaultQualityProfileId; + } + + if (minimumAvailability !== defaultMinimumAvailability) { + newState.minimumAvailability = defaultMinimumAvailability; + } + + if (rootFolderPath !== defaultRootFolderPath) { + newState.rootFolderPath = defaultRootFolderPath; + } + + if (!_.isEmpty(newState)) { + this.setState(newState); + } + } + + // + // Listeners + + // + // Listeners + + onExcludeSelectedPress = () => { + this.setState({ isExcludeMovieModalOpen: true }); + } + + onExcludeMovieModalClose = () => { + this.setState({ isExcludeMovieModalOpen: false }); + } + + onAddMoviesPress = () => { + const { + monitor, + qualityProfileId, + minimumAvailability, + rootFolderPath + } = this.state; + + const addOptions = { + monitor, + qualityProfileId, + minimumAvailability, + rootFolderPath + }; + + this.props.onAddMoviesPress({ addOptions }); + } + + // + // Render + + render() { + const { + selectedIds, + selectedCount, + isAdding, + isExcluding, + onInputChange + } = this.props; + + const { + monitor, + qualityProfileId, + minimumAvailability, + rootFolderPath, + isExcludeMovieModalOpen + } = this.state; + + const monitoredOptions = [ + { key: true, value: 'Monitored' }, + { key: false, value: 'Unmonitored' } + ]; + + return ( + +
+ + + +
+ +
+ + + +
+ +
+ + + +
+ +
+ + + +
+ +
+
+ + +
+
+ + Add Movies + +
+ + + Add Exclusion + +
+
+
+ + +
+ ); + } +} + +DiscoverMovieFooter.propTypes = { + selectedIds: PropTypes.arrayOf(PropTypes.number).isRequired, + selectedCount: PropTypes.number.isRequired, + isAdding: PropTypes.bool.isRequired, + isExcluding: PropTypes.bool.isRequired, + defaultMonitor: PropTypes.string.isRequired, + defaultQualityProfileId: PropTypes.number, + defaultMinimumAvailability: PropTypes.string, + defaultRootFolderPath: PropTypes.string, + onInputChange: PropTypes.func.isRequired, + onAddMoviesPress: PropTypes.func.isRequired, + onExcludeMoviesPress: PropTypes.func.isRequired +}; + +export default DiscoverMovieFooter; diff --git a/frontend/src/DiscoverMovie/DiscoverMovieFooterConnector.js b/frontend/src/DiscoverMovie/DiscoverMovieFooterConnector.js new file mode 100644 index 000000000..aa5148d93 --- /dev/null +++ b/frontend/src/DiscoverMovie/DiscoverMovieFooterConnector.js @@ -0,0 +1,72 @@ +import PropTypes from 'prop-types'; +import React, { Component } from 'react'; +import { connect } from 'react-redux'; +import { createSelector } from 'reselect'; +import { setAddMovieDefault } from 'Store/Actions/discoverMovieActions'; +import DiscoverMovieFooter from './DiscoverMovieFooter'; + +function createMapStateToProps() { + return createSelector( + (state) => state.discoverMovie, + (state) => state.settings.netImportExclusions, + (state, { selectedIds }) => selectedIds, + (discoverMovie, netImportExclusions, selectedIds) => { + const { + monitor: defaultMonitor, + qualityProfileId: defaultQualityProfileId, + minimumAvailability: defaultMinimumAvailability, + rootFolderPath: defaultRootFolderPath + } = discoverMovie.defaults; + + const { + isAdding + } = discoverMovie; + + const { + isSaving + } = netImportExclusions; + + return { + selectedCount: selectedIds.length, + isAdding, + isExcluding: isSaving, + defaultMonitor, + defaultQualityProfileId, + defaultMinimumAvailability, + defaultRootFolderPath + }; + } + ); +} + +const mapDispatchToProps = { + setAddMovieDefault +}; + +class DiscoverMovieFooterConnector extends Component { + + // + // Listeners + + onInputChange = ({ name, value }) => { + this.props.setAddMovieDefault({ [name]: value }); + } + + // + // Render + + render() { + return ( + + ); + } +} + +DiscoverMovieFooterConnector.propTypes = { + setAddMovieDefault: PropTypes.func.isRequired +}; + +export default connect(createMapStateToProps, mapDispatchToProps)(DiscoverMovieFooterConnector); diff --git a/frontend/src/DiscoverMovie/DiscoverMovieFooterLabel.css b/frontend/src/DiscoverMovie/DiscoverMovieFooterLabel.css new file mode 100644 index 000000000..9b4b40be6 --- /dev/null +++ b/frontend/src/DiscoverMovie/DiscoverMovieFooterLabel.css @@ -0,0 +1,8 @@ +.label { + margin-bottom: 3px; + font-weight: bold; +} + +.savingIcon { + margin-left: 8px; +} diff --git a/frontend/src/DiscoverMovie/DiscoverMovieFooterLabel.js b/frontend/src/DiscoverMovie/DiscoverMovieFooterLabel.js new file mode 100644 index 000000000..854037f77 --- /dev/null +++ b/frontend/src/DiscoverMovie/DiscoverMovieFooterLabel.js @@ -0,0 +1,40 @@ +import PropTypes from 'prop-types'; +import React from 'react'; +import { icons } from 'Helpers/Props'; +import SpinnerIcon from 'Components/SpinnerIcon'; +import styles from './DiscoverMovieFooterLabel.css'; + +function DiscoverMovieFooterLabel(props) { + const { + className, + label, + isSaving + } = props; + + return ( +
+ {label} + + { + isSaving && + + } +
+ ); +} + +DiscoverMovieFooterLabel.propTypes = { + className: PropTypes.string.isRequired, + label: PropTypes.string.isRequired, + isSaving: PropTypes.bool.isRequired +}; + +DiscoverMovieFooterLabel.defaultProps = { + className: styles.label +}; + +export default DiscoverMovieFooterLabel; diff --git a/frontend/src/DiscoverMovie/Exclusion/ExcludeMovieModal.js b/frontend/src/DiscoverMovie/Exclusion/ExcludeMovieModal.js new file mode 100644 index 000000000..8535c4001 --- /dev/null +++ b/frontend/src/DiscoverMovie/Exclusion/ExcludeMovieModal.js @@ -0,0 +1,33 @@ +import PropTypes from 'prop-types'; +import React from 'react'; +import { sizes } from 'Helpers/Props'; +import Modal from 'Components/Modal/Modal'; +import ExcludeMovieModalContentConnector from './ExcludeMovieModalContentConnector'; + +function ExcludeMovieModal(props) { + const { + isOpen, + onModalClose, + ...otherProps + } = props; + + return ( + + + + ); +} + +ExcludeMovieModal.propTypes = { + isOpen: PropTypes.bool.isRequired, + onModalClose: PropTypes.func.isRequired +}; + +export default ExcludeMovieModal; diff --git a/frontend/src/DiscoverMovie/Exclusion/ExcludeMovieModalContent.css b/frontend/src/DiscoverMovie/Exclusion/ExcludeMovieModalContent.css new file mode 100644 index 000000000..dbfef0871 --- /dev/null +++ b/frontend/src/DiscoverMovie/Exclusion/ExcludeMovieModalContent.css @@ -0,0 +1,12 @@ +.pathContainer { + margin-bottom: 20px; +} + +.pathIcon { + margin-right: 8px; +} + +.deleteFilesMessage { + margin-top: 20px; + color: $dangerColor; +} diff --git a/frontend/src/DiscoverMovie/Exclusion/ExcludeMovieModalContent.js b/frontend/src/DiscoverMovie/Exclusion/ExcludeMovieModalContent.js new file mode 100644 index 000000000..fc0180853 --- /dev/null +++ b/frontend/src/DiscoverMovie/Exclusion/ExcludeMovieModalContent.js @@ -0,0 +1,69 @@ +import PropTypes from 'prop-types'; +import React, { Component } from 'react'; +import { kinds } from 'Helpers/Props'; +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 './ExcludeMovieModalContent.css'; + +class ExcludeMovieModalContent extends Component { + + // + // Listeners + + onExcludeMovieConfirmed = () => { + this.props.onExcludePress(); + } + + // + // Render + + render() { + const { + tmdbId, + title, + onModalClose + } = this.props; + + return ( + + + Exclude - {title} ({tmdbId}) + + + +
+ Exclude {title}? This will prevent Radarr from adding automatically via list sync. +
+ +
+ + + + + + +
+ ); + } +} + +ExcludeMovieModalContent.propTypes = { + tmdbId: PropTypes.number.isRequired, + title: PropTypes.string.isRequired, + onExcludePress: PropTypes.func.isRequired, + onModalClose: PropTypes.func.isRequired +}; + +export default ExcludeMovieModalContent; diff --git a/frontend/src/DiscoverMovie/Exclusion/ExcludeMovieModalContentConnector.js b/frontend/src/DiscoverMovie/Exclusion/ExcludeMovieModalContentConnector.js new file mode 100644 index 000000000..8426a3308 --- /dev/null +++ b/frontend/src/DiscoverMovie/Exclusion/ExcludeMovieModalContentConnector.js @@ -0,0 +1,47 @@ +import PropTypes from 'prop-types'; +import React, { Component } from 'react'; +import { connect } from 'react-redux'; +import { addNetImportExclusions } from 'Store/Actions/discoverMovieActions'; +import ExcludeMovieModalContent from './ExcludeMovieModalContent'; + +const mapDispatchToProps = { + addNetImportExclusions +}; + +class ExcludeMovieModalContentConnector extends Component { + + // + // Listeners + + onExcludePress = () => { + this.props.addNetImportExclusions([{ + tmdbId: this.props.tmdbId, + movieTitle: this.props.title, + movieYear: this.props.year + }]); + + this.props.onModalClose(true); + } + + // + // Render + + render() { + return ( + + ); + } +} + +ExcludeMovieModalContentConnector.propTypes = { + tmdbId: PropTypes.number.isRequired, + title: PropTypes.string.isRequired, + year: PropTypes.number.isRequired, + onModalClose: PropTypes.func.isRequired, + addNetImportExclusions: PropTypes.func.isRequired +}; + +export default connect(undefined, mapDispatchToProps)(ExcludeMovieModalContentConnector); diff --git a/frontend/src/AddMovie/AddListMovie/Menus/AddListMovieFilterMenu.js b/frontend/src/DiscoverMovie/Menus/AddListMovieFilterMenu.js similarity index 91% rename from frontend/src/AddMovie/AddListMovie/Menus/AddListMovieFilterMenu.js rename to frontend/src/DiscoverMovie/Menus/AddListMovieFilterMenu.js index 25e2f14fa..31702eacc 100644 --- a/frontend/src/AddMovie/AddListMovie/Menus/AddListMovieFilterMenu.js +++ b/frontend/src/DiscoverMovie/Menus/AddListMovieFilterMenu.js @@ -2,7 +2,7 @@ import PropTypes from 'prop-types'; import React from 'react'; import { align } from 'Helpers/Props'; import FilterMenu from 'Components/Menu/FilterMenu'; -import AddListMovieFilterModalConnector from 'AddMovie/AddListMovie/AddListMovieFilterModalConnector'; +import AddListMovieFilterModalConnector from 'DiscoverMovie/AddListMovieFilterModalConnector'; function AddListMovieFilterMenu(props) { const { diff --git a/frontend/src/AddMovie/AddListMovie/Menus/AddListMovieSortMenu.js b/frontend/src/DiscoverMovie/Menus/AddListMovieSortMenu.js similarity index 81% rename from frontend/src/AddMovie/AddListMovie/Menus/AddListMovieSortMenu.js rename to frontend/src/DiscoverMovie/Menus/AddListMovieSortMenu.js index b95996a0e..81996dea8 100644 --- a/frontend/src/AddMovie/AddListMovie/Menus/AddListMovieSortMenu.js +++ b/frontend/src/DiscoverMovie/Menus/AddListMovieSortMenu.js @@ -63,6 +63,24 @@ function AddListMovieSortMenu(props) { > Physical Release + + + Rating + + + + Certification + ); diff --git a/frontend/src/AddMovie/AddListMovie/Menus/AddListMovieViewMenu.js b/frontend/src/DiscoverMovie/Menus/AddListMovieViewMenu.js similarity index 100% rename from frontend/src/AddMovie/AddListMovie/Menus/AddListMovieViewMenu.js rename to frontend/src/DiscoverMovie/Menus/AddListMovieViewMenu.js diff --git a/frontend/src/DiscoverMovie/NoDiscoverMovie.css b/frontend/src/DiscoverMovie/NoDiscoverMovie.css new file mode 100644 index 000000000..38a01f391 --- /dev/null +++ b/frontend/src/DiscoverMovie/NoDiscoverMovie.css @@ -0,0 +1,11 @@ +.message { + margin-top: 10px; + margin-bottom: 30px; + text-align: center; + font-size: 20px; +} + +.buttonContainer { + margin-top: 20px; + text-align: center; +} diff --git a/frontend/src/DiscoverMovie/NoDiscoverMovie.js b/frontend/src/DiscoverMovie/NoDiscoverMovie.js new file mode 100644 index 000000000..8a0d230cb --- /dev/null +++ b/frontend/src/DiscoverMovie/NoDiscoverMovie.js @@ -0,0 +1,60 @@ +import PropTypes from 'prop-types'; +import React from 'react'; +import { kinds } from 'Helpers/Props'; +import Button from 'Components/Link/Button'; +import styles from './NoDiscoverMovie.css'; + +function NoDiscoverMovie(props) { + const { totalItems } = props; + + if (totalItems > 0) { + return ( +
+
+ All movies are hidden due to the applied filter. +
+
+ ); + } + + return ( +
+
+ No list items or recommendations found, to get started you'll want to add a new movie, import some existing ones, or add a list. +
+ +
+ +
+ +
+ +
+ +
+ +
+
+ ); +} + +NoDiscoverMovie.propTypes = { + totalItems: PropTypes.number.isRequired +}; + +export default NoDiscoverMovie; diff --git a/frontend/src/AddMovie/AddListMovie/Overview/AddListMovieOverview.css b/frontend/src/DiscoverMovie/Overview/AddListMovieOverview.css similarity index 74% rename from frontend/src/AddMovie/AddListMovie/Overview/AddListMovieOverview.css rename to frontend/src/DiscoverMovie/Overview/AddListMovieOverview.css index 0a5cdfa00..f4959c12d 100644 --- a/frontend/src/AddMovie/AddListMovie/Overview/AddListMovieOverview.css +++ b/frontend/src/DiscoverMovie/Overview/AddListMovieOverview.css @@ -1,13 +1,5 @@ $hoverScale: 1.05; -.container { - &:hover { - .content { - background-color: $tableRowHoverBackgroundColor; - } - } -} - .content { display: flex; flex-grow: 1; @@ -17,6 +9,13 @@ $hoverScale: 1.05; position: relative; } +.editorSelect { + position: absolute; + top: 0; + left: 5px; + z-index: 3; +} + .posterContainer { position: relative; } @@ -33,17 +32,10 @@ $hoverScale: 1.05; } } -.ended { - position: absolute; - top: 0; - right: 0; - z-index: 1; - width: 0; - height: 0; - border-width: 0 25px 25px 0; - border-style: solid; - border-color: transparent $dangerColor transparent transparent; - color: $white; +.exclusionIcon { + margin-left: 10px; + color: $dangerColor; + pointer-events: all; } .info { @@ -59,8 +51,6 @@ $hoverScale: 1.05; justify-content: space-between; flex: 0 0 auto; margin-bottom: 10px; - font-weight: 300; - font-size: 30px; line-height: 32px; } diff --git a/frontend/src/AddMovie/AddListMovie/Overview/AddListMovieOverview.js b/frontend/src/DiscoverMovie/Overview/AddListMovieOverview.js similarity index 58% rename from frontend/src/AddMovie/AddListMovie/Overview/AddListMovieOverview.js rename to frontend/src/DiscoverMovie/Overview/AddListMovieOverview.js index d56a4406e..a59a0d23d 100644 --- a/frontend/src/AddMovie/AddListMovie/Overview/AddListMovieOverview.js +++ b/frontend/src/DiscoverMovie/Overview/AddListMovieOverview.js @@ -1,11 +1,16 @@ import PropTypes from 'prop-types'; import React, { Component } from 'react'; import TextTruncate from 'react-text-truncate'; +import { icons } from 'Helpers/Props'; +import Icon from 'Components/Icon'; +import IconButton from 'Components/Link/IconButton'; import dimensions from 'Styles/Variables/dimensions'; import fonts from 'Styles/Variables/fonts'; +import CheckInput from 'Components/Form/CheckInput'; import MoviePoster from 'Movie/MoviePoster'; import Link from 'Components/Link/Link'; -import AddNewMovieModal from 'AddMovie/AddNewMovie/AddNewMovieModal'; +import AddNewDiscoverMovieModal from 'DiscoverMovie/AddNewDiscoverMovieModal'; +import ExcludeMovieModal from 'DiscoverMovie/Exclusion/ExcludeMovieModal'; import styles from './AddListMovieOverview.css'; const columnPadding = parseInt(dimensions.movieIndexColumnPadding); @@ -32,7 +37,8 @@ class AddListMovieOverview extends Component { super(props, context); this.state = { - isNewAddMovieModalOpen: false + isNewAddMovieModalOpen: false, + isExcludeMovieModalOpen: false }; } @@ -47,6 +53,23 @@ class AddListMovieOverview extends Component { this.setState({ isNewAddMovieModalOpen: false }); } + onExcludeMoviePress = () => { + this.setState({ isExcludeMovieModalOpen: true }); + } + + onExcludeMovieModalClose = () => { + this.setState({ isExcludeMovieModalOpen: false }); + } + + onChange = ({ value, shiftKey }) => { + const { + tmdbId, + onSelectedChange + } = this.props; + + onSelectedChange({ id: tmdbId, value, shiftKey }); + } + // // Render @@ -63,11 +86,14 @@ class AddListMovieOverview extends Component { posterHeight, rowHeight, isSmallScreen, - isExistingMovie + isExisting, + isExcluded, + isSelected } = this.props; const { - isNewAddMovieModalOpen + isNewAddMovieModalOpen, + isExcludeMovieModalOpen } = this.state; const elementStyle = { @@ -75,19 +101,24 @@ class AddListMovieOverview extends Component { height: `${posterHeight}px` }; - const linkProps = isExistingMovie ? { to: `/movie/${titleSlug}` } : { onPress: this.onPress }; + const linkProps = isExisting ? { to: `/movie/${titleSlug}` } : { onPress: this.onPress }; const contentHeight = getContentHeight(rowHeight, isSmallScreen); const overviewHeight = contentHeight - titleRowHeight; return (
- +
+
+ +
- {title} ({year}) + + {title}({year}) + { + isExcluded && + + } + + +
+ +
@@ -113,10 +167,10 @@ class AddListMovieOverview extends Component {
- +
- + +
); } @@ -136,7 +198,6 @@ AddListMovieOverview.propTypes = { folder: PropTypes.string.isRequired, year: PropTypes.number.isRequired, overview: PropTypes.string.isRequired, - monitored: PropTypes.bool.isRequired, status: PropTypes.string.isRequired, titleSlug: PropTypes.string.isRequired, images: PropTypes.arrayOf(PropTypes.object).isRequired, @@ -149,8 +210,10 @@ AddListMovieOverview.propTypes = { longDateFormat: PropTypes.string.isRequired, timeFormat: PropTypes.string.isRequired, isSmallScreen: PropTypes.bool.isRequired, - isExistingMovie: PropTypes.bool.isRequired, - isExclusionMovie: PropTypes.bool.isRequired + isExisting: PropTypes.bool.isRequired, + isExcluded: PropTypes.bool.isRequired, + isSelected: PropTypes.bool, + onSelectedChange: PropTypes.func.isRequired }; export default AddListMovieOverview; diff --git a/frontend/src/AddMovie/AddListMovie/Overview/AddListMovieOverviewConnector.js b/frontend/src/DiscoverMovie/Overview/AddListMovieOverviewConnector.js similarity index 56% rename from frontend/src/AddMovie/AddListMovie/Overview/AddListMovieOverviewConnector.js rename to frontend/src/DiscoverMovie/Overview/AddListMovieOverviewConnector.js index 50e0b4080..49911b8ba 100644 --- a/frontend/src/AddMovie/AddListMovie/Overview/AddListMovieOverviewConnector.js +++ b/frontend/src/DiscoverMovie/Overview/AddListMovieOverviewConnector.js @@ -1,19 +1,13 @@ import { connect } from 'react-redux'; import { createSelector } from 'reselect'; -import createExistingMovieSelector from 'Store/Selectors/createExistingMovieSelector'; -import createExclusionMovieSelector from 'Store/Selectors/createExclusionMovieSelector'; import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector'; import AddListMovieOverview from './AddListMovieOverview'; function createMapStateToProps() { return createSelector( - createExistingMovieSelector(), - createExclusionMovieSelector(), createDimensionsSelector(), - (isExistingMovie, isExclusionMovie, dimensions) => { + (dimensions) => { return { - isExistingMovie, - isExclusionMovie, isSmallScreen: dimensions.isSmallScreen }; } diff --git a/frontend/src/DiscoverMovie/Overview/AddListMovieOverviews.css b/frontend/src/DiscoverMovie/Overview/AddListMovieOverviews.css new file mode 100644 index 000000000..5f8ca5baf --- /dev/null +++ b/frontend/src/DiscoverMovie/Overview/AddListMovieOverviews.css @@ -0,0 +1,11 @@ +.grid { + flex: 1 0 auto; +} + +.container { + &:hover { + .content { + background-color: $tableRowHoverBackgroundColor; + } + } +} diff --git a/frontend/src/AddMovie/AddListMovie/Overview/AddListMovieOverviews.js b/frontend/src/DiscoverMovie/Overview/AddListMovieOverviews.js similarity index 91% rename from frontend/src/AddMovie/AddListMovie/Overview/AddListMovieOverviews.js rename to frontend/src/DiscoverMovie/Overview/AddListMovieOverviews.js index 24d608422..88c94e72c 100644 --- a/frontend/src/AddMovie/AddListMovie/Overview/AddListMovieOverviews.js +++ b/frontend/src/DiscoverMovie/Overview/AddListMovieOverviews.js @@ -2,10 +2,10 @@ import PropTypes from 'prop-types'; import React, { Component } from 'react'; import { Grid, WindowScroller } from 'react-virtualized'; import getIndexOfFirstCharacter from 'Utilities/Array/getIndexOfFirstCharacter'; -import hasDifferentItemsOrOrder from 'Utilities/Object/hasDifferentItems'; +import hasDifferentItemsOrOrder from 'Utilities/Object/hasDifferentItemsOrOrder'; import dimensions from 'Styles/Variables/dimensions'; import Measure from 'Components/Measure'; -import AddListMovieItemConnector from 'AddMovie/AddListMovie/AddListMovieItemConnector'; +import AddListMovieItemConnector from 'DiscoverMovie/AddListMovieItemConnector'; import AddListMovieOverviewConnector from './AddListMovieOverviewConnector'; import styles from './AddListMovieOverviews.css'; @@ -81,7 +81,7 @@ class AddListMovieOverviews extends Component { if (this._grid && (prevState.width !== width || prevState.rowHeight !== rowHeight || - hasDifferentItemsOrOrder(prevProps.items, items))) { + hasDifferentItemsOrOrder(prevProps.items, items, 'tmdbId'))) { // recomputeGridSize also forces Grid to discard its cache of rendered cells this._grid.recomputeGridSize(); } @@ -133,7 +133,9 @@ class AddListMovieOverviews extends Component { shortDateFormat, longDateFormat, timeFormat, - isSmallScreen + isSmallScreen, + selectedState, + onSelectedChange } = this.props; const { @@ -155,7 +157,7 @@ class AddListMovieOverviews extends Component { style={style} >
); @@ -187,7 +191,8 @@ class AddListMovieOverviews extends Component { const { isSmallScreen, scroller, - items + items, + selectedState } = this.props; const { @@ -224,6 +229,7 @@ class AddListMovieOverviews extends Component { scrollTop={scrollTop} overscanRowCount={2} cellRenderer={this.cellRenderer} + selectedState={selectedState} scrollToAlignment={'start'} isScrollingOptout={true} /> @@ -247,7 +253,9 @@ AddListMovieOverviews.propTypes = { shortDateFormat: PropTypes.string.isRequired, longDateFormat: PropTypes.string.isRequired, isSmallScreen: PropTypes.bool.isRequired, - timeFormat: PropTypes.string.isRequired + timeFormat: PropTypes.string.isRequired, + selectedState: PropTypes.object.isRequired, + onSelectedChange: PropTypes.func.isRequired }; export default AddListMovieOverviews; diff --git a/frontend/src/AddMovie/AddListMovie/Overview/AddListMovieOverviewsConnector.js b/frontend/src/DiscoverMovie/Overview/AddListMovieOverviewsConnector.js similarity index 94% rename from frontend/src/AddMovie/AddListMovie/Overview/AddListMovieOverviewsConnector.js rename to frontend/src/DiscoverMovie/Overview/AddListMovieOverviewsConnector.js index c61a12638..39a49dadc 100644 --- a/frontend/src/AddMovie/AddListMovie/Overview/AddListMovieOverviewsConnector.js +++ b/frontend/src/DiscoverMovie/Overview/AddListMovieOverviewsConnector.js @@ -6,7 +6,7 @@ import AddListMovieOverviews from './AddListMovieOverviews'; function createMapStateToProps() { return createSelector( - (state) => state.addMovie.overviewOptions, + (state) => state.discoverMovie.overviewOptions, createUISettingsSelector(), createDimensionsSelector(), (overviewOptions, uiSettings, dimensions) => { diff --git a/frontend/src/AddMovie/AddListMovie/Overview/Options/AddListMovieOverviewOptionsModal.js b/frontend/src/DiscoverMovie/Overview/Options/AddListMovieOverviewOptionsModal.js similarity index 100% rename from frontend/src/AddMovie/AddListMovie/Overview/Options/AddListMovieOverviewOptionsModal.js rename to frontend/src/DiscoverMovie/Overview/Options/AddListMovieOverviewOptionsModal.js diff --git a/frontend/src/AddMovie/AddListMovie/Overview/Options/AddListMovieOverviewOptionsModalContent.js b/frontend/src/DiscoverMovie/Overview/Options/AddListMovieOverviewOptionsModalContent.js similarity index 100% rename from frontend/src/AddMovie/AddListMovie/Overview/Options/AddListMovieOverviewOptionsModalContent.js rename to frontend/src/DiscoverMovie/Overview/Options/AddListMovieOverviewOptionsModalContent.js diff --git a/frontend/src/AddMovie/AddListMovie/Overview/Options/AddListMovieOverviewOptionsModalContentConnector.js b/frontend/src/DiscoverMovie/Overview/Options/AddListMovieOverviewOptionsModalContentConnector.js similarity index 74% rename from frontend/src/AddMovie/AddListMovie/Overview/Options/AddListMovieOverviewOptionsModalContentConnector.js rename to frontend/src/DiscoverMovie/Overview/Options/AddListMovieOverviewOptionsModalContentConnector.js index 2974dc670..585a13d1d 100644 --- a/frontend/src/AddMovie/AddListMovie/Overview/Options/AddListMovieOverviewOptionsModalContentConnector.js +++ b/frontend/src/DiscoverMovie/Overview/Options/AddListMovieOverviewOptionsModalContentConnector.js @@ -1,13 +1,13 @@ import { connect } from 'react-redux'; import { createSelector } from 'reselect'; -import { setListMovieOverviewOption } from 'Store/Actions/addMovieActions'; +import { setListMovieOverviewOption } from 'Store/Actions/discoverMovieActions'; import AddListMovieOverviewOptionsModalContent from './AddListMovieOverviewOptionsModalContent'; function createMapStateToProps() { return createSelector( - (state) => state.addMovie, - (addMovie) => { - return addMovie.overviewOptions; + (state) => state.discoverMovie, + (discoverMovie) => { + return discoverMovie.overviewOptions; } ); } diff --git a/frontend/src/AddMovie/AddListMovie/Posters/AddListMoviePoster.css b/frontend/src/DiscoverMovie/Posters/AddListMoviePoster.css similarity index 93% rename from frontend/src/AddMovie/AddListMovie/Posters/AddListMoviePoster.css rename to frontend/src/DiscoverMovie/Posters/AddListMoviePoster.css index 6b97b322e..4b38afaef 100644 --- a/frontend/src/AddMovie/AddListMovie/Posters/AddListMoviePoster.css +++ b/frontend/src/DiscoverMovie/Posters/AddListMoviePoster.css @@ -1,9 +1,5 @@ $hoverScale: 1.05; -.container { - padding: 10px; -} - .content { transition: all 200ms ease-in; @@ -54,10 +50,11 @@ $hoverScale: 1.05; font-size: $smallFontSize; } -.ended { +.excluded { position: absolute; top: 0; right: 0; + z-index: 1; width: 0; height: 0; border-width: 0 25px 25px 0; @@ -79,6 +76,13 @@ $hoverScale: 1.05; transition: opacity 0; } +.editorSelect { + position: absolute; + top: 10px; + left: 10px; + z-index: 3; +} + .action { composes: button from '~Components/Link/IconButton.css'; diff --git a/frontend/src/AddMovie/AddListMovie/Posters/AddListMoviePoster.js b/frontend/src/DiscoverMovie/Posters/AddListMoviePoster.js similarity index 58% rename from frontend/src/AddMovie/AddListMovie/Posters/AddListMoviePoster.js rename to frontend/src/DiscoverMovie/Posters/AddListMoviePoster.js index f333ab821..ab9ee60ae 100644 --- a/frontend/src/AddMovie/AddListMovie/Posters/AddListMoviePoster.js +++ b/frontend/src/DiscoverMovie/Posters/AddListMoviePoster.js @@ -1,8 +1,13 @@ import PropTypes from 'prop-types'; import React, { Component } from 'react'; +import { icons } from 'Helpers/Props'; +import IconButton from 'Components/Link/IconButton'; +import CheckInput from 'Components/Form/CheckInput'; +import Label from 'Components/Label'; import Link from 'Components/Link/Link'; import MoviePoster from 'Movie/MoviePoster'; -import AddNewMovieModal from 'AddMovie/AddNewMovie/AddNewMovieModal'; +import AddNewDiscoverMovieModal from 'DiscoverMovie/AddNewDiscoverMovieModal'; +import ExcludeMovieModal from 'DiscoverMovie/Exclusion/ExcludeMovieModal'; import styles from './AddListMoviePoster.css'; class AddListMoviePoster extends Component { @@ -15,7 +20,8 @@ class AddListMoviePoster extends Component { this.state = { hasPosterError: false, - isNewAddMovieModalOpen: false + isNewAddMovieModalOpen: false, + isExcludeMovieModalOpen: false }; } @@ -30,6 +36,14 @@ class AddListMoviePoster extends Component { this.setState({ isNewAddMovieModalOpen: false }); } + onExcludeMoviePress = () => { + this.setState({ isExcludeMovieModalOpen: true }); + } + + onExcludeMovieModalClose = () => { + this.setState({ isExcludeMovieModalOpen: false }); + } + onPosterLoad = () => { if (this.state.hasPosterError) { this.setState({ hasPosterError: false }); @@ -42,6 +56,15 @@ class AddListMoviePoster extends Component { } } + onChange = ({ value, shiftKey }) => { + const { + tmdbId, + onSelectedChange + } = this.props; + + onSelectedChange({ id: tmdbId, value, shiftKey }); + } + // // Render @@ -52,21 +75,23 @@ class AddListMoviePoster extends Component { year, overview, folder, - status, titleSlug, images, posterWidth, posterHeight, showTitle, - isExistingMovie + isExisting, + isExcluded, + isSelected } = this.props; const { hasPosterError, - isNewAddMovieModalOpen + isNewAddMovieModalOpen, + isExcludeMovieModalOpen } = this.state; - const linkProps = isExistingMovie ? { to: `/movie/${titleSlug}` } : { onPress: this.onPress }; + const linkProps = isExisting ? { to: `/movie/${titleSlug}` } : { onPress: this.onPress }; const elementStyle = { width: `${posterWidth}px`, @@ -77,10 +102,31 @@ class AddListMoviePoster extends Component {
{ - status === 'ended' && +
+ +
+ } + + + + { + isExcluded &&
} @@ -116,8 +162,8 @@ class AddListMoviePoster extends Component {
} - + +
); } @@ -146,14 +200,10 @@ AddListMoviePoster.propTypes = { showRelativeDates: PropTypes.bool.isRequired, shortDateFormat: PropTypes.string.isRequired, timeFormat: PropTypes.string.isRequired, - isExistingMovie: PropTypes.bool.isRequired, - isExclusionMovie: PropTypes.bool.isRequired -}; - -AddListMoviePoster.defaultProps = { - statistics: { - movieFileCount: 0 - } + isExisting: PropTypes.bool.isRequired, + isExcluded: PropTypes.bool.isRequired, + isSelected: PropTypes.bool, + onSelectedChange: PropTypes.func.isRequired }; export default AddListMoviePoster; diff --git a/frontend/src/AddMovie/AddListMovie/Posters/AddListMoviePosterConnector.js b/frontend/src/DiscoverMovie/Posters/AddListMoviePosterConnector.js similarity index 56% rename from frontend/src/AddMovie/AddListMovie/Posters/AddListMoviePosterConnector.js rename to frontend/src/DiscoverMovie/Posters/AddListMoviePosterConnector.js index beef0b083..ebec33139 100644 --- a/frontend/src/AddMovie/AddListMovie/Posters/AddListMoviePosterConnector.js +++ b/frontend/src/DiscoverMovie/Posters/AddListMoviePosterConnector.js @@ -1,19 +1,13 @@ import { connect } from 'react-redux'; import { createSelector } from 'reselect'; -import createExistingMovieSelector from 'Store/Selectors/createExistingMovieSelector'; -import createExclusionMovieSelector from 'Store/Selectors/createExclusionMovieSelector'; import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector'; import AddListMoviePoster from './AddListMoviePoster'; function createMapStateToProps() { return createSelector( - createExistingMovieSelector(), - createExclusionMovieSelector(), createDimensionsSelector(), - (isExistingMovie, isExclusionMovie, dimensions) => { + ( dimensions) => { return { - isExistingMovie, - isExclusionMovie, isSmallScreen: dimensions.isSmallScreen }; } diff --git a/frontend/src/AddMovie/AddListMovie/Posters/AddListMoviePosterInfo.css b/frontend/src/DiscoverMovie/Posters/AddListMoviePosterInfo.css similarity index 100% rename from frontend/src/AddMovie/AddListMovie/Posters/AddListMoviePosterInfo.css rename to frontend/src/DiscoverMovie/Posters/AddListMoviePosterInfo.css diff --git a/frontend/src/AddMovie/AddListMovie/Posters/AddListMoviePosterInfo.js b/frontend/src/DiscoverMovie/Posters/AddListMoviePosterInfo.js similarity index 100% rename from frontend/src/AddMovie/AddListMovie/Posters/AddListMoviePosterInfo.js rename to frontend/src/DiscoverMovie/Posters/AddListMoviePosterInfo.js diff --git a/frontend/src/AddMovie/AddListMovie/Posters/AddListMoviePosters.css b/frontend/src/DiscoverMovie/Posters/AddListMoviePosters.css similarity index 100% rename from frontend/src/AddMovie/AddListMovie/Posters/AddListMoviePosters.css rename to frontend/src/DiscoverMovie/Posters/AddListMoviePosters.css diff --git a/frontend/src/AddMovie/AddListMovie/Posters/AddListMoviePosters.js b/frontend/src/DiscoverMovie/Posters/AddListMoviePosters.js similarity index 86% rename from frontend/src/AddMovie/AddListMovie/Posters/AddListMoviePosters.js rename to frontend/src/DiscoverMovie/Posters/AddListMoviePosters.js index 58d39b39f..ca0073fc4 100644 --- a/frontend/src/AddMovie/AddListMovie/Posters/AddListMoviePosters.js +++ b/frontend/src/DiscoverMovie/Posters/AddListMoviePosters.js @@ -2,10 +2,10 @@ import PropTypes from 'prop-types'; import React, { Component } from 'react'; import { Grid, WindowScroller } from 'react-virtualized'; import getIndexOfFirstCharacter from 'Utilities/Array/getIndexOfFirstCharacter'; -import hasDifferentItemsOrOrder from 'Utilities/Object/hasDifferentItems'; +import hasDifferentItemsOrOrder from 'Utilities/Object/hasDifferentItemsOrOrder'; import dimensions from 'Styles/Variables/dimensions'; import Measure from 'Components/Measure'; -import AddListMovieItemConnector from 'AddMovie/AddListMovie/AddListMovieItemConnector'; +import AddListMovieItemConnector from 'DiscoverMovie/AddListMovieItemConnector'; import AddListMoviePosterConnector from './AddListMoviePosterConnector'; import styles from './AddListMoviePosters.css'; @@ -36,9 +36,7 @@ function calculateColumnWidth(width, posterSize, isSmallScreen) { function calculateRowHeight(posterHeight, sortKey, isSmallScreen, posterOptions) { const { detailedProgressBar, - showTitle, - showMonitored, - showQualityProfile + showTitle } = posterOptions; const nextAiringHeight = 19; @@ -54,14 +52,6 @@ function calculateRowHeight(posterHeight, sortKey, isSmallScreen, posterOptions) heights.push(19); } - if (showMonitored) { - heights.push(19); - } - - if (showQualityProfile) { - heights.push(19); - } - switch (sortKey) { case 'studio': default: @@ -94,6 +84,7 @@ class AddListMoviePosters extends Component { this._isInitialized = false; this._grid = null; + this._padding = props.isSmallScreen ? columnPaddingSmallScreen : columnPadding; } componentDidUpdate(prevProps, prevState) { @@ -101,7 +92,9 @@ class AddListMoviePosters extends Component { items, sortKey, posterOptions, - jumpToCharacter + jumpToCharacter, + isSmallScreen, + selectedState } = this.props; const { @@ -113,7 +106,7 @@ class AddListMoviePosters extends Component { if (prevProps.sortKey !== sortKey || prevProps.posterOptions !== posterOptions) { - this.calculateGrid(); + this.calculateGrid(width, isSmallScreen); } if (this._grid && @@ -121,7 +114,8 @@ class AddListMoviePosters extends Component { prevState.columnWidth !== columnWidth || prevState.columnCount !== columnCount || prevState.rowHeight !== rowHeight || - hasDifferentItemsOrOrder(prevProps.items, items))) { + prevProps.selectedState !== selectedState || + hasDifferentItemsOrOrder(prevProps.items, items, 'tmdbId'))) { // recomputeGridSize also forces Grid to discard its cache of rendered cells this._grid.recomputeGridSize(); } @@ -153,10 +147,9 @@ class AddListMoviePosters extends Component { posterOptions } = this.props; - const padding = isSmallScreen ? columnPaddingSmallScreen : columnPadding; const columnWidth = calculateColumnWidth(width, posterOptions.size, isSmallScreen); const columnCount = Math.max(Math.floor(width / columnWidth), 1); - const posterWidth = columnWidth - padding; + const posterWidth = columnWidth - this._padding * 2; const posterHeight = calculatePosterHeight(posterWidth); const rowHeight = calculateRowHeight(posterHeight, sortKey, isSmallScreen, posterOptions); @@ -177,7 +170,9 @@ class AddListMoviePosters extends Component { posterOptions, showRelativeDates, shortDateFormat, - timeFormat + timeFormat, + selectedState, + onSelectedChange } = this.props; const { @@ -190,7 +185,8 @@ class AddListMoviePosters extends Component { showTitle } = posterOptions; - const movie = items[rowIndex * columnCount + columnIndex]; + const movieIdx = rowIndex * columnCount + columnIndex; + const movie = items[movieIdx]; if (!movie) { return null; @@ -198,11 +194,15 @@ class AddListMoviePosters extends Component { return (
); @@ -231,7 +233,8 @@ class AddListMoviePosters extends Component { const { isSmallScreen, scroller, - items + items, + selectedState } = this.props; const { @@ -272,6 +275,7 @@ class AddListMoviePosters extends Component { scrollTop={scrollTop} overscanRowCount={2} cellRenderer={this.cellRenderer} + selectedState={selectedState} scrollToAlignment={'start'} isScrollingOptOut={true} /> @@ -294,7 +298,9 @@ AddListMoviePosters.propTypes = { showRelativeDates: PropTypes.bool.isRequired, shortDateFormat: PropTypes.string.isRequired, isSmallScreen: PropTypes.bool.isRequired, - timeFormat: PropTypes.string.isRequired + timeFormat: PropTypes.string.isRequired, + selectedState: PropTypes.object.isRequired, + onSelectedChange: PropTypes.func.isRequired }; export default AddListMoviePosters; diff --git a/frontend/src/AddMovie/AddListMovie/Posters/AddListMoviePostersConnector.js b/frontend/src/DiscoverMovie/Posters/AddListMoviePostersConnector.js similarity index 94% rename from frontend/src/AddMovie/AddListMovie/Posters/AddListMoviePostersConnector.js rename to frontend/src/DiscoverMovie/Posters/AddListMoviePostersConnector.js index 73fadcdcc..3d83be7c4 100644 --- a/frontend/src/AddMovie/AddListMovie/Posters/AddListMoviePostersConnector.js +++ b/frontend/src/DiscoverMovie/Posters/AddListMoviePostersConnector.js @@ -6,7 +6,7 @@ import AddListMoviePosters from './AddListMoviePosters'; function createMapStateToProps() { return createSelector( - (state) => state.addMovie.posterOptions, + (state) => state.discoverMovie.posterOptions, createUISettingsSelector(), createDimensionsSelector(), (posterOptions, uiSettings, dimensions) => { diff --git a/frontend/src/AddMovie/AddListMovie/Posters/Options/AddListMoviePosterOptionsModal.js b/frontend/src/DiscoverMovie/Posters/Options/AddListMoviePosterOptionsModal.js similarity index 100% rename from frontend/src/AddMovie/AddListMovie/Posters/Options/AddListMoviePosterOptionsModal.js rename to frontend/src/DiscoverMovie/Posters/Options/AddListMoviePosterOptionsModal.js diff --git a/frontend/src/AddMovie/AddListMovie/Posters/Options/AddListMoviePosterOptionsModalContent.js b/frontend/src/DiscoverMovie/Posters/Options/AddListMoviePosterOptionsModalContent.js similarity index 100% rename from frontend/src/AddMovie/AddListMovie/Posters/Options/AddListMoviePosterOptionsModalContent.js rename to frontend/src/DiscoverMovie/Posters/Options/AddListMoviePosterOptionsModalContent.js diff --git a/frontend/src/AddMovie/AddListMovie/Posters/Options/AddListMoviePosterOptionsModalContentConnector.js b/frontend/src/DiscoverMovie/Posters/Options/AddListMoviePosterOptionsModalContentConnector.js similarity index 74% rename from frontend/src/AddMovie/AddListMovie/Posters/Options/AddListMoviePosterOptionsModalContentConnector.js rename to frontend/src/DiscoverMovie/Posters/Options/AddListMoviePosterOptionsModalContentConnector.js index edfc388be..9416f13fc 100644 --- a/frontend/src/AddMovie/AddListMovie/Posters/Options/AddListMoviePosterOptionsModalContentConnector.js +++ b/frontend/src/DiscoverMovie/Posters/Options/AddListMoviePosterOptionsModalContentConnector.js @@ -1,13 +1,13 @@ import { connect } from 'react-redux'; import { createSelector } from 'reselect'; -import { setListMoviePosterOption } from 'Store/Actions/addMovieActions'; +import { setListMoviePosterOption } from 'Store/Actions/discoverMovieActions'; import AddListMoviePosterOptionsModalContent from './AddListMoviePosterOptionsModalContent'; function createMapStateToProps() { return createSelector( - (state) => state.addMovie, - (addMovie) => { - return addMovie.posterOptions; + (state) => state.discoverMovie, + (discoverMovie) => { + return discoverMovie.posterOptions; } ); } diff --git a/frontend/src/AddMovie/AddListMovie/Table/AddListMovieActionsCell.js b/frontend/src/DiscoverMovie/Table/AddListMovieActionsCell.js similarity index 100% rename from frontend/src/AddMovie/AddListMovie/Table/AddListMovieActionsCell.js rename to frontend/src/DiscoverMovie/Table/AddListMovieActionsCell.js diff --git a/frontend/src/AddMovie/AddListMovie/Table/AddListMovieHeader.css b/frontend/src/DiscoverMovie/Table/AddListMovieHeader.css similarity index 100% rename from frontend/src/AddMovie/AddListMovie/Table/AddListMovieHeader.css rename to frontend/src/DiscoverMovie/Table/AddListMovieHeader.css diff --git a/frontend/src/AddMovie/AddListMovie/Table/AddListMovieHeader.js b/frontend/src/DiscoverMovie/Table/AddListMovieHeader.js similarity index 82% rename from frontend/src/AddMovie/AddListMovie/Table/AddListMovieHeader.js rename to frontend/src/DiscoverMovie/Table/AddListMovieHeader.js index 71cb685c8..f189915ae 100644 --- a/frontend/src/AddMovie/AddListMovie/Table/AddListMovieHeader.js +++ b/frontend/src/DiscoverMovie/Table/AddListMovieHeader.js @@ -4,6 +4,7 @@ import { icons } from 'Helpers/Props'; import IconButton from 'Components/Link/IconButton'; import VirtualTableHeader from 'Components/Table/VirtualTableHeader'; import VirtualTableHeaderCell from 'Components/Table/VirtualTableHeaderCell'; +import VirtualTableSelectAllHeaderCell from 'Components/Table/VirtualTableSelectAllHeaderCell'; import TableOptionsModal from 'Components/Table/TableOptions/TableOptionsModal'; import styles from './AddListMovieHeader.css'; @@ -38,11 +39,21 @@ class AddListMovieHeader extends Component { const { columns, onTableOptionChange, + allSelected, + allUnselected, + onSelectAllChange, ...otherProps } = this.props; return ( + + { columns.map((column) => { const { @@ -100,7 +111,10 @@ class AddListMovieHeader extends Component { AddListMovieHeader.propTypes = { columns: PropTypes.arrayOf(PropTypes.object).isRequired, - onTableOptionChange: PropTypes.func.isRequired + onTableOptionChange: PropTypes.func.isRequired, + allSelected: PropTypes.bool.isRequired, + allUnselected: PropTypes.bool.isRequired, + onSelectAllChange: PropTypes.func.isRequired }; export default AddListMovieHeader; diff --git a/frontend/src/AddMovie/AddListMovie/Table/AddListMovieHeaderConnector.js b/frontend/src/DiscoverMovie/Table/AddListMovieHeaderConnector.js similarity index 81% rename from frontend/src/AddMovie/AddListMovie/Table/AddListMovieHeaderConnector.js rename to frontend/src/DiscoverMovie/Table/AddListMovieHeaderConnector.js index 64109721f..f4b9551c8 100644 --- a/frontend/src/AddMovie/AddListMovie/Table/AddListMovieHeaderConnector.js +++ b/frontend/src/DiscoverMovie/Table/AddListMovieHeaderConnector.js @@ -1,5 +1,5 @@ import { connect } from 'react-redux'; -import { setListMovieTableOption } from 'Store/Actions/addMovieActions'; +import { setListMovieTableOption } from 'Store/Actions/discoverMovieActions'; import AddListMovieHeader from './AddListMovieHeader'; function createMapDispatchToProps(dispatch, props) { diff --git a/frontend/src/DiscoverMovie/Table/AddListMovieRow.css b/frontend/src/DiscoverMovie/Table/AddListMovieRow.css new file mode 100644 index 000000000..912c6ce4d --- /dev/null +++ b/frontend/src/DiscoverMovie/Table/AddListMovieRow.css @@ -0,0 +1,65 @@ +.cell { + composes: cell from '~Components/Table/Cells/VirtualTableRowCell.css'; + + display: flex; + align-items: center; +} + +.status { + composes: cell; + + flex: 0 0 60px; +} + +.collection, +.sortTitle { + composes: cell; + + flex: 4 0 110px; +} + +.studio { + composes: cell; + + flex: 2 0 90px; +} + +.inCinemas, +.physicalRelease, +.genres { + composes: cell; + + flex: 0 0 180px; +} + +.movieStatus, +.certification { + composes: cell; + + flex: 0 0 100px; +} + +.ratings { + composes: cell; + + flex: 0 0 80px; +} + +.tags { + composes: cell; + + flex: 1 0 60px; +} + +.actions { + composes: cell; + + flex: 0 1 90px; + min-width: 60px; +} + +.checkInput { + composes: input from '~Components/Form/CheckInput.css'; + + margin-top: 0; +} diff --git a/frontend/src/AddMovie/AddListMovie/Table/AddListMovieRow.js b/frontend/src/DiscoverMovie/Table/AddListMovieRow.js similarity index 69% rename from frontend/src/AddMovie/AddListMovie/Table/AddListMovieRow.js rename to frontend/src/DiscoverMovie/Table/AddListMovieRow.js index 2a96225cc..d8e32f607 100644 --- a/frontend/src/AddMovie/AddListMovie/Table/AddListMovieRow.js +++ b/frontend/src/DiscoverMovie/Table/AddListMovieRow.js @@ -1,11 +1,15 @@ import PropTypes from 'prop-types'; import React, { Component } from 'react'; +import { icons } from 'Helpers/Props'; import HeartRating from 'Components/HeartRating'; +import IconButton from 'Components/Link/IconButton'; import VirtualTableRowCell from 'Components/Table/Cells/VirtualTableRowCell'; import RelativeDateCellConnector from 'Components/Table/Cells/RelativeDateCellConnector'; -import MovieStatusCell from './MovieStatusCell'; +import ListMovieStatusCell from './ListMovieStatusCell'; import Link from 'Components/Link/Link'; -import AddNewMovieModal from 'AddMovie/AddNewMovie/AddNewMovieModal'; +import AddNewDiscoverMovieModal from 'DiscoverMovie/AddNewDiscoverMovieModal'; +import ExcludeMovieModal from 'DiscoverMovie/Exclusion/ExcludeMovieModal'; +import VirtualTableSelectCell from 'Components/Table/Cells/VirtualTableSelectCell'; import styles from './AddListMovieRow.css'; class AddListMovieRow extends Component { @@ -17,14 +21,15 @@ class AddListMovieRow extends Component { super(props, context); this.state = { - isNewAddMovieModalOpen: false + isNewAddMovieModalOpen: false, + isExcludeMovieModalOpen: false }; } // // Listeners - onPress = () => { + onAddMoviePress = () => { this.setState({ isNewAddMovieModalOpen: true }); } @@ -32,6 +37,14 @@ class AddListMovieRow extends Component { this.setState({ isNewAddMovieModalOpen: false }); } + onExcludeMoviePress = () => { + this.setState({ isExcludeMovieModalOpen: true }); + } + + onExcludeMovieModalClose = () => { + this.setState({ isExcludeMovieModalOpen: false }); + } + // // Render @@ -52,17 +65,30 @@ class AddListMovieRow extends Component { ratings, certification, columns, - isExistingMovie + isExisting, + isExcluded, + isSelected, + onSelectedChange } = this.props; const { - isNewAddMovieModalOpen + isNewAddMovieModalOpen, + isExcludeMovieModalOpen } = this.state; - const linkProps = isExistingMovie ? { to: `/movie/${titleSlug}` } : { onPress: this.onPress }; + const linkProps = isExisting ? { to: `/movie/${titleSlug}` } : { onPress: this.onAddMoviePress }; return ( <> + + { columns.map((column) => { const { @@ -76,10 +102,11 @@ class AddListMovieRow extends Component { if (name === 'status') { return ( - ); @@ -172,12 +199,28 @@ class AddListMovieRow extends Component { ); } + if (name === 'actions') { + return ( + + + + ); + } + return null; }) } - + + ); } @@ -207,7 +258,10 @@ AddListMovieRow.propTypes = { ratings: PropTypes.object.isRequired, certification: PropTypes.string, columns: PropTypes.arrayOf(PropTypes.object).isRequired, - isExistingMovie: PropTypes.bool.isRequired + isExisting: PropTypes.bool.isRequired, + isExcluded: PropTypes.bool.isRequired, + isSelected: PropTypes.bool, + onSelectedChange: PropTypes.func.isRequired }; AddListMovieRow.defaultProps = { diff --git a/frontend/src/AddMovie/AddListMovie/Table/AddListMovieRowConnector.js b/frontend/src/DiscoverMovie/Table/AddListMovieRowConnector.js similarity index 56% rename from frontend/src/AddMovie/AddListMovie/Table/AddListMovieRowConnector.js rename to frontend/src/DiscoverMovie/Table/AddListMovieRowConnector.js index 684b7efc9..b00c42737 100644 --- a/frontend/src/AddMovie/AddListMovie/Table/AddListMovieRowConnector.js +++ b/frontend/src/DiscoverMovie/Table/AddListMovieRowConnector.js @@ -1,19 +1,13 @@ import { connect } from 'react-redux'; import { createSelector } from 'reselect'; -import createExistingMovieSelector from 'Store/Selectors/createExistingMovieSelector'; -import createExclusionMovieSelector from 'Store/Selectors/createExclusionMovieSelector'; import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector'; import AddListMovieRow from './AddListMovieRow'; function createMapStateToProps() { return createSelector( - createExistingMovieSelector(), - createExclusionMovieSelector(), createDimensionsSelector(), - (isExistingMovie, isExclusionMovie, dimensions) => { + (dimensions) => { return { - isExistingMovie, - isExclusionMovie, isSmallScreen: dimensions.isSmallScreen }; } diff --git a/frontend/src/AddMovie/AddListMovie/Table/AddListMovieTable.css b/frontend/src/DiscoverMovie/Table/AddListMovieTable.css similarity index 100% rename from frontend/src/AddMovie/AddListMovie/Table/AddListMovieTable.css rename to frontend/src/DiscoverMovie/Table/AddListMovieTable.css diff --git a/frontend/src/AddMovie/AddListMovie/Table/AddListMovieTable.js b/frontend/src/DiscoverMovie/Table/AddListMovieTable.js similarity index 74% rename from frontend/src/AddMovie/AddListMovie/Table/AddListMovieTable.js rename to frontend/src/DiscoverMovie/Table/AddListMovieTable.js index 8d8faf42e..5278ec69a 100644 --- a/frontend/src/AddMovie/AddListMovie/Table/AddListMovieTable.js +++ b/frontend/src/DiscoverMovie/Table/AddListMovieTable.js @@ -4,7 +4,7 @@ import getIndexOfFirstCharacter from 'Utilities/Array/getIndexOfFirstCharacter'; import { sortDirections } from 'Helpers/Props'; import VirtualTable from 'Components/Table/VirtualTable'; import VirtualTableRow from 'Components/Table/VirtualTableRow'; -import AddListMovieItemConnector from 'AddMovie/AddListMovie/AddListMovieItemConnector'; +import AddListMovieItemConnector from 'DiscoverMovie/AddListMovieItemConnector'; import AddListMovieHeaderConnector from './AddListMovieHeaderConnector'; import AddListMovieRowConnector from './AddListMovieRowConnector'; import styles from './AddListMovieTable.css'; @@ -46,7 +46,9 @@ class AddListMovieTable extends Component { rowRenderer = ({ key, rowIndex, style }) => { const { items, - columns + columns, + selectedState, + onSelectedChange } = this.props; const movie = items[rowIndex]; @@ -57,10 +59,12 @@ class AddListMovieTable extends Component { style={style} > ); @@ -75,8 +79,13 @@ class AddListMovieTable extends Component { columns, sortKey, sortDirection, + isSmallScreen, + onSortPress, scroller, - onSortPress + allSelected, + allUnselected, + onSelectAllChange, + selectedState } = this.props; return ( @@ -84,6 +93,7 @@ class AddListMovieTable extends Component { className={styles.tableContainer} items={items} scrollIndex={this.state.scrollIndex} + isSmallScreen={isSmallScreen} scroller={scroller} rowHeight={38} overscanRowCount={2} @@ -94,8 +104,12 @@ class AddListMovieTable extends Component { sortKey={sortKey} sortDirection={sortDirection} onSortPress={onSortPress} + allSelected={allSelected} + allUnselected={allUnselected} + onSelectAllChange={onSelectAllChange} /> } + selectedState={selectedState} columns={columns} /> ); @@ -108,8 +122,14 @@ AddListMovieTable.propTypes = { sortKey: PropTypes.string, sortDirection: PropTypes.oneOf(sortDirections.all), jumpToCharacter: PropTypes.string, + isSmallScreen: PropTypes.bool.isRequired, scroller: PropTypes.instanceOf(Element).isRequired, - onSortPress: PropTypes.func.isRequired + onSortPress: PropTypes.func.isRequired, + allSelected: PropTypes.bool.isRequired, + allUnselected: PropTypes.bool.isRequired, + selectedState: PropTypes.object.isRequired, + onSelectedChange: PropTypes.func.isRequired, + onSelectAllChange: PropTypes.func.isRequired }; export default AddListMovieTable; diff --git a/frontend/src/AddMovie/AddListMovie/Table/AddListMovieTableConnector.js b/frontend/src/DiscoverMovie/Table/AddListMovieTableConnector.js similarity index 84% rename from frontend/src/AddMovie/AddListMovie/Table/AddListMovieTableConnector.js rename to frontend/src/DiscoverMovie/Table/AddListMovieTableConnector.js index f0988c2d9..f1ca887a6 100644 --- a/frontend/src/AddMovie/AddListMovie/Table/AddListMovieTableConnector.js +++ b/frontend/src/DiscoverMovie/Table/AddListMovieTableConnector.js @@ -1,12 +1,12 @@ import { connect } from 'react-redux'; import { createSelector } from 'reselect'; -import { setListMovieSort } from 'Store/Actions/addMovieActions'; +import { setListMovieSort } from 'Store/Actions/discoverMovieActions'; import AddListMovieTable from './AddListMovieTable'; function createMapStateToProps() { return createSelector( (state) => state.app.dimensions, - (state) => state.addMovie.columns, + (state) => state.discoverMovie.columns, (dimensions, columns) => { return { isSmallScreen: dimensions.isSmallScreen, diff --git a/frontend/src/AddMovie/AddListMovie/Table/MovieStatusCell.css b/frontend/src/DiscoverMovie/Table/ListMovieStatusCell.css similarity index 76% rename from frontend/src/AddMovie/AddListMovie/Table/MovieStatusCell.css rename to frontend/src/DiscoverMovie/Table/ListMovieStatusCell.css index fbcd5eee9..441cd6669 100644 --- a/frontend/src/AddMovie/AddListMovie/Table/MovieStatusCell.css +++ b/frontend/src/DiscoverMovie/Table/ListMovieStatusCell.css @@ -7,3 +7,7 @@ .statusIcon { width: 20px !important; } + +.exclusionIcon { + color: $dangerColor; +} diff --git a/frontend/src/DiscoverMovie/Table/ListMovieStatusCell.js b/frontend/src/DiscoverMovie/Table/ListMovieStatusCell.js new file mode 100644 index 000000000..f161b7424 --- /dev/null +++ b/frontend/src/DiscoverMovie/Table/ListMovieStatusCell.js @@ -0,0 +1,56 @@ +import PropTypes from 'prop-types'; +import React from 'react'; +import { icons } from 'Helpers/Props'; +import Icon from 'Components/Icon'; +import VirtualTableRowCell from 'Components/Table/Cells/TableRowCell'; +import { getMovieStatusDetails } from 'Movie/MovieStatus'; +import styles from './ListMovieStatusCell.css'; + +function ListMovieStatusCell(props) { + const { + className, + status, + isExclusion, + component: Component, + ...otherProps + } = props; + + const statusDetails = getMovieStatusDetails(status); + + return ( + + + + { + isExclusion ? + : null + } + + + ); +} + +ListMovieStatusCell.propTypes = { + className: PropTypes.string.isRequired, + status: PropTypes.string.isRequired, + isExclusion: PropTypes.bool.isRequired, + component: PropTypes.elementType +}; + +ListMovieStatusCell.defaultProps = { + className: styles.status, + component: VirtualTableRowCell +}; + +export default ListMovieStatusCell; diff --git a/frontend/src/Movie/Index/Posters/MovieIndexPoster.css b/frontend/src/Movie/Index/Posters/MovieIndexPoster.css index 65f34c4c8..7dddf9b37 100644 --- a/frontend/src/Movie/Index/Posters/MovieIndexPoster.css +++ b/frontend/src/Movie/Index/Posters/MovieIndexPoster.css @@ -60,6 +60,7 @@ $hoverScale: 1.05; position: absolute; top: 0; right: 0; + z-index: 1; width: 0; height: 0; border-width: 0 25px 25px 0; diff --git a/frontend/src/Movie/Index/Posters/MovieIndexPosters.css b/frontend/src/Movie/Index/Posters/MovieIndexPosters.css index d80f951a0..9c6520fb5 100644 --- a/frontend/src/Movie/Index/Posters/MovieIndexPosters.css +++ b/frontend/src/Movie/Index/Posters/MovieIndexPosters.css @@ -1,7 +1,3 @@ .grid { flex: 1 0 auto; } - -.container { - padding: 10px; -} diff --git a/frontend/src/Movie/Index/Posters/MovieIndexPosters.js b/frontend/src/Movie/Index/Posters/MovieIndexPosters.js index 5f7eddb8e..606a298f1 100644 --- a/frontend/src/Movie/Index/Posters/MovieIndexPosters.js +++ b/frontend/src/Movie/Index/Posters/MovieIndexPosters.js @@ -104,6 +104,7 @@ class MovieIndexPosters extends Component { this._isInitialized = false; this._grid = null; + this._padding = props.isSmallScreen ? columnPaddingSmallScreen : columnPadding; } componentDidUpdate(prevProps, prevState) { @@ -112,6 +113,7 @@ class MovieIndexPosters extends Component { sortKey, posterOptions, jumpToCharacter, + isSmallScreen, isMovieEditorActive } = this.props; @@ -124,7 +126,7 @@ class MovieIndexPosters extends Component { if (prevProps.sortKey !== sortKey || prevProps.posterOptions !== posterOptions) { - this.calculateGrid(); + this.calculateGrid(width, isSmallScreen); } if (this._grid && @@ -165,10 +167,9 @@ class MovieIndexPosters extends Component { posterOptions } = this.props; - const padding = isSmallScreen ? columnPaddingSmallScreen : columnPadding; const columnWidth = calculateColumnWidth(width, posterOptions.size, isSmallScreen); const columnCount = Math.max(Math.floor(width / columnWidth), 1); - const posterWidth = columnWidth - padding; + const posterWidth = columnWidth - this._padding * 2; const posterHeight = calculatePosterHeight(posterWidth); const rowHeight = calculateRowHeight(posterHeight, sortKey, isSmallScreen, posterOptions); @@ -219,7 +220,10 @@ class MovieIndexPosters extends Component {
{ - acc.push({ - id: movie.studio, - name: movie.studio - }); - - return acc; - }, []); - - return tagList.sort(sortByName); - } - }, - { - name: 'inCinemas', - label: 'In Cinemas', - type: filterBuilderTypes.DATE, - valueType: filterBuilderValueTypes.DATE - }, - { - name: 'physicalRelease', - label: 'Physical Release', - type: filterBuilderTypes.DATE, - valueType: filterBuilderValueTypes.DATE - }, - { - name: 'genres', - label: 'Genres', - type: filterBuilderTypes.ARRAY, - optionsSelector: function(items) { - const tagList = items.reduce((acc, movie) => { - movie.genres.forEach((genre) => { - acc.push({ - id: genre, - name: genre - }); - }); - - return acc; - }, []); - - return tagList.sort(sortByName); - } - }, - { - name: 'ratings', - label: 'Rating', - type: filterBuilderTypes.NUMBER - }, - { - name: 'certification', - label: 'Certification', - type: filterBuilderTypes.EXACT - }, - { - name: 'tags', - label: 'Tags', - type: filterBuilderTypes.ARRAY, - valueType: filterBuilderValueTypes.TAG - } - ] + } }; export const persistState = [ - 'addMovie.defaults', - 'addMovie.sortKey', - 'addMovie.sortDirection', - 'addMovie.selectedFilterKey', - 'addMovie.customFilters', - 'addMovie.view', - 'addMovie.columns', - 'addMovie.posterOptions', - 'addMovie.overviewOptions', - 'addMovie.tableOptions' + 'addMovie.defaults' ]; // @@ -254,16 +50,6 @@ export const SET_ADD_MOVIE_VALUE = 'addMovie/setAddMovieValue'; export const CLEAR_ADD_MOVIE = 'addMovie/clearAddMovie'; export const SET_ADD_MOVIE_DEFAULT = 'addMovie/setAddMovieDefault'; -export const FETCH_LIST_MOVIES = 'addMovie/fetchListMovies'; -export const FETCH_DISCOVER_MOVIES = 'addMovie/fetchDiscoverMovies'; - -export const SET_LIST_MOVIE_SORT = 'addMovie/setListMovieSort'; -export const SET_LIST_MOVIE_FILTER = 'addMovie/setListMovieFilter'; -export const SET_LIST_MOVIE_VIEW = 'addMovie/setListMovieView'; -export const SET_LIST_MOVIE_TABLE_OPTION = 'addMovie/setListMovieTableOption'; -export const SET_LIST_MOVIE_POSTER_OPTION = 'addMovie/setListMoviePosterOption'; -export const SET_LIST_MOVIE_OVERVIEW_OPTION = 'addMovie/setListMovieOverviewOption'; - // // Action Creators @@ -272,16 +58,6 @@ export const addMovie = createThunk(ADD_MOVIE); export const clearAddMovie = createAction(CLEAR_ADD_MOVIE); export const setAddMovieDefault = createAction(SET_ADD_MOVIE_DEFAULT); -export const fetchListMovies = createThunk(FETCH_LIST_MOVIES); -export const fetchDiscoverMovies = createThunk(FETCH_DISCOVER_MOVIES); - -export const setListMovieSort = createAction(SET_LIST_MOVIE_SORT); -export const setListMovieFilter = createAction(SET_LIST_MOVIE_FILTER); -export const setListMovieView = createAction(SET_LIST_MOVIE_VIEW); -export const setListMovieTableOption = createAction(SET_LIST_MOVIE_TABLE_OPTION); -export const setListMoviePosterOption = createAction(SET_LIST_MOVIE_POSTER_OPTION); -export const setListMovieOverviewOption = createAction(SET_LIST_MOVIE_OVERVIEW_OPTION); - export const setAddMovieValue = createAction(SET_ADD_MOVIE_VALUE, (payload) => { return { section, @@ -294,10 +70,6 @@ export const setAddMovieValue = createAction(SET_ADD_MOVIE_VALUE, (payload) => { export const actionHandlers = handleThunks({ - [FETCH_LIST_MOVIES]: createFetchHandler(section, '/netimport/movies'), - - [FETCH_DISCOVER_MOVIES]: createFetchHandler(section, '/movies/discover'), - [LOOKUP_MOVIE]: function(getState, payload, dispatch) { dispatch(set({ section, isFetching: true })); @@ -393,54 +165,14 @@ export const reducers = createHandleActions({ return updateSectionState(state, section, newState); }, - [SET_LIST_MOVIE_SORT]: createSetClientSideCollectionSortReducer(section), - [SET_LIST_MOVIE_FILTER]: createSetClientSideCollectionFilterReducer(section), + [CLEAR_ADD_MOVIE]: function(state) { + const { + defaults, + view, + ...otherDefaultState + } = defaultState; - [SET_LIST_MOVIE_VIEW]: function(state, { payload }) { - return Object.assign({}, state, { view: payload.view }); - }, - - [SET_LIST_MOVIE_TABLE_OPTION]: createSetTableOptionReducer(section), - - [SET_LIST_MOVIE_POSTER_OPTION]: function(state, { payload }) { - const posterOptions = state.posterOptions; - - return { - ...state, - posterOptions: { - ...posterOptions, - ...payload - } - }; - }, - - [SET_LIST_MOVIE_OVERVIEW_OPTION]: function(state, { payload }) { - const overviewOptions = state.overviewOptions; - - return { - ...state, - overviewOptions: { - ...overviewOptions, - ...payload - } - }; - }, - - // [CLEAR_ADD_MOVIE]: function(state) { - // const { - // defaults, - // view, - // ...otherDefaultState - // } = defaultState; - - // return Object.assign({}, state, otherDefaultState); - // } - - [CLEAR_ADD_MOVIE]: createClearReducer(section, { - isFetching: false, - isPopulated: false, - error: null, - items: [] - }) + return Object.assign({}, state, otherDefaultState); + } }, defaultState, section); diff --git a/frontend/src/Store/Actions/discoverMovieActions.js b/frontend/src/Store/Actions/discoverMovieActions.js new file mode 100644 index 000000000..989bbd1ec --- /dev/null +++ b/frontend/src/Store/Actions/discoverMovieActions.js @@ -0,0 +1,584 @@ +import _ from 'lodash'; +import { createAction } from 'redux-actions'; +import { batchActions } from 'redux-batched-actions'; +import getSectionState from 'Utilities/State/getSectionState'; +import updateSectionState from 'Utilities/State/updateSectionState'; +import createAjaxRequest from 'Utilities/createAjaxRequest'; +import getNewMovie from 'Utilities/Movie/getNewMovie'; +import { filterBuilderTypes, filterBuilderValueTypes, filterTypes, sortDirections } from 'Helpers/Props'; +import sortByName from 'Utilities/Array/sortByName'; +import { createThunk, handleThunks } from 'Store/thunks'; +import createSetSettingValueReducer from './Creators/Reducers/createSetSettingValueReducer'; +import createHandleActions from './Creators/createHandleActions'; +import { set, updateItem, removeItem } from './baseActions'; +import { filterPredicates } from './movieActions'; +import createSetTableOptionReducer from './Creators/Reducers/createSetTableOptionReducer'; +import createSetClientSideCollectionSortReducer from './Creators/Reducers/createSetClientSideCollectionSortReducer'; +import createSetClientSideCollectionFilterReducer from './Creators/Reducers/createSetClientSideCollectionFilterReducer'; +import createClearReducer from './Creators/Reducers/createClearReducer'; + +// +// Variables + +export const section = 'discoverMovie'; + +// +// State + +export const defaultState = { + isFetching: false, + isPopulated: false, + error: null, + isAdding: false, + isAdded: false, + addError: null, + items: [], + sortKey: 'sortTitle', + sortDirection: sortDirections.ASCENDING, + secondarySortKey: 'sortTitle', + secondarySortDirection: sortDirections.ASCENDING, + view: 'overview', + + defaults: { + rootFolderPath: '', + monitor: 'true', + qualityProfileId: 0, + minimumAvailability: 'announced', + tags: [] + }, + + posterOptions: { + size: 'large', + showTitle: false + }, + + overviewOptions: { + detailedProgressBar: false, + size: 'medium', + showStudio: true + }, + + tableOptions: { + // showSearchAction: false + }, + + columns: [ + { + name: 'status', + columnLabel: 'Status', + isSortable: true, + isVisible: true, + isModifiable: false + }, + { + name: 'sortTitle', + label: 'Movie Title', + isSortable: true, + isVisible: true, + isModifiable: false + }, + { + name: 'studio', + label: 'Studio', + isSortable: true, + isVisible: true + }, + { + name: 'inCinemas', + label: 'In Cinemas', + isSortable: true, + isVisible: true + }, + { + name: 'physicalRelease', + label: 'Physical Release', + isSortable: true, + isVisible: false + }, + { + name: 'genres', + label: 'Genres', + isSortable: false, + isVisible: false + }, + { + name: 'ratings', + label: 'Rating', + isSortable: true, + isVisible: false + }, + { + name: 'certification', + label: 'Certification', + isSortable: true, + isVisible: false + }, + { + name: 'actions', + columnLabel: 'Actions', + isVisible: true, + isModifiable: false + } + ], + + sortPredicates: { + status: function(item) { + let result = 0; + + if (item.isExcluded) { + result += 4; + } + + if (item.status === 'announced') { + result++; + } + + if (item.status === 'inCinemas') { + result += 2; + } + + if (item.status === 'released') { + result += 3; + } + + return result; + }, + + studio: function(item) { + const studio = item.studio; + + return studio ? studio.toLowerCase() : ''; + }, + + ratings: function(item) { + const { ratings = {} } = item; + + return ratings.value; + } + }, + + selectedFilterKey: 'newNotExcluded', + + filters: [ + { + key: 'all', + label: 'All', + filters: [] + }, + { + key: 'newNotExcluded', + label: 'New Non-Excluded', + filters: [ + { + key: 'isExisting', + value: false, + type: filterTypes.EQUAL + }, + { + key: 'isExcluded', + value: false, + type: filterTypes.EQUAL + } + ] + } + ], + + filterPredicates, + + filterBuilderProps: [ + { + name: 'status', + label: 'Status', + type: filterBuilderTypes.EXACT, + valueType: filterBuilderValueTypes.MOVIE_STATUS + }, + { + name: 'studio', + label: 'Studio', + type: filterBuilderTypes.ARRAY, + optionsSelector: function(items) { + const tagList = items.reduce((acc, movie) => { + acc.push({ + id: movie.studio, + name: movie.studio + }); + + return acc; + }, []); + + return tagList.sort(sortByName); + } + }, + { + name: 'inCinemas', + label: 'In Cinemas', + type: filterBuilderTypes.DATE, + valueType: filterBuilderValueTypes.DATE + }, + { + name: 'physicalRelease', + label: 'Physical Release', + type: filterBuilderTypes.DATE, + valueType: filterBuilderValueTypes.DATE + }, + { + name: 'genres', + label: 'Genres', + type: filterBuilderTypes.ARRAY, + optionsSelector: function(items) { + const tagList = items.reduce((acc, movie) => { + movie.genres.forEach((genre) => { + acc.push({ + id: genre, + name: genre + }); + }); + + return acc; + }, []); + + return tagList.sort(sortByName); + } + }, + { + name: 'ratings', + label: 'Rating', + type: filterBuilderTypes.NUMBER + }, + { + name: 'certification', + label: 'Certification', + type: filterBuilderTypes.EXACT + }, + { + name: 'isExcluded', + label: 'On Excluded List', + type: filterBuilderTypes.EXACT, + valueType: filterBuilderValueTypes.BOOL + }, + { + name: 'isExisting', + label: 'Exists in Library', + type: filterBuilderTypes.EXACT, + valueType: filterBuilderValueTypes.BOOL + } + ] +}; + +export const persistState = [ + 'discoverMovie.defaults', + 'discoverMovie.sortKey', + 'discoverMovie.sortDirection', + 'discoverMovie.selectedFilterKey', + 'discoverMovie.customFilters', + 'discoverMovie.view', + 'discoverMovie.columns', + 'discoverMovie.posterOptions', + 'discoverMovie.overviewOptions', + 'discoverMovie.tableOptions' +]; + +// +// Actions Types + +export const ADD_MOVIE = 'discoverMovie/addMovie'; +export const ADD_MOVIES = 'discoverMovie/addMovies'; +export const SET_ADD_MOVIE_VALUE = 'discoverMovie/setAddMovieValue'; +export const CLEAR_ADD_MOVIE = 'discoverMovie/clearAddMovie'; +export const SET_ADD_MOVIE_DEFAULT = 'discoverMovie/setAddMovieDefault'; + +export const FETCH_DISCOVER_MOVIES = 'discoverMovie/fetchDiscoverMovies'; + +export const SET_LIST_MOVIE_SORT = 'discoverMovie/setListMovieSort'; +export const SET_LIST_MOVIE_FILTER = 'discoverMovie/setListMovieFilter'; +export const SET_LIST_MOVIE_VIEW = 'discoverMovie/setListMovieView'; +export const SET_LIST_MOVIE_TABLE_OPTION = 'discoverMovie/setListMovieTableOption'; +export const SET_LIST_MOVIE_POSTER_OPTION = 'discoverMovie/setListMoviePosterOption'; +export const SET_LIST_MOVIE_OVERVIEW_OPTION = 'discoverMovie/setListMovieOverviewOption'; + +export const ADD_NET_IMPORT_EXCLUSIONS = 'discoverMovie/addNetImportExclusions'; + +// +// Action Creators + +export const addMovie = createThunk(ADD_MOVIE); +export const addMovies = createThunk(ADD_MOVIES); +export const clearAddMovie = createAction(CLEAR_ADD_MOVIE); +export const setAddMovieDefault = createAction(SET_ADD_MOVIE_DEFAULT); + +export const fetchDiscoverMovies = createThunk(FETCH_DISCOVER_MOVIES); + +export const setListMovieSort = createAction(SET_LIST_MOVIE_SORT); +export const setListMovieFilter = createAction(SET_LIST_MOVIE_FILTER); +export const setListMovieView = createAction(SET_LIST_MOVIE_VIEW); +export const setListMovieTableOption = createAction(SET_LIST_MOVIE_TABLE_OPTION); +export const setListMoviePosterOption = createAction(SET_LIST_MOVIE_POSTER_OPTION); +export const setListMovieOverviewOption = createAction(SET_LIST_MOVIE_OVERVIEW_OPTION); + +export const addNetImportExclusions = createThunk(ADD_NET_IMPORT_EXCLUSIONS); + +export const setAddMovieValue = createAction(SET_ADD_MOVIE_VALUE, (payload) => { + return { + section, + ...payload + }; +}); + +// +// Action Handlers + +export const actionHandlers = handleThunks({ + + [FETCH_DISCOVER_MOVIES]: function(getState, payload, dispatch) { + dispatch(set({ section, isFetching: true })); + + const { + id, + ...otherPayload + } = payload; + + const promise = createAjaxRequest({ + url: '/movies/discover', + data: otherPayload, + traditional: true + }).request; + + promise.done((data) => { + // set an Id so the selectors and updaters done blow up. + data = data.map((movie) => ({ ...movie, id: movie.tmdbId })); + + dispatch(batchActions([ + ...data.map((movie) => updateItem({ section, ...movie })), + + set({ + section, + isFetching: false, + isPopulated: true, + error: null + }) + ])); + }); + + promise.fail((xhr) => { + dispatch(set({ + section, + isFetching: false, + isPopulated: false, + error: xhr.aborted ? null : xhr + })); + }); + }, + + [ADD_MOVIE]: function(getState, payload, dispatch) { + dispatch(set({ section, isAdding: true })); + + const tmdbId = payload.tmdbId; + const items = getState().discoverMovie.items; + const itemToUpdate = _.find(items, { tmdbId }); + + const newMovie = getNewMovie(_.cloneDeep(itemToUpdate), payload); + newMovie.id = 0; + + const promise = createAjaxRequest({ + url: '/movie', + method: 'POST', + contentType: 'application/json', + data: JSON.stringify(newMovie) + }).request; + + promise.done((data) => { + dispatch(batchActions([ + updateItem({ section: 'movies', ...data }), + + removeItem({ section: 'discoverMovie', ...itemToUpdate }), + + set({ + section, + isAdding: false, + isAdded: true, + addError: null + }) + ])); + }); + + promise.fail((xhr) => { + dispatch(set({ + section, + isAdding: false, + isAdded: false, + addError: xhr + })); + }); + }, + + [ADD_MOVIES]: function(getState, payload, dispatch) { + dispatch(set({ section, isAdding: true })); + + const ids = payload.ids; + const addOptions = payload.addOptions; + const items = getState().discoverMovie.items; + const addedIds = []; + + const allNewMovies = ids.reduce((acc, id) => { + const item = items.find((i) => i.id === id); + const selectedMovie = item; + + // Make sure we have a selected movie and + // the same movie hasn't been added yet. + if (selectedMovie && !acc.some((a) => a.tmdbId === selectedMovie.tmdbId)) { + const newMovie = getNewMovie(_.cloneDeep(selectedMovie), addOptions); + newMovie.id = 0; + + addedIds.push(id); + acc.push(newMovie); + } + + return acc; + }, []); + + const promise = createAjaxRequest({ + url: '/movie/import', + method: 'POST', + contentType: 'application/json', + data: JSON.stringify(allNewMovies) + }).request; + + promise.done((data) => { + dispatch(batchActions([ + set({ + section, + isAdding: false, + isAdded: true + }), + + ...data.map((movie) => updateItem({ section: 'movies', ...movie })), + + ...addedIds.map((id) => removeItem({ section, id })) + ])); + }); + + promise.fail((xhr) => { + dispatch(batchActions( + set({ + section, + isImporting: false, + isImported: true + }), + + addedIds.map((id) => updateItem({ + section, + id, + importError: xhr + })) + )); + }); + }, + + [ADD_NET_IMPORT_EXCLUSIONS]: function(getState, payload, dispatch) { + + const ids = payload.ids; + const items = getState().discoverMovie.items; + + const exclusions = ids.reduce((acc, id) => { + const item = items.find((i) => i.tmdbId === id); + + const newExclusion = { + tmdbId: id, + movieTitle: item.title, + movieYear: item.year + }; + + acc.push(newExclusion); + + return acc; + }, []); + + const promise = createAjaxRequest({ + url: '/exclusions', + method: 'POST', + data: JSON.stringify(exclusions) + }).request; + + promise.done((data) => { + dispatch(batchActions([ + ...data.map((item) => updateItem({ section: 'settings.netImportExclusions', ...item })), + + ...data.map((item) => updateItem({ section, id: item.tmdbId, isExcluded: true })), + + set({ + section, + isSaving: false, + saveError: null + }) + ])); + }); + + promise.fail((xhr) => { + dispatch(set({ + section, + isSaving: false, + saveError: xhr + })); + }); + } +}); + +// +// Reducers + +export const reducers = createHandleActions({ + + [SET_ADD_MOVIE_VALUE]: createSetSettingValueReducer(section), + + [SET_ADD_MOVIE_DEFAULT]: function(state, { payload }) { + const newState = getSectionState(state, section); + + newState.defaults = { + ...newState.defaults, + ...payload + }; + + return updateSectionState(state, section, newState); + }, + + [SET_LIST_MOVIE_SORT]: createSetClientSideCollectionSortReducer(section), + [SET_LIST_MOVIE_FILTER]: createSetClientSideCollectionFilterReducer(section), + + [SET_LIST_MOVIE_VIEW]: function(state, { payload }) { + return Object.assign({}, state, { view: payload.view }); + }, + + [SET_LIST_MOVIE_TABLE_OPTION]: createSetTableOptionReducer(section), + + [SET_LIST_MOVIE_POSTER_OPTION]: function(state, { payload }) { + const posterOptions = state.posterOptions; + + return { + ...state, + posterOptions: { + ...posterOptions, + ...payload + } + }; + }, + + [SET_LIST_MOVIE_OVERVIEW_OPTION]: function(state, { payload }) { + const overviewOptions = state.overviewOptions; + + return { + ...state, + overviewOptions: { + ...overviewOptions, + ...payload + } + }; + }, + + [CLEAR_ADD_MOVIE]: createClearReducer(section, { + isFetching: false, + isPopulated: false, + error: null, + items: [] + }) + +}, defaultState, section); diff --git a/frontend/src/Store/Actions/index.js b/frontend/src/Store/Actions/index.js index 03d201680..281739940 100644 --- a/frontend/src/Store/Actions/index.js +++ b/frontend/src/Store/Actions/index.js @@ -5,6 +5,7 @@ import * as calendar from './calendarActions'; import * as captcha from './captchaActions'; import * as customFilters from './customFilterActions'; import * as commands from './commandActions'; +import * as discoverMovie from './discoverMovieActions'; import * as movieFiles from './movieFileActions'; import * as extraFiles from './extraFileActions'; import * as history from './historyActions'; @@ -33,6 +34,7 @@ export default [ captcha, commands, customFilters, + discoverMovie, movieFiles, extraFiles, history, diff --git a/frontend/src/Store/Selectors/createAddMovieClientSideCollectionItemsSelector.js b/frontend/src/Store/Selectors/createDiscoverMovieClientSideCollectionItemsSelector.js similarity index 83% rename from frontend/src/Store/Selectors/createAddMovieClientSideCollectionItemsSelector.js rename to frontend/src/Store/Selectors/createDiscoverMovieClientSideCollectionItemsSelector.js index 24ec81bbe..04fcd4f03 100644 --- a/frontend/src/Store/Selectors/createAddMovieClientSideCollectionItemsSelector.js +++ b/frontend/src/Store/Selectors/createDiscoverMovieClientSideCollectionItemsSelector.js @@ -26,11 +26,11 @@ function createUnoptimizedSelector(uiSection) { ); } -function createAddMovieClientSideCollectionItemsSelector(uiSection) { +function createDiscoverMovieClientSideCollectionItemsSelector(uiSection) { return createDeepEqualSelector( createUnoptimizedSelector(uiSection), (movies) => movies ); } -export default createAddMovieClientSideCollectionItemsSelector; +export default createDiscoverMovieClientSideCollectionItemsSelector; diff --git a/frontend/src/Store/Selectors/createAddListMovieSelector.js b/frontend/src/Store/Selectors/createDiscoverMovieSelector.js similarity index 64% rename from frontend/src/Store/Selectors/createAddListMovieSelector.js rename to frontend/src/Store/Selectors/createDiscoverMovieSelector.js index 99fd1b39e..da5bc282a 100644 --- a/frontend/src/Store/Selectors/createAddListMovieSelector.js +++ b/frontend/src/Store/Selectors/createDiscoverMovieSelector.js @@ -1,13 +1,13 @@ import { createSelector } from 'reselect'; -function createAddListMovieSelector() { +function createDiscoverMovieSelector() { return createSelector( (state, { movieId }) => movieId, - (state) => state.addMovie, + (state) => state.discoverMovie, (movieId, allMovies) => { return allMovies.items.find((movie) => movie.tmdbId === movieId); } ); } -export default createAddListMovieSelector; +export default createDiscoverMovieSelector; diff --git a/frontend/src/Store/scrollPositions.js b/frontend/src/Store/scrollPositions.js index 5e822c568..99a558b4d 100644 --- a/frontend/src/Store/scrollPositions.js +++ b/frontend/src/Store/scrollPositions.js @@ -1,6 +1,6 @@ const scrollPositions = { movieIndex: 0, - addMovie: 0 + discoverMovie: 0 }; export default scrollPositions; diff --git a/frontend/src/Styles/Variables/dimensions.js b/frontend/src/Styles/Variables/dimensions.js index c59846531..a277f51dc 100644 --- a/frontend/src/Styles/Variables/dimensions.js +++ b/frontend/src/Styles/Variables/dimensions.js @@ -47,7 +47,7 @@ module.exports = { modalBodyPadding: '30px', // Movie - movieIndexColumnPadding: '20px', - movieIndexColumnPaddingSmallScreen: '10px', + movieIndexColumnPadding: '10px', + movieIndexColumnPaddingSmallScreen: '5px', movieIndexOverviewInfoRowHeight: '21px' }; diff --git a/frontend/src/Utilities/Table/toggleSelected.js b/frontend/src/Utilities/Table/toggleSelected.js index dbc0d6223..d03dcef36 100644 --- a/frontend/src/Utilities/Table/toggleSelected.js +++ b/frontend/src/Utilities/Table/toggleSelected.js @@ -1,7 +1,7 @@ import areAllSelected from './areAllSelected'; import getToggledRange from './getToggledRange'; -function toggleSelected(state, items, id, selected, shiftKey) { +function toggleSelected(state, items, id, selected, shiftKey, idProp = 'id') { const lastToggled = state.lastToggled; const selectedState = { ...state.selectedState, @@ -16,7 +16,7 @@ function toggleSelected(state, items, id, selected, shiftKey) { const { lower, upper } = getToggledRange(items, id, lastToggled); for (let i = lower; i < upper; i++) { - selectedState[items[i].id] = selected; + selectedState[items[i][idProp]] = selected; } } diff --git a/src/NzbDrone.Core/NetImport/ImportExclusions/ImportExclusionsService.cs b/src/NzbDrone.Core/NetImport/ImportExclusions/ImportExclusionsService.cs index 072d1a4b8..a938f1e72 100644 --- a/src/NzbDrone.Core/NetImport/ImportExclusions/ImportExclusionsService.cs +++ b/src/NzbDrone.Core/NetImport/ImportExclusions/ImportExclusionsService.cs @@ -11,6 +11,7 @@ public interface IImportExclusionsService List GetAllExclusions(); bool IsMovieExcluded(int tmdbId); ImportExclusion AddExclusion(ImportExclusion exclusion); + List AddExclusions(List exclusions); void RemoveExclusion(ImportExclusion exclusion); ImportExclusion GetById(int id); } @@ -37,6 +38,13 @@ public ImportExclusion AddExclusion(ImportExclusion exclusion) return _exclusionRepository.Insert(exclusion); } + public List AddExclusions(List exclusions) + { + _exclusionRepository.InsertMany(exclusions); + + return exclusions; + } + public List GetAllExclusions() { return _exclusionRepository.All().ToList(); diff --git a/src/Radarr.Api.V3/NetImport/ImportExclusionsModule.cs b/src/Radarr.Api.V3/NetImport/ImportExclusionsModule.cs index ad440f1ed..44d650b09 100644 --- a/src/Radarr.Api.V3/NetImport/ImportExclusionsModule.cs +++ b/src/Radarr.Api.V3/NetImport/ImportExclusionsModule.cs @@ -3,6 +3,7 @@ using NzbDrone.Core.NetImport; using NzbDrone.Core.NetImport.ImportExclusions; using Radarr.Http; +using Radarr.Http.Extensions; namespace Radarr.Api.V3.NetImport { @@ -15,9 +16,9 @@ public ImportExclusionsModule(NetImportFactory netImportFactory, IImportExclusio { _exclusionService = exclusionService; GetResourceAll = GetAll; - CreateResource = AddExclusion; DeleteResource = RemoveExclusion; GetResourceById = GetById; + Post("/", x => AddExclusions()); SharedValidator.RuleFor(c => c.TmdbId).GreaterThan(0); SharedValidator.RuleFor(c => c.MovieTitle).NotEmpty(); @@ -34,12 +35,13 @@ public ImportExclusionsResource GetById(int id) return _exclusionService.GetById(id).ToResource(); } - public int AddExclusion(ImportExclusionsResource exclusionResource) + public object AddExclusions() { - var model = exclusionResource.ToModel(); + var resource = Request.Body.FromJson>(); + var newMovies = resource.ToModel(); // TODO: Add some more validation here and auto pull the title if not provided - return _exclusionService.AddExclusion(model).Id; + return _exclusionService.AddExclusions(newMovies).ToResource(); } public void RemoveExclusion(int id) diff --git a/src/Radarr.Api.V3/NetImport/ImportExclusionsResource.cs b/src/Radarr.Api.V3/NetImport/ImportExclusionsResource.cs index 718819e0a..123ed1098 100644 --- a/src/Radarr.Api.V3/NetImport/ImportExclusionsResource.cs +++ b/src/Radarr.Api.V3/NetImport/ImportExclusionsResource.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Linq; +using NzbDrone.Core.NetImport.ImportExclusions; namespace Radarr.Api.V3.NetImport { @@ -13,7 +14,7 @@ public class ImportExclusionsResource : ProviderResource public static class ImportExclusionsResourceMapper { - public static ImportExclusionsResource ToResource(this NzbDrone.Core.NetImport.ImportExclusions.ImportExclusion model) + public static ImportExclusionsResource ToResource(this ImportExclusion model) { if (model == null) { @@ -29,19 +30,24 @@ public static ImportExclusionsResource ToResource(this NzbDrone.Core.NetImport.I }; } - public static List ToResource(this IEnumerable exclusions) + public static List ToResource(this IEnumerable exclusions) { return exclusions.Select(ToResource).ToList(); } - public static NzbDrone.Core.NetImport.ImportExclusions.ImportExclusion ToModel(this ImportExclusionsResource resource) + public static ImportExclusion ToModel(this ImportExclusionsResource resource) { - return new NzbDrone.Core.NetImport.ImportExclusions.ImportExclusion + return new ImportExclusion { TmdbId = resource.TmdbId, MovieTitle = resource.MovieTitle, MovieYear = resource.MovieYear }; } + + public static List ToModel(this IEnumerable resources) + { + return resources.Select(ToModel).ToList(); + } } }