diff --git a/web/app/assets/javascripts/layout.js b/web/app/assets/javascripts/layout.js index aec9eecca..970307673 100644 --- a/web/app/assets/javascripts/layout.js +++ b/web/app/assets/javascripts/layout.js @@ -99,7 +99,9 @@ layoutScreens(width, height); layoutSidebar(width, height); layoutHeader(width, height); - layoutNotify(width, height); + layoutNotify1(width, height); + layoutNotify2(width, height); + layoutNotify3(width, height); layoutFooter(width, height); $(document).triggerHandler('layout_resized'); @@ -325,14 +327,41 @@ $('[layout="header"]').css(css); } - function layoutNotify(screenWidth, screenHeight) { - var $notify = $('[layout="notify"]'); - var nHeight = $notify.height(); + function layoutNotify1(screenWidth, screenHeight) { + var $notify1 = $('[layout="notify1"]'); + //var nHeight = $notify1.height(); var notifyStyle = { bottom: '0px', - position: 'fixed' + position: 'fixed', + padding: '20px' }; - $notify.css(notifyStyle); + $notify1.css(notifyStyle); + } + + function layoutNotify2(screenWidth, screenHeight) { + var $notify1 = $('[layout="notify1"]'); + var $notify2 = $('[layout="notify2"]'); + var nHeight = $notify1.height(); + var notifyStyle = { + bottom: (nHeight + 41).toString()+'px', + position: 'fixed', + padding: '20px' + }; + $notify2.css(notifyStyle); + } + + function layoutNotify3(screenWidth, screenHeight) { + var $notify1 = $('[layout="notify1"]'); + var $notify2 = $('[layout="notify2"]'); + var $notify3 = $('[layout="notify3"]'); + var nHeight1 = $notify1.height(); + var nHeight2 = $notify2.height(); + var notifyStyle = { + bottom: (nHeight1 + nHeight2 + 41 + 41).toString()+'px', + position: 'fixed', + padding: '20px' + }; + $notify3.css(notifyStyle); } function layoutFooter(screenWidth, screenHeight) { @@ -806,6 +835,16 @@ return false; } + function expandNotificationPanel(){ + if(!context.JK.currentUserId) { + showDialog('login-required-dialog'); + return false; + } + expandedPanel = 'panelNotifications'; + layout(); + return false; + } + function wizardLinkClicked(evt) { evt.preventDefault(); var targetStepId = $(evt.currentTarget).attr("layout-wizard-link"); @@ -915,8 +954,73 @@ // used for concurrent notifications var notifyQueue = []; - var firstNotification = false; - var notifyDetails; + var notify1Showing = false; + var notify2Showing = false; + var notify3Showing = false; + var isMouseOverNotify1 = false; + var isMouseOverNotify2 = false; + var isMouseOverNotify3 = false; + var notify1Elapsed = false; + var notify2Elapsed = false; + var notify3Elapsed = false; + + var $notify1 = $('[layout="notify1"]'); + var $notify2 = $('[layout="notify2"]'); + var $notify3 = $('[layout="notify3"]'); + + $notify1.on('mouseenter', mouseEnterNotification) + $notify1.on('mouseleave', mouseLeaveToNotification) + $notify2.on('mouseenter', mouseEnterNotification) + $notify2.on('mouseleave', mouseLeaveToNotification) + $notify3.on('mouseenter', mouseEnterNotification) + $notify3.on('mouseleave', mouseLeaveToNotification) + + function mouseEnterNotification(evt){ + var $notify = $(evt.target); + console.log("mouseEnter", $notify.prop("id")); + switch($notify.prop("id")){ + case 'notification1': + isMouseOverNotify1 = true; + break; + case 'notification2': + isMouseOverNotify2 = true; + break; + case 'notification3': + isMouseOverNotify3 = true; + break; + } + } + + function mouseLeaveToNotification(evt){ + var $notify = $(evt.target); + console.log("mouseLeave", $notify.prop("id")); + switch($notify.prop("id")){ + case 'notification1': + if(notify1Elapsed){ + $notify1.hide(0); + notify1Showing = false; + } + isMouseOverNotify1 = false; + break; + case 'notification2': + if(notify2Elapsed){ + $notify2.hide(0); + notify2Showing = false; + } + isMouseOverNotify2 = false; + break; + case 'notification3': + if(notify3Elapsed){ + $notify3.hide(0); + notify3Showing = false; + } + isMouseOverNotify3 = false; + break; + } + } + + //var firstNotification = false; + //var notifyDetails; var okButton = { id: "btn-okay", @@ -939,7 +1043,58 @@ cancelButton ]; + // this.notify = function (message, buttons, noCancel) { + // if (!buttons) { + // if (noCancel) { + // buttons = [okButton]; + // } + // else { + // buttons = defaultButtons; + // } + // } + + // // this allows clients to just specify the important action button without having to repeat the cancel descripton everywhere + // if (buttons.length === 1) { + // // jkolyer: changed default to remove cancel as this is used as an alert, not a confirmation (see ConfirmDialog) + // // buttons.push(cancelButton); + // } + + // var $notify = $('[layout="notify"]'); + + // if (notifyQueue.length === 0) { + // firstNotification = true; + // setNotificationInfo(message, buttons, $notify); + // } + + // notifyQueue.push({message: message, descriptor: buttons}); + // // JW - speeding up the in/out parts of notify. Extending non-moving time. + // $notify.hide(0) + // .show({ + // duration: 0, + // queue: true, + // complete: function () { + // notifyDetails = notifyQueue.shift(); + + // // shift 1 more time if this is first notification being displayed + // if (firstNotification) { + // notifyDetails = notifyQueue.shift(); + // firstNotification = false; + // } + + // if (notifyDetails !== undefined) { + // setNotificationInfo(notifyDetails.message, notifyDetails.descriptor, $notify); + // } + + // notifyDetails = {}; + // } + // }) + // .delay(8000) + // .hide(0) + // }; + this.notify = function (message, buttons, noCancel) { + console.log("call this.notify"); + if (!buttons) { if (noCancel) { buttons = [okButton]; @@ -949,46 +1104,182 @@ } } - // this allows clients to just specify the important action button without having to repeat the cancel descripton everywhere - if (buttons.length === 1) { - // jkolyer: changed default to remove cancel as this is used as an alert, not a confirmation (see ConfirmDialog) - // buttons.push(cancelButton); - } - - var $notify = $('[layout="notify"]'); - - if (notifyQueue.length === 0) { - firstNotification = true; - setNotificationInfo(message, buttons, $notify); - } - notifyQueue.push({message: message, descriptor: buttons}); - // JW - speeding up the in/out parts of notify. Extending non-moving time. - $notify.slideDown(250) - .delay(4000) - .slideUp({ - duration: 400, - queue: true, - complete: function () { - notifyDetails = notifyQueue.shift(); - // shift 1 more time if this is first notification being displayed - if (firstNotification) { - notifyDetails = notifyQueue.shift(); - firstNotification = false; + showNotificationToast(); + + } + + function showNotificationToast(){ + if(!notify1Showing){ + var notifyDetails1 = notifyQueue.shift(); + if (notifyDetails1 !== undefined) { + setNotificationInfo(notifyDetails1.message, notifyDetails1.descriptor, $notify1); + notify1Elapsed = false; + $notify1.hide(0) + .show({ + duration: 0, + //queue: true, + complete: function () { + console.log("Show notification1") + notify1Showing = true; + notifyDetails1 = {}; } - - if (notifyDetails !== undefined) { - setNotificationInfo(notifyDetails.message, notifyDetails.descriptor, $notify); + }) + //.delay(8000); + + setTimeout(function(){ + notify1Elapsed = true; + if(!isMouseOverNotify1){ + $notify1.hide(0, function(){ + notify1Showing = false; + console.log("Hide notification1") + }) } + }, 8000) + + } - notifyDetails = {}; - } - }); - }; + }else if(!notify2Showing){ + var notifyDetails2 = notifyQueue.shift(); + if (notifyDetails2 !== undefined) { + setNotificationInfo(notifyDetails2.message, notifyDetails2.descriptor, $notify2); + notify2Elapsed = false; + $notify2.hide(0) + .show({ + duration: 0, + //queue: true, + complete: function () { + console.log("Show notification2") + notify2Showing = true; + notifyDetails2 = {}; + } + }); + // .delay(8000) + // .hide(0, function(){ + // notify2Showing = false; + // console.log("Hide notification2") + // }) + setTimeout(function(){ + notify2Elapsed = true; + if(!isMouseOverNotify2){ + $notify2.hide(0, function(){ + notify2Showing = false; + console.log("Hide notification2") + }) + } + }, 8000); + } + + }else if(!notify3Showing){ + var notifyDetails3 = notifyQueue.shift(); + if (notifyDetails3 !== undefined) { + setNotificationInfo(notifyDetails3.message, notifyDetails3.descriptor, $notify3); + notify3Elapsed = false; + $notify3.hide(0) + .show({ + duration: 0, + //queue: true, + complete: function () { + console.log("Show notification3") + notify3Showing = true; + notifyDetails3 = {}; + } + }) + // .delay(8000) + // .hide(0, function(){ + // notify3Showing = false; + // console.log("Hide notification3") + // }) + setTimeout(function(){ + notify3Elapsed = true; + if(!isMouseOverNotify3){ + $notify3.hide(0, function(){ + notify3Showing = false; + console.log("Hide notification3") + }) + } + }, 8000); + } + + }else if(notify1Showing && notify2Showing && notify3Showing){ + //try again after 1 second + setTimeout(function(){ + showNotificationToast() + }, 1000) + } + } + + // function setNotificationInfo(message, buttons, notificationSelector) { + // var $notify = $('[layout="notify"]'); + // $('h2', $notify).text(message.title); + // $('p', $notify).empty(); + // if (message.text instanceof jQuery) { + // $('p', $notify).append(message.text) + // } + // else { + // $('p', $notify).html(message.text); + // } + + // if (message.icon_url) { + // $('#avatar', $notify).attr('src', message.icon_url); + // $('#notify-avatar', $notify).show(); + // } + // else { + // $('#notify-avatar', $notify).hide(); + // } + + // if (message.detail) { + // $('div.detail', $notify).html(message.detail).show(); + // } + // else { + // $('div.detail', $notify).hide(); + // } + + // var $buttonDiv = $('#buttons', $notify); + // $buttonDiv.empty(); + + // $.each(buttons, function(index, val) { + + // // build button HTML based on KV pairs + // var keys = Object.keys(val); + // var buttonHtml = '  '; + // $buttonDiv.append(buttonHtml); + + // // ensure it doesn't fire twice + // $('#' + val.id, $notify).unbind('click'); + + // $('#' + val.id, $notify).click(function() { + // if (val.callback !== undefined) { + // if (val.callback_args) { + // val.callback(val.callback_args); + // } + // else { + // val.callback(); + // } + // return false; + // } + // else { + // notificationSelector.hide(); + // return false; + // } + // }); + // }); + // } function setNotificationInfo(message, buttons, notificationSelector) { - var $notify = $('[layout="notify"]'); + var $notify = notificationSelector; $('h2', $notify).text(message.title); $('p', $notify).empty(); if (message.text instanceof jQuery) { @@ -1048,7 +1339,7 @@ return false; } else { - notificationSelector.hide(); + $notify.hide(); return false; } }); @@ -1093,6 +1384,7 @@ this.getCurrentScreen = function() { return currentScreen; // will be a string of the layout-id of the active screen } + this.close = function (evt) { close(evt); }; @@ -1156,6 +1448,10 @@ events(); }; + this.expandNotificationPanel = function(){ + return expandNotificationPanel() + } + return this; }; diff --git a/web/app/assets/javascripts/notificationPanel.js b/web/app/assets/javascripts/notificationPanel.js index 2cd252382..937def4c6 100644 --- a/web/app/assets/javascripts/notificationPanel.js +++ b/web/app/assets/javascripts/notificationPanel.js @@ -119,7 +119,7 @@ if(isNotificationsPanelVisible()) { if(missedNotificationsWhileAway) { // catch user's eye, then put count to 0 - pulseToDark(); + // pulseToDark(); if(queuedNotificationCreatedAt) { app.updateNotificationSeen(queuedNotification, queuedNotificationCreatedAt); } @@ -147,9 +147,11 @@ } function events() { - $(context).on(EVENTS.DIALOG_CLOSED, function(e, data) {if(data.dialogCount == 0) userCameBack(); }); - $(window).focus(userCameBack); - $(window).blur(windowBlurred); + // $(context).on(EVENTS.DIALOG_CLOSED, function(e, data) { + // if(data.dialogCount == 0) userCameBack(); + // }); + //$(window).focus(userCameBack); + //$(window).blur(windowBlurred); app.user() .done(function(user) { setCount(user.new_notifications); @@ -160,6 +162,8 @@ $panel.on('open', opened); + $contents.on('click', opened); + // friend notifications registerFriendRequest(); registerFriendRequestAccepted(); @@ -582,6 +586,7 @@ } }] ); + context.JK.layout.expandNotificationPanel(); }); } @@ -635,6 +640,7 @@ } }] ); + context.JK.layout.expandNotificationPanel(); }); } @@ -652,6 +658,7 @@ "text": payload.msg, "icon_url": context.JK.resolveAvatarUrl(payload.photo_url) }); + context.JK.layout.expandNotificationPanel(); }); } @@ -692,7 +699,7 @@ logger.debug("Handling FRIEND_REQUEST msg " + JSON.stringify(payload)); handleNotification(payload, header.type); - + app.notify({ "title": "New Friend Request", "text": payload.msg, @@ -710,6 +717,8 @@ } }] ); + context.JK.layout.expandNotificationPanel(); + //opened() }); } @@ -790,6 +799,7 @@ } }] ); + context.JK.layout.expandNotificationPanel(); // THERE IS A RACE CONDITION THAT CAUSES AN ERROR WHEN INVOKING THE CODE BELOW // THE ACTIVEMUSICSESSION HAS NOT YET BEEN FULLY CREATED B/C THE CREATOR'S CLIENT IS @@ -1410,13 +1420,33 @@ $list.prepend(notificationHtml); - onNotificationOccurred(payload); + drawAttentionToNotification(payload, type); + + onNotificationOccurred(payload, type); initializeActions(payload, type); return true; } + function drawAttentionToNotification(payload, type){ + if (type === context.JK.MessageType.FRIEND_REQUEST || + type === context.JK.MessageType.FRIEND_REQUEST_ACCEPTED || + type === context.JK.MessageType.JOIN_REQUEST || + type === context.JK.MessageType.JOIN_REQUEST_APPROVED || + type === context.JK.MessageType.SESSION_INVITATION) { + var $newLi = $list.find('li[notification-id="' + payload.notification_id + '"]'); + $newLi.css("border", "solid 1px #01545f"); + $newLi.css("margin-bottom", "5px"); + $newLi.css("padding", "10px 0"); + $newLi.css("background-color", "#1099aa"); + } + } + + function notificationPanelClick(){ + opened() + } + function approveJoinRequest(args) { rest.updateJoinRequest(args.join_request_id, true) .done(function(response) { diff --git a/web/app/assets/stylesheets/client/notify.scss b/web/app/assets/stylesheets/client/notify.scss index 806f8f790..8891b7a84 100644 --- a/web/app/assets/stylesheets/client/notify.scss +++ b/web/app/assets/stylesheets/client/notify.scss @@ -1,6 +1,6 @@ @import "client/common.scss"; -#notification { +.notification { position:absolute; left:30%; padding:20px; @@ -14,7 +14,7 @@ box-shadow: 0px 0px 15px rgba(50, 50, 50, 1); } -#notification h2 { +.notification h2 { font-size: 1.5em; color:#fff; font-weight:200; @@ -23,7 +23,7 @@ margin-bottom:10px; } -#notification p { +.notification p { margin-bottom:20px; .error-text { @@ -31,7 +31,7 @@ } } -#notification p .text-message { +.notification p .text-message { white-space: pre-wrap; word-wrap: break-word; } diff --git a/web/app/views/clients/_notify.html.erb b/web/app/views/clients/_notify.html.erb index d058e9903..f4d8966d0 100644 --- a/web/app/views/clients/_notify.html.erb +++ b/web/app/views/clients/_notify.html.erb @@ -1,4 +1,4 @@ - + + + \ No newline at end of file diff --git a/web/spec/features/notification_highlighter_spec.rb b/web/spec/features/notification_highlighter_spec.rb index 187c2a22b..d6b262b97 100644 --- a/web/spec/features/notification_highlighter_spec.rb +++ b/web/spec/features/notification_highlighter_spec.rb @@ -11,7 +11,6 @@ describe "Notification Highlighter", :js => true, :type => :feature, :capybara_f shared_examples_for :notification_badge do |options| it "in correct state" do - save_screenshot("notification_highlighter.png") #sign_in_poltergeist(user) unless page.has_selector?('h2', 'musicians') sign_in_poltergeist(user) unless page.has_selector?('h2', text: 'musicians') badge = find("#{NOTIFICATION_PANEL} .badge", text: options[:count], visible: :all) @@ -59,6 +58,7 @@ describe "Notification Highlighter", :js => true, :type => :feature, :capybara_f it_behaves_like :notification_badge, highlighted: true, count:1, action: :click end + end @@ -80,7 +80,7 @@ describe "Notification Highlighter", :js => true, :type => :feature, :capybara_f before(:each) do notification = Notification.send_text_message("text message", user2, user) notification.errors.any?.should be false - find('#notification #btn-reply') # wait for notification to show, so that we know the sidebar had a chance to update + find('.notification #btn-reply') # wait for notification to show, so that we know the sidebar had a chance to update end it_behaves_like :notification_badge, highlighted: true, count:1 @@ -91,7 +91,7 @@ describe "Notification Highlighter", :js => true, :type => :feature, :capybara_f document_blur notification = Notification.send_text_message("text message 2", user2, user) notification.errors.any?.should be false - find('#notification #btn-reply') + find('.notification #btn-reply') end it_behaves_like :notification_badge, highlighted: true, count:1 @@ -104,6 +104,19 @@ describe "Notification Highlighter", :js => true, :type => :feature, :capybara_f end end end + + + describe "click on notification panel", focus: true do + before(:each) do + notification = Notification.send_text_message("text message", user2, user) + notification.errors.any?.should be false + find('.notification #btn-reply') # wait for notification to show, so that we know the sidebar had a chance to update + find('#sidebar-notification-list').click + end + + it_behaves_like :notification_badge, highlighted: false, count: 0 + end + end end @@ -162,10 +175,109 @@ describe "Notification Highlighter", :js => true, :type => :feature, :capybara_f badge = find("#{NOTIFICATION_PANEL} .badge", text: '1') badge['class'].include?('highlighted').should == true - find('#notification #btn-accept', text: 'ACCEPT').click + find('.notification #btn-accept', text: 'ACCEPT').click badge = find("#{NOTIFICATION_PANEL} .badge", text: '0') badge['class'].include?('highlighted').should == false end end + + describe "priority notifications", focus: true do + let(:user1) { FactoryGirl.create(:user) } + let(:user2) { FactoryGirl.create(:user) } + + before(:each) do + Notification.destroy_all + Friendship.destroy_all + FriendRequest.destroy_all + JoinRequest.destroy_all + fast_signin(user2, "/client") + wait_until_curtain_gone + end + + describe "friend request" do + before do + req1 = FriendRequest.new + req1.user_id = user1.id + req1.friend_id = user2.id + req1.save! + req1.reload + Notification.send_friend_request(req1.id, user1.id, user2.id) + find('#btn-notification-action', text: 'ACCEPT') + expect(page).to have_selector("#{NOTIFICATION_PANEL} li.notification-entry", count: 1) + find('.note-text', text: "#{user1.name} has sent you a friend request.") + end + it "save_screenshot" do + save_screenshot("friend_request.png") + end + it_behaves_like :notification_badge, highlighted: false, count: 0 + end + + describe "friend request accepted" do + before do + Notification.send_friend_request_accepted(user2.id, user1.id) + expect(page).to have_selector("#{NOTIFICATION_PANEL} li.notification-entry", count: 1) + find('.note-text', text: "#{user1.name} has accepted your friend request.") + end + it "save_screenshot" do + save_screenshot("friend_request_accepted.png") + end + it_behaves_like :notification_badge, highlighted: false, count: 0 + end + + describe "music session join request" do + before do + ms = FactoryGirl.create(:music_session, creator: user2) + join_request = JoinRequest.new + join_request.music_session = ms + join_request.user = user1 + text = "#{user1.name} has requested to join your session." + join_request.text = text + join_request.save + + Notification.send_join_request(ms, join_request, text) + expect(page).to have_selector("#{NOTIFICATION_PANEL} li.notification-entry", count: 1) + find('.note-text', text: text) + end + it "save_screenshot" do + save_screenshot("music_session_join_request.png") + end + it_behaves_like :notification_badge, highlighted: false, count: 0 + end + + describe "music session join request approved" do + before do + ms = FactoryGirl.create(:music_session, creator: user1) + join_request = JoinRequest.new + join_request.music_session = ms + join_request.user = user2 + text = "#{user1.name} has approved your request to join the session." + join_request.text = text + join_request.save + + Notification.send_join_request_approved(ms, join_request) + expect(page).to have_selector("#{NOTIFICATION_PANEL} li.notification-entry", count: 1) + find('.note-text', text: text) + end + it "save_screenshot" do + save_screenshot("music_session_join_request_approved.png") + end + it_behaves_like :notification_badge, highlighted: false, count: 0 + end + + # describe "music session invitation" do + # before do + # ms = FactoryGirl.create(:music_session, creator: user1) + # text = "You have been invited to join a session by #{user1.name}." + # Notification.send_session_invitation(user2, user1, ms.id) + # expect(page).to have_selector("#{NOTIFICATION_PANEL} li.notification-entry", count: 1) + # find('.note-text', text: text) + # end + # it "save_screenshot" do + # save_screenshot("music_session_invitation.png") + # end + # it_behaves_like :notification_badge, highlighted: false, count: 0 + # end + + end end diff --git a/web/spec/features/notification_spec.rb b/web/spec/features/notification_spec.rb index baa9d2075..4df5dca55 100644 --- a/web/spec/features/notification_spec.rb +++ b/web/spec/features/notification_spec.rb @@ -4,11 +4,8 @@ describe "Notification Subpanel", :js => true, :type => :feature, :capybara_feat subject { page } - let(:user) { FactoryGirl.create(:user, last_jam_locidispid: 1) } - - describe "user with no notifications" do before(:each) do fast_signin(user, "/client") @@ -35,4 +32,34 @@ describe "Notification Subpanel", :js => true, :type => :feature, :capybara_feat expect(page).to have_selector("#{NOTIFICATION_PANEL} li.notification-entry", count: 1) end end + + +end + +describe "Notification Toast", js: true, type: :feature, capybara_feature: true do + let(:user) { FactoryGirl.create(:user) } + let(:user1) { FactoryGirl.create(:user) } + let(:user2) { FactoryGirl.create(:user) } + let(:user3) { FactoryGirl.create(:user) } + + before(:each) do + fast_signin(user, "/client") + end + + it "popup 3 toasts" do + wait_until_curtain_gone + create_friend_request(user1, user) + create_friend_request(user2, user) + create_friend_request(user3, user) + expect(page).to have_selector(".notification", count: 3) + end + + + def create_friend_request(source_user, targe_user) + req = FriendRequest.new + req.user_id = source_user.id + req.friend_id = targe_user.id + req.save! + Notification.send_friend_request(req.id, source_user.id, targe_user.id) + end end \ No newline at end of file