diff --git a/admin/config/application.rb b/admin/config/application.rb index 0ae29cd8e..36a91e1a5 100644 --- a/admin/config/application.rb +++ b/admin/config/application.rb @@ -119,5 +119,7 @@ module JamAdmin config.twitter_app_secret = ENV['TWITTER_APP_SECRET'] || 'Azcy3QqfzYzn2fsojFPYXcn72yfwa0vG6wWDrZ3KT8' config.ffmpeg_path = ENV['FFMPEG_PATH'] || (File.exist?('/usr/local/bin/ffmpeg') ? '/usr/local/bin/ffmpeg' : '/usr/bin/ffmpeg') + + config.max_audio_downloads = 100 end end diff --git a/db/manifest b/db/manifest index 0c78cb3a7..aba246d9b 100755 --- a/db/manifest +++ b/db/manifest @@ -137,4 +137,6 @@ cascading_delete_constraints_for_release.sql events_social_description.sql fix_broken_cities.sql notifications_with_text.sql +notification_seen_at.sql +order_event_session.sql emails.sql diff --git a/db/up/notification_seen_at.sql b/db/up/notification_seen_at.sql new file mode 100644 index 000000000..67b29ddd8 --- /dev/null +++ b/db/up/notification_seen_at.sql @@ -0,0 +1 @@ +ALTER TABLE users ADD COLUMN notification_seen_at TIMESTAMP; \ No newline at end of file diff --git a/db/up/order_event_session.sql b/db/up/order_event_session.sql new file mode 100644 index 000000000..ba36f8f94 --- /dev/null +++ b/db/up/order_event_session.sql @@ -0,0 +1 @@ +ALTER TABLE event_sessions ADD COLUMN ordinal INTEGER; \ No newline at end of file diff --git a/pb/src/client_container.proto b/pb/src/client_container.proto index 4ae83c193..1df3bebf8 100644 --- a/pb/src/client_container.proto +++ b/pb/src/client_container.proto @@ -472,7 +472,8 @@ message TestClientMessage { // sent from client to server periodically to let server track if the client is truly alive and avoid TCP timeout scenarios // the server will send a HeartbeatAck in response to this message Heartbeat { - + optional string notification_seen = 1; + optional string notification_seen_at = 2; } // target: client diff --git a/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/password_changed.html.erb b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/password_changed.html.erb index a140d13fe..534066c5a 100644 --- a/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/password_changed.html.erb +++ b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/password_changed.html.erb @@ -1,3 +1,3 @@ <% provide(:title, 'Jamkazam Password Changed') %> -You just changed your password at Jamkazam. +You just changed your password at JamKazam. diff --git a/ruby/lib/jam_ruby/models/event_session.rb b/ruby/lib/jam_ruby/models/event_session.rb index 7385de68d..9d4fa8a9d 100644 --- a/ruby/lib/jam_ruby/models/event_session.rb +++ b/ruby/lib/jam_ruby/models/event_session.rb @@ -1,6 +1,6 @@ class JamRuby::EventSession < ActiveRecord::Base - attr_accessible :event_id, :user_id, :band_id, :starts_at, :ends_at, :pinned_state, :position, :img_url, :img_width, :img_height, as: :admin + attr_accessible :event_id, :user_id, :band_id, :starts_at, :ends_at, :pinned_state, :position, :img_url, :img_width, :img_height, :ordinal, as: :admin belongs_to :user, class_name: 'JamRuby::User' belongs_to :band, class_name: 'JamRuby::Band' @@ -13,6 +13,44 @@ class JamRuby::EventSession < ActiveRecord::Base before_validation :sanitize_active_admin + def has_public_mixed_recordings? + public_mixed_recordings.length > 0 + end + + def public_mixed_recordings + recordings.select { |recording| recording if recording.has_mix? && recording.is_public? } + end + + def recordings + recordings=[] + + sessions.each do |session_history| + recordings = recordings + session_history.recordings + end + + recordings + end + + # ideally this is based on some proper association with the event, not such a slushy time grab + def sessions + if ready_display + query = MusicSessionHistory.where(fan_access: true).where(created_at: (self.starts_at - 12.hours)..(self.ends_at + 12.hours)) + if self.user_id + query = query.where(user_id: self.user_id) + elsif self.band_id + query = query.where(band_id: self.band_id) + else + raise 'invalid state in event_session_button' + end + query + else + [] + end + end + + def ready_display + self.starts_at && self.ends_at && (self.user_id || self.band_id) + end def sanitize_active_admin self.img_url = nil if self.img_url == '' self.user_id = nil if self.user_id == '' diff --git a/ruby/lib/jam_ruby/models/music_session_history.rb b/ruby/lib/jam_ruby/models/music_session_history.rb index 089969434..5e3637284 100644 --- a/ruby/lib/jam_ruby/models/music_session_history.rb +++ b/ruby/lib/jam_ruby/models/music_session_history.rb @@ -157,6 +157,10 @@ module JamRuby music_session && music_session.mount end + def recordings + Recording.where(music_session_id: self.id) + end + def end_history self.update_attribute(:session_removed_at, Time.now) diff --git a/ruby/lib/jam_ruby/models/recording.rb b/ruby/lib/jam_ruby/models/recording.rb index 570734180..645ccf6b8 100644 --- a/ruby/lib/jam_ruby/models/recording.rb +++ b/ruby/lib/jam_ruby/models/recording.rb @@ -132,7 +132,7 @@ module JamRuby end def has_access?(user) - return users.exists?(user) + users.exists?(user) end # Start recording a session. @@ -342,6 +342,9 @@ module JamRuby save end + def is_public? + claimed_recordings.where(is_public: true).length > 0 + end # meant to be used as a way to 'pluck' a claimed_recording appropriate for user. def candidate_claimed_recording diff --git a/ruby/lib/jam_ruby/models/user.rb b/ruby/lib/jam_ruby/models/user.rb index 0fa73c0a5..7651d14ca 100644 --- a/ruby/lib/jam_ruby/models/user.rb +++ b/ruby/lib/jam_ruby/models/user.rb @@ -292,7 +292,33 @@ module JamRuby recordings.concat(msh) recordings.sort! {|a,b| b.created_at <=> a.created_at}.first(5) end - + + # returns the # of new notifications + def new_notifications + search = Notification.select('id').where(target_user_id: self.id) + search = search.where('created_at > ?', self.notification_seen_at) if self.notification_seen_at + search.count + end + + # the user can pass in a timestamp string, or the keyword 'LATEST' + # if LATEST is specified, we'll use the latest_notification as the timestamp + # if not, just use seen as-is + def update_notification_seen_at seen + new_latest_seen = nil + if seen == 'LATEST' + latest = self.latest_notification + new_latest_seen = latest.created_at if latest + else + new_latest_seen = seen + end + + self.notification_seen_at = new_latest_seen + end + + def latest_notification + Notification.select('created_at').where(target_user_id: id).limit(1).order('created_at DESC').first + end + def confirm_email! self.email_confirmed = true end diff --git a/ruby/spec/jam_ruby/models/track_spec.rb b/ruby/spec/jam_ruby/models/track_spec.rb index 781456720..be63c781b 100644 --- a/ruby/spec/jam_ruby/models/track_spec.rb +++ b/ruby/spec/jam_ruby/models/track_spec.rb @@ -116,8 +116,8 @@ describe Track do tracks = Track.sync(connection.client_id, [{:id => track.id, :client_track_id => track.client_track_id, :sound => track.sound, :instrument_id => track.instrument_id}]) tracks.length.should == 1 found = tracks[0] - found.id.should == track.id - found.updated_at.should == track.updated_at + expect(found.id).to eq track.id + expect(found.updated_at.to_i).to eq track.updated_at.to_i end end end \ No newline at end of file diff --git a/web/app/assets/javascripts/AAB_message_factory.js b/web/app/assets/javascripts/AAB_message_factory.js index 7f11e603d..04bd51344 100644 --- a/web/app/assets/javascripts/AAB_message_factory.js +++ b/web/app/assets/javascripts/AAB_message_factory.js @@ -99,8 +99,10 @@ }; // Heartbeat message - factory.heartbeat = function() { + factory.heartbeat = function(lastNotificationSeen, lastNotificationSeenAt) { var data = {}; + data.notification_seen = lastNotificationSeen; + data.notification_seen_at = lastNotificationSeenAt; return client_container(msg.HEARTBEAT, route_to.SERVER, data); }; diff --git a/web/app/assets/javascripts/acceptFriendRequestDialog.js b/web/app/assets/javascripts/acceptFriendRequestDialog.js index 4706105ca..66a4fe2f2 100644 --- a/web/app/assets/javascripts/acceptFriendRequestDialog.js +++ b/web/app/assets/javascripts/acceptFriendRequestDialog.js @@ -19,6 +19,7 @@ var user = null; var sending = false; var friendRequest = null; + var sidebar = null; function reset() { sending = false; @@ -52,6 +53,7 @@ rest.acceptFriendRequest(buildAcceptRequest()) .done(function() { app.layout.closeDialog('accept-friend-request') + sidebar.refreshFriends(); }) .fail(function(jqXHR) { app.notifyServerError(jqXHR, 'Unable to Accept Friend Request'); @@ -167,7 +169,7 @@ reset(); } - function initialize() { + function initialize(sidebarInstance) { var dialogBindings = { 'beforeShow' : beforeShow, 'afterHide': afterHide @@ -176,6 +178,7 @@ app.bindDialog('accept-friend-request', dialogBindings); + sidebar = sidebarInstance; $dialog = $('#accept-friend-request-dialog'); $dialogContents = $dialog.find('.dialog-inner'); $notFriendsTemplate = $('#template-friend-request-not-friends'); diff --git a/web/app/assets/javascripts/application.js b/web/app/assets/javascripts/application.js index 68db544ad..1210d1d7a 100644 --- a/web/app/assets/javascripts/application.js +++ b/web/app/assets/javascripts/application.js @@ -28,6 +28,7 @@ //= require jquery.infinitescroll //= require jquery.hoverIntent //= require jquery.dotdotdot +//= require jquery.pulse //= require AAA_Log //= require globals //= require AAB_message_factory diff --git a/web/app/assets/javascripts/feed_item_recording.js b/web/app/assets/javascripts/feed_item_recording.js index 0d8793446..834aedae5 100644 --- a/web/app/assets/javascripts/feed_item_recording.js +++ b/web/app/assets/javascripts/feed_item_recording.js @@ -7,6 +7,7 @@ var claimedRecordingId = $parentElement.attr('data-claimed-recording-id'); var recordingId = $parentElement.attr('id'); + var mode = $parentElement.attr('data-mode'); var $feedItem = $parentElement; var $name = $('.name', $feedItem); diff --git a/web/app/assets/javascripts/jam_rest.js b/web/app/assets/javascripts/jam_rest.js index 2b19b5d44..fbdf74a2a 100644 --- a/web/app/assets/javascripts/jam_rest.js +++ b/web/app/assets/javascripts/jam_rest.js @@ -934,6 +934,7 @@ } function getNotifications(options) { + if(!options) options = {}; var id = getId(options); return $.ajax({ type: "GET", diff --git a/web/app/assets/javascripts/jamkazam.js b/web/app/assets/javascripts/jamkazam.js index ea0a39710..f00e9b02d 100644 --- a/web/app/assets/javascripts/jamkazam.js +++ b/web/app/assets/javascripts/jamkazam.js @@ -28,6 +28,8 @@ var lastHeartbeatFound = false; var heartbeatAckCheckInterval = null; var userDeferred = null; + var notificationLastSeenAt = undefined; + var notificationLastSeen = undefined; var opts = { inClient: true, // specify false if you want the app object but none of the client-oriented features @@ -91,8 +93,9 @@ function _heartbeat() { if (app.heartbeatActive) { - - var message = context.JK.MessageFactory.heartbeat(); + var message = context.JK.MessageFactory.heartbeat(notificationLastSeen, notificationLastSeenAt); + notificationLastSeenAt = undefined; + notificationLastSeen = undefined; context.JK.JamServer.send(message); lastHeartbeatFound = false; } @@ -384,6 +387,28 @@ return userDeferred; } + this.updateNotificationSeen = function(notificationId, notificationCreatedAt) { + var time = new Date(notificationCreatedAt); + + if(!notificationCreatedAt) { + throw 'invalid value passed to updateNotificationSeen' + } + + if(!notificationLastSeenAt) { + notificationLastSeenAt = notificationCreatedAt; + notificationLastSeen = notificationId; + logger.debug("updated notificationLastSeenAt with: " + notificationCreatedAt); + } + else if(time.getTime() > new Date(notificationLastSeenAt).getTime()) { + notificationLastSeenAt = notificationCreatedAt; + notificationLastSeen = notificationId; + logger.debug("updated notificationLastSeenAt with: " + notificationCreatedAt); + } + else { + logger.debug("ignored notificationLastSeenAt for: " + notificationCreatedAt); + } + } + this.unloadFunction = function () { logger.debug("window.unload function called."); diff --git a/web/app/assets/javascripts/layout.js b/web/app/assets/javascripts/layout.js index d948945dc..555cc0d6d 100644 --- a/web/app/assets/javascripts/layout.js +++ b/web/app/assets/javascripts/layout.js @@ -251,7 +251,8 @@ if (!sidebarVisible) { return; } - var $expandedPanelContents = $('[layout-id="' + expandedPanel + '"] [layout-panel="contents"]'); + var $expandedPanel = $('[layout-id="' + expandedPanel + '"]'); + var $expandedPanelContents = $expandedPanel.find('[layout-panel="contents"]'); var combinedHeaderHeight = $('[layout-panel="contents"]').length * opts.panelHeaderHeight; var searchHeight = $('.sidebar .search').first().height(); var expanderHeight = $('[layout-sidebar-expander]').height(); @@ -259,6 +260,7 @@ $('[layout-panel="contents"]').hide(); $('[layout-panel="contents"]').css({"height": "1px"}); $expandedPanelContents.show(); + $expandedPanel.triggerHandler('open') $expandedPanelContents.animate({"height": expandedPanelHeight + "px"}, opts.animationDuration); } @@ -425,6 +427,7 @@ unstackDialogs($overlay); $dialog.hide(); dialogEvent(dialog, 'afterHide'); + $(me).triggerHandler('dialog_closed', {dialogCount: openDialogs.length}) } function screenEvent(screen, evtName, data) { @@ -526,6 +529,29 @@ } } + function isDialogShowing() { + return openDialogs.length > 0; + } + + function currentDialog() { + if(openDialogs.length == 0) return null; + + return openDialogs[openDialogs.length - 1]; + } + + // payload is a notification event from websocket gateway + function dialogObscuredNotification(payload) { + var openDialog = currentDialog(); + if(!openDialog) return false; + + if(typeof openDialog.handledNotification === 'function') { + return !openDialog.handledNotification(payload); + } + else { + return true; + } + } + /** * Responsible for keeping N dialogs in correct stacked order, * also moves the .dialog-overlay such that it hides/obscures all dialogs except the highest one @@ -849,6 +875,14 @@ showDialog(dialog, options); }; + this.dialogObscuredNotification = function() { + return dialogObscuredNotification(); + } + + this.isDialogShowing = function() { + return isDialogShowing(); + } + this.close = function (evt) { close(evt); }; diff --git a/web/app/assets/javascripts/notificationPanel.js b/web/app/assets/javascripts/notificationPanel.js new file mode 100644 index 000000000..90e9d5d5c --- /dev/null +++ b/web/app/assets/javascripts/notificationPanel.js @@ -0,0 +1,905 @@ +(function(context,$) { + + "use strict"; + + context.JK = context.JK || {}; + context.JK.NotificationPanel = function(app) { + var logger = context.JK.logger; + var friends = []; + var rest = context.JK.Rest(); + var missedNotificationsWhileAway = false; + var $panel = null; + var $expanded = null; + var $contents = null; + var $count = null; + var $list = null; + var $notificationTemplate = null; + var sidebar = null; + var darkenedColor = '#0D7B89'; + var highlightedColor = 'white' + var textMessageDialog = null; + var queuedNotification = null; + var queuedNotificationCreatedAt = null; + + function isNotificationsPanelVisible() { + return $contents.is(':visible') + } + + function incrementNotificationCount() { + var count = parseInt($count.text()); + setCount(count + 1); + } + + // set the element to white, and pulse it down to the un-highlighted value 2x, then set + function pulseToDark() { + logger.debug("pulsing notification badge") + lowlightCount(); + $count.pulse({'background-color' : highlightedColor}, {pulses: 2}, function() { + $count.removeAttr('style') + setCount(0); + }) + } + + function setCount(count) { + $count.text(count); + } + + function lowlightCount() { + $count.removeClass('highlighted'); + } + + function highlightCount() { + $count.addClass('highlighted'); + } + + function queueNotificationSeen(notificationId, notificationCreatedAt) { + + var time = new Date(notificationCreatedAt); + + if(!notificationCreatedAt) { + throw 'invalid value passed to queuedNotificationCreatedAt' + } + + if(!queuedNotificationCreatedAt) { + queuedNotification = notificationId; + queuedNotificationCreatedAt = notificationCreatedAt; + logger.debug("updated queuedNotificationCreatedAt with: " + notificationCreatedAt); + } + else if(time.getTime() > new Date(queuedNotificationCreatedAt).getTime()) { + queuedNotification = notificationId; + queuedNotificationCreatedAt = notificationCreatedAt; + logger.debug("updated queuedNotificationCreatedAt with: " + notificationCreatedAt); + } + else { + logger.debug("ignored queuedNotificationCreatedAt for: " + notificationCreatedAt); + } + } + + function onNotificationOccurred(payload) { + if(userCanSeeNotifications(payload)) { + app.updateNotificationSeen(payload.notification_id, payload.created_at); + } + else { + queueNotificationSeen(payload.notification_id, payload.created_at); + highlightCount(); + incrementNotificationCount(); + missedNotificationsWhileAway = true; + } + } + + function userCameBack() { + if(isNotificationsPanelVisible()) { + if(missedNotificationsWhileAway) { + // catch user's eye, then put count to 0 + pulseToDark(); + if(queuedNotificationCreatedAt) { + app.updateNotificationSeen(queuedNotification, queuedNotificationCreatedAt); + } + } + } + + queuedNotification = null; + queuedNotificationCreatedAt = null; + missedNotificationsWhileAway = false; + } + + function opened() { + queuedNotification = null; + queuedNotificationCreatedAt = null; + rest.updateUser({notification_seen_at: 'LATEST'}) + .done(function(response) { + lowlightCount(); + setCount(0); + }) + .fail(app.ajaxError) + } + + function windowBlurred() { + + } + + function events() { + $(app.layout).on('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); + if(user.new_notifications > 0) { + highlightCount(); + } + }); + + $panel.on('open', opened); + + // friend notifications + registerFriendRequest(); + registerFriendRequestAccepted(); + registerNewUserFollower(); + registerNewBandFollower(); + + // session notifications + registerSessionInvitation(); + registerSessionEnded(); + registerJoinRequest(); + registerJoinRequestApproved(); + registerJoinRequestRejected(); + registerMusicianSessionJoin(); + registerBandSessionJoin(); + + // recording notifications + registerMusicianRecordingSaved(); + registerBandRecordingSaved(); + registerRecordingMasterMixComplete(); + + // band notifications + registerBandInvitation(); + registerBandInvitationAccepted(); + + // register text messages + registerTextMessage(); + } + + function populate() { + // retrieve pending notifications for this user + rest.getNotifications() + .done(function(response) { + updateNotificationList(response); + }) + .fail(app.ajaxError) + } + + function updateNotificationList(response) { + $list.empty(); + + $.each(response, function(index, val) { + + if(val.description == 'TEXT_MESSAGE') { + val.formatted_msg = textMessageDialog.formatTextMessage(val.message.substring(0, 200), val.source_user_id, val.source_user.name, val.message.length > 200).html(); + } + + // fill in template for Connect pre-click + var template = $notificationTemplate.html(); + var notificationHtml = context.JK.fillTemplate(template, { + notificationId: val.notification_id, + sessionId: val.session_id, + avatar_url: context.JK.resolveAvatarUrl(val.photo_url), + text: val.formatted_msg, + date: $.timeago(val.created_at) + }); + + $list.append(notificationHtml); + + // val.description contains the notification record's description value from the DB (i.e., type) + initializeActions(val, val.description); + }); + } + + + function initializeActions(payload, type) { + + var $notification = $('li[notification-id=' + payload.notification_id + ']'); + var $btnNotificationAction = '#btn-notification-action'; + + // wire up "x" button to delete notification + $notification.find('#img-delete-notification').click(deleteNotificationHandler); + + // customize action buttons based on notification type + if (type === context.JK.MessageType.FRIEND_REQUEST) { + var $action_btn = $notification.find($btnNotificationAction); + $action_btn.text('ACCEPT'); + $action_btn.click(function() { + acceptFriendRequest(payload); + }); + } + + else if (type === context.JK.MessageType.FRIEND_REQUEST_ACCEPTED) { + $notification.find('#div-actions').hide(); + } + + else if (type === context.JK.MessageType.NEW_USER_FOLLOWER || type === context.JK.MessageType.NEW_BAND_FOLLOWER) { + $notification.find('#div-actions').hide(); + } + + else if (type === context.JK.MessageType.SESSION_INVITATION) { + var $action_btn = $notification.find($btnNotificationAction); + $action_btn.text('JOIN'); + $action_btn.click(function() { + openTerms(payload); + }); + } + + else if (type === context.JK.MessageType.JOIN_REQUEST) { + var $action_btn = $notification.find($btnNotificationAction); + $action_btn.text('APPROVE'); + $action_btn.click(function() { + approveJoinRequest(payload); + }); + } + + else if (type === context.JK.MessageType.JOIN_REQUEST_APPROVED) { + var $action_btn = $notification.find($btnNotificationAction); + $action_btn.text('JOIN'); + $action_btn.click(function() { + openTerms(payload); + }); + } + + else if (type === context.JK.MessageType.JOIN_REQUEST_REJECTED) { + $notification.find('#div-actions').hide(); + } + + else if (type === context.JK.MessageType.MUSICIAN_SESSION_JOIN || type === context.JK.MessageType.BAND_SESSION_JOIN) { + + var actionText = ''; + var callback; + if (context.JK.currentUserMusician) { + // user is MUSICIAN; musician_access = TRUE + if (payload.musician_access) { + actionText = "JOIN"; + callback = joinSession; + } + // user is MUSICIAN; fan_access = TRUE + else if (payload.fan_access) { + actionText = "LISTEN"; + callback = listenToSession; + } + } + else { + // user is FAN; fan_access = TRUE + if (payload.fan_access) { + actionText = "LISTEN"; + callback = listenToSession; + } + } + + var $action_btn = $notification.find($btnNotificationAction); + $action_btn.text(actionText); + $action_btn.click(function() { + callback(payload); + }); + } + + else if (type === context.JK.MessageType.MUSICIAN_RECORDING_SAVED || type === context.JK.MessageType.BAND_RECORDING_SAVED) { + var $action_btn = $notification.find($btnNotificationAction); + $action_btn.text('LISTEN'); + $action_btn.click(function() { + listenToRecording(payload); + }); + } + + else if (type === context.JK.MessageType.RECORDING_MASTER_MIX_COMPLETE) { + $notification.find('#div-actions').hide(); + context.jamClient.OnDownloadAvailable(); // poke backend, letting it know a download is available + } + + else if (type === context.JK.MessageType.BAND_INVITATION) { + var $action_btn = $notification.find($btnNotificationAction); + $action_btn.text('ACCEPT'); + $action_btn.click(function() { + acceptBandInvitation(payload); + }); + } + else if (type === context.JK.MessageType.BAND_INVITATION_ACCEPTED) { + $notification.find('#div-actions').hide(); + } + else if (type === context.JK.MessageType.TEXT_MESSAGE) { + var $action_btn = $notification.find($btnNotificationAction); + $action_btn.text('REPLY'); + $action_btn.click(function() { + var userId = $notification.find('.more-text-available').attr('data-sender-id'); + app.layout.showDialog('text-message', { d1: userId }); + }); + + var moreTextLink = $notification.find('.more-text-available'); + var textMessage = $notification.find('.text-message'); + var clipped_msg = textMessage.attr('data-is-clipped') === 'true'; + + if(clipped_msg) { + moreTextLink.text('more').show(); + moreTextLink.click(function(e) { + var userId = $(this).attr('data-sender-id'); + + return false; + }); + } + else { + moreTextLink.hide(); + } + } + } + + function acceptBandInvitation(args) { + rest.updateBandInvitation( + args.band_id, + args.band_invitation_id, + true + ).done(function(response) { + deleteNotification(args.notification_id); // delete notification corresponding to this friend request + }).error(app.ajaxError); + } + + + function deleteNotification(notificationId) { + var url = "/api/users/" + context.JK.currentUserId + "/notifications/" + notificationId; + $.ajax({ + type: "DELETE", + dataType: "json", + contentType: 'application/json', + url: url, + processData: false, + success: function(response) { + $('li[notification-id=' + notificationId + ']').hide(); + //decrementNotificationCount(); + }, + error: app.ajaxError + }); + } + + + function listenToSession(args) { + deleteNotification(args.notification_id); + context.JK.popExternalLink('/sessions/' + args.session_id); + } + + /*********** TODO: THE NEXT 3 FUNCTIONS ARE COPIED FROM sessionList.js. REFACTOR TO COMMON PLACE. *************/ + function joinSession(args) { + // NOTE: invited musicians get their own notification, so no need to check if user has invitation here + // like other places because an invited user would never get this notification + if (args.musician_access) { + if (args.approval_required) { + openAlert(args.session_id); + } + else { + openTerms(args); + } + } + deleteNotification(args.notification_id); + } + + + function registerJoinRequestApproved() { + context.JK.JamServer.registerMessageCallback(context.JK.MessageType.JOIN_REQUEST_APPROVED, function(header, payload) { + logger.debug("Handling JOIN_REQUEST_APPROVED msg " + JSON.stringify(payload)); + + handleNotification(payload, header.type); + + app.notify({ + "title": "Join Request Approved", + "text": payload.msg, + "icon_url": context.JK.resolveAvatarUrl(payload.photo_url) + }, { + "ok_text": "JOIN SESSION", + "ok_callback": openTerms, + "ok_callback_args": { "session_id": payload.session_id, "notification_id": payload.notification_id } + }); + }); + } + + function registerJoinRequestRejected() { + context.JK.JamServer.registerMessageCallback(context.JK.MessageType.JOIN_REQUEST_REJECTED, function(header, payload) { + logger.debug("Handling JOIN_REQUEST_REJECTED msg " + JSON.stringify(payload)); + + handleNotification(payload, header.type); + + app.notify({ + "title": "Join Request Rejected", + "text": payload.msg, + "icon_url": context.JK.resolveAvatarUrl(payload.photo_url) + }); + }); + } + + + function registerJoinRequest() { + context.JK.JamServer.registerMessageCallback(context.JK.MessageType.JOIN_REQUEST, function(header, payload) { + logger.debug("Handling JOIN_REQUEST msg " + JSON.stringify(payload)); + + handleNotification(payload, header.type); + + app.notify({ + "title": "New Join Request", + "text": payload.msg, + "icon_url": context.JK.resolveAvatarUrl(payload.photo_url) + }, { + "ok_text": "APPROVE", + "ok_callback": approveJoinRequest, + "ok_callback_args": { "join_request_id": payload.join_request_id, "notification_id": payload.notification_id }, + "cancel_text": "REJECT", + "cancel_callback": rejectJoinRequest, + "cancel_callback_args": { "join_request_id": payload.join_request_id, "notification_id": payload.notification_id } + }); + }); + } + + + function registerFriendRequestAccepted() { + context.JK.JamServer.registerMessageCallback(context.JK.MessageType.FRIEND_REQUEST_ACCEPTED, function(header, payload) { + logger.debug("Handling FRIEND_REQUEST_ACCEPTED msg " + JSON.stringify(payload)); + + handleNotification(payload, header.type); + + sidebar.refreshFriends(); + + app.notify({ + "title": "Friend Request Accepted", + "text": payload.msg, + "icon_url": context.JK.resolveAvatarUrl(payload.photo_url) + }); + }); + } + + function registerNewUserFollower() { + + context.JK.JamServer.registerMessageCallback(context.JK.MessageType.NEW_USER_FOLLOWER, function(header, payload) { + logger.debug("Handling NEW_USER_FOLLOWER msg " + JSON.stringify(payload)); + + handleNotification(payload, header.type); + + app.notify({ + "title": "New Follower", + "text": payload.msg, + "icon_url": context.JK.resolveAvatarUrl(payload.photo_url) + }); + }); + } + + + function registerNewBandFollower() { + + context.JK.JamServer.registerMessageCallback(context.JK.MessageType.NEW_BAND_FOLLOWER, function(header, payload) { + logger.debug("Handling NEW_BAND_FOLLOWER msg " + JSON.stringify(payload)); + + handleNotification(payload, header.type); + + app.notify({ + "title": "New Band Follower", + "text": payload.msg, + "icon_url": context.JK.resolveAvatarUrl(payload.photo_url) + }); + }); + } + + + function registerFriendRequest() { + context.JK.JamServer.registerMessageCallback(context.JK.MessageType.FRIEND_REQUEST, function(header, payload) { + logger.debug("Handling FRIEND_REQUEST msg " + JSON.stringify(payload)); + + handleNotification(payload, header.type); + + app.notify({ + "title": "New Friend Request", + "text": payload.msg, + "icon_url": context.JK.resolveAvatarUrl(payload.photo_url) + }, { + "ok_text": "ACCEPT", + "ok_callback": acceptFriendRequest, + "ok_callback_args": { "friend_request_id": payload.friend_request_id, "notification_id": payload.notification_id } + }); + }); + } + + function acceptFriendRequest(args) { + + rest.acceptFriendRequest({ + status: 'accept', + friend_request_id: args.friend_request_id + }).done(function(response) { + deleteNotification(args.notification_id); // delete notification corresponding to this friend request + sidebar.refreshFriends(); // refresh friends panel when request is accepted + }).error(app.ajaxError); + } + + function registerSessionEnded() { + // TODO: this should clean up all notifications related to this session + context.JK.JamServer.registerMessageCallback(context.JK.MessageType.SESSION_ENDED, function(header, payload) { + logger.debug("Handling SESSION_ENDED msg " + JSON.stringify(payload)); + deleteSessionNotifications(payload.session_id); + }); + } + + // remove all notifications for this session + function deleteSessionNotifications(sessionId) { + $('li[session-id=' + sessionId + ']').hide(); + //decrementNotificationCount(); + } + + function registerSessionInvitation() { + context.JK.JamServer.registerMessageCallback(context.JK.MessageType.SESSION_INVITATION, function(header, payload) { + logger.debug("Handling SESSION_INVITATION msg " + JSON.stringify(payload)); + + handleNotification(payload, header.type); + + var participants = []; + rest.getSession(payload.session_id).done(function(response) { + $.each(response.participants, function(index, val) { + participants.push({"photo_url": context.JK.resolveAvatarUrl(val.user.photo_url), "name": val.user.name}); + }); + + var participantHtml = "You have been invited to join a session with:

"; + participantHtml += ""; + + $.each(participants, function(index, val) { + if (index < 4) { + participantHtml += ""; + } + }); + + participantHtml += "
" + val.name + "
"; + + app.notify({ + "title": "Session Invitation", + "text": participantHtml + }, { + "ok_text": "JOIN SESSION", + "ok_callback": openTerms, + "ok_callback_args": { "session_id": payload.session_id, "notification_id": payload.notification_id } + }); + }).error(app.ajaxError); + + }); + } + + + function registerMusicianSessionJoin() { + context.JK.JamServer.registerMessageCallback(context.JK.MessageType.MUSICIAN_SESSION_JOIN, function(header, payload) { + logger.debug("Handling MUSICIAN_SESSION_JOIN msg " + JSON.stringify(payload)); + + var okText = ''; + var showNotification = false; + var callback; + if (context.JK.currentUserMusician) { + // user is MUSICIAN; musician_access = TRUE + if (payload.musician_access) { + showNotification = true; + okText = "JOIN"; + callback = joinSession; + } + // user is MUSICIAN; fan_access = TRUE + else if (payload.fan_access) { + showNotification = true; + okText = "LISTEN"; + callback = listenToSession; + } + } + else { + // user is FAN; fan_access = TRUE + if (payload.fan_access) { + showNotification = true; + okText = "LISTEN"; + callback = listenToSession; + } + } + + if (showNotification) { + handleNotification(payload, header.type); + + app.notify({ + "title": "Musician Joined Session", + "text": payload.msg, + "icon_url": context.JK.resolveAvatarUrl(payload.photo_url) + }, { + "ok_text": okText, + "ok_callback": callback, + "ok_callback_args": { + "session_id": payload.session_id, + "fan_access": payload.fan_access, + "musician_access": payload.musician_access, + "approval_required": payload.approval_required, + "notification_id": payload.notification_id + } + } + ); + } + }); + } + + + function registerBandSessionJoin() { + context.JK.JamServer.registerMessageCallback(context.JK.MessageType.BAND_SESSION_JOIN, function(header, payload) { + logger.debug("Handling BAND_SESSION_JOIN msg " + JSON.stringify(payload)); + + var okText = ''; + var showNotification = false; + var callback; + if (context.JK.currentUserMusician) { + // user is MUSICIAN; musician_access = TRUE + if (payload.musician_access) { + showNotification = true; + okText = "JOIN"; + callback = joinSession; + } + // user is MUSICIAN; fan_access = TRUE + else if (payload.fan_access) { + showNotification = true; + okText = "LISTEN"; + callback = listenToSession; + } + } + else { + // user is FAN; fan_access = TRUE + if (payload.fan_access) { + showNotification = true; + okText = "LISTEN"; + callback = listenToSession; + } + } + + if (showNotification) { + handleNotification(payload, header.type); + + app.notify({ + "title": "Band Joined Session", + "text": payload.msg, + "icon_url": context.JK.resolveAvatarUrl(payload.photo_url) + }, { + "ok_text": "LISTEN", + "ok_callback": callback, + "ok_callback_args": { + "session_id": payload.session_id, + "fan_access": payload.fan_access, + "musician_access": payload.musician_access, + "approval_required": payload.approval_required, + "notification_id": payload.notification_id + } + } + ); + } + }); + } + + + function registerMusicianRecordingSaved() { + context.JK.JamServer.registerMessageCallback(context.JK.MessageType.MUSICIAN_RECORDING_SAVED, function(header, payload) { + logger.debug("Handling MUSICIAN_RECORDING_SAVED msg " + JSON.stringify(payload)); + + handleNotification(payload, header.type); + + app.notify({ + "title": "Musician Recording Saved", + "text": payload.msg, + "icon_url": context.JK.resolveAvatarUrl(payload.photo_url) + }, { + "ok_text": "LISTEN", + "ok_callback": listenToRecording, + "ok_callback_args": { + "recording_id": payload.recording_id, + "notification_id": payload.notification_id + } + }); + }); + } + + function registerBandRecordingSaved() { + context.JK.JamServer.registerMessageCallback(context.JK.MessageType.BAND_RECORDING_SAVED, function(header, payload) { + logger.debug("Handling BAND_RECORDING_SAVED msg " + JSON.stringify(payload)); + + handleNotification(payload, header.type); + + app.notify({ + "title": "Band Recording Saved", + "text": payload.msg, + "icon_url": context.JK.resolveAvatarUrl(payload.photo_url) + }, { + "ok_text": "LISTEN", + "ok_callback": listenToRecording, + "ok_callback_args": { + "recording_id": payload.recording_id, + "notification_id": payload.notification_id + } + }); + }); + } + + + function registerRecordingMasterMixComplete() { + context.JK.JamServer.registerMessageCallback(context.JK.MessageType.RECORDING_MASTER_MIX_COMPLETE, function(header, payload) { + logger.debug("Handling RECORDING_MASTER_MIX_COMPLETE msg " + JSON.stringify(payload)); + + handleNotification(payload, header.type); + + app.notify({ + "title": "Recording Master Mix Complete", + "text": payload.msg, + "icon_url": context.JK.resolveAvatarUrl(payload.photo_url) + }, { + "ok_text": "SHARE", + "ok_callback": shareRecording, + "ok_callback_args": { + "recording_id": payload.recording_id + } + }); + }); + } + + function shareRecording(args) { + var recordingId = args.recording_id; + } + + function registerBandInvitation() { + context.JK.JamServer.registerMessageCallback(context.JK.MessageType.BAND_INVITATION, function(header, payload) { + logger.debug("Handling BAND_INVITATION msg " + JSON.stringify(payload)); + + handleNotification(payload, header.type); + + app.notify({ + "title": "Band Invitation", + "text": payload.msg, + "icon_url": context.JK.resolveAvatarUrl(payload.photo_url) + }, { + "ok_text": "ACCEPT", + "ok_callback": acceptBandInvitation, + "ok_callback_args": { + "band_invitation_id": payload.band_invitation_id, + "band_id": payload.band_id, + "notification_id": payload.notification_id + } + }); + }); + } + + + function registerTextMessage() { + context.JK.JamServer.registerMessageCallback(context.JK.MessageType.TEXT_MESSAGE, function(header, payload) { + logger.debug("Handling TEXT_MESSAGE msg " + JSON.stringify(payload)); + + textMessageDialog.messageReceived(payload); + + handleNotification(payload, header.type); + }); + } + + function registerBandInvitationAccepted() { + context.JK.JamServer.registerMessageCallback(context.JK.MessageType.BAND_INVITATION_ACCEPTED, function(header, payload) { + logger.debug("Handling BAND_INVITATION_ACCEPTED msg " + JSON.stringify(payload)); + + handleNotification(payload, header.type); + + app.notify({ + "title": "Band Invitation Accepted", + "text": payload.msg, + "icon_url": context.JK.resolveAvatarUrl(payload.photo_url) + }); + }); + } + + + // one important limitation; if the user is focused on an iframe, this will be false + // however, if they are doing something with Facebook or the photo picker, this may actually still be desirable + function userCanSeeNotifications(payload) { + return document.hasFocus() && !app.layout.dialogObscuredNotification(payload); + } + + // default handler for incoming notification + function handleNotification(payload, type) { + + // on a load of notifications, it is possible to load a very new notification, + // and get a websocket notification right after for that same notification, + // so we need to protect against such duplicates + if($list.find('li[notification-id="' + payload.notification_id + '"]').length > 0) { + return false; + } + + // add notification to sidebar + var template = $notificationTemplate.html(); + var notificationHtml = context.JK.fillTemplate(template, { + notificationId: payload.notification_id, + sessionId: payload.session_id, + avatar_url: context.JK.resolveAvatarUrl(payload.photo_url), + text: payload.msg instanceof jQuery ? payload.msg.html() : payload.msg , + date: $.timeago(payload.created_at) + }); + + $list.prepend(notificationHtml); + + onNotificationOccurred(payload); + + initializeActions(payload, type); + + return true; + } + + + function onCreateJoinRequest(sessionId) { + var joinRequest = {}; + joinRequest.music_session = sessionId; + joinRequest.user = context.JK.currentUserId; + rest.createJoinRequest(joinRequest) + .done(function(response) { + + }).error(context.JK.app.ajaxError); + + context.JK.app.layout.closeDialog('alert'); + } + + function approveJoinRequest(args) { + rest.updateJoinRequest(args.join_request_id, true) + .done(function(response) { + deleteNotification(args.notification_id); + }).error(app.ajaxError); + } + + function rejectJoinRequest(args) { + rest.updateJoinRequest(args.join_request_id, false) + .done(function(response) { + deleteNotification(args.notification_id); + }).error(app.ajaxError); + } + + function openTerms(args) { + var termsDialog = new context.JK.TermsDialog(app, args, onTermsAccepted); + termsDialog.initialize(); + app.layout.showDialog('terms'); + } + + function onTermsAccepted(args) { + deleteNotification(args.notification_id); + context.location = '/client#/session/' + args.session_id; + } + + + function openAlert(sessionId) { + var alertDialog = new context.JK.AlertDialog(context.JK.app, "YES", + "You must be approved to join this session. Would you like to send a request to join?", + sessionId, onCreateJoinRequest); + + alertDialog.initialize(); + context.JK.app.layout.showDialog('alert'); + } + + function listenToRecording(args) { + deleteNotification(args.notification_id); + context.JK.popExternalLink('/recordings/' + args.recording_id); + } + + function deleteNotificationHandler(evt) { + evt.stopPropagation(); + var notificationId = $(this).attr('notification-id'); + deleteNotification(notificationId); + } + + function initialize(sidebarInstance, textMessageDialogInstance) { + sidebar = sidebarInstance; + textMessageDialog = textMessageDialogInstance; + $panel = $('[layout-id="panelNotifications"]'); + $expanded = $panel.find('.panel.expanded'); + $contents = $panel.find('.panelcontents'); + $count = $panel.find('#sidebar-notification-count'); + $list = $panel.find('#sidebar-notification-list'); + $notificationTemplate = $('#template-notification-panel'); + if($panel.length == 0) throw "notifications panel not found" + if($expanded.length == 0) throw "notifications expanded content not found" + if($contents.length == 0) throw "notifications contents not found" + if($count.length == 0) throw "notifications count element not found"; + if($list.length == 0) throw "notification list element not found"; + if($notificationTemplate.length == 0) throw "notification template not found"; + + events(); + + populate(); + }; + + this.initialize = initialize; + this.onNotificationOccurred = onNotificationOccurred; + }; +})(window, jQuery); diff --git a/web/app/assets/javascripts/sidebar.js b/web/app/assets/javascripts/sidebar.js index 6e4f49831..bac8a80cf 100644 --- a/web/app/assets/javascripts/sidebar.js +++ b/web/app/assets/javascripts/sidebar.js @@ -9,6 +9,8 @@ var rest = context.JK.Rest(); var invitationDialog = null; var textMessageDialog = null; + var notificationPanel = null; + var me = null; function initializeSearchPanel() { $('#search_text_type').change(function() { @@ -22,48 +24,20 @@ emptySearchResults(); } + function refreshFriends() { + rest.getFriends(). + done(function(response) { + friends = response; + updateFriendList(response); + // set friend count + $('#sidebar-friend-count').html(response.length); + }) + } + function initializeFriendsPanel() { - ///////////////////////////////////////////////////////////// - // THIS IS TEST CODE TO GENERATE BACK TO BACK NOTIFICATIONS - // app.notify({ - // "title": "TEST 1", - // "text": "Test 1", - // "icon_url": context.JK.resolveAvatarUrl("") - // }); - - // app.notify({ - // "title": "TEST 2", - // "text": "Test 2", - // "icon_url": context.JK.resolveAvatarUrl("") - // }); - - // app.notify({ - // "title": "TEST 3", - // "text": "Test 3", - // "icon_url": context.JK.resolveAvatarUrl("") - // }); - ///////////////////////////////////////////////////////////// - $('#sidebar-search-header').hide(); - - var url = "/api/users/" + context.JK.currentUserId + "/friends" - $.ajax({ - type: "GET", - dataType: "json", - contentType: 'application/json', - url: url, - processData: false, - success: function(response) { - friends = response; - updateFriendList(response); - - // set friend count - $('#sidebar-friend-count').html(response.length); - }, - error: app.ajaxError - }); - + refreshFriends(); return false; } @@ -116,204 +90,8 @@ } function initializeNotificationsPanel() { - // retrieve pending notifications for this user - var url = "/api/users/" + context.JK.currentUserId + "/notifications" - $.ajax({ - type: "GET", - dataType: "json", - contentType: 'application/json', - url: url, - processData: false, - success: function(response) { - - updateNotificationList(response); - - // set notification count - $('#sidebar-notification-count').html(response.length); - }, - error: app.ajaxError - }); - } - - function updateNotificationList(response) { - $('#sidebar-notification-list').empty(); - - $.each(response, function(index, val) { - - if(val.description == 'TEXT_MESSAGE') { - val.formatted_msg = textMessageDialog.formatTextMessage(val.message.substring(0, 200), val.source_user_id, val.source_user.name, val.message.length > 200).html(); - } - - // fill in template for Connect pre-click - var template = $('#template-notification-panel').html(); - var notificationHtml = context.JK.fillTemplate(template, { - notificationId: val.notification_id, - sessionId: val.session_id, - avatar_url: context.JK.resolveAvatarUrl(val.photo_url), - text: val.formatted_msg, - date: $.timeago(val.created_at) - }); - - $('#sidebar-notification-list').append(notificationHtml); - - // val.description contains the notification record's description value from the DB (i.e., type) - initializeActions(val, val.description); - }); - } - - function initializeActions(payload, type) { - - var $notification = $('li[notification-id=' + payload.notification_id + ']'); - var $btnNotificationAction = '#btn-notification-action'; - - // wire up "x" button to delete notification - $notification.find('#img-delete-notification').click(deleteNotificationHandler); - - // customize action buttons based on notification type - if (type === context.JK.MessageType.FRIEND_REQUEST) { - var $action_btn = $notification.find($btnNotificationAction); - $action_btn.text('ACCEPT'); - $action_btn.click(function() { - acceptFriendRequest(payload); - }); - } - - else if (type === context.JK.MessageType.FRIEND_REQUEST_ACCEPTED) { - $notification.find('#div-actions').hide(); - } - - else if (type === context.JK.MessageType.NEW_USER_FOLLOWER || type === context.JK.MessageType.NEW_BAND_FOLLOWER) { - $notification.find('#div-actions').hide(); - } - - else if (type === context.JK.MessageType.SESSION_INVITATION) { - var $action_btn = $notification.find($btnNotificationAction); - $action_btn.text('JOIN'); - $action_btn.click(function() { - openTerms(payload); - }); - } - - else if (type === context.JK.MessageType.JOIN_REQUEST) { - var $action_btn = $notification.find($btnNotificationAction); - $action_btn.text('APPROVE'); - $action_btn.click(function() { - approveJoinRequest(payload); - }); - } - - else if (type === context.JK.MessageType.JOIN_REQUEST_APPROVED) { - var $action_btn = $notification.find($btnNotificationAction); - $action_btn.text('JOIN'); - $action_btn.click(function() { - openTerms(payload); - }); - } - - else if (type === context.JK.MessageType.JOIN_REQUEST_REJECTED) { - $notification.find('#div-actions').hide(); - } - - else if (type === context.JK.MessageType.MUSICIAN_SESSION_JOIN || type === context.JK.MessageType.BAND_SESSION_JOIN) { - - var actionText = ''; - var callback; - if (context.JK.currentUserMusician) { - // user is MUSICIAN; musician_access = TRUE - if (payload.musician_access) { - actionText = "JOIN"; - callback = joinSession; - } - // user is MUSICIAN; fan_access = TRUE - else if (payload.fan_access) { - actionText = "LISTEN"; - callback = listenToSession; - } - } - else { - // user is FAN; fan_access = TRUE - if (payload.fan_access) { - actionText = "LISTEN"; - callback = listenToSession; - } - } - - var $action_btn = $notification.find($btnNotificationAction); - $action_btn.text(actionText); - $action_btn.click(function() { - callback(payload); - }); - } - - else if (type === context.JK.MessageType.MUSICIAN_RECORDING_SAVED || type === context.JK.MessageType.BAND_RECORDING_SAVED) { - var $action_btn = $notification.find($btnNotificationAction); - $action_btn.text('LISTEN'); - $action_btn.click(function() { - listenToRecording(payload); - }); - } - - else if (type === context.JK.MessageType.RECORDING_MASTER_MIX_COMPLETE) { - $notification.find('#div-actions').hide(); - context.jamClient.OnDownloadAvailable(); // poke backend, letting it know a download is available - } - - else if (type === context.JK.MessageType.BAND_INVITATION) { - var $action_btn = $notification.find($btnNotificationAction); - $action_btn.text('ACCEPT'); - $action_btn.click(function() { - acceptBandInvitation(payload); - }); - } - else if (type === context.JK.MessageType.BAND_INVITATION_ACCEPTED) { - $notification.find('#div-actions').hide(); - } - else if (type === context.JK.MessageType.TEXT_MESSAGE) { - var $action_btn = $notification.find($btnNotificationAction); - $action_btn.text('REPLY'); - $action_btn.click(function() { - var userId = $notification.find('.more-text-available').attr('data-sender-id'); - app.layout.showDialog('text-message', { d1: userId }); - }); - - var moreTextLink = $notification.find('.more-text-available'); - var textMessage = $notification.find('.text-message'); - var clipped_msg = textMessage.attr('data-is-clipped') === 'true'; - - if(clipped_msg) { - moreTextLink.text('more').show(); - moreTextLink.click(function(e) { - var userId = $(this).attr('data-sender-id'); - - return false; - }); - } - else { - moreTextLink.hide(); - } - } - } - - function deleteNotificationHandler(evt) { - evt.stopPropagation(); - var notificationId = $(this).attr('notification-id'); - deleteNotification(notificationId); - } - - function deleteNotification(notificationId) { - var url = "/api/users/" + context.JK.currentUserId + "/notifications/" + notificationId; - $.ajax({ - type: "DELETE", - dataType: "json", - contentType: 'application/json', - url: url, - processData: false, - success: function(response) { - $('li[notification-id=' + notificationId + ']').hide(); - decrementNotificationCount(); - }, - error: app.ajaxError - }); + notificationPanel = new context.JK.NotificationPanel(app); + notificationPanel.initialize(me, textMessageDialog); } function initializeChatPanel() { @@ -362,51 +140,6 @@ $('#sidebar-search-results').height('0px'); } - function incrementNotificationCount() { - var count = parseInt($('#sidebar-notification-count').html()); - $('#sidebar-notification-count').html(count + 1); - } - - function decrementNotificationCount() { - var count = parseInt($('#sidebar-notification-count').html()); - if (count === 0) { - $('#sidebar-notification-count').html(0); - } - else { - $('#sidebar-notification-count').html(count - 1); - } - } - - // default handler for incoming notification - function handleNotification(payload, type) { - - // on a load of notifications, it is possible to load a very new notification, - // and get a websocket notification right after for that same notification, - // so we need to protect against such duplicates - if($('#sidebar-notification-list').find('li[notification-id="' + payload.notification_id + '"]').length > 0) { - return false; - } - - // increment displayed notification count - incrementNotificationCount(); - - // add notification to sidebar - var template = $("#template-notification-panel").html(); - var notificationHtml = context.JK.fillTemplate(template, { - notificationId: payload.notification_id, - sessionId: payload.session_id, - avatar_url: context.JK.resolveAvatarUrl(payload.photo_url), - text: payload.msg instanceof jQuery ? payload.msg.html() : payload.msg , - date: $.timeago(payload.created_at) - }); - - $('#sidebar-notification-list').prepend(notificationHtml); - - initializeActions(payload, type); - - return true; - } - var delay = (function(){ var timer = 0; return function(callback, ms) { @@ -464,32 +197,14 @@ // friend notifications registerFriendUpdate(); - registerFriendRequest(); - registerFriendRequestAccepted(); - registerNewUserFollower(); - registerNewBandFollower(); // session invitations - registerSessionInvitation(); - registerSessionEnded(); - registerJoinRequest(); - registerJoinRequestApproved(); - registerJoinRequestRejected(); registerSessionJoin(); registerSessionDepart(); - registerMusicianSessionJoin(); - registerBandSessionJoin(); // recording notifications - registerMusicianRecordingSaved(); - registerBandRecordingSaved(); registerRecordingStarted(); registerRecordingEnded(); - registerRecordingMasterMixComplete(); - - // band notifications - registerBandInvitation(); - registerBandInvitationAccepted(); // broadcast notifications registerSourceUpRequested(); @@ -497,9 +212,6 @@ registerSourceUp(); registerSourceDown(); - // register text messages - registerTextMessage(); - // watch for Invite More Users events $('#sidebar-div .btn-email-invitation').click(function() { invitationDialog.showEmailDialog(); @@ -533,210 +245,6 @@ }); } - function registerFriendRequest() { - context.JK.JamServer.registerMessageCallback(context.JK.MessageType.FRIEND_REQUEST, function(header, payload) { - logger.debug("Handling FRIEND_REQUEST msg " + JSON.stringify(payload)); - - handleNotification(payload, header.type); - - app.notify({ - "title": "New Friend Request", - "text": payload.msg, - "icon_url": context.JK.resolveAvatarUrl(payload.photo_url) - }, { - "ok_text": "ACCEPT", - "ok_callback": acceptFriendRequest, - "ok_callback_args": { "friend_request_id": payload.friend_request_id, "notification_id": payload.notification_id } - }); - }); - } - - function acceptFriendRequest(args) { - - rest.acceptFriendRequest({ - status: 'accept', - friend_request_id: args.friend_request_id - }).done(function(response) { - deleteNotification(args.notification_id); // delete notification corresponding to this friend request - initializeFriendsPanel(); // refresh friends panel when request is accepted - }).error(app.ajaxError); - } - - function registerFriendRequestAccepted() { - context.JK.JamServer.registerMessageCallback(context.JK.MessageType.FRIEND_REQUEST_ACCEPTED, function(header, payload) { - logger.debug("Handling FRIEND_REQUEST_ACCEPTED msg " + JSON.stringify(payload)); - - handleNotification(payload, header.type); - - initializeFriendsPanel(); - - app.notify({ - "title": "Friend Request Accepted", - "text": payload.msg, - "icon_url": context.JK.resolveAvatarUrl(payload.photo_url) - }); - }); - } - - function registerNewUserFollower() { - - context.JK.JamServer.registerMessageCallback(context.JK.MessageType.NEW_USER_FOLLOWER, function(header, payload) { - logger.debug("Handling NEW_USER_FOLLOWER msg " + JSON.stringify(payload)); - - handleNotification(payload, header.type); - - app.notify({ - "title": "New Follower", - "text": payload.msg, - "icon_url": context.JK.resolveAvatarUrl(payload.photo_url) - }); - }); - } - - function registerNewBandFollower() { - - context.JK.JamServer.registerMessageCallback(context.JK.MessageType.NEW_BAND_FOLLOWER, function(header, payload) { - logger.debug("Handling NEW_BAND_FOLLOWER msg " + JSON.stringify(payload)); - - handleNotification(payload, header.type); - - app.notify({ - "title": "New Band Follower", - "text": payload.msg, - "icon_url": context.JK.resolveAvatarUrl(payload.photo_url) - }); - }); - - } - - function registerSessionInvitation() { - context.JK.JamServer.registerMessageCallback(context.JK.MessageType.SESSION_INVITATION, function(header, payload) { - logger.debug("Handling SESSION_INVITATION msg " + JSON.stringify(payload)); - - handleNotification(payload, header.type); - - var participants = []; - rest.getSession(payload.session_id).done(function(response) { - $.each(response.participants, function(index, val) { - participants.push({"photo_url": context.JK.resolveAvatarUrl(val.user.photo_url), "name": val.user.name}); - }); - - var participantHtml = "You have been invited to join a session with:

"; - participantHtml += ""; - - $.each(participants, function(index, val) { - if (index < 4) { - participantHtml += ""; - } - }); - - participantHtml += "
" + val.name + "
"; - - app.notify({ - "title": "Session Invitation", - "text": participantHtml - }, { - "ok_text": "JOIN SESSION", - "ok_callback": openTerms, - "ok_callback_args": { "session_id": payload.session_id, "notification_id": payload.notification_id } - }); - }).error(app.ajaxError); - - }); - } - - function openTerms(args) { - var termsDialog = new context.JK.TermsDialog(app, args, onTermsAccepted); - termsDialog.initialize(); - app.layout.showDialog('terms'); - } - - function onTermsAccepted(args) { - deleteNotification(args.notification_id); - context.location = '/client#/session/' + args.session_id; - } - - function registerSessionEnded() { - // TODO: this should clean up all notifications related to this session - context.JK.JamServer.registerMessageCallback(context.JK.MessageType.SESSION_ENDED, function(header, payload) { - logger.debug("Handling SESSION_ENDED msg " + JSON.stringify(payload)); - deleteSessionNotifications(payload.session_id); - }); - } - - // remove all notifications for this session - function deleteSessionNotifications(sessionId) { - $('li[session-id=' + sessionId + ']').hide(); - decrementNotificationCount(); - } - - function registerJoinRequest() { - context.JK.JamServer.registerMessageCallback(context.JK.MessageType.JOIN_REQUEST, function(header, payload) { - logger.debug("Handling JOIN_REQUEST msg " + JSON.stringify(payload)); - - handleNotification(payload, header.type); - - app.notify({ - "title": "New Join Request", - "text": payload.msg, - "icon_url": context.JK.resolveAvatarUrl(payload.photo_url) - }, { - "ok_text": "APPROVE", - "ok_callback": approveJoinRequest, - "ok_callback_args": { "join_request_id": payload.join_request_id, "notification_id": payload.notification_id }, - "cancel_text": "REJECT", - "cancel_callback": rejectJoinRequest, - "cancel_callback_args": { "join_request_id": payload.join_request_id, "notification_id": payload.notification_id } - }); - }); - } - - function approveJoinRequest(args) { - rest.updateJoinRequest(args.join_request_id, true) - .done(function(response) { - deleteNotification(args.notification_id); - }).error(app.ajaxError); - } - - function rejectJoinRequest(args) { - rest.updateJoinRequest(args.join_request_id, false) - .done(function(response) { - deleteNotification(args.notification_id); - }).error(app.ajaxError); - } - - function registerJoinRequestApproved() { - context.JK.JamServer.registerMessageCallback(context.JK.MessageType.JOIN_REQUEST_APPROVED, function(header, payload) { - logger.debug("Handling JOIN_REQUEST_APPROVED msg " + JSON.stringify(payload)); - - handleNotification(payload, header.type); - - app.notify({ - "title": "Join Request Approved", - "text": payload.msg, - "icon_url": context.JK.resolveAvatarUrl(payload.photo_url) - }, { - "ok_text": "JOIN SESSION", - "ok_callback": openTerms, - "ok_callback_args": { "session_id": payload.session_id, "notification_id": payload.notification_id } - }); - }); - } - - function registerJoinRequestRejected() { - context.JK.JamServer.registerMessageCallback(context.JK.MessageType.JOIN_REQUEST_REJECTED, function(header, payload) { - logger.debug("Handling JOIN_REQUEST_REJECTED msg " + JSON.stringify(payload)); - - handleNotification(payload, header.type); - - app.notify({ - "title": "Join Request Rejected", - "text": payload.msg, - "icon_url": context.JK.resolveAvatarUrl(payload.photo_url) - }); - }); - } - function registerSessionJoin() { context.JK.JamServer.registerMessageCallback(context.JK.MessageType.SESSION_JOIN, function(header, payload) { logger.debug("Handling SESSION_JOIN msg " + JSON.stringify(payload)); @@ -769,201 +277,6 @@ }); } - function registerMusicianSessionJoin() { - context.JK.JamServer.registerMessageCallback(context.JK.MessageType.MUSICIAN_SESSION_JOIN, function(header, payload) { - logger.debug("Handling MUSICIAN_SESSION_JOIN msg " + JSON.stringify(payload)); - - var okText = ''; - var showNotification = false; - var callback; - if (context.JK.currentUserMusician) { - // user is MUSICIAN; musician_access = TRUE - if (payload.musician_access) { - showNotification = true; - okText = "JOIN"; - callback = joinSession; - } - // user is MUSICIAN; fan_access = TRUE - else if (payload.fan_access) { - showNotification = true; - okText = "LISTEN"; - callback = listenToSession; - } - } - else { - // user is FAN; fan_access = TRUE - if (payload.fan_access) { - showNotification = true; - okText = "LISTEN"; - callback = listenToSession; - } - } - - if (showNotification) { - handleNotification(payload, header.type); - - app.notify({ - "title": "Musician Joined Session", - "text": payload.msg, - "icon_url": context.JK.resolveAvatarUrl(payload.photo_url) - }, { - "ok_text": okText, - "ok_callback": callback, - "ok_callback_args": { - "session_id": payload.session_id, - "fan_access": payload.fan_access, - "musician_access": payload.musician_access, - "approval_required": payload.approval_required, - "notification_id": payload.notification_id - } - } - ); - } - }); - } - - function registerBandSessionJoin() { - context.JK.JamServer.registerMessageCallback(context.JK.MessageType.BAND_SESSION_JOIN, function(header, payload) { - logger.debug("Handling BAND_SESSION_JOIN msg " + JSON.stringify(payload)); - - var okText = ''; - var showNotification = false; - var callback; - if (context.JK.currentUserMusician) { - // user is MUSICIAN; musician_access = TRUE - if (payload.musician_access) { - showNotification = true; - okText = "JOIN"; - callback = joinSession; - } - // user is MUSICIAN; fan_access = TRUE - else if (payload.fan_access) { - showNotification = true; - okText = "LISTEN"; - callback = listenToSession; - } - } - else { - // user is FAN; fan_access = TRUE - if (payload.fan_access) { - showNotification = true; - okText = "LISTEN"; - callback = listenToSession; - } - } - - if (showNotification) { - handleNotification(payload, header.type); - - app.notify({ - "title": "Band Joined Session", - "text": payload.msg, - "icon_url": context.JK.resolveAvatarUrl(payload.photo_url) - }, { - "ok_text": "LISTEN", - "ok_callback": callback, - "ok_callback_args": { - "session_id": payload.session_id, - "fan_access": payload.fan_access, - "musician_access": payload.musician_access, - "approval_required": payload.approval_required, - "notification_id": payload.notification_id - } - } - ); - } - }); - } - - function listenToSession(args) { - deleteNotification(args.notification_id); - context.JK.popExternalLink('/sessions/' + args.session_id); - } - - /*********** TODO: THE NEXT 3 FUNCTIONS ARE COPIED FROM sessionList.js. REFACTOR TO COMMON PLACE. *************/ - function joinSession(args) { - // NOTE: invited musicians get their own notification, so no need to check if user has invitation here - // like other places because an invited user would never get this notification - if (args.musician_access) { - if (args.approval_required) { - openAlert(args.session_id); - } - else { - openTerms(args); - } - } - deleteNotification(args.notification_id); - } - - function openAlert(sessionId) { - var alertDialog = new context.JK.AlertDialog(context.JK.app, "YES", - "You must be approved to join this session. Would you like to send a request to join?", - sessionId, onCreateJoinRequest); - - alertDialog.initialize(); - context.JK.app.layout.showDialog('alert'); - } - - function onCreateJoinRequest(sessionId) { - var joinRequest = {}; - joinRequest.music_session = sessionId; - joinRequest.user = context.JK.currentUserId; - rest.createJoinRequest(joinRequest) - .done(function(response) { - - }).error(context.JK.app.ajaxError); - - context.JK.app.layout.closeDialog('alert'); - } - ////////////////////////////////////////////////////////////////////////////////////////// - - function registerMusicianRecordingSaved() { - context.JK.JamServer.registerMessageCallback(context.JK.MessageType.MUSICIAN_RECORDING_SAVED, function(header, payload) { - logger.debug("Handling MUSICIAN_RECORDING_SAVED msg " + JSON.stringify(payload)); - - handleNotification(payload, header.type); - - app.notify({ - "title": "Musician Recording Saved", - "text": payload.msg, - "icon_url": context.JK.resolveAvatarUrl(payload.photo_url) - }, { - "ok_text": "LISTEN", - "ok_callback": listenToRecording, - "ok_callback_args": { - "recording_id": payload.recording_id, - "notification_id": payload.notification_id - } - }); - }); - } - - function registerBandRecordingSaved() { - context.JK.JamServer.registerMessageCallback(context.JK.MessageType.BAND_RECORDING_SAVED, function(header, payload) { - logger.debug("Handling BAND_RECORDING_SAVED msg " + JSON.stringify(payload)); - - handleNotification(payload, header.type); - - app.notify({ - "title": "Band Recording Saved", - "text": payload.msg, - "icon_url": context.JK.resolveAvatarUrl(payload.photo_url) - }, { - "ok_text": "LISTEN", - "ok_callback": listenToRecording, - "ok_callback_args": { - "recording_id": payload.recording_id, - "notification_id": payload.notification_id - } - }); - }); - } - - function listenToRecording(args) { - deleteNotification(args.notification_id); - context.JK.popExternalLink('/recordings/' + args.recording_id); - } - function registerRecordingStarted() { context.JK.JamServer.registerMessageCallback(context.JK.MessageType.RECORDING_STARTED, function(header, payload) { logger.debug("Handling RECORDING_STARTED msg " + JSON.stringify(payload)); @@ -988,86 +301,6 @@ }); } - function registerRecordingMasterMixComplete() { - context.JK.JamServer.registerMessageCallback(context.JK.MessageType.RECORDING_MASTER_MIX_COMPLETE, function(header, payload) { - logger.debug("Handling RECORDING_MASTER_MIX_COMPLETE msg " + JSON.stringify(payload)); - - handleNotification(payload, header.type); - - app.notify({ - "title": "Recording Master Mix Complete", - "text": payload.msg, - "icon_url": context.JK.resolveAvatarUrl(payload.photo_url) - }, { - "ok_text": "SHARE", - "ok_callback": shareRecording, - "ok_callback_args": { - "recording_id": payload.recording_id - } - }); - }); - } - - function shareRecording(args) { - var recordingId = args.recording_id; - } - - function registerBandInvitation() { - context.JK.JamServer.registerMessageCallback(context.JK.MessageType.BAND_INVITATION, function(header, payload) { - logger.debug("Handling BAND_INVITATION msg " + JSON.stringify(payload)); - - handleNotification(payload, header.type); - - app.notify({ - "title": "Band Invitation", - "text": payload.msg, - "icon_url": context.JK.resolveAvatarUrl(payload.photo_url) - }, { - "ok_text": "ACCEPT", - "ok_callback": acceptBandInvitation, - "ok_callback_args": { - "band_invitation_id": payload.band_invitation_id, - "band_id": payload.band_id, - "notification_id": payload.notification_id - } - }); - }); - } - - function acceptBandInvitation(args) { - rest.updateBandInvitation( - args.band_id, - args.band_invitation_id, - true - ).done(function(response) { - deleteNotification(args.notification_id); // delete notification corresponding to this friend request - }).error(app.ajaxError); - } - - function registerTextMessage() { - context.JK.JamServer.registerMessageCallback(context.JK.MessageType.TEXT_MESSAGE, function(header, payload) { - logger.debug("Handling TEXT_MESSAGE msg " + JSON.stringify(payload)); - - textMessageDialog.messageReceived(payload); - - handleNotification(payload, header.type); - }); - } - - function registerBandInvitationAccepted() { - context.JK.JamServer.registerMessageCallback(context.JK.MessageType.BAND_INVITATION_ACCEPTED, function(header, payload) { - logger.debug("Handling BAND_INVITATION_ACCEPTED msg " + JSON.stringify(payload)); - - handleNotification(payload, header.type); - - app.notify({ - "title": "Band Invitation Accepted", - "text": payload.msg, - "icon_url": context.JK.resolveAvatarUrl(payload.photo_url) - }); - }); - } - function registerSourceUpRequested() { context.JK.JamServer.registerMessageCallback(context.JK.MessageType.SOURCE_UP_REQUESTED, function(header, payload) { @@ -1174,13 +407,16 @@ } this.initialize = function(invitationDialogInstance, textMessageDialogInstance) { + me = this; + invitationDialog = invitationDialogInstance; + textMessageDialog = textMessageDialogInstance; events(); initializeSearchPanel(); initializeFriendsPanel(); initializeChatPanel(); initializeNotificationsPanel(); - invitationDialog = invitationDialogInstance; - textMessageDialog = textMessageDialogInstance; }; + + this.refreshFriends = refreshFriends; } })(window,jQuery); \ No newline at end of file diff --git a/web/app/assets/javascripts/textMessageDialog.js b/web/app/assets/javascripts/textMessageDialog.js index e76d413ba..3bcc2c5c2 100644 --- a/web/app/assets/javascripts/textMessageDialog.js +++ b/web/app/assets/javascripts/textMessageDialog.js @@ -47,8 +47,7 @@ return message; } - function sendMessage(e) { - + function sendMessage() { var msg = $textBox.val(); if(!msg || msg == '') { // don't bother the server with empty messages @@ -124,6 +123,15 @@ return markedUpMsg; } + // we handled the notification, meaning the dialog showed this message as a chat message + function handledNotification(payload) { + return showing && payload.description == "TEXT_MESSAGE" && payload.sender_id == otherId; + } + + function afterShow(args) { + $textBox.focus(); + } + function beforeShow(args) { app.layout.closeDialog('text-message') // ensure no others are showing. this is a singleton dialog @@ -175,13 +183,38 @@ reset(); } - function postMessage(e) { + function pasteIntoInput(el, text) { + el.focus(); + if (typeof el.selectionStart == "number" + && typeof el.selectionEnd == "number") { + var val = el.value; + var selStart = el.selectionStart; + el.value = val.slice(0, selStart) + text + val.slice(el.selectionEnd); + el.selectionEnd = el.selectionStart = selStart + text.length; + } else if (typeof document.selection != "undefined") { + var textRange = document.selection.createRange(); + textRange.text = text; + textRange.collapse(false); + textRange.select(); + } + } - return false; + function handleEnter(evt) { + if (evt.keyCode == 13 && evt.shiftKey) { + pasteIntoInput(this, "\n"); + evt.preventDefault(); + } + else if(evt.keyCode == 13 && !evt.shiftKey){ + sendMessage(); + return false; + } } function events() { - $form.submit(postMessage) + $form.submit(sendMessage) + + // http://stackoverflow.com/questions/6014702/how-do-i-detect-shiftenter-and-generate-a-new-line-in-textarea + $textBox.keydown(handleEnter); } @@ -234,6 +267,7 @@ function initialize() { var dialogBindings = { 'beforeShow' : beforeShow, + 'afterShow' : afterShow, 'afterHide': afterHide }; @@ -253,6 +287,7 @@ this.initialize = initialize; this.messageReceived = messageReceived; this.formatTextMessage = formatTextMessage; + this.handledNotification = handledNotification; } return this; diff --git a/web/app/assets/javascripts/utils.js b/web/app/assets/javascripts/utils.js index 583881cbe..61ea676c7 100644 --- a/web/app/assets/javascripts/utils.js +++ b/web/app/assets/javascripts/utils.js @@ -153,9 +153,9 @@ $element.bt(text, options); } - context.JK.bindHoverEvents = function ($parent) { + context.JK.bindHoverEvents = function ($parent) { - if(!$parent) { + if (!$parent) { $parent = $('body'); } @@ -328,9 +328,9 @@ } // creates an array with entries like [{ id: "drums", description: "Drums"}, ] - context.JK.listInstruments = function() { + context.JK.listInstruments = function () { var instrumentArray = []; - $.each(context.JK.server_to_client_instrument_map, function(key, val) { + $.each(context.JK.server_to_client_instrument_map, function (key, val) { instrumentArray.push({"id": context.JK.server_to_client_instrument_map[key].client_id, "description": key}); }); return instrumentArray; @@ -652,22 +652,22 @@ return hasFlash; } - context.JK.hasOneConfiguredDevice = function() { + context.JK.hasOneConfiguredDevice = function () { var result = context.jamClient.FTUEGetGoodConfigurationList(); logger.debug("hasOneConfiguredDevice: ", result); return result.length > 0; }; - context.JK.getGoodAudioConfigs = function() { + context.JK.getGoodAudioConfigs = function () { var result = context.jamClient.FTUEGetGoodAudioConfigurations(); logger.debug("goodAudioConfigs=%o", result); return result; }; - context.JK.getGoodConfigMap = function() { + context.JK.getGoodConfigMap = function () { var goodConfigMap = []; var goodConfigs = context.JK.getGoodAudioConfigs(); - $.each(goodConfigs, function(index, profileKey) { + $.each(goodConfigs, function (index, profileKey) { var friendlyName = context.jamClient.FTUEGetConfigurationDevice(profileKey); goodConfigMap.push({key: profileKey, name: friendlyName}); }); @@ -675,12 +675,12 @@ return goodConfigMap; } - context.JK.getBadAudioConfigs = function() { + context.JK.getBadAudioConfigs = function () { var badAudioConfigs = []; var allAudioConfigs = context.jamClient.FTUEGetAllAudioConfigurations(); var goodAudioConfigs = context.JK.getGoodAudioConfigs(); - for (var i=0; i < allAudioConfigs.length; i++) { + for (var i = 0; i < allAudioConfigs.length; i++) { if ($.inArray(allAudioConfigs[i], goodAudioConfigs) === -1) { badAudioConfigs.push(allAudioConfigs[i]); } @@ -689,10 +689,10 @@ return badAudioConfigs; }; - context.JK.getBadConfigMap = function() { + context.JK.getBadConfigMap = function () { var badConfigMap = []; var badConfigs = context.JK.getBadAudioConfigs(); - $.each(badConfigs, function(index, profileKey) { + $.each(badConfigs, function (index, profileKey) { var friendlyName = context.jamClient.FTUEGetConfigurationDevice(profileKey); badConfigMap.push({key: profileKey, name: friendlyName}); }); @@ -700,7 +700,7 @@ return badConfigMap; } - context.JK.getFirstGoodDevice = function(preferredDeviceId) { + context.JK.getFirstGoodDevice = function (preferredDeviceId) { var badConfigs = context.JK.getBadAudioConfigs(); function getGoodDevice() { @@ -713,7 +713,7 @@ } return deviceId; } - + var deviceId = null; if (preferredDeviceId) { @@ -724,7 +724,7 @@ } else { deviceId = getGoodDevice(); - } + } } else { deviceId = getGoodDevice(); @@ -733,14 +733,14 @@ } // returns /client#/home for http://www.jamkazam.com/client#/home - context.JK.locationPath = function() { + context.JK.locationPath = function () { var bits = context.location.href.split('/'); return '/' + bits.slice(3).join('/'); } - context.JK.nowUTC = function() { + context.JK.nowUTC = function () { var d = new Date(); - return new Date( d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate(), d.getUTCHours(), d.getUTCMinutes(), d.getUTCSeconds() ); + return new Date(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate(), d.getUTCHours(), d.getUTCMinutes(), d.getUTCSeconds()); } /* * A JavaScript implementation of the RSA Data Security, Inc. MD5 Message diff --git a/web/app/assets/javascripts/web/downloads.js b/web/app/assets/javascripts/web/downloads.js index 1c3976f90..82ef0a1da 100644 --- a/web/app/assets/javascripts/web/downloads.js +++ b/web/app/assets/javascripts/web/downloads.js @@ -78,8 +78,8 @@ var clicked = $(this); var href = clicked.attr('href'); if(href != "#") { + context.JK.GA.trackDownload(clicked.attr('data-platform')); rest.userDownloadedClient().always(function() { - context.JK.GA.trackDownload(clicked.attr('data-platform')); $('body').append('