(function(context,$) {
"use strict";
context.JK = context.JK || {};
context.JK.NotificationPanel = function(app) {
var EVENTS = context.JK.EVENTS;
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;
var sessionUtils = context.JK.SessionUtils;
var notificationBatchSize = 20;
var currentNotificationPage = 0;
var didLoadAllNotifications = false, isLoading = false;
var ui = new context.JK.UIHelper(JK.app);
function isNotificationsPanelVisible() {
return $contents.is(':visible')
}
function incrementNotificationCount() {
var count = parseInt($count.text());
setCount(count + 1);
}
function decrementNotificationCount() {
var count = parseInt($count.text());
if(count > 0) {
count = count - 1;
setCount(count);
if(count == 0) {
lowlightCount();
missedNotificationsWhileAway = false;
}
}
}
// 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, type) {
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 {
if(app.layout.isNoisyNotification(payload) && !missedNotificationsWhileAway) {
// this handles a special case--if a notification is too noisy while away, then don't bother
// incrementing anything on the sidebar or otherwise distracting the user
app.updateNotificationSeen(payload.notification_id, payload.created_at);
}
else {
queueNotificationSeen(payload.notification_id, payload.created_at, payload.description);
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() {
$(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);
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();
// scheduled sessions
registerScheduledSessionInvitation();
registerScheduledSessionRsvp();
registerScheduledSessionRsvpApproved();
registerScheduledSessionRsvpCancelled();
registerScheduledSessionRsvpCancelledOrg();
registerScheduledSessionCancelled();
registerScheduledSessionRescheduled();
registerScheduledSessionReminder();
registerScheduledSessionComment();
// recording notifications
registerMusicianRecordingSaved();
registerBandRecordingSaved();
registerRecordingMasterMixComplete();
registerRecordingStreamMixComplete();
// band notifications
registerBandInvitation();
registerBandInvitationAccepted();
// register text messages
registerTextMessage();
}
function buildParams() {
return { offset: currentNotificationPage * notificationBatchSize, limit: notificationBatchSize};
}
function populate() {
if (isLoading || didLoadAllNotifications) return;
isLoading = true;
// retrieve pending notifications for this user
rest.getNotifications(buildParams())
.done(function(response) {
updateNotificationList(response);
isLoading = false;
})
.fail(function() {
isLoading = false;
app.ajaxError();
})
}
function updateNotificationList(response) {
$.each(response, function(index, val) {
if(val.description == context.JK.MessageType.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,
hoveraction: val.session_id ? "session" : "",
avatar_url: context.JK.resolveAvatarUrl(val.photo_url),
text: val.formatted_msg,
date: $.timeago(val.created_at),
userId: val.source_user_id
});
$list.append(notificationHtml);
// val.description contains the notification record's description value from the DB (i.e., type)
initializeActions(val, val.description);
});
if(response.length < notificationBatchSize) {
didLoadAllNotifications = true;
}
currentNotificationPage++;
}
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.RECORDING_MASTER_MIX_COMPLETE) {
var $action_btn = $notification.find($btnNotificationAction);
$action_btn.text('SHARE');
$action_btn.click(function() {
rest.getRecording({id: payload.recording_id})
.done(function(recording) {
if(recording.my) {
ui.launchShareDialog(recording.my.id, 'recording');
}
else {
context.JK.Banner.showAlert('Unable to share recording')
}
})
.fail(app.ajaxError)
});
}
else if (type === context.JK.MessageType.RECORDING_STREAM_MIX_COMPLETE) {
var $action_btn = $notification.find($btnNotificationAction);
$action_btn.text('SHARE');
$action_btn.click(function() {
rest.getRecording({id: payload.recording_id})
.done(function(recording) {
if(recording.my) {
ui.launchShareDialog(recording.my.id, 'recording');
}
else {
context.JK.Banner.showAlert('Unable to share recording')
}
})
.fail(app.ajaxError)
});
}
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);
if (actionText === '') {
$action_btn.hide();
}
else {
$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();
logger.debug("context.jamClient.OnDownloadAvailable!")
context.jamClient.OnDownloadAvailable(); // poke backend, letting it know a download is available
}
else if (type === context.JK.MessageType.JAM_TRACK_SIGN_COMPLETE) {
$notification.find('#div-actions').hide();
logger.debug("context.jamClient.OnDownloadAvailable!")
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');
app.layout.showDialog('text-message', { d1: userId });
return false;
});
}
else {
moreTextLink.hide();
}
}
else if (type === context.JK.MessageType.SCHEDULED_SESSION_INVITATION) {
linkSessionInfoNotification(payload, $notification, $btnNotificationAction);
}
else if (type === context.JK.MessageType.SCHEDULED_SESSION_RSVP) {
var $action_btn = $notification.find($btnNotificationAction);
$action_btn.text('MANAGE RSVP');
$action_btn.click(function() {
context.location = "/client#/account/sessionDetail/" + payload.session_id;
});
}
else if (type === context.JK.MessageType.SCHEDULED_SESSION_RSVP_APPROVED) {
linkSessionInfoNotification(payload, $notification, $btnNotificationAction);
}
else if (type === context.JK.MessageType.SCHEDULED_SESSION_RSVP_CANCELLED) {
linkSessionInfoNotification(payload, $notification, $btnNotificationAction);
}
else if (type === context.JK.MessageType.SCHEDULED_SESSION_RSVP_CANCELLED_ORG) {
linkSessionInfoNotification(payload, $notification, $btnNotificationAction);
}
else if (type === context.JK.MessageType.SCHEDULED_SESSION_CANCELLED) {
linkSessionInfoNotification(payload, $notification, $btnNotificationAction);
}
else if (type === context.JK.MessageType.SCHEDULED_SESSION_RESCHEDULED) {
linkSessionInfoNotification(payload, $notification, $btnNotificationAction);
}
else if (type === context.JK.MessageType.SCHEDULED_SESSION_REMINDER) {
linkSessionInfoNotification(payload, $notification, $btnNotificationAction);
}
else if (type === context.JK.MessageType.SCHEDULED_SESSION_COMMENT) {
linkSessionInfoNotification(payload, $notification, $btnNotificationAction);
}
context.JK.bindHoverEvents($("#sidebar-div"));
}
function linkSessionInfoNotification(payload, $notification, $btnNotificationAction) {
var $action_btn = $notification.find($btnNotificationAction);
$action_btn.text('SESSION DETAILS');
$action_btn.click(function() {
openSessionInfoWebPage({"session_id": payload.session_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 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) {
var notification = $('li[notification-id=' + notificationId + ']');
if(notification.length > 0) {
decrementNotificationCount();
}
notification.remove();
},
error: app.ajaxError
});
}
function listenToSession(args) {
deleteNotification(args.notification_id);
context.JK.popExternalLink('/sessions/' + args.session_id);
}
function joinSession(args) {
sessionUtils.joinSession(args.session_id);
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)
}, [{
id: "btn-join",
text: "JOIN SESSION",
"layout-action": "close",
href: "#",
"class": "button-orange",
callback: openTerms,
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)
}, [{
id: "btn-approve",
text: "APPROVE",
"layout-action": "close",
href: "#",
"class": "button-orange",
callback: approveJoinRequest,
callback_args: {
"join_request_id": payload.join_request_id,
"notification_id": payload.notification_id
}
},
{
id: "btn-reject",
text: "REJECT",
"layout-action": "close",
href: "#",
"class": "button-grey",
callback: rejectJoinRequest,
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)
}, [{
id: "btn-accept",
text: "ACCEPT",
"layout-action": "close",
href: "#",
"class": "button-orange",
callback: acceptFriendRequest,
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);
app.notify({
"title": "Session Invitation",
"text": payload.msg
}, [{
id: "btn-join",
text: "JOIN SESSION",
"layout-action": "close",
href: "#",
"class": "button-orange",
callback: openTerms,
callback_args: {
"session_id": payload.session_id,
"notification_id": payload.notification_id
}
}]
);
// 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
// STILL SETTING UP THE SESSION WHEN THE NOTIFICATION IS SENT
// SEE ActiveMusicSession#participant_create
// 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 += "
| " + val.name + " |