diff --git a/couchpotato/core/plugins/movie/main.py b/couchpotato/core/plugins/movie/main.py index aa578ca9..2b377bb8 100644 --- a/couchpotato/core/plugins/movie/main.py +++ b/couchpotato/core/plugins/movie/main.py @@ -69,7 +69,7 @@ class MoviePlugin(Plugin): addApiView('movie.edit', self.edit, docs = { 'desc': 'Add new movie to the wanted list', 'params': { - 'id': {'desc': 'Movie ID you want to edit'}, + 'id': {'desc': 'Movie ID(s) you want to edit.', 'type': 'int (comma separated)'}, 'profile_id': {'desc': 'ID of quality profile you want the edit the movie to.'}, 'default_title': {'desc': 'Movie title to use for searches. Has to be one of the titles returned by movie.search.'}, } @@ -77,7 +77,7 @@ class MoviePlugin(Plugin): addApiView('movie.delete', self.delete, docs = { 'desc': 'Delete a movie from the wanted list', 'params': { - 'id': {'desc': 'Movie ID you want to delete'}, + 'id': {'desc': 'Movie ID(s) you want to delete.', 'type': 'int (comma separated)'}, } }) @@ -301,19 +301,23 @@ class MoviePlugin(Plugin): params = getParams() db = get_session() - m = db.query(Movie).filter_by(id = params.get('id')).first() - m.profile_id = params.get('profile_id') + ids = params.get('id').split(',') + for movie_id in ids: - # Default title - for title in m.library.titles: - title.default = params.get('default_title').lower() == title.title.lower() + m = db.query(Movie).filter_by(id = movie_id).first() + m.profile_id = params.get('profile_id') - db.commit() + # Default title + if params.get('default_title'): + for title in m.library.titles: + title.default = params.get('default_title').lower() == title.title.lower() - fireEvent('movie.restatus', m.id) + db.commit() - movie_dict = m.to_dict(self.default_dict) - fireEventAsync('searcher.single', movie_dict) + fireEvent('movie.restatus', m.id) + + movie_dict = m.to_dict(self.default_dict) + fireEventAsync('searcher.single', movie_dict) return jsonified({ 'success': True, @@ -326,9 +330,11 @@ class MoviePlugin(Plugin): status = fireEvent('status.add', 'deleted', single = True) - movie = db.query(Movie).filter_by(id = params.get('id')).first() - movie.status_id = status.get('id') - db.commit() + ids = params.get('id').split(',') + for movie_id in ids: + movie = db.query(Movie).filter_by(id = movie_id).first() + movie.status_id = status.get('id') + db.commit() return jsonified({ 'success': True, diff --git a/couchpotato/core/plugins/movie/static/list.js b/couchpotato/core/plugins/movie/static/list.js index 883e15b8..e1971055 100644 --- a/couchpotato/core/plugins/movie/static/list.js +++ b/couchpotato/core/plugins/movie/static/list.js @@ -3,7 +3,7 @@ var MovieList = new Class({ Implements: [Options], options: { - navigation: false, + navigation: true, limit: 50 }, @@ -76,11 +76,15 @@ var MovieList = new Class({ var actions = a[info.status.identifier.capitalize()] || a.Wanted || {}; var m = new Movie(self, { - 'actions': actions + 'actions': actions, + 'view': self.current_view, + 'onSelect': self.calculateSelected.bind(self) }, info); $(m).inject(self.movie_list); m.fireEvent('injected'); + self.movies.include(m) + }); }, @@ -89,8 +93,12 @@ var MovieList = new Class({ var self = this; var chars = '#ABCDEFGHIJKLMNOPQRSTUVWXYZ'; + self.current_view = self.getSavedView(); + self.el.addClass(self.current_view+'_list') + self.navigation = new Element('div.alph_nav').adopt( - self.alpha = new Element('ul.inlay', { + self.navigation_actions = new Element('ul.inlay.actions.reversed'), + self.navigation_alpha = new Element('ul.inlay.numbers', { 'events': { 'click:relay(li)': function(e, el){ self.movie_list.empty() @@ -99,24 +107,73 @@ var MovieList = new Class({ } } }), - self.search_input = new Element('input.inlay', { + self.navigation_search_input = new Element('input.inlay', { 'placeholder': 'Search', 'events': { 'keyup': self.search.bind(self), 'change': self.search.bind(self) } - })/*, - self.view = new Element('ul.inlay').adopt( - new Element('li.list'), - new Element('li.thumbnails'), - new Element('li.text') - )*/ + }), + 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) + } + }) + ) + ) ).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.id ? profile.id : profile.data.id, + 'text': profile.label ? profile.label : profile.data.label + }).inject(self.mass_edit_quality) + }); + + // Actions + ['mass_edit', 'thumbs', 'list'].each(function(view){ + self.navigation_actions.adopt( + new Element('li.'+view+(self.current_view == view ? '.active' : '')+'[data-view='+view+']', { + 'events': { + 'click': function(e){ + var a = 'active'; + self.navigation_actions.getElements('.'+a).removeClass(a); + self.changeView(this.get('data-view')); + this.addClass(a); + } + } + }).adopt(new Element('span')) + ) + }); + // All self.letters['all'] = new Element('li.letter_all.available.active', { 'text': 'ALL', - }).inject(self.alpha); + }).inject(self.navigation_alpha); // Chars chars.split('').each(function(c){ @@ -124,7 +181,7 @@ var MovieList = new Class({ 'text': c, 'class': 'letter_'+c, 'data-letter': c - }).inject(self.alpha); + }).inject(self.navigation_alpha); }); // Get available chars and highlight @@ -153,10 +210,106 @@ var MovieList = new Class({ }, + 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; + var ids = self.getSelectedMovies() + + var qObj = new Question('Are you sure you want to delete the selected movies?', 'Items using this profile, will be set to the default quality.', [{ + 'text': 'Yes, delete them', + 'class': 'delete', + 'events': { + 'click': function(e){ + (e).stop(); + Api.request('movie.delete', { + 'data': { + 'id': ids.join(',') + }, + 'onSuccess': function(){ + qObj.close(); + + self.movies.each(function(movie){ + if (movie.isSelected()){ + $(movie).destroy() + self.movies.erase(movie) + } + }); + + self.calculateSelected() + } + }); + + } + } + }, { + 'text': 'Cancel', + 'cancel': true + }]); + + }, + + changeQualitySelected: function(){ + var self = this; + var ids = self.getSelectedMovies() + + Api.request('movie.edit', { + 'data': { + 'id': ids.join(','), + 'profile_id': self.mass_edit_quality.get('value') + }, + 'onSuccess': self.search.bind(self) + }); + }, + + 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.navigation.getElements('.active').removeClass('active') + self.movies = [] + self.calculateSelected() + self.navigation_alpha.getElements('.active').removeClass('active') self.offset = 0; self.load_more.show(); self.scrollspy.start(); @@ -172,12 +325,32 @@ var MovieList = new Class({ }, + changeView: function(new_view){ + var self = this; + + self.movies.each(function(movie){ + movie.changeView(new_view) + }); + + self.el + .removeClass(self.current_view+'_list') + .addClass(new_view+'_list') + + self.current_view = new_view; + Cookie.write(self.options.identifier+'_view', new_view, {duration: 1000}); + }, + + getSavedView: function(){ + var self = this; + return Cookie.read(self.options.identifier+'_view') || 'thumb'; + }, + search: function(){ var self = this; if(self.search_timer) clearTimeout(self.search_timer); self.search_timer = (function(){ - var search_value = self.search_input.get('value'); + var search_value = self.navigation_search_input.get('value'); if (search_value == self.last_search_value) return self.reset() diff --git a/couchpotato/core/plugins/movie/static/movie.css b/couchpotato/core/plugins/movie/static/movie.css index 087312e3..adde2cb6 100644 --- a/couchpotato/core/plugins/movie/static/movie.css +++ b/couchpotato/core/plugins/movie/static/movie.css @@ -5,31 +5,61 @@ */ .movies { - padding: 50px 0 20px; + padding: 60px 0 20px; } + .movies.mass_edit_list { + padding-top: 90px; + } + .movies .movie { position: relative; border-radius: 4px; margin: 10px 0; - overflow: hidden; + width: 100%; + transition: all 0.2s linear; } - .movies .movie:hover { - border-color: #ddd #fff #fff #ddd; + .movies .movie.list_view, .movies .movie.mass_edit_view { + margin: 1px 0; + border-radius: 0; + background: no-repeat; + box-shadow: none; + border-bottom: 1px solid rgba(255,255,255,0.05); } + .movies .movie.list_view:hover, .movies .movie.mass_edit_view:hover { + background: rgba(255,255,255,0.03); + } + .movies .data { padding: 20px; height: 180px; width: 840px; position: relative; float: right; - border-radius: 0; - -moz-border-radius: 0; - -webkit-border-radius: 0; overflow: hidden; + transition: all 0.2s linear; } + .movies .list_view .data, .movies .mass_edit_view .data { + height: 30px; + padding: 3px 10px; + width: 938px; + box-shadow: none; + border: 0; + background: none; + } + + .movies .movie .check { + display: none; + } + + .movies.mass_edit_list .movie .check { + float: left; + display: block; + margin: 7px 0 0 5px; + } + .movies .poster { float: left; width: 120px; @@ -37,8 +67,17 @@ overflow: hidden; height: 180px; border-radius: 4px 0 0 4px; + transition: all 0.2s linear; } + .movies .list_view .poster, .movies .mass_edit_view .poster { + width: 20px; + height: 30px; + } + .movies.mass_edit_list .poster { + display: none; + } + .movies .poster img, .options .poster img { width: 101%; height: 101%; @@ -49,8 +88,14 @@ font-weight: bold; margin-bottom: 10px; float: left; - width: 80%; + width: 50%; + transition: all 0.2s linear; + overflow: hidden; } + .movies .list_view .info .title, .movies .mass_edit_view .info .title { + font-size: 16px; + font-weight: normal; + } .movies .info .year { font-size: 30px; @@ -59,7 +104,12 @@ color: #bbb; width: 10%; text-align: right; + transition: all 0.2s linear; } + .movies .list_view .info .year, .movies .mass_edit_view .info .year { + font-size: 16px; + width: 6%; + } .movies .info .rating { font-size: 30px; @@ -78,12 +128,20 @@ .movies .data:hover .description { overflow: auto; } - + .movies .list_view .info .description, .movies .mass_edit_view .info .description { + display: none; + } + .movies .data .quality span { padding: 0 5px; font-weight: bold; } .movies .data .quality span:first-child {padding-left: 0;} + .movies .list_view .data .quality, .movies .mass_edit_view .data .quality { + text-align: right; + float: right; + width: 35%; + } .movies .data .quality .available { color: orange; } .movies .data .quality .snatched { color: lightgreen; } @@ -95,7 +153,10 @@ margin-top: -25px; } .movies .data:hover .action { opacity: 0.6; } - .movies .data:hover .action:hover { opacity: 1; } + .movies .data:hover .action:hover { opacity: 1; } + .movies.mass_edit_list .data .actions { + display: none; + } .movies .data .action { background-repeat: no-repeat; @@ -106,6 +167,11 @@ padding: 3px; opacity: 0; } + + .movies .list_view .data:hover .actions, .movies .mass_edit_view .data:hover .actions { + margin: -35px -7px 0 0; + background: #4e5969; + } .movies .delete_container { clear: both; @@ -183,9 +249,9 @@ text-align: left; padding: 0 10px; } - .movies .options .table.files .name { width: 598px; } - .movies .options .table .type { width: 120px; } - .movies .options .table .is_available { width: 80px; } + .movies .options .table.files .name { width: 608px; } + .movies .options .table .type { width: 130px; } + .movies .options .table .is_available { width: 90px; } .movies .options .table a { width: 30px !important; @@ -220,17 +286,18 @@ position: fixed; z-index: 2; top: 0; - padding: 100px 7px 7px; - margin: -7px; - width: 960px; + padding: 100px 60px 7px; + width: 1082px; + margin: 0 -60px; + box-shadow: 0 20px 20px -22px rgba(0,0,0,0.1); } .movies .alph_nav.float { - box-shadow: 0 0 10px rgba(0,0,0,0.4); - border-radius: 4px; + box-shadow: 0 30px 30px -32px rgba(0,0,0,0.5); + border-radius: 0; background: #4e5969; } - + .movies .alph_nav ul { list-style: none; padding: 0 0 1px; @@ -250,7 +317,7 @@ color: #666; border: 1px solid transparent; } - .movies .alph_nav li:first-child { + .movies .alph_nav .numbers li:first-child { width: 34px; } .movies .alph_nav li.active, .movies .alph_nav li:hover { @@ -265,4 +332,91 @@ padding: 6px 5px; margin: 0; float: right; - } \ No newline at end of file + width: 155px; + height: 25px; + float: right; + } + + .movies .alph_nav .actions { + margin: 0 32px 0 0; + -moz-user-select: none; + } + .movies .alph_nav .actions li { + border-radius: 1px; + width: auto; + } + .movies .alph_nav .actions li.active { + background: none; + border: 1px solid transparent; + box-shadow: none; + } + .movies .alph_nav .actions li span { + display: block; + background: url('../images/sprite.png') no-repeat; + width: 25px; + height: 100%; + } + + .movies .alph_nav .actions li.mass_edit span { + background-position: 3px 3px; + } + + .movies .alph_nav .actions li.list span { + background-position: 3px -95px; + } + + .movies .alph_nav .actions li.thumbs span { + background-position: 3px -74px; + } + + .movies .alph_nav .actions li:first-child { + border-radius: 3px 0 0 3px; + } + .movies .alph_nav .actions li:last-child { + border-radius: 0 3px 3px 0; + } + + .movies .alph_nav .mass_edit_form { + clear: both; + text-align: center; + display: none; + } + .movies.mass_edit_list .mass_edit_form { + display: block; + } + .movies.mass_edit_list .mass_edit_form .select { + float: left; + margin: 5px 0 0 5px; + font-size: 14px; + } + .movies.mass_edit_list .mass_edit_form .select span { + vertical-align: middle; + opacity: 0.7; + } + .movies.mass_edit_list .mass_edit_form .select .count { + font-weight: bold; + margin: 0 3px 0 10px; + } + + .movies .alph_nav .mass_edit_form .quality { + float: left; + padding: 8px 0 0; + margin: 0 0 0 16px; + } + .movies .alph_nav .mass_edit_form .quality select { + width: 120px; + margin-right: 5px; + } + .movies .alph_nav .mass_edit_form .button { + padding: 3px 7px; + } + + .movies .alph_nav .mass_edit_form .delete { + float: left; + padding: 8px 0 0 8px; + } + + .movies .alph_nav .mass_edit_form .delete span { + margin: 0 10px 0 0; + } + \ No newline at end of file diff --git a/couchpotato/core/plugins/movie/static/movie.js b/couchpotato/core/plugins/movie/static/movie.js index 4b7ac27c..55d51d44 100644 --- a/couchpotato/core/plugins/movie/static/movie.js +++ b/couchpotato/core/plugins/movie/static/movie.js @@ -8,6 +8,7 @@ var Movie = new Class({ var self = this; self.data = data; + self.view = options.view || 'thumb'; self.profile = Quality.getProfile(data.profile_id) || {}; self.parent(self, options); @@ -17,6 +18,13 @@ var Movie = new Class({ var self = this; self.el = new Element('div.movie.inlay').adopt( + self.select_checkbox = new Element('input[type=checkbox].inlay', { + 'events': { + 'change': function(){ + self.fireEvent('select') + } + } + }), self.thumbnail = File.Select.single('poster', self.data.library.files), self.data_container = new Element('div.data.inlay.light', { 'tween': { @@ -44,6 +52,9 @@ var Movie = new Class({ ) ); + 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){ @@ -108,7 +119,13 @@ var Movie = new Class({ var self = this; if(direction == 'in'){ - self.el.addEvent('outerClick', self.slide.bind(self, 'out')) + self.temp_view = self.view; + self.changeView('thumb') + + self.el.addEvent('outerClick', function(){ + self.changeView(self.temp_view) + self.slide('out') + }) el.show(); self.data_container.tween('right', 0, -840); } @@ -123,8 +140,27 @@ var Movie = new Class({ } }, + changeView: function(new_view){ + var self = this; + + self.el + .removeClass(self.view+'_view') + .addClass(new_view+'_view') + + self.view = new_view; + }, + get: function(attr){ return this.data[attr] || this.data.library[attr] + }, + + select: function(bool){ + var self = this; + self.select_checkbox_class[bool ? 'check' : 'uncheck']() + }, + + isSelected: function(){ + return this.select_checkbox.get('checked'); } }); diff --git a/couchpotato/static/style/main.css b/couchpotato/static/style/main.css index 4392e9c3..fef35b24 100644 --- a/couchpotato/static/style/main.css +++ b/couchpotato/static/style/main.css @@ -161,11 +161,11 @@ body > .spinner, .mask{ margin: 0; width: 100%; z-index: 5; - box-shadow: 0 0 5px rgba(0,0,0,0.05); + box-shadow: 0 20px 30px -30px rgba(0,0,0,0.05); transition: box-shadow .4s cubic-bezier(0.9,0,0.1,1); } .header.with_shadow { - box-shadow: 0 0 20px rgba(0,0,0,0.3); + box-shadow: 0 20px 30px -30px rgba(0,0,0,0.3); } .header > div { @@ -342,22 +342,22 @@ body > .spinner, .mask{ color: #fff; border: 0; border-radius:3px; - background: #282d34; + background-color: #282d34; box-shadow: inset 0 1px 8px rgba(0,0,0,0.25), 0 1px 0px rgba(255,255,255,0.25); } .inlay.light { - background: #47515f; + background-color: #47515f; outline: none; box-shadow: inset 0 1px 8px rgba(0,0,0,0.05), 0 1px 0px rgba(255,255,255,0.15); } .inlay:focus { - background: #3a4350; + background-color: #3a4350; outline: none; } -.onlay, .inlay .selected, .inlay > li:hover, .inlay > li.active { +.onlay, .inlay .selected, .inlay:not(.reversed) > li:hover, .inlay > li.active, .inlay.reversed > li { border-radius:3px; border: 1px solid #252930; box-shadow: inset 0 1px 0px rgba(255,255,255,0.20), 0 0 3px rgba(0,0,0, 0.2);