Commit 473d78b5 by Jessica Forrester Committed by GitHub

Merge pull request #117 from spadgett/toast-performance

Improve toast notification performance
parents b51786f7 9a05daa0
...@@ -1538,7 +1538,7 @@ angular.module('openshiftCommonServices') ...@@ -1538,7 +1538,7 @@ angular.module('openshiftCommonServices')
} }
// Use `$rootScope.$emit` instead of NotificationsService directly // Use `$rootScope.$emit` instead of NotificationsService directly
// so that DataService doesn't add a dependency on `openshiftCommonUI` // so that DataService doesn't add a dependency on `openshiftCommonUI`
$rootScope.$emit('addNotification', { $rootScope.$emit('NotificationsService.addNotification', {
type: 'error', type: 'error',
message: msg message: msg
}); });
...@@ -2083,7 +2083,7 @@ DataService.prototype.createStream = function(resource, name, context, opts, isR ...@@ -2083,7 +2083,7 @@ DataService.prototype.createStream = function(resource, name, context, opts, isR
} }
// Use `$rootScope.$emit` instead of NotificationsService directly // Use `$rootScope.$emit` instead of NotificationsService directly
// so that DataService doesn't add a dependency on `openshiftCommonUI` // so that DataService doesn't add a dependency on `openshiftCommonUI`
$rootScope.$emit('addNotification', { $rootScope.$emit('NotificationsService.addNotification', {
type: 'error', type: 'error',
message: msg message: msg
}); });
...@@ -2114,7 +2114,7 @@ DataService.prototype.createStream = function(resource, name, context, opts, isR ...@@ -2114,7 +2114,7 @@ DataService.prototype.createStream = function(resource, name, context, opts, isR
} }
// Use `$rootScope.$emit` instead of NotificationsService directly // Use `$rootScope.$emit` instead of NotificationsService directly
// so that DataService doesn't add a dependency on `openshiftCommonUI` // so that DataService doesn't add a dependency on `openshiftCommonUI`
$rootScope.$emit('addNotification', { $rootScope.$emit('NotificationsService.addNotification', {
type: 'error', type: 'error',
message: msg message: msg
}); });
...@@ -2304,7 +2304,7 @@ DataService.prototype.createStream = function(resource, name, context, opts, isR ...@@ -2304,7 +2304,7 @@ DataService.prototype.createStream = function(resource, name, context, opts, isR
if (_.get(opts, 'errorNotification', true)) { if (_.get(opts, 'errorNotification', true)) {
// Use `$rootScope.$emit` instead of NotificationsService directly // Use `$rootScope.$emit` instead of NotificationsService directly
// so that DataService doesn't add a dependency on `openshiftCommonUI` // so that DataService doesn't add a dependency on `openshiftCommonUI`
$rootScope.$emit('addNotification', { $rootScope.$emit('NotificationsService.addNotification', {
id: 'websocket_retry_halted', id: 'websocket_retry_halted',
type: 'error', type: 'error',
message: 'Server connection interrupted.', message: 'Server connection interrupted.',
......
...@@ -390,7 +390,7 @@ hawtioPluginLoader.addModule('openshiftCommonUI'); ...@@ -390,7 +390,7 @@ hawtioPluginLoader.addModule('openshiftCommonUI');
$templateCache.put('src/components/toast-notifications/toast-notifications.html', $templateCache.put('src/components/toast-notifications/toast-notifications.html',
"<div class=\"toast-notifications-list-pf\">\n" + "<div class=\"toast-notifications-list-pf\">\n" +
" <div ng-repeat=\"(notificationID, notification) in notifications track by (notificationID + (notification.message || notification.details))\" ng-if=\"!notification.hidden\"\n" + " <div ng-repeat=\"(notificationID, notification) in notifications track by (notificationID + (notification.message || notification.details))\" ng-if=\"!notification.hidden || notification.isHover\"\n" +
" ng-mouseenter=\"setHover(notification, true)\" ng-mouseleave=\"setHover(notification, false)\">\n" + " ng-mouseenter=\"setHover(notification, true)\" ng-mouseleave=\"setHover(notification, false)\">\n" +
" <div class=\"toast-pf alert {{notification.type | alertStatus}}\" ng-class=\"{'alert-dismissable': !hideCloseButton}\">\n" + " <div class=\"toast-pf alert {{notification.type | alertStatus}}\" ng-class=\"{'alert-dismissable': !hideCloseButton}\">\n" +
" <button ng-if=\"!hideCloseButton\" type=\"button\" class=\"close\" ng-click=\"close(notification)\">\n" + " <button ng-if=\"!hideCloseButton\" type=\"button\" class=\"close\" ng-click=\"close(notification)\">\n" +
...@@ -1016,49 +1016,79 @@ angular.module('openshiftCommonUI') ...@@ -1016,49 +1016,79 @@ angular.module('openshiftCommonUI')
;'use strict'; ;'use strict';
angular.module('openshiftCommonUI') angular.module('openshiftCommonUI')
.directive('toastNotifications', function(NotificationsService, $timeout) { .directive('toastNotifications', function(NotificationsService, $rootScope, $timeout) {
return { return {
restrict: 'E', restrict: 'E',
scope: {}, scope: {},
templateUrl: 'src/components/toast-notifications/toast-notifications.html', templateUrl: 'src/components/toast-notifications/toast-notifications.html',
link: function($scope) { link: function($scope) {
$scope.notifications = NotificationsService.getNotifications(); $scope.notifications = [];
$scope.close = function(notification) { // A notification is removed if it has hidden set and the user isn't
// currently hovering over it.
var isRemoved = function(notification) {
return notification.hidden && !notification.isHover;
};
var removeNotification = function(notification) {
notification.isHover = false;
notification.hidden = true; notification.hidden = true;
};
// Remove items that are now hidden to keep the array from growing
// indefinitely. We loop over the entire array each digest loop, even
// if everything is hidden, and any watch update triggers a new digest
// loop. If the array grows large, it can hurt performance.
var pruneRemovedNotifications = function() {
$scope.notifications = _.reject($scope.notifications, isRemoved);
};
$scope.close = function(notification) {
removeNotification(notification);
if (_.isFunction(notification.onClose)) { if (_.isFunction(notification.onClose)) {
notification.onClose(); notification.onClose();
} }
}; };
$scope.onClick = function(notification, link) { $scope.onClick = function(notification, link) {
if (_.isFunction(link.onClick)) { if (_.isFunction(link.onClick)) {
// If onClick() returns true, also hide the alert. // If onClick() returns true, also hide the alert.
var close = link.onClick(); var close = link.onClick();
if (close) { if (close) {
notification.hidden = true; removeNotification(notification);
} }
} }
}; };
$scope.setHover = function(notification, isHover) { $scope.setHover = function(notification, isHover) {
// Don't change anything if the notification was already removed.
// Avoids a potential issue where the flag is reset during the slide
// out animation.
if (!isRemoved(notification)) {
notification.isHover = isHover; notification.isHover = isHover;
}
}; };
$scope.$watch('notifications', function() { // Listen for updates from NotificationsService to show a notification.
_.each($scope.notifications, function(notification) { var deregisterNotificationListener = $rootScope.$on('NotificationsService.onNotificationAdded', function(event, notification) {
if (NotificationsService.isAutoDismiss(notification) && !notification.hidden) { $scope.notifications.push(notification);
if (!notification.timerId) { if (NotificationsService.isAutoDismiss(notification)) {
notification.timerId = $timeout(function () { $timeout(function () {
notification.timerId = -1;
if (!notification.isHover) {
notification.hidden = true; notification.hidden = true;
}
}, NotificationsService.dismissDelay); }, NotificationsService.dismissDelay);
} else if (notification.timerId === -1 && !notification.isHover) {
notification.hidden = true;
} }
// Whenever we add a new notification, also remove any hidden toasts
// so that the array doesn't grow indefinitely.
pruneRemovedNotifications();
});
$scope.$on('$destroy', function() {
if (deregisterNotificationListener) {
deregisterNotificationListener();
deregisterNotificationListener = null;
} }
}); });
}, true);
} }
}; };
}); });
...@@ -1998,6 +2028,7 @@ angular.module('openshiftCommonUI').provider('NotificationsService', function() ...@@ -1998,6 +2028,7 @@ angular.module('openshiftCommonUI').provider('NotificationsService', function()
} }
notifications.push(notification); notifications.push(notification);
$rootScope.$emit('NotificationsService.onNotificationAdded', notification);
}; };
var hideNotification = function (notificationID) { var hideNotification = function (notificationID) {
...@@ -2050,7 +2081,7 @@ angular.module('openshiftCommonUI').provider('NotificationsService', function() ...@@ -2050,7 +2081,7 @@ angular.module('openshiftCommonUI').provider('NotificationsService', function()
}; };
// Also handle `addNotification` events on $rootScope, which is used by DataService. // Also handle `addNotification` events on $rootScope, which is used by DataService.
$rootScope.$on('addNotification', function(event, data) { $rootScope.$on('NotificationsService.addNotification', function(event, data) {
addNotification(data); addNotification(data);
}); });
......
...@@ -561,7 +561,7 @@ hawtioPluginLoader.addModule('openshiftCommonUI'); ...@@ -561,7 +561,7 @@ hawtioPluginLoader.addModule('openshiftCommonUI');
$templateCache.put('src/components/toast-notifications/toast-notifications.html', $templateCache.put('src/components/toast-notifications/toast-notifications.html',
"<div class=\"toast-notifications-list-pf\">\n" + "<div class=\"toast-notifications-list-pf\">\n" +
" <div ng-repeat=\"(notificationID, notification) in notifications track by (notificationID + (notification.message || notification.details))\" ng-if=\"!notification.hidden\"\n" + " <div ng-repeat=\"(notificationID, notification) in notifications track by (notificationID + (notification.message || notification.details))\" ng-if=\"!notification.hidden || notification.isHover\"\n" +
" ng-mouseenter=\"setHover(notification, true)\" ng-mouseleave=\"setHover(notification, false)\">\n" + " ng-mouseenter=\"setHover(notification, true)\" ng-mouseleave=\"setHover(notification, false)\">\n" +
" <div class=\"toast-pf alert {{notification.type | alertStatus}}\" ng-class=\"{'alert-dismissable': !hideCloseButton}\">\n" + " <div class=\"toast-pf alert {{notification.type | alertStatus}}\" ng-class=\"{'alert-dismissable': !hideCloseButton}\">\n" +
" <button ng-if=\"!hideCloseButton\" type=\"button\" class=\"close\" ng-click=\"close(notification)\">\n" + " <button ng-if=\"!hideCloseButton\" type=\"button\" class=\"close\" ng-click=\"close(notification)\">\n" +
...@@ -1187,49 +1187,79 @@ angular.module('openshiftCommonUI') ...@@ -1187,49 +1187,79 @@ angular.module('openshiftCommonUI')
;'use strict'; ;'use strict';
angular.module('openshiftCommonUI') angular.module('openshiftCommonUI')
.directive('toastNotifications', ["NotificationsService", "$timeout", function(NotificationsService, $timeout) { .directive('toastNotifications', ["NotificationsService", "$rootScope", "$timeout", function(NotificationsService, $rootScope, $timeout) {
return { return {
restrict: 'E', restrict: 'E',
scope: {}, scope: {},
templateUrl: 'src/components/toast-notifications/toast-notifications.html', templateUrl: 'src/components/toast-notifications/toast-notifications.html',
link: function($scope) { link: function($scope) {
$scope.notifications = NotificationsService.getNotifications(); $scope.notifications = [];
$scope.close = function(notification) { // A notification is removed if it has hidden set and the user isn't
// currently hovering over it.
var isRemoved = function(notification) {
return notification.hidden && !notification.isHover;
};
var removeNotification = function(notification) {
notification.isHover = false;
notification.hidden = true; notification.hidden = true;
};
// Remove items that are now hidden to keep the array from growing
// indefinitely. We loop over the entire array each digest loop, even
// if everything is hidden, and any watch update triggers a new digest
// loop. If the array grows large, it can hurt performance.
var pruneRemovedNotifications = function() {
$scope.notifications = _.reject($scope.notifications, isRemoved);
};
$scope.close = function(notification) {
removeNotification(notification);
if (_.isFunction(notification.onClose)) { if (_.isFunction(notification.onClose)) {
notification.onClose(); notification.onClose();
} }
}; };
$scope.onClick = function(notification, link) { $scope.onClick = function(notification, link) {
if (_.isFunction(link.onClick)) { if (_.isFunction(link.onClick)) {
// If onClick() returns true, also hide the alert. // If onClick() returns true, also hide the alert.
var close = link.onClick(); var close = link.onClick();
if (close) { if (close) {
notification.hidden = true; removeNotification(notification);
} }
} }
}; };
$scope.setHover = function(notification, isHover) { $scope.setHover = function(notification, isHover) {
// Don't change anything if the notification was already removed.
// Avoids a potential issue where the flag is reset during the slide
// out animation.
if (!isRemoved(notification)) {
notification.isHover = isHover; notification.isHover = isHover;
}
}; };
$scope.$watch('notifications', function() { // Listen for updates from NotificationsService to show a notification.
_.each($scope.notifications, function(notification) { var deregisterNotificationListener = $rootScope.$on('NotificationsService.onNotificationAdded', function(event, notification) {
if (NotificationsService.isAutoDismiss(notification) && !notification.hidden) { $scope.notifications.push(notification);
if (!notification.timerId) { if (NotificationsService.isAutoDismiss(notification)) {
notification.timerId = $timeout(function () { $timeout(function () {
notification.timerId = -1;
if (!notification.isHover) {
notification.hidden = true; notification.hidden = true;
}
}, NotificationsService.dismissDelay); }, NotificationsService.dismissDelay);
} else if (notification.timerId === -1 && !notification.isHover) {
notification.hidden = true;
} }
// Whenever we add a new notification, also remove any hidden toasts
// so that the array doesn't grow indefinitely.
pruneRemovedNotifications();
});
$scope.$on('$destroy', function() {
if (deregisterNotificationListener) {
deregisterNotificationListener();
deregisterNotificationListener = null;
} }
}); });
}, true);
} }
}; };
}]); }]);
...@@ -3262,7 +3292,7 @@ angular.module('openshiftCommonServices') ...@@ -3262,7 +3292,7 @@ angular.module('openshiftCommonServices')
} }
// Use `$rootScope.$emit` instead of NotificationsService directly // Use `$rootScope.$emit` instead of NotificationsService directly
// so that DataService doesn't add a dependency on `openshiftCommonUI` // so that DataService doesn't add a dependency on `openshiftCommonUI`
$rootScope.$emit('addNotification', { $rootScope.$emit('NotificationsService.addNotification', {
type: 'error', type: 'error',
message: msg message: msg
}); });
...@@ -3807,7 +3837,7 @@ DataService.prototype.createStream = function(resource, name, context, opts, isR ...@@ -3807,7 +3837,7 @@ DataService.prototype.createStream = function(resource, name, context, opts, isR
} }
// Use `$rootScope.$emit` instead of NotificationsService directly // Use `$rootScope.$emit` instead of NotificationsService directly
// so that DataService doesn't add a dependency on `openshiftCommonUI` // so that DataService doesn't add a dependency on `openshiftCommonUI`
$rootScope.$emit('addNotification', { $rootScope.$emit('NotificationsService.addNotification', {
type: 'error', type: 'error',
message: msg message: msg
}); });
...@@ -3838,7 +3868,7 @@ DataService.prototype.createStream = function(resource, name, context, opts, isR ...@@ -3838,7 +3868,7 @@ DataService.prototype.createStream = function(resource, name, context, opts, isR
} }
// Use `$rootScope.$emit` instead of NotificationsService directly // Use `$rootScope.$emit` instead of NotificationsService directly
// so that DataService doesn't add a dependency on `openshiftCommonUI` // so that DataService doesn't add a dependency on `openshiftCommonUI`
$rootScope.$emit('addNotification', { $rootScope.$emit('NotificationsService.addNotification', {
type: 'error', type: 'error',
message: msg message: msg
}); });
...@@ -4028,7 +4058,7 @@ DataService.prototype.createStream = function(resource, name, context, opts, isR ...@@ -4028,7 +4058,7 @@ DataService.prototype.createStream = function(resource, name, context, opts, isR
if (_.get(opts, 'errorNotification', true)) { if (_.get(opts, 'errorNotification', true)) {
// Use `$rootScope.$emit` instead of NotificationsService directly // Use `$rootScope.$emit` instead of NotificationsService directly
// so that DataService doesn't add a dependency on `openshiftCommonUI` // so that DataService doesn't add a dependency on `openshiftCommonUI`
$rootScope.$emit('addNotification', { $rootScope.$emit('NotificationsService.addNotification', {
id: 'websocket_retry_halted', id: 'websocket_retry_halted',
type: 'error', type: 'error',
message: 'Server connection interrupted.', message: 'Server connection interrupted.',
...@@ -5178,6 +5208,7 @@ angular.module('openshiftCommonUI').provider('NotificationsService', function() ...@@ -5178,6 +5208,7 @@ angular.module('openshiftCommonUI').provider('NotificationsService', function()
} }
notifications.push(notification); notifications.push(notification);
$rootScope.$emit('NotificationsService.onNotificationAdded', notification);
}; };
var hideNotification = function (notificationID) { var hideNotification = function (notificationID) {
...@@ -5230,7 +5261,7 @@ angular.module('openshiftCommonUI').provider('NotificationsService', function() ...@@ -5230,7 +5261,7 @@ angular.module('openshiftCommonUI').provider('NotificationsService', function()
}; };
// Also handle `addNotification` events on $rootScope, which is used by DataService. // Also handle `addNotification` events on $rootScope, which is used by DataService.
$rootScope.$on('addNotification', function(event, data) { $rootScope.$on('NotificationsService.addNotification', function(event, data) {
addNotification(data); addNotification(data);
}); });
......
...@@ -91,7 +91,7 @@ $templateCache.put("src/components/binding/bindServiceForm.html", '<div class="b ...@@ -91,7 +91,7 @@ $templateCache.put("src/components/binding/bindServiceForm.html", '<div class="b
$templateCache.put("src/components/create-project/createProject.html", '<form name="createProjectForm" novalidate>\n <fieldset ng-disabled="disableInputs">\n <div class="form-group">\n <label for="name" class="required">Name</label>\n <span ng-class="{\'has-error\': (createProjectForm.name.$error.pattern && createProjectForm.name.$touched) || nameTaken}">\n <input class="form-control input-lg"\n name="name"\n id="name"\n placeholder="my-project"\n type="text"\n required\n take-focus\n minlength="2"\n maxlength="63"\n pattern="[a-z0-9]([-a-z0-9]*[a-z0-9])?"\n aria-describedby="nameHelp"\n ng-model="name"\n ng-model-options="{ updateOn: \'default blur\' }"\n ng-change="nameTaken = false"\n autocorrect="off"\n autocapitalize="off"\n spellcheck="false">\n </span>\n <div>\n <span class="help-block">A unique name for the project.</span>\n </div>\n <div class="has-error">\n <span id="nameHelp" class="help-block" ng-if="createProjectForm.name.$error.required && createProjectForm.name.$dirty">\n Name is required.\n </span>\n </div>\n <div class="has-error">\n <span id="nameHelp" class="help-block" ng-if="createProjectForm.name.$error.minlength && createProjectForm.name.$touched">\n Name must have at least two characters.\n </span>\n </div>\n <div class="has-error">\n <span id="nameHelp" class="help-block" ng-if="createProjectForm.name.$error.pattern && createProjectForm.name.$touched">\n Project names may only contain lower-case letters, numbers, and dashes.\n They may not start or end with a dash.\n </span>\n </div>\n <div class="has-error">\n <span class="help-block" ng-if="nameTaken">\n This name is already in use. Please choose a different name.\n </span>\n </div>\n </div>\n\n <div class="form-group">\n <label for="displayName">Display Name</label>\n <input class="form-control input-lg"\n name="displayName"\n id="displayName"\n placeholder="My Project"\n type="text"\n ng-model="displayName">\n </div>\n\n <div class="form-group">\n <label for="description">Description</label>\n <textarea class="form-control input-lg"\n name="description"\n id="description"\n placeholder="A short description."\n ng-model="description"></textarea>\n </div>\n\n <div class="button-group">\n <button type="submit"\n class="btn btn-primary btn-lg"\n ng-class="{\'dialog-btn\': isDialog}"\n ng-click="createProject()"\n ng-disabled="createProjectForm.$invalid || nameTaken || disableInputs"\n value="">\n Create\n </button>\n <button\n class="btn btn-default btn-lg"\n ng-class="{\'dialog-btn\': isDialog}"\n ng-click="cancelCreateProject()">\n Cancel\n </button>\n </div>\n </fieldset>\n</form>\n'), $templateCache.put("src/components/create-project/createProject.html", '<form name="createProjectForm" novalidate>\n <fieldset ng-disabled="disableInputs">\n <div class="form-group">\n <label for="name" class="required">Name</label>\n <span ng-class="{\'has-error\': (createProjectForm.name.$error.pattern && createProjectForm.name.$touched) || nameTaken}">\n <input class="form-control input-lg"\n name="name"\n id="name"\n placeholder="my-project"\n type="text"\n required\n take-focus\n minlength="2"\n maxlength="63"\n pattern="[a-z0-9]([-a-z0-9]*[a-z0-9])?"\n aria-describedby="nameHelp"\n ng-model="name"\n ng-model-options="{ updateOn: \'default blur\' }"\n ng-change="nameTaken = false"\n autocorrect="off"\n autocapitalize="off"\n spellcheck="false">\n </span>\n <div>\n <span class="help-block">A unique name for the project.</span>\n </div>\n <div class="has-error">\n <span id="nameHelp" class="help-block" ng-if="createProjectForm.name.$error.required && createProjectForm.name.$dirty">\n Name is required.\n </span>\n </div>\n <div class="has-error">\n <span id="nameHelp" class="help-block" ng-if="createProjectForm.name.$error.minlength && createProjectForm.name.$touched">\n Name must have at least two characters.\n </span>\n </div>\n <div class="has-error">\n <span id="nameHelp" class="help-block" ng-if="createProjectForm.name.$error.pattern && createProjectForm.name.$touched">\n Project names may only contain lower-case letters, numbers, and dashes.\n They may not start or end with a dash.\n </span>\n </div>\n <div class="has-error">\n <span class="help-block" ng-if="nameTaken">\n This name is already in use. Please choose a different name.\n </span>\n </div>\n </div>\n\n <div class="form-group">\n <label for="displayName">Display Name</label>\n <input class="form-control input-lg"\n name="displayName"\n id="displayName"\n placeholder="My Project"\n type="text"\n ng-model="displayName">\n </div>\n\n <div class="form-group">\n <label for="description">Description</label>\n <textarea class="form-control input-lg"\n name="description"\n id="description"\n placeholder="A short description."\n ng-model="description"></textarea>\n </div>\n\n <div class="button-group">\n <button type="submit"\n class="btn btn-primary btn-lg"\n ng-class="{\'dialog-btn\': isDialog}"\n ng-click="createProject()"\n ng-disabled="createProjectForm.$invalid || nameTaken || disableInputs"\n value="">\n Create\n </button>\n <button\n class="btn btn-default btn-lg"\n ng-class="{\'dialog-btn\': isDialog}"\n ng-click="cancelCreateProject()">\n Cancel\n </button>\n </div>\n </fieldset>\n</form>\n'),
$templateCache.put("src/components/delete-project/delete-project-button.html", '<div class="actions">\n <!-- Avoid whitespace inside the link -->\n <a href=""\n ng-click="$event.stopPropagation(); openDeleteModal()"\n role="button"\n class="action-button"\n ng-attr-aria-disabled="{{disableDelete ? \'true\' : undefined}}"\n ng-class="{ \'disabled-link\': disableDelete }"\n ><i class="fa fa-trash-o" aria-hidden="true"\n ></i><span class="sr-only">Delete Project {{projectName}}</span></a>\n</div>\n'), $templateCache.put("src/components/delete-project/delete-project-modal.html", '<div class="delete-resource-modal">\n <!-- Use a form so that the enter key submits when typing a project name to confirm. -->\n <form>\n <div class="modal-body">\n <h1>Are you sure you want to delete the project\n \'<strong>{{displayName ? displayName : projectName}}</strong>\'?</h1>\n <p>\n This will <strong>delete all resources</strong> associated with\n the project {{displayName ? displayName : projectName}} and <strong>cannot be\n undone</strong>. Make sure this is something you really want to do!\n </p>\n <div ng-show="typeNameToConfirm">\n <p>Type the name of the project to confirm.</p>\n <p>\n <label class="sr-only" for="resource-to-delete">project to delete</label>\n <input\n ng-model="confirmName"\n id="resource-to-delete"\n type="text"\n class="form-control input-lg"\n autocorrect="off"\n autocapitalize="off"\n spellcheck="false"\n autofocus>\n </p>\n </div>\n </div>\n <div class="modal-footer">\n <button ng-disabled="typeNameToConfirm && confirmName !== projectName && confirmName !== displayName" class="btn btn-lg btn-danger" type="submit" ng-click="delete();">Delete</button>\n <button class="btn btn-lg btn-default" type="button" ng-click="cancel();">Cancel</button>\n </div>\n </form>\n</div>\n'), $templateCache.put("src/components/delete-project/delete-project-button.html", '<div class="actions">\n <!-- Avoid whitespace inside the link -->\n <a href=""\n ng-click="$event.stopPropagation(); openDeleteModal()"\n role="button"\n class="action-button"\n ng-attr-aria-disabled="{{disableDelete ? \'true\' : undefined}}"\n ng-class="{ \'disabled-link\': disableDelete }"\n ><i class="fa fa-trash-o" aria-hidden="true"\n ></i><span class="sr-only">Delete Project {{projectName}}</span></a>\n</div>\n'), $templateCache.put("src/components/delete-project/delete-project-modal.html", '<div class="delete-resource-modal">\n <!-- Use a form so that the enter key submits when typing a project name to confirm. -->\n <form>\n <div class="modal-body">\n <h1>Are you sure you want to delete the project\n \'<strong>{{displayName ? displayName : projectName}}</strong>\'?</h1>\n <p>\n This will <strong>delete all resources</strong> associated with\n the project {{displayName ? displayName : projectName}} and <strong>cannot be\n undone</strong>. Make sure this is something you really want to do!\n </p>\n <div ng-show="typeNameToConfirm">\n <p>Type the name of the project to confirm.</p>\n <p>\n <label class="sr-only" for="resource-to-delete">project to delete</label>\n <input\n ng-model="confirmName"\n id="resource-to-delete"\n type="text"\n class="form-control input-lg"\n autocorrect="off"\n autocapitalize="off"\n spellcheck="false"\n autofocus>\n </p>\n </div>\n </div>\n <div class="modal-footer">\n <button ng-disabled="typeNameToConfirm && confirmName !== projectName && confirmName !== displayName" class="btn btn-lg btn-danger" type="submit" ng-click="delete();">Delete</button>\n <button class="btn btn-lg btn-default" type="button" ng-click="cancel();">Cancel</button>\n </div>\n </form>\n</div>\n'),
$templateCache.put("src/components/delete-project/delete-project.html", '<a href="javascript:void(0)"\n ng-click="openDeleteModal()"\n role="button"\n ng-attr-aria-disabled="{{disableDelete ? \'true\' : undefined}}"\n ng-class="{ \'disabled-link\': disableDelete }"\n>{{label || \'Delete\'}}</a>\n'), $templateCache.put("src/components/edit-project/editProject.html", '<form name="editProjectForm">\n <fieldset ng-disabled="disableInputs">\n <div class="form-group">\n <label for="displayName">Display Name</label>\n <input class="form-control input-lg"\n name="displayName"\n id="displayName"\n placeholder="My Project"\n type="text"\n ng-model="editableFields.displayName">\n </div>\n\n <div class="form-group">\n <label for="description">Description</label>\n <textarea class="form-control input-lg"\n name="description"\n id="description"\n placeholder="A short description."\n ng-model="editableFields.description"></textarea>\n </div>\n\n <div class="button-group">\n <button type="submit"\n class="btn btn-primary btn-lg"\n ng-class="{\'dialog-btn\': isDialog}"\n ng-click="update()"\n ng-disabled="editProjectForm.$invalid || disableInputs"\n value="">{{submitButtonLabel}}</button>\n <button\n class="btn btn-default btn-lg"\n ng-class="{\'dialog-btn\': isDialog}"\n ng-click="cancelEditProject()">\n Cancel\n </button>\n </div>\n </fieldset>\n</form>\n'), $templateCache.put("src/components/delete-project/delete-project.html", '<a href="javascript:void(0)"\n ng-click="openDeleteModal()"\n role="button"\n ng-attr-aria-disabled="{{disableDelete ? \'true\' : undefined}}"\n ng-class="{ \'disabled-link\': disableDelete }"\n>{{label || \'Delete\'}}</a>\n'), $templateCache.put("src/components/edit-project/editProject.html", '<form name="editProjectForm">\n <fieldset ng-disabled="disableInputs">\n <div class="form-group">\n <label for="displayName">Display Name</label>\n <input class="form-control input-lg"\n name="displayName"\n id="displayName"\n placeholder="My Project"\n type="text"\n ng-model="editableFields.displayName">\n </div>\n\n <div class="form-group">\n <label for="description">Description</label>\n <textarea class="form-control input-lg"\n name="description"\n id="description"\n placeholder="A short description."\n ng-model="editableFields.description"></textarea>\n </div>\n\n <div class="button-group">\n <button type="submit"\n class="btn btn-primary btn-lg"\n ng-class="{\'dialog-btn\': isDialog}"\n ng-click="update()"\n ng-disabled="editProjectForm.$invalid || disableInputs"\n value="">{{submitButtonLabel}}</button>\n <button\n class="btn btn-default btn-lg"\n ng-class="{\'dialog-btn\': isDialog}"\n ng-click="cancelEditProject()">\n Cancel\n </button>\n </div>\n </fieldset>\n</form>\n'),
$templateCache.put("src/components/toast-notifications/toast-notifications.html", '<div class="toast-notifications-list-pf">\n <div ng-repeat="(notificationID, notification) in notifications track by (notificationID + (notification.message || notification.details))" ng-if="!notification.hidden"\n ng-mouseenter="setHover(notification, true)" ng-mouseleave="setHover(notification, false)">\n <div class="toast-pf alert {{notification.type | alertStatus}}" ng-class="{\'alert-dismissable\': !hideCloseButton}">\n <button ng-if="!hideCloseButton" type="button" class="close" ng-click="close(notification)">\n <span class="pficon pficon-close" aria-hidden="true"></span>\n <span class="sr-only">Close</span>\n </button>\n <span class="{{notification.type | alertIcon}}" aria-hidden="true"></span>\n <span class="sr-only">{{notification.type}}</span>\n <span class="toast-notification-message" ng-if="notification.message">{{notification.message}}</span>\n <span ng-if="notification.details">\n <truncate-long-text\n limit="200"\n content="notification.details"\n use-word-boundary="true"\n expandable="true"\n hide-collapse="true">\n </truncate-long-text>\n </span>\n <span ng-repeat="link in notification.links">\n <a ng-if="!link.href" href="" ng-click="onClick(notification, link)" role="button">{{link.label}}</a>\n <a ng-if="link.href" ng-href="{{link.href}}" ng-attr-target="{{link.target}}">{{link.label}}</a>\n <span ng-if="!$last" class="toast-action-divider">|</span>\n </span>\n </div>\n </div>\n</div>\n'), $templateCache.put("src/components/toast-notifications/toast-notifications.html", '<div class="toast-notifications-list-pf">\n <div ng-repeat="(notificationID, notification) in notifications track by (notificationID + (notification.message || notification.details))" ng-if="!notification.hidden || notification.isHover"\n ng-mouseenter="setHover(notification, true)" ng-mouseleave="setHover(notification, false)">\n <div class="toast-pf alert {{notification.type | alertStatus}}" ng-class="{\'alert-dismissable\': !hideCloseButton}">\n <button ng-if="!hideCloseButton" type="button" class="close" ng-click="close(notification)">\n <span class="pficon pficon-close" aria-hidden="true"></span>\n <span class="sr-only">Close</span>\n </button>\n <span class="{{notification.type | alertIcon}}" aria-hidden="true"></span>\n <span class="sr-only">{{notification.type}}</span>\n <span class="toast-notification-message" ng-if="notification.message">{{notification.message}}</span>\n <span ng-if="notification.details">\n <truncate-long-text\n limit="200"\n content="notification.details"\n use-word-boundary="true"\n expandable="true"\n hide-collapse="true">\n </truncate-long-text>\n </span>\n <span ng-repeat="link in notification.links">\n <a ng-if="!link.href" href="" ng-click="onClick(notification, link)" role="button">{{link.label}}</a>\n <a ng-if="link.href" ng-href="{{link.href}}" ng-attr-target="{{link.target}}">{{link.label}}</a>\n <span ng-if="!$last" class="toast-action-divider">|</span>\n </span>\n </div>\n </div>\n</div>\n'),
$templateCache.put("src/components/truncate-long-text/truncateLongText.html", '<!--\n Do not remove class `truncated-content` (here or below) even though it\'s not\n styled directly in origin-web-common. `truncated-content` is used by\n origin-web-console in certain contexts.\n-->\n<span ng-if="!truncated" ng-bind-html="content | highlightKeywords : keywords" class="truncated-content"></span>\n<span ng-if="truncated">\n <span ng-if="!toggles.expanded">\n <span ng-attr-title="{{content}}" class="truncation-block">\n <span ng-bind-html="truncatedContent | highlightKeywords : keywords" class="truncated-content"></span>&hellip;\n </span>\n <a ng-if="expandable" href="" ng-click="toggles.expanded = true" class="nowrap">See All</a>\n </span>\n <span ng-if="toggles.expanded">\n <div ng-if="prettifyJson" class="well">\n <span ng-if="!hideCollapse" class="pull-right" style="margin-top: -10px;"><a href="" ng-click="toggles.expanded = false" class="truncation-collapse-link">Collapse</a></span>\n <span ng-bind-html="content | prettifyJSON | highlightKeywords : keywords" class="pretty-json truncated-content"></span>\n </div>\n <span ng-if="!prettifyJson">\n <span ng-if="!hideCollapse" class="pull-right"><a href="" ng-click="toggles.expanded = false" class="truncation-collapse-link">Collapse</a></span>\n <span ng-bind-html="content | highlightKeywords : keywords" class="truncated-content"></span>\n </span>\n </span>\n</span>\n'); $templateCache.put("src/components/truncate-long-text/truncateLongText.html", '<!--\n Do not remove class `truncated-content` (here or below) even though it\'s not\n styled directly in origin-web-common. `truncated-content` is used by\n origin-web-console in certain contexts.\n-->\n<span ng-if="!truncated" ng-bind-html="content | highlightKeywords : keywords" class="truncated-content"></span>\n<span ng-if="truncated">\n <span ng-if="!toggles.expanded">\n <span ng-attr-title="{{content}}" class="truncation-block">\n <span ng-bind-html="truncatedContent | highlightKeywords : keywords" class="truncated-content"></span>&hellip;\n </span>\n <a ng-if="expandable" href="" ng-click="toggles.expanded = true" class="nowrap">See All</a>\n </span>\n <span ng-if="toggles.expanded">\n <div ng-if="prettifyJson" class="well">\n <span ng-if="!hideCollapse" class="pull-right" style="margin-top: -10px;"><a href="" ng-click="toggles.expanded = false" class="truncation-collapse-link">Collapse</a></span>\n <span ng-bind-html="content | prettifyJSON | highlightKeywords : keywords" class="pretty-json truncated-content"></span>\n </div>\n <span ng-if="!prettifyJson">\n <span ng-if="!hideCollapse" class="pull-right"><a href="" ng-click="toggles.expanded = false" class="truncation-collapse-link">Collapse</a></span>\n <span ng-bind-html="content | highlightKeywords : keywords" class="truncated-content"></span>\n </span>\n </span>\n</span>\n');
} ]), angular.module("openshiftCommonUI").component("bindApplicationForm", { } ]), angular.module("openshiftCommonUI").component("bindApplicationForm", {
controllerAs:"ctrl", controllerAs:"ctrl",
...@@ -401,28 +401,38 @@ t && (t.closest("a", element).length || t.closest("button", element).length) || ...@@ -401,28 +401,38 @@ t && (t.closest("a", element).length || t.closest("button", element).length) ||
}); });
} }
}; };
}), angular.module("openshiftCommonUI").directive("toastNotifications", [ "NotificationsService", "$timeout", function(NotificationsService, $timeout) { }), angular.module("openshiftCommonUI").directive("toastNotifications", [ "NotificationsService", "$rootScope", "$timeout", function(NotificationsService, $rootScope, $timeout) {
return { return {
restrict:"E", restrict:"E",
scope:{}, scope:{},
templateUrl:"src/components/toast-notifications/toast-notifications.html", templateUrl:"src/components/toast-notifications/toast-notifications.html",
link:function($scope) { link:function($scope) {
$scope.notifications = NotificationsService.getNotifications(), $scope.close = function(notification) { $scope.notifications = [];
notification.hidden = !0, _.isFunction(notification.onClose) && notification.onClose(); var isRemoved = function(notification) {
return notification.hidden && !notification.isHover;
}, removeNotification = function(notification) {
notification.isHover = !1, notification.hidden = !0;
}, pruneRemovedNotifications = function() {
$scope.notifications = _.reject($scope.notifications, isRemoved);
};
$scope.close = function(notification) {
removeNotification(notification), _.isFunction(notification.onClose) && notification.onClose();
}, $scope.onClick = function(notification, link) { }, $scope.onClick = function(notification, link) {
if (_.isFunction(link.onClick)) { if (_.isFunction(link.onClick)) {
var close = link.onClick(); var close = link.onClick();
close && (notification.hidden = !0); close && removeNotification(notification);
} }
}, $scope.setHover = function(notification, isHover) { }, $scope.setHover = function(notification, isHover) {
notification.isHover = isHover; isRemoved(notification) || (notification.isHover = isHover);
}, $scope.$watch("notifications", function() { };
_.each($scope.notifications, function(notification) { var deregisterNotificationListener = $rootScope.$on("NotificationsService.onNotificationAdded", function(event, notification) {
NotificationsService.isAutoDismiss(notification) && !notification.hidden && (notification.timerId ? -1 !== notification.timerId || notification.isHover || (notification.hidden = !0) :notification.timerId = $timeout(function() { $scope.notifications.push(notification), NotificationsService.isAutoDismiss(notification) && $timeout(function() {
notification.timerId = -1, notification.isHover || (notification.hidden = !0); notification.hidden = !0;
}, NotificationsService.dismissDelay)); }, NotificationsService.dismissDelay), pruneRemovedNotifications();
});
$scope.$on("$destroy", function() {
deregisterNotificationListener && (deregisterNotificationListener(), deregisterNotificationListener = null);
}); });
}, !0);
} }
}; };
} ]), angular.module("openshiftCommonUI").directive("truncateLongText", [ "truncateFilter", function(truncateFilter) { } ]), angular.module("openshiftCommonUI").directive("truncateLongText", [ "truncateFilter", function(truncateFilter) {
...@@ -1355,7 +1365,7 @@ self._isImmutable(resource) && (existingImmutableData ? existingImmutableData.up ...@@ -1355,7 +1365,7 @@ self._isImmutable(resource) && (existingImmutableData ? existingImmutableData.up
}).error(function(data, status, headers, config) { }).error(function(data, status, headers, config) {
if (opts.errorNotification !== !1) { if (opts.errorNotification !== !1) {
var msg = "Failed to get " + resource + "/" + name; var msg = "Failed to get " + resource + "/" + name;
0 !== status && (msg += " (" + status + ")"), $rootScope.$emit("addNotification", { 0 !== status && (msg += " (" + status + ")"), $rootScope.$emit("NotificationsService.addNotification", {
type:"error", type:"error",
message:msg message:msg
}); });
...@@ -1567,7 +1577,7 @@ self._listInFlight(key, !1); ...@@ -1567,7 +1577,7 @@ self._listInFlight(key, !1);
var deferred = self._listDeferred(key); var deferred = self._listDeferred(key);
if (delete self._listDeferredMap[key], deferred.reject(data, status, headers, config), _.get(opts, "errorNotification", !0)) { if (delete self._listDeferredMap[key], deferred.reject(data, status, headers, config), _.get(opts, "errorNotification", !0)) {
var msg = "Failed to list " + resource; var msg = "Failed to list " + resource;
0 !== status && (msg += " (" + status + ")"), $rootScope.$emit("addNotification", { 0 !== status && (msg += " (" + status + ")"), $rootScope.$emit("NotificationsService.addNotification", {
type:"error", type:"error",
message:msg message:msg
}); });
...@@ -1584,7 +1594,7 @@ self._listInFlight(key, !1); ...@@ -1584,7 +1594,7 @@ self._listInFlight(key, !1);
var deferred = self._listDeferred(key); var deferred = self._listDeferred(key);
if (delete self._listDeferredMap[key], deferred.reject(data, status, headers, config), _.get(opts, "errorNotification", !0)) { if (delete self._listDeferredMap[key], deferred.reject(data, status, headers, config), _.get(opts, "errorNotification", !0)) {
var msg = "Failed to list " + resource; var msg = "Failed to list " + resource;
0 !== status && (msg += " (" + status + ")"), $rootScope.$emit("addNotification", { 0 !== status && (msg += " (" + status + ")"), $rootScope.$emit("NotificationsService.addNotification", {
type:"error", type:"error",
message:msg message:msg
}); });
...@@ -1652,7 +1662,7 @@ if (eventWS !== registeredWS) return void Logger.log("Skipping reopen, eventWS d ...@@ -1652,7 +1662,7 @@ if (eventWS !== registeredWS) return void Logger.log("Skipping reopen, eventWS d
if (this._watchInFlight(key, !1), eventWS.shouldClose) return void Logger.log("Skipping reopen, eventWS was explicitly closed", eventWS); if (this._watchInFlight(key, !1), eventWS.shouldClose) return void Logger.log("Skipping reopen, eventWS was explicitly closed", eventWS);
if (event.wasClean) return void Logger.log("Skipping reopen, clean close", event); if (event.wasClean) return void Logger.log("Skipping reopen, clean close", event);
if (!this._watchCallbacks(key).has()) return void Logger.log("Skipping reopen, no listeners registered for resource/context", resource, context); if (!this._watchCallbacks(key).has()) return void Logger.log("Skipping reopen, no listeners registered for resource/context", resource, context);
if (this._isTooManyWebsocketRetries(key)) return void (_.get(opts, "errorNotification", !0) && $rootScope.$emit("addNotification", { if (this._isTooManyWebsocketRetries(key)) return void (_.get(opts, "errorNotification", !0) && $rootScope.$emit("NotificationsService.addNotification", {
id:"websocket_retry_halted", id:"websocket_retry_halted",
type:"error", type:"error",
message:"Server connection interrupted.", message:"Server connection interrupted.",
...@@ -2225,7 +2235,7 @@ this.dismissDelay = 8e3, this.autoDismissTypes = [ "info", "success" ], this.$ge ...@@ -2225,7 +2235,7 @@ this.dismissDelay = 8e3, this.autoDismissTypes = [ "info", "success" ], this.$ge
var notifications = [], dismissDelay = this.dismissDelay, autoDismissTypes = this.autoDismissTypes, notificationHiddenKey = function(notificationID, namespace) { var notifications = [], dismissDelay = this.dismissDelay, autoDismissTypes = this.autoDismissTypes, notificationHiddenKey = function(notificationID, namespace) {
return namespace ? "hide/notification/" + namespace + "/" + notificationID :"hide/notification/" + notificationID; return namespace ? "hide/notification/" + namespace + "/" + notificationID :"hide/notification/" + notificationID;
}, addNotification = function(notification) { }, addNotification = function(notification) {
isNotificationPermanentlyHidden(notification) || isNotificationVisible(notification) || notifications.push(notification); isNotificationPermanentlyHidden(notification) || isNotificationVisible(notification) || (notifications.push(notification), $rootScope.$emit("NotificationsService.onNotificationAdded", notification));
}, hideNotification = function(notificationID) { }, hideNotification = function(notificationID) {
notificationID && _.each(notifications, function(notification) { notificationID && _.each(notifications, function(notification) {
notification.id === notificationID && (notification.hidden = !0); notification.id === notificationID && (notification.hidden = !0);
...@@ -2248,7 +2258,7 @@ return !next.hidden && notification.id === next.id; ...@@ -2248,7 +2258,7 @@ return !next.hidden && notification.id === next.id;
}, isAutoDismiss = function(notification) { }, isAutoDismiss = function(notification) {
return _.includes(autoDismissTypes, notification.type); return _.includes(autoDismissTypes, notification.type);
}; };
return $rootScope.$on("addNotification", function(event, data) { return $rootScope.$on("NotificationsService.addNotification", function(event, data) {
addNotification(data); addNotification(data);
}), { }), {
addNotification:addNotification, addNotification:addNotification,
......
...@@ -361,7 +361,7 @@ angular.module('openshiftCommonUI').run(['$templateCache', function($templateCac ...@@ -361,7 +361,7 @@ angular.module('openshiftCommonUI').run(['$templateCache', function($templateCac
$templateCache.put('src/components/toast-notifications/toast-notifications.html', $templateCache.put('src/components/toast-notifications/toast-notifications.html',
"<div class=\"toast-notifications-list-pf\">\n" + "<div class=\"toast-notifications-list-pf\">\n" +
" <div ng-repeat=\"(notificationID, notification) in notifications track by (notificationID + (notification.message || notification.details))\" ng-if=\"!notification.hidden\"\n" + " <div ng-repeat=\"(notificationID, notification) in notifications track by (notificationID + (notification.message || notification.details))\" ng-if=\"!notification.hidden || notification.isHover\"\n" +
" ng-mouseenter=\"setHover(notification, true)\" ng-mouseleave=\"setHover(notification, false)\">\n" + " ng-mouseenter=\"setHover(notification, true)\" ng-mouseleave=\"setHover(notification, false)\">\n" +
" <div class=\"toast-pf alert {{notification.type | alertStatus}}\" ng-class=\"{'alert-dismissable': !hideCloseButton}\">\n" + " <div class=\"toast-pf alert {{notification.type | alertStatus}}\" ng-class=\"{'alert-dismissable': !hideCloseButton}\">\n" +
" <button ng-if=\"!hideCloseButton\" type=\"button\" class=\"close\" ng-click=\"close(notification)\">\n" + " <button ng-if=\"!hideCloseButton\" type=\"button\" class=\"close\" ng-click=\"close(notification)\">\n" +
......
<div class="toast-notifications-list-pf"> <div class="toast-notifications-list-pf">
<div ng-repeat="(notificationID, notification) in notifications track by (notificationID + (notification.message || notification.details))" ng-if="!notification.hidden" <div ng-repeat="(notificationID, notification) in notifications track by (notificationID + (notification.message || notification.details))" ng-if="!notification.hidden || notification.isHover"
ng-mouseenter="setHover(notification, true)" ng-mouseleave="setHover(notification, false)"> ng-mouseenter="setHover(notification, true)" ng-mouseleave="setHover(notification, false)">
<div class="toast-pf alert {{notification.type | alertStatus}}" ng-class="{'alert-dismissable': !hideCloseButton}"> <div class="toast-pf alert {{notification.type | alertStatus}}" ng-class="{'alert-dismissable': !hideCloseButton}">
<button ng-if="!hideCloseButton" type="button" class="close" ng-click="close(notification)"> <button ng-if="!hideCloseButton" type="button" class="close" ng-click="close(notification)">
......
'use strict'; 'use strict';
angular.module('openshiftCommonUI') angular.module('openshiftCommonUI')
.directive('toastNotifications', function(NotificationsService, $timeout) { .directive('toastNotifications', function(NotificationsService, $rootScope, $timeout) {
return { return {
restrict: 'E', restrict: 'E',
scope: {}, scope: {},
templateUrl: 'src/components/toast-notifications/toast-notifications.html', templateUrl: 'src/components/toast-notifications/toast-notifications.html',
link: function($scope) { link: function($scope) {
$scope.notifications = NotificationsService.getNotifications(); $scope.notifications = [];
$scope.close = function(notification) { // A notification is removed if it has hidden set and the user isn't
// currently hovering over it.
var isRemoved = function(notification) {
return notification.hidden && !notification.isHover;
};
var removeNotification = function(notification) {
notification.isHover = false;
notification.hidden = true; notification.hidden = true;
};
// Remove items that are now hidden to keep the array from growing
// indefinitely. We loop over the entire array each digest loop, even
// if everything is hidden, and any watch update triggers a new digest
// loop. If the array grows large, it can hurt performance.
var pruneRemovedNotifications = function() {
$scope.notifications = _.reject($scope.notifications, isRemoved);
};
$scope.close = function(notification) {
removeNotification(notification);
if (_.isFunction(notification.onClose)) { if (_.isFunction(notification.onClose)) {
notification.onClose(); notification.onClose();
} }
}; };
$scope.onClick = function(notification, link) { $scope.onClick = function(notification, link) {
if (_.isFunction(link.onClick)) { if (_.isFunction(link.onClick)) {
// If onClick() returns true, also hide the alert. // If onClick() returns true, also hide the alert.
var close = link.onClick(); var close = link.onClick();
if (close) { if (close) {
notification.hidden = true; removeNotification(notification);
} }
} }
}; };
$scope.setHover = function(notification, isHover) { $scope.setHover = function(notification, isHover) {
// Don't change anything if the notification was already removed.
// Avoids a potential issue where the flag is reset during the slide
// out animation.
if (!isRemoved(notification)) {
notification.isHover = isHover; notification.isHover = isHover;
}
}; };
$scope.$watch('notifications', function() { // Listen for updates from NotificationsService to show a notification.
_.each($scope.notifications, function(notification) { var deregisterNotificationListener = $rootScope.$on('NotificationsService.onNotificationAdded', function(event, notification) {
if (NotificationsService.isAutoDismiss(notification) && !notification.hidden) { $scope.notifications.push(notification);
if (!notification.timerId) { if (NotificationsService.isAutoDismiss(notification)) {
notification.timerId = $timeout(function () { $timeout(function () {
notification.timerId = -1;
if (!notification.isHover) {
notification.hidden = true; notification.hidden = true;
}
}, NotificationsService.dismissDelay); }, NotificationsService.dismissDelay);
} else if (notification.timerId === -1 && !notification.isHover) {
notification.hidden = true;
} }
// Whenever we add a new notification, also remove any hidden toasts
// so that the array doesn't grow indefinitely.
pruneRemovedNotifications();
});
$scope.$on('$destroy', function() {
if (deregisterNotificationListener) {
deregisterNotificationListener();
deregisterNotificationListener = null;
} }
}); });
}, true);
} }
}; };
}); });
...@@ -418,7 +418,7 @@ angular.module('openshiftCommonServices') ...@@ -418,7 +418,7 @@ angular.module('openshiftCommonServices')
} }
// Use `$rootScope.$emit` instead of NotificationsService directly // Use `$rootScope.$emit` instead of NotificationsService directly
// so that DataService doesn't add a dependency on `openshiftCommonUI` // so that DataService doesn't add a dependency on `openshiftCommonUI`
$rootScope.$emit('addNotification', { $rootScope.$emit('NotificationsService.addNotification', {
type: 'error', type: 'error',
message: msg message: msg
}); });
...@@ -963,7 +963,7 @@ DataService.prototype.createStream = function(resource, name, context, opts, isR ...@@ -963,7 +963,7 @@ DataService.prototype.createStream = function(resource, name, context, opts, isR
} }
// Use `$rootScope.$emit` instead of NotificationsService directly // Use `$rootScope.$emit` instead of NotificationsService directly
// so that DataService doesn't add a dependency on `openshiftCommonUI` // so that DataService doesn't add a dependency on `openshiftCommonUI`
$rootScope.$emit('addNotification', { $rootScope.$emit('NotificationsService.addNotification', {
type: 'error', type: 'error',
message: msg message: msg
}); });
...@@ -994,7 +994,7 @@ DataService.prototype.createStream = function(resource, name, context, opts, isR ...@@ -994,7 +994,7 @@ DataService.prototype.createStream = function(resource, name, context, opts, isR
} }
// Use `$rootScope.$emit` instead of NotificationsService directly // Use `$rootScope.$emit` instead of NotificationsService directly
// so that DataService doesn't add a dependency on `openshiftCommonUI` // so that DataService doesn't add a dependency on `openshiftCommonUI`
$rootScope.$emit('addNotification', { $rootScope.$emit('NotificationsService.addNotification', {
type: 'error', type: 'error',
message: msg message: msg
}); });
...@@ -1184,7 +1184,7 @@ DataService.prototype.createStream = function(resource, name, context, opts, isR ...@@ -1184,7 +1184,7 @@ DataService.prototype.createStream = function(resource, name, context, opts, isR
if (_.get(opts, 'errorNotification', true)) { if (_.get(opts, 'errorNotification', true)) {
// Use `$rootScope.$emit` instead of NotificationsService directly // Use `$rootScope.$emit` instead of NotificationsService directly
// so that DataService doesn't add a dependency on `openshiftCommonUI` // so that DataService doesn't add a dependency on `openshiftCommonUI`
$rootScope.$emit('addNotification', { $rootScope.$emit('NotificationsService.addNotification', {
id: 'websocket_retry_halted', id: 'websocket_retry_halted',
type: 'error', type: 'error',
message: 'Server connection interrupted.', message: 'Server connection interrupted.',
......
...@@ -23,6 +23,7 @@ angular.module('openshiftCommonUI').provider('NotificationsService', function() ...@@ -23,6 +23,7 @@ angular.module('openshiftCommonUI').provider('NotificationsService', function()
} }
notifications.push(notification); notifications.push(notification);
$rootScope.$emit('NotificationsService.onNotificationAdded', notification);
}; };
var hideNotification = function (notificationID) { var hideNotification = function (notificationID) {
...@@ -75,7 +76,7 @@ angular.module('openshiftCommonUI').provider('NotificationsService', function() ...@@ -75,7 +76,7 @@ angular.module('openshiftCommonUI').provider('NotificationsService', function()
}; };
// Also handle `addNotification` events on $rootScope, which is used by DataService. // Also handle `addNotification` events on $rootScope, which is used by DataService.
$rootScope.$on('addNotification', function(event, data) { $rootScope.$on('NotificationsService.addNotification', function(event, data) {
addNotification(data); addNotification(data);
}); });
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment