Merge pull request #231 from Sonarr/unmonitored-calendar

Unmonitored episodes on calendar
pull/4/head
Mark McDowall 10 years ago
commit c43296ffe9

@ -31,7 +31,7 @@ namespace NzbDrone.Api.Calendar
if (queryStart.HasValue) start = DateTime.Parse(queryStart.Value); if (queryStart.HasValue) start = DateTime.Parse(queryStart.Value);
if (queryEnd.HasValue) end = DateTime.Parse(queryEnd.Value); if (queryEnd.HasValue) end = DateTime.Parse(queryEnd.Value);
var episodes = _episodeService.EpisodesBetweenDates(start, end); var episodes = _episodeService.EpisodesBetweenDates(start, end, false);
var icalCalendar = new iCalendar(); var icalCalendar = new iCalendar();
foreach (var episode in episodes.OrderBy(v => v.AirDateUtc.Value)) foreach (var episode in episodes.OrderBy(v => v.AirDateUtc.Value))

@ -23,14 +23,17 @@ namespace NzbDrone.Api.Calendar
{ {
var start = DateTime.Today; var start = DateTime.Today;
var end = DateTime.Today.AddDays(2); var end = DateTime.Today.AddDays(2);
var includeUnmonitored = false;
var queryStart = Request.Query.Start; var queryStart = Request.Query.Start;
var queryEnd = Request.Query.End; var queryEnd = Request.Query.End;
var queryIncludeUnmonitored = Request.Query.Unmonitored;
if (queryStart.HasValue) start = DateTime.Parse(queryStart.Value); if (queryStart.HasValue) start = DateTime.Parse(queryStart.Value);
if (queryEnd.HasValue) end = DateTime.Parse(queryEnd.Value); if (queryEnd.HasValue) end = DateTime.Parse(queryEnd.Value);
if (queryIncludeUnmonitored.HasValue) includeUnmonitored = Convert.ToBoolean(queryIncludeUnmonitored.Value);
var resources = ToListResource(() => _episodeService.EpisodesBetweenDates(start, end)); var resources = ToListResource(() => _episodeService.EpisodesBetweenDates(start, end, includeUnmonitored));
return resources.OrderBy(e => e.AirDateUtc).ToList(); return resources.OrderBy(e => e.AirDateUtc).ToList();
} }

@ -33,7 +33,7 @@ namespace NzbDrone.Core.Test.TvTests.EpisodeRepositoryTests
[Test] [Test]
public void should_get_episodes() public void should_get_episodes()
{ {
var episodes = Subject.EpisodesBetweenDates(DateTime.Today.AddDays(-1), DateTime.Today.AddDays(3)); var episodes = Subject.EpisodesBetweenDates(DateTime.Today.AddDays(-1), DateTime.Today.AddDays(3), false);
episodes.Should().HaveCount(1); episodes.Should().HaveCount(1);
} }
} }

@ -47,7 +47,7 @@ namespace NzbDrone.Core.IndexerSearch
public void MissingEpisodesAiredAfter(DateTime dateTime, IEnumerable<Int32> grabbed) public void MissingEpisodesAiredAfter(DateTime dateTime, IEnumerable<Int32> grabbed)
{ {
var missing = _episodeService.EpisodesBetweenDates(dateTime, DateTime.UtcNow) var missing = _episodeService.EpisodesBetweenDates(dateTime, DateTime.UtcNow, false)
.Where(e => !e.HasFile && .Where(e => !e.HasFile &&
!_queueService.GetQueue().Select(q => q.Episode.Id).Contains(e.Id) && !_queueService.GetQueue().Select(q => q.Episode.Id).Contains(e.Id) &&
!grabbed.Contains(e.Id)) !grabbed.Contains(e.Id))

@ -26,7 +26,7 @@ namespace NzbDrone.Core.Tv
PagingSpec<Episode> EpisodesWhereCutoffUnmet(PagingSpec<Episode> pagingSpec, List<QualitiesBelowCutoff> qualitiesBelowCutoff, bool includeSpecials); PagingSpec<Episode> EpisodesWhereCutoffUnmet(PagingSpec<Episode> pagingSpec, List<QualitiesBelowCutoff> qualitiesBelowCutoff, bool includeSpecials);
List<Episode> FindEpisodesBySceneNumbering(int seriesId, int seasonNumber, int episodeNumber); List<Episode> FindEpisodesBySceneNumbering(int seriesId, int seasonNumber, int episodeNumber);
Episode FindEpisodeBySceneNumbering(int seriesId, int sceneAbsoluteEpisodeNumber); Episode FindEpisodeBySceneNumbering(int seriesId, int sceneAbsoluteEpisodeNumber);
List<Episode> EpisodesBetweenDates(DateTime startDate, DateTime endDate); List<Episode> EpisodesBetweenDates(DateTime startDate, DateTime endDate, bool includeUnmonitored);
void SetMonitoredFlat(Episode episode, bool monitored); void SetMonitoredFlat(Episode episode, bool monitored);
void SetMonitoredBySeason(int seriesId, int seasonNumber, bool monitored); void SetMonitoredBySeason(int seriesId, int seasonNumber, bool monitored);
void SetFileId(int episodeId, int fileId); void SetFileId(int episodeId, int fileId);
@ -151,14 +151,20 @@ namespace NzbDrone.Core.Tv
return episodes.Single(); return episodes.Single();
} }
public List<Episode> EpisodesBetweenDates(DateTime startDate, DateTime endDate) public List<Episode> EpisodesBetweenDates(DateTime startDate, DateTime endDate, bool includeUnmonitored)
{ {
return Query.Join<Episode, Series>(JoinType.Inner, e => e.Series, (e, s) => e.SeriesId == s.Id) var query = Query.Join<Episode, Series>(JoinType.Inner, e => e.Series, (e, s) => e.SeriesId == s.Id)
.Where<Episode>(e => e.AirDateUtc >= startDate) .Where<Episode>(e => e.AirDateUtc >= startDate)
.AndWhere(e => e.AirDateUtc <= endDate) .AndWhere(e => e.AirDateUtc <= endDate);
.AndWhere(e => e.Monitored)
.AndWhere(e => e.Series.Monitored)
.ToList(); if (!includeUnmonitored)
{
query.AndWhere(e => e.Monitored)
.AndWhere(e => e.Series.Monitored);
}
return query.ToList();
} }
public void SetMonitoredFlat(Episode episode, bool monitored) public void SetMonitoredFlat(Episode episode, bool monitored)

@ -30,7 +30,7 @@ namespace NzbDrone.Core.Tv
void UpdateEpisode(Episode episode); void UpdateEpisode(Episode episode);
void SetEpisodeMonitored(int episodeId, bool monitored); void SetEpisodeMonitored(int episodeId, bool monitored);
void UpdateEpisodes(List<Episode> episodes); void UpdateEpisodes(List<Episode> episodes);
List<Episode> EpisodesBetweenDates(DateTime start, DateTime end); List<Episode> EpisodesBetweenDates(DateTime start, DateTime end, bool includeUnmonitored);
void InsertMany(List<Episode> episodes); void InsertMany(List<Episode> episodes);
void UpdateMany(List<Episode> episodes); void UpdateMany(List<Episode> episodes);
void DeleteMany(List<Episode> episodes); void DeleteMany(List<Episode> episodes);
@ -169,9 +169,9 @@ namespace NzbDrone.Core.Tv
_episodeRepository.UpdateMany(episodes); _episodeRepository.UpdateMany(episodes);
} }
public List<Episode> EpisodesBetweenDates(DateTime start, DateTime end) public List<Episode> EpisodesBetweenDates(DateTime start, DateTime end, bool includeUnmonitored)
{ {
var episodes = _episodeRepository.EpisodesBetweenDates(start.ToUniversalTime(), end.ToUniversalTime()); var episodes = _episodeRepository.EpisodesBetweenDates(start.ToUniversalTime(), end.ToUniversalTime(), includeUnmonitored);
return episodes; return episodes;
} }

@ -2,8 +2,9 @@ var Backbone = require('backbone');
var EpisodeModel = require('../Series/EpisodeModel'); var EpisodeModel = require('../Series/EpisodeModel');
module.exports = Backbone.Collection.extend({ module.exports = Backbone.Collection.extend({
url : window.NzbDrone.ApiRoot + '/calendar', url : window.NzbDrone.ApiRoot + '/calendar',
model : EpisodeModel, model : EpisodeModel,
tableName : 'calendar',
comparator : function(model) { comparator : function(model) {
var date = new Date(model.get('airDateUtc')); var date = new Date(model.get('airDateUtc'));

@ -3,34 +3,94 @@ var Marionette = require('marionette');
var UpcomingCollectionView = require('./UpcomingCollectionView'); var UpcomingCollectionView = require('./UpcomingCollectionView');
var CalendarView = require('./CalendarView'); var CalendarView = require('./CalendarView');
var CalendarFeedView = require('./CalendarFeedView'); var CalendarFeedView = require('./CalendarFeedView');
var ToolbarLayout = require('../Shared/Toolbar/ToolbarLayout');
module.exports = Marionette.Layout.extend({ module.exports = Marionette.Layout.extend({
template : 'Calendar/CalendarLayoutTemplate', template : 'Calendar/CalendarLayoutTemplate',
regions : { regions : {
upcoming : '#x-upcoming', upcoming : '#x-upcoming',
calendar : '#x-calendar' calendar : '#x-calendar',
}, toolbar : '#x-toolbar'
events : {
'click .x-ical' : '_showiCal'
}, },
onShow : function() { onShow : function() {
this._showUpcoming(); this._showUpcoming();
this._showCalendar(); this._showCalendar();
this._showToolbar();
}, },
_showUpcoming : function() { _showUpcoming : function() {
this.upcoming.show(new UpcomingCollectionView()); this.upcomingView = new UpcomingCollectionView();
this.upcoming.show(this.upcomingView);
}, },
_showCalendar : function() { _showCalendar : function() {
this.calendar.show(new CalendarView()); this.calendarView = new CalendarView();
this.calendar.show(this.calendarView);
}, },
_showiCal : function() { _showiCal : function() {
var view = new CalendarFeedView(); var view = new CalendarFeedView();
AppLayout.modalRegion.show(view); AppLayout.modalRegion.show(view);
},
_showToolbar : function() {
var leftSideButtons = {
type : 'default',
storeState : false,
items : [
{
title : 'Get iCal Link',
icon : 'icon-sonarr-calendar-o',
callback : this._showiCal,
ownerContext : this
}
]
};
var filterOptions = {
type : 'radio',
storeState : true,
menuKey : 'calendar.show',
defaultAction : 'monitored',
items : [
{
key : 'all',
title : '',
tooltip : 'All',
icon : 'icon-sonarr-all',
callback : this._setCalendarFilter
},
{
key : 'monitored',
title : '',
tooltip : 'Monitored Only',
icon : 'icon-sonarr-monitored',
callback : this._setCalendarFilter
}
]
};
this.toolbar.show(new ToolbarLayout({
left : [leftSideButtons],
right : [filterOptions],
context : this,
floatOnMobile : true
}));
},
_setCalendarFilter : function(buttonContext) {
var mode = buttonContext.model.get('key');
if (mode === 'all') {
this.calendarView.setShowUnmonitored(true);
this.upcomingView.setShowUnmonitored(true);
}
else {
this.calendarView.setShowUnmonitored(false);
this.upcomingView.setShowUnmonitored(false);
}
} }
}); });

@ -3,23 +3,20 @@
<div class="pull-left"> <div class="pull-left">
<h4>Upcoming</h4> <h4>Upcoming</h4>
</div> </div>
<div class="pull-right">
<h4>
<i class="icon-sonarr-calendar-o ical x-ical"></i>
</h4>
</div>
<div id="x-upcoming"/> <div id="x-upcoming"/>
</div> </div>
<div class="col-md-9 col-xs-12"> <div class="col-md-9 col-xs-12">
<div id="x-toolbar" class="calendar-toolbar"/>
<div id="x-calendar" class="calendar"/> <div id="x-calendar" class="calendar"/>
<div class="legend calendar"> <div class="legend calendar">
<ul class='legend-labels'> <ul class='legend-labels'>
<li><span class="premiere" title="Premiere episode hasn't aired yet"></span>Unaired Premiere</li> <li class="legend-label"><span class="premiere" title="Premiere episode hasn't aired yet"></span>Unaired Premiere</li>
<li><span class="primary" title="Episode hasn't aired yet"></span>Unaired</li> <li class="legend-label"><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 class="legend-label"><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 class="legend-label"><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 class="legend-label"><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 class="legend-label"><span class="success" title="Episode was downloaded and sorted"></span>Downloaded</li>
<li class="legend-label"><span class="unmonitored" title="Episode is unmonitored"></span>Unmonitored</li>
</ul> </ul>
</div> </div>
</div> </div>

@ -2,10 +2,11 @@ var $ = require('jquery');
var vent = require('vent'); var vent = require('vent');
var Marionette = require('marionette'); var Marionette = require('marionette');
var moment = require('moment'); var moment = require('moment');
var CalendarCollection = require('./Collection'); var CalendarCollection = require('./CalendarCollection');
var UiSettings = require('../Shared/UiSettingsModel'); var UiSettings = require('../Shared/UiSettingsModel');
var QueueCollection = require('../Activity/Queue/QueueCollection'); var QueueCollection = require('../Activity/Queue/QueueCollection');
var Config = require('../Config'); var Config = require('../Config');
require('../Mixins/backbone.signalr.mixin'); require('../Mixins/backbone.signalr.mixin');
require('fullcalendar'); require('fullcalendar');
require('jquery.easypiechart'); require('jquery.easypiechart');
@ -14,6 +15,7 @@ module.exports = Marionette.ItemView.extend({
storageKey : 'calendar.view', storageKey : 'calendar.view',
initialize : function() { initialize : function() {
this.showUnmonitored = Config.getValue('calendar.show', 'monitored') === 'all';
this.collection = new CalendarCollection().bindSignalR({ updateOnly : true }); this.collection = new CalendarCollection().bindSignalR({ updateOnly : true });
this.listenTo(this.collection, 'change', this._reloadCalendarEvents); this.listenTo(this.collection, 'change', this._reloadCalendarEvents);
this.listenTo(QueueCollection, 'sync', this._reloadCalendarEvents); this.listenTo(QueueCollection, 'sync', this._reloadCalendarEvents);
@ -27,6 +29,13 @@ module.exports = Marionette.ItemView.extend({
this.$('.fc-button-today').click(); this.$('.fc-button-today').click();
}, },
setShowUnmonitored : function (showUnmonitored) {
if (this.showUnmonitored !== showUnmonitored) {
this.showUnmonitored = showUnmonitored;
this._getEvents(this.$el.fullCalendar('getView'));
}
},
_viewRender : function(view) { _viewRender : function(view) {
if ($(window).width() < 768) { if ($(window).width() < 768) {
this.$('.fc-header-title').show(); this.$('.fc-header-title').show();
@ -105,8 +114,9 @@ module.exports = Marionette.ItemView.extend({
this.collection.fetch({ this.collection.fetch({
data : { data : {
start : start, start : start,
end : end end : end,
unmonitored : this.showUnmonitored
}, },
success : this._setEventData.bind(this) success : this._setEventData.bind(this)
}); });
@ -145,6 +155,7 @@ module.exports = Marionette.ItemView.extend({
var currentTime = moment(); var currentTime = moment();
var start = moment(element.get('airDateUtc')); var start = moment(element.get('airDateUtc'));
var end = moment(endTime); var end = moment(endTime);
var monitored = element.get('series').monitored && element.get('monitored');
var statusLevel = 'primary'; var statusLevel = 'primary';
@ -156,6 +167,10 @@ module.exports = Marionette.ItemView.extend({
statusLevel = 'purple'; statusLevel = 'purple';
} }
else if (!monitored) {
statusLevel = 'unmonitored';
}
else if (currentTime.isAfter(start) && currentTime.isBefore(end)) { else if (currentTime.isAfter(start) && currentTime.isBefore(end)) {
statusLevel = 'warning'; statusLevel = 'warning';
} }
@ -231,6 +246,7 @@ module.exports = Marionette.ItemView.extend({
return options; return options;
}, },
_addStatusIcon : function(element, icon, tooltip) { _addStatusIcon : function(element, icon, tooltip) {
this.$(element).find('.fc-event-time').after('<span class="status pull-right"><i class="{0}"></i></span>'.format(icon)); this.$(element).find('.fc-event-time').after('<span class="status pull-right"><i class="{0}"></i></span>'.format(icon));
this.$(element).find('.status').tooltip({ this.$(element).find('.status').tooltip({

@ -2,14 +2,16 @@ var _ = require('underscore');
var Marionette = require('marionette'); var Marionette = require('marionette');
var UpcomingCollection = require('./UpcomingCollection'); var UpcomingCollection = require('./UpcomingCollection');
var UpcomingItemView = require('./UpcomingItemView'); var UpcomingItemView = require('./UpcomingItemView');
var Config = require('../Config');
require('../Mixins/backbone.signalr.mixin'); require('../Mixins/backbone.signalr.mixin');
module.exports = Marionette.CollectionView.extend({ module.exports = Marionette.CollectionView.extend({
itemView : UpcomingItemView, itemView : UpcomingItemView,
initialize : function() { initialize : function() {
this.showUnmonitored = Config.getValue('calendar.show', 'monitored') === 'all';
this.collection = new UpcomingCollection().bindSignalR({ updateOnly : true }); this.collection = new UpcomingCollection().bindSignalR({ updateOnly : true });
this.collection.fetch(); this._fetchCollection();
this._fetchCollection = _.bind(this._fetchCollection, this); this._fetchCollection = _.bind(this._fetchCollection, this);
this.timer = window.setInterval(this._fetchCollection, 60 * 60 * 1000); this.timer = window.setInterval(this._fetchCollection, 60 * 60 * 1000);
@ -19,7 +21,14 @@ module.exports = Marionette.CollectionView.extend({
window.clearInterval(this.timer); window.clearInterval(this.timer);
}, },
setShowUnmonitored : function (showUnmonitored) {
if (this.showUnmonitored !== showUnmonitored) {
this.showUnmonitored = showUnmonitored;
this._fetchCollection();
}
},
_fetchCollection : function() { _fetchCollection : function() {
this.collection.fetch(); this.collection.fetch({ data: { unmonitored : this.showUnmonitored }});
} }
}); });

@ -36,7 +36,7 @@
} }
.fc-state-highlight { .fc-state-highlight {
background : #f1f1f1; background : #dbdbdb;
} }
.past { .past {
@ -119,17 +119,21 @@
border-color : @droneTeal; border-color : @droneTeal;
} }
.unmonitored {
border-color : grey;
}
.episode-title { .episode-title {
.btn-link; .btn-link;
.text-overflow; .text-overflow;
color : @link-color; color : @link-color;
margin-top : 1px; margin-top : 1px;
display : inline-block; display : inline-block;
@media (max-width: @screen-xs-min) { @media (max-width: @screen-xs-min) {
width : 140px; width : 140px;
} }
@media (min-width: @screen-md-min) { @media (min-width: @screen-md-min) {
width : 135px; width : 135px;
} }
@ -138,7 +142,7 @@
.calendar { .calendar {
background-position : -160px -128px; // background-position : -160px -128px;
.primary { .primary {
border-color : @btn-primary-bg; border-color : @btn-primary-bg;
@ -185,10 +189,28 @@
background-color : @droneTeal; background-color : @droneTeal;
} }
.unmonitored {
border-color : grey;
background-color : grey;
}
.chart { .chart {
margin-top : 2px; margin-top : 2px;
margin-right: 2px; margin-right: 2px;
} }
.legend-labels {
width : 500px;
@media (max-width: @screen-xs-min) {
width : 400px;
}
}
.legend-label {
display : inline-block;
width : 150px;
}
} }
.ical { .ical {
@ -211,3 +233,9 @@
margin-bottom : 5px; margin-bottom : 5px;
} }
} }
.calendar-toolbar {
.page-toolbar {
margin-bottom : 10px;
}
}

@ -20,6 +20,7 @@ Handlebars.registerHelper('StatusLevel', function() {
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);
var monitored = this.series.monitored && this.monitored;
if (hasFile) { if (hasFile) {
return 'success'; return 'success';
@ -29,6 +30,10 @@ Handlebars.registerHelper('StatusLevel', function() {
return 'purple'; return 'purple';
} }
else if (!monitored) {
return 'unmonitored';
}
if (this.episodeNumber === 1) { if (this.episodeNumber === 1) {
return 'premiere'; return 'premiere';
} }

@ -24,6 +24,10 @@ module.exports = Marionette.Layout.extend({
throw 'context needs to be passed'; throw 'context needs to be passed';
} }
this.templateHelpers = {
floatOnMobile : options.floatOnMobile || false
};
this.left = options.left; this.left = options.left;
this.right = options.right; this.right = options.right;
this.toolbarContext = options.context; this.toolbarContext = options.context;
@ -51,7 +55,7 @@ module.exports = Marionette.Layout.extend({
_.each(buttonGroup.items, function(button) { _.each(buttonGroup.items, function(button) {
if (buttonGroup.storeState && !button.key) { if (buttonGroup.storeState && !button.key) {
throw 'must provide key for all buttons when storSstate is enabled'; throw 'must provide key for all buttons when storeState is enabled';
} }
var model = new ButtonModel(button); var model = new ButtonModel(button);

@ -1,2 +1,2 @@
<div class="page-toolbar pull-left pull-none-xs x-toolbar-left" /> <div class="page-toolbar pull-left {{#unless floatOnMobile}}pull-none-xs{{/unless}} x-toolbar-left" />
<div class="page-toolbar pull-right pull-none-xs x-toolbar-right" /> <div class="page-toolbar pull-right {{#unless floatOnMobile}}pull-none-xs{{/unless}} x-toolbar-right" />

Loading…
Cancel
Save