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:
parent
26495aaa4b
commit
00717a638a
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 () {
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
.fc-view {
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.fc-event-title {
|
||||
padding: 0 2px;
|
||||
display: block;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
@import "Overrides/bootstrap";
|
||||
@import "Overrides/browser";
|
||||
@import "Overrides/bootstrap.toggle-switch";
|
||||
@import "Overrides/fullcalendar";
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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';
|
||||
}
|
||||
|
|
|
@ -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
Loading…
Reference in New Issue