diff --git a/couchpotato/core/media/_base/library/main.py b/couchpotato/core/media/_base/library/main.py
old mode 100644
new mode 100755
index b90f4c38..b565d16e
--- a/couchpotato/core/media/_base/library/main.py
+++ b/couchpotato/core/media/_base/library/main.py
@@ -11,10 +11,13 @@ class Library(LibraryBase):
def __init__(self):
addEvent('library.title', self.title)
addEvent('library.related', self.related)
+ addEvent('library.tree', self.tree)
+
addEvent('library.root', self.root)
addApiView('library.query', self.queryView)
addApiView('library.related', self.relatedView)
+ addApiView('library.tree', self.treeView)
def queryView(self, media_id, **kwargs):
db = get_db()
@@ -32,6 +35,14 @@ class Library(LibraryBase):
'result': fireEvent('library.related', media, single = True)
}
+ def treeView(self, media_id, **kwargs):
+ db = get_db()
+ media = db.get('id', media_id)
+
+ return {
+ 'result': fireEvent('library.tree', media, single = True)
+ }
+
def title(self, library):
return fireEvent(
'library.query',
@@ -52,7 +63,9 @@ class Library(LibraryBase):
while cur and cur.get('parent_id'):
cur = db.get('id', cur['parent_id'])
- result[cur['type']] = cur
+ parts = cur['type'].split('.')
+
+ result[parts[-1]] = cur
return result
@@ -64,3 +77,28 @@ class Library(LibraryBase):
cur = db.get('id', cur['parent_id'])
return cur
+
+ def tree(self, media):
+ result = media
+
+ db = get_db()
+ items = db.get_many('media_children', media['_id'], with_doc = True)
+
+ keys = []
+
+ for item in items:
+ parts = item['doc']['type'].split('.')
+ key = parts[-1] + 's'
+
+ if key not in result:
+ result[key] = {}
+
+ if key not in keys:
+ keys.append(key)
+
+ result[key][item['_id']] = fireEvent('library.tree', item['doc'], single = True)
+
+ for key in keys:
+ result[key] = result[key].values()
+
+ return result
diff --git a/couchpotato/core/media/show/_base/static/1_wanted.js b/couchpotato/core/media/show/_base/static/1_wanted.js
old mode 100644
new mode 100755
index e7b308bf..2400071e
--- a/couchpotato/core/media/show/_base/static/1_wanted.js
+++ b/couchpotato/core/media/show/_base/static/1_wanted.js
@@ -12,7 +12,7 @@ Page.Shows = new Class({
if(!self.wanted){
// Wanted movies
- self.wanted = new MovieList({
+ self.wanted = new ShowList({
'identifier': 'wanted',
'status': 'active',
'type': 'show',
diff --git a/couchpotato/core/media/show/_base/static/episode.js b/couchpotato/core/media/show/_base/static/episode.js
new file mode 100755
index 00000000..2ba18a97
--- /dev/null
+++ b/couchpotato/core/media/show/_base/static/episode.js
@@ -0,0 +1,71 @@
+var Episode = new Class({
+
+ Extends: BlockBase,
+
+ action: {},
+
+ initialize: function(show, data){
+ var self = this;
+
+ self.show = show;
+ self.data = data;
+
+ self.el = new Element('div.item');
+ self.el_actions = new Element('div.actions');
+
+ self.create();
+ },
+
+ create: function(){
+ var self = this;
+
+ self.el.set('id', 'episode_'+self.data._id);
+
+ self.el.adopt(
+ new Element('span.episode', {'text': (self.data.info.number || 0)}),
+ new Element('span.name', {'text': self.getTitle()}),
+ new Element('span.firstaired', {'text': self.data.info.firstaired})
+ );
+
+ self.el_actions.inject(self.el);
+
+ if(self.data.identifiers && self.data.identifiers.imdb) {
+ new Element('a.imdb.icon2', {
+ 'title': 'Go to the IMDB page of ' + self.show.getTitle(),
+ 'href': 'http://www.imdb.com/title/' + self.data.identifiers.imdb + '/',
+ 'target': '_blank'
+ }).inject(self.el_actions);
+ }
+
+ new Element('a.refresh.icon2', {
+ 'title': 'Refresh the episode info and do a forced search',
+ 'events': {
+ 'click': self.doRefresh.bind(self)
+ }
+ }).inject(self.el_actions);
+ },
+
+ getTitle: function(){
+ var self = this;
+
+ var title = '';
+
+ if(self.data.info.titles && self.data.info.titles.length > 0) {
+ title = self.data.info.titles[0];
+ } else {
+ title = 'Episode ' + self.data.info.number;
+ }
+
+ return title;
+ },
+
+ doRefresh: function(e) {
+ var self = this;
+
+ Api.request('media.refresh', {
+ 'data': {
+ 'id': self.data._id
+ }
+ });
+ }
+});
\ No newline at end of file
diff --git a/couchpotato/core/media/show/_base/static/list.js b/couchpotato/core/media/show/_base/static/list.js
new file mode 100755
index 00000000..a32e9b7c
--- /dev/null
+++ b/couchpotato/core/media/show/_base/static/list.js
@@ -0,0 +1,635 @@
+var ShowList = new Class({
+
+ Implements: [Events, Options],
+
+ options: {
+ navigation: true,
+ limit: 50,
+ load_more: true,
+ loader: true,
+ menu: [],
+ add_new: false,
+ force_view: false
+ },
+
+ movies: [],
+ movies_added: {},
+ total_movies: 0,
+ letters: {},
+ filter: null,
+
+ initialize: function(options){
+ var self = this;
+ self.setOptions(options);
+
+ self.offset = 0;
+ self.filter = self.options.filter || {
+ 'starts_with': null,
+ 'search': null
+ };
+
+ self.el = new Element('div.shows').adopt(
+ self.title = self.options.title ? new Element('h2', {
+ 'text': self.options.title,
+ 'styles': {'display': 'none'}
+ }) : null,
+ self.description = self.options.description ? new Element('div.description', {
+ 'html': self.options.description,
+ 'styles': {'display': 'none'}
+ }) : null,
+ self.movie_list = new Element('div'),
+ self.load_more = self.options.load_more ? new Element('a.load_more', {
+ 'events': {
+ 'click': self.loadMore.bind(self)
+ }
+ }) : null
+ );
+
+ if($(window).getSize().x <= 480 && !self.options.force_view)
+ self.changeView('list');
+ else
+ self.changeView(self.getSavedView() || self.options.view || 'details');
+
+ self.getMovies();
+
+ App.on('movie.added', self.movieAdded.bind(self));
+ App.on('movie.deleted', self.movieDeleted.bind(self))
+ },
+
+ movieDeleted: function(notification){
+ var self = this;
+
+ if(self.movies_added[notification.data._id]){
+ self.movies.each(function(movie){
+ if(movie.get('_id') == notification.data._id){
+ movie.destroy();
+ delete self.movies_added[notification.data._id];
+ self.setCounter(self.counter_count-1);
+ self.total_movies--;
+ }
+ })
+ }
+
+ self.checkIfEmpty();
+ },
+
+ movieAdded: function(notification){
+ var self = this;
+
+ self.fireEvent('movieAdded', notification);
+ if(self.options.add_new && !self.movies_added[notification.data._id] && notification.data.status == self.options.status){
+ window.scroll(0,0);
+ self.createMovie(notification.data, 'top');
+ self.setCounter(self.counter_count+1);
+
+ self.checkIfEmpty();
+ }
+ },
+
+ create: function(){
+ var self = this;
+
+ // Create the alphabet nav
+ if(self.options.navigation)
+ self.createNavigation();
+
+ if(self.options.load_more)
+ self.scrollspy = new ScrollSpy({
+ min: function(){
+ var c = self.load_more.getCoordinates();
+ return c.top - window.document.getSize().y - 300
+ },
+ onEnter: self.loadMore.bind(self)
+ });
+
+ self.created = true;
+ },
+
+ addMovies: function(movies, total){
+ var self = this;
+
+ if(!self.created) self.create();
+
+ // do scrollspy
+ if(movies.length < self.options.limit && self.scrollspy){
+ self.load_more.hide();
+ self.scrollspy.stop();
+ }
+
+ Object.each(movies, function(movie){
+ self.createMovie(movie);
+ });
+
+ self.total_movies += total;
+ self.setCounter(total);
+
+ },
+
+ setCounter: function(count){
+ var self = this;
+
+ if(!self.navigation_counter) return;
+
+ self.counter_count = count;
+ self.navigation_counter.set('text', (count || 0) + ' movies');
+
+ if (self.empty_message) {
+ self.empty_message.destroy();
+ self.empty_message = null;
+ }
+
+ if(self.total_movies && count == 0 && !self.empty_message){
+ var message = (self.filter.search ? 'for "'+self.filter.search+'"' : '') +
+ (self.filter.starts_with ? ' in '+self.filter.starts_with+'' : '');
+
+ self.empty_message = new Element('.message', {
+ 'html': 'No movies found ' + message + '.
'
+ }).grab(
+ new Element('a', {
+ 'text': 'Reset filter',
+ 'events': {
+ 'click': function(){
+ self.filter = {
+ 'starts_with': null,
+ 'search': null
+ };
+ self.navigation_search_input.set('value', '');
+ self.reset();
+ self.activateLetter();
+ self.getMovies(true);
+ self.last_search_value = '';
+ }
+ }
+ })
+ ).inject(self.movie_list);
+
+ }
+
+ },
+
+ createMovie: function(movie, inject_at){
+ var self = this;
+ var m = new Show(self, {
+ 'actions': self.options.actions,
+ 'view': self.current_view,
+ 'onSelect': self.calculateSelected.bind(self)
+ }, movie);
+
+ $(m).inject(self.movie_list, inject_at || 'bottom');
+
+ m.fireEvent('injected');
+
+ self.movies.include(m);
+ self.movies_added[movie._id] = true;
+ },
+
+ createNavigation: function(){
+ var self = this;
+ var chars = '#ABCDEFGHIJKLMNOPQRSTUVWXYZ';
+
+ self.el.addClass('with_navigation');
+
+ self.navigation = new Element('div.alph_nav').adopt(
+ self.mass_edit_form = new Element('div.mass_edit_form').adopt(
+ new Element('span.select').adopt(
+ self.mass_edit_select = new Element('input[type=checkbox].inlay', {
+ 'events': {
+ 'change': self.massEditToggleAll.bind(self)
+ }
+ }),
+ self.mass_edit_selected = new Element('span.count', {'text': 0}),
+ self.mass_edit_selected_label = new Element('span', {'text': 'selected'})
+ ),
+ new Element('div.quality').adopt(
+ self.mass_edit_quality = new Element('select'),
+ new Element('a.button.orange', {
+ 'text': 'Change quality',
+ 'events': {
+ 'click': self.changeQualitySelected.bind(self)
+ }
+ })
+ ),
+ new Element('div.delete').adopt(
+ new Element('span[text=or]'),
+ new Element('a.button.red', {
+ 'text': 'Delete',
+ 'events': {
+ 'click': self.deleteSelected.bind(self)
+ }
+ })
+ ),
+ new Element('div.refresh').adopt(
+ new Element('span[text=or]'),
+ new Element('a.button.green', {
+ 'text': 'Refresh',
+ 'events': {
+ 'click': self.refreshSelected.bind(self)
+ }
+ })
+ )
+ ),
+ new Element('div.menus').adopt(
+ self.navigation_counter = new Element('span.counter[title=Total]'),
+ self.filter_menu = new Block.Menu(self, {
+ 'class': 'filter'
+ }),
+ self.navigation_actions = new Element('ul.actions', {
+ 'events': {
+ 'click:relay(li)': function(e, el){
+ var a = 'active';
+ self.navigation_actions.getElements('.'+a).removeClass(a);
+ self.changeView(el.get('data-view'));
+ this.addClass(a);
+
+ el.inject(el.getParent(), 'top');
+ el.getSiblings().hide();
+ setTimeout(function(){
+ el.getSiblings().setStyle('display', null);
+ }, 100)
+ }
+ }
+ }),
+ self.navigation_menu = new Block.Menu(self, {
+ 'class': 'extra'
+ })
+ )
+ ).inject(self.el, 'top');
+
+ // Mass edit
+ self.mass_edit_select_class = new Form.Check(self.mass_edit_select);
+ Quality.getActiveProfiles().each(function(profile){
+ new Element('option', {
+ 'value': profile.get('_id'),
+ 'text': profile.get('label')
+ }).inject(self.mass_edit_quality)
+ });
+
+ self.filter_menu.addLink(
+ self.navigation_search_input = new Element('input', {
+ 'title': 'Search through ' + self.options.identifier,
+ 'placeholder': 'Search through ' + self.options.identifier,
+ 'events': {
+ 'keyup': self.search.bind(self),
+ 'change': self.search.bind(self)
+ }
+ })
+ ).addClass('search');
+
+ var available_chars;
+ self.filter_menu.addEvent('open', function(){
+ self.navigation_search_input.focus();
+
+ // Get available chars and highlight
+ if(!available_chars && (self.navigation.isDisplayed() || self.navigation.isVisible()))
+ Api.request('media.available_chars', {
+ 'data': Object.merge({
+ 'status': self.options.status
+ }, self.filter),
+ 'onSuccess': function(json){
+ available_chars = json.chars;
+
+ available_chars.each(function(c){
+ self.letters[c.capitalize()].addClass('available')
+ })
+
+ }
+ });
+ });
+
+ self.filter_menu.addLink(
+ self.navigation_alpha = new Element('ul.numbers', {
+ 'events': {
+ 'click:relay(li.available)': function(e, el){
+ self.activateLetter(el.get('data-letter'));
+ self.getMovies(true)
+ }
+ }
+ })
+ );
+
+ // Actions
+ ['mass_edit', 'details', 'list'].each(function(view){
+ var current = self.current_view == view;
+ new Element('li', {
+ 'class': 'icon2 ' + view + (current ? ' active ' : ''),
+ 'data-view': view
+ }).inject(self.navigation_actions, current ? 'top' : 'bottom');
+ });
+
+ // All
+ self.letters['all'] = new Element('li.letter_all.available.active', {
+ 'text': 'ALL'
+ }).inject(self.navigation_alpha);
+
+ // Chars
+ chars.split('').each(function(c){
+ self.letters[c] = new Element('li', {
+ 'text': c,
+ 'class': 'letter_'+c,
+ 'data-letter': c
+ }).inject(self.navigation_alpha);
+ });
+
+ // Add menu or hide
+ if (self.options.menu.length > 0)
+ self.options.menu.each(function(menu_item){
+ self.navigation_menu.addLink(menu_item);
+ });
+ else
+ self.navigation_menu.hide();
+
+ },
+
+ calculateSelected: function(){
+ var self = this;
+
+ var selected = 0,
+ movies = self.movies.length;
+ self.movies.each(function(movie){
+ selected += movie.isSelected() ? 1 : 0
+ });
+
+ var indeterminate = selected > 0 && selected < movies,
+ checked = selected == movies && selected > 0;
+
+ self.mass_edit_select.set('indeterminate', indeterminate);
+
+ self.mass_edit_select_class[checked ? 'check' : 'uncheck']();
+ self.mass_edit_select_class.element[indeterminate ? 'addClass' : 'removeClass']('indeterminate');
+
+ self.mass_edit_selected.set('text', selected);
+ },
+
+ deleteSelected: function(){
+ var self = this,
+ ids = self.getSelectedMovies(),
+ help_msg = self.identifier == 'wanted' ? 'If you do, you won\'t be able to watch them, as they won\'t get downloaded!' : 'Your files will be safe, this will only delete the reference from the CouchPotato manage list';
+
+ var qObj = new Question('Are you sure you want to delete '+ids.length+' movie'+ (ids.length != 1 ? 's' : '') +'?', help_msg, [{
+ 'text': 'Yes, delete '+(ids.length != 1 ? 'them' : 'it'),
+ 'class': 'delete',
+ 'events': {
+ 'click': function(e){
+ (e).preventDefault();
+ this.set('text', 'Deleting..');
+ Api.request('media.delete', {
+ 'method': 'post',
+ 'data': {
+ 'id': ids.join(','),
+ 'delete_from': self.options.identifier
+ },
+ 'onSuccess': function(){
+ qObj.close();
+
+ var erase_movies = [];
+ self.movies.each(function(movie){
+ if (movie.isSelected()){
+ $(movie).destroy();
+ erase_movies.include(movie);
+ }
+ });
+
+ erase_movies.each(function(movie){
+ self.movies.erase(movie);
+ movie.destroy();
+ self.setCounter(self.counter_count-1);
+ self.total_movies--;
+ });
+
+ self.calculateSelected();
+ }
+ });
+
+ }
+ }
+ }, {
+ 'text': 'Cancel',
+ 'cancel': true
+ }]);
+
+ },
+
+ changeQualitySelected: function(){
+ var self = this;
+ var ids = self.getSelectedMovies();
+
+ Api.request('movie.edit', {
+ 'method': 'post',
+ 'data': {
+ 'id': ids.join(','),
+ 'profile_id': self.mass_edit_quality.get('value')
+ },
+ 'onSuccess': self.search.bind(self)
+ });
+ },
+
+ refreshSelected: function(){
+ var self = this;
+ var ids = self.getSelectedMovies();
+
+ Api.request('media.refresh', {
+ 'method': 'post',
+ 'data': {
+ 'id': ids.join(',')
+ }
+ });
+ },
+
+ getSelectedMovies: function(){
+ var self = this;
+
+ var ids = [];
+ self.movies.each(function(movie){
+ if (movie.isSelected())
+ ids.include(movie.get('_id'))
+ });
+
+ return ids
+ },
+
+ massEditToggleAll: function(){
+ var self = this;
+
+ var select = self.mass_edit_select.get('checked');
+
+ self.movies.each(function(movie){
+ movie.select(select)
+ });
+
+ self.calculateSelected()
+ },
+
+ reset: function(){
+ var self = this;
+
+ self.movies = [];
+ if(self.mass_edit_select)
+ self.calculateSelected();
+ if(self.navigation_alpha)
+ self.navigation_alpha.getElements('.active').removeClass('active');
+
+ self.offset = 0;
+ if(self.scrollspy){
+ //self.load_more.show();
+ self.scrollspy.start();
+ }
+ },
+
+ activateLetter: function(letter){
+ var self = this;
+
+ self.reset();
+
+ self.letters[letter || 'all'].addClass('active');
+ self.filter.starts_with = letter;
+
+ },
+
+ changeView: function(new_view){
+ var self = this;
+
+ self.el
+ .removeClass(self.current_view+'_list')
+ .addClass(new_view+'_list');
+
+ self.current_view = new_view;
+ Cookie.write(self.options.identifier+'_view2', new_view, {duration: 1000});
+ },
+
+ getSavedView: function(){
+ var self = this;
+ return Cookie.read(self.options.identifier+'_view2');
+ },
+
+ search: function(){
+ var self = this;
+
+ if(self.search_timer) clearTimeout(self.search_timer);
+ self.search_timer = (function(){
+ var search_value = self.navigation_search_input.get('value');
+ if (search_value == self.last_search_value) return;
+
+ self.reset();
+
+ self.activateLetter();
+ self.filter.search = search_value;
+
+ self.getMovies(true);
+
+ self.last_search_value = search_value;
+
+ }).delay(250);
+
+ },
+
+ update: function(){
+ var self = this;
+
+ self.reset();
+ self.getMovies(true);
+ },
+
+ getMovies: function(reset){
+ var self = this;
+
+ if(self.scrollspy){
+ self.scrollspy.stop();
+ self.load_more.set('text', 'loading...');
+ }
+
+ if(self.movies.length == 0 && self.options.loader){
+
+ self.loader_first = new Element('div.loading').adopt(
+ new Element('div.message', {'text': self.options.title ? 'Loading \'' + self.options.title + '\'' : 'Loading...'})
+ ).inject(self.el, 'top');
+
+ createSpinner(self.loader_first, {
+ radius: 4,
+ length: 4,
+ width: 1
+ });
+
+ self.el.setStyle('min-height', 93);
+
+ }
+
+ Api.request(self.options.api_call || 'media.list', {
+ 'data': Object.merge({
+ 'type': self.options.type || 'movie',
+ 'status': self.options.status,
+ 'limit_offset': self.options.limit ? self.options.limit + ',' + self.offset : null
+ }, self.filter),
+ 'onSuccess': function(json){
+
+ if(reset)
+ self.movie_list.empty();
+
+ if(self.loader_first){
+ var lf = self.loader_first;
+ self.loader_first.addClass('hide');
+ self.loader_first = null;
+ setTimeout(function(){
+ lf.destroy();
+ }, 20000);
+ self.el.setStyle('min-height', null);
+ }
+
+ self.store(json.movies);
+ self.addMovies(json.movies, json.total || json.movies.length);
+ if(self.scrollspy) {
+ self.load_more.set('text', 'load more movies');
+ self.scrollspy.start();
+ }
+
+ self.checkIfEmpty();
+ self.fireEvent('loaded');
+ }
+ });
+ },
+
+ loadMore: function(){
+ var self = this;
+ if(self.offset >= self.options.limit)
+ self.getMovies()
+ },
+
+ store: function(movies){
+ var self = this;
+
+ self.offset += movies.length;
+
+ },
+
+ checkIfEmpty: function(){
+ var self = this;
+
+ var is_empty = self.movies.length == 0 && (self.total_movies == 0 || self.total_movies === undefined);
+
+ if(self.title)
+ self.title[is_empty ? 'hide' : 'show']();
+
+ if(self.description)
+ self.description.setStyle('display', [is_empty ? 'none' : '']);
+
+ if(is_empty && self.options.on_empty_element){
+ self.options.on_empty_element.inject(self.loader_first || self.title || self.movie_list, 'after');
+
+ if(self.navigation)
+ self.navigation.hide();
+
+ self.empty_element = self.options.on_empty_element;
+ }
+ else if(self.empty_element){
+ self.empty_element.destroy();
+
+ if(self.navigation)
+ self.navigation.show();
+ }
+
+ },
+
+ toElement: function(){
+ return this.el;
+ }
+
+});
diff --git a/couchpotato/core/media/show/_base/static/show.css b/couchpotato/core/media/show/_base/static/show.css
new file mode 100755
index 00000000..14dc462d
--- /dev/null
+++ b/couchpotato/core/media/show/_base/static/show.css
@@ -0,0 +1,1077 @@
+.shows {
+ padding: 10px 0 20px;
+ position: relative;
+ z-index: 3;
+ width: 100%;
+}
+
+ .shows > div {
+ clear: both;
+ }
+
+ .shows > div .message {
+ display: block;
+ padding: 20px;
+ font-size: 20px;
+ color: white;
+ text-align: center;
+ }
+ .shows > div .message a {
+ padding: 20px;
+ display: block;
+ }
+
+ .shows.thumbs_list > div:not(.description) {
+ margin-right: -4px;
+ }
+
+ .shows .loading {
+ display: block;
+ padding: 20px 0 0 0;
+ width: 100%;
+ z-index: 3;
+ transition: all .4s cubic-bezier(0.9,0,0.1,1);
+ height: 40px;
+ opacity: 1;
+ position: absolute;
+ text-align: center;
+ }
+ .shows .loading.hide {
+ height: 0;
+ padding: 0;
+ opacity: 0;
+ margin-top: -20px;
+ overflow: hidden;
+ }
+
+ .shows .loading .spinner {
+ display: inline-block;
+ }
+
+ .shows .loading .message {
+ margin: 0 20px;
+ }
+
+ .shows h2 {
+ margin-bottom: 20px;
+ }
+
+ @media all and (max-width: 480px) {
+ .shows h2 {
+ font-size: 25px;
+ margin-bottom: 10px;
+ }
+ }
+
+ .shows > .description {
+ position: absolute;
+ top: 30px;
+ right: 0;
+ font-style: italic;
+ opacity: 0.8;
+ }
+ .shows:hover > .description {
+ opacity: 1;
+ }
+
+ @media all and (max-width: 860px) {
+ .shows > .description {
+ display: none;
+ }
+ }
+
+ .shows.thumbs_list {
+ padding: 20px 0 20px;
+ }
+
+ .home .shows {
+ padding-top: 6px;
+ }
+
+ .shows .show {
+ position: relative;
+ margin: 10px 0;
+ padding-left: 20px;
+ overflow: hidden;
+ width: 100%;
+ height: 180px;
+ transition: all 0.6s cubic-bezier(0.9,0,0.1,1);
+ transition-property: width, height;
+ background: rgba(0,0,0,.2);
+ }
+
+ .shows .show.expanded {
+ height: 360px;
+ }
+
+ .shows .show .table.expanded {
+ height: 360px;
+ }
+
+ .shows.mass_edit_list .show {
+ padding-left: 22px;
+ background: none;
+ }
+
+ .shows.details_list .show {
+ padding-left: 120px;
+ }
+
+ .shows.list_list .show:not(.details_view),
+ .shows.mass_edit_list .show {
+ height: 30px;
+ border-bottom: 1px solid rgba(255,255,255,.15);
+ }
+
+ .shows.list_list .show:last-child,
+ .shows.mass_edit_list .show:last-child {
+ border: none;
+ }
+
+ .shows.thumbs_list .show {
+ width: 16.66667%;
+ height: auto;
+ min-height: 200px;
+ display: inline-block;
+ margin: 0;
+ padding: 0;
+ vertical-align: top;
+ line-height: 0;
+ }
+
+ @media all and (max-width: 800px) {
+ .shows.thumbs_list .show {
+ width: 25%;
+ min-height: 100px;
+ }
+ }
+
+ .shows .show .mask {
+ position: absolute;
+ top: 0;
+ left: 0;
+ height: 100%;
+ width: 100%;
+ }
+
+ .shows.list_list .show:not(.details_view),
+ .shows.mass_edit_list .show {
+ margin: 0;
+ }
+
+ .shows .data {
+ padding: 20px;
+ height: 100%;
+ width: 100%;
+ position: relative;
+ transition: all .6s cubic-bezier(0.9,0,0.1,1);
+ right: 0;
+ }
+ .shows.list_list .show:not(.details_view) .data,
+ .shows.mass_edit_list .show .data {
+ padding: 0 0 0 10px;
+ border: 0;
+ background: #4e5969;
+ }
+ .shows.mass_edit_list .show .data {
+ padding-left: 8px;
+ }
+
+ .shows.thumbs_list .data {
+ position: absolute;
+ left: 0;
+ top: 0;
+ width: 100%;
+ padding: 10px;
+ height: 100%;
+ background: none;
+ transition: none;
+ }
+
+ .shows.thumbs_list .show:hover .data {
+ background: rgba(0,0,0,0.9);
+ }
+
+ .shows .data.hide_right {
+ right: -100%;
+ }
+
+ .shows .show .check {
+ display: none;
+ }
+
+ .shows.mass_edit_list .show .check {
+ position: absolute;
+ left: 0;
+ top: 0;
+ display: block;
+ margin: 7px 0 0 5px;
+ }
+
+ .shows .poster {
+ position: absolute;
+ left: 0;
+ width: 120px;
+ height: 180px;
+ line-height: 0;
+ overflow: hidden;
+ transition: all .6s cubic-bezier(0.9,0,0.1,1);
+ background: rgba(0,0,0,.1);
+ }
+ .shows.thumbs_list .poster {
+ position: relative;
+ }
+ .shows.list_list .show:not(.details_view) .poster,
+ .shows.mass_edit_list .poster {
+ width: 20px;
+ height: 30px;
+ }
+ .shows.mass_edit_list .poster {
+ display: none;
+ }
+
+ .shows.thumbs_list .poster {
+ width: 100%;
+ height: 100%;
+ transition: none;
+ background: no-repeat center;
+ background-size: cover;
+ }
+ .shows.thumbs_list .no_thumbnail .empty_file {
+ width: 100%;
+ height: 100%;
+ }
+
+ .shows .poster img,
+ .options .poster img {
+ width: 100%;
+ height: 100%;
+ }
+ .shows.thumbs_list .poster img {
+ height: auto;
+ width: 100%;
+ top: 0;
+ bottom: 0;
+ opacity: 0;
+ }
+
+ .shows .info {
+ position: relative;
+ height: 100%;
+ width: 100%;
+ }
+
+ .shows .info .title {
+ font-size: 28px;
+ font-weight: bold;
+ margin-bottom: 10px;
+ margin-top: 2px;
+ width: 100%;
+ padding-right: 80px;
+ transition: all 0.2s linear;
+ height: 35px;
+ top: -5px;
+ position: relative;
+ }
+ .shows.list_list .info .title,
+ .shows.mass_edit_list .info .title {
+ height: 100%;
+ top: 0;
+ margin: 0;
+ }
+ .touch_enabled .shows.list_list .info .title {
+ display: inline-block;
+ padding-right: 55px;
+ }
+
+ .shows .info .title a {
+ display: inline-block;
+ text-overflow: ellipsis;
+ overflow: hidden;
+ white-space: nowrap;
+ width: 100%;
+ height: 100%;
+ line-height: 30px;
+ color: rgb(255, 255, 255);
+ }
+
+ .shows .info .title a:hover {
+ color: rgba(255, 255, 255, 0.6);
+ }
+
+ .shows.thumbs_list .info .title a {
+ white-space: normal;
+ overflow: auto;
+ height: auto;
+ text-align: left;
+ }
+
+ @media all and (max-width: 480px) {
+ .shows.thumbs_list .show .info .title a,
+ .shows.thumbs_list .show .info .year {
+ font-size: 15px;
+ line-height: 15px;
+ overflow: hidden;
+ }
+ }
+
+ .shows.list_list .show:not(.details_view) .info .title,
+ .shows.mass_edit_list .info .title {
+ font-size: 16px;
+ font-weight: normal;
+ width: auto;
+ }
+
+ .shows.thumbs_list .show:not(.no_thumbnail) .info {
+ display: none;
+ }
+ .shows.thumbs_list .show:hover .info {
+ display: block;
+ }
+
+ .shows.thumbs_list .info .title {
+ font-size: 21px;
+ word-wrap: break-word;
+ padding: 0;
+ height: 100%;
+ }
+
+ .shows .info .year {
+ position: absolute;
+ color: #bbb;
+ right: 0;
+ top: 6px;
+ text-align: right;
+ transition: all 0.2s linear;
+ font-weight: normal;
+ }
+ .shows.list_list .show:not(.details_view) .info .year,
+ .shows.mass_edit_list .info .year {
+ font-size: 1.25em;
+ right: 10px;
+ }
+
+ .shows.thumbs_list .info .year {
+ font-size: 23px;
+ margin: 0;
+ bottom: 0;
+ left: 0;
+ top: auto;
+ right: auto;
+ color: #FFF;
+ line-height: 18px;
+ }
+
+ .touch_enabled .shows.list_list .show .info .year {
+ font-size: 1em;
+ }
+
+ .shows .info .description {
+ top: 30px;
+ clear: both;
+ bottom: 30px;
+ position: absolute;
+ }
+ .shows.list_list .show:not(.details_view) .info .description,
+ .shows.mass_edit_list .info .description,
+ .shows.thumbs_list .info .description {
+ display: none;
+ }
+
+ .shows .data .quality {
+ position: absolute;
+ bottom: 2px;
+ display: block;
+ min-height: 20px;
+ }
+
+ .shows.list_list .show:hover .data .quality {
+ display: none;
+ }
+
+ .touch_enabled .shows.list_list .show .data .quality {
+ position: relative;
+ display: inline-block;
+ margin: 0;
+ top: -4px;
+ }
+
+ @media all and (max-width: 480px) {
+ .shows .data .quality {
+ display: none;
+ }
+ }
+
+ .shows .status_suggest .data .quality,
+ .shows.thumbs_list .data .quality {
+ display: none;
+ }
+
+ .shows .data .quality span {
+ padding: 2px 3px;
+ opacity: 0.5;
+ font-size: 10px;
+ height: 16px;
+ line-height: 12px;
+ vertical-align: middle;
+ display: inline-block;
+ text-transform: uppercase;
+ font-weight: normal;
+ margin: 0 4px 0 0;
+ border-radius: 2px;
+ background-color: rgba(255,255,255,0.1);
+ }
+ .shows.list_list .data .quality,
+ .shows.mass_edit_list .data .quality {
+ text-align: right;
+ right: 0;
+ margin-right: 60px;
+ z-index: 1;
+ top: 5px;
+ }
+
+ .shows .data .quality .available,
+ .shows .data .quality .snatched,
+ .shows .data .quality .seeding {
+ opacity: 1;
+ cursor: pointer;
+ }
+
+ .shows .data .quality .available { background-color: #578bc3; }
+ .shows .data .quality .failed,
+ .shows .data .quality .missing,
+ .shows .data .quality .ignored { background-color: #a43d34; }
+ .shows .data .quality .snatched { background-color: #a2a232; }
+ .shows .data .quality .done {
+ background-color: #369545;
+ opacity: 1;
+ }
+ .shows .data .quality .seeding { background-color: #0a6819; }
+ .shows .data .quality .finish {
+ background-image: url('../../images/sprite.png');
+ background-repeat: no-repeat;
+ background-position: 0 2px;
+ padding-left: 14px;
+ background-size: 14px
+ }
+
+ .shows .data .actions {
+ position: absolute;
+ bottom: 17px;
+ right: 20px;
+ line-height: 0;
+ top: 0;
+ width: auto;
+ opacity: 0;
+ display: none;
+ }
+ @media all and (max-width: 480px) {
+ .shows .data .actions {
+ display: none !important;
+ }
+ }
+
+ .shows .show:hover .data .actions,
+ .touch_enabled .shows .show .data .actions {
+ opacity: 1;
+ display: inline-block;
+ }
+
+ .shows.details_list .data .actions {
+ top: auto;
+ bottom: 18px;
+ }
+
+ .shows .show:hover .actions {
+ opacity: 1;
+ display: inline-block;
+ }
+ .shows.thumbs_list .data .actions {
+ bottom: 12px;
+ right: 10px;
+ top: auto;
+ }
+
+ .shows .show:hover .action { opacity: 0.6; }
+ .shows .show:hover .action:hover { opacity: 1; }
+
+ .shows .data .action {
+ display: inline-block;
+ height: 22px;
+ min-width: 33px;
+ padding: 0 5px;
+ line-height: 26px;
+ text-align: center;
+ font-size: 13px;
+ color: #FFF;
+ margin-left: 1px;
+ }
+ .shows .data .action.trailer { color: #FFF; }
+ .shows .data .action.download { color: #b9dec0; }
+ .shows .data .action.edit { color: #c6b589; }
+ .shows .data .action.refresh { color: #cbeecc; }
+ .shows .data .action.delete { color: #e9b0b0; }
+ .shows .data .action.directory { color: #ffed92; }
+ .shows .data .action.readd { color: #c2fac5; }
+
+ .shows.mass_edit_list .show .data .actions {
+ display: none;
+ }
+
+ .shows.list_list .show:not(.details_view):hover .actions,
+ .shows.mass_edit_list .show:hover .actions,
+ .touch_enabled .shows.list_list .show:not(.details_view) .actions {
+ margin: 0;
+ background: #4e5969;
+ top: 2px;
+ bottom: 2px;
+ right: 5px;
+ z-index: 3;
+ }
+
+ .shows .delete_container {
+ clear: both;
+ text-align: center;
+ font-size: 20px;
+ position: absolute;
+ padding: 80px 0 0;
+ left: 120px;
+ right: 0;
+ }
+ .shows .delete_container .or {
+ padding: 10px;
+ }
+ .shows .delete_container .delete {
+ background-color: #ff321c;
+ font-weight: normal;
+ }
+ .shows .delete_container .delete:hover {
+ color: #fff;
+ background-color: #d32917;
+ }
+
+ .shows .options {
+ position: absolute;
+ right: 0;
+ left: 120px;
+ }
+
+ .shows .options .form {
+ margin: 80px 0 0;
+ font-size: 20px;
+ text-align: center;
+ }
+
+ .shows .options .form select {
+ margin-right: 20px;
+ }
+
+ .shows .options .table {
+ height: 180px;
+ overflow: auto;
+ line-height: 2em;
+ }
+ .shows .options .table .item {
+ border-bottom: 1px solid rgba(255,255,255,0.1);
+ }
+ .shows .options .table .item.ignored span,
+ .shows .options .table .item.failed span {
+ text-decoration: line-through;
+ color: rgba(255,255,255,0.4);
+ }
+ .shows .options .table .item.ignored .delete:before,
+ .shows .options .table .item.failed .delete:before {
+ display: inline-block;
+ content: "\e04b";
+ transform: scale(-1, 1);
+ }
+
+ .shows .options .table .item:last-child { border: 0; }
+ .shows .options .table .item:nth-child(even) {
+ background: rgba(255,255,255,0.05);
+ }
+ .shows .options .table .item:not(.head):hover {
+ background: rgba(255,255,255,0.03);
+ }
+
+ .shows .options .table .item > * {
+ display: inline-block;
+ padding: 0 5px;
+ width: 60px;
+ min-height: 24px;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ text-align: center;
+ vertical-align: top;
+ border-left: 1px solid rgba(255, 255, 255, 0.1);
+ }
+ .shows .options .table .item > *:first-child {
+ border: 0;
+ }
+ .shows .options .table .provider {
+ width: 120px;
+ text-overflow: ellipsis;
+ overflow: hidden;
+ }
+ .shows .options .table .name {
+ width: 340px;
+ overflow: hidden;
+ text-align: left;
+ padding: 0 10px;
+ }
+ .shows .options .table.files .name { width: 590px; }
+ .shows .options .table .type { width: 130px; }
+ .shows .options .table .is_available { width: 90px; }
+ .shows .options .table .age,
+ .shows .options .table .size { width: 40px; }
+
+ .shows .options .table a {
+ width: 30px !important;
+ height: 20px;
+ opacity: 0.8;
+ line-height: 25px;
+ }
+ .shows .options .table a:hover { opacity: 1; }
+ .shows .options .table a.download { color: #a7fbaf; }
+ .shows .options .table a.delete { color: #fda3a3; }
+ .shows .options .table .ignored a.delete,
+ .shows .options .table .failed a.delete { color: #b5fda3; }
+
+ .shows .options .table .head > * {
+ font-weight: bold;
+ font-size: 14px;
+ padding-top: 4px;
+ padding-bottom: 4px;
+ height: auto;
+ }
+
+ .trailer_container {
+ width: 100%;
+ background: #000;
+ text-align: center;
+ transition: all .6s cubic-bezier(0.9,0,0.1,1);
+ overflow: hidden;
+ left: 0;
+ position: absolute;
+ z-index: 10;
+ }
+ @media only screen and (device-width: 768px) {
+ .trailer_container iframe {
+ margin-top: 25px;
+ }
+ }
+
+ .trailer_container.hide {
+ height: 0 !important;
+ }
+
+ .hide_trailer {
+ position: absolute;
+ top: 0;
+ left: 50%;
+ margin-left: -50px;
+ width: 100px;
+ text-align: center;
+ padding: 3px 10px;
+ background: #4e5969;
+ transition: all .2s cubic-bezier(0.9,0,0.1,1) .2s;
+ z-index: 11;
+ }
+ .hide_trailer.hide {
+ top: -30px;
+ }
+
+ .shows .show .episodes .item {
+ position: relative;
+ }
+
+ .shows .show .episodes .actions {
+ position: absolute;
+ width: auto;
+ right: 0;
+
+ border-left: none;
+ }
+
+ .shows .show .episodes .actions .refresh {
+ color: #cbeecc;
+ }
+
+ .shows .show .try_container {
+ padding: 5px 10px;
+ text-align: center;
+ }
+
+ .shows .show .try_container a {
+ margin: 0 5px;
+ padding: 2px 5px;
+ }
+
+ .shows .show .releases .next_release {
+ border-left: 6px solid #2aa300;
+ }
+
+ .shows .show .releases .next_release > :first-child {
+ margin-left: -6px;
+ }
+
+ .shows .show .releases .last_release {
+ border-left: 6px solid #ffa200;
+ }
+
+ .shows .show .releases .last_release > :first-child {
+ margin-left: -6px;
+ }
+ .shows .show .trynext {
+ display: inline;
+ position: absolute;
+ right: 180px;
+ z-index: 2;
+ opacity: 0;
+ background: #4e5969;
+ text-align: right;
+ height: 100%;
+ top: 0;
+ }
+ .touch_enabled .shows .show .trynext {
+ display: none;
+ }
+
+ @media all and (max-width: 480px) {
+ .shows .show .trynext {
+ display: none;
+ }
+ }
+ .shows.mass_edit_list .trynext { display: none; }
+ .wanted .shows .show .trynext {
+ padding-right: 30px;
+ }
+ .shows .show:hover .trynext,
+ .touch_enabled .shows.details_list .show .trynext {
+ opacity: 1;
+ }
+
+ .shows.details_list .show .trynext {
+ background: #47515f;
+ padding: 0;
+ right: 0;
+ height: 25px;
+ }
+
+ .shows .show .trynext a {
+ background-position: 5px center;
+ padding: 0 5px 0 25px;
+ margin-right: 10px;
+ color: #FFF;
+ height: 100%;
+ line-height: 27px;
+ font-family: OpenSans, "Helvetica Neue", Helvetica, Arial, Geneva, sans-serif;
+ }
+ .shows .show .trynext a:before {
+ margin: 2px 0 0 -20px;
+ position: absolute;
+ font-family: 'Elusive-Icons';
+ }
+ .shows.details_list .show .trynext a {
+ line-height: 23px;
+ }
+ .shows .show .trynext a:last-child {
+ margin: 0;
+ }
+ .shows .show .trynext a:hover,
+ .touch_enabled .shows .show .trynext a {
+ background-color: #369545;
+ }
+
+ .shows .load_more {
+ display: block;
+ padding: 10px;
+ text-align: center;
+ font-size: 20px;
+ }
+ .shows .load_more.loading {
+ opacity: .5;
+ }
+
+.shows .alph_nav {
+ height: 44px;
+}
+
+ @media all and (max-width: 480px) {
+ .shows .alph_nav {
+ display: none;
+ }
+ }
+
+ .shows .alph_nav .menus {
+ display: inline-block;
+ float: right;
+ }
+
+.shows .alph_nav .numbers,
+.shows .alph_nav .counter,
+.shows .alph_nav .actions {
+ list-style: none;
+ padding: 0 0 1px;
+ margin: 0;
+ user-select: none;
+}
+
+ .shows .alph_nav .counter {
+ display: inline-block;
+ text-align: right;
+ padding: 0 10px;
+ height: 100%;
+ line-height: 43px;
+ border-right: 1px solid rgba(255,255,255,.07);
+ }
+
+ .shows .alph_nav .numbers li,
+ .shows .alph_nav .actions li {
+ display: inline-block;
+ vertical-align: top;
+ height: 100%;
+ line-height: 30px;
+ text-align: center;
+ border: 1px solid transparent;
+ transition: all 0.1s ease-in-out;
+ }
+
+ .shows .alph_nav .numbers li {
+ width: 30px;
+ height: 30px;
+ opacity: 0.3;
+ }
+ .shows .alph_nav .numbers li.letter_all {
+ width: 60px;
+ }
+
+ .shows .alph_nav li.available {
+ font-weight: bold;
+ cursor: pointer;
+ opacity: 1;
+
+ }
+ .shows .alph_nav li.active.available,
+ .shows .alph_nav li.available:hover {
+ background: rgba(0,0,0,.1);
+ }
+
+ .shows .alph_nav .search input {
+ width: 100%;
+ height: 44px;
+ display: inline-block;
+ border: 0;
+ background: none;
+ color: #444;
+ font-size: 14px;
+ padding: 0 10px 0 30px;
+ border-bottom: 1px solid rgba(0,0,0,.08);
+ }
+ .shows .alph_nav .search input:focus {
+ background: rgba(0,0,0,.08);
+ }
+
+ .shows .alph_nav .search input::-webkit-input-placeholder {
+ color: #444;
+ opacity: .6;
+ }
+
+ .shows .alph_nav .search:before {
+ font-family: 'Elusive-Icons';
+ content: "\e03e";
+ position: absolute;
+ height: 20px;
+ line-height: 45px;
+ font-size: 12px;
+ margin: 0 0 0 10px;
+ opacity: .6;
+ color: #444;
+ }
+
+ .shows .alph_nav .actions {
+ -moz-user-select: none;
+ width: 44px;
+ height: 44px;
+ display: inline-block;
+ vertical-align: top;
+ z-index: 200;
+ position: relative;
+ border: 1px solid rgba(255,255,255,.07);
+ border-width: 0 1px;
+ }
+ .shows .alph_nav .actions:hover {
+ box-shadow: 0 100px 20px -10px rgba(0,0,0,0.55);
+ }
+ .shows .alph_nav .actions li {
+ width: 100%;
+ height: 45px;
+ line-height: 40px;
+ position: relative;
+ z-index: 20;
+ display: none;
+ cursor: pointer;
+ }
+ .shows .alph_nav .actions:hover li:not(.active) {
+ display: block;
+ background: #FFF;
+ color: #444;
+ }
+ .shows .alph_nav .actions li:hover:not(.active) {
+ background: #ccc;
+ }
+ .shows .alph_nav .actions li.active {
+ display: block;
+ }
+
+ .shows .alph_nav .actions li.mass_edit:before {
+ content: "\e070";
+ }
+
+ .shows .alph_nav .actions li.list:before {
+ content: "\e0d8";
+ }
+
+ .shows .alph_nav .actions li.details:before {
+ content: "\e022";
+ }
+
+ .shows .alph_nav .mass_edit_form {
+ clear: both;
+ text-align: center;
+ display: none;
+ overflow: hidden;
+ float: left;
+ height: 44px;
+ line-height: 44px;
+ }
+ .shows.mass_edit_list .mass_edit_form {
+ display: inline-block;
+ }
+ .shows.mass_edit_list .mass_edit_form .select {
+ font-size: 14px;
+ display: inline-block;
+ }
+ .shows.mass_edit_list .mass_edit_form .select .check {
+ display: inline-block;
+ vertical-align: middle;
+ margin: -4px 0 0 5px;
+ }
+ .shows.mass_edit_list .mass_edit_form .select span {
+ opacity: 0.7;
+ }
+ .shows.mass_edit_list .mass_edit_form .select .count {
+ font-weight: bold;
+ margin: 0 3px 0 10px;
+ }
+
+ .shows .alph_nav .mass_edit_form .quality {
+ display: inline-block;
+ margin: 0 0 0 16px;
+ }
+ .shows .alph_nav .mass_edit_form .quality select {
+ width: 120px;
+ margin-right: 5px;
+ }
+ .shows .alph_nav .mass_edit_form .button {
+ padding: 3px 7px;
+ }
+
+ .shows .alph_nav .mass_edit_form .refresh,
+ .shows .alph_nav .mass_edit_form .delete {
+ display: inline-block;
+ margin-left: 8px;
+ }
+
+ .shows .alph_nav .mass_edit_form .refresh span,
+ .shows .alph_nav .mass_edit_form .delete span {
+ margin: 0 10px 0 0;
+ }
+
+ .shows .alph_nav .more_menu > a {
+ background: none;
+ }
+
+ .shows .alph_nav .more_menu.extra > a:before {
+ content: '...';
+ font-size: 1.7em;
+ line-height: 23px;
+ text-align: center;
+ display: block;
+ }
+
+ .shows .alph_nav .more_menu.filter {
+ }
+
+ .shows .alph_nav .more_menu.filter > a:before {
+ content: "\e0e8";
+ font-family: 'Elusive-Icons';
+ line-height: 33px;
+ display: block;
+ text-align: center;
+ }
+
+ .shows .alph_nav .more_menu.filter .wrapper {
+ right: 88px;
+ width: 300px;
+ }
+
+.shows .empty_wanted {
+ background-image: url('../../images/emptylist.png');
+ background-position: 80% 0;
+ height: 750px;
+ width: 100%;
+ max-width: 900px;
+ padding-top: 260px;
+}
+
+.shows .empty_manage {
+ text-align: center;
+ font-size: 25px;
+ line-height: 150%;
+ padding: 40px 0;
+}
+
+ .shows .empty_manage .after_manage {
+ margin-top: 30px;
+ font-size: 16px;
+ }
+
+ .shows .progress {
+ padding: 10px;
+ margin: 5px 0;
+ text-align: left;
+ }
+
+ .shows .progress > div {
+ padding: 5px 10px;
+ font-size: 12px;
+ line-height: 12px;
+ text-align: left;
+ display: inline-block;
+ width: 49%;
+ background: rgba(255, 255, 255, 0.05);
+ margin: 2px 0.5%;
+ }
+
+ .shows .progress > div .folder {
+ display: inline-block;
+ padding: 5px 20px 5px 0;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ overflow: hidden;
+ width: 85%;
+ direction: ltr;
+ vertical-align: middle;
+ }
+
+ .shows .progress > div .percentage {
+ display: inline-block;
+ text-transform: uppercase;
+ font-weight: normal;
+ font-size: 20px;
+ border-left: 1px solid rgba(255, 255, 255, .2);
+ width: 15%;
+ text-align: right;
+ vertical-align: middle;
+ }
diff --git a/couchpotato/core/media/show/_base/static/show.episodes.js b/couchpotato/core/media/show/_base/static/show.episodes.js
new file mode 100755
index 00000000..ace93af4
--- /dev/null
+++ b/couchpotato/core/media/show/_base/static/show.episodes.js
@@ -0,0 +1,103 @@
+var Episodes = new Class({
+ initialize: function(show) {
+ var self = this;
+
+ self.show = show;
+ },
+
+ open: function(){
+ var self = this;
+
+ if(!self.container){
+ self.container = new Element('div.options').grab(
+ self.episodes_container = new Element('div.episodes.table')
+ );
+
+ self.container.inject(self.show, 'top');
+
+ Api.request('library.tree', {
+ 'data': {
+ 'media_id': self.show.data._id
+ },
+ 'onComplete': function(json){
+ self.data = json.result;
+
+ self.createEpisodes();
+ }
+ });
+ }
+
+ self.show.slide('in', self.container);
+ },
+
+ createEpisodes: function() {
+ var self = this;
+
+ self.data.seasons.sort(self.sortSeasons);
+ self.data.seasons.each(function(season) {
+ self.createSeason(season);
+
+ season.episodes.sort(self.sortEpisodes);
+ season.episodes.each(function(episode) {
+ self.createEpisode(episode);
+ });
+ });
+ },
+
+ createSeason: function(season) {
+ var self = this,
+ title = '';
+
+ if(season.info.number) {
+ title = 'Season ' + season.info.number;
+ } else {
+ // Season 0 / Specials
+ title = 'Specials';
+ }
+
+ season['el'] = new Element('div', {
+ 'class': 'item head',
+ 'id': 'season_'+season._id
+ }).adopt(
+ new Element('span.name', {'text': title})
+ ).inject(self.episodes_container);
+ },
+
+ createEpisode: function(episode){
+ var self = this,
+ e = new Episode(self.show, episode);
+
+ $(e).inject(self.episodes_container);
+ },
+
+ sortSeasons: function(a, b) {
+ // Move "Specials" to the bottom of the list
+ if(!a.info.number) {
+ return 1;
+ }
+
+ if(!b.info.number) {
+ return -1;
+ }
+
+ // Order seasons descending
+ if(a.info.number < b.info.number)
+ return -1;
+
+ if(a.info.number > b.info.number)
+ return 1;
+
+ return 0;
+ },
+
+ sortEpisodes: function(a, b) {
+ // Order episodes descending
+ if(a.info.number < b.info.number)
+ return -1;
+
+ if(a.info.number > b.info.number)
+ return 1;
+
+ return 0;
+ }
+});
\ No newline at end of file
diff --git a/couchpotato/core/media/show/_base/static/show.js b/couchpotato/core/media/show/_base/static/show.js
new file mode 100755
index 00000000..b91e637c
--- /dev/null
+++ b/couchpotato/core/media/show/_base/static/show.js
@@ -0,0 +1,347 @@
+var Show = new Class({
+
+ Extends: BlockBase,
+
+ action: {},
+
+ initialize: function(list, options, data){
+ var self = this;
+
+ self.data = data;
+ self.view = options.view || 'details';
+ self.list = list;
+
+ self.el = new Element('div.show');
+
+ self.episodes = new Episodes(self);
+
+ self.profile = Quality.getProfile(data.profile_id) || {};
+ self.category = CategoryList.getCategory(data.category_id) || {};
+ self.parent(self, options);
+
+ self.addEvents();
+ },
+
+ addEvents: function(){
+ var self = this;
+
+ self.global_events = {};
+
+ // Do refresh with new data
+ self.global_events['movie.update'] = function(notification){
+ if(self.data._id != notification.data._id) return;
+
+ self.busy(false);
+ self.removeView();
+ self.update.delay(2000, self, notification);
+ };
+ App.on('movie.update', self.global_events['movie.update']);
+
+ // Add spinner on load / search
+ ['media.busy', 'movie.searcher.started'].each(function(listener){
+ self.global_events[listener] = function(notification){
+ if(notification.data && (self.data._id == notification.data._id || (typeOf(notification.data._id) == 'array' && notification.data._id.indexOf(self.data._id) > -1)))
+ self.busy(true);
+ };
+ App.on(listener, self.global_events[listener]);
+ });
+
+ // Remove spinner
+ self.global_events['movie.searcher.ended'] = function(notification){
+ if(notification.data && self.data._id == notification.data._id)
+ self.busy(false)
+ };
+ App.on('movie.searcher.ended', self.global_events['movie.searcher.ended']);
+
+ // Reload when releases have updated
+ self.global_events['release.update_status'] = function(notification){
+ var data = notification.data;
+ if(data && self.data._id == data.movie_id){
+
+ if(!self.data.releases)
+ self.data.releases = [];
+
+ self.data.releases.push({'quality': data.quality, 'status': data.status});
+ self.updateReleases();
+ }
+ };
+
+ App.on('release.update_status', self.global_events['release.update_status']);
+
+ },
+
+ destroy: function(){
+ var self = this;
+
+ self.el.destroy();
+ delete self.list.movies_added[self.get('id')];
+ self.list.movies.erase(self);
+
+ self.list.checkIfEmpty();
+
+ // Remove events
+ Object.each(self.global_events, function(handle, listener){
+ App.off(listener, handle);
+ });
+ },
+
+ busy: function(set_busy, timeout){
+ var self = this;
+
+ if(!set_busy){
+ setTimeout(function(){
+ if(self.spinner){
+ self.mask.fade('out');
+ setTimeout(function(){
+ if(self.mask)
+ self.mask.destroy();
+ if(self.spinner)
+ self.spinner.el.destroy();
+ self.spinner = null;
+ self.mask = null;
+ }, timeout || 400);
+ }
+ }, timeout || 1000)
+ }
+ else if(!self.spinner) {
+ self.createMask();
+ self.spinner = createSpinner(self.mask);
+ self.mask.fade('in');
+ }
+ },
+
+ createMask: function(){
+ var self = this;
+ self.mask = new Element('div.mask', {
+ 'styles': {
+ 'z-index': 4
+ }
+ }).inject(self.el, 'top').fade('hide');
+ },
+
+ update: function(notification){
+ var self = this;
+
+ self.data = notification.data;
+ self.el.empty();
+ self.removeView();
+
+ self.profile = Quality.getProfile(self.data.profile_id) || {};
+ self.category = CategoryList.getCategory(self.data.category_id) || {};
+ self.create();
+
+ self.busy(false);
+ },
+
+ create: function(){
+ var self = this;
+
+ self.el.addClass('status_'+self.get('status'));
+
+ self.el.adopt(
+ self.select_checkbox = new Element('input[type=checkbox].inlay', {
+ 'events': {
+ 'change': function(){
+ self.fireEvent('select')
+ }
+ }
+ }),
+ self.thumbnail = (self.data.files && self.data.files.image_poster) ? new Element('img', {
+ 'class': 'type_image poster',
+ 'src': Api.createUrl('file.cache') + self.data.files.image_poster[0].split(Api.getOption('path_sep')).pop()
+ }): null,
+ self.data_container = new Element('div.data.inlay.light').adopt(
+ self.info_container = new Element('div.info').adopt(
+ new Element('div.title').adopt(
+ self.title = new Element('a', {
+ 'events': {
+ 'click': function(e){
+ self.episodes.open();
+ }
+ },
+ 'text': self.getTitle() || 'n/a'
+ }),
+ self.year = new Element('div.year', {
+ 'text': self.data.info.year || 'n/a'
+ })
+ ),
+ self.description = new Element('div.description.tiny_scroll', {
+ 'text': self.data.info.plot
+ }),
+ self.quality = new Element('div.quality', {
+ 'events': {
+ 'click': function(e){
+ var releases = self.el.getElement('.actions .releases');
+ if(releases.isVisible())
+ releases.fireEvent('click', [e])
+ }
+ }
+ })
+ ),
+ self.actions = new Element('div.actions')
+ )
+ );
+
+ if(!self.thumbnail)
+ self.el.addClass('no_thumbnail');
+
+ //self.changeView(self.view);
+ self.select_checkbox_class = new Form.Check(self.select_checkbox);
+
+ // Add profile
+ if(self.profile.data)
+ self.profile.getTypes().each(function(type){
+
+ var q = self.addQuality(type.get('quality'), type.get('3d'));
+ if((type.finish == true || type.get('finish')) && !q.hasClass('finish')){
+ q.addClass('finish');
+ q.set('title', q.get('title') + ' Will finish searching for this movie if this quality is found.')
+ }
+
+ });
+
+ // Add releases
+ self.updateReleases();
+
+ Object.each(self.options.actions, function(action, key){
+ self.action[key.toLowerCase()] = action = new self.options.actions[key](self);
+ if(action.el)
+ self.actions.adopt(action)
+ });
+
+ },
+
+ updateReleases: function(){
+ var self = this;
+ if(!self.data.releases || self.data.releases.length == 0) return;
+
+ self.data.releases.each(function(release){
+
+ var q = self.quality.getElement('.q_'+ release.quality+(release.is_3d ? '.is_3d' : ':not(.is_3d)')),
+ status = release.status;
+
+ if(!q && (status == 'snatched' || status == 'seeding' || status == 'done'))
+ q = self.addQuality(release.quality, release.is_3d || false);
+
+ if (q && !q.hasClass(status)){
+ q.addClass(status);
+ q.set('title', (q.get('title') ? q.get('title') : '') + ' status: '+ status)
+ }
+
+ });
+ },
+
+ addQuality: function(quality, is_3d){
+ var self = this;
+
+ var q = Quality.getQuality(quality);
+ return new Element('span', {
+ 'text': q.label + (is_3d ? ' 3D' : ''),
+ 'class': 'q_'+q.identifier + (is_3d ? ' is_3d' : ''),
+ 'title': ''
+ }).inject(self.quality);
+
+ },
+
+ getTitle: function(){
+ var self = this;
+
+ if(self.data.title)
+ return self.getUnprefixedTitle(self.data.title);
+ else if(self.data.info.titles.length > 0)
+ return self.getUnprefixedTitle(self.data.info.titles[0]);
+
+ return 'Unknown movie'
+ },
+
+ getUnprefixedTitle: function(t){
+ if(t.substr(0, 4).toLowerCase() == 'the ')
+ t = t.substr(4) + ', The';
+ else if(t.substr(0, 3).toLowerCase() == 'an ')
+ t = t.substr(3) + ', An';
+ else if(t.substr(0, 2).toLowerCase() == 'a ')
+ t = t.substr(2) + ', A';
+ return t;
+ },
+
+ slide: function(direction, el){
+ var self = this;
+
+ if(direction == 'in'){
+ self.temp_view = self.view;
+ self.changeView('details');
+
+ self.el.addEvent('outerClick', function(){
+ self.removeView();
+ self.slide('out')
+ });
+ el.show();
+
+
+ self.el.addClass('expanded');
+ self.el.getElements('.table').addClass('expanded');
+
+ self.data_container.addClass('hide_right');
+ }
+ else {
+ self.el.removeEvents('outerClick');
+
+ setTimeout(function(){
+ if(self.el)
+ {
+ self.el.getElements('> :not(.data):not(.poster):not(.movie_container)').hide();
+ self.el.getElements('.table').removeClass('expanded');
+ }
+ }, 600);
+
+ self.el.removeClass('expanded');
+ self.data_container.removeClass('hide_right');
+ }
+ },
+
+ changeView: function(new_view){
+ var self = this;
+
+ if(self.el)
+ self.el
+ .removeClass(self.view+'_view')
+ .addClass(new_view+'_view');
+
+ self.view = new_view;
+ },
+
+ removeView: function(){
+ var self = this;
+
+ self.el.removeClass(self.view+'_view')
+ },
+
+ getIdentifier: function(){
+ var self = this;
+
+ try {
+ return self.get('identifiers').imdb;
+ }
+ catch (e){ }
+
+ return self.get('imdb');
+ },
+
+ get: function(attr){
+ return this.data[attr] || this.data.info[attr]
+ },
+
+ select: function(bool){
+ var self = this;
+ self.select_checkbox_class[bool ? 'check' : 'uncheck']()
+ },
+
+ isSelected: function(){
+ return this.select_checkbox.get('checked');
+ },
+
+ toElement: function(){
+ return this.el;
+ }
+
+});
diff --git a/couchpotato/core/media/show/episode.py b/couchpotato/core/media/show/episode.py
old mode 100644
new mode 100755
index 0f05f284..e184e900
--- a/couchpotato/core/media/show/episode.py
+++ b/couchpotato/core/media/show/episode.py
@@ -26,7 +26,7 @@ class Episode(MediaBase):
# Add Season
episode_info = {
'_t': 'media',
- 'type': 'episode',
+ 'type': 'show.episode',
'identifiers': identifiers,
'parent_id': parent_id,
'info': info, # Returned dict by providers
@@ -63,7 +63,16 @@ class Episode(MediaBase):
# Get new info
if not info:
- info = fireEvent('episode.info', episode.get('identifiers'), merge = True)
+ season = db.get('id', episode['parent_id'])
+ show = db.get('id', season['parent_id'])
+
+ info = fireEvent(
+ 'episode.info', show.get('identifiers'), {
+ 'season_identifier': season.get('info', {}).get('number'),
+ 'episode_identifier': episode.get('identifiers')
+ },
+ merge = True
+ )
# Update/create media
if force:
diff --git a/couchpotato/core/media/show/library/episode.py b/couchpotato/core/media/show/library/episode.py
old mode 100644
new mode 100755
index 7161f2d2..26b5c3de
--- a/couchpotato/core/media/show/library/episode.py
+++ b/couchpotato/core/media/show/library/episode.py
@@ -14,7 +14,7 @@ class EpisodeLibraryPlugin(LibraryBase):
addEvent('library.identifier', self.identifier)
def query(self, media, first = True, condense = True, include_identifier = True, **kwargs):
- if media.get('type') != 'episode':
+ if media.get('type') != 'show.episode':
return
related = fireEvent('library.related', media, single = True)
@@ -43,7 +43,7 @@ class EpisodeLibraryPlugin(LibraryBase):
return titles
def identifier(self, media):
- if media.get('type') != 'episode':
+ if media.get('type') != 'show.episode':
return
identifier = {
diff --git a/couchpotato/core/media/show/library/season.py b/couchpotato/core/media/show/library/season.py
old mode 100644
new mode 100755
index 228d1432..89489af6
--- a/couchpotato/core/media/show/library/season.py
+++ b/couchpotato/core/media/show/library/season.py
@@ -14,7 +14,7 @@ class SeasonLibraryPlugin(LibraryBase):
addEvent('library.identifier', self.identifier)
def query(self, media, first = True, condense = True, include_identifier = True, **kwargs):
- if media.get('type') != 'season':
+ if media.get('type') != 'show.season':
return
related = fireEvent('library.related', media, single = True)
@@ -44,7 +44,7 @@ class SeasonLibraryPlugin(LibraryBase):
return titles
def identifier(self, media):
- if media.get('type') != 'season':
+ if media.get('type') != 'show.season':
return
return {
diff --git a/couchpotato/core/media/show/providers/info/thetvdb.py b/couchpotato/core/media/show/providers/info/thetvdb.py
index b2e58d96..e57057c3 100755
--- a/couchpotato/core/media/show/providers/info/thetvdb.py
+++ b/couchpotato/core/media/show/providers/info/thetvdb.py
@@ -172,15 +172,16 @@ class TheTVDb(ShowProvider):
"""Either return a list of all episodes or a single episode.
If episode_identifer contains an episode number to search for
"""
- season_identifier = params.get('season_identifier', None)
- episode_identifier = params.get('episode_identifier', None)
+ season_identifier = self.getIdentifier(params.get('season_identifier', None))
+ episode_identifier = self.getIdentifier(params.get('episode_identifier', None))
+ identifier = self.getIdentifier(identifier)
if not identifier and season_identifier is None:
return False
# season_identifier must contain the 'show id : season number' since there is no tvdb id
# for season and we need a reference to both the show id and season number
- if season_identifier:
+ if not identifier and season_identifier:
try:
identifier, season_identifier = season_identifier.split(':')
season_identifier = int(season_identifier)
@@ -205,15 +206,21 @@ class TheTVDb(ShowProvider):
for episode in season.values():
if episode_identifier is not None and episode['id'] == toUnicode(episode_identifier):
- result = self._parseEpisode(show, episode)
+ result = self._parseEpisode(episode)
self.setCache(cache_key, result)
return result
else:
- result.append(self._parseEpisode(show, episode))
+ result.append(self._parseEpisode(episode))
self.setCache(cache_key, result)
return result
+ def getIdentifier(self, value):
+ if type(value) is dict:
+ return value.get('thetvdb')
+
+ return value
+
def _parseShow(self, show):
#
diff --git a/couchpotato/core/media/show/searcher/episode.py b/couchpotato/core/media/show/searcher/episode.py
old mode 100644
new mode 100755
index 1e6b253c..ace9768e
--- a/couchpotato/core/media/show/searcher/episode.py
+++ b/couchpotato/core/media/show/searcher/episode.py
@@ -116,7 +116,7 @@ class EpisodeSearcher(SearcherBase, ShowTypeBase):
log.info2('Too early to search for %s, %s', (too_early_to_search, query))
def correctRelease(self, release = None, media = None, quality = None, **kwargs):
- if media.get('type') != 'episode':
+ if media.get('type') != 'show.episode':
return
retention = Env.setting('retention', section = 'nzb')
diff --git a/couchpotato/core/media/show/season.py b/couchpotato/core/media/show/season.py
old mode 100644
new mode 100755
index a2dde780..6197c9ca
--- a/couchpotato/core/media/show/season.py
+++ b/couchpotato/core/media/show/season.py
@@ -28,7 +28,7 @@ class Season(MediaBase):
# Add Season
season_info = {
'_t': 'media',
- 'type': 'season',
+ 'type': 'show.season',
'identifiers': identifiers,
'parent_id': parent_id,
'info': info, # Returned dict by providers