diff --git a/NzbDrone.Api/Bootstrapper.cs b/NzbDrone.Api/Bootstrapper.cs index 8fa20f538..190f0aeda 100644 --- a/NzbDrone.Api/Bootstrapper.cs +++ b/NzbDrone.Api/Bootstrapper.cs @@ -57,7 +57,8 @@ namespace NzbDrone.Api //Series Mapper.CreateMap() - .ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.SeriesId)); + .ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.SeriesId)) + .ForMember(dest => dest.CustomStartDate, opt => opt.ResolveUsing().FromMember(src => src.CustomStartDate)); //.ForMember(dest => dest.BacklogSetting, opt => opt.MapFrom(src => Convert.ToInt32(src.BacklogSetting))); } diff --git a/NzbDrone.Api/NzbDrone.Api.csproj b/NzbDrone.Api/NzbDrone.Api.csproj index b6249c9d5..8c0150e64 100644 --- a/NzbDrone.Api/NzbDrone.Api.csproj +++ b/NzbDrone.Api/NzbDrone.Api.csproj @@ -95,6 +95,7 @@ + diff --git a/NzbDrone.Api/Resolvers/NullableDatetimeToString.cs b/NzbDrone.Api/Resolvers/NullableDatetimeToString.cs new file mode 100644 index 000000000..d7138fdbc --- /dev/null +++ b/NzbDrone.Api/Resolvers/NullableDatetimeToString.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using AutoMapper; +using NzbDrone.Api.QualityProfiles; +using NzbDrone.Core.Repository.Quality; + +namespace NzbDrone.Api.Resolvers +{ + public class NullableDatetimeToString : ValueResolver + { + protected override String ResolveCore(DateTime? source) + { + if(!source.HasValue) + return String.Empty; + + return source.Value.ToString("yyyy-MM-dd"); + } + } +} diff --git a/NzbDrone.Api/Series/SeriesModel.cs b/NzbDrone.Api/Series/SeriesModel.cs index 2fd100293..e8e084dc1 100644 --- a/NzbDrone.Api/Series/SeriesModel.cs +++ b/NzbDrone.Api/Series/SeriesModel.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Text; +using NzbDrone.Api.QualityProfiles; using NzbDrone.Core.Model; namespace NzbDrone.Api.Series @@ -12,6 +13,7 @@ namespace NzbDrone.Api.Series //Todo: Sorters should be done completely on the client //Todo: Is there an easy way to keep IgnoreArticlesWhenSorting in sync between, Series, History, Missing? + //Todo: We should get the entire QualityProfile instead of ID and Name separately //View Only public String Title { get; set; } @@ -41,6 +43,6 @@ namespace NzbDrone.Api.Series public Boolean SeasonFolder { get; set; } public Boolean Monitored { get; set; } public BacklogSettingType BacklogSetting { get; set; } - public DateTime? CustomStartDate { get; set; } + public String CustomStartDate { get; set; } } } diff --git a/NzbDrone.Api/Series/SeriesModule.cs b/NzbDrone.Api/Series/SeriesModule.cs index 4645706aa..7ba929cda 100644 --- a/NzbDrone.Api/Series/SeriesModule.cs +++ b/NzbDrone.Api/Series/SeriesModule.cs @@ -1,4 +1,6 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; +using System.Globalization; using System.Linq; using AutoMapper; using FluentValidation; @@ -7,6 +9,7 @@ using NzbDrone.Api.Extentions; using NzbDrone.Api.QualityProfiles; using NzbDrone.Common; using NzbDrone.Core.Jobs; +using NzbDrone.Core.Model; using NzbDrone.Core.Providers; using NzbDrone.Core.Providers.Core; @@ -25,7 +28,10 @@ namespace NzbDrone.Api.Series Get["/"] = x => AllSeries(); Get["/{id}"] = x => GetSeries((int)x.id); Post["/"] = x => AddSeries(); - Delete["/{id}"] = x => DeleteSeries((int)x.id); + Put["/"] = x => UpdateSeries(); + + //Todo: Backbone failing and not sending the id properly... wtf + Delete["/"] = x => DeleteSeries(0); } private Response AllSeries() @@ -59,9 +65,43 @@ namespace NzbDrone.Api.Series return new Response { StatusCode = HttpStatusCode.Created }; } + private Response UpdateSeries() + { + var request = Request.Body.FromJson(); + + var series = _seriesProvider.GetSeries(request.Id); + + series.Monitored = request.Monitored; + series.SeasonFolder = request.SeasonFolder; + series.QualityProfileId = request.QualityProfileId; + + var oldPath = series.Path; + + series.Path = request.Path; + series.BacklogSetting = (BacklogSettingType)request.BacklogSetting; + + if (!String.IsNullOrWhiteSpace(request.CustomStartDate)) + series.CustomStartDate = DateTime.Parse(request.CustomStartDate, null, DateTimeStyles.RoundtripKind); + + else + series.CustomStartDate = null; + + _seriesProvider.UpdateSeries(series); + + if (oldPath != series.Path) + _jobProvider.QueueJob(typeof(DiskScanJob), new { SeriesId = series.SeriesId }); + + _seriesProvider.UpdateSeries(series); + + return request.AsResponse(); + } + private Response DeleteSeries(int id) { - //_seriesProvider.DeleteSeries(id); + var seriesId = Convert.ToInt32(Request.Headers["id"].FirstOrDefault()); + var deleteFiles = Convert.ToBoolean(Request.Headers["deleteFiles"].FirstOrDefault()); + + _jobProvider.QueueJob(typeof(DeleteSeriesJob), new {SeriesId = seriesId, DeleteFiles = deleteFiles}); return new Response { StatusCode = HttpStatusCode.OK }; } } diff --git a/NzbDrone.Web/NzbDrone.Web.csproj b/NzbDrone.Web/NzbDrone.Web.csproj index 413d2c5b8..f3bf22022 100644 --- a/NzbDrone.Web/NzbDrone.Web.csproj +++ b/NzbDrone.Web/NzbDrone.Web.csproj @@ -409,6 +409,8 @@ + + diff --git a/NzbDrone.Web/_backboneApp/AddSeries/New/AddNewSeriesView.js b/NzbDrone.Web/_backboneApp/AddSeries/New/AddNewSeriesView.js index 63a042f53..fdb070a53 100644 --- a/NzbDrone.Web/_backboneApp/AddSeries/New/AddNewSeriesView.js +++ b/NzbDrone.Web/_backboneApp/AddSeries/New/AddNewSeriesView.js @@ -16,8 +16,6 @@ NzbDrone.AddSeries.AddNewSeriesView = Backbone.Marionette.Layout.extend({ collection: new NzbDrone.AddSeries.SearchResultCollection(), - - initialize: function (options) { if (options.rootFolders === undefined) { throw 'rootFolder arg. is required.'; @@ -27,9 +25,8 @@ NzbDrone.AddSeries.AddNewSeriesView = Backbone.Marionette.Layout.extend({ throw 'qualityProfiles arg. is required.'; } - this.rootFoldersCollection = options.rootFolders; - this.qualityProfilesCollection = options.qualityProfiles; + this.qualityProfileCollection = options.qualityProfiles; }, onRender: function () { @@ -44,7 +41,6 @@ NzbDrone.AddSeries.AddNewSeriesView = Backbone.Marionette.Layout.extend({ }); this.resultView = new NzbDrone.AddSeries.SearchResultView({ collection: this.collection }); - }, search: function (context) { @@ -80,7 +76,7 @@ NzbDrone.AddSeries.AddNewSeriesView = Backbone.Marionette.Layout.extend({ resultUpdated: function (options, context) { _.each(options.models, function (model) { model.set('rootFolders', context.rootFoldersCollection); - model.set('qualityProfiles', context.qualityProfilesCollection); + model.set('qualityProfiles', context.qualityProfileCollection); }); context.searchResult.show(context.resultView); diff --git a/NzbDrone.Web/_backboneApp/AddSeries/New/SearchResultView.js b/NzbDrone.Web/_backboneApp/AddSeries/New/SearchResultView.js index bb148f215..771d4dc31 100644 --- a/NzbDrone.Web/_backboneApp/AddSeries/New/SearchResultView.js +++ b/NzbDrone.Web/_backboneApp/AddSeries/New/SearchResultView.js @@ -31,6 +31,7 @@ NzbDrone.AddSeries.SearchItemView = Backbone.Marionette.ItemView.extend({ var quality = this.ui.qualityProfile.val(); var rootFolderId = this.ui.rootFolder.val(); + //Todo: This wiil create an invalid path on linux... var rootPath = this.model.get('rootFolders').get(rootFolderId).get('path'); var path = rootPath + "\\" + title; diff --git a/NzbDrone.Web/_backboneApp/Content/Bootstrap/modals.less b/NzbDrone.Web/_backboneApp/Content/Bootstrap/modals.less index 8e272d409..957a3209a 100644 --- a/NzbDrone.Web/_backboneApp/Content/Bootstrap/modals.less +++ b/NzbDrone.Web/_backboneApp/Content/Bootstrap/modals.less @@ -26,8 +26,8 @@ top: 10%; left: 50%; z-index: @zindexModal; - width: 560px; - margin-left: -280px; + width: 660px; + margin-left: -330px; background-color: @white; border: 1px solid #999; border: 1px solid rgba(0,0,0,.3); @@ -60,7 +60,7 @@ .modal-body { position: relative; overflow-y: auto; - max-height: 400px; + max-height: 450px; padding: 15px; } // Remove bottom margin if need be diff --git a/NzbDrone.Web/_backboneApp/Content/base.css b/NzbDrone.Web/_backboneApp/Content/base.css index badfbb889..86d24c523 100644 --- a/NzbDrone.Web/_backboneApp/Content/base.css +++ b/NzbDrone.Web/_backboneApp/Content/base.css @@ -9,7 +9,7 @@ body { background: #191919 url(images/background.jpg) no-repeat right top; - font-size: 13px; + font-size: 14px; color: #3C3C3C; background-attachment: fixed; } @@ -84,6 +84,7 @@ body { line-height: 1em; } +/* Progress Bar Text */ .progress { width: 125px; position: relative; @@ -106,4 +107,23 @@ body { width: 125px; font-size: 12px; text-align: center; +} + +/* Todo: Should move this to somehting modal/form specific */ + +label.checkbox { + font-size: 14px; + line-height: normal; + color: #595959; +} + +label, .form-horizontal input, .form-horizontal select { + font-size: 14px; + line-height: 14px; +} + +label.control-label { + font-size: 16px; + line-height: 16px; + font-weight: bold; } \ No newline at end of file diff --git a/NzbDrone.Web/_backboneApp/Series/DeleteSeriesTemplate.html b/NzbDrone.Web/_backboneApp/Series/DeleteSeriesTemplate.html index 372dcdd30..6607393a3 100644 --- a/NzbDrone.Web/_backboneApp/Series/DeleteSeriesTemplate.html +++ b/NzbDrone.Web/_backboneApp/Series/DeleteSeriesTemplate.html @@ -1,10 +1,16 @@  \ No newline at end of file diff --git a/NzbDrone.Web/_backboneApp/Series/DeleteSeriesView.js b/NzbDrone.Web/_backboneApp/Series/DeleteSeriesView.js index 15639a923..53bd98c75 100644 --- a/NzbDrone.Web/_backboneApp/Series/DeleteSeriesView.js +++ b/NzbDrone.Web/_backboneApp/Series/DeleteSeriesView.js @@ -21,7 +21,8 @@ NzbDrone.Series.DeleteSeriesView = Backbone.Marionette.ItemView.extend({ }, removeSeries: function () { - this.model.destroy({ wait: true }); + //Todo: why the fuck doesn't destroy send the ID? + this.model.destroy({ wait: true, headers: { id: this.model.get('id'), deleteFiles: $('#delete-from-disk').prop('checked') } }); this.model.collection.remove(this.model); this.$el.parent().modal('hide'); }, diff --git a/NzbDrone.Web/_backboneApp/Series/EditSeriesTemplate.html b/NzbDrone.Web/_backboneApp/Series/EditSeriesTemplate.html new file mode 100644 index 000000000..91aaf2ec6 --- /dev/null +++ b/NzbDrone.Web/_backboneApp/Series/EditSeriesTemplate.html @@ -0,0 +1,73 @@ + + + \ No newline at end of file diff --git a/NzbDrone.Web/_backboneApp/Series/EditSeriesView.js b/NzbDrone.Web/_backboneApp/Series/EditSeriesView.js new file mode 100644 index 000000000..7ab89769e --- /dev/null +++ b/NzbDrone.Web/_backboneApp/Series/EditSeriesView.js @@ -0,0 +1,52 @@ +'use strict'; +/*global NzbDrone, Backbone*/ +/// +/// +/// +/// + +NzbDrone.Series.EditSeriesView = Backbone.Marionette.ItemView.extend({ + template: 'Series/EditSeriesTemplate', + tagName: 'div', + className: "modal", + + ui: { + progressbar: '.progress .bar', + qualityProfile: '.x-quality-profile', + backlogSettings: '.x-backlog-setting', + }, + + events: { + 'click .x-save': 'saveSeries', + 'click .x-remove': 'removeSeries' + }, + + initialize: function (options) { + this.qualityProfileCollection = options.qualityProfiles; + this.model.set('qualityProfiles', this.qualityProfileCollection); + }, + + onRender: function () { + NzbDrone.ModelBinder.bind(this.model, this.el); + }, + + qualityProfileCollection: new NzbDrone.Quality.QualityProfileCollection(), + + saveSeries: function () { + //Todo: Get qualityProfile + backlog setting from UI + var qualityProfile = this.ui.qualityProfile.val(); + var qualityProfileText = this.ui.qualityProfile.children('option:selected').text(); + var backlogSetting = this.ui.backlogSettings.val(); + + this.model.set({ qualityProfileId: qualityProfile, backlogSetting: backlogSetting, qualityProfileName: qualityProfileText }); + + this.model.save(); + this.trigger('saved'); + this.$el.parent().modal('hide'); + }, + + removeSeries: function () { + var view = new NzbDrone.Series.DeleteSeriesView({ model: this.model }); + NzbDrone.modalRegion.show(view); + } +}); \ No newline at end of file diff --git a/NzbDrone.Web/_backboneApp/Series/Index/IndexLayout.js b/NzbDrone.Web/_backboneApp/Series/Index/IndexLayout.js index ff47b9b42..7d2244dda 100644 --- a/NzbDrone.Web/_backboneApp/Series/Index/IndexLayout.js +++ b/NzbDrone.Web/_backboneApp/Series/Index/IndexLayout.js @@ -8,8 +8,7 @@ NzbDrone.Series.IndexLayout = Backbone.Marionette.Layout.extend({ route: 'Series/index', ui: { - edit: '.edit-series', - delele: '.delete-series' + }, regions: { diff --git a/NzbDrone.Web/_backboneApp/Series/Index/SeriesItemTemplate.html b/NzbDrone.Web/_backboneApp/Series/Index/SeriesItemTemplate.html index fb77bf604..43a2d40fd 100644 --- a/NzbDrone.Web/_backboneApp/Series/Index/SeriesItemTemplate.html +++ b/NzbDrone.Web/_backboneApp/Series/Index/SeriesItemTemplate.html @@ -7,4 +7,7 @@ {{{formatProgress episodeFileCount episodeCount}}} -Edit/ + + + + diff --git a/NzbDrone.Web/_backboneApp/Series/Index/SeriesItemView.js b/NzbDrone.Web/_backboneApp/Series/Index/SeriesItemView.js index 3f77d9a1e..8a5ed8f23 100644 --- a/NzbDrone.Web/_backboneApp/Series/Index/SeriesItemView.js +++ b/NzbDrone.Web/_backboneApp/Series/Index/SeriesItemView.js @@ -3,7 +3,9 @@ /// /// /// +/// /// +/// NzbDrone.Series.Index.SeriesItemView = Backbone.Marionette.ItemView.extend({ template: 'Series/Index/SeriesItemTemplate', @@ -14,25 +16,45 @@ NzbDrone.Series.Index.SeriesItemView = Backbone.Marionette.ItemView.extend({ }, events: { - 'click .x-remove': 'removeSeries', + 'click .x-edit': 'editSeries', + 'click .x-remove': 'removeSeries' + }, + + initialize: function(options) { + this.qualityProfileCollection = options.qualityProfiles; }, onRender: function () { NzbDrone.ModelBinder.bind(this.model, this.el); }, - removeSeries: function () { - //this.model.destroy({ wait: true }); - //this.model.collection.remove(this.model); + qualityProfileCollection: new NzbDrone.Quality.QualityProfileCollection(), + editSeries: function () { + var view = new NzbDrone.Series.EditSeriesView({ model: this.model, qualityProfiles: this.qualityProfileCollection }); + view.on('saved', this.render, this); + NzbDrone.modalRegion.show(view); + }, + + removeSeries: function () { var view = new NzbDrone.Series.DeleteSeriesView({ model: this.model }); NzbDrone.modalRegion.show(view); }, + onSave: function() { + alert("saved!"); + } }); NzbDrone.Series.Index.SeriesCollectionView = Backbone.Marionette.CompositeView.extend({ itemView: NzbDrone.Series.Index.SeriesItemView, + itemViewOptions: {}, template: 'Series/Index/SeriesCollectionTemplate', tagName: 'table', className: 'table table-hover', + qualityProfileCollection: new NzbDrone.Quality.QualityProfileCollection(), + + initialize: function() { + this.qualityProfileCollection.fetch(); + this.itemViewOptions = { qualityProfiles: this.qualityProfileCollection }; + } });