diff --git a/frontend/src/AddMovie/AddNewMovie/AddNewMovieModalContent.js b/frontend/src/AddMovie/AddNewMovie/AddNewMovieModalContent.js index a5eac49a8..74ef61bbc 100644 --- a/frontend/src/AddMovie/AddNewMovie/AddNewMovieModalContent.js +++ b/frontend/src/AddMovie/AddNewMovie/AddNewMovieModalContent.js @@ -55,6 +55,7 @@ class AddNewMovieModalContent extends Component { rootFolderPath, monitor, qualityProfileId, + minimumAvailability, folder, tags, isSmallScreen, @@ -126,6 +127,17 @@ class AddNewMovieModalContent extends Component { /> + + Minimum Availability + + + + Quality Profile @@ -191,6 +203,7 @@ AddNewMovieModalContent.propTypes = { rootFolderPath: PropTypes.object, monitor: PropTypes.object.isRequired, qualityProfileId: PropTypes.object, + minimumAvailability: PropTypes.object.isRequired, folder: PropTypes.string.isRequired, tags: PropTypes.object.isRequired, isSmallScreen: PropTypes.bool.isRequired, diff --git a/frontend/src/AddMovie/AddNewMovie/AddNewMovieModalContentConnector.js b/frontend/src/AddMovie/AddNewMovie/AddNewMovieModalContentConnector.js index 78691c862..b3253d880 100644 --- a/frontend/src/AddMovie/AddNewMovie/AddNewMovieModalContentConnector.js +++ b/frontend/src/AddMovie/AddNewMovie/AddNewMovieModalContentConnector.js @@ -59,6 +59,7 @@ class AddNewMovieModalContentConnector extends Component { rootFolderPath, monitor, qualityProfileId, + minimumAvailability, tags } = this.props; @@ -67,6 +68,7 @@ class AddNewMovieModalContentConnector extends Component { rootFolderPath: rootFolderPath.value, monitor: monitor.value, qualityProfileId: qualityProfileId.value, + minimumAvailability: minimumAvailability.value, tags: tags.value, searchForMovie }); @@ -91,6 +93,7 @@ AddNewMovieModalContentConnector.propTypes = { rootFolderPath: PropTypes.object, monitor: PropTypes.object.isRequired, qualityProfileId: PropTypes.object, + minimumAvailability: PropTypes.object.isRequired, tags: PropTypes.object.isRequired, onModalClose: PropTypes.func.isRequired, setAddMovieDefault: PropTypes.func.isRequired, diff --git a/frontend/src/AddMovie/ImportMovie/Import/ImportMovieFooter.js b/frontend/src/AddMovie/ImportMovie/Import/ImportMovieFooter.js index 4c7eb7352..8b0d3142c 100644 --- a/frontend/src/AddMovie/ImportMovie/Import/ImportMovieFooter.js +++ b/frontend/src/AddMovie/ImportMovie/Import/ImportMovieFooter.js @@ -22,12 +22,14 @@ class ImportMovieFooter extends Component { const { defaultMonitor, - defaultQualityProfileId + defaultQualityProfileId, + defaultMinimumAvailability } = props; this.state = { monitor: defaultMonitor, - qualityProfileId: defaultQualityProfileId + qualityProfileId: defaultQualityProfileId, + minimumAvailability: defaultMinimumAvailability }; } @@ -35,13 +37,16 @@ class ImportMovieFooter extends Component { const { defaultMonitor, defaultQualityProfileId, + defaultMinimumAvailability, isMonitorMixed, - isQualityProfileIdMixed + isQualityProfileIdMixed, + isMinimumAvailabilityMixed } = this.props; const { monitor, - qualityProfileId + qualityProfileId, + minimumAvailability } = this.state; const newState = {}; @@ -58,6 +63,12 @@ class ImportMovieFooter extends Component { newState.qualityProfileId = defaultQualityProfileId; } + if (isMinimumAvailabilityMixed && minimumAvailability !== MIXED) { + newState.minimumAvailability = MIXED; + } else if (!isMinimumAvailabilityMixed && minimumAvailability !== defaultMinimumAvailability) { + newState.minimumAvailability = defaultMinimumAvailability; + } + if (!_.isEmpty(newState)) { this.setState(newState); } @@ -81,13 +92,15 @@ class ImportMovieFooter extends Component { isLookingUpMovie, isMonitorMixed, isQualityProfileIdMixed, + isMinimumAvailabilityMixed, onImportPress, onCancelLookupPress } = this.props; const { monitor, - qualityProfileId + qualityProfileId, + minimumAvailability } = this.state; return ( @@ -107,6 +120,21 @@ class ImportMovieFooter extends Component { /> +
+
+ Minimum Availability +
+ + +
+
Quality Profile @@ -174,8 +202,10 @@ ImportMovieFooter.propTypes = { isLookingUpMovie: PropTypes.bool.isRequired, defaultMonitor: PropTypes.string.isRequired, defaultQualityProfileId: PropTypes.number, + defaultMinimumAvailability: PropTypes.string, isMonitorMixed: PropTypes.bool.isRequired, isQualityProfileIdMixed: PropTypes.bool.isRequired, + isMinimumAvailabilityMixed: PropTypes.bool.isRequired, onInputChange: PropTypes.func.isRequired, onImportPress: PropTypes.func.isRequired, onCancelLookupPress: PropTypes.func.isRequired diff --git a/frontend/src/AddMovie/ImportMovie/Import/ImportMovieFooterConnector.js b/frontend/src/AddMovie/ImportMovie/Import/ImportMovieFooterConnector.js index 28f991b0d..3c0c58325 100644 --- a/frontend/src/AddMovie/ImportMovie/Import/ImportMovieFooterConnector.js +++ b/frontend/src/AddMovie/ImportMovie/Import/ImportMovieFooterConnector.js @@ -18,7 +18,8 @@ function createMapStateToProps() { (addMovie, importMovie, selectedIds) => { const { monitor: defaultMonitor, - qualityProfileId: defaultQualityProfileId + qualityProfileId: defaultQualityProfileId, + minimumAvailability: defaultMinimumAvailability } = addMovie.defaults; const { @@ -29,6 +30,7 @@ function createMapStateToProps() { const isMonitorMixed = isMixed(items, selectedIds, defaultMonitor, 'monitor'); const isQualityProfileIdMixed = isMixed(items, selectedIds, defaultQualityProfileId, 'qualityProfileId'); + const isMinimumAvailabilityMixed = isMixed(items, selectedIds, defaultMinimumAvailability, 'minimumAvailability'); return { selectedCount: selectedIds.length, @@ -36,8 +38,10 @@ function createMapStateToProps() { isImporting, defaultMonitor, defaultQualityProfileId, + defaultMinimumAvailability, isMonitorMixed, - isQualityProfileIdMixed + isQualityProfileIdMixed, + isMinimumAvailabilityMixed }; } ); diff --git a/frontend/src/AddMovie/ImportMovie/Import/ImportMovieHeader.css b/frontend/src/AddMovie/ImportMovie/Import/ImportMovieHeader.css index 2682c188d..dec42e592 100644 --- a/frontend/src/AddMovie/ImportMovie/Import/ImportMovieHeader.css +++ b/frontend/src/AddMovie/ImportMovie/Import/ImportMovieHeader.css @@ -11,6 +11,7 @@ min-width: 185px; } +.minimumAvailability, .qualityProfile { composes: headerCell from '~Components/Table/VirtualTableHeaderCell.css'; diff --git a/frontend/src/AddMovie/ImportMovie/Import/ImportMovieHeader.js b/frontend/src/AddMovie/ImportMovie/Import/ImportMovieHeader.js index 7b59b409f..eaf61ccf3 100644 --- a/frontend/src/AddMovie/ImportMovie/Import/ImportMovieHeader.js +++ b/frontend/src/AddMovie/ImportMovie/Import/ImportMovieHeader.js @@ -34,6 +34,13 @@ function ImportMovieHeader(props) { Monitor + + Min Availability + + + + + + { @@ -165,6 +167,7 @@ ImportMovieTable.propTypes = { unmappedFolders: PropTypes.arrayOf(PropTypes.object), defaultMonitor: PropTypes.string.isRequired, defaultQualityProfileId: PropTypes.number, + defaultMinimumAvailability: PropTypes.string, allSelected: PropTypes.bool.isRequired, allUnselected: PropTypes.bool.isRequired, selectedState: PropTypes.object.isRequired, diff --git a/frontend/src/AddMovie/ImportMovie/Import/ImportMovieTableConnector.js b/frontend/src/AddMovie/ImportMovie/Import/ImportMovieTableConnector.js index 42499c703..2717336c9 100644 --- a/frontend/src/AddMovie/ImportMovie/Import/ImportMovieTableConnector.js +++ b/frontend/src/AddMovie/ImportMovie/Import/ImportMovieTableConnector.js @@ -14,6 +14,7 @@ function createMapStateToProps() { return { defaultMonitor: addMovie.defaults.monitor, defaultQualityProfileId: addMovie.defaults.qualityProfileId, + defaultMinimumAvailability: addMovie.defaults.minimumAvailability, items: importMovie.items, isSmallScreen: dimensions.isSmallScreen, allMovies diff --git a/frontend/src/Components/Form/AvailabilitySelectInput.js b/frontend/src/Components/Form/AvailabilitySelectInput.js new file mode 100644 index 000000000..af9bdb2d6 --- /dev/null +++ b/frontend/src/Components/Form/AvailabilitySelectInput.js @@ -0,0 +1,54 @@ +import PropTypes from 'prop-types'; +import React from 'react'; +import SelectInput from './SelectInput'; + +const availabilityOptions = [ + { key: 'announced', value: 'Announced' }, + { key: 'inCinemas', value: 'In Cinemas' }, + { key: 'released', value: 'Released' }, + { key: 'preDB', value: 'PreDB' } +]; + +function AvailabilitySelectInput(props) { + const values = [...availabilityOptions]; + + const { + includeNoChange, + includeMixed + } = props; + + if (includeNoChange) { + values.unshift({ + key: 'noChange', + value: 'No Change', + disabled: true + }); + } + + if (includeMixed) { + values.unshift({ + key: 'mixed', + value: '(Mixed)', + disabled: true + }); + } + + return ( + + ); +} + +AvailabilitySelectInput.propTypes = { + includeNoChange: PropTypes.bool.isRequired, + includeMixed: PropTypes.bool.isRequired +}; + +AvailabilitySelectInput.defaultProps = { + includeNoChange: false, + includeMixed: false +}; + +export default AvailabilitySelectInput; diff --git a/frontend/src/Components/Form/FormInputGroup.js b/frontend/src/Components/Form/FormInputGroup.js index 5cbbc1f37..06a4de336 100644 --- a/frontend/src/Components/Form/FormInputGroup.js +++ b/frontend/src/Components/Form/FormInputGroup.js @@ -3,6 +3,7 @@ import React from 'react'; import { inputTypes } from 'Helpers/Props'; import Link from 'Components/Link/Link'; import AutoCompleteInput from './AutoCompleteInput'; +import AvailabilitySelectInput from './AvailabilitySelectInput'; import CaptchaInputConnector from './CaptchaInputConnector'; import CheckInput from './CheckInput'; import DeviceInputConnector from './DeviceInputConnector'; @@ -26,6 +27,9 @@ function getComponent(type) { case inputTypes.AUTO_COMPLETE: return AutoCompleteInput; + case inputTypes.AVAILABILITY_SELECT: + return AvailabilitySelectInput; + case inputTypes.CAPTCHA: return CaptchaInputConnector; diff --git a/frontend/src/Helpers/Props/inputTypes.js b/frontend/src/Helpers/Props/inputTypes.js index 92a7d2d39..5f42c4c3c 100644 --- a/frontend/src/Helpers/Props/inputTypes.js +++ b/frontend/src/Helpers/Props/inputTypes.js @@ -1,4 +1,5 @@ export const AUTO_COMPLETE = 'autoComplete'; +export const AVAILABILITY_SELECT = 'availabilitySelect'; export const CAPTCHA = 'captcha'; export const CHECK = 'check'; export const DEVICE = 'device'; @@ -16,6 +17,7 @@ export const TEXT_TAG = 'textTag'; export const all = [ AUTO_COMPLETE, + AVAILABILITY_SELECT, CAPTCHA, CHECK, DEVICE, diff --git a/frontend/src/Movie/Edit/EditMovieModalContent.js b/frontend/src/Movie/Edit/EditMovieModalContent.js index 40cdf816e..7a27800e3 100644 --- a/frontend/src/Movie/Edit/EditMovieModalContent.js +++ b/frontend/src/Movie/Edit/EditMovieModalContent.js @@ -69,6 +69,7 @@ class EditMovieModalContent extends Component { const { monitored, qualityProfileId, + minimumAvailability, // Id, path, tags @@ -96,6 +97,17 @@ class EditMovieModalContent extends Component { /> + + Minimum Availability + + + + Quality Profile diff --git a/frontend/src/Movie/Edit/EditMovieModalContentConnector.js b/frontend/src/Movie/Edit/EditMovieModalContentConnector.js index a5bd045a1..11971a69e 100644 --- a/frontend/src/Movie/Edit/EditMovieModalContentConnector.js +++ b/frontend/src/Movie/Edit/EditMovieModalContentConnector.js @@ -39,6 +39,7 @@ function createMapStateToProps() { const movieSettings = _.pick(movie, [ 'monitored', 'qualityProfileId', + 'minimumAvailability', 'path', 'tags' ]); diff --git a/frontend/src/Movie/Editor/MovieEditorFooter.js b/frontend/src/Movie/Editor/MovieEditorFooter.js index 501b72a81..3ce3ae4ca 100644 --- a/frontend/src/Movie/Editor/MovieEditorFooter.js +++ b/frontend/src/Movie/Editor/MovieEditorFooter.js @@ -2,6 +2,7 @@ import PropTypes from 'prop-types'; import React, { Component } from 'react'; import { kinds } from 'Helpers/Props'; import SelectInput from 'Components/Form/SelectInput'; +import AvailabilitySelectInput from 'Components/Form/AvailabilitySelectInput'; import QualityProfileSelectInputConnector from 'Components/Form/QualityProfileSelectInputConnector'; import RootFolderSelectInputConnector from 'Components/Form/RootFolderSelectInputConnector'; import SpinnerButton from 'Components/Link/SpinnerButton'; @@ -25,6 +26,7 @@ class MovieEditorFooter extends Component { this.state = { monitored: NO_CHANGE, qualityProfileId: NO_CHANGE, + minimumAvailability: NO_CHANGE, rootFolderPath: NO_CHANGE, savingTags: false, isDeleteMovieModalOpen: false, @@ -44,6 +46,7 @@ class MovieEditorFooter extends Component { this.setState({ monitored: NO_CHANGE, qualityProfileId: NO_CHANGE, + minimumAvailability: NO_CHANGE, rootFolderPath: NO_CHANGE, savingTags: false }); @@ -140,6 +143,7 @@ class MovieEditorFooter extends Component { const { monitored, qualityProfileId, + minimumAvailability, rootFolderPath, savingTags, isTagsModalOpen, @@ -186,6 +190,21 @@ class MovieEditorFooter extends Component { />
+
+ + + +
+
- ); + if (name === 'select') { + if (isMovieEditorActive) { + return ( + + ); + } + + return null; } if (name === 'actions') { diff --git a/frontend/src/Movie/Index/Table/MovieIndexRow.css b/frontend/src/Movie/Index/Table/MovieIndexRow.css index 2585cd57b..03179a787 100644 --- a/frontend/src/Movie/Index/Table/MovieIndexRow.css +++ b/frontend/src/Movie/Index/Table/MovieIndexRow.css @@ -1,23 +1,36 @@ -.status { +.cell { composes: cell from '~Components/Table/Cells/VirtualTableRowCell.css'; + display: flex; + align-items: center; +} + +.status { + composes: cell; + flex: 0 0 60px; } .sortTitle { - composes: cell from '~Components/Table/Cells/VirtualTableRowCell.css'; + composes: cell; flex: 4 0 110px; } +.minimumAvailability { + composes: cell; + + flex: 0 0 140px; +} + .studio { - composes: cell from '~Components/Table/Cells/VirtualTableRowCell.css'; + composes: cell; flex: 2 0 90px; } .qualityProfileId { - composes: cell from '~Components/Table/Cells/VirtualTableRowCell.css'; + composes: cell; flex: 1 0 125px; } @@ -26,44 +39,44 @@ .inCinemas, .physicalRelease, .genres { - composes: cell from '~Components/Table/Cells/VirtualTableRowCell.css'; + composes: cell; flex: 0 0 180px; } .movieStatus, .certification { - composes: cell from '~Components/Table/Cells/VirtualTableRowCell.css'; + composes: cell; flex: 0 0 100px; } .path { - composes: cell from '~Components/Table/Cells/VirtualTableRowCell.css'; + composes: cell; flex: 1 0 150px; } .sizeOnDisk { - composes: cell from '~Components/Table/Cells/VirtualTableRowCell.css'; + composes: cell; flex: 0 0 120px; } .ratings { - composes: headerCell from '~Components/Table/VirtualTableHeaderCell.css'; + composes: cell; flex: 0 0 80px; } .tags { - composes: cell from '~Components/Table/Cells/VirtualTableRowCell.css'; + composes: cell; flex: 1 0 60px; } .actions { - composes: cell from '~Components/Table/Cells/VirtualTableRowCell.css'; + composes: cell; flex: 0 1 90px; } diff --git a/frontend/src/Movie/Index/Table/MovieIndexRow.js b/frontend/src/Movie/Index/Table/MovieIndexRow.js index 939016435..c7c47bdc7 100644 --- a/frontend/src/Movie/Index/Table/MovieIndexRow.js +++ b/frontend/src/Movie/Index/Table/MovieIndexRow.js @@ -1,6 +1,7 @@ import PropTypes from 'prop-types'; import React, { Component } from 'react'; // import getProgressBarKind from 'Utilities/Series/getProgressBarKind'; +import titleCase from 'Utilities/String/titleCase'; import { icons } from 'Helpers/Props'; import HeartRating from 'Components/HeartRating'; import IconButton from 'Components/Link/IconButton'; @@ -73,6 +74,7 @@ class MovieIndexRow extends Component { added, inCinemas, physicalRelease, + minimumAvailability, path, genres, ratings, @@ -201,6 +203,17 @@ class MovieIndexRow extends Component { ); } + if (name === 'minimumAvailability') { + return ( + + {titleCase(minimumAvailability)} + + ); + } + if (name === 'path') { return ( MovieIds { get; set; } public bool? Monitored { get; set; } public int? QualityProfileId { get; set; } + public MovieStatusType? MinimumAvailability { get; set; } public string RootFolderPath { get; set; } public List Tags { get; set; } public ApplyTags ApplyTags { get; set; }