Added missing view

This commit is contained in:
Mark McDowall 2013-03-20 20:02:57 -07:00
parent 9ff7aa1bf7
commit 05c7b4f4ef
17 changed files with 205 additions and 6 deletions

View File

@ -2,6 +2,7 @@
using AutoMapper; using AutoMapper;
using NzbDrone.Api.Calendar; using NzbDrone.Api.Calendar;
using NzbDrone.Api.Episodes; using NzbDrone.Api.Episodes;
using NzbDrone.Api.Missing;
using NzbDrone.Api.QualityProfiles; using NzbDrone.Api.QualityProfiles;
using NzbDrone.Api.QualityType; using NzbDrone.Api.QualityType;
using NzbDrone.Api.Resolvers; using NzbDrone.Api.Resolvers;
@ -52,6 +53,11 @@ namespace NzbDrone.Api
//Episode //Episode
Mapper.CreateMap<Episode, EpisodeResource>(); Mapper.CreateMap<Episode, EpisodeResource>();
//Missing
Mapper.CreateMap<Episode, MissingResource>()
.ForMember(dest => dest.SeriesTitle, opt => opt.MapFrom(src => src.Series.Title))
.ForMember(dest => dest.EpisodeTitle, opt => opt.MapFrom(src => src.Title));
} }
} }
} }

View File

@ -0,0 +1,31 @@
using System;
using System.Collections.Generic;
using System.Linq;
using AutoMapper;
using Nancy;
using NzbDrone.Api.Extensions;
using NzbDrone.Core.Tv;
namespace NzbDrone.Api.Missing
{
public class MissingModule : NzbDroneApiModule
{
private readonly EpisodeService _episodeService;
public MissingModule(EpisodeService episodeService)
: base("/missing")
{
_episodeService = episodeService;
Get["/"] = x => GetMissingEpisodes();
}
private Response GetMissingEpisodes()
{
bool includeSpecials;
Boolean.TryParse(PrimitiveExtensions.ToNullSafeString(Request.Query.IncludeSpecials), out includeSpecials);
var episodes = _episodeService.EpisodesWithoutFiles(includeSpecials);
return Mapper.Map<List<Episode>, List<MissingResource>>(episodes).AsResponse();
}
}
}

View File

@ -0,0 +1,17 @@
using System;
using System.Linq;
namespace NzbDrone.Api.Missing
{
public class MissingResource
{
public Int32 SeriesId { get; set; }
public String SeriesTitle { get; set; }
public Int32 EpisodeId { get; set; }
public String EpisodeTitle { get; set; }
public Int32 SeasonNumber { get; set; }
public Int32 EpisodeNumber { get; set; }
public DateTime? AirDate { get; set; }
public String Overview { get; set; }
}
}

View File

@ -120,6 +120,8 @@
<Compile Include="FrontendModule\IndexModule.cs" /> <Compile Include="FrontendModule\IndexModule.cs" />
<Compile Include="FrontendModule\BootstrapModule.cs" /> <Compile Include="FrontendModule\BootstrapModule.cs" />
<Compile Include="FrontendModule\LessService.cs" /> <Compile Include="FrontendModule\LessService.cs" />
<Compile Include="Missing\MissingResource.cs" />
<Compile Include="Missing\MissingModule.cs" />
<Compile Include="Resolvers\EndTimeResolver.cs" /> <Compile Include="Resolvers\EndTimeResolver.cs" />
<Compile Include="Resolvers\NextAiringResolver.cs" /> <Compile Include="Resolvers\NextAiringResolver.cs" />
<Compile Include="Resolvers\NullableDatetimeToString.cs" /> <Compile Include="Resolvers\NullableDatetimeToString.cs" />

View File

@ -3,7 +3,7 @@
'Calendar/CalendarCollectionView', 'Shared/NotificationView', 'Calendar/CalendarCollectionView', 'Shared/NotificationView',
'Shared/NotFoundView', 'MainMenuView', 'HeaderView', 'Shared/NotFoundView', 'MainMenuView', 'HeaderView',
'Series/Details/SeriesDetailsView', 'Series/EpisodeCollection', 'Series/Details/SeriesDetailsView', 'Series/EpisodeCollection',
'Settings/SettingsLayout'], 'Settings/SettingsLayout', 'Missing/MissingCollectionView'],
function (app, modalRegion) { function (app, modalRegion) {
var controller = Backbone.Marionette.Controller.extend({ var controller = Backbone.Marionette.Controller.extend({
@ -48,12 +48,23 @@
var settingsModel = new NzbDrone.Settings.SettingsModel(); var settingsModel = new NzbDrone.Settings.SettingsModel();
settingsModel.fetch({ settingsModel.fetch({
success: function(settings){ success: function(settings) {
NzbDrone.mainRegion.show(new NzbDrone.Settings.SettingsLayout(this, action, query, settings)); NzbDrone.mainRegion.show(new NzbDrone.Settings.SettingsLayout(this, action, query, settings));
} }
}); });
}, },
missing: function(action, query) {
this.setTitle('Missing');
var missingCollection = new NzbDrone.Missing.MissingCollection();
missingCollection.fetch({
success: function(missing) {
NzbDrone.mainRegion.show(new NzbDrone.Missing.MissingCollectionView(this, action, query, missing));
}
})
},
notFound: function () { notFound: function () {
this.setTitle('Not Found'); this.setTitle('Not Found');
NzbDrone.mainRegion.show(new NzbDrone.Shared.NotFoundView(this)); NzbDrone.mainRegion.show(new NzbDrone.Shared.NotFoundView(this));

View File

@ -0,0 +1,9 @@
define(['app', 'Missing/MissingModel'], function () {
NzbDrone.Missing.MissingCollection = Backbone.Collection.extend({
url: NzbDrone.Constants.ApiRoot + '/missing',
model: NzbDrone.Missing.MissingModel,
comparator: function(model) {
return model.get('airDate');
}
});
});

View File

@ -0,0 +1,12 @@
<table class="table table-hover x-missing-table">
<thead>
<tr>
<th>Series Title</th>
<th>Episode</th>
<th>Episode Title</th>
<th>Air Date</th>
<th></th>
</tr>
</thead>
<tbody></tbody>
</table>

View File

@ -0,0 +1,75 @@
'use strict';
define(['app', 'Missing/MissingItemView'], function (app) {
NzbDrone.Missing.MissingCollectionView = Backbone.Marionette.CompositeView.extend({
itemView: NzbDrone.Missing.MissingItemView,
itemViewContainer: 'tbody',
template: 'Missing/MissingCollectionTemplate',
ui:{
table : '.x-missing-table'
},
initialize: function (context, action, query, collection) {
this.collection = collection;
},
onCompositeCollectionRendered: function() {
this.ui.table.trigger('update');
if(!this.tableSorter && this.collection.length > 0)
{
this.tableSorter = this.ui.table.tablesorter({
textExtraction: function (node) {
return node.innerHTML;
},
sortList: [[3,1]],
headers: {
0: {
sorter: 'innerHtml'
},
1: {
sorter: false
},
2: {
sorter: false
},
3: {
sorter: 'date'
},
4: {
sorter: false
}
}
});
//Todo: We should extract these common settings out
this.ui.table.find('th.header').each(function(){
$(this).append('<i class="icon-sort pull-right">');
});
this.ui.table.bind("sortEnd", function() {
$(this).find('th.header i').each(function(){
$(this).remove();
});
$(this).find('th.header').each(function () {
if (!$(this).hasClass('headerSortDown') && !$(this).hasClass('headerSortUp'))
$(this).append('<i class="icon-sort pull-right">');
});
$(this).find('th.headerSortDown').each(function(){
$(this).append('<i class="icon-sort-up pull-right">');
});
$(this).find('th.headerSortUp').each(function(){
$(this).append('<i class="icon-sort-down pull-right">');
});
});
}
else
{
this.ui.table.trigger('update');
}
}
});
});

View File

@ -0,0 +1,5 @@
<td><a href="/series/details/{{seriesId}}">{{seriesTitle}}</a></td>
<td>{{seasonNumber}}x{{paddedEpisodeNumber}}</td>
<td name="episodeTitle"></td>
<td><span title="{{formatedDateString}}" data-date="{{airDate}}">{{bestDateString}}</span></td>
<td><i class="icon-search x-search" title="Search for Episode"></i></td>

View File

@ -0,0 +1,16 @@
'use strict';
define([
'app',
'Missing/MissingCollection'
], function () {
NzbDrone.Missing.MissingItemView = Backbone.Marionette.ItemView.extend({
template: 'Missing/MissingItemTemplate',
tagName: 'tr',
onRender: function () {
NzbDrone.ModelBinder.bind(this.model, this.el);
}
})
})

View File

@ -0,0 +1,12 @@
define(['app'], function (app) {
NzbDrone.Missing.MissingModel = Backbone.Model.extend({
mutators: {
bestDateString: function () {
return bestDateString(this.get('airDate'));
},
paddedEpisodeNumber: function(){
return this.get('episodeNumber');
}
}
});
});

View File

@ -6,7 +6,7 @@ function bestDateString(sourceDate){
if (date.isYesterday()) return 'Yesterday'; if (date.isYesterday()) return 'Yesterday';
if (date.isToday()) return 'Today'; if (date.isToday()) return 'Today';
if (date.isTomorrow()) return 'Tomorrow'; if (date.isTomorrow()) return 'Tomorrow';
if (date.isBefore(Date.create().addDays(7))) return date.format('{Weekday}'); if (date.isAfter(Date.create('tomorrow')) && date.isBefore(Date.create().addDays(7))) return date.format('{Weekday}');
return date.format('{MM}/{dd}/{yyyy}'); return date.format('{MM}/{dd}/{yyyy}');
} }

View File

@ -15,6 +15,7 @@
'calendar': 'calendar', 'calendar': 'calendar',
'settings': 'settings', 'settings': 'settings',
'settings/:action(/:query)': 'settings', 'settings/:action(/:query)': 'settings',
'missing': 'missing',
':whatever': 'notFound' ':whatever': 'notFound'
} }
}); });

View File

@ -57,6 +57,7 @@ define(['app', 'Quality/QualityProfileCollection', 'Series/Index/SeriesItemView'
} }
}); });
//Todo: We should extract these common settings out
this.ui.table.find('th.header').each(function(){ this.ui.table.find('th.header').each(function(){
$(this).append('<i class="icon-sort pull-right">'); $(this).append('<i class="icon-sort pull-right">');
}); });
@ -72,11 +73,11 @@ define(['app', 'Quality/QualityProfileCollection', 'Series/Index/SeriesItemView'
}); });
$(this).find('th.headerSortDown').each(function(){ $(this).find('th.headerSortDown').each(function(){
$(this).append('<i class="icon-sort-down pull-right">'); $(this).append('<i class="icon-sort-up pull-right">');
}); });
$(this).find('th.headerSortUp').each(function(){ $(this).find('th.headerSortUp').each(function(){
$(this).append('<i class="icon-sort-up pull-right">'); $(this).append('<i class="icon-sort-down pull-right">');
}); });
}); });
} }

View File

@ -52,6 +52,7 @@ define('app', function () {
window.NzbDrone.Settings.Notifications = {}; window.NzbDrone.Settings.Notifications = {};
window.NzbDrone.Settings.System = {}; window.NzbDrone.Settings.System = {};
window.NzbDrone.Settings.Misc = {}; window.NzbDrone.Settings.Misc = {};
window.NzbDrone.Missing = {};
window.NzbDrone.Events = { window.NzbDrone.Events = {
OpenModalDialog :'openModal', OpenModalDialog :'openModal',

View File

@ -61,7 +61,7 @@ namespace NzbDrone.Core.Tvdb
[XmlElement] [XmlElement]
public int EpisodeNumber { get; set; } public int EpisodeNumber { get; set; }
[XmlIgnore] [XmlElement]
public DateTime FirstAired { get; set; } public DateTime FirstAired { get; set; }
[XmlElement] [XmlElement]