Commit 9a05daa0 by Samuel Padgett

Improve toast notification performance

parent b51786f7
......@@ -1538,7 +1538,7 @@ angular.module('openshiftCommonServices')
}
// Use `$rootScope.$emit` instead of NotificationsService directly
// so that DataService doesn't add a dependency on `openshiftCommonUI`
$rootScope.$emit('addNotification', {
$rootScope.$emit('NotificationsService.addNotification', {
type: 'error',
message: msg
});
......@@ -2083,7 +2083,7 @@ DataService.prototype.createStream = function(resource, name, context, opts, isR
}
// Use `$rootScope.$emit` instead of NotificationsService directly
// so that DataService doesn't add a dependency on `openshiftCommonUI`
$rootScope.$emit('addNotification', {
$rootScope.$emit('NotificationsService.addNotification', {
type: 'error',
message: msg
});
......@@ -2114,7 +2114,7 @@ DataService.prototype.createStream = function(resource, name, context, opts, isR
}
// Use `$rootScope.$emit` instead of NotificationsService directly
// so that DataService doesn't add a dependency on `openshiftCommonUI`
$rootScope.$emit('addNotification', {
$rootScope.$emit('NotificationsService.addNotification', {
type: 'error',
message: msg
});
......@@ -2304,7 +2304,7 @@ DataService.prototype.createStream = function(resource, name, context, opts, isR
if (_.get(opts, 'errorNotification', true)) {
// Use `$rootScope.$emit` instead of NotificationsService directly
// so that DataService doesn't add a dependency on `openshiftCommonUI`
$rootScope.$emit('addNotification', {
$rootScope.$emit('NotificationsService.addNotification', {
id: 'websocket_retry_halted',
type: 'error',
message: 'Server connection interrupted.',
......
......@@ -390,7 +390,7 @@ hawtioPluginLoader.addModule('openshiftCommonUI');
$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" +
" <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" +
......@@ -1016,49 +1016,79 @@ angular.module('openshiftCommonUI')
;'use strict';
angular.module('openshiftCommonUI')
.directive('toastNotifications', function(NotificationsService, $timeout) {
.directive('toastNotifications', function(NotificationsService, $rootScope, $timeout) {
return {
restrict: 'E',
scope: {},
templateUrl: 'src/components/toast-notifications/toast-notifications.html',
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;
};
// 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)) {
notification.onClose();
}
};
$scope.onClick = function(notification, link) {
if (_.isFunction(link.onClick)) {
// If onClick() returns true, also hide the alert.
var close = link.onClick();
if (close) {
notification.hidden = true;
removeNotification(notification);
}
}
};
$scope.setHover = function(notification, isHover) {
notification.isHover = 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;
}
};
$scope.$watch('notifications', function() {
_.each($scope.notifications, function(notification) {
if (NotificationsService.isAutoDismiss(notification) && !notification.hidden) {
if (!notification.timerId) {
notification.timerId = $timeout(function () {
notification.timerId = -1;
if (!notification.isHover) {
notification.hidden = true;
}
}, NotificationsService.dismissDelay);
} else if (notification.timerId === -1 && !notification.isHover) {
notification.hidden = true;
}
}
});
}, true);
// Listen for updates from NotificationsService to show a notification.
var deregisterNotificationListener = $rootScope.$on('NotificationsService.onNotificationAdded', function(event, notification) {
$scope.notifications.push(notification);
if (NotificationsService.isAutoDismiss(notification)) {
$timeout(function () {
notification.hidden = true;
}, NotificationsService.dismissDelay);
}
// 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;
}
});
}
};
});
......@@ -1998,6 +2028,7 @@ angular.module('openshiftCommonUI').provider('NotificationsService', function()
}
notifications.push(notification);
$rootScope.$emit('NotificationsService.onNotificationAdded', notification);
};
var hideNotification = function (notificationID) {
......@@ -2050,7 +2081,7 @@ angular.module('openshiftCommonUI').provider('NotificationsService', function()
};
// 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);
});
......
......@@ -561,7 +561,7 @@ hawtioPluginLoader.addModule('openshiftCommonUI');
$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" +
" <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" +
......@@ -1187,49 +1187,79 @@ angular.module('openshiftCommonUI')
;'use strict';
angular.module('openshiftCommonUI')
.directive('toastNotifications', ["NotificationsService", "$timeout", function(NotificationsService, $timeout) {
.directive('toastNotifications', ["NotificationsService", "$rootScope", "$timeout", function(NotificationsService, $rootScope, $timeout) {
return {
restrict: 'E',
scope: {},
templateUrl: 'src/components/toast-notifications/toast-notifications.html',
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;
};
// 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)) {
notification.onClose();
}
};
$scope.onClick = function(notification, link) {
if (_.isFunction(link.onClick)) {
// If onClick() returns true, also hide the alert.
var close = link.onClick();
if (close) {
notification.hidden = true;
removeNotification(notification);
}
}
};
$scope.setHover = function(notification, isHover) {
notification.isHover = 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;
}
};
$scope.$watch('notifications', function() {
_.each($scope.notifications, function(notification) {
if (NotificationsService.isAutoDismiss(notification) && !notification.hidden) {
if (!notification.timerId) {
notification.timerId = $timeout(function () {
notification.timerId = -1;
if (!notification.isHover) {
notification.hidden = true;
}
}, NotificationsService.dismissDelay);
} else if (notification.timerId === -1 && !notification.isHover) {
notification.hidden = true;
}
}
});
}, true);
// Listen for updates from NotificationsService to show a notification.
var deregisterNotificationListener = $rootScope.$on('NotificationsService.onNotificationAdded', function(event, notification) {
$scope.notifications.push(notification);
if (NotificationsService.isAutoDismiss(notification)) {
$timeout(function () {
notification.hidden = true;
}, NotificationsService.dismissDelay);
}
// 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;
}
});
}
};
}]);
......@@ -3262,7 +3292,7 @@ angular.module('openshiftCommonServices')
}
// Use `$rootScope.$emit` instead of NotificationsService directly
// so that DataService doesn't add a dependency on `openshiftCommonUI`
$rootScope.$emit('addNotification', {
$rootScope.$emit('NotificationsService.addNotification', {
type: 'error',
message: msg
});
......@@ -3807,7 +3837,7 @@ DataService.prototype.createStream = function(resource, name, context, opts, isR
}
// Use `$rootScope.$emit` instead of NotificationsService directly
// so that DataService doesn't add a dependency on `openshiftCommonUI`
$rootScope.$emit('addNotification', {
$rootScope.$emit('NotificationsService.addNotification', {
type: 'error',
message: msg
});
......@@ -3838,7 +3868,7 @@ DataService.prototype.createStream = function(resource, name, context, opts, isR
}
// Use `$rootScope.$emit` instead of NotificationsService directly
// so that DataService doesn't add a dependency on `openshiftCommonUI`
$rootScope.$emit('addNotification', {
$rootScope.$emit('NotificationsService.addNotification', {
type: 'error',
message: msg
});
......@@ -4028,7 +4058,7 @@ DataService.prototype.createStream = function(resource, name, context, opts, isR
if (_.get(opts, 'errorNotification', true)) {
// Use `$rootScope.$emit` instead of NotificationsService directly
// so that DataService doesn't add a dependency on `openshiftCommonUI`
$rootScope.$emit('addNotification', {
$rootScope.$emit('NotificationsService.addNotification', {
id: 'websocket_retry_halted',
type: 'error',
message: 'Server connection interrupted.',
......@@ -5178,6 +5208,7 @@ angular.module('openshiftCommonUI').provider('NotificationsService', function()
}
notifications.push(notification);
$rootScope.$emit('NotificationsService.onNotificationAdded', notification);
};
var hideNotification = function (notificationID) {
......@@ -5230,7 +5261,7 @@ angular.module('openshiftCommonUI').provider('NotificationsService', function()
};
// 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);
});
......
......@@ -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/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/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');
} ]), angular.module("openshiftCommonUI").component("bindApplicationForm", {
controllerAs:"ctrl",
......@@ -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 {
restrict:"E",
scope:{},
templateUrl:"src/components/toast-notifications/toast-notifications.html",
link:function($scope) {
$scope.notifications = NotificationsService.getNotifications(), $scope.close = function(notification) {
notification.hidden = !0, _.isFunction(notification.onClose) && notification.onClose();
$scope.notifications = [];
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) {
if (_.isFunction(link.onClick)) {
var close = link.onClick();
close && (notification.hidden = !0);
close && removeNotification(notification);
}
}, $scope.setHover = function(notification, isHover) {
notification.isHover = isHover;
}, $scope.$watch("notifications", function() {
_.each($scope.notifications, function(notification) {
NotificationsService.isAutoDismiss(notification) && !notification.hidden && (notification.timerId ? -1 !== notification.timerId || notification.isHover || (notification.hidden = !0) :notification.timerId = $timeout(function() {
notification.timerId = -1, notification.isHover || (notification.hidden = !0);
}, NotificationsService.dismissDelay));
isRemoved(notification) || (notification.isHover = isHover);
};
var deregisterNotificationListener = $rootScope.$on("NotificationsService.onNotificationAdded", function(event, notification) {
$scope.notifications.push(notification), NotificationsService.isAutoDismiss(notification) && $timeout(function() {
notification.hidden = !0;
}, NotificationsService.dismissDelay), pruneRemovedNotifications();
});
$scope.$on("$destroy", function() {
deregisterNotificationListener && (deregisterNotificationListener(), deregisterNotificationListener = null);
});
}, !0);
}
};
} ]), angular.module("openshiftCommonUI").directive("truncateLongText", [ "truncateFilter", function(truncateFilter) {
......@@ -1355,7 +1365,7 @@ self._isImmutable(resource) && (existingImmutableData ? existingImmutableData.up
}).error(function(data, status, headers, config) {
if (opts.errorNotification !== !1) {
var msg = "Failed to get " + resource + "/" + name;
0 !== status && (msg += " (" + status + ")"), $rootScope.$emit("addNotification", {
0 !== status && (msg += " (" + status + ")"), $rootScope.$emit("NotificationsService.addNotification", {
type:"error",
message:msg
});
......@@ -1567,7 +1577,7 @@ self._listInFlight(key, !1);
var deferred = self._listDeferred(key);
if (delete self._listDeferredMap[key], deferred.reject(data, status, headers, config), _.get(opts, "errorNotification", !0)) {
var msg = "Failed to list " + resource;
0 !== status && (msg += " (" + status + ")"), $rootScope.$emit("addNotification", {
0 !== status && (msg += " (" + status + ")"), $rootScope.$emit("NotificationsService.addNotification", {
type:"error",
message:msg
});
......@@ -1584,7 +1594,7 @@ self._listInFlight(key, !1);
var deferred = self._listDeferred(key);
if (delete self._listDeferredMap[key], deferred.reject(data, status, headers, config), _.get(opts, "errorNotification", !0)) {
var msg = "Failed to list " + resource;
0 !== status && (msg += " (" + status + ")"), $rootScope.$emit("addNotification", {
0 !== status && (msg += " (" + status + ")"), $rootScope.$emit("NotificationsService.addNotification", {
type:"error",
message:msg
});
......@@ -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 (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._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",
type:"error",
message:"Server connection interrupted.",
......@@ -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) {
return namespace ? "hide/notification/" + namespace + "/" + notificationID :"hide/notification/" + notificationID;
}, 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) {
notificationID && _.each(notifications, function(notification) {
notification.id === notificationID && (notification.hidden = !0);
......@@ -2248,7 +2258,7 @@ return !next.hidden && notification.id === next.id;
}, isAutoDismiss = function(notification) {
return _.includes(autoDismissTypes, notification.type);
};
return $rootScope.$on("addNotification", function(event, data) {
return $rootScope.$on("NotificationsService.addNotification", function(event, data) {
addNotification(data);
}), {
addNotification:addNotification,
......
......@@ -361,7 +361,7 @@ angular.module('openshiftCommonUI').run(['$templateCache', function($templateCac
$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" +
" <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" +
......
<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)">
<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)">
......
'use strict';
angular.module('openshiftCommonUI')
.directive('toastNotifications', function(NotificationsService, $timeout) {
.directive('toastNotifications', function(NotificationsService, $rootScope, $timeout) {
return {
restrict: 'E',
scope: {},
templateUrl: 'src/components/toast-notifications/toast-notifications.html',
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;
};
// 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)) {
notification.onClose();
}
};
$scope.onClick = function(notification, link) {
if (_.isFunction(link.onClick)) {
// If onClick() returns true, also hide the alert.
var close = link.onClick();
if (close) {
notification.hidden = true;
removeNotification(notification);
}
}
};
$scope.setHover = function(notification, isHover) {
notification.isHover = 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;
}
};
$scope.$watch('notifications', function() {
_.each($scope.notifications, function(notification) {
if (NotificationsService.isAutoDismiss(notification) && !notification.hidden) {
if (!notification.timerId) {
notification.timerId = $timeout(function () {
notification.timerId = -1;
if (!notification.isHover) {
notification.hidden = true;
}
}, NotificationsService.dismissDelay);
} else if (notification.timerId === -1 && !notification.isHover) {
notification.hidden = true;
}
}
});
}, true);
// Listen for updates from NotificationsService to show a notification.
var deregisterNotificationListener = $rootScope.$on('NotificationsService.onNotificationAdded', function(event, notification) {
$scope.notifications.push(notification);
if (NotificationsService.isAutoDismiss(notification)) {
$timeout(function () {
notification.hidden = true;
}, NotificationsService.dismissDelay);
}
// 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;
}
});
}
};
});
......@@ -418,7 +418,7 @@ angular.module('openshiftCommonServices')
}
// Use `$rootScope.$emit` instead of NotificationsService directly
// so that DataService doesn't add a dependency on `openshiftCommonUI`
$rootScope.$emit('addNotification', {
$rootScope.$emit('NotificationsService.addNotification', {
type: 'error',
message: msg
});
......@@ -963,7 +963,7 @@ DataService.prototype.createStream = function(resource, name, context, opts, isR
}
// Use `$rootScope.$emit` instead of NotificationsService directly
// so that DataService doesn't add a dependency on `openshiftCommonUI`
$rootScope.$emit('addNotification', {
$rootScope.$emit('NotificationsService.addNotification', {
type: 'error',
message: msg
});
......@@ -994,7 +994,7 @@ DataService.prototype.createStream = function(resource, name, context, opts, isR
}
// Use `$rootScope.$emit` instead of NotificationsService directly
// so that DataService doesn't add a dependency on `openshiftCommonUI`
$rootScope.$emit('addNotification', {
$rootScope.$emit('NotificationsService.addNotification', {
type: 'error',
message: msg
});
......@@ -1184,7 +1184,7 @@ DataService.prototype.createStream = function(resource, name, context, opts, isR
if (_.get(opts, 'errorNotification', true)) {
// Use `$rootScope.$emit` instead of NotificationsService directly
// so that DataService doesn't add a dependency on `openshiftCommonUI`
$rootScope.$emit('addNotification', {
$rootScope.$emit('NotificationsService.addNotification', {
id: 'websocket_retry_halted',
type: 'error',
message: 'Server connection interrupted.',
......
......@@ -23,6 +23,7 @@ angular.module('openshiftCommonUI').provider('NotificationsService', function()
}
notifications.push(notification);
$rootScope.$emit('NotificationsService.onNotificationAdded', notification);
};
var hideNotification = function (notificationID) {
......@@ -75,7 +76,7 @@ angular.module('openshiftCommonUI').provider('NotificationsService', function()
};
// 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);
});
......
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