Updated full calendar to 1.6.4

Calendar/Upcoming now update on grab/download events
Better use of backbone collection on calendar
New: Calendar will auto refresh when episodes are grabbed and downloaded
This commit is contained in:
Mark McDowall 2013-11-30 02:53:53 -08:00
parent 26495aaa4b
commit 00717a638a
13 changed files with 2307 additions and 1502 deletions

View File

@ -3,25 +3,35 @@ using System.Collections.Generic;
using System.Linq;
using NzbDrone.Api.Episodes;
using NzbDrone.Api.Extensions;
using NzbDrone.Api.Mapping;
using NzbDrone.Core.Datastore.Events;
using NzbDrone.Core.Download;
using NzbDrone.Core.MediaFiles.Events;
using NzbDrone.Core.Messaging.Commands;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Tv;
namespace NzbDrone.Api.Calendar
{
public class CalendarModule : NzbDroneRestModule<EpisodeResource>
public class CalendarModule : NzbDroneRestModuleWithSignalR<EpisodeResource, Episode>,
IHandle<EpisodeGrabbedEvent>,
IHandle<EpisodeDownloadedEvent>
{
private readonly IEpisodeService _episodeService;
private readonly SeriesRepository _seriesRepository;
public CalendarModule(IEpisodeService episodeService, SeriesRepository seriesRepository)
: base("/calendar")
public CalendarModule(ICommandExecutor commandExecutor,
IEpisodeService episodeService,
SeriesRepository seriesRepository)
: base(commandExecutor, "calendar")
{
_episodeService = episodeService;
_seriesRepository = seriesRepository;
GetResourceAll = GetPaged;
GetResourceAll = GetCalendar;
}
private List<EpisodeResource> GetPaged()
private List<EpisodeResource> GetCalendar()
{
var start = DateTime.Today;
var end = DateTime.Today.AddDays(2);
@ -37,5 +47,24 @@ namespace NzbDrone.Api.Calendar
return resources.OrderBy(e => e.AirDate).ToList();
}
public void Handle(EpisodeGrabbedEvent message)
{
foreach (var episode in message.Episode.Episodes)
{
var resource = episode.InjectTo<EpisodeResource>();
resource.Downloading = true;
BroadcastResourceChange(ModelAction.Updated, resource);
}
}
public void Handle(EpisodeDownloadedEvent message)
{
foreach (var episode in message.Episode.Episodes)
{
BroadcastResourceChange(ModelAction.Updated, episode.Id);
}
}
}
}

View File

@ -2,10 +2,9 @@
define(
[
'marionette',
'Calendar/UpcomingCollection',
'Calendar/UpcomingCollectionView',
'Calendar/CalendarView',
], function (Marionette, UpcomingCollection, UpcomingCollectionView, CalendarView) {
'Calendar/CalendarView'
], function (Marionette, UpcomingCollectionView, CalendarView) {
return Marionette.Layout.extend({
template: 'Calendar/CalendarLayoutTemplate',
@ -14,20 +13,13 @@ define(
calendar: '#x-calendar'
},
initialize: function () {
this.upcomingCollection = new UpcomingCollection();
this.upcomingCollection.fetch();
},
onShow: function () {
this._showUpcoming();
this._showCalendar();
},
_showUpcoming: function () {
this.upcoming.show(new UpcomingCollectionView({
collection: this.upcomingCollection
}));
this.upcoming.show(new UpcomingCollectionView());
},
_showCalendar: function () {

View File

@ -9,6 +9,7 @@
<ul class='legend-labels'>
<li><span class="primary" title="Episode hasn't aired yet"></span>Unaired</li>
<li><span class="warning" title="Episode is currently airing"></span>On Air</li>
<li><span class="purple" title="Episode is currently downloading"></span>Downloading</li>
<li><span class="danger" title="Episode file has not been found"></span>Missing</li>
<li><span class="success" title="Episode was downloaded and sorted"></span>Downloaded</li>
</ul>

View File

@ -7,14 +7,17 @@ define(
'moment',
'Calendar/Collection',
'System/StatusModel',
'History/Queue/QueueCollection',
'Mixins/backbone.signalr.mixin',
'fullcalendar'
], function (vent, Marionette, moment, CalendarCollection, StatusModel) {
], function (vent, Marionette, moment, CalendarCollection, StatusModel, QueueCollection) {
var _instance;
return Marionette.ItemView.extend({
initialize: function () {
this.collection = new CalendarCollection();
this.collection = new CalendarCollection().bindSignalR();
this.listenTo(this.collection, 'change', this._reloadCalendarEvents);
},
render : function () {
@ -36,7 +39,7 @@ define(
prev: '<i class="icon-arrow-left"></i>',
next: '<i class="icon-arrow-right"></i>'
},
events : this.getEvents,
viewRender : this._getEvents,
eventRender : function (event, element) {
self.$(element).addClass(event.statusLevel);
self.$(element).children('.fc-event-inner').addClass(event.statusLevel);
@ -53,43 +56,50 @@ define(
this.$('.fc-button-today').click();
},
getEvents: function (start, end, callback) {
var startDate = moment(start).toISOString();
var endDate = moment(end).toISOString();
_getEvents: function (view) {
var start = moment(view.visStart).toISOString();
var end = moment(view.visEnd).toISOString();
_instance.$el.fullCalendar('removeEvents');
_instance.collection.fetch({
data : { start: startDate, end: endDate },
success: function (calendarCollection) {
calendarCollection.each(function (element) {
var episodeTitle = element.get('title');
var seriesTitle = element.get('series').title;
var start = element.get('airDateUtc');
var runtime = element.get('series').runtime;
var end = moment(start).add('minutes', runtime).toISOString();
element.set({
title : seriesTitle,
episodeTitle: episodeTitle,
start : start,
end : end,
allDay : false
});
element.set('statusLevel', _instance.getStatusLevel(element));
element.set('model', element);
});
callback(calendarCollection.toJSON());
data : { start: start, end: end },
success: function (collection) {
_instance._setEventData(collection);
}
});
},
getStatusLevel: function (element) {
_setEventData: function (collection) {
var events = [];
collection.each(function (model) {
var seriesTitle = model.get('series').title;
var start = model.get('airDateUtc');
var runtime = model.get('series').runtime;
var end = moment(start).add('minutes', runtime).toISOString();
var event = {
title : seriesTitle,
start : start,
end : end,
allDay : false,
statusLevel : _instance._getStatusLevel(model, end),
model : model
};
events.push(event);
});
_instance.$el.fullCalendar('addEventSource', events);
},
_getStatusLevel: function (element, endTime) {
var hasFile = element.get('hasFile');
var downloading = QueueCollection.findEpisode(element.get('id')) || element.get('downloading');
var currentTime = moment();
var start = moment(element.get('airDateUtc'));
var end = moment(element.get('end'));
var end = moment(endTime);
var statusLevel = 'primary';
@ -97,6 +107,10 @@ define(
statusLevel = 'success';
}
if (downloading) {
statusLevel = 'purple';
}
else if (currentTime.isAfter(start) && currentTime.isBefore(end)) {
statusLevel = 'warning';
}
@ -105,13 +119,17 @@ define(
statusLevel = 'danger';
}
var test = currentTime.startOf('day').format('LLLL');
if (end.isBefore(currentTime.startOf('day'))) {
statusLevel += ' past';
}
return statusLevel;
},
_reloadCalendarEvents: function () {
window.alert('collection changed');
this.$el.fullCalendar('removeEvents');
this._setEventData(this.collection);
}
});
});

View File

@ -3,9 +3,22 @@
define(
[
'marionette',
'Calendar/UpcomingItemView'
], function (Marionette, UpcomingItemView) {
'Calendar/UpcomingCollection',
'Calendar/UpcomingItemView',
'Mixins/backbone.signalr.mixin'
], function (Marionette, UpcomingCollection, UpcomingItemView) {
return Marionette.CollectionView.extend({
itemView: UpcomingItemView
itemView: UpcomingItemView,
initialize: function () {
this.collection = new UpcomingCollection().bindSignalR();
this.collection.fetch();
this.listenTo(this.collection, 'change', this._refresh);
},
_refresh: function () {
this.render();
}
});
});

View File

@ -3,12 +3,11 @@
define(
[
'reqres',
'underscore',
'Cells/NzbDroneCell',
'History/Queue/QueueCollection',
'moment',
'Shared/FormatHelpers'
], function (reqres, _, NzbDroneCell, QueueCollection, Moment, FormatHelpers) {
], function (reqres, NzbDroneCell, QueueCollection, Moment, FormatHelpers) {
return NzbDroneCell.extend({
className: 'episode-status-cell',
@ -56,10 +55,7 @@ define(
else {
var model = this.model;
var downloading = _.find(QueueCollection.models, function (queueModel) {
return queueModel.get('episode').id === model.get('id');
});
var downloading = QueueCollection.findEpisode(model.get('id'));
if (downloading || this.model.get('downloading')) {
icon = 'icon-nd-downloading';

View File

@ -0,0 +1,11 @@
.fc-view {
overflow: visible;
}
.fc-event-title {
padding: 0 2px;
display: block;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}

View File

@ -1,5 +1,5 @@
/*!
* FullCalendar v1.6.1 Stylesheet
/*!
* FullCalendar v1.6.4 Stylesheet
* Docs & License: http://arshaw.com/fullcalendar/
* (c) 2013 Adam Shaw
*/
@ -102,11 +102,12 @@ html .fc,
.fc-content {
clear: both;
zoom: 1; /* for IE7, gives accurate coordinates for [un]freezeContentHeight */
}
.fc-view {
width: 100%; /* needed for view switching (when view is absolute) */
/*overflow: hidden;*/
width: 100%;
overflow: hidden;
}
@ -232,7 +233,8 @@ html .fc,
.fc-state-down,
.fc-state-active {
background : #cccccc none;
background-color: #cccccc;
background-image: none;
outline: 0;
box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);
}
@ -250,6 +252,15 @@ html .fc,
/* Global Event Styles
------------------------------------------------------------------------*/
.fc-event-container > * {
z-index: 8;
}
.fc-event-container > .ui-draggable-dragging,
.fc-event-container > .ui-resizable-resizing {
z-index: 9;
}
.fc-event {
border: 1px solid #3a87ad; /* default BORDER color */
background-color: #3a87ad; /* default BACKGROUND color */
@ -279,14 +290,7 @@ a.fc-event,
.fc-event-time,
.fc-event-title {
padding: 0 2px;
display: block;
}
.fc-event-title {
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
padding: 0 1px;
}
.fc .ui-resizable-handle {

View File

@ -1,3 +1,4 @@
@import "Overrides/bootstrap";
@import "Overrides/browser";
@import "Overrides/bootstrap.toggle-switch";
@import "Overrides/fullcalendar";

View File

@ -4,11 +4,7 @@
<h3>
<i class="icon-bookmark x-episode-monitored" title="Toggle monitored status" />
{{#if episodeTitle}}
{{title}} - {{EpisodeNumber}} - {{episodeTitle}}
{{else}}
{{series.title}} - {{EpisodeNumber}} - {{title}}
{{/if}}
</h3>
</div>

View File

@ -20,6 +20,7 @@ define(
Handlebars.registerHelper('StatusLevel', function () {
var hasFile = this.hasFile;
var downloading = require('History/Queue/QueueCollection').findEpisode(this.id) || this.downloading;
var currentTime = Moment();
var start = Moment(this.airDateUtc);
var end = Moment(this.end);
@ -28,6 +29,10 @@ define(
return 'success';
}
if (downloading) {
return 'purple';
}
if (currentTime.isAfter(start) && currentTime.isBefore(end)) {
return 'warning';
}

View File

@ -1,13 +1,20 @@
'use strict';
define(
[
'underscore',
'backbone',
'History/Queue/QueueModel',
'Mixins/backbone.signalr.mixin'
], function (Backbone, QueueModel) {
], function (_, Backbone, QueueModel) {
var QueueCollection = Backbone.Collection.extend({
url : window.NzbDrone.ApiRoot + '/queue',
model: QueueModel
model: QueueModel,
findEpisode: function (episodeId) {
return _.find(this.models, function (queueModel) {
return queueModel.get('episode').id === episodeId;
});
}
});
var collection = new QueueCollection().bindSignalR();

File diff suppressed because it is too large Load Diff