From b7c70d750a2c1dfd33204f3602cac42dc5bc93c6 Mon Sep 17 00:00:00 2001 From: Leonardo Galli Date: Thu, 29 Dec 2016 17:38:54 +0100 Subject: [PATCH] Movies should now show on the main page. However, a lot has to be done to the detail controller before it is really going to work. --- .../MediaCover/MediaCoverService.cs | 96 +++++ .../MediaCover/MediaCoversUpdatedEvent.cs | 7 + src/UI/AddMovies/SearchResultView.js | 2 + src/UI/Handlebars/Helpers/Series.js | 2 +- src/UI/Movies/Details/EpisodeNumberCell.js | 47 +++ .../Details/EpisodeNumberCellTemplate.hbs | 39 ++ src/UI/Movies/Details/EpisodeWarningCell.js | 21 ++ src/UI/Movies/Details/InfoView.js | 18 + src/UI/Movies/Details/InfoViewTemplate.hbs | 73 ++++ src/UI/Movies/Details/MoviesDetailsLayout.js | 264 +++++++++++++ .../Movies/Details/MoviesDetailsTemplate.hbs | 38 ++ src/UI/Movies/Details/SeasonCollectionView.js | 44 +++ src/UI/Movies/Details/SeasonLayout.js | 301 +++++++++++++++ .../Movies/Details/SeasonLayoutTemplate.hbs | 50 +++ src/UI/Movies/Index/EmptyTemplate.hbs | 16 + src/UI/Movies/Index/EmptyView.js | 5 + .../Movies/Index/EpisodeProgressPartial.hbs | 4 + src/UI/Movies/Index/FooterModel.js | 4 + src/UI/Movies/Index/FooterView.js | 5 + src/UI/Movies/Index/FooterViewTemplate.hbs | 46 +++ src/UI/Movies/Index/MoviesIndexItemView.js | 35 ++ src/UI/Movies/Index/MoviesIndexLayout.js | 354 ++++++++++++++++++ .../Index/MoviesIndexLayoutTemplate.hbs | 12 + .../Overview/SeriesOverviewCollectionView.js | 8 + .../SeriesOverviewCollectionViewTemplate.hbs | 1 + .../Index/Overview/SeriesOverviewItemView.js | 7 + .../SeriesOverviewItemViewTemplate.hbs | 56 +++ .../Posters/SeriesPostersCollectionView.js | 8 + .../SeriesPostersCollectionViewTemplate.hbs | 1 + .../Index/Posters/SeriesPostersItemView.js | 19 + .../Posters/SeriesPostersItemViewTemplate.hbs | 30 ++ src/UI/Movies/MoviesController.js | 34 ++ src/UI/main.js | 2 +- 33 files changed, 1647 insertions(+), 2 deletions(-) create mode 100644 src/UI/Movies/Details/EpisodeNumberCell.js create mode 100644 src/UI/Movies/Details/EpisodeNumberCellTemplate.hbs create mode 100644 src/UI/Movies/Details/EpisodeWarningCell.js create mode 100644 src/UI/Movies/Details/InfoView.js create mode 100644 src/UI/Movies/Details/InfoViewTemplate.hbs create mode 100644 src/UI/Movies/Details/MoviesDetailsLayout.js create mode 100644 src/UI/Movies/Details/MoviesDetailsTemplate.hbs create mode 100644 src/UI/Movies/Details/SeasonCollectionView.js create mode 100644 src/UI/Movies/Details/SeasonLayout.js create mode 100644 src/UI/Movies/Details/SeasonLayoutTemplate.hbs create mode 100644 src/UI/Movies/Index/EmptyTemplate.hbs create mode 100644 src/UI/Movies/Index/EmptyView.js create mode 100644 src/UI/Movies/Index/EpisodeProgressPartial.hbs create mode 100644 src/UI/Movies/Index/FooterModel.js create mode 100644 src/UI/Movies/Index/FooterView.js create mode 100644 src/UI/Movies/Index/FooterViewTemplate.hbs create mode 100644 src/UI/Movies/Index/MoviesIndexItemView.js create mode 100644 src/UI/Movies/Index/MoviesIndexLayout.js create mode 100644 src/UI/Movies/Index/MoviesIndexLayoutTemplate.hbs create mode 100644 src/UI/Movies/Index/Overview/SeriesOverviewCollectionView.js create mode 100644 src/UI/Movies/Index/Overview/SeriesOverviewCollectionViewTemplate.hbs create mode 100644 src/UI/Movies/Index/Overview/SeriesOverviewItemView.js create mode 100644 src/UI/Movies/Index/Overview/SeriesOverviewItemViewTemplate.hbs create mode 100644 src/UI/Movies/Index/Posters/SeriesPostersCollectionView.js create mode 100644 src/UI/Movies/Index/Posters/SeriesPostersCollectionViewTemplate.hbs create mode 100644 src/UI/Movies/Index/Posters/SeriesPostersItemView.js create mode 100644 src/UI/Movies/Index/Posters/SeriesPostersItemViewTemplate.hbs create mode 100644 src/UI/Movies/MoviesController.js diff --git a/src/NzbDrone.Core/MediaCover/MediaCoverService.cs b/src/NzbDrone.Core/MediaCover/MediaCoverService.cs index deb2b35a5..048d04068 100644 --- a/src/NzbDrone.Core/MediaCover/MediaCoverService.cs +++ b/src/NzbDrone.Core/MediaCover/MediaCoverService.cs @@ -22,6 +22,8 @@ public interface IMapCoversToLocal public class MediaCoverService : IHandleAsync, + IHandleAsync, + IHandleAsync, IHandleAsync, IMapCoversToLocal { @@ -83,6 +85,8 @@ private string GetSeriesCoverPath(int seriesId) return Path.Combine(_coverRootFolder, seriesId.ToString()); } + + private void EnsureCovers(Series series) { foreach (var cover in series.Images) @@ -110,6 +114,33 @@ private void EnsureCovers(Series series) } } + private void EnsureCovers(Movie movie) + { + foreach (var cover in movie.Images) + { + var fileName = GetCoverPath(movie.Id, cover.CoverType); + var alreadyExists = false; + try + { + alreadyExists = _coverExistsSpecification.AlreadyExists(cover.Url, fileName); + if (!alreadyExists) + { + DownloadCover(movie, cover); + } + } + catch (WebException e) + { + _logger.Warn(string.Format("Couldn't download media cover for {0}. {1}", movie, e.Message)); + } + catch (Exception e) + { + _logger.Error(e, "Couldn't download media cover for " + movie); + } + + EnsureResizedCovers(movie, cover, !alreadyExists); + } + } + private void DownloadCover(Series series, MediaCover cover) { var fileName = GetCoverPath(series.Id, cover.CoverType); @@ -118,6 +149,14 @@ private void DownloadCover(Series series, MediaCover cover) _httpClient.DownloadFile(cover.Url, fileName); } + private void DownloadCover(Movie series, MediaCover cover) + { + var fileName = GetCoverPath(series.Id, cover.CoverType); + + _logger.Info("Downloading {0} for {1} {2}", cover.CoverType, series, cover.Url); + _httpClient.DownloadFile(cover.Url, fileName); + } + private void EnsureResizedCovers(Series series, MediaCover cover, bool forceResize) { int[] heights; @@ -163,12 +202,69 @@ private void EnsureResizedCovers(Series series, MediaCover cover, bool forceResi } } + private void EnsureResizedCovers(Movie series, MediaCover cover, bool forceResize) + { + int[] heights; + + switch (cover.CoverType) + { + default: + return; + + case MediaCoverTypes.Poster: + case MediaCoverTypes.Headshot: + heights = new[] { 500, 250 }; + break; + + case MediaCoverTypes.Banner: + heights = new[] { 70, 35 }; + break; + + case MediaCoverTypes.Fanart: + case MediaCoverTypes.Screenshot: + heights = new[] { 360, 180 }; + break; + } + + foreach (var height in heights) + { + var mainFileName = GetCoverPath(series.Id, cover.CoverType); + var resizeFileName = GetCoverPath(series.Id, cover.CoverType, height); + + if (forceResize || !_diskProvider.FileExists(resizeFileName) || _diskProvider.GetFileSize(resizeFileName) == 0) + { + _logger.Debug("Resizing {0}-{1} for {2}", cover.CoverType, height, series); + + try + { + _resizer.Resize(mainFileName, resizeFileName, height); + } + catch + { + _logger.Debug("Couldn't resize media cover {0}-{1} for {2}, using full size image instead.", cover.CoverType, height, series); + } + } + } + } + public void HandleAsync(SeriesUpdatedEvent message) { EnsureCovers(message.Series); _eventAggregator.PublishEvent(new MediaCoversUpdatedEvent(message.Series)); } + public void HandleAsync(MovieUpdatedEvent message) + { + EnsureCovers(message.Movie); + _eventAggregator.PublishEvent(new MediaCoversUpdatedEvent(message.Movie)); + } + + public void HandleAsync(MovieAddedEvent message) + { + EnsureCovers(message.Movie); + _eventAggregator.PublishEvent(new MediaCoversUpdatedEvent(message.Movie)); + } + public void HandleAsync(SeriesDeletedEvent message) { var path = GetSeriesCoverPath(message.Series.Id); diff --git a/src/NzbDrone.Core/MediaCover/MediaCoversUpdatedEvent.cs b/src/NzbDrone.Core/MediaCover/MediaCoversUpdatedEvent.cs index 7335f7f9b..2f56e7cb0 100644 --- a/src/NzbDrone.Core/MediaCover/MediaCoversUpdatedEvent.cs +++ b/src/NzbDrone.Core/MediaCover/MediaCoversUpdatedEvent.cs @@ -7,9 +7,16 @@ public class MediaCoversUpdatedEvent : IEvent { public Series Series { get; set; } + public Movie Movie { get; set; } + public MediaCoversUpdatedEvent(Series series) { Series = series; } + + public MediaCoversUpdatedEvent(Movie movie) + { + Movie = movie; + } } } diff --git a/src/UI/AddMovies/SearchResultView.js b/src/UI/AddMovies/SearchResultView.js index ff697b7d9..839b2d1ee 100644 --- a/src/UI/AddMovies/SearchResultView.js +++ b/src/UI/AddMovies/SearchResultView.js @@ -43,6 +43,8 @@ var view = Marionette.ItemView.extend({ throw 'model is required'; } + console.log(this.route); + this.templateHelpers = {}; this._configureTemplateHelpers(); diff --git a/src/UI/Handlebars/Helpers/Series.js b/src/UI/Handlebars/Helpers/Series.js index f22ad5165..a95b2b10e 100644 --- a/src/UI/Handlebars/Helpers/Series.js +++ b/src/UI/Handlebars/Helpers/Series.js @@ -40,7 +40,7 @@ Handlebars.registerHelper('tvMazeUrl', function() { }); Handlebars.registerHelper('route', function() { - return StatusModel.get('urlBase') + '/series/' + this.titleSlug; + return StatusModel.get('urlBase') + '/movies/' + this.titleSlug; }); Handlebars.registerHelper('percentOfEpisodes', function() { diff --git a/src/UI/Movies/Details/EpisodeNumberCell.js b/src/UI/Movies/Details/EpisodeNumberCell.js new file mode 100644 index 000000000..9a84e644e --- /dev/null +++ b/src/UI/Movies/Details/EpisodeNumberCell.js @@ -0,0 +1,47 @@ +var Marionette = require('marionette'); +var NzbDroneCell = require('../../Cells/NzbDroneCell'); +var reqres = require('../../reqres'); +var SeriesCollection = require('../SeriesCollection'); + +module.exports = NzbDroneCell.extend({ + className : 'episode-number-cell', + template : 'Series/Details/EpisodeNumberCellTemplate', + + render : function() { + this.$el.empty(); + this.$el.html(this.model.get('episodeNumber')); + + var series = SeriesCollection.get(this.model.get('seriesId')); + + if (series.get('seriesType') === 'anime' && this.model.has('absoluteEpisodeNumber')) { + this.$el.html('{0} ({1})'.format(this.model.get('episodeNumber'), this.model.get('absoluteEpisodeNumber'))); + } + + var alternateTitles = []; + + if (reqres.hasHandler(reqres.Requests.GetAlternateNameBySeasonNumber)) { + alternateTitles = reqres.request(reqres.Requests.GetAlternateNameBySeasonNumber, this.model.get('seriesId'), this.model.get('seasonNumber'), this.model.get('sceneSeasonNumber')); + } + + if (this.model.get('sceneSeasonNumber') > 0 || this.model.get('sceneEpisodeNumber') > 0 || this.model.has('sceneAbsoluteEpisodeNumber') || alternateTitles.length > 0) { + this.templateFunction = Marionette.TemplateCache.get(this.template); + + var json = this.model.toJSON(); + json.alternateTitles = alternateTitles; + + var html = this.templateFunction(json); + + this.$el.popover({ + content : html, + html : true, + trigger : 'hover', + title : 'Scene Information', + placement : 'right', + container : this.$el + }); + } + + this.delegateEvents(); + return this; + } +}); \ No newline at end of file diff --git a/src/UI/Movies/Details/EpisodeNumberCellTemplate.hbs b/src/UI/Movies/Details/EpisodeNumberCellTemplate.hbs new file mode 100644 index 000000000..a9028a423 --- /dev/null +++ b/src/UI/Movies/Details/EpisodeNumberCellTemplate.hbs @@ -0,0 +1,39 @@ +
+ {{#if sceneSeasonNumber}} +
+
Season
+
{{sceneSeasonNumber}}
+
+ {{/if}} + + {{#if sceneEpisodeNumber}} +
+
Episode
+
{{sceneEpisodeNumber}}
+
+ {{/if}} + + {{#if sceneAbsoluteEpisodeNumber}} +
+
Absolute
+
{{sceneAbsoluteEpisodeNumber}}
+
+ {{/if}} + + {{#if alternateTitles}} +
+ {{#if_gt alternateTitles.length compare="1"}} +
Titles
+ {{else}} +
Title
+ {{/if_gt}} +
+
    + {{#each alternateTitles}} +
  • {{title}}
  • + {{/each}} +
+
+
+ {{/if}} +
\ No newline at end of file diff --git a/src/UI/Movies/Details/EpisodeWarningCell.js b/src/UI/Movies/Details/EpisodeWarningCell.js new file mode 100644 index 000000000..c9befe7a1 --- /dev/null +++ b/src/UI/Movies/Details/EpisodeWarningCell.js @@ -0,0 +1,21 @@ +var NzbDroneCell = require('../../Cells/NzbDroneCell'); +var SeriesCollection = require('../SeriesCollection'); + +module.exports = NzbDroneCell.extend({ + className : 'episode-warning-cell', + + render : function() { + this.$el.empty(); + + if (this.model.get('unverifiedSceneNumbering')) { + this.$el.html(''); + } + + else if (SeriesCollection.get(this.model.get('seriesId')).get('seriesType') === 'anime' && this.model.get('seasonNumber') > 0 && !this.model.has('absoluteEpisodeNumber')) { + this.$el.html(''); + } + + this.delegateEvents(); + return this; + } +}); \ No newline at end of file diff --git a/src/UI/Movies/Details/InfoView.js b/src/UI/Movies/Details/InfoView.js new file mode 100644 index 000000000..c7fab9fc4 --- /dev/null +++ b/src/UI/Movies/Details/InfoView.js @@ -0,0 +1,18 @@ +var Marionette = require('marionette'); + +module.exports = Marionette.ItemView.extend({ + template : 'Series/Details/InfoViewTemplate', + + initialize : function(options) { + this.episodeFileCollection = options.episodeFileCollection; + + this.listenTo(this.model, 'change', this.render); + this.listenTo(this.episodeFileCollection, 'sync', this.render); + }, + + templateHelpers : function() { + return { + fileCount : this.episodeFileCollection.length + }; + } +}); \ No newline at end of file diff --git a/src/UI/Movies/Details/InfoViewTemplate.hbs b/src/UI/Movies/Details/InfoViewTemplate.hbs new file mode 100644 index 000000000..666003f77 --- /dev/null +++ b/src/UI/Movies/Details/InfoViewTemplate.hbs @@ -0,0 +1,73 @@ +
+
+ {{profile profileId}} + + {{#if network}} + {{network}} + {{/if}} + + {{runtime}} minutes + {{path}} + + {{#if ratings}} + {{ratings.value}} + {{/if}} + + {{Bytes sizeOnDisk}} + + {{#if_eq fileCount compare="1"}} + 1 file + {{else}} + {{fileCount}} files + {{/if_eq}} + + {{#if_eq status compare="continuing"}} + Continuing + {{else}} + Ended + {{/if_eq}} +
+
+ + + + {{#if imdbId}} + IMDB + {{/if}} + + {{#if tvRageId}} + TV Rage + {{/if}} + + {{#if tvMazeId}} + TV Maze + {{/if}} + +
+
+ +{{#if alternateTitles}} +
+
+ {{#each alternateTitles}} + {{#if_eq seasonNumber compare="-1"}} + {{title}} + {{/if_eq}} + + {{#if_eq sceneSeasonNumber compare="-1"}} + {{title}} + {{/if_eq}} + {{/each}} +
+
+{{/if}} + +{{#if tags}} +
+
+ {{tagDisplay tags}} +
+
+{{/if}} diff --git a/src/UI/Movies/Details/MoviesDetailsLayout.js b/src/UI/Movies/Details/MoviesDetailsLayout.js new file mode 100644 index 000000000..eb8a74e28 --- /dev/null +++ b/src/UI/Movies/Details/MoviesDetailsLayout.js @@ -0,0 +1,264 @@ +var $ = require('jquery'); +var _ = require('underscore'); +var vent = require('vent'); +var reqres = require('../../reqres'); +var Marionette = require('marionette'); +var Backbone = require('backbone'); +var MoviesCollection = require('../MoviesCollection'); +var InfoView = require('./InfoView'); +var CommandController = require('../../Commands/CommandController'); +var LoadingView = require('../../Shared/LoadingView'); +var EpisodeFileEditorLayout = require('../../EpisodeFile/Editor/EpisodeFileEditorLayout'); +require('backstrech'); +require('../../Mixins/backbone.signalr.mixin'); + +module.exports = Marionette.Layout.extend({ + itemViewContainer : '.x-movie-seasons', + template : 'Movies/Details/MoviesDetailsTemplate', + + regions : { + seasons : '#seasons', + info : '#info' + }, + + ui : { + header : '.x-header', + monitored : '.x-monitored', + edit : '.x-edit', + refresh : '.x-refresh', + rename : '.x-rename', + search : '.x-search', + poster : '.x-movie-poster', + manualSearch : '.x-manual-search' + }, + + events : { + 'click .x-episode-file-editor' : '_openEpisodeFileEditor', + 'click .x-monitored' : '_toggleMonitored', + 'click .x-edit' : '_editMovies', + 'click .x-refresh' : '_refreshMovies', + 'click .x-rename' : '_renameMovies', + 'click .x-search' : '_moviesSearch', + 'click .x-manual-search' : '_manualSearchM' + }, + + initialize : function() { + this.moviesCollection = MoviesCollection.clone(); + this.moviesCollection.shadowCollection.bindSignalR(); + + this.listenTo(this.model, 'change:monitored', this._setMonitoredState); + this.listenTo(this.model, 'remove', this._moviesRemoved); + this.listenTo(vent, vent.Events.CommandComplete, this._commandComplete); + + this.listenTo(this.model, 'change', function(model, options) { + if (options && options.changeSource === 'signalr') { + this._refresh(); + } + }); + + this.listenTo(this.model, 'change:images', this._updateImages); + }, + + onShow : function() { + this._showBackdrop(); + this._showSeasons(); + this._setMonitoredState(); + this._showInfo(); + }, + + onRender : function() { + CommandController.bindToCommand({ + element : this.ui.refresh, + command : { + name : 'refreshMovies' + } + }); + CommandController.bindToCommand({ + element : this.ui.search, + command : { + name : 'moviesSearch' + } + }); + + CommandController.bindToCommand({ + element : this.ui.rename, + command : { + name : 'renameFiles', + movieId : this.model.id, + seasonNumber : -1 + } + }); + }, + + onClose : function() { + if (this._backstrech) { + this._backstrech.destroy(); + delete this._backstrech; + } + + $('body').removeClass('backdrop'); + reqres.removeHandler(reqres.Requests.GetEpisodeFileById); + }, + + _getImage : function(type) { + var image = _.where(this.model.get('images'), { coverType : type }); + + if (image && image[0]) { + return image[0].url; + } + + return undefined; + }, + + _toggleMonitored : function() { + var savePromise = this.model.save('monitored', !this.model.get('monitored'), { wait : true }); + + this.ui.monitored.spinForPromise(savePromise); + }, + + _setMonitoredState : function() { + var monitored = this.model.get('monitored'); + + this.ui.monitored.removeAttr('data-idle-icon'); + this.ui.monitored.removeClass('fa-spin icon-sonarr-spinner'); + + if (monitored) { + this.ui.monitored.addClass('icon-sonarr-monitored'); + this.ui.monitored.removeClass('icon-sonarr-unmonitored'); + this.$el.removeClass('movie-not-monitored'); + } else { + this.ui.monitored.addClass('icon-sonarr-unmonitored'); + this.ui.monitored.removeClass('icon-sonarr-monitored'); + this.$el.addClass('movie-not-monitored'); + } + }, + + _editMovies : function() { + vent.trigger(vent.Commands.EditMoviesCommand, { movie : this.model }); + }, + + _refreshMovies : function() { + CommandController.Execute('refreshMovies', { + name : 'refreshMovies', + movieId : this.model.id + }); + }, + + _moviesRemoved : function() { + Backbone.history.navigate('/', { trigger : true }); + }, + + _renameMovies : function() { + vent.trigger(vent.Commands.ShowRenamePreview, { movie : this.model }); + }, + + _moviesSearch : function() { + CommandController.Execute('moviesSearch', { + name : 'moviesSearch', + movieId : this.model.id + }); + }, + + _showSeasons : function() { + var self = this; + + return; + + reqres.setHandler(reqres.Requests.GetEpisodeFileById, function(episodeFileId) { + return self.episodeFileCollection.get(episodeFileId); + }); + + reqres.setHandler(reqres.Requests.GetAlternateNameBySeasonNumber, function(moviesId, seasonNumber, sceneSeasonNumber) { + if (self.model.get('id') !== moviesId) { + return []; + } + + if (sceneSeasonNumber === undefined) { + sceneSeasonNumber = seasonNumber; + } + + return _.where(self.model.get('alternateTitles'), + function(alt) { + return alt.sceneSeasonNumber === sceneSeasonNumber || alt.seasonNumber === seasonNumber; + }); + }); + + $.when(this.episodeCollection.fetch(), this.episodeFileCollection.fetch()).done(function() { + var seasonCollectionView = new SeasonCollectionView({ + collection : self.seasonCollection, + episodeCollection : self.episodeCollection, + movies : self.model + }); + + if (!self.isClosed) { + self.seasons.show(seasonCollectionView); + } + }); + }, + + _showInfo : function() { + this.info.show(new InfoView({ + model : this.model, + episodeFileCollection : this.episodeFileCollection + })); + }, + + _commandComplete : function(options) { + if (options.command.get('name') === 'renamefiles') { + if (options.command.get('moviesId') === this.model.get('id')) { + this._refresh(); + } + } + }, + + _refresh : function() { + this.seasonCollection.add(this.model.get('seasons'), { merge : true }); + this.episodeCollection.fetch(); + this.episodeFileCollection.fetch(); + + this._setMonitoredState(); + this._showInfo(); + }, + + _openEpisodeFileEditor : function() { + var view = new EpisodeFileEditorLayout({ + movies : this.model, + episodeCollection : this.episodeCollection + }); + + vent.trigger(vent.Commands.OpenModalCommand, view); + }, + + _updateImages : function () { + var poster = this._getImage('poster'); + + if (poster) { + this.ui.poster.attr('src', poster); + } + + this._showBackdrop(); + }, + + _showBackdrop : function () { + $('body').addClass('backdrop'); + var fanArt = this._getImage('fanart'); + + if (fanArt) { + this._backstrech = $.backstretch(fanArt); + } else { + $('body').removeClass('backdrop'); + } + }, + + _manualSearchM : function() { + console.warn("Manual Search started"); + console.warn(this.model.get("moviesId")); + console.warn(this.model) + console.warn(this.episodeCollection); + vent.trigger(vent.Commands.ShowEpisodeDetails, { + episode : this.episodeCollection.models[0], + hideMoviesLink : true, + openingTab : 'search' + }); + } +}); diff --git a/src/UI/Movies/Details/MoviesDetailsTemplate.hbs b/src/UI/Movies/Details/MoviesDetailsTemplate.hbs new file mode 100644 index 000000000..8bacf94b0 --- /dev/null +++ b/src/UI/Movies/Details/MoviesDetailsTemplate.hbs @@ -0,0 +1,38 @@ +
+
+ {{poster}} +
+
+
+

+ + {{title}} +
+
+ +
+
+ +
+
+ +
+ + +
+ +
+
+

+
+
+ {{overview}} +
+
+
+
+
diff --git a/src/UI/Movies/Details/SeasonCollectionView.js b/src/UI/Movies/Details/SeasonCollectionView.js new file mode 100644 index 000000000..24da6171c --- /dev/null +++ b/src/UI/Movies/Details/SeasonCollectionView.js @@ -0,0 +1,44 @@ +var _ = require('underscore'); +var Marionette = require('marionette'); +var SeasonLayout = require('./SeasonLayout'); +var AsSortedCollectionView = require('../../Mixins/AsSortedCollectionView'); + +var view = Marionette.CollectionView.extend({ + + itemView : SeasonLayout, + + initialize : function(options) { + if (!options.episodeCollection) { + throw 'episodeCollection is needed'; + } + + this.episodeCollection = options.episodeCollection; + this.series = options.series; + }, + + itemViewOptions : function() { + return { + episodeCollection : this.episodeCollection, + series : this.series + }; + }, + + onEpisodeGrabbed : function(message) { + if (message.episode.series.id !== this.episodeCollection.seriesId) { + return; + } + + var self = this; + + _.each(message.episode.episodes, function(episode) { + var ep = self.episodeCollection.get(episode.id); + ep.set('downloading', true); + }); + + this.render(); + } +}); + +AsSortedCollectionView.call(view); + +module.exports = view; \ No newline at end of file diff --git a/src/UI/Movies/Details/SeasonLayout.js b/src/UI/Movies/Details/SeasonLayout.js new file mode 100644 index 000000000..cf10b6fa8 --- /dev/null +++ b/src/UI/Movies/Details/SeasonLayout.js @@ -0,0 +1,301 @@ +var vent = require('vent'); +var Marionette = require('marionette'); +var Backgrid = require('backgrid'); +var ToggleCell = require('../../Cells/EpisodeMonitoredCell'); +var EpisodeTitleCell = require('../../Cells/EpisodeTitleCell'); +var RelativeDateCell = require('../../Cells/RelativeDateCell'); +var EpisodeStatusCell = require('../../Cells/EpisodeStatusCell'); +var EpisodeActionsCell = require('../../Cells/EpisodeActionsCell'); +var EpisodeNumberCell = require('./EpisodeNumberCell'); +var EpisodeWarningCell = require('./EpisodeWarningCell'); +var CommandController = require('../../Commands/CommandController'); +var EpisodeFileEditorLayout = require('../../EpisodeFile/Editor/EpisodeFileEditorLayout'); +var moment = require('moment'); +var _ = require('underscore'); +var Messenger = require('../../Shared/Messenger'); + +module.exports = Marionette.Layout.extend({ + template : 'Series/Details/SeasonLayoutTemplate', + + ui : { + seasonSearch : '.x-season-search', + seasonMonitored : '.x-season-monitored', + seasonRename : '.x-season-rename' + }, + + events : { + 'click .x-season-episode-file-editor' : '_openEpisodeFileEditor', + 'click .x-season-monitored' : '_seasonMonitored', + 'click .x-season-search' : '_seasonSearch', + 'click .x-season-rename' : '_seasonRename', + 'click .x-show-hide-episodes' : '_showHideEpisodes', + 'dblclick .series-season h2' : '_showHideEpisodes' + }, + + regions : { + episodeGrid : '.x-episode-grid' + }, + + columns : [ + { + name : 'monitored', + label : '', + cell : ToggleCell, + trueClass : 'icon-sonarr-monitored', + falseClass : 'icon-sonarr-unmonitored', + tooltip : 'Toggle monitored status', + sortable : false + }, + { + name : 'episodeNumber', + label : '#', + cell : EpisodeNumberCell + }, + { + name : 'this', + label : '', + cell : EpisodeWarningCell, + sortable : false, + className : 'episode-warning-cell' + }, + { + name : 'this', + label : 'Title', + hideSeriesLink : true, + cell : EpisodeTitleCell, + sortable : false + }, + { + name : 'airDateUtc', + label : 'Air Date', + cell : RelativeDateCell + }, + { + name : 'status', + label : 'Status', + cell : EpisodeStatusCell, + sortable : false + }, + { + name : 'this', + label : '', + cell : EpisodeActionsCell, + sortable : false + } + ], + + templateHelpers : function() { + var episodeCount = this.episodeCollection.filter(function(episode) { + return episode.get('hasFile') || episode.get('monitored') && moment(episode.get('airDateUtc')).isBefore(moment()); + }).length; + + var episodeFileCount = this.episodeCollection.where({ hasFile : true }).length; + var percentOfEpisodes = 100; + + if (episodeCount > 0) { + percentOfEpisodes = episodeFileCount / episodeCount * 100; + } + + return { + showingEpisodes : this.showingEpisodes, + episodeCount : episodeCount, + episodeFileCount : episodeFileCount, + percentOfEpisodes : percentOfEpisodes + }; + }, + + initialize : function(options) { + if (!options.episodeCollection) { + throw 'episodeCollection is required'; + } + + this.series = options.series; + this.fullEpisodeCollection = options.episodeCollection; + this.episodeCollection = this.fullEpisodeCollection.bySeason(this.model.get('seasonNumber')); + this._updateEpisodeCollection(); + + this.showingEpisodes = this._shouldShowEpisodes(); + + this.listenTo(this.model, 'sync', this._afterSeasonMonitored); + this.listenTo(this.episodeCollection, 'sync', this.render); + + this.listenTo(this.fullEpisodeCollection, 'sync', this._refreshEpisodes); + }, + + onRender : function() { + if (this.showingEpisodes) { + this._showEpisodes(); + } + + this._setSeasonMonitoredState(); + + CommandController.bindToCommand({ + element : this.ui.seasonSearch, + command : { + name : 'seasonSearch', + seriesId : this.series.id, + seasonNumber : this.model.get('seasonNumber') + } + }); + + CommandController.bindToCommand({ + element : this.ui.seasonRename, + command : { + name : 'renameFiles', + seriesId : this.series.id, + seasonNumber : this.model.get('seasonNumber') + } + }); + }, + + _seasonSearch : function() { + CommandController.Execute('seasonSearch', { + name : 'seasonSearch', + seriesId : this.series.id, + seasonNumber : this.model.get('seasonNumber') + }); + }, + + _seasonRename : function() { + vent.trigger(vent.Commands.ShowRenamePreview, { + series : this.series, + seasonNumber : this.model.get('seasonNumber') + }); + }, + + _seasonMonitored : function() { + if (!this.series.get('monitored')) { + + Messenger.show({ + message : 'Unable to change monitored state when series is not monitored', + type : 'error' + }); + + return; + } + + var name = 'monitored'; + this.model.set(name, !this.model.get(name)); + this.series.setSeasonMonitored(this.model.get('seasonNumber')); + + var savePromise = this.series.save().always(this._afterSeasonMonitored.bind(this)); + + this.ui.seasonMonitored.spinForPromise(savePromise); + }, + + _afterSeasonMonitored : function() { + var self = this; + + _.each(this.episodeCollection.models, function(episode) { + episode.set({ monitored : self.model.get('monitored') }); + }); + + this.render(); + }, + + _setSeasonMonitoredState : function() { + this.ui.seasonMonitored.removeClass('icon-sonarr-spinner fa-spin'); + + if (this.model.get('monitored')) { + this.ui.seasonMonitored.addClass('icon-sonarr-monitored'); + this.ui.seasonMonitored.removeClass('icon-sonarr-unmonitored'); + } else { + this.ui.seasonMonitored.addClass('icon-sonarr-unmonitored'); + this.ui.seasonMonitored.removeClass('icon-sonarr-monitored'); + } + }, + + _showEpisodes : function() { + this.episodeGrid.show(new Backgrid.Grid({ + columns : this.columns, + collection : this.episodeCollection, + className : 'table table-hover season-grid' + })); + }, + + _shouldShowEpisodes : function() { + var startDate = moment().add('month', -1); + var endDate = moment().add('year', 1); + + return this.episodeCollection.some(function(episode) { + var airDate = episode.get('airDateUtc'); + + if (airDate) { + var airDateMoment = moment(airDate); + + if (airDateMoment.isAfter(startDate) && airDateMoment.isBefore(endDate)) { + return true; + } + } + + return false; + }); + }, + + _showHideEpisodes : function() { + if (this.showingEpisodes) { + this.showingEpisodes = false; + this.episodeGrid.close(); + } else { + this.showingEpisodes = true; + this._showEpisodes(); + } + + this.templateHelpers.showingEpisodes = this.showingEpisodes; + this.render(); + }, + + _episodeMonitoredToggled : function(options) { + var model = options.model; + var shiftKey = options.shiftKey; + + if (!this.episodeCollection.get(model.get('id'))) { + return; + } + + if (!shiftKey) { + return; + } + + var lastToggled = this.episodeCollection.lastToggled; + + if (!lastToggled) { + return; + } + + var currentIndex = this.episodeCollection.indexOf(model); + var lastIndex = this.episodeCollection.indexOf(lastToggled); + + var low = Math.min(currentIndex, lastIndex); + var high = Math.max(currentIndex, lastIndex); + var range = _.range(low + 1, high); + + this.episodeCollection.lastToggled = model; + }, + + _updateEpisodeCollection : function() { + var self = this; + + this.episodeCollection.add(this.fullEpisodeCollection.bySeason(this.model.get('seasonNumber')).models, { merge : true }); + + this.episodeCollection.each(function(model) { + model.episodeCollection = self.episodeCollection; + }); + }, + + _refreshEpisodes : function() { + this._updateEpisodeCollection(); + this.episodeCollection.fullCollection.sort(); + this.render(); + }, + + _openEpisodeFileEditor : function() { + var view = new EpisodeFileEditorLayout({ + model : this.model, + series : this.series, + episodeCollection : this.episodeCollection + }); + + vent.trigger(vent.Commands.OpenModalCommand, view); + } +}); \ No newline at end of file diff --git a/src/UI/Movies/Details/SeasonLayoutTemplate.hbs b/src/UI/Movies/Details/SeasonLayoutTemplate.hbs new file mode 100644 index 000000000..06034f19d --- /dev/null +++ b/src/UI/Movies/Details/SeasonLayoutTemplate.hbs @@ -0,0 +1,50 @@ +
+

+ + + {{#if seasonNumber}} + Season {{seasonNumber}} + {{else}} + Specials + {{/if}} + + + {{#if_eq episodeCount compare=0}} + {{#if monitored}} +   + {{else}} +   + {{/if}} + {{else}} + {{#if_eq percentOfEpisodes compare=100}} + {{episodeFileCount}} / {{episodeCount}} + {{else}} + {{episodeFileCount}} / {{episodeCount}} + {{/if_eq}} + {{/if_eq}} + + +
+ +
+
+ +
+ +
+

+
+

+ {{#if showingEpisodes}} + + Hide Episodes + {{else}} + + Show Episodes + {{/if}} +

+
+
+
diff --git a/src/UI/Movies/Index/EmptyTemplate.hbs b/src/UI/Movies/Index/EmptyTemplate.hbs new file mode 100644 index 000000000..06fb40fe5 --- /dev/null +++ b/src/UI/Movies/Index/EmptyTemplate.hbs @@ -0,0 +1,16 @@ +
+
+
+ + You must be new around here, You should add some series. +
+
+ +
diff --git a/src/UI/Movies/Index/EmptyView.js b/src/UI/Movies/Index/EmptyView.js new file mode 100644 index 000000000..01dcc07a4 --- /dev/null +++ b/src/UI/Movies/Index/EmptyView.js @@ -0,0 +1,5 @@ +var Marionette = require('marionette'); + +module.exports = Marionette.CompositeView.extend({ + template : 'Series/Index/EmptyTemplate' +}); \ No newline at end of file diff --git a/src/UI/Movies/Index/EpisodeProgressPartial.hbs b/src/UI/Movies/Index/EpisodeProgressPartial.hbs new file mode 100644 index 000000000..db5c49a2b --- /dev/null +++ b/src/UI/Movies/Index/EpisodeProgressPartial.hbs @@ -0,0 +1,4 @@ +
+ {{episodeFileCount}} / {{episodeCount}} +
{{episodeFileCount}} / {{episodeCount}}
+
\ No newline at end of file diff --git a/src/UI/Movies/Index/FooterModel.js b/src/UI/Movies/Index/FooterModel.js new file mode 100644 index 000000000..235552061 --- /dev/null +++ b/src/UI/Movies/Index/FooterModel.js @@ -0,0 +1,4 @@ +var Backbone = require('backbone'); +var _ = require('underscore'); + +module.exports = Backbone.Model.extend({}); \ No newline at end of file diff --git a/src/UI/Movies/Index/FooterView.js b/src/UI/Movies/Index/FooterView.js new file mode 100644 index 000000000..1d31cc404 --- /dev/null +++ b/src/UI/Movies/Index/FooterView.js @@ -0,0 +1,5 @@ +var Marionette = require('marionette'); + +module.exports = Marionette.CompositeView.extend({ + template : 'Series/Index/FooterViewTemplate' +}); \ No newline at end of file diff --git a/src/UI/Movies/Index/FooterViewTemplate.hbs b/src/UI/Movies/Index/FooterViewTemplate.hbs new file mode 100644 index 000000000..1b45fa747 --- /dev/null +++ b/src/UI/Movies/Index/FooterViewTemplate.hbs @@ -0,0 +1,46 @@ +
+
+
    +
  • Continuing (All episodes downloaded)
  • +
  • Ended (All episodes downloaded)
  • +
  • Missing Episodes (Series monitored)
  • +
  • Missing Episodes (Series not monitored)
  • +
+
+
+
+
+
+
Series
+
{{series}}
+ +
Ended
+
{{ended}}
+ +
Continuing
+
{{continuing}}
+
+
+ +
+
+
Monitored
+
{{monitored}}
+ +
Unmonitored
+
{{unmonitored}}
+
+
+ +
+
+
Episodes
+
{{episodes}}
+ +
Files
+
{{episodeFiles}}
+
+
+
+
+
diff --git a/src/UI/Movies/Index/MoviesIndexItemView.js b/src/UI/Movies/Index/MoviesIndexItemView.js new file mode 100644 index 000000000..427fe489e --- /dev/null +++ b/src/UI/Movies/Index/MoviesIndexItemView.js @@ -0,0 +1,35 @@ +var vent = require('vent'); +var Marionette = require('marionette'); +var CommandController = require('../../Commands/CommandController'); + +module.exports = Marionette.ItemView.extend({ + ui : { + refresh : '.x-refresh' + }, + + events : { + 'click .x-edit' : '_editSeries', + 'click .x-refresh' : '_refreshSeries' + }, + + onRender : function() { + CommandController.bindToCommand({ + element : this.ui.refresh, + command : { + name : 'refreshSeries', + seriesId : this.model.get('id') + } + }); + }, + + _editSeries : function() { + vent.trigger(vent.Commands.EditSeriesCommand, { series : this.model }); + }, + + _refreshSeries : function() { + CommandController.Execute('refreshSeries', { + name : 'refreshSeries', + seriesId : this.model.id + }); + } +}); \ No newline at end of file diff --git a/src/UI/Movies/Index/MoviesIndexLayout.js b/src/UI/Movies/Index/MoviesIndexLayout.js new file mode 100644 index 000000000..128575333 --- /dev/null +++ b/src/UI/Movies/Index/MoviesIndexLayout.js @@ -0,0 +1,354 @@ +var _ = require('underscore'); +var Marionette = require('marionette'); +var Backgrid = require('backgrid'); +var PosterCollectionView = require('./Posters/SeriesPostersCollectionView'); +var ListCollectionView = require('./Overview/SeriesOverviewCollectionView'); +var EmptyView = require('./EmptyView'); +var MoviesCollection = require('../MoviesCollection'); +var RelativeDateCell = require('../../Cells/RelativeDateCell'); +var SeriesTitleCell = require('../../Cells/SeriesTitleCell'); +var TemplatedCell = require('../../Cells/TemplatedCell'); +var ProfileCell = require('../../Cells/ProfileCell'); +var EpisodeProgressCell = require('../../Cells/EpisodeProgressCell'); +var SeriesActionsCell = require('../../Cells/SeriesActionsCell'); +var SeriesStatusCell = require('../../Cells/SeriesStatusCell'); +var FooterView = require('./FooterView'); +var FooterModel = require('./FooterModel'); +var ToolbarLayout = require('../../Shared/Toolbar/ToolbarLayout'); +require('../../Mixins/backbone.signalr.mixin'); + +module.exports = Marionette.Layout.extend({ + template : 'Movies/Index/MoviesIndexLayoutTemplate', + + regions : { + seriesRegion : '#x-series', + toolbar : '#x-toolbar', + toolbar2 : '#x-toolbar2', + footer : '#x-series-footer' + }, + + columns : [ + { + name : 'statusWeight', + label : '', + cell : SeriesStatusCell + }, + { + name : 'title', + label : 'Title', + cell : SeriesTitleCell, + cellValue : 'this', + sortValue : 'sortTitle' + }, + { + name : 'seasonCount', + label : 'Seasons', + cell : 'integer' + }, + { + name : 'profileId', + label : 'Profile', + cell : ProfileCell + }, + { + name : 'network', + label : 'Network', + cell : 'string' + }, + { + name : 'nextAiring', + label : 'Next Airing', + cell : RelativeDateCell + }, + { + name : 'percentOfEpisodes', + label : 'Episodes', + cell : EpisodeProgressCell, + className : 'episode-progress-cell' + }, + { + name : 'this', + label : '', + sortable : false, + cell : SeriesActionsCell + } + ], + + leftSideButtons : { + type : 'default', + storeState : false, + collapse : true, + items : [ + { + title : 'Add Movie', + icon : 'icon-sonarr-add', + route : 'addmovies' + }, + { + title : 'Season Pass', + icon : 'icon-sonarr-monitored', + route : 'seasonpass' + }, + { + title : 'Series Editor', + icon : 'icon-sonarr-edit', + route : 'serieseditor' + }, + { + title : 'RSS Sync', + icon : 'icon-sonarr-rss', + command : 'rsssync', + errorMessage : 'RSS Sync Failed!' + }, + { + title : 'Update Library', + icon : 'icon-sonarr-refresh', + command : 'refreshseries', + successMessage : 'Library was updated!', + errorMessage : 'Library update failed!' + } + ] + }, + + initialize : function() { + this.seriesCollection = MoviesCollection.clone(); + this.seriesCollection.shadowCollection.bindSignalR(); + + this.listenTo(this.seriesCollection.shadowCollection, 'sync', function(model, collection, options) { + this.seriesCollection.fullCollection.resetFiltered(); + this._renderView(); + }); + + this.listenTo(this.seriesCollection.shadowCollection, 'add', function(model, collection, options) { + this.seriesCollection.fullCollection.resetFiltered(); + this._renderView(); + }); + + this.listenTo(this.seriesCollection.shadowCollection, 'remove', function(model, collection, options) { + this.seriesCollection.fullCollection.resetFiltered(); + this._renderView(); + }); + + this.sortingOptions = { + type : 'sorting', + storeState : false, + viewCollection : this.seriesCollection, + items : [ + { + title : 'Title', + name : 'title' + }, + { + title : 'Seasons', + name : 'seasonCount' + }, + { + title : 'Quality', + name : 'profileId' + }, + { + title : 'Network', + name : 'network' + }, + { + title : 'Next Airing', + name : 'nextAiring' + }, + { + title : 'Episodes', + name : 'percentOfEpisodes' + } + ] + }; + + this.filteringOptions = { + type : 'radio', + storeState : true, + menuKey : 'series.filterMode', + defaultAction : 'all', + items : [ + { + key : 'all', + title : '', + tooltip : 'All', + icon : 'icon-sonarr-all', + callback : this._setFilter + }, + { + key : 'monitored', + title : '', + tooltip : 'Monitored Only', + icon : 'icon-sonarr-monitored', + callback : this._setFilter + }, + { + key : 'continuing', + title : '', + tooltip : 'Continuing Only', + icon : 'icon-sonarr-series-continuing', + callback : this._setFilter + }, + { + key : 'ended', + title : '', + tooltip : 'Ended Only', + icon : 'icon-sonarr-series-ended', + callback : this._setFilter + }, + { + key : 'missing', + title : '', + tooltip : 'Missing', + icon : 'icon-sonarr-missing', + callback : this._setFilter + } + ] + }; + + this.viewButtons = { + type : 'radio', + storeState : true, + menuKey : 'seriesViewMode', + defaultAction : 'listView', + items : [ + { + key : 'posterView', + title : '', + tooltip : 'Posters', + icon : 'icon-sonarr-view-poster', + callback : this._showPosters + }, + { + key : 'listView', + title : '', + tooltip : 'Overview List', + icon : 'icon-sonarr-view-list', + callback : this._showList + }, + { + key : 'tableView', + title : '', + tooltip : 'Table', + icon : 'icon-sonarr-view-table', + callback : this._showTable + } + ] + }; + }, + + onShow : function() { + this._showToolbar(); + this._fetchCollection(); + }, + + _showTable : function() { + this.currentView = new Backgrid.Grid({ + collection : this.seriesCollection, + columns : this.columns, + className : 'table table-hover' + }); + + this._renderView(); + }, + + _showList : function() { + this.currentView = new ListCollectionView({ + collection : this.seriesCollection + }); + + this._renderView(); + }, + + _showPosters : function() { + this.currentView = new PosterCollectionView({ + collection : this.seriesCollection + }); + + this._renderView(); + }, + + _renderView : function() { + if (MoviesCollection.length === 0) { + this.seriesRegion.show(new EmptyView()); + + this.toolbar.close(); + this.toolbar2.close(); + } else { + this.seriesRegion.show(this.currentView); + + this._showToolbar(); + this._showFooter(); + } + }, + + _fetchCollection : function() { + this.seriesCollection.fetch(); + }, + + _setFilter : function(buttonContext) { + var mode = buttonContext.model.get('key'); + + this.seriesCollection.setFilterMode(mode); + }, + + _showToolbar : function() { + if (this.toolbar.currentView) { + return; + } + + this.toolbar2.show(new ToolbarLayout({ + right : [ + this.filteringOptions + ], + context : this + })); + + this.toolbar.show(new ToolbarLayout({ + right : [ + this.sortingOptions, + this.viewButtons + ], + left : [ + this.leftSideButtons + ], + context : this + })); + }, + + _showFooter : function() { + var footerModel = new FooterModel(); + var series = MoviesCollection.models.length; + var episodes = 0; + var episodeFiles = 0; + var ended = 0; + var continuing = 0; + var monitored = 0; + + _.each(MoviesCollection.models, function(model) { + episodes += model.get('episodeCount'); + episodeFiles += model.get('episodeFileCount'); + + if (model.get('status').toLowerCase() === 'ended') { + ended++; + } else { + continuing++; + } + + if (model.get('monitored')) { + monitored++; + } + }); + + footerModel.set({ + series : series, + ended : ended, + continuing : continuing, + monitored : monitored, + unmonitored : series - monitored, + episodes : episodes, + episodeFiles : episodeFiles + }); + + this.footer.show(new FooterView({ model : footerModel })); + } +}); diff --git a/src/UI/Movies/Index/MoviesIndexLayoutTemplate.hbs b/src/UI/Movies/Index/MoviesIndexLayoutTemplate.hbs new file mode 100644 index 000000000..0c41b4108 --- /dev/null +++ b/src/UI/Movies/Index/MoviesIndexLayoutTemplate.hbs @@ -0,0 +1,12 @@ +
+
+
+
+ +
+
+
+
+
+ + diff --git a/src/UI/Movies/Index/Overview/SeriesOverviewCollectionView.js b/src/UI/Movies/Index/Overview/SeriesOverviewCollectionView.js new file mode 100644 index 000000000..7db4b76f0 --- /dev/null +++ b/src/UI/Movies/Index/Overview/SeriesOverviewCollectionView.js @@ -0,0 +1,8 @@ +var Marionette = require('marionette'); +var ListItemView = require('./SeriesOverviewItemView'); + +module.exports = Marionette.CompositeView.extend({ + itemView : ListItemView, + itemViewContainer : '#x-series-list', + template : 'Series/Index/Overview/SeriesOverviewCollectionViewTemplate' +}); \ No newline at end of file diff --git a/src/UI/Movies/Index/Overview/SeriesOverviewCollectionViewTemplate.hbs b/src/UI/Movies/Index/Overview/SeriesOverviewCollectionViewTemplate.hbs new file mode 100644 index 000000000..046bb3348 --- /dev/null +++ b/src/UI/Movies/Index/Overview/SeriesOverviewCollectionViewTemplate.hbs @@ -0,0 +1 @@ +
diff --git a/src/UI/Movies/Index/Overview/SeriesOverviewItemView.js b/src/UI/Movies/Index/Overview/SeriesOverviewItemView.js new file mode 100644 index 000000000..0d3a23227 --- /dev/null +++ b/src/UI/Movies/Index/Overview/SeriesOverviewItemView.js @@ -0,0 +1,7 @@ +var vent = require('vent'); +var Marionette = require('marionette'); +var SeriesIndexItemView = require('../MoviesIndexItemView'); + +module.exports = SeriesIndexItemView.extend({ + template : 'Movies/Index/Overview/MoviesOverviewItemViewTemplate' +}); diff --git a/src/UI/Movies/Index/Overview/SeriesOverviewItemViewTemplate.hbs b/src/UI/Movies/Index/Overview/SeriesOverviewItemViewTemplate.hbs new file mode 100644 index 000000000..ee6ddddee --- /dev/null +++ b/src/UI/Movies/Index/Overview/SeriesOverviewItemViewTemplate.hbs @@ -0,0 +1,56 @@ +
+
+ +
+
+ +
+
+ + +
+
+
+ +
+
+   +
+
+
+
+ {{#if_eq status compare="ended"}} + Ended + {{/if_eq}} + + {{#if nextAiring}} + {{RelativeDate nextAiring}} + {{/if}} + + {{seasonCountHelper}} + + {{profile profileId}} +
+
+ {{> EpisodeProgressPartial }} +
+
+
+
+
diff --git a/src/UI/Movies/Index/Posters/SeriesPostersCollectionView.js b/src/UI/Movies/Index/Posters/SeriesPostersCollectionView.js new file mode 100644 index 000000000..0d6094f1c --- /dev/null +++ b/src/UI/Movies/Index/Posters/SeriesPostersCollectionView.js @@ -0,0 +1,8 @@ +var Marionette = require('marionette'); +var PosterItemView = require('./SeriesPostersItemView'); + +module.exports = Marionette.CompositeView.extend({ + itemView : PosterItemView, + itemViewContainer : '#x-series-posters', + template : 'Series/Index/Posters/SeriesPostersCollectionViewTemplate' +}); \ No newline at end of file diff --git a/src/UI/Movies/Index/Posters/SeriesPostersCollectionViewTemplate.hbs b/src/UI/Movies/Index/Posters/SeriesPostersCollectionViewTemplate.hbs new file mode 100644 index 000000000..11b8e8ac7 --- /dev/null +++ b/src/UI/Movies/Index/Posters/SeriesPostersCollectionViewTemplate.hbs @@ -0,0 +1 @@ +
    \ No newline at end of file diff --git a/src/UI/Movies/Index/Posters/SeriesPostersItemView.js b/src/UI/Movies/Index/Posters/SeriesPostersItemView.js new file mode 100644 index 000000000..bc4545906 --- /dev/null +++ b/src/UI/Movies/Index/Posters/SeriesPostersItemView.js @@ -0,0 +1,19 @@ +var SeriesIndexItemView = require('../MoviesIndexItemView'); + +module.exports = SeriesIndexItemView.extend({ + tagName : 'li', + template : 'Movies/Index/Posters/SeriesPostersItemViewTemplate', + + initialize : function() { + this.events['mouseenter .x-series-poster-container'] = 'posterHoverAction'; + this.events['mouseleave .x-series-poster-container'] = 'posterHoverAction'; + + this.ui.controls = '.x-series-controls'; + this.ui.title = '.x-title'; + }, + + posterHoverAction : function() { + this.ui.controls.slideToggle(); + this.ui.title.slideToggle(); + } +}); diff --git a/src/UI/Movies/Index/Posters/SeriesPostersItemViewTemplate.hbs b/src/UI/Movies/Index/Posters/SeriesPostersItemViewTemplate.hbs new file mode 100644 index 000000000..fba301c4f --- /dev/null +++ b/src/UI/Movies/Index/Posters/SeriesPostersItemViewTemplate.hbs @@ -0,0 +1,30 @@ +
    +
    +
    +
    + + +
    + {{#unless_eq status compare="continuing"}} +
    Ended
    + {{/unless_eq}} + + {{poster}} +
    {{title}}
    +
    +
    + {{title}} +
    +
    +
    + +
    +
    + {{> EpisodeProgressPartial }} + + {{#if nextAiring}} + {{RelativeDate nextAiring}} + {{/if}} +
    +
    +
    diff --git a/src/UI/Movies/MoviesController.js b/src/UI/Movies/MoviesController.js new file mode 100644 index 000000000..ba0f825eb --- /dev/null +++ b/src/UI/Movies/MoviesController.js @@ -0,0 +1,34 @@ +var NzbDroneController = require('../Shared/NzbDroneController'); +var AppLayout = require('../AppLayout'); +var MoviesCollection = require('./MoviesCollection'); +var MoviesIndexLayout = require('./Index/MoviesIndexLayout'); +var MoviesDetailsLayout = require('./Details/MoviesDetailsLayout'); + +module.exports = NzbDroneController.extend({ + _originalInit : NzbDroneController.prototype.initialize, + + initialize : function() { + this.route('', this.series); + this.route('movies', this.series); + this.route('movies/:query', this.seriesDetails); + + this._originalInit.apply(this, arguments); + }, + + series : function() { + this.setTitle('Movies'); + this.showMainRegion(new MoviesIndexLayout()); + }, + + seriesDetails : function(query) { + var series = MoviesCollection.where({ titleSlug : query }); + + if (series.length !== 0) { + var targetMovie = series[0]; + this.setTitle(targetMovie.get('title')); + this.showMainRegion(new MoviesDetailsLayout({ model : targetMovie })); + } else { + this.showNotFound(); + } + } +}); diff --git a/src/UI/main.js b/src/UI/main.js index f46f68b93..3978b36e0 100644 --- a/src/UI/main.js +++ b/src/UI/main.js @@ -5,7 +5,7 @@ var RouteBinder = require('./jQuery/RouteBinder'); var SignalRBroadcaster = require('./Shared/SignalRBroadcaster'); var NavbarLayout = require('./Navbar/NavbarLayout'); var AppLayout = require('./AppLayout'); -var SeriesController = require('./Series/SeriesController'); +var SeriesController = require('./Movies/MoviesController'); var Router = require('./Router'); var ModalController = require('./Shared/Modal/ModalController'); var ControlPanelController = require('./Shared/ControlPanel/ControlPanelController');