Explorar o código

Show new chat message notification

James Allen %!s(int64=12) %!d(string=hai) anos
pai
achega
c6f51cf5ac

+ 9 - 2
services/web/app/views/project/editor/chat.jade

@@ -12,7 +12,9 @@ aside.chat(
 			.loading(ng-show="chat.loading")
 				i.fa.fa-fw.fa-spin.fa-refresh
 				| Loading...
-			ul.list-unstyled
+			ul.list-unstyled(
+				ng-click="resetUnreadMessages()"
+			)
 				li.message(
 					ng-repeat="message in chat.messages"
 					ng-controller="ChatMessageController"
@@ -34,6 +36,11 @@ aside.chat(
 							p(ng-repeat="content in message.contents track by $index") {{ content }}
 
 	.new-message
-		textarea(placeholder="Your message...", on-enter="sendMessage()", ng-model="newMessageContent")
+		textarea(
+			placeholder="Your message...",
+			on-enter="sendMessage()",
+			ng-model="newMessageContent",
+			ng-click="resetUnreadMessages()"
+		)
 
 

+ 6 - 1
services/web/app/views/project/editor/header.jade

@@ -64,4 +64,9 @@ header.toolbar.toolbar-header(ng-cloak, ng-hide="state.loading")
 			ng-click="toggleChat()",
 			ng-controller="ChatButtonController"
 		)
-			i.fa.fa-fw.fa-comment
+			i.fa.fa-fw.fa-comment(
+				ng-class="{ 'bounce': unreadMessages > 0 }"
+			)
+			span.label.label-info(
+				ng-show="unreadMessages > 0"
+			) {{ unreadMessages }}

+ 41 - 2
services/web/public/coffee/ide/chat/controllers/ChatButtonController.coffee

@@ -1,7 +1,46 @@
 define [
 	"base"
 ], (App) ->
-	App.controller "ChatButtonController", ["$scope", ($scope) ->
+	App.controller "ChatButtonController", ($scope, ide) ->
 		$scope.toggleChat = () ->
 			$scope.ui.chatOpen = !$scope.ui.chatOpen
-	]
+			$scope.resetUnreadMessages()
+			
+		$scope.unreadMessages = 0
+		$scope.resetUnreadMessages = () ->
+			$scope.unreadMessages = 0
+			
+		$scope.$on "chat:resetUnreadMessages", (e) ->
+			$scope.resetUnreadMessages()
+			
+		$scope.$on "chat:newMessage", (e, message) ->
+			if message?
+				if message.user.id != ide.$scope.user.id
+					if !$scope.ui.chatOpen
+						$scope.unreadMessages += 1
+					flashTitle()
+
+		focussed = true
+		newMessageNotificationTimeout = null
+		originalTitle = null
+		$(window).on "focus", () ->
+			clearNewMessageNotification()
+			focussed = true
+		$(window).on "blur", () ->
+			focussed = false
+
+		flashTitle = () ->
+			if !focussed and !newMessageNotificationTimeout?
+				originalTitle ||= window.document.title
+				do changeTitle = () =>
+					if window.document.title == originalTitle
+						window.document.title = "New Message"
+					else
+						window.document.title = originalTitle
+					newMessageNotificationTimeout = setTimeout changeTitle, 800
+
+		clearNewMessageNotification = () ->
+			clearTimeout newMessageNotificationTimeout
+			newMessageNotificationTimeout = null
+			if originalTitle?
+				window.document.title = originalTitle

+ 8 - 1
services/web/public/coffee/ide/chat/controllers/ChatController.coffee

@@ -2,7 +2,7 @@ define [
 	"base"
 	"ide/chat/services/chatMessages"
 ], (App) ->
-	App.controller "ChatController", ($scope, chatMessages, @ide, $location) ->
+	App.controller "ChatController", ($scope, chatMessages, ide, $location) ->
 		$scope.chat = chatMessages.state
 		
 		$scope.$watch "chat.messages", (messages) ->
@@ -12,6 +12,13 @@ define [
 		
 		$scope.$on "layout:chat:resize", () ->
 			$scope.$emit "updateScrollPosition"
+			
+		$scope.$watch "chat.newMessage", (message) ->
+			if message?
+				ide.$scope.$broadcast "chat:newMessage", message
+				
+		$scope.resetUnreadMessages = () ->
+			ide.$scope.$broadcast "chat:resetUnreadMessages"
 				
 		$scope.sendMessage = ->
 			chatMessages

+ 4 - 1
services/web/public/coffee/ide/chat/services/chatMessages.coffee

@@ -12,10 +12,13 @@ define [
 				loading: false
 				atEnd: false
 				nextBeforeTimestamp: null
+				newMessage: null
 		}
 			
 		ide.socket.on "new-chat-message", (message) =>
-			appendMessage(message)
+			ide.$scope.$apply () ->
+				chat.state.newMessage = message
+				appendMessage(message)
 			
 		chat.loadMoreMessages = () ->
 			return if chat.state.atEnd

+ 86 - 1
services/web/public/stylesheets/app/base.less

@@ -12,4 +12,89 @@
 	img {
 		margin-top: -10px;
 	}
-}
+}
+
+@-webkit-keyframes bounce {
+  0%, 10%, 26%, 40%, 50% {
+    -webkit-transition-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000);
+    transition-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000);
+    -webkit-transform: translate3d(0,0,0);
+    transform: translate3d(0,0,0);
+  }
+
+  20%, 21% {
+    -webkit-transition-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060);
+    transition-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060);
+    -webkit-transform: translate3d(0, -10px, 0);
+    transform: translate3d(0, -10px, 0);
+  }
+
+  35% {
+    -webkit-transition-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060);
+    transition-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060);
+    -webkit-transform: translate3d(0, -5px, 0);
+    transform: translate3d(0, -5px, 0);
+  }
+
+  45% {
+    -webkit-transform: translate3d(0,-2px,0);
+    transform: translate3d(0,-2px,0);
+  }
+  
+  50% {
+    -webkit-transform: translate3d(0,0,0);
+    transform: translate3d(0,0,0);
+  }
+}
+
+@keyframes bounce {
+  0%, 10%, 26%, 40%, 50% {
+    -webkit-transition-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000);
+    transition-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000);
+    -webkit-transform: translate3d(0,0,0);
+    -ms-transform: translate3d(0,0,0);
+    transform: translate3d(0,0,0);
+  }
+
+  20%, 21% {
+    -webkit-transition-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060);
+    transition-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060);
+    -webkit-transform: translate3d(0, -10px, 0);
+    -ms-transform: translate3d(0, -10px, 0);
+    transform: translate3d(0, -10px, 0);
+  }
+
+  35% {
+    -webkit-transition-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060);
+    transition-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060);
+    -webkit-transform: translate3d(0, -5px, 0);
+    -ms-transform: translate3d(0, -5px, 0);
+    transform: translate3d(0, -5px, 0);
+  }
+
+  45% {
+    -webkit-transform: translate3d(0,-2px,0);
+    -ms-transform: translate3d(0,-2px,0);
+    transform: translate3d(0,-2px,0);
+  }
+
+  50% {
+    -webkit-transform: translate3d(0,0,0);
+    -ms-transform: translate3d(0,0,0);
+    transform: translate3d(0,0,0);
+  }
+}
+
+.bounce {
+  -webkit-animation-duration: 2s;
+  animation-duration: 2s;
+  -webkit-animation-fill-mode: both;
+  animation-fill-mode: both;
+  -webkit-animation-iteration-count: infinite;
+  animation-iteration-count: infinite;
+  -webkit-animation-name: bounce;
+  animation-name: bounce;
+  -webkit-transform-origin: center bottom;
+  -ms-transform-origin: center bottom;
+  transform-origin: center bottom;
+}

+ 0 - 8
services/web/public/stylesheets/app/editor/pdf.less

@@ -75,14 +75,6 @@
 
 .toolbar {
 	.log-btn {
-		position: relative;
-		.label {
-			position: absolute;
-			top: 0;
-			right: 0;
-			padding: .15em .6em .2em;
-			font-size: 60%;
-		}
 		&.active, &:active {
 			.label {
 				display: none;

+ 15 - 0
services/web/public/stylesheets/app/editor/toolbar.less

@@ -2,6 +2,17 @@
 	height: 40px;
 	border-bottom: 1px solid @toolbar-border-color;
 
+	a {
+		position: relative;
+		.label {
+			position: absolute;
+			top: 0;
+			right: 0;
+			padding: .15em .6em .2em;
+			font-size: 60%;
+		}
+	}
+
 	a:not(.btn) {
 		display: inline-block;
 		color: @gray-light;
@@ -32,6 +43,10 @@
 			background-color: @link-color;
 			.box-shadow(inset 0 3px 5px rgba(0, 0, 0, 0.225));
 		}
+		.label {
+			top: 4px;
+			right: 4px;
+		}
 	}
 
 	.toolbar-right {