Merge branch 'master' of bitbucket.org:jamkazam/jam-web
This commit is contained in:
commit
6bf0f499bd
1
Gemfile
1
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
|
||||
|
|
|
|||
Binary file not shown.
|
After Width: | Height: | Size: 5.8 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 8.5 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 7.2 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 8.4 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 7.1 KiB |
|
|
@ -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);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 += '<img src="' + inst + '" width="24" height="24" /> ';
|
||||
}
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 = {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
@ -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);
|
||||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
object @user.friends
|
||||
|
||||
attributes :id, :first_name, :last_name, :city, :state, :country, :email, :online, :photo_url
|
||||
attributes :id, :first_name, :last_name, :name, :location, :city, :state, :country, :email, :online, :photo_url
|
||||
|
|
@ -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
|
||||
|
|
@ -100,7 +100,7 @@
|
|||
<!-- friend invitation box -->
|
||||
<div class="friendbox">
|
||||
<div id="selected-friends"></div>
|
||||
<input id="friend-input" type="text" value="Type a friend's name" />
|
||||
<input id="friend-input" type="text" placeholder="Type a friend's name" />
|
||||
</div>
|
||||
|
||||
<div class="mt35 mb15">
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@
|
|||
|
||||
<!-- keyword filter -->
|
||||
<div class="search-box">
|
||||
<input id="session-keyword-srch" type="text" name="search" value="Search by Keyword" />
|
||||
<input id="session-keyword-srch" type="text" name="search" placeholder="Search by Keyword" />
|
||||
</div>
|
||||
<div class="right mr10">
|
||||
<a id="btn-refresh" href="#/findSession" style="text-decoration:none;" class="button-grey">REFRESH</a>
|
||||
|
|
|
|||
|
|
@ -1,19 +1,49 @@
|
|||
<!-- Search Results Screen -->
|
||||
<div layout="screen" layout-id="searchResults" layout-arg="query" class="screen secondary">
|
||||
<div class="content-head">
|
||||
<!--
|
||||
<div class="content-icon">
|
||||
<%= image_tag "shared/icon_session.png", {:height => 19, :width => 19} %>
|
||||
<a href="#"><%= image_tag "content/icon_search.png", :size => "19x19" %></a>
|
||||
</div>
|
||||
-->
|
||||
<h1>search results</h1>
|
||||
<%= render "screen_navigation" %>
|
||||
</div>
|
||||
<p>Brian - fill this in from Jeff's mockups. Probably need templates for individual blocks</p>
|
||||
<!-- TODO remove me... just an example -->
|
||||
<p id="show-query"></p>
|
||||
<div class="search-result-header">
|
||||
<div class="right mr30"><span id="result-count"></span> for "<span id="query"></span>"</div>
|
||||
<div class="left ml35">
|
||||
<a id="musician-search" href="#" class="search-nav active">MUSICIANS</a>
|
||||
<a id="band-search" href="#" class="search-nav">BANDS</a>
|
||||
<a id="fand-search" href="#" class="search-nav">FANS</a>
|
||||
<a id="recording-search" href="#" class="search-nav">RECORDINGS</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="content-scroller">
|
||||
<div id="search-results" class="content-wrapper" style="padding-left:50px;">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="text/template" id="template-searchResult">
|
||||
<!-- fill in for a template for one search result with curly braces for replacement vars -->
|
||||
<script type="text/template" id="template-search-result">
|
||||
<div user-id="{userId}" class="search-result">
|
||||
<a href="#" class="avatar-small"><img src="{avatar_url}" /></a>
|
||||
<div class="search-result-name">{userName}<br />
|
||||
<span class="search-result-location">{location}</span>
|
||||
</div>
|
||||
<br clear="left" />
|
||||
<div id="instruments" class="left ml10 nowrap">
|
||||
{instruments}
|
||||
</div>
|
||||
<a id="btn-connect-friend" class="button-orange smallbutton right">CONNECT</a>
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script type="text/template" id="template-invitation-sent">
|
||||
<div user-id="{userId}" class="search-connected">
|
||||
<div style="margin-top:10px;">
|
||||
<br />
|
||||
<img src="/assets/content/icon_goodquality.png" width="16" height="16" />
|
||||
<br />
|
||||
<b>Invitation Sent!</b><br />
|
||||
<a href="{profile_url}">View {first_name}"s Profile</a>
|
||||
</div>
|
||||
</div>
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -26,67 +26,10 @@
|
|||
</div>
|
||||
<div layout-panel="expanded" class="panel expanded">
|
||||
<div layout-panel="header" class="panel-header">
|
||||
<h2>friends<div class="badge">4</div></h2>
|
||||
<h2>friends<div id='sidebar-friend-count' class="badge"></div></h2>
|
||||
</div>
|
||||
<div layout-panel="contents" class="panelcontents">
|
||||
<ul>
|
||||
<li>
|
||||
<div class="avatar-small">
|
||||
<%= image_tag "avatars/avatar_david.jpg" %>
|
||||
</div>
|
||||
<div class="friend-name">
|
||||
David Wilson<br/>
|
||||
<span class="friend-status">
|
||||
<a href="#">In Session</a>, started at 12:34 pm
|
||||
</span>
|
||||
</div>
|
||||
<div class="friend-icon">
|
||||
<%= image_tag "shared/icon_session.png", :width=>24, :height=>24 %>
|
||||
</div>
|
||||
<br clear="all" />
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<div class="avatar-small">
|
||||
<%= image_tag "shared/avatar_creepyeye.jpg" %>
|
||||
</div>
|
||||
<div class="friend-name">
|
||||
Brian Smith<br/>
|
||||
<span class="friend-status">Available</span>
|
||||
</div>
|
||||
<div class="friend-icon"></div>
|
||||
<br clear="all" />
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<div class="avatar-small">
|
||||
<%= image_tag "shared/avatar_silverfox.jpg" %>
|
||||
</div>
|
||||
<div class="friend-name">
|
||||
Peter Walker<br/>
|
||||
<span class="friend-status">
|
||||
Recording: <a href="#">Seven Trails</a>
|
||||
</span>
|
||||
</div>
|
||||
<div class="friend-icon">
|
||||
<%= image_tag "sidebar/icon_recording.png", :width=>24, :height=>24 %>
|
||||
</div>
|
||||
<br clear="all" />
|
||||
</li>
|
||||
|
||||
<li class="offline">
|
||||
<div class="avatar-small">
|
||||
<%= image_tag "shared/avatar_saltnpepper.jpg" %>
|
||||
</div>
|
||||
<div class="friend-name">
|
||||
Seth Call<br/>
|
||||
<span class="friend-status">
|
||||
Offline
|
||||
</span>
|
||||
</div>
|
||||
<div class="friend-icon"></div>
|
||||
<br clear="all" />
|
||||
</li>
|
||||
<ul id="sidebar-friend-list">
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -222,3 +165,17 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="text/template" id="template-friend-panel">
|
||||
<li class="{cssClass}">
|
||||
<div class="avatar-small"><img src="{avatar_url}" /></div>
|
||||
<div class="friend-name">
|
||||
{userName}<br/>
|
||||
<span class="friend-status">
|
||||
{status} {extra_info}
|
||||
</span>
|
||||
</div>
|
||||
<div class="friend-icon">{info_image_url}</div>
|
||||
<br clear="all" />
|
||||
</li>
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -72,6 +72,9 @@
|
|||
var header = new JK.Header(JK.app);
|
||||
header.initialize();
|
||||
|
||||
var sidebar = new JK.Sidebar(JK.app);
|
||||
sidebar.initialize();
|
||||
|
||||
var homeScreen = new JK.HomeScreen(JK.app);
|
||||
homeScreen.initialize();
|
||||
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@
|
|||
<%= stylesheet_link_tag "client/createSession", media: "all" %>
|
||||
<%= stylesheet_link_tag "client/genreSelector", media: "all" %>
|
||||
<%= stylesheet_link_tag "client/sessionList", media: "all" %>
|
||||
<%= stylesheet_link_tag "client/searchResults", media: "all" %>
|
||||
<%= include_gon %>
|
||||
<%= javascript_include_tag "application" %>
|
||||
<%= csrf_meta_tags %>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,136 @@
|
|||
<% if Rails.env == "test" || Rails.env == "development" %>
|
||||
<script type='text/javascript'>
|
||||
$(document).ready(function() {
|
||||
JK = JK || {};
|
||||
JK.websocket_gateway_uri = '<%= Rails.application.config.websocket_gateway_uri %>'.replace(/localhost/,location.hostname);
|
||||
if (!(window.jamClient)) { window.jamClient = new JK.FakeJamClient(); }
|
||||
|
||||
// override the onOpen to manage login; do this before connect()
|
||||
JK.JamServer.onOpen = function() {
|
||||
JK.logger.log("<%=@prefix%> connection_state: onOpen: logging in: "+'<%=params[:user]%> '+ ' <%=params[:password]%>');
|
||||
if ($.cookie("remember_token")) {
|
||||
JK.JamServer.rememberLogin();
|
||||
<% if params[:user] && params[:password] %>
|
||||
} else {
|
||||
JK.JamServer.send(JK.MessageFactory.login_with_user_pass('<%=params[:user]%>', '<%=params[:password]%>'));
|
||||
<% end %>
|
||||
}
|
||||
}
|
||||
// setup server and app
|
||||
JK.JamServer.connect();
|
||||
JK.app = JK.JamKazam();
|
||||
JK.app.initialize();
|
||||
// disable normal heartbeat as we need to test without
|
||||
JK.app.heartbeatActive = false;
|
||||
|
||||
// cache of the music_session_id created in the test
|
||||
var music_session_id;
|
||||
|
||||
// checks all participants of a music_session for one with our client_id
|
||||
function musicSessionContainsClientID(music_session) {
|
||||
var pp = music_session['participants'];
|
||||
for (ii=0; ii < pp.length; ii++) {
|
||||
if (JK.app.client_id == pp[ii]['client_id']) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// checks to see if the music_session has been deleted, as it should be after expire duration delay
|
||||
function isExpired() {
|
||||
// do a GET call and check for participant; confirm the participant is not there (based on client_id)
|
||||
$.ajax({
|
||||
type: 'GET',
|
||||
dataType: 'json',
|
||||
contentType: 'application/json',
|
||||
url: '/api/sessions/'+music_session_id,
|
||||
processData: false,
|
||||
success: function(music_session) {
|
||||
if (musicSessionContainsClientID(music_session)) {
|
||||
// FIXME: report error; connection should be deleted
|
||||
JK.logger.log("<%=@prefix%> connection_state: isExpired: ERROR: connection NOT deleted");
|
||||
} else {
|
||||
JK.logger.log("<%=@prefix%> connection_state: isExpired: SUCCESS: connection deleted");
|
||||
}
|
||||
},
|
||||
error:function (xhr, ajaxOptions, thrownError){
|
||||
if(xhr.status==404) {
|
||||
JK.logger.log("<%=@prefix%> connection_state: isExpired: SUCCESS: 404");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// after waiting for the stale duration, is the connection still available? if ok, run isExpire after delay
|
||||
function isStale() {
|
||||
// do a GET call and check for participant; confirm the participant is there (based on client_id)
|
||||
$.ajax({
|
||||
type: 'GET',
|
||||
dataType: 'json',
|
||||
contentType: 'application/json',
|
||||
url: '/api/sessions/'+music_session_id,
|
||||
processData: false,
|
||||
success: function(data) {
|
||||
if (musicSessionContainsClientID(data)) {
|
||||
JK.logger.log("<%=@prefix%> connection_state: isStale: connection was found as expected");
|
||||
window.setTimeout(isExpired,
|
||||
<%= (Rails.application.config.websocket_gateway_connect_time_expire + 1) * 1000 %>);
|
||||
} else {
|
||||
// FIXME: report error; connection should not be deleted
|
||||
JK.logger.log("<%=@prefix%> connection_state: isStale: ERROR: connection was NOT FOUND");
|
||||
}
|
||||
},
|
||||
error:function (xhr, ajaxOptions, thrownError){
|
||||
if(xhr.status==404) {
|
||||
// FIXME: report error
|
||||
JK.logger.log("<%=@prefix%> connection_state: isStale: ERROR: 404");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function createMusicSession() {
|
||||
var data = {
|
||||
client_id: JK.app.client_id,
|
||||
description: 'asdf',
|
||||
as_musician: true,
|
||||
legal_terms: true,
|
||||
genres: ['classical'],
|
||||
musician_access: true,
|
||||
fan_chat: true,
|
||||
fan_access: true,
|
||||
approval_required: true,
|
||||
tracks: [{instrument_id: 'electric guitar', sound: "mono"}]
|
||||
}
|
||||
JK.logger.log("<%=@prefix%> connection_state: createMusicSession: ... ");
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
dataType: 'json',
|
||||
contentType: 'application/json',
|
||||
url: '/api/sessions/',
|
||||
data: JSON.stringify(data),
|
||||
processData: false,
|
||||
success: function(music_session) {
|
||||
music_session_id = music_session['id'];
|
||||
JK.logger.log("<%=@prefix%> connection_state: createMusicSession: music_session_id = "+music_session_id);
|
||||
window.setTimeout(isStale,
|
||||
<%= (Rails.application.config.websocket_gateway_connect_time_stale + 1) * 1000 %>);
|
||||
},
|
||||
error:function (xhr, ajaxOptions, thrownError){
|
||||
JK.logger.log("<%=@prefix%> connection_state: createMusicSession: ERROR: "+thrownError);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function myLoggedIn(header, payload) {
|
||||
JK.logger.log("<%=@prefix%> connection_state: myLoggedIn: "+payload.client_id);
|
||||
JK.app.client_id = payload.client_id;
|
||||
$.cookie('client_id', payload.client_id);
|
||||
JK.logger.log("<%=@prefix%> connection_state: cookie: "+$.cookie('client_id'));
|
||||
$.cookie('remember_token', payload.token);
|
||||
createMusicSession();
|
||||
}
|
||||
JK.JamServer.registerMessageCallback(JK.MessageType.LOGIN_ACK, myLoggedIn);
|
||||
|
||||
});
|
||||
</script>
|
||||
<% end %> <!-- Rails.env=='test'||'dev' -->
|
||||
|
|
@ -83,15 +83,19 @@ module SampleApp
|
|||
|
||||
# websocket-gateway configs
|
||||
|
||||
# Runs the websocket gateway within the web app
|
||||
config.websocket_gateway_uri = "ws://localhost:6767/websocket"
|
||||
|
||||
# Websocket-gateway embedded configs
|
||||
config.websocket_gateway_enable = false
|
||||
config.websocket_gateway_connect_time_stale = 30
|
||||
config.websocket_gateway_connect_time_expire = 180
|
||||
if Rails.env=='test'
|
||||
config.websocket_gateway_connect_time_stale = 2
|
||||
config.websocket_gateway_connect_time_expire = 5
|
||||
else
|
||||
config.websocket_gateway_connect_time_stale = 30
|
||||
config.websocket_gateway_connect_time_expire = 180
|
||||
end
|
||||
config.websocket_gateway_internal_debug = false
|
||||
config.websocket_gateway_port = 6767
|
||||
# Runs the websocket gateway within the web app
|
||||
config.websocket_gateway_uri = "ws://localhost:#{config.websocket_gateway_port}/websocket"
|
||||
|
||||
# set this to false if you want to disable signups (lock down public user creation)
|
||||
config.signup_enabled = true
|
||||
|
|
|
|||
|
|
@ -42,4 +42,6 @@ SampleApp::Application.configure do
|
|||
config.show_log_configuration = true
|
||||
|
||||
config.websocket_gateway_enable = true
|
||||
|
||||
TEST_CONNECT_STATES = false
|
||||
end
|
||||
|
|
|
|||
|
|
@ -43,6 +43,13 @@ SampleApp::Application.configure do
|
|||
# For testing omniauth
|
||||
OmniAuth.config.test_mode = true
|
||||
|
||||
config.websocket_gateway_enable = false
|
||||
TEST_CONNECT_STATES = false
|
||||
if defined?(TEST_CONNECT_STATES) && TEST_CONNECT_STATES
|
||||
TEST_CONNECT_STATE_JS_LOG_PREFIX = '*** ASSERT'
|
||||
TEST_CONNECT_STATE_JS_CONSOLE = '/tmp/jam_connect_js.out'
|
||||
config.websocket_gateway_enable = true
|
||||
else
|
||||
config.websocket_gateway_enable = false
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -33,6 +33,8 @@ SampleApp::Application.routes.draw do
|
|||
|
||||
match '/confirm/:signup_token', to: 'users#signup_confirm', as: 'signup_confirm'
|
||||
|
||||
match '/test_connection', to: 'sessions#connection_state', :as => :connection_state
|
||||
|
||||
# password reset
|
||||
match '/request_reset_password' => 'users#request_reset_password', :via => :get
|
||||
match '/reset_password' => 'users#reset_password', :via => :post
|
||||
|
|
|
|||
|
|
@ -0,0 +1,47 @@
|
|||
require 'spec_helper'
|
||||
|
||||
if defined?(TEST_CONNECT_STATES) && TEST_CONNECT_STATES
|
||||
describe "ConnectionStates", :js => true, :type => :feature, :capybara_feature => true do
|
||||
|
||||
before(:all) do
|
||||
Capybara.javascript_driver = :poltergeist
|
||||
Capybara.current_driver = Capybara.javascript_driver
|
||||
@user = FactoryGirl.create(:user)
|
||||
end
|
||||
|
||||
it "visits the connection_state test page and let it run its cycle", :js => true do
|
||||
visit "/test_connection?user=#{@user.email}&password=foobar"
|
||||
page.status_code.should be(200)
|
||||
|
||||
# sleep for the duration of stale+expire delay to give browser time to run through the JS
|
||||
sleep_dur = Rails.application.config.websocket_gateway_connect_time_stale +
|
||||
Rails.application.config.websocket_gateway_connect_time_expire
|
||||
# add 1 second each for stale and expire dur used in test_connection; plus 10% buffer
|
||||
sleep_dur = (sleep_dur + 2) * 1.1
|
||||
$stdout.puts("*** sleeping for: #{sleep_dur} seconds to allow browser JS to run")
|
||||
sleep(sleep_dur)
|
||||
|
||||
# FIXME: The next step is to process the JS console output and raise assertions
|
||||
# as appropriate; there is currently a database problem wherein inserted Connection records
|
||||
# are not found after login; it's prolly an issue with db transactions, but will require more
|
||||
# debugging to determine the cause. The connection row is created properly in the login process
|
||||
# but when creating music_session, the connection is not found.
|
||||
|
||||
File.exists?(TEST_CONNECT_STATE_JS_CONSOLE).should be_true
|
||||
TEST_CONNECT_STATE_JS_CONSOLE_IO.flush
|
||||
|
||||
jsfunctions = %W{ myLoggedIn createMusicSession isStale isExpired }
|
||||
jsconsole = File.read(TEST_CONNECT_STATE_JS_CONSOLE)
|
||||
jsconsole.split("\n").each do |line|
|
||||
next unless line =~ /^#{Regexp.escape(TEST_CONNECT_STATE_JS_LOG_PREFIX)}/
|
||||
# $stdout.puts("*** console line = #{line}")
|
||||
/ERROR/.match(line).should be_nil
|
||||
|
||||
# FIXME: do more validation of console output here...
|
||||
jsfunctions.delete_if { |fcn| line =~ /#{fcn}/ }
|
||||
end
|
||||
jsfunctions.count.should == 0
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
|
@ -7,7 +7,7 @@ require 'active_record'
|
|||
require 'action_mailer'
|
||||
require 'jam_db'
|
||||
require 'jam_ruby'
|
||||
require 'spec_db'
|
||||
require "#{File.dirname(__FILE__)}/spec_db"
|
||||
|
||||
include JamRuby
|
||||
|
||||
|
|
@ -22,41 +22,57 @@ Spork.prefork do
|
|||
# Loading more in this block will cause your tests to run faster. However,
|
||||
# if you change any configuration or code from libraries loaded here, you'll
|
||||
# need to restart spork for it take effect.
|
||||
# This file is copied to spec/ when you run 'rails generate rspec:install'
|
||||
ENV["RAILS_ENV"] ||= 'test'
|
||||
require File.expand_path("../../config/environment", __FILE__)
|
||||
require 'rspec/rails'
|
||||
require 'rspec/autorun'
|
||||
# This file is copied to spec/ when you run 'rails generate rspec:install'
|
||||
ENV["RAILS_ENV"] ||= 'test'
|
||||
require File.expand_path("../../config/environment", __FILE__)
|
||||
require 'rspec/rails'
|
||||
require 'rspec/autorun'
|
||||
|
||||
# Requires supporting ruby files with custom matchers and macros, etc,
|
||||
# in spec/support/ and its subdirectories.
|
||||
Dir[Rails.root.join("spec/support/**/*.rb")].each {|f| require f}
|
||||
require 'capybara'
|
||||
require 'capybara/rspec'
|
||||
|
||||
ActiveRecord::Base.logger = Logger.new(STDOUT) if defined?(ActiveRecord::Base)
|
||||
if defined?(TEST_CONNECT_STATES) && TEST_CONNECT_STATES
|
||||
require 'capybara/poltergeist'
|
||||
TEST_CONNECT_STATE_JS_CONSOLE_IO = File.open(TEST_CONNECT_STATE_JS_CONSOLE, 'w')
|
||||
Capybara.register_driver :poltergeist do |app|
|
||||
Capybara::Poltergeist::Driver.new(app, { phantomjs_logger: TEST_CONNECT_STATE_JS_CONSOLE_IO })
|
||||
end
|
||||
Capybara.javascript_driver = :poltergeist
|
||||
end
|
||||
|
||||
RSpec.configure do |config|
|
||||
# ## Mock Framework
|
||||
#
|
||||
# If you prefer to use mocha, flexmock or RR, uncomment the appropriate line:
|
||||
#
|
||||
# config.mock_with :mocha
|
||||
# config.mock_with :flexmock
|
||||
# config.mock_with :rr
|
||||
config.mock_with :rspec
|
||||
# Requires supporting ruby files with custom matchers and macros, etc,
|
||||
# in spec/support/ and its subdirectories.
|
||||
Dir[Rails.root.join("spec/support/**/*.rb")].each {|f| require f}
|
||||
|
||||
# Remove this line if you're not using ActiveRecord or ActiveRecord fixtures
|
||||
config.fixture_path = "#{::Rails.root}/spec/fixtures"
|
||||
ActiveRecord::Base.logger = Logger.new(STDOUT) if defined?(ActiveRecord::Base)
|
||||
|
||||
# If you're not using ActiveRecord, or you'd prefer not to run each of your
|
||||
# examples within a transaction, remove the following line or assign false
|
||||
# instead of true.
|
||||
config.use_transactional_fixtures = true
|
||||
RSpec.configure do |config|
|
||||
# ## Mock Framework
|
||||
#
|
||||
# If you prefer to use mocha, flexmock or RR, uncomment the appropriate line:
|
||||
#
|
||||
# config.mock_with :mocha
|
||||
# config.mock_with :flexmock
|
||||
# config.mock_with :rr
|
||||
config.mock_with :rspec
|
||||
|
||||
# If true, the base class of anonymous controllers will be inferred
|
||||
# automatically. This will be the default behavior in future versions of
|
||||
# rspec-rails.
|
||||
config.infer_base_class_for_anonymous_controllers = false
|
||||
end
|
||||
# Remove this line if you're not using ActiveRecord or ActiveRecord fixtures
|
||||
config.fixture_path = "#{::Rails.root}/spec/fixtures"
|
||||
|
||||
# If you're not using ActiveRecord, or you'd prefer not to run each of your
|
||||
# examples within a transaction, remove the following line or assign false
|
||||
# instead of true.
|
||||
if defined?(TEST_CONNECT_STATES) && TEST_CONNECT_STATES
|
||||
config.use_transactional_fixtures = false
|
||||
else
|
||||
config.use_transactional_fixtures = true
|
||||
end
|
||||
|
||||
# If true, the base class of anonymous controllers will be inferred
|
||||
# automatically. This will be the default behavior in future versions of
|
||||
# rspec-rails.
|
||||
config.infer_base_class_for_anonymous_controllers = false
|
||||
end
|
||||
end
|
||||
|
||||
Spork.each_run do
|
||||
|
|
|
|||
|
|
@ -0,0 +1,14 @@
|
|||
if defined?(TEST_CONNECT_STATES) && TEST_CONNECT_STATES
|
||||
class ActiveRecord::Base
|
||||
mattr_accessor :shared_connection
|
||||
@@shared_connection = nil
|
||||
|
||||
def self.connection
|
||||
@@shared_connection || retrieve_connection
|
||||
end
|
||||
end
|
||||
|
||||
# Forces all threads to share the same connection. This works on
|
||||
# Capybara because it starts the web server in a thread.
|
||||
ActiveRecord::Base.shared_connection = ActiveRecord::Base.connection
|
||||
end
|
||||
Loading…
Reference in New Issue