diff --git a/admin/config/application.rb b/admin/config/application.rb
index 0ae29cd8e..36a91e1a5 100644
--- a/admin/config/application.rb
+++ b/admin/config/application.rb
@@ -119,5 +119,7 @@ module JamAdmin
config.twitter_app_secret = ENV['TWITTER_APP_SECRET'] || 'Azcy3QqfzYzn2fsojFPYXcn72yfwa0vG6wWDrZ3KT8'
config.ffmpeg_path = ENV['FFMPEG_PATH'] || (File.exist?('/usr/local/bin/ffmpeg') ? '/usr/local/bin/ffmpeg' : '/usr/bin/ffmpeg')
+
+ config.max_audio_downloads = 100
end
end
diff --git a/db/manifest b/db/manifest
index 0c78cb3a7..aba246d9b 100755
--- a/db/manifest
+++ b/db/manifest
@@ -137,4 +137,6 @@ cascading_delete_constraints_for_release.sql
events_social_description.sql
fix_broken_cities.sql
notifications_with_text.sql
+notification_seen_at.sql
+order_event_session.sql
emails.sql
diff --git a/db/up/notification_seen_at.sql b/db/up/notification_seen_at.sql
new file mode 100644
index 000000000..67b29ddd8
--- /dev/null
+++ b/db/up/notification_seen_at.sql
@@ -0,0 +1 @@
+ALTER TABLE users ADD COLUMN notification_seen_at TIMESTAMP;
\ No newline at end of file
diff --git a/db/up/order_event_session.sql b/db/up/order_event_session.sql
new file mode 100644
index 000000000..ba36f8f94
--- /dev/null
+++ b/db/up/order_event_session.sql
@@ -0,0 +1 @@
+ALTER TABLE event_sessions ADD COLUMN ordinal INTEGER;
\ No newline at end of file
diff --git a/pb/src/client_container.proto b/pb/src/client_container.proto
index 4ae83c193..1df3bebf8 100644
--- a/pb/src/client_container.proto
+++ b/pb/src/client_container.proto
@@ -472,7 +472,8 @@ message TestClientMessage {
// sent from client to server periodically to let server track if the client is truly alive and avoid TCP timeout scenarios
// the server will send a HeartbeatAck in response to this
message Heartbeat {
-
+ optional string notification_seen = 1;
+ optional string notification_seen_at = 2;
}
// target: client
diff --git a/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/password_changed.html.erb b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/password_changed.html.erb
index a140d13fe..534066c5a 100644
--- a/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/password_changed.html.erb
+++ b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/password_changed.html.erb
@@ -1,3 +1,3 @@
<% provide(:title, 'Jamkazam Password Changed') %>
-You just changed your password at Jamkazam.
+You just changed your password at JamKazam.
diff --git a/ruby/lib/jam_ruby/models/event_session.rb b/ruby/lib/jam_ruby/models/event_session.rb
index 7385de68d..9d4fa8a9d 100644
--- a/ruby/lib/jam_ruby/models/event_session.rb
+++ b/ruby/lib/jam_ruby/models/event_session.rb
@@ -1,6 +1,6 @@
class JamRuby::EventSession < ActiveRecord::Base
- attr_accessible :event_id, :user_id, :band_id, :starts_at, :ends_at, :pinned_state, :position, :img_url, :img_width, :img_height, as: :admin
+ attr_accessible :event_id, :user_id, :band_id, :starts_at, :ends_at, :pinned_state, :position, :img_url, :img_width, :img_height, :ordinal, as: :admin
belongs_to :user, class_name: 'JamRuby::User'
belongs_to :band, class_name: 'JamRuby::Band'
@@ -13,6 +13,44 @@ class JamRuby::EventSession < ActiveRecord::Base
before_validation :sanitize_active_admin
+ def has_public_mixed_recordings?
+ public_mixed_recordings.length > 0
+ end
+
+ def public_mixed_recordings
+ recordings.select { |recording| recording if recording.has_mix? && recording.is_public? }
+ end
+
+ def recordings
+ recordings=[]
+
+ sessions.each do |session_history|
+ recordings = recordings + session_history.recordings
+ end
+
+ recordings
+ end
+
+ # ideally this is based on some proper association with the event, not such a slushy time grab
+ def sessions
+ if ready_display
+ query = MusicSessionHistory.where(fan_access: true).where(created_at: (self.starts_at - 12.hours)..(self.ends_at + 12.hours))
+ if self.user_id
+ query = query.where(user_id: self.user_id)
+ elsif self.band_id
+ query = query.where(band_id: self.band_id)
+ else
+ raise 'invalid state in event_session_button'
+ end
+ query
+ else
+ []
+ end
+ end
+
+ def ready_display
+ self.starts_at && self.ends_at && (self.user_id || self.band_id)
+ end
def sanitize_active_admin
self.img_url = nil if self.img_url == ''
self.user_id = nil if self.user_id == ''
diff --git a/ruby/lib/jam_ruby/models/music_session_history.rb b/ruby/lib/jam_ruby/models/music_session_history.rb
index 089969434..5e3637284 100644
--- a/ruby/lib/jam_ruby/models/music_session_history.rb
+++ b/ruby/lib/jam_ruby/models/music_session_history.rb
@@ -157,6 +157,10 @@ module JamRuby
music_session && music_session.mount
end
+ def recordings
+ Recording.where(music_session_id: self.id)
+ end
+
def end_history
self.update_attribute(:session_removed_at, Time.now)
diff --git a/ruby/lib/jam_ruby/models/recording.rb b/ruby/lib/jam_ruby/models/recording.rb
index 570734180..645ccf6b8 100644
--- a/ruby/lib/jam_ruby/models/recording.rb
+++ b/ruby/lib/jam_ruby/models/recording.rb
@@ -132,7 +132,7 @@ module JamRuby
end
def has_access?(user)
- return users.exists?(user)
+ users.exists?(user)
end
# Start recording a session.
@@ -342,6 +342,9 @@ module JamRuby
save
end
+ def is_public?
+ claimed_recordings.where(is_public: true).length > 0
+ end
# meant to be used as a way to 'pluck' a claimed_recording appropriate for user.
def candidate_claimed_recording
diff --git a/ruby/lib/jam_ruby/models/user.rb b/ruby/lib/jam_ruby/models/user.rb
index 0fa73c0a5..7651d14ca 100644
--- a/ruby/lib/jam_ruby/models/user.rb
+++ b/ruby/lib/jam_ruby/models/user.rb
@@ -292,7 +292,33 @@ module JamRuby
recordings.concat(msh)
recordings.sort! {|a,b| b.created_at <=> a.created_at}.first(5)
end
-
+
+ # returns the # of new notifications
+ def new_notifications
+ search = Notification.select('id').where(target_user_id: self.id)
+ search = search.where('created_at > ?', self.notification_seen_at) if self.notification_seen_at
+ search.count
+ end
+
+ # the user can pass in a timestamp string, or the keyword 'LATEST'
+ # if LATEST is specified, we'll use the latest_notification as the timestamp
+ # if not, just use seen as-is
+ def update_notification_seen_at seen
+ new_latest_seen = nil
+ if seen == 'LATEST'
+ latest = self.latest_notification
+ new_latest_seen = latest.created_at if latest
+ else
+ new_latest_seen = seen
+ end
+
+ self.notification_seen_at = new_latest_seen
+ end
+
+ def latest_notification
+ Notification.select('created_at').where(target_user_id: id).limit(1).order('created_at DESC').first
+ end
+
def confirm_email!
self.email_confirmed = true
end
diff --git a/ruby/spec/jam_ruby/models/track_spec.rb b/ruby/spec/jam_ruby/models/track_spec.rb
index 781456720..be63c781b 100644
--- a/ruby/spec/jam_ruby/models/track_spec.rb
+++ b/ruby/spec/jam_ruby/models/track_spec.rb
@@ -116,8 +116,8 @@ describe Track do
tracks = Track.sync(connection.client_id, [{:id => track.id, :client_track_id => track.client_track_id, :sound => track.sound, :instrument_id => track.instrument_id}])
tracks.length.should == 1
found = tracks[0]
- found.id.should == track.id
- found.updated_at.should == track.updated_at
+ expect(found.id).to eq track.id
+ expect(found.updated_at.to_i).to eq track.updated_at.to_i
end
end
end
\ No newline at end of file
diff --git a/web/app/assets/javascripts/AAB_message_factory.js b/web/app/assets/javascripts/AAB_message_factory.js
index 7f11e603d..04bd51344 100644
--- a/web/app/assets/javascripts/AAB_message_factory.js
+++ b/web/app/assets/javascripts/AAB_message_factory.js
@@ -99,8 +99,10 @@
};
// Heartbeat message
- factory.heartbeat = function() {
+ factory.heartbeat = function(lastNotificationSeen, lastNotificationSeenAt) {
var data = {};
+ data.notification_seen = lastNotificationSeen;
+ data.notification_seen_at = lastNotificationSeenAt;
return client_container(msg.HEARTBEAT, route_to.SERVER, data);
};
diff --git a/web/app/assets/javascripts/acceptFriendRequestDialog.js b/web/app/assets/javascripts/acceptFriendRequestDialog.js
index 4706105ca..66a4fe2f2 100644
--- a/web/app/assets/javascripts/acceptFriendRequestDialog.js
+++ b/web/app/assets/javascripts/acceptFriendRequestDialog.js
@@ -19,6 +19,7 @@
var user = null;
var sending = false;
var friendRequest = null;
+ var sidebar = null;
function reset() {
sending = false;
@@ -52,6 +53,7 @@
rest.acceptFriendRequest(buildAcceptRequest())
.done(function() {
app.layout.closeDialog('accept-friend-request')
+ sidebar.refreshFriends();
})
.fail(function(jqXHR) {
app.notifyServerError(jqXHR, 'Unable to Accept Friend Request');
@@ -167,7 +169,7 @@
reset();
}
- function initialize() {
+ function initialize(sidebarInstance) {
var dialogBindings = {
'beforeShow' : beforeShow,
'afterHide': afterHide
@@ -176,6 +178,7 @@
app.bindDialog('accept-friend-request', dialogBindings);
+ sidebar = sidebarInstance;
$dialog = $('#accept-friend-request-dialog');
$dialogContents = $dialog.find('.dialog-inner');
$notFriendsTemplate = $('#template-friend-request-not-friends');
diff --git a/web/app/assets/javascripts/application.js b/web/app/assets/javascripts/application.js
index 68db544ad..1210d1d7a 100644
--- a/web/app/assets/javascripts/application.js
+++ b/web/app/assets/javascripts/application.js
@@ -28,6 +28,7 @@
//= require jquery.infinitescroll
//= require jquery.hoverIntent
//= require jquery.dotdotdot
+//= require jquery.pulse
//= require AAA_Log
//= require globals
//= require AAB_message_factory
diff --git a/web/app/assets/javascripts/feed_item_recording.js b/web/app/assets/javascripts/feed_item_recording.js
index 0d8793446..834aedae5 100644
--- a/web/app/assets/javascripts/feed_item_recording.js
+++ b/web/app/assets/javascripts/feed_item_recording.js
@@ -7,6 +7,7 @@
var claimedRecordingId = $parentElement.attr('data-claimed-recording-id');
var recordingId = $parentElement.attr('id');
+ var mode = $parentElement.attr('data-mode');
var $feedItem = $parentElement;
var $name = $('.name', $feedItem);
diff --git a/web/app/assets/javascripts/jam_rest.js b/web/app/assets/javascripts/jam_rest.js
index 2b19b5d44..fbdf74a2a 100644
--- a/web/app/assets/javascripts/jam_rest.js
+++ b/web/app/assets/javascripts/jam_rest.js
@@ -934,6 +934,7 @@
}
function getNotifications(options) {
+ if(!options) options = {};
var id = getId(options);
return $.ajax({
type: "GET",
diff --git a/web/app/assets/javascripts/jamkazam.js b/web/app/assets/javascripts/jamkazam.js
index ea0a39710..f00e9b02d 100644
--- a/web/app/assets/javascripts/jamkazam.js
+++ b/web/app/assets/javascripts/jamkazam.js
@@ -28,6 +28,8 @@
var lastHeartbeatFound = false;
var heartbeatAckCheckInterval = null;
var userDeferred = null;
+ var notificationLastSeenAt = undefined;
+ var notificationLastSeen = undefined;
var opts = {
inClient: true, // specify false if you want the app object but none of the client-oriented features
@@ -91,8 +93,9 @@
function _heartbeat() {
if (app.heartbeatActive) {
-
- var message = context.JK.MessageFactory.heartbeat();
+ var message = context.JK.MessageFactory.heartbeat(notificationLastSeen, notificationLastSeenAt);
+ notificationLastSeenAt = undefined;
+ notificationLastSeen = undefined;
context.JK.JamServer.send(message);
lastHeartbeatFound = false;
}
@@ -384,6 +387,28 @@
return userDeferred;
}
+ this.updateNotificationSeen = function(notificationId, notificationCreatedAt) {
+ var time = new Date(notificationCreatedAt);
+
+ if(!notificationCreatedAt) {
+ throw 'invalid value passed to updateNotificationSeen'
+ }
+
+ if(!notificationLastSeenAt) {
+ notificationLastSeenAt = notificationCreatedAt;
+ notificationLastSeen = notificationId;
+ logger.debug("updated notificationLastSeenAt with: " + notificationCreatedAt);
+ }
+ else if(time.getTime() > new Date(notificationLastSeenAt).getTime()) {
+ notificationLastSeenAt = notificationCreatedAt;
+ notificationLastSeen = notificationId;
+ logger.debug("updated notificationLastSeenAt with: " + notificationCreatedAt);
+ }
+ else {
+ logger.debug("ignored notificationLastSeenAt for: " + notificationCreatedAt);
+ }
+ }
+
this.unloadFunction = function () {
logger.debug("window.unload function called.");
diff --git a/web/app/assets/javascripts/layout.js b/web/app/assets/javascripts/layout.js
index d948945dc..555cc0d6d 100644
--- a/web/app/assets/javascripts/layout.js
+++ b/web/app/assets/javascripts/layout.js
@@ -251,7 +251,8 @@
if (!sidebarVisible) {
return;
}
- var $expandedPanelContents = $('[layout-id="' + expandedPanel + '"] [layout-panel="contents"]');
+ var $expandedPanel = $('[layout-id="' + expandedPanel + '"]');
+ var $expandedPanelContents = $expandedPanel.find('[layout-panel="contents"]');
var combinedHeaderHeight = $('[layout-panel="contents"]').length * opts.panelHeaderHeight;
var searchHeight = $('.sidebar .search').first().height();
var expanderHeight = $('[layout-sidebar-expander]').height();
@@ -259,6 +260,7 @@
$('[layout-panel="contents"]').hide();
$('[layout-panel="contents"]').css({"height": "1px"});
$expandedPanelContents.show();
+ $expandedPanel.triggerHandler('open')
$expandedPanelContents.animate({"height": expandedPanelHeight + "px"}, opts.animationDuration);
}
@@ -425,6 +427,7 @@
unstackDialogs($overlay);
$dialog.hide();
dialogEvent(dialog, 'afterHide');
+ $(me).triggerHandler('dialog_closed', {dialogCount: openDialogs.length})
}
function screenEvent(screen, evtName, data) {
@@ -526,6 +529,29 @@
}
}
+ function isDialogShowing() {
+ return openDialogs.length > 0;
+ }
+
+ function currentDialog() {
+ if(openDialogs.length == 0) return null;
+
+ return openDialogs[openDialogs.length - 1];
+ }
+
+ // payload is a notification event from websocket gateway
+ function dialogObscuredNotification(payload) {
+ var openDialog = currentDialog();
+ if(!openDialog) return false;
+
+ if(typeof openDialog.handledNotification === 'function') {
+ return !openDialog.handledNotification(payload);
+ }
+ else {
+ return true;
+ }
+ }
+
/**
* Responsible for keeping N dialogs in correct stacked order,
* also moves the .dialog-overlay such that it hides/obscures all dialogs except the highest one
@@ -849,6 +875,14 @@
showDialog(dialog, options);
};
+ this.dialogObscuredNotification = function() {
+ return dialogObscuredNotification();
+ }
+
+ this.isDialogShowing = function() {
+ return isDialogShowing();
+ }
+
this.close = function (evt) {
close(evt);
};
diff --git a/web/app/assets/javascripts/notificationPanel.js b/web/app/assets/javascripts/notificationPanel.js
new file mode 100644
index 000000000..90e9d5d5c
--- /dev/null
+++ b/web/app/assets/javascripts/notificationPanel.js
@@ -0,0 +1,905 @@
+(function(context,$) {
+
+ "use strict";
+
+ context.JK = context.JK || {};
+ context.JK.NotificationPanel = function(app) {
+ var logger = context.JK.logger;
+ var friends = [];
+ var rest = context.JK.Rest();
+ var missedNotificationsWhileAway = false;
+ var $panel = null;
+ var $expanded = null;
+ var $contents = null;
+ var $count = null;
+ var $list = null;
+ var $notificationTemplate = null;
+ var sidebar = null;
+ var darkenedColor = '#0D7B89';
+ var highlightedColor = 'white'
+ var textMessageDialog = null;
+ var queuedNotification = null;
+ var queuedNotificationCreatedAt = null;
+
+ function isNotificationsPanelVisible() {
+ return $contents.is(':visible')
+ }
+
+ function incrementNotificationCount() {
+ var count = parseInt($count.text());
+ setCount(count + 1);
+ }
+
+ // set the element to white, and pulse it down to the un-highlighted value 2x, then set
+ function pulseToDark() {
+ logger.debug("pulsing notification badge")
+ lowlightCount();
+ $count.pulse({'background-color' : highlightedColor}, {pulses: 2}, function() {
+ $count.removeAttr('style')
+ setCount(0);
+ })
+ }
+
+ function setCount(count) {
+ $count.text(count);
+ }
+
+ function lowlightCount() {
+ $count.removeClass('highlighted');
+ }
+
+ function highlightCount() {
+ $count.addClass('highlighted');
+ }
+
+ function queueNotificationSeen(notificationId, notificationCreatedAt) {
+
+ var time = new Date(notificationCreatedAt);
+
+ if(!notificationCreatedAt) {
+ throw 'invalid value passed to queuedNotificationCreatedAt'
+ }
+
+ if(!queuedNotificationCreatedAt) {
+ queuedNotification = notificationId;
+ queuedNotificationCreatedAt = notificationCreatedAt;
+ logger.debug("updated queuedNotificationCreatedAt with: " + notificationCreatedAt);
+ }
+ else if(time.getTime() > new Date(queuedNotificationCreatedAt).getTime()) {
+ queuedNotification = notificationId;
+ queuedNotificationCreatedAt = notificationCreatedAt;
+ logger.debug("updated queuedNotificationCreatedAt with: " + notificationCreatedAt);
+ }
+ else {
+ logger.debug("ignored queuedNotificationCreatedAt for: " + notificationCreatedAt);
+ }
+ }
+
+ function onNotificationOccurred(payload) {
+ if(userCanSeeNotifications(payload)) {
+ app.updateNotificationSeen(payload.notification_id, payload.created_at);
+ }
+ else {
+ queueNotificationSeen(payload.notification_id, payload.created_at);
+ highlightCount();
+ incrementNotificationCount();
+ missedNotificationsWhileAway = true;
+ }
+ }
+
+ function userCameBack() {
+ if(isNotificationsPanelVisible()) {
+ if(missedNotificationsWhileAway) {
+ // catch user's eye, then put count to 0
+ pulseToDark();
+ if(queuedNotificationCreatedAt) {
+ app.updateNotificationSeen(queuedNotification, queuedNotificationCreatedAt);
+ }
+ }
+ }
+
+ queuedNotification = null;
+ queuedNotificationCreatedAt = null;
+ missedNotificationsWhileAway = false;
+ }
+
+ function opened() {
+ queuedNotification = null;
+ queuedNotificationCreatedAt = null;
+ rest.updateUser({notification_seen_at: 'LATEST'})
+ .done(function(response) {
+ lowlightCount();
+ setCount(0);
+ })
+ .fail(app.ajaxError)
+ }
+
+ function windowBlurred() {
+
+ }
+
+ function events() {
+ $(app.layout).on('dialog_closed', function(e, data) {if(data.dialogCount == 0) userCameBack(); });
+ $(window).focus(userCameBack);
+ $(window).blur(windowBlurred);
+ app.user()
+ .done(function(user) {
+ setCount(user.new_notifications);
+ if(user.new_notifications > 0) {
+ highlightCount();
+ }
+ });
+
+ $panel.on('open', opened);
+
+ // friend notifications
+ registerFriendRequest();
+ registerFriendRequestAccepted();
+ registerNewUserFollower();
+ registerNewBandFollower();
+
+ // session notifications
+ registerSessionInvitation();
+ registerSessionEnded();
+ registerJoinRequest();
+ registerJoinRequestApproved();
+ registerJoinRequestRejected();
+ registerMusicianSessionJoin();
+ registerBandSessionJoin();
+
+ // recording notifications
+ registerMusicianRecordingSaved();
+ registerBandRecordingSaved();
+ registerRecordingMasterMixComplete();
+
+ // band notifications
+ registerBandInvitation();
+ registerBandInvitationAccepted();
+
+ // register text messages
+ registerTextMessage();
+ }
+
+ function populate() {
+ // retrieve pending notifications for this user
+ rest.getNotifications()
+ .done(function(response) {
+ updateNotificationList(response);
+ })
+ .fail(app.ajaxError)
+ }
+
+ function updateNotificationList(response) {
+ $list.empty();
+
+ $.each(response, function(index, val) {
+
+ if(val.description == 'TEXT_MESSAGE') {
+ val.formatted_msg = textMessageDialog.formatTextMessage(val.message.substring(0, 200), val.source_user_id, val.source_user.name, val.message.length > 200).html();
+ }
+
+ // fill in template for Connect pre-click
+ var template = $notificationTemplate.html();
+ var notificationHtml = context.JK.fillTemplate(template, {
+ notificationId: val.notification_id,
+ sessionId: val.session_id,
+ avatar_url: context.JK.resolveAvatarUrl(val.photo_url),
+ text: val.formatted_msg,
+ date: $.timeago(val.created_at)
+ });
+
+ $list.append(notificationHtml);
+
+ // val.description contains the notification record's description value from the DB (i.e., type)
+ initializeActions(val, val.description);
+ });
+ }
+
+
+ function initializeActions(payload, type) {
+
+ var $notification = $('li[notification-id=' + payload.notification_id + ']');
+ var $btnNotificationAction = '#btn-notification-action';
+
+ // wire up "x" button to delete notification
+ $notification.find('#img-delete-notification').click(deleteNotificationHandler);
+
+ // customize action buttons based on notification type
+ if (type === context.JK.MessageType.FRIEND_REQUEST) {
+ var $action_btn = $notification.find($btnNotificationAction);
+ $action_btn.text('ACCEPT');
+ $action_btn.click(function() {
+ acceptFriendRequest(payload);
+ });
+ }
+
+ else if (type === context.JK.MessageType.FRIEND_REQUEST_ACCEPTED) {
+ $notification.find('#div-actions').hide();
+ }
+
+ else if (type === context.JK.MessageType.NEW_USER_FOLLOWER || type === context.JK.MessageType.NEW_BAND_FOLLOWER) {
+ $notification.find('#div-actions').hide();
+ }
+
+ else if (type === context.JK.MessageType.SESSION_INVITATION) {
+ var $action_btn = $notification.find($btnNotificationAction);
+ $action_btn.text('JOIN');
+ $action_btn.click(function() {
+ openTerms(payload);
+ });
+ }
+
+ else if (type === context.JK.MessageType.JOIN_REQUEST) {
+ var $action_btn = $notification.find($btnNotificationAction);
+ $action_btn.text('APPROVE');
+ $action_btn.click(function() {
+ approveJoinRequest(payload);
+ });
+ }
+
+ else if (type === context.JK.MessageType.JOIN_REQUEST_APPROVED) {
+ var $action_btn = $notification.find($btnNotificationAction);
+ $action_btn.text('JOIN');
+ $action_btn.click(function() {
+ openTerms(payload);
+ });
+ }
+
+ else if (type === context.JK.MessageType.JOIN_REQUEST_REJECTED) {
+ $notification.find('#div-actions').hide();
+ }
+
+ else if (type === context.JK.MessageType.MUSICIAN_SESSION_JOIN || type === context.JK.MessageType.BAND_SESSION_JOIN) {
+
+ var actionText = '';
+ var callback;
+ if (context.JK.currentUserMusician) {
+ // user is MUSICIAN; musician_access = TRUE
+ if (payload.musician_access) {
+ actionText = "JOIN";
+ callback = joinSession;
+ }
+ // user is MUSICIAN; fan_access = TRUE
+ else if (payload.fan_access) {
+ actionText = "LISTEN";
+ callback = listenToSession;
+ }
+ }
+ else {
+ // user is FAN; fan_access = TRUE
+ if (payload.fan_access) {
+ actionText = "LISTEN";
+ callback = listenToSession;
+ }
+ }
+
+ var $action_btn = $notification.find($btnNotificationAction);
+ $action_btn.text(actionText);
+ $action_btn.click(function() {
+ callback(payload);
+ });
+ }
+
+ else if (type === context.JK.MessageType.MUSICIAN_RECORDING_SAVED || type === context.JK.MessageType.BAND_RECORDING_SAVED) {
+ var $action_btn = $notification.find($btnNotificationAction);
+ $action_btn.text('LISTEN');
+ $action_btn.click(function() {
+ listenToRecording(payload);
+ });
+ }
+
+ else if (type === context.JK.MessageType.RECORDING_MASTER_MIX_COMPLETE) {
+ $notification.find('#div-actions').hide();
+ context.jamClient.OnDownloadAvailable(); // poke backend, letting it know a download is available
+ }
+
+ else if (type === context.JK.MessageType.BAND_INVITATION) {
+ var $action_btn = $notification.find($btnNotificationAction);
+ $action_btn.text('ACCEPT');
+ $action_btn.click(function() {
+ acceptBandInvitation(payload);
+ });
+ }
+ else if (type === context.JK.MessageType.BAND_INVITATION_ACCEPTED) {
+ $notification.find('#div-actions').hide();
+ }
+ else if (type === context.JK.MessageType.TEXT_MESSAGE) {
+ var $action_btn = $notification.find($btnNotificationAction);
+ $action_btn.text('REPLY');
+ $action_btn.click(function() {
+ var userId = $notification.find('.more-text-available').attr('data-sender-id');
+ app.layout.showDialog('text-message', { d1: userId });
+ });
+
+ var moreTextLink = $notification.find('.more-text-available');
+ var textMessage = $notification.find('.text-message');
+ var clipped_msg = textMessage.attr('data-is-clipped') === 'true';
+
+ if(clipped_msg) {
+ moreTextLink.text('more').show();
+ moreTextLink.click(function(e) {
+ var userId = $(this).attr('data-sender-id');
+
+ return false;
+ });
+ }
+ else {
+ moreTextLink.hide();
+ }
+ }
+ }
+
+ function acceptBandInvitation(args) {
+ rest.updateBandInvitation(
+ args.band_id,
+ args.band_invitation_id,
+ true
+ ).done(function(response) {
+ deleteNotification(args.notification_id); // delete notification corresponding to this friend request
+ }).error(app.ajaxError);
+ }
+
+
+ function deleteNotification(notificationId) {
+ var url = "/api/users/" + context.JK.currentUserId + "/notifications/" + notificationId;
+ $.ajax({
+ type: "DELETE",
+ dataType: "json",
+ contentType: 'application/json',
+ url: url,
+ processData: false,
+ success: function(response) {
+ $('li[notification-id=' + notificationId + ']').hide();
+ //decrementNotificationCount();
+ },
+ error: app.ajaxError
+ });
+ }
+
+
+ function listenToSession(args) {
+ deleteNotification(args.notification_id);
+ context.JK.popExternalLink('/sessions/' + args.session_id);
+ }
+
+ /*********** TODO: THE NEXT 3 FUNCTIONS ARE COPIED FROM sessionList.js. REFACTOR TO COMMON PLACE. *************/
+ function joinSession(args) {
+ // NOTE: invited musicians get their own notification, so no need to check if user has invitation here
+ // like other places because an invited user would never get this notification
+ if (args.musician_access) {
+ if (args.approval_required) {
+ openAlert(args.session_id);
+ }
+ else {
+ openTerms(args);
+ }
+ }
+ deleteNotification(args.notification_id);
+ }
+
+
+ function registerJoinRequestApproved() {
+ context.JK.JamServer.registerMessageCallback(context.JK.MessageType.JOIN_REQUEST_APPROVED, function(header, payload) {
+ logger.debug("Handling JOIN_REQUEST_APPROVED msg " + JSON.stringify(payload));
+
+ handleNotification(payload, header.type);
+
+ app.notify({
+ "title": "Join Request Approved",
+ "text": payload.msg,
+ "icon_url": context.JK.resolveAvatarUrl(payload.photo_url)
+ }, {
+ "ok_text": "JOIN SESSION",
+ "ok_callback": openTerms,
+ "ok_callback_args": { "session_id": payload.session_id, "notification_id": payload.notification_id }
+ });
+ });
+ }
+
+ function registerJoinRequestRejected() {
+ context.JK.JamServer.registerMessageCallback(context.JK.MessageType.JOIN_REQUEST_REJECTED, function(header, payload) {
+ logger.debug("Handling JOIN_REQUEST_REJECTED msg " + JSON.stringify(payload));
+
+ handleNotification(payload, header.type);
+
+ app.notify({
+ "title": "Join Request Rejected",
+ "text": payload.msg,
+ "icon_url": context.JK.resolveAvatarUrl(payload.photo_url)
+ });
+ });
+ }
+
+
+ function registerJoinRequest() {
+ context.JK.JamServer.registerMessageCallback(context.JK.MessageType.JOIN_REQUEST, function(header, payload) {
+ logger.debug("Handling JOIN_REQUEST msg " + JSON.stringify(payload));
+
+ handleNotification(payload, header.type);
+
+ app.notify({
+ "title": "New Join Request",
+ "text": payload.msg,
+ "icon_url": context.JK.resolveAvatarUrl(payload.photo_url)
+ }, {
+ "ok_text": "APPROVE",
+ "ok_callback": approveJoinRequest,
+ "ok_callback_args": { "join_request_id": payload.join_request_id, "notification_id": payload.notification_id },
+ "cancel_text": "REJECT",
+ "cancel_callback": rejectJoinRequest,
+ "cancel_callback_args": { "join_request_id": payload.join_request_id, "notification_id": payload.notification_id }
+ });
+ });
+ }
+
+
+ function registerFriendRequestAccepted() {
+ context.JK.JamServer.registerMessageCallback(context.JK.MessageType.FRIEND_REQUEST_ACCEPTED, function(header, payload) {
+ logger.debug("Handling FRIEND_REQUEST_ACCEPTED msg " + JSON.stringify(payload));
+
+ handleNotification(payload, header.type);
+
+ sidebar.refreshFriends();
+
+ app.notify({
+ "title": "Friend Request Accepted",
+ "text": payload.msg,
+ "icon_url": context.JK.resolveAvatarUrl(payload.photo_url)
+ });
+ });
+ }
+
+ function registerNewUserFollower() {
+
+ context.JK.JamServer.registerMessageCallback(context.JK.MessageType.NEW_USER_FOLLOWER, function(header, payload) {
+ logger.debug("Handling NEW_USER_FOLLOWER msg " + JSON.stringify(payload));
+
+ handleNotification(payload, header.type);
+
+ app.notify({
+ "title": "New Follower",
+ "text": payload.msg,
+ "icon_url": context.JK.resolveAvatarUrl(payload.photo_url)
+ });
+ });
+ }
+
+
+ function registerNewBandFollower() {
+
+ context.JK.JamServer.registerMessageCallback(context.JK.MessageType.NEW_BAND_FOLLOWER, function(header, payload) {
+ logger.debug("Handling NEW_BAND_FOLLOWER msg " + JSON.stringify(payload));
+
+ handleNotification(payload, header.type);
+
+ app.notify({
+ "title": "New Band Follower",
+ "text": payload.msg,
+ "icon_url": context.JK.resolveAvatarUrl(payload.photo_url)
+ });
+ });
+ }
+
+
+ function registerFriendRequest() {
+ context.JK.JamServer.registerMessageCallback(context.JK.MessageType.FRIEND_REQUEST, function(header, payload) {
+ logger.debug("Handling FRIEND_REQUEST msg " + JSON.stringify(payload));
+
+ handleNotification(payload, header.type);
+
+ app.notify({
+ "title": "New Friend Request",
+ "text": payload.msg,
+ "icon_url": context.JK.resolveAvatarUrl(payload.photo_url)
+ }, {
+ "ok_text": "ACCEPT",
+ "ok_callback": acceptFriendRequest,
+ "ok_callback_args": { "friend_request_id": payload.friend_request_id, "notification_id": payload.notification_id }
+ });
+ });
+ }
+
+ function acceptFriendRequest(args) {
+
+ rest.acceptFriendRequest({
+ status: 'accept',
+ friend_request_id: args.friend_request_id
+ }).done(function(response) {
+ deleteNotification(args.notification_id); // delete notification corresponding to this friend request
+ sidebar.refreshFriends(); // refresh friends panel when request is accepted
+ }).error(app.ajaxError);
+ }
+
+ function registerSessionEnded() {
+ // TODO: this should clean up all notifications related to this session
+ context.JK.JamServer.registerMessageCallback(context.JK.MessageType.SESSION_ENDED, function(header, payload) {
+ logger.debug("Handling SESSION_ENDED msg " + JSON.stringify(payload));
+ deleteSessionNotifications(payload.session_id);
+ });
+ }
+
+ // remove all notifications for this session
+ function deleteSessionNotifications(sessionId) {
+ $('li[session-id=' + sessionId + ']').hide();
+ //decrementNotificationCount();
+ }
+
+ function registerSessionInvitation() {
+ context.JK.JamServer.registerMessageCallback(context.JK.MessageType.SESSION_INVITATION, function(header, payload) {
+ logger.debug("Handling SESSION_INVITATION msg " + JSON.stringify(payload));
+
+ handleNotification(payload, header.type);
+
+ var participants = [];
+ rest.getSession(payload.session_id).done(function(response) {
+ $.each(response.participants, function(index, val) {
+ participants.push({"photo_url": context.JK.resolveAvatarUrl(val.user.photo_url), "name": val.user.name});
+ });
+
+ var participantHtml = "You have been invited to join a session with:
";
+ participantHtml += "
";
+
+ $.each(participants, function(index, val) {
+ if (index < 4) {
+ participantHtml += " + ") | " + val.name + " |
";
+ }
+ });
+
+ participantHtml += "
";
+
+ app.notify({
+ "title": "Session Invitation",
+ "text": participantHtml
+ }, {
+ "ok_text": "JOIN SESSION",
+ "ok_callback": openTerms,
+ "ok_callback_args": { "session_id": payload.session_id, "notification_id": payload.notification_id }
+ });
+ }).error(app.ajaxError);
+
+ });
+ }
+
+
+ function registerMusicianSessionJoin() {
+ context.JK.JamServer.registerMessageCallback(context.JK.MessageType.MUSICIAN_SESSION_JOIN, function(header, payload) {
+ logger.debug("Handling MUSICIAN_SESSION_JOIN msg " + JSON.stringify(payload));
+
+ var okText = '';
+ var showNotification = false;
+ var callback;
+ if (context.JK.currentUserMusician) {
+ // user is MUSICIAN; musician_access = TRUE
+ if (payload.musician_access) {
+ showNotification = true;
+ okText = "JOIN";
+ callback = joinSession;
+ }
+ // user is MUSICIAN; fan_access = TRUE
+ else if (payload.fan_access) {
+ showNotification = true;
+ okText = "LISTEN";
+ callback = listenToSession;
+ }
+ }
+ else {
+ // user is FAN; fan_access = TRUE
+ if (payload.fan_access) {
+ showNotification = true;
+ okText = "LISTEN";
+ callback = listenToSession;
+ }
+ }
+
+ if (showNotification) {
+ handleNotification(payload, header.type);
+
+ app.notify({
+ "title": "Musician Joined Session",
+ "text": payload.msg,
+ "icon_url": context.JK.resolveAvatarUrl(payload.photo_url)
+ }, {
+ "ok_text": okText,
+ "ok_callback": callback,
+ "ok_callback_args": {
+ "session_id": payload.session_id,
+ "fan_access": payload.fan_access,
+ "musician_access": payload.musician_access,
+ "approval_required": payload.approval_required,
+ "notification_id": payload.notification_id
+ }
+ }
+ );
+ }
+ });
+ }
+
+
+ function registerBandSessionJoin() {
+ context.JK.JamServer.registerMessageCallback(context.JK.MessageType.BAND_SESSION_JOIN, function(header, payload) {
+ logger.debug("Handling BAND_SESSION_JOIN msg " + JSON.stringify(payload));
+
+ var okText = '';
+ var showNotification = false;
+ var callback;
+ if (context.JK.currentUserMusician) {
+ // user is MUSICIAN; musician_access = TRUE
+ if (payload.musician_access) {
+ showNotification = true;
+ okText = "JOIN";
+ callback = joinSession;
+ }
+ // user is MUSICIAN; fan_access = TRUE
+ else if (payload.fan_access) {
+ showNotification = true;
+ okText = "LISTEN";
+ callback = listenToSession;
+ }
+ }
+ else {
+ // user is FAN; fan_access = TRUE
+ if (payload.fan_access) {
+ showNotification = true;
+ okText = "LISTEN";
+ callback = listenToSession;
+ }
+ }
+
+ if (showNotification) {
+ handleNotification(payload, header.type);
+
+ app.notify({
+ "title": "Band Joined Session",
+ "text": payload.msg,
+ "icon_url": context.JK.resolveAvatarUrl(payload.photo_url)
+ }, {
+ "ok_text": "LISTEN",
+ "ok_callback": callback,
+ "ok_callback_args": {
+ "session_id": payload.session_id,
+ "fan_access": payload.fan_access,
+ "musician_access": payload.musician_access,
+ "approval_required": payload.approval_required,
+ "notification_id": payload.notification_id
+ }
+ }
+ );
+ }
+ });
+ }
+
+
+ function registerMusicianRecordingSaved() {
+ context.JK.JamServer.registerMessageCallback(context.JK.MessageType.MUSICIAN_RECORDING_SAVED, function(header, payload) {
+ logger.debug("Handling MUSICIAN_RECORDING_SAVED msg " + JSON.stringify(payload));
+
+ handleNotification(payload, header.type);
+
+ app.notify({
+ "title": "Musician Recording Saved",
+ "text": payload.msg,
+ "icon_url": context.JK.resolveAvatarUrl(payload.photo_url)
+ }, {
+ "ok_text": "LISTEN",
+ "ok_callback": listenToRecording,
+ "ok_callback_args": {
+ "recording_id": payload.recording_id,
+ "notification_id": payload.notification_id
+ }
+ });
+ });
+ }
+
+ function registerBandRecordingSaved() {
+ context.JK.JamServer.registerMessageCallback(context.JK.MessageType.BAND_RECORDING_SAVED, function(header, payload) {
+ logger.debug("Handling BAND_RECORDING_SAVED msg " + JSON.stringify(payload));
+
+ handleNotification(payload, header.type);
+
+ app.notify({
+ "title": "Band Recording Saved",
+ "text": payload.msg,
+ "icon_url": context.JK.resolveAvatarUrl(payload.photo_url)
+ }, {
+ "ok_text": "LISTEN",
+ "ok_callback": listenToRecording,
+ "ok_callback_args": {
+ "recording_id": payload.recording_id,
+ "notification_id": payload.notification_id
+ }
+ });
+ });
+ }
+
+
+ function registerRecordingMasterMixComplete() {
+ context.JK.JamServer.registerMessageCallback(context.JK.MessageType.RECORDING_MASTER_MIX_COMPLETE, function(header, payload) {
+ logger.debug("Handling RECORDING_MASTER_MIX_COMPLETE msg " + JSON.stringify(payload));
+
+ handleNotification(payload, header.type);
+
+ app.notify({
+ "title": "Recording Master Mix Complete",
+ "text": payload.msg,
+ "icon_url": context.JK.resolveAvatarUrl(payload.photo_url)
+ }, {
+ "ok_text": "SHARE",
+ "ok_callback": shareRecording,
+ "ok_callback_args": {
+ "recording_id": payload.recording_id
+ }
+ });
+ });
+ }
+
+ function shareRecording(args) {
+ var recordingId = args.recording_id;
+ }
+
+ function registerBandInvitation() {
+ context.JK.JamServer.registerMessageCallback(context.JK.MessageType.BAND_INVITATION, function(header, payload) {
+ logger.debug("Handling BAND_INVITATION msg " + JSON.stringify(payload));
+
+ handleNotification(payload, header.type);
+
+ app.notify({
+ "title": "Band Invitation",
+ "text": payload.msg,
+ "icon_url": context.JK.resolveAvatarUrl(payload.photo_url)
+ }, {
+ "ok_text": "ACCEPT",
+ "ok_callback": acceptBandInvitation,
+ "ok_callback_args": {
+ "band_invitation_id": payload.band_invitation_id,
+ "band_id": payload.band_id,
+ "notification_id": payload.notification_id
+ }
+ });
+ });
+ }
+
+
+ function registerTextMessage() {
+ context.JK.JamServer.registerMessageCallback(context.JK.MessageType.TEXT_MESSAGE, function(header, payload) {
+ logger.debug("Handling TEXT_MESSAGE msg " + JSON.stringify(payload));
+
+ textMessageDialog.messageReceived(payload);
+
+ handleNotification(payload, header.type);
+ });
+ }
+
+ function registerBandInvitationAccepted() {
+ context.JK.JamServer.registerMessageCallback(context.JK.MessageType.BAND_INVITATION_ACCEPTED, function(header, payload) {
+ logger.debug("Handling BAND_INVITATION_ACCEPTED msg " + JSON.stringify(payload));
+
+ handleNotification(payload, header.type);
+
+ app.notify({
+ "title": "Band Invitation Accepted",
+ "text": payload.msg,
+ "icon_url": context.JK.resolveAvatarUrl(payload.photo_url)
+ });
+ });
+ }
+
+
+ // one important limitation; if the user is focused on an iframe, this will be false
+ // however, if they are doing something with Facebook or the photo picker, this may actually still be desirable
+ function userCanSeeNotifications(payload) {
+ return document.hasFocus() && !app.layout.dialogObscuredNotification(payload);
+ }
+
+ // default handler for incoming notification
+ function handleNotification(payload, type) {
+
+ // on a load of notifications, it is possible to load a very new notification,
+ // and get a websocket notification right after for that same notification,
+ // so we need to protect against such duplicates
+ if($list.find('li[notification-id="' + payload.notification_id + '"]').length > 0) {
+ return false;
+ }
+
+ // add notification to sidebar
+ var template = $notificationTemplate.html();
+ var notificationHtml = context.JK.fillTemplate(template, {
+ notificationId: payload.notification_id,
+ sessionId: payload.session_id,
+ avatar_url: context.JK.resolveAvatarUrl(payload.photo_url),
+ text: payload.msg instanceof jQuery ? payload.msg.html() : payload.msg ,
+ date: $.timeago(payload.created_at)
+ });
+
+ $list.prepend(notificationHtml);
+
+ onNotificationOccurred(payload);
+
+ initializeActions(payload, type);
+
+ return true;
+ }
+
+
+ function onCreateJoinRequest(sessionId) {
+ var joinRequest = {};
+ joinRequest.music_session = sessionId;
+ joinRequest.user = context.JK.currentUserId;
+ rest.createJoinRequest(joinRequest)
+ .done(function(response) {
+
+ }).error(context.JK.app.ajaxError);
+
+ context.JK.app.layout.closeDialog('alert');
+ }
+
+ function approveJoinRequest(args) {
+ rest.updateJoinRequest(args.join_request_id, true)
+ .done(function(response) {
+ deleteNotification(args.notification_id);
+ }).error(app.ajaxError);
+ }
+
+ function rejectJoinRequest(args) {
+ rest.updateJoinRequest(args.join_request_id, false)
+ .done(function(response) {
+ deleteNotification(args.notification_id);
+ }).error(app.ajaxError);
+ }
+
+ function openTerms(args) {
+ var termsDialog = new context.JK.TermsDialog(app, args, onTermsAccepted);
+ termsDialog.initialize();
+ app.layout.showDialog('terms');
+ }
+
+ function onTermsAccepted(args) {
+ deleteNotification(args.notification_id);
+ context.location = '/client#/session/' + args.session_id;
+ }
+
+
+ function openAlert(sessionId) {
+ var alertDialog = new context.JK.AlertDialog(context.JK.app, "YES",
+ "You must be approved to join this session. Would you like to send a request to join?",
+ sessionId, onCreateJoinRequest);
+
+ alertDialog.initialize();
+ context.JK.app.layout.showDialog('alert');
+ }
+
+ function listenToRecording(args) {
+ deleteNotification(args.notification_id);
+ context.JK.popExternalLink('/recordings/' + args.recording_id);
+ }
+
+ function deleteNotificationHandler(evt) {
+ evt.stopPropagation();
+ var notificationId = $(this).attr('notification-id');
+ deleteNotification(notificationId);
+ }
+
+ function initialize(sidebarInstance, textMessageDialogInstance) {
+ sidebar = sidebarInstance;
+ textMessageDialog = textMessageDialogInstance;
+ $panel = $('[layout-id="panelNotifications"]');
+ $expanded = $panel.find('.panel.expanded');
+ $contents = $panel.find('.panelcontents');
+ $count = $panel.find('#sidebar-notification-count');
+ $list = $panel.find('#sidebar-notification-list');
+ $notificationTemplate = $('#template-notification-panel');
+ if($panel.length == 0) throw "notifications panel not found"
+ if($expanded.length == 0) throw "notifications expanded content not found"
+ if($contents.length == 0) throw "notifications contents not found"
+ if($count.length == 0) throw "notifications count element not found";
+ if($list.length == 0) throw "notification list element not found";
+ if($notificationTemplate.length == 0) throw "notification template not found";
+
+ events();
+
+ populate();
+ };
+
+ this.initialize = initialize;
+ this.onNotificationOccurred = onNotificationOccurred;
+ };
+})(window, jQuery);
diff --git a/web/app/assets/javascripts/sidebar.js b/web/app/assets/javascripts/sidebar.js
index 6e4f49831..bac8a80cf 100644
--- a/web/app/assets/javascripts/sidebar.js
+++ b/web/app/assets/javascripts/sidebar.js
@@ -9,6 +9,8 @@
var rest = context.JK.Rest();
var invitationDialog = null;
var textMessageDialog = null;
+ var notificationPanel = null;
+ var me = null;
function initializeSearchPanel() {
$('#search_text_type').change(function() {
@@ -22,48 +24,20 @@
emptySearchResults();
}
+ function refreshFriends() {
+ rest.getFriends().
+ done(function(response) {
+ friends = response;
+ updateFriendList(response);
+ // set friend count
+ $('#sidebar-friend-count').html(response.length);
+ })
+ }
+
function initializeFriendsPanel() {
- /////////////////////////////////////////////////////////////
- // THIS IS TEST CODE TO GENERATE BACK TO BACK NOTIFICATIONS
- // app.notify({
- // "title": "TEST 1",
- // "text": "Test 1",
- // "icon_url": context.JK.resolveAvatarUrl("")
- // });
-
- // app.notify({
- // "title": "TEST 2",
- // "text": "Test 2",
- // "icon_url": context.JK.resolveAvatarUrl("")
- // });
-
- // app.notify({
- // "title": "TEST 3",
- // "text": "Test 3",
- // "icon_url": context.JK.resolveAvatarUrl("")
- // });
- /////////////////////////////////////////////////////////////
-
$('#sidebar-search-header').hide();
-
- var url = "/api/users/" + context.JK.currentUserId + "/friends"
- $.ajax({
- type: "GET",
- dataType: "json",
- contentType: 'application/json',
- url: url,
- processData: false,
- success: function(response) {
- friends = response;
- updateFriendList(response);
-
- // set friend count
- $('#sidebar-friend-count').html(response.length);
- },
- error: app.ajaxError
- });
-
+ refreshFriends();
return false;
}
@@ -116,204 +90,8 @@
}
function initializeNotificationsPanel() {
- // retrieve pending notifications for this user
- var url = "/api/users/" + context.JK.currentUserId + "/notifications"
- $.ajax({
- type: "GET",
- dataType: "json",
- contentType: 'application/json',
- url: url,
- processData: false,
- success: function(response) {
-
- updateNotificationList(response);
-
- // set notification count
- $('#sidebar-notification-count').html(response.length);
- },
- error: app.ajaxError
- });
- }
-
- function updateNotificationList(response) {
- $('#sidebar-notification-list').empty();
-
- $.each(response, function(index, val) {
-
- if(val.description == 'TEXT_MESSAGE') {
- val.formatted_msg = textMessageDialog.formatTextMessage(val.message.substring(0, 200), val.source_user_id, val.source_user.name, val.message.length > 200).html();
- }
-
- // fill in template for Connect pre-click
- var template = $('#template-notification-panel').html();
- var notificationHtml = context.JK.fillTemplate(template, {
- notificationId: val.notification_id,
- sessionId: val.session_id,
- avatar_url: context.JK.resolveAvatarUrl(val.photo_url),
- text: val.formatted_msg,
- date: $.timeago(val.created_at)
- });
-
- $('#sidebar-notification-list').append(notificationHtml);
-
- // val.description contains the notification record's description value from the DB (i.e., type)
- initializeActions(val, val.description);
- });
- }
-
- function initializeActions(payload, type) {
-
- var $notification = $('li[notification-id=' + payload.notification_id + ']');
- var $btnNotificationAction = '#btn-notification-action';
-
- // wire up "x" button to delete notification
- $notification.find('#img-delete-notification').click(deleteNotificationHandler);
-
- // customize action buttons based on notification type
- if (type === context.JK.MessageType.FRIEND_REQUEST) {
- var $action_btn = $notification.find($btnNotificationAction);
- $action_btn.text('ACCEPT');
- $action_btn.click(function() {
- acceptFriendRequest(payload);
- });
- }
-
- else if (type === context.JK.MessageType.FRIEND_REQUEST_ACCEPTED) {
- $notification.find('#div-actions').hide();
- }
-
- else if (type === context.JK.MessageType.NEW_USER_FOLLOWER || type === context.JK.MessageType.NEW_BAND_FOLLOWER) {
- $notification.find('#div-actions').hide();
- }
-
- else if (type === context.JK.MessageType.SESSION_INVITATION) {
- var $action_btn = $notification.find($btnNotificationAction);
- $action_btn.text('JOIN');
- $action_btn.click(function() {
- openTerms(payload);
- });
- }
-
- else if (type === context.JK.MessageType.JOIN_REQUEST) {
- var $action_btn = $notification.find($btnNotificationAction);
- $action_btn.text('APPROVE');
- $action_btn.click(function() {
- approveJoinRequest(payload);
- });
- }
-
- else if (type === context.JK.MessageType.JOIN_REQUEST_APPROVED) {
- var $action_btn = $notification.find($btnNotificationAction);
- $action_btn.text('JOIN');
- $action_btn.click(function() {
- openTerms(payload);
- });
- }
-
- else if (type === context.JK.MessageType.JOIN_REQUEST_REJECTED) {
- $notification.find('#div-actions').hide();
- }
-
- else if (type === context.JK.MessageType.MUSICIAN_SESSION_JOIN || type === context.JK.MessageType.BAND_SESSION_JOIN) {
-
- var actionText = '';
- var callback;
- if (context.JK.currentUserMusician) {
- // user is MUSICIAN; musician_access = TRUE
- if (payload.musician_access) {
- actionText = "JOIN";
- callback = joinSession;
- }
- // user is MUSICIAN; fan_access = TRUE
- else if (payload.fan_access) {
- actionText = "LISTEN";
- callback = listenToSession;
- }
- }
- else {
- // user is FAN; fan_access = TRUE
- if (payload.fan_access) {
- actionText = "LISTEN";
- callback = listenToSession;
- }
- }
-
- var $action_btn = $notification.find($btnNotificationAction);
- $action_btn.text(actionText);
- $action_btn.click(function() {
- callback(payload);
- });
- }
-
- else if (type === context.JK.MessageType.MUSICIAN_RECORDING_SAVED || type === context.JK.MessageType.BAND_RECORDING_SAVED) {
- var $action_btn = $notification.find($btnNotificationAction);
- $action_btn.text('LISTEN');
- $action_btn.click(function() {
- listenToRecording(payload);
- });
- }
-
- else if (type === context.JK.MessageType.RECORDING_MASTER_MIX_COMPLETE) {
- $notification.find('#div-actions').hide();
- context.jamClient.OnDownloadAvailable(); // poke backend, letting it know a download is available
- }
-
- else if (type === context.JK.MessageType.BAND_INVITATION) {
- var $action_btn = $notification.find($btnNotificationAction);
- $action_btn.text('ACCEPT');
- $action_btn.click(function() {
- acceptBandInvitation(payload);
- });
- }
- else if (type === context.JK.MessageType.BAND_INVITATION_ACCEPTED) {
- $notification.find('#div-actions').hide();
- }
- else if (type === context.JK.MessageType.TEXT_MESSAGE) {
- var $action_btn = $notification.find($btnNotificationAction);
- $action_btn.text('REPLY');
- $action_btn.click(function() {
- var userId = $notification.find('.more-text-available').attr('data-sender-id');
- app.layout.showDialog('text-message', { d1: userId });
- });
-
- var moreTextLink = $notification.find('.more-text-available');
- var textMessage = $notification.find('.text-message');
- var clipped_msg = textMessage.attr('data-is-clipped') === 'true';
-
- if(clipped_msg) {
- moreTextLink.text('more').show();
- moreTextLink.click(function(e) {
- var userId = $(this).attr('data-sender-id');
-
- return false;
- });
- }
- else {
- moreTextLink.hide();
- }
- }
- }
-
- function deleteNotificationHandler(evt) {
- evt.stopPropagation();
- var notificationId = $(this).attr('notification-id');
- deleteNotification(notificationId);
- }
-
- function deleteNotification(notificationId) {
- var url = "/api/users/" + context.JK.currentUserId + "/notifications/" + notificationId;
- $.ajax({
- type: "DELETE",
- dataType: "json",
- contentType: 'application/json',
- url: url,
- processData: false,
- success: function(response) {
- $('li[notification-id=' + notificationId + ']').hide();
- decrementNotificationCount();
- },
- error: app.ajaxError
- });
+ notificationPanel = new context.JK.NotificationPanel(app);
+ notificationPanel.initialize(me, textMessageDialog);
}
function initializeChatPanel() {
@@ -362,51 +140,6 @@
$('#sidebar-search-results').height('0px');
}
- function incrementNotificationCount() {
- var count = parseInt($('#sidebar-notification-count').html());
- $('#sidebar-notification-count').html(count + 1);
- }
-
- function decrementNotificationCount() {
- var count = parseInt($('#sidebar-notification-count').html());
- if (count === 0) {
- $('#sidebar-notification-count').html(0);
- }
- else {
- $('#sidebar-notification-count').html(count - 1);
- }
- }
-
- // default handler for incoming notification
- function handleNotification(payload, type) {
-
- // on a load of notifications, it is possible to load a very new notification,
- // and get a websocket notification right after for that same notification,
- // so we need to protect against such duplicates
- if($('#sidebar-notification-list').find('li[notification-id="' + payload.notification_id + '"]').length > 0) {
- return false;
- }
-
- // increment displayed notification count
- incrementNotificationCount();
-
- // add notification to sidebar
- var template = $("#template-notification-panel").html();
- var notificationHtml = context.JK.fillTemplate(template, {
- notificationId: payload.notification_id,
- sessionId: payload.session_id,
- avatar_url: context.JK.resolveAvatarUrl(payload.photo_url),
- text: payload.msg instanceof jQuery ? payload.msg.html() : payload.msg ,
- date: $.timeago(payload.created_at)
- });
-
- $('#sidebar-notification-list').prepend(notificationHtml);
-
- initializeActions(payload, type);
-
- return true;
- }
-
var delay = (function(){
var timer = 0;
return function(callback, ms) {
@@ -464,32 +197,14 @@
// friend notifications
registerFriendUpdate();
- registerFriendRequest();
- registerFriendRequestAccepted();
- registerNewUserFollower();
- registerNewBandFollower();
// session invitations
- registerSessionInvitation();
- registerSessionEnded();
- registerJoinRequest();
- registerJoinRequestApproved();
- registerJoinRequestRejected();
registerSessionJoin();
registerSessionDepart();
- registerMusicianSessionJoin();
- registerBandSessionJoin();
// recording notifications
- registerMusicianRecordingSaved();
- registerBandRecordingSaved();
registerRecordingStarted();
registerRecordingEnded();
- registerRecordingMasterMixComplete();
-
- // band notifications
- registerBandInvitation();
- registerBandInvitationAccepted();
// broadcast notifications
registerSourceUpRequested();
@@ -497,9 +212,6 @@
registerSourceUp();
registerSourceDown();
- // register text messages
- registerTextMessage();
-
// watch for Invite More Users events
$('#sidebar-div .btn-email-invitation').click(function() {
invitationDialog.showEmailDialog();
@@ -533,210 +245,6 @@
});
}
- function registerFriendRequest() {
- context.JK.JamServer.registerMessageCallback(context.JK.MessageType.FRIEND_REQUEST, function(header, payload) {
- logger.debug("Handling FRIEND_REQUEST msg " + JSON.stringify(payload));
-
- handleNotification(payload, header.type);
-
- app.notify({
- "title": "New Friend Request",
- "text": payload.msg,
- "icon_url": context.JK.resolveAvatarUrl(payload.photo_url)
- }, {
- "ok_text": "ACCEPT",
- "ok_callback": acceptFriendRequest,
- "ok_callback_args": { "friend_request_id": payload.friend_request_id, "notification_id": payload.notification_id }
- });
- });
- }
-
- function acceptFriendRequest(args) {
-
- rest.acceptFriendRequest({
- status: 'accept',
- friend_request_id: args.friend_request_id
- }).done(function(response) {
- deleteNotification(args.notification_id); // delete notification corresponding to this friend request
- initializeFriendsPanel(); // refresh friends panel when request is accepted
- }).error(app.ajaxError);
- }
-
- function registerFriendRequestAccepted() {
- context.JK.JamServer.registerMessageCallback(context.JK.MessageType.FRIEND_REQUEST_ACCEPTED, function(header, payload) {
- logger.debug("Handling FRIEND_REQUEST_ACCEPTED msg " + JSON.stringify(payload));
-
- handleNotification(payload, header.type);
-
- initializeFriendsPanel();
-
- app.notify({
- "title": "Friend Request Accepted",
- "text": payload.msg,
- "icon_url": context.JK.resolveAvatarUrl(payload.photo_url)
- });
- });
- }
-
- function registerNewUserFollower() {
-
- context.JK.JamServer.registerMessageCallback(context.JK.MessageType.NEW_USER_FOLLOWER, function(header, payload) {
- logger.debug("Handling NEW_USER_FOLLOWER msg " + JSON.stringify(payload));
-
- handleNotification(payload, header.type);
-
- app.notify({
- "title": "New Follower",
- "text": payload.msg,
- "icon_url": context.JK.resolveAvatarUrl(payload.photo_url)
- });
- });
- }
-
- function registerNewBandFollower() {
-
- context.JK.JamServer.registerMessageCallback(context.JK.MessageType.NEW_BAND_FOLLOWER, function(header, payload) {
- logger.debug("Handling NEW_BAND_FOLLOWER msg " + JSON.stringify(payload));
-
- handleNotification(payload, header.type);
-
- app.notify({
- "title": "New Band Follower",
- "text": payload.msg,
- "icon_url": context.JK.resolveAvatarUrl(payload.photo_url)
- });
- });
-
- }
-
- function registerSessionInvitation() {
- context.JK.JamServer.registerMessageCallback(context.JK.MessageType.SESSION_INVITATION, function(header, payload) {
- logger.debug("Handling SESSION_INVITATION msg " + JSON.stringify(payload));
-
- handleNotification(payload, header.type);
-
- var participants = [];
- rest.getSession(payload.session_id).done(function(response) {
- $.each(response.participants, function(index, val) {
- participants.push({"photo_url": context.JK.resolveAvatarUrl(val.user.photo_url), "name": val.user.name});
- });
-
- var participantHtml = "You have been invited to join a session with:
";
- participantHtml += "";
-
- $.each(participants, function(index, val) {
- if (index < 4) {
- participantHtml += " + ") | " + val.name + " |
";
- }
- });
-
- participantHtml += "
";
-
- app.notify({
- "title": "Session Invitation",
- "text": participantHtml
- }, {
- "ok_text": "JOIN SESSION",
- "ok_callback": openTerms,
- "ok_callback_args": { "session_id": payload.session_id, "notification_id": payload.notification_id }
- });
- }).error(app.ajaxError);
-
- });
- }
-
- function openTerms(args) {
- var termsDialog = new context.JK.TermsDialog(app, args, onTermsAccepted);
- termsDialog.initialize();
- app.layout.showDialog('terms');
- }
-
- function onTermsAccepted(args) {
- deleteNotification(args.notification_id);
- context.location = '/client#/session/' + args.session_id;
- }
-
- function registerSessionEnded() {
- // TODO: this should clean up all notifications related to this session
- context.JK.JamServer.registerMessageCallback(context.JK.MessageType.SESSION_ENDED, function(header, payload) {
- logger.debug("Handling SESSION_ENDED msg " + JSON.stringify(payload));
- deleteSessionNotifications(payload.session_id);
- });
- }
-
- // remove all notifications for this session
- function deleteSessionNotifications(sessionId) {
- $('li[session-id=' + sessionId + ']').hide();
- decrementNotificationCount();
- }
-
- function registerJoinRequest() {
- context.JK.JamServer.registerMessageCallback(context.JK.MessageType.JOIN_REQUEST, function(header, payload) {
- logger.debug("Handling JOIN_REQUEST msg " + JSON.stringify(payload));
-
- handleNotification(payload, header.type);
-
- app.notify({
- "title": "New Join Request",
- "text": payload.msg,
- "icon_url": context.JK.resolveAvatarUrl(payload.photo_url)
- }, {
- "ok_text": "APPROVE",
- "ok_callback": approveJoinRequest,
- "ok_callback_args": { "join_request_id": payload.join_request_id, "notification_id": payload.notification_id },
- "cancel_text": "REJECT",
- "cancel_callback": rejectJoinRequest,
- "cancel_callback_args": { "join_request_id": payload.join_request_id, "notification_id": payload.notification_id }
- });
- });
- }
-
- function approveJoinRequest(args) {
- rest.updateJoinRequest(args.join_request_id, true)
- .done(function(response) {
- deleteNotification(args.notification_id);
- }).error(app.ajaxError);
- }
-
- function rejectJoinRequest(args) {
- rest.updateJoinRequest(args.join_request_id, false)
- .done(function(response) {
- deleteNotification(args.notification_id);
- }).error(app.ajaxError);
- }
-
- function registerJoinRequestApproved() {
- context.JK.JamServer.registerMessageCallback(context.JK.MessageType.JOIN_REQUEST_APPROVED, function(header, payload) {
- logger.debug("Handling JOIN_REQUEST_APPROVED msg " + JSON.stringify(payload));
-
- handleNotification(payload, header.type);
-
- app.notify({
- "title": "Join Request Approved",
- "text": payload.msg,
- "icon_url": context.JK.resolveAvatarUrl(payload.photo_url)
- }, {
- "ok_text": "JOIN SESSION",
- "ok_callback": openTerms,
- "ok_callback_args": { "session_id": payload.session_id, "notification_id": payload.notification_id }
- });
- });
- }
-
- function registerJoinRequestRejected() {
- context.JK.JamServer.registerMessageCallback(context.JK.MessageType.JOIN_REQUEST_REJECTED, function(header, payload) {
- logger.debug("Handling JOIN_REQUEST_REJECTED msg " + JSON.stringify(payload));
-
- handleNotification(payload, header.type);
-
- app.notify({
- "title": "Join Request Rejected",
- "text": payload.msg,
- "icon_url": context.JK.resolveAvatarUrl(payload.photo_url)
- });
- });
- }
-
function registerSessionJoin() {
context.JK.JamServer.registerMessageCallback(context.JK.MessageType.SESSION_JOIN, function(header, payload) {
logger.debug("Handling SESSION_JOIN msg " + JSON.stringify(payload));
@@ -769,201 +277,6 @@
});
}
- function registerMusicianSessionJoin() {
- context.JK.JamServer.registerMessageCallback(context.JK.MessageType.MUSICIAN_SESSION_JOIN, function(header, payload) {
- logger.debug("Handling MUSICIAN_SESSION_JOIN msg " + JSON.stringify(payload));
-
- var okText = '';
- var showNotification = false;
- var callback;
- if (context.JK.currentUserMusician) {
- // user is MUSICIAN; musician_access = TRUE
- if (payload.musician_access) {
- showNotification = true;
- okText = "JOIN";
- callback = joinSession;
- }
- // user is MUSICIAN; fan_access = TRUE
- else if (payload.fan_access) {
- showNotification = true;
- okText = "LISTEN";
- callback = listenToSession;
- }
- }
- else {
- // user is FAN; fan_access = TRUE
- if (payload.fan_access) {
- showNotification = true;
- okText = "LISTEN";
- callback = listenToSession;
- }
- }
-
- if (showNotification) {
- handleNotification(payload, header.type);
-
- app.notify({
- "title": "Musician Joined Session",
- "text": payload.msg,
- "icon_url": context.JK.resolveAvatarUrl(payload.photo_url)
- }, {
- "ok_text": okText,
- "ok_callback": callback,
- "ok_callback_args": {
- "session_id": payload.session_id,
- "fan_access": payload.fan_access,
- "musician_access": payload.musician_access,
- "approval_required": payload.approval_required,
- "notification_id": payload.notification_id
- }
- }
- );
- }
- });
- }
-
- function registerBandSessionJoin() {
- context.JK.JamServer.registerMessageCallback(context.JK.MessageType.BAND_SESSION_JOIN, function(header, payload) {
- logger.debug("Handling BAND_SESSION_JOIN msg " + JSON.stringify(payload));
-
- var okText = '';
- var showNotification = false;
- var callback;
- if (context.JK.currentUserMusician) {
- // user is MUSICIAN; musician_access = TRUE
- if (payload.musician_access) {
- showNotification = true;
- okText = "JOIN";
- callback = joinSession;
- }
- // user is MUSICIAN; fan_access = TRUE
- else if (payload.fan_access) {
- showNotification = true;
- okText = "LISTEN";
- callback = listenToSession;
- }
- }
- else {
- // user is FAN; fan_access = TRUE
- if (payload.fan_access) {
- showNotification = true;
- okText = "LISTEN";
- callback = listenToSession;
- }
- }
-
- if (showNotification) {
- handleNotification(payload, header.type);
-
- app.notify({
- "title": "Band Joined Session",
- "text": payload.msg,
- "icon_url": context.JK.resolveAvatarUrl(payload.photo_url)
- }, {
- "ok_text": "LISTEN",
- "ok_callback": callback,
- "ok_callback_args": {
- "session_id": payload.session_id,
- "fan_access": payload.fan_access,
- "musician_access": payload.musician_access,
- "approval_required": payload.approval_required,
- "notification_id": payload.notification_id
- }
- }
- );
- }
- });
- }
-
- function listenToSession(args) {
- deleteNotification(args.notification_id);
- context.JK.popExternalLink('/sessions/' + args.session_id);
- }
-
- /*********** TODO: THE NEXT 3 FUNCTIONS ARE COPIED FROM sessionList.js. REFACTOR TO COMMON PLACE. *************/
- function joinSession(args) {
- // NOTE: invited musicians get their own notification, so no need to check if user has invitation here
- // like other places because an invited user would never get this notification
- if (args.musician_access) {
- if (args.approval_required) {
- openAlert(args.session_id);
- }
- else {
- openTerms(args);
- }
- }
- deleteNotification(args.notification_id);
- }
-
- function openAlert(sessionId) {
- var alertDialog = new context.JK.AlertDialog(context.JK.app, "YES",
- "You must be approved to join this session. Would you like to send a request to join?",
- sessionId, onCreateJoinRequest);
-
- alertDialog.initialize();
- context.JK.app.layout.showDialog('alert');
- }
-
- function onCreateJoinRequest(sessionId) {
- var joinRequest = {};
- joinRequest.music_session = sessionId;
- joinRequest.user = context.JK.currentUserId;
- rest.createJoinRequest(joinRequest)
- .done(function(response) {
-
- }).error(context.JK.app.ajaxError);
-
- context.JK.app.layout.closeDialog('alert');
- }
- //////////////////////////////////////////////////////////////////////////////////////////
-
- function registerMusicianRecordingSaved() {
- context.JK.JamServer.registerMessageCallback(context.JK.MessageType.MUSICIAN_RECORDING_SAVED, function(header, payload) {
- logger.debug("Handling MUSICIAN_RECORDING_SAVED msg " + JSON.stringify(payload));
-
- handleNotification(payload, header.type);
-
- app.notify({
- "title": "Musician Recording Saved",
- "text": payload.msg,
- "icon_url": context.JK.resolveAvatarUrl(payload.photo_url)
- }, {
- "ok_text": "LISTEN",
- "ok_callback": listenToRecording,
- "ok_callback_args": {
- "recording_id": payload.recording_id,
- "notification_id": payload.notification_id
- }
- });
- });
- }
-
- function registerBandRecordingSaved() {
- context.JK.JamServer.registerMessageCallback(context.JK.MessageType.BAND_RECORDING_SAVED, function(header, payload) {
- logger.debug("Handling BAND_RECORDING_SAVED msg " + JSON.stringify(payload));
-
- handleNotification(payload, header.type);
-
- app.notify({
- "title": "Band Recording Saved",
- "text": payload.msg,
- "icon_url": context.JK.resolveAvatarUrl(payload.photo_url)
- }, {
- "ok_text": "LISTEN",
- "ok_callback": listenToRecording,
- "ok_callback_args": {
- "recording_id": payload.recording_id,
- "notification_id": payload.notification_id
- }
- });
- });
- }
-
- function listenToRecording(args) {
- deleteNotification(args.notification_id);
- context.JK.popExternalLink('/recordings/' + args.recording_id);
- }
-
function registerRecordingStarted() {
context.JK.JamServer.registerMessageCallback(context.JK.MessageType.RECORDING_STARTED, function(header, payload) {
logger.debug("Handling RECORDING_STARTED msg " + JSON.stringify(payload));
@@ -988,86 +301,6 @@
});
}
- function registerRecordingMasterMixComplete() {
- context.JK.JamServer.registerMessageCallback(context.JK.MessageType.RECORDING_MASTER_MIX_COMPLETE, function(header, payload) {
- logger.debug("Handling RECORDING_MASTER_MIX_COMPLETE msg " + JSON.stringify(payload));
-
- handleNotification(payload, header.type);
-
- app.notify({
- "title": "Recording Master Mix Complete",
- "text": payload.msg,
- "icon_url": context.JK.resolveAvatarUrl(payload.photo_url)
- }, {
- "ok_text": "SHARE",
- "ok_callback": shareRecording,
- "ok_callback_args": {
- "recording_id": payload.recording_id
- }
- });
- });
- }
-
- function shareRecording(args) {
- var recordingId = args.recording_id;
- }
-
- function registerBandInvitation() {
- context.JK.JamServer.registerMessageCallback(context.JK.MessageType.BAND_INVITATION, function(header, payload) {
- logger.debug("Handling BAND_INVITATION msg " + JSON.stringify(payload));
-
- handleNotification(payload, header.type);
-
- app.notify({
- "title": "Band Invitation",
- "text": payload.msg,
- "icon_url": context.JK.resolveAvatarUrl(payload.photo_url)
- }, {
- "ok_text": "ACCEPT",
- "ok_callback": acceptBandInvitation,
- "ok_callback_args": {
- "band_invitation_id": payload.band_invitation_id,
- "band_id": payload.band_id,
- "notification_id": payload.notification_id
- }
- });
- });
- }
-
- function acceptBandInvitation(args) {
- rest.updateBandInvitation(
- args.band_id,
- args.band_invitation_id,
- true
- ).done(function(response) {
- deleteNotification(args.notification_id); // delete notification corresponding to this friend request
- }).error(app.ajaxError);
- }
-
- function registerTextMessage() {
- context.JK.JamServer.registerMessageCallback(context.JK.MessageType.TEXT_MESSAGE, function(header, payload) {
- logger.debug("Handling TEXT_MESSAGE msg " + JSON.stringify(payload));
-
- textMessageDialog.messageReceived(payload);
-
- handleNotification(payload, header.type);
- });
- }
-
- function registerBandInvitationAccepted() {
- context.JK.JamServer.registerMessageCallback(context.JK.MessageType.BAND_INVITATION_ACCEPTED, function(header, payload) {
- logger.debug("Handling BAND_INVITATION_ACCEPTED msg " + JSON.stringify(payload));
-
- handleNotification(payload, header.type);
-
- app.notify({
- "title": "Band Invitation Accepted",
- "text": payload.msg,
- "icon_url": context.JK.resolveAvatarUrl(payload.photo_url)
- });
- });
- }
-
function registerSourceUpRequested() {
context.JK.JamServer.registerMessageCallback(context.JK.MessageType.SOURCE_UP_REQUESTED, function(header, payload) {
@@ -1174,13 +407,16 @@
}
this.initialize = function(invitationDialogInstance, textMessageDialogInstance) {
+ me = this;
+ invitationDialog = invitationDialogInstance;
+ textMessageDialog = textMessageDialogInstance;
events();
initializeSearchPanel();
initializeFriendsPanel();
initializeChatPanel();
initializeNotificationsPanel();
- invitationDialog = invitationDialogInstance;
- textMessageDialog = textMessageDialogInstance;
};
+
+ this.refreshFriends = refreshFriends;
}
})(window,jQuery);
\ No newline at end of file
diff --git a/web/app/assets/javascripts/textMessageDialog.js b/web/app/assets/javascripts/textMessageDialog.js
index e76d413ba..3bcc2c5c2 100644
--- a/web/app/assets/javascripts/textMessageDialog.js
+++ b/web/app/assets/javascripts/textMessageDialog.js
@@ -47,8 +47,7 @@
return message;
}
- function sendMessage(e) {
-
+ function sendMessage() {
var msg = $textBox.val();
if(!msg || msg == '') {
// don't bother the server with empty messages
@@ -124,6 +123,15 @@
return markedUpMsg;
}
+ // we handled the notification, meaning the dialog showed this message as a chat message
+ function handledNotification(payload) {
+ return showing && payload.description == "TEXT_MESSAGE" && payload.sender_id == otherId;
+ }
+
+ function afterShow(args) {
+ $textBox.focus();
+ }
+
function beforeShow(args) {
app.layout.closeDialog('text-message') // ensure no others are showing. this is a singleton dialog
@@ -175,13 +183,38 @@
reset();
}
- function postMessage(e) {
+ function pasteIntoInput(el, text) {
+ el.focus();
+ if (typeof el.selectionStart == "number"
+ && typeof el.selectionEnd == "number") {
+ var val = el.value;
+ var selStart = el.selectionStart;
+ el.value = val.slice(0, selStart) + text + val.slice(el.selectionEnd);
+ el.selectionEnd = el.selectionStart = selStart + text.length;
+ } else if (typeof document.selection != "undefined") {
+ var textRange = document.selection.createRange();
+ textRange.text = text;
+ textRange.collapse(false);
+ textRange.select();
+ }
+ }
- return false;
+ function handleEnter(evt) {
+ if (evt.keyCode == 13 && evt.shiftKey) {
+ pasteIntoInput(this, "\n");
+ evt.preventDefault();
+ }
+ else if(evt.keyCode == 13 && !evt.shiftKey){
+ sendMessage();
+ return false;
+ }
}
function events() {
- $form.submit(postMessage)
+ $form.submit(sendMessage)
+
+ // http://stackoverflow.com/questions/6014702/how-do-i-detect-shiftenter-and-generate-a-new-line-in-textarea
+ $textBox.keydown(handleEnter);
}
@@ -234,6 +267,7 @@
function initialize() {
var dialogBindings = {
'beforeShow' : beforeShow,
+ 'afterShow' : afterShow,
'afterHide': afterHide
};
@@ -253,6 +287,7 @@
this.initialize = initialize;
this.messageReceived = messageReceived;
this.formatTextMessage = formatTextMessage;
+ this.handledNotification = handledNotification;
}
return this;
diff --git a/web/app/assets/javascripts/utils.js b/web/app/assets/javascripts/utils.js
index 583881cbe..61ea676c7 100644
--- a/web/app/assets/javascripts/utils.js
+++ b/web/app/assets/javascripts/utils.js
@@ -153,9 +153,9 @@
$element.bt(text, options);
}
- context.JK.bindHoverEvents = function ($parent) {
+ context.JK.bindHoverEvents = function ($parent) {
- if(!$parent) {
+ if (!$parent) {
$parent = $('body');
}
@@ -328,9 +328,9 @@
}
// creates an array with entries like [{ id: "drums", description: "Drums"}, ]
- context.JK.listInstruments = function() {
+ context.JK.listInstruments = function () {
var instrumentArray = [];
- $.each(context.JK.server_to_client_instrument_map, function(key, val) {
+ $.each(context.JK.server_to_client_instrument_map, function (key, val) {
instrumentArray.push({"id": context.JK.server_to_client_instrument_map[key].client_id, "description": key});
});
return instrumentArray;
@@ -652,22 +652,22 @@
return hasFlash;
}
- context.JK.hasOneConfiguredDevice = function() {
+ context.JK.hasOneConfiguredDevice = function () {
var result = context.jamClient.FTUEGetGoodConfigurationList();
logger.debug("hasOneConfiguredDevice: ", result);
return result.length > 0;
};
- context.JK.getGoodAudioConfigs = function() {
+ context.JK.getGoodAudioConfigs = function () {
var result = context.jamClient.FTUEGetGoodAudioConfigurations();
logger.debug("goodAudioConfigs=%o", result);
return result;
};
- context.JK.getGoodConfigMap = function() {
+ context.JK.getGoodConfigMap = function () {
var goodConfigMap = [];
var goodConfigs = context.JK.getGoodAudioConfigs();
- $.each(goodConfigs, function(index, profileKey) {
+ $.each(goodConfigs, function (index, profileKey) {
var friendlyName = context.jamClient.FTUEGetConfigurationDevice(profileKey);
goodConfigMap.push({key: profileKey, name: friendlyName});
});
@@ -675,12 +675,12 @@
return goodConfigMap;
}
- context.JK.getBadAudioConfigs = function() {
+ context.JK.getBadAudioConfigs = function () {
var badAudioConfigs = [];
var allAudioConfigs = context.jamClient.FTUEGetAllAudioConfigurations();
var goodAudioConfigs = context.JK.getGoodAudioConfigs();
- for (var i=0; i < allAudioConfigs.length; i++) {
+ for (var i = 0; i < allAudioConfigs.length; i++) {
if ($.inArray(allAudioConfigs[i], goodAudioConfigs) === -1) {
badAudioConfigs.push(allAudioConfigs[i]);
}
@@ -689,10 +689,10 @@
return badAudioConfigs;
};
- context.JK.getBadConfigMap = function() {
+ context.JK.getBadConfigMap = function () {
var badConfigMap = [];
var badConfigs = context.JK.getBadAudioConfigs();
- $.each(badConfigs, function(index, profileKey) {
+ $.each(badConfigs, function (index, profileKey) {
var friendlyName = context.jamClient.FTUEGetConfigurationDevice(profileKey);
badConfigMap.push({key: profileKey, name: friendlyName});
});
@@ -700,7 +700,7 @@
return badConfigMap;
}
- context.JK.getFirstGoodDevice = function(preferredDeviceId) {
+ context.JK.getFirstGoodDevice = function (preferredDeviceId) {
var badConfigs = context.JK.getBadAudioConfigs();
function getGoodDevice() {
@@ -713,7 +713,7 @@
}
return deviceId;
}
-
+
var deviceId = null;
if (preferredDeviceId) {
@@ -724,7 +724,7 @@
}
else {
deviceId = getGoodDevice();
- }
+ }
}
else {
deviceId = getGoodDevice();
@@ -733,14 +733,14 @@
}
// returns /client#/home for http://www.jamkazam.com/client#/home
- context.JK.locationPath = function() {
+ context.JK.locationPath = function () {
var bits = context.location.href.split('/');
return '/' + bits.slice(3).join('/');
}
- context.JK.nowUTC = function() {
+ context.JK.nowUTC = function () {
var d = new Date();
- return new Date( d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate(), d.getUTCHours(), d.getUTCMinutes(), d.getUTCSeconds() );
+ return new Date(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate(), d.getUTCHours(), d.getUTCMinutes(), d.getUTCSeconds());
}
/*
* A JavaScript implementation of the RSA Data Security, Inc. MD5 Message
diff --git a/web/app/assets/javascripts/web/downloads.js b/web/app/assets/javascripts/web/downloads.js
index 1c3976f90..82ef0a1da 100644
--- a/web/app/assets/javascripts/web/downloads.js
+++ b/web/app/assets/javascripts/web/downloads.js
@@ -78,8 +78,8 @@
var clicked = $(this);
var href = clicked.attr('href');
if(href != "#") {
+ context.JK.GA.trackDownload(clicked.attr('data-platform'));
rest.userDownloadedClient().always(function() {
- context.JK.GA.trackDownload(clicked.attr('data-platform'));
$('body').append('')
});
}
diff --git a/web/app/assets/javascripts/web/welcome.js b/web/app/assets/javascripts/web/welcome.js
index 0d72c6a76..2403fa2dc 100644
--- a/web/app/assets/javascripts/web/welcome.js
+++ b/web/app/assets/javascripts/web/welcome.js
@@ -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']) {
diff --git a/web/app/assets/stylesheets/client/ftue.css.scss b/web/app/assets/stylesheets/client/ftue.css.scss
index fa37a754e..a5f6796de 100644
--- a/web/app/assets/stylesheets/client/ftue.css.scss
+++ b/web/app/assets/stylesheets/client/ftue.css.scss
@@ -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;
}
diff --git a/web/app/assets/stylesheets/client/shareDialog.css.scss b/web/app/assets/stylesheets/client/shareDialog.css.scss
index dee33c55e..74d095e53 100644
--- a/web/app/assets/stylesheets/client/shareDialog.css.scss
+++ b/web/app/assets/stylesheets/client/shareDialog.css.scss
@@ -219,7 +219,7 @@
.share-message {
width: 100%;
- padding:0;
+ padding:4px;
}
.error-msg {
diff --git a/web/app/assets/stylesheets/client/sidebar.css.scss b/web/app/assets/stylesheets/client/sidebar.css.scss
index 4234bc5e3..9134f21b1 100644
--- a/web/app/assets/stylesheets/client/sidebar.css.scss
+++ b/web/app/assets/stylesheets/client/sidebar.css.scss
@@ -42,6 +42,10 @@
-webkit-border-radius:50%;
-moz-border-radius:50%;
border-radius:50%;
+
+ &.highlighted {
+ background-color:white;
+ }
}
.expander {
diff --git a/web/app/assets/stylesheets/client/textMessageDialog.css.scss b/web/app/assets/stylesheets/client/textMessageDialog.css.scss
index 04f837e53..5605adfce 100644
--- a/web/app/assets/stylesheets/client/textMessageDialog.css.scss
+++ b/web/app/assets/stylesheets/client/textMessageDialog.css.scss
@@ -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;
+ }
}
\ No newline at end of file
diff --git a/web/app/assets/stylesheets/web/audioWidgets.css.scss b/web/app/assets/stylesheets/web/audioWidgets.css.scss
index ce9b3c251..a21ce84e6 100644
--- a/web/app/assets/stylesheets/web/audioWidgets.css.scss
+++ b/web/app/assets/stylesheets/web/audioWidgets.css.scss
@@ -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;
diff --git a/web/app/assets/stylesheets/web/welcome.css.scss b/web/app/assets/stylesheets/web/welcome.css.scss
index d16cb7bab..e78d9df34 100644
--- a/web/app/assets/stylesheets/web/welcome.css.scss
+++ b/web/app/assets/stylesheets/web/welcome.css.scss
@@ -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 */
}
/* _____________________________ *
diff --git a/web/app/controllers/api_users_controller.rb b/web/app/controllers/api_users_controller.rb
index ea3d28c47..5dc55cf9c 100644
--- a/web/app/controllers/api_users_controller.rb
+++ b/web/app/controllers/api_users_controller.rb
@@ -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?
diff --git a/web/app/controllers/users_controller.rb b/web/app/controllers/users_controller.rb
index 348cf7469..d5fb20d6f 100644
--- a/web/app/controllers/users_controller.rb
+++ b/web/app/controllers/users_controller.rb
@@ -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
diff --git a/web/app/views/api_music_sessions/show.rabl b/web/app/views/api_music_sessions/show.rabl
index c42d940b6..0b1c30540 100644
--- a/web/app/views/api_music_sessions/show.rabl
+++ b/web/app/views/api_music_sessions/show.rabl
@@ -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
diff --git a/web/app/views/api_users/show.rabl b/web/app/views/api_users/show.rabl
index 9c1d18422..eff5b3c5c 100644
--- a/web/app/views/api_users/show.rabl
+++ b/web/app/views/api_users/show.rabl
@@ -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|
diff --git a/web/app/views/clients/index.html.erb b/web/app/views/clients/index.html.erb
index d28fd6ae5..68b765856 100644
--- a/web/app/views/clients/index.html.erb
+++ b/web/app/views/clients/index.html.erb
@@ -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);
diff --git a/web/app/views/events/_event_session.html.haml b/web/app/views/events/_event_session.html.haml
index 3bb2a449f..419dc9bb8 100644
--- a/web/app/views/events/_event_session.html.haml
+++ b/web/app/views/events/_event_session.html.haml
@@ -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' }
\ No newline at end of file
diff --git a/web/app/views/events/event.html.haml b/web/app/views/events/event.html.haml
index 866dcbdc5..9a9248c6c 100644
--- a/web/app/views/events/event.html.haml
+++ b/web/app/views/events/event.html.haml
@@ -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'}
diff --git a/web/app/views/users/_feed_music_session.html.haml b/web/app/views/users/_feed_music_session.html.haml
index c64534191..ff40d20d6 100644
--- a/web/app/views/users/_feed_music_session.html.haml
+++ b/web/app/views/users/_feed_music_session.html.haml
@@ -68,4 +68,8 @@
%br{:clear => "all"}/
- %br/
\ No newline at end of file
+ %br/
+:javascript
+ $(function () {
+ new window.JK.FeedItemSession($('.feed-entry[data-music-session="#{feed_item.id}"]'));
+ })
\ No newline at end of file
diff --git a/web/app/views/users/_feed_recording.html.haml b/web/app/views/users/_feed_recording.html.haml
index 18f1b7b97..dc4051602 100644
--- a/web/app/views/users/_feed_recording.html.haml
+++ b/web/app/views/users/_feed_recording.html.haml
@@ -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}"]'));
+ })
diff --git a/web/app/views/users/welcome.html.haml b/web/app/views/users/welcome.html.haml
index f2d987044..6624039b7 100644
--- a/web/app/views/users/welcome.html.haml
+++ b/web/app/views/users/welcome.html.haml
@@ -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"
diff --git a/web/spec/features/accept_friend_request_dialog_spec.rb b/web/spec/features/accept_friend_request_dialog_spec.rb
index dd9376da5..6fdc81b64 100644
--- a/web/spec/features/accept_friend_request_dialog_spec.rb
+++ b/web/spec/features/accept_friend_request_dialog_spec.rb
@@ -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
diff --git a/web/spec/features/event_spec.rb b/web/spec/features/event_spec.rb
index 4ea9dab33..7295f4416 100644
--- a/web/spec/features/event_spec.rb
+++ b/web/spec/features/event_spec.rb
@@ -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
diff --git a/web/spec/features/home_spec.rb b/web/spec/features/home_spec.rb
index 1585f9c73..7c206be4b 100644
--- a/web/spec/features/home_spec.rb
+++ b/web/spec/features/home_spec.rb
@@ -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) }
diff --git a/web/spec/features/notification_highlighter_spec.rb b/web/spec/features/notification_highlighter_spec.rb
new file mode 100644
index 000000000..d75946605
--- /dev/null
+++ b/web/spec/features/notification_highlighter_spec.rb
@@ -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
diff --git a/web/spec/features/signup_spec.rb b/web/spec/features/signup_spec.rb
index 3b11319a2..f8f223cf4 100644
--- a/web/spec/features/signup_spec.rb
+++ b/web/spec/features/signup_spec.rb
@@ -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
diff --git a/web/spec/features/text_message_spec.rb b/web/spec/features/text_message_spec.rb
index 601eb7c93..517b135da 100644
--- a/web/spec/features/text_message_spec.rb
+++ b/web/spec/features/text_message_spec.rb
@@ -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)
diff --git a/web/spec/support/client_interactions.rb b/web/spec/support/client_interactions.rb
index 8b4bc2121..a02c12026 100644
--- a/web/spec/support/client_interactions.rb
+++ b/web/spec/support/client_interactions.rb
@@ -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
\ No newline at end of file
diff --git a/web/vendor/assets/javascripts/jquery.pulse.js b/web/vendor/assets/javascripts/jquery.pulse.js
new file mode 100644
index 000000000..872c24b31
--- /dev/null
+++ b/web/vendor/assets/javascripts/jquery.pulse.js
@@ -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 );
diff --git a/websocket-gateway/lib/jam_websockets/router.rb b/websocket-gateway/lib/jam_websockets/router.rb
index 70ee50421..0f3c8b0e0 100644
--- a/websocket-gateway/lib/jam_websockets/router.rb
+++ b/websocket-gateway/lib/jam_websockets/router.rb
@@ -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 != ''