This commit is contained in:
Jonathan Kolyer 2014-03-19 18:44:35 +00:00
commit e29907f09b
49 changed files with 1592 additions and 882 deletions

View File

@ -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

View File

@ -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

View File

@ -0,0 +1 @@
ALTER TABLE users ADD COLUMN notification_seen_at TIMESTAMP;

View File

@ -0,0 +1 @@
ALTER TABLE event_sessions ADD COLUMN ordinal INTEGER;

View File

@ -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

View File

@ -1,3 +1,3 @@
<% provide(:title, 'Jamkazam Password Changed') %>
You just changed your password at Jamkazam.
You just changed your password at JamKazam.

View File

@ -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 == ''

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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);
};

View File

@ -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');

View File

@ -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

View File

@ -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);

View File

@ -934,6 +934,7 @@
}
function getNotifications(options) {
if(!options) options = {};
var id = getId(options);
return $.ajax({
type: "GET",

View File

@ -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.");

View File

@ -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);
};

View File

@ -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: <br/><br/>";
participantHtml += "<table><tbody>";
$.each(participants, function(index, val) {
if (index < 4) {
participantHtml += "<tr><td><img class='avatar-small' src='" + context.JK.resolveAvatarUrl(val.photo_url) + "' /></td><td>" + val.name + "</td></tr>";
}
});
participantHtml += "</tbody></table>";
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);

View File

@ -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: <br/><br/>";
participantHtml += "<table><tbody>";
$.each(participants, function(index, val) {
if (index < 4) {
participantHtml += "<tr><td><img class='avatar-small' src='" + context.JK.resolveAvatarUrl(val.photo_url) + "' /></td><td>" + val.name + "</td></tr>";
}
});
participantHtml += "</tbody></table>";
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);

View File

@ -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;

View File

@ -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

View File

@ -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('<iframe class="downloading" src="' + clicked.attr('href') + '" style="display:none"/>')
});
}

View File

@ -61,17 +61,6 @@
});
$('.carousel').show()
$.each($('.feed-entry'), function (index, feedEntry) {
var $feedEntry = $(this);
if ($feedEntry.is('.recording-entry')) {
new context.JK.FeedItemRecording($feedEntry);
}
else {
new context.JK.FeedItemSession($feedEntry);
}
})
context.JK.TickDuration('.feed-entry.music-session-history-entry .inprogress .tick-duration');
if ($.QueryString['showVideo']) {

View File

@ -604,7 +604,7 @@ table.audiogeartable {
left:10px;
width:150px;
height:18px;
background-image:url(../images/content/bkg_slider_gain_horiz.png);
background-image:url(/assets/content/bkg_slider_gain_horiz.png);
background-repeat:repeat-x;
}

View File

@ -219,7 +219,7 @@
.share-message {
width: 100%;
padding:0;
padding:4px;
}
.error-msg {

View File

@ -42,6 +42,10 @@
-webkit-border-radius:50%;
-moz-border-radius:50%;
border-radius:50%;
&.highlighted {
background-color:white;
}
}
.expander {

View File

@ -29,6 +29,7 @@
.previous-message-text {
line-height:18px;
white-space:pre-line;
}
.previous-message-timestamp {
@ -52,4 +53,9 @@
width:100%;
height:40px;
}
.btn-send-text-message {
text-align:center;
width:50px;
}
}

View File

@ -109,13 +109,43 @@
}
}
.feed-entry {
position:relative;
display:block;
white-space:nowrap;
min-width:700px;
border-bottom:solid 1px #666;
max-height:74px;
overflow:hidden;
margin-top:20px;
&:nth-child(1) {
margin-top:0;
}
&[data-mode="minimal"] {
.avatar-small {
display:none;
}
.feed-type-title {
display:none;
}
.recording-controls-holder {
width: 65%;
float:left;
}
.name-and-description {
margin-left:0;
}
min-width:300px;
border-bottom-width:0;
}
.session-controls, .recording-controls {
display:inline-block;
&.ended {
background-color: #471f18;
}
&.inprogress {
@ -129,6 +159,16 @@
}
}
.recording-controls-holder {
float:right;
width:40%;
}
.name-and-description {
float:left;
width:30%;
margin-left:20px;
}
.recording-controls {
.recording-position {
width:70%;
@ -147,22 +187,6 @@
}
}
}
}
.feed-entry {
position:relative;
display:block;
white-space:nowrap;
min-width:700px;
border-bottom:solid 1px #666;
max-height:74px;
overflow:hidden;
margin-top:20px;
&:nth-child(1) {
margin-top:0;
}
/**
&.animate-down {
-webkit-transition: max-height height 2s;

View File

@ -242,7 +242,7 @@ Version: 1.1
.carousel .slides .spinner
{
background : #000 url(loading.gif) no-repeat center; /* video preloader */
background : #000 /*url(loading.gif)*/ no-repeat center; /* video preloader */
}
/* _____________________________ *

View File

@ -44,6 +44,12 @@ class ApiUsersController < ApiController
@user.show_whats_next = params[:show_whats_next] if params.has_key?(:show_whats_next)
@user.subscribe_email = params[:subscribe_email] if params.has_key?(:subscribe_email)
@user.biography = params[:biography] if params.has_key?(:biography)
# allow keyword of 'LATEST' to mean set the notification_seen_at to the most recent notification for this user
if params.has_key?(:notification_seen_at)
@user.update_notification_seen_at params[:notification_seen_at]
end
@user.save
if @user.errors.any?

View File

@ -173,12 +173,9 @@ class UsersController < ApplicationController
else
sign_in @user
if @user.musician
redirect_to :action => :congratulations_musician, :type => 'Native'
else
redirect_to :action => :congratulations_fan, :type => 'Native'
end
destination = @user.musician ? :congratulations_musician : :congratulations_fan
redirect_to :action => destination, :type => @user.user_authorization('facebook') ? 'Facebook' : 'Native'
end
end

View File

@ -1,4 +1,4 @@
object @music_session
object @music_session
if !current_user
# there should be more data returned, but we need to think very carefully about what data is public for a music session

View File

@ -10,7 +10,8 @@ end
# give back more info if the user being fetched is yourself
if @user == current_user
attributes :email, :original_fpfile, :cropped_fpfile, :crop_selection, :session_settings, :show_whats_next, :subscribe_email, :auth_twitter
attributes :email, :original_fpfile, :cropped_fpfile, :crop_selection, :session_settings, :show_whats_next, :subscribe_email, :auth_twitter, :new_notifications
elsif current_user
node :is_friend do |uu|

View File

@ -135,7 +135,6 @@
textMessageDialog.initialize();
var acceptFriendRequestDialog = new JK.AcceptFriendRequestDialog(JK.app);
acceptFriendRequestDialog.initialize();
var localRecordingsDialog = new JK.LocalRecordingsDialog(JK.app);
localRecordingsDialog.initialize();
@ -155,6 +154,7 @@
header.initialize();
var sidebar = new JK.Sidebar(JK.app);
acceptFriendRequestDialog.initialize(sidebar);
sidebar.initialize(invitationDialog, textMessageDialog);
var homeScreen = new JK.HomeScreen(JK.app);

View File

@ -6,15 +6,19 @@
%span.event-title= event_session_title(event_session)
.landing-details.event
.left.f20.teal.time
%strong
= event_session_start_hour(event_session)
.right.session-button
= event_session_button(event_session)
- unless event_session.has_public_mixed_recordings?
.left.f20.teal.time
%strong
= event_session_start_hour(event_session)
.right.session-button
= event_session_button(event_session)
%br{ clear:'all' }
.left.bio
= event_session_description(event_session)
.left
- event_session.public_mixed_recordings.each do |recording|
= render :partial => "users/feed_recording", locals: { feed_item: recording, mode: 'minimal'}
%br
%br
%br{ clear:'all' }

View File

@ -23,7 +23,7 @@
%h2 ARTIST LINEUP
%br
= render :partial => "event_session", :collection => @event.event_sessions.order('starts_at')
= render :partial => "event_session", :collection => @event.event_sessions.order('ordinal, starts_at')
%br{clear:'all'}

View File

@ -68,4 +68,8 @@
%br{:clear => "all"}/
%br/
%br/
:javascript
$(function () {
new window.JK.FeedItemSession($('.feed-entry[data-music-session="#{feed_item.id}"]'));
})

View File

@ -1,21 +1,24 @@
.feed-entry.recording-entry{:id => feed_item.id, :'data-claimed-recording-id' => feed_item.candidate_claimed_recording.id}
/ default values for template
- mode ||= local_assigns[:mode] = local_assigns.fetch(:mode, 'full')
.feed-entry.recording-entry{:id => feed_item.id, :'data-claimed-recording-id' => feed_item.candidate_claimed_recording.id, :'data-mode' => mode}
/ avatar
.avatar-small.ib
= recording_avatar(feed_item)
/ type and artist
.left.ml20.w15
.left.ml20.w15.feed-type-title
.title{hoveraction: 'recording', :'recording-id' => feed_item.candidate_claimed_recording.id } RECORDING
.artist
= recording_artist_name(feed_item)
= timeago(feed_item.created_at, class: 'small created_at')
/ name and description
.left.ml20.w30
.name-and-description
.name.dotdotdot
= recording_name(feed_item)
.description.dotdotdot
= recording_description(feed_item)
/ timeline and controls
.right.w40
.recording-controls-holder
/ recording play controls
.recording-controls{ class: feed_item.candidate_claimed_recording.has_mix? ? 'has-mix' : 'no-mix'}
/ play button
@ -83,3 +86,7 @@
%br{:clear => "all"}/
%br/
:javascript
$(function () {
new window.JK.FeedItemRecording($('.feed-entry[data-claimed-recording-id="#{feed_item.candidate_claimed_recording.id}"]'));
})

View File

@ -9,8 +9,8 @@
- content_for :after_black_bar do
- if @jamfest_2014
.jamfest{style: 'top:-70px;position:relative'}
%a{ href: event_path(@jamfest_2014.slug), style: 'font-size:24px' }
Join us online March 12 for Virtual Jam Fest!
%a{ href: event_path(@jamfest_2014.slug), style: 'font-size:20px;margin-top:11px' }
Listen to the terrific band performances from Virtual Jam Fest 2014!
%div{style: "padding-top:20px;"}
.right
= render :partial => "buzz"

View File

@ -30,6 +30,9 @@ describe "Accept Friend Request", :js => true, :type => :feature, :capybara_feat
page.should_not have_selector('h1', text: 'friend request')
friend_request.reload
friend_request.status.should == 'accept'
# make sure the friend list is refreshed
find("[layout-id=\"panelFriends\"] .friend-name[user-id=\"#{@user2.id}\"]", visible: false)
end
it "already accepted" do

View File

@ -115,15 +115,25 @@ describe "Events", :js => true, :type => :feature, :capybara_feature => true, :s
@event_session2.starts_at = 4.hours.ago
@event_session2.save!
visit "/events/so_latency"
expect(page).to have_css(".landing-band.event[data-event-session=\"#{@event_session2.id}\"]")
first(".landing-band.event[data-event-session='#{@event_session2.id}']:nth-child(1)")
# test that it sorts correctly by putting this later event second
@event_session2.starts_at = 4.hours.from_now
@event_session2.save!
visit "/events/so_latency"
expect(page).to have_css(".landing-band.event[data-event-session=\"#{@event_session.id}\"]")
first(".landing-band.event[data-event-session='#{@event_session.id}']:nth-child(1)")
# test that it sorts correctly by putting this later event first, because ordinal is specified
@event_session2.ordinal = 0
@event_session2.save!
visit "/events/so_latency"
first(".landing-band.event[data-event-session='#{@event_session2.id}']:nth-child(1)")
# associate a recording, and verify that the display changes to have no description, but has a recording widget
mix = FactoryGirl.create(:mix)
mix.recording.music_session_id = music_session_history.id
mix.recording.save!
visit "/events/so_latency"
find(".feed-entry.recording-entry[data-claimed-recording-id='#{mix.recording.claimed_recordings[0].id}']")
end
end

View File

@ -8,6 +8,7 @@ describe "Home Screen", :js => true, :type => :feature, :capybara_feature => tru
Capybara.javascript_driver = :poltergeist
Capybara.current_driver = Capybara.javascript_driver
Capybara.default_wait_time = 10
MusicSession.delete_all
end
let(:user) { FactoryGirl.create(:user) }

View File

@ -0,0 +1,168 @@
require 'spec_helper'
describe "Notification Highlighter", :js => true, :type => :feature, :capybara_feature => true do
subject { page }
before(:all) do
Capybara.javascript_driver = :poltergeist
Capybara.current_driver = Capybara.javascript_driver
Capybara.default_wait_time = 10
end
let(:user) { FactoryGirl.create(:user) }
let(:user2) { FactoryGirl.create(:user) }
shared_examples_for :notification_badge do |options|
it "in correct state" do
sign_in_poltergeist(user) unless page.has_selector?('h2', 'musicians')
badge = find("#{NOTIFICATION_PANEL} .badge", text:options[:count])
badge['class'].include?('highlighted').should == options[:highlighted]
if options[:action] == :click
badge.trigger(:click)
badge = find("#{NOTIFICATION_PANEL} .badge", text:0)
badge['class'].include?('highlighted').should == false
end
end
end
describe "user with no notifications" do
it_behaves_like :notification_badge, highlighted: false, count:0
describe "and realtime notifications with sidebar closed" do
before(:each) do
sign_in_poltergeist(user)
document_focus
user.reload
end
it_behaves_like :notification_badge, highlighted: false, count:0
describe "sees notification" do
before(:each) do
notification = Notification.send_text_message("text message", user2, user)
notification.errors.any?.should be_false
end
it_behaves_like :notification_badge, highlighted: false, count:0, action: :click
end
describe "document out of focus" do
before(:each) do
document_blur
notification = Notification.send_text_message("text message", user2, user)
notification.errors.any?.should be_false
end
it_behaves_like :notification_badge, highlighted: true, count:1, action: :click
end
end
describe "and realtime notifications with sidebar open" do
before(:each) do
# generate one message so that count = 1 to start
notification = Notification.send_text_message("text message", user2, user)
notification.errors.any?.should be_false
sign_in_poltergeist(user)
document_focus
open_notifications
badge = find("#{NOTIFICATION_PANEL} .badge", text:'0') # wait for the opening of the sidebar to bring count to 0
user.reload
end
it_behaves_like :notification_badge, highlighted: false, count:0
describe "sees notification" do
before(:each) do
notification = Notification.send_text_message("text message", user2, user)
notification.errors.any?.should be_false
find('#notification #ok-button') # wait for notification to show, so that we know the sidebar had a chance to update
end
it_behaves_like :notification_badge, highlighted: false, count:0
end
describe "document out of focus" do
before(:each) do
document_blur
notification = Notification.send_text_message("text message 2", user2, user)
notification.errors.any?.should be_false
find('#notification #ok-button')
end
it_behaves_like :notification_badge, highlighted: true, count:1
describe "user comes back" do
before(:each) do
window_focus
it_behaves_like :notification_badge, highlighted: false, count:1
end
end
end
end
end
describe "user with new notifications" do
before(:each) do
# create a notification
notification = Notification.send_text_message("text message", user2, user)
notification.errors.any?.should be_false
end
it_behaves_like :notification_badge, highlighted:true, count:1, action: :click
describe "user has previously seen notifications" do
before(:each) do
user.update_notification_seen_at 'LATEST'
user.save!
end
it_behaves_like :notification_badge, highlighted: false, count:0, action: :click
describe "user again has unseen notifications" do
before(:each) do
# create a notification
notification = Notification.send_text_message("text message", user2, user)
notification.errors.any?.should be_false
end
it_behaves_like :notification_badge, highlighted:true, count:1, action: :click
end
end
end
describe "user no unseen notifications" do
describe "notification occurs in realtime" do
describe "sidebar is open" do
describe "user can see notifications" do
it "stays deactivated" do
end
end
describe "user can not see notifications" do
describe "with dialog open" do
it "temporarily activates" do
end
end
describe "with document blurred" do
it "temporarily activates" do
end
end
end
end
end
end
end

View File

@ -162,6 +162,30 @@ describe "Signup", :js => true, :type => :feature, :capybara_feature => true do
end
describe "signup facebook user" do
before do
@fb_signup = FactoryGirl.create(:facebook_signup)
visit "#{signup_path}?facebook_signup=#{@fb_signup.lookup_id}"
find('#jam_ruby_user_first_name')
sleep 1 # if I don't do this, first_name and/or last name intermittently fail to fill out
fill_in "jam_ruby_user[first_name]", with: "Mike"
fill_in "jam_ruby_user[last_name]", with: "Jones"
fill_in "jam_ruby_user[email]", with: "newuser_fb@jamkazam.com"
fill_in "jam_ruby_user[password]", with: "jam123"
fill_in "jam_ruby_user[password_confirmation]", with: "jam123"
check("jam_ruby_user[instruments][drums][selected]")
check("jam_ruby_user[terms_of_service]")
click_button "CREATE ACCOUNT"
end
it "success" do
page.should have_title("JamKazam")
should have_selector('div.tagline', text: "Congratulations!")
uri = URI.parse(current_url)
"#{uri.path}?#{uri.query}".should == congratulations_musician_path(:type => 'Facebook')
end
end
def signup_invited_user
visit "#{signup_path}?invitation_code=#{@invited_user.invitation_code}"
find('#jam_ruby_user_first_name')
@ -239,7 +263,6 @@ describe "Signup", :js => true, :type => :feature, :capybara_feature => true do
end
end
end

View File

@ -42,7 +42,7 @@ describe "Text Message", :js => true, :type => :feature, :capybara_feature => tr
notification = Notification.send_text_message("bibbity bobbity boo", @user2, @user1)
notification.errors.any?.should be_false
open_sidebar
open_notifications
# find the notification and click REPLY
find("[layout-id='panelNotifications'] [notification-id='#{notification.id}'] .button-orange", text:'REPLY').trigger(:click)

View File

@ -1,6 +1,8 @@
# methods here all assume you are in /client
NOTIFICATION_PANEL = '[layout-id="panelNotifications"]'
# enters text into the search sidebar
def site_search(text, options = {})
within('#searchForm') do
@ -56,11 +58,41 @@ def send_text_message(msg, options={})
end
end
def open_sidebar
find('[layout-id="panelNotifications"] .panel-header').trigger(:click)
def open_notifications
find("#{NOTIFICATION_PANEL} .panel-header").trigger(:click)
end
def hover_intent(element)
element.hover
element.hover
end
# forces document.hasFocus() to return false
def document_blur
page.evaluate_script(%{(function() {
// save original
if(!window.documentFocus) { window.documentFocus = window.document.hasFocus; }
window.document.hasFocus = function() {
console.log("document.hasFocus() returns false");
return false;
}
})()})
end
def document_focus
page.evaluate_script(%{(function() {
// save original
if(!window.documentFocus) { window.documentFocus = window.document.hasFocus; }
window.document.hasFocus = function() {
console.log("document.hasFocus() returns true");
return true;
}
})()})
end
# simulates focus event on window
def window_focus
page.evaluate_script(%{window.jQuery(window).trigger('focus');})
end

View File

@ -0,0 +1,72 @@
/*global jQuery*/
/*jshint curly:false*/
;(function ( $, window) {
"use strict";
var defaults = {
pulses : 1,
interval : 0,
returnDelay : 0,
duration : 500
};
$.fn.pulse = function(properties, options, callback) {
// $(...).pulse('destroy');
var stop = properties === 'destroy';
if (typeof options === 'function') {
callback = options;
options = {};
}
options = $.extend({}, defaults, options);
if (!(options.interval >= 0)) options.interval = 0;
if (!(options.returnDelay >= 0)) options.returnDelay = 0;
if (!(options.duration >= 0)) options.duration = 500;
if (!(options.pulses >= -1)) options.pulses = 1;
if (typeof callback !== 'function') callback = function(){};
return this.each(function () {
var el = $(this),
property,
original = {};
var data = el.data('pulse') || {};
data.stop = stop;
el.data('pulse', data);
for (property in properties) {
if (properties.hasOwnProperty(property)) original[property] = el.css(property);
}
var timesPulsed = 0;
function animate() {
if (typeof el.data('pulse') === 'undefined') return;
if (el.data('pulse').stop) return;
if (options.pulses > -1 && ++timesPulsed > options.pulses) return callback.apply(el);
el.animate(
properties,
{
duration : options.duration / 2,
complete : function(){
window.setTimeout(function(){
el.animate(original, {
duration : options.duration / 2,
complete : function() {
window.setTimeout(animate, options.interval);
}
});
},options.returnDelay);
}
}
);
}
animate();
});
};
})( jQuery, window, document );

View File

@ -565,9 +565,19 @@ module JamWebsockets
raise SessionError, 'connection state is gone. please reconnect.'
else
Connection.transaction do
music_session = MusicSession.select(:track_changes_counter).find_by_id(connection.music_session_id) if connection.music_session_id
track_changes_counter = music_session.track_changes_counter if music_session
# send back track_changes_counter if in a session
if connection.music_session_id
music_session = MusicSession.select(:track_changes_counter).find_by_id(connection.music_session_id)
track_changes_counter = music_session.track_changes_counter if music_session
end
# update connection updated_at
connection.touch
# update user's notification_seen_at field if the heartbeat indicates it saw one
# first we try to use the notification id, which should usually exist.
# if not, then fallback to notification_seen_at, which is approximately the last time we saw a notification
update_notification_seen_at(connection, context, heartbeat)
end
ConnectionManager.active_record_transaction do |connection_manager|
@ -593,6 +603,34 @@ module JamWebsockets
end
end
def update_notification_seen_at(connection, context, heartbeat)
notification_id_field = heartbeat.notification_seen if heartbeat.value_for_tag(1)
if notification_id_field
notification = Notification.find_by_id(notification_id_field)
if notification
connection.user.notification_seen_at = notification.created_at
unless connection.user.save(validate: false)
@log.error "unable to update notification_seen_at via id field for client #{context}. errors: #{connection.user.errors.inspect}"
end
else
notification_seen_at_parsed = nil
notification_seen_at = heartbeat.notification_seen_at if heartbeat.value_for_tag(2)
begin
notification_seen_at_parsed = Time.parse(notification_seen_at) if notification_seen_at && notification_seen_at.length > 0
rescue Exception => e
@log.error "unable to parse notification_seen_at in heartbeat from #{context}. notification_seen_at: #{notification_seen_at}"
end
if notification_seen_at_parsed
connection.user.notification_seen_at = notification_seen_at
unless connection.user.save(validate: false)
@log.error "unable to update notification_seen_at via time field for client #{context}. errors: #{connection.user.errors.inspect}"
end
end
end
end
end
def valid_login(username, password, token, client_id)
if !token.nil? && token != ''