From 1e57e952db24a925f7a2175d34601f79c3e9e34f Mon Sep 17 00:00:00 2001 From: Jonathan Wong Date: Wed, 26 Aug 2015 17:01:17 -0700 Subject: [PATCH 1/6] 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 2/6] 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 3/6] 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 4/6] 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 5/6] 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 60380c6a99a2994d72e51f7a1f7b1e6aa1b00eb4 Mon Sep 17 00:00:00 2001 From: Jonathan Wong Date: Thu, 27 Aug 2015 00:48:10 -0700 Subject: [PATCH 6/6] 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)