Merge pull request #546 from Sonarr/season-pass
Season pass supports multi-select and new toggling optionspull/4/head
commit
27980b2cd6
@ -0,0 +1,128 @@
|
||||
var _ = require('underscore');
|
||||
var $ = require('jquery');
|
||||
var Marionette = require('marionette');
|
||||
var vent = require('vent');
|
||||
var RootFolders = require('../AddSeries/RootFolders/RootFolderCollection');
|
||||
|
||||
module.exports = Marionette.ItemView.extend({
|
||||
template : 'SeasonPass/SeasonPassFooterViewTemplate',
|
||||
|
||||
ui : {
|
||||
monitor : '.x-monitor',
|
||||
selectedCount : '.x-selected-count',
|
||||
container : '.series-editor-footer',
|
||||
actions : '.x-action',
|
||||
indicator : '.x-indicator',
|
||||
indicatorIcon : '.x-indicator-icon'
|
||||
},
|
||||
|
||||
events : {
|
||||
'click .x-update' : '_update'
|
||||
},
|
||||
|
||||
initialize : function(options) {
|
||||
this.seriesCollection = options.collection;
|
||||
|
||||
RootFolders.fetch().done(function() {
|
||||
RootFolders.synced = true;
|
||||
});
|
||||
|
||||
this.editorGrid = options.editorGrid;
|
||||
this.listenTo(this.seriesCollection, 'backgrid:selected', this._updateInfo);
|
||||
},
|
||||
|
||||
onRender : function() {
|
||||
this._updateInfo();
|
||||
},
|
||||
|
||||
_update : function() {
|
||||
var self = this;
|
||||
var selected = this.editorGrid.getSelectedModels();
|
||||
var monitoringOptions;
|
||||
|
||||
_.each(selected, function(model) {
|
||||
monitoringOptions = self._getMonitoringOptions(model);
|
||||
|
||||
model.set('addOptions', monitoringOptions);
|
||||
});
|
||||
|
||||
var promise = $.ajax({
|
||||
url : window.NzbDrone.ApiRoot + '/seasonpass',
|
||||
type : 'POST',
|
||||
data : JSON.stringify({
|
||||
series : _.map(selected, function (model) {
|
||||
return model.toJSON();
|
||||
}),
|
||||
monitoringOptions : monitoringOptions
|
||||
})
|
||||
});
|
||||
|
||||
this.ui.indicator.show();
|
||||
|
||||
promise.always(function () {
|
||||
self.ui.indicator.hide();
|
||||
});
|
||||
|
||||
promise.done(function () {
|
||||
self.seriesCollection.trigger('seasonpass:saved');
|
||||
});
|
||||
},
|
||||
|
||||
_updateInfo : function() {
|
||||
var selected = this.editorGrid.getSelectedModels();
|
||||
var selectedCount = selected.length;
|
||||
|
||||
this.ui.selectedCount.html('{0} series selected'.format(selectedCount));
|
||||
|
||||
if (selectedCount === 0) {
|
||||
this.ui.actions.attr('disabled', 'disabled');
|
||||
} else {
|
||||
this.ui.actions.removeAttr('disabled');
|
||||
}
|
||||
},
|
||||
|
||||
_getMonitoringOptions : function(model) {
|
||||
var monitor = this.ui.monitor.val();
|
||||
var lastSeason = _.max(model.get('seasons'), 'seasonNumber');
|
||||
var firstSeason = _.min(_.reject(model.get('seasons'), { seasonNumber : 0 }), 'seasonNumber');
|
||||
|
||||
model.setSeasonPass(firstSeason.seasonNumber);
|
||||
|
||||
var options = {
|
||||
ignoreEpisodesWithFiles : false,
|
||||
ignoreEpisodesWithoutFiles : false
|
||||
};
|
||||
|
||||
if (monitor === 'all') {
|
||||
return options;
|
||||
}
|
||||
|
||||
else if (monitor === 'future') {
|
||||
options.ignoreEpisodesWithFiles = true;
|
||||
options.ignoreEpisodesWithoutFiles = true;
|
||||
}
|
||||
|
||||
else if (monitor === 'latest') {
|
||||
model.setSeasonPass(lastSeason.seasonNumber);
|
||||
}
|
||||
|
||||
else if (monitor === 'first') {
|
||||
model.setSeasonPass(lastSeason.seasonNumber + 1);
|
||||
model.setSeasonMonitored(firstSeason.seasonNumber);
|
||||
}
|
||||
|
||||
else if (monitor === 'missing') {
|
||||
options.ignoreEpisodesWithFiles = true;
|
||||
}
|
||||
|
||||
else if (monitor === 'existing') {
|
||||
options.ignoreEpisodesWithoutFiles = true;
|
||||
}
|
||||
|
||||
else if (monitor === 'none') {
|
||||
model.setSeasonPass(lastSeason.seasonNumber + 1);
|
||||
}
|
||||
|
||||
return options;
|
||||
}
|
||||
});
|
@ -0,0 +1,25 @@
|
||||
<div class="series-editor-footer">
|
||||
<div class="row">
|
||||
<div class="form-group col-md-2">
|
||||
<label>Monitor</label>
|
||||
|
||||
<select class="form-control x-action x-monitor">
|
||||
<option value="all">All</option>
|
||||
<option value="future">Future</option>
|
||||
<option value="missing">Missing</option>
|
||||
<option value="existing">Existing</option>
|
||||
<option value="first">First Season</option>
|
||||
<option value="latest">Latest Season</option>
|
||||
<option value="none">None</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group col-md-3 actions">
|
||||
<label class="x-selected-count">0 series selected</label>
|
||||
<div>
|
||||
<button class="btn btn-primary x-action x-update">Update Selected Series</button>
|
||||
<span class="indicator x-indicator"><i class="icon-sonarr-spinner fa-spin"></i></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,26 @@
|
||||
var _ = require('underscore');
|
||||
var TemplatedCell = require('../Cells/TemplatedCell');
|
||||
//require('../Handlebars/Helpers/Numbers');
|
||||
|
||||
module.exports = TemplatedCell.extend({
|
||||
className : 'seasons-cell',
|
||||
template : 'SeasonPass/SeasonsCellTemplate',
|
||||
|
||||
events : {
|
||||
'click .x-season-monitored' : '_toggleSeasonMonitored'
|
||||
},
|
||||
|
||||
_toggleSeasonMonitored : function(e) {
|
||||
var target = this.$(e.target).closest('.x-season-monitored');
|
||||
var seasonNumber = parseInt(this.$(target).data('season-number'), 10);
|
||||
var icon = this.$(target).children('.x-season-monitored-icon');
|
||||
|
||||
this.model.setSeasonMonitored(seasonNumber);
|
||||
|
||||
//TODO: unbounce the save so we don't multiple to the server at the same time
|
||||
var savePromise = this.model.save();
|
||||
|
||||
icon.spinForPromise(savePromise);
|
||||
savePromise.always(this.render.bind(this));
|
||||
}
|
||||
});
|
@ -0,0 +1,29 @@
|
||||
{{#each seasons}}
|
||||
{{debug}}
|
||||
<span class="season">
|
||||
<span class="label">
|
||||
<span class="x-season-monitored season-monitored" title="Toggle season monitored status" data-season-number="{{seasonNumber}}">
|
||||
<i class="x-season-monitored-icon {{#if monitored}}icon-sonarr-monitored{{else}}icon-sonarr-unmonitored{{/if}}"/>
|
||||
</span>
|
||||
{{#if_eq seasonNumber compare="0"}}
|
||||
<span class="season-number">Specials</span>
|
||||
{{else}}
|
||||
<span class="season-number">S{{Pad2 seasonNumber}}</span>
|
||||
{{/if_eq}}
|
||||
</span><span class="label">
|
||||
{{#with statistics}}
|
||||
{{#if_eq totalEpisodeCount compare=0}}
|
||||
<span class="season-status" title="No aired episodes"> </span>
|
||||
{{else}}
|
||||
{{#if_eq percentOfEpisodes compare=100}}
|
||||
<span class="season-status" title="{{episodeFileCount}}/{{totalEpisodeCount}} episodes downloaded">{{episodeFileCount}}/{{totalEpisodeCount}}</span>
|
||||
{{else}}
|
||||
<span class="season-status" title="{{episodeFileCount}}/{{totalEpisodeCount}} episodes downloaded">{{episodeFileCount}}/{{totalEpisodeCount}}</span>
|
||||
{{/if_eq}}
|
||||
{{/if_eq}}
|
||||
{{else}}
|
||||
<span class="season-status" title="No aired episodes"> </span>
|
||||
{{/with}}
|
||||
</span>
|
||||
</span>
|
||||
{{/each}}
|
@ -1,6 +0,0 @@
|
||||
var Marionette = require('marionette');
|
||||
var SeriesLayout = require('./SeriesLayout');
|
||||
|
||||
module.exports = Marionette.CollectionView.extend({
|
||||
itemView : SeriesLayout
|
||||
});
|
@ -1,152 +0,0 @@
|
||||
var _ = require('underscore');
|
||||
var Marionette = require('marionette');
|
||||
var Backgrid = require('backgrid');
|
||||
var SeasonCollection = require('../Series/SeasonCollection');
|
||||
|
||||
module.exports = Marionette.Layout.extend({
|
||||
template : 'SeasonPass/SeriesLayoutTemplate',
|
||||
|
||||
ui : {
|
||||
seasonSelect : '.x-season-select',
|
||||
expander : '.x-expander',
|
||||
seasonGrid : '.x-season-grid',
|
||||
seriesMonitored : '.x-series-monitored'
|
||||
},
|
||||
|
||||
events : {
|
||||
'change .x-season-select' : '_seasonSelected',
|
||||
'click .x-expander' : '_expand',
|
||||
'click .x-latest' : '_latest',
|
||||
'click .x-all' : '_all',
|
||||
'click .x-monitored' : '_toggleSeasonMonitored',
|
||||
'click .x-series-monitored' : '_toggleSeriesMonitored'
|
||||
},
|
||||
|
||||
regions : {
|
||||
seasonGrid : '.x-season-grid'
|
||||
},
|
||||
|
||||
initialize : function() {
|
||||
this.listenTo(this.model, 'sync', this._setSeriesMonitoredState);
|
||||
this.seasonCollection = new SeasonCollection(this.model.get('seasons'));
|
||||
this.expanded = false;
|
||||
},
|
||||
|
||||
onRender : function() {
|
||||
if (!this.expanded) {
|
||||
this.ui.seasonGrid.hide();
|
||||
}
|
||||
|
||||
this._setExpanderIcon();
|
||||
this._setSeriesMonitoredState();
|
||||
},
|
||||
|
||||
_seasonSelected : function() {
|
||||
var seasonNumber = parseInt(this.ui.seasonSelect.val(), 10);
|
||||
|
||||
if (seasonNumber === -1 || isNaN(seasonNumber)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._setSeasonMonitored(seasonNumber);
|
||||
},
|
||||
|
||||
_expand : function() {
|
||||
if (this.expanded) {
|
||||
this.ui.seasonGrid.slideUp();
|
||||
this.expanded = false;
|
||||
}
|
||||
|
||||
else {
|
||||
this.ui.seasonGrid.slideDown();
|
||||
this.expanded = true;
|
||||
}
|
||||
|
||||
this._setExpanderIcon();
|
||||
},
|
||||
|
||||
_setExpanderIcon : function() {
|
||||
if (this.expanded) {
|
||||
this.ui.expander.removeClass('icon-sonarr-expand');
|
||||
this.ui.expander.addClass('icon-sonarr-expanded');
|
||||
}
|
||||
|
||||
else {
|
||||
this.ui.expander.removeClass('icon-sonarr-expanded');
|
||||
this.ui.expander.addClass('icon-sonarr-expand');
|
||||
}
|
||||
},
|
||||
|
||||
_latest : function() {
|
||||
var season = _.max(this.model.get('seasons'), function(s) {
|
||||
return s.seasonNumber;
|
||||
});
|
||||
|
||||
this._setSeasonMonitored(season.seasonNumber);
|
||||
},
|
||||
|
||||
_all : function() {
|
||||
var minSeasonNotZero = _.min(_.reject(this.model.get('seasons'), { seasonNumber : 0 }), 'seasonNumber');
|
||||
|
||||
this._setSeasonMonitored(minSeasonNotZero.seasonNumber);
|
||||
},
|
||||
|
||||
_setSeasonMonitored : function(seasonNumber) {
|
||||
var self = this;
|
||||
|
||||
this.model.setSeasonPass(seasonNumber);
|
||||
|
||||
var promise = this.model.save();
|
||||
|
||||
promise.done(function(data) {
|
||||
self.seasonCollection = new SeasonCollection(data);
|
||||
self.render();
|
||||
});
|
||||
},
|
||||
|
||||
_toggleSeasonMonitored : function(e) {
|
||||
var seasonNumber = 0;
|
||||
var element;
|
||||
|
||||
if (e.target.localName === 'i') {
|
||||
seasonNumber = parseInt(this.$(e.target).parent('td').attr('data-season-number'), 10);
|
||||
element = this.$(e.target);
|
||||
}
|
||||
|
||||
else {
|
||||
seasonNumber = parseInt(this.$(e.target).attr('data-season-number'), 10);
|
||||
element = this.$(e.target).children('i');
|
||||
}
|
||||
|
||||
this.model.setSeasonMonitored(seasonNumber);
|
||||
|
||||
var savePromise = this.model.save().always(this.render.bind(this));
|
||||
element.spinForPromise(savePromise);
|
||||
},
|
||||
|
||||
_afterToggleSeasonMonitored : function() {
|
||||
this.render();
|
||||
},
|
||||
|
||||
_setSeriesMonitoredState : function() {
|
||||
var monitored = this.model.get('monitored');
|
||||
|
||||
this.ui.seriesMonitored.removeAttr('data-idle-icon');
|
||||
|
||||
if (monitored) {
|
||||
this.ui.seriesMonitored.addClass('icon-sonarr-monitored');
|
||||
this.ui.seriesMonitored.removeClass('icon-sonarr-unmonitored');
|
||||
} else {
|
||||
this.ui.seriesMonitored.addClass('icon-sonarr-unmonitored');
|
||||
this.ui.seriesMonitored.removeClass('icon-sonarr-monitored');
|
||||
}
|
||||
},
|
||||
|
||||
_toggleSeriesMonitored : function() {
|
||||
var savePromise = this.model.save('monitored', !this.model.get('monitored'), {
|
||||
wait : true
|
||||
});
|
||||
|
||||
this.ui.seriesMonitored.spinForPromise(savePromise);
|
||||
}
|
||||
});
|
@ -1,75 +0,0 @@
|
||||
<div class="seasonpass-series">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<i class="icon-sonarr-expand x-expander expander pull-left"/>
|
||||
<i class="x-series-monitored series-monitor-toggle pull-left" title="Toggle monitored state for entire series"/>
|
||||
<div class="title col-md-5">
|
||||
<a href="{{route}}">
|
||||
{{title}}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="col-md-2">
|
||||
<select class="form-control x-season-select season-select">
|
||||
<option value="-1">Select season...</option>
|
||||
{{#each seasons}}
|
||||
{{#if_eq seasonNumber compare="0"}}
|
||||
<option value="{{seasonNumber}}">Specials</option>
|
||||
{{else}}
|
||||
<option value="{{seasonNumber}}">Season {{seasonNumber}}</option>
|
||||
{{/if_eq}}
|
||||
{{/each}}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="col-md-1">
|
||||
<span class="help-inline">
|
||||
<i class="icon-sonarr-form-info" title="Selecting a season will unmonitor all previous seasons"/>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<span class="season-pass-button">
|
||||
<button class="btn x-latest last">Latest Season Only</button>
|
||||
</span>
|
||||
|
||||
<span class="season-pass-button">
|
||||
<button class="btn x-all">All Seasons</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-11">
|
||||
<div class="x-season-grid season-grid">
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th class="sortable">Season</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{#each seasons}}
|
||||
<tr>
|
||||
<td class="toggle-cell x-monitored" data-season-number="{{seasonNumber}}">
|
||||
{{#if monitored}}
|
||||
<i class="icon-sonarr-monitored"></i>
|
||||
{{else}}
|
||||
<i class="icon-sonarr-unmonitored"></i>
|
||||
{{/if}}
|
||||
</td>
|
||||
<td>
|
||||
{{#if_eq seasonNumber compare="0"}}
|
||||
Specials
|
||||
{{else}}
|
||||
Season {{seasonNumber}}
|
||||
{{/if_eq}}
|
||||
</td>
|
||||
</tr>
|
||||
{{/each}}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,49 @@
|
||||
@import "../Content/badges.less";
|
||||
@import "../Shared/Styles/clickable.less";
|
||||
|
||||
.season {
|
||||
display : inline-block;
|
||||
margin-bottom : 4px;
|
||||
|
||||
.label {
|
||||
.badge-inverse();
|
||||
|
||||
display : inline-block;
|
||||
padding : 4px;
|
||||
|
||||
font-size : 14px;
|
||||
}
|
||||
|
||||
.label:first-child {
|
||||
border-right : 0px;
|
||||
border-top-right-radius : 0.0em;
|
||||
border-bottom-right-radius : 0.0em;
|
||||
color : #777;
|
||||
background-color : #eee;
|
||||
}
|
||||
|
||||
.label:last-child {
|
||||
border-left : 0px;
|
||||
border-top-left-radius : 0.0em;
|
||||
border-bottom-left-radius : 0.0em;
|
||||
color : #999;
|
||||
background-color : #f7f7f7;
|
||||
}
|
||||
|
||||
.season-monitored {
|
||||
width : 16px;
|
||||
|
||||
i {
|
||||
.clickable();
|
||||
}
|
||||
}
|
||||
|
||||
.season-number {
|
||||
font-size : 12px;
|
||||
}
|
||||
|
||||
.season-status {
|
||||
display : inline-block;
|
||||
vertical-align : baseline !important;
|
||||
}
|
||||
}
|
Loading…
Reference in new issue