From 8ab0b2677399ae2146043e378a2e42167527fee2 Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Thu, 19 Feb 2015 22:14:04 -0800 Subject: [PATCH] Episode file editor New: Ability to delete all episode files in a series or season New: Ability to change quality for all episode files in a series or season --- src/UI/Cells/EpisodeFilePathCell.js | 19 ++ src/UI/Content/icons.less | 4 + src/UI/EpisodeFile/Editor/EmptyView.js | 5 + .../EpisodeFile/Editor/EmptyViewTemplate.hbs | 5 + .../Editor/EpisodeFileEditorLayout.js | 185 ++++++++++++++++++ .../EpisodeFileEditorLayoutTemplate.hbs | 28 +++ .../EpisodeFile/Editor/QualitySelectView.js | 35 ++++ .../Editor/QualitySelectViewTemplate.hbs | 10 + src/UI/Series/Details/SeasonLayout.js | 28 ++- .../Series/Details/SeasonLayoutTemplate.hbs | 3 + src/UI/Series/Details/SeriesDetailsLayout.js | 21 +- .../Series/Details/SeriesDetailsTemplate.hbs | 3 + src/UI/Series/series.less | 2 +- 13 files changed, 334 insertions(+), 14 deletions(-) create mode 100644 src/UI/Cells/EpisodeFilePathCell.js create mode 100644 src/UI/EpisodeFile/Editor/EmptyView.js create mode 100644 src/UI/EpisodeFile/Editor/EmptyViewTemplate.hbs create mode 100644 src/UI/EpisodeFile/Editor/EpisodeFileEditorLayout.js create mode 100644 src/UI/EpisodeFile/Editor/EpisodeFileEditorLayoutTemplate.hbs create mode 100644 src/UI/EpisodeFile/Editor/QualitySelectView.js create mode 100644 src/UI/EpisodeFile/Editor/QualitySelectViewTemplate.hbs diff --git a/src/UI/Cells/EpisodeFilePathCell.js b/src/UI/Cells/EpisodeFilePathCell.js new file mode 100644 index 000000000..5f3916ead --- /dev/null +++ b/src/UI/Cells/EpisodeFilePathCell.js @@ -0,0 +1,19 @@ +var reqres = require('../reqres'); +var NzbDroneCell = require('./NzbDroneCell'); + +module.exports = NzbDroneCell.extend({ + className : 'episode-file-path-cell', + + render : function() { + this.$el.empty(); + + if (reqres.hasHandler(reqres.Requests.GetEpisodeFileById)) { + var episodeFile = reqres.request(reqres.Requests.GetEpisodeFileById, this.model.get('episodeFileId')); + + this.$el.html(episodeFile.get('relativePath')); + } + + this.delegateEvents(); + return this; + } +}); \ No newline at end of file diff --git a/src/UI/Content/icons.less b/src/UI/Content/icons.less index 9514ad9b3..ef2cb3f6e 100644 --- a/src/UI/Content/icons.less +++ b/src/UI/Content/icons.less @@ -467,3 +467,7 @@ .icon-sonarr-backup-update { .fa-icon-content(@fa-var-retweet); } + +.icon-sonarr-episode-file { + .fa-icon-content(@fa-var-file-video-o); +} diff --git a/src/UI/EpisodeFile/Editor/EmptyView.js b/src/UI/EpisodeFile/Editor/EmptyView.js new file mode 100644 index 000000000..e84453524 --- /dev/null +++ b/src/UI/EpisodeFile/Editor/EmptyView.js @@ -0,0 +1,5 @@ +var Marionette = require('marionette'); + +module.exports = Marionette.CompositeView.extend({ + template : 'EpisodeFile/Editor/EmptyViewTemplate' +}); \ No newline at end of file diff --git a/src/UI/EpisodeFile/Editor/EmptyViewTemplate.hbs b/src/UI/EpisodeFile/Editor/EmptyViewTemplate.hbs new file mode 100644 index 000000000..0a51692de --- /dev/null +++ b/src/UI/EpisodeFile/Editor/EmptyViewTemplate.hbs @@ -0,0 +1,5 @@ +
+
+ No episode files +
+
diff --git a/src/UI/EpisodeFile/Editor/EpisodeFileEditorLayout.js b/src/UI/EpisodeFile/Editor/EpisodeFileEditorLayout.js new file mode 100644 index 000000000..d3f5e99e2 --- /dev/null +++ b/src/UI/EpisodeFile/Editor/EpisodeFileEditorLayout.js @@ -0,0 +1,185 @@ +var _ = require('underscore'); +var reqres = require('../../reqres'); +var Marionette = require('marionette'); +var Backgrid = require('backgrid'); +var SelectAllCell = require('../../Cells/SelectAllCell'); +var EpisodeNumberCell = require('../../Series/Details/EpisodeNumberCell'); +var SeasonEpisodeNumberCell = require('../../Cells/EpisodeNumberCell'); +var EpisodeFilePathCell = require('../../Cells/EpisodeFilePathCell'); +var EpisodeStatusCell = require('../../Cells/EpisodeStatusCell'); +var RelativeDateCell = require('../../Cells/RelativeDateCell'); +var EpisodeCollection = require('../../Series/EpisodeCollection'); +var ProfileSchemaCollection = require('../../Settings/Profile/ProfileSchemaCollection'); +var QualitySelectView = require('./QualitySelectView'); +var EmptyView = require('./EmptyView'); + +module.exports = Marionette.Layout.extend({ + className : 'modal-lg', + template : 'EpisodeFile/Editor/EpisodeFileEditorLayoutTemplate', + + regions : { + episodeGrid : '.x-episode-list', + quality : '.x-quality' + }, + + ui : { + seasonMonitored : '.x-season-monitored' + }, + + events : { + 'click .x-season-monitored' : '_seasonMonitored', + 'click .x-delete-files' : '_deleteFiles' + }, + + initialize : function(options) { + if (!options.series) { + throw 'series is required'; + } + + if (!options.episodeCollection) { + throw 'episodeCollection is required'; + } + + var filtered = options.episodeCollection.filter(function(episode) { + return episode.get('episodeFileId') > 0; + }); + + this.series = options.series; + this.episodeCollection = options.episodeCollection; + this.filteredEpisodes = new EpisodeCollection(filtered); + + this.templateHelpers = {}; + this.templateHelpers.series = this.series.toJSON(); + + this._getColumns(); + }, + + onRender : function() { + this._getQualities(); + this._showEpisodes(); + }, + + _getColumns : function () { + var episodeCell = {}; + + if (this.model) { + episodeCell.name = 'episodeNumber'; + episodeCell.label = '#'; + episodeCell.cell = EpisodeNumberCell; + } + + else { + episodeCell.name = 'this'; + episodeCell.label = 'Episode'; + episodeCell.cell = SeasonEpisodeNumberCell; + } + + this.columns = [ + { + name : '', + cell : SelectAllCell, + headerCell : 'select-all', + sortable : false + }, + episodeCell, + { + name : 'episodeNumber', + label : 'Relative Path', + cell : EpisodeFilePathCell + }, + { + name : 'airDateUtc', + label : 'Air Date', + cell : RelativeDateCell + }, + { + name : 'status', + label : 'Quality', + cell : EpisodeStatusCell, + sortable : false + } + ]; + + if (!this.model) { + this.columns[1].name = 'this'; + this.columns[1].cell = SeasonEpisodeNumberCell; + } + }, + + _showEpisodes : function() { + if (this.filteredEpisodes.length === 0) { + this.episodeGrid.show(new EmptyView()); + return; + } + + this.episodeGridView = new Backgrid.Grid({ + columns : this.columns, + collection : this.filteredEpisodes, + className : 'table table-hover season-grid' + }); + + this.episodeGrid.show(this.episodeGridView); + }, + + _getQualities : function() { + var self = this; + + var profileSchemaCollection = new ProfileSchemaCollection(); + var promise = profileSchemaCollection.fetch(); + + promise.done(function() { + var profile = profileSchemaCollection.first(); + + self.qualitySelectView = new QualitySelectView({ qualities: _.map(profile.get('items'), 'quality') }); + self.listenTo(self.qualitySelectView, 'seasonedit:quality', self._changeQuality); + + self.quality.show(self.qualitySelectView); + }); + }, + + _changeQuality : function(options) { + var newQuality = { + quality : options.selected, + revision : { + version : 1, + real : 0 + } + }; + + var selected = this._getSelectedEpisodeFileIds(); + + _.each(selected, function(episodeFileId) { + if (reqres.hasHandler(reqres.Requests.GetEpisodeFileById)) { + var episodeFile = reqres.request(reqres.Requests.GetEpisodeFileById, episodeFileId); + episodeFile.set('quality', newQuality); + episodeFile.save(); + } + }); + }, + + _deleteFiles : function() { + if (!window.confirm('Are you sure you want to delete the episode files for the selected episodes?')) { + return; + } + + var selected = this._getSelectedEpisodeFileIds(); + + _.each(selected, function(episodeFileId) { + if (reqres.hasHandler(reqres.Requests.GetEpisodeFileById)) { + var episodeFile = reqres.request(reqres.Requests.GetEpisodeFileById, episodeFileId); + + episodeFile.destroy(); + } + }); + + _.each(this.episodeGridView.getSelectedModels(), function(episode) { + this.episodeGridView.removeRow(episode); + }, this); + }, + + _getSelectedEpisodeFileIds: function () { + return _.uniq(_.map(this.episodeGridView.getSelectedModels(), function (episode) { + return episode.get('episodeFileId'); + })); + } +}); \ No newline at end of file diff --git a/src/UI/EpisodeFile/Editor/EpisodeFileEditorLayoutTemplate.hbs b/src/UI/EpisodeFile/Editor/EpisodeFileEditorLayoutTemplate.hbs new file mode 100644 index 000000000..aee8fbd26 --- /dev/null +++ b/src/UI/EpisodeFile/Editor/EpisodeFileEditorLayoutTemplate.hbs @@ -0,0 +1,28 @@ + diff --git a/src/UI/EpisodeFile/Editor/QualitySelectView.js b/src/UI/EpisodeFile/Editor/QualitySelectView.js new file mode 100644 index 000000000..beac4f304 --- /dev/null +++ b/src/UI/EpisodeFile/Editor/QualitySelectView.js @@ -0,0 +1,35 @@ +var _ = require('underscore'); +var Marionette = require('marionette'); + +module.exports = Marionette.ItemView.extend({ + template : 'EpisodeFile/Editor/QualitySelectViewTemplate', + + ui : { + select : '.x-select' + }, + + events : { + 'change .x-select' : '_changeSelect' + }, + + initialize : function (options) { + this.qualities = options.qualities; + + this.templateHelpers = { + qualities : this.qualities + }; + }, + + _changeSelect : function () { + var value = this.ui.select.val(); + + if (value === 'choose') { + return; + } + + var quality = _.find(this.qualities, { 'id': parseInt(value) }); + + this.trigger('seasonedit:quality', { selected : quality }); + this.ui.select.val('choose'); + } +}); \ No newline at end of file diff --git a/src/UI/EpisodeFile/Editor/QualitySelectViewTemplate.hbs b/src/UI/EpisodeFile/Editor/QualitySelectViewTemplate.hbs new file mode 100644 index 000000000..4108191e2 --- /dev/null +++ b/src/UI/EpisodeFile/Editor/QualitySelectViewTemplate.hbs @@ -0,0 +1,10 @@ +
+
+ +
+
diff --git a/src/UI/Series/Details/SeasonLayout.js b/src/UI/Series/Details/SeasonLayout.js index fd83636d5..eecbf460e 100644 --- a/src/UI/Series/Details/SeasonLayout.js +++ b/src/UI/Series/Details/SeasonLayout.js @@ -9,6 +9,7 @@ var EpisodeActionsCell = require('../../Cells/EpisodeActionsCell'); var EpisodeNumberCell = require('./EpisodeNumberCell'); var EpisodeWarningCell = require('./EpisodeWarningCell'); var CommandController = require('../../Commands/CommandController'); +var EpisodeFileEditorLayout = require('../../EpisodeFile/Editor/EpisodeFileEditorLayout'); var moment = require('moment'); var _ = require('underscore'); var Messenger = require('../../Shared/Messenger'); @@ -23,11 +24,12 @@ module.exports = Marionette.Layout.extend({ }, events : { - 'click .x-season-monitored' : '_seasonMonitored', - 'click .x-season-search' : '_seasonSearch', - 'click .x-season-rename' : '_seasonRename', - 'click .x-show-hide-episodes' : '_showHideEpisodes', - 'dblclick .series-season h2' : '_showHideEpisodes' + 'click .x-season-episode-file-editor' : '_openEpisodeFileEditor', + 'click .x-season-monitored' : '_seasonMonitored', + 'click .x-season-search' : '_seasonSearch', + 'click .x-season-rename' : '_seasonRename', + 'click .x-show-hide-episodes' : '_showHideEpisodes', + 'dblclick .series-season h2' : '_showHideEpisodes' }, regions : { @@ -104,7 +106,7 @@ module.exports = Marionette.Layout.extend({ initialize : function(options) { if (!options.episodeCollection) { - throw 'episodeCollection is needed'; + throw 'episodeCollection is required'; } this.series = options.series; @@ -117,7 +119,7 @@ module.exports = Marionette.Layout.extend({ this.listenTo(this.model, 'sync', this._afterSeasonMonitored); this.listenTo(this.episodeCollection, 'sync', this.render); - this.listenTo(this.fullEpisodeCollection, 'sync', this._refreshEpsiodes); + this.listenTo(this.fullEpisodeCollection, 'sync', this._refreshEpisodes); }, onRender : function() { @@ -281,8 +283,18 @@ module.exports = Marionette.Layout.extend({ }); }, - _refreshEpsiodes : function() { + _refreshEpisodes : function() { this._updateEpisodeCollection(); this.render(); + }, + + _openEpisodeFileEditor : function() { + var view = new EpisodeFileEditorLayout({ + model : this.model, + series : this.series, + episodeCollection : this.episodeCollection + }); + + vent.trigger(vent.Commands.OpenModalCommand, view); } }); \ No newline at end of file diff --git a/src/UI/Series/Details/SeasonLayoutTemplate.hbs b/src/UI/Series/Details/SeasonLayoutTemplate.hbs index a71053652..b987ca8be 100644 --- a/src/UI/Series/Details/SeasonLayoutTemplate.hbs +++ b/src/UI/Series/Details/SeasonLayoutTemplate.hbs @@ -24,6 +24,9 @@ {{/if_eq}} +
+ +
diff --git a/src/UI/Series/Details/SeriesDetailsLayout.js b/src/UI/Series/Details/SeriesDetailsLayout.js index 85328b9ef..980b875da 100644 --- a/src/UI/Series/Details/SeriesDetailsLayout.js +++ b/src/UI/Series/Details/SeriesDetailsLayout.js @@ -12,6 +12,7 @@ var SeasonCollectionView = require('./SeasonCollectionView'); var InfoView = require('./InfoView'); var CommandController = require('../../Commands/CommandController'); var LoadingView = require('../../Shared/LoadingView'); +var EpisodeFileEditorLayout = require('../../EpisodeFile/Editor/EpisodeFileEditorLayout'); require('backstrech'); require('../../Mixins/backbone.signalr.mixin'); @@ -34,11 +35,12 @@ module.exports = Marionette.Layout.extend({ }, events : { - 'click .x-monitored' : '_toggleMonitored', - 'click .x-edit' : '_editSeries', - 'click .x-refresh' : '_refreshSeries', - 'click .x-rename' : '_renameSeries', - 'click .x-search' : '_seriesSearch' + 'click .x-episode-file-editor' : '_openEpisodeFileEditor', + 'click .x-monitored' : '_toggleMonitored', + 'click .x-edit' : '_editSeries', + 'click .x-refresh' : '_refreshSeries', + 'click .x-rename' : '_renameSeries', + 'click .x-search' : '_seriesSearch' }, initialize : function() { @@ -219,5 +221,14 @@ module.exports = Marionette.Layout.extend({ this._setMonitoredState(); this._showInfo(); + }, + + _openEpisodeFileEditor : function() { + var view = new EpisodeFileEditorLayout({ + series : this.model, + episodeCollection : this.episodeCollection + }); + + vent.trigger(vent.Commands.OpenModalCommand, view); } }); \ No newline at end of file diff --git a/src/UI/Series/Details/SeriesDetailsTemplate.hbs b/src/UI/Series/Details/SeriesDetailsTemplate.hbs index 4c31b371a..97421bbb4 100644 --- a/src/UI/Series/Details/SeriesDetailsTemplate.hbs +++ b/src/UI/Series/Details/SeriesDetailsTemplate.hbs @@ -8,6 +8,9 @@ {{title}}
+
+ +
diff --git a/src/UI/Series/series.less b/src/UI/Series/series.less index fe0c01744..26870ba20 100644 --- a/src/UI/Series/series.less +++ b/src/UI/Series/series.less @@ -312,7 +312,7 @@ } .season-actions { - width: 70px; + width: 100px; } .season-actions, .series-actions {