diff --git a/Gemfile b/Gemfile
index 1e0e868f4..d14e02d67 100644
--- a/Gemfile
+++ b/Gemfile
@@ -78,6 +78,7 @@ group :test, :cucumber do
gem 'rack-test'
# gem 'rb-fsevent', '0.9.1', :require => false
# gem 'growl', '1.0.3'
+ gem 'poltergeist'
end
group :production do
diff --git a/app/assets/images/content/bkg_home_account_x.jpg b/app/assets/images/content/bkg_home_account_x.jpg
new file mode 100644
index 000000000..43cc67ff6
Binary files /dev/null and b/app/assets/images/content/bkg_home_account_x.jpg differ
diff --git a/app/assets/images/content/bkg_home_create_x.jpg b/app/assets/images/content/bkg_home_create_x.jpg
new file mode 100644
index 000000000..4e05cbda8
Binary files /dev/null and b/app/assets/images/content/bkg_home_create_x.jpg differ
diff --git a/app/assets/images/content/bkg_home_feed_x.jpg b/app/assets/images/content/bkg_home_feed_x.jpg
new file mode 100644
index 000000000..2535c1bc4
Binary files /dev/null and b/app/assets/images/content/bkg_home_feed_x.jpg differ
diff --git a/app/assets/images/content/bkg_home_find_x.jpg b/app/assets/images/content/bkg_home_find_x.jpg
new file mode 100644
index 000000000..fd93fbc4c
Binary files /dev/null and b/app/assets/images/content/bkg_home_find_x.jpg differ
diff --git a/app/assets/images/content/bkg_home_profile_x.jpg b/app/assets/images/content/bkg_home_profile_x.jpg
new file mode 100644
index 000000000..6d4fe73c7
Binary files /dev/null and b/app/assets/images/content/bkg_home_profile_x.jpg differ
diff --git a/app/assets/javascripts/AAB_message_factory.js b/app/assets/javascripts/AAB_message_factory.js
index 5c31c0a79..76f08b556 100644
--- a/app/assets/javascripts/AAB_message_factory.js
+++ b/app/assets/javascripts/AAB_message_factory.js
@@ -69,7 +69,10 @@
// create a login message using token (a cookie or similiar)
factory.login_with_token = function(token) {
- var login = { token : token };
+ //context.JK.logger.debug("*** login_with_token: client_id = "+$.cookie("client_id"));
+ var login = { token : token,
+ client_id : $.cookie("client_id")
+ };
return client_container(msg.LOGIN, route_to.SERVER, login);
};
diff --git a/app/assets/javascripts/JamServer.js b/app/assets/javascripts/JamServer.js
index b21c5d526..e9d9b21fc 100644
--- a/app/assets/javascripts/JamServer.js
+++ b/app/assets/javascripts/JamServer.js
@@ -1,7 +1,3 @@
-// TODO: Rename? This is really only the websocket/messaging
-// part of the server (none of the REST calls go through this).
-// perhaps something like RealTimeMessages or something...
-//
// The wrapper around the web-socket connection to the server
(function(context, $) {
@@ -32,7 +28,7 @@
server.unregisterMessageCallback = function(messageType, callback) {
if (server.dispatchTable[messageType] !== undefined) {
- for(var i = server.dispatchTable.length; i--;) {
+ for(var i = server.dispatchTable[messageType].length; i--;) {
if (server.dispatchTable[messageType][i] === callback)
{
server.dispatchTable[messageType].splice(i, 1);
@@ -56,12 +52,15 @@
server.socket.onclose = server.onClose;
};
- server.onOpen = function() {
- logger.log("server.onOpen");
+ server.rememberLogin = function() {
var token, loginMessage;
token = $.cookie("remember_token");
loginMessage = msg_factory.login_with_token(token);
server.send(loginMessage);
+ }
+ server.onOpen = function() {
+ logger.log("server.onOpen");
+ server.rememberLogin();
};
server.onMessage = function(e) {
@@ -153,18 +152,6 @@
}
});
- server.registerMessageCallback(context.JK.MessageType.USER_JOINED_MUSIC_SESSION, function(header, payload) {
- context.JK.refreshMusicSession(payload.session_id);
- });
-
- // TODO: not used
- server.registerMessageCallback(context.JK.MessageType.LOGIN_MUSIC_SESSION_ACK, function(header, payload) {
- if (context.jamClient !== undefined)
- {
- // TODO: modify the LOGIN_MUSIC_SESSION_ACK message to include session_id
- context.jamClient.JoinSession({ sessionID : payload.session_id});
- }
- });
// Callbacks from jamClient
if (context.jamClient !== undefined)
diff --git a/app/assets/javascripts/createSession.js b/app/assets/javascripts/createSession.js
index 8768ce4b7..b1b4336cb 100644
--- a/app/assets/javascripts/createSession.js
+++ b/app/assets/javascripts/createSession.js
@@ -128,12 +128,13 @@
}
// FIXME TODO:
+ // This code is duplicated in sessionModel.js -- refactor
// 1. If no previous session data, a single stereo track with the
// top instrument in the user's profile.
// 2. Otherwise, use the tracks from the last created session.
// Defaulting to 1st instrument in profile always at the moment.
var track = { instrument_id: "electric guitar", sound: "stereo" };
- if (context.JK.userMe.instruments.length) {
+ if (context.JK.userMe.instruments && context.JK.userMe.instruments.length) {
track = {
instrument_id: context.JK.userMe.instruments[0].instrument_id,
sound: "stereo"
diff --git a/app/assets/javascripts/header.js b/app/assets/javascripts/header.js
index b6466ad45..0e7f45135 100644
--- a/app/assets/javascripts/header.js
+++ b/app/assets/javascripts/header.js
@@ -11,7 +11,7 @@
context.JK.Header = function(app) {
var logger = context.JK.logger;
- var searcher; // Will hold an instance to a JK.Searcher (search.js)
+ var searcher; // Will hold an instance to a JK.Searcher (search.js)
var userMe = null;
var instrumentAutoComplete;
var instrumentIds = [];
@@ -156,10 +156,7 @@
function updateHeader() {
$('#user').html(userMe.name);
- var photoUrl = userMe.photo_url;
- if (!(photoUrl)) {
- photoUrl = "/assets/shared/avatar_default.jpg";
- }
+ var photoUrl = context.JK.resolveAvatarUrl(userMe.photo_url);
$('#header-avatar').attr('src', photoUrl);
}
diff --git a/app/assets/javascripts/jamkazam.js b/app/assets/javascripts/jamkazam.js
index 520f20d8a..453919d57 100644
--- a/app/assets/javascripts/jamkazam.js
+++ b/app/assets/javascripts/jamkazam.js
@@ -7,7 +7,7 @@
var JamKazam = context.JK.JamKazam = function() {
var app;
var logger = context.JK.logger;
- var heartbeatInterval = null;
+ var heartbeatInterval=null;
var opts = {
layoutOpts: {}
@@ -42,20 +42,21 @@
$(routes.handler);
}
- function _handleLoginAck(header, payload) {
- var heartbeatMS = payload.heartbeat_interval * 1000;
- logger.debug("Login ACK. Setting up heartbeat every " + heartbeatMS + " MS");
- heartbeatInterval = context.setInterval(_heartbeat, heartbeatMS);
- }
-
function _heartbeat() {
- var message = context.JK.MessageFactory.heartbeat();
- context.JK.JamServer.send(message);
+ if (app.heartbeatActive) {
+ var message = context.JK.MessageFactory.heartbeat();
+ context.JK.JamServer.send(message);
+ }
}
function loggedIn(header, payload) {
app.clientId = payload.client_id;
- logger.debug("jamkazam.js: loggedIn. clientId now " + app.clientId);
+ $.cookie('client_id', payload.client_id);
+ $.cookie('remember_token', payload.token);
+
+ var heartbeatMS = payload.heartbeat_interval * 1000;
+ logger.debug("jamkazam.js.loggedIn(): clientId now " + app.clientId + "; Setting up heartbeat every " + heartbeatMS + " MS");
+ heartbeatInterval = context.setInterval(_heartbeat, heartbeatMS);
}
function registerLoginAck() {
@@ -143,6 +144,9 @@
events();
};
+ // enable temporary suspension of heartbeat for fine-grained control
+ this.heartbeatActive = true;
+
/**
* Expose clientId as a public variable.
* Will be set upon LOGIN_ACK
diff --git a/app/assets/javascripts/searchResults.js b/app/assets/javascripts/searchResults.js
index ccdb50477..860a7e92d 100644
--- a/app/assets/javascripts/searchResults.js
+++ b/app/assets/javascripts/searchResults.js
@@ -7,27 +7,116 @@
var logger = context.JK.logger;
var query;
+ var instrument_logo_map = context.JK.getInstrumentIconMap24();
+
function beforeShow(data) {
query = data.query;
}
function afterShow(data) {
- // TODO remove me - just showing that you should
- // have access to the query variable from the search
- // box.
- $('#show-query').text('Query is ' + query);
}
function search(evt) {
evt.stopPropagation();
+
+ $('#search-results').empty();
var query = $('#search-input').val();
context.location = '#/searchResults/:' + query;
+
+ $('#query').html(query);
+
+ var url = "/api/search?query=" + query;
+ $.ajax({
+ type: "GET",
+ dataType: "json",
+ contentType: 'application/json',
+ url: url,
+ processData: false,
+ success: function(response) {
+ $.each(response.musicians, function(index, val) {
+
+ // fill in template for Connect pre-click
+ var template = $('#template-search-result').html();
+ var searchResultHtml = context.JK.fillTemplate(template, {
+ userId: val.id,
+ avatar_url: context.JK.resolveAvatarUrl(val.photo_url),
+ userName: val.name,
+ location: val.location,
+ instruments: getInstrumentHtml(val.instruments)
+ });
+
+ $('#search-results').append(searchResultHtml);
+
+ // fill in template for Connect post-click
+ template = $('#template-invitation-sent').html();
+ var invitationSentHtml = context.JK.fillTemplate(template, {
+ userId: val.id,
+ first_name: val.first_name,
+ profile_url: "/users/" + val.id
+ });
+
+ $('#search-results').append(invitationSentHtml);
+
+ // initialize visibility of the divs
+ $('div[user-id=' + val.id + '].search-connected').hide();
+ $('div[user-id=' + val.id + '].search-result').show();
+
+ // wire up button click handler
+ $('div[user-id=' + val.id + ']').find('#btn-connect-friend').click(sendFriendRequest);
+ });
+ var resultCount = response.musicians.length;
+ $('#result-count').html(resultCount);
+ if (resultCount === 1) {
+ $('#result-count').append(" Result ");
+ }
+ else {
+ $('#result-count').append(" Results ");
+ }
+ },
+ error: app.ajaxError
+ });
+
return false;
}
+ function sendFriendRequest(evt) {
+ evt.stopPropagation();
+ var userId = $(this).parent().attr('user-id');
+
+ //$(this).parent().empty();
+
+ var url = "/api/users/" + context.JK.currentUserId + "/friend_requests";
+ $.ajax({
+ type: "POST",
+ dataType: "json",
+ contentType: 'application/json',
+ url: url,
+ data: '{"friend_id":"' + userId + '"}',
+ processData: false,
+ success: function(response) {
+ // toggle the pre-click and post-click divs
+ $('div[user-id=' + userId + '].search-connected').show();
+ $('div[user-id=' + userId + '].search-result').hide();
+ },
+ error: app.ajaxError
+ });
+ }
+
+ function getInstrumentHtml(instruments) {
+ var instrumentLogoHtml = '';
+ if (instruments !== undefined) {
+ for (var i=0; i < instruments.length; i++) {
+ var inst = '../assets/content/icon_instrument_default24.png';
+ if (instruments[i].instrument_id in instrument_logo_map) {
+ inst = instrument_logo_map[instruments[i].instrument_id];
+ instrumentLogoHtml += ' ';
+ }
+ }
+ }
+ return instrumentLogoHtml;
+ }
+
function events() {
- // not sure it should go here long-term, but wiring
- // up the event handler for the search box in the sidebar.
$('#searchForm').submit(search);
}
diff --git a/app/assets/javascripts/session.js b/app/assets/javascripts/session.js
index 207a2b62e..3a6e0077b 100644
--- a/app/assets/javascripts/session.js
+++ b/app/assets/javascripts/session.js
@@ -5,11 +5,11 @@
context.JK = context.JK || {};
context.JK.SessionScreen = function(app) {
var logger = context.JK.logger;
+ var sessionModel = null;
var sessionId;
- var session = null;
- var users = {}; // Light cache of user info for session users.
var tracks = {};
var mixers = [];
+
// TODO Consolidate dragged controls and handles
var $draggingFaderHandle = null;
var $draggingFader = null;
@@ -36,14 +36,7 @@
}
};
- var instrumentIcons = {
- "keyboard": "content/icon_instrument_keyboard45.png",
- "electric guitar": "content/icon_instrument_guitar45.png",
- "bass guitar": "content/icon_instrument_guitar45.png",
- "voice": "content/icon_instrument_vocal45.png",
- "saxophone": "content/icon_instrument_saxophone45.png"
- };
-
+ var instrumentIcons = context.JK.getInstrumentIconMap45();
// Recreate ChannelGroupIDs ENUM from C++
var ChannelGroupIds = {
@@ -86,80 +79,23 @@
}
function afterCurrentUserLoaded() {
- // Set a flag indicating we're still joining.
- // This flag is set to true in refreshSession.js
- // after various asynch calls complete.
- context.JK.sessionJoined = false;
- context.JK.joinMusicSession(sessionId, app);
-
- function whenSessionJoined() {
- if (!(context.JK.sessionJoined)) {
- context.setTimeout(whenSessionJoined, 100);
- } else {
- // ready to proceed.
- $.ajax({
- type: "GET",
- url: "/api/sessions/" + sessionId,
- success: updateSession,
- error: fetchSessionError
- });
- }
- }
- whenSessionJoined();
-
+ sessionModel = new context.JK.SessionModel(
+ context.JK.JamServer,
+ context.jamClient,
+ context.JK.userMe
+ );
+ sessionModel.subscribe('sessionScreen', sessionChanged);
+ sessionModel.joinSession(sessionId);
}
function beforeHide(data) {
- // Move joinSession function to this file for ease of finding it?
- context.JK.leaveMusicSession(sessionId);
+ sessionModel.leaveCurrentSession(sessionId);
// 'unregister' for callbacks
context.jamClient.SessionRegisterCallback("");
}
- function fetchSessionError() {
- context.location = '#/home';
- }
-
- function updateSession(sessionData) {
- session = sessionData;
- updateParticipants(function() { renderSession(); });
- }
-
- /**
- * Make sure that for each participant in the session, we have user info.
- */
- function updateParticipants(onComplete) {
- var callCount = 0;
- $.each(session.participants, function(index, value) {
- if (!(this.user.id in users)) {
- var userInfoUrl = "/api/users/" + this.user.id;
- callCount += 1;
- $.ajax({
- type: "GET",
- url: userInfoUrl,
- success: function(user) {
- callCount -= 1;
- users[user.id] = user;
- },
- error: function(jqXHR, textStatus, errorThrown) {
- callCount -= 1;
- logger.error('Error getting user info from ' + userInfoUrl);
- }
- });
- }
- });
- if (!(onComplete)) {
- return;
- }
- // TODO: generalize this pattern. Likely needed elsewhere.
- function checker() {
- if (callCount === 0) {
- onComplete();
- } else {
- context.setTimeout(checker, 10);
- }
- }
- checker();
+ function sessionChanged() {
+ renderSession();
}
function renderSession() {
@@ -178,17 +114,6 @@
mixers = holder.mixers;
}
- function _participantForClientId(clientId) {
- var foundParticipant = null;
- $.each(session.participants, function(index, participant) {
- if (participant.client_id === clientId) {
- foundParticipant = participant;
- return false;
- }
- });
- return foundParticipant;
- }
-
function _mixerForClientId(clientId, groupIds) {
var foundMixer = null;
$.each(mixers, function(index, mixer) {
@@ -254,17 +179,14 @@
// Draw tracks from participants, then setup timers to look for the
// mixers that go with those participants, if they're missing.
lookingForMixersCount = 0;
- $.each(session.participants, function(index, participant) {
+ $.each(sessionModel.participants(), function(index, participant) {
var name = participant.user.name;
if (!(name)) {
name = participant.user.first_name + ' ' + participant.user.last_name;
}
var instrumentIcon = _instrumentIconFromId(participant.tracks[0].instrument_id);
- var photoUrl = participant.user.photo_url;
- if (!(photoUrl)) {
- photoUrl = "/assets/shared/avatar_default.jpg";
- }
+ var photoUrl = context.JK.resolveAvatarUrl(participant.user.photo_url);
var myTrack = false;
@@ -405,25 +327,6 @@
tracks[trackData.clientId] = new context.JK.SessionTrack(trackData.clientId);
}
- function _userJoinedSession(header, payload) {
- reloadAndUpdateSession();
- }
-
- function _userLeftSession(header, payload) {
- reloadAndUpdateSession();
- }
-
- function reloadAndUpdateSession() {
- $.ajax({
- type: "GET",
- url: "/api/sessions/" + sessionId,
- success: function(response) { updateSession(response); },
- error: function(jqXHR, textStatus, errorThrown) {
- logger.error("Error loading session " + sessionId);
- }
- });
- }
-
function handleBridgeCallback() {
var eventName = null;
var mixerId = null;
@@ -771,13 +674,6 @@
}
this.initialize = function() {
- context.JK.JamServer.registerMessageCallback(
- context.JK.MessageType.USER_JOINED_MUSIC_SESSION,
- _userJoinedSession);
- context.JK.JamServer.registerMessageCallback(
- context.JK.MessageType.USER_LEFT_MUSIC_SESSION,
- _userLeftSession);
-
context.jamClient.SetVURefreshRate(150);
events();
var screenBindings = {
diff --git a/app/assets/javascripts/sessionList.js b/app/assets/javascripts/sessionList.js
index df1b17275..cf98590e6 100644
--- a/app/assets/javascripts/sessionList.js
+++ b/app/assets/javascripts/sessionList.js
@@ -16,16 +16,7 @@
MUSICIANS_ONLY:"Musicians Only"
};
- // JW: TODO - I'm building a similar map in session.js
- // At somepoint, we should refactor to somewhere we can share.
- var instrument_logo_map = {
- "acoustic guitar": '../assets/content/icon_instrument_guitar24.png',
- "bass guitar": '../assets/content/icon_instrument_guitar24.png',
- "electric guitar": '../assets/content/icon_instrument_guitar24.png',
- "keyboard": '../assets/content/icon_instrument_keyboard24.png',
- "saxophone": '/assets/content/icon_instrument_saxophone24.png',
- "voice": '../assets/content/icon_instrument_vocal24.png'
- };
+ var instrument_logo_map = context.JK.getInstrumentIconMap24();
var _logger = context.JK.logger;
@@ -82,7 +73,7 @@
var id = participant.user.id;
var name = participant.user.name;
- var photoUrl = participant.user.photo_url ? participant.user.photo_url : "/assets/shared/avatar_default.jpg";
+ var photoUrl = context.JK.resolveAvatarUrl(participant.user.photo_url);
var musicianVals = {
avatar_url: photoUrl,
profile_url: "users/" + id,
diff --git a/app/assets/javascripts/sessionModel.js b/app/assets/javascripts/sessionModel.js
index 37f974727..043a10289 100644
--- a/app/assets/javascripts/sessionModel.js
+++ b/app/assets/javascripts/sessionModel.js
@@ -7,60 +7,216 @@
context.JK = context.JK || {};
var logger = context.JK.logger;
- var sessions = {};
+ context.JK.SessionModel = function(server, client, currentUser) {
+ var clientId = client.clientID;
+ var currentSessionId = null; // Set on join, prior to setting currentSession.
+ var currentSession = null;
+ var subscribers = {};
+ var users = {}; // User info for session participants
- sessions.instances = {};
-
- sessions.JoinSession = function(session_id) {
- if (sessions.instances[session_id] !== undefined) {
- logger.error("ERROR: Joined a session twice: " + session_id);
- return;
+ function id() {
+ return currentSession.id;
}
- sessions.instances[session_id] = {
- id: session_id,
- participants: {}
- };
- };
-
- sessions.LeaveSession = function(session_id) {
- if (sessions.instances[session_id] !== undefined) {
- delete sessions.instances[session_id];
- }
- };
-
- sessions.UpdateSessionParticipants = function(session_id, participants) {
- var added = [];
- var removed = [];
- var session = sessions.instances[session_id];
-
- if (session === undefined) {
- logger.error("ERROR: Unknown session: " + session_id);
- return;
- }
-
- var toBeRemoved = $.extend({}, session.participants);
-
- $.each(participants, function (index, participant) {
- if (session.participants[participant.client_id] === undefined)
- {
- session.participants[participant.client_id] = participant;
- added.push(participant);
+ function participants() {
+ if (currentSession) {
+ return currentSession.participants;
+ } else {
+ return [];
}
- else
- {
- delete toBeRemoved[participant.client_id];
+ }
+
+ /**
+ * Join the session specified by the provided id.
+ */
+ function joinSession(sessionId) {
+ currentSessionId = sessionId;
+ logger.debug("SessionModel.joinSession(" + sessionId + ")");
+ joinSessionRest(sessionId, function() {
+ refreshCurrentSession();
+ });
+ server.registerMessageCallback(
+ context.JK.MessageType.USER_JOINED_MUSIC_SESSION,
+ refreshCurrentSession);
+ server.registerMessageCallback(
+ context.JK.MessageType.USER_LEFT_MUSIC_SESSION,
+ refreshCurrentSession);
+ }
+
+ /**
+ * Leave the current session
+ */
+ function leaveCurrentSession() {
+ logger.debug("SessionModel.leaveCurrentSession()");
+ // TODO - sessionChanged will be called with currentSession = null
+ server.unregisterMessageCallback(
+ context.JK.MessageType.USER_JOINED_MUSIC_SESSION,
+ refreshCurrentSession);
+ server.unregisterMessageCallback(
+ context.JK.MessageType.USER_LEFT_MUSIC_SESSION,
+ refreshCurrentSession);
+ leaveSessionRest(currentSessionId, sessionChanged);
+ currentSession = null;
+ currentSessionId = null;
+ }
+
+ /**
+ * Refresh the current session, and participants.
+ */
+ function refreshCurrentSession() {
+ logger.debug("SessionModel.refreshCurrentSession()");
+ refreshCurrentSessionRest(function() {
+ refreshCurrentSessionParticipantsRest(sessionChanged);
+ });
+ }
+
+ /**
+ * Subscribe for sessionChanged events. Provide a subscriberId
+ * and a callback to be invoked on session changes.
+ */
+ function subscribe(subscriberId, sessionChangedCallback) {
+ logger.debug("SessionModel.subscribe(" + subscriberId + ", [callback])");
+ subscribers[subscriberId] = sessionChangedCallback;
+ }
+
+ /**
+ * Notify subscribers that the current session has changed.
+ */
+ function sessionChanged() {
+ logger.debug("SessionModel.sessionChanged()");
+ for (var subscriberId in subscribers) {
+ subscribers[subscriberId]();
}
- });
+ }
- $.each(toBeRemoved, function(client_id, participant) {
- delete session.participants[client_id];
- removed.push(participant);
- });
+ /**
+ * Reload the session data from the REST server, calling
+ * the provided callback when complete.
+ */
+ function refreshCurrentSessionRest(callback) {
+ var url = "/api/sessions/" + currentSessionId;
+ $.ajax({
+ type: "GET",
+ url: url,
+ success: function(response) {
+ currentSession = response;
+ callback();
+ },
+ error: ajaxError
+ });
+ }
+
+ /**
+ * Ensure that we have user info for all current participants.
+ */
+ function refreshCurrentSessionParticipantsRest(callback) {
+ var callCount = 0;
+ $.each(participants(), function(index, value) {
+ if (!(this.user.id in users)) {
+ var userInfoUrl = "/api/users/" + this.user.id;
+ callCount += 1;
+ $.ajax({
+ type: "GET",
+ url: userInfoUrl,
+ success: function(user) {
+ callCount -= 1;
+ users[user.id] = user;
+ },
+ error: function(jqXHR, textStatus, errorThrown) {
+ callCount -= 1;
+ logger.error('Error getting user info from ' + userInfoUrl);
+ }
+ });
+ }
+ });
+ if (!(callback)) {
+ return;
+ }
+ context.JK.joinCalls(
+ function() { return callCount === 0; }, callback, 10);
+ }
+
+ function participantForClientId(clientId) {
+ var foundParticipant = null;
+ $.each(currentSession.participants, function(index, participant) {
+ if (participant.client_id === clientId) {
+ foundParticipant = participant;
+ return false;
+ }
+ });
+ return foundParticipant;
+ }
+
+ /**
+ * Make the server calls to join the current user to
+ * the session provided.
+ */
+ function joinSessionRest(sessionId, callback) {
+ var tracks = getUserTracks();
+ var data = {
+ client_id: clientId,
+ ip_address: server.publicIP,
+ as_musician: true,
+ tracks: tracks
+ };
+ var url = "/api/sessions/" + sessionId + "/participants";
+ $.ajax({
+ type: "POST",
+ dataType: "json",
+ contentType: 'application/json',
+ url: url,
+ data: JSON.stringify(data),
+ processData:false,
+ success: function(response) {
+ client.JoinSession({ sessionID: sessionId });
+ callback();
+ },
+ error: ajaxError
+ });
+ }
+
+ function leaveSessionRest(sessionId, callback) {
+ var url = "/api/participants/" + clientId;
+ $.ajax({
+ type: "DELETE",
+ url: url,
+ success: function (response) {
+ client.LeaveSession({ sessionID: sessionId });
+ callback();
+ },
+ error: ajaxError
+ });
+ }
+
+ function ajaxError(jqXHR, textStatus, errorMessage) {
+ logger.error("Unexpected ajax error: " + textStatus);
+ }
+
+ function getUserTracks() {
+ // FIXME. Setting tracks for join session. Only looking at profile
+ // for now. Needs to check jamClient instruments and if set, use those
+ // as first preference. Also need to support jamClient possibly having
+ // multiple tracks.
+ // TODO: Defaulting to electric guitar...
+ var track = { instrument_id: "electric guitar", sound: "stereo" };
+ if (currentUser.instruments && currentUser.instruments.length) {
+ track = {
+ instrument_id: currentUser.instruments[0].instrument_id,
+ sound: "stereo"
+ };
+ }
+ return [track];
+ }
+
+ // Public interface
+ this.id = id;
+ this.participants = participants;
+ this.joinSession = joinSession;
+ this.leaveCurrentSession = leaveCurrentSession;
+ this.refreshCurrentSession = refreshCurrentSession;
+ this.subscribe = subscribe;
+ this.participantForClientId = participantForClientId;
- return { added: added, removed: removed };
};
- context.JK.Sessions = sessions;
-
})(window,jQuery);
\ No newline at end of file
diff --git a/app/assets/javascripts/sidebar.js b/app/assets/javascripts/sidebar.js
new file mode 100644
index 000000000..e1ca1e9a0
--- /dev/null
+++ b/app/assets/javascripts/sidebar.js
@@ -0,0 +1,80 @@
+(function(context,$) {
+
+ "use strict";
+
+ context.JK = context.JK || {};
+ context.JK.Sidebar = function(app) {
+ var logger = context.JK.logger;
+
+ function populateFriendsPanel() {
+
+ var url = "/api/users/" + context.JK.currentUserId + "/friends"
+ $.ajax({
+ type: "GET",
+ dataType: "json",
+ contentType: 'application/json',
+ url: url,
+ processData: false,
+ success: function(response) {
+ $.each(response, function(index, val) {
+
+ var css = val.online ? '' : 'offline';
+
+ // fill in template for Connect pre-click
+ var template = $('#template-friend-panel').html();
+ var searchResultHtml = context.JK.fillTemplate(template, {
+ userId: val.id,
+ cssClass: css,
+ avatar_url: context.JK.resolveAvatarUrl(val.photo_url),
+ userName: val.name,
+ status: val.online ? 'Available' : 'Offline',
+ extra_info: '',
+ info_image_url: ''
+ });
+
+ $('#sidebar-friend-list').append(searchResultHtml);
+ });
+
+ // set friend count
+ $('#sidebar-friend-count').html(response.length);
+ },
+ error: app.ajaxError
+ });
+
+ return false;
+ }
+
+ function sendFriendRequest(evt) {
+ evt.stopPropagation();
+ var userId = $(this).parent().attr('user-id');
+
+ //$(this).parent().empty();
+
+ var url = "/api/users/" + context.JK.currentUserId + "/friend_requests";
+ $.ajax({
+ type: "POST",
+ dataType: "json",
+ contentType: 'application/json',
+ url: url,
+ data: '{"friend_id":"' + userId + '"}',
+ processData: false,
+ success: function(response) {
+ // toggle the pre-click and post-click divs
+ $('div[user-id=' + userId + '].search-connected').show();
+ $('div[user-id=' + userId + '].search-result').hide();
+ },
+ error: app.ajaxError
+ });
+ }
+
+ function events() {
+ populateFriendsPanel();
+ }
+
+ this.initialize = function() {
+ events();
+ };
+
+ };
+
+})(window,jQuery);
\ No newline at end of file
diff --git a/app/assets/javascripts/utils.js b/app/assets/javascripts/utils.js
index 3faad3af4..afb214297 100644
--- a/app/assets/javascripts/utils.js
+++ b/app/assets/javascripts/utils.js
@@ -7,6 +7,24 @@
context.JK = context.JK || {};
+ var instrumentIconMap24 = {
+ "acoustic guitar": '../assets/content/icon_instrument_guitar24.png',
+ "bass guitar": '../assets/content/icon_instrument_guitar24.png',
+ "electric guitar": '../assets/content/icon_instrument_guitar24.png',
+ "keyboard": '../assets/content/icon_instrument_keyboard24.png',
+ "saxophone": '../assets/content/icon_instrument_saxophone24.png',
+ "voice": '../assets/content/icon_instrument_vocal24.png'
+ };
+
+ var instrumentIconMap45 = {
+ "acoustic guitar": '../assets/content/icon_instrument_guitar45.png',
+ "bass guitar": "../assets/content/icon_instrument_guitar45.png",
+ "electric guitar": "../assets/content/icon_instrument_guitar45.png",
+ "keyboard": "../assets/content/icon_instrument_keyboard45.png",
+ "saxophone": "../assets/content/icon_instrument_saxophone45.png",
+ "voice": "../assets/content/icon_instrument_vocal45.png"
+ };
+
// Uber-simple templating
// var template = "Hey {name}";
// var vals = { name: "Jon" };
@@ -18,6 +36,18 @@
return template;
};
+ context.JK.resolveAvatarUrl = function(photo_url) {
+ return photo_url ? photo_url : "/assets/shared/avatar_default.jpg";
+ }
+
+ context.JK.getInstrumentIconMap24 = function() {
+ return instrumentIconMap24;
+ }
+
+ context.JK.getInstrumentIconMap45 = function() {
+ return instrumentIconMap45;
+ }
+
/*
* Get the length of a dictionary
*/
@@ -31,6 +61,25 @@
return count;
};
+ /**
+ * Way to verify that a number of parallel tasks have all completed.
+ * Provide a function to evaluate completion, and a callback to
+ * invoke when that function evaluates to true.
+ * NOTE: this does not pause execution, it simply ensures that
+ * when the test function evaluates to true, the callback will
+ * be invoked.
+ */
+ context.JK.joinCalls = function(completionTestFunction, callback, interval) {
+ function doneYet() {
+ if (completionTestFunction()) {
+ callback();
+ } else {
+ context.setTimeout(doneYet, interval);
+ }
+ }
+ doneYet();
+ };
+
/*
* A JavaScript implementation of the RSA Data Security, Inc. MD5 Message
* Digest Algorithm, as defined in RFC 1321.
diff --git a/app/assets/stylesheets/client/home.css.scss b/app/assets/stylesheets/client/home.css.scss
index 2438666ba..dd1e7ebb9 100644
--- a/app/assets/stylesheets/client/home.css.scss
+++ b/app/assets/stylesheets/client/home.css.scss
@@ -43,5 +43,21 @@
.homecard.hover {
border: 1px solid $translucent2 !important;
+ background-color: #b32712;
+}
+.homecard.createsession.hover {
+ background-image: url(/assets/content/bkg_home_create_x.jpg);
+}
+.homecard.findsession.hover {
+ background-image: url(/assets/content/bkg_home_find_x.jpg);
+}
+.homecard.profile.hover {
+ background-image: url(/assets/content/bkg_home_profile_x.jpg);
+}
+.homecard.feed.hover {
+ background-image: url(/assets/content/bkg_home_feed_x.jpg);
+}
+.homecard.account.hover {
+ background-image: url(/assets/content/bkg_home_account_x.jpg);
}
diff --git a/app/assets/stylesheets/client/searchResults.css.scss b/app/assets/stylesheets/client/searchResults.css.scss
new file mode 100644
index 000000000..6734ec71b
--- /dev/null
+++ b/app/assets/stylesheets/client/searchResults.css.scss
@@ -0,0 +1,94 @@
+.search-result-header {
+ width:100%;
+ padding:11px 0px 11px 0px;
+ background-color:#4c4c4c;
+ min-height:20px;
+ overflow-x:hidden;
+}
+
+a.search-nav {
+ font-size:13px;
+ color:#fff;
+ text-decoration:none;
+ margin-right:40px;
+ float:left;
+ font-weight:200;
+ padding-bottom:4px;
+}
+
+a.search-nav.active, a.search-nav.active:hover {
+ font-weight:700;
+ border-bottom:solid 3px #ed3618;
+}
+
+a.search-nav:hover {
+ border-bottom: dotted 2px #ed3618;
+}
+
+.search-result {
+ width:193px;
+ min-height:85px;
+ background-color:#242323;
+ position:relative;
+ float:left;
+ margin:10px 20px 10px 0px;
+ padding-bottom:5px;
+}
+
+.search-connected {
+ background-color:#4c4c4c;
+ width:193px;
+ min-height:85px;
+ position:relative;
+ float:left;
+ margin:10px 20px 10px 0px;
+ padding-bottom:5px;
+ font-size:11px;
+ text-align:center;
+ vertical-align:middle;
+}
+
+.search-connected a {
+ color:#B3DD15;
+}
+
+.search-band-genres {
+ float:left;
+ width:40%;
+ font-size:10px;
+ margin-left:10px;
+ padding-right:5px;
+}
+
+.search-result-name {
+ float:left;
+ font-size:12px;
+ margin-top:12px;
+ font-weight:bold;
+}
+
+.search-result-location {
+ font-size:11px;
+ color:#D5E2E4;
+ font-weight:200;
+}
+
+.avatar-small {
+ float:left;
+ padding:1px;
+ width:36px;
+ height:36px;
+ background-color:#ed3618;
+ margin:10px;
+ -webkit-border-radius:18px;
+ -moz-border-radius:18px;
+ border-radius:18px;
+}
+
+.avatar-small img {
+ width: 36px;
+ height: 36px;
+ -webkit-border-radius:18px;
+ -moz-border-radius:18px;
+ border-radius:18px;
+}
\ No newline at end of file
diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb
index 4607e508a..301858051 100644
--- a/app/controllers/sessions_controller.rb
+++ b/app/controllers/sessions_controller.rb
@@ -76,4 +76,13 @@ class SessionsController < ApplicationController
def failure
end
+
+ def connection_state
+ if (defined?(TEST_CONNECT_STATES) && TEST_CONNECT_STATES) || 'development'==Rails.env
+ @prefix = defined?(TEST_CONNECT_STATE_JS_LOG_PREFIX) ? TEST_CONNECT_STATE_JS_LOG_PREFIX : '*** '
+ render('connection_state', :layout => 'client') && return
+ end
+ render :nothing => true, :status => 404
+ end
+
end
diff --git a/app/views/api_search/index.rabl b/app/views/api_search/index.rabl
index ba13dce45..1bcb83003 100644
--- a/app/views/api_search/index.rabl
+++ b/app/views/api_search/index.rabl
@@ -8,13 +8,25 @@ end
unless @search.musicians.nil? || @search.musicians.size == 0
child(:musicians => :musicians) {
- attributes :id, :first_name, :last_name, :location, :photo_url
+ attributes :id, :first_name, :last_name, :name, :location, :photo_url
+
+ node :is_friend do |musician|
+ musician.friends?(current_user)
+ end
+
+ child :musician_instruments => :instruments do
+ attributes :instrument_id, :description, :proficiency_level, :priority
+ end
}
end
unless @search.fans.nil? || @search.fans.size == 0
child(:fans => :fans) {
- attributes :id, :first_name, :last_name, :location, :photo_url
+ attributes :id, :first_name, :last_name, :name, :location, :photo_url
+
+ node :is_friend do |fan|
+ fan.friends?(current_user)
+ end
}
end
@@ -26,6 +38,6 @@ end
unless @search.friends.nil? || @search.friends.size == 0
child(:friends => :friends) {
- attributes :id, :first_name, :last_name, :city, :state, :country, :email, :online, :photo_url, :musician
+ attributes :id, :first_name, :last_name, :name, :location, :email, :online, :photo_url, :musician
}
end
diff --git a/app/views/api_users/friend_index.rabl b/app/views/api_users/friend_index.rabl
index 511a0d127..40eb8fd7c 100644
--- a/app/views/api_users/friend_index.rabl
+++ b/app/views/api_users/friend_index.rabl
@@ -1,3 +1,3 @@
object @user.friends
-attributes :id, :first_name, :last_name, :city, :state, :country, :email, :online, :photo_url
\ No newline at end of file
+attributes :id, :first_name, :last_name, :name, :location, :city, :state, :country, :email, :online, :photo_url
\ No newline at end of file
diff --git a/app/views/api_users/show.rabl b/app/views/api_users/show.rabl
index 8d0831668..fdee4f67f 100644
--- a/app/views/api_users/show.rabl
+++ b/app/views/api_users/show.rabl
@@ -21,9 +21,6 @@ end
unless @user.instruments.nil? || @user.instruments.size == 0
child :musician_instruments => :instruments do
- attributes :description, :proficiency_level, :priority
- node :instrument_id do |instrument|
- instrument.instrument_id
- end
+ attributes :description, :proficiency_level, :priority, :instrument_id
end
end
\ No newline at end of file
diff --git a/app/views/clients/_createSession.html.erb b/app/views/clients/_createSession.html.erb
index 755253602..98f22b3aa 100644
--- a/app/views/clients/_createSession.html.erb
+++ b/app/views/clients/_createSession.html.erb
@@ -100,7 +100,7 @@
Brian - fill this in from Jeff's mockups. Probably need templates for individual blocks
- - +