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 System.Linq;
|
||||||
using NzbDrone.Api.Episodes;
|
using NzbDrone.Api.Episodes;
|
||||||
using NzbDrone.Api.Extensions;
|
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;
|
using NzbDrone.Core.Tv;
|
||||||
|
|
||||||
namespace NzbDrone.Api.Calendar
|
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 IEpisodeService _episodeService;
|
||||||
private readonly SeriesRepository _seriesRepository;
|
private readonly SeriesRepository _seriesRepository;
|
||||||
|
|
||||||
public CalendarModule(IEpisodeService episodeService, SeriesRepository seriesRepository)
|
public CalendarModule(ICommandExecutor commandExecutor,
|
||||||
: base("/calendar")
|
IEpisodeService episodeService,
|
||||||
|
SeriesRepository seriesRepository)
|
||||||
|
: base(commandExecutor, "calendar")
|
||||||
{
|
{
|
||||||
_episodeService = episodeService;
|
_episodeService = episodeService;
|
||||||
_seriesRepository = seriesRepository;
|
_seriesRepository = seriesRepository;
|
||||||
|
|
||||||
GetResourceAll = GetPaged;
|
GetResourceAll = GetCalendar;
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<EpisodeResource> GetPaged()
|
private List<EpisodeResource> GetCalendar()
|
||||||
{
|
{
|
||||||
var start = DateTime.Today;
|
var start = DateTime.Today;
|
||||||
var end = DateTime.Today.AddDays(2);
|
var end = DateTime.Today.AddDays(2);
|
||||||
|
@ -37,5 +47,24 @@ namespace NzbDrone.Api.Calendar
|
||||||
|
|
||||||
return resources.OrderBy(e => e.AirDate).ToList();
|
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(
|
define(
|
||||||
[
|
[
|
||||||
'marionette',
|
'marionette',
|
||||||
'Calendar/UpcomingCollection',
|
|
||||||
'Calendar/UpcomingCollectionView',
|
'Calendar/UpcomingCollectionView',
|
||||||
'Calendar/CalendarView',
|
'Calendar/CalendarView'
|
||||||
], function (Marionette, UpcomingCollection, UpcomingCollectionView, CalendarView) {
|
], function (Marionette, UpcomingCollectionView, CalendarView) {
|
||||||
return Marionette.Layout.extend({
|
return Marionette.Layout.extend({
|
||||||
template: 'Calendar/CalendarLayoutTemplate',
|
template: 'Calendar/CalendarLayoutTemplate',
|
||||||
|
|
||||||
|
@ -14,20 +13,13 @@ define(
|
||||||
calendar: '#x-calendar'
|
calendar: '#x-calendar'
|
||||||
},
|
},
|
||||||
|
|
||||||
initialize: function () {
|
|
||||||
this.upcomingCollection = new UpcomingCollection();
|
|
||||||
this.upcomingCollection.fetch();
|
|
||||||
},
|
|
||||||
|
|
||||||
onShow: function () {
|
onShow: function () {
|
||||||
this._showUpcoming();
|
this._showUpcoming();
|
||||||
this._showCalendar();
|
this._showCalendar();
|
||||||
},
|
},
|
||||||
|
|
||||||
_showUpcoming: function () {
|
_showUpcoming: function () {
|
||||||
this.upcoming.show(new UpcomingCollectionView({
|
this.upcoming.show(new UpcomingCollectionView());
|
||||||
collection: this.upcomingCollection
|
|
||||||
}));
|
|
||||||
},
|
},
|
||||||
|
|
||||||
_showCalendar: function () {
|
_showCalendar: function () {
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
<ul class='legend-labels'>
|
<ul class='legend-labels'>
|
||||||
<li><span class="primary" title="Episode hasn't aired yet"></span>Unaired</li>
|
<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="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="danger" title="Episode file has not been found"></span>Missing</li>
|
||||||
<li><span class="success" title="Episode was downloaded and sorted"></span>Downloaded</li>
|
<li><span class="success" title="Episode was downloaded and sorted"></span>Downloaded</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
|
@ -7,14 +7,17 @@ define(
|
||||||
'moment',
|
'moment',
|
||||||
'Calendar/Collection',
|
'Calendar/Collection',
|
||||||
'System/StatusModel',
|
'System/StatusModel',
|
||||||
|
'History/Queue/QueueCollection',
|
||||||
|
'Mixins/backbone.signalr.mixin',
|
||||||
'fullcalendar'
|
'fullcalendar'
|
||||||
], function (vent, Marionette, moment, CalendarCollection, StatusModel) {
|
], function (vent, Marionette, moment, CalendarCollection, StatusModel, QueueCollection) {
|
||||||
|
|
||||||
var _instance;
|
var _instance;
|
||||||
|
|
||||||
return Marionette.ItemView.extend({
|
return Marionette.ItemView.extend({
|
||||||
initialize: function () {
|
initialize: function () {
|
||||||
this.collection = new CalendarCollection();
|
this.collection = new CalendarCollection().bindSignalR();
|
||||||
|
this.listenTo(this.collection, 'change', this._reloadCalendarEvents);
|
||||||
},
|
},
|
||||||
render : function () {
|
render : function () {
|
||||||
|
|
||||||
|
@ -36,7 +39,7 @@ define(
|
||||||
prev: '<i class="icon-arrow-left"></i>',
|
prev: '<i class="icon-arrow-left"></i>',
|
||||||
next: '<i class="icon-arrow-right"></i>'
|
next: '<i class="icon-arrow-right"></i>'
|
||||||
},
|
},
|
||||||
events : this.getEvents,
|
viewRender : this._getEvents,
|
||||||
eventRender : function (event, element) {
|
eventRender : function (event, element) {
|
||||||
self.$(element).addClass(event.statusLevel);
|
self.$(element).addClass(event.statusLevel);
|
||||||
self.$(element).children('.fc-event-inner').addClass(event.statusLevel);
|
self.$(element).children('.fc-event-inner').addClass(event.statusLevel);
|
||||||
|
@ -53,43 +56,50 @@ define(
|
||||||
this.$('.fc-button-today').click();
|
this.$('.fc-button-today').click();
|
||||||
},
|
},
|
||||||
|
|
||||||
getEvents: function (start, end, callback) {
|
_getEvents: function (view) {
|
||||||
var startDate = moment(start).toISOString();
|
var start = moment(view.visStart).toISOString();
|
||||||
var endDate = moment(end).toISOString();
|
var end = moment(view.visEnd).toISOString();
|
||||||
|
|
||||||
|
_instance.$el.fullCalendar('removeEvents');
|
||||||
|
|
||||||
_instance.collection.fetch({
|
_instance.collection.fetch({
|
||||||
data : { start: startDate, end: endDate },
|
data : { start: start, end: end },
|
||||||
success: function (calendarCollection) {
|
success: function (collection) {
|
||||||
calendarCollection.each(function (element) {
|
_instance._setEventData(collection);
|
||||||
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());
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
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 hasFile = element.get('hasFile');
|
||||||
|
var downloading = QueueCollection.findEpisode(element.get('id')) || element.get('downloading');
|
||||||
var currentTime = moment();
|
var currentTime = moment();
|
||||||
var start = moment(element.get('airDateUtc'));
|
var start = moment(element.get('airDateUtc'));
|
||||||
var end = moment(element.get('end'));
|
var end = moment(endTime);
|
||||||
|
|
||||||
var statusLevel = 'primary';
|
var statusLevel = 'primary';
|
||||||
|
|
||||||
|
@ -97,6 +107,10 @@ define(
|
||||||
statusLevel = 'success';
|
statusLevel = 'success';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (downloading) {
|
||||||
|
statusLevel = 'purple';
|
||||||
|
}
|
||||||
|
|
||||||
else if (currentTime.isAfter(start) && currentTime.isBefore(end)) {
|
else if (currentTime.isAfter(start) && currentTime.isBefore(end)) {
|
||||||
statusLevel = 'warning';
|
statusLevel = 'warning';
|
||||||
}
|
}
|
||||||
|
@ -105,13 +119,17 @@ define(
|
||||||
statusLevel = 'danger';
|
statusLevel = 'danger';
|
||||||
}
|
}
|
||||||
|
|
||||||
var test = currentTime.startOf('day').format('LLLL');
|
|
||||||
|
|
||||||
if (end.isBefore(currentTime.startOf('day'))) {
|
if (end.isBefore(currentTime.startOf('day'))) {
|
||||||
statusLevel += ' past';
|
statusLevel += ' past';
|
||||||
}
|
}
|
||||||
|
|
||||||
return statusLevel;
|
return statusLevel;
|
||||||
|
},
|
||||||
|
|
||||||
|
_reloadCalendarEvents: function () {
|
||||||
|
window.alert('collection changed');
|
||||||
|
this.$el.fullCalendar('removeEvents');
|
||||||
|
this._setEventData(this.collection);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -3,9 +3,22 @@
|
||||||
define(
|
define(
|
||||||
[
|
[
|
||||||
'marionette',
|
'marionette',
|
||||||
'Calendar/UpcomingItemView'
|
'Calendar/UpcomingCollection',
|
||||||
], function (Marionette, UpcomingItemView) {
|
'Calendar/UpcomingItemView',
|
||||||
|
'Mixins/backbone.signalr.mixin'
|
||||||
|
], function (Marionette, UpcomingCollection, UpcomingItemView) {
|
||||||
return Marionette.CollectionView.extend({
|
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(
|
define(
|
||||||
[
|
[
|
||||||
'reqres',
|
'reqres',
|
||||||
'underscore',
|
|
||||||
'Cells/NzbDroneCell',
|
'Cells/NzbDroneCell',
|
||||||
'History/Queue/QueueCollection',
|
'History/Queue/QueueCollection',
|
||||||
'moment',
|
'moment',
|
||||||
'Shared/FormatHelpers'
|
'Shared/FormatHelpers'
|
||||||
], function (reqres, _, NzbDroneCell, QueueCollection, Moment, FormatHelpers) {
|
], function (reqres, NzbDroneCell, QueueCollection, Moment, FormatHelpers) {
|
||||||
return NzbDroneCell.extend({
|
return NzbDroneCell.extend({
|
||||||
|
|
||||||
className: 'episode-status-cell',
|
className: 'episode-status-cell',
|
||||||
|
@ -56,10 +55,7 @@ define(
|
||||||
|
|
||||||
else {
|
else {
|
||||||
var model = this.model;
|
var model = this.model;
|
||||||
|
var downloading = QueueCollection.findEpisode(model.get('id'));
|
||||||
var downloading = _.find(QueueCollection.models, function (queueModel) {
|
|
||||||
return queueModel.get('episode').id === model.get('id');
|
|
||||||
});
|
|
||||||
|
|
||||||
if (downloading || this.model.get('downloading')) {
|
if (downloading || this.model.get('downloading')) {
|
||||||
icon = 'icon-nd-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/
|
* Docs & License: http://arshaw.com/fullcalendar/
|
||||||
* (c) 2013 Adam Shaw
|
* (c) 2013 Adam Shaw
|
||||||
*/
|
*/
|
||||||
|
@ -102,11 +102,12 @@ html .fc,
|
||||||
|
|
||||||
.fc-content {
|
.fc-content {
|
||||||
clear: both;
|
clear: both;
|
||||||
|
zoom: 1; /* for IE7, gives accurate coordinates for [un]freezeContentHeight */
|
||||||
}
|
}
|
||||||
|
|
||||||
.fc-view {
|
.fc-view {
|
||||||
width: 100%; /* needed for view switching (when view is absolute) */
|
width: 100%;
|
||||||
/*overflow: hidden;*/
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -232,8 +233,9 @@ html .fc,
|
||||||
|
|
||||||
.fc-state-down,
|
.fc-state-down,
|
||||||
.fc-state-active {
|
.fc-state-active {
|
||||||
background : #cccccc none;
|
background-color: #cccccc;
|
||||||
outline: 0;
|
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);
|
box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -249,6 +251,15 @@ html .fc,
|
||||||
|
|
||||||
/* Global Event Styles
|
/* 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 {
|
.fc-event {
|
||||||
border: 1px solid #3a87ad; /* default BORDER color */
|
border: 1px solid #3a87ad; /* default BORDER color */
|
||||||
|
@ -279,15 +290,8 @@ a.fc-event,
|
||||||
|
|
||||||
.fc-event-time,
|
.fc-event-time,
|
||||||
.fc-event-title {
|
.fc-event-title {
|
||||||
padding: 0 2px;
|
padding: 0 1px;
|
||||||
display: block;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.fc-event-title {
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fc .ui-resizable-handle {
|
.fc .ui-resizable-handle {
|
||||||
display: block;
|
display: block;
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
@import "Overrides/bootstrap";
|
@import "Overrides/bootstrap";
|
||||||
@import "Overrides/browser";
|
@import "Overrides/browser";
|
||||||
@import "Overrides/bootstrap.toggle-switch";
|
@import "Overrides/bootstrap.toggle-switch";
|
||||||
|
@import "Overrides/fullcalendar";
|
||||||
|
|
|
@ -4,11 +4,7 @@
|
||||||
|
|
||||||
<h3>
|
<h3>
|
||||||
<i class="icon-bookmark x-episode-monitored" title="Toggle monitored status" />
|
<i class="icon-bookmark x-episode-monitored" title="Toggle monitored status" />
|
||||||
{{#if episodeTitle}}
|
{{series.title}} - {{EpisodeNumber}} - {{title}}
|
||||||
{{title}} - {{EpisodeNumber}} - {{episodeTitle}}
|
|
||||||
{{else}}
|
|
||||||
{{series.title}} - {{EpisodeNumber}} - {{title}}
|
|
||||||
{{/if}}
|
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -20,6 +20,7 @@ define(
|
||||||
Handlebars.registerHelper('StatusLevel', function () {
|
Handlebars.registerHelper('StatusLevel', function () {
|
||||||
|
|
||||||
var hasFile = this.hasFile;
|
var hasFile = this.hasFile;
|
||||||
|
var downloading = require('History/Queue/QueueCollection').findEpisode(this.id) || this.downloading;
|
||||||
var currentTime = Moment();
|
var currentTime = Moment();
|
||||||
var start = Moment(this.airDateUtc);
|
var start = Moment(this.airDateUtc);
|
||||||
var end = Moment(this.end);
|
var end = Moment(this.end);
|
||||||
|
@ -28,6 +29,10 @@ define(
|
||||||
return 'success';
|
return 'success';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (downloading) {
|
||||||
|
return 'purple';
|
||||||
|
}
|
||||||
|
|
||||||
if (currentTime.isAfter(start) && currentTime.isBefore(end)) {
|
if (currentTime.isAfter(start) && currentTime.isBefore(end)) {
|
||||||
return 'warning';
|
return 'warning';
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,20 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
define(
|
define(
|
||||||
[
|
[
|
||||||
|
'underscore',
|
||||||
'backbone',
|
'backbone',
|
||||||
'History/Queue/QueueModel',
|
'History/Queue/QueueModel',
|
||||||
'Mixins/backbone.signalr.mixin'
|
'Mixins/backbone.signalr.mixin'
|
||||||
], function (Backbone, QueueModel) {
|
], function (_, Backbone, QueueModel) {
|
||||||
var QueueCollection = Backbone.Collection.extend({
|
var QueueCollection = Backbone.Collection.extend({
|
||||||
url : window.NzbDrone.ApiRoot + '/queue',
|
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();
|
var collection = new QueueCollection().bindSignalR();
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue