mirror of
https://github.com/Radarr/Radarr.git
synced 2024-11-04 10:02:40 +01:00
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
This commit is contained in:
parent
b8f7ca0749
commit
a20222fbef
@ -61,7 +61,7 @@ const mapDispatchToProps = {
|
|||||||
dispatchImportMovie: importMovie,
|
dispatchImportMovie: importMovie,
|
||||||
dispatchClearImportMovie: clearImportMovie,
|
dispatchClearImportMovie: clearImportMovie,
|
||||||
dispatchFetchRootFolders: fetchRootFolders,
|
dispatchFetchRootFolders: fetchRootFolders,
|
||||||
dispatchSetAddSeriesDefault: setAddMovieDefault
|
dispatchSetAddMovieDefault: setAddMovieDefault
|
||||||
};
|
};
|
||||||
|
|
||||||
class ImportMovieConnector extends Component {
|
class ImportMovieConnector extends Component {
|
||||||
@ -74,7 +74,7 @@ class ImportMovieConnector extends Component {
|
|||||||
qualityProfiles,
|
qualityProfiles,
|
||||||
defaultQualityProfileId,
|
defaultQualityProfileId,
|
||||||
dispatchFetchRootFolders,
|
dispatchFetchRootFolders,
|
||||||
dispatchSetAddSeriesDefault
|
dispatchSetAddMovieDefault
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
if (!this.props.rootFoldersPopulated) {
|
if (!this.props.rootFoldersPopulated) {
|
||||||
@ -93,7 +93,7 @@ class ImportMovieConnector extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (setDefaults) {
|
if (setDefaults) {
|
||||||
dispatchSetAddSeriesDefault(setDefaultPayload);
|
dispatchSetAddMovieDefault(setDefaultPayload);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -105,7 +105,7 @@ class ImportMovieConnector extends Component {
|
|||||||
// Listeners
|
// Listeners
|
||||||
|
|
||||||
onInputChange = (ids, name, value) => {
|
onInputChange = (ids, name, value) => {
|
||||||
this.props.dispatchSetAddSeriesDefault({ [name]: value });
|
this.props.dispatchSetAddMovieDefault({ [name]: value });
|
||||||
|
|
||||||
ids.forEach((id) => {
|
ids.forEach((id) => {
|
||||||
this.props.dispatchSetImportMovieValue({
|
this.props.dispatchSetImportMovieValue({
|
||||||
@ -146,7 +146,7 @@ ImportMovieConnector.propTypes = {
|
|||||||
dispatchImportMovie: PropTypes.func.isRequired,
|
dispatchImportMovie: PropTypes.func.isRequired,
|
||||||
dispatchClearImportMovie: PropTypes.func.isRequired,
|
dispatchClearImportMovie: PropTypes.func.isRequired,
|
||||||
dispatchFetchRootFolders: PropTypes.func.isRequired,
|
dispatchFetchRootFolders: PropTypes.func.isRequired,
|
||||||
dispatchSetAddSeriesDefault: PropTypes.func.isRequired
|
dispatchSetAddMovieDefault: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
export default connect(createMapStateToProps, mapDispatchToProps)(ImportMovieConnector);
|
export default connect(createMapStateToProps, mapDispatchToProps)(ImportMovieConnector);
|
||||||
|
@ -5,8 +5,8 @@ import { cancelLookupMovie } from 'Store/Actions/importMovieActions';
|
|||||||
import ImportMovieFooter from './ImportMovieFooter';
|
import ImportMovieFooter from './ImportMovieFooter';
|
||||||
|
|
||||||
function isMixed(items, selectedIds, defaultValue, key) {
|
function isMixed(items, selectedIds, defaultValue, key) {
|
||||||
return _.some(items, (series) => {
|
return _.some(items, (movie) => {
|
||||||
return selectedIds.indexOf(series.id) > -1 && series[key] !== defaultValue;
|
return selectedIds.indexOf(movie.id) > -1 && movie[key] !== defaultValue;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,7 +23,7 @@
|
|||||||
min-width: 170px;
|
min-width: 170px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.series {
|
.movie {
|
||||||
composes: cell from '~Components/Table/Cells/VirtualTableRowCell.css';
|
composes: cell from '~Components/Table/Cells/VirtualTableRowCell.css';
|
||||||
|
|
||||||
flex: 0 1 400px;
|
flex: 0 1 400px;
|
||||||
|
@ -53,7 +53,7 @@ function ImportMovieRow(props) {
|
|||||||
/>
|
/>
|
||||||
</VirtualTableRowCell>
|
</VirtualTableRowCell>
|
||||||
|
|
||||||
<VirtualTableRowCell className={styles.series}>
|
<VirtualTableRowCell className={styles.movie}>
|
||||||
<ImportMovieSelectMovieConnector
|
<ImportMovieSelectMovieConnector
|
||||||
id={id}
|
id={id}
|
||||||
isExistingMovie={isExistingMovie}
|
isExistingMovie={isExistingMovie}
|
||||||
|
@ -66,23 +66,23 @@ class ImportMovieTable extends Component {
|
|||||||
const isExistingMovie = !!selectedMovie &&
|
const isExistingMovie = !!selectedMovie &&
|
||||||
_.some(prevProps.allMovies, { tmdbId: selectedMovie.tmdbId });
|
_.some(prevProps.allMovies, { tmdbId: selectedMovie.tmdbId });
|
||||||
|
|
||||||
// Props doesn't have a selected series or
|
// Props doesn't have a selected movie or
|
||||||
// the selected series is an existing series.
|
// the selected movie is an existing movie.
|
||||||
if ((!selectedMovie && prevItem.selectedMovie) || (isExistingMovie && !prevItem.selectedMovie)) {
|
if ((!selectedMovie && prevItem.selectedMovie) || (isExistingMovie && !prevItem.selectedMovie)) {
|
||||||
onSelectedChange({ id, value: false });
|
onSelectedChange({ id, value: false });
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// State is selected, but a series isn't selected or
|
// State is selected, but a movie isn't selected or
|
||||||
// the selected series is an existing series.
|
// the selected movie is an existing movie.
|
||||||
if (isSelected && (!selectedMovie || isExistingMovie)) {
|
if (isSelected && (!selectedMovie || isExistingMovie)) {
|
||||||
onSelectedChange({ id, value: false });
|
onSelectedChange({ id, value: false });
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// A series is being selected that wasn't previously selected.
|
// A movie is being selected that wasn't previously selected.
|
||||||
if (selectedMovie && selectedMovie !== prevItem.selectedMovie) {
|
if (selectedMovie && selectedMovie !== prevItem.selectedMovie) {
|
||||||
onSelectedChange({ id, value: true });
|
onSelectedChange({ id, value: true });
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
.series {
|
.movie {
|
||||||
padding: 10px 20px;
|
padding: 10px 20px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
|
@ -26,7 +26,7 @@ class ImportMovieSearchResult extends Component {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Link
|
<Link
|
||||||
className={styles.series}
|
className={styles.movie}
|
||||||
onPress={this.onPress}
|
onPress={this.onPress}
|
||||||
>
|
>
|
||||||
<ImportMovieTitle
|
<ImportMovieTitle
|
||||||
|
@ -259,7 +259,7 @@ class ImportMovieSelectMovie extends Component {
|
|||||||
title={item.title}
|
title={item.title}
|
||||||
year={item.year}
|
year={item.year}
|
||||||
studio={item.studio}
|
studio={item.studio}
|
||||||
onPress={this.onSeriesSelect}
|
onPress={this.onMovieSelect}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
|
@ -7,7 +7,6 @@ import Switch from 'Components/Router/Switch';
|
|||||||
import MovieIndexConnector from 'Movie/Index/MovieIndexConnector';
|
import MovieIndexConnector from 'Movie/Index/MovieIndexConnector';
|
||||||
import AddNewMovieConnector from 'AddMovie/AddNewMovie/AddNewMovieConnector';
|
import AddNewMovieConnector from 'AddMovie/AddNewMovie/AddNewMovieConnector';
|
||||||
import ImportMovies from 'AddMovie/ImportMovie/ImportMovies';
|
import ImportMovies from 'AddMovie/ImportMovie/ImportMovies';
|
||||||
import SeriesEditorConnector from 'Movie/Editor/SeriesEditorConnector';
|
|
||||||
import MovieDetailsPageConnector from 'Movie/Details/MovieDetailsPageConnector';
|
import MovieDetailsPageConnector from 'Movie/Details/MovieDetailsPageConnector';
|
||||||
import CalendarPageConnector from 'Calendar/CalendarPageConnector';
|
import CalendarPageConnector from 'Calendar/CalendarPageConnector';
|
||||||
import HistoryConnector from 'Activity/History/HistoryConnector';
|
import HistoryConnector from 'Activity/History/HistoryConnector';
|
||||||
@ -77,11 +76,6 @@ function AppRoutes(props) {
|
|||||||
component={ImportMovies}
|
component={ImportMovies}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Route
|
|
||||||
path="/serieseditor"
|
|
||||||
component={SeriesEditorConnector}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Route
|
<Route
|
||||||
path="/movie/:titleSlug"
|
path="/movie/:titleSlug"
|
||||||
component={MovieDetailsPageConnector}
|
component={MovieDetailsPageConnector}
|
||||||
|
@ -163,7 +163,7 @@ class CalendarLinkModalContent extends Component {
|
|||||||
type={inputTypes.TAG}
|
type={inputTypes.TAG}
|
||||||
name="tags"
|
name="tags"
|
||||||
value={tags}
|
value={tags}
|
||||||
helpText="Feed will only contain series with at least one matching tag"
|
helpText="Feed will only contain movies with at least one matching tag"
|
||||||
onChange={this.onInputChange}
|
onChange={this.onInputChange}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
|
@ -12,7 +12,7 @@ export const MISSING_EPISODE_SEARCH = 'MissingEpisodeSearch';
|
|||||||
export const MOVE_MOVIE = 'MoveMovie';
|
export const MOVE_MOVIE = 'MoveMovie';
|
||||||
export const REFRESH_MOVIE = 'RefreshMovie';
|
export const REFRESH_MOVIE = 'RefreshMovie';
|
||||||
export const RENAME_FILES = 'RenameFiles';
|
export const RENAME_FILES = 'RenameFiles';
|
||||||
export const RENAME_SERIES = 'RenameSeries';
|
export const RENAME_MOVIE = 'RenameMovie';
|
||||||
export const RESET_API_KEY = 'ResetApiKey';
|
export const RESET_API_KEY = 'ResetApiKey';
|
||||||
export const RSS_SYNC = 'RssSync';
|
export const RSS_SYNC = 'RssSync';
|
||||||
export const MOVIE_SEARCH = 'MoviesSearch';
|
export const MOVIE_SEARCH = 'MoviesSearch';
|
||||||
|
@ -7,7 +7,7 @@ import styles from './MonitorToggleButton.css';
|
|||||||
|
|
||||||
function getTooltip(monitored, isDisabled) {
|
function getTooltip(monitored, isDisabled) {
|
||||||
if (isDisabled) {
|
if (isDisabled) {
|
||||||
return 'Cannot toogle monitored state when series is unmonitored';
|
return 'Cannot toogle monitored state when movie is unmonitored';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (monitored) {
|
if (monitored) {
|
||||||
|
@ -136,8 +136,8 @@ class MovieSearchInput extends Component {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If an suggestion is not selected go to the first series,
|
// If an suggestion is not selected go to the first movie,
|
||||||
// otherwise go to the selected series.
|
// otherwise go to the selected movie.
|
||||||
|
|
||||||
if (highlightedSuggestionIndex == null) {
|
if (highlightedSuggestionIndex == null) {
|
||||||
this.goToMovie(suggestions[0]);
|
this.goToMovie(suggestions[0]);
|
||||||
|
@ -19,7 +19,7 @@ const SIDEBAR_WIDTH = parseInt(dimensions.sidebarWidth);
|
|||||||
|
|
||||||
const links = [
|
const links = [
|
||||||
{
|
{
|
||||||
iconName: icons.SERIES_CONTINUING,
|
iconName: icons.MOVIE_CONTINUING,
|
||||||
title: 'Movies',
|
title: 'Movies',
|
||||||
to: '/',
|
to: '/',
|
||||||
alias: '/movies',
|
alias: '/movies',
|
||||||
|
@ -34,6 +34,7 @@ import {
|
|||||||
faCalendarAlt as fasCalendarAlt,
|
faCalendarAlt as fasCalendarAlt,
|
||||||
faCaretDown as fasCaretDown,
|
faCaretDown as fasCaretDown,
|
||||||
faCheck as fasCheck,
|
faCheck as fasCheck,
|
||||||
|
faCheckSquare as fasCheckSquare,
|
||||||
faChevronCircleDown as fasChevronCircleDown,
|
faChevronCircleDown as fasChevronCircleDown,
|
||||||
faChevronCircleRight as fasChevronCircleRight,
|
faChevronCircleRight as fasChevronCircleRight,
|
||||||
faChevronCircleUp as fasChevronCircleUp,
|
faChevronCircleUp as fasChevronCircleUp,
|
||||||
@ -117,6 +118,7 @@ export const CARET_DOWN = fasCaretDown;
|
|||||||
export const CHECK = fasCheck;
|
export const CHECK = fasCheck;
|
||||||
export const CHECK_INDETERMINATE = fasMinus;
|
export const CHECK_INDETERMINATE = fasMinus;
|
||||||
export const CHECK_CIRCLE = fasCheckCircle;
|
export const CHECK_CIRCLE = fasCheckCircle;
|
||||||
|
export const CHECK_SQUARE = fasCheckSquare;
|
||||||
export const CIRCLE = fasCircle;
|
export const CIRCLE = fasCircle;
|
||||||
export const CIRCLE_OUTLINE = farCircle;
|
export const CIRCLE_OUTLINE = farCircle;
|
||||||
export const CLEAR = fasTrashAlt;
|
export const CLEAR = fasTrashAlt;
|
||||||
@ -180,7 +182,7 @@ export const SAVE = fasSave;
|
|||||||
export const SCHEDULED = farClock;
|
export const SCHEDULED = farClock;
|
||||||
export const SCORE = fasUserPlus;
|
export const SCORE = fasUserPlus;
|
||||||
export const SEARCH = fasSearch;
|
export const SEARCH = fasSearch;
|
||||||
export const SERIES_CONTINUING = fasPlay;
|
export const MOVIE_CONTINUING = fasPlay;
|
||||||
export const SERIES_ENDED = fasStop;
|
export const SERIES_ENDED = fasStop;
|
||||||
export const SETTINGS = fasCogs;
|
export const SETTINGS = fasCogs;
|
||||||
export const SHUTDOWN = fasPowerOff;
|
export const SHUTDOWN = fasPowerOff;
|
||||||
|
@ -527,7 +527,7 @@ class MovieDetails extends Component {
|
|||||||
<InteractiveImportModal
|
<InteractiveImportModal
|
||||||
isOpen={isInteractiveImportModalOpen}
|
isOpen={isInteractiveImportModalOpen}
|
||||||
folder={path}
|
folder={path}
|
||||||
allowSeriesChange={false}
|
allowMovieChange={false}
|
||||||
showFilterExistingFiles={true}
|
showFilterExistingFiles={true}
|
||||||
showImportMode={false}
|
showImportMode={false}
|
||||||
onModalClose={this.onInteractiveImportModalClose}
|
onModalClose={this.onInteractiveImportModalClose}
|
||||||
|
@ -69,7 +69,7 @@ function createMapStateToProps() {
|
|||||||
const isRefreshing = isMovieRefreshing || allMoviesRefreshing;
|
const isRefreshing = isMovieRefreshing || allMoviesRefreshing;
|
||||||
const isSearching = isCommandExecuting(findCommand(commands, { name: commandNames.MOVIE_SEARCH, movieIds: [movie.id] }));
|
const isSearching = isCommandExecuting(findCommand(commands, { name: commandNames.MOVIE_SEARCH, movieIds: [movie.id] }));
|
||||||
const isRenamingFiles = isCommandExecuting(findCommand(commands, { name: commandNames.RENAME_FILES, movieId: movie.id }));
|
const isRenamingFiles = isCommandExecuting(findCommand(commands, { name: commandNames.RENAME_FILES, movieId: movie.id }));
|
||||||
const isRenamingMovieCommand = findCommand(commands, { name: commandNames.RENAME_SERIES });
|
const isRenamingMovieCommand = findCommand(commands, { name: commandNames.RENAME_MOVIE });
|
||||||
const isRenamingMovie = (
|
const isRenamingMovie = (
|
||||||
isCommandExecuting(isRenamingMovieCommand) &&
|
isCommandExecuting(isRenamingMovieCommand) &&
|
||||||
isRenamingMovieCommand.body.movieIds.indexOf(movie.id) > -1
|
isRenamingMovieCommand.body.movieIds.indexOf(movie.id) > -1
|
||||||
|
@ -54,7 +54,7 @@ class MovieDetailsPageConnector extends Component {
|
|||||||
if (!titleSlug) {
|
if (!titleSlug) {
|
||||||
return (
|
return (
|
||||||
<NotFound
|
<NotFound
|
||||||
message="Sorry, that series cannot be found."
|
message="Sorry, that movie cannot be found."
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,7 @@ import Form from 'Components/Form/Form';
|
|||||||
import FormGroup from 'Components/Form/FormGroup';
|
import FormGroup from 'Components/Form/FormGroup';
|
||||||
import FormLabel from 'Components/Form/FormLabel';
|
import FormLabel from 'Components/Form/FormLabel';
|
||||||
import FormInputGroup from 'Components/Form/FormInputGroup';
|
import FormInputGroup from 'Components/Form/FormInputGroup';
|
||||||
import MoveMovieModal from 'Movie/MoveSeries/MoveSeriesModal';
|
import MoveMovieModal from 'Movie/MoveMovie/MoveMovieModal';
|
||||||
import styles from './EditMovieModalContent.css';
|
import styles from './EditMovieModalContent.css';
|
||||||
|
|
||||||
class EditMovieModalContent extends Component {
|
class EditMovieModalContent extends Component {
|
||||||
@ -45,7 +45,7 @@ class EditMovieModalContent extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onMoveSeriesPress = () => {
|
onMoveMoviePress = () => {
|
||||||
this.setState({ isConfirmMoveModalOpen: false });
|
this.setState({ isConfirmMoveModalOpen: false });
|
||||||
|
|
||||||
this.props.onSavePress(true);
|
this.props.onSavePress(true);
|
||||||
@ -159,7 +159,7 @@ class EditMovieModalContent extends Component {
|
|||||||
destinationPath={path.value}
|
destinationPath={path.value}
|
||||||
isOpen={this.state.isConfirmMoveModalOpen}
|
isOpen={this.state.isConfirmMoveModalOpen}
|
||||||
onSavePress={this.onSavePress}
|
onSavePress={this.onSavePress}
|
||||||
onMoveSeriesPress={this.onMoveSeriesPress}
|
onMoveMoviePress={this.onMoveMoviePress}
|
||||||
/>
|
/>
|
||||||
</ModalContent>
|
</ModalContent>
|
||||||
);
|
);
|
||||||
|
@ -29,21 +29,21 @@ function createMapStateToProps() {
|
|||||||
(state) => state.movies,
|
(state) => state.movies,
|
||||||
createMovieSelector(),
|
createMovieSelector(),
|
||||||
createIsPathChangingSelector(),
|
createIsPathChangingSelector(),
|
||||||
(seriesState, movie, isPathChanging) => {
|
(moviesState, movie, isPathChanging) => {
|
||||||
const {
|
const {
|
||||||
isSaving,
|
isSaving,
|
||||||
saveError,
|
saveError,
|
||||||
pendingChanges
|
pendingChanges
|
||||||
} = seriesState;
|
} = moviesState;
|
||||||
|
|
||||||
const seriesSettings = _.pick(movie, [
|
const movieSettings = _.pick(movie, [
|
||||||
'monitored',
|
'monitored',
|
||||||
'qualityProfileId',
|
'qualityProfileId',
|
||||||
'path',
|
'path',
|
||||||
'tags'
|
'tags'
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const settings = selectSettings(seriesSettings, pendingChanges, saveError);
|
const settings = selectSettings(movieSettings, pendingChanges, saveError);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
title: movie.title,
|
title: movie.title,
|
||||||
@ -97,7 +97,7 @@ class EditMovieModalContentConnector extends Component {
|
|||||||
{...this.props}
|
{...this.props}
|
||||||
onInputChange={this.onInputChange}
|
onInputChange={this.onInputChange}
|
||||||
onSavePress={this.onSavePress}
|
onSavePress={this.onSavePress}
|
||||||
onMoveSeriesPress={this.onMoveSeriesPress}
|
onMoveMoviePress={this.onMoveMoviePress}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -43,7 +43,7 @@ class DeleteMovieModalContent extends Component {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
series,
|
movies,
|
||||||
onModalClose
|
onModalClose
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const deleteFiles = this.state.deleteFiles;
|
const deleteFiles = this.state.deleteFiles;
|
||||||
@ -51,19 +51,19 @@ class DeleteMovieModalContent extends Component {
|
|||||||
return (
|
return (
|
||||||
<ModalContent onModalClose={onModalClose}>
|
<ModalContent onModalClose={onModalClose}>
|
||||||
<ModalHeader>
|
<ModalHeader>
|
||||||
Delete Selected Series
|
Delete Selected Movie(s)
|
||||||
</ModalHeader>
|
</ModalHeader>
|
||||||
|
|
||||||
<ModalBody>
|
<ModalBody>
|
||||||
<div>
|
<div>
|
||||||
<FormGroup>
|
<FormGroup>
|
||||||
<FormLabel>{`Delete Series Folder${series.length > 1 ? 's' : ''}`}</FormLabel>
|
<FormLabel>{`Delete Movie Folder${movies.length > 1 ? 's' : ''}`}</FormLabel>
|
||||||
|
|
||||||
<FormInputGroup
|
<FormInputGroup
|
||||||
type={inputTypes.CHECK}
|
type={inputTypes.CHECK}
|
||||||
name="deleteFiles"
|
name="deleteFiles"
|
||||||
value={deleteFiles}
|
value={deleteFiles}
|
||||||
helpText={`Delete Series Folder${series.length > 1 ? 's' : ''} and all contents`}
|
helpText={`Delete Movie Folder${movies.length > 1 ? 's' : ''} and all contents`}
|
||||||
kind={kinds.DANGER}
|
kind={kinds.DANGER}
|
||||||
onChange={this.onDeleteFilesChange}
|
onChange={this.onDeleteFilesChange}
|
||||||
/>
|
/>
|
||||||
@ -71,12 +71,12 @@ class DeleteMovieModalContent extends Component {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={styles.message}>
|
<div className={styles.message}>
|
||||||
{`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' : ''}?`}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ul>
|
<ul>
|
||||||
{
|
{
|
||||||
series.map((s) => {
|
movies.map((s) => {
|
||||||
return (
|
return (
|
||||||
<li key={s.title}>
|
<li key={s.title}>
|
||||||
<span>{s.title}</span>
|
<span>{s.title}</span>
|
||||||
@ -115,7 +115,7 @@ class DeleteMovieModalContent extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
DeleteMovieModalContent.propTypes = {
|
DeleteMovieModalContent.propTypes = {
|
||||||
series: PropTypes.arrayOf(PropTypes.object).isRequired,
|
movies: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
onModalClose: PropTypes.func.isRequired,
|
onModalClose: PropTypes.func.isRequired,
|
||||||
onDeleteSelectedPress: PropTypes.func.isRequired
|
onDeleteSelectedPress: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
@ -2,20 +2,20 @@ import _ from 'lodash';
|
|||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
import createAllMoviesSelector from 'Store/Selectors/createAllMoviesSelector';
|
import createAllMoviesSelector from 'Store/Selectors/createAllMoviesSelector';
|
||||||
import { bulkDeleteMovie } from 'Store/Actions/movieEditorActions';
|
import { bulkDeleteMovie } from 'Store/Actions/movieIndexActions';
|
||||||
import DeleteMovieModalContent from './DeleteMovieModalContent';
|
import DeleteMovieModalContent from './DeleteMovieModalContent';
|
||||||
|
|
||||||
function createMapStateToProps() {
|
function createMapStateToProps() {
|
||||||
return createSelector(
|
return createSelector(
|
||||||
(state, { seriesIds }) => seriesIds,
|
(state, { movieIds }) => movieIds,
|
||||||
createAllMoviesSelector(),
|
createAllMoviesSelector(),
|
||||||
(seriesIds, allMovies) => {
|
(movieIds, allMovies) => {
|
||||||
const selectedMovie = _.intersectionWith(allMovies, seriesIds, (s, id) => {
|
const selectedMovie = _.intersectionWith(allMovies, movieIds, (s, id) => {
|
||||||
return s.id === id;
|
return s.id === id;
|
||||||
});
|
});
|
||||||
|
|
||||||
const sortedSeries = _.orderBy(selectedMovie, 'sortTitle');
|
const sortedMovies = _.orderBy(selectedMovie, 'sortTitle');
|
||||||
const series = _.map(sortedSeries, (s) => {
|
const movies = _.map(sortedMovies, (s) => {
|
||||||
return {
|
return {
|
||||||
title: s.title,
|
title: s.title,
|
||||||
path: s.path
|
path: s.path
|
||||||
@ -23,7 +23,7 @@ function createMapStateToProps() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
series
|
movies
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@ -33,7 +33,7 @@ function createMapDispatchToProps(dispatch, props) {
|
|||||||
return {
|
return {
|
||||||
onDeleteSelectedPress(deleteFiles) {
|
onDeleteSelectedPress(deleteFiles) {
|
||||||
dispatch(bulkDeleteMovie({
|
dispatch(bulkDeleteMovie({
|
||||||
seriesIds: props.seriesIds,
|
movieIds: props.movieIds,
|
||||||
deleteFiles
|
deleteFiles
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
@ -6,15 +6,15 @@ import QualityProfileSelectInputConnector from 'Components/Form/QualityProfileSe
|
|||||||
import RootFolderSelectInputConnector from 'Components/Form/RootFolderSelectInputConnector';
|
import RootFolderSelectInputConnector from 'Components/Form/RootFolderSelectInputConnector';
|
||||||
import SpinnerButton from 'Components/Link/SpinnerButton';
|
import SpinnerButton from 'Components/Link/SpinnerButton';
|
||||||
import PageContentFooter from 'Components/Page/PageContentFooter';
|
import PageContentFooter from 'Components/Page/PageContentFooter';
|
||||||
import MoveSeriesModal from 'Movie/MoveSeries/MoveSeriesModal';
|
import MoveMovieModal from 'Movie/MoveMovie/MoveMovieModal';
|
||||||
import TagsModal from './Tags/TagsModal';
|
import TagsModal from './Tags/TagsModal';
|
||||||
import DeleteMovieModal from './Delete/DeleteMovieModal';
|
import DeleteMovieModal from './Delete/DeleteMovieModal';
|
||||||
import SeriesEditorFooterLabel from './SeriesEditorFooterLabel';
|
import MovieEditorFooterLabel from './MovieEditorFooterLabel';
|
||||||
import styles from './SeriesEditorFooter.css';
|
import styles from './MovieEditorFooter.css';
|
||||||
|
|
||||||
const NO_CHANGE = 'noChange';
|
const NO_CHANGE = 'noChange';
|
||||||
|
|
||||||
class SeriesEditorFooter extends Component {
|
class MovieEditorFooter extends Component {
|
||||||
|
|
||||||
//
|
//
|
||||||
// Lifecycle
|
// Lifecycle
|
||||||
@ -112,7 +112,7 @@ class SeriesEditorFooter extends Component {
|
|||||||
this.props.onSaveSelected({ rootFolderPath: this.state.destinationRootFolder });
|
this.props.onSaveSelected({ rootFolderPath: this.state.destinationRootFolder });
|
||||||
}
|
}
|
||||||
|
|
||||||
onMoveSeriesPress = () => {
|
onMoveMoviePress = () => {
|
||||||
this.setState({
|
this.setState({
|
||||||
isConfirmMoveModalOpen: false,
|
isConfirmMoveModalOpen: false,
|
||||||
destinationRootFolder: null
|
destinationRootFolder: null
|
||||||
@ -129,12 +129,12 @@ class SeriesEditorFooter extends Component {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
seriesIds,
|
movieIds,
|
||||||
selectedCount,
|
selectedCount,
|
||||||
isSaving,
|
isSaving,
|
||||||
isDeleting,
|
isDeleting,
|
||||||
isOrganizingSeries,
|
isOrganizingMovie,
|
||||||
onOrganizeSeriesPress
|
onOrganizeMoviePress
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@ -157,8 +157,8 @@ class SeriesEditorFooter extends Component {
|
|||||||
return (
|
return (
|
||||||
<PageContentFooter>
|
<PageContentFooter>
|
||||||
<div className={styles.inputContainer}>
|
<div className={styles.inputContainer}>
|
||||||
<SeriesEditorFooterLabel
|
<MovieEditorFooterLabel
|
||||||
label="Monitor Series"
|
label="Monitor Movie"
|
||||||
isSaving={isSaving && monitored !== NO_CHANGE}
|
isSaving={isSaving && monitored !== NO_CHANGE}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@ -172,7 +172,7 @@ class SeriesEditorFooter extends Component {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={styles.inputContainer}>
|
<div className={styles.inputContainer}>
|
||||||
<SeriesEditorFooterLabel
|
<MovieEditorFooterLabel
|
||||||
label="Quality Profile"
|
label="Quality Profile"
|
||||||
isSaving={isSaving && qualityProfileId !== NO_CHANGE}
|
isSaving={isSaving && qualityProfileId !== NO_CHANGE}
|
||||||
/>
|
/>
|
||||||
@ -187,7 +187,7 @@ class SeriesEditorFooter extends Component {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={styles.inputContainer}>
|
<div className={styles.inputContainer}>
|
||||||
<SeriesEditorFooterLabel
|
<MovieEditorFooterLabel
|
||||||
label="Root Folder"
|
label="Root Folder"
|
||||||
isSaving={isSaving && rootFolderPath !== NO_CHANGE}
|
isSaving={isSaving && rootFolderPath !== NO_CHANGE}
|
||||||
/>
|
/>
|
||||||
@ -204,8 +204,8 @@ class SeriesEditorFooter extends Component {
|
|||||||
|
|
||||||
<div className={styles.buttonContainer}>
|
<div className={styles.buttonContainer}>
|
||||||
<div className={styles.buttonContainerContent}>
|
<div className={styles.buttonContainerContent}>
|
||||||
<SeriesEditorFooterLabel
|
<MovieEditorFooterLabel
|
||||||
label={`${selectedCount} Series Selected`}
|
label={`${selectedCount} Movie(s) Selected`}
|
||||||
isSaving={false}
|
isSaving={false}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@ -214,9 +214,9 @@ class SeriesEditorFooter extends Component {
|
|||||||
<SpinnerButton
|
<SpinnerButton
|
||||||
className={styles.organizeSelectedButton}
|
className={styles.organizeSelectedButton}
|
||||||
kind={kinds.WARNING}
|
kind={kinds.WARNING}
|
||||||
isSpinning={isOrganizingSeries}
|
isSpinning={isOrganizingMovie}
|
||||||
isDisabled={!selectedCount || isOrganizingSeries}
|
isDisabled={!selectedCount || isOrganizingMovie}
|
||||||
onPress={onOrganizeSeriesPress}
|
onPress={onOrganizeMoviePress}
|
||||||
>
|
>
|
||||||
Rename Files
|
Rename Files
|
||||||
</SpinnerButton>
|
</SpinnerButton>
|
||||||
@ -224,7 +224,7 @@ class SeriesEditorFooter extends Component {
|
|||||||
<SpinnerButton
|
<SpinnerButton
|
||||||
className={styles.tagsButton}
|
className={styles.tagsButton}
|
||||||
isSpinning={isSaving && savingTags}
|
isSpinning={isSaving && savingTags}
|
||||||
isDisabled={!selectedCount || isOrganizingSeries}
|
isDisabled={!selectedCount || isOrganizingMovie}
|
||||||
onPress={this.onTagsPress}
|
onPress={this.onTagsPress}
|
||||||
>
|
>
|
||||||
Set Tags
|
Set Tags
|
||||||
@ -246,38 +246,38 @@ class SeriesEditorFooter extends Component {
|
|||||||
|
|
||||||
<TagsModal
|
<TagsModal
|
||||||
isOpen={isTagsModalOpen}
|
isOpen={isTagsModalOpen}
|
||||||
seriesIds={seriesIds}
|
movieIds={movieIds}
|
||||||
onApplyTagsPress={this.onApplyTagsPress}
|
onApplyTagsPress={this.onApplyTagsPress}
|
||||||
onModalClose={this.onTagsModalClose}
|
onModalClose={this.onTagsModalClose}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<DeleteMovieModal
|
<DeleteMovieModal
|
||||||
isOpen={isDeleteMovieModalOpen}
|
isOpen={isDeleteMovieModalOpen}
|
||||||
seriesIds={seriesIds}
|
movieIds={movieIds}
|
||||||
onModalClose={this.onDeleteMovieModalClose}
|
onModalClose={this.onDeleteMovieModalClose}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<MoveSeriesModal
|
<MoveMovieModal
|
||||||
destinationRootFolder={destinationRootFolder}
|
destinationRootFolder={destinationRootFolder}
|
||||||
isOpen={isConfirmMoveModalOpen}
|
isOpen={isConfirmMoveModalOpen}
|
||||||
onSavePress={this.onSaveRootFolderPress}
|
onSavePress={this.onSaveRootFolderPress}
|
||||||
onMoveSeriesPress={this.onMoveSeriesPress}
|
onMoveMoviePress={this.onMoveMoviePress}
|
||||||
/>
|
/>
|
||||||
</PageContentFooter>
|
</PageContentFooter>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SeriesEditorFooter.propTypes = {
|
MovieEditorFooter.propTypes = {
|
||||||
seriesIds: PropTypes.arrayOf(PropTypes.number).isRequired,
|
movieIds: PropTypes.arrayOf(PropTypes.number).isRequired,
|
||||||
selectedCount: PropTypes.number.isRequired,
|
selectedCount: PropTypes.number.isRequired,
|
||||||
isSaving: PropTypes.bool.isRequired,
|
isSaving: PropTypes.bool.isRequired,
|
||||||
saveError: PropTypes.object,
|
saveError: PropTypes.object,
|
||||||
isDeleting: PropTypes.bool.isRequired,
|
isDeleting: PropTypes.bool.isRequired,
|
||||||
deleteError: PropTypes.object,
|
deleteError: PropTypes.object,
|
||||||
isOrganizingSeries: PropTypes.bool.isRequired,
|
isOrganizingMovie: PropTypes.bool.isRequired,
|
||||||
onSaveSelected: PropTypes.func.isRequired,
|
onSaveSelected: PropTypes.func.isRequired,
|
||||||
onOrganizeSeriesPress: PropTypes.func.isRequired
|
onOrganizeMoviePress: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
export default SeriesEditorFooter;
|
export default MovieEditorFooter;
|
@ -2,9 +2,9 @@ import PropTypes from 'prop-types';
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { icons } from 'Helpers/Props';
|
import { icons } from 'Helpers/Props';
|
||||||
import SpinnerIcon from 'Components/SpinnerIcon';
|
import SpinnerIcon from 'Components/SpinnerIcon';
|
||||||
import styles from './SeriesEditorFooterLabel.css';
|
import styles from './MovieEditorFooterLabel.css';
|
||||||
|
|
||||||
function SeriesEditorFooterLabel(props) {
|
function MovieEditorFooterLabel(props) {
|
||||||
const {
|
const {
|
||||||
className,
|
className,
|
||||||
label,
|
label,
|
||||||
@ -27,14 +27,14 @@ function SeriesEditorFooterLabel(props) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
SeriesEditorFooterLabel.propTypes = {
|
MovieEditorFooterLabel.propTypes = {
|
||||||
className: PropTypes.string.isRequired,
|
className: PropTypes.string.isRequired,
|
||||||
label: PropTypes.string.isRequired,
|
label: PropTypes.string.isRequired,
|
||||||
isSaving: PropTypes.bool.isRequired
|
isSaving: PropTypes.bool.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
SeriesEditorFooterLabel.defaultProps = {
|
MovieEditorFooterLabel.defaultProps = {
|
||||||
className: styles.label
|
className: styles.label
|
||||||
};
|
};
|
||||||
|
|
||||||
export default SeriesEditorFooterLabel;
|
export default MovieEditorFooterLabel;
|
@ -1,9 +1,9 @@
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import Modal from 'Components/Modal/Modal';
|
import Modal from 'Components/Modal/Modal';
|
||||||
import OrganizeSeriesModalContentConnector from './OrganizeSeriesModalContentConnector';
|
import OrganizeMovieModalContentConnector from './OrganizeMovieModalContentConnector';
|
||||||
|
|
||||||
function OrganizeSeriesModal(props) {
|
function OrganizeMovieModal(props) {
|
||||||
const {
|
const {
|
||||||
isOpen,
|
isOpen,
|
||||||
onModalClose,
|
onModalClose,
|
||||||
@ -15,7 +15,7 @@ function OrganizeSeriesModal(props) {
|
|||||||
isOpen={isOpen}
|
isOpen={isOpen}
|
||||||
onModalClose={onModalClose}
|
onModalClose={onModalClose}
|
||||||
>
|
>
|
||||||
<OrganizeSeriesModalContentConnector
|
<OrganizeMovieModalContentConnector
|
||||||
{...otherProps}
|
{...otherProps}
|
||||||
onModalClose={onModalClose}
|
onModalClose={onModalClose}
|
||||||
/>
|
/>
|
||||||
@ -23,9 +23,9 @@ function OrganizeSeriesModal(props) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
OrganizeSeriesModal.propTypes = {
|
OrganizeMovieModal.propTypes = {
|
||||||
isOpen: PropTypes.bool.isRequired,
|
isOpen: PropTypes.bool.isRequired,
|
||||||
onModalClose: PropTypes.func.isRequired
|
onModalClose: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
export default OrganizeSeriesModal;
|
export default OrganizeMovieModal;
|
@ -8,24 +8,24 @@ import ModalContent from 'Components/Modal/ModalContent';
|
|||||||
import ModalHeader from 'Components/Modal/ModalHeader';
|
import ModalHeader from 'Components/Modal/ModalHeader';
|
||||||
import ModalBody from 'Components/Modal/ModalBody';
|
import ModalBody from 'Components/Modal/ModalBody';
|
||||||
import ModalFooter from 'Components/Modal/ModalFooter';
|
import ModalFooter from 'Components/Modal/ModalFooter';
|
||||||
import styles from './OrganizeSeriesModalContent.css';
|
import styles from './OrganizeMovieModalContent.css';
|
||||||
|
|
||||||
function OrganizeSeriesModalContent(props) {
|
function OrganizeMovieModalContent(props) {
|
||||||
const {
|
const {
|
||||||
seriesTitles,
|
movieTitles,
|
||||||
onModalClose,
|
onModalClose,
|
||||||
onOrganizeSeriesPress
|
onOrganizeMoviePress
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ModalContent onModalClose={onModalClose}>
|
<ModalContent onModalClose={onModalClose}>
|
||||||
<ModalHeader>
|
<ModalHeader>
|
||||||
Organize Selected Series
|
Organize Selected Movies
|
||||||
</ModalHeader>
|
</ModalHeader>
|
||||||
|
|
||||||
<ModalBody>
|
<ModalBody>
|
||||||
<Alert>
|
<Alert>
|
||||||
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
|
||||||
<Icon
|
<Icon
|
||||||
className={styles.renameIcon}
|
className={styles.renameIcon}
|
||||||
name={icons.ORGANIZE}
|
name={icons.ORGANIZE}
|
||||||
@ -33,12 +33,12 @@ function OrganizeSeriesModalContent(props) {
|
|||||||
</Alert>
|
</Alert>
|
||||||
|
|
||||||
<div className={styles.message}>
|
<div className={styles.message}>
|
||||||
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)?
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ul>
|
<ul>
|
||||||
{
|
{
|
||||||
seriesTitles.map((title) => {
|
movieTitles.map((title) => {
|
||||||
return (
|
return (
|
||||||
<li key={title}>
|
<li key={title}>
|
||||||
{title}
|
{title}
|
||||||
@ -56,7 +56,7 @@ function OrganizeSeriesModalContent(props) {
|
|||||||
|
|
||||||
<Button
|
<Button
|
||||||
kind={kinds.DANGER}
|
kind={kinds.DANGER}
|
||||||
onPress={onOrganizeSeriesPress}
|
onPress={onOrganizeMoviePress}
|
||||||
>
|
>
|
||||||
Organize
|
Organize
|
||||||
</Button>
|
</Button>
|
||||||
@ -65,10 +65,10 @@ function OrganizeSeriesModalContent(props) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
OrganizeSeriesModalContent.propTypes = {
|
OrganizeMovieModalContent.propTypes = {
|
||||||
seriesTitles: PropTypes.arrayOf(PropTypes.string).isRequired,
|
movieTitles: PropTypes.arrayOf(PropTypes.string).isRequired,
|
||||||
onModalClose: PropTypes.func.isRequired,
|
onModalClose: PropTypes.func.isRequired,
|
||||||
onOrganizeSeriesPress: PropTypes.func.isRequired
|
onOrganizeMoviePress: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
export default OrganizeSeriesModalContent;
|
export default OrganizeMovieModalContent;
|
@ -6,22 +6,22 @@ import { createSelector } from 'reselect';
|
|||||||
import createAllMoviesSelector from 'Store/Selectors/createAllMoviesSelector';
|
import createAllMoviesSelector from 'Store/Selectors/createAllMoviesSelector';
|
||||||
import { executeCommand } from 'Store/Actions/commandActions';
|
import { executeCommand } from 'Store/Actions/commandActions';
|
||||||
import * as commandNames from 'Commands/commandNames';
|
import * as commandNames from 'Commands/commandNames';
|
||||||
import OrganizeSeriesModalContent from './OrganizeSeriesModalContent';
|
import OrganizeMovieModalContent from './OrganizeMovieModalContent';
|
||||||
|
|
||||||
function createMapStateToProps() {
|
function createMapStateToProps() {
|
||||||
return createSelector(
|
return createSelector(
|
||||||
(state, { seriesIds }) => seriesIds,
|
(state, { movieIds }) => movieIds,
|
||||||
createAllMoviesSelector(),
|
createAllMoviesSelector(),
|
||||||
(seriesIds, allMovies) => {
|
(movieIds, allMovies) => {
|
||||||
const series = _.intersectionWith(allMovies, seriesIds, (s, id) => {
|
const movies = _.intersectionWith(allMovies, movieIds, (s, id) => {
|
||||||
return s.id === id;
|
return s.id === id;
|
||||||
});
|
});
|
||||||
|
|
||||||
const sortedSeries = _.orderBy(series, 'sortTitle');
|
const sortedMovies = _.orderBy(movies, 'sortTitle');
|
||||||
const seriesTitles = _.map(sortedSeries, 'title');
|
const movieTitles = _.map(sortedMovies, 'title');
|
||||||
|
|
||||||
return {
|
return {
|
||||||
seriesTitles
|
movieTitles
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@ -31,15 +31,15 @@ const mapDispatchToProps = {
|
|||||||
executeCommand
|
executeCommand
|
||||||
};
|
};
|
||||||
|
|
||||||
class OrganizeSeriesModalContentConnector extends Component {
|
class OrganizeMovieModalContentConnector extends Component {
|
||||||
|
|
||||||
//
|
//
|
||||||
// Listeners
|
// Listeners
|
||||||
|
|
||||||
onOrganizeSeriesPress = () => {
|
onOrganizeMoviePress = () => {
|
||||||
this.props.executeCommand({
|
this.props.executeCommand({
|
||||||
name: commandNames.RENAME_SERIES,
|
name: commandNames.RENAME_MOVIE,
|
||||||
seriesIds: this.props.seriesIds
|
movieIds: this.props.movieIds
|
||||||
});
|
});
|
||||||
|
|
||||||
this.props.onModalClose(true);
|
this.props.onModalClose(true);
|
||||||
@ -50,18 +50,18 @@ class OrganizeSeriesModalContentConnector extends Component {
|
|||||||
|
|
||||||
render(props) {
|
render(props) {
|
||||||
return (
|
return (
|
||||||
<OrganizeSeriesModalContent
|
<OrganizeMovieModalContent
|
||||||
{...this.props}
|
{...this.props}
|
||||||
onOrganizeSeriesPress={this.onOrganizeSeriesPress}
|
onOrganizeMoviePress={this.onOrganizeMoviePress}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
OrganizeSeriesModalContentConnector.propTypes = {
|
OrganizeMovieModalContentConnector.propTypes = {
|
||||||
seriesIds: PropTypes.arrayOf(PropTypes.number).isRequired,
|
movieIds: PropTypes.arrayOf(PropTypes.number).isRequired,
|
||||||
onModalClose: PropTypes.func.isRequired,
|
onModalClose: PropTypes.func.isRequired,
|
||||||
executeCommand: PropTypes.func.isRequired
|
executeCommand: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
export default connect(createMapStateToProps, mapDispatchToProps)(OrganizeSeriesModalContentConnector);
|
export default connect(createMapStateToProps, mapDispatchToProps)(OrganizeMovieModalContentConnector);
|
@ -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 (
|
|
||||||
<PageContent title="Series Editor">
|
|
||||||
<PageToolbar>
|
|
||||||
<PageToolbarSection />
|
|
||||||
<PageToolbarSection alignContent={align.RIGHT}>
|
|
||||||
<FilterMenu
|
|
||||||
alignMenu={align.RIGHT}
|
|
||||||
selectedFilterKey={selectedFilterKey}
|
|
||||||
filters={filters}
|
|
||||||
customFilters={customFilters}
|
|
||||||
filterModalConnectorComponent={SeriesEditorFilterModalConnector}
|
|
||||||
onFilterSelect={onFilterSelect}
|
|
||||||
/>
|
|
||||||
</PageToolbarSection>
|
|
||||||
</PageToolbar>
|
|
||||||
|
|
||||||
<PageContentBodyConnector>
|
|
||||||
{
|
|
||||||
isFetching && !isPopulated &&
|
|
||||||
<LoadingIndicator />
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
!isFetching && !!error &&
|
|
||||||
<div>Unable to load the calendar</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
!error && isPopulated && !!items.length &&
|
|
||||||
<div>
|
|
||||||
<Table
|
|
||||||
columns={columns}
|
|
||||||
sortKey={sortKey}
|
|
||||||
sortDirection={sortDirection}
|
|
||||||
selectAll={true}
|
|
||||||
allSelected={allSelected}
|
|
||||||
allUnselected={allUnselected}
|
|
||||||
onSortPress={onSortPress}
|
|
||||||
onSelectAllChange={this.onSelectAllChange}
|
|
||||||
>
|
|
||||||
<TableBody>
|
|
||||||
{
|
|
||||||
items.map((item) => {
|
|
||||||
return (
|
|
||||||
<SeriesEditorRowConnector
|
|
||||||
key={item.id}
|
|
||||||
{...item}
|
|
||||||
columns={columns}
|
|
||||||
isSelected={selectedState[item.id]}
|
|
||||||
onSelectedChange={this.onSelectedChange}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
</TableBody>
|
|
||||||
</Table>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
!error && isPopulated && !items.length &&
|
|
||||||
<NoMovie totalItems={totalItems} />
|
|
||||||
}
|
|
||||||
</PageContentBodyConnector>
|
|
||||||
|
|
||||||
<SeriesEditorFooter
|
|
||||||
seriesIds={selectedMovieIds}
|
|
||||||
selectedCount={selectedMovieIds.length}
|
|
||||||
isSaving={isSaving}
|
|
||||||
saveError={saveError}
|
|
||||||
isDeleting={isDeleting}
|
|
||||||
deleteError={deleteError}
|
|
||||||
isOrganizingSeries={isOrganizingSeries}
|
|
||||||
onSaveSelected={this.onSaveSelected}
|
|
||||||
onOrganizeSeriesPress={this.onOrganizeSeriesPress}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<OrganizeSeriesModal
|
|
||||||
isOpen={this.state.isOrganizingSeriesModalOpen}
|
|
||||||
seriesIds={selectedMovieIds}
|
|
||||||
onModalClose={this.onOrganizeSeriesModalClose}
|
|
||||||
/>
|
|
||||||
</PageContent>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
@ -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 (
|
|
||||||
<SeriesEditor
|
|
||||||
{...this.props}
|
|
||||||
onSortPress={this.onSortPress}
|
|
||||||
onFilterSelect={this.onFilterSelect}
|
|
||||||
onSaveSelected={this.onSaveSelected}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
@ -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);
|
|
@ -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 (
|
|
||||||
<TableRow>
|
|
||||||
<TableSelectCell
|
|
||||||
id={id}
|
|
||||||
isSelected={isSelected}
|
|
||||||
onSelectedChange={onSelectedChange}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<MovieStatusCell
|
|
||||||
monitored={monitored}
|
|
||||||
status={status}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<TableRowCell>
|
|
||||||
<MovieTitleLink
|
|
||||||
titleSlug={titleSlug}
|
|
||||||
title={title}
|
|
||||||
/>
|
|
||||||
</TableRowCell>
|
|
||||||
|
|
||||||
<TableRowCell>
|
|
||||||
{qualityProfile.name}
|
|
||||||
</TableRowCell>
|
|
||||||
|
|
||||||
<TableRowCell>
|
|
||||||
{path}
|
|
||||||
</TableRowCell>
|
|
||||||
|
|
||||||
<TableRowCell>
|
|
||||||
<TagListConnector
|
|
||||||
tags={tags}
|
|
||||||
/>
|
|
||||||
</TableRowCell>
|
|
||||||
</TableRow>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
@ -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 (
|
|
||||||
<SeriesEditorRow
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
SeriesEditorRowConnector.propTypes = {
|
|
||||||
qualityProfileId: PropTypes.number.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
export default connect(createMapStateToProps)(SeriesEditorRowConnector);
|
|
@ -49,7 +49,7 @@ class TagsModalContent extends Component {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
seriesTags,
|
movieTags,
|
||||||
tagList,
|
tagList,
|
||||||
onModalClose
|
onModalClose
|
||||||
} = this.props;
|
} = this.props;
|
||||||
@ -93,7 +93,7 @@ class TagsModalContent extends Component {
|
|||||||
value={applyTags}
|
value={applyTags}
|
||||||
values={applyTagsOptions}
|
values={applyTagsOptions}
|
||||||
helpTexts={[
|
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',
|
'Add: Add the tags the existing list of tags',
|
||||||
'Remove: Remove the entered tags',
|
'Remove: Remove the entered tags',
|
||||||
'Replace: Replace the tags with the entered tags (enter no tags to clear all tags)'
|
'Replace: Replace the tags with the entered tags (enter no tags to clear all tags)'
|
||||||
@ -107,7 +107,7 @@ class TagsModalContent extends Component {
|
|||||||
|
|
||||||
<div className={styles.result}>
|
<div className={styles.result}>
|
||||||
{
|
{
|
||||||
seriesTags.map((t) => {
|
movieTags.map((t) => {
|
||||||
const tag = _.find(tagList, { id: t });
|
const tag = _.find(tagList, { id: t });
|
||||||
|
|
||||||
if (!tag) {
|
if (!tag) {
|
||||||
@ -139,7 +139,7 @@ class TagsModalContent extends Component {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (seriesTags.indexOf(t) > -1) {
|
if (movieTags.indexOf(t) > -1) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -178,7 +178,7 @@ class TagsModalContent extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TagsModalContent.propTypes = {
|
TagsModalContent.propTypes = {
|
||||||
seriesTags: PropTypes.arrayOf(PropTypes.number).isRequired,
|
movieTags: PropTypes.arrayOf(PropTypes.number).isRequired,
|
||||||
tagList: PropTypes.arrayOf(PropTypes.object).isRequired,
|
tagList: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
onModalClose: PropTypes.func.isRequired,
|
onModalClose: PropTypes.func.isRequired,
|
||||||
onApplyTagsPress: PropTypes.func.isRequired
|
onApplyTagsPress: PropTypes.func.isRequired
|
||||||
|
@ -7,18 +7,18 @@ import TagsModalContent from './TagsModalContent';
|
|||||||
|
|
||||||
function createMapStateToProps() {
|
function createMapStateToProps() {
|
||||||
return createSelector(
|
return createSelector(
|
||||||
(state, { seriesIds }) => seriesIds,
|
(state, { movieIds }) => movieIds,
|
||||||
createAllMoviesSelector(),
|
createAllMoviesSelector(),
|
||||||
createTagsSelector(),
|
createTagsSelector(),
|
||||||
(seriesIds, allMovies, tagList) => {
|
(movieIds, allMovies, tagList) => {
|
||||||
const series = _.intersectionWith(allMovies, seriesIds, (s, id) => {
|
const movies = _.intersectionWith(allMovies, movieIds, (s, id) => {
|
||||||
return s.id === id;
|
return s.id === id;
|
||||||
});
|
});
|
||||||
|
|
||||||
const seriesTags = _.uniq(_.concat(..._.map(series, 'tags')));
|
const movieTags = _.uniq(_.concat(..._.map(movies, 'tags')));
|
||||||
|
|
||||||
return {
|
return {
|
||||||
seriesTags,
|
movieTags,
|
||||||
tagList
|
tagList
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
.blankpad {
|
.blankpad {
|
||||||
padding-left:2em;
|
|
||||||
padding-top: 10px;
|
padding-top: 10px;
|
||||||
padding-bottom: 10px;
|
padding-bottom: 10px;
|
||||||
|
padding-left: 2em;
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,9 @@ import _ from 'lodash';
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import hasDifferentItems from 'Utilities/Object/hasDifferentItems';
|
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 { align, icons, sortDirections } from 'Helpers/Props';
|
||||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||||
import PageContent from 'Components/Page/PageContent';
|
import PageContent from 'Components/Page/PageContent';
|
||||||
@ -23,7 +26,9 @@ import MovieIndexFilterMenu from './Menus/MovieIndexFilterMenu';
|
|||||||
import MovieIndexSortMenu from './Menus/MovieIndexSortMenu';
|
import MovieIndexSortMenu from './Menus/MovieIndexSortMenu';
|
||||||
import MovieIndexViewMenu from './Menus/MovieIndexViewMenu';
|
import MovieIndexViewMenu from './Menus/MovieIndexViewMenu';
|
||||||
import MovieIndexFooterConnector from './MovieIndexFooterConnector';
|
import MovieIndexFooterConnector from './MovieIndexFooterConnector';
|
||||||
|
import MovieEditorFooter from 'Movie/Editor/MovieEditorFooter.js';
|
||||||
import InteractiveImportModal from 'InteractiveImport/InteractiveImportModal';
|
import InteractiveImportModal from 'InteractiveImport/InteractiveImportModal';
|
||||||
|
import OrganizeMovieModal from 'Movie/Editor/Organize/OrganizeMovieModal';
|
||||||
import styles from './MovieIndex.css';
|
import styles from './MovieIndex.css';
|
||||||
|
|
||||||
function getViewComponent(view) {
|
function getViewComponent(view) {
|
||||||
@ -53,12 +58,19 @@ class MovieIndex extends Component {
|
|||||||
isPosterOptionsModalOpen: false,
|
isPosterOptionsModalOpen: false,
|
||||||
isOverviewOptionsModalOpen: false,
|
isOverviewOptionsModalOpen: false,
|
||||||
isInteractiveImportModalOpen: false,
|
isInteractiveImportModalOpen: false,
|
||||||
|
isMovieEditorActive: false,
|
||||||
|
isOrganizingMovieModalOpen: false,
|
||||||
|
allSelected: false,
|
||||||
|
allUnselected: false,
|
||||||
|
lastToggled: null,
|
||||||
|
selectedState: {},
|
||||||
isRendered: false
|
isRendered: false
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.setJumpBarItems();
|
this.setJumpBarItems();
|
||||||
|
this.setSelectedState();
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(prevProps) {
|
componentDidUpdate(prevProps) {
|
||||||
@ -66,7 +78,9 @@ class MovieIndex extends Component {
|
|||||||
items,
|
items,
|
||||||
sortKey,
|
sortKey,
|
||||||
sortDirection,
|
sortDirection,
|
||||||
scrollTop
|
scrollTop,
|
||||||
|
isDeleting,
|
||||||
|
deleteError
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@ -75,11 +89,20 @@ class MovieIndex extends Component {
|
|||||||
sortDirection !== prevProps.sortDirection
|
sortDirection !== prevProps.sortDirection
|
||||||
) {
|
) {
|
||||||
this.setJumpBarItems();
|
this.setJumpBarItems();
|
||||||
|
this.setSelectedState();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.state.jumpToCharacter != null && scrollTop !== prevProps.scrollTop) {
|
if (this.state.jumpToCharacter != null && scrollTop !== prevProps.scrollTop) {
|
||||||
this.setState({ jumpToCharacter: null });
|
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 });
|
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() {
|
setJumpBarItems() {
|
||||||
const {
|
const {
|
||||||
items,
|
items,
|
||||||
@ -149,10 +211,51 @@ class MovieIndex extends Component {
|
|||||||
this.setState({ isInteractiveImportModalOpen: false });
|
this.setState({ isInteractiveImportModalOpen: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onMovieEditorTogglePress = () => {
|
||||||
|
if (this.state.isMovieEditorActive) {
|
||||||
|
this.setState({ isMovieEditorActive: false });
|
||||||
|
} else {
|
||||||
|
this.setState({ isMovieEditorActive: true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onJumpBarItemPress = (jumpToCharacter) => {
|
onJumpBarItemPress = (jumpToCharacter) => {
|
||||||
this.setState({ 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 = () => {
|
onRender = () => {
|
||||||
this.setState({ isRendered: true }, () => {
|
this.setState({ isRendered: true }, () => {
|
||||||
const {
|
const {
|
||||||
@ -193,6 +296,11 @@ class MovieIndex extends Component {
|
|||||||
view,
|
view,
|
||||||
isRefreshingMovie,
|
isRefreshingMovie,
|
||||||
isRssSyncExecuting,
|
isRssSyncExecuting,
|
||||||
|
isOrganizingMovie,
|
||||||
|
isSaving,
|
||||||
|
saveError,
|
||||||
|
isDeleting,
|
||||||
|
deleteError,
|
||||||
scrollTop,
|
scrollTop,
|
||||||
onSortSelect,
|
onSortSelect,
|
||||||
onFilterSelect,
|
onFilterSelect,
|
||||||
@ -209,9 +317,15 @@ class MovieIndex extends Component {
|
|||||||
isPosterOptionsModalOpen,
|
isPosterOptionsModalOpen,
|
||||||
isOverviewOptionsModalOpen,
|
isOverviewOptionsModalOpen,
|
||||||
isInteractiveImportModalOpen,
|
isInteractiveImportModalOpen,
|
||||||
isRendered
|
isMovieEditorActive,
|
||||||
|
isRendered,
|
||||||
|
selectedState,
|
||||||
|
allSelected,
|
||||||
|
allUnselected
|
||||||
} = this.state;
|
} = this.state;
|
||||||
|
|
||||||
|
const selectedMovieIds = this.getSelectedIds();
|
||||||
|
|
||||||
const ViewComponent = getViewComponent(view);
|
const ViewComponent = getViewComponent(view);
|
||||||
const isLoaded = !!(!error && isPopulated && items.length && contentBody);
|
const isLoaded = !!(!error && isPopulated && items.length && contentBody);
|
||||||
const hasNoMovie = !totalItems;
|
const hasNoMovie = !totalItems;
|
||||||
@ -248,16 +362,38 @@ class MovieIndex extends Component {
|
|||||||
<PageToolbarButton
|
<PageToolbarButton
|
||||||
label="Manual Import"
|
label="Manual Import"
|
||||||
iconName={icons.INTERACTIVE}
|
iconName={icons.INTERACTIVE}
|
||||||
|
isDisabled={hasNoMovie}
|
||||||
onPress={this.onInteractiveImportPress}
|
onPress={this.onInteractiveImportPress}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<PageToolbarSeparator />
|
<PageToolbarSeparator />
|
||||||
|
|
||||||
|
{
|
||||||
|
isMovieEditorActive ?
|
||||||
|
<PageToolbarButton
|
||||||
|
label="Movie Index"
|
||||||
|
iconName={icons.MOVIE_CONTINUING}
|
||||||
|
isDisabled={hasNoMovie}
|
||||||
|
onPress={this.onMovieEditorTogglePress}
|
||||||
|
/> :
|
||||||
<PageToolbarButton
|
<PageToolbarButton
|
||||||
label="Movie Editor"
|
label="Movie Editor"
|
||||||
iconName={icons.EDIT}
|
iconName={icons.EDIT}
|
||||||
isDisabled={hasNoMovie}
|
isDisabled={hasNoMovie}
|
||||||
|
onPress={this.onMovieEditorTogglePress}
|
||||||
/>
|
/>
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
isMovieEditorActive ?
|
||||||
|
<PageToolbarButton
|
||||||
|
label={allSelected ? 'Unselect All' : 'Select All'}
|
||||||
|
iconName={icons.CHECK_SQUARE}
|
||||||
|
isDisabled={hasNoMovie}
|
||||||
|
onPress={this.onSelectAllPress}
|
||||||
|
/> :
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
</PageToolbarSection>
|
</PageToolbarSection>
|
||||||
|
|
||||||
@ -360,10 +496,19 @@ class MovieIndex extends Component {
|
|||||||
scrollTop={scrollTop}
|
scrollTop={scrollTop}
|
||||||
jumpToCharacter={jumpToCharacter}
|
jumpToCharacter={jumpToCharacter}
|
||||||
onRender={this.onRender}
|
onRender={this.onRender}
|
||||||
|
isMovieEditorActive={isMovieEditorActive}
|
||||||
|
allSelected={allSelected}
|
||||||
|
allUnselected={allUnselected}
|
||||||
|
onSelectedChange={this.onSelectedChange}
|
||||||
|
onSelectAllChange={this.onSelectAllChange}
|
||||||
|
selectedState={selectedState}
|
||||||
{...otherProps}
|
{...otherProps}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{
|
||||||
|
!isMovieEditorActive &&
|
||||||
<MovieIndexFooterConnector />
|
<MovieIndexFooterConnector />
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -382,6 +527,21 @@ class MovieIndex extends Component {
|
|||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{
|
||||||
|
isLoaded && isMovieEditorActive &&
|
||||||
|
<MovieEditorFooter
|
||||||
|
movieIds={selectedMovieIds}
|
||||||
|
selectedCount={selectedMovieIds.length}
|
||||||
|
isSaving={isSaving}
|
||||||
|
saveError={saveError}
|
||||||
|
isDeleting={isDeleting}
|
||||||
|
deleteError={deleteError}
|
||||||
|
isOrganizingMovie={isOrganizingMovie}
|
||||||
|
onSaveSelected={this.onSaveSelected}
|
||||||
|
onOrganizeMoviePress={this.onOrganizeMoviePress}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
|
||||||
<MovieIndexPosterOptionsModal
|
<MovieIndexPosterOptionsModal
|
||||||
isOpen={isPosterOptionsModalOpen}
|
isOpen={isPosterOptionsModalOpen}
|
||||||
onModalClose={this.onPosterOptionsModalClose}
|
onModalClose={this.onPosterOptionsModalClose}
|
||||||
@ -396,6 +556,12 @@ class MovieIndex extends Component {
|
|||||||
isOpen={isInteractiveImportModalOpen}
|
isOpen={isInteractiveImportModalOpen}
|
||||||
onModalClose={this.onInteractiveImportModalClose}
|
onModalClose={this.onInteractiveImportModalClose}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<OrganizeMovieModal
|
||||||
|
isOpen={this.state.isOrganizingMovieModalOpen}
|
||||||
|
movieIds={selectedMovieIds}
|
||||||
|
onModalClose={this.onOrganizeMovieModalClose}
|
||||||
|
/>
|
||||||
</PageContent>
|
</PageContent>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -415,15 +581,21 @@ MovieIndex.propTypes = {
|
|||||||
sortDirection: PropTypes.oneOf(sortDirections.all),
|
sortDirection: PropTypes.oneOf(sortDirections.all),
|
||||||
view: PropTypes.string.isRequired,
|
view: PropTypes.string.isRequired,
|
||||||
isRefreshingMovie: PropTypes.bool.isRequired,
|
isRefreshingMovie: PropTypes.bool.isRequired,
|
||||||
|
isOrganizingMovie: PropTypes.bool.isRequired,
|
||||||
isRssSyncExecuting: PropTypes.bool.isRequired,
|
isRssSyncExecuting: PropTypes.bool.isRequired,
|
||||||
scrollTop: PropTypes.number.isRequired,
|
scrollTop: PropTypes.number.isRequired,
|
||||||
isSmallScreen: PropTypes.bool.isRequired,
|
isSmallScreen: PropTypes.bool.isRequired,
|
||||||
|
isSaving: PropTypes.bool.isRequired,
|
||||||
|
saveError: PropTypes.object,
|
||||||
|
isDeleting: PropTypes.bool.isRequired,
|
||||||
|
deleteError: PropTypes.object,
|
||||||
onSortSelect: PropTypes.func.isRequired,
|
onSortSelect: PropTypes.func.isRequired,
|
||||||
onFilterSelect: PropTypes.func.isRequired,
|
onFilterSelect: PropTypes.func.isRequired,
|
||||||
onViewSelect: PropTypes.func.isRequired,
|
onViewSelect: PropTypes.func.isRequired,
|
||||||
onRefreshMoviePress: PropTypes.func.isRequired,
|
onRefreshMoviePress: PropTypes.func.isRequired,
|
||||||
onRssSyncPress: PropTypes.func.isRequired,
|
onRssSyncPress: PropTypes.func.isRequired,
|
||||||
onScroll: PropTypes.func.isRequired
|
onScroll: PropTypes.func.isRequired,
|
||||||
|
onSaveSelected: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
export default MovieIndex;
|
export default MovieIndex;
|
||||||
|
@ -8,7 +8,7 @@ import createCommandExecutingSelector from 'Store/Selectors/createCommandExecuti
|
|||||||
import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector';
|
import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector';
|
||||||
import { fetchMovies } from 'Store/Actions/movieActions';
|
import { fetchMovies } from 'Store/Actions/movieActions';
|
||||||
import scrollPositions from 'Store/scrollPositions';
|
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 { executeCommand } from 'Store/Actions/commandActions';
|
||||||
import * as commandNames from 'Commands/commandNames';
|
import * as commandNames from 'Commands/commandNames';
|
||||||
import withScrollPosition from 'Components/withScrollPosition';
|
import withScrollPosition from 'Components/withScrollPosition';
|
||||||
@ -42,17 +42,20 @@ function createMapStateToProps() {
|
|||||||
createMovieClientSideCollectionItemsSelector('movieIndex'),
|
createMovieClientSideCollectionItemsSelector('movieIndex'),
|
||||||
createCommandExecutingSelector(commandNames.REFRESH_MOVIE),
|
createCommandExecutingSelector(commandNames.REFRESH_MOVIE),
|
||||||
createCommandExecutingSelector(commandNames.RSS_SYNC),
|
createCommandExecutingSelector(commandNames.RSS_SYNC),
|
||||||
|
createCommandExecutingSelector(commandNames.RENAME_MOVIE),
|
||||||
createDimensionsSelector(),
|
createDimensionsSelector(),
|
||||||
(
|
(
|
||||||
movies,
|
movies,
|
||||||
isRefreshingMovie,
|
isRefreshingMovie,
|
||||||
isRssSyncExecuting,
|
isRssSyncExecuting,
|
||||||
|
isOrganizingMovie,
|
||||||
dimensionsState
|
dimensionsState
|
||||||
) => {
|
) => {
|
||||||
return {
|
return {
|
||||||
...movies,
|
...movies,
|
||||||
isRefreshingMovie,
|
isRefreshingMovie,
|
||||||
isRssSyncExecuting,
|
isRssSyncExecuting,
|
||||||
|
isOrganizingMovie,
|
||||||
isSmallScreen: dimensionsState.isSmallScreen
|
isSmallScreen: dimensionsState.isSmallScreen
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -81,6 +84,10 @@ function createMapDispatchToProps(dispatch, props) {
|
|||||||
dispatch(setMovieView({ view }));
|
dispatch(setMovieView({ view }));
|
||||||
},
|
},
|
||||||
|
|
||||||
|
dispatchSaveMovieEditor(payload) {
|
||||||
|
dispatch(saveMovieEditor(payload));
|
||||||
|
},
|
||||||
|
|
||||||
onRefreshMoviePress() {
|
onRefreshMoviePress() {
|
||||||
dispatch(executeCommand({
|
dispatch(executeCommand({
|
||||||
name: commandNames.REFRESH_MOVIE
|
name: commandNames.REFRESH_MOVIE
|
||||||
@ -128,6 +135,10 @@ class MovieIndexConnector extends Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onSaveSelected = (payload) => {
|
||||||
|
this.props.dispatchSaveMovieEditor(payload);
|
||||||
|
}
|
||||||
|
|
||||||
onScroll = ({ scrollTop }) => {
|
onScroll = ({ scrollTop }) => {
|
||||||
this.setState({
|
this.setState({
|
||||||
scrollTop
|
scrollTop
|
||||||
@ -146,6 +157,7 @@ class MovieIndexConnector extends Component {
|
|||||||
scrollTop={this.state.scrollTop}
|
scrollTop={this.state.scrollTop}
|
||||||
onViewSelect={this.onViewSelect}
|
onViewSelect={this.onViewSelect}
|
||||||
onScroll={this.onScroll}
|
onScroll={this.onScroll}
|
||||||
|
onSaveSelected={this.onSaveSelected}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -156,7 +168,8 @@ MovieIndexConnector.propTypes = {
|
|||||||
view: PropTypes.string.isRequired,
|
view: PropTypes.string.isRequired,
|
||||||
scrollTop: PropTypes.number.isRequired,
|
scrollTop: PropTypes.number.isRequired,
|
||||||
dispatchFetchMovies: PropTypes.func.isRequired,
|
dispatchFetchMovies: PropTypes.func.isRequired,
|
||||||
dispatchSetMovieView: PropTypes.func.isRequired
|
dispatchSetMovieView: PropTypes.func.isRequired,
|
||||||
|
dispatchSaveMovieEditor: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
export default withScrollPosition(
|
export default withScrollPosition(
|
||||||
|
@ -17,6 +17,13 @@ $hoverScale: 1.05;
|
|||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.editorSelect {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 5px;
|
||||||
|
z-index: 3;
|
||||||
|
}
|
||||||
|
|
||||||
.posterContainer {
|
.posterContainer {
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ import fonts from 'Styles/Variables/fonts';
|
|||||||
import IconButton from 'Components/Link/IconButton';
|
import IconButton from 'Components/Link/IconButton';
|
||||||
import Link from 'Components/Link/Link';
|
import Link from 'Components/Link/Link';
|
||||||
import SpinnerIconButton from 'Components/Link/SpinnerIconButton';
|
import SpinnerIconButton from 'Components/Link/SpinnerIconButton';
|
||||||
|
import CheckInput from 'Components/Form/CheckInput';
|
||||||
import MoviePoster from 'Movie/MoviePoster';
|
import MoviePoster from 'Movie/MoviePoster';
|
||||||
import EditMovieModalConnector from 'Movie/Edit/EditMovieModalConnector';
|
import EditMovieModalConnector from 'Movie/Edit/EditMovieModalConnector';
|
||||||
import DeleteMovieModal from 'Movie/Delete/DeleteMovieModal';
|
import DeleteMovieModal from 'Movie/Delete/DeleteMovieModal';
|
||||||
@ -65,6 +66,15 @@ class MovieIndexOverview extends Component {
|
|||||||
this.setState({ isDeleteMovieModalOpen: false });
|
this.setState({ isDeleteMovieModalOpen: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onChange = ({ value, shiftKey }) => {
|
||||||
|
const {
|
||||||
|
id,
|
||||||
|
onSelectedChange
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
onSelectedChange({ id, value, shiftKey });
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// Render
|
// Render
|
||||||
|
|
||||||
@ -94,6 +104,9 @@ class MovieIndexOverview extends Component {
|
|||||||
isSearchingMovie,
|
isSearchingMovie,
|
||||||
onRefreshMoviePress,
|
onRefreshMoviePress,
|
||||||
onSearchPress,
|
onSearchPress,
|
||||||
|
isMovieEditorActive,
|
||||||
|
isSelected,
|
||||||
|
onSelectedChange,
|
||||||
...otherProps
|
...otherProps
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
@ -118,11 +131,15 @@ class MovieIndexOverview extends Component {
|
|||||||
<div className={styles.poster}>
|
<div className={styles.poster}>
|
||||||
<div className={styles.posterContainer}>
|
<div className={styles.posterContainer}>
|
||||||
{
|
{
|
||||||
status === 'ended' &&
|
isMovieEditorActive &&
|
||||||
<div
|
<div className={styles.editorSelect}>
|
||||||
className={styles.ended}
|
<CheckInput
|
||||||
title="Ended"
|
className={styles.checkInput}
|
||||||
|
name={id.toString()}
|
||||||
|
value={isSelected}
|
||||||
|
onChange={this.onChange}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
<Link
|
<Link
|
||||||
@ -253,7 +270,10 @@ MovieIndexOverview.propTypes = {
|
|||||||
isRefreshingMovie: PropTypes.bool.isRequired,
|
isRefreshingMovie: PropTypes.bool.isRequired,
|
||||||
isSearchingMovie: PropTypes.bool.isRequired,
|
isSearchingMovie: PropTypes.bool.isRequired,
|
||||||
onRefreshMoviePress: PropTypes.func.isRequired,
|
onRefreshMoviePress: PropTypes.func.isRequired,
|
||||||
onSearchPress: PropTypes.func.isRequired
|
onSearchPress: PropTypes.func.isRequired,
|
||||||
|
isMovieEditorActive: PropTypes.bool.isRequired,
|
||||||
|
isSelected: PropTypes.bool,
|
||||||
|
onSelectedChange: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
export default MovieIndexOverview;
|
export default MovieIndexOverview;
|
||||||
|
@ -169,7 +169,10 @@ class MovieIndexOverviews extends Component {
|
|||||||
shortDateFormat,
|
shortDateFormat,
|
||||||
longDateFormat,
|
longDateFormat,
|
||||||
timeFormat,
|
timeFormat,
|
||||||
isSmallScreen
|
isSmallScreen,
|
||||||
|
selectedState,
|
||||||
|
isMovieEditorActive,
|
||||||
|
onSelectedChange
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@ -201,6 +204,9 @@ class MovieIndexOverviews extends Component {
|
|||||||
style={style}
|
style={style}
|
||||||
movieId={movie.id}
|
movieId={movie.id}
|
||||||
qualityProfileId={movie.qualityProfileId}
|
qualityProfileId={movie.qualityProfileId}
|
||||||
|
isSelected={selectedState[movie.id]}
|
||||||
|
onSelectedChange={onSelectedChange}
|
||||||
|
isMovieEditorActive={isMovieEditorActive}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -227,7 +233,8 @@ class MovieIndexOverviews extends Component {
|
|||||||
items,
|
items,
|
||||||
scrollTop,
|
scrollTop,
|
||||||
isSmallScreen,
|
isSmallScreen,
|
||||||
onScroll
|
onScroll,
|
||||||
|
selectedState
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@ -257,6 +264,7 @@ class MovieIndexOverviews extends Component {
|
|||||||
overscanRowCount={2}
|
overscanRowCount={2}
|
||||||
cellRenderer={this.cellRenderer}
|
cellRenderer={this.cellRenderer}
|
||||||
onSectionRendered={this.onSectionRendered}
|
onSectionRendered={this.onSectionRendered}
|
||||||
|
selectedState={selectedState}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -282,7 +290,10 @@ MovieIndexOverviews.propTypes = {
|
|||||||
isSmallScreen: PropTypes.bool.isRequired,
|
isSmallScreen: PropTypes.bool.isRequired,
|
||||||
timeFormat: PropTypes.string.isRequired,
|
timeFormat: PropTypes.string.isRequired,
|
||||||
onRender: PropTypes.func.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;
|
export default MovieIndexOverviews;
|
||||||
|
@ -85,6 +85,13 @@ $hoverScale: 1.05;
|
|||||||
transition: opacity 0;
|
transition: opacity 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.editorSelect {
|
||||||
|
position: absolute;
|
||||||
|
top: 10px;
|
||||||
|
left: 10px;
|
||||||
|
z-index: 3;
|
||||||
|
}
|
||||||
|
|
||||||
.action {
|
.action {
|
||||||
composes: button from '~Components/Link/IconButton.css';
|
composes: button from '~Components/Link/IconButton.css';
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@ import PropTypes from 'prop-types';
|
|||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import { icons } from 'Helpers/Props';
|
import { icons } from 'Helpers/Props';
|
||||||
import IconButton from 'Components/Link/IconButton';
|
import IconButton from 'Components/Link/IconButton';
|
||||||
|
import CheckInput from 'Components/Form/CheckInput';
|
||||||
import SpinnerIconButton from 'Components/Link/SpinnerIconButton';
|
import SpinnerIconButton from 'Components/Link/SpinnerIconButton';
|
||||||
import Label from 'Components/Label';
|
import Label from 'Components/Label';
|
||||||
import Link from 'Components/Link/Link';
|
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
|
// Render
|
||||||
|
|
||||||
@ -89,6 +99,9 @@ class MovieIndexPoster extends Component {
|
|||||||
isSearchingMovie,
|
isSearchingMovie,
|
||||||
onRefreshMoviePress,
|
onRefreshMoviePress,
|
||||||
onSearchPress,
|
onSearchPress,
|
||||||
|
isMovieEditorActive,
|
||||||
|
isSelected,
|
||||||
|
onSelectedChange,
|
||||||
...otherProps
|
...otherProps
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
@ -109,6 +122,17 @@ class MovieIndexPoster extends Component {
|
|||||||
<div className={styles.container} style={style}>
|
<div className={styles.container} style={style}>
|
||||||
<div className={styles.content}>
|
<div className={styles.content}>
|
||||||
<div className={styles.posterContainer}>
|
<div className={styles.posterContainer}>
|
||||||
|
{
|
||||||
|
isMovieEditorActive &&
|
||||||
|
<div className={styles.editorSelect}>
|
||||||
|
<CheckInput
|
||||||
|
className={styles.checkInput}
|
||||||
|
name={id.toString()}
|
||||||
|
value={isSelected}
|
||||||
|
onChange={this.onChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
<Label className={styles.controls}>
|
<Label className={styles.controls}>
|
||||||
<SpinnerIconButton
|
<SpinnerIconButton
|
||||||
className={styles.action}
|
className={styles.action}
|
||||||
@ -249,7 +273,10 @@ MovieIndexPoster.propTypes = {
|
|||||||
isRefreshingMovie: PropTypes.bool.isRequired,
|
isRefreshingMovie: PropTypes.bool.isRequired,
|
||||||
isSearchingMovie: PropTypes.bool.isRequired,
|
isSearchingMovie: PropTypes.bool.isRequired,
|
||||||
onRefreshMoviePress: PropTypes.func.isRequired,
|
onRefreshMoviePress: PropTypes.func.isRequired,
|
||||||
onSearchPress: PropTypes.func.isRequired
|
onSearchPress: PropTypes.func.isRequired,
|
||||||
|
isMovieEditorActive: PropTypes.bool.isRequired,
|
||||||
|
isSelected: PropTypes.bool,
|
||||||
|
onSelectedChange: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
MovieIndexPoster.defaultProps = {
|
MovieIndexPoster.defaultProps = {
|
||||||
|
@ -195,7 +195,10 @@ class MovieIndexPosters extends Component {
|
|||||||
posterOptions,
|
posterOptions,
|
||||||
showRelativeDates,
|
showRelativeDates,
|
||||||
shortDateFormat,
|
shortDateFormat,
|
||||||
timeFormat
|
timeFormat,
|
||||||
|
selectedState,
|
||||||
|
isMovieEditorActive,
|
||||||
|
onSelectedChange
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@ -234,6 +237,9 @@ class MovieIndexPosters extends Component {
|
|||||||
style={style}
|
style={style}
|
||||||
movieId={movie.id}
|
movieId={movie.id}
|
||||||
qualityProfileId={movie.qualityProfileId}
|
qualityProfileId={movie.qualityProfileId}
|
||||||
|
isSelected={selectedState[movie.id]}
|
||||||
|
onSelectedChange={onSelectedChange}
|
||||||
|
isMovieEditorActive={isMovieEditorActive}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -260,7 +266,8 @@ class MovieIndexPosters extends Component {
|
|||||||
items,
|
items,
|
||||||
scrollTop,
|
scrollTop,
|
||||||
isSmallScreen,
|
isSmallScreen,
|
||||||
onScroll
|
onScroll,
|
||||||
|
selectedState
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@ -294,6 +301,7 @@ class MovieIndexPosters extends Component {
|
|||||||
overscanRowCount={2}
|
overscanRowCount={2}
|
||||||
cellRenderer={this.cellRenderer}
|
cellRenderer={this.cellRenderer}
|
||||||
onSectionRendered={this.onSectionRendered}
|
onSectionRendered={this.onSectionRendered}
|
||||||
|
selectedState={selectedState}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -318,7 +326,10 @@ MovieIndexPosters.propTypes = {
|
|||||||
isSmallScreen: PropTypes.bool.isRequired,
|
isSmallScreen: PropTypes.bool.isRequired,
|
||||||
timeFormat: PropTypes.string.isRequired,
|
timeFormat: PropTypes.string.isRequired,
|
||||||
onRender: PropTypes.func.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 MovieIndexPosters;
|
export default MovieIndexPosters;
|
||||||
|
@ -144,7 +144,7 @@ class MovieIndexPosterOptionsModalContent extends Component {
|
|||||||
type={inputTypes.CHECK}
|
type={inputTypes.CHECK}
|
||||||
name="showTitle"
|
name="showTitle"
|
||||||
value={showTitle}
|
value={showTitle}
|
||||||
helpText="Show series title under poster"
|
helpText="Show movie title under poster"
|
||||||
onChange={this.onChangePosterOption}
|
onChange={this.onChangePosterOption}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
|
@ -4,6 +4,7 @@ import { icons } from 'Helpers/Props';
|
|||||||
import IconButton from 'Components/Link/IconButton';
|
import IconButton from 'Components/Link/IconButton';
|
||||||
import VirtualTableHeader from 'Components/Table/VirtualTableHeader';
|
import VirtualTableHeader from 'Components/Table/VirtualTableHeader';
|
||||||
import VirtualTableHeaderCell from 'Components/Table/VirtualTableHeaderCell';
|
import VirtualTableHeaderCell from 'Components/Table/VirtualTableHeaderCell';
|
||||||
|
import VirtualTableSelectAllHeaderCell from 'Components/Table/VirtualTableSelectAllHeaderCell';
|
||||||
import TableOptionsModal from 'Components/Table/TableOptions/TableOptionsModal';
|
import TableOptionsModal from 'Components/Table/TableOptions/TableOptionsModal';
|
||||||
import MovieIndexTableOptionsConnector from './MovieIndexTableOptionsConnector';
|
import MovieIndexTableOptionsConnector from './MovieIndexTableOptionsConnector';
|
||||||
import styles from './MovieIndexHeader.css';
|
import styles from './MovieIndexHeader.css';
|
||||||
@ -39,6 +40,10 @@ class MovieIndexHeader extends Component {
|
|||||||
const {
|
const {
|
||||||
columns,
|
columns,
|
||||||
onTableOptionChange,
|
onTableOptionChange,
|
||||||
|
allSelected,
|
||||||
|
allUnselected,
|
||||||
|
onSelectAllChange,
|
||||||
|
isMovieEditorActive,
|
||||||
...otherProps
|
...otherProps
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
@ -57,6 +62,17 @@ class MovieIndexHeader extends Component {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isMovieEditorActive && name === 'select') {
|
||||||
|
return (
|
||||||
|
<VirtualTableSelectAllHeaderCell
|
||||||
|
key={name}
|
||||||
|
allSelected={allSelected}
|
||||||
|
allUnselected={allUnselected}
|
||||||
|
onSelectAllChange={onSelectAllChange}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (name === 'actions') {
|
if (name === 'actions') {
|
||||||
return (
|
return (
|
||||||
<VirtualTableHeaderCell
|
<VirtualTableHeaderCell
|
||||||
@ -102,7 +118,11 @@ class MovieIndexHeader extends Component {
|
|||||||
|
|
||||||
MovieIndexHeader.propTypes = {
|
MovieIndexHeader.propTypes = {
|
||||||
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
|
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,
|
||||||
|
isMovieEditorActive: PropTypes.bool.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
export default MovieIndexHeader;
|
export default MovieIndexHeader;
|
||||||
|
@ -15,6 +15,7 @@ import MovieTitleLink from 'Movie/MovieTitleLink';
|
|||||||
import EditMovieModalConnector from 'Movie/Edit/EditMovieModalConnector';
|
import EditMovieModalConnector from 'Movie/Edit/EditMovieModalConnector';
|
||||||
import DeleteMovieModal from 'Movie/Delete/DeleteMovieModal';
|
import DeleteMovieModal from 'Movie/Delete/DeleteMovieModal';
|
||||||
import MovieStatusCell from './MovieStatusCell';
|
import MovieStatusCell from './MovieStatusCell';
|
||||||
|
import VirtualTableSelectCell from 'Components/Table/Cells/VirtualTableSelectCell';
|
||||||
import styles from './MovieIndexRow.css';
|
import styles from './MovieIndexRow.css';
|
||||||
|
|
||||||
class MovieIndexRow extends Component {
|
class MovieIndexRow extends Component {
|
||||||
@ -80,8 +81,11 @@ class MovieIndexRow extends Component {
|
|||||||
columns,
|
columns,
|
||||||
isRefreshingMovie,
|
isRefreshingMovie,
|
||||||
isSearchingMovie,
|
isSearchingMovie,
|
||||||
|
isMovieEditorActive,
|
||||||
|
isSelected,
|
||||||
onRefreshMoviePress,
|
onRefreshMoviePress,
|
||||||
onSearchPress
|
onSearchPress,
|
||||||
|
onSelectedChange
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@ -102,6 +106,19 @@ class MovieIndexRow extends Component {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isMovieEditorActive && name === 'select') {
|
||||||
|
return (
|
||||||
|
<VirtualTableSelectCell
|
||||||
|
inputClassName={styles.checkInput}
|
||||||
|
id={id}
|
||||||
|
key={name}
|
||||||
|
isSelected={isSelected}
|
||||||
|
isDisabled={false}
|
||||||
|
onSelectedChange={onSelectedChange}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (name === 'status') {
|
if (name === 'status') {
|
||||||
return (
|
return (
|
||||||
<MovieStatusCell
|
<MovieStatusCell
|
||||||
@ -322,7 +339,10 @@ MovieIndexRow.propTypes = {
|
|||||||
isRefreshingMovie: PropTypes.bool.isRequired,
|
isRefreshingMovie: PropTypes.bool.isRequired,
|
||||||
isSearchingMovie: PropTypes.bool.isRequired,
|
isSearchingMovie: PropTypes.bool.isRequired,
|
||||||
onRefreshMoviePress: PropTypes.func.isRequired,
|
onRefreshMoviePress: PropTypes.func.isRequired,
|
||||||
onSearchPress: PropTypes.func.isRequired
|
onSearchPress: PropTypes.func.isRequired,
|
||||||
|
isMovieEditorActive: PropTypes.bool.isRequired,
|
||||||
|
isSelected: PropTypes.bool,
|
||||||
|
onSelectedChange: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
MovieIndexRow.defaultProps = {
|
MovieIndexRow.defaultProps = {
|
||||||
|
@ -22,10 +22,13 @@ class MovieIndexTable extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(prevProps) {
|
componentDidUpdate(prevProps) {
|
||||||
|
const {
|
||||||
|
items
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
const jumpToCharacter = this.props.jumpToCharacter;
|
const jumpToCharacter = this.props.jumpToCharacter;
|
||||||
|
|
||||||
if (jumpToCharacter != null && jumpToCharacter !== prevProps.jumpToCharacter) {
|
if (jumpToCharacter != null && jumpToCharacter !== prevProps.jumpToCharacter) {
|
||||||
const items = this.props.items;
|
|
||||||
|
|
||||||
const scrollIndex = getIndexOfFirstCharacter(items, jumpToCharacter);
|
const scrollIndex = getIndexOfFirstCharacter(items, jumpToCharacter);
|
||||||
|
|
||||||
@ -43,7 +46,10 @@ class MovieIndexTable extends Component {
|
|||||||
rowRenderer = ({ key, rowIndex, style }) => {
|
rowRenderer = ({ key, rowIndex, style }) => {
|
||||||
const {
|
const {
|
||||||
items,
|
items,
|
||||||
columns
|
columns,
|
||||||
|
selectedState,
|
||||||
|
onSelectedChange,
|
||||||
|
isMovieEditorActive
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const movie = items[rowIndex];
|
const movie = items[rowIndex];
|
||||||
@ -56,6 +62,9 @@ class MovieIndexTable extends Component {
|
|||||||
columns={columns}
|
columns={columns}
|
||||||
movieId={movie.id}
|
movieId={movie.id}
|
||||||
qualityProfileId={movie.qualityProfileId}
|
qualityProfileId={movie.qualityProfileId}
|
||||||
|
isSelected={selectedState[movie.id]}
|
||||||
|
onSelectedChange={onSelectedChange}
|
||||||
|
isMovieEditorActive={isMovieEditorActive}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -75,7 +84,12 @@ class MovieIndexTable extends Component {
|
|||||||
contentBody,
|
contentBody,
|
||||||
onSortPress,
|
onSortPress,
|
||||||
onRender,
|
onRender,
|
||||||
onScroll
|
onScroll,
|
||||||
|
allSelected,
|
||||||
|
allUnselected,
|
||||||
|
onSelectAllChange,
|
||||||
|
isMovieEditorActive,
|
||||||
|
selectedState
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -95,8 +109,13 @@ class MovieIndexTable extends Component {
|
|||||||
sortKey={sortKey}
|
sortKey={sortKey}
|
||||||
sortDirection={sortDirection}
|
sortDirection={sortDirection}
|
||||||
onSortPress={onSortPress}
|
onSortPress={onSortPress}
|
||||||
|
allSelected={allSelected}
|
||||||
|
allUnselected={allUnselected}
|
||||||
|
onSelectAllChange={onSelectAllChange}
|
||||||
|
isMovieEditorActive={isMovieEditorActive}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
|
selectedState={selectedState}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
filters={filters}
|
filters={filters}
|
||||||
sortKey={sortKey}
|
sortKey={sortKey}
|
||||||
@ -120,7 +139,13 @@ MovieIndexTable.propTypes = {
|
|||||||
isSmallScreen: PropTypes.bool.isRequired,
|
isSmallScreen: PropTypes.bool.isRequired,
|
||||||
onSortPress: PropTypes.func.isRequired,
|
onSortPress: PropTypes.func.isRequired,
|
||||||
onRender: PropTypes.func.isRequired,
|
onRender: PropTypes.func.isRequired,
|
||||||
onScroll: PropTypes.func.isRequired
|
onScroll: PropTypes.func.isRequired,
|
||||||
|
allSelected: PropTypes.bool.isRequired,
|
||||||
|
allUnselected: PropTypes.bool.isRequired,
|
||||||
|
selectedState: PropTypes.object.isRequired,
|
||||||
|
onSelectedChange: PropTypes.func.isRequired,
|
||||||
|
onSelectAllChange: PropTypes.func.isRequired,
|
||||||
|
isMovieEditorActive: PropTypes.bool.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
export default MovieIndexTable;
|
export default MovieIndexTable;
|
||||||
|
@ -27,7 +27,7 @@ function MovieStatusCell(props) {
|
|||||||
|
|
||||||
<Icon
|
<Icon
|
||||||
className={styles.statusIcon}
|
className={styles.statusIcon}
|
||||||
name={status === 'released' ? icons.SERIES_ENDED : icons.SERIES_CONTINUING}
|
name={status === 'released' ? icons.SERIES_ENDED : icons.MOVIE_CONTINUING}
|
||||||
title={status === 'ended' ? 'Ended' : 'Continuing'}
|
title={status === 'ended' ? 'Ended' : 'Continuing'}
|
||||||
|
|
||||||
/>
|
/>
|
||||||
|
@ -7,16 +7,16 @@ import ModalContent from 'Components/Modal/ModalContent';
|
|||||||
import ModalHeader from 'Components/Modal/ModalHeader';
|
import ModalHeader from 'Components/Modal/ModalHeader';
|
||||||
import ModalBody from 'Components/Modal/ModalBody';
|
import ModalBody from 'Components/Modal/ModalBody';
|
||||||
import ModalFooter from 'Components/Modal/ModalFooter';
|
import ModalFooter from 'Components/Modal/ModalFooter';
|
||||||
import styles from './MoveSeriesModal.css';
|
import styles from './MoveMovieModal.css';
|
||||||
|
|
||||||
function MoveSeriesModal(props) {
|
function MoveMovieModal(props) {
|
||||||
const {
|
const {
|
||||||
originalPath,
|
originalPath,
|
||||||
destinationPath,
|
destinationPath,
|
||||||
destinationRootFolder,
|
destinationRootFolder,
|
||||||
isOpen,
|
isOpen,
|
||||||
onSavePress,
|
onSavePress,
|
||||||
onMoveSeriesPress
|
onMoveMoviePress
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@ -46,8 +46,8 @@ function MoveSeriesModal(props) {
|
|||||||
<ModalBody>
|
<ModalBody>
|
||||||
{
|
{
|
||||||
destinationRootFolder ?
|
destinationRootFolder ?
|
||||||
`Would you like to move the series folders to '${destinationRootFolder}'?` :
|
`Would you like to move the movie folders to '${destinationRootFolder}'?` :
|
||||||
`Would you like to move the series files from '${originalPath}' to '${destinationPath}'?`
|
`Would you like to move the movie files from '${originalPath}' to '${destinationPath}'?`
|
||||||
}
|
}
|
||||||
</ModalBody>
|
</ModalBody>
|
||||||
|
|
||||||
@ -61,7 +61,7 @@ function MoveSeriesModal(props) {
|
|||||||
|
|
||||||
<Button
|
<Button
|
||||||
kind={kinds.DANGER}
|
kind={kinds.DANGER}
|
||||||
onPress={onMoveSeriesPress}
|
onPress={onMoveMoviePress}
|
||||||
>
|
>
|
||||||
Yes, Move the Files
|
Yes, Move the Files
|
||||||
</Button>
|
</Button>
|
||||||
@ -71,13 +71,13 @@ function MoveSeriesModal(props) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
MoveSeriesModal.propTypes = {
|
MoveMovieModal.propTypes = {
|
||||||
originalPath: PropTypes.string,
|
originalPath: PropTypes.string,
|
||||||
destinationPath: PropTypes.string,
|
destinationPath: PropTypes.string,
|
||||||
destinationRootFolder: PropTypes.string,
|
destinationRootFolder: PropTypes.string,
|
||||||
isOpen: PropTypes.bool.isRequired,
|
isOpen: PropTypes.bool.isRequired,
|
||||||
onSavePress: PropTypes.func.isRequired,
|
onSavePress: PropTypes.func.isRequired,
|
||||||
onMoveSeriesPress: PropTypes.func.isRequired
|
onMoveMoviePress: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
export default MoveSeriesModal;
|
export default MoveMovieModal;
|
@ -1,5 +1,5 @@
|
|||||||
.blankpad {
|
.blankpad {
|
||||||
padding-left:2em;
|
|
||||||
padding-top: 10px;
|
padding-top: 10px;
|
||||||
padding-bottom: 10px;
|
padding-bottom: 10px;
|
||||||
|
padding-left: 2em;
|
||||||
}
|
}
|
||||||
|
@ -2,27 +2,27 @@
|
|||||||
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
||||||
|
|
||||||
word-break: break-all;
|
word-break: break-all;
|
||||||
}
|
}
|
||||||
|
|
||||||
.quality,
|
.quality,
|
||||||
.language {
|
.language {
|
||||||
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
||||||
}
|
}
|
||||||
|
|
||||||
.language {
|
.language {
|
||||||
width: 100px;
|
width: 100px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.rejected,
|
.rejected,
|
||||||
.download {
|
.download {
|
||||||
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
||||||
|
|
||||||
width: 50px;
|
width: 50px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.age,
|
.age,
|
||||||
.size {
|
.size {
|
||||||
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
||||||
|
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.blankpad {
|
.blankpad {
|
||||||
padding-left:2em;
|
|
||||||
padding-top: 10px;
|
padding-top: 10px;
|
||||||
padding-bottom: 10px;
|
padding-bottom: 10px;
|
||||||
|
padding-left: 2em;
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
.episodeFormat {
|
.standardMovieFormat {
|
||||||
margin-left: 5px;
|
margin-left: 5px;
|
||||||
font-family: $monoSpaceFontFamily;
|
font-family: $monoSpaceFontFamily;
|
||||||
}
|
}
|
||||||
|
@ -75,7 +75,7 @@ class OrganizePreviewModalContent extends Component {
|
|||||||
error,
|
error,
|
||||||
items,
|
items,
|
||||||
renameEpisodes,
|
renameEpisodes,
|
||||||
episodeFormat,
|
standardMovieFormat,
|
||||||
path,
|
path,
|
||||||
onModalClose
|
onModalClose
|
||||||
} = this.props;
|
} = this.props;
|
||||||
@ -129,8 +129,8 @@ class OrganizePreviewModalContent extends Component {
|
|||||||
|
|
||||||
<div>
|
<div>
|
||||||
Naming pattern:
|
Naming pattern:
|
||||||
<span className={styles.episodeFormat}>
|
<span className={styles.standardMovieFormat}>
|
||||||
{episodeFormat}
|
{standardMovieFormat}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</Alert>
|
</Alert>
|
||||||
@ -140,11 +140,11 @@ class OrganizePreviewModalContent extends Component {
|
|||||||
items.map((item) => {
|
items.map((item) => {
|
||||||
return (
|
return (
|
||||||
<OrganizePreviewRow
|
<OrganizePreviewRow
|
||||||
key={item.episodeFileId}
|
key={item.movieFileId}
|
||||||
id={item.episodeFileId}
|
id={item.movieFileId}
|
||||||
existingPath={item.existingPath}
|
existingPath={item.existingPath}
|
||||||
newPath={item.newPath}
|
newPath={item.newPath}
|
||||||
isSelected={selectedState[item.episodeFileId]}
|
isSelected={selectedState[item.movieFileId]}
|
||||||
onSelectedChange={this.onSelectedChange}
|
onSelectedChange={this.onSelectedChange}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@ -190,10 +190,9 @@ OrganizePreviewModalContent.propTypes = {
|
|||||||
isPopulated: PropTypes.bool.isRequired,
|
isPopulated: PropTypes.bool.isRequired,
|
||||||
error: PropTypes.object,
|
error: PropTypes.object,
|
||||||
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
seasonNumber: PropTypes.string.isRequired,
|
|
||||||
path: PropTypes.string.isRequired,
|
path: PropTypes.string.isRequired,
|
||||||
renameEpisodes: PropTypes.bool,
|
renameEpisodes: PropTypes.bool,
|
||||||
episodeFormat: PropTypes.string,
|
standardMovieFormat: PropTypes.string,
|
||||||
onOrganizePress: PropTypes.func.isRequired,
|
onOrganizePress: PropTypes.func.isRequired,
|
||||||
onModalClose: PropTypes.func.isRequired
|
onModalClose: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
@ -14,14 +14,14 @@ function createMapStateToProps() {
|
|||||||
(state) => state.organizePreview,
|
(state) => state.organizePreview,
|
||||||
(state) => state.settings.naming,
|
(state) => state.settings.naming,
|
||||||
createMovieSelector(),
|
createMovieSelector(),
|
||||||
(organizePreview, naming, series) => {
|
(organizePreview, naming, movie) => {
|
||||||
const props = { ...organizePreview };
|
const props = { ...organizePreview };
|
||||||
props.isFetching = organizePreview.isFetching || naming.isFetching;
|
props.isFetching = organizePreview.isFetching || naming.isFetching;
|
||||||
props.isPopulated = organizePreview.isPopulated && naming.isPopulated;
|
props.isPopulated = organizePreview.isPopulated && naming.isPopulated;
|
||||||
props.error = organizePreview.error || naming.error;
|
props.error = organizePreview.error || naming.error;
|
||||||
props.renameEpisodes = naming.item.renameEpisodes;
|
props.renameEpisodes = naming.item.renameEpisodes;
|
||||||
props.episodeFormat = naming.item.episodeFormat;
|
props.standardMovieFormat = naming.item.standardMovieFormat;
|
||||||
props.path = series.path;
|
props.path = movie.path;
|
||||||
|
|
||||||
return props;
|
return props;
|
||||||
}
|
}
|
||||||
@ -41,13 +41,11 @@ class OrganizePreviewModalContentConnector extends Component {
|
|||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
const {
|
const {
|
||||||
seriesId,
|
movieId
|
||||||
seasonNumber
|
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
this.props.fetchOrganizePreview({
|
this.props.fetchOrganizePreview({
|
||||||
seriesId,
|
movieId
|
||||||
seasonNumber
|
|
||||||
});
|
});
|
||||||
|
|
||||||
this.props.fetchNamingSettings();
|
this.props.fetchNamingSettings();
|
||||||
@ -59,7 +57,7 @@ class OrganizePreviewModalContentConnector extends Component {
|
|||||||
onOrganizePress = (files) => {
|
onOrganizePress = (files) => {
|
||||||
this.props.executeCommand({
|
this.props.executeCommand({
|
||||||
name: commandNames.RENAME_FILES,
|
name: commandNames.RENAME_FILES,
|
||||||
seriesId: this.props.seriesId,
|
movieId: this.props.movieId,
|
||||||
files
|
files
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -80,8 +78,7 @@ class OrganizePreviewModalContentConnector extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
OrganizePreviewModalContentConnector.propTypes = {
|
OrganizePreviewModalContentConnector.propTypes = {
|
||||||
seriesId: PropTypes.number.isRequired,
|
movieId: PropTypes.number.isRequired,
|
||||||
seasonNumber: PropTypes.number,
|
|
||||||
fetchOrganizePreview: PropTypes.func.isRequired,
|
fetchOrganizePreview: PropTypes.func.isRequired,
|
||||||
fetchNamingSettings: PropTypes.func.isRequired,
|
fetchNamingSettings: PropTypes.func.isRequired,
|
||||||
executeCommand: PropTypes.func.isRequired,
|
executeCommand: PropTypes.func.isRequired,
|
||||||
|
@ -76,7 +76,7 @@ function EditRestrictionModalContent(props) {
|
|||||||
<FormInputGroup
|
<FormInputGroup
|
||||||
type={inputTypes.TAG}
|
type={inputTypes.TAG}
|
||||||
name="tags"
|
name="tags"
|
||||||
helpText="Restrictions will apply to series at least one matching tag. Leave blank to apply to all series"
|
helpText="Restrictions will apply to movies at least one matching tag. Leave blank to apply to all movies"
|
||||||
{...tags}
|
{...tags}
|
||||||
onChange={onInputChange}
|
onChange={onInputChange}
|
||||||
/>
|
/>
|
||||||
|
@ -326,7 +326,7 @@ class MediaManagement extends Component {
|
|||||||
<FormInputGroup
|
<FormInputGroup
|
||||||
type={inputTypes.TEXT}
|
type={inputTypes.TEXT}
|
||||||
name="folderChmod"
|
name="folderChmod"
|
||||||
helpText="Octal, applied to series/season folders created by Radarr"
|
helpText="Octal, applied to movie folders created by Radarr"
|
||||||
values={fileDateOptions}
|
values={fileDateOptions}
|
||||||
onChange={onInputChange}
|
onChange={onInputChange}
|
||||||
{...settings.folderChmod}
|
{...settings.folderChmod}
|
||||||
|
@ -98,7 +98,7 @@ function EditNotificationModalContent(props) {
|
|||||||
<FormInputGroup
|
<FormInputGroup
|
||||||
type={inputTypes.CHECK}
|
type={inputTypes.CHECK}
|
||||||
name="onGrab"
|
name="onGrab"
|
||||||
helpText="Be notified when episodes are available for download and has been sent to a download client"
|
helpText="Be notified when movies are available for download and has been sent to a download client"
|
||||||
isDisabled={!supportsOnGrab.value}
|
isDisabled={!supportsOnGrab.value}
|
||||||
{...onGrab}
|
{...onGrab}
|
||||||
onChange={onInputChange}
|
onChange={onInputChange}
|
||||||
@ -111,7 +111,7 @@ function EditNotificationModalContent(props) {
|
|||||||
<FormInputGroup
|
<FormInputGroup
|
||||||
type={inputTypes.CHECK}
|
type={inputTypes.CHECK}
|
||||||
name="onDownload"
|
name="onDownload"
|
||||||
helpText="Be notified when episodes are successfully imported"
|
helpText="Be notified when movies are successfully imported"
|
||||||
isDisabled={!supportsOnDownload.value}
|
isDisabled={!supportsOnDownload.value}
|
||||||
{...onDownload}
|
{...onDownload}
|
||||||
onChange={onInputChange}
|
onChange={onInputChange}
|
||||||
@ -126,7 +126,7 @@ function EditNotificationModalContent(props) {
|
|||||||
<FormInputGroup
|
<FormInputGroup
|
||||||
type={inputTypes.CHECK}
|
type={inputTypes.CHECK}
|
||||||
name="onUpgrade"
|
name="onUpgrade"
|
||||||
helpText="Be notified when episodes are upgraded to a better quality"
|
helpText="Be notified when movies are upgraded to a better quality"
|
||||||
isDisabled={!supportsOnUpgrade.value}
|
isDisabled={!supportsOnUpgrade.value}
|
||||||
{...onUpgrade}
|
{...onUpgrade}
|
||||||
onChange={onInputChange}
|
onChange={onInputChange}
|
||||||
@ -140,7 +140,7 @@ function EditNotificationModalContent(props) {
|
|||||||
<FormInputGroup
|
<FormInputGroup
|
||||||
type={inputTypes.CHECK}
|
type={inputTypes.CHECK}
|
||||||
name="onRename"
|
name="onRename"
|
||||||
helpText="Be notified when episodes are renamed"
|
helpText="Be notified when movies are renamed"
|
||||||
isDisabled={!supportsOnRename.value}
|
isDisabled={!supportsOnRename.value}
|
||||||
{...onRename}
|
{...onRename}
|
||||||
onChange={onInputChange}
|
onChange={onInputChange}
|
||||||
@ -153,7 +153,7 @@ function EditNotificationModalContent(props) {
|
|||||||
<FormInputGroup
|
<FormInputGroup
|
||||||
type={inputTypes.TAG}
|
type={inputTypes.TAG}
|
||||||
name="tags"
|
name="tags"
|
||||||
helpText="Only send notifications for series with at least one matching tag"
|
helpText="Only send notifications for movies with at least one matching tag"
|
||||||
{...tags}
|
{...tags}
|
||||||
onChange={onInputChange}
|
onChange={onInputChange}
|
||||||
/>
|
/>
|
||||||
|
@ -110,7 +110,7 @@ function EditDelayProfileModalContent(props) {
|
|||||||
{
|
{
|
||||||
id === 1 ?
|
id === 1 ?
|
||||||
<Alert>
|
<Alert>
|
||||||
This is the default profile. It applies to all series that don't have an explicit profile.
|
This is the default profile. It applies to all movies that don't have an explicit profile.
|
||||||
</Alert> :
|
</Alert> :
|
||||||
|
|
||||||
<FormGroup>
|
<FormGroup>
|
||||||
@ -120,7 +120,7 @@ function EditDelayProfileModalContent(props) {
|
|||||||
type={inputTypes.TAG}
|
type={inputTypes.TAG}
|
||||||
name="tags"
|
name="tags"
|
||||||
{...tags}
|
{...tags}
|
||||||
helpText="Applies to series with at least one matching tag"
|
helpText="Applies to movies with at least one matching tag"
|
||||||
onChange={onInputChange}
|
onChange={onInputChange}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
|
@ -109,7 +109,7 @@ function Settings() {
|
|||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
<div className={styles.summary}>
|
<div className={styles.summary}>
|
||||||
Create metadata files when episodes are imported or series are refreshed
|
Create metadata files when movies are imported or refreshed
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Link
|
<Link
|
||||||
|
@ -190,8 +190,8 @@ export const actionHandlers = handleThunks({
|
|||||||
const item = _.find(items, { id });
|
const item = _.find(items, { id });
|
||||||
const selectedMovie = item.selectedMovie;
|
const selectedMovie = item.selectedMovie;
|
||||||
|
|
||||||
// Make sure we have a selected series and
|
// Make sure we have a selected movie and
|
||||||
// the same series hasn't been added yet.
|
// the same movie hasn't been added yet.
|
||||||
if (selectedMovie && !_.some(acc, { tmdbId: selectedMovie.tmdbId })) {
|
if (selectedMovie && !_.some(acc, { tmdbId: selectedMovie.tmdbId })) {
|
||||||
const newMovie = getNewMovie(_.cloneDeep(selectedMovie), item);
|
const newMovie = getNewMovie(_.cloneDeep(selectedMovie), item);
|
||||||
newMovie.path = item.path;
|
newMovie.path = item.path;
|
||||||
|
@ -17,7 +17,6 @@ import * as queue from './queueActions';
|
|||||||
import * as releases from './releaseActions';
|
import * as releases from './releaseActions';
|
||||||
import * as rootFolders from './rootFolderActions';
|
import * as rootFolders from './rootFolderActions';
|
||||||
import * as movies from './movieActions';
|
import * as movies from './movieActions';
|
||||||
import * as movieEditor from './movieEditorActions';
|
|
||||||
import * as movieHistory from './movieHistoryActions';
|
import * as movieHistory from './movieHistoryActions';
|
||||||
import * as movieIndex from './movieIndexActions';
|
import * as movieIndex from './movieIndexActions';
|
||||||
import * as settings from './settingsActions';
|
import * as settings from './settingsActions';
|
||||||
@ -44,7 +43,6 @@ export default [
|
|||||||
releases,
|
releases,
|
||||||
rootFolders,
|
rootFolders,
|
||||||
movies,
|
movies,
|
||||||
movieEditor,
|
|
||||||
movieHistory,
|
movieHistory,
|
||||||
movieIndex,
|
movieIndex,
|
||||||
settings,
|
settings,
|
||||||
|
@ -1,179 +0,0 @@
|
|||||||
import { createAction } from 'redux-actions';
|
|
||||||
import { batchActions } from 'redux-batched-actions';
|
|
||||||
import createAjaxRequest from 'Utilities/createAjaxRequest';
|
|
||||||
import { filterBuilderTypes, filterBuilderValueTypes, sortDirections } from 'Helpers/Props';
|
|
||||||
import { createThunk, handleThunks } from 'Store/thunks';
|
|
||||||
import createSetClientSideCollectionSortReducer from './Creators/Reducers/createSetClientSideCollectionSortReducer';
|
|
||||||
import createSetClientSideCollectionFilterReducer from './Creators/Reducers/createSetClientSideCollectionFilterReducer';
|
|
||||||
import createHandleActions from './Creators/createHandleActions';
|
|
||||||
import { set, updateItem } from './baseActions';
|
|
||||||
import { filters, filterPredicates, sortPredicates } from './movieActions';
|
|
||||||
|
|
||||||
//
|
|
||||||
// Variables
|
|
||||||
|
|
||||||
export const section = 'movieEditor';
|
|
||||||
|
|
||||||
//
|
|
||||||
// State
|
|
||||||
|
|
||||||
export const defaultState = {
|
|
||||||
isSaving: false,
|
|
||||||
saveError: null,
|
|
||||||
isDeleting: false,
|
|
||||||
deleteError: null,
|
|
||||||
sortKey: 'sortTitle',
|
|
||||||
sortDirection: sortDirections.ASCENDING,
|
|
||||||
secondarySortKey: 'sortTitle',
|
|
||||||
secondarySortDirection: sortDirections.ASCENDING,
|
|
||||||
selectedFilterKey: 'all',
|
|
||||||
filters,
|
|
||||||
filterPredicates,
|
|
||||||
|
|
||||||
filterBuilderProps: [
|
|
||||||
{
|
|
||||||
name: 'monitored',
|
|
||||||
label: 'Monitored',
|
|
||||||
type: filterBuilderTypes.EXACT,
|
|
||||||
valueType: filterBuilderValueTypes.BOOL
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'status',
|
|
||||||
label: 'Status',
|
|
||||||
type: filterBuilderTypes.EXACT,
|
|
||||||
valueType: filterBuilderValueTypes.SERIES_STATUS
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'qualityProfileId',
|
|
||||||
label: 'Quality Profile',
|
|
||||||
type: filterBuilderTypes.EXACT,
|
|
||||||
valueType: filterBuilderValueTypes.QUALITY_PROFILE
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'path',
|
|
||||||
label: 'Path',
|
|
||||||
type: filterBuilderTypes.STRING
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'rootFolderPath',
|
|
||||||
label: 'Root Folder Path',
|
|
||||||
type: filterBuilderTypes.EXACT
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'tags',
|
|
||||||
label: 'Tags',
|
|
||||||
type: filterBuilderTypes.ARRAY,
|
|
||||||
valueType: filterBuilderValueTypes.TAG
|
|
||||||
}
|
|
||||||
],
|
|
||||||
|
|
||||||
sortPredicates
|
|
||||||
};
|
|
||||||
|
|
||||||
export const persistState = [
|
|
||||||
'movieEditor.sortKey',
|
|
||||||
'movieEditor.sortDirection',
|
|
||||||
'movieEditor.selectedFilterKey',
|
|
||||||
'movieEditor.customFilters'
|
|
||||||
];
|
|
||||||
|
|
||||||
//
|
|
||||||
// Actions Types
|
|
||||||
|
|
||||||
export const SET_MOVIE_EDITOR_SORT = 'movieEditor/setMovieEditorSort';
|
|
||||||
export const SET_MOVIE_EDITOR_FILTER = 'movieEditor/setMovieEditorFilter';
|
|
||||||
export const SAVE_MOVIE_EDITOR = 'movieEditor/saveMovieEditor';
|
|
||||||
export const BULK_DELETE_MOVIE = 'movieEditor/bulkDeleteMovie';
|
|
||||||
//
|
|
||||||
// Action Creators
|
|
||||||
|
|
||||||
export const setMovieEditorSort = createAction(SET_MOVIE_EDITOR_SORT);
|
|
||||||
export const setMovieEditorFilter = createAction(SET_MOVIE_EDITOR_FILTER);
|
|
||||||
export const saveMovieEditor = createThunk(SAVE_MOVIE_EDITOR);
|
|
||||||
export const bulkDeleteMovie = createThunk(BULK_DELETE_MOVIE);
|
|
||||||
//
|
|
||||||
// Action Handlers
|
|
||||||
|
|
||||||
export const actionHandlers = handleThunks({
|
|
||||||
[SAVE_MOVIE_EDITOR]: function(getState, payload, dispatch) {
|
|
||||||
dispatch(set({
|
|
||||||
section,
|
|
||||||
isSaving: true
|
|
||||||
}));
|
|
||||||
|
|
||||||
const promise = createAjaxRequest({
|
|
||||||
url: '/movie/editor',
|
|
||||||
method: 'PUT',
|
|
||||||
data: JSON.stringify(payload),
|
|
||||||
dataType: 'json'
|
|
||||||
}).request;
|
|
||||||
|
|
||||||
promise.done((data) => {
|
|
||||||
dispatch(batchActions([
|
|
||||||
...data.map((movie) => {
|
|
||||||
return updateItem({
|
|
||||||
id: movie.id,
|
|
||||||
section: 'movies',
|
|
||||||
...movie
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
|
|
||||||
set({
|
|
||||||
section,
|
|
||||||
isSaving: false,
|
|
||||||
saveError: null
|
|
||||||
})
|
|
||||||
]));
|
|
||||||
});
|
|
||||||
|
|
||||||
promise.fail((xhr) => {
|
|
||||||
dispatch(set({
|
|
||||||
section,
|
|
||||||
isSaving: false,
|
|
||||||
saveError: xhr
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
[BULK_DELETE_MOVIE]: function(getState, payload, dispatch) {
|
|
||||||
dispatch(set({
|
|
||||||
section,
|
|
||||||
isDeleting: true
|
|
||||||
}));
|
|
||||||
|
|
||||||
const promise = createAjaxRequest({
|
|
||||||
url: '/movie/editor',
|
|
||||||
method: 'DELETE',
|
|
||||||
data: JSON.stringify(payload),
|
|
||||||
dataType: 'json'
|
|
||||||
}).request;
|
|
||||||
|
|
||||||
promise.done(() => {
|
|
||||||
// SignaR will take care of removing the series from the collection
|
|
||||||
|
|
||||||
dispatch(set({
|
|
||||||
section,
|
|
||||||
isDeleting: false,
|
|
||||||
deleteError: null
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
|
|
||||||
promise.fail((xhr) => {
|
|
||||||
dispatch(set({
|
|
||||||
section,
|
|
||||||
isDeleting: false,
|
|
||||||
deleteError: xhr
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
//
|
|
||||||
// Reducers
|
|
||||||
|
|
||||||
export const reducers = createHandleActions({
|
|
||||||
|
|
||||||
[SET_MOVIE_EDITOR_SORT]: createSetClientSideCollectionSortReducer(section),
|
|
||||||
[SET_MOVIE_EDITOR_FILTER]: createSetClientSideCollectionFilterReducer(section)
|
|
||||||
|
|
||||||
}, defaultState, section);
|
|
@ -1,10 +1,14 @@
|
|||||||
import { createAction } from 'redux-actions';
|
import { createAction } from 'redux-actions';
|
||||||
|
import { batchActions } from 'redux-batched-actions';
|
||||||
|
import createAjaxRequest from 'Utilities/createAjaxRequest';
|
||||||
import sortByName from 'Utilities/Array/sortByName';
|
import sortByName from 'Utilities/Array/sortByName';
|
||||||
import { filterBuilderTypes, filterBuilderValueTypes, sortDirections } from 'Helpers/Props';
|
import { filterBuilderTypes, filterBuilderValueTypes, sortDirections } from 'Helpers/Props';
|
||||||
import createSetTableOptionReducer from './Creators/Reducers/createSetTableOptionReducer';
|
import createSetTableOptionReducer from './Creators/Reducers/createSetTableOptionReducer';
|
||||||
import createSetClientSideCollectionSortReducer from './Creators/Reducers/createSetClientSideCollectionSortReducer';
|
import createSetClientSideCollectionSortReducer from './Creators/Reducers/createSetClientSideCollectionSortReducer';
|
||||||
import createSetClientSideCollectionFilterReducer from './Creators/Reducers/createSetClientSideCollectionFilterReducer';
|
import createSetClientSideCollectionFilterReducer from './Creators/Reducers/createSetClientSideCollectionFilterReducer';
|
||||||
|
import { createThunk, handleThunks } from 'Store/thunks';
|
||||||
import createHandleActions from './Creators/createHandleActions';
|
import createHandleActions from './Creators/createHandleActions';
|
||||||
|
import { set, updateItem } from './baseActions';
|
||||||
import { filters, filterPredicates, sortPredicates } from './movieActions';
|
import { filters, filterPredicates, sortPredicates } from './movieActions';
|
||||||
//
|
//
|
||||||
// Variables
|
// Variables
|
||||||
@ -15,6 +19,10 @@ export const section = 'movieIndex';
|
|||||||
// State
|
// State
|
||||||
|
|
||||||
export const defaultState = {
|
export const defaultState = {
|
||||||
|
isSaving: false,
|
||||||
|
saveError: null,
|
||||||
|
isDeleting: false,
|
||||||
|
deleteError: null,
|
||||||
sortKey: 'sortTitle',
|
sortKey: 'sortTitle',
|
||||||
sortDirection: sortDirections.ASCENDING,
|
sortDirection: sortDirections.ASCENDING,
|
||||||
secondarySortKey: 'sortTitle',
|
secondarySortKey: 'sortTitle',
|
||||||
@ -47,6 +55,13 @@ export const defaultState = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
columns: [
|
columns: [
|
||||||
|
{
|
||||||
|
name: 'select',
|
||||||
|
columnLabel: 'select',
|
||||||
|
isSortable: false,
|
||||||
|
isVisible: true,
|
||||||
|
isModifiable: false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'status',
|
name: 'status',
|
||||||
columnLabel: 'Status',
|
columnLabel: 'Status',
|
||||||
@ -214,8 +229,8 @@ export const defaultState = {
|
|||||||
label: 'Genres',
|
label: 'Genres',
|
||||||
type: filterBuilderTypes.ARRAY,
|
type: filterBuilderTypes.ARRAY,
|
||||||
optionsSelector: function(items) {
|
optionsSelector: function(items) {
|
||||||
const tagList = items.reduce((acc, series) => {
|
const tagList = items.reduce((acc, movie) => {
|
||||||
series.genres.forEach((genre) => {
|
movie.genres.forEach((genre) => {
|
||||||
acc.push({
|
acc.push({
|
||||||
id: genre,
|
id: genre,
|
||||||
name: genre
|
name: genre
|
||||||
@ -268,6 +283,8 @@ export const SET_MOVIE_VIEW = 'movieIndex/setMovieView';
|
|||||||
export const SET_MOVIE_TABLE_OPTION = 'movieIndex/setMovieTableOption';
|
export const SET_MOVIE_TABLE_OPTION = 'movieIndex/setMovieTableOption';
|
||||||
export const SET_MOVIE_POSTER_OPTION = 'movieIndex/setMoviePosterOption';
|
export const SET_MOVIE_POSTER_OPTION = 'movieIndex/setMoviePosterOption';
|
||||||
export const SET_MOVIE_OVERVIEW_OPTION = 'movieIndex/setMovieOverviewOption';
|
export const SET_MOVIE_OVERVIEW_OPTION = 'movieIndex/setMovieOverviewOption';
|
||||||
|
export const SAVE_MOVIE_EDITOR = 'movieIndex/saveMovieEditor';
|
||||||
|
export const BULK_DELETE_MOVIE = 'movieIndex/bulkDeleteMovie';
|
||||||
|
|
||||||
//
|
//
|
||||||
// Action Creators
|
// Action Creators
|
||||||
@ -278,6 +295,85 @@ export const setMovieView = createAction(SET_MOVIE_VIEW);
|
|||||||
export const setMovieTableOption = createAction(SET_MOVIE_TABLE_OPTION);
|
export const setMovieTableOption = createAction(SET_MOVIE_TABLE_OPTION);
|
||||||
export const setMoviePosterOption = createAction(SET_MOVIE_POSTER_OPTION);
|
export const setMoviePosterOption = createAction(SET_MOVIE_POSTER_OPTION);
|
||||||
export const setMovieOverviewOption = createAction(SET_MOVIE_OVERVIEW_OPTION);
|
export const setMovieOverviewOption = createAction(SET_MOVIE_OVERVIEW_OPTION);
|
||||||
|
export const saveMovieEditor = createThunk(SAVE_MOVIE_EDITOR);
|
||||||
|
export const bulkDeleteMovie = createThunk(BULK_DELETE_MOVIE);
|
||||||
|
|
||||||
|
//
|
||||||
|
// Action Handlers
|
||||||
|
|
||||||
|
export const actionHandlers = handleThunks({
|
||||||
|
[SAVE_MOVIE_EDITOR]: function(getState, payload, dispatch) {
|
||||||
|
dispatch(set({
|
||||||
|
section,
|
||||||
|
isSaving: true
|
||||||
|
}));
|
||||||
|
|
||||||
|
const promise = createAjaxRequest({
|
||||||
|
url: '/movie/editor',
|
||||||
|
method: 'PUT',
|
||||||
|
data: JSON.stringify(payload),
|
||||||
|
dataType: 'json'
|
||||||
|
}).request;
|
||||||
|
|
||||||
|
promise.done((data) => {
|
||||||
|
dispatch(batchActions([
|
||||||
|
...data.map((movie) => {
|
||||||
|
return updateItem({
|
||||||
|
id: movie.id,
|
||||||
|
section: 'movies',
|
||||||
|
...movie
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
|
||||||
|
set({
|
||||||
|
section,
|
||||||
|
isSaving: false,
|
||||||
|
saveError: null
|
||||||
|
})
|
||||||
|
]));
|
||||||
|
});
|
||||||
|
|
||||||
|
promise.fail((xhr) => {
|
||||||
|
dispatch(set({
|
||||||
|
section,
|
||||||
|
isSaving: false,
|
||||||
|
saveError: xhr
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
[BULK_DELETE_MOVIE]: function(getState, payload, dispatch) {
|
||||||
|
dispatch(set({
|
||||||
|
section,
|
||||||
|
isDeleting: true
|
||||||
|
}));
|
||||||
|
|
||||||
|
const promise = createAjaxRequest({
|
||||||
|
url: '/movie/editor',
|
||||||
|
method: 'DELETE',
|
||||||
|
data: JSON.stringify(payload),
|
||||||
|
dataType: 'json'
|
||||||
|
}).request;
|
||||||
|
|
||||||
|
promise.done(() => {
|
||||||
|
// SignaR will take care of removing the movie from the collection
|
||||||
|
|
||||||
|
dispatch(set({
|
||||||
|
section,
|
||||||
|
isDeleting: false,
|
||||||
|
deleteError: null
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
promise.fail((xhr) => {
|
||||||
|
dispatch(set({
|
||||||
|
section,
|
||||||
|
isDeleting: false,
|
||||||
|
deleteError: xhr
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
//
|
//
|
||||||
// Reducers
|
// Reducers
|
||||||
|
@ -8,10 +8,10 @@ function createImportMovieItemSelector() {
|
|||||||
(state) => state.addMovie,
|
(state) => state.addMovie,
|
||||||
(state) => state.importMovie,
|
(state) => state.importMovie,
|
||||||
createAllMoviesSelector(),
|
createAllMoviesSelector(),
|
||||||
(id, addMovie, importMovie, series) => {
|
(id, addMovie, importMovie, movies) => {
|
||||||
const item = _.find(importMovie.items, { id }) || {};
|
const item = _.find(importMovie.items, { id }) || {};
|
||||||
const selectedMovie = item && item.selectedMovie;
|
const selectedMovie = item && item.selectedMovie;
|
||||||
const isExistingMovie = !!selectedMovie && _.some(series, { tvdbId: selectedMovie.tvdbId });
|
const isExistingMovie = !!selectedMovie && _.some(movies, { tmdbId: selectedMovie.tmdbId });
|
||||||
|
|
||||||
return {
|
return {
|
||||||
defaultMonitor: addMovie.defaults.monitor,
|
defaultMonitor: addMovie.defaults.monitor,
|
||||||
|
@ -46,7 +46,7 @@ module.exports = {
|
|||||||
// Modal
|
// Modal
|
||||||
modalBodyPadding: '30px',
|
modalBodyPadding: '30px',
|
||||||
|
|
||||||
// Series
|
// Movie
|
||||||
movieIndexColumnPadding: '20px',
|
movieIndexColumnPadding: '20px',
|
||||||
movieIndexColumnPaddingSmallScreen: '10px',
|
movieIndexColumnPaddingSmallScreen: '10px',
|
||||||
movieIndexOverviewInfoRowHeight: '21px'
|
movieIndexOverviewInfoRowHeight: '21px'
|
||||||
|
51
src/NzbDrone.Core/Movies/Commands/BulkMoveMovieCommand.cs
Normal file
51
src/NzbDrone.Core/Movies/Commands/BulkMoveMovieCommand.cs
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using NzbDrone.Core.Messaging.Commands;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Movies.Commands
|
||||||
|
{
|
||||||
|
public class BulkMoveMovieCommand : Command
|
||||||
|
{
|
||||||
|
public List<BulkMoveMovie> Movies { get; set; }
|
||||||
|
public string DestinationRootFolder { get; set; }
|
||||||
|
|
||||||
|
public override bool SendUpdatesToClient => true;
|
||||||
|
public override bool RequiresDiskAccess => true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class BulkMoveMovie : IEquatable<BulkMoveMovie>
|
||||||
|
{
|
||||||
|
public int MovieId { get; set; }
|
||||||
|
public string SourcePath { get; set; }
|
||||||
|
|
||||||
|
public bool Equals(BulkMoveMovie other)
|
||||||
|
{
|
||||||
|
if (other == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return MovieId.Equals(other.MovieId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Equals(object obj)
|
||||||
|
{
|
||||||
|
if (obj == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (obj.GetType() != GetType())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return MovieId.Equals(((BulkMoveMovie)obj).MovieId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
return MovieId.GetHashCode();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -220,6 +220,7 @@
|
|||||||
<Compile Include="Movies\AlternativeTitles\AlternativeTitle.cs" />
|
<Compile Include="Movies\AlternativeTitles\AlternativeTitle.cs" />
|
||||||
<Compile Include="Movies\AlternativeTitles\AlternativeTitleRepository.cs" />
|
<Compile Include="Movies\AlternativeTitles\AlternativeTitleRepository.cs" />
|
||||||
<Compile Include="Movies\AlternativeTitles\AlternativeTitleService.cs" />
|
<Compile Include="Movies\AlternativeTitles\AlternativeTitleService.cs" />
|
||||||
|
<Compile Include="Movies\Commands\BulkMoveMovieCommand.cs" />
|
||||||
<Compile Include="Movies\Events\MoviesImportedEvent.cs" />
|
<Compile Include="Movies\Events\MoviesImportedEvent.cs" />
|
||||||
<Compile Include="NetImport\NetImportListLevels.cs" />
|
<Compile Include="NetImport\NetImportListLevels.cs" />
|
||||||
<Compile Include="NetImport\TMDb\TMDbLanguageCodes.cs" />
|
<Compile Include="NetImport\TMDb\TMDbLanguageCodes.cs" />
|
||||||
|
@ -7,58 +7,98 @@
|
|||||||
using Radarr.Http.REST;
|
using Radarr.Http.REST;
|
||||||
using NzbDrone.Core.Movies;
|
using NzbDrone.Core.Movies;
|
||||||
using Radarr.Http.Mapping;
|
using Radarr.Http.Mapping;
|
||||||
|
using NzbDrone.Common.Extensions;
|
||||||
|
using NzbDrone.Core.Movies.Commands;
|
||||||
|
using NzbDrone.Core.Messaging.Commands;
|
||||||
|
|
||||||
namespace Radarr.Api.V2.Movies
|
namespace Radarr.Api.V2.Movies
|
||||||
{
|
{
|
||||||
public class MovieEditorModule : RadarrV2Module
|
public class MovieEditorModule : RadarrV2Module
|
||||||
{
|
{
|
||||||
private readonly IMovieService _movieService;
|
private readonly IMovieService _movieService;
|
||||||
|
private readonly IManageCommandQueue _commandQueueManager;
|
||||||
|
|
||||||
public MovieEditorModule(IMovieService movieService)
|
public MovieEditorModule(IMovieService movieService, IManageCommandQueue commandQueueManager)
|
||||||
: base("/movie/editor")
|
: base("/movie/editor")
|
||||||
{
|
{
|
||||||
_movieService = movieService;
|
_movieService = movieService;
|
||||||
Put["/"] = Movie => SaveAll();
|
_commandQueueManager = commandQueueManager;
|
||||||
Put["/delete"] = Movie => DeleteSelected();
|
Put["/"] = movie => SaveAll();
|
||||||
|
Delete["/"] = movie => DeleteMovies();
|
||||||
}
|
}
|
||||||
|
|
||||||
private Response SaveAll()
|
private Response SaveAll()
|
||||||
{
|
{
|
||||||
var resources = Request.Body.FromJson<List<MovieResource>>();
|
var resource = Request.Body.FromJson<MovieEditorResource>();
|
||||||
|
var moviesToUpdate = _movieService.GetMovies(resource.MovieIds);
|
||||||
|
var moviesToMove = new List<BulkMoveMovie>();
|
||||||
|
|
||||||
var Movie = resources.Select(MovieResource => MovieResource.ToModel(_movieService.GetMovie(MovieResource.Id))).ToList();
|
foreach (var movie in moviesToUpdate)
|
||||||
|
{
|
||||||
|
if (resource.Monitored.HasValue)
|
||||||
|
{
|
||||||
|
movie.Monitored = resource.Monitored.Value;
|
||||||
|
}
|
||||||
|
|
||||||
return _movieService.UpdateMovie(Movie)
|
if (resource.QualityProfileId.HasValue)
|
||||||
|
{
|
||||||
|
movie.ProfileId = resource.QualityProfileId.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (resource.RootFolderPath.IsNotNullOrWhiteSpace())
|
||||||
|
{
|
||||||
|
movie.RootFolderPath = resource.RootFolderPath;
|
||||||
|
moviesToMove.Add(new BulkMoveMovie
|
||||||
|
{
|
||||||
|
MovieId = movie.Id,
|
||||||
|
SourcePath = movie.Path
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (resource.Tags != null)
|
||||||
|
{
|
||||||
|
var newTags = resource.Tags;
|
||||||
|
var applyTags = resource.ApplyTags;
|
||||||
|
|
||||||
|
switch (applyTags)
|
||||||
|
{
|
||||||
|
case ApplyTags.Add:
|
||||||
|
newTags.ForEach(t => movie.Tags.Add(t));
|
||||||
|
break;
|
||||||
|
case ApplyTags.Remove:
|
||||||
|
newTags.ForEach(t => movie.Tags.Remove(t));
|
||||||
|
break;
|
||||||
|
case ApplyTags.Replace:
|
||||||
|
movie.Tags = new HashSet<int>(newTags);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (resource.MoveFiles && moviesToMove.Any())
|
||||||
|
{
|
||||||
|
_commandQueueManager.Push(new BulkMoveMovieCommand
|
||||||
|
{
|
||||||
|
DestinationRootFolder = resource.RootFolderPath,
|
||||||
|
Movies = moviesToMove
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return _movieService.UpdateMovie(moviesToUpdate)
|
||||||
.ToResource()
|
.ToResource()
|
||||||
.AsResponse(HttpStatusCode.Accepted);
|
.AsResponse(HttpStatusCode.Accepted);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Response DeleteSelected()
|
private Response DeleteMovies()
|
||||||
{
|
{
|
||||||
var deleteFiles = false;
|
var resource = Request.Body.FromJson<MovieEditorResource>();
|
||||||
var addExclusion = false;
|
|
||||||
var deleteFilesQuery = Request.Query.deleteFiles;
|
|
||||||
var addExclusionQuery = Request.Query.addExclusion;
|
|
||||||
|
|
||||||
if (deleteFilesQuery.HasValue)
|
foreach (var id in resource.MovieIds)
|
||||||
{
|
{
|
||||||
deleteFiles = Convert.ToBoolean(deleteFilesQuery.Value);
|
_movieService.DeleteMovie(id, false, false);
|
||||||
}
|
|
||||||
if (addExclusionQuery.HasValue)
|
|
||||||
{
|
|
||||||
addExclusion = Convert.ToBoolean(addExclusionQuery.Value);
|
|
||||||
}
|
|
||||||
var ids = Request.Body.FromJson<List<int>>();
|
|
||||||
|
|
||||||
foreach (var id in ids)
|
|
||||||
{
|
|
||||||
_movieService.DeleteMovie(id, deleteFiles, addExclusion);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Response
|
return new object().AsResponse();
|
||||||
{
|
|
||||||
StatusCode = HttpStatusCode.Accepted
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
24
src/Radarr.Api.V2/Movies/MovieEditorResource.cs
Normal file
24
src/Radarr.Api.V2/Movies/MovieEditorResource.cs
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using NzbDrone.Core.Movies;
|
||||||
|
|
||||||
|
namespace Radarr.Api.V2.Movies
|
||||||
|
{
|
||||||
|
class MovieEditorResource
|
||||||
|
{
|
||||||
|
public List<int> MovieIds { get; set; }
|
||||||
|
public bool? Monitored { get; set; }
|
||||||
|
public int? QualityProfileId { get; set; }
|
||||||
|
public string RootFolderPath { get; set; }
|
||||||
|
public List<int> Tags { get; set; }
|
||||||
|
public ApplyTags ApplyTags { get; set; }
|
||||||
|
public bool MoveFiles { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum ApplyTags
|
||||||
|
{
|
||||||
|
Add,
|
||||||
|
Remove,
|
||||||
|
Replace
|
||||||
|
}
|
||||||
|
}
|
@ -136,6 +136,7 @@
|
|||||||
<Compile Include="Movies\AlternativeYearModule.cs" />
|
<Compile Include="Movies\AlternativeYearModule.cs" />
|
||||||
<Compile Include="Movies\AlternativeYearResource.cs" />
|
<Compile Include="Movies\AlternativeYearResource.cs" />
|
||||||
<Compile Include="Movies\FetchMovieListModule.cs" />
|
<Compile Include="Movies\FetchMovieListModule.cs" />
|
||||||
|
<Compile Include="Movies\MovieEditorResource.cs" />
|
||||||
<Compile Include="Movies\MovieImportModule.cs" />
|
<Compile Include="Movies\MovieImportModule.cs" />
|
||||||
<Compile Include="Movies\MovieDiscoverModule.cs" />
|
<Compile Include="Movies\MovieDiscoverModule.cs" />
|
||||||
<Compile Include="Movies\MovieEditorModule.cs" />
|
<Compile Include="Movies\MovieEditorModule.cs" />
|
||||||
|
@ -42,7 +42,7 @@ public static JsonResponse<TModel> AsResponse<TModel>(this TModel model, HttpSta
|
|||||||
|
|
||||||
public static IDictionary<string, string> DisableCache(this IDictionary<string, string> headers)
|
public static IDictionary<string, string> DisableCache(this IDictionary<string, string> headers)
|
||||||
{
|
{
|
||||||
headers["Cache-Control"] = "no-cache, no-store, must-revalidate";
|
headers["Cache-Control"] = "no-cache, no-store, must-revalidate, max-age=0";
|
||||||
headers["Pragma"] = "no-cache";
|
headers["Pragma"] = "no-cache";
|
||||||
headers["Expires"] = "0";
|
headers["Expires"] = "0";
|
||||||
|
|
||||||
|
@ -54,5 +54,17 @@ public static bool GetBooleanQueryParameter(this Request request, string paramet
|
|||||||
|
|
||||||
return defaultValue;
|
return defaultValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static int GetIntegerQueryParameter(this Request request, string parameter, int defaultValue = 0)
|
||||||
|
{
|
||||||
|
var parameterValue = request.Query[parameter];
|
||||||
|
|
||||||
|
if (parameterValue.HasValue)
|
||||||
|
{
|
||||||
|
return int.Parse(parameterValue.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,7 @@ public abstract class HtmlMapperBase : StaticResourceMapperBase
|
|||||||
{
|
{
|
||||||
private readonly IDiskProvider _diskProvider;
|
private readonly IDiskProvider _diskProvider;
|
||||||
private readonly Func<ICacheBreakerProvider> _cacheBreakProviderFactory;
|
private readonly Func<ICacheBreakerProvider> _cacheBreakProviderFactory;
|
||||||
private static readonly Regex ReplaceRegex = new Regex(@"(?:(?<attribute>href|src|json)=\"")(?<path>.*?(?<extension>css|js|png|ico|ics|svg))(?:\"")(?:\s(?<nohash>data-no-hash))?", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
private static readonly Regex ReplaceRegex = new Regex(@"(?:(?<attribute>href|src)=\"")(?<path>.*?(?<extension>css|js|png|ico|ics|svg|json))(?:\"")(?:\s(?<nohash>data-no-hash))?", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||||
|
|
||||||
private string _generatedContent;
|
private string _generatedContent;
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user