From a20222fbefa658cf39b1001e4e7fdf50513e14f0 Mon Sep 17 00:00:00 2001 From: Qstick Date: Fri, 12 Jul 2019 20:40:37 -0400 Subject: [PATCH] New: Movie Editor in Movie Index (#3606) * Fixed: Movie Editor in Movie Index * Fixed: CSS Style Issues * Fixed: Ensure only items shown are selected * Fixed: Cleanup and Rename from Editor --- .../Import/ImportMovieConnector.js | 10 +- .../Import/ImportMovieFooterConnector.js | 4 +- .../ImportMovie/Import/ImportMovieRow.css | 2 +- .../ImportMovie/Import/ImportMovieRow.js | 2 +- .../ImportMovie/Import/ImportMovieTable.js | 10 +- .../SelectMovie/ImportMovieSearchResult.css | 2 +- .../SelectMovie/ImportMovieSearchResult.js | 2 +- .../SelectMovie/ImportMovieSelectMovie.js | 2 +- frontend/src/App/AppRoutes.js | 6 - .../Calendar/iCal/CalendarLinkModalContent.js | 2 +- frontend/src/Commands/commandNames.js | 2 +- .../src/Components/MonitorToggleButton.js | 2 +- .../Page/Header/MovieSearchInput.js | 4 +- .../Components/Page/Sidebar/PageSidebar.js | 2 +- frontend/src/Helpers/Props/icons.js | 4 +- frontend/src/Movie/Details/MovieDetails.js | 2 +- .../Movie/Details/MovieDetailsConnector.js | 2 +- .../Details/MovieDetailsPageConnector.js | 2 +- .../src/Movie/Edit/EditMovieModalContent.js | 6 +- .../Edit/EditMovieModalContentConnector.js | 10 +- .../Editor/Delete/DeleteMovieModalContent.js | 14 +- .../DeleteMovieModalContentConnector.js | 16 +- ...EditorFooter.css => MovieEditorFooter.css} | 0 ...esEditorFooter.js => MovieEditorFooter.js} | 54 ++-- ...erLabel.css => MovieEditorFooterLabel.css} | 0 ...oterLabel.js => MovieEditorFooterLabel.js} | 10 +- ...zeSeriesModal.js => OrganizeMovieModal.js} | 10 +- ...tent.css => OrganizeMovieModalContent.css} | 0 ...ontent.js => OrganizeMovieModalContent.js} | 26 +- ... => OrganizeMovieModalContentConnector.js} | 32 +-- frontend/src/Movie/Editor/SeriesEditor.js | 268 ------------------ .../src/Movie/Editor/SeriesEditorConnector.js | 88 ------ .../SeriesEditorFilterModalConnector.js | 24 -- frontend/src/Movie/Editor/SeriesEditorRow.js | 97 ------- .../Movie/Editor/SeriesEditorRowConnector.js | 31 -- .../src/Movie/Editor/Tags/TagsModalContent.js | 10 +- .../Editor/Tags/TagsModalContentConnector.js | 10 +- .../History/MovieHistoryTableContent.css | 6 +- frontend/src/Movie/Index/MovieIndex.js | 190 ++++++++++++- .../src/Movie/Index/MovieIndexConnector.js | 17 +- .../Index/Overview/MovieIndexOverview.css | 7 + .../Index/Overview/MovieIndexOverview.js | 32 ++- .../Index/Overview/MovieIndexOverviews.js | 17 +- .../Movie/Index/Posters/MovieIndexPoster.css | 7 + .../Movie/Index/Posters/MovieIndexPoster.js | 29 +- .../Movie/Index/Posters/MovieIndexPosters.js | 17 +- .../MovieIndexPosterOptionsModalContent.js | 2 +- .../src/Movie/Index/Table/MovieIndexHeader.js | 22 +- .../src/Movie/Index/Table/MovieIndexRow.js | 24 +- .../src/Movie/Index/Table/MovieIndexTable.js | 33 ++- .../src/Movie/Index/Table/MovieStatusCell.js | 2 +- .../MoveMovieModal.css} | 0 .../MoveMovieModal.js} | 18 +- .../Movie/Titles/MovieTitlesTableContent.css | 6 +- .../MovieFile/Editor/MovieFileEditorRow.css | 54 ++-- .../Editor/MovieFileEditorTableContent.css | 2 +- .../Organize/OrganizePreviewModalContent.css | 2 +- .../Organize/OrganizePreviewModalContent.js | 15 +- .../OrganizePreviewModalContentConnector.js | 17 +- .../EditRestrictionModalContent.js | 2 +- .../MediaManagement/MediaManagement.js | 2 +- .../EditNotificationModalContent.js | 10 +- .../Delay/EditDelayProfileModalContent.js | 4 +- frontend/src/Settings/Settings.js | 2 +- .../src/Store/Actions/importMovieActions.js | 4 +- frontend/src/Store/Actions/index.js | 2 - .../src/Store/Actions/movieEditorActions.js | 179 ------------ .../src/Store/Actions/movieIndexActions.js | 100 ++++++- .../createImportMovieItemSelector.js | 4 +- frontend/src/Styles/Variables/dimensions.js | 2 +- .../Movies/Commands/BulkMoveMovieCommand.cs | 51 ++++ src/NzbDrone.Core/NzbDrone.Core.csproj | 1 + src/Radarr.Api.V2/Movies/MovieEditorModule.cs | 94 ++++-- .../Movies/MovieEditorResource.cs | 24 ++ src/Radarr.Api.V2/Radarr.Api.V2.csproj | 1 + .../Extensions/ReqResExtensions.cs | 2 +- .../Extensions/RequestExtensions.cs | 12 + .../Frontend/Mappers/HtmlMapperBase.cs | 2 +- 78 files changed, 823 insertions(+), 962 deletions(-) rename frontend/src/Movie/Editor/{SeriesEditorFooter.css => MovieEditorFooter.css} (100%) rename frontend/src/Movie/Editor/{SeriesEditorFooter.js => MovieEditorFooter.js} (84%) rename frontend/src/Movie/Editor/{SeriesEditorFooterLabel.css => MovieEditorFooterLabel.css} (100%) rename frontend/src/Movie/Editor/{SeriesEditorFooterLabel.js => MovieEditorFooterLabel.js} (74%) rename frontend/src/Movie/Editor/Organize/{OrganizeSeriesModal.js => OrganizeMovieModal.js} (64%) rename frontend/src/Movie/Editor/Organize/{OrganizeSeriesModalContent.css => OrganizeMovieModalContent.css} (100%) rename frontend/src/Movie/Editor/Organize/{OrganizeSeriesModalContent.js => OrganizeMovieModalContent.js} (66%) rename frontend/src/Movie/Editor/Organize/{OrganizeSeriesModalContentConnector.js => OrganizeMovieModalContentConnector.js} (55%) delete mode 100644 frontend/src/Movie/Editor/SeriesEditor.js delete mode 100644 frontend/src/Movie/Editor/SeriesEditorConnector.js delete mode 100644 frontend/src/Movie/Editor/SeriesEditorFilterModalConnector.js delete mode 100644 frontend/src/Movie/Editor/SeriesEditorRow.js delete mode 100644 frontend/src/Movie/Editor/SeriesEditorRowConnector.js rename frontend/src/Movie/{MoveSeries/MoveSeriesModal.css => MoveMovie/MoveMovieModal.css} (100%) rename frontend/src/Movie/{MoveSeries/MoveSeriesModal.js => MoveMovie/MoveMovieModal.js} (79%) delete mode 100644 frontend/src/Store/Actions/movieEditorActions.js create mode 100644 src/NzbDrone.Core/Movies/Commands/BulkMoveMovieCommand.cs create mode 100644 src/Radarr.Api.V2/Movies/MovieEditorResource.cs diff --git a/frontend/src/AddMovie/ImportMovie/Import/ImportMovieConnector.js b/frontend/src/AddMovie/ImportMovie/Import/ImportMovieConnector.js index 5b2000cc1..03c8bac50 100644 --- a/frontend/src/AddMovie/ImportMovie/Import/ImportMovieConnector.js +++ b/frontend/src/AddMovie/ImportMovie/Import/ImportMovieConnector.js @@ -61,7 +61,7 @@ const mapDispatchToProps = { dispatchImportMovie: importMovie, dispatchClearImportMovie: clearImportMovie, dispatchFetchRootFolders: fetchRootFolders, - dispatchSetAddSeriesDefault: setAddMovieDefault + dispatchSetAddMovieDefault: setAddMovieDefault }; class ImportMovieConnector extends Component { @@ -74,7 +74,7 @@ class ImportMovieConnector extends Component { qualityProfiles, defaultQualityProfileId, dispatchFetchRootFolders, - dispatchSetAddSeriesDefault + dispatchSetAddMovieDefault } = this.props; if (!this.props.rootFoldersPopulated) { @@ -93,7 +93,7 @@ class ImportMovieConnector extends Component { } if (setDefaults) { - dispatchSetAddSeriesDefault(setDefaultPayload); + dispatchSetAddMovieDefault(setDefaultPayload); } } @@ -105,7 +105,7 @@ class ImportMovieConnector extends Component { // Listeners onInputChange = (ids, name, value) => { - this.props.dispatchSetAddSeriesDefault({ [name]: value }); + this.props.dispatchSetAddMovieDefault({ [name]: value }); ids.forEach((id) => { this.props.dispatchSetImportMovieValue({ @@ -146,7 +146,7 @@ ImportMovieConnector.propTypes = { dispatchImportMovie: PropTypes.func.isRequired, dispatchClearImportMovie: PropTypes.func.isRequired, dispatchFetchRootFolders: PropTypes.func.isRequired, - dispatchSetAddSeriesDefault: PropTypes.func.isRequired + dispatchSetAddMovieDefault: PropTypes.func.isRequired }; export default connect(createMapStateToProps, mapDispatchToProps)(ImportMovieConnector); diff --git a/frontend/src/AddMovie/ImportMovie/Import/ImportMovieFooterConnector.js b/frontend/src/AddMovie/ImportMovie/Import/ImportMovieFooterConnector.js index 28cb4ea59..28f991b0d 100644 --- a/frontend/src/AddMovie/ImportMovie/Import/ImportMovieFooterConnector.js +++ b/frontend/src/AddMovie/ImportMovie/Import/ImportMovieFooterConnector.js @@ -5,8 +5,8 @@ import { cancelLookupMovie } from 'Store/Actions/importMovieActions'; import ImportMovieFooter from './ImportMovieFooter'; function isMixed(items, selectedIds, defaultValue, key) { - return _.some(items, (series) => { - return selectedIds.indexOf(series.id) > -1 && series[key] !== defaultValue; + return _.some(items, (movie) => { + return selectedIds.indexOf(movie.id) > -1 && movie[key] !== defaultValue; }); } diff --git a/frontend/src/AddMovie/ImportMovie/Import/ImportMovieRow.css b/frontend/src/AddMovie/ImportMovie/Import/ImportMovieRow.css index bc1d15d47..77360124e 100644 --- a/frontend/src/AddMovie/ImportMovie/Import/ImportMovieRow.css +++ b/frontend/src/AddMovie/ImportMovie/Import/ImportMovieRow.css @@ -23,7 +23,7 @@ min-width: 170px; } -.series { +.movie { composes: cell from '~Components/Table/Cells/VirtualTableRowCell.css'; flex: 0 1 400px; diff --git a/frontend/src/AddMovie/ImportMovie/Import/ImportMovieRow.js b/frontend/src/AddMovie/ImportMovie/Import/ImportMovieRow.js index 0839432ff..022f8c298 100644 --- a/frontend/src/AddMovie/ImportMovie/Import/ImportMovieRow.js +++ b/frontend/src/AddMovie/ImportMovie/Import/ImportMovieRow.js @@ -53,7 +53,7 @@ function ImportMovieRow(props) { /> - + ); }) diff --git a/frontend/src/App/AppRoutes.js b/frontend/src/App/AppRoutes.js index 5558d0e0f..c37b1c9b7 100644 --- a/frontend/src/App/AppRoutes.js +++ b/frontend/src/App/AppRoutes.js @@ -7,7 +7,6 @@ import Switch from 'Components/Router/Switch'; import MovieIndexConnector from 'Movie/Index/MovieIndexConnector'; import AddNewMovieConnector from 'AddMovie/AddNewMovie/AddNewMovieConnector'; import ImportMovies from 'AddMovie/ImportMovie/ImportMovies'; -import SeriesEditorConnector from 'Movie/Editor/SeriesEditorConnector'; import MovieDetailsPageConnector from 'Movie/Details/MovieDetailsPageConnector'; import CalendarPageConnector from 'Calendar/CalendarPageConnector'; import HistoryConnector from 'Activity/History/HistoryConnector'; @@ -77,11 +76,6 @@ function AppRoutes(props) { component={ImportMovies} /> - - diff --git a/frontend/src/Commands/commandNames.js b/frontend/src/Commands/commandNames.js index fc73bafdb..ff5942136 100644 --- a/frontend/src/Commands/commandNames.js +++ b/frontend/src/Commands/commandNames.js @@ -12,7 +12,7 @@ export const MISSING_EPISODE_SEARCH = 'MissingEpisodeSearch'; export const MOVE_MOVIE = 'MoveMovie'; export const REFRESH_MOVIE = 'RefreshMovie'; export const RENAME_FILES = 'RenameFiles'; -export const RENAME_SERIES = 'RenameSeries'; +export const RENAME_MOVIE = 'RenameMovie'; export const RESET_API_KEY = 'ResetApiKey'; export const RSS_SYNC = 'RssSync'; export const MOVIE_SEARCH = 'MoviesSearch'; diff --git a/frontend/src/Components/MonitorToggleButton.js b/frontend/src/Components/MonitorToggleButton.js index 15ac3d7fe..69360fd81 100644 --- a/frontend/src/Components/MonitorToggleButton.js +++ b/frontend/src/Components/MonitorToggleButton.js @@ -7,7 +7,7 @@ import styles from './MonitorToggleButton.css'; function getTooltip(monitored, isDisabled) { if (isDisabled) { - return 'Cannot toogle monitored state when series is unmonitored'; + return 'Cannot toogle monitored state when movie is unmonitored'; } if (monitored) { diff --git a/frontend/src/Components/Page/Header/MovieSearchInput.js b/frontend/src/Components/Page/Header/MovieSearchInput.js index a2d1c0ab1..199a9a1b1 100644 --- a/frontend/src/Components/Page/Header/MovieSearchInput.js +++ b/frontend/src/Components/Page/Header/MovieSearchInput.js @@ -136,8 +136,8 @@ class MovieSearchInput extends Component { return; } - // If an suggestion is not selected go to the first series, - // otherwise go to the selected series. + // If an suggestion is not selected go to the first movie, + // otherwise go to the selected movie. if (highlightedSuggestionIndex == null) { this.goToMovie(suggestions[0]); diff --git a/frontend/src/Components/Page/Sidebar/PageSidebar.js b/frontend/src/Components/Page/Sidebar/PageSidebar.js index 15ec12f5d..ad5c805bd 100644 --- a/frontend/src/Components/Page/Sidebar/PageSidebar.js +++ b/frontend/src/Components/Page/Sidebar/PageSidebar.js @@ -19,7 +19,7 @@ const SIDEBAR_WIDTH = parseInt(dimensions.sidebarWidth); const links = [ { - iconName: icons.SERIES_CONTINUING, + iconName: icons.MOVIE_CONTINUING, title: 'Movies', to: '/', alias: '/movies', diff --git a/frontend/src/Helpers/Props/icons.js b/frontend/src/Helpers/Props/icons.js index 1125f661b..945294d22 100644 --- a/frontend/src/Helpers/Props/icons.js +++ b/frontend/src/Helpers/Props/icons.js @@ -34,6 +34,7 @@ import { faCalendarAlt as fasCalendarAlt, faCaretDown as fasCaretDown, faCheck as fasCheck, + faCheckSquare as fasCheckSquare, faChevronCircleDown as fasChevronCircleDown, faChevronCircleRight as fasChevronCircleRight, faChevronCircleUp as fasChevronCircleUp, @@ -117,6 +118,7 @@ export const CARET_DOWN = fasCaretDown; export const CHECK = fasCheck; export const CHECK_INDETERMINATE = fasMinus; export const CHECK_CIRCLE = fasCheckCircle; +export const CHECK_SQUARE = fasCheckSquare; export const CIRCLE = fasCircle; export const CIRCLE_OUTLINE = farCircle; export const CLEAR = fasTrashAlt; @@ -180,7 +182,7 @@ export const SAVE = fasSave; export const SCHEDULED = farClock; export const SCORE = fasUserPlus; export const SEARCH = fasSearch; -export const SERIES_CONTINUING = fasPlay; +export const MOVIE_CONTINUING = fasPlay; export const SERIES_ENDED = fasStop; export const SETTINGS = fasCogs; export const SHUTDOWN = fasPowerOff; diff --git a/frontend/src/Movie/Details/MovieDetails.js b/frontend/src/Movie/Details/MovieDetails.js index 3c47120d9..8fb8d51d8 100644 --- a/frontend/src/Movie/Details/MovieDetails.js +++ b/frontend/src/Movie/Details/MovieDetails.js @@ -527,7 +527,7 @@ class MovieDetails extends Component { -1 diff --git a/frontend/src/Movie/Details/MovieDetailsPageConnector.js b/frontend/src/Movie/Details/MovieDetailsPageConnector.js index 585a2c760..b8e873b64 100644 --- a/frontend/src/Movie/Details/MovieDetailsPageConnector.js +++ b/frontend/src/Movie/Details/MovieDetailsPageConnector.js @@ -54,7 +54,7 @@ class MovieDetailsPageConnector extends Component { if (!titleSlug) { return ( ); } diff --git a/frontend/src/Movie/Edit/EditMovieModalContent.js b/frontend/src/Movie/Edit/EditMovieModalContent.js index 8fb17a9c6..40cdf816e 100644 --- a/frontend/src/Movie/Edit/EditMovieModalContent.js +++ b/frontend/src/Movie/Edit/EditMovieModalContent.js @@ -11,7 +11,7 @@ import Form from 'Components/Form/Form'; import FormGroup from 'Components/Form/FormGroup'; import FormLabel from 'Components/Form/FormLabel'; import FormInputGroup from 'Components/Form/FormInputGroup'; -import MoveMovieModal from 'Movie/MoveSeries/MoveSeriesModal'; +import MoveMovieModal from 'Movie/MoveMovie/MoveMovieModal'; import styles from './EditMovieModalContent.css'; class EditMovieModalContent extends Component { @@ -45,7 +45,7 @@ class EditMovieModalContent extends Component { } } - onMoveSeriesPress = () => { + onMoveMoviePress = () => { this.setState({ isConfirmMoveModalOpen: false }); this.props.onSavePress(true); @@ -159,7 +159,7 @@ class EditMovieModalContent extends Component { destinationPath={path.value} isOpen={this.state.isConfirmMoveModalOpen} onSavePress={this.onSavePress} - onMoveSeriesPress={this.onMoveSeriesPress} + onMoveMoviePress={this.onMoveMoviePress} /> ); diff --git a/frontend/src/Movie/Edit/EditMovieModalContentConnector.js b/frontend/src/Movie/Edit/EditMovieModalContentConnector.js index 7f6b11860..a5bd045a1 100644 --- a/frontend/src/Movie/Edit/EditMovieModalContentConnector.js +++ b/frontend/src/Movie/Edit/EditMovieModalContentConnector.js @@ -29,21 +29,21 @@ function createMapStateToProps() { (state) => state.movies, createMovieSelector(), createIsPathChangingSelector(), - (seriesState, movie, isPathChanging) => { + (moviesState, movie, isPathChanging) => { const { isSaving, saveError, pendingChanges - } = seriesState; + } = moviesState; - const seriesSettings = _.pick(movie, [ + const movieSettings = _.pick(movie, [ 'monitored', 'qualityProfileId', 'path', 'tags' ]); - const settings = selectSettings(seriesSettings, pendingChanges, saveError); + const settings = selectSettings(movieSettings, pendingChanges, saveError); return { title: movie.title, @@ -97,7 +97,7 @@ class EditMovieModalContentConnector extends Component { {...this.props} onInputChange={this.onInputChange} onSavePress={this.onSavePress} - onMoveSeriesPress={this.onMoveSeriesPress} + onMoveMoviePress={this.onMoveMoviePress} /> ); } diff --git a/frontend/src/Movie/Editor/Delete/DeleteMovieModalContent.js b/frontend/src/Movie/Editor/Delete/DeleteMovieModalContent.js index 94ad87a34..71dbc946e 100644 --- a/frontend/src/Movie/Editor/Delete/DeleteMovieModalContent.js +++ b/frontend/src/Movie/Editor/Delete/DeleteMovieModalContent.js @@ -43,7 +43,7 @@ class DeleteMovieModalContent extends Component { render() { const { - series, + movies, onModalClose } = this.props; const deleteFiles = this.state.deleteFiles; @@ -51,19 +51,19 @@ class DeleteMovieModalContent extends Component { return ( - Delete Selected Series + Delete Selected Movie(s)
- {`Delete Series Folder${series.length > 1 ? 's' : ''}`} + {`Delete Movie Folder${movies.length > 1 ? 's' : ''}`} 1 ? 's' : ''} and all contents`} + helpText={`Delete Movie Folder${movies.length > 1 ? 's' : ''} and all contents`} kind={kinds.DANGER} onChange={this.onDeleteFilesChange} /> @@ -71,12 +71,12 @@ class DeleteMovieModalContent extends Component {
- {`Are you sure you want to delete ${series.length} selected series${deleteFiles ? ' and all contents' : ''}?`} + {`Are you sure you want to delete ${movies.length} selected movie(s)${deleteFiles ? ' and all contents' : ''}?`}
    { - series.map((s) => { + movies.map((s) => { return (
  • {s.title} @@ -115,7 +115,7 @@ class DeleteMovieModalContent extends Component { } DeleteMovieModalContent.propTypes = { - series: PropTypes.arrayOf(PropTypes.object).isRequired, + movies: PropTypes.arrayOf(PropTypes.object).isRequired, onModalClose: PropTypes.func.isRequired, onDeleteSelectedPress: PropTypes.func.isRequired }; diff --git a/frontend/src/Movie/Editor/Delete/DeleteMovieModalContentConnector.js b/frontend/src/Movie/Editor/Delete/DeleteMovieModalContentConnector.js index 2ff44ea29..d890b965f 100644 --- a/frontend/src/Movie/Editor/Delete/DeleteMovieModalContentConnector.js +++ b/frontend/src/Movie/Editor/Delete/DeleteMovieModalContentConnector.js @@ -2,20 +2,20 @@ import _ from 'lodash'; import { connect } from 'react-redux'; import { createSelector } from 'reselect'; import createAllMoviesSelector from 'Store/Selectors/createAllMoviesSelector'; -import { bulkDeleteMovie } from 'Store/Actions/movieEditorActions'; +import { bulkDeleteMovie } from 'Store/Actions/movieIndexActions'; import DeleteMovieModalContent from './DeleteMovieModalContent'; function createMapStateToProps() { return createSelector( - (state, { seriesIds }) => seriesIds, + (state, { movieIds }) => movieIds, createAllMoviesSelector(), - (seriesIds, allMovies) => { - const selectedMovie = _.intersectionWith(allMovies, seriesIds, (s, id) => { + (movieIds, allMovies) => { + const selectedMovie = _.intersectionWith(allMovies, movieIds, (s, id) => { return s.id === id; }); - const sortedSeries = _.orderBy(selectedMovie, 'sortTitle'); - const series = _.map(sortedSeries, (s) => { + const sortedMovies = _.orderBy(selectedMovie, 'sortTitle'); + const movies = _.map(sortedMovies, (s) => { return { title: s.title, path: s.path @@ -23,7 +23,7 @@ function createMapStateToProps() { }); return { - series + movies }; } ); @@ -33,7 +33,7 @@ function createMapDispatchToProps(dispatch, props) { return { onDeleteSelectedPress(deleteFiles) { dispatch(bulkDeleteMovie({ - seriesIds: props.seriesIds, + movieIds: props.movieIds, deleteFiles })); diff --git a/frontend/src/Movie/Editor/SeriesEditorFooter.css b/frontend/src/Movie/Editor/MovieEditorFooter.css similarity index 100% rename from frontend/src/Movie/Editor/SeriesEditorFooter.css rename to frontend/src/Movie/Editor/MovieEditorFooter.css diff --git a/frontend/src/Movie/Editor/SeriesEditorFooter.js b/frontend/src/Movie/Editor/MovieEditorFooter.js similarity index 84% rename from frontend/src/Movie/Editor/SeriesEditorFooter.js rename to frontend/src/Movie/Editor/MovieEditorFooter.js index d49940213..501b72a81 100644 --- a/frontend/src/Movie/Editor/SeriesEditorFooter.js +++ b/frontend/src/Movie/Editor/MovieEditorFooter.js @@ -6,15 +6,15 @@ import QualityProfileSelectInputConnector from 'Components/Form/QualityProfileSe import RootFolderSelectInputConnector from 'Components/Form/RootFolderSelectInputConnector'; import SpinnerButton from 'Components/Link/SpinnerButton'; import PageContentFooter from 'Components/Page/PageContentFooter'; -import MoveSeriesModal from 'Movie/MoveSeries/MoveSeriesModal'; +import MoveMovieModal from 'Movie/MoveMovie/MoveMovieModal'; import TagsModal from './Tags/TagsModal'; import DeleteMovieModal from './Delete/DeleteMovieModal'; -import SeriesEditorFooterLabel from './SeriesEditorFooterLabel'; -import styles from './SeriesEditorFooter.css'; +import MovieEditorFooterLabel from './MovieEditorFooterLabel'; +import styles from './MovieEditorFooter.css'; const NO_CHANGE = 'noChange'; -class SeriesEditorFooter extends Component { +class MovieEditorFooter extends Component { // // Lifecycle @@ -112,7 +112,7 @@ class SeriesEditorFooter extends Component { this.props.onSaveSelected({ rootFolderPath: this.state.destinationRootFolder }); } - onMoveSeriesPress = () => { + onMoveMoviePress = () => { this.setState({ isConfirmMoveModalOpen: false, destinationRootFolder: null @@ -129,12 +129,12 @@ class SeriesEditorFooter extends Component { render() { const { - seriesIds, + movieIds, selectedCount, isSaving, isDeleting, - isOrganizingSeries, - onOrganizeSeriesPress + isOrganizingMovie, + onOrganizeMoviePress } = this.props; const { @@ -157,8 +157,8 @@ class SeriesEditorFooter extends Component { return (
    - @@ -172,7 +172,7 @@ class SeriesEditorFooter extends Component {
    - @@ -187,7 +187,7 @@ class SeriesEditorFooter extends Component {
    - @@ -204,8 +204,8 @@ class SeriesEditorFooter extends Component {
    - @@ -214,9 +214,9 @@ class SeriesEditorFooter extends Component { Rename Files @@ -224,7 +224,7 @@ class SeriesEditorFooter extends Component { Set Tags @@ -246,38 +246,38 @@ class SeriesEditorFooter extends Component { - ); } } -SeriesEditorFooter.propTypes = { - seriesIds: PropTypes.arrayOf(PropTypes.number).isRequired, +MovieEditorFooter.propTypes = { + movieIds: PropTypes.arrayOf(PropTypes.number).isRequired, selectedCount: PropTypes.number.isRequired, isSaving: PropTypes.bool.isRequired, saveError: PropTypes.object, isDeleting: PropTypes.bool.isRequired, deleteError: PropTypes.object, - isOrganizingSeries: PropTypes.bool.isRequired, + isOrganizingMovie: PropTypes.bool.isRequired, onSaveSelected: PropTypes.func.isRequired, - onOrganizeSeriesPress: PropTypes.func.isRequired + onOrganizeMoviePress: PropTypes.func.isRequired }; -export default SeriesEditorFooter; +export default MovieEditorFooter; diff --git a/frontend/src/Movie/Editor/SeriesEditorFooterLabel.css b/frontend/src/Movie/Editor/MovieEditorFooterLabel.css similarity index 100% rename from frontend/src/Movie/Editor/SeriesEditorFooterLabel.css rename to frontend/src/Movie/Editor/MovieEditorFooterLabel.css diff --git a/frontend/src/Movie/Editor/SeriesEditorFooterLabel.js b/frontend/src/Movie/Editor/MovieEditorFooterLabel.js similarity index 74% rename from frontend/src/Movie/Editor/SeriesEditorFooterLabel.js rename to frontend/src/Movie/Editor/MovieEditorFooterLabel.js index fc77ece44..df3775f65 100644 --- a/frontend/src/Movie/Editor/SeriesEditorFooterLabel.js +++ b/frontend/src/Movie/Editor/MovieEditorFooterLabel.js @@ -2,9 +2,9 @@ import PropTypes from 'prop-types'; import React from 'react'; import { icons } from 'Helpers/Props'; import SpinnerIcon from 'Components/SpinnerIcon'; -import styles from './SeriesEditorFooterLabel.css'; +import styles from './MovieEditorFooterLabel.css'; -function SeriesEditorFooterLabel(props) { +function MovieEditorFooterLabel(props) { const { className, label, @@ -27,14 +27,14 @@ function SeriesEditorFooterLabel(props) { ); } -SeriesEditorFooterLabel.propTypes = { +MovieEditorFooterLabel.propTypes = { className: PropTypes.string.isRequired, label: PropTypes.string.isRequired, isSaving: PropTypes.bool.isRequired }; -SeriesEditorFooterLabel.defaultProps = { +MovieEditorFooterLabel.defaultProps = { className: styles.label }; -export default SeriesEditorFooterLabel; +export default MovieEditorFooterLabel; diff --git a/frontend/src/Movie/Editor/Organize/OrganizeSeriesModal.js b/frontend/src/Movie/Editor/Organize/OrganizeMovieModal.js similarity index 64% rename from frontend/src/Movie/Editor/Organize/OrganizeSeriesModal.js rename to frontend/src/Movie/Editor/Organize/OrganizeMovieModal.js index c970392ec..e82d9e92c 100644 --- a/frontend/src/Movie/Editor/Organize/OrganizeSeriesModal.js +++ b/frontend/src/Movie/Editor/Organize/OrganizeMovieModal.js @@ -1,9 +1,9 @@ import PropTypes from 'prop-types'; import React from 'react'; import Modal from 'Components/Modal/Modal'; -import OrganizeSeriesModalContentConnector from './OrganizeSeriesModalContentConnector'; +import OrganizeMovieModalContentConnector from './OrganizeMovieModalContentConnector'; -function OrganizeSeriesModal(props) { +function OrganizeMovieModal(props) { const { isOpen, onModalClose, @@ -15,7 +15,7 @@ function OrganizeSeriesModal(props) { isOpen={isOpen} onModalClose={onModalClose} > - @@ -23,9 +23,9 @@ function OrganizeSeriesModal(props) { ); } -OrganizeSeriesModal.propTypes = { +OrganizeMovieModal.propTypes = { isOpen: PropTypes.bool.isRequired, onModalClose: PropTypes.func.isRequired }; -export default OrganizeSeriesModal; +export default OrganizeMovieModal; diff --git a/frontend/src/Movie/Editor/Organize/OrganizeSeriesModalContent.css b/frontend/src/Movie/Editor/Organize/OrganizeMovieModalContent.css similarity index 100% rename from frontend/src/Movie/Editor/Organize/OrganizeSeriesModalContent.css rename to frontend/src/Movie/Editor/Organize/OrganizeMovieModalContent.css diff --git a/frontend/src/Movie/Editor/Organize/OrganizeSeriesModalContent.js b/frontend/src/Movie/Editor/Organize/OrganizeMovieModalContent.js similarity index 66% rename from frontend/src/Movie/Editor/Organize/OrganizeSeriesModalContent.js rename to frontend/src/Movie/Editor/Organize/OrganizeMovieModalContent.js index 10a459d52..3f5c0601a 100644 --- a/frontend/src/Movie/Editor/Organize/OrganizeSeriesModalContent.js +++ b/frontend/src/Movie/Editor/Organize/OrganizeMovieModalContent.js @@ -8,24 +8,24 @@ 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 './OrganizeSeriesModalContent.css'; +import styles from './OrganizeMovieModalContent.css'; -function OrganizeSeriesModalContent(props) { +function OrganizeMovieModalContent(props) { const { - seriesTitles, + movieTitles, onModalClose, - onOrganizeSeriesPress + onOrganizeMoviePress } = props; return ( - Organize Selected Series + Organize Selected Movies - Tip: To preview a rename... select "Cancel" then any series title and use the + Tip: To preview a rename... select "Cancel" then click any movie title and use the
    - Are you sure you want to organize all files in the {seriesTitles.length} selected series? + Are you sure you want to organize all files in the {movieTitles.length} selected movie(s)?
      { - seriesTitles.map((title) => { + movieTitles.map((title) => { return (
    • {title} @@ -56,7 +56,7 @@ function OrganizeSeriesModalContent(props) { @@ -65,10 +65,10 @@ function OrganizeSeriesModalContent(props) { ); } -OrganizeSeriesModalContent.propTypes = { - seriesTitles: PropTypes.arrayOf(PropTypes.string).isRequired, +OrganizeMovieModalContent.propTypes = { + movieTitles: PropTypes.arrayOf(PropTypes.string).isRequired, onModalClose: PropTypes.func.isRequired, - onOrganizeSeriesPress: PropTypes.func.isRequired + onOrganizeMoviePress: PropTypes.func.isRequired }; -export default OrganizeSeriesModalContent; +export default OrganizeMovieModalContent; diff --git a/frontend/src/Movie/Editor/Organize/OrganizeSeriesModalContentConnector.js b/frontend/src/Movie/Editor/Organize/OrganizeMovieModalContentConnector.js similarity index 55% rename from frontend/src/Movie/Editor/Organize/OrganizeSeriesModalContentConnector.js rename to frontend/src/Movie/Editor/Organize/OrganizeMovieModalContentConnector.js index 95fa7ebd6..43dc877b6 100644 --- a/frontend/src/Movie/Editor/Organize/OrganizeSeriesModalContentConnector.js +++ b/frontend/src/Movie/Editor/Organize/OrganizeMovieModalContentConnector.js @@ -6,22 +6,22 @@ import { createSelector } from 'reselect'; import createAllMoviesSelector from 'Store/Selectors/createAllMoviesSelector'; import { executeCommand } from 'Store/Actions/commandActions'; import * as commandNames from 'Commands/commandNames'; -import OrganizeSeriesModalContent from './OrganizeSeriesModalContent'; +import OrganizeMovieModalContent from './OrganizeMovieModalContent'; function createMapStateToProps() { return createSelector( - (state, { seriesIds }) => seriesIds, + (state, { movieIds }) => movieIds, createAllMoviesSelector(), - (seriesIds, allMovies) => { - const series = _.intersectionWith(allMovies, seriesIds, (s, id) => { + (movieIds, allMovies) => { + const movies = _.intersectionWith(allMovies, movieIds, (s, id) => { return s.id === id; }); - const sortedSeries = _.orderBy(series, 'sortTitle'); - const seriesTitles = _.map(sortedSeries, 'title'); + const sortedMovies = _.orderBy(movies, 'sortTitle'); + const movieTitles = _.map(sortedMovies, 'title'); return { - seriesTitles + movieTitles }; } ); @@ -31,15 +31,15 @@ const mapDispatchToProps = { executeCommand }; -class OrganizeSeriesModalContentConnector extends Component { +class OrganizeMovieModalContentConnector extends Component { // // Listeners - onOrganizeSeriesPress = () => { + onOrganizeMoviePress = () => { this.props.executeCommand({ - name: commandNames.RENAME_SERIES, - seriesIds: this.props.seriesIds + name: commandNames.RENAME_MOVIE, + movieIds: this.props.movieIds }); this.props.onModalClose(true); @@ -50,18 +50,18 @@ class OrganizeSeriesModalContentConnector extends Component { render(props) { return ( - ); } } -OrganizeSeriesModalContentConnector.propTypes = { - seriesIds: PropTypes.arrayOf(PropTypes.number).isRequired, +OrganizeMovieModalContentConnector.propTypes = { + movieIds: PropTypes.arrayOf(PropTypes.number).isRequired, onModalClose: PropTypes.func.isRequired, executeCommand: PropTypes.func.isRequired }; -export default connect(createMapStateToProps, mapDispatchToProps)(OrganizeSeriesModalContentConnector); +export default connect(createMapStateToProps, mapDispatchToProps)(OrganizeMovieModalContentConnector); diff --git a/frontend/src/Movie/Editor/SeriesEditor.js b/frontend/src/Movie/Editor/SeriesEditor.js deleted file mode 100644 index 401cb73ab..000000000 --- a/frontend/src/Movie/Editor/SeriesEditor.js +++ /dev/null @@ -1,268 +0,0 @@ -import PropTypes from 'prop-types'; -import React, { Component } from 'react'; -import getSelectedIds from 'Utilities/Table/getSelectedIds'; -import selectAll from 'Utilities/Table/selectAll'; -import toggleSelected from 'Utilities/Table/toggleSelected'; -import { align, sortDirections } from 'Helpers/Props'; -import LoadingIndicator from 'Components/Loading/LoadingIndicator'; -import PageContent from 'Components/Page/PageContent'; -import PageContentBodyConnector from 'Components/Page/PageContentBodyConnector'; -import PageToolbar from 'Components/Page/Toolbar/PageToolbar'; -import PageToolbarSection from 'Components/Page/Toolbar/PageToolbarSection'; -import FilterMenu from 'Components/Menu/FilterMenu'; -import Table from 'Components/Table/Table'; -import TableBody from 'Components/Table/TableBody'; -import NoMovie from 'Movie/NoMovie'; -import OrganizeSeriesModal from './Organize/OrganizeSeriesModal'; -import SeriesEditorRowConnector from './SeriesEditorRowConnector'; -import SeriesEditorFooter from './SeriesEditorFooter'; -import SeriesEditorFilterModalConnector from './SeriesEditorFilterModalConnector'; - -function getColumns() { - return [ - { - name: 'status', - isSortable: true, - isVisible: true - }, - { - name: 'sortTitle', - label: 'Title', - isSortable: true, - isVisible: true - }, - { - name: 'qualityProfileId', - label: 'Quality Profile', - isSortable: true, - isVisible: true - }, - { - name: 'path', - label: 'Path', - isSortable: true, - isVisible: true - }, - { - name: 'tags', - label: 'Tags', - isSortable: false, - isVisible: true - } - ]; -} - -class SeriesEditor extends Component { - - // - // Lifecycle - - constructor(props, context) { - super(props, context); - - this.state = { - allSelected: false, - allUnselected: false, - lastToggled: null, - selectedState: {}, - isOrganizingSeriesModalOpen: false, - columns: getColumns() - }; - } - - componentDidUpdate(prevProps) { - const { - isDeleting, - deleteError - } = this.props; - - const hasFinishedDeleting = prevProps.isDeleting && - !isDeleting && - !deleteError; - - if (hasFinishedDeleting) { - this.onSelectAllChange({ value: false }); - } - } - - // - // Control - - getSelectedIds = () => { - return getSelectedIds(this.state.selectedState); - } - - // - // Listeners - - onSelectAllChange = ({ value }) => { - this.setState(selectAll(this.state.selectedState, value)); - } - - onSelectedChange = ({ id, value, shiftKey = false }) => { - this.setState((state) => { - return toggleSelected(state, this.props.items, id, value, shiftKey); - }); - } - - onSaveSelected = (changes) => { - this.props.onSaveSelected({ - seriesIds: this.getSelectedIds(), - ...changes - }); - } - - onOrganizeSeriesPress = () => { - this.setState({ isOrganizingSeriesModalOpen: true }); - } - - onOrganizeSeriesModalClose = (organized) => { - this.setState({ isOrganizingSeriesModalOpen: false }); - - if (organized === true) { - this.onSelectAllChange({ value: false }); - } - } - - // - // Render - - render() { - const { - isFetching, - isPopulated, - error, - totalItems, - items, - selectedFilterKey, - filters, - customFilters, - sortKey, - sortDirection, - isSaving, - saveError, - isDeleting, - deleteError, - isOrganizingSeries, - onSortPress, - onFilterSelect - } = this.props; - - const { - allSelected, - allUnselected, - selectedState, - columns - } = this.state; - - const selectedMovieIds = this.getSelectedIds(); - - return ( - - - - - - - - - - { - isFetching && !isPopulated && - - } - - { - !isFetching && !!error && -
      Unable to load the calendar
      - } - - { - !error && isPopulated && !!items.length && -
      - - - { - items.map((item) => { - return ( - - ); - }) - } - -
      -
      - } - - { - !error && isPopulated && !items.length && - - } -
      - - - - -
      - ); - } -} - -SeriesEditor.propTypes = { - isFetching: PropTypes.bool.isRequired, - isPopulated: PropTypes.bool.isRequired, - error: PropTypes.object, - totalItems: PropTypes.number.isRequired, - items: PropTypes.arrayOf(PropTypes.object).isRequired, - sortKey: PropTypes.string, - sortDirection: PropTypes.oneOf(sortDirections.all), - selectedFilterKey: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired, - filters: PropTypes.arrayOf(PropTypes.object).isRequired, - customFilters: PropTypes.arrayOf(PropTypes.object).isRequired, - isSaving: PropTypes.bool.isRequired, - saveError: PropTypes.object, - isDeleting: PropTypes.bool.isRequired, - deleteError: PropTypes.object, - isOrganizingSeries: PropTypes.bool.isRequired, - onSortPress: PropTypes.func.isRequired, - onFilterSelect: PropTypes.func.isRequired, - onSaveSelected: PropTypes.func.isRequired -}; - -export default SeriesEditor; diff --git a/frontend/src/Movie/Editor/SeriesEditorConnector.js b/frontend/src/Movie/Editor/SeriesEditorConnector.js deleted file mode 100644 index c46fc3b57..000000000 --- a/frontend/src/Movie/Editor/SeriesEditorConnector.js +++ /dev/null @@ -1,88 +0,0 @@ -import PropTypes from 'prop-types'; -import React, { Component } from 'react'; -import { connect } from 'react-redux'; -import { createSelector } from 'reselect'; -import createClientSideCollectionSelector from 'Store/Selectors/createClientSideCollectionSelector'; -import createCommandExecutingSelector from 'Store/Selectors/createCommandExecutingSelector'; -import { setSeriesEditorSort, setSeriesEditorFilter, saveSeriesEditor } from 'Store/Actions/movieEditorActions'; -import { fetchRootFolders } from 'Store/Actions/rootFolderActions'; -import { executeCommand } from 'Store/Actions/commandActions'; -import * as commandNames from 'Commands/commandNames'; -import SeriesEditor from './SeriesEditor'; - -function createMapStateToProps() { - return createSelector( - createClientSideCollectionSelector('movies', 'movieEditor'), - createCommandExecutingSelector(commandNames.RENAME_SERIES), - (series, isOrganizingSeries) => { - return { - isOrganizingSeries, - ...series - }; - } - ); -} - -const mapDispatchToProps = { - dispatchSetSeriesEditorSort: setSeriesEditorSort, - dispatchSetSeriesEditorFilter: setSeriesEditorFilter, - dispatchSaveMovieEditor: saveSeriesEditor, - dispatchFetchRootFolders: fetchRootFolders, - dispatchExecuteCommand: executeCommand -}; - -class SeriesEditorConnector extends Component { - - // - // Lifecycle - - componentDidMount() { - this.props.dispatchFetchRootFolders(); - } - - // - // Listeners - - onSortPress = (sortKey) => { - this.props.dispatchSetSeriesEditorSort({ sortKey }); - } - - onFilterSelect = (selectedFilterKey) => { - this.props.dispatchSetSeriesEditorFilter({ selectedFilterKey }); - } - - onSaveSelected = (payload) => { - this.props.dispatchSaveMovieEditor(payload); - } - - onMoveSelected = (payload) => { - this.props.dispatchExecuteCommand({ - name: commandNames.MOVE_SERIES, - ...payload - }); - } - - // - // Render - - render() { - return ( - - ); - } -} - -SeriesEditorConnector.propTypes = { - dispatchSetSeriesEditorSort: PropTypes.func.isRequired, - dispatchSetSeriesEditorFilter: PropTypes.func.isRequired, - dispatchSaveMovieEditor: PropTypes.func.isRequired, - dispatchFetchRootFolders: PropTypes.func.isRequired, - dispatchExecuteCommand: PropTypes.func.isRequired -}; - -export default connect(createMapStateToProps, mapDispatchToProps)(SeriesEditorConnector); diff --git a/frontend/src/Movie/Editor/SeriesEditorFilterModalConnector.js b/frontend/src/Movie/Editor/SeriesEditorFilterModalConnector.js deleted file mode 100644 index ff9ab119e..000000000 --- a/frontend/src/Movie/Editor/SeriesEditorFilterModalConnector.js +++ /dev/null @@ -1,24 +0,0 @@ -import { connect } from 'react-redux'; -import { createSelector } from 'reselect'; -import { setSeriesEditorFilter } from 'Store/Actions/movieEditorActions'; -import FilterModal from 'Components/Filter/FilterModal'; - -function createMapStateToProps() { - return createSelector( - (state) => state.movies.items, - (state) => state.moviesEditor.filterBuilderProps, - (sectionItems, filterBuilderProps) => { - return { - sectionItems, - filterBuilderProps, - customFilterType: 'movieEditor' - }; - } - ); -} - -const mapDispatchToProps = { - dispatchSetFilter: setSeriesEditorFilter -}; - -export default connect(createMapStateToProps, mapDispatchToProps)(FilterModal); diff --git a/frontend/src/Movie/Editor/SeriesEditorRow.js b/frontend/src/Movie/Editor/SeriesEditorRow.js deleted file mode 100644 index 742e34925..000000000 --- a/frontend/src/Movie/Editor/SeriesEditorRow.js +++ /dev/null @@ -1,97 +0,0 @@ -// import _ from 'lodash'; -import PropTypes from 'prop-types'; -import React, { Component } from 'react'; -// import titleCase from 'Utilities/String/titleCase'; -import TagListConnector from 'Components/TagListConnector'; -// import CheckInput from 'Components/Form/CheckInput'; -import TableRow from 'Components/Table/TableRow'; -import TableRowCell from 'Components/Table/Cells/TableRowCell'; -import TableSelectCell from 'Components/Table/Cells/TableSelectCell'; -import MovieTitleLink from 'Movie/MovieTitleLink'; -import MovieStatusCell from 'Movie/Index/Table/MovieStatusCell'; - -class SeriesEditorRow extends Component { - - // - // Listeners - - onSeasonFolderChange = () => { - // Mock handler to satisfy `onChange` being required for `CheckInput`. - // - } - - // - // Render - - render() { - const { - id, - status, - titleSlug, - title, - monitored, - qualityProfile, - path, - tags, - // columns, - isSelected, - onSelectedChange - } = this.props; - - return ( - - - - - - - - - - - {qualityProfile.name} - - - - {path} - - - - - - - ); - } -} - -SeriesEditorRow.propTypes = { - id: PropTypes.number.isRequired, - status: PropTypes.string.isRequired, - titleSlug: PropTypes.string.isRequired, - title: PropTypes.string.isRequired, - monitored: PropTypes.bool.isRequired, - qualityProfile: PropTypes.object.isRequired, - path: PropTypes.string.isRequired, - tags: PropTypes.arrayOf(PropTypes.number).isRequired, - columns: PropTypes.arrayOf(PropTypes.object).isRequired, - isSelected: PropTypes.bool, - onSelectedChange: PropTypes.func.isRequired -}; - -SeriesEditorRow.defaultProps = { - tags: [] -}; - -export default SeriesEditorRow; diff --git a/frontend/src/Movie/Editor/SeriesEditorRowConnector.js b/frontend/src/Movie/Editor/SeriesEditorRowConnector.js deleted file mode 100644 index 1b70e92fe..000000000 --- a/frontend/src/Movie/Editor/SeriesEditorRowConnector.js +++ /dev/null @@ -1,31 +0,0 @@ -import PropTypes from 'prop-types'; -import React from 'react'; -import { connect } from 'react-redux'; -import { createSelector } from 'reselect'; -import createQualityProfileSelector from 'Store/Selectors/createQualityProfileSelector'; -import SeriesEditorRow from './SeriesEditorRow'; - -function createMapStateToProps() { - return createSelector( - createQualityProfileSelector(), - (qualityProfile) => { - return { - qualityProfile - }; - } - ); -} - -function SeriesEditorRowConnector(props) { - return ( - - ); -} - -SeriesEditorRowConnector.propTypes = { - qualityProfileId: PropTypes.number.isRequired -}; - -export default connect(createMapStateToProps)(SeriesEditorRowConnector); diff --git a/frontend/src/Movie/Editor/Tags/TagsModalContent.js b/frontend/src/Movie/Editor/Tags/TagsModalContent.js index ccc1120db..e03ef4a28 100644 --- a/frontend/src/Movie/Editor/Tags/TagsModalContent.js +++ b/frontend/src/Movie/Editor/Tags/TagsModalContent.js @@ -49,7 +49,7 @@ class TagsModalContent extends Component { render() { const { - seriesTags, + movieTags, tagList, onModalClose } = this.props; @@ -93,7 +93,7 @@ class TagsModalContent extends Component { value={applyTags} values={applyTagsOptions} helpTexts={[ - 'How to apply tags to the selected series', + 'How to apply tags to the selected movies', 'Add: Add the tags the existing list of tags', 'Remove: Remove the entered tags', 'Replace: Replace the tags with the entered tags (enter no tags to clear all tags)' @@ -107,7 +107,7 @@ class TagsModalContent extends Component {
      { - seriesTags.map((t) => { + movieTags.map((t) => { const tag = _.find(tagList, { id: t }); if (!tag) { @@ -139,7 +139,7 @@ class TagsModalContent extends Component { return null; } - if (seriesTags.indexOf(t) > -1) { + if (movieTags.indexOf(t) > -1) { return null; } @@ -178,7 +178,7 @@ class TagsModalContent extends Component { } TagsModalContent.propTypes = { - seriesTags: PropTypes.arrayOf(PropTypes.number).isRequired, + movieTags: PropTypes.arrayOf(PropTypes.number).isRequired, tagList: PropTypes.arrayOf(PropTypes.object).isRequired, onModalClose: PropTypes.func.isRequired, onApplyTagsPress: PropTypes.func.isRequired diff --git a/frontend/src/Movie/Editor/Tags/TagsModalContentConnector.js b/frontend/src/Movie/Editor/Tags/TagsModalContentConnector.js index 50c780385..75d2006d4 100644 --- a/frontend/src/Movie/Editor/Tags/TagsModalContentConnector.js +++ b/frontend/src/Movie/Editor/Tags/TagsModalContentConnector.js @@ -7,18 +7,18 @@ import TagsModalContent from './TagsModalContent'; function createMapStateToProps() { return createSelector( - (state, { seriesIds }) => seriesIds, + (state, { movieIds }) => movieIds, createAllMoviesSelector(), createTagsSelector(), - (seriesIds, allMovies, tagList) => { - const series = _.intersectionWith(allMovies, seriesIds, (s, id) => { + (movieIds, allMovies, tagList) => { + const movies = _.intersectionWith(allMovies, movieIds, (s, id) => { return s.id === id; }); - const seriesTags = _.uniq(_.concat(..._.map(series, 'tags'))); + const movieTags = _.uniq(_.concat(..._.map(movies, 'tags'))); return { - seriesTags, + movieTags, tagList }; } diff --git a/frontend/src/Movie/History/MovieHistoryTableContent.css b/frontend/src/Movie/History/MovieHistoryTableContent.css index ed117bd32..34be6dff1 100644 --- a/frontend/src/Movie/History/MovieHistoryTableContent.css +++ b/frontend/src/Movie/History/MovieHistoryTableContent.css @@ -1,5 +1,5 @@ .blankpad { - padding-left:2em; - padding-top: 10px; - padding-bottom: 10px; + padding-top: 10px; + padding-bottom: 10px; + padding-left: 2em; } diff --git a/frontend/src/Movie/Index/MovieIndex.js b/frontend/src/Movie/Index/MovieIndex.js index f50a3f497..1245b7c6d 100644 --- a/frontend/src/Movie/Index/MovieIndex.js +++ b/frontend/src/Movie/Index/MovieIndex.js @@ -2,6 +2,9 @@ import _ from 'lodash'; import PropTypes from 'prop-types'; import React, { Component } from 'react'; import hasDifferentItems from 'Utilities/Object/hasDifferentItems'; +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'; @@ -23,7 +26,9 @@ import MovieIndexFilterMenu from './Menus/MovieIndexFilterMenu'; import MovieIndexSortMenu from './Menus/MovieIndexSortMenu'; import MovieIndexViewMenu from './Menus/MovieIndexViewMenu'; import MovieIndexFooterConnector from './MovieIndexFooterConnector'; +import MovieEditorFooter from 'Movie/Editor/MovieEditorFooter.js'; import InteractiveImportModal from 'InteractiveImport/InteractiveImportModal'; +import OrganizeMovieModal from 'Movie/Editor/Organize/OrganizeMovieModal'; import styles from './MovieIndex.css'; function getViewComponent(view) { @@ -53,12 +58,19 @@ class MovieIndex extends Component { isPosterOptionsModalOpen: false, isOverviewOptionsModalOpen: false, isInteractiveImportModalOpen: false, + isMovieEditorActive: false, + isOrganizingMovieModalOpen: false, + allSelected: false, + allUnselected: false, + lastToggled: null, + selectedState: {}, isRendered: false }; } componentDidMount() { this.setJumpBarItems(); + this.setSelectedState(); } componentDidUpdate(prevProps) { @@ -66,7 +78,9 @@ class MovieIndex extends Component { items, sortKey, sortDirection, - scrollTop + scrollTop, + isDeleting, + deleteError } = this.props; if ( @@ -75,11 +89,20 @@ class MovieIndex extends Component { sortDirection !== prevProps.sortDirection ) { this.setJumpBarItems(); + this.setSelectedState(); } if (this.state.jumpToCharacter != null && scrollTop !== prevProps.scrollTop) { this.setState({ jumpToCharacter: null }); } + + const hasFinishedDeleting = prevProps.isDeleting && + !isDeleting && + !deleteError; + + if (hasFinishedDeleting) { + this.onSelectAllChange({ value: false }); + } } // @@ -89,6 +112,45 @@ class MovieIndex extends Component { this.setState({ contentBody: ref }); } + getSelectedIds = () => { + return getSelectedIds(this.state.selectedState); + } + + setSelectedState() { + const { + items + } = this.props; + + const { + selectedState + } = this.state; + + const newSelectedState = {}; + + items.forEach((movie) => { + const isItemSelected = selectedState[movie.id]; + + if (isItemSelected) { + newSelectedState[movie.id] = isItemSelected; + } else { + newSelectedState[movie.id] = 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, @@ -149,10 +211,51 @@ class MovieIndex extends Component { this.setState({ isInteractiveImportModalOpen: false }); } + onMovieEditorTogglePress = () => { + if (this.state.isMovieEditorActive) { + this.setState({ isMovieEditorActive: false }); + } else { + this.setState({ isMovieEditorActive: true }); + } + } + onJumpBarItemPress = (jumpToCharacter) => { 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); + }); + } + + onSaveSelected = (changes) => { + this.props.onSaveSelected({ + movieIds: this.getSelectedIds(), + ...changes + }); + } + + onOrganizeMoviePress = () => { + this.setState({ isOrganizingMovieModalOpen: true }); + } + + onOrganizeMovieModalClose = (organized) => { + this.setState({ isOrganizingMovieModalOpen: false }); + + if (organized === true) { + this.onSelectAllChange({ value: false }); + } + } + onRender = () => { this.setState({ isRendered: true }, () => { const { @@ -193,6 +296,11 @@ class MovieIndex extends Component { view, isRefreshingMovie, isRssSyncExecuting, + isOrganizingMovie, + isSaving, + saveError, + isDeleting, + deleteError, scrollTop, onSortSelect, onFilterSelect, @@ -209,9 +317,15 @@ class MovieIndex extends Component { isPosterOptionsModalOpen, isOverviewOptionsModalOpen, isInteractiveImportModalOpen, - isRendered + isMovieEditorActive, + isRendered, + selectedState, + allSelected, + allUnselected } = this.state; + const selectedMovieIds = this.getSelectedIds(); + const ViewComponent = getViewComponent(view); const isLoaded = !!(!error && isPopulated && items.length && contentBody); const hasNoMovie = !totalItems; @@ -248,16 +362,38 @@ class MovieIndex extends Component { - + { + isMovieEditorActive ? + : + + } + + { + isMovieEditorActive ? + : + null + } @@ -360,10 +496,19 @@ class MovieIndex extends Component { scrollTop={scrollTop} jumpToCharacter={jumpToCharacter} onRender={this.onRender} + isMovieEditorActive={isMovieEditorActive} + allSelected={allSelected} + allUnselected={allUnselected} + onSelectedChange={this.onSelectedChange} + onSelectAllChange={this.onSelectAllChange} + selectedState={selectedState} {...otherProps} /> - + { + !isMovieEditorActive && + + }
      } @@ -382,6 +527,21 @@ class MovieIndex extends Component { }
    + { + isLoaded && isMovieEditorActive && + + } + + + ); } @@ -415,15 +581,21 @@ MovieIndex.propTypes = { sortDirection: PropTypes.oneOf(sortDirections.all), view: PropTypes.string.isRequired, isRefreshingMovie: PropTypes.bool.isRequired, + isOrganizingMovie: PropTypes.bool.isRequired, isRssSyncExecuting: PropTypes.bool.isRequired, scrollTop: PropTypes.number.isRequired, isSmallScreen: PropTypes.bool.isRequired, + isSaving: PropTypes.bool.isRequired, + saveError: PropTypes.object, + isDeleting: PropTypes.bool.isRequired, + deleteError: PropTypes.object, onSortSelect: PropTypes.func.isRequired, onFilterSelect: PropTypes.func.isRequired, onViewSelect: PropTypes.func.isRequired, onRefreshMoviePress: PropTypes.func.isRequired, onRssSyncPress: PropTypes.func.isRequired, - onScroll: PropTypes.func.isRequired + onScroll: PropTypes.func.isRequired, + onSaveSelected: PropTypes.func.isRequired }; export default MovieIndex; diff --git a/frontend/src/Movie/Index/MovieIndexConnector.js b/frontend/src/Movie/Index/MovieIndexConnector.js index 39fc1b5cb..5723fd197 100644 --- a/frontend/src/Movie/Index/MovieIndexConnector.js +++ b/frontend/src/Movie/Index/MovieIndexConnector.js @@ -8,7 +8,7 @@ import createCommandExecutingSelector from 'Store/Selectors/createCommandExecuti import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector'; import { fetchMovies } from 'Store/Actions/movieActions'; import scrollPositions from 'Store/scrollPositions'; -import { setMovieSort, setMovieFilter, setMovieView, setMovieTableOption } from 'Store/Actions/movieIndexActions'; +import { setMovieSort, setMovieFilter, setMovieView, setMovieTableOption, saveMovieEditor } from 'Store/Actions/movieIndexActions'; import { executeCommand } from 'Store/Actions/commandActions'; import * as commandNames from 'Commands/commandNames'; import withScrollPosition from 'Components/withScrollPosition'; @@ -42,17 +42,20 @@ function createMapStateToProps() { createMovieClientSideCollectionItemsSelector('movieIndex'), createCommandExecutingSelector(commandNames.REFRESH_MOVIE), createCommandExecutingSelector(commandNames.RSS_SYNC), + createCommandExecutingSelector(commandNames.RENAME_MOVIE), createDimensionsSelector(), ( movies, isRefreshingMovie, isRssSyncExecuting, + isOrganizingMovie, dimensionsState ) => { return { ...movies, isRefreshingMovie, isRssSyncExecuting, + isOrganizingMovie, isSmallScreen: dimensionsState.isSmallScreen }; } @@ -81,6 +84,10 @@ function createMapDispatchToProps(dispatch, props) { dispatch(setMovieView({ view })); }, + dispatchSaveMovieEditor(payload) { + dispatch(saveMovieEditor(payload)); + }, + onRefreshMoviePress() { dispatch(executeCommand({ name: commandNames.REFRESH_MOVIE @@ -128,6 +135,10 @@ class MovieIndexConnector extends Component { }); } + onSaveSelected = (payload) => { + this.props.dispatchSaveMovieEditor(payload); + } + onScroll = ({ scrollTop }) => { this.setState({ scrollTop @@ -146,6 +157,7 @@ class MovieIndexConnector extends Component { scrollTop={this.state.scrollTop} onViewSelect={this.onViewSelect} onScroll={this.onScroll} + onSaveSelected={this.onSaveSelected} /> ); } @@ -156,7 +168,8 @@ MovieIndexConnector.propTypes = { view: PropTypes.string.isRequired, scrollTop: PropTypes.number.isRequired, dispatchFetchMovies: PropTypes.func.isRequired, - dispatchSetMovieView: PropTypes.func.isRequired + dispatchSetMovieView: PropTypes.func.isRequired, + dispatchSaveMovieEditor: PropTypes.func.isRequired }; export default withScrollPosition( diff --git a/frontend/src/Movie/Index/Overview/MovieIndexOverview.css b/frontend/src/Movie/Index/Overview/MovieIndexOverview.css index 054319ebc..c4470e7b6 100644 --- a/frontend/src/Movie/Index/Overview/MovieIndexOverview.css +++ b/frontend/src/Movie/Index/Overview/MovieIndexOverview.css @@ -17,6 +17,13 @@ $hoverScale: 1.05; position: relative; } +.editorSelect { + position: absolute; + top: 0; + left: 5px; + z-index: 3; +} + .posterContainer { position: relative; } diff --git a/frontend/src/Movie/Index/Overview/MovieIndexOverview.js b/frontend/src/Movie/Index/Overview/MovieIndexOverview.js index f9c4919ec..8f50b5c84 100644 --- a/frontend/src/Movie/Index/Overview/MovieIndexOverview.js +++ b/frontend/src/Movie/Index/Overview/MovieIndexOverview.js @@ -7,6 +7,7 @@ import fonts from 'Styles/Variables/fonts'; import IconButton from 'Components/Link/IconButton'; import Link from 'Components/Link/Link'; import SpinnerIconButton from 'Components/Link/SpinnerIconButton'; +import CheckInput from 'Components/Form/CheckInput'; import MoviePoster from 'Movie/MoviePoster'; import EditMovieModalConnector from 'Movie/Edit/EditMovieModalConnector'; import DeleteMovieModal from 'Movie/Delete/DeleteMovieModal'; @@ -65,6 +66,15 @@ class MovieIndexOverview extends Component { this.setState({ isDeleteMovieModalOpen: false }); } + onChange = ({ value, shiftKey }) => { + const { + id, + onSelectedChange + } = this.props; + + onSelectedChange({ id, value, shiftKey }); + } + // // Render @@ -94,6 +104,9 @@ class MovieIndexOverview extends Component { isSearchingMovie, onRefreshMoviePress, onSearchPress, + isMovieEditorActive, + isSelected, + onSelectedChange, ...otherProps } = this.props; @@ -118,11 +131,15 @@ class MovieIndexOverview extends Component {
    { - status === 'ended' && -
    + isMovieEditorActive && +
    + +
    } ); } @@ -227,7 +233,8 @@ class MovieIndexOverviews extends Component { items, scrollTop, isSmallScreen, - onScroll + onScroll, + selectedState } = this.props; const { @@ -257,6 +264,7 @@ class MovieIndexOverviews extends Component { overscanRowCount={2} cellRenderer={this.cellRenderer} onSectionRendered={this.onSectionRendered} + selectedState={selectedState} /> ); } @@ -282,7 +290,10 @@ MovieIndexOverviews.propTypes = { isSmallScreen: PropTypes.bool.isRequired, timeFormat: PropTypes.string.isRequired, onRender: PropTypes.func.isRequired, - onScroll: PropTypes.func.isRequired + onScroll: PropTypes.func.isRequired, + selectedState: PropTypes.object.isRequired, + onSelectedChange: PropTypes.func.isRequired, + isMovieEditorActive: PropTypes.bool.isRequired }; export default MovieIndexOverviews; diff --git a/frontend/src/Movie/Index/Posters/MovieIndexPoster.css b/frontend/src/Movie/Index/Posters/MovieIndexPoster.css index f2358a641..6799288df 100644 --- a/frontend/src/Movie/Index/Posters/MovieIndexPoster.css +++ b/frontend/src/Movie/Index/Posters/MovieIndexPoster.css @@ -85,6 +85,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/Movie/Index/Posters/MovieIndexPoster.js b/frontend/src/Movie/Index/Posters/MovieIndexPoster.js index 0ae0cfa46..1d15f30a7 100644 --- a/frontend/src/Movie/Index/Posters/MovieIndexPoster.js +++ b/frontend/src/Movie/Index/Posters/MovieIndexPoster.js @@ -2,6 +2,7 @@ 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 SpinnerIconButton from 'Components/Link/SpinnerIconButton'; import Label from 'Components/Label'; import Link from 'Components/Link/Link'; @@ -61,6 +62,15 @@ class MovieIndexPoster extends Component { } } + onChange = ({ value, shiftKey }) => { + const { + id, + onSelectedChange + } = this.props; + + onSelectedChange({ id, value, shiftKey }); + } + // // Render @@ -89,6 +99,9 @@ class MovieIndexPoster extends Component { isSearchingMovie, onRefreshMoviePress, onSearchPress, + isMovieEditorActive, + isSelected, + onSelectedChange, ...otherProps } = this.props; @@ -109,6 +122,17 @@ class MovieIndexPoster extends Component {
    + { + isMovieEditorActive && +
    + +
    + }