Series/Index wired up to backbone, just need to add DT

This commit is contained in:
Mark McDowall 2013-02-10 02:28:56 -08:00 committed by kay.one
parent 84fbfb5d48
commit 796f63680a
17 changed files with 265 additions and 26 deletions

View File

@ -57,7 +57,8 @@ namespace NzbDrone.Api
//Series //Series
Mapper.CreateMap<Core.Repository.Series, SeriesModel>() Mapper.CreateMap<Core.Repository.Series, SeriesModel>()
.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<NullableDatetimeToString>().FromMember(src => src.CustomStartDate));
//.ForMember(dest => dest.BacklogSetting, opt => opt.MapFrom(src => Convert.ToInt32(src.BacklogSetting))); //.ForMember(dest => dest.BacklogSetting, opt => opt.MapFrom(src => Convert.ToInt32(src.BacklogSetting)));
} }

View File

@ -95,6 +95,7 @@
<Compile Include="Directories\DirectoryModule.cs" /> <Compile Include="Directories\DirectoryModule.cs" />
<Compile Include="Extentions\NancyJsonSerializer.cs" /> <Compile Include="Extentions\NancyJsonSerializer.cs" />
<Compile Include="Extentions\Serializer.cs" /> <Compile Include="Extentions\Serializer.cs" />
<Compile Include="Resolvers\NullableDatetimeToString.cs" />
<Compile Include="RootFolders\RootFolderModule.cs" /> <Compile Include="RootFolders\RootFolderModule.cs" />
<Compile Include="Series\SeriesModel.cs" /> <Compile Include="Series\SeriesModel.cs" />
<Compile Include="Series\SeriesModule.cs" /> <Compile Include="Series\SeriesModule.cs" />

View File

@ -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<DateTime?, String>
{
protected override String ResolveCore(DateTime? source)
{
if(!source.HasValue)
return String.Empty;
return source.Value.ToString("yyyy-MM-dd");
}
}
}

View File

@ -2,6 +2,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using NzbDrone.Api.QualityProfiles;
using NzbDrone.Core.Model; using NzbDrone.Core.Model;
namespace NzbDrone.Api.Series namespace NzbDrone.Api.Series
@ -12,6 +13,7 @@ namespace NzbDrone.Api.Series
//Todo: Sorters should be done completely on the client //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: 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 //View Only
public String Title { get; set; } public String Title { get; set; }
@ -41,6 +43,6 @@ namespace NzbDrone.Api.Series
public Boolean SeasonFolder { get; set; } public Boolean SeasonFolder { get; set; }
public Boolean Monitored { get; set; } public Boolean Monitored { get; set; }
public BacklogSettingType BacklogSetting { get; set; } public BacklogSettingType BacklogSetting { get; set; }
public DateTime? CustomStartDate { get; set; } public String CustomStartDate { get; set; }
} }
} }

View File

@ -1,4 +1,6 @@
using System.Collections.Generic; using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq; using System.Linq;
using AutoMapper; using AutoMapper;
using FluentValidation; using FluentValidation;
@ -7,6 +9,7 @@ using NzbDrone.Api.Extentions;
using NzbDrone.Api.QualityProfiles; using NzbDrone.Api.QualityProfiles;
using NzbDrone.Common; using NzbDrone.Common;
using NzbDrone.Core.Jobs; using NzbDrone.Core.Jobs;
using NzbDrone.Core.Model;
using NzbDrone.Core.Providers; using NzbDrone.Core.Providers;
using NzbDrone.Core.Providers.Core; using NzbDrone.Core.Providers.Core;
@ -25,7 +28,10 @@ namespace NzbDrone.Api.Series
Get["/"] = x => AllSeries(); Get["/"] = x => AllSeries();
Get["/{id}"] = x => GetSeries((int)x.id); Get["/{id}"] = x => GetSeries((int)x.id);
Post["/"] = x => AddSeries(); 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() private Response AllSeries()
@ -59,9 +65,43 @@ namespace NzbDrone.Api.Series
return new Response { StatusCode = HttpStatusCode.Created }; return new Response { StatusCode = HttpStatusCode.Created };
} }
private Response UpdateSeries()
{
var request = Request.Body.FromJson<SeriesModel>();
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) 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 }; return new Response { StatusCode = HttpStatusCode.OK };
} }
} }

View File

@ -409,6 +409,8 @@
<Content Include="_backboneApp\JsLibraries\backbone.js" /> <Content Include="_backboneApp\JsLibraries\backbone.js" />
<Content Include="_backboneApp\JsLibraries\backbone.marionette.js" /> <Content Include="_backboneApp\JsLibraries\backbone.marionette.js" />
<Content Include="_backboneApp\AddSeries\addSeriesLayoutTemplate.html" /> <Content Include="_backboneApp\AddSeries\addSeriesLayoutTemplate.html" />
<Content Include="_backboneApp\Series\EditSeriesTemplate.html" />
<Content Include="_backboneApp\Series\EditSeriesView.js" />
<Content Include="_backboneApp\Shared\ModalRegion.js" /> <Content Include="_backboneApp\Shared\ModalRegion.js" />
<Content Include="_backboneApp\Series\DeleteSeriesTemplate.html" /> <Content Include="_backboneApp\Series\DeleteSeriesTemplate.html" />
<Content Include="_backboneApp\Series\Index\SeriesCollectionTemplate.html" /> <Content Include="_backboneApp\Series\Index\SeriesCollectionTemplate.html" />

View File

@ -16,8 +16,6 @@ NzbDrone.AddSeries.AddNewSeriesView = Backbone.Marionette.Layout.extend({
collection: new NzbDrone.AddSeries.SearchResultCollection(), collection: new NzbDrone.AddSeries.SearchResultCollection(),
initialize: function (options) { initialize: function (options) {
if (options.rootFolders === undefined) { if (options.rootFolders === undefined) {
throw 'rootFolder arg. is required.'; throw 'rootFolder arg. is required.';
@ -27,9 +25,8 @@ NzbDrone.AddSeries.AddNewSeriesView = Backbone.Marionette.Layout.extend({
throw 'qualityProfiles arg. is required.'; throw 'qualityProfiles arg. is required.';
} }
this.rootFoldersCollection = options.rootFolders; this.rootFoldersCollection = options.rootFolders;
this.qualityProfilesCollection = options.qualityProfiles; this.qualityProfileCollection = options.qualityProfiles;
}, },
onRender: function () { onRender: function () {
@ -44,7 +41,6 @@ NzbDrone.AddSeries.AddNewSeriesView = Backbone.Marionette.Layout.extend({
}); });
this.resultView = new NzbDrone.AddSeries.SearchResultView({ collection: this.collection }); this.resultView = new NzbDrone.AddSeries.SearchResultView({ collection: this.collection });
}, },
search: function (context) { search: function (context) {
@ -80,7 +76,7 @@ NzbDrone.AddSeries.AddNewSeriesView = Backbone.Marionette.Layout.extend({
resultUpdated: function (options, context) { resultUpdated: function (options, context) {
_.each(options.models, function (model) { _.each(options.models, function (model) {
model.set('rootFolders', context.rootFoldersCollection); model.set('rootFolders', context.rootFoldersCollection);
model.set('qualityProfiles', context.qualityProfilesCollection); model.set('qualityProfiles', context.qualityProfileCollection);
}); });
context.searchResult.show(context.resultView); context.searchResult.show(context.resultView);

View File

@ -31,6 +31,7 @@ NzbDrone.AddSeries.SearchItemView = Backbone.Marionette.ItemView.extend({
var quality = this.ui.qualityProfile.val(); var quality = this.ui.qualityProfile.val();
var rootFolderId = this.ui.rootFolder.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 rootPath = this.model.get('rootFolders').get(rootFolderId).get('path');
var path = rootPath + "\\" + title; var path = rootPath + "\\" + title;

View File

@ -26,8 +26,8 @@
top: 10%; top: 10%;
left: 50%; left: 50%;
z-index: @zindexModal; z-index: @zindexModal;
width: 560px; width: 660px;
margin-left: -280px; margin-left: -330px;
background-color: @white; background-color: @white;
border: 1px solid #999; border: 1px solid #999;
border: 1px solid rgba(0,0,0,.3); border: 1px solid rgba(0,0,0,.3);
@ -60,7 +60,7 @@
.modal-body { .modal-body {
position: relative; position: relative;
overflow-y: auto; overflow-y: auto;
max-height: 400px; max-height: 450px;
padding: 15px; padding: 15px;
} }
// Remove bottom margin if need be // Remove bottom margin if need be

View File

@ -9,7 +9,7 @@
body { body {
background: #191919 url(images/background.jpg) no-repeat right top; background: #191919 url(images/background.jpg) no-repeat right top;
font-size: 13px; font-size: 14px;
color: #3C3C3C; color: #3C3C3C;
background-attachment: fixed; background-attachment: fixed;
} }
@ -84,6 +84,7 @@ body {
line-height: 1em; line-height: 1em;
} }
/* Progress Bar Text */
.progress { .progress {
width: 125px; width: 125px;
position: relative; position: relative;
@ -106,4 +107,23 @@ body {
width: 125px; width: 125px;
font-size: 12px; font-size: 12px;
text-align: center; 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;
} }

View File

@ -1,10 +1,16 @@
<div class="modal-header"> <div class="modal-header">
<h2>Delete {{title}}</h2> <h3>Delete: {{title}}</h3>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<p>Are you sure you want to delete '{{title}}'? </p> <p>Are you sure you want to delete '{{title}}'?</p>
<div class="series-delete-files">
<label class="checkbox">
<input id="delete-from-disk" type="checkbox" value="true">
Delete all files from disk?
</label>
</div>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button class="btn" data-dismiss="modal">cancel</button> <button class="btn" data-dismiss="modal">cancel</button>
<button class="btn btn-danger x-confirm-delete">Ok</button> <button class="btn btn-danger x-confirm-delete">delete</button>
</div> </div>

View File

@ -21,7 +21,8 @@ NzbDrone.Series.DeleteSeriesView = Backbone.Marionette.ItemView.extend({
}, },
removeSeries: function () { 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.model.collection.remove(this.model);
this.$el.parent().modal('hide'); this.$el.parent().modal('hide');
}, },

View File

@ -0,0 +1,73 @@
<div class="modal-header">
<h3>Edit: {{title}}</h3>
</div>
<div class="modal-body">
<div class="form-horizontal">
<div class="control-group">
<label class="control-label" for="inputMonitored">Monitored</label>
<div class="controls">
<label class="checkbox">
<input type="checkbox" id ="inputMonitored" name="monitored">
Should NzbDrone download episodes for this series?
</label>
</div>
</div>
<div class="control-group">
<label class="control-label" for="inputSeasonFolder">Use Season Folder</label>
<div class="controls">
<label class="checkbox">
<input type="checkbox" id ="inputSeasonFolder" name="seasonFolder">
Should downloaded episodes be stored in season folders?
</label>
</div>
</div>
<div class="control-group">
<label class="control-label" for="inputQualityProfile">Quality Profile</label>
<div class="controls">
<select class="x-quality-profile" name="qualityProfileId">
{{#each qualityProfiles.models}}
<option value="{{id}}">{{attributes.name}}</option>
{{/each}}
</select>
<span class="help-inline">Which Quality Profile should NzbDrone use to download episodes?</span>
</div>
</div>
<div class="control-group">
<label class="control-label" for="inputPath">Path</label>
<div class="controls">
<input type="text" id="inputPath" placeholder="Path" name="path">
<span class="help-inline">Where should NzbDrone store episodes for this series?</span>
</div>
</div>
<div class="control-group">
<label class="control-label" for="inputBacklogSetting">Backlog Setting</label>
<div class="controls">
<select id="inputBacklogSetting" class="inputClass x-backlog-setting" name="backlogSetting">
<option value="0">Inherit</option>
<option value="1">Enable</option>
<option value="2">Disable</option>
</select>
<span class="help-inline">Should NzbDrone search for missing episodes every 30 days?</span>
</div>
</div>
<div class="control-group">
<label class="control-label" for="inputCustomStartDate">Custom Start Date</label>
<div class="controls">
<input type="date" id="inputCustomStartDate" name="customStartDate">
<span class="help-inline">Should NzbDrone only download episodes after your preferred start date?</span>
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-danger pull-left x-remove" >delete</button>
<button class="btn" data-dismiss="modal">cancel</button>
<button class="btn btn-primary x-save">save</button>
</div>

View File

@ -0,0 +1,52 @@
'use strict';
/*global NzbDrone, Backbone*/
/// <reference path="../app.js" />
/// <reference path="SeriesModel.js" />
/// <reference path="DeleteSeriesView.js" />
/// <reference path="../Quality/qualityProfileCollection.js" />
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);
}
});

View File

@ -8,8 +8,7 @@ NzbDrone.Series.IndexLayout = Backbone.Marionette.Layout.extend({
route: 'Series/index', route: 'Series/index',
ui: { ui: {
edit: '.edit-series',
delele: '.delete-series'
}, },
regions: { regions: {

View File

@ -7,4 +7,7 @@
<td> <td>
{{{formatProgress episodeFileCount episodeCount}}} {{{formatProgress episodeFileCount episodeCount}}}
<td> <td>
<td>Edit/<i class="icon-remove x-remove" title="Delete Series"></i></td> <td>
<i class="icon-cog x-edit" title="Edit Series"></i>
<i class="icon-remove x-remove" title="Delete Series"></i>
</td>

View File

@ -3,7 +3,9 @@
/// <reference path="../../app.js" /> /// <reference path="../../app.js" />
/// <reference path="../SeriesModel.js" /> /// <reference path="../SeriesModel.js" />
/// <reference path="../SeriesCollection.js" /> /// <reference path="../SeriesCollection.js" />
/// <reference path="../EditSeriesView.js" />
/// <reference path="../DeleteSeriesView.js" /> /// <reference path="../DeleteSeriesView.js" />
/// <reference path="../../Quality/qualityProfileCollection.js" />
NzbDrone.Series.Index.SeriesItemView = Backbone.Marionette.ItemView.extend({ NzbDrone.Series.Index.SeriesItemView = Backbone.Marionette.ItemView.extend({
template: 'Series/Index/SeriesItemTemplate', template: 'Series/Index/SeriesItemTemplate',
@ -14,25 +16,45 @@ NzbDrone.Series.Index.SeriesItemView = Backbone.Marionette.ItemView.extend({
}, },
events: { events: {
'click .x-remove': 'removeSeries', 'click .x-edit': 'editSeries',
'click .x-remove': 'removeSeries'
},
initialize: function(options) {
this.qualityProfileCollection = options.qualityProfiles;
}, },
onRender: function () { onRender: function () {
NzbDrone.ModelBinder.bind(this.model, this.el); NzbDrone.ModelBinder.bind(this.model, this.el);
}, },
removeSeries: function () { qualityProfileCollection: new NzbDrone.Quality.QualityProfileCollection(),
//this.model.destroy({ wait: true });
//this.model.collection.remove(this.model);
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 }); var view = new NzbDrone.Series.DeleteSeriesView({ model: this.model });
NzbDrone.modalRegion.show(view); NzbDrone.modalRegion.show(view);
}, },
onSave: function() {
alert("saved!");
}
}); });
NzbDrone.Series.Index.SeriesCollectionView = Backbone.Marionette.CompositeView.extend({ NzbDrone.Series.Index.SeriesCollectionView = Backbone.Marionette.CompositeView.extend({
itemView: NzbDrone.Series.Index.SeriesItemView, itemView: NzbDrone.Series.Index.SeriesItemView,
itemViewOptions: {},
template: 'Series/Index/SeriesCollectionTemplate', template: 'Series/Index/SeriesCollectionTemplate',
tagName: 'table', tagName: 'table',
className: 'table table-hover', className: 'table table-hover',
qualityProfileCollection: new NzbDrone.Quality.QualityProfileCollection(),
initialize: function() {
this.qualityProfileCollection.fetch();
this.itemViewOptions = { qualityProfiles: this.qualityProfileCollection };
}
}); });