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 @@
+
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 {