diff --git a/frontend/src/Calendar/CalendarConnector.js b/frontend/src/Calendar/CalendarConnector.js index b297ee51f..535d94fe8 100644 --- a/frontend/src/Calendar/CalendarConnector.js +++ b/frontend/src/Calendar/CalendarConnector.js @@ -76,16 +76,15 @@ class CalendarConnector extends Component { } = this.props; if (hasDifferentItems(prevProps.items, items)) { - const movieIds = selectUniqueIds(items, 'id'); const movieFileIds = selectUniqueIds(items, 'movieFileId'); - if (items.length) { - this.props.fetchQueueDetails({ movieIds }); - } - if (movieFileIds.length) { this.props.fetchMovieFiles({ movieFileIds }); } + + if (items.length) { + this.props.fetchQueueDetails(); + } } if (prevProps.time !== time) { diff --git a/frontend/src/Components/Label.css b/frontend/src/Components/Label.css index 185cea903..b54bba63b 100644 --- a/frontend/src/Components/Label.css +++ b/frontend/src/Components/Label.css @@ -87,6 +87,15 @@ } } +.queue { + border-color: $queueColor; + background-color: $queueColor; + + &.outline { + color: $queueColor; + } +} + /** Sizes **/ .small { diff --git a/frontend/src/Components/ProgressBar.css b/frontend/src/Components/ProgressBar.css index 777187eec..f95514478 100644 --- a/frontend/src/Components/ProgressBar.css +++ b/frontend/src/Components/ProgressBar.css @@ -73,6 +73,10 @@ background-color: $infoColor; } +.queue { + background-color: $queueColor; +} + .small { height: $progressBarSmallHeight; diff --git a/frontend/src/Helpers/Props/kinds.js b/frontend/src/Helpers/Props/kinds.js index fd2c17f7b..d7d61c9f4 100644 --- a/frontend/src/Helpers/Props/kinds.js +++ b/frontend/src/Helpers/Props/kinds.js @@ -8,6 +8,7 @@ export const PRIMARY = 'primary'; export const PURPLE = 'purple'; export const SUCCESS = 'success'; export const WARNING = 'warning'; +export const QUEUE = 'queue'; export const all = [ DANGER, @@ -19,5 +20,6 @@ export const all = [ PRIMARY, PURPLE, SUCCESS, - WARNING + WARNING, + QUEUE ]; diff --git a/frontend/src/Movie/Details/MovieDetails.js b/frontend/src/Movie/Details/MovieDetails.js index 89657102b..77ace6349 100644 --- a/frontend/src/Movie/Details/MovieDetails.js +++ b/frontend/src/Movie/Details/MovieDetails.js @@ -267,6 +267,7 @@ class MovieDetails extends Component { onMonitorTogglePress, onRefreshPress, onSearchPress, + queueDetails, movieRuntimeFormat } = this.props; @@ -505,6 +506,7 @@ class MovieDetails extends Component { hasMovieFiles={hasMovieFiles} monitored={monitored} isAvailable={isAvailable} + queueDetails={queueDetails} /> @@ -774,6 +776,7 @@ MovieDetails.propTypes = { onRefreshPress: PropTypes.func.isRequired, onSearchPress: PropTypes.func.isRequired, onGoToMovie: PropTypes.func.isRequired, + queueDetails: PropTypes.object, movieRuntimeFormat: PropTypes.string.isRequired }; diff --git a/frontend/src/Movie/Details/MovieDetailsConnector.js b/frontend/src/Movie/Details/MovieDetailsConnector.js index 9ce0ebb5e..247736e7b 100644 --- a/frontend/src/Movie/Details/MovieDetailsConnector.js +++ b/frontend/src/Movie/Details/MovieDetailsConnector.js @@ -87,9 +87,10 @@ function createMapStateToProps() { createAllMoviesSelector(), createCommandsSelector(), createDimensionsSelector(), + (state) => state.queue.details, (state) => state.app.isSidebarVisible, (state) => state.settings.ui.item.movieRuntimeFormat, - (titleSlug, movieFiles, movieCredits, extraFiles, allMovies, commands, dimensions, isSidebarVisible, movieRuntimeFormat) => { + (titleSlug, movieFiles, movieCredits, extraFiles, allMovies, commands, dimensions, queueDetails, isSidebarVisible, movieRuntimeFormat) => { const sortedMovies = _.orderBy(allMovies, 'sortTitle'); const movieIndex = _.findIndex(sortedMovies, { titleSlug }); const movie = sortedMovies[movieIndex]; @@ -162,6 +163,7 @@ function createMapStateToProps() { nextMovie, isSmallScreen: dimensions.isSmallScreen, isSidebarVisible, + queueDetails, movieRuntimeFormat }; } diff --git a/frontend/src/Movie/Details/MovieStatusLabel.css b/frontend/src/Movie/Details/MovieStatusLabel.css index 0be337a50..04470753b 100644 --- a/frontend/src/Movie/Details/MovieStatusLabel.css +++ b/frontend/src/Movie/Details/MovieStatusLabel.css @@ -17,3 +17,8 @@ padding-left: 2px; border-left: 4px solid $warningColor; } + +.queue { + padding-left: 2px; + border-left: 4px solid $queueColor; +} diff --git a/frontend/src/Movie/Details/MovieStatusLabel.js b/frontend/src/Movie/Details/MovieStatusLabel.js index 9a3831676..4af960a8b 100644 --- a/frontend/src/Movie/Details/MovieStatusLabel.js +++ b/frontend/src/Movie/Details/MovieStatusLabel.js @@ -1,36 +1,50 @@ import PropTypes from 'prop-types'; import React from 'react'; +import getQueueStatusText from 'Utilities/Movie/getQueueStatusText'; +import translate from 'Utilities/String/translate'; import styles from './MovieStatusLabel.css'; -function getMovieStatus(hasFile, isMonitored, isAvailable) { +function getMovieStatus(hasFile, isMonitored, isAvailable, queueDetails = false) { + + if (queueDetails.items[0]) { + const queueStatus = queueDetails.items[0].status; + const queueState = queueDetails.items[0].trackedDownloadStatus; + const queueStatusText = getQueueStatusText(queueStatus, queueState); + return queueStatusText.longText; + } if (hasFile) { - return 'Downloaded'; + return translate('Downloaded'); } if (!isMonitored) { - return 'Unmonitored'; + return translate('Unmonitored'); } if (isAvailable && !hasFile) { - return 'Missing'; + return translate('Missing'); } - return 'Unreleased'; + return translate('Unreleased'); } function MovieStatusLabel(props) { const { hasMovieFiles, monitored, - isAvailable + isAvailable, + queueDetails } = props; - const status = getMovieStatus(hasMovieFiles, monitored, isAvailable); + const status = getMovieStatus(hasMovieFiles, monitored, isAvailable, queueDetails); + let statusClass = status; + if (queueDetails.items.length) { + statusClass = 'queue'; + } return ( {status} @@ -40,7 +54,8 @@ function MovieStatusLabel(props) { MovieStatusLabel.propTypes = { hasMovieFiles: PropTypes.bool.isRequired, monitored: PropTypes.bool.isRequired, - isAvailable: PropTypes.bool.isRequired + isAvailable: PropTypes.bool.isRequired, + queueDetails: PropTypes.object }; MovieStatusLabel.defaultProps = { diff --git a/frontend/src/Movie/Index/MovieIndexConnector.js b/frontend/src/Movie/Index/MovieIndexConnector.js index 3051d7aad..a70e1f488 100644 --- a/frontend/src/Movie/Index/MovieIndexConnector.js +++ b/frontend/src/Movie/Index/MovieIndexConnector.js @@ -6,6 +6,7 @@ import * as commandNames from 'Commands/commandNames'; import withScrollPosition from 'Components/withScrollPosition'; import { executeCommand } from 'Store/Actions/commandActions'; import { saveMovieEditor, setMovieFilter, setMovieSort, setMovieTableOption, setMovieView } from 'Store/Actions/movieIndexActions'; +import { clearQueueDetails, fetchQueueDetails } from 'Store/Actions/queueActions'; import { fetchRootFolders } from 'Store/Actions/rootFolderActions'; import scrollPositions from 'Store/scrollPositions'; import createCommandExecutingSelector from 'Store/Selectors/createCommandExecutingSelector'; @@ -45,6 +46,14 @@ function createMapStateToProps() { function createMapDispatchToProps(dispatch, props) { return { + fetchQueueDetails() { + dispatch(fetchQueueDetails()); + }, + + clearQueueDetails() { + dispatch(clearQueueDetails()); + }, + dispatchFetchRootFolders() { dispatch(fetchRootFolders()); }, @@ -96,6 +105,11 @@ class MovieIndexConnector extends Component { componentDidMount() { // TODO: Fetch root folders here for now, but should eventually fetch on editor toggle and check loaded before showing controls this.props.dispatchFetchRootFolders(); + this.props.fetchQueueDetails(); + } + + componentWillUnmount() { + this.props.clearQueueDetails(); } // @@ -134,7 +148,10 @@ MovieIndexConnector.propTypes = { view: PropTypes.string.isRequired, dispatchFetchRootFolders: PropTypes.func.isRequired, dispatchSetMovieView: PropTypes.func.isRequired, - dispatchSaveMovieEditor: PropTypes.func.isRequired + dispatchSaveMovieEditor: PropTypes.func.isRequired, + fetchQueueDetails: PropTypes.func.isRequired, + clearQueueDetails: PropTypes.func.isRequired, + items: PropTypes.arrayOf(PropTypes.object) }; export default withScrollPosition( diff --git a/frontend/src/Movie/Index/MovieIndexFooter.css b/frontend/src/Movie/Index/MovieIndexFooter.css index 5e368cccf..312013972 100644 --- a/frontend/src/Movie/Index/MovieIndexFooter.css +++ b/frontend/src/Movie/Index/MovieIndexFooter.css @@ -18,6 +18,12 @@ border-radius: 4px; } +.queue { + composes: legendItemColor; + + background-color: $queueColor; +} + .continuing { composes: legendItemColor; diff --git a/frontend/src/Movie/Index/MovieIndexFooter.js b/frontend/src/Movie/Index/MovieIndexFooter.js index fbc09d34b..ad746d09a 100644 --- a/frontend/src/Movie/Index/MovieIndexFooter.js +++ b/frontend/src/Movie/Index/MovieIndexFooter.js @@ -26,12 +26,6 @@ class MovieIndexFooter extends PureComponent { movieFiles += 1; } - // if (s.status === 'ended') { - // ended++; - // } else { - // continuing++; - // } - if (s.monitored) { monitored++; } @@ -78,6 +72,13 @@ class MovieIndexFooter extends PureComponent { +
+
+
+ {translate('Queued')} +
+
+
diff --git a/frontend/src/Movie/Index/MovieIndexItemConnector.js b/frontend/src/Movie/Index/MovieIndexItemConnector.js index 6a898cce0..5a0ac4714 100644 --- a/frontend/src/Movie/Index/MovieIndexItemConnector.js +++ b/frontend/src/Movie/Index/MovieIndexItemConnector.js @@ -32,11 +32,13 @@ function createMapStateToProps() { createMovieQualityProfileSelector(), selectShowSearchAction(), createExecutingCommandsSelector(), + (state) => state.queue.details.items, ( movie, qualityProfile, showSearchAction, - executingCommands + executingCommands, + queueItems ) => { // If a movie is deleted this selector may fire before the parent @@ -62,12 +64,25 @@ function createMapStateToProps() { ); }); + let queueStatus = null; + let queueState = null; + + for (const q in queueItems) { + if (queueItems[q].movieId === movie.id) { + queueStatus = queueItems[q].status; + queueState = queueItems[q].trackedDownloadState; + break; + } + } + return { ...movie, qualityProfile, showSearchAction, isRefreshingMovie, - isSearchingMovie + isSearchingMovie, + queueStatus, + queueState }; } ); diff --git a/frontend/src/Movie/Index/Overview/MovieIndexOverview.css b/frontend/src/Movie/Index/Overview/MovieIndexOverview.css index f1e14053f..4e59d1739 100644 --- a/frontend/src/Movie/Index/Overview/MovieIndexOverview.css +++ b/frontend/src/Movie/Index/Overview/MovieIndexOverview.css @@ -97,3 +97,8 @@ $hoverScale: 1.05; .externalLinks { margin-right: 0.5em; } + +.queue { + padding-left: 2px; + border-left: 4px solid $queueColor; +} diff --git a/frontend/src/Movie/Index/Overview/MovieIndexOverview.js b/frontend/src/Movie/Index/Overview/MovieIndexOverview.js index 83bf1257d..3db6f616a 100644 --- a/frontend/src/Movie/Index/Overview/MovieIndexOverview.js +++ b/frontend/src/Movie/Index/Overview/MovieIndexOverview.js @@ -113,6 +113,8 @@ class MovieIndexOverview extends Component { isMovieEditorActive, isSelected, onSelectedChange, + queueStatus, + queueState, ...otherProps } = this.props; @@ -170,6 +172,8 @@ class MovieIndexOverview extends Component { status={status} posterWidth={posterWidth} detailedProgressBar={overviewOptions.detailedProgressBar} + queueStatus={queueStatus} + queueState={queueState} />
@@ -300,7 +304,9 @@ MovieIndexOverview.propTypes = { onSelectedChange: PropTypes.func.isRequired, tmdbId: PropTypes.number.isRequired, imdbId: PropTypes.string, - youTubeTrailerId: PropTypes.string + youTubeTrailerId: PropTypes.string, + queueStatus: PropTypes.string, + queueState: PropTypes.string }; export default MovieIndexOverview; diff --git a/frontend/src/Movie/Index/Posters/MovieIndexPoster.js b/frontend/src/Movie/Index/Posters/MovieIndexPoster.js index d4dfbf6dc..f6192de2f 100644 --- a/frontend/src/Movie/Index/Posters/MovieIndexPoster.js +++ b/frontend/src/Movie/Index/Posters/MovieIndexPoster.js @@ -108,6 +108,8 @@ class MovieIndexPoster extends Component { isMovieEditorActive, isSelected, onSelectedChange, + queueStatus, + queueState, ...otherProps } = this.props; @@ -224,6 +226,8 @@ class MovieIndexPoster extends Component { status={status} posterWidth={posterWidth} detailedProgressBar={detailedProgressBar} + queueStatus={queueStatus} + queueState={queueState} /> { @@ -301,7 +305,9 @@ MovieIndexPoster.propTypes = { onSelectedChange: PropTypes.func.isRequired, tmdbId: PropTypes.number.isRequired, imdbId: PropTypes.string, - youTubeTrailerId: PropTypes.string + youTubeTrailerId: PropTypes.string, + queueStatus: PropTypes.string, + queueState: PropTypes.string }; MovieIndexPoster.defaultProps = { diff --git a/frontend/src/Movie/Index/ProgressBar/MovieIndexProgressBar.js b/frontend/src/Movie/Index/ProgressBar/MovieIndexProgressBar.js index 0b6ccadb8..d201bb4ad 100644 --- a/frontend/src/Movie/Index/ProgressBar/MovieIndexProgressBar.js +++ b/frontend/src/Movie/Index/ProgressBar/MovieIndexProgressBar.js @@ -3,6 +3,9 @@ import React from 'react'; import ProgressBar from 'Components/ProgressBar'; import { sizes } from 'Helpers/Props'; import getProgressBarKind from 'Utilities/Movie/getProgressBarKind'; +import getQueueStatusText from 'Utilities/Movie/getQueueStatusText'; +import titleCase from 'Utilities/String/titleCase'; +import translate from 'Utilities/String/translate'; import styles from './MovieIndexProgressBar.css'; function MovieIndexProgressBar(props) { @@ -11,20 +14,43 @@ function MovieIndexProgressBar(props) { status, hasFile, posterWidth, - detailedProgressBar + detailedProgressBar, + queueStatus, + queueState } = props; const progress = 100; + const queueStatusText = getQueueStatusText(queueStatus, queueState); + let movieStatus = (status === 'released' && hasFile) ? 'downloaded' : status; + + if (movieStatus === 'deleted') { + movieStatus = 'announced'; + + if (hasFile) { + movieStatus = 'downloaded'; + } else { + movieStatus = 'released'; + } + } + + if (movieStatus === 'announced') { + movieStatus = translate('NotAvailable'); + } + + if (movieStatus === 'released') { + movieStatus = translate('Missing'); + } return ( ); } @@ -34,7 +60,9 @@ MovieIndexProgressBar.propTypes = { hasFile: PropTypes.bool.isRequired, status: PropTypes.string.isRequired, posterWidth: PropTypes.number.isRequired, - detailedProgressBar: PropTypes.bool.isRequired + detailedProgressBar: PropTypes.bool.isRequired, + queueStatus: PropTypes.string, + queueState: PropTypes.string }; export default MovieIndexProgressBar; diff --git a/frontend/src/Movie/Index/Table/MovieIndexRow.js b/frontend/src/Movie/Index/Table/MovieIndexRow.js index b63ad5f4f..ef1c57efb 100644 --- a/frontend/src/Movie/Index/Table/MovieIndexRow.js +++ b/frontend/src/Movie/Index/Table/MovieIndexRow.js @@ -98,6 +98,8 @@ class MovieIndexRow extends Component { onRefreshMoviePress, onSearchPress, onSelectedChange, + queueStatus, + queueState, movieRuntimeFormat } = this.props; @@ -315,6 +317,8 @@ class MovieIndexRow extends Component { > ); @@ -464,6 +468,8 @@ MovieIndexRow.propTypes = { tmdbId: PropTypes.number.isRequired, imdbId: PropTypes.string, youTubeTrailerId: PropTypes.string, + queueStatus: PropTypes.string, + queueState: PropTypes.string, movieRuntimeFormat: PropTypes.string.isRequired }; diff --git a/frontend/src/Movie/MovieFileStatus.js b/frontend/src/Movie/MovieFileStatus.js index aba5a265e..31b915829 100644 --- a/frontend/src/Movie/MovieFileStatus.js +++ b/frontend/src/Movie/MovieFileStatus.js @@ -1,11 +1,9 @@ import PropTypes from 'prop-types'; import React from 'react'; -import QueueDetails from 'Activity/Queue/QueueDetails'; -import Icon from 'Components/Icon'; import Label from 'Components/Label'; -import ProgressBar from 'Components/ProgressBar'; -import { icons, kinds, sizes } from 'Helpers/Props'; +import { kinds } from 'Helpers/Props'; import MovieQuality from 'Movie/MovieQuality'; +import getQueueStatusText from 'Utilities/Movie/getQueueStatusText'; import translate from 'Utilities/String/translate'; import styles from './MovieFileStatus.css'; @@ -13,47 +11,25 @@ function MovieFileStatus(props) { const { isAvailable, monitored, - grabbed, - queueItem, - movieFile + movieFile, + queueStatus, + queueState } = props; const hasMovieFile = !!movieFile; - const isQueued = !!queueItem; const hasReleased = isAvailable; - if (isQueued) { - const { - sizeleft, - size - } = queueItem; - - const progress = (100 - sizeleft / size * 100); + if (queueStatus) { + const queueStatusText = getQueueStatusText(queueStatus, queueState); return (
- - } - /> -
- ); - } - - if (grabbed) { - return ( -
- +
); } @@ -115,9 +91,9 @@ function MovieFileStatus(props) { MovieFileStatus.propTypes = { isAvailable: PropTypes.bool, monitored: PropTypes.bool.isRequired, - grabbed: PropTypes.bool, - queueItem: PropTypes.object, - movieFile: PropTypes.object + movieFile: PropTypes.object, + queueStatus: PropTypes.string, + queueState: PropTypes.string }; export default MovieFileStatus; diff --git a/frontend/src/Movie/MovieFileStatusConnector.js b/frontend/src/Movie/MovieFileStatusConnector.js index 4812f94b4..3afaf555f 100644 --- a/frontend/src/Movie/MovieFileStatusConnector.js +++ b/frontend/src/Movie/MovieFileStatusConnector.js @@ -4,14 +4,12 @@ import React, { Component } from 'react'; import { connect } from 'react-redux'; import { createSelector } from 'reselect'; import createMovieSelector from 'Store/Selectors/createMovieSelector'; -import createQueueItemSelector from 'Store/Selectors/createQueueItemSelector'; import MovieFileStatus from './MovieFileStatus'; function createMapStateToProps() { return createSelector( createMovieSelector(), - createQueueItemSelector(), - (movie, queueItem) => { + (movie) => { const result = _.pick(movie, [ 'inCinemas', 'isAvailable', @@ -19,7 +17,6 @@ function createMapStateToProps() { 'grabbed' ]); - result.queueItem = queueItem; result.movieFile = movie.movieFile; return result; @@ -45,7 +42,9 @@ class MovieFileStatusConnector extends Component { } MovieFileStatusConnector.propTypes = { - movieId: PropTypes.number.isRequired + movieId: PropTypes.number.isRequired, + queueStatus: PropTypes.string, + queueState: PropTypes.string }; export default connect(createMapStateToProps, mapDispatchToProps)(MovieFileStatusConnector); diff --git a/frontend/src/Store/Actions/queueActions.js b/frontend/src/Store/Actions/queueActions.js index bac6fb358..56944426f 100644 --- a/frontend/src/Store/Actions/queueActions.js +++ b/frontend/src/Store/Actions/queueActions.js @@ -231,12 +231,7 @@ export const actionHandlers = handleThunks({ params = getState().queue.details.params; } - // Ensure there are params before trying to fetch the queue - // so we don't make a bad request to the server. - - if (params && !_.isEmpty(params)) { - fetchQueueDetailsHelper(getState, params, dispatch); - } + fetchQueueDetailsHelper(getState, params, dispatch); }, ...createServerSideCollectionHandlers( diff --git a/frontend/src/Styles/Variables/colors.js b/frontend/src/Styles/Variables/colors.js index 764922982..24e83f557 100644 --- a/frontend/src/Styles/Variables/colors.js +++ b/frontend/src/Styles/Variables/colors.js @@ -14,6 +14,7 @@ module.exports = { dangerColor: '#f05050', warningColor: '#ffa500', infoColor: '#5d9cec', + queueColor: '#7a43b6', purple: '#7a43b6', pink: '#ff69b4', radarrYellow, diff --git a/frontend/src/Utilities/Movie/getProgressBarKind.js b/frontend/src/Utilities/Movie/getProgressBarKind.js index cc270f5e8..69e64950b 100644 --- a/frontend/src/Utilities/Movie/getProgressBarKind.js +++ b/frontend/src/Utilities/Movie/getProgressBarKind.js @@ -1,6 +1,10 @@ import { kinds } from 'Helpers/Props'; -function getProgressBarKind(status, monitored, hasFile) { +function getProgressBarKind(status, monitored, hasFile, queue = false) { + if (queue) { + return kinds.QUEUE; + } + if (status === 'announced') { return kinds.PRIMARY; } diff --git a/frontend/src/Utilities/Movie/getQueueStatusText.js b/frontend/src/Utilities/Movie/getQueueStatusText.js new file mode 100644 index 000000000..768515d4a --- /dev/null +++ b/frontend/src/Utilities/Movie/getQueueStatusText.js @@ -0,0 +1,61 @@ +import titleCase from 'Utilities/String/titleCase'; +import translate from 'Utilities/String/translate'; + +export default function getQueueStatusText(queueStatus, queueState) { + if (!queueStatus) { + return; + } + + let statusLong = translate('Downloading'); + let statusShort = translate('Downloading'); + + switch (true) { + case queueStatus !== 'completed': + switch (queueStatus) { + case 'queue': + case 'paused': + case 'failed': + statusLong = `${translate('Downloading')}: ${translate(titleCase(queueStatus))}`; + statusShort = titleCase(queueStatus); + break; + case 'delay': + statusLong = `${translate('Downloading')}: ${translate('Pending')}`; + statusShort = translate('Pending'); + break; + case 'DownloadClientUnavailable': + case 'warning': + statusLong = `${translate('Downloading')}: ${translate('Error')}`; + statusShort = translate('Error'); + break; + case 'downloading': + statusLong = titleCase(queueStatus); + statusShort = titleCase(queueStatus); + break; + default: + } + break; + + case queueStatus === 'completed': + switch (queueState) { + case 'importPending': + statusLong = `${translate('Downloaded')}: ${translate('Pending')}`; + statusShort = translate('Downloaded'); + break; + case 'importing': + statusLong = `${translate('Downloaded')}: ${translate('Importing')}`; + statusShort = translate('Downloaded'); + break; + case 'failedPending': + statusLong = `${translate('Downloaded')}: ${translate('Waiting')}`; + statusShort = translate('Downloaded'); + break; + default: + } + break; + + default: + } + + const result = { longText: statusLong, shortText: statusShort }; + return result; +} diff --git a/src/NzbDrone.Core/Localization/Core/en.json b/src/NzbDrone.Core/Localization/Core/en.json index dbe579e6e..9474f4da8 100644 --- a/src/NzbDrone.Core/Localization/Core/en.json +++ b/src/NzbDrone.Core/Localization/Core/en.json @@ -762,6 +762,7 @@ "UnableToLoadUISettings": "Unable to load UI settings", "Unavailable": "Unavailable", "Ungroup": "Ungroup", + "Unreleased": "Unreleased", "UnmappedFolders": "Unmapped Folders", "Unmonitored": "Unmonitored", "UnmonitoredHelpText": "Include unmonitored movies in the iCal feed",