diff --git a/CHANGES.md b/CHANGES.md index af856ea6..2260c12b 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -58,6 +58,13 @@ * Change the episodes downloaded stat to display e.g. 2843 / 2844 as 99.9% instead of rounding to 100% * Change 'never' episode row color away from blue on Display Show page when indexer airdate is not defined * Add tint to archived episode row colour to differentiate it from downloaded episodes on the Display Show page +* Add indication of shows with never aired episodes on Episode Overview page +* Add "Collapse" button and visuals for Expanding... and Collapsing... states +* Add the number of episodes marked with the status being queried to Episode Overview page +* Add indication of shows with never aired episodes on Episode Overview page +* Change to separate "Set as wanted" to prevent disaster selection on Episode Overview page +* Remove restriction to not display snatched eps link in footer on Episode Overview page +* Change the shows episodes count text colour to visually separete from year numbers at the end of show names * Fix release group not recognised from manually downloaded filename [develop changelog] diff --git a/gui/slick/css/dark.css b/gui/slick/css/dark.css index d421f1d4..71caaf03 100644 --- a/gui/slick/css/dark.css +++ b/gui/slick/css/dark.css @@ -382,11 +382,13 @@ ul.tags li a{ border:1px solid #111 } +.sickbeardTable tr.header td, .sickbeardTable th{ color:#fff; background-color:#15528F } +.sickbeardTable tr.header td, .sickbeardTable th, .sickbeardTable td{ border-top:1px solid #222; diff --git a/gui/slick/css/style.css b/gui/slick/css/style.css index ae96007e..49dad9e1 100644 --- a/gui/slick/css/style.css +++ b/gui/slick/css/style.css @@ -1324,6 +1324,7 @@ span.snatched b{ clear:both } +.sickbeardTable tr.header td, .sickbeardTable th{ color:#fff; text-align:center; @@ -1331,13 +1332,18 @@ span.snatched b{ white-space:nowrap } -.sickbeardTable th, +.sickbeardTable tr.header td, +.sickbeardTable th, .sickbeardTable td{ border-top:1px solid #fff; border-left:1px solid #fff; padding:4px } +.sickbeardTable tr.header td{ + padding:4px 8px +} + th.row-seasonheader{ border:none; background-color:#fff; @@ -2247,6 +2253,7 @@ div.metadataDiv .disabled{ manage*.tmpl ========================================================================== */ +.sickbeardTable tr.header td, .manageTable th{ white-space:normal; line-height:24px @@ -2300,6 +2307,11 @@ a.whitelink{ color:#fff } +input.get_more_eps, +input.get_less_eps{ + display:none +} + /* ======================================================================= 404.tmpl ========================================================================== */ diff --git a/gui/slick/interfaces/default/inc_bottom.tmpl b/gui/slick/interfaces/default/inc_bottom.tmpl index b2d0d809..f194a3c9 100644 --- a/gui/slick/interfaces/default/inc_bottom.tmpl +++ b/gui/slick/interfaces/default/inc_bottom.tmpl @@ -56,11 +56,8 @@ ( '', ' (+%s snatched)'\ -% ( - str(ep_snatched), - '%s'\ - % (localRoot, str(ep_snatched)) - )['Episode Overview' != localheader] +% '%s' + % (localRoot, str(ep_snatched)) )[0 < ep_snatched] %> / $ep_total episodes downloaded $ep_percentage | recent search: <%= str(sickbeard.recentSearchScheduler.timeLeft()).split('.')[0] %> diff --git a/gui/slick/interfaces/default/manage_episodeStatuses.tmpl b/gui/slick/interfaces/default/manage_episodeStatuses.tmpl index b9587f86..7cabd7cf 100644 --- a/gui/slick/interfaces/default/manage_episodeStatuses.tmpl +++ b/gui/slick/interfaces/default/manage_episodeStatuses.tmpl @@ -16,70 +16,96 @@

$title

#end if -#if not $whichStatus or ($whichStatus and not $ep_counts): +#if not $whichStatus or ($whichStatus and not $ep_counts) -#if $whichStatus: -

None of your episodes have status $common.statusStrings[$int($whichStatus)]

-#end if + #if $whichStatus: +

no episodes have status $common.statusStrings[$int($whichStatus)].lower()

+ #end if -
- Manage episodes with status - -
+
+ + Manage episodes with status + + +
#else + #if $whichStatus in ($common.ARCHIVED, $common.IGNORED, $common.SNATCHED): + #set $row_class = 'good' + #else + #set $row_class = $common.Overview.overviewStrings[$whichStatus] + #end if + + #set $statusList = [$common.SKIPPED, $common.ARCHIVED, $common.IGNORED] + #if $int($whichStatus) in $statusList + $statusList.remove($int($whichStatus)) + #end if + + #if $int($whichStatus) in [$common.SNATCHED, $common.SNATCHED_PROPER] + $statusList.append($common.FAILED) + #end if + -
- + + -

${len($sorted_show_ids)} Shows containing $common.statusStrings[$int($whichStatus)] episodes

+

$ep_count episode#echo ('s', '')[1 == $ep_count]# marked $common.statusStrings[$int($whichStatus)].lower() in ${len($sorted_show_ids)} show#echo ('s', '')[1 == len($sorted_show_ids)]#

-#if $whichStatus in ($common.ARCHIVED, $common.IGNORED, $common.SNATCHED): - #set $row_class = 'good' -#else - #set $row_class = $common.Overview.overviewStrings[$whichStatus] -#end if - + -
- Set checked shows/episodes to + #for $curStatus in $statusList: + + #end for + + -#if $int($whichStatus) in [$common.SNATCHED, $common.SNATCHED_PROPER] - $statusList.append($common.FAILED) -#end if + Override checked status to + + +
-#for $curStatus in $statusList: - -#end for - - - +
+ + + +
-
- - - -
- - -#for $cur_indexer_id in $sorted_show_ids: - - - - -#end for -
$show_names[$cur_indexer_id] ($ep_counts[$cur_indexer_id])
-
+ + + + #for $cur_indexer_id in $sorted_show_ids: + #if 0 == int($never_counts[$cur_indexer_id]) + #set $output = '%d' % $ep_counts[$cur_indexer_id] + #elif $ep_counts[$cur_indexer_id] != $never_counts[$cur_indexer_id] + #set $diff = $ep_counts[$cur_indexer_id] - $never_counts[$cur_indexer_id] + #set $output = '%d' % $diff + ('', (' episode%s plus %s never with an airdate' % (('s', '')[1 == $ep_counts[$cur_indexer_id]], $never_counts[$cur_indexer_id])))[0 < $never_counts[$cur_indexer_id]] + #else + #set $output = '%s never with an airdate' % (('all %s %ss', '%s %s')[1 == $ep_counts[$cur_indexer_id]] % ($ep_counts[$cur_indexer_id], 'episode')) + #end if + + + + + #end for + +
+ $show_names[$cur_indexer_id] ($output) +
+ #end if diff --git a/gui/slick/js/manageEpisodeStatuses.js b/gui/slick/js/manageEpisodeStatuses.js index 8b31e102..d002dc94 100644 --- a/gui/slick/js/manageEpisodeStatuses.js +++ b/gui/slick/js/manageEpisodeStatuses.js @@ -1,17 +1,18 @@ $(document).ready(function() { - function make_row(indexer_id, season, episode, name, checked) { + function make_row(indexer_id, season, episode, name, checked, airdate_never) { var checkedbox = (checked ? ' checked' : ''), - row_class = $('#row_class').val(); + row_class = $('#row_class').val(), + ep_id = season + 'x' + episode; - return ' ' + return ' ' + ' ' + '' - + ' ' + season + 'x' + episode + '' - + ' ' + name + '' + + ' ' + ep_id + '' + + ' ' + name + (airdate_never ? ' (airdate is never, this should change in time)' : '') + '' + ' '; } @@ -27,48 +28,52 @@ $(document).ready(function() { $('.' + indexer_id + '-epcheck').prop('checked', $(this).prop('checked')); }); - $('.get_more_eps').click(function(){ - var cur_indexer_id = $(this).attr('id'); - var checked = $('#allCheck-' + cur_indexer_id).prop('checked'); - var last_row = $('tr#' + cur_indexer_id); - - $.getJSON(sbRoot + '/manage/showEpisodeStatuses', - { - indexer_id: cur_indexer_id, - whichStatus: $('#oldStatus').val() - }, - function (data) { - $.each(data, function(season,eps){ - $.each(eps, function(episode, name) { - last_row.after(make_row(cur_indexer_id, season, episode, name, checked)); - }); - }); - }); - $(this).hide(); - ($('.get_more_eps:visible').length == 0 ? $('.expandAll').hide() : ''); - }); + $('.get_more_eps').show(); + function show_episodes(btn_element) { + var match = btn_element.attr('id').match(/(.*)[-](.*)/); + if (null == match) + return false; - $('.expandAll').click(function() { - $('.get_more_eps').each(function() { - var cur_indexer_id = $(this).attr('id'); - var checked = $('#allCheck-' + cur_indexer_id).prop('checked'); - var last_row = $('tr#' + cur_indexer_id); + var cur_indexer_id = match[1], action = match[2], checked = $('#allCheck-' + cur_indexer_id).prop('checked'), + show_header = $('tr#' + cur_indexer_id), episode_rows = $('tr[id*="ep-' + cur_indexer_id + '"]'), + void_var = 'more' == action && episode_rows.show() || episode_rows.hide(); + $('input#' + match[0]).val('more' == action ? 'Expanding...' : 'Collapsing...'); + + if (0 == episode_rows.length) { $.getJSON(sbRoot + '/manage/showEpisodeStatuses', { indexer_id: cur_indexer_id, whichStatus: $('#oldStatus').val() }, function (data) { - $.each(data, function(season, eps) { - $.each(eps, function(episode, name) { - last_row.after(make_row(cur_indexer_id, season, episode, name, checked)); + $.each(data, function(season, eps){ + $.each(eps, function(episode, meta) { + show_header.after(make_row(cur_indexer_id, season, episode, meta.name, checked, meta.airdate_never)); }); }); + $('input#' + match[0]).val('more' == action ? 'Expand' : 'Collapse'); + btn_element.hide(); + $('input[id="' + cur_indexer_id + '-' + ('more' == action ? 'less' : 'more') + '"]').show(); }); - $(this).hide(); - }); + } else { + $('input#' + match[0]).val('more' == action ? 'Expand' : 'Collapse'); + btn_element.hide(); + $('input[id="' + cur_indexer_id + '-' + ('more' == action ? 'less' : 'more') + '"]').show(); + } + + } + + $('.get_more_eps,.get_less_eps').click(function(){ + show_episodes($(this)); + ($('.get_more_eps:visible').length == 0 ? $('.expandAll').hide() : ''); + }); + + $('.expandAll').click(function() { $(this).hide(); + $('.get_more_eps').each(function() { + show_episodes($(this)); + }); }); // selects all visible episode checkboxes. diff --git a/sickbeard/webserve.py b/sickbeard/webserve.py index 13f59897..76842872 100644 --- a/sickbeard/webserve.py +++ b/sickbeard/webserve.py @@ -1487,7 +1487,7 @@ class Home(MainHandler): sql_l = [] for curEp in eps.split('|'): - logger.log(u'Attempting to set status on episode ' + curEp + ' to ' + status, logger.DEBUG) + logger.log(u'Attempting to set status on episode %s to %s' % (curEp, status), logger.DEBUG) epInfo = curEp.split('x') @@ -2481,7 +2481,7 @@ class Manage(MainHandler): myDB = db.DBConnection() cur_show_results = myDB.select( - 'SELECT season, episode, name FROM tv_episodes WHERE showid = ? AND season != 0 AND status IN (' + ','.join( + 'SELECT season, episode, name, airdate FROM tv_episodes WHERE showid = ? AND season != 0 AND status IN (' + ','.join( ['?'] * len(status_list)) + ')', [int(indexer_id)] + status_list) result = {} @@ -2492,7 +2492,7 @@ class Manage(MainHandler): if cur_season not in result: result[cur_season] = {} - result[cur_season][cur_episode] = cur_result['name'] + result[cur_season][cur_episode] = {'name': cur_result['name'], 'airdate_never': (True, False)[1000 < int(cur_result['airdate'])]} return json.dumps(result) @@ -2516,12 +2516,14 @@ class Manage(MainHandler): myDB = db.DBConnection() status_results = myDB.select( - 'SELECT show_name, tv_shows.indexer_id as indexer_id FROM tv_episodes, tv_shows WHERE tv_episodes.status IN (' + ','.join( + 'SELECT show_name, tv_shows.indexer_id as indexer_id, airdate FROM tv_episodes, tv_shows WHERE tv_episodes.status IN (' + ','.join( ['?'] * len( status_list)) + ') AND season != 0 AND tv_episodes.showid = tv_shows.indexer_id ORDER BY show_name', status_list) ep_counts = {} + ep_count = 0 + never_counts = {} show_names = {} sorted_show_ids = [] for cur_status_result in status_results: @@ -2530,6 +2532,11 @@ class Manage(MainHandler): ep_counts[cur_indexer_id] = 1 else: ep_counts[cur_indexer_id] += 1 + ep_count += 1 + if cur_indexer_id not in never_counts: + never_counts[cur_indexer_id] = 0 + if 1000 > int(cur_status_result['airdate']): + never_counts[cur_indexer_id] += 1 show_names[cur_indexer_id] = cur_status_result['show_name'] if cur_indexer_id not in sorted_show_ids: @@ -2537,11 +2544,12 @@ class Manage(MainHandler): t.show_names = show_names t.ep_counts = ep_counts + t.ep_count = ep_count + t.never_counts = never_counts t.sorted_show_ids = sorted_show_ids return t.respond() - def changeEpisodeStatuses(self, oldStatus, newStatus, *args, **kwargs): - + def changeEpisodeStatuses(self, oldStatus, newStatus, wantedStatus=sickbeard.common.UNKNOWN, *args, **kwargs): status_list = [int(oldStatus)] if status_list[0] == SNATCHED: status_list = Quality.SNATCHED + Quality.SNATCHED_PROPER @@ -2550,17 +2558,20 @@ class Manage(MainHandler): # make a list of all shows and their associated args for arg in kwargs: - indexer_id, what = arg.split('-') - # we don't care about unchecked checkboxes if kwargs[arg] != 'on': continue + indexer_id, what = arg.split('-') + if indexer_id not in to_change: to_change[indexer_id] = [] to_change[indexer_id].append(what) + if sickbeard.common.WANTED == int(wantedStatus): + newStatus = sickbeard.common.WANTED + myDB = db.DBConnection() for cur_indexer_id in to_change: