From 84d30e4ee083aec8eca58e4c1f6251df31b297aa Mon Sep 17 00:00:00 2001 From: Martin Edenhofer Date: Tue, 8 Dec 2015 00:51:33 +0100 Subject: [PATCH] Added desktop notifications. --- .../_application_controller.coffee | 19 ++++++++ .../app/controllers/_settings/area.coffee | 4 +- .../javascripts/app/controllers/chat.coffee | 21 ++++++++- .../app/controllers/getting_started.coffee | 5 +- .../javascripts/app/controllers/login.coffee | 5 +- .../app/controllers/taskbar_widget.coffee | 5 ++ .../app/controllers/widget/notify.coffee | 46 ++++++++++++++++--- .../widget/online_notification.coffee | 25 ++++++---- .../app/views/task_widget_tasks.jst.eco | 2 +- .../online_notification_content.jst.eco | 2 +- test/browser/chat_test.rb | 36 ++++++++------- 11 files changed, 124 insertions(+), 46 deletions(-) diff --git a/app/assets/javascripts/app/controllers/_application_controller.coffee b/app/assets/javascripts/app/controllers/_application_controller.coffee index 8c7874846..9ae7aa129 100644 --- a/app/assets/javascripts/app/controllers/_application_controller.coffee +++ b/app/assets/javascripts/app/controllers/_application_controller.coffee @@ -550,6 +550,25 @@ class App.Controller extends Spine.Controller 'meta-task-update' ) + locationVerify: (e, callback) => + newLocation = $(e.currentTarget).attr 'href' + @log 'debug', "new location #{newLocation}" + return if !newLocation + currentLocation = Spine.Route.getPath() + @log 'debug', "current location #{currentLocation}" + return if newLocation.replace(/#/, '') isnt currentLocation + @locationExecute(newLocation, callback) + + locationExecute: (location, callback) => + if callback + callback() + location = location.replace(/#/, '') + @log 'debug', "execute controller again for '#{location}' because of same hash" + Spine.Route.matchRoutes(location) + + logoUrl: -> + "#{@Config.get('image_path')}/#{@Config.get('product_logo')}" + class App.ControllerPermanent extends App.Controller constructor: -> super diff --git a/app/assets/javascripts/app/controllers/_settings/area.coffee b/app/assets/javascripts/app/controllers/_settings/area.coffee index e31195d6f..d8bf7c90c 100644 --- a/app/assets/javascripts/app/controllers/_settings/area.coffee +++ b/app/assets/javascripts/app/controllers/_settings/area.coffee @@ -150,11 +150,9 @@ class App.SettingsAreaLogo extends App.Controller @render() render: -> - logoFile = App.Config.get('product_logo') - logoUrl = App.Config.get('image_path') + "/#{logoFile}" @html App.view('settings/logo')( setting: @setting - logoUrl: logoUrl + logoUrl: @logoUrl() ) onLogoPick: (event) => diff --git a/app/assets/javascripts/app/controllers/chat.coffee b/app/assets/javascripts/app/controllers/chat.coffee index 4f633bd8d..19ed5aba6 100644 --- a/app/assets/javascripts/app/controllers/chat.coffee +++ b/app/assets/javascripts/app/controllers/chat.coffee @@ -94,6 +94,10 @@ class App.CustomerChat extends App.Controller # do not play sound on initial load if counter > 0 && @lastWaitingChatCount isnt undefined @sounds.chat_new.play() + @notifyDesktop( + title: "#{counter} #{App.i18n.translateInline('Waiting Customers')}", + url: '#customer_chat' + ) @lastWaitingChatCount = counter # collect chat window messages @@ -152,7 +156,6 @@ class App.CustomerChat extends App.Controller addChat: (session) -> return if @chatWindows[session.session_id] chat = new ChatWindow - name: "#{session.created_at}" session: session removeCallback: @removeChat messageCallback: @updateNavMenu @@ -234,6 +237,9 @@ class ChatWindow extends App.Controller @isAgentTyping = false @resetUnreadMessages() + chat = App.Chat.find(@session.chat_id) + @name = "#{chat.displayName()} [##{@session.id}]" + @on 'layout-change', @scrollToBottom @bind('chat_session_typing', (data) => @@ -252,10 +258,14 @@ class ChatWindow extends App.Controller @addStatusMessage("#{data.realname} has left the conversation") @goOffline() ) + @bind('chat_focus', (data) => + return if data.session_id isnt @session.session_id + @focus() + ) render: -> @html App.view('customer_chat/chat_window') - name: @options.name + name: @name @el.one 'transitionend', @onTransitionend @@ -389,6 +399,13 @@ class ChatWindow extends App.Controller @addUnreadMessages() @updateModified(true) @sounds.message.play() + @notifyDesktop( + title: @name + body: message + url: '#customer_chat' + callback: => + App.Event.trigger('chat_focus', { session_id: @session.session_id }) + ) unreadMessages: => @unreadMessagesCounter diff --git a/app/assets/javascripts/app/controllers/getting_started.coffee b/app/assets/javascripts/app/controllers/getting_started.coffee index d3e6d2d96..3b05f7af4 100644 --- a/app/assets/javascripts/app/controllers/getting_started.coffee +++ b/app/assets/javascripts/app/controllers/getting_started.coffee @@ -305,13 +305,10 @@ class Base extends App.Wizard else url = "#{http_type}://#{fqdn}" - logoFile = App.Config.get('product_logo') - logoUrl = App.Config.get('image_path') + "/#{logoFile}" - organization = App.Config.get('organization') @html App.view('getting_started/base')( url: url - logoUrl: logoUrl + logoUrl: @logoUrl() organization: organization ) @$('input, select').first().focus() diff --git a/app/assets/javascripts/app/controllers/login.coffee b/app/assets/javascripts/app/controllers/login.coffee index d55e6f917..64f952179 100644 --- a/app/assets/javascripts/app/controllers/login.coffee +++ b/app/assets/javascripts/app/controllers/login.coffee @@ -53,12 +53,9 @@ class Index extends App.ControllerContent if @Config.get( provider.config ) is true || @Config.get( provider.config ) is 'true' auth_providers.push provider - logoFile = App.Config.get('product_logo') - logoUrl = App.Config.get('image_path') + "/#{logoFile}" - @html App.view('login')( item: data - logoUrl: logoUrl + logoUrl: @logoUrl() auth_providers: auth_providers ) diff --git a/app/assets/javascripts/app/controllers/taskbar_widget.coffee b/app/assets/javascripts/app/controllers/taskbar_widget.coffee index 1d92c362d..56d51396f 100644 --- a/app/assets/javascripts/app/controllers/taskbar_widget.coffee +++ b/app/assets/javascripts/app/controllers/taskbar_widget.coffee @@ -1,6 +1,7 @@ class App.TaskbarWidget extends App.Controller events: 'click .js-close': 'remove' + 'click .js-locationVerify': 'location' constructor: -> super @@ -74,6 +75,10 @@ class App.TaskbarWidget extends App.Controller @el.sortable(dndOptions) + location: (e) => + return if !$(e.currentTarget).hasClass('is-modified') + @locationVerify(e) + remove: (e, key = false, force = false) => e.preventDefault() if !key diff --git a/app/assets/javascripts/app/controllers/widget/notify.coffee b/app/assets/javascripts/app/controllers/widget/notify.coffee index 7f4ad870b..7baedd4a1 100644 --- a/app/assets/javascripts/app/controllers/widget/notify.coffee +++ b/app/assets/javascripts/app/controllers/widget/notify.coffee @@ -1,4 +1,7 @@ class App.Notify extends App.ControllerWidgetPermanent + desktopNotify: {} + desktopNotifyCounter: 0 + events: 'click .alert': 'destroy' @@ -12,17 +15,48 @@ class App.Notify extends App.ControllerWidgetPermanent @log 'notify:removeall', @ @destroyAll() - @bind 'notifyDesktop', (data) -> + @bind 'notifyDesktop', (data) => + return if !window.Notification + if !data['icon'] - data['icon'] = 'unknown' - notify.createNotification( data.msg, data ) + data['icon'] = @logoUrl() + + timeout = 60000 * 60 * 24 + if document.hasFocus() + timeout = 4000 + + @desktopNotifyCounter += 1 + counter = @desktopNotifyCounter + notification = new window.Notification(data.title, data) + @desktopNotify[counter] = notification + + notification.onclose = (e) => + delete @desktopNotify[counter] + + notification.onclick = (e) => + window.focus() + if data.url + @locationExecute(data.url) + if data.callback + data.callback() + + if data.timeout || timeout + App.Delay.set( + -> notification.close() + data.timeout || timeout + ) # request desktop notification after login @bind 'auth', (data) -> if !_.isEmpty(data) - notify.requestPermission() + return if !window.Notification + window.Notification.requestPermission() - notify.config( pageVisibility: false ) + $(window).focus( + => + for counter, notification of @desktopNotify + notification.close() + ) render: (data) -> @@ -53,4 +87,4 @@ class App.Notify extends App.ControllerWidgetPermanent destroyAll: -> $.noty.closeAll() -App.Config.set( 'notify', App.Notify, 'Widgets' ) +App.Config.set('notify', App.Notify, 'Widgets') diff --git a/app/assets/javascripts/app/controllers/widget/online_notification.coffee b/app/assets/javascripts/app/controllers/widget/online_notification.coffee index 14e92231f..bf93ac964 100644 --- a/app/assets/javascripts/app/controllers/widget/online_notification.coffee +++ b/app/assets/javascripts/app/controllers/widget/online_notification.coffee @@ -1,4 +1,6 @@ class App.OnlineNotificationWidget extends App.Controller + alreadyShown: {} + elements: '.js-toggleNotifications': 'toggle' @@ -102,6 +104,7 @@ class App.OnlineNotificationWidget extends App.Controller fetch: => load = (items) => + @fetchedData = true App.OnlineNotification.refresh( items, { clear: true } ) @updateContent() App.OnlineNotification.fetchFull(load) @@ -133,17 +136,21 @@ class App.OnlineNotificationWidget extends App.Controller notificationsContainer = $('.js-notificationsContainer .popover-content') + # generate desktop notifications + for item in items + if !@alreadyShown[item.id] + @alreadyShown[item.id] = true + if @fetchedData + word = "#{item.type}d" + title = "#{item.created_by.displayName()} #{App.i18n.translateInline(word)} #{App.i18n.translateInline(item.object_name)} #{item.title}" + @notifyDesktop( + url: item.link + title: title + ) + # execute controller again of already open (because hash hasn't changed, we need to do it manually) notificationsContainer.find('.js-locationVerify').on('click', (e) => - newLocation = $(e.target).attr 'href' - if !newLocation - newLocation = $(e.target).closest('.js-locationVerify').attr 'href' - return if !newLocation - currentLocation = Spine.Route.getPath() - return if newLocation.replace(/#/, '') isnt currentLocation - @hidePopover() - @log 'debug', "execute controller again for '#{currentLocation}' because of same hash" - Spine.Route.matchRoutes(currentLocation) + @locationVerify(e, @hidePopover) ) # close notification list on click diff --git a/app/assets/javascripts/app/views/task_widget_tasks.jst.eco b/app/assets/javascripts/app/views/task_widget_tasks.jst.eco index 07e9c897c..f2ffcd51f 100644 --- a/app/assets/javascripts/app/views/task_widget_tasks.jst.eco +++ b/app/assets/javascripts/app/views/task_widget_tasks.jst.eco @@ -1,5 +1,5 @@ <% for item in @taskItems: %> - +