diff --git a/gui/slick/interfaces/default/home_browseShows.tmpl b/gui/slick/interfaces/default/home_browseShows.tmpl index a8215764..c5f01738 100644 --- a/gui/slick/interfaces/default/home_browseShows.tmpl +++ b/gui/slick/interfaces/default/home_browseShows.tmpl @@ -42,6 +42,62 @@ $(this).css('cursor', 'help'); $(this).qtip({ show: {solo:true}, + // Change qTip to manual hide when it contains many roles to scroll + hide: {event:(5 < $(this).closest('div.show-card').attr('data-nroles')) ? 'unfocus' : 'mouseleave'}, + events: { // Callback events + render: function(event, api) { + // Grab the tooltip element from the API + var tooltip = api.elements.tooltip + tooltip.bind('tooltipshow', function(event, api) { + var showcardEl = $(api.target).closest('div.show-card') + if ('1' === showcardEl.attr('data-ajax')) { // do a one time fetch + var qtipEl = $(this).find('.qtip-content'), + premiereEl = qtipEl.find('.premiere'), + genreEl = qtipEl.find('.genre'), + overviewEl = qtipEl.find('.overview'), + oldestEl = $('#oldest'), + newestEl = $('#newest'); + + // Set initial text + overviewEl.html('Fetching overview...'); + $.getJSON($.SickGear.Root + '/add-shows/tvm-get-showinfo', { + tvid_prodid: showcardEl.attr('data-id'), + oldest_dt: $('#oldest').attr('data-oldest-dt'), + newest_dt: $('#newest').attr('data-newest-dt'), + }, + function (data) { + if (undefined !== data.overview) { + showcardEl.attr('data-ajax', '0'); // mark one time fetch as completed + if (undefined !== data.oldest) { + oldestEl.attr('data-oldest-dt', data.oldest_dt) + oldestEl.html(data.oldest); + } else if (undefined !== data.newest) { + newestEl.attr('data-newest-dt', data.newest_dt) + newestEl.html(data.newest); + } + var premiere = ''; + if (data.str_premiered.length) { + showcardEl.attr('data-premiered', data.ord_premiered); + premiere = "First air" + (data.started_past ? 'ed' : 's') + ": " + data.str_premiered + ""; + } + if (data.genres) { + genreEl.css('display', 'block'); + genreEl.find('em').html(data.genres); + } + overviewEl.html(data.overview); + if (data.network.length) { + premiere += "On: " + data.network + ""; + } + premiereEl.html(premiere); + } else { + overviewEl.html('Failed to fetch TVmaze overview' ); + } + } + ) + } + }) + } + }, position: {viewport:$(window), my:'left center', adjust:{y: -10,x: 2 }}, style: {tip: {corner:true, method:'polygon'}, classes:'qtip-rounded qtip-bootstrap qtip-shadow ui-tooltip-sb'} }); @@ -264,7 +320,7 @@ $(function() { } }); - $('.service, .browse-image').each(addQTip); + $('.service, a.browse-image').each(addQTip); if (config.homeSearchFocus) { $('#search_show_name').focus(); @@ -309,9 +365,10 @@ $(function() {
@@ -356,7 +413,7 @@ $(function() { #end if #if $use_ratings and $use_votes - + #end if @@ -483,7 +540,7 @@ $(function() {

$browse_title

#if $kwargs and $kwargs.get('oldest')
- First aired from $kwargs['oldest'] until $kwargs['newest'] + First aired from $kwargs['oldest'] until $kwargs['newest']
#end if @@ -506,7 +563,7 @@ $(function() { #if 'returning' == $mode #set $overview = '%s: %s' % ( 'Season %s' % $this_show['episode_season'], - $this_show['episode_overview'] or $this_show['overview']) + $this_show[('episode_overview', 'overview')['No overview yet' == $this_show['episode_overview']]]) #else #set $overview = $this_show['overview'] #end if @@ -517,14 +574,13 @@ $(function() { #if $use_ratings: #set $data_rating = $try_float($this_show['rating']) #end if - -
+
#if $use_ratings or $use_votes

#if $use_ratings#$this_show['rating']#if $re.search(r'^\d+(\.\d+)?$', (str($this_show['rating'])))#%#end if##end if##if $use_votes#$this_show['votes'] $term_vote.lower()#end if#

#slurp# + #else +

 

#end if #if 'url_tvdb' in $this_show and $this_show['url_tvdb']
List[Tuple[str, int, str, int]] return [(ch.name.replace('"', "'"), r_t, RoleTypes.reverse[r_t], ch.episode_count) - for r_t in cur_show_info.cast or [] for ch in cur_show_info.cast[r_t]] + for r_t in cur_show_info.cast or [] for ch in cur_show_info.cast[r_t] if ch.name] + @staticmethod + def allow_browse_mru(mode_or_mru): + # Fix an issue where a default view mixed with a deriviative view that requires a param will break the default + # Disallows default views from using derivative mru's + return 'person' not in mode_or_mru def tmdb_default(self): method = getattr(self, sickgear.TMDB_MRU, None) - if not callable(method): + if not callable(method) or not self.allow_browse_mru(sickgear.TMDB_MRU): return self.tmdb_upcoming() return method() @@ -5354,7 +5390,7 @@ class AddShows(Home): def tmdb_person(self, person_tmdb_id=None, **kwargs): return self.browse_tmdb( - 'Person at TMDB', mode='get_person', p_id=person_tmdb_id, **kwargs) + 'Person at TMDB', mode='person', p_id=person_tmdb_id, **kwargs) def browse_tmdb(self, browse_title, **kwargs): @@ -5376,7 +5412,7 @@ class AddShows(Home): items = t.get_trending() elif 'trending_week' == mode: items = t.get_trending(time_window='week') - elif 'get_person' == mode: + elif 'person' == mode: items = [] p_item = t.get_person(get_show_credits=True, **kwargs) # type: TVInfoPerson if p_item: @@ -5390,8 +5426,6 @@ class AddShows(Home): else: dup[c.ti_show.id].cast[RoleTypes.ActorMain].extend(c.ti_show.cast[RoleTypes.ActorMain]) del dup - else: - p_item = None else: items = t.discover() @@ -5427,19 +5461,17 @@ class AddShows(Home): language = ((cur_show_info.language and 'jap' in cur_show_info.language.lower()) and 'jp' or 'en') filtered.append(dict( - p_ref=p_ref, - p_chars=self._make_char_person_list(cur_show_info), ord_premiered=ord_premiered, str_premiered=str_premiered, started_past=started_past, - episode_overview=helpers.xhtml_escape(cur_show_info.overview[:250:]).strip('*').strip(), + episode_overview=self.clean_overview(cur_show_info), episode_season=cur_show_info.season, - genres=', '.join(cur_show_info.genre_list) - or (cur_show_info.genre and (cur_show_info.genre.strip('|').replace('|', ', ')) or ''), + genres=(', '.join(cur_show_info.genre_list) + or (cur_show_info.genre and (cur_show_info.genre.strip('|').replace('|', ', ')) or '') + ).lower(), ids=cur_show_info.ids.__dict__, images=images, - overview=(helpers.xhtml_escape(cur_show_info.overview[:250:]).strip('*').strip() - or 'No overview yet'), + overview=self.clean_overview(cur_show_info), title=cur_show_info.seriesname, language=language, language_img=sickgear.MEMCACHE_FLAG_IMAGES.get(language, False), @@ -5447,15 +5479,23 @@ class AddShows(Home): country_img=sickgear.MEMCACHE_FLAG_IMAGES.get(cc.lower(), False), network=network_name, url_src_db=base_url % cur_show_info.id, - votes=cur_show_info.popularity or 0, + rating=0 < (cur_show_info.rating or 0) and + ('%.2f' % (cur_show_info.rating * 10)).replace('.00', '') or 0, + votes=('%.2f' % cur_show_info.popularity) or 0, )) + if p_ref: + filtered[-1].update(dict( + p_name=p_item.name, + p_ref=p_ref, + p_chars=self._make_char_person_list(cur_show_info) + )) except (BaseException, Exception): pass - kwargs.update(dict(oldest=oldest, newest=newest, use_ratings=False, use_filter=True, term_vote='Score')) + kwargs.update(dict(oldest=oldest, newest=newest, use_filter=True, term_vote='Score')) kwargs.update(dict(footnote=footnote, use_networks=use_networks)) - if mode: + if mode and self.allow_browse_mru(mode): func = 'tmdb_%s' % mode if callable(getattr(self, func, None)): sickgear.TMDB_MRU = func @@ -5470,7 +5510,7 @@ class AddShows(Home): def trakt_default(self): method = getattr(self, sickgear.TRAKT_MRU, None) - if not callable(method): + if not callable(method) or not self.allow_browse_mru(sickgear.TMDB_MRU): return self.trakt_trending() return method() @@ -5602,7 +5642,7 @@ class AddShows(Home): if not items: error_msg = 'No items in watchlist. Use the "Add to watchlist" button at the Trakt website' raise ValueError(error_msg) - elif 'get_person' == api_method: + elif 'person' == mode: items = [] p_item = t.get_person(get_show_credits=True, **kwargs) # type: TVInfoPerson if p_item: @@ -5613,8 +5653,6 @@ class AddShows(Home): dup[c.ti_show.id] = c.ti_show items.append(c.ti_show) del dup - else: - p_item = None else: items = t.get_trending() except TraktAuthException as e: @@ -5670,9 +5708,6 @@ class AddShows(Home): images = {} if not image else dict(poster=dict(thumb=image)) filtered.append(dict( - p_ref=p_ref, - p_item=p_item, - p_chars=self._make_char_person_list(cur_show_info), ord_premiered=ord_premiered, str_premiered=str_premiered, ord_returning=ord_returning, @@ -5680,14 +5715,13 @@ class AddShows(Home): started_past=started_past, # air time not yet available 16.11.2015 return_past=return_past, episode_number=episode_info.episodenumber, - episode_overview=helpers.xhtml_escape(episode_info.overview[:250:]).strip('*').strip(), + episode_overview=self.clean_overview(episode_info), episode_season=getattr(episode_info.season, 'number', 1), genres=(', '.join(['%s' % v for v in cur_show_info.genre_list])), ids=cur_show_info.ids.__dict__, images=images, network=network_name, - overview=(helpers.xhtml_escape(cur_show_info.overview[:250:]).strip('*').strip() - or 'No overview yet'), + overview=self.clean_overview(cur_show_info), rating=0 < (cur_show_info.rating or 0) and ('%.2f' % (cur_show_info.rating * 10)).replace('.00', '') or 0, title=(cur_show_info.seriesname or '').strip(), @@ -5699,7 +5733,14 @@ class AddShows(Home): url_tvdb=( '' if not (isinstance(cur_show_info.ids.tvdb, integer_types) and 0 < cur_show_info.ids.tvdb) else sickgear.TVInfoAPI(TVINFO_TVDB).config['show_url'] % cur_show_info.ids.tvdb), - votes=cur_show_info.vote_count or '0')) + votes=cur_show_info.vote_count or '0' + )) + if p_ref: + filtered[-1].update(dict( + p_name=p_item.name, + p_ref=p_ref, + p_chars=self._make_char_person_list(cur_show_info) + )) except (BaseException, Exception): pass @@ -5712,7 +5753,7 @@ class AddShows(Home): return self.browse_trakt( 'get_person', - 'Person on Trakt', + 'Person at Trakt', mode='person', footnote='Note; Expect default placeholder images in this list', p_id=person_trakt_id @@ -5735,14 +5776,11 @@ class AddShows(Home): error_msg = 'No items in watchlist. Use the "Add to watchlist" button at the Trakt website' return self.browse_shows(browse_type, browse_title, filtered, error_msg=error_msg, show_header=1, **kwargs) - if 'get_person' == api_method and filtered: - browse_title = f'{getattr(filtered[0]["p_item"], "name", "")} (Person) on Trakt' - kwargs.update(dict(oldest=oldest, newest=newest, error_msg=error_msg, use_networks=use_networks)) if not any(m in mode for m in ('recommended', 'watchlist', 'person')): mode = mode.split('-') - if mode: + if mode and self.allow_browse_mru(mode): func = 'trakt_%s' % mode[0] if callable(getattr(self, func, None)): param = '' if 1 == len(mode) or mode[1] not in ['year', 'month', 'week', 'all'] else \ @@ -5951,7 +5989,7 @@ class AddShows(Home): network=network or None, ids=ids, images='' if not img_uri else images, - overview='No overview yet' if not overview else helpers.xhtml_escape(overview[:250:]), + overview=self.clean_overview(overview), rating=None, title=title, url_src_db='https://www.pogdesign.co.uk/%s' % url_path.strip('/'), @@ -5979,7 +6017,7 @@ class AddShows(Home): def tvm_default(self): method = getattr(self, sickgear.TVM_MRU, None) - if not callable(method): + if not callable(method) or not self.allow_browse_mru(sickgear.TMDB_MRU): return self.tvm_premieres() return method() @@ -5993,7 +6031,52 @@ class AddShows(Home): def tvm_person(self, person_tvm_id=None, **kwargs): return self.browse_tvm( - 'Person at TVmaze', mode='get_person', p_id=person_tvm_id, **kwargs) + 'Person at TVmaze', mode='person', p_id=person_tvm_id, **kwargs) + + @staticmethod + def clean_overview(info=None): + # type (AnyStr, TVInfoShow) -> AnyStr + text = info if isinstance(info, str) else info.overview + if text: + result = helpers.xhtml_escape(re.sub(r'[\r\n]+', ' ', text[:250:])).strip('*').strip() + result = re.sub(r'([!?.])(?=\w)', r'\1 ', result) + result = re.sub(r'([,.!][^,.!]*?)$', '...', result) + return result.replace('.....', '...') + return 'No overview yet' + + def tvm_get_showinfo(self, tvid_prodid=None, oldest_dt=9999999, newest_dt=0): + result = {} + if 'tvmaze' in tvid_prodid: + tvid = TVINFO_TVMAZE + tvinfo_config = sickgear.TVInfoAPI(tvid).api_params.copy() + t = sickgear.TVInfoAPI(tvid).setup(**tvinfo_config) # type: Union[TvmazeIndexer, TVInfoBase] + show_info = t.get_show(int(tvid_prodid.replace('tvmaze:','')), load_episodes=False) + + oldest_dt, newest_dt = int(oldest_dt), int(newest_dt) + ord_premiered, str_premiered, started_past, old_dt, new_dt, oldest, newest, \ + ok_returning, ord_returning, str_returning, return_past \ + = self.sanitise_dates(show_info.firstaired, oldest_dt, newest_dt, None, None) + result = dict( + ord_premiered=ord_premiered, + str_premiered=str_premiered, + #ord_returning=ord_returning, + #str_returning=str_returning, + started_past=started_past, + #return_past=return_past, + genres=((show_info.genre or '') + or ', '.join(show_info.genre_list) + or ', '.join(show_info.show_type) or '').strip('|').replace('|', ', ').lower(), + overview=self.clean_overview(show_info), + network=show_info.network or ', '.join(show_info.networks) or '', + ) + if old_dt < oldest_dt: + result['oldest_dt'] = old_dt + result['oldest'] = oldest + elif new_dt > newest_dt: + result['newest_dt'] = old_dt + result['newest'] = newest, + + return json_dumps(result) def browse_tvm(self, browse_title, **kwargs): @@ -6009,7 +6092,7 @@ class AddShows(Home): t = sickgear.TVInfoAPI(tvid).setup(**tvinfo_config) # type: Union[TvmazeIndexer, TVInfoBase] if 'premieres' == mode: items = t.get_premieres() - elif 'get_person' == mode: + elif 'person' == mode: items = [] p_item = t.get_person(get_show_credits=True, **kwargs) # type: TVInfoPerson if p_item: @@ -6024,17 +6107,16 @@ class AddShows(Home): dup[c.ti_show.id].cast[RoleTypes.ActorMain].extend(c.ti_show.cast[RoleTypes.ActorMain]) dup[c.ti_show.id].cast[RoleTypes.ActorGuest].extend(c.ti_show.cast[RoleTypes.ActorGuest]) del dup - else: - p_item = None else: items = t.get_returning() # handle switching between returning and premieres sickgear.BROWSELIST_MRU.setdefault(browse_type, dict()) - showfilter = ('by_returning', 'by_premiered')['premieres' == mode] - saved_showsort = sickgear.BROWSELIST_MRU[browse_type].get('tvm_%s' % mode) or '*,asc' - showsort = saved_showsort + (',%s' % showfilter, '')[3 == len(saved_showsort.split(','))] - sickgear.BROWSELIST_MRU[browse_type].update(dict(showfilter=showfilter, showsort=showsort)) + if mode in ('premieres', 'returning'): + showfilter = ('by_returning', 'by_premiered')['premieres' == mode] + saved_showsort = sickgear.BROWSELIST_MRU[browse_type].get('tvm_%s' % mode) or '*,asc' + showsort = saved_showsort + (f',{showfilter}', '')[3 == len(saved_showsort.split(','))] + sickgear.BROWSELIST_MRU[browse_type].update(dict(showfilter=showfilter, showsort=showsort)) oldest, newest, oldest_dt, newest_dt, dedupe = None, None, 9999999, 0, [] use_networks = False @@ -6057,7 +6139,7 @@ class AddShows(Home): if 'returning' == mode and not ok_returning: continue - image = self._make_cache_image_url(tvid, cur_show_info, use_source_id='get_person' == mode) + image = self._make_cache_image_url(tvid, cur_show_info, use_source_id='person' == mode) images = {} if not image else dict(poster=dict(thumb=image)) network_name = cur_show_info.network @@ -6070,9 +6152,11 @@ class AddShows(Home): language = (('jap' in (cur_show_info.language or '').lower()) and 'jp' or 'en') + overview = self.clean_overview(cur_show_info) + overview_ajax = ("No overview yet" == overview + and p_ref and not bool(cur_show_info.cast[RoleTypes.ActorMain])) + filtered.append(dict( - p_ref=p_ref, - p_chars=self._make_char_person_list(cur_show_info), ord_premiered=ord_premiered, str_premiered=str_premiered, ord_returning=ord_returning, @@ -6080,14 +6164,15 @@ class AddShows(Home): started_past=started_past, return_past=return_past, episode_number=episode_info.episodenumber or '', - episode_overview=helpers.xhtml_escape(episode_info.overview[:250:]).strip(), + episode_overview=self.clean_overview(episode_info), episode_season=getattr(episode_info.season, 'number', episode_info.seasonnumber), - genres=((cur_show_info.genre or '').strip('|').replace('|', ', ') - or ', '.join(cur_show_info.show_type) or ''), + genres=((cur_show_info.genre or '') + or ', '.join(cur_show_info.genre_list) + or ', '.join(cur_show_info.show_type) or '').strip('|').replace('|', ', ').lower(), ids=cur_show_info.ids.__dict__, images=images, - overview=(helpers.xhtml_escape(cur_show_info.overview[:250:]).strip('*').strip() - or 'No overview yet'), + overview_ajax=(0, 1)[overview_ajax], + overview=overview, rating=cur_show_info.rating or cur_show_info.popularity or 0, title=cur_show_info.seriesname, language=language, @@ -6097,13 +6182,23 @@ class AddShows(Home): network=network_name, url_src_db=base_url % cur_show_info.id, )) + if p_ref: + filtered[-1].update(dict( + p_name=p_item.name or None, + p_ref=p_ref, + p_chars=self._make_char_person_list(cur_show_info) + )) except (BaseException, Exception): pass - kwargs.update(dict(oldest=oldest, newest=newest)) - kwargs.update(dict(footnote=footnote, use_votes=False, use_networks=use_networks)) + kwargs.update(dict(oldest=oldest, newest=newest, oldest_dt=oldest_dt, newest_dt=newest_dt)) - if mode: + params = dict(footnote=footnote, use_votes=False, use_networks=use_networks) + if p_ref: + params.update(dict(use_ratings=False)) + kwargs.update(params) + + if mode and self.allow_browse_mru(mode): func = 'tvm_%s' % mode if callable(getattr(self, func, None)): sickgear.TVM_MRU = func @@ -6200,7 +6295,8 @@ class AddShows(Home): t = PageTemplate(web_handler=self, file='home_browseShows.tmpl') t.submenu = self.home_menu() t.browse_type = browse_type - t.browse_title = browse_title + t.browse_title = browse_title if ('person' != kwargs.get('mode') or not shows) \ + else f'{shows[0].get("p_name", "")} (Person) on {browse_type}' t.p_ref = (0 < len(shows) and shows[0].get('p_ref')) or None t.saved_showfilter = sickgear.BROWSELIST_MRU.get(browse_type, {}).get('showfilter', '') t.saved_showsort = sickgear.BROWSELIST_MRU.get(browse_type, {}).get('showsort', '*,asc,by_order')