diff --git a/NzbDrone.Api/Seasons/SeasonModule.cs b/NzbDrone.Api/Seasons/SeasonModule.cs index 773faf85f..cd68b2881 100644 --- a/NzbDrone.Api/Seasons/SeasonModule.cs +++ b/NzbDrone.Api/Seasons/SeasonModule.cs @@ -14,13 +14,20 @@ public SeasonModule(ISeasonService seasonService) GetResourceAll = GetSeasons; UpdateResource = SetMonitored; + + Post["/pass"] = x => SetSeasonPass(); } private List GetSeasons() { var seriesId = Request.Query.SeriesId; - return ToListResource(() => _seasonService.GetSeasonsBySeries(seriesId)); + if (seriesId.HasValue) + { + return ToListResource(() => _seasonService.GetSeasonsBySeries(seriesId)); + } + + return ToListResource(() => _seasonService.GetAllSeasons()); } private SeasonResource SetMonitored(SeasonResource seasonResource) @@ -29,5 +36,13 @@ private SeasonResource SetMonitored(SeasonResource seasonResource) return seasonResource; } + + private List SetSeasonPass() + { + var seriesId = Request.Form.SeriesId; + var seasonNumber = Request.Form.SeasonNumber; + + return ToListResource(() => _seasonService.SetSeasonPass(seriesId, seasonNumber)); + } } } \ No newline at end of file diff --git a/NzbDrone.Core/Tv/SeasonService.cs b/NzbDrone.Core/Tv/SeasonService.cs index a7ca88c4b..95eff2c76 100644 Binary files a/NzbDrone.Core/Tv/SeasonService.cs and b/NzbDrone.Core/Tv/SeasonService.cs differ diff --git a/UI/Controller.js b/UI/Controller.js index b75d8cdf3..1223fb9c2 100644 --- a/UI/Controller.js +++ b/UI/Controller.js @@ -16,10 +16,11 @@ define( 'Logs/Files/Layout', 'Release/Layout', 'System/Layout', + 'SeasonPass/Layout', 'Shared/NotFoundView', 'Shared/Modal/Region' ], function (App, Marionette, HistoryLayout, SettingsLayout, AddSeriesLayout, SeriesIndexLayout, SeriesDetailsLayout, SeriesCollection, MissingLayout, SeriesModel, CalendarLayout, - LogsLayout, LogFileLayout, ReleaseLayout, SystemLayout, NotFoundView) { + LogsLayout, LogFileLayout, ReleaseLayout, SystemLayout, SeasonPassLayout, NotFoundView) { return Marionette.Controller.extend({ series: function () { @@ -40,11 +41,6 @@ define( } }, - - _showSeriesDetail: function(seriesModel){ - - }, - addSeries: function (action) { this._setTitle('Add Series'); App.mainRegion.show(new AddSeriesLayout({action: action})); @@ -55,7 +51,6 @@ define( App.mainRegion.show(new CalendarLayout()); }, - settings: function (action) { this._setTitle('Settings'); App.mainRegion.show(new SettingsLayout({action: action})); @@ -95,6 +90,11 @@ define( App.mainRegion.show(new SystemLayout()); }, + seasonPass: function () { + this._setTitle('Season Pass'); + App.mainRegion.show(new SeasonPassLayout()); + }, + notFound: function () { this._setTitle('Not Found'); App.mainRegion.show(new NotFoundView(this)); diff --git a/UI/Missing/MissingLayout.js b/UI/Missing/MissingLayout.js index 1abad6cd1..805998380 100644 --- a/UI/Missing/MissingLayout.js +++ b/UI/Missing/MissingLayout.js @@ -9,8 +9,9 @@ define( 'Cells/EpisodeTitleCell', 'Cells/RelativeDateCell', 'Shared/Grid/Pager', + 'Shared/Toolbar/ToolbarLayout', 'Shared/LoadingView' - ], function (Marionette, Backgrid, MissingCollection, SeriesTitleCell, EpisodeNumberCell, EpisodeTitleCell, RelativeDateCell, GridPager, LoadingView) { + ], function (Marionette, Backgrid, MissingCollection, SeriesTitleCell, EpisodeNumberCell, EpisodeTitleCell, RelativeDateCell, GridPager, ToolbarLayout, LoadingView) { return Marionette.Layout.extend({ template: 'Missing/MissingLayoutTemplate', @@ -47,6 +48,19 @@ define( } ], + leftSideButtons: { + type : 'default', + storeState: false, + items : + [ + { + title : 'Season Pass', + icon : 'icon-bookmark', + route : 'seasonpass' + } + ] + }, + _showTable: function () { this.missing.show(new Backgrid.Grid({ columns : this.columns, @@ -69,6 +83,18 @@ define( this.missingCollection.fetch().done(function () { self._showTable(); }); + + this._showToolbar(); + }, + + _showToolbar: function () { + this.toolbar.show(new ToolbarLayout({ + left : + [ + this.leftSideButtons + ], + context: this + })); } }); }); diff --git a/UI/Router.js b/UI/Router.js index f4abb0e91..65ebc869a 100644 --- a/UI/Router.js +++ b/UI/Router.js @@ -26,6 +26,7 @@ require( 'logs/:action' : 'logs', 'rss' : 'rss', 'system' : 'system', + 'seasonpass' : 'seasonPass', ':whatever' : 'notFound' } }); diff --git a/UI/SeasonPass/Layout.js b/UI/SeasonPass/Layout.js new file mode 100644 index 000000000..cb4500daf --- /dev/null +++ b/UI/SeasonPass/Layout.js @@ -0,0 +1,39 @@ +'use strict'; +define( + [ + 'marionette', + 'Series/SeriesCollection', + 'Series/SeasonCollection', + 'SeasonPass/SeriesCollectionView', + 'Shared/LoadingView' + ], function (Marionette, + SeriesCollection, + SeasonCollection, + SeriesCollectionView, + LoadingView) { + return Marionette.Layout.extend({ + template: 'SeasonPass/LayoutTemplate', + + regions: { + series: '#x-series' + }, + + onShow: function () { + var self = this; + + this.series.show(new LoadingView()); + + this.seriesCollection = SeriesCollection; + this.seasonCollection = new SeasonCollection(); + + var promise = this.seasonCollection.fetch(); + + promise.done(function () { + self.series.show(new SeriesCollectionView({ + collection: self.seriesCollection, + seasonCollection: self.seasonCollection + })); + }); + } + }); + }); diff --git a/UI/SeasonPass/LayoutTemplate.html b/UI/SeasonPass/LayoutTemplate.html new file mode 100644 index 000000000..152d9d158 --- /dev/null +++ b/UI/SeasonPass/LayoutTemplate.html @@ -0,0 +1,11 @@ +
+
+
Season Pass allows you to quickly change the monitored status of seasons for all your series in one place
+
+
+ +
+
+
+
+
\ No newline at end of file diff --git a/UI/SeasonPass/SeriesCollectionView.js b/UI/SeasonPass/SeriesCollectionView.js new file mode 100644 index 000000000..48cf5c39e --- /dev/null +++ b/UI/SeasonPass/SeriesCollectionView.js @@ -0,0 +1,26 @@ +'use strict'; +define( + [ + 'marionette', + 'SeasonPass/SeriesLayout' + ], function (Marionette, SeriesLayout) { + return Marionette.CollectionView.extend({ + + itemView: SeriesLayout, + + initialize: function (options) { + + if (!options.seasonCollection) { + throw 'seasonCollection is needed'; + } + + this.seasonCollection = options.seasonCollection; + }, + + itemViewOptions: function () { + return { + seasonCollection: this.seasonCollection + }; + } + }); + }); diff --git a/UI/SeasonPass/SeriesLayout.js b/UI/SeasonPass/SeriesLayout.js new file mode 100644 index 000000000..f0382adfd --- /dev/null +++ b/UI/SeasonPass/SeriesLayout.js @@ -0,0 +1,117 @@ +'use strict'; +define( + [ + 'marionette', + 'backgrid', + 'Series/SeasonCollection', + 'Cells/ToggleCell', + 'Shared/Actioneer' + ], function (Marionette, Backgrid, SeasonCollection, ToggleCell, Actioneer) { + return Marionette.Layout.extend({ + template: 'SeasonPass/SeriesLayoutTemplate', + + ui: { + seasonSelect: '.x-season-select', + expander : '.x-expander', + seasonGrid : '.x-season-grid' + }, + + events: { + 'change .x-season-select': '_seasonSelected', + 'click .x-expander' : '_expand' + }, + + regions: { + seasonGrid: '.x-season-grid' + }, + + columns: + [ + { + name : 'monitored', + label : '', + cell : ToggleCell, + trueClass : 'icon-bookmark', + falseClass: 'icon-bookmark-empty', + tooltip : 'Toggle monitored status', + sortable : false + }, + { + name : 'seasonNumber', + label: 'Season', + cell : Backgrid.IntegerCell.extend({ + className: 'season-number-cell' + }) + } + ], + + initialize: function (options) { + this.seasonCollection = options.seasonCollection.bySeries(this.model.get('id')); + this.model.set('seasons', this.seasonCollection); + this.expanded = false; + }, + + onRender: function () { + this.seasonGrid.show(new Backgrid.Grid({ + columns : this.columns, + collection: this.seasonCollection, + className : 'table table-condensed season-grid span5' + })); + + if (!this.expanded) { + this.seasonGrid.$el.hide(); + } + + this._setExpanderIcon(); + }, + + _seasonSelected: function () { + var self = this; + var seasonNumber = parseInt(this.ui.seasonSelect.val()); + + if (seasonNumber == -1) { + return; + } + + var promise = $.ajax({ + url: this.seasonCollection.url + '/pass', + type: 'POST', + data: { + seriesId: this.model.get('id'), + seasonNumber: seasonNumber + } + }); + + promise.done(function (data) { + self.seasonCollection = new SeasonCollection(data); + self.render(); + }); + }, + + _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-chevron-right'); + this.ui.expander.addClass('icon-chevron-down'); + } + + else { + this.ui.expander.removeClass('icon-chevron-down'); + this.ui.expander.addClass('icon-chevron-right'); + } + } + }); + }); diff --git a/UI/SeasonPass/SeriesLayoutTemplate.html b/UI/SeasonPass/SeriesLayoutTemplate.html new file mode 100644 index 000000000..a9d4ac7e2 --- /dev/null +++ b/UI/SeasonPass/SeriesLayoutTemplate.html @@ -0,0 +1,32 @@ +
+
+
+ + + + + {{title}} + + + + + + +
+
+ +
+
+
+
+
+
diff --git a/UI/Series/SeasonCollection.js b/UI/Series/SeasonCollection.js index a1abbc898..68e312ff1 100644 --- a/UI/Series/SeasonCollection.js +++ b/UI/Series/SeasonCollection.js @@ -1,24 +1,25 @@ 'use strict'; define( [ - 'Series/SeasonModel', - 'backbone.pageable' - ], function (SeasonModel, PageAbleCollection) { - return PageAbleCollection.extend({ + 'backbone', + 'Series/SeasonModel' + ], function (Backbone, SeasonModel) { + return Backbone.Collection.extend({ url : window.ApiRoot + '/season', model: SeasonModel, - mode: 'client', - - state: { - sortKey : 'seasonNumber', - order : 1, - pageSize: 1000000 + comparator: function (season) { + return -season.get('seasonNumber'); }, - queryParams: { - sortKey: null, - order : null + bySeries: function (series) { + var filtered = this.filter(function (season) { + return season.get('seriesId') === series; + }); + + var SeasonCollection = require('Series/SeasonCollection'); + + return new SeasonCollection(filtered); } }); }); diff --git a/UI/Series/series.less b/UI/Series/series.less index 82e37be67..061fb3bce 100644 --- a/UI/Series/series.less +++ b/UI/Series/series.less @@ -246,4 +246,30 @@ .series-legend { padding-top: 5px; +} + +.seasonpass-series { + .card; + margin : 20px 0px; + + .title { + font-weight: 300; + font-size: 24px; + line-height: 30px; + margin-left: 5px; + } + + .season-select { + margin-bottom: 0px; + } + + .expander { + .clickable; + line-height: 30px; + margin-left: 8px; + } + + .season-grid { + margin-top: 10px; + } } \ No newline at end of file