Merge pull request #231 from Sonarr/unmonitored-calendar
Unmonitored episodes on calendar
This commit is contained in:
commit
c43296ffe9
|
@ -31,7 +31,7 @@ namespace NzbDrone.Api.Calendar
|
|||
if (queryStart.HasValue) start = DateTime.Parse(queryStart.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();
|
||||
|
||||
foreach (var episode in episodes.OrderBy(v => v.AirDateUtc.Value))
|
||||
|
|
|
@ -23,14 +23,17 @@ namespace NzbDrone.Api.Calendar
|
|||
{
|
||||
var start = DateTime.Today;
|
||||
var end = DateTime.Today.AddDays(2);
|
||||
var includeUnmonitored = false;
|
||||
|
||||
var queryStart = Request.Query.Start;
|
||||
var queryEnd = Request.Query.End;
|
||||
var queryIncludeUnmonitored = Request.Query.Unmonitored;
|
||||
|
||||
if (queryStart.HasValue) start = DateTime.Parse(queryStart.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();
|
||||
}
|
||||
|
|
|
@ -33,7 +33,7 @@ namespace NzbDrone.Core.Test.TvTests.EpisodeRepositoryTests
|
|||
[Test]
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -47,7 +47,7 @@ namespace NzbDrone.Core.IndexerSearch
|
|||
|
||||
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 &&
|
||||
!_queueService.GetQueue().Select(q => q.Episode.Id).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);
|
||||
List<Episode> FindEpisodesBySceneNumbering(int seriesId, int seasonNumber, int episodeNumber);
|
||||
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 SetMonitoredBySeason(int seriesId, int seasonNumber, bool monitored);
|
||||
void SetFileId(int episodeId, int fileId);
|
||||
|
@ -151,14 +151,20 @@ namespace NzbDrone.Core.Tv
|
|||
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)
|
||||
.Where<Episode>(e => e.AirDateUtc >= startDate)
|
||||
.AndWhere(e => e.AirDateUtc <= endDate)
|
||||
.AndWhere(e => e.Monitored)
|
||||
.AndWhere(e => e.Series.Monitored)
|
||||
.ToList();
|
||||
var query = Query.Join<Episode, Series>(JoinType.Inner, e => e.Series, (e, s) => e.SeriesId == s.Id)
|
||||
.Where<Episode>(e => e.AirDateUtc >= startDate)
|
||||
.AndWhere(e => e.AirDateUtc <= endDate);
|
||||
|
||||
|
||||
if (!includeUnmonitored)
|
||||
{
|
||||
query.AndWhere(e => e.Monitored)
|
||||
.AndWhere(e => e.Series.Monitored);
|
||||
}
|
||||
|
||||
return query.ToList();
|
||||
}
|
||||
|
||||
public void SetMonitoredFlat(Episode episode, bool monitored)
|
||||
|
|
|
@ -30,7 +30,7 @@ namespace NzbDrone.Core.Tv
|
|||
void UpdateEpisode(Episode episode);
|
||||
void SetEpisodeMonitored(int episodeId, bool monitored);
|
||||
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 UpdateMany(List<Episode> episodes);
|
||||
void DeleteMany(List<Episode> episodes);
|
||||
|
@ -169,9 +169,9 @@ namespace NzbDrone.Core.Tv
|
|||
_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;
|
||||
}
|
||||
|
|
|
@ -2,8 +2,9 @@ var Backbone = require('backbone');
|
|||
var EpisodeModel = require('../Series/EpisodeModel');
|
||||
|
||||
module.exports = Backbone.Collection.extend({
|
||||
url : window.NzbDrone.ApiRoot + '/calendar',
|
||||
model : EpisodeModel,
|
||||
url : window.NzbDrone.ApiRoot + '/calendar',
|
||||
model : EpisodeModel,
|
||||
tableName : 'calendar',
|
||||
|
||||
comparator : function(model) {
|
||||
var date = new Date(model.get('airDateUtc'));
|
|
@ -3,34 +3,94 @@ var Marionette = require('marionette');
|
|||
var UpcomingCollectionView = require('./UpcomingCollectionView');
|
||||
var CalendarView = require('./CalendarView');
|
||||
var CalendarFeedView = require('./CalendarFeedView');
|
||||
var ToolbarLayout = require('../Shared/Toolbar/ToolbarLayout');
|
||||
|
||||
module.exports = Marionette.Layout.extend({
|
||||
template : 'Calendar/CalendarLayoutTemplate',
|
||||
|
||||
regions : {
|
||||
upcoming : '#x-upcoming',
|
||||
calendar : '#x-calendar'
|
||||
},
|
||||
|
||||
events : {
|
||||
'click .x-ical' : '_showiCal'
|
||||
calendar : '#x-calendar',
|
||||
toolbar : '#x-toolbar'
|
||||
},
|
||||
|
||||
onShow : function() {
|
||||
this._showUpcoming();
|
||||
this._showCalendar();
|
||||
this._showToolbar();
|
||||
},
|
||||
|
||||
_showUpcoming : function() {
|
||||
this.upcoming.show(new UpcomingCollectionView());
|
||||
this.upcomingView = new UpcomingCollectionView();
|
||||
this.upcoming.show(this.upcomingView);
|
||||
},
|
||||
|
||||
_showCalendar : function() {
|
||||
this.calendar.show(new CalendarView());
|
||||
this.calendarView = new CalendarView();
|
||||
this.calendar.show(this.calendarView);
|
||||
},
|
||||
|
||||
_showiCal : function() {
|
||||
var view = new CalendarFeedView();
|
||||
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">
|
||||
<h4>Upcoming</h4>
|
||||
</div>
|
||||
<div class="pull-right">
|
||||
<h4>
|
||||
<i class="icon-sonarr-calendar-o ical x-ical"></i>
|
||||
</h4>
|
||||
</div>
|
||||
<div id="x-upcoming"/>
|
||||
</div>
|
||||
<div class="col-md-9 col-xs-12">
|
||||
<div id="x-toolbar" class="calendar-toolbar"/>
|
||||
<div id="x-calendar" class="calendar"/>
|
||||
<div class="legend calendar">
|
||||
<ul class='legend-labels'>
|
||||
<li><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><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>
|
||||
<li class="legend-label"><span class="premiere" title="Premiere episode hasn't aired yet"></span>Unaired Premiere</li>
|
||||
<li class="legend-label"><span class="primary" title="Episode hasn't aired yet"></span>Unaired</li>
|
||||
<li class="legend-label"><span class="warning" title="Episode is currently airing"></span>On Air</li>
|
||||
<li class="legend-label"><span class="purple" title="Episode is currently downloading"></span>Downloading</li>
|
||||
<li class="legend-label"><span class="danger" title="Episode file has not been found"></span>Missing</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>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -2,10 +2,11 @@ var $ = require('jquery');
|
|||
var vent = require('vent');
|
||||
var Marionette = require('marionette');
|
||||
var moment = require('moment');
|
||||
var CalendarCollection = require('./Collection');
|
||||
var CalendarCollection = require('./CalendarCollection');
|
||||
var UiSettings = require('../Shared/UiSettingsModel');
|
||||
var QueueCollection = require('../Activity/Queue/QueueCollection');
|
||||
var Config = require('../Config');
|
||||
|
||||
require('../Mixins/backbone.signalr.mixin');
|
||||
require('fullcalendar');
|
||||
require('jquery.easypiechart');
|
||||
|
@ -14,6 +15,7 @@ module.exports = Marionette.ItemView.extend({
|
|||
storageKey : 'calendar.view',
|
||||
|
||||
initialize : function() {
|
||||
this.showUnmonitored = Config.getValue('calendar.show', 'monitored') === 'all';
|
||||
this.collection = new CalendarCollection().bindSignalR({ updateOnly : true });
|
||||
this.listenTo(this.collection, 'change', this._reloadCalendarEvents);
|
||||
this.listenTo(QueueCollection, 'sync', this._reloadCalendarEvents);
|
||||
|
@ -27,6 +29,13 @@ module.exports = Marionette.ItemView.extend({
|
|||
this.$('.fc-button-today').click();
|
||||
},
|
||||
|
||||
setShowUnmonitored : function (showUnmonitored) {
|
||||
if (this.showUnmonitored !== showUnmonitored) {
|
||||
this.showUnmonitored = showUnmonitored;
|
||||
this._getEvents(this.$el.fullCalendar('getView'));
|
||||
}
|
||||
},
|
||||
|
||||
_viewRender : function(view) {
|
||||
if ($(window).width() < 768) {
|
||||
this.$('.fc-header-title').show();
|
||||
|
@ -105,8 +114,9 @@ module.exports = Marionette.ItemView.extend({
|
|||
|
||||
this.collection.fetch({
|
||||
data : {
|
||||
start : start,
|
||||
end : end
|
||||
start : start,
|
||||
end : end,
|
||||
unmonitored : this.showUnmonitored
|
||||
},
|
||||
success : this._setEventData.bind(this)
|
||||
});
|
||||
|
@ -145,6 +155,7 @@ module.exports = Marionette.ItemView.extend({
|
|||
var currentTime = moment();
|
||||
var start = moment(element.get('airDateUtc'));
|
||||
var end = moment(endTime);
|
||||
var monitored = element.get('series').monitored && element.get('monitored');
|
||||
|
||||
var statusLevel = 'primary';
|
||||
|
||||
|
@ -156,6 +167,10 @@ module.exports = Marionette.ItemView.extend({
|
|||
statusLevel = 'purple';
|
||||
}
|
||||
|
||||
else if (!monitored) {
|
||||
statusLevel = 'unmonitored';
|
||||
}
|
||||
|
||||
else if (currentTime.isAfter(start) && currentTime.isBefore(end)) {
|
||||
statusLevel = 'warning';
|
||||
}
|
||||
|
@ -231,6 +246,7 @@ module.exports = Marionette.ItemView.extend({
|
|||
|
||||
return options;
|
||||
},
|
||||
|
||||
_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('.status').tooltip({
|
||||
|
|
|
@ -2,14 +2,16 @@ var _ = require('underscore');
|
|||
var Marionette = require('marionette');
|
||||
var UpcomingCollection = require('./UpcomingCollection');
|
||||
var UpcomingItemView = require('./UpcomingItemView');
|
||||
var Config = require('../Config');
|
||||
require('../Mixins/backbone.signalr.mixin');
|
||||
|
||||
module.exports = Marionette.CollectionView.extend({
|
||||
itemView : UpcomingItemView,
|
||||
|
||||
initialize : function() {
|
||||
this.showUnmonitored = Config.getValue('calendar.show', 'monitored') === 'all';
|
||||
this.collection = new UpcomingCollection().bindSignalR({ updateOnly : true });
|
||||
this.collection.fetch();
|
||||
this._fetchCollection();
|
||||
|
||||
this._fetchCollection = _.bind(this._fetchCollection, this);
|
||||
this.timer = window.setInterval(this._fetchCollection, 60 * 60 * 1000);
|
||||
|
@ -19,7 +21,14 @@ module.exports = Marionette.CollectionView.extend({
|
|||
window.clearInterval(this.timer);
|
||||
},
|
||||
|
||||
setShowUnmonitored : function (showUnmonitored) {
|
||||
if (this.showUnmonitored !== showUnmonitored) {
|
||||
this.showUnmonitored = showUnmonitored;
|
||||
this._fetchCollection();
|
||||
}
|
||||
},
|
||||
|
||||
_fetchCollection : function() {
|
||||
this.collection.fetch();
|
||||
this.collection.fetch({ data: { unmonitored : this.showUnmonitored }});
|
||||
}
|
||||
});
|
|
@ -36,7 +36,7 @@
|
|||
}
|
||||
|
||||
.fc-state-highlight {
|
||||
background : #f1f1f1;
|
||||
background : #dbdbdb;
|
||||
}
|
||||
|
||||
.past {
|
||||
|
@ -119,17 +119,21 @@
|
|||
border-color : @droneTeal;
|
||||
}
|
||||
|
||||
.unmonitored {
|
||||
border-color : grey;
|
||||
}
|
||||
|
||||
.episode-title {
|
||||
.btn-link;
|
||||
.text-overflow;
|
||||
color : @link-color;
|
||||
margin-top : 1px;
|
||||
display : inline-block;
|
||||
|
||||
|
||||
@media (max-width: @screen-xs-min) {
|
||||
width : 140px;
|
||||
}
|
||||
|
||||
|
||||
@media (min-width: @screen-md-min) {
|
||||
width : 135px;
|
||||
}
|
||||
|
@ -138,7 +142,7 @@
|
|||
|
||||
.calendar {
|
||||
|
||||
background-position : -160px -128px;
|
||||
// background-position : -160px -128px;
|
||||
|
||||
.primary {
|
||||
border-color : @btn-primary-bg;
|
||||
|
@ -185,10 +189,28 @@
|
|||
background-color : @droneTeal;
|
||||
}
|
||||
|
||||
.unmonitored {
|
||||
border-color : grey;
|
||||
background-color : grey;
|
||||
}
|
||||
|
||||
.chart {
|
||||
margin-top : 2px;
|
||||
margin-right: 2px;
|
||||
}
|
||||
|
||||
.legend-labels {
|
||||
width : 500px;
|
||||
|
||||
@media (max-width: @screen-xs-min) {
|
||||
width : 400px;
|
||||
}
|
||||
}
|
||||
|
||||
.legend-label {
|
||||
display : inline-block;
|
||||
width : 150px;
|
||||
}
|
||||
}
|
||||
|
||||
.ical {
|
||||
|
@ -211,3 +233,9 @@
|
|||
margin-bottom : 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.calendar-toolbar {
|
||||
.page-toolbar {
|
||||
margin-bottom : 10px;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@ Handlebars.registerHelper('StatusLevel', function() {
|
|||
var currentTime = moment();
|
||||
var start = moment(this.airDateUtc);
|
||||
var end = moment(this.end);
|
||||
var monitored = this.series.monitored && this.monitored;
|
||||
|
||||
if (hasFile) {
|
||||
return 'success';
|
||||
|
@ -29,6 +30,10 @@ Handlebars.registerHelper('StatusLevel', function() {
|
|||
return 'purple';
|
||||
}
|
||||
|
||||
else if (!monitored) {
|
||||
return 'unmonitored';
|
||||
}
|
||||
|
||||
if (this.episodeNumber === 1) {
|
||||
return 'premiere';
|
||||
}
|
||||
|
|
|
@ -24,6 +24,10 @@ module.exports = Marionette.Layout.extend({
|
|||
throw 'context needs to be passed';
|
||||
}
|
||||
|
||||
this.templateHelpers = {
|
||||
floatOnMobile : options.floatOnMobile || false
|
||||
};
|
||||
|
||||
this.left = options.left;
|
||||
this.right = options.right;
|
||||
this.toolbarContext = options.context;
|
||||
|
@ -51,7 +55,7 @@ module.exports = Marionette.Layout.extend({
|
|||
|
||||
_.each(buttonGroup.items, function(button) {
|
||||
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);
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
<div class="page-toolbar pull-left pull-none-xs x-toolbar-left" />
|
||||
<div class="page-toolbar pull-right pull-none-xs x-toolbar-right" />
|
||||
<div class="page-toolbar pull-left {{#unless floatOnMobile}}pull-none-xs{{/unless}} x-toolbar-left" />
|
||||
<div class="page-toolbar pull-right {{#unless floatOnMobile}}pull-none-xs{{/unless}} x-toolbar-right" />
|
||||
|
|
Loading…
Reference in New Issue