diff --git a/ApiClient.js b/ApiClient.js index ebea5b9860..a90d2dcf8b 100644 --- a/ApiClient.js +++ b/ApiClient.js @@ -243,6 +243,61 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout) { }); }; + self.getNotificationSummary = function (userId) { + + if (!userId) { + throw new Error("null userId"); + } + + var url = self.getUrl("Notifications/" + userId + "/Summary"); + + return self.ajax({ + type: "GET", + url: url, + dataType: "json" + }); + }; + + self.getNotifications = function (userId, options) { + + if (!userId) { + throw new Error("null userId"); + } + + var url = self.getUrl("Notifications/" + userId, options || {}); + + return self.ajax({ + type: "GET", + url: url, + dataType: "json" + }); + }; + + self.markNotificationsRead = function (userId, idList, isRead) { + + if (!userId) { + throw new Error("null userId"); + } + + if (!idList || !idList.length) { + throw new Error("null idList"); + } + + var suffix = isRead ? "Read" : "Unread"; + + var params = { + UserId: userId, + Ids: idList.join(',') + }; + + var url = self.getUrl("Notifications/" + userId + "/" + suffix, params); + + return self.ajax({ + type: "POST", + url: url + }); + }; + /** * Gets the current server status */ @@ -1937,7 +1992,7 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout) { if (!item) { throw new Error("null item"); } - + var url = self.getUrl("Artists/" + self.encodeName(item.Name)); return self.ajax({ diff --git a/dashboard-ui/css/images/notifications/error.png b/dashboard-ui/css/images/notifications/error.png index c6b5693c4e..ec55035e9c 100644 Binary files a/dashboard-ui/css/images/notifications/error.png and b/dashboard-ui/css/images/notifications/error.png differ diff --git a/dashboard-ui/css/images/notifications/info.png b/dashboard-ui/css/images/notifications/info.png index fb724718fd..624ccee378 100644 Binary files a/dashboard-ui/css/images/notifications/info.png and b/dashboard-ui/css/images/notifications/info.png differ diff --git a/dashboard-ui/css/notifications.css b/dashboard-ui/css/notifications.css new file mode 100644 index 0000000000..5a361994bf --- /dev/null +++ b/dashboard-ui/css/notifications.css @@ -0,0 +1,160 @@ +.btnNotifications { + text-decoration: none; + vertical-align: middle; + -moz-border-radius: 2px; + -webkit-border-radius: 2px; + border-radius: 2px; + padding: 8px 12px; + text-align: center; + text-decoration: none !important; + -moz-user-select: none; + -webkit-user-select: none; + background-color: #f5f5f5; + background-image: -webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#f1f1f1)); + background-image: -webkit-linear-gradient(top,#f5f5f5,#f1f1f1); + background-image: -moz-linear-gradient(top,#f5f5f5,#f1f1f1); + background-image: -ms-linear-gradient(top,#f5f5f5,#f1f1f1); + background-image: -o-linear-gradient(top,#f5f5f5,#f1f1f1); + background-image: linear-gradient(top,#f5f5f5,#f1f1f1); + border: 1px solid #dcdcdc; + color: #666; +} + +.levelNormal { + background-color: #4d90fe; + background-image: -webkit-gradient(linear,left top,left bottom,from(#4d90fe),to(#4787ed)); + background-image: -webkit-linear-gradient(top,#4d90fe,#4787ed); + background-image: -moz-linear-gradient(top,#4d90fe,#4787ed); + background-image: -ms-linear-gradient(top,#4d90fe,#4787ed); + background-image: -o-linear-gradient(top,#4d90fe,#4787ed); + background-image: linear-gradient(top,#4d90fe,#4787ed); + border-color: #3079ed; + color: #fff!important; +} + +.levelWarning { + background-color: #FF7537; + background-image: none; + border-color: #FF7537; + color: #fff!important; +} + +.levelError { + background-color: #d14836; + background-image: -webkit-gradient(linear,left top,left bottom,from(#dd4b39),to(#d14836)); + background-image: -webkit-linear-gradient(top,#dd4b39,#d14836); + background-image: -moz-linear-gradient(top,#dd4b39,#d14836); + background-image: -ms-linear-gradient(top,#dd4b39,#d14836); + background-image: -o-linear-gradient(top,#dd4b39,#d14836); + background-image: linear-gradient(top,#dd4b39,#d14836); + border-color: #c6322a; + color: #fff!important; +} + +.flyoutNotification { + border-top: 1px solid #ccc; + background: #f8f8f8; + color: #555; +} + + .flyoutNotification p { + margin: .7em 0; + } + +.notificationsFlyout { + width: 300px; +} + +.notificationName { + font-weight: bold; + color: #555; +} + +.notificationImage { + display: none; +} + +.unreadFlyoutNotification { + background-color: #ECEEF4; +} + +.btnMarkReadContainer { + padding: .5em; +} + +.imgNotification, .imgNotificationInner { + width: 60px; + height: 60px; +} + +.imgNotificationInner { + background-repeat: no-repeat; + background-size: contain; + background-position: center center; +} + +.imgNotificationIcon { + background-size: 50% auto; +} + + +.imgNotificationNormal { + background-color: #4d90fe; + background-image: -webkit-gradient(linear,left top,left bottom,from(#4d90fe),to(#4787ed)); + background-image: -webkit-linear-gradient(top,#4d90fe,#4787ed); + background-image: -moz-linear-gradient(top,#4d90fe,#4787ed); + background-image: -ms-linear-gradient(top,#4d90fe,#4787ed); + background-image: -o-linear-gradient(top,#4d90fe,#4787ed); + background-image: linear-gradient(top,#4d90fe,#4787ed); +} + +.imgNotificationNormal .imgNotificationInner { + background-image: url(images/notifications/info.png); +} + +.imgNotificationError { + background-color: #d14836; + background-image: -webkit-gradient(linear,left top,left bottom,from(#dd4b39),to(#d14836)); + background-image: -webkit-linear-gradient(top,#dd4b39,#d14836); + background-image: -moz-linear-gradient(top,#dd4b39,#d14836); + background-image: -ms-linear-gradient(top,#dd4b39,#d14836); + background-image: -o-linear-gradient(top,#dd4b39,#d14836); + background-image: linear-gradient(top,#dd4b39,#d14836); +} + +.imgNotificationError .imgNotificationInner { + background-image: url(images/notifications/error.png); +} + +.imgNotificationWarning { + background-color: #FF7537; +} + +.imgNotificationWarning .imgNotificationInner { + background-image: url(images/notifications/error.png); +} + +@media all and (min-width: 500px) { + .notificationsFlyout { + width: 400px; + } +} + +@media all and (min-width: 600px) { + .notificationsFlyout { + width: 500px; + } + + + .notificationImage { + display: inline-block; + vertical-align: top; + margin: 0 1em; + padding: .7em 0; + } + + .notificationContent { + display: inline-block; + vertical-align: top; + } +} diff --git a/dashboard-ui/css/search.css b/dashboard-ui/css/search.css index fb7676186d..5043637f04 100644 --- a/dashboard-ui/css/search.css +++ b/dashboard-ui/css/search.css @@ -35,7 +35,6 @@ text-decoration: none !important; -moz-user-select: none; -webkit-user-select: none; - text-align: center; } .txtSearch, .btnSearch { diff --git a/dashboard-ui/scripts/notifications.js b/dashboard-ui/scripts/notifications.js new file mode 100644 index 0000000000..31ccb80240 --- /dev/null +++ b/dashboard-ui/scripts/notifications.js @@ -0,0 +1,191 @@ +(function ($, document, Dashboard) { + + var userId; + var getNotificationsSummaryPromise; + + function getNotificationsSummary() { + + getNotificationsSummaryPromise = getNotificationsSummaryPromise || ApiClient.getNotificationSummary(userId); + + return getNotificationsSummaryPromise; + } + + function updateNotificationCount() { + + getNotificationsSummary().done(function (summary) { + + var elem = $('.btnNotifications').removeClass('levelNormal').removeClass('levelWarning').removeClass('levelError').html(summary.UnreadCount); + + if (summary.UnreadCount) { + elem.addClass('level' + summary.MaxUnreadNotificationLevel); + } + }); + } + + function showNotificationsFlyout() { + + var context = this; + + var html = '
'; + + html += 'Close'; + + html += '
'; + html += '

Notifications

'; + html += '
'; + + html += '
'; + + html += '

Loading...'; + + html += '

'; + + html += ''; + html += '
'; + + html += '
'; + + $(document.body).append(html); + + $('.notificationsFlyout').popup({ positionTo: context }).trigger('create').popup("open").on("popupafterclose", function () { + + $(this).off("popupafterclose").remove(); + + }).on('click', '.btnMarkRead', function () { + + + var ids = $('.unreadFlyoutNotification').map(function () { + + return this.getAttribute('data-notificationid'); + + }).get(); + + ApiClient.markNotificationsRead(Dashboard.getCurrentUserId(), ids, true).done(function () { + + $('.notificationsFlyout').popup("close"); + + }); + + }); + + refreshFlyoutContents(); + } + + function refreshFlyoutContents() { + + var limit = 5; + var startIndex = 0; + + ApiClient.getNotifications(Dashboard.getCurrentUserId(), { StartIndex: startIndex, Limit: limit }).done(function (result) { + + listUnreadNotifications(result.Notifications, result.TotalRecordCount, startIndex, limit); + + }); + } + + function listUnreadNotifications(notifications, totalRecordCount, startIndex, limit) { + + var elem = $('.notificationsFlyoutlist'); + + if (!totalRecordCount) { + elem.html('

No unread notifications.

'); + $('.btnMarkReadContainer').hide(); + return; + } + + if (notifications.filter(function(n) { + + return !n.IsRead; + + }).length) { + $('.btnMarkReadContainer').show(); + } else { + $('.btnMarkReadContainer').hide(); + } + + + var html = ''; + + for (var i = 0, length = notifications.length; i < length; i++) { + + var notification = notifications[i]; + + html += getNotificationHtml(notification); + + } + + elem.html(html); + } + + function getNotificationHtml(notification) { + + var html = ''; + + var cssClass = notification.IsRead ? "flyoutNotification" : "flyoutNotification unreadFlyoutNotification"; + + html += '
'; + + html += '
'; + html += getImageHtml(notification); + html += '
'; + + html += '
'; + + html += '

' + notification.Name + '

'; + + html += '

' + humane_date(notification.Date) + '

'; + + if (notification.Description) { + html += '

' + notification.Description + '

'; + } + + html += '
'; + html += '
'; + + return html; + } + + function getImageHtml(notification) { + + if (notification.Level == "Error") { + + return '
'; + + } + if (notification.Level == "Warning") { + + return '
'; + + } + + return '
'; + + } + + $(Dashboard).on('interiorheaderrendered', function (e, header, user) { + + if (!user || $('.notificationsButton', header).length) { + return; + } + + userId = user.Id; + + $('0').insertAfter($('.btnCurrentUser', header)).on('click', showNotificationsFlyout); + + updateNotificationCount(); + }); + + $(ApiClient).on("websocketmessage", function (e, msg) { + + + if (msg.MessageType === "NotificationUpdated" || msg.MessageType === "NotificationAdded" || msg.MessageType === "NotificationsMarkedRead") { + + getNotificationsSummaryPromise = null; + + updateNotificationCount(); + } + + }); + + +})(jQuery, document, Dashboard); \ No newline at end of file diff --git a/dashboard-ui/scripts/site.js b/dashboard-ui/scripts/site.js index 4b1ae74e94..67397a1e83 100644 --- a/dashboard-ui/scripts/site.js +++ b/dashboard-ui/scripts/site.js @@ -445,7 +445,7 @@ var Dashboard = { html += 'Close'; html += '
'; - html += '

' + user.Name + '

'; + html += '

' + user.Name + '

'; html += '
'; html += '
'; @@ -594,6 +594,8 @@ var Dashboard = { } }); } + + $(Dashboard).trigger('interiorheaderrendered', [header, user]); }, ensureToolsMenu: function (page) { diff --git a/packages.config b/packages.config index ef87d534e7..439f8cb979 100644 --- a/packages.config +++ b/packages.config @@ -1,6 +1,6 @@  - + \ No newline at end of file