New: Season Pass supports multi-select New: Improved Season Pass toggling Closes #396pull/2/head
parent
28e2cf97da
commit
155c82c199
@ -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,30 @@
|
|||||||
|
{{#each seasons}}
|
||||||
|
<span class="season label label-default">
|
||||||
|
<span>
|
||||||
|
<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>
|
||||||
|
</span>
|
||||||
|
{{#if_eq seasonNumber compare="0"}}
|
||||||
|
<span class="season-number">Specials</span>
|
||||||
|
{{else}}
|
||||||
|
<span class="season-number">S{{Pad2 seasonNumber}}</span>
|
||||||
|
{{/if_eq}}
|
||||||
|
|
||||||
|
<!--{{#if_eq statistics.episodeCount compare=0}}-->
|
||||||
|
<!--{{#if monitored}}-->
|
||||||
|
<!--<span class="badge badge-primary season-status" title="No aired episodes"> </span>-->
|
||||||
|
<!--{{else}}-->
|
||||||
|
<!--<span class="badge badge-warning season-status" title="Season is not monitored"> </span>-->
|
||||||
|
<!--{{/if}}-->
|
||||||
|
<!--{{else}}-->
|
||||||
|
<!--{{#with statistics}}-->
|
||||||
|
<!--{{#if_eq percentOfEpisodes compare=100}}-->
|
||||||
|
<!--<span class="badge badge-success season-status" title="{{episodeFileCount}}/{{episodeCount}} episodes downloaded">{{episodeFileCount}} / {{episodeCount}}</span>-->
|
||||||
|
<!--{{else}}-->
|
||||||
|
<!--<span class="badge badge-danger season-status" title="{{episodeFileCount}}/{{episodeCount}} episodes downloaded">{{episodeFileCount}} / {{episodeCount}}</span>-->
|
||||||
|
<!--{{/if_eq}}-->
|
||||||
|
<!--{{/with}}-->
|
||||||
|
<!--{{/if_eq}}-->
|
||||||
|
</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,21 @@
|
|||||||
|
@import "../Shared/Styles/clickable.less";
|
||||||
|
|
||||||
|
.season {
|
||||||
|
display : inline-block;
|
||||||
|
font-size : 14px;
|
||||||
|
margin-bottom : 4px;
|
||||||
|
background-color: #eee;
|
||||||
|
border: 1px solid #999;
|
||||||
|
color: #999;
|
||||||
|
|
||||||
|
.season-monitored {
|
||||||
|
display : inline-block;
|
||||||
|
width : 16px;
|
||||||
|
|
||||||
|
.clickable();
|
||||||
|
}
|
||||||
|
|
||||||
|
.season-number {
|
||||||
|
font-size : 12px;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in new issue