From 41e1fee4b467da8bc0f3e4a28b7311f54164fa8e Mon Sep 17 00:00:00 2001 From: Tim Date: Wed, 26 Aug 2015 23:53:42 +0200 Subject: [PATCH 01/54] Match user refreshes on user_id not username as it causes issues if usernames change. --- plexpy/plextv.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/plexpy/plextv.py b/plexpy/plextv.py index b952004b..cac4009a 100644 --- a/plexpy/plextv.py +++ b/plexpy/plextv.py @@ -27,9 +27,8 @@ def refresh_users(): if len(result) > 0: for item in result: - control_value_dict = {"username": item['username']} - new_value_dict = {"user_id": item['user_id'], - "username": item['username'], + control_value_dict = {"user_id": item['user_id']} + new_value_dict = {"username": item['username'], "thumb": item['thumb'], "email": item['email'], "is_home_user": item['is_home_user'], From ff0ed1abe4c29721093950def61a685489372f38 Mon Sep 17 00:00:00 2001 From: Tim Date: Thu, 27 Aug 2015 00:35:29 +0200 Subject: [PATCH 02/54] Fix info pages bugging out if non-existent rating key is passed. --- plexpy/webserve.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/plexpy/webserve.py b/plexpy/webserve.py index 93092587..54fc8d9f 100644 --- a/plexpy/webserve.py +++ b/plexpy/webserve.py @@ -747,16 +747,19 @@ class WebInterface(object): @cherrypy.expose def info(self, item_id=None, source=None, **kwargs): + metadata = None if source == 'history': data_factory = datafactory.DataFactory() - result = data_factory.get_metadata_details(row_id=item_id) + metadata = data_factory.get_metadata_details(row_id=item_id) else: pms_connect = pmsconnect.PmsConnect() - result = pms_connect.get_metadata_details(rating_key=item_id)['metadata'] + result = pms_connect.get_metadata_details(rating_key=item_id) + if result: + metadata = result['metadata'] - if result: - return serve_template(templatename="info.html", data=result, title="Info") + if metadata: + return serve_template(templatename="info.html", data=metadata, title="Info") else: logger.warn('Unable to retrieve data.') return serve_template(templatename="info.html", data=None, title="Info") From 8ae2f718f4e90d49ef1eab739071a6a0c266e61c Mon Sep 17 00:00:00 2001 From: Jonathan Wong Date: Mon, 24 Aug 2015 12:13:38 -0700 Subject: [PATCH 03/54] Initial implementation of homepage top lists * Top lists for all stats, default max 5 items --- data/interfaces/default/css/plexpy.css | 291 +++++++++++----- data/interfaces/default/home_stats.html | 420 ++++++++++++++++++++---- data/interfaces/default/index.html | 6 +- plexpy/config.py | 3 +- plexpy/datafactory.py | 17 +- plexpy/webserve.py | 10 +- 6 files changed, 585 insertions(+), 162 deletions(-) diff --git a/data/interfaces/default/css/plexpy.css b/data/interfaces/default/css/plexpy.css index 587bee7b..38ae1dde 100644 --- a/data/interfaces/default/css/plexpy.css +++ b/data/interfaces/default/css/plexpy.css @@ -1350,40 +1350,27 @@ a .season-episodes-card-overlay:hover { list-style: none; margin: 0; } -.home-platforms-instance-poster { - margin-left: 0px; +.home-platforms-instance { + background-color: #282828; + position: relative; + float: left; + width: 300px; + padding: 10px; + margin-right: 20px; + margin-bottom: 20px; + webkit-box-sizing: content-box; + box-sizing: content-box; + z-index: 0; } -.home-platforms-instance-poster .poster-face { +.home-platforms-instance li { + position: relative; +} +.home-platforms-instance-info { + float: left; + position: relative; + padding-left: 80px; + width: 100%; height: 120px; - width: 80px; -} -.home-platforms-instance-box { - background-size: contain; - position: absolute; - left: 10px; - bottom: 35px; - height: 80px; - width: 80px; - -webkit-border-radius: 3px; - -moz-border-radius: 3px; - border-radius: 3px; - webkit-box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); - -moz-box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); - box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); -} -.home-platforms-instance-oval { - background-size: contain; - position: absolute; - left: 10px; - bottom: 35px; - height: 80px; - width: 80px; - -webkit-border-radius: 50%; - -moz-border-radius: 50%; - border-radius: 50%; - webkit-box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); - -moz-box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); - box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); } .home-platforms-instance-name { float: left; @@ -1395,64 +1382,214 @@ a .season-episodes-card-overlay:hover { line-height: 15px; font-weight: bold; width: 100%; - padding: 10px 0 0 10px; + padding: 0 0 0 20px; +} +.home-platforms-instance-name h5 { + font-size: 16px; + margin: 20px 0 2px 0; } .home-platforms-instance-playcount { float: left; - width: 180px; -} -.home-platforms-instance-mediainfo { - float: left; - background-color: #282828; - position: absolute; - bottom: 0; - left: 0; - padding-left: 170px; - width: 100%; - height: 175px; -} -.home-platforms-instance-media { position: relative; - float: left; - width: 375px; - height: 225px; - padding-bottom: 10px; - margin-right: 25px; - margin-bottom: 25px; - webkit-box-sizing: content-box; - box-sizing: content-box; + padding: 6px 0 0 20px; + width: 100%; } -.home-platforms-instance-info { - float: left; - background-color: #282828; - position: absolute; +.home-platforms-instance-playcount h3 { + font-size: 30px; + font-weight: bold; + color: #F9AA03; + line-height: 22px; + position: relative; top: 5px; - left: 0; - padding-left: 100px; - width: 100%; - height: 120px; -} -.home-platforms-instance { - background-color: #282828; - position: relative; + margin: 0 5px 0 0; float: left; - width: 300px; +} +.home-platforms-instance-playcount p { + color: #aaa; + font-size: 12px; + float: left; + position: relative; + top: 14px; + left: 0px; + margin-right: 5px; +} +.home-platforms-instance-poster { + margin-left: 0px; + position: absolute; +} +.home-platforms-instance-poster .home-platforms-poster-face { + background-position: center; + background-size: cover; height: 120px; - padding: 10px; - margin-right: 20px; - margin-bottom: 20px; - webkit-box-sizing: content-box; - box-sizing: content-box; + width: 80px; } -a .home-platforms-instance-oval:hover { +.home-platforms-instance-box { + background-position: center; + background-size: cover; + margin: 20px 0 0 0px; + height: 80px; + width: 80px; + position: relative; + -webkit-border-radius: 3px; + -moz-border-radius: 3px; + border-radius: 3px; + webkit-box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); + -moz-box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); + box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); + overflow: hidden; +} +.home-platforms-instance-oval { + background-position: center; + background-size: cover; + margin: 20px 0 0 0px; + height: 80px; + width: 80px; + position: relative; + -webkit-border-radius: 50%; + -moz-border-radius: 50%; + border-radius: 50%; + webkit-box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); + -moz-box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); + box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); + overflow: hidden; +} +.home-platforms-instance-list { + float: left; + position: relative; + width: 100%; + padding: 0 10px 20px 10px; +} +.home-platforms-instance-list li { + margin-top: 25px; + position: relative; + height: 60px; +} +.home-platforms-instance-list-number { + background-color: #e9a049; + float: left; + position: absolute; + top: -10px; + left: 10px; + height: 20px; + width: 20px; + display: block; + text-align: center; + padding-top: 1px; + -webkit-border-radius: 50%; + -moz-border-radius: 50%; + border-radius: 50%; +} +.home-platforms-instance-list-number h4 { + color: #000; + font-size: 15px; + font-weight: bold; + margin: 0; +} +.home-platforms-instance-list-info { + float: left; + position: relative; + padding-left: 75px; + width: 100%; + height: 60px; +} +.home-platforms-instance-list-name { + float: left; + color: #fff; + text-overflow: ellipsis; + overflow: hidden; + position: relative; + font-size: 13px; + line-height: 15px; + font-weight: bold; + width: 100%; + padding: 2px 0 0 10px; +} +.home-platforms-instance-list-name h5 { + margin: 5px 0px; +} +.home-platforms-instance-list-playcount { + float: left; + position: relative; + padding: 4px 0 0 10px; + width: 100%; +} +.home-platforms-instance-list-playcount h3 { + font-size: 20px; + font-weight: bold; + color: #F9AA03; + line-height: 22px; + position: relative; + margin: 0 5px 0 0; + float: left; +} +.home-platforms-instance-list-playcount p { + color: #aaa; + font-size: 12px; + float: left; + position: relative; + top: 5px; + left: 0px; + margin-right: 5px; +} +.home-platforms-instance-list-poster { + position: absolute; + left: 20px; +} +.home-platforms-instance-list-poster .home-platforms-list-poster-face { + background-position: center; + background-size: cover; + height: 60px; + width: 40px; +} +.home-platforms-instance-list-box { + background-position: center; + background-size: cover; + margin: 10px 0 0 20px; + height: 40px; + width: 40px; + position: relative; + -webkit-border-radius: 3px; + -moz-border-radius: 3px; + border-radius: 3px; + webkit-box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); + -moz-box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); + box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); + overflow: hidden; +} +.home-platforms-instance-list-oval { + background-position: center; + background-size: cover; + margin: 10px 0 0 20px; + height: 40px; + width: 40px; + position: relative; + -webkit-border-radius: 50%; + -moz-border-radius: 50%; + border-radius: 50%; + webkit-box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); + -moz-box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); + box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); + overflow: hidden; +} +a .home-platforms-instance-box:hover, +a .home-platforms-instance-oval:hover, +a .home-platforms-instance-list-box:hover, +a .home-platforms-instance-list-oval:hover, +.home-platforms-poster-face:hover, +.home-platforms-list-poster-face:hover + { webkit-box-shadow: inset 0 0 0 2px #e9a049; -moz-box-shadow: inset 0 0 0 2px #e9a049; box-shadow: inset 0 0 0 2px #e9a049; } -a .home-platforms-instance-box:hover { - webkit-box-shadow: inset 0 0 0 2px #e9a049; - -moz-box-shadow: inset 0 0 0 2px #e9a049; - box-shadow: inset 0 0 0 2px #e9a049; +.home-platforms-instance .slider { + background-color: #282828; + width: 320px; + display: none; + position: absolute; + top: 130px; + left: -10px; + z-index: 2; } .history-table-title { text-overflow: ellipsis; diff --git a/data/interfaces/default/home_stats.html b/data/interfaces/default/home_stats.html index 2a05ca34..c6a99149 100644 --- a/data/interfaces/default/home_stats.html +++ b/data/interfaces/default/home_stats.html @@ -48,7 +48,7 @@ DOCUMENTATION :: END def hd(minutes): if int(minutes) > 60: hours = int(helpers.cast_to_float(minutes) / 60) - minutes = int(helpers.cast_to_float(minutes) % hours) + minutes = int(helpers.cast_to_float(minutes) % 60 ) if minutes > 0: return "

" + str(hours) + "

hrs

" + str(minutes) + "

mins

" else: @@ -58,199 +58,479 @@ DOCUMENTATION :: END %> % if data: -% if data[0]['rows'] or data[2]['rows']: +% if data[0]['rows'] or data[1]['rows'] or data[2]['rows'] or data[3]['rows'] or data[4]['rows'] or data[5]['rows']: + % else:
No stats for selected period.

% endif diff --git a/data/interfaces/default/index.html b/data/interfaces/default/index.html index 9e221a9d..8a31845c 100644 --- a/data/interfaces/default/index.html +++ b/data/interfaces/default/index.html @@ -45,12 +45,12 @@ diff --git a/plexpy/config.py b/plexpy/config.py index 5fe59e29..51145685 100644 --- a/plexpy/config.py +++ b/plexpy/config.py @@ -1,4 +1,4 @@ -import plexpy.logger +import plexpy.logger import itertools import os import re @@ -84,6 +84,7 @@ _CONFIG_DEFINITIONS = { 'GROWL_ON_WATCHED': (int, 'Growl', 0), 'HOME_STATS_LENGTH': (int, 'General', 30), 'HOME_STATS_TYPE': (int, 'General', 0), + 'HOME_STATS_COUNT': (int, 'General', 5), 'HTTPS_CERT': (str, 'General', ''), 'HTTPS_KEY': (str, 'General', ''), 'HTTP_HOST': (str, 'General', '0.0.0.0'), diff --git a/plexpy/datafactory.py b/plexpy/datafactory.py index b60c654b..5257f5ae 100644 --- a/plexpy/datafactory.py +++ b/plexpy/datafactory.py @@ -129,7 +129,7 @@ class DataFactory(object): return dict - def get_home_stats(self, time_range='30', stat_type='0'): + def get_home_stats(self, time_range='30', stat_type='0', stat_count='5'): monitor_db = database.MonitorDatabase() if not time_range.isdigit(): @@ -137,6 +137,9 @@ class DataFactory(object): sort_type = 'total_plays' if stat_type == '0' else 'total_duration' + if not time_range.isdigit(): + stat_count = '5' + # This actually determines the output order in the home page stats_queries = ["top_tv", "popular_tv", "top_movies", "popular_movies", "top_users", "top_platforms"] home_stats = [] @@ -161,7 +164,7 @@ class DataFactory(object): '>= datetime("now", "-%s days", "localtime") ' \ 'AND session_history_metadata.media_type = "episode" ' \ 'GROUP BY session_history_metadata.grandparent_title ' \ - 'ORDER BY %s DESC LIMIT 10' % (time_range, sort_type) + 'ORDER BY %s DESC LIMIT %s' % (time_range, sort_type, stat_count) result = monitor_db.select(query) except: logger.warn("Unable to execute database query.") @@ -207,7 +210,7 @@ class DataFactory(object): '>= datetime("now", "-%s days", "localtime") ' \ 'AND session_history_metadata.media_type = "movie" ' \ 'GROUP BY session_history_metadata.full_title ' \ - 'ORDER BY %s DESC LIMIT 10' % (time_range, sort_type) + 'ORDER BY %s DESC LIMIT %s' % (time_range, sort_type, stat_count) result = monitor_db.select(query) except: logger.warn("Unable to execute database query.") @@ -251,7 +254,7 @@ class DataFactory(object): 'AND session_history_metadata.media_type = "episode" ' \ 'GROUP BY session_history_metadata.grandparent_title ' \ 'ORDER BY users_watched DESC, total_plays DESC ' \ - 'LIMIT 10' % time_range + 'LIMIT %s' % (time_range, stat_count) result = monitor_db.select(query) except: logger.warn("Unable to execute database query.") @@ -293,7 +296,7 @@ class DataFactory(object): 'AND session_history_metadata.media_type = "movie" ' \ 'GROUP BY session_history_metadata.full_title ' \ 'ORDER BY users_watched DESC, total_plays DESC ' \ - 'LIMIT 10' % time_range + 'LIMIT %s' % (time_range, stat_count) result = monitor_db.select(query) except: logger.warn("Unable to execute database query.") @@ -338,7 +341,7 @@ class DataFactory(object): 'WHERE datetime(session_history.stopped, "unixepoch", "localtime") >= ' \ 'datetime("now", "-%s days", "localtime") '\ 'GROUP BY session_history.user_id ' \ - 'ORDER BY %s DESC LIMIT 10' % (time_range, sort_type) + 'ORDER BY %s DESC LIMIT %s' % (time_range, sort_type, stat_count) result = monitor_db.select(query) except: logger.warn("Unable to execute database query.") @@ -386,7 +389,7 @@ class DataFactory(object): 'WHERE datetime(session_history.stopped, "unixepoch", "localtime") ' \ '>= datetime("now", "-%s days", "localtime") ' \ 'GROUP BY session_history.platform ' \ - 'ORDER BY total_plays DESC' % time_range + 'ORDER BY total_plays DESC LIMIT %s' % (time_range, stat_count) result = monitor_db.select(query) except: logger.warn("Unable to execute database query.") diff --git a/plexpy/webserve.py b/plexpy/webserve.py index 54fc8d9f..3c0bf770 100644 --- a/plexpy/webserve.py +++ b/plexpy/webserve.py @@ -1,4 +1,4 @@ -# This file is part of PlexPy. +# This file is part of PlexPy. # # PlexPy is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -66,7 +66,8 @@ class WebInterface(object): def home(self): config = { "home_stats_length": plexpy.CONFIG.HOME_STATS_LENGTH, - "home_stats_type": plexpy.CONFIG.HOME_STATS_TYPE + "home_stats_type": plexpy.CONFIG.HOME_STATS_TYPE, + "home_stats_count": plexpy.CONFIG.HOME_STATS_COUNT } return serve_template(templatename="index.html", title="Home", config=config) @@ -119,9 +120,9 @@ class WebInterface(object): return json.dumps(formats) @cherrypy.expose - def home_stats(self, time_range='30', stat_type='0', **kwargs): + def home_stats(self, time_range='30', stat_type='0', stat_count='5', **kwargs): data_factory = datafactory.DataFactory() - stats_data = data_factory.get_home_stats(time_range=time_range, stat_type=stat_type) + stats_data = data_factory.get_home_stats(time_range=time_range, stat_type=stat_type, stat_count=stat_count) return serve_template(templatename="home_stats.html", title="Stats", data=stats_data) @@ -453,6 +454,7 @@ class WebInterface(object): "notify_on_watched_body_text": plexpy.CONFIG.NOTIFY_ON_WATCHED_BODY_TEXT, "home_stats_length": plexpy.CONFIG.HOME_STATS_LENGTH, "home_stats_type": checked(plexpy.CONFIG.HOME_STATS_TYPE), + "home_stats_count": checked(plexpy.CONFIG.HOME_STATS_COUNT), "buffer_threshold": plexpy.CONFIG.BUFFER_THRESHOLD, "buffer_wait": plexpy.CONFIG.BUFFER_WAIT } From 58efd299ccba497d21112b69ac1193bbfc033268 Mon Sep 17 00:00:00 2001 From: Jonathan Wong Date: Mon, 24 Aug 2015 15:01:39 -0700 Subject: [PATCH 04/54] Add setting to change the number of items in the top lists --- data/interfaces/default/settings.html | 9 +++++++++ plexpy/webserve.py | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/data/interfaces/default/settings.html b/data/interfaces/default/settings.html index 3145c4ae..16abd1e1 100644 --- a/data/interfaces/default/settings.html +++ b/data/interfaces/default/settings.html @@ -96,6 +96,15 @@ available_notification_agents = notifiers.available_notification_agents()

Specify the number of days for the statistics on the home page. Default is 30 days.

+
+ +
+
+ +
+
+

Specify the number of items to show in the top lists for the statistics on the home page. Max is 10 items.

+
@@ -430,8 +439,9 @@ available_notification_agents = notifiers.available_notification_agents()
- +
+

Set the progress percentage of when a watched notification should be triggered. Minimum 50, Maximum 95.

@@ -561,7 +571,7 @@ available_notification_agents = notifiers.available_notification_agents()

Notification Agents

- Toggle the desired notification options by clicking the bolt icon and configure it by selecting the settings icon to the right. + Toggle the desired notification options by clicking the bell icon and configure it by clicking the settings icon to the right.


    @@ -569,9 +579,9 @@ available_notification_agents = notifiers.available_notification_agents()
  • % if agent['on_play'] or agent['on_stop'] or agent['on_pause'] or agent['on_resume'] or agent['on_buffer'] or agent['on_watched']: - + % else: - + % endif ${agent['name']} % if agent['has_config']: From 3fc573bd9078a1cb4c2b25cf2d98010e6206df7f Mon Sep 17 00:00:00 2001 From: Jonathan Wong Date: Mon, 24 Aug 2015 23:43:37 -0700 Subject: [PATCH 07/54] Remove console.log and more css cleanup --- data/interfaces/default/css/plexpy.css | 22 +++++++--------------- data/interfaces/default/home_stats.html | 1 - 2 files changed, 7 insertions(+), 16 deletions(-) diff --git a/data/interfaces/default/css/plexpy.css b/data/interfaces/default/css/plexpy.css index 57153fd2..c2773235 100644 --- a/data/interfaces/default/css/plexpy.css +++ b/data/interfaces/default/css/plexpy.css @@ -449,11 +449,6 @@ input[type="color"], box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); overflow: hidden; } -a .poster-face:hover { - webkit-box-shadow: inset 0 0 0 2px #e9a049; - -moz-box-shadow: inset 0 0 0 2px #e9a049; - box-shadow: inset 0 0 0 2px #e9a049; -} .cover-face { background-position: center; background-size: cover; @@ -465,16 +460,6 @@ a .poster-face:hover { -moz-box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); } -a .cover-face:hover { - webkit-box-shadow: inset 0 0 0 2px #e9a049; - -moz-box-shadow: inset 0 0 0 2px #e9a049; - box-shadow: inset 0 0 0 2px #e9a049; -} -a .users-poster-face:hover { - webkit-box-shadow: inset 0 0 0 2px #e9a049; - -moz-box-shadow: inset 0 0 0 2px #e9a049; - box-shadow: inset 0 0 0 2px #e9a049; -} .users-poster-face { overflow: hidden; float: left; @@ -488,6 +473,13 @@ a .users-poster-face:hover { -moz-box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); } +a .poster-face:hover, +a .cover-face:hover, +a .users-poster-face:hover { + webkit-box-shadow: inset 0 0 0 2px #e9a049; + -moz-box-shadow: inset 0 0 0 2px #e9a049; + box-shadow: inset 0 0 0 2px #e9a049; +} .users-poster-face img { bottom: 0; overflow: hidden; diff --git a/data/interfaces/default/home_stats.html b/data/interfaces/default/home_stats.html index f2d1c078..f8b6c27f 100644 --- a/data/interfaces/default/home_stats.html +++ b/data/interfaces/default/home_stats.html @@ -532,7 +532,6 @@ DOCUMENTATION :: END $('.home-platforms-instance-list-chevron i.fa').on('click', function() { inProgress = true; - console.log($(this)); instanceBoxChevron = $(this); instanceBox = $(this).parents('.home-platforms-instance'); instanceBoxSlider = instanceBox.find('.slider'); From 2193d06363c87a2ac1d9580251323a031b3da99d Mon Sep 17 00:00:00 2001 From: Jonathan Wong Date: Wed, 26 Aug 2015 16:03:16 -0700 Subject: [PATCH 08/54] Only toggle top lists on click Fixed borders on the slider --- data/interfaces/default/css/plexpy.css | 23 ++++++++------ data/interfaces/default/home_stats.html | 42 +++++++------------------ 2 files changed, 25 insertions(+), 40 deletions(-) diff --git a/data/interfaces/default/css/plexpy.css b/data/interfaces/default/css/plexpy.css index c2773235..ac05a5e2 100644 --- a/data/interfaces/default/css/plexpy.css +++ b/data/interfaces/default/css/plexpy.css @@ -1574,13 +1574,6 @@ a .season-episodes-card-overlay:hover { box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); overflow: hidden; } -.home-platforms-instance:hover .home-platforms-instance-list-chevron i.fa-chevron-down{ - color: #eb8600; - -webkit-transform: rotate(180deg); - -ms-transform: rotate(180deg); - -o-transform: rotate(180deg); - transform: rotate(180deg); -} .home-platforms-instance-list-chevron { position: absolute; top: 100px; @@ -1594,6 +1587,16 @@ a .season-episodes-card-overlay:hover { -o-transition: all 0.3s ease; transition: all 0.3s ease; } +.home-platforms-instance-list-chevron i:hover { + color: #eb8600; +} +.home-platforms-instance-list-chevron.active i.fa-chevron-down{ + color: #eb8600; + -webkit-transform: rotate(180deg); + -ms-transform: rotate(180deg); + -o-transform: rotate(180deg); + transform: rotate(180deg); +} a .home-platforms-instance-box:hover, a .home-platforms-instance-oval:hover, a .home-platforms-instance-list-box:hover, @@ -1612,9 +1615,9 @@ a .home-platforms-instance-list-oval:hover, position: absolute; top: 129px; left: -10px; - webkit-box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); - -moz-box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); - box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); + border-right: 1px solid rgba(255,255,255,.1); + border-bottom: 1px solid rgba(255,255,255,.1); + border-left: 1px solid rgba(255,255,255,.1); z-index: 1; } .history-table-title { diff --git a/data/interfaces/default/home_stats.html b/data/interfaces/default/home_stats.html index f8b6c27f..4c5f498a 100644 --- a/data/interfaces/default/home_stats.html +++ b/data/interfaces/default/home_stats.html @@ -528,36 +528,18 @@ DOCUMENTATION :: END % endfor
% else:
No stats for selected period.

From d627e0efdbc060b7fb68082c167d73a0e4f37afd Mon Sep 17 00:00:00 2001 From: Jonathan Wong Date: Wed, 26 Aug 2015 16:23:54 -0700 Subject: [PATCH 09/54] Create body container so scrollbar is beneath navbar --- data/interfaces/default/base.html | 8 +++++--- data/interfaces/default/css/plexpy.css | 27 +++++++++++++++++++++++++- 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/data/interfaces/default/base.html b/data/interfaces/default/base.html index 3bffc64a..f0918641 100644 --- a/data/interfaces/default/base.html +++ b/data/interfaces/default/base.html @@ -1,4 +1,4 @@ -<% +<% import plexpy from plexpy import version %> @@ -52,7 +52,7 @@ from plexpy import version - PlexPy + PlexPy ${next.headerIncludes()} -${next.body()} +
+ ${next.body()} +
diff --git a/data/interfaces/default/css/plexpy.css b/data/interfaces/default/css/plexpy.css index 587bee7b..f7936e13 100644 --- a/data/interfaces/default/css/plexpy.css +++ b/data/interfaces/default/css/plexpy.css @@ -39,6 +39,9 @@ img { } .navbar { background: #000; + -webkit-box-shadow: 0 0 0 3px rgba(0,0,0,.2); + -moz-box-shadow: 0 0 0 3px rgba(0,0,0,.2); + box-shadow: 0 0 0 3px rgba(0,0,0,.2); } .navbar-brand { padding: 5px 5px; @@ -1824,7 +1827,29 @@ a .home-platforms-instance-box:hover { position: relative; margin-right: 3px; } - #updatebar a:hover { color: #F9AA03; +} +.body-container { + position: absolute; + top: 50px; + right: 0; + bottom: 0; + left: 0; + overflow-x: hidden; + overflow-y: auto; + +} +::-webkit-scrollbar { + width: 15px; +} +::-webkit-scrollbar-track { + background-color: rgba(0,0,0,.2); +} +::-webkit-scrollbar-thumb { + min-height: 50px; + background-color: rgba(255,255,255,.15); + border: 3px solid transparent; + border-radius: 8px; + background-clip: padding-box; } \ No newline at end of file From 1e57e952db24a925f7a2175d34601f79c3e9e34f Mon Sep 17 00:00:00 2001 From: Jonathan Wong Date: Wed, 26 Aug 2015 17:01:17 -0700 Subject: [PATCH 10/54] Overhaul of info pages * Updated style to match Plex/Web * Full page background art * Larger posters * Navigation bar --- data/interfaces/default/css/plexpy.css | 416 +++++++++----------- data/interfaces/default/info.html | 500 ++++++++++++------------- 2 files changed, 405 insertions(+), 511 deletions(-) diff --git a/data/interfaces/default/css/plexpy.css b/data/interfaces/default/css/plexpy.css index 587bee7b..4b2b60a0 100644 --- a/data/interfaces/default/css/plexpy.css +++ b/data/interfaces/default/css/plexpy.css @@ -1,7 +1,8 @@ body { font-family: 'Open Sans', sans-serif; color: #fff; - padding-top: 50px; + margin-top: 50px; + overflow: hidden; } a { color: #eee; @@ -39,6 +40,9 @@ img { } .navbar { background: #000; + -webkit-box-shadow: 0 0 0 3px rgba(0,0,0,.2); + -moz-box-shadow: 0 0 0 3px rgba(0,0,0,.2); + box-shadow: 0 0 0 3px rgba(0,0,0,.2); } .navbar-brand { padding: 5px 5px; @@ -488,24 +492,12 @@ a .users-poster-face:hover { -moz-box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); } -.users-poster-face img { - bottom: 0; - overflow: hidden; - float: left; - background-color: #323232; - background-position: center; - background-size: cover; - -webkit-border-radius: 1000px; - -moz-border-radius: 1000px; - border-radius: 1000px; - -webkit-box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); - box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); - width: 40px; - height: 40px; -} -.users-poster-face img:hover{ - -webkit-box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 2px #F9AA03; - box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 2px #F9AA03; +a .poster-face:hover, +a .cover-face:hover, +a .users-poster-face:hover { + webkit-box-shadow: inset 0 0 0 2px #e9a049; + -moz-box-shadow: inset 0 0 0 2px #e9a049; + box-shadow: inset 0 0 0 2px #e9a049; } .users-name { position: relative; @@ -537,13 +529,6 @@ a .users-poster-face:hover { margin-bottom: 11px; text-align: center; } -.dashboard-activity-poster-face img { - bottom: 0; - overflow: hidden; - height: 140px; - min-width: 140px; - max-width: 250px; -} .dashboard-activity-poster-music-bg { background-position: center; position:absolute; @@ -683,8 +668,8 @@ a .users-poster-face:hover { float: left; min-height: 340px; } -.dashboard-recent-media-metacontainer{ - width: 153px; +.dashboard-recent-media-metacontainer { + width: 150px; font-size: 13px; margin-bottom: 20px; clear: both; @@ -699,7 +684,7 @@ a .users-poster-face:hover { margin: 0; line-height: 15px; font-weight: normal; - width: 153px; + width: 150px; white-space: nowrap; text-align: left; clear: both; @@ -714,244 +699,208 @@ a .users-poster-face:hover { } .art-face { background-repeat: no-repeat; - background-position: 50% 0%; - background-size: 100%; - height: 540px; - overflow: hidden; - min-width: 280px; - position: relative; - -webkit-border-radius: 2px; - -moz-border-radius: 2px; - border-radius: 2px; - -webkit-box-shadow: 0 0 0px rgba(0,0,0,0.75); - -moz-box-shadow: 0 0 0px rgba(0,0,0,0.75); - box-shadow: 0 0 0px rgba(0,0,0,0.75); - -webkit-transform: translateZ(0); - -moz-transform: translateZ(0); - -ms-transform: translateZ(0); - -o-transform: translateZ(0); - transform: translateZ(0); - -webkit-transform-style: preserve-3d; - -moz-transform-style: preserve-3d; - -ms-transform-style: preserve-3d; - -o-transform-style: preserve-3d; - transform-style: preserve-3d; - -webkit-perspective: 1000; - -moz-perspective: 1000; - -ms-perspective: 1000; - -o-perspective: 1000; - perspective: 1000; - -webkit-backface-visibility: hidden; - -moz-backface-visibility: hidden; - -ms-backface-visibility: hidden; - -o-backface-visibility: hidden; - backface-visibility: hidden; - clear: both; -} -.art-face img { - -webkit-border-radius: 2px; - -moz-border-radius: 2px; - border-radius: 2px; - border-color: #1d1d1d; - -webkit-border-radius: 2px; - -moz-border-radius: 2px; - border-radius: 2px; - -webkit-box-shadow: 0 0 10px rgba(0,0,0,0.75); - -moz-box-shadow: 0 0 10px rgba(0,0,0,0.75); - box-shadow: 0 0 10px rgba(0,0,0,0.75); - -webkit-transform: translateZ(0); - -moz-transform: translateZ(0); - -ms-transform: translateZ(0); - -o-transform: translateZ(0); - transform: translateZ(0); - -webkit-transform-style: preserve-3d; - -moz-transform-style: preserve-3d; - -ms-transform-style: preserve-3d; - -o-transform-style: preserve-3d; - transform-style: preserve-3d; - -webkit-perspective: 1000; - -moz-perspective: 1000; - -ms-perspective: 1000; - -o-perspective: 1000; - perspective: 1000; - -webkit-backface-visibility: hidden; - -moz-backface-visibility: hidden; - -ms-backface-visibility: hidden; - -o-backface-visibility: hidden; - backface-visibility: hidden; + background-position: center center; + background-attachment: scroll; + background-size: cover; + opacity: 0; + position: absolute; + top: 0; + bottom: 0; + width: 100%; } .art-music-face { background-repeat: no-repeat; - background-position: 50% 50%; - background-size: 100%; + background-position: center; + background-size: cover; width: 250px; - height: 141px; - overflow: hidden; position: relative; - -webkit-border-radius: 2px; - -moz-border-radius: 2px; - border-radius: 2px; - -webkit-box-shadow: 0 0 0px rgba(0,0,0,0.75); - -moz-box-shadow: 0 0 0px rgba(0,0,0,0.75); - box-shadow: 0 0 0px rgba(0,0,0,0.75); - -webkit-transform: translateZ(0); - -moz-transform: translateZ(0); - -ms-transform: translateZ(0); - -o-transform: translateZ(0); - transform: translateZ(0); - -webkit-transform-style: preserve-3d; - -moz-transform-style: preserve-3d; - -ms-transform-style: preserve-3d; - -o-transform-style: preserve-3d; - transform-style: preserve-3d; - -webkit-perspective: 1000; - -moz-perspective: 1000; - -ms-perspective: 1000; - -o-perspective: 1000; - perspective: 1000; - -webkit-backface-visibility: hidden; - -moz-backface-visibility: hidden; - -ms-backface-visibility: hidden; - -o-backface-visibility: hidden; - backface-visibility: hidden; - clear: both; + top: 0; + bottom: 0; } -.summary-wrapper { - height: auto; - width: 100%; +.summary-container { position: absolute; + top: 0; + right: 0; bottom: 0; + left: 0; + overflow-x: hidden; + overflow-y: auto; } -.summary-overlay { - background-color: rgba(0,0,0,0.85); - color: #fff; - font-size: 14px; - height: auto; - position: relative; - top: 0px; - border-color: #1d1d1d; - border: 2px solid #191919; +.summary-container .table-card-header, +.summary-container .table-card-back { + opacity: 0.90; } -.summary-content-wrapper { - margin-left: auto; - margin-right: auto; - width: 960px; +.summary-navbar { + background-color: rgba(255,255,255,.03); + height: 100px; + line-height: 50px; + padding-top: 50px; } -.summary-content-poster { - position: relative; - top: -10px; - float: left; - margin-left: 25px; - width: 150px; - height: 225px; -} -.summary-content-poster img { - bottom: 0; - overflow: hidden; - -webkit-border-radius: 3px; - -moz-border-radius: 3px; - border-radius: 3px; - -webkit-box-shadow: 0 0 10px rgba(0,0,0,0.75); - -moz-box-shadow: 0 0 10px rgba(0,0,0,0.75); - box-shadow: 0 0 10px rgba(0,0,0,0.75); - -webkit-transform: translateZ(0); - -moz-transform: translateZ(0); - -ms-transform: translateZ(0); - -o-transform: translateZ(0); - transform: translateZ(0); - -webkit-transform-style: preserve-3d; - -moz-transform-style: preserve-3d; - -ms-transform-style: preserve-3d; - -o-transform-style: preserve-3d; - transform-style: preserve-3d; - -webkit-perspective: 1000; - -moz-perspective: 1000; - -ms-perspective: 1000; - -o-perspective: 1000; - perspective: 1000; - -webkit-backface-visibility: hidden; - -moz-backface-visibility: hidden; - -ms-backface-visibility: hidden; - -o-backface-visibility: hidden; - backface-visibility: hidden; -} -.summary-content { - position: relative; - top: 0px; - left: 20px; +.summary-navbar-list { + padding: 0 25px; color: #999; - height: auto; - overflow: hidden; - margin-right: 20px; - margin-bottom: 20px; } -.summary-content-title h1{ - margin-top: 15px; - margin-bottom: 15px; - color: #F9AA03; - font-size: 24px; - line-height: 32px; +.summary-navbar-list span { float: left; + margin-right: 20px; } -.summary-content-title h1 a{ +.summary-navbar-list span a { + color: #999; +} +.summary-navbar-list span a:hover { color: #F9AA03; } -.summary-content-title h1 a:hover{ +.summary-content-title-wrapper { + height: 150px; + padding-top: 50px; +} +.summary-content-title { + overflow: hidden; + position: relative; + max-height: 100px; +} +.summary-content-title h1 { + margin-top: 0; + margin-bottom: 10px; + color: #F9AA03; + font-size: 28px; + line-height: 40px; + float: left; + clear: left; +} +.summary-content-title h1 a { + color: #F9AA03; +} +.summary-content-title h1 a:hover { color: #F9AA03; text-decoration: underline; } +.summary-content-title h2 { + margin-top: 0; + margin-bottom: 10px; + color: #fff; + font-size: 28px; + line-height: 40px; + float: left; + clear: left; +} +.summary-content-title h3 { + margin-top: 0; + margin-bottom: 10px; + color: #999; + font-size: 28px; + line-height: 40px; + float: right; +} +.summary-content-title h3 a:hover { + text-decoration: underline; +} +.summary-content-title h3 a { + color: #999; +} +.summary-content-wrapper { + background-image: linear-gradient(rgba(0,0,0,.4),rgba(19,19,19,.4) 50%,rgba(26,26,26,.4)); + background-clip: content-box; + min-height: 100%; +} +.summary-content-poster { + float: left; + width: 250px; + margin: 0 40px 0 25px; + height: 100px; + overflow: visible; +} +.summary-poster-face { + background-position: center; + background-size: cover; + height: 375px; + width: 250px; + position: relative; + webkit-box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); + -moz-box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); + box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); + overflow: hidden; +} +.summary-poster-face-episode { + background-position: center; + background-size: cover; + height: 140px; + width: 250px; + position: relative; + webkit-box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); + -moz-box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); + box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); + overflow: hidden; +} +a .summary-poster-face:hover, +a .summary-poster-face-episode:hover { + webkit-box-shadow: inset 0 0 0 2px #e9a049; + -moz-box-shadow: inset 0 0 0 2px #e9a049; + box-shadow: inset 0 0 0 2px #e9a049; +} +.summary-content-padding { + float: left; + width: 250px; + height: 1px; + margin: 0 40px 20px 25px; +} +.summary-content { + position: relative; + color: #999; + overflow: hidden; + padding-right: 10px; + margin-bottom: 20px; +} .summary-content-details-wrapper { width: 100%; - padding-bottom: 15px; - margin-top: -10px; - clear: both; + padding: 10px 0 15px 0; position: relative; - top: -15px; + float: left; } .summary-content-director { float: left; - margin-right: 10px; line-height: 24px; } .summary-content-director strong { color: #fff; margin-left: 2px; + margin-right: 10px; } .summary-content-studio { float: left; - margin-right: 10px; line-height: 24px; } .summary-content-studio strong { color: #fff; margin-left: 2px; + margin-right: 10px; } .summary-content-airdate { float: left; - margin-right: 10px; line-height: 24px; } .summary-content-airdate strong { color: #fff; margin-left: 2px; + margin-right: 10px; } .summary-content-duration { float: left; - margin-right: 10px; line-height: 24px; } .summary-content-duration strong { color: #fff; margin-left: 2px; + margin-right: 10px; } .summary-content-content-rating { float: left; line-height: 24px; - margin-right: 10px; } .summary-content-content-rating strong { color: #fff; margin-left: 2px; + margin-right: 10px; } .summary-content-summary { overflow: hidden; @@ -959,14 +908,12 @@ a .users-poster-face:hover { float: left; position: relative; clear: both; - padding-right: 10px; height: auto; max-height: 160px; padding-bottom: 0px; } .summary-content-people-wrapper { - margin-top: 25px; - margin-right: 25px; + margin-right: 20px; float: left; } .summary-content-people-wrapper hidden-phone hidden-tablet { @@ -975,9 +922,7 @@ a .users-poster-face:hover { min-height: 0px; } .summary-content-actors { - margin-top: 0px; - margin-left: 0px; - margin-right: 15px; + margin-top: 13px; font-size: 12px; line-height: 18px; color: #999; @@ -985,6 +930,7 @@ a .users-poster-face:hover { } .summary-content-actors ul { padding-left:20px; + margin-bottom: 0; } .summary-content-actors li { list-style: none; @@ -995,9 +941,7 @@ a .users-poster-face:hover { color: #fff; } .summary-content-genres { - margin-top: 0px; - margin-left: 0px; - margin-right: 15px; + margin-top: 13px; font-size: 12px; line-height: 18px; color: #999; @@ -1005,6 +949,7 @@ a .users-poster-face:hover { } .summary-content-genres ul { padding-left:20px; + margin-bottom: 0; } .summary-content-genres li { list-style: none; @@ -1015,9 +960,7 @@ a .users-poster-face:hover { color: #fff; } .summary-content-writers { - margin-top: 0px; - margin-left: 0px; - margin-right: 15px; + margin-top: 13px; font-size: 12px; line-height: 18px; color: #999; @@ -1025,6 +968,7 @@ a .users-poster-face:hover { } .summary-content-writers ul { padding-left: 20px; + margin-bottom: 0; } .summary-content-writers li { list-style: none; @@ -1037,7 +981,6 @@ a .users-poster-face:hover { .rateit { display: -moz-inline-box; display: inline-block; - position: relative; -webkit-user-select: none; -khtml-user-select: none; -moz-user-select: none; @@ -1045,18 +988,21 @@ a .users-poster-face:hover { -ms-user-select: none; user-select: none; -webkit-touch-callout: none; - float: right; - margin-top: 23px; - margin-right: 20px; overflow: hidden; white-space: nowrap; + float: right; + margin-top: 3px; + height: 21px; } .rateit .rateit-range { - background: url(../images/star-gray-16.png); + background: url(../images/star-gray-32.png); + background-size: contain; height: 16px; } .rateit .rateit-selected { - background: url(../images/star-16.png); + background: url(../images/star-32.png); + background-size: contain; + height: 16px; } .season-episodes-wrapper { } @@ -1090,17 +1036,6 @@ a .season-episodes-card-overlay:hover { -moz-box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); } -.season-episodes-poster-face img { - bottom: 0; - overflow: hidden; - width: 250px; - height: 140px; -} -.season-episodes-poster-face img:hover { - webkit-box-shadow: 0 0 0 2px #F9AA03; - -moz-box-shadow: 0 0 0 2px #F9AA03; - box-shadow: 0 0 0 2px #F9AA03; -} .season-episodes-card-overlay { position: absolute; left: 0; @@ -1184,20 +1119,6 @@ a .season-episodes-card-overlay:hover { -moz-box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); } -.user-info-poster-face img { - bottom: 0; - margin-right: 15px; - overflow: hidden; - float: left; - background-color: #323232; - background-position: center; - background-size: cover; - -webkit-border-radius: 1000px; - -moz-border-radius: 1000px; - border-radius: 1000px; - -webkit-box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); - box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); -} .user-info-username { font-size: 24px; color: #fff; @@ -1824,7 +1745,6 @@ a .home-platforms-instance-box:hover { position: relative; margin-right: 3px; } - #updatebar a:hover { color: #F9AA03; } \ No newline at end of file diff --git a/data/interfaces/default/info.html b/data/interfaces/default/info.html index 5dfc51dc..790c13c9 100644 --- a/data/interfaces/default/info.html +++ b/data/interfaces/default/info.html @@ -47,211 +47,205 @@ DOCUMENTATION :: END % if data:
-
-
-
-
-
-
- -
-
- % if data['type'] == 'movie': -

${data['title']}

- % elif data['type'] == 'season': -

${data['parent_title']} (${data['title']})

- % elif data['type'] == 'episode': -

${data['grandparent_title']} - ${data['title']} - (Season ${data['parent_index']}, Episode ${data['index']})

- % else: -

${data['title']}

- % endif -
- % if (data['type'] == 'movie' or data['type'] == 'show' or data['type'] == 'episode') and data['rating']: - - % endif -
-
- % if (data['type'] == 'episode' or data['type'] == 'movie') and data['directors']: - Directed by ${data['directors'][0]} - % endif -
-
- % if (data['type'] == 'show' or data['type'] == 'movie') and data['studio']: - Studio ${data['studio']} - % endif -
-
- % if data['type'] == 'movie': - Year ${data['year']} - % elif data['type'] == 'show': - Aired ${data['year']} - % elif data['type'] == 'episode': - Aired ${data['originally_available_at']} - % endif -
-
- Runtime ${data['duration']} mins -
-
- % if (data['type'] == 'episode' or data['type'] == 'movie' or data['type'] == 'show') and data['content_rating']: - Rated ${data['content_rating']} - % endif -
-
-
-

${data['summary']}

-
-
-
-
- - -
-
+
+
+
+
+
+ % if data['type'] == 'movie': + Movie + + ${data['title']} + % elif data['type'] == 'show': + TV Shows + + ${data['title']} + % elif data['type'] == 'season': + TV Shows + + ${data['parent_title']} + + Season ${data['index']} + % elif data['type'] == 'episode': + TV Shows + + ${data['grandparent_title']} + + Season ${data['parent_index']} + + ${data['title']} + % endif
-
-
-
-% if data['type'] == 'movie' or data['type'] == 'episode' or data['type'] == 'show': -
-
-
-
-
- Watch History for ${data['title']} -
-
- -   - - -
-
-
- - - - - - - - - - - - - - - - - - -
DeleteTimeUserIP AddressPlatformTitleStartedPausedStoppedDuration
-
- - - -% elif data['type'] == 'season': -
-
-
-
-
- Episode List for ${data['title']} +
+
+ % if data['type'] == 'movie' or data['type'] == 'show' or data['type'] == 'season': + + % else: + + % endif +
+
+ % if (data['type'] == 'movie' or data['type'] == 'show' or data['type'] == 'episode') and data['rating']: + + % endif +
+ % if (data['type'] == 'episode' or data['type'] == 'movie') and data['directors']: + Directed by ${data['directors'][0]} + % endif +
+
+ % if (data['type'] == 'show' or data['type'] == 'movie') and data['studio']: + Studio ${data['studio']} + % endif +
+
+ % if data['type'] == 'movie': + Year ${data['year']} + % elif data['type'] == 'show': + Aired ${data['year']} + % elif data['type'] == 'episode': + Aired ${data['originally_available_at']} + % endif +
+
+ Runtime ${data['duration']} mins +
+
+ % if (data['type'] == 'episode' or data['type'] == 'movie' or data['type'] == 'show') and data['content_rating']: + Rated ${data['content_rating']} + % endif +
+
+
+

${data['summary']}

+
+
+
+
+ + +
+ % if data['type'] == 'movie' or data['type'] == 'episode' or data['type'] == 'show': +
+
+
+ Watch History for ${data['title']} +
+
+   + +
+
+
+ + + + + + + + + + + + + + + + + +
DeleteTimeUserIP AddressPlatformTitleStartedPausedStoppedDuration
+
+ + +
+ % elif data['type'] == 'season': +
+
+
+ Episode List for ${data['title']} +
+
+
+
+
+ % endif
-
-
-
- % endif
@@ -259,8 +253,10 @@ DOCUMENTATION :: END
-

Error retrieving item data. This media may not be available in the Plex Media Server database - anymore.

+

+ Error retrieving item data. This media may not be available in the Plex Media Server database + anymore. +

@@ -292,52 +288,28 @@ DOCUMENTATION :: END type: 'post', data: function ( d ) { return { 'json_data': JSON.stringify( d ), - 'rating_key': ${data['rating_key']} - }; - } - } + 'rating_key': ${data['rating_key']} + }; + } + } history_table = $('#history_table').DataTable(history_table_options); - var colvis = new $.fn.dataTable.ColVis(history_table, { buttonText: ' Select columns', buttonClass: 'btn btn-dark', exclude: [0, 10] }); - $(colvis.button()).appendTo('div.colvis-button-bar'); - - clearSearchButton('history_table', history_table); + var colvis = new $.fn.dataTable.ColVis(history_table, { buttonText: ' Select columns', buttonClass: 'btn btn-dark', exclude: [0, 10] }); + $(colvis.button()).appendTo('div.colvis-button-bar'); - $('#row-edit-mode').on('click', function() { - $('#delete-message').popover(); + clearSearchButton('history_table', history_table); - if ($(this).hasClass('active')) { - if (history_to_delete.length > 0) { - $('#deleteCount').text(history_to_delete.length); - $('#confirm-modal').modal(); - $('#confirm-modal').one('click', '#confirm-delete', function () { - for (var i = 0; i < history_to_delete.length; i++) { - $.ajax({ - url: 'delete_history_rows', - data: { row_id: history_to_delete[i] }, - async: true, - success: function (data) { - var msg = "User history purged"; - showMsg(msg, false, true, 2000); - } - }); - } - history_table.draw(); - }); - } - - $('.delete-control').each(function () { - $(this).find('button.btn-danger').toggleClass('btn-warning').toggleClass('btn-danger'); - $(this).addClass('hidden'); - }); - - } else { - history_to_delete = []; - $('.delete-control').each(function() { - $(this).removeClass('hidden'); - }); - } - }); + $('#row-edit-mode').click(function() { + if ($(this).hasClass('active')) { + $('.delete-control').each(function() { + $(this).addClass('hidden'); + }); + } else { + $('.delete-control').each(function() { + $(this).removeClass('hidden'); + }); + } + }); }); % elif data['type'] == 'show': @@ -349,31 +321,30 @@ DOCUMENTATION :: END type: 'post', data: function ( d ) { return { 'json_data': JSON.stringify( d ), - 'grandparent_rating_key': ${data['rating_key']} - }; - } - } + 'grandparent_rating_key': ${data['rating_key']} + }; + } + } history_table = $('#history_table').DataTable(history_table_options); - var colvis = new $.fn.dataTable.ColVis(history_table, { buttonText: ' Select columns', buttonClass: 'btn btn-dark', exclude: [0, 10] }); - $(colvis.button()).appendTo('div.colvis-button-bar'); + var colvis = new $.fn.dataTable.ColVis(history_table, { buttonText: ' Select columns', buttonClass: 'btn btn-dark', exclude: [0, 10] }); + $(colvis.button()).appendTo('div.colvis-button-bar'); - clearSearchButton('history_table', history_table); + clearSearchButton('history_table', history_table); - $('#row-edit-mode').click(function() { - if ($(this).hasClass('active')) { - $('.delete-control').each(function() { - $(this).addClass('hidden'); - }); - } else { - $('.delete-control').each(function() { - $(this).removeClass('hidden'); - }); - } - }); + $('#row-edit-mode').click(function() { + if ($(this).hasClass('active')) { + $('.delete-control').each(function() { + $(this).addClass('hidden'); + }); + } else { + $('.delete-control').each(function() { + $(this).removeClass('hidden'); + }); + } + }); }); % endif - % if data['type'] == 'season': % endif @@ -392,4 +363,7 @@ DOCUMENTATION :: END $("#runtime").html(millisecondsToMinutes($("#runtime").text(), true)); % endif + From 7328fcd0fb9a9fdc8033c5c0d2e49211bac5cffa Mon Sep 17 00:00:00 2001 From: Jonathan Wong Date: Wed, 26 Aug 2015 16:23:54 -0700 Subject: [PATCH 11/54] Create body container so scrollbar is beneath navbar --- data/interfaces/default/base.html | 8 +++++--- data/interfaces/default/css/plexpy.css | 23 +++++++++++++++++++++++ 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/data/interfaces/default/base.html b/data/interfaces/default/base.html index 3bffc64a..f0918641 100644 --- a/data/interfaces/default/base.html +++ b/data/interfaces/default/base.html @@ -1,4 +1,4 @@ -<% +<% import plexpy from plexpy import version %> @@ -52,7 +52,7 @@ from plexpy import version - PlexPy + PlexPy
${next.headerIncludes()} -${next.body()} +
+ ${next.body()} +
diff --git a/data/interfaces/default/css/plexpy.css b/data/interfaces/default/css/plexpy.css index 4b2b60a0..04cdb7c0 100644 --- a/data/interfaces/default/css/plexpy.css +++ b/data/interfaces/default/css/plexpy.css @@ -1747,4 +1747,27 @@ a .home-platforms-instance-box:hover { } #updatebar a:hover { color: #F9AA03; +} +.body-container { + position: absolute; + top: 50px; + right: 0; + bottom: 0; + left: 0; + overflow-x: hidden; + overflow-y: auto; + +} +::-webkit-scrollbar { + width: 15px; +} +::-webkit-scrollbar-track { + background-color: rgba(0,0,0,.2); +} +::-webkit-scrollbar-thumb { + min-height: 50px; + background-color: rgba(255,255,255,.15); + border: 3px solid transparent; + border-radius: 8px; + background-clip: padding-box; } \ No newline at end of file From 6720d696eb398278681805f4cd162b9d5c82b8e2 Mon Sep 17 00:00:00 2001 From: Jonathan Wong Date: Wed, 26 Aug 2015 17:09:50 -0700 Subject: [PATCH 12/54] Fix typo --- data/interfaces/default/info.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/interfaces/default/info.html b/data/interfaces/default/info.html index 790c13c9..078d1670 100644 --- a/data/interfaces/default/info.html +++ b/data/interfaces/default/info.html @@ -53,7 +53,7 @@ DOCUMENTATION :: END
% if data['type'] == 'movie': - Movie + Movies ${data['title']} % elif data['type'] == 'show': From a126703f44cbeb470c7f2d80bdf21d15712e2c85 Mon Sep 17 00:00:00 2001 From: Jonathan Wong Date: Wed, 26 Aug 2015 17:24:29 -0700 Subject: [PATCH 13/54] Fix content-wrapper background --- data/interfaces/default/css/plexpy.css | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/data/interfaces/default/css/plexpy.css b/data/interfaces/default/css/plexpy.css index 04cdb7c0..3404b600 100644 --- a/data/interfaces/default/css/plexpy.css +++ b/data/interfaces/default/css/plexpy.css @@ -732,9 +732,8 @@ a .users-poster-face:hover { } .summary-navbar { background-color: rgba(255,255,255,.03); - height: 100px; + height: 50px; line-height: 50px; - padding-top: 50px; } .summary-navbar-list { padding: 0 25px; @@ -801,7 +800,8 @@ a .users-poster-face:hover { .summary-content-wrapper { background-image: linear-gradient(rgba(0,0,0,.4),rgba(19,19,19,.4) 50%,rgba(26,26,26,.4)); background-clip: content-box; - min-height: 100%; + min-height: calc(100% - 200px); + position: inherit; } .summary-content-poster { float: left; @@ -820,6 +820,7 @@ a .users-poster-face:hover { -moz-box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); overflow: hidden; + z-index: 1; } .summary-poster-face-episode { background-position: center; @@ -831,6 +832,7 @@ a .users-poster-face:hover { -moz-box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); overflow: hidden; + z-index: 1; } a .summary-poster-face:hover, a .summary-poster-face-episode:hover { From 53830a7711ca45484e4074eff26c792efaaa6ada Mon Sep 17 00:00:00 2001 From: Jonathan Wong Date: Wed, 26 Aug 2015 17:27:13 -0700 Subject: [PATCH 14/54] Fix info navbar overflow --- data/interfaces/default/css/plexpy.css | 2 ++ 1 file changed, 2 insertions(+) diff --git a/data/interfaces/default/css/plexpy.css b/data/interfaces/default/css/plexpy.css index 3404b600..a4bbd743 100644 --- a/data/interfaces/default/css/plexpy.css +++ b/data/interfaces/default/css/plexpy.css @@ -734,6 +734,8 @@ a .users-poster-face:hover { background-color: rgba(255,255,255,.03); height: 50px; line-height: 50px; + max-height: 50px; + overflow: hidden; } .summary-navbar-list { padding: 0 25px; From 84ffcc7948f65eb4c154cef7a3beb69dbe2d2d6d Mon Sep 17 00:00:00 2001 From: Jonathan Wong Date: Wed, 26 Aug 2015 17:56:17 -0700 Subject: [PATCH 15/54] Change alert and button color on info and user history tables --- data/interfaces/default/history.html | 2 +- data/interfaces/default/info.html | 38 +++++++++++++++++++++++----- data/interfaces/default/user.html | 17 ++++++------- 3 files changed, 40 insertions(+), 17 deletions(-) diff --git a/data/interfaces/default/history.html b/data/interfaces/default/history.html index c7960d20..addf5e7c 100644 --- a/data/interfaces/default/history.html +++ b/data/interfaces/default/history.html @@ -103,7 +103,7 @@ data: { row_id: history_to_delete[i] }, async: true, success: function (data) { - var msg = "User history purged"; + var msg = "History deleted"; showMsg(msg, false, true, 2000); } }); diff --git a/data/interfaces/default/info.html b/data/interfaces/default/info.html index 5dfc51dc..3eb5785a 100644 --- a/data/interfaces/default/info.html +++ b/data/interfaces/default/info.html @@ -185,12 +185,11 @@ DOCUMENTATION :: END Watch History for ${data['title']}
- -   - + +
@@ -359,12 +358,37 @@ DOCUMENTATION :: END clearSearchButton('history_table', history_table); - $('#row-edit-mode').click(function() { + $('#row-edit-mode').on('click', function() { + $('#row-edit-mode-alert').fadeIn(200); + if ($(this).hasClass('active')) { - $('.delete-control').each(function() { + if (history_to_delete.length > 0) { + $('#deleteCount').text(history_to_delete.length); + $('#confirm-modal').modal(); + $('#confirm-modal').one('click', '#confirm-delete', function () { + for (var i = 0; i < history_to_delete.length; i++) { + $.ajax({ + url: 'delete_history_rows', + data: { row_id: history_to_delete[i] }, + async: true, + success: function (data) { + var msg = "History deleted"; + showMsg(msg, false, true, 2000); + } + }); + } + history_table.draw(); + }); + } + + $('.delete-control').each(function () { + $(this).find('button.btn-danger').toggleClass('btn-warning').toggleClass('btn-danger'); $(this).addClass('hidden'); + $('#row-edit-mode-alert').fadeOut(200); }); + } else { + history_to_delete = []; $('.delete-control').each(function() { $(this).removeClass('hidden'); }); diff --git a/data/interfaces/default/user.html b/data/interfaces/default/user.html index c2176a10..65a43548 100644 --- a/data/interfaces/default/user.html +++ b/data/interfaces/default/user.html @@ -154,13 +154,11 @@ from plexpy import helpers
- -   - - + + +
@@ -390,7 +388,7 @@ from plexpy import helpers }); $('#row-edit-mode').on('click', function() { - $('#delete-message').popover(); + $('#row-edit-mode-alert').fadeIn(200); if ($(this).hasClass('active')) { if (history_to_delete.length > 0) { @@ -403,7 +401,7 @@ from plexpy import helpers data: { row_id: history_to_delete[i] }, async: true, success: function (data) { - var msg = "User history purged"; + var msg = "History deleted"; showMsg(msg, false, true, 2000); } }); @@ -415,6 +413,7 @@ from plexpy import helpers $('.delete-control').each(function () { $(this).find('button.btn-danger').toggleClass('btn-warning').toggleClass('btn-danger'); $(this).addClass('hidden'); + $('#row-edit-mode-alert').fadeOut(200); }); } else { From ef5d9d66042615197e944bd16ba6ed15296deede Mon Sep 17 00:00:00 2001 From: Jonathan Wong Date: Wed, 26 Aug 2015 18:18:40 -0700 Subject: [PATCH 16/54] Make sure buttons are cleared when entering delete mode --- data/interfaces/default/history.html | 2 +- data/interfaces/default/info.html | 2 +- data/interfaces/default/user.html | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/data/interfaces/default/history.html b/data/interfaces/default/history.html index addf5e7c..0615ea28 100644 --- a/data/interfaces/default/history.html +++ b/data/interfaces/default/history.html @@ -113,7 +113,6 @@ } $('.delete-control').each(function () { - $(this).find('button.btn-danger').toggleClass('btn-warning').toggleClass('btn-danger'); $(this).addClass('hidden'); $('#row-edit-mode-alert').fadeOut(200); }); @@ -121,6 +120,7 @@ } else { history_to_delete = []; $('.delete-control').each(function() { + $(this).find('button.btn-danger').toggleClass('btn-warning').toggleClass('btn-danger'); $(this).removeClass('hidden'); }); } diff --git a/data/interfaces/default/info.html b/data/interfaces/default/info.html index 3eb5785a..b3b8f44f 100644 --- a/data/interfaces/default/info.html +++ b/data/interfaces/default/info.html @@ -382,7 +382,6 @@ DOCUMENTATION :: END } $('.delete-control').each(function () { - $(this).find('button.btn-danger').toggleClass('btn-warning').toggleClass('btn-danger'); $(this).addClass('hidden'); $('#row-edit-mode-alert').fadeOut(200); }); @@ -390,6 +389,7 @@ DOCUMENTATION :: END } else { history_to_delete = []; $('.delete-control').each(function() { + $(this).find('button.btn-danger').toggleClass('btn-warning').toggleClass('btn-danger'); $(this).removeClass('hidden'); }); } diff --git a/data/interfaces/default/user.html b/data/interfaces/default/user.html index 65a43548..cc6b18a4 100644 --- a/data/interfaces/default/user.html +++ b/data/interfaces/default/user.html @@ -411,7 +411,6 @@ from plexpy import helpers } $('.delete-control').each(function () { - $(this).find('button.btn-danger').toggleClass('btn-warning').toggleClass('btn-danger'); $(this).addClass('hidden'); $('#row-edit-mode-alert').fadeOut(200); }); @@ -419,6 +418,7 @@ from plexpy import helpers } else { history_to_delete = []; $('.delete-control').each(function() { + $(this).find('button.btn-danger').toggleClass('btn-warning').toggleClass('btn-danger'); $(this).removeClass('hidden'); }); } From c08d581df3d41c6372058fd0997024cb7d793378 Mon Sep 17 00:00:00 2001 From: Jonathan Wong Date: Wed, 26 Aug 2015 18:20:29 -0700 Subject: [PATCH 17/54] Fix group purge on users table to remember state on table redraw * Changed popover to alert box * Changed button colour --- data/interfaces/default/css/plexpy.css | 3 +- data/interfaces/default/js/tables/users.js | 15 ++++-- data/interfaces/default/users.html | 55 +++++++--------------- 3 files changed, 31 insertions(+), 42 deletions(-) diff --git a/data/interfaces/default/css/plexpy.css b/data/interfaces/default/css/plexpy.css index 587bee7b..a47ffd04 100644 --- a/data/interfaces/default/css/plexpy.css +++ b/data/interfaces/default/css/plexpy.css @@ -1528,7 +1528,8 @@ a .home-platforms-instance-box:hover { .button-bar { float: right; } -.colvis-button-bar { +.colvis-button-bar, +.refresh-users-button { float: right; } .nav-settings, diff --git a/data/interfaces/default/js/tables/users.js b/data/interfaces/default/js/tables/users.js index 8a5154df..0e9719db 100644 --- a/data/interfaces/default/js/tables/users.js +++ b/data/interfaces/default/js/tables/users.js @@ -1,3 +1,5 @@ +var users_to_purge = []; + users_list_table_options = { "language": { "search": "Search: ", @@ -187,6 +189,11 @@ users_list_table_options = { "preDrawCallback": function(settings) { var msg = "
 Fetching rows...
"; showMsg(msg, false, false, 0) + }, + "rowCallback": function (row, rowData) { + if ($.inArray(rowData['user_id'], users_to_purge) !== -1) { + $(row).find('button[data-id="' + rowData['user_id'] + '"]').toggleClass('btn-warning').toggleClass('btn-danger'); + } } } @@ -271,9 +278,11 @@ $('#users_list_table').on('click', 'td.edit-control > .edit-user-toggles > butto var row = users_list_table.row(tr); var rowData = row.data(); - if ($(this).hasClass('active')) { - $(this).toggleClass('btn-warning').toggleClass('btn-danger'); + var index = $.inArray(rowData['user_id'], users_to_purge); + if (index === -1) { + users_to_purge.push(rowData['user_id']); } else { - $(this).toggleClass('btn-danger').toggleClass('btn-warning'); + users_to_purge.splice(index, 1); } + $(this).toggleClass('btn-warning').toggleClass('btn-danger'); }); \ No newline at end of file diff --git a/data/interfaces/default/users.html b/data/interfaces/default/users.html index df36a858..fe35f072 100644 --- a/data/interfaces/default/users.html +++ b/data/interfaces/default/users.html @@ -12,12 +12,11 @@ All Users
- -   - - + +   +
@@ -85,20 +84,19 @@ clearSearchButton('users_list_table', users_list_table); - var users_to_purge = []; $('#row-edit-mode').on('click', function () { - $('#purge-message').popover(); + $('#row-edit-mode-alert').fadeIn(200); + $('#users-to-delete').html(''); if ($(this).hasClass('active')) { - users_to_purge = []; - ul = $('#users-to-delete'); - ul.html(''); - $('.edit-control button.btn-danger').map(function () { - users_to_purge.push($(this).attr('data-id')); - ul.append('
  • ' + $('div[data-id=' + $(this).attr('data-id') + '] > input').val() + '
  • ') - }); if (users_to_purge.length > 0) { - $('#users-to-delete').append + $('.edit-control').each(function () { + $(this).find('button.btn-danger').toggleClass('btn-warning').toggleClass('btn-danger'); + }); + + for (var i = 0; i < users_to_purge.length; i++) { + $('#users-to-delete').append('
  • ' + $('div[data-id=' + users_to_purge[i] + '] > input').val() + '
  • '); + } $('#confirm-modal').modal(); $('#confirm-modal').one('click', '#confirm-purge', function () { for (var i = 0; i < users_to_purge.length; i++) { @@ -118,35 +116,16 @@ } $('.edit-control').each(function () { - $(this).find('button.btn-danger').toggleClass('btn-warning').toggleClass('btn-danger'); $(this).addClass('hidden'); - }); - $('.edit-user-control > .edit-user-name').each(function () { - a = $(this).children('a'); - input = $(this).children('input'); - a.text(input.val()); - a.removeClass('hidden'); - input.addClass('hidden'); + $('#row-edit-mode-alert').fadeOut(200); }); } else { + users_to_purge = []; $('.edit-control').each(function () { + $(this).find('button.btn-danger').toggleClass('btn-warning').toggleClass('btn-danger'); $(this).removeClass('hidden'); }); - $('.edit-user-control > .edit-user-name').each(function () { - $(this).children('a').addClass('hidden'); - $(this).children('input').removeClass('hidden'); - }); - - } - }); - - $(window).resize(function () { - if ($('.popover').popover().is(':visible')) { - var popover = $('.popover'); - popover.addClass("noTransition"); - $('#purge-message').popover('show'); - popover.removeClass("noTransition"); } }); }); From 73125153c87ce3ec27218b1f11aa7ee4315e0b0d Mon Sep 17 00:00:00 2001 From: Jonathan Wong Date: Wed, 26 Aug 2015 19:36:08 -0700 Subject: [PATCH 18/54] Update graphs history table modal * Tooltips * Stream details modal --- data/interfaces/default/css/plexpy.css | 5 ++ .../default/history_table_modal.html | 29 +++++++- .../default/js/tables/history_table_modal.js | 67 ++++++++++++++++--- 3 files changed, 92 insertions(+), 9 deletions(-) diff --git a/data/interfaces/default/css/plexpy.css b/data/interfaces/default/css/plexpy.css index a47ffd04..27c7b5f7 100644 --- a/data/interfaces/default/css/plexpy.css +++ b/data/interfaces/default/css/plexpy.css @@ -296,6 +296,10 @@ fieldset[disabled] .btn-bright.active { .modal-body i.fa { color: #fff; } +.modal-body td:hover a .fa, +.modal-body a:focus i.fa { + color: #f9aa03; +} .modal-body strong { color: #F9AA03; } @@ -1748,6 +1752,7 @@ a .home-platforms-instance-box:hover { } .history-title .popover.right { margin-left: 12px; + z-index: 5; } .history-title .popover.right .popover-content { padding: 5px 8px; diff --git a/data/interfaces/default/history_table_modal.html b/data/interfaces/default/history_table_modal.html index cfff340d..152589eb 100644 --- a/data/interfaces/default/history_table_modal.html +++ b/data/interfaces/default/history_table_modal.html @@ -27,7 +27,8 @@
    - + % else: diff --git a/data/interfaces/default/js/tables/history_table_modal.js b/data/interfaces/default/js/tables/history_table_modal.js index 1c684907..8a0afa22 100644 --- a/data/interfaces/default/js/tables/history_table_modal.js +++ b/data/interfaces/default/js/tables/history_table_modal.js @@ -74,6 +74,19 @@ history_table_modal_options = { { "targets": [3], "data":"player", + "createdCell": function (td, cellData, rowData, row, col) { + if (cellData !== '') { + var transcode_dec = ''; + if (rowData['video_decision'] === 'transcode') { + transcode_dec = ''; + } else if (rowData['video_decision'] === 'copy') { + transcode_dec = ''; + } else if (rowData['video_decision'] === 'direct play' || rowData['video_decision'] === '') { + transcode_dec = ''; + } + $(td).html(''); + } + }, "className": "no-wrap hidden-sm hidden-xs modal-control" }, { @@ -81,14 +94,21 @@ history_table_modal_options = { "data":"full_title", "createdCell": function (td, cellData, rowData, row, col) { if (cellData !== '') { - if (rowData['media_type'] === 'movie' || rowData['media_type'] === 'episode') { - var transcode_dec = ''; - if (rowData['video_decision'] === 'transcode') { - transcode_dec = ' '; - } - $(td).html('
    ' + transcode_dec + '
    '); + var media_type = ''; + var thumb_popover = ''; + if (rowData['media_type'] === 'movie') { + media_type = ''; + thumb_popover = '' + cellData + ' (' + rowData['year'] + ')' + $(td).html(''); + } else if (rowData['media_type'] === 'episode') { + media_type = ''; + thumb_popover = '' + cellData + ' \ + (S' + ('00' + rowData['parent_media_index']).slice(-2) + 'E' + ('00' + rowData['media_index']).slice(-2) + ')' + $(td).html(''); } else if (rowData['media_type'] === 'track') { - $(td).html('
    ' + cellData + '
    '); + media_type = ''; + thumb_popover = '' + cellData + ' (' + rowData['parent_title'] + ')' + $(td).html('
    ' + media_type + ' ' + thumb_popover + '
    '); } else { $(td).html('' + cellData + ''); } @@ -100,9 +120,40 @@ history_table_modal_options = { // Jump to top of page // $('html,body').scrollTop(0); $('#ajaxMsg').fadeOut(); + + // Create the tooltips. + $('.transcode-tooltip').tooltip(); + $('.media-type-tooltip').tooltip(); + $('.thumb-tooltip').popover({ + html: true, + trigger: 'hover', + placement: 'right', + content: function () { + return '
    '; + } + }); }, "preDrawCallback": function(settings) { var msg = "
     Fetching rows...
    "; showMsg(msg, false, false, 0) } -} \ No newline at end of file +} + +$('#history_table').on('click', 'td.modal-control', function () { + var tr = $(this).parents('tr'); + var row = history_table.row(tr); + var rowData = row.data(); + + function showStreamDetails() { + $.ajax({ + url: 'get_stream_data', + data: { row_id: rowData['id'], user: rowData['friendly_name'] }, + cache: false, + async: true, + complete: function (xhr, status) { + $("#info-modal").html(xhr.responseText); + } + }); + } + showStreamDetails(); +}); \ No newline at end of file From dd7390d1119162db9c18bca647b18d0ee9e1a3e8 Mon Sep 17 00:00:00 2001 From: Jonathan Wong Date: Wed, 26 Aug 2015 20:17:42 -0700 Subject: [PATCH 19/54] Fix box shadow on slider --- data/interfaces/default/css/plexpy.css | 11 ++++++----- data/interfaces/default/home_stats.html | 1 - 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/data/interfaces/default/css/plexpy.css b/data/interfaces/default/css/plexpy.css index ac05a5e2..9283347c 100644 --- a/data/interfaces/default/css/plexpy.css +++ b/data/interfaces/default/css/plexpy.css @@ -1454,7 +1454,7 @@ a .season-episodes-card-overlay:hover { float: left; position: relative; width: 100%; - padding: 0 10px 20px 10px; + padding: 0 10px 25px 10px; } .home-platforms-instance-list li { margin-top: 25px; @@ -1613,12 +1613,13 @@ a .home-platforms-instance-list-oval:hover, width: 320px; display: none; position: absolute; - top: 129px; + top: 128px; left: -10px; - border-right: 1px solid rgba(255,255,255,.1); - border-bottom: 1px solid rgba(255,255,255,.1); - border-left: 1px solid rgba(255,255,255,.1); + webkit-box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); + -moz-box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); + box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); z-index: 1; + clip: rect(1px, 324px, 1000px, -2px); } .history-table-title { text-overflow: ellipsis; diff --git a/data/interfaces/default/home_stats.html b/data/interfaces/default/home_stats.html index 4c5f498a..870b2075 100644 --- a/data/interfaces/default/home_stats.html +++ b/data/interfaces/default/home_stats.html @@ -536,7 +536,6 @@ DOCUMENTATION :: END topZIndex++; instanceBoxChevron.toggleClass('active'); - instanceBoxSlider.toggleClass('slider-open'); instanceBoxSlider.css('z-index', topZIndex); instanceBoxSlider.stop().slideToggle(500); }); From dfbf435c77120f335ef8797c4e4cfcb94c232fce Mon Sep 17 00:00:00 2001 From: Jonathan Wong Date: Wed, 26 Aug 2015 20:34:06 -0700 Subject: [PATCH 20/54] Fix clipping on long titles --- data/interfaces/default/css/plexpy.css | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/data/interfaces/default/css/plexpy.css b/data/interfaces/default/css/plexpy.css index 9283347c..57d9bf17 100644 --- a/data/interfaces/default/css/plexpy.css +++ b/data/interfaces/default/css/plexpy.css @@ -1378,9 +1378,16 @@ a .season-episodes-card-overlay:hover { width: 100%; padding: 0 0 0 20px; } +.home-platforms-instance-name h4 { + margin: 10px 0 20px 0; +} .home-platforms-instance-name h5 { - font-size: 16px; - margin: 20px 0 2px 0; + font-size: 14px; + line-height: 16px; + margin: 15px 0 2px 0; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; } .home-platforms-instance-playcount { float: left; From f00de8e548066dce6b7cf3f2c3a92fd1f3709b30 Mon Sep 17 00:00:00 2001 From: Jonathan Wong Date: Wed, 26 Aug 2015 20:41:05 -0700 Subject: [PATCH 21/54] Another clipping text fix --- data/interfaces/default/css/plexpy.css | 3 +++ 1 file changed, 3 insertions(+) diff --git a/data/interfaces/default/css/plexpy.css b/data/interfaces/default/css/plexpy.css index 57d9bf17..25c6b5b3 100644 --- a/data/interfaces/default/css/plexpy.css +++ b/data/interfaces/default/css/plexpy.css @@ -1513,6 +1513,9 @@ a .season-episodes-card-overlay:hover { } .home-platforms-instance-list-name h5 { margin: 5px 0px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; } .home-platforms-instance-list-playcount { float: left; From b91a4844e07b3b3b648aa34d4df9809f53cecb70 Mon Sep 17 00:00:00 2001 From: Jonathan Wong Date: Wed, 26 Aug 2015 23:45:19 -0700 Subject: [PATCH 22/54] Cleanup dashboard activity styles --- data/interfaces/default/css/plexpy.css | 213 ++++++++++++------ data/interfaces/default/current_activity.html | 40 ++-- 2 files changed, 159 insertions(+), 94 deletions(-) diff --git a/data/interfaces/default/css/plexpy.css b/data/interfaces/default/css/plexpy.css index 25c6b5b3..97d2b46e 100644 --- a/data/interfaces/default/css/plexpy.css +++ b/data/interfaces/default/css/plexpy.css @@ -80,24 +80,6 @@ img { .padded-header h3 { font-size: 20px; } -.activity-progress { - overflow: hidden; - height: 4px; - background-color: #111; - float: left; - width: 100%; - margin: 0 10px 5px 0; -} -.activity-progress .bar { - background-color: #faa732; - background-image: -moz-linear-gradient(top, #fbb450, #f89406); - background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#fbb450), to(#f89406)); - background-image: -webkit-linear-gradient(top, #fbb450, #f89406); - background-image: -o-linear-gradient(top, #fbb450, #f89406); - background-image: linear-gradient(to bottom, #fbb450, #f89406); - background-repeat: repeat-x; - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffbb450', endColorstr='#fff89406', GradientType=0); -} .btn { outline:0px !important; -webkit-appearance:none; @@ -431,20 +413,13 @@ input[type="color"], text-align: center; pointer-events: none; } -.poster { - float: left; - min-height: 225px; - min-width: 150px; - margin-bottom: 8px; - position: relative; -} .poster-face { background-position: center; background-size: cover; height: 225px; width: 150px; position: relative; - webkit-box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); + -webkit-box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); -moz-box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); overflow: hidden; @@ -456,7 +431,7 @@ input[type="color"], width: 150px; position: absolute; bottom: 0; - webkit-box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); + -webkit-box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); -moz-box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); } @@ -469,14 +444,14 @@ input[type="color"], -webkit-border-radius: 50%; -moz-border-radius: 50%; border-radius: 50%; - webkit-box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); + -webkit-box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); -moz-box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); } a .poster-face:hover, a .cover-face:hover, a .users-poster-face:hover { - webkit-box-shadow: inset 0 0 0 2px #e9a049; + -webkit-box-shadow: inset 0 0 0 2px #e9a049; -moz-box-shadow: inset 0 0 0 2px #e9a049; box-shadow: inset 0 0 0 2px #e9a049; } @@ -508,19 +483,19 @@ a .users-poster-face:hover { margin-left: 5px; float: left; } -.dashboard-activity-metadata-wrapper { +.dashboard-instance { + padding-bottom: 15px; + float: left; position: relative; - left: 0px; - width: 250px; - font-size: 13px; + margin-right: 27px; } -.dashboard-activity-button-info { - position: absolute; - top: 3px; - left: 5px; - z-index: 1; +.dashboard-poster { + float: left; + min-width: 150px; + margin-bottom: 8px; + position: relative; } -.dashboard-activity-poster-face { +.dashboard-activity-poster { height: 141px; width: 250px; position: relative; @@ -528,21 +503,85 @@ a .users-poster-face:hover { background-color: rgba(255,255,255,.03); margin-bottom: 11px; text-align: center; -} -.dashboard-activity-poster-face img { - bottom: 0; overflow: hidden; - height: 140px; - min-width: 140px; - max-width: 250px; } -.dashboard-activity-poster-music-bg { +.dashboard-activity-poster-face { background-position: center; - position:absolute; - width:100%; - height:100%; - opacity: 0.7; - z-index: -999; + background-size: cover; + height: 140px; + width: 250px; + position: relative; + -webkit-box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); + -moz-box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); + box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); + margin-bottom: 11px; + text-align: center; +} +.dashboard-activity-cover-face-bg { + background-position: center; + background-size: cover; + background-repeat: no-repeat; + height: 140px; + width: 250px; + position: relative; + -webkit-box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); + -moz-box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); + box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); + margin-bottom: 11px; + text-align: center; + -webkit-filter: blur(5px); + -moz-filter: blur(5px); + filter: blur(5px); + opacity: 0.75; +} +.dashboard-activity-cover-face { + background-position: center; + background-size: cover; + background-repeat: no-repeat; + height: 140px; + width: 140px; + position: relative; + -webkit-box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); + -moz-box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); + box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); + margin-bottom: 11px; + text-align: center; + position: absolute; + top: 0; + left: 55px; +} +.dashboard-activity-clip-face-bg { + background-position: center; + background-size: cover; + background-repeat: no-repeat; + height: 140px; + width: 250px; + position: relative; + -webkit-box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); + -moz-box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); + box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); + margin-bottom: 11px; + text-align: center; + -webkit-filter: blur(5px); + -moz-filter: blur(5px); + filter: blur(5px); + opacity: 0.75; +} +.dashboard-activity-clip-face { + background-position: center; + background-size: cover; + background-repeat: no-repeat; + height: 140px; + width: 94px; + position: relative; + -webkit-box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); + -moz-box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); + box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); + margin-bottom: 11px; + text-align: center; + position: absolute; + top: 0; + left: 78px; } .dashboard-activity-poster-info-bar { position:absolute; @@ -581,6 +620,36 @@ a .users-poster-face:hover { font-size: 12px; color: #eee; } +.dashboard-activity-progress { + overflow: hidden; + height: 4px; + background-color: #111; + float: left; + width: 100%; + margin: 0 10px 5px 0; +} +.dashboard-activity-progress .bar { + background-color: #faa732; + background-image: -moz-linear-gradient(top, #fbb450, #f89406); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#fbb450), to(#f89406)); + background-image: -webkit-linear-gradient(top, #fbb450, #f89406); + background-image: -o-linear-gradient(top, #fbb450, #f89406); + background-image: linear-gradient(to bottom, #fbb450, #f89406); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffbb450', endColorstr='#fff89406', GradientType=0); +} +.dashboard-activity-button-info { + position: absolute; + top: 3px; + left: 5px; + z-index: 1; +} +.dashboard-activity-metadata-wrapper { + position: relative; + left: 0px; + width: 250px; + font-size: 13px; +} .dashboard-activity-metadata-user { text-overflow: ellipsis; overflow: hidden; @@ -611,7 +680,9 @@ a .users-poster-face:hover { .dashboard-activity-metadata-platform { float: right; } -.dashboard-activity-metadata-platform img { +.dashboard-activity-metadata-platform-box { + background-position: center; + background-size: cover; width: 40px; height: 40px; } @@ -651,12 +722,6 @@ a .users-poster-face:hover { color: #fff; font-weight: normal; } -.instance { - padding-bottom: 15px; - float: left; - position: relative; - margin-right: 27px; -} .dashboard-recent-media-row { width: 100%; margin:0 auto; @@ -1063,7 +1128,7 @@ a .users-poster-face:hover { margin-right: 25px; } a .season-episodes-card-overlay:hover { - webkit-box-shadow: inset 0 0 0 2px #e9a049; + -webkit-box-shadow: inset 0 0 0 2px #e9a049; -moz-box-shadow: inset 0 0 0 2px #e9a049; box-shadow: inset 0 0 0 2px #e9a049; } @@ -1078,7 +1143,7 @@ a .season-episodes-card-overlay:hover { height: 140px; width: 250px; position: relative; - webkit-box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); + -webkit-box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); -moz-box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); } @@ -1089,7 +1154,7 @@ a .season-episodes-card-overlay:hover { height: 140px; } .season-episodes-poster-face img:hover { - webkit-box-shadow: 0 0 0 2px #F9AA03; + -webkit-box-shadow: 0 0 0 2px #F9AA03; -moz-box-shadow: 0 0 0 2px #F9AA03; box-shadow: 0 0 0 2px #F9AA03; } @@ -1172,7 +1237,7 @@ a .season-episodes-card-overlay:hover { -webkit-border-radius: 50%; -moz-border-radius: 50%; border-radius: 50%; - webkit-box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); + -webkit-box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); -moz-box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); } @@ -1297,7 +1362,7 @@ a .season-episodes-card-overlay:hover { float: left; width: 75px; border-radius: 3px; - webkit-box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); + -webkit-box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); -moz-box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); background-size: contain; @@ -1350,9 +1415,9 @@ a .season-episodes-card-overlay:hover { padding: 10px; margin-right: 20px; margin-bottom: 20px; - webkit-box-sizing: content-box; + -webkit-box-sizing: content-box; box-sizing: content-box; - webkit-box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); + -webkit-box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); -moz-box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); } @@ -1423,7 +1488,7 @@ a .season-episodes-card-overlay:hover { background-size: cover; height: 120px; width: 80px; - webkit-box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); + -webkit-box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); -moz-box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); } @@ -1437,7 +1502,7 @@ a .season-episodes-card-overlay:hover { -webkit-border-radius: 3px; -moz-border-radius: 3px; border-radius: 3px; - webkit-box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); + -webkit-box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); -moz-box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); overflow: hidden; @@ -1452,7 +1517,7 @@ a .season-episodes-card-overlay:hover { -webkit-border-radius: 50%; -moz-border-radius: 50%; border-radius: 50%; - webkit-box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); + -webkit-box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); -moz-box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); overflow: hidden; @@ -1482,7 +1547,7 @@ a .season-episodes-card-overlay:hover { -webkit-border-radius: 50%; -moz-border-radius: 50%; border-radius: 50%; - webkit-box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); + -webkit-box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); -moz-box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); } @@ -1550,7 +1615,7 @@ a .season-episodes-card-overlay:hover { background-size: cover; height: 60px; width: 40px; - webkit-box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); + -webkit-box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); -moz-box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); } @@ -1564,7 +1629,7 @@ a .season-episodes-card-overlay:hover { -webkit-border-radius: 3px; -moz-border-radius: 3px; border-radius: 3px; - webkit-box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); + -webkit-box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); -moz-box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); overflow: hidden; @@ -1579,7 +1644,7 @@ a .season-episodes-card-overlay:hover { -webkit-border-radius: 50%; -moz-border-radius: 50%; border-radius: 50%; - webkit-box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); + -webkit-box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); -moz-box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); overflow: hidden; @@ -1614,7 +1679,7 @@ a .home-platforms-instance-list-oval:hover, .home-platforms-poster-face:hover, .home-platforms-list-poster-face:hover { - webkit-box-shadow: inset 0 0 0 2px #e9a049; + -webkit-box-shadow: inset 0 0 0 2px #e9a049; -moz-box-shadow: inset 0 0 0 2px #e9a049; box-shadow: inset 0 0 0 2px #e9a049; } @@ -1625,7 +1690,7 @@ a .home-platforms-instance-list-oval:hover, position: absolute; top: 128px; left: -10px; - webkit-box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); + -webkit-box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); -moz-box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); z-index: 1; diff --git a/data/interfaces/default/current_activity.html b/data/interfaces/default/current_activity.html index 3bea2b89..ef401039 100644 --- a/data/interfaces/default/current_activity.html +++ b/data/interfaces/default/current_activity.html @@ -61,22 +61,23 @@ DOCUMENTATION :: END % if data is not None: % if data['stream_count'] != '0': % for a in data['sessions']: -
    -
    -
    +
    +
    +
    % if a['type'] == 'movie' and not a['indexes']: - +
    % elif a['indexes']: - + % else: - % if a['type'] == 'track': -
    - % endif - % if a['type'] == 'clip': - - % else: - - % endif + % if a['type'] == 'track': +
    +
    + % elif a['type'] == 'clip': +
    +
    + % else: +
    + % endif % endif
    @@ -96,7 +97,7 @@ DOCUMENTATION :: END @@ -122,8 +123,9 @@ available_notification_agents = notifiers.available_notification_agents()
    - +
    +

    Port to bind web server to. Note that ports below 1024 may require root.

    @@ -223,8 +225,9 @@ available_notification_agents = notifiers.available_notification_agents()
    - +
    +

    Port that Plex Media Server is listening on.

    @@ -286,8 +289,9 @@ available_notification_agents = notifiers.available_notification_agents()
    - +
    +

    The interval (in hours) PlexPy will request an updated friends list from Plex.tv. 1 minimum, 24 maximum.

    @@ -328,8 +332,9 @@ available_notification_agents = notifiers.available_notification_agents()
    - +
    +

    The interval (in seconds) PlexPy will ping your Plex Server. Min 30 seconds, Recommended 60 seconds.

    @@ -344,11 +349,12 @@ available_notification_agents = notifiers.available_notification_agents()

    Keep records of all video items played from your Plex Media Server.

    - +
    - +
    +

    The interval (in seconds) PlexPy will wait for a video item to be active before logging it. 0 to disable.

    @@ -380,8 +386,9 @@ available_notification_agents = notifiers.available_notification_agents()
    - +
    +

    How many buffer events should we wait before triggering the first warning. Buffer events increment on each monitor ping if play state is buffering. 0 to disable buffer warnings.

    @@ -389,8 +396,9 @@ available_notification_agents = notifiers.available_notification_agents()
    - +
    +

    The value (in seconds) PlexPy should wait before triggering the next buffer warning. 0 to always trigger.

    @@ -421,8 +429,9 @@ available_notification_agents = notifiers.available_notification_agents()
    - +
    +

    Set the progress percentage of when a watched notification should be triggered. Minimum 50, Maximum 95.

    From 60380c6a99a2994d72e51f7a1f7b1e6aa1b00eb4 Mon Sep 17 00:00:00 2001 From: Jonathan Wong Date: Thu, 27 Aug 2015 00:48:10 -0700 Subject: [PATCH 24/54] Add season list to show page, and watch history to season page --- data/interfaces/default/css/plexpy.css | 107 ++++++++++-- data/interfaces/default/info.html | 154 ++++++++++++------ .../interfaces/default/info_episode_list.html | 2 +- data/interfaces/default/info_season_list.html | 55 +++++++ plexpy/datafactory.py | 2 + plexpy/pmsconnect.py | 62 ++++++- plexpy/webserve.py | 19 ++- 7 files changed, 329 insertions(+), 72 deletions(-) create mode 100644 data/interfaces/default/info_season_list.html diff --git a/data/interfaces/default/css/plexpy.css b/data/interfaces/default/css/plexpy.css index a4bbd743..79d37e1a 100644 --- a/data/interfaces/default/css/plexpy.css +++ b/data/interfaces/default/css/plexpy.css @@ -1008,17 +1008,102 @@ a .summary-poster-face-episode:hover { background-size: contain; height: 16px; } +.show-seasons-wrapper { +} +.show-seasons-instance { + list-style: none; + margin: 0; +} +.show-seasons-instance li { + float: left; + position: relative; + left: 0px; + margin-right: 25px; + margin-bottom: 25px; +} +a .show-seasons-card-overlay:hover { + webkit-box-shadow: inset 0 0 0 2px #e9a049; + -moz-box-shadow: inset 0 0 0 2px #e9a049; + box-shadow: inset 0 0 0 2px #e9a049; +} +.show-seasons-poster { + float: left; + position: relative; + left: 0px; +} +.show-seasons-card-overlay { + position: absolute; + left: 0; + right: 0; + bottom: 0; + text-align: left; + background: -moz-linear-gradient(top, rgba(0,0,0,0) 30%, rgba(0,0,0,1) 100%); + background: -webkit-gradient(linear, left top, left bottom, color-stop(30%,rgba(0,0,0,0)), color-stop(100%,rgba(0,0,0,1))); + background: -webkit-linear-gradient(top, rgba(0,0,0,0) 30%,rgba(0,0,0,1) 100%); + background: -o-linear-gradient(top, rgba(0,0,0,0) 30%,rgba(0,0,0,1) 100%); + background: -ms-linear-gradient(top, rgba(0,0,0,0) 30%,rgba(0,0,0,1) 100%); + background: linear-gradient(to bottom, rgba(0,0,0,0) 30%,rgba(0,0,0,1) 100%); + height: 225px; +} +.show-seasons-overlay-text { + color: #aaa; + font-size: 12px; + float: left; + position: absolute; + left: 8px; + bottom: 5px; +} +.show-seasons-instance-text-wrapper { + width: 150px; + font-size: 13px; + margin-bottom: 20px; + clear: both; +} +.show-seasons-instance-text-wrapper h3 { + padding: 5px 3px 0 3px; + color: #fff; + text-overflow: ellipsis; + overflow: hidden; + position: relative; + font-size: 13px; + margin: 0; + line-height: 15px; + font-weight: normal; + width: 250px; + white-space: nowrap; + text-align: left; + clear: both; +} +.show-seasons-title a { + text-decoration: none; + font-size: 14px; + font-weight: normal; + color: #fff; + float: left; + text-overflow: ellipsis; + overflow: hidden; + position: relative; + white-space: nowrap; + width: 205px; + margin-top: 2px; + margin-left: 0px; + margin-bottom: 20px; +} +.show-seasons a:hover { + color: #F9AA03; +} .season-episodes-wrapper { } .season-episodes-instance { list-style: none; - margin: 0 0 0px 0px; + margin: 0; } .season-episodes-instance li { float: left; position: relative; left: 0px; margin-right: 25px; + margin-bottom: 25px; } a .season-episodes-card-overlay:hover { webkit-box-shadow: inset 0 0 0 2px #e9a049; @@ -1059,11 +1144,13 @@ a .season-episodes-card-overlay:hover { font-size: 11px; text-shadow: 0 1px 5px rgba(0,0,0,0.2); } -.season-episodes-instance-text-wrapper { - width: 250px; - font-size: 13px; - margin-bottom: 20px; - clear: both; +.season-episodes-overlay-text { + color: #aaa; + font-size: 12px; + float: left; + position: absolute; + left: 8px; + bottom: 5px; } .season-episodes-instance-text-wrapper h3 { padding: 5px 3px 0 3px; @@ -1098,14 +1185,6 @@ a .season-episodes-card-overlay:hover { .season-episodes a:hover { color: #F9AA03; } -.season-episodes-season { - color: #aaa; - font-size: 12px; - float: left; - position: absolute; - left: 8px; - bottom: 5px; -} .user-info-wrapper { height: 113px; } diff --git a/data/interfaces/default/info.html b/data/interfaces/default/info.html index 078d1670..c2cacf2a 100644 --- a/data/interfaces/default/info.html +++ b/data/interfaces/default/info.html @@ -197,7 +197,30 @@ DOCUMENTATION :: END % endif
    - % if data['type'] == 'movie' or data['type'] == 'episode' or data['type'] == 'show': + % if data['type'] == 'show': +
    +
    +
    + Season List for ${data['title']} +
    +
    +
    +
    +
    +
    + % elif data['type'] == 'season': +
    +
    +
    + Episode List for ${data['title']} +
    +
    +
    +
    +
    +
    + % endif + % if data['type'] == 'movie' or data['type'] == 'episode' or data['type'] == 'show' or data['type'] == 'season':
    @@ -233,18 +256,7 @@ DOCUMENTATION :: END
    - % elif data['type'] == 'season': -
    -
    -
    - Episode List for ${data['title']} -
    -
    -
    -
    -
    - % endif -
    + % endif
    @@ -288,28 +300,27 @@ DOCUMENTATION :: END type: 'post', data: function ( d ) { return { 'json_data': JSON.stringify( d ), - 'rating_key': ${data['rating_key']} - }; - } - } + 'rating_key': ${data['rating_key']} }; + } + } history_table = $('#history_table').DataTable(history_table_options); - var colvis = new $.fn.dataTable.ColVis(history_table, { buttonText: ' Select columns', buttonClass: 'btn btn-dark', exclude: [0, 10] }); - $(colvis.button()).appendTo('div.colvis-button-bar'); + var colvis = new $.fn.dataTable.ColVis(history_table, { buttonText: ' Select columns', buttonClass: 'btn btn-dark', exclude: [0, 10] }); + $(colvis.button()).appendTo('div.colvis-button-bar'); - clearSearchButton('history_table', history_table); + clearSearchButton('history_table', history_table); - $('#row-edit-mode').click(function() { - if ($(this).hasClass('active')) { - $('.delete-control').each(function() { - $(this).addClass('hidden'); - }); - } else { - $('.delete-control').each(function() { - $(this).removeClass('hidden'); - }); - } - }); + $('#row-edit-mode').click(function() { + if ($(this).hasClass('active')) { + $('.delete-control').each(function() { + $(this).addClass('hidden'); + }); + } else { + $('.delete-control').each(function() { + $(this).removeClass('hidden'); + }); + } + }); }); % elif data['type'] == 'show': @@ -321,40 +332,75 @@ DOCUMENTATION :: END type: 'post', data: function ( d ) { return { 'json_data': JSON.stringify( d ), - 'grandparent_rating_key': ${data['rating_key']} - }; - } - } - history_table = $('#history_table').DataTable(history_table_options); - var colvis = new $.fn.dataTable.ColVis(history_table, { buttonText: ' Select columns', buttonClass: 'btn btn-dark', exclude: [0, 10] }); - $(colvis.button()).appendTo('div.colvis-button-bar'); - - clearSearchButton('history_table', history_table); - - $('#row-edit-mode').click(function() { - if ($(this).hasClass('active')) { - $('.delete-control').each(function() { - $(this).addClass('hidden'); - }); - } else { - $('.delete-control').each(function() { - $(this).removeClass('hidden'); - }); + 'grandparent_rating_key': ${data['rating_key']} }; + } } + history_table = $('#history_table').DataTable(history_table_options); + var colvis = new $.fn.dataTable.ColVis(history_table, { buttonText: ' Select columns', buttonClass: 'btn btn-dark', exclude: [0, 10] }); + $(colvis.button()).appendTo('div.colvis-button-bar'); + + clearSearchButton('history_table', history_table); + + $('#row-edit-mode').click(function() { + if ($(this).hasClass('active')) { + $('.delete-control').each(function() { + $(this).addClass('hidden'); + }); + } else { + $('.delete-control').each(function() { + $(this).removeClass('hidden'); + }); + } + }); }); + $.ajax({ + url: 'get_show_children', + type: "GET", + async: true, + data: { rating_key : ${data['rating_key']} }, + complete: function(xhr, status) { + $("#season-list").html(xhr.responseText); } }); % endif % if data['type'] == 'season': + % endif diff --git a/data/interfaces/default/info_episode_list.html b/data/interfaces/default/info_episode_list.html index ef02c42c..11651969 100644 --- a/data/interfaces/default/info_episode_list.html +++ b/data/interfaces/default/info_episode_list.html @@ -34,7 +34,7 @@ DOCUMENTATION :: END
    -
    +
    Episode ${a['index']}
    diff --git a/data/interfaces/default/info_season_list.html b/data/interfaces/default/info_season_list.html new file mode 100644 index 00000000..6da68e0e --- /dev/null +++ b/data/interfaces/default/info_season_list.html @@ -0,0 +1,55 @@ +<%doc> +USAGE DOCUMENTATION :: PLEASE LEAVE THIS AT THE TOP OF THIS FILE + +For Mako templating syntax documentation please visit: http://docs.makotemplates.org/en/latest/ + +Filename: info_season_list.html +Version: 0.1 +Variable names: data [list] + +data :: Usable parameters + +== Global keys == +season_count Returns the number of seasons in the array. +season_list Returns an array of seasons. + +data['season_list'] :: Usable paramaters + +== Global keys == +rating_key Returns the unique identifier for the media item. +thumb Returns the location of the item's thumbnail. Use with pms_image_proxy. +title Returns the name of the season. +index Returns the season number. + +DOCUMENTATION :: END + + +% if data != None: +% if data['season_count'] > 0: +
    + +
    +% endif +% endif \ No newline at end of file diff --git a/plexpy/datafactory.py b/plexpy/datafactory.py index b60c654b..e052a1f7 100644 --- a/plexpy/datafactory.py +++ b/plexpy/datafactory.py @@ -55,6 +55,7 @@ class DataFactory(object): (CASE WHEN session_history_metadata.duration IS NULL THEN 1.0 ELSE \ session_history_metadata.duration * 1.0 END) * 100) as percent_complete', 'session_history.grandparent_rating_key as grandparent_rating_key', + 'session_history.parent_rating_key as parent_rating_key', 'session_history.rating_key as rating_key', 'session_history.user', 'session_history_metadata.media_type', @@ -112,6 +113,7 @@ class DataFactory(object): "duration": item["duration"], "percent_complete": item["percent_complete"], "grandparent_rating_key": item["grandparent_rating_key"], + "parent_rating_key": item["parent_rating_key"], "rating_key": item["rating_key"], "user": item["user"], "media_type": item["media_type"], diff --git a/plexpy/pmsconnect.py b/plexpy/pmsconnect.py index 7fb15618..dba5b0b8 100644 --- a/plexpy/pmsconnect.py +++ b/plexpy/pmsconnect.py @@ -89,6 +89,23 @@ class PmsConnect(object): return request + """ + Return list of seasons in requested show. + + Parameters required: rating_key { ratingKey of parent } + Optional parameters: output_format { dict, json } + + Output: array + """ + def get_season_list(self, rating_key='', output_format=''): + uri = '/library/metadata/' + rating_key + '/children' + request = self.request_handler.make_request(uri=uri, + proto=self.protocol, + request_type='GET', + output_format=output_format) + + return request + """ Return list of episodes in requested season. @@ -103,7 +120,7 @@ class PmsConnect(object): proto=self.protocol, request_type='GET', output_format=output_format) - + return request """ @@ -798,6 +815,49 @@ class PmsConnect(object): return session_output + """ + Return processed and validated season list. + + Output: array + """ + def get_show_children(self, rating_key=''): + season_data = self.get_season_list(rating_key, output_format='xml') + + try: + xml_head = season_data.getElementsByTagName('MediaContainer') + except: + logger.warn("Unable to parse XML for get_season_list.") + return [] + + season_list = [] + + for a in xml_head: + if a.getAttribute('size'): + if a.getAttribute('size') == '0': + logger.debug(u"No season data.") + episode_list = {'season_count': '0', + 'season_list': [] + } + return season_list + + if a.getElementsByTagName('Directory'): + result_data = a.getElementsByTagName('Directory') + for result in result_data: + season_output = {'rating_key': helpers.get_xml_attr(result, 'ratingKey'), + 'index': helpers.get_xml_attr(result, 'index'), + 'title': helpers.get_xml_attr(result, 'title'), + 'thumb': helpers.get_xml_attr(result, 'thumb'), + 'parent_thumb': helpers.get_xml_attr(a, 'thumb') + } + season_list.append(season_output) + + output = {'season_count': helpers.get_xml_attr(xml_head[0], 'size'), + 'title': helpers.get_xml_attr(xml_head[0], 'title2'), + 'season_list': season_list + } + + return output + """ Return processed and validated episode list. diff --git a/plexpy/webserve.py b/plexpy/webserve.py index 54fc8d9f..92c378d0 100644 --- a/plexpy/webserve.py +++ b/plexpy/webserve.py @@ -1,4 +1,4 @@ -# This file is part of PlexPy. +# This file is part of PlexPy. # # PlexPy is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -567,6 +567,9 @@ class WebInterface(object): if 'rating_key' in kwargs: rating_key = kwargs.get('rating_key', "") custom_where = [['rating_key', rating_key]] + if 'parent_rating_key' in kwargs: + rating_key = kwargs.get('parent_rating_key', "") + custom_where = [['parent_rating_key', rating_key]] if 'grandparent_rating_key' in kwargs: rating_key = kwargs.get('grandparent_rating_key', "") custom_where = [['grandparent_rating_key', rating_key]] @@ -804,7 +807,19 @@ class WebInterface(object): return serve_template(templatename="user_platform_stats.html", data=None, title="Platform Stats") @cherrypy.expose - def get_children(self, rating_key='', **kwargs): + def get_show_children(self, rating_key='', **kwargs): + + pms_connect = pmsconnect.PmsConnect() + result = pms_connect.get_show_children(rating_key) + + if result: + return serve_template(templatename="info_season_list.html", data=result, title="Season List") + else: + logger.warn('Unable to retrieve data.') + return serve_template(templatename="info_season_list.html", data=None, title="Season List") + + @cherrypy.expose + def get_season_children(self, rating_key='', **kwargs): pms_connect = pmsconnect.PmsConnect() result = pms_connect.get_season_children(rating_key) From 7bbcc575b045c08da5db0a9ac26aeba68028da75 Mon Sep 17 00:00:00 2001 From: Jonathan Wong Date: Thu, 27 Aug 2015 12:50:31 -0700 Subject: [PATCH 25/54] Style current activity to match Plex/Web --- data/interfaces/default/css/plexpy.css | 309 +++++++++++------- data/interfaces/default/current_activity.html | 223 ++++++------- plexpy/pmsconnect.py | 7 + 3 files changed, 305 insertions(+), 234 deletions(-) diff --git a/data/interfaces/default/css/plexpy.css b/data/interfaces/default/css/plexpy.css index 97d2b46e..6df761b6 100644 --- a/data/interfaces/default/css/plexpy.css +++ b/data/interfaces/default/css/plexpy.css @@ -84,14 +84,6 @@ img { outline:0px !important; -webkit-appearance:none; } -.btn-activity-info, -.btn-activity-info:focus { - color: #aaa; - background-color: rgba(0,0,0,0.5); -} -.btn-activity-info:hover { - color: #eee; -} .btn-dark { color: #d7d7d7; background-color: #3B3B3B; @@ -484,117 +476,183 @@ a .users-poster-face:hover { float: left; } .dashboard-instance { - padding-bottom: 15px; float: left; position: relative; - margin-right: 27px; -} -.dashboard-poster { - float: left; - min-width: 150px; - margin-bottom: 8px; - position: relative; + height: 260px; + width: 350px; + margin-right: 25px; + margin-bottom: 25px; } .dashboard-activity-poster { - height: 141px; - width: 250px; + height: 200px; + width: 350px; position: relative; - top: 0px; - background-color: rgba(255,255,255,.03); - margin-bottom: 11px; - text-align: center; overflow: hidden; } +a:hover .dashboard-activity-poster { + -webkit-box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 2px #e9a049; + -moz-box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 2px #e9a049; + box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 2px #e9a049; +} .dashboard-activity-poster-face { background-position: center; background-size: cover; - height: 140px; - width: 250px; - position: relative; + height: 200px; + width: 350px; + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; -webkit-box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); -moz-box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); - margin-bottom: 11px; - text-align: center; + z-index: -3; } .dashboard-activity-cover-face-bg { background-position: center; background-size: cover; background-repeat: no-repeat; - height: 140px; - width: 250px; - position: relative; + height: 200px; + width: 350px; + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; -webkit-box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); -moz-box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); - margin-bottom: 11px; - text-align: center; -webkit-filter: blur(5px); -moz-filter: blur(5px); filter: blur(5px); opacity: 0.75; + z-index: -3; } .dashboard-activity-cover-face { background-position: center; background-size: cover; background-repeat: no-repeat; - height: 140px; - width: 140px; - position: relative; + height: 200px; + width: 200px; -webkit-box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); -moz-box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); - margin-bottom: 11px; - text-align: center; position: absolute; top: 0; - left: 55px; + left: 77px; + z-index: -3; } .dashboard-activity-clip-face-bg { background-position: center; background-size: cover; background-repeat: no-repeat; - height: 140px; - width: 250px; - position: relative; + height: 200px; + width: 350px; + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; -webkit-box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); -moz-box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); - margin-bottom: 11px; - text-align: center; -webkit-filter: blur(5px); -moz-filter: blur(5px); filter: blur(5px); opacity: 0.75; + z-index: -3; } .dashboard-activity-clip-face { background-position: center; background-size: cover; background-repeat: no-repeat; - height: 140px; - width: 94px; + height: 200px; + width: 130px; position: relative; -webkit-box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); -moz-box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); - margin-bottom: 11px; - text-align: center; position: absolute; top: 0; - left: 78px; + left: 110px; + z-index: -3; +} +.dashboard-activity-info-details-overlay { + text-align: left; + background-image: -webkit-gradient(linear,left 0,left 100%,from(rgba(0,0,0,.6)),to(rgba(0,0,0,.8))); + background-image: -webkit-linear-gradient(top,rgba(0,0,0,.6),0,rgba(0,0,0,.8),100%); + background-image: -moz-linear-gradient(top,rgba(0,0,0,.6) 0,rgba(0,0,0,.8) 100%); + background-image: linear-gradient(to bottom,rgba(0,0,0,.6) 0,rgba(0,0,0,.8) 100%); + background-repeat: repeat-x; + position: absolute; + top: 0; + width: 100%; + height: 100%; + display: none; + z-index: -1; +} +.dashboard-activity-button-info { + width: 50px; + height: 50px; + position: absolute; + top: 10px; + right: 10px; + z-index: 1; +} +.btn-activity-info, +.btn-activity-info:focus { + color: #999; + background-color: rgba(0,0,0,0.5); +} +.btn-activity-info:hover { + color: #fff; +} +.dashboard-activity-info-details-content { + height: 200px; + width: 350px; + line-height: 25px; + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + color: #aaa; + padding: 10px; +} +.dashboard-activity-info-details-content strong { + color: #fff; + font-weight: normal; +} +.dashboard-activity-info-platform { + width: 100%; + height: 50px; + margin-bottom: 10px; +} +.dashboard-activity-info-platform-box { + float: left; + background-position: center; + background-size: cover; + width: 50px; + height: 50px; + -webkit-border-radius: 3px; + -moz-border-radius: 3px; + border-radius: 3px; + margin-right: 10px; } .dashboard-activity-poster-info-bar { position:absolute; - color: #F9AA03; - bottom: 0; - width:100%; - height: 100px; - background: -moz-linear-gradient(top, rgba(0,0,0,0) 30%, rgba(0,0,0,1) 100%); - background: -webkit-gradient(linear, left top, left bottom, color-stop(30%,rgba(0,0,0,0)), color-stop(100%,rgba(0,0,0,1))); - background: -webkit-linear-gradient(top, rgba(0,0,0,0) 30%,rgba(0,0,0,1) 100%); - background: -o-linear-gradient(top, rgba(0,0,0,0) 30%,rgba(0,0,0,1) 100%); - background: -ms-linear-gradient(top, rgba(0,0,0,0) 30%,rgba(0,0,0,1) 100%); - background: linear-gradient(to bottom, rgba(0,0,0,0) 30%,rgba(0,0,0,1) 100%); + width: 100%; + height: 100%; + background-image: -webkit-gradient(linear,left 30%,left 100%,from(rgba(0,0,0,0)),to(rgba(0,0,0,0.75))); + background-image: -webkit-linear-gradient(top,rgba(0,0,0,0),30%,rgba(0,0,0,0.75),100%); + background-image: -moz-linear-gradient(top,rgba(0,0,0,0) 30%,rgba(0,0,0,0.75) 100%); + background-image: linear-gradient(to bottom,rgba(0,0,0,0) 30%,rgba(0,0,0,0.75) 100%); + opacity: 0; + -webkit-transition: all .2s; + transition: all .2s; + z-index: -2; +} +.dashboard-activity-poster:hover .dashboard-activity-poster-info-bar { + opacity: 1; } .dashboard-activity-poster-info-text { position: absolute; @@ -612,23 +670,28 @@ a .users-poster-face:hover { } .dashboard-activity-poster-info-time { position: absolute; - bottom: 0; - right: 0; - float: right; + bottom: 5px; + right: 10px; text-align: right; - padding: 5px 8px; font-size: 12px; color: #eee; } .dashboard-activity-progress { - overflow: hidden; - height: 4px; - background-color: #111; - float: left; width: 100%; - margin: 0 10px 5px 0; + height: 5px; + margin-bottom: 5px; +} +.dashboard-activity-progress-bar { + overflow: hidden; + height: 5px; + background-color: #111; + width: 100%; + z-index: -1; + -webkit-transition: all 0s; + transition: all 0s; } .dashboard-activity-progress .bar { + padding-top: 6px; background-color: #faa732; background-image: -moz-linear-gradient(top, #fbb450, #f89406); background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#fbb450), to(#f89406)); @@ -638,90 +701,88 @@ a .users-poster-face:hover { background-repeat: repeat-x; filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffbb450', endColorstr='#fff89406', GradientType=0); } -.dashboard-activity-button-info { - position: absolute; - top: 3px; - left: 5px; - z-index: 1; -} .dashboard-activity-metadata-wrapper { position: relative; - left: 0px; - width: 250px; + width: 350px; + height: 50px; font-size: 13px; -} -.dashboard-activity-metadata-user { - text-overflow: ellipsis; - overflow: hidden; - white-space: nowrap; - width: 80%; - margin-right: 5px; - float: left; -} -.dashboard-activity-metadata-user a { - color: #F9AA03; -} -.dashboard-activity-metadata-user a:hover { - color: #FFCD62; + padding: 0 3px; } .dashboard-activity-metadata-title { text-overflow: ellipsis; overflow: hidden; white-space: nowrap; font-size: 14px; - width: 80%; - margin-right: 5px; + line-height: 25px; color: #fff; + max-width: 300px; +} +.dashboard-activity-metadata-subtitle { + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + font-size: 14px; + line-height: 25px; + color: #999; + max-width: 172px; float: left; } +.dashboard-activity-metadata-user { + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + font-size: 14px; + line-height: 25px; + color: #999; + text-align: right; + max-width: 122px; + float: right; +} +.dashboard-activity-metadata-user-thumb { + background-position: center; + background-size: cover; + margin-top: 5px; + margin-left: 10px; + height: 40px; + width: 40px; + position: relative; + -webkit-border-radius: 50%; + -moz-border-radius: 50%; + border-radius: 50%; + -webkit-box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); + -moz-box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); + box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); + overflow: hidden; + float: right; +} +a .dashboard-activity-metadata-user-thumb:hover { + -webkit-box-shadow: inset 0 0 0 2px #e9a049; + -moz-box-shadow: inset 0 0 0 2px #e9a049; + box-shadow: inset 0 0 0 2px #e9a049; +} +.dashboard-activity-metadata-user a { + color: #999; +} +.dashboard-activity-metadata-user a:hover { + color: #F9AA03; +} .dashboard-activity-metadata-title a:hover { color: #F9AA03; } -.dashboard-activity-metadata-platform { - float: right; -} -.dashboard-activity-metadata-platform-box { - background-position: center; - background-size: cover; - width: 40px; - height: 40px; -} .dashboard-activity-metadata-progress-wrapper { margin-bottom: 20px; font-size: 12px; font-weight: bold; color: #F9AA03; } -.dashboard-activity-metadata-progress-minutes { - width: 100%; -} -.dashboard-activity-instance-overlay { +/*.dashboard-activity-instance-overlay { position: relative; top: -12px; text-align: left; height: 53px; width: 250px; border-radius: 0 0 3px 3px; -} -.dashboard-activity-info-details-overlay { - text-align: left; - background-color: rgba(0,0,0,0.5); - width: 250px; - padding: 5px 0px 5px 0px; -} -.dashboard-activity-info-details-content { - margin-left: 5px; - margin-right: 5px; - width: 245px; - text-overflow: ellipsis; - overflow: hidden; - white-space: nowrap; - color: #999; -} -.dashboard-activity-info-details-content strong { - color: #fff; - font-weight: normal; -} +}*/ .dashboard-recent-media-row { width: 100%; margin:0 auto; diff --git a/data/interfaces/default/current_activity.html b/data/interfaces/default/current_activity.html index ef401039..92d28b0e 100644 --- a/data/interfaces/default/current_activity.html +++ b/data/interfaces/default/current_activity.html @@ -28,8 +28,10 @@ user Returns the name of the user owning the session. user_id Returns the Plex user id if available. machine_id Returns the machine id of the players being used. friendly_name Returns the friendlly name of the user owning the session. +user_thumb Returns the profile picture of the user owning the session. state Returns the state of the current session. Either 'playing', 'paused' or 'buffering'. title Returns the name of the episode, movie or music track. +year Returns the year of the episode, movie, or clip. player Returns the name of the platform used to play the stream. platform Returns the type of platform used to play the stream. audio_decision Returns the audio transcode decision. Either 'transcode', 'copy' or 'direct play'. @@ -62,132 +64,137 @@ DOCUMENTATION :: END % if data['stream_count'] != '0': % for a in data['sessions']:
    -
    + % if a['type'] == 'movie' or a['type'] == 'episode': + + % endif
    % if a['type'] == 'movie' and not a['indexes']:
    % elif a['indexes']: % else: - % if a['type'] == 'track': + % if a['type'] == 'episode': +
    + % elif a['type'] == 'track':
    % elif a['type'] == 'clip':
    % else: -
    +
    % endif % endif -
    -
    +
    + +
    +
    +
    +
    + ${a['player']}
    + % if a['state'] == 'playing': + State  Playing + % elif a['state'] == 'paused': + State  Paused + % elif a['state'] == 'buffering': + State  Buffering + % endif +
    % if a['type'] == 'track': - Track ${a['media_index']} - % elif a['type'] == 'episode': - Season ${a['parent_media_index']}, Episode ${a['media_index']} - % else: - ${a['title']} + % if a['audio_decision'] == 'direct play': + Stream  Direct Play + % else: + Stream  Transcoding + % endif +
    + % if a['audio_decision'] == 'direct play': + Audio  Direct Play (${a['audio_codec']}) (${a['audio_channels']}ch) + % elif a['audio_decision'] == 'Copy': + Audio  Direct Stream (${a['transcode_audio_codec']}) (${a['transcode_audio_channels']}ch) + % elif a['audio_decision'] != 'transcode': + Audio  Transcode (${a['transcode_audio_codec']}) (${a['transcode_audio_channels']}ch) + % endif + % elif a['type'] == 'episode' or a['type'] == 'movie' or a['type'] == 'clip': + % if a['video_decision'] == 'direct play': + Stream  Direct Play + % else: + Stream  Transcoding + % endif +
    + % if a['video_decision'] == 'direct play': + Video  Direct Play (${a['video_codec']}) (${a['width']}x${a['height']}) + % elif a['video_decision'] == 'copy': + Video  Direct Stream (${a['transcode_video_codec']}) (${a['width']}x${a['height']}) + % elif a['video_decision'] == 'transcode': + Video  Transcode (${a['transcode_video_codec']}) (${a['width']}x${a['height']}) + % endif +
    + % if a['audio_decision'] == 'direct play': + Audio  Direct Play (${a['audio_codec']}) (${a['audio_channels']}ch) + % elif a['audio_decision'] == 'copy': + Audio  Direct Stream (${a['transcode_audio_codec']}) (${a['transcode_audio_channels']}ch) + % elif a['audio_decision'] == 'transcode': + Audio  Transcode (${a['transcode_audio_codec']}) (${a['transcode_audio_channels']}ch) + % endif % endif +
    +
    +
    ${a['progress']}/${a['duration']}
    -
    % endfor @@ -201,20 +208,16 @@ DOCUMENTATION :: END $(this).html(millisecondsToMinutes($(this).text(), false)); }); - // Hide the info bar on page load - $('.dashboard-activity-poster-info-bar').hide(); - - // When mouse over the activity pane, show an info bar with extra info. - $('.dashboard-activity-poster').hover(function() { - $('.dashboard-activity-poster-info-bar', this).stop().slideDown('fast'); - }, function() { - $('.dashboard-activity-poster-info-bar', this).stop().slideUp('fast'); - }); - // Tooltips $('.dashboard-activity-metadata-platform').each(function() { $(this).tooltip(); }); + + // Show/Hide activity info + $('.btn-activity-info').on('click', function(e) { + e.preventDefault(); + $($(this).attr('data-target')).toggle(); + }); % else:
    Nothing is currently being watched.

    diff --git a/plexpy/pmsconnect.py b/plexpy/pmsconnect.py index 7fb15618..aa9d79d3 100644 --- a/plexpy/pmsconnect.py +++ b/plexpy/pmsconnect.py @@ -555,6 +555,7 @@ class PmsConnect(object): 'user': user_details['username'], 'user_id': user_details['user_id'], 'friendly_name': user_details['friendly_name'], + 'user_thumb': user_details['thumb'], 'player': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'title'), 'platform': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'platform'), 'machine_id': machine_id, @@ -664,6 +665,7 @@ class PmsConnect(object): 'user': user_details['username'], 'user_id': user_details['user_id'], 'friendly_name': user_details['friendly_name'], + 'user_thumb': user_details['thumb'], 'player': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'title'), 'platform': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'platform'), 'machine_id': machine_id, @@ -671,6 +673,7 @@ class PmsConnect(object): 'grandparent_title': helpers.get_xml_attr(session, 'grandparentTitle'), 'parent_title': helpers.get_xml_attr(session, 'parentTitle'), 'title': helpers.get_xml_attr(session, 'title'), + 'year': helpers.get_xml_attr(session, 'year'), 'rating_key': helpers.get_xml_attr(session, 'ratingKey'), 'parent_rating_key': helpers.get_xml_attr(session, 'parentRatingKey'), 'grandparent_rating_key': helpers.get_xml_attr(session, 'grandparentRatingKey'), @@ -711,6 +714,7 @@ class PmsConnect(object): 'user': user_details['username'], 'user_id': user_details['user_id'], 'friendly_name': user_details['friendly_name'], + 'user_thumb': user_details['thumb'], 'player': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'title'), 'platform': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'platform'), 'machine_id': machine_id, @@ -718,6 +722,7 @@ class PmsConnect(object): 'grandparent_title': helpers.get_xml_attr(session, 'grandparentTitle'), 'parent_title': helpers.get_xml_attr(session, 'parentTitle'), 'title': helpers.get_xml_attr(session, 'title'), + 'year': helpers.get_xml_attr(session, 'year'), 'rating_key': helpers.get_xml_attr(session, 'ratingKey'), 'parent_rating_key': helpers.get_xml_attr(session, 'parentRatingKey'), 'grandparent_rating_key': helpers.get_xml_attr(session, 'grandparentRatingKey'), @@ -758,6 +763,7 @@ class PmsConnect(object): 'user': user_details['username'], 'user_id': user_details['user_id'], 'friendly_name': user_details['friendly_name'], + 'user_thumb': user_details['thumb'], 'player': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'title'), 'platform': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'platform'), 'machine_id': machine_id, @@ -765,6 +771,7 @@ class PmsConnect(object): 'grandparent_title': helpers.get_xml_attr(session, 'grandparentTitle'), 'parent_title': helpers.get_xml_attr(session, 'parentTitle'), 'title': helpers.get_xml_attr(session, 'title'), + 'year': helpers.get_xml_attr(session, 'year'), 'rating_key': helpers.get_xml_attr(session, 'ratingKey'), 'parent_rating_key': helpers.get_xml_attr(session, 'parentRatingKey'), 'grandparent_rating_key': helpers.get_xml_attr(session, 'grandparentRatingKey'), From 99dd133a9a1bacb40af57a14aa8c3e53b85309e6 Mon Sep 17 00:00:00 2001 From: Jonathan Wong Date: Thu, 27 Aug 2015 13:00:40 -0700 Subject: [PATCH 26/54] Increase home stats card width to match dashboard activity --- data/interfaces/default/css/plexpy.css | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/data/interfaces/default/css/plexpy.css b/data/interfaces/default/css/plexpy.css index 6df761b6..c8cbef88 100644 --- a/data/interfaces/default/css/plexpy.css +++ b/data/interfaces/default/css/plexpy.css @@ -1472,7 +1472,7 @@ a .season-episodes-card-overlay:hover { background-color: #282828; position: relative; float: left; - width: 300px; + width: 330px; padding: 10px; margin-right: 20px; margin-bottom: 20px; @@ -1746,7 +1746,7 @@ a .home-platforms-instance-list-oval:hover, } .home-platforms-instance .slider { background-color: #2d2d2d; - width: 320px; + width: 350px; display: none; position: absolute; top: 128px; @@ -1755,7 +1755,7 @@ a .home-platforms-instance-list-oval:hover, -moz-box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); z-index: 1; - clip: rect(1px, 324px, 1000px, -2px); + clip: rect(1px, 354px, 1000px, -2px); } .history-table-title { text-overflow: ellipsis; From 0f10c21a66d288f4dceda2b3452c68881fc9dfbe Mon Sep 17 00:00:00 2001 From: Jonathan Wong Date: Wed, 26 Aug 2015 17:56:17 -0700 Subject: [PATCH 27/54] Change alert and button color on info and user history tables --- data/interfaces/default/history.html | 2 +- data/interfaces/default/info.html | 34 +++++++++++++++++++++++++--- data/interfaces/default/user.html | 17 +++++++------- 3 files changed, 40 insertions(+), 13 deletions(-) diff --git a/data/interfaces/default/history.html b/data/interfaces/default/history.html index c7960d20..addf5e7c 100644 --- a/data/interfaces/default/history.html +++ b/data/interfaces/default/history.html @@ -103,7 +103,7 @@ data: { row_id: history_to_delete[i] }, async: true, success: function (data) { - var msg = "User history purged"; + var msg = "History deleted"; showMsg(msg, false, true, 2000); } }); diff --git a/data/interfaces/default/info.html b/data/interfaces/default/info.html index c2cacf2a..440781f8 100644 --- a/data/interfaces/default/info.html +++ b/data/interfaces/default/info.html @@ -227,8 +227,11 @@ DOCUMENTATION :: END Watch History for ${data['title']}
    -   + +
    @@ -341,12 +344,37 @@ DOCUMENTATION :: END clearSearchButton('history_table', history_table); - $('#row-edit-mode').click(function() { + $('#row-edit-mode').on('click', function() { + $('#row-edit-mode-alert').fadeIn(200); + if ($(this).hasClass('active')) { - $('.delete-control').each(function() { + if (history_to_delete.length > 0) { + $('#deleteCount').text(history_to_delete.length); + $('#confirm-modal').modal(); + $('#confirm-modal').one('click', '#confirm-delete', function () { + for (var i = 0; i < history_to_delete.length; i++) { + $.ajax({ + url: 'delete_history_rows', + data: { row_id: history_to_delete[i] }, + async: true, + success: function (data) { + var msg = "History deleted"; + showMsg(msg, false, true, 2000); + } + }); + } + history_table.draw(); + }); + } + + $('.delete-control').each(function () { + $(this).find('button.btn-danger').toggleClass('btn-warning').toggleClass('btn-danger'); $(this).addClass('hidden'); + $('#row-edit-mode-alert').fadeOut(200); }); + } else { + history_to_delete = []; $('.delete-control').each(function() { $(this).removeClass('hidden'); }); diff --git a/data/interfaces/default/user.html b/data/interfaces/default/user.html index c2176a10..65a43548 100644 --- a/data/interfaces/default/user.html +++ b/data/interfaces/default/user.html @@ -154,13 +154,11 @@ from plexpy import helpers
    - -   - - + + +
    @@ -390,7 +388,7 @@ from plexpy import helpers }); $('#row-edit-mode').on('click', function() { - $('#delete-message').popover(); + $('#row-edit-mode-alert').fadeIn(200); if ($(this).hasClass('active')) { if (history_to_delete.length > 0) { @@ -403,7 +401,7 @@ from plexpy import helpers data: { row_id: history_to_delete[i] }, async: true, success: function (data) { - var msg = "User history purged"; + var msg = "History deleted"; showMsg(msg, false, true, 2000); } }); @@ -415,6 +413,7 @@ from plexpy import helpers $('.delete-control').each(function () { $(this).find('button.btn-danger').toggleClass('btn-warning').toggleClass('btn-danger'); $(this).addClass('hidden'); + $('#row-edit-mode-alert').fadeOut(200); }); } else { From e41fa7a08e4bd2d8db42fce3aa46793a014e0830 Mon Sep 17 00:00:00 2001 From: Jonathan Wong Date: Wed, 26 Aug 2015 18:18:40 -0700 Subject: [PATCH 28/54] Make sure buttons are cleared when entering delete mode --- data/interfaces/default/history.html | 2 +- data/interfaces/default/info.html | 2 +- data/interfaces/default/user.html | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/data/interfaces/default/history.html b/data/interfaces/default/history.html index addf5e7c..0615ea28 100644 --- a/data/interfaces/default/history.html +++ b/data/interfaces/default/history.html @@ -113,7 +113,6 @@ } $('.delete-control').each(function () { - $(this).find('button.btn-danger').toggleClass('btn-warning').toggleClass('btn-danger'); $(this).addClass('hidden'); $('#row-edit-mode-alert').fadeOut(200); }); @@ -121,6 +120,7 @@ } else { history_to_delete = []; $('.delete-control').each(function() { + $(this).find('button.btn-danger').toggleClass('btn-warning').toggleClass('btn-danger'); $(this).removeClass('hidden'); }); } diff --git a/data/interfaces/default/info.html b/data/interfaces/default/info.html index 440781f8..46465a92 100644 --- a/data/interfaces/default/info.html +++ b/data/interfaces/default/info.html @@ -368,7 +368,6 @@ DOCUMENTATION :: END } $('.delete-control').each(function () { - $(this).find('button.btn-danger').toggleClass('btn-warning').toggleClass('btn-danger'); $(this).addClass('hidden'); $('#row-edit-mode-alert').fadeOut(200); }); @@ -376,6 +375,7 @@ DOCUMENTATION :: END } else { history_to_delete = []; $('.delete-control').each(function() { + $(this).find('button.btn-danger').toggleClass('btn-warning').toggleClass('btn-danger'); $(this).removeClass('hidden'); }); } diff --git a/data/interfaces/default/user.html b/data/interfaces/default/user.html index 65a43548..cc6b18a4 100644 --- a/data/interfaces/default/user.html +++ b/data/interfaces/default/user.html @@ -411,7 +411,6 @@ from plexpy import helpers } $('.delete-control').each(function () { - $(this).find('button.btn-danger').toggleClass('btn-warning').toggleClass('btn-danger'); $(this).addClass('hidden'); $('#row-edit-mode-alert').fadeOut(200); }); @@ -419,6 +418,7 @@ from plexpy import helpers } else { history_to_delete = []; $('.delete-control').each(function() { + $(this).find('button.btn-danger').toggleClass('btn-warning').toggleClass('btn-danger'); $(this).removeClass('hidden'); }); } From e7b1e177d27f2a67c30e5d7e265cc650bdc3f1be Mon Sep 17 00:00:00 2001 From: Jonathan Wong Date: Wed, 26 Aug 2015 18:20:29 -0700 Subject: [PATCH 29/54] Fix group purge on users table to remember state on table redraw * Changed popover to alert box * Changed button colour --- data/interfaces/default/css/plexpy.css | 3 +- data/interfaces/default/js/tables/users.js | 15 ++++-- data/interfaces/default/users.html | 55 +++++++--------------- 3 files changed, 31 insertions(+), 42 deletions(-) diff --git a/data/interfaces/default/css/plexpy.css b/data/interfaces/default/css/plexpy.css index ebbc2489..8b75c1db 100644 --- a/data/interfaces/default/css/plexpy.css +++ b/data/interfaces/default/css/plexpy.css @@ -1823,7 +1823,8 @@ a .home-platforms-instance-list-oval:hover, .button-bar { float: right; } -.colvis-button-bar { +.colvis-button-bar, +.refresh-users-button { float: right; } .nav-settings, diff --git a/data/interfaces/default/js/tables/users.js b/data/interfaces/default/js/tables/users.js index 8a5154df..0e9719db 100644 --- a/data/interfaces/default/js/tables/users.js +++ b/data/interfaces/default/js/tables/users.js @@ -1,3 +1,5 @@ +var users_to_purge = []; + users_list_table_options = { "language": { "search": "Search: ", @@ -187,6 +189,11 @@ users_list_table_options = { "preDrawCallback": function(settings) { var msg = "
     Fetching rows...
    "; showMsg(msg, false, false, 0) + }, + "rowCallback": function (row, rowData) { + if ($.inArray(rowData['user_id'], users_to_purge) !== -1) { + $(row).find('button[data-id="' + rowData['user_id'] + '"]').toggleClass('btn-warning').toggleClass('btn-danger'); + } } } @@ -271,9 +278,11 @@ $('#users_list_table').on('click', 'td.edit-control > .edit-user-toggles > butto var row = users_list_table.row(tr); var rowData = row.data(); - if ($(this).hasClass('active')) { - $(this).toggleClass('btn-warning').toggleClass('btn-danger'); + var index = $.inArray(rowData['user_id'], users_to_purge); + if (index === -1) { + users_to_purge.push(rowData['user_id']); } else { - $(this).toggleClass('btn-danger').toggleClass('btn-warning'); + users_to_purge.splice(index, 1); } + $(this).toggleClass('btn-warning').toggleClass('btn-danger'); }); \ No newline at end of file diff --git a/data/interfaces/default/users.html b/data/interfaces/default/users.html index df36a858..fe35f072 100644 --- a/data/interfaces/default/users.html +++ b/data/interfaces/default/users.html @@ -12,12 +12,11 @@ All Users
    - -   - - + +   +
    @@ -85,20 +84,19 @@ clearSearchButton('users_list_table', users_list_table); - var users_to_purge = []; $('#row-edit-mode').on('click', function () { - $('#purge-message').popover(); + $('#row-edit-mode-alert').fadeIn(200); + $('#users-to-delete').html(''); if ($(this).hasClass('active')) { - users_to_purge = []; - ul = $('#users-to-delete'); - ul.html(''); - $('.edit-control button.btn-danger').map(function () { - users_to_purge.push($(this).attr('data-id')); - ul.append('
  • ' + $('div[data-id=' + $(this).attr('data-id') + '] > input').val() + '
  • ') - }); if (users_to_purge.length > 0) { - $('#users-to-delete').append + $('.edit-control').each(function () { + $(this).find('button.btn-danger').toggleClass('btn-warning').toggleClass('btn-danger'); + }); + + for (var i = 0; i < users_to_purge.length; i++) { + $('#users-to-delete').append('
  • ' + $('div[data-id=' + users_to_purge[i] + '] > input').val() + '
  • '); + } $('#confirm-modal').modal(); $('#confirm-modal').one('click', '#confirm-purge', function () { for (var i = 0; i < users_to_purge.length; i++) { @@ -118,35 +116,16 @@ } $('.edit-control').each(function () { - $(this).find('button.btn-danger').toggleClass('btn-warning').toggleClass('btn-danger'); $(this).addClass('hidden'); - }); - $('.edit-user-control > .edit-user-name').each(function () { - a = $(this).children('a'); - input = $(this).children('input'); - a.text(input.val()); - a.removeClass('hidden'); - input.addClass('hidden'); + $('#row-edit-mode-alert').fadeOut(200); }); } else { + users_to_purge = []; $('.edit-control').each(function () { + $(this).find('button.btn-danger').toggleClass('btn-warning').toggleClass('btn-danger'); $(this).removeClass('hidden'); }); - $('.edit-user-control > .edit-user-name').each(function () { - $(this).children('a').addClass('hidden'); - $(this).children('input').removeClass('hidden'); - }); - - } - }); - - $(window).resize(function () { - if ($('.popover').popover().is(':visible')) { - var popover = $('.popover'); - popover.addClass("noTransition"); - $('#purge-message').popover('show'); - popover.removeClass("noTransition"); } }); }); From ab78b59f61e8aba3d1333f2af19add8deef4427b Mon Sep 17 00:00:00 2001 From: Jonathan Wong Date: Wed, 26 Aug 2015 19:36:08 -0700 Subject: [PATCH 30/54] Update graphs history table modal * Tooltips * Stream details modal --- data/interfaces/default/css/plexpy.css | 5 ++ .../default/history_table_modal.html | 29 +++++++- .../default/js/tables/history_table_modal.js | 67 ++++++++++++++++--- 3 files changed, 92 insertions(+), 9 deletions(-) diff --git a/data/interfaces/default/css/plexpy.css b/data/interfaces/default/css/plexpy.css index 8b75c1db..d473e6fc 100644 --- a/data/interfaces/default/css/plexpy.css +++ b/data/interfaces/default/css/plexpy.css @@ -282,6 +282,10 @@ fieldset[disabled] .btn-bright.active { .modal-body i.fa { color: #fff; } +.modal-body td:hover a .fa, +.modal-body a:focus i.fa { + color: #f9aa03; +} .modal-body strong { color: #F9AA03; } @@ -2043,6 +2047,7 @@ a .home-platforms-instance-list-oval:hover, } .history-title .popover.right { margin-left: 12px; + z-index: 5; } .history-title .popover.right .popover-content { padding: 5px 8px; diff --git a/data/interfaces/default/history_table_modal.html b/data/interfaces/default/history_table_modal.html index cfff340d..152589eb 100644 --- a/data/interfaces/default/history_table_modal.html +++ b/data/interfaces/default/history_table_modal.html @@ -27,7 +27,8 @@
    - + % else: diff --git a/data/interfaces/default/js/tables/history_table_modal.js b/data/interfaces/default/js/tables/history_table_modal.js index 1c684907..8a0afa22 100644 --- a/data/interfaces/default/js/tables/history_table_modal.js +++ b/data/interfaces/default/js/tables/history_table_modal.js @@ -74,6 +74,19 @@ history_table_modal_options = { { "targets": [3], "data":"player", + "createdCell": function (td, cellData, rowData, row, col) { + if (cellData !== '') { + var transcode_dec = ''; + if (rowData['video_decision'] === 'transcode') { + transcode_dec = ''; + } else if (rowData['video_decision'] === 'copy') { + transcode_dec = ''; + } else if (rowData['video_decision'] === 'direct play' || rowData['video_decision'] === '') { + transcode_dec = ''; + } + $(td).html(''); + } + }, "className": "no-wrap hidden-sm hidden-xs modal-control" }, { @@ -81,14 +94,21 @@ history_table_modal_options = { "data":"full_title", "createdCell": function (td, cellData, rowData, row, col) { if (cellData !== '') { - if (rowData['media_type'] === 'movie' || rowData['media_type'] === 'episode') { - var transcode_dec = ''; - if (rowData['video_decision'] === 'transcode') { - transcode_dec = ' '; - } - $(td).html('
    ' + transcode_dec + '
    '); + var media_type = ''; + var thumb_popover = ''; + if (rowData['media_type'] === 'movie') { + media_type = ''; + thumb_popover = '' + cellData + ' (' + rowData['year'] + ')' + $(td).html(''); + } else if (rowData['media_type'] === 'episode') { + media_type = ''; + thumb_popover = '' + cellData + ' \ + (S' + ('00' + rowData['parent_media_index']).slice(-2) + 'E' + ('00' + rowData['media_index']).slice(-2) + ')' + $(td).html(''); } else if (rowData['media_type'] === 'track') { - $(td).html('
    ' + cellData + '
    '); + media_type = ''; + thumb_popover = '' + cellData + ' (' + rowData['parent_title'] + ')' + $(td).html('
    ' + media_type + ' ' + thumb_popover + '
    '); } else { $(td).html('' + cellData + ''); } @@ -100,9 +120,40 @@ history_table_modal_options = { // Jump to top of page // $('html,body').scrollTop(0); $('#ajaxMsg').fadeOut(); + + // Create the tooltips. + $('.transcode-tooltip').tooltip(); + $('.media-type-tooltip').tooltip(); + $('.thumb-tooltip').popover({ + html: true, + trigger: 'hover', + placement: 'right', + content: function () { + return '
    '; + } + }); }, "preDrawCallback": function(settings) { var msg = "
     Fetching rows...
    "; showMsg(msg, false, false, 0) } -} \ No newline at end of file +} + +$('#history_table').on('click', 'td.modal-control', function () { + var tr = $(this).parents('tr'); + var row = history_table.row(tr); + var rowData = row.data(); + + function showStreamDetails() { + $.ajax({ + url: 'get_stream_data', + data: { row_id: rowData['id'], user: rowData['friendly_name'] }, + cache: false, + async: true, + complete: function (xhr, status) { + $("#info-modal").html(xhr.responseText); + } + }); + } + showStreamDetails(); +}); \ No newline at end of file From 16b1b2a781bcded1f7a27ad1b4519748619f6bb8 Mon Sep 17 00:00:00 2001 From: Jonathan Wong Date: Thu, 27 Aug 2015 14:41:12 -0700 Subject: [PATCH 31/54] Add episode number to info navbar --- data/interfaces/default/info.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/interfaces/default/info.html b/data/interfaces/default/info.html index 04185bf5..6b7eddbb 100644 --- a/data/interfaces/default/info.html +++ b/data/interfaces/default/info.html @@ -73,7 +73,7 @@ DOCUMENTATION :: END Season ${data['parent_index']} - ${data['title']} + Eipsode ${data['index']} - ${data['title']} % endif
    From 9fc44f793b8c958328fc6c7363e3db1625860469 Mon Sep 17 00:00:00 2001 From: Jonathan Wong Date: Thu, 27 Aug 2015 15:31:54 -0700 Subject: [PATCH 32/54] Change episodes default to art on dashboard --- data/interfaces/default/current_activity.html | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/data/interfaces/default/current_activity.html b/data/interfaces/default/current_activity.html index 92d28b0e..de2610e1 100644 --- a/data/interfaces/default/current_activity.html +++ b/data/interfaces/default/current_activity.html @@ -68,21 +68,22 @@ DOCUMENTATION :: END % endif
    + % if a['type'] == 'movie' and not a['indexes']:
    + % elif a['type'] == 'episode' and not a['indexes']: +
    % elif a['indexes']: - + % else: - % if a['type'] == 'episode': -
    - % elif a['type'] == 'track': + % if a['type'] == 'track':
    % elif a['type'] == 'clip':
    % else: -
    +
    % endif % endif
    @@ -208,11 +209,6 @@ DOCUMENTATION :: END $(this).html(millisecondsToMinutes($(this).text(), false)); }); - // Tooltips - $('.dashboard-activity-metadata-platform').each(function() { - $(this).tooltip(); - }); - // Show/Hide activity info $('.btn-activity-info').on('click', function(e) { e.preventDefault(); From a682cd31afa429a1772553b383096f4fd6769023 Mon Sep 17 00:00:00 2001 From: Jonathan Wong Date: Thu, 27 Aug 2015 15:38:39 -0700 Subject: [PATCH 33/54] Remove video fields from stream info modal if item is a track --- data/interfaces/default/stream_data.html | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/data/interfaces/default/stream_data.html b/data/interfaces/default/stream_data.html index 6aba69ec..c0cf86fd 100644 --- a/data/interfaces/default/stream_data.html +++ b/data/interfaces/default/stream_data.html @@ -54,6 +54,7 @@ DOCUMENTATION :: END

    Stream Details

    + % if data['media_type'] != 'track':
    Video
      % if data['transcode_video_dec'] != 'direct play': @@ -74,6 +75,7 @@ DOCUMENTATION :: END
    • Video Height: ${data['height']}
    • % endif
    + % endif
    Audio
      % if data['transcode_audio_dec'] != 'direct play': @@ -91,11 +93,14 @@ DOCUMENTATION :: END

      Media Source Details

      • Container: ${data['container']}
      • + % if data['media_type'] != 'track':
      • Resolution: ${data['height']}p
      • + % endif
      • Bitrate: ${data['bitrate']} kbps
    + % if data['media_type'] != 'track':

    Video Source Details

    • Width: ${data['width']}
    • @@ -104,6 +109,7 @@ DOCUMENTATION :: END
    • Video Frame Rate: ${data['video_framerate']}
    • Video Codec: ${data['video_codec']}
    + % endif

    Audio Source Details

    - + % elif top_stat['stat_id'] == 'last_watched' and top_stat['rows']: +
    +
  • +
    + +
    +
    + % if top_stat['rows'][0]['user_id']: + + % else: + + % endif + ${top_stat['rows'][0]['friendly_name']} + +
    +

    + + + - ${top_stat['rows'][0]['platform_type']} +

    +
    +
    + + % if top_stat['rows'][0]['thumb']: +
    +
    +
    + % else: +
    +
    +
    + % endif +
    + %if len(top_stat['rows']) > 1: +
    + + % endif +
  • +
    % endif % endfor diff --git a/data/interfaces/default/index.html b/data/interfaces/default/index.html index 8a31845c..9c1c361f 100644 --- a/data/interfaces/default/index.html +++ b/data/interfaces/default/index.html @@ -110,6 +110,17 @@ }); }); + var date_format = 'YYYY-MM-DD'; + var time_format = 'hh:mm a'; + $.ajax({ + url: 'get_date_formats', + type: 'GET', + success: function(data) { + date_format = data.date_format; + time_format = data.time_format; + } + }); + getHomeStats(${config['home_stats_length']}, ${config['home_stats_type']}, ${config['home_stats_count']}); diff --git a/plexpy/datafactory.py b/plexpy/datafactory.py index c7fed0d7..9607ed3c 100644 --- a/plexpy/datafactory.py +++ b/plexpy/datafactory.py @@ -143,7 +143,7 @@ class DataFactory(object): stat_count = '5' # This actually determines the output order in the home page - stats_queries = ["top_tv", "popular_tv", "top_movies", "popular_movies", "top_users", "top_platforms"] + stats_queries = ["top_tv", "popular_tv", "top_movies", "popular_movies", "top_users", "top_platforms", "last_watched"] home_stats = [] for stat in stats_queries: @@ -361,7 +361,7 @@ class DataFactory(object): 'total_plays': item[2], 'total_duration': item[3], 'last_play': item[4], - 'thumb': user_thumb, + 'user_thumb': user_thumb, 'grandparent_thumb': '', 'users_watched': '', 'rating_key': '', @@ -418,6 +418,59 @@ class DataFactory(object): 'stat_type': sort_type, 'rows': top_platform}) + elif 'last_watched' in stat: + last_watched = [] + try: + query = 'SELECT session_history_metadata.id, ' \ + 'session_history.user, ' \ + '(case when users.friendly_name is null then session_history.user else ' \ + 'users.friendly_name end) as friendly_name,' \ + 'users.user_id, ' \ + 'users.custom_avatar_url as user_thumb, ' \ + 'session_history_metadata.full_title, ' \ + 'session_history_metadata.rating_key, ' \ + 'session_history_metadata.thumb, ' \ + 'session_history_metadata.grandparent_thumb, ' \ + 'MAX(session_history.started) as last_watch, ' \ + 'session_history.player as platform ' \ + 'FROM session_history_metadata ' \ + 'JOIN session_history ON session_history_metadata.id = session_history.id ' \ + 'LEFT OUTER JOIN users ON session_history.user_id = users.user_id ' \ + 'WHERE datetime(session_history.stopped, "unixepoch", "localtime") ' \ + '>= datetime("now", "-%s days", "localtime") ' \ + 'AND (session_history_metadata.media_type = "movie" ' \ + 'OR session_history_metadata.media_type = "episode") ' \ + 'GROUP BY session_history_metadata.full_title ' \ + 'ORDER BY last_watch DESC ' \ + 'LIMIT %s' % (time_range, stat_count) + result = monitor_db.select(query) + except: + logger.warn("Unable to execute database query.") + return None + + for item in result: + if not item[8] or item[8] == '': + thumb = item[7] + else: + thumb = item[8] + + row = {'row_id': item[0], + 'user': item[1], + 'friendly_name': item[2], + 'user_id': item[3], + 'user_thumb': item[4], + 'title': item[5], + 'rating_key': item[6], + 'thumb': thumb, + 'grandparent_thumb': item[8], + 'last_watch': item[9], + 'platform': item[10], + } + last_watched.append(row) + + home_stats.append({'stat_id': stat, + 'rows': last_watched}) + return home_stats def get_stream_details(self, row_id=None): From 85a3f1553138eda28ef5cbcce46dd142187cf6e8 Mon Sep 17 00:00:00 2001 From: Jonathan Wong Date: Fri, 28 Aug 2015 01:31:35 -0700 Subject: [PATCH 35/54] Fix platform_type missing for last watched home stats --- plexpy/datafactory.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plexpy/datafactory.py b/plexpy/datafactory.py index 9607ed3c..c45b9de0 100644 --- a/plexpy/datafactory.py +++ b/plexpy/datafactory.py @@ -464,7 +464,7 @@ class DataFactory(object): 'thumb': thumb, 'grandparent_thumb': item[8], 'last_watch': item[9], - 'platform': item[10], + 'platform_type': item[10], } last_watched.append(row) From 883a18320846c2a0e659cfe2ebbace7e63e45971 Mon Sep 17 00:00:00 2001 From: Tim Date: Fri, 28 Aug 2015 11:40:22 +0200 Subject: [PATCH 36/54] Fix broken bif indexes on activity pane. Fix incorrect video dimensions when media transcoded on activity pane. --- data/interfaces/default/current_activity.html | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/data/interfaces/default/current_activity.html b/data/interfaces/default/current_activity.html index 92d28b0e..a61a2456 100644 --- a/data/interfaces/default/current_activity.html +++ b/data/interfaces/default/current_activity.html @@ -71,7 +71,7 @@ DOCUMENTATION :: END % if a['type'] == 'movie' and not a['indexes']:
    % elif a['indexes']: - + % else: % if a['type'] == 'episode':
    @@ -128,7 +128,7 @@ DOCUMENTATION :: END % elif a['video_decision'] == 'copy': Video  Direct Stream (${a['transcode_video_codec']}) (${a['width']}x${a['height']}) % elif a['video_decision'] == 'transcode': - Video  Transcode (${a['transcode_video_codec']}) (${a['width']}x${a['height']}) + Video  Transcode (${a['transcode_video_codec']}) (${a['transcode_width']}x${a['transcode_height']}) % endif
    % if a['audio_decision'] == 'direct play': @@ -200,9 +200,10 @@ DOCUMENTATION :: END % endfor diff --git a/data/interfaces/default/server_stats.html b/data/interfaces/default/server_stats.html new file mode 100644 index 00000000..780f84a2 --- /dev/null +++ b/data/interfaces/default/server_stats.html @@ -0,0 +1,75 @@ +<%doc> +USAGE DOCUMENTATION :: PLEASE LEAVE THIS AT THE TOP OF THIS FILE + +For Mako templating syntax documentation please visit: http://docs.makotemplates.org/en/latest/ + +Filename: server_stats.html +Version: 0.1 +Variable names: data [array] + +data[array_index] :: Usable parameters + +data['type'] Returns the type of the library. Either 'movie', 'show', 'photo', or 'artist'. +data['rows'] Returns an array containing stat data + +data[array_index]['rows'] :: Usable parameters + +title Returns the title of the library. +thumb Returns the thumb of the library. +count Returns the number of items in the library. +count_type Returns the sorting type for the library + +== Only if 'type' is 'show' +episode_count Return the number of episodes in the library. +episode_count_type Return the sorting type for the episodes. + +== Only if 'type' is 'artist' +album_count Return the number of episodes in the library. +album_count_type Return the sorting type for the episodes. + +DOCUMENTATION :: END + + +% if data: +
      + % for library in data: +
      +
    • +
      +
      +

      ${library['rows']['title']}

      +
      ${library['rows']['count_type']}
      +
      +
      +

      ${library['rows']['count']}

      +
      + % if library['type'] == 'show': +
      +
      ${library['rows']['episode_count_type']}
      +

      ${library['rows']['episode_count']}

      +
      + % endif + % if library['type'] == 'artist': +
      +
      ${library['rows']['album_count_type']}
      +

      ${library['rows']['album_count']}

      +
      + % endif +
      + % if library['rows']['thumb']: +
      +
      +
      + % else: +
      +
      +
      + % endif +
    • +
      + % endfor +
    +% else: +
    Unable to retrieve data from database. Please check your settings. +

    +% endif \ No newline at end of file diff --git a/plexpy/pmsconnect.py b/plexpy/pmsconnect.py index f23eb32d..4b59c79a 100644 --- a/plexpy/pmsconnect.py +++ b/plexpy/pmsconnect.py @@ -171,6 +171,38 @@ class PmsConnect(object): return request + """ + Return list of libraries on server. + + Optional parameters: output_format { dict, json } + + Output: array + """ + def get_libraries_list(self, output_format=''): + uri = '/library/sections' + request = self.request_handler.make_request(uri=uri, + proto=self.protocol, + request_type='GET', + output_format=output_format) + + return request + + """ + Return list of items in library on server. + + Optional parameters: output_format { dict, json } + + Output: array + """ + def get_library_list(self, section_key='', list_type='all', count='0', sort_type='', output_format=''): + uri = '/library/sections/' + section_key + '/' + list_type +'?X-Plex-Container-Start=0&X-Plex-Container-Size=' + count + sort_type + request = self.request_handler.make_request(uri=uri, + proto=self.protocol, + request_type='GET', + output_format=output_format) + + return request + """ Return sync item details. @@ -984,6 +1016,151 @@ class PmsConnect(object): logger.debug(u"Server preferences queried but no parameter received.") return None + """ + Return processed and validated server libraries list. + + Output: array + """ + def get_server_children(self): + libraries_data = self.get_libraries_list(output_format='xml') + + try: + xml_head = libraries_data.getElementsByTagName('MediaContainer') + except: + logger.warn("Unable to parse XML for get_libraries_list.") + return [] + + libraries_list = [] + + for a in xml_head: + if a.getAttribute('size'): + if a.getAttribute('size') == '0': + logger.debug(u"No libraries data.") + libraries_list = {'libraries_count': '0', + 'libraries_list': [] + } + return libraries_list + + if a.getElementsByTagName('Directory'): + result_data = a.getElementsByTagName('Directory') + for result in result_data: + libraries_output = {'key': helpers.get_xml_attr(result, 'key'), + 'type': helpers.get_xml_attr(result, 'type'), + 'title': helpers.get_xml_attr(result, 'title'), + 'thumb': helpers.get_xml_attr(result, 'thumb') + } + libraries_list.append(libraries_output) + + output = {'libraries_count': helpers.get_xml_attr(xml_head[0], 'size'), + 'title': helpers.get_xml_attr(xml_head[0], 'title1'), + 'libraries_list': libraries_list + } + + return output + + """ + Return processed and validated server library items list. + + Parameters required: library_type { movie, show, episode, artist } + section_key { unique library key } + + Output: array + """ + def get_library_children(self, library_type='', section_key='', list_type='all', sort_type = ''): + + # Currently only grab the library with 1 items so 'size' is not 0 + count = '1' + + if library_type == 'movie': + sort_type = '&type=1' + elif library_type == 'show': + sort_type = '&type=2' + elif library_type == 'episode': + sort_type = '&type=4' + elif library_type == 'album': + list_type = 'albums' + + library_data = self.get_library_list(section_key, list_type, count, sort_type, output_format='xml') + + try: + xml_head = library_data.getElementsByTagName('MediaContainer') + except: + logger.warn("Unable to parse XML for get_library_children.") + return [] + + library_list = [] + + for a in xml_head: + if a.getAttribute('size'): + if a.getAttribute('size') == '0': + logger.debug(u"No library data.") + library_list = {'library_count': '0', + 'library_list': [] + } + return library_list + + if a.getElementsByTagName('Directory'): + result_data = a.getElementsByTagName('Directory') + for result in result_data: + library_output = {'key': helpers.get_xml_attr(result, 'key'), + 'type': helpers.get_xml_attr(result, 'type'), + 'title': helpers.get_xml_attr(result, 'title'), + 'thumb': helpers.get_xml_attr(result, 'thumb') + } + library_list.append(library_output) + + output = {'library_count': helpers.get_xml_attr(xml_head[0], 'totalSize'), + 'count_type': helpers.get_xml_attr(xml_head[0], 'title2'), + 'library_list': library_list + } + + return output + + """ + Return processed and validated server statistics. + + Output: array + """ + def get_server_stats(self): + server_info = self.get_servers_info() + server_libraries = self.get_server_children() + + server_stats = [] + + if server_libraries['libraries_count'] != '0': + libraries_list = server_libraries['libraries_list'] + + for library in libraries_list: + library_type = library['type'] + section_key = library['key'] + library_list = self.get_library_children(library_type, section_key) + + if library_list['library_count'] != '0': + library_stats = {'title': library['title'], + 'thumb': library['thumb'], + 'count': library_list['library_count'], + 'count_type': library_list['count_type'] + } + + if library_type == 'show': + episode_list = self.get_library_children(library_type='episode', section_key=section_key) + episode_stats = {'episode_count': episode_list['library_count'], + 'episode_count_type': 'All Episodes' + } + library_stats.update(episode_stats) + + if library_type == 'artist': + album_list = self.get_library_children(library_type='album', section_key=section_key) + album_stats = {'album_count': album_list['library_count'], + 'album_count_type': 'All Albums' + } + library_stats.update(album_stats) + + server_stats.append({'type': library_type, + 'rows': library_stats}) + + return server_stats + """ Return image data as array. Array contains the image content type and image binary diff --git a/plexpy/webserve.py b/plexpy/webserve.py index f2b6067b..1346698f 100644 --- a/plexpy/webserve.py +++ b/plexpy/webserve.py @@ -67,7 +67,8 @@ class WebInterface(object): config = { "home_stats_length": plexpy.CONFIG.HOME_STATS_LENGTH, "home_stats_type": plexpy.CONFIG.HOME_STATS_TYPE, - "home_stats_count": plexpy.CONFIG.HOME_STATS_COUNT + "home_stats_count": plexpy.CONFIG.HOME_STATS_COUNT, + "pms_identifier": plexpy.CONFIG.PMS_IDENTIFIER, } return serve_template(templatename="index.html", title="Home", config=config) @@ -126,6 +127,13 @@ class WebInterface(object): return serve_template(templatename="home_stats.html", title="Stats", data=stats_data) + @cherrypy.expose + def server_stats(self, **kwargs): + pms_connect = pmsconnect.PmsConnect() + stats_data = pms_connect.get_server_stats() + + return serve_template(templatename="server_stats.html", title="Server Stats", data=stats_data) + @cherrypy.expose def history(self): return serve_template(templatename="history.html", title="History") From 0245668907d9af7d8117b3fb92e74cdf57ee3664 Mon Sep 17 00:00:00 2001 From: Jonathan Wong Date: Sat, 29 Aug 2015 13:15:29 -0700 Subject: [PATCH 42/54] Add "items" text --- data/interfaces/default/css/plexpy.css | 8 ++++++++ data/interfaces/default/server_stats.html | 3 +++ 2 files changed, 11 insertions(+) diff --git a/data/interfaces/default/css/plexpy.css b/data/interfaces/default/css/plexpy.css index 678d6ba1..da38a26d 100644 --- a/data/interfaces/default/css/plexpy.css +++ b/data/interfaces/default/css/plexpy.css @@ -1596,6 +1596,14 @@ a .season-episodes-card-overlay:hover { padding-top: 6px; float: left; } +.home-platforms-instance-name2 p { + color: #aaa; + font-size: 12px; + float: left; + position: relative; + top: 21px; + left: 0px; +} .home-platforms-instance-playcount { float: left; position: relative; diff --git a/data/interfaces/default/server_stats.html b/data/interfaces/default/server_stats.html index 780f84a2..14c0ccd3 100644 --- a/data/interfaces/default/server_stats.html +++ b/data/interfaces/default/server_stats.html @@ -42,17 +42,20 @@ DOCUMENTATION :: END

    ${library['rows']['count']}

    +

    items

    % if library['type'] == 'show':
    ${library['rows']['episode_count_type']}

    ${library['rows']['episode_count']}

    +

    items

    % endif % if library['type'] == 'artist':
    ${library['rows']['album_count_type']}

    ${library['rows']['album_count']}

    +

    items

    % endif
    From 6ff902e6531f85cbde38bbbdb2bea43f95d79f40 Mon Sep 17 00:00:00 2001 From: Jonathan Wong Date: Sat, 29 Aug 2015 13:16:03 -0700 Subject: [PATCH 43/54] Fix playcounts 1px off --- data/interfaces/default/css/plexpy.css | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/data/interfaces/default/css/plexpy.css b/data/interfaces/default/css/plexpy.css index da38a26d..20c9b452 100644 --- a/data/interfaces/default/css/plexpy.css +++ b/data/interfaces/default/css/plexpy.css @@ -1433,7 +1433,7 @@ a .season-episodes-card-overlay:hover { font-size: 12px; float: left; position: relative; - top: 14px; + top: 15px; left: 0px; } .user-overview-stats-instance h3 strong{ @@ -1516,7 +1516,7 @@ a .season-episodes-card-overlay:hover { font-size: 12px; float: left; position: relative; - top: 14px; + top: 15px; left: 0px; } .home-platforms { @@ -1625,7 +1625,7 @@ a .season-episodes-card-overlay:hover { font-size: 12px; float: left; position: relative; - top: 14px; + top: 15px; left: 0px; margin-right: 5px; } From 82fc314b35685e061acc6ab802df401ec2cfefbe Mon Sep 17 00:00:00 2001 From: Jonathan Wong Date: Sat, 29 Aug 2015 13:20:21 -0700 Subject: [PATCH 44/54] Another 1px off fix --- data/interfaces/default/css/plexpy.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/interfaces/default/css/plexpy.css b/data/interfaces/default/css/plexpy.css index 20c9b452..a55aacec 100644 --- a/data/interfaces/default/css/plexpy.css +++ b/data/interfaces/default/css/plexpy.css @@ -1638,7 +1638,7 @@ a .season-episodes-card-overlay:hover { .home-platforms-instance-last-user h5 { font-size: 12px; position: relative; - margin: 0 0 3px 0; + margin: 0 0 2px 0; float: left; } .home-platforms-instance-last-user p { From d44bd2f35b33f458a1321fb4522cb7049e47be3b Mon Sep 17 00:00:00 2001 From: Jonathan Wong Date: Sat, 29 Aug 2015 13:24:44 -0700 Subject: [PATCH 45/54] Shift secondary count right to account for 4 digit items --- data/interfaces/default/css/plexpy.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/interfaces/default/css/plexpy.css b/data/interfaces/default/css/plexpy.css index a55aacec..6cfc445a 100644 --- a/data/interfaces/default/css/plexpy.css +++ b/data/interfaces/default/css/plexpy.css @@ -1575,7 +1575,7 @@ a .season-episodes-card-overlay:hover { .home-platforms-instance-name2 { position: absolute; top: 34px; - left: 210px; + left: 215px; } .home-platforms-instance-name2 h5 { font-size: 14px; From 98f5c33af780765fe3ef276fd6c6b45a73d33df8 Mon Sep 17 00:00:00 2001 From: Tim Date: Sun, 30 Aug 2015 17:54:45 +0200 Subject: [PATCH 46/54] Fix album art on recently added. --- data/interfaces/default/css/plexpy.css | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/data/interfaces/default/css/plexpy.css b/data/interfaces/default/css/plexpy.css index 6cfc445a..b2f51c3a 100644 --- a/data/interfaces/default/css/plexpy.css +++ b/data/interfaces/default/css/plexpy.css @@ -413,6 +413,10 @@ input[type="color"], text-align: center; pointer-events: none; } +.poster { + position: relative; + height: 225px; +} .poster-face { background-position: center; background-size: cover; From 389ce97d03a301bff8141ef19504efec8e11a3ea Mon Sep 17 00:00:00 2001 From: Jonathan Wong Date: Sun, 30 Aug 2015 11:01:52 -0700 Subject: [PATCH 47/54] Rename server statistics to library statistics --- data/interfaces/default/css/plexpy.css | 2 +- data/interfaces/default/index.html | 20 +++++++++---------- .../{server_stats.html => library_stats.html} | 6 +++--- plexpy/pmsconnect.py | 15 +++++++------- plexpy/webserve.py | 6 +++--- 5 files changed, 24 insertions(+), 25 deletions(-) rename data/interfaces/default/{server_stats.html => library_stats.html} (88%) diff --git a/data/interfaces/default/css/plexpy.css b/data/interfaces/default/css/plexpy.css index 6cfc445a..5651ee7f 100644 --- a/data/interfaces/default/css/plexpy.css +++ b/data/interfaces/default/css/plexpy.css @@ -1665,7 +1665,7 @@ a .season-episodes-card-overlay:hover { -moz-box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); } -.home-platforms-instance-poster .home-platforms-server-thumb { +.home-platforms-instance-poster .home-platforms-library-thumb { background-position: center; background-size: cover; height: 80px; diff --git a/data/interfaces/default/index.html b/data/interfaces/default/index.html index 5efc6f8b..b73bdbe0 100644 --- a/data/interfaces/default/index.html +++ b/data/interfaces/default/index.html @@ -29,10 +29,10 @@
    -
    -

    Server Statistics

    +
    +

    Library Statistics

    -
    +
    Loading stats...

    @@ -94,7 +94,7 @@ }); } - function getServerStatsHeader() { + function getLibraryStatsHeader() { $.ajax({ "url": "get_servers_info", type: "post", @@ -110,19 +110,19 @@ break; } } - $('#server-statistics-header h3').append(' ' + server_name + '') + $('#library-statistics-header h3').append(' ' + server_name + '') } }); } - function getServerStats() { + function getLibraryStats() { $.ajax({ - url: 'server_stats', + url: 'library_stats', cache: false, async: true, data: { }, complete: function(xhr, status) { - $("#server-stats").html(xhr.responseText); + $("#library-stats").html(xhr.responseText); } }); } @@ -166,8 +166,8 @@ }); getHomeStats(${config['home_stats_length']}, ${config['home_stats_type']}, ${config['home_stats_count']}); - getServerStatsHeader(); - getServerStats(); + getLibraryStatsHeader(); + getLibraryStats(); diff --git a/data/interfaces/default/server_stats.html b/data/interfaces/default/library_stats.html similarity index 88% rename from data/interfaces/default/server_stats.html rename to data/interfaces/default/library_stats.html index 14c0ccd3..66b774d9 100644 --- a/data/interfaces/default/server_stats.html +++ b/data/interfaces/default/library_stats.html @@ -3,7 +3,7 @@ USAGE DOCUMENTATION :: PLEASE LEAVE THIS AT THE TOP OF THIS FILE For Mako templating syntax documentation please visit: http://docs.makotemplates.org/en/latest/ -Filename: server_stats.html +Filename: library_stats.html Version: 0.1 Variable names: data [array] @@ -61,11 +61,11 @@ DOCUMENTATION :: END
    % if library['rows']['thumb']:
    -
    +
    % else:
    -
    +
    % endif diff --git a/plexpy/pmsconnect.py b/plexpy/pmsconnect.py index 4b59c79a..d766e56e 100644 --- a/plexpy/pmsconnect.py +++ b/plexpy/pmsconnect.py @@ -1121,11 +1121,10 @@ class PmsConnect(object): Output: array """ - def get_server_stats(self): - server_info = self.get_servers_info() + def get_library_stats(self): server_libraries = self.get_server_children() - server_stats = [] + server_library_stats = [] if server_libraries['libraries_count'] != '0': libraries_list = server_libraries['libraries_list'] @@ -1152,14 +1151,14 @@ class PmsConnect(object): if library_type == 'artist': album_list = self.get_library_children(library_type='album', section_key=section_key) album_stats = {'album_count': album_list['library_count'], - 'album_count_type': 'All Albums' - } + 'album_count_type': 'All Albums' + } library_stats.update(album_stats) - server_stats.append({'type': library_type, - 'rows': library_stats}) + server_library_stats.append({'type': library_type, + 'rows': library_stats}) - return server_stats + return server_library_stats """ Return image data as array. diff --git a/plexpy/webserve.py b/plexpy/webserve.py index 1346698f..5d2ba311 100644 --- a/plexpy/webserve.py +++ b/plexpy/webserve.py @@ -128,11 +128,11 @@ class WebInterface(object): return serve_template(templatename="home_stats.html", title="Stats", data=stats_data) @cherrypy.expose - def server_stats(self, **kwargs): + def library_stats(self, **kwargs): pms_connect = pmsconnect.PmsConnect() - stats_data = pms_connect.get_server_stats() + stats_data = pms_connect.get_library_stats() - return serve_template(templatename="server_stats.html", title="Server Stats", data=stats_data) + return serve_template(templatename="library_stats.html", title="Library Stats", data=stats_data) @cherrypy.expose def history(self): From 9d60ed7174fcc8b3bdea8ea99119688c21b4cde2 Mon Sep 17 00:00:00 2001 From: Jonathan Wong Date: Fri, 4 Sep 2015 23:03:16 -0700 Subject: [PATCH 48/54] Fix top platforms sort by duration --- plexpy/datafactory.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plexpy/datafactory.py b/plexpy/datafactory.py index c45b9de0..fb587228 100644 --- a/plexpy/datafactory.py +++ b/plexpy/datafactory.py @@ -391,7 +391,7 @@ class DataFactory(object): 'WHERE datetime(session_history.stopped, "unixepoch", "localtime") ' \ '>= datetime("now", "-%s days", "localtime") ' \ 'GROUP BY session_history.platform ' \ - 'ORDER BY total_plays DESC LIMIT %s' % (time_range, stat_count) + 'ORDER BY %s DESC LIMIT %s' % (time_range, sort_type, stat_count) result = monitor_db.select(query) except: logger.warn("Unable to execute database query.") From 58ea4a65e149b1fa14ac0aa62311f89b2229b5ab Mon Sep 17 00:00:00 2001 From: Jonathan Wong Date: Sat, 5 Sep 2015 17:21:05 -0700 Subject: [PATCH 49/54] Fix info pages for mobile layout --- data/interfaces/default/css/plexpy.css | 1 + 1 file changed, 1 insertion(+) diff --git a/data/interfaces/default/css/plexpy.css b/data/interfaces/default/css/plexpy.css index 09906e51..a1f31a3b 100644 --- a/data/interfaces/default/css/plexpy.css +++ b/data/interfaces/default/css/plexpy.css @@ -932,6 +932,7 @@ a .dashboard-activity-metadata-user-thumb:hover { background-clip: content-box; min-height: calc(100% - 200px); position: inherit; + width: 100%; } .summary-content-poster { float: left; From 52fae6df0e78540ed0b19991736650ed54429ff0 Mon Sep 17 00:00:00 2001 From: Jonathan Wong Date: Sat, 5 Sep 2015 17:35:29 -0700 Subject: [PATCH 50/54] Fix dashboard activity and home stats for mobile layout * Specify dashboard activity as 100% width instead of a set px width --- data/interfaces/default/css/plexpy.css | 42 +++++++++++++++++--------- 1 file changed, 27 insertions(+), 15 deletions(-) diff --git a/data/interfaces/default/css/plexpy.css b/data/interfaces/default/css/plexpy.css index a1f31a3b..4e42c815 100644 --- a/data/interfaces/default/css/plexpy.css +++ b/data/interfaces/default/css/plexpy.css @@ -478,7 +478,7 @@ a .users-poster-face:hover { } .dashboard-activity-poster { height: 200px; - width: 350px; + width: 100%; position: relative; overflow: hidden; } @@ -490,8 +490,8 @@ a:hover .dashboard-activity-poster { .dashboard-activity-poster-face { background-position: center; background-size: cover; - height: 200px; - width: 350px; + height: 100%; + width: 100%; position: absolute; top: 0; right: 0; @@ -503,13 +503,11 @@ a:hover .dashboard-activity-poster { z-index: -3; } .dashboard-activity-cover-face-bg { -} -.dashboard-activity-poster-music-bg { background-position: center; background-size: cover; background-repeat: no-repeat; - height: 200px; - width: 350px; + height: 100%; + width: 100%; position: absolute; top: 0; right: 0; @@ -528,22 +526,22 @@ a:hover .dashboard-activity-poster { background-position: center; background-size: cover; background-repeat: no-repeat; - height: 200px; + height: 100%; width: 200px; -webkit-box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); -moz-box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); position: absolute; top: 0; - left: 77px; + left: calc(50% - 100px); z-index: -3; } .dashboard-activity-clip-face-bg { background-position: center; background-size: cover; background-repeat: no-repeat; - height: 200px; - width: 350px; + height: 100%; + width: 100%; position: absolute; top: 0; right: 0; @@ -562,7 +560,7 @@ a:hover .dashboard-activity-poster { background-position: center; background-size: cover; background-repeat: no-repeat; - height: 200px; + height: 100%; width: 130px; position: relative; -webkit-box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); @@ -570,7 +568,7 @@ a:hover .dashboard-activity-poster { box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); position: absolute; top: 0; - left: 110px; + left: calc(50% - 65px); z-index: -3; } .dashboard-activity-info-details-overlay { @@ -605,7 +603,7 @@ a:hover .dashboard-activity-poster { } .dashboard-activity-info-details-content { height: 200px; - width: 350px; + width: 100%; line-height: 25px; text-overflow: ellipsis; overflow: hidden; @@ -698,7 +696,7 @@ a:hover .dashboard-activity-poster { } .dashboard-activity-metadata-wrapper { position: relative; - width: 350px; + width: 100%; height: 50px; font-size: 13px; padding: 0 3px; @@ -2330,3 +2328,17 @@ a .home-platforms-instance-list-oval:hover, border-radius: 8px; background-clip: padding-box; } + +@media only screen + and (min-device-width: 300px) + and (max-device-width: 375px) { + .home-platforms-instance { + width: calc(100% - 20px); + } + .home-platforms-instance .slider { + width: calc(100% + 20px); + } + .dashboard-instance { + width: 100%; + } +} \ No newline at end of file From 423360e82033438ba97866f7d2d4049356445096 Mon Sep 17 00:00:00 2001 From: Jonathan Wong Date: Sat, 5 Sep 2015 18:01:44 -0700 Subject: [PATCH 51/54] Simplify css for homepage mobile layout fix --- data/interfaces/default/css/plexpy.css | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/data/interfaces/default/css/plexpy.css b/data/interfaces/default/css/plexpy.css index 4e42c815..1d563e98 100644 --- a/data/interfaces/default/css/plexpy.css +++ b/data/interfaces/default/css/plexpy.css @@ -473,8 +473,8 @@ a .users-poster-face:hover { position: relative; height: 260px; width: 350px; - margin-right: 25px; - margin-bottom: 25px; + margin-right: 20px; + margin-bottom: 20px; } .dashboard-activity-poster { height: 200px; @@ -1891,7 +1891,7 @@ a .home-platforms-instance-list-oval:hover, } .home-platforms-instance .slider { background-color: #2d2d2d; - width: 350px; + width: calc(100% + 20px); display: none; position: absolute; top: 128px; @@ -2335,9 +2335,6 @@ a .home-platforms-instance-list-oval:hover, .home-platforms-instance { width: calc(100% - 20px); } - .home-platforms-instance .slider { - width: calc(100% + 20px); - } .dashboard-instance { width: 100%; } From aaa1f0aa33206b46e9fa94ceb992988168247fde Mon Sep 17 00:00:00 2001 From: Jonathan Wong Date: Sat, 5 Sep 2015 18:08:21 -0700 Subject: [PATCH 52/54] Change mobile layout for devices up to 400px wide --- data/interfaces/default/css/plexpy.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/interfaces/default/css/plexpy.css b/data/interfaces/default/css/plexpy.css index 1d563e98..d8379a65 100644 --- a/data/interfaces/default/css/plexpy.css +++ b/data/interfaces/default/css/plexpy.css @@ -2331,7 +2331,7 @@ a .home-platforms-instance-list-oval:hover, @media only screen and (min-device-width: 300px) - and (max-device-width: 375px) { + and (max-device-width: 400px) { .home-platforms-instance { width: calc(100% - 20px); } From 2d739f64cf620244673795248f47e85ee38360b0 Mon Sep 17 00:00:00 2001 From: Tim Date: Sun, 6 Sep 2015 13:49:09 +0200 Subject: [PATCH 53/54] Fix some issues where some channel streams are being reported as episodes. Fix images for activity pane when channels/clips are being played. --- data/interfaces/default/current_activity.html | 13 +++++++++++-- plexpy/pmsconnect.py | 12 ++++++++++-- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/data/interfaces/default/current_activity.html b/data/interfaces/default/current_activity.html index 1225d6fe..d166683d 100644 --- a/data/interfaces/default/current_activity.html +++ b/data/interfaces/default/current_activity.html @@ -80,8 +80,17 @@ DOCUMENTATION :: END
    % elif a['type'] == 'clip': -
    -
    + % if a['art'][:4] == 'http': +
    + % elif a['thumb'][:4] == 'http': +
    + % else: + % if a['art']: +
    + % else: +
    + % endif + % endif % else:
    % endif diff --git a/plexpy/pmsconnect.py b/plexpy/pmsconnect.py index d766e56e..d25e783e 100644 --- a/plexpy/pmsconnect.py +++ b/plexpy/pmsconnect.py @@ -748,9 +748,13 @@ class PmsConnect(object): 'duration': duration, 'progress': progress, 'progress_percent': str(helpers.get_percent(progress, duration)), - 'type': helpers.get_xml_attr(session, 'type'), 'indexes': use_indexes } + if helpers.get_xml_attr(session, 'ratingKey').isdigit(): + session_output['type'] = helpers.get_xml_attr(session, 'type') + else: + session_output['type'] = 'clip' + elif helpers.get_xml_attr(session, 'type') == 'movie': session_output = {'session_key': helpers.get_xml_attr(session, 'sessionKey'), 'media_index': helpers.get_xml_attr(session, 'index'), @@ -797,9 +801,13 @@ class PmsConnect(object): 'duration': duration, 'progress': progress, 'progress_percent': str(helpers.get_percent(progress, duration)), - 'type': helpers.get_xml_attr(session, 'type'), 'indexes': use_indexes } + if helpers.get_xml_attr(session, 'ratingKey').isdigit(): + session_output['type'] = helpers.get_xml_attr(session, 'type') + else: + session_output['type'] = 'clip' + elif helpers.get_xml_attr(session, 'type') == 'clip': session_output = {'session_key': helpers.get_xml_attr(session, 'sessionKey'), 'media_index': helpers.get_xml_attr(session, 'index'), From 527e7be13d22cf601844e321265e5b650ece6cbb Mon Sep 17 00:00:00 2001 From: Tim Date: Sun, 6 Sep 2015 14:41:40 +0200 Subject: [PATCH 54/54] v1.1.6 --- CHANGELOG.md | 17 +++++++++++++++++ plexpy/version.py | 2 +- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 781a3886..a749bf1c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,22 @@ # Changelog +## v1.1.6 (2015-09-06) + +* Home stats cards are now expandable to show multiple items. Configurable in settings. Thanks @JonnyWong. +* Completely redesigned media info pages. Thanks @JonnyWong. +* Redesigned activity pane to match Plex Web more closely. Thanks @JonnyWong. +* New Library stats on home page, shows total item counts per library. Thanks @JonnyWong. +* New last watched card in home stats. Shows last watched items. Thanks @JonnyWong. +* Improved some layout issues on mobile devices. Thanks @JonnyWong. +* Fixed issue where some clip/channel items are reported as episodes and causing exceptions. +* Many styling improvements and fixes. Thanks @JonnyWong. +* Fixed incorrect sort on home stats platform count by duration. Thanks @JonnyWong. +* Fix issue where user refresh would continually be called as "Local" user didn't exist in database. +* Fixed styling on graph stream modal. Thanks @JonnyWong. +* Fixed some issues with users page editing. Thanks @JonnyWong. +* Fix error page when clicking through to an item that no longer exists. + + ## v1.1.5 (2015-08-27) * Fix git tag being one release behind. diff --git a/plexpy/version.py b/plexpy/version.py index 60c9383f..9fbd0cc4 100644 --- a/plexpy/version.py +++ b/plexpy/version.py @@ -1,2 +1,2 @@ PLEXPY_VERSION = "master" -PLEXPY_RELEASE_VERSION = "1.1.5" +PLEXPY_RELEASE_VERSION = "1.1.6"