diff --git a/zeppelin-web/bower.json b/zeppelin-web/bower.json index 4e5c3534066..70a08b48515 100644 --- a/zeppelin-web/bower.json +++ b/zeppelin-web/bower.json @@ -11,6 +11,7 @@ "angular-animate": "1.3.8", "angular-touch": "1.3.8", "angular-route": "1.3.8", + "angular-resource": "1.3.8", "angular-bootstrap": "~0.13.0", "angular-websocket": "~1.0.13", "ace-builds": "1.1.9", @@ -28,7 +29,8 @@ "angular-filter": "~0.5.4", "ngtoast": "~1.5.5", "ng-focus-if": "~1.0.2", - "bootstrap3-dialog": "bootstrap-dialog#~1.34.7" + "bootstrap3-dialog": "bootstrap-dialog#~1.34.7", + "floatThead": "~1.3.2" }, "devDependencies": { "angular-mocks": "1.3.8" @@ -61,4 +63,4 @@ "name": "highlightjs" } } -} +} \ No newline at end of file diff --git a/zeppelin-web/src/app/app.controller.js b/zeppelin-web/src/app/app.controller.js index 2c302b542d9..f6af014bfe4 100644 --- a/zeppelin-web/src/app/app.controller.js +++ b/zeppelin-web/src/app/app.controller.js @@ -13,9 +13,11 @@ */ 'use strict'; -angular.module('zeppelinWebApp').controller('MainCtrl', function($scope, $rootScope, $window) { +angular.module('zeppelinWebApp').controller('MainCtrl', function($scope, $rootScope, $window, webNotification) { $rootScope.compiledScope = $scope.$new(true, $rootScope); $scope.looknfeel = 'default'; + $scope.windowFocus = true; + $rootScope.hasNewStatus = false; var init = function() { $scope.asIframe = (($window.location.href.indexOf('asIframe') > -1) ? true : false); @@ -45,4 +47,41 @@ angular.module('zeppelinWebApp').controller('MainCtrl', function($scope, $rootSc BootstrapDialog.defaultOptions.onshown = function() { angular.element('#' + this.id).find('.btn:last').focus(); }; + + $rootScope.$on('hasNewStatus', function(event, data) { + if (!event.defaultPrevented && data && data === true && $rootScope.hasNewStatus === false) { + $rootScope.hasNewStatus = true; + + // Send desktop notification + webNotification.showNotification('Zeppelin Notification', { + body: 'You have a job finished!', + icon: 'my-icon.ico', + onClick: function onNotificationClicked() { + console.log('Notification clicked.'); + }, + autoClose: 4000 //auto close the notification after 2 seconds (you can manually close it via hide function) + }, function onShow(error, hide) { + if (error) { + window.alert('Unable to show notification: ' + error.message); + } else { + setTimeout(function hideNotification() { + hide(); + }, 5000); + } + }); + + event.preventDefault(); + } + }); + + $window.onblur = function (){ + $rootScope.windowFocus = false; + }; + + $window.onfocus = function (){ + $rootScope.windowFocus = true; + if($rootScope.hasNewStatus === true) { + $rootScope.hasNewStatus = false; + } + }; }); diff --git a/zeppelin-web/src/app/app.js b/zeppelin-web/src/app/app.js index 64d43bb3c59..d1c0a7940e0 100644 --- a/zeppelin-web/src/app/app.js +++ b/zeppelin-web/src/app/app.js @@ -32,7 +32,8 @@ angular.module('zeppelinWebApp', [ 'puElasticInput', 'xeditable', 'ngToast', - 'focus-if' + 'focus-if', + 'angular-web-notification' ]) .filter('breakFilter', function() { return function (text) { diff --git a/zeppelin-web/src/app/notebook/paragraph/paragraph.controller.js b/zeppelin-web/src/app/notebook/paragraph/paragraph.controller.js index 8eafa6fb4dd..72bbb3c1a09 100644 --- a/zeppelin-web/src/app/notebook/paragraph/paragraph.controller.js +++ b/zeppelin-web/src/app/notebook/paragraph/paragraph.controller.js @@ -206,6 +206,11 @@ angular.module('zeppelinWebApp') $scope.paragraph.result = data.paragraph.result; $scope.paragraph.settings = data.paragraph.settings; + // when paragraph finishes, activate blinking tab notification if user is not focusing the the tab. + if (statusChanged && ($rootScope.windowFocus === false)) { + $rootScope.$emit('hasNewStatus', true); + } + if (!$scope.asIframe) { $scope.paragraph.config = data.paragraph.config; initializeDefault(); diff --git a/zeppelin-web/src/index.html b/zeppelin-web/src/index.html index 4bddd9849d4..1954a870212 100644 --- a/zeppelin-web/src/index.html +++ b/zeppelin-web/src/index.html @@ -122,6 +122,9 @@ + + +