Merge branch 'develop' into vrfs925
This commit is contained in:
commit
bf1e315b48
|
|
@ -48,7 +48,7 @@ gem 'unf', '0.1.3' #optional fog dependency
|
|||
gem 'country-select'
|
||||
gem 'aasm', '3.0.16'
|
||||
gem 'postgres-copy', '0.6.0'
|
||||
gem 'aws-sdk', '1.29.1'
|
||||
gem 'aws-sdk' #, '1.29.1'
|
||||
gem 'bugsnag'
|
||||
gem 'gon'
|
||||
gem 'cocoon'
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ gem 'amqp', '1.0.2'
|
|||
gem 'will_paginate'
|
||||
gem 'actionmailer', '3.2.13'
|
||||
gem 'sendgrid', '1.2.0'
|
||||
gem 'aws-sdk', '1.29.1'
|
||||
gem 'aws-sdk' #, '1.29.1'
|
||||
gem 'carrierwave', '0.9.0'
|
||||
gem 'aasm', '3.0.16'
|
||||
gem 'devise', '>= 1.1.2'
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ module JamWebEventMachine
|
|||
end
|
||||
|
||||
|
||||
def self.run_em
|
||||
def self.run_em(calling_thread)
|
||||
|
||||
EM.run do
|
||||
# this is global because we need to check elsewhere if we are currently connected to amqp before signalling success with some APIs, such as 'create session'
|
||||
|
|
@ -53,6 +53,8 @@ module JamWebEventMachine
|
|||
MQRouter.user_exchange = exchange
|
||||
end
|
||||
end
|
||||
|
||||
calling_thread.wakeup
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -63,9 +65,12 @@ module JamWebEventMachine
|
|||
end
|
||||
|
||||
def self.run
|
||||
|
||||
current = Thread.current
|
||||
Thread.new do
|
||||
run_em
|
||||
run_em(current)
|
||||
end
|
||||
Thread.stop
|
||||
end
|
||||
|
||||
def self.start
|
||||
|
|
@ -79,8 +84,9 @@ module JamWebEventMachine
|
|||
EM.stop
|
||||
end
|
||||
@@log.debug("starting EventMachine")
|
||||
current = Thread.current
|
||||
Thread.new do
|
||||
run_em
|
||||
run_em(current)
|
||||
end
|
||||
die_gracefully_on_signal
|
||||
end
|
||||
|
|
|
|||
|
|
@ -102,7 +102,7 @@ FOO
|
|||
|
||||
def send_test_batch
|
||||
self.perform_event('do_test_run!')
|
||||
if 'test'==Rails.env
|
||||
if 'test' == Rails.env
|
||||
BatchMailer.send_batch_email_test(self.id).deliver!
|
||||
else
|
||||
BatchMailer.send_batch_email_test(self.id).deliver
|
||||
|
|
|
|||
|
|
@ -366,6 +366,8 @@ module JamRuby
|
|||
|
||||
def send_session_ended(session_id)
|
||||
|
||||
return if session_id.nil? # so we don't query every notification in the system with a nil session_id
|
||||
|
||||
notifications = Notification.where(:session_id => session_id)
|
||||
|
||||
# publish to all users who have a notification for this session
|
||||
|
|
|
|||
|
|
@ -68,6 +68,8 @@ describe RecordedTrack do
|
|||
describe "aws-based operations", :aws => true do
|
||||
|
||||
def put_file_to_aws(signed_data, contents)
|
||||
|
||||
begin
|
||||
RestClient.put( signed_data[:url],
|
||||
contents,
|
||||
{
|
||||
|
|
@ -76,6 +78,11 @@ describe RecordedTrack do
|
|||
:'Content-MD5' => signed_data[:md5],
|
||||
:Authorization => signed_data[:authorization]
|
||||
})
|
||||
rescue => e
|
||||
puts e.response
|
||||
raise e
|
||||
end
|
||||
|
||||
end
|
||||
# create a test file
|
||||
upload_file='some_file.ogg'
|
||||
|
|
|
|||
|
|
@ -1,3 +1,6 @@
|
|||
|
||||
ENV["RAILS_ENV"] = "test"
|
||||
|
||||
require 'simplecov'
|
||||
require 'support/utilities'
|
||||
require 'active_record'
|
||||
|
|
@ -31,13 +34,14 @@ require 'resque_spec/scheduler'
|
|||
|
||||
include JamRuby
|
||||
|
||||
|
||||
# manually register observers
|
||||
ActiveRecord::Base.add_observer InvitedUserObserver.instance
|
||||
ActiveRecord::Base.add_observer UserObserver.instance
|
||||
ActiveRecord::Base.add_observer FeedbackObserver.instance
|
||||
ActiveRecord::Base.add_observer RecordedTrackObserver.instance
|
||||
|
||||
RecordedTrack.observers.disable :all # only a few tests want this observer active
|
||||
#RecordedTrack.observers.disable :all # only a few tests want this observer active
|
||||
|
||||
# put ActionMailer into test mode
|
||||
ActionMailer::Base.delivery_method = :test
|
||||
|
|
|
|||
|
|
@ -2,8 +2,9 @@ JAMKAZAM_TESTING_BUCKET = 'jamkazam-testing' # cuz i'm not comfortable using aws
|
|||
|
||||
def app_config
|
||||
klass = Class.new do
|
||||
|
||||
def aws_bucket
|
||||
JAMKAZAM_TESTING_BUCKET
|
||||
JAMKAZAM_TESTING_BUCKET
|
||||
end
|
||||
|
||||
def aws_access_key_id
|
||||
|
|
@ -14,6 +15,18 @@ def app_config
|
|||
'h0V0ffr3JOp/UtgaGrRfAk25KHNiO9gm8Pj9m6v3'
|
||||
end
|
||||
|
||||
def aws_region
|
||||
'us-east-1'
|
||||
end
|
||||
|
||||
def aws_bucket_public
|
||||
'jamkazam-testing-public'
|
||||
end
|
||||
|
||||
def aws_cache
|
||||
'315576000'
|
||||
end
|
||||
|
||||
def audiomixer_path
|
||||
# you can specify full path to audiomixer with AUDIOMIXER_PATH env variable...
|
||||
# or we check for audiomixer path in the user's workspace
|
||||
|
|
@ -77,30 +90,6 @@ def app_config
|
|||
"#{external_protocol}#{external_hostname}#{(external_port == 80 || external_port == 443) ? '' : ':' + external_port.to_s}"
|
||||
end
|
||||
|
||||
def aws_access_key_id
|
||||
'AKIAJESQY24TOT542UHQ'
|
||||
end
|
||||
|
||||
def aws_secret_access_key
|
||||
'h0V0ffr3JOp/UtgaGrRfAk25KHNiO9gm8Pj9m6v3'
|
||||
end
|
||||
|
||||
def aws_region
|
||||
'us-east-1'
|
||||
end
|
||||
|
||||
def aws_bucket
|
||||
'jamkazam-testing'
|
||||
end
|
||||
|
||||
def aws_bucket_public
|
||||
'jamkazam-testing-public'
|
||||
end
|
||||
|
||||
def aws_cache
|
||||
'315576000'
|
||||
end
|
||||
|
||||
def max_audio_downloads
|
||||
100
|
||||
end
|
||||
|
|
@ -124,7 +113,7 @@ def app_config
|
|||
|
||||
end
|
||||
|
||||
return klass.new
|
||||
klass.new
|
||||
end
|
||||
|
||||
def run_tests? type
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ gem 'fb_graph', '2.5.9'
|
|||
gem 'sendgrid', '1.2.0'
|
||||
gem 'recaptcha', '0.3.4'
|
||||
gem 'filepicker-rails', '0.1.0'
|
||||
gem 'aws-sdk', '1.29.1'
|
||||
gem 'aws-sdk' #, '1.29.1'
|
||||
gem 'aasm', '3.0.16'
|
||||
gem 'carrierwave', '0.9.0'
|
||||
gem 'carrierwave_direct'
|
||||
|
|
|
|||
|
|
@ -1,15 +1,47 @@
|
|||
// The wrapper around the web-socket connection to the server
|
||||
(function(context, $) {
|
||||
// manages the connection, heartbeats, and reconnect logic.
|
||||
// presents itself as a dialog, or in-situ banner (_jamServer.html.haml)
|
||||
(function (context, $) {
|
||||
|
||||
"use strict";
|
||||
"use strict";
|
||||
|
||||
context.JK = context.JK || {};
|
||||
context.JK = context.JK || {};
|
||||
|
||||
var logger = context.JK.logger;
|
||||
var msg_factory = context.JK.MessageFactory;
|
||||
var logger = context.JK.logger;
|
||||
var msg_factory = context.JK.MessageFactory;
|
||||
// Let socket.io know where WebSocketMain.swf is
|
||||
context.WEB_SOCKET_SWF_LOCATION = "assets/flash/WebSocketMain.swf";
|
||||
|
||||
// Let socket.io know where WebSocketMain.swf is
|
||||
context.WEB_SOCKET_SWF_LOCATION = "assets/flash/WebSocketMain.swf";
|
||||
context.JK.JamServer = function (app) {
|
||||
|
||||
// heartbeat
|
||||
var heartbeatInterval = null;
|
||||
var heartbeatMS = null;
|
||||
var heartbeatMissedMS = 10000; // if 5 seconds go by and we haven't seen a heartbeat ack, get upset
|
||||
var lastHeartbeatAckTime = null;
|
||||
var lastHeartbeatFound = false;
|
||||
var heartbeatAckCheckInterval = null;
|
||||
var notificationLastSeenAt = undefined;
|
||||
var notificationLastSeen = undefined;
|
||||
|
||||
// reconnection logic
|
||||
var connectDeferred = null;
|
||||
var freezeInteraction = false;
|
||||
var countdownInterval = null;
|
||||
var reconnectAttemptLookup = [2, 2, 2, 4, 8, 15, 30];
|
||||
var reconnectAttempt = 0;
|
||||
var reconnectingWaitPeriodStart = null;
|
||||
var reconnectDueTime = null;
|
||||
var connectTimeout = null;
|
||||
|
||||
// elements
|
||||
var $inSituBanner = null;
|
||||
var $inSituBannerHolder = null;
|
||||
var $messageContents = null;
|
||||
var $dialog = null;
|
||||
var $templateServerConnection = null;
|
||||
var $templateDisconnected = null;
|
||||
var $currentDisplay = null;
|
||||
|
||||
var server = {};
|
||||
server.socket = {};
|
||||
|
|
@ -21,141 +53,459 @@
|
|||
server.connected = false;
|
||||
|
||||
|
||||
// if activeElementVotes is null, then we are assuming this is the initial connect sequence
|
||||
function initiateReconnect(activeElementVotes, in_error) {
|
||||
var initialConnect = !!activeElementVotes;
|
||||
|
||||
freezeInteraction = activeElementVotes && ((activeElementVotes.dialog && activeElementVotes.dialog.freezeInteraction === true) || (activeElementVotes.screen && activeElementVotes.screen.freezeInteraction === true));
|
||||
|
||||
if(!initialConnect) {
|
||||
context.JK.CurrentSessionModel.onWebsocketDisconnected(in_error);
|
||||
}
|
||||
|
||||
if(in_error) {
|
||||
reconnectAttempt = 0;
|
||||
$currentDisplay = renderDisconnected();
|
||||
beginReconnectPeriod();
|
||||
}
|
||||
}
|
||||
|
||||
// handles logic if the websocket connection closes, and if it was in error then also prompt for reconnect
|
||||
function closedCleanup(in_error) {
|
||||
if(server.connected) {
|
||||
server.connected = false;
|
||||
|
||||
context.JK.CurrentSessionModel.onWebsocketDisconnected(in_error);
|
||||
// stop future heartbeats
|
||||
if (heartbeatInterval != null) {
|
||||
clearInterval(heartbeatInterval);
|
||||
heartbeatInterval = null;
|
||||
}
|
||||
|
||||
// notify anyone listening that the socket closed
|
||||
var len = server.socketClosedListeners.length;
|
||||
for(var i = 0; i < len; i++) {
|
||||
try {
|
||||
server.socketClosedListeners[i](in_error);
|
||||
} catch (ex) {
|
||||
logger.warn('exception in callback for websocket closed event:' + ex);
|
||||
}
|
||||
}
|
||||
// stop checking for heartbeat acks
|
||||
if (heartbeatAckCheckInterval != null) {
|
||||
clearTimeout(heartbeatAckCheckInterval);
|
||||
heartbeatAckCheckInterval = null;
|
||||
}
|
||||
|
||||
if (server.connected) {
|
||||
server.connected = false;
|
||||
if(app.clientUpdating) {
|
||||
// we don't want to do a 'cover the whole screen' dialog
|
||||
// because the client update is already showing.
|
||||
return;
|
||||
}
|
||||
|
||||
server.reconnecting = true;
|
||||
|
||||
var result = app.activeElementEvent('beforeDisconnect');
|
||||
|
||||
initiateReconnect(result, in_error);
|
||||
|
||||
app.activeElementEvent('afterDisconnect');
|
||||
|
||||
// notify anyone listening that the socket closed
|
||||
var len = server.socketClosedListeners.length;
|
||||
for (var i = 0; i < len; i++) {
|
||||
try {
|
||||
server.socketClosedListeners[i](in_error);
|
||||
} catch (ex) {
|
||||
logger.warn('exception in callback for websocket closed event:' + ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
server.registerOnSocketClosed = function(callback) {
|
||||
server.socketClosedListeners.push(callback);
|
||||
////////////////////
|
||||
//// HEARTBEAT /////
|
||||
////////////////////
|
||||
function _heartbeatAckCheck() {
|
||||
|
||||
// if we've seen an ack to the latest heartbeat, don't bother with checking again
|
||||
// this makes us resilient to front-end hangs
|
||||
if (lastHeartbeatFound) {
|
||||
return;
|
||||
}
|
||||
|
||||
// check if the server is still sending heartbeat acks back down
|
||||
// this logic equates to 'if we have not received a heartbeat within heartbeatMissedMS, then get upset
|
||||
if (new Date().getTime() - lastHeartbeatAckTime.getTime() > heartbeatMissedMS) {
|
||||
logger.error("no heartbeat ack received from server after ", heartbeatMissedMS, " seconds . giving up on socket connection");
|
||||
context.JK.JamServer.close(true);
|
||||
}
|
||||
else {
|
||||
lastHeartbeatFound = true;
|
||||
}
|
||||
}
|
||||
|
||||
server.registerMessageCallback = function(messageType, callback) {
|
||||
if (server.dispatchTable[messageType] === undefined) {
|
||||
server.dispatchTable[messageType] = [];
|
||||
}
|
||||
|
||||
server.dispatchTable[messageType].push(callback);
|
||||
};
|
||||
|
||||
server.unregisterMessageCallback = function(messageType, callback) {
|
||||
if (server.dispatchTable[messageType] !== undefined) {
|
||||
for(var i = server.dispatchTable[messageType].length; i--;) {
|
||||
if (server.dispatchTable[messageType][i] === callback)
|
||||
{
|
||||
server.dispatchTable[messageType].splice(i, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (server.dispatchTable[messageType].length === 0) {
|
||||
delete server.dispatchTable[messageType];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
server.connect = function() {
|
||||
logger.log("server.connect");
|
||||
var uri = context.JK.websocket_gateway_uri; // Set in index.html.erb.
|
||||
//var uri = context.gon.websocket_gateway_uri; // Leaving here for now, as we're looking for a better solution.
|
||||
server.socket = new context.WebSocket(uri);
|
||||
server.socket.onopen = server.onOpen;
|
||||
server.socket.onmessage = server.onMessage;
|
||||
server.socket.onclose = server.onClose;
|
||||
};
|
||||
|
||||
server.close = function(in_error) {
|
||||
logger.log("closing websocket");
|
||||
|
||||
server.socket.close();
|
||||
|
||||
closedCleanup(in_error);
|
||||
function _heartbeat() {
|
||||
if (app.heartbeatActive) {
|
||||
var message = context.JK.MessageFactory.heartbeat(notificationLastSeen, notificationLastSeenAt);
|
||||
notificationLastSeenAt = undefined;
|
||||
notificationLastSeen = undefined;
|
||||
context.JK.JamServer.send(message);
|
||||
lastHeartbeatFound = false;
|
||||
}
|
||||
}
|
||||
|
||||
server.rememberLogin = function() {
|
||||
var token, loginMessage;
|
||||
token = $.cookie("remember_token");
|
||||
var clientType = context.jamClient.IsNativeClient() ? 'client' : 'browser';
|
||||
loginMessage = msg_factory.login_with_token(token, null, clientType);
|
||||
server.send(loginMessage);
|
||||
};
|
||||
function loggedIn(header, payload) {
|
||||
|
||||
server.onOpen = function() {
|
||||
logger.log("server.onOpen");
|
||||
server.rememberLogin();
|
||||
};
|
||||
if(!connectTimeout) {
|
||||
clearTimeout(connectTimeout);
|
||||
connectTimeout = null;
|
||||
}
|
||||
|
||||
server.onMessage = function(e) {
|
||||
var message = JSON.parse(e.data),
|
||||
messageType = message.type.toLowerCase(),
|
||||
payload = message[messageType],
|
||||
callbacks = server.dispatchTable[message.type];
|
||||
app.clientId = payload.client_id;
|
||||
|
||||
if(message.type != context.JK.MessageType.HEARTBEAT_ACK && message.type != context.JK.MessageType.PEER_MESSAGE) {
|
||||
logger.log("server.onMessage:" + messageType + " payload:" + JSON.stringify(payload));
|
||||
// tell the backend that we have logged in
|
||||
context.jamClient.OnLoggedIn(payload.user_id, payload.token);
|
||||
|
||||
$.cookie('client_id', payload.client_id);
|
||||
|
||||
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);
|
||||
heartbeatAckCheckInterval = context.setInterval(_heartbeatAckCheck, 1000);
|
||||
lastHeartbeatAckTime = new Date(new Date().getTime() + heartbeatMS); // add a little forgiveness to server for initial heartbeat
|
||||
|
||||
connectDeferred.resolve();
|
||||
app.activeElementEvent('afterConnect', payload);
|
||||
|
||||
}
|
||||
|
||||
function heartbeatAck(header, payload) {
|
||||
lastHeartbeatAckTime = new Date();
|
||||
|
||||
context.JK.CurrentSessionModel.trackChanges(header, payload);
|
||||
}
|
||||
|
||||
function registerLoginAck() {
|
||||
logger.debug("register for loggedIn to set clientId");
|
||||
context.JK.JamServer.registerMessageCallback(context.JK.MessageType.LOGIN_ACK, loggedIn);
|
||||
}
|
||||
|
||||
function registerHeartbeatAck() {
|
||||
logger.debug("register for heartbeatAck");
|
||||
context.JK.JamServer.registerMessageCallback(context.JK.MessageType.HEARTBEAT_ACK, heartbeatAck);
|
||||
}
|
||||
|
||||
function registerSocketClosed() {
|
||||
logger.debug("register for socket closed");
|
||||
context.JK.JamServer.registerOnSocketClosed(socketClosed);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Called whenever the websocket closes; this gives us a chance to cleanup things that should be stopped/cleared
|
||||
* @param in_error did the socket close abnormally?
|
||||
*/
|
||||
function socketClosed(in_error) {
|
||||
|
||||
// tell the backend that we have logged out
|
||||
context.jamClient.OnLoggedOut();
|
||||
|
||||
}
|
||||
|
||||
///////////////////
|
||||
/// RECONNECT /////
|
||||
///////////////////
|
||||
function internetUp() {
|
||||
var start = new Date().getTime();
|
||||
server.connect()
|
||||
.done(function() {
|
||||
guardAgainstRapidTransition(start, performReconnect);
|
||||
})
|
||||
.fail(function() {
|
||||
guardAgainstRapidTransition(start, closedOnReconnectAttempt);
|
||||
});
|
||||
}
|
||||
|
||||
// websocket couldn't connect. let's try again soon
|
||||
function closedOnReconnectAttempt() {
|
||||
failedReconnect();
|
||||
}
|
||||
|
||||
function performReconnect() {
|
||||
|
||||
if($currentDisplay.is('.no-websocket-connection')) {
|
||||
$currentDisplay.hide();
|
||||
|
||||
// TODO: tell certain elements that we've reconnected
|
||||
}
|
||||
else {
|
||||
context.JK.CurrentSessionModel.leaveCurrentSession()
|
||||
.always(function() {
|
||||
window.location.reload();
|
||||
});
|
||||
}
|
||||
server.reconnecting = false;
|
||||
}
|
||||
|
||||
function buildOptions() {
|
||||
return {};
|
||||
}
|
||||
|
||||
function renderDisconnected() {
|
||||
|
||||
var content = null;
|
||||
if(freezeInteraction) {
|
||||
var template = $templateDisconnected.html();
|
||||
var templateHtml = $(context.JK.fillTemplate(template, buildOptions()));
|
||||
templateHtml.find('.reconnect-countdown').html(formatDelaySecs(reconnectDelaySecs()));
|
||||
content = context.JK.Banner.show({
|
||||
html : templateHtml,
|
||||
type: 'reconnect'
|
||||
}) ;
|
||||
}
|
||||
else {
|
||||
var $inSituContent = $(context._.template($templateServerConnection.html(), buildOptions(), { variable: 'data' }));
|
||||
$inSituContent.find('.reconnect-countdown').html(formatDelaySecs(reconnectDelaySecs()));
|
||||
$messageContents.empty();
|
||||
$messageContents.append($inSituContent);
|
||||
$inSituBannerHolder.show();
|
||||
content = $inSituBannerHolder;
|
||||
}
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
function formatDelaySecs(secs) {
|
||||
return $('<span class="countdown-seconds"><span class="countdown">' + secs + '</span> ' + (secs == 1 ? ' second.<span style="visibility:hidden">s</span>' : 'seconds.') + '</span>');
|
||||
}
|
||||
|
||||
function setCountdown($parent) {
|
||||
$parent.find('.reconnect-countdown').html(formatDelaySecs(reconnectDelaySecs()));
|
||||
}
|
||||
|
||||
function renderCouldNotReconnect() {
|
||||
return renderDisconnected();
|
||||
}
|
||||
|
||||
function renderReconnecting() {
|
||||
$currentDisplay.find('.reconnect-progress-msg').text('Attempting to reconnect...')
|
||||
|
||||
if($currentDisplay.is('.no-websocket-connection')) {
|
||||
$currentDisplay.find('.disconnected-reconnect').removeClass('reconnect-enabled').addClass('reconnect-disabled');
|
||||
}
|
||||
else {
|
||||
$currentDisplay.find('.disconnected-reconnect').removeClass('button-orange').addClass('button-grey');
|
||||
}
|
||||
}
|
||||
|
||||
function failedReconnect() {
|
||||
reconnectAttempt += 1;
|
||||
$currentDisplay = renderCouldNotReconnect();
|
||||
beginReconnectPeriod();
|
||||
}
|
||||
|
||||
function guardAgainstRapidTransition(start, nextStep) {
|
||||
var now = new Date().getTime();
|
||||
|
||||
if ((now - start) < 1500) {
|
||||
setTimeout(function() {
|
||||
nextStep();
|
||||
}, 1500 - (now - start))
|
||||
}
|
||||
else {
|
||||
nextStep();
|
||||
}
|
||||
}
|
||||
|
||||
function attemptReconnect() {
|
||||
|
||||
var start = new Date().getTime();
|
||||
|
||||
renderReconnecting();
|
||||
|
||||
rest.serverHealthCheck()
|
||||
.done(function() {
|
||||
guardAgainstRapidTransition(start, internetUp);
|
||||
})
|
||||
.fail(function(xhr, textStatus, errorThrown) {
|
||||
|
||||
if(xhr && xhr.status >= 100) {
|
||||
// we could connect to the server, and it's alive
|
||||
guardAgainstRapidTransition(start, internetUp);
|
||||
}
|
||||
else {
|
||||
guardAgainstRapidTransition(start, failedReconnect);
|
||||
}
|
||||
});
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function clearReconnectTimers() {
|
||||
if(countdownInterval) {
|
||||
clearInterval(countdownInterval);
|
||||
countdownInterval = null;
|
||||
}
|
||||
}
|
||||
|
||||
function beginReconnectPeriod() {
|
||||
// allow user to force reconnect
|
||||
$currentDisplay.find('a.disconnected-reconnect').unbind('click').click(function() {
|
||||
if($(this).is('.button-orange') || $(this).is('.reconnect-enabled')) {
|
||||
clearReconnectTimers();
|
||||
attemptReconnect();
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
if (callbacks !== undefined) {
|
||||
var len = callbacks.length;
|
||||
for(var i = 0; i < len; i++) {
|
||||
try {
|
||||
callbacks[i](message, payload);
|
||||
} catch (ex) {
|
||||
logger.warn('exception in callback for websocket message:' + ex);
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
reconnectingWaitPeriodStart = new Date().getTime();
|
||||
reconnectDueTime = reconnectingWaitPeriodStart + reconnectDelaySecs() * 1000;
|
||||
|
||||
// update count down timer periodically
|
||||
countdownInterval = setInterval(function() {
|
||||
var now = new Date().getTime();
|
||||
if(now > reconnectDueTime) {
|
||||
clearReconnectTimers();
|
||||
attemptReconnect();
|
||||
}
|
||||
else {
|
||||
logger.log("Unexpected message type %s.", message.type);
|
||||
var secondsUntilReconnect = Math.ceil((reconnectDueTime - now) / 1000);
|
||||
$currentDisplay.find('.reconnect-countdown').html(formatDelaySecs(secondsUntilReconnect));
|
||||
}
|
||||
}, 333);
|
||||
}
|
||||
|
||||
function reconnectDelaySecs() {
|
||||
if (reconnectAttempt > reconnectAttemptLookup.length - 1) {
|
||||
return reconnectAttemptLookup[reconnectAttemptLookup.length - 1];
|
||||
}
|
||||
else {
|
||||
return reconnectAttemptLookup[reconnectAttempt];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
server.registerOnSocketClosed = function (callback) {
|
||||
server.socketClosedListeners.push(callback);
|
||||
}
|
||||
|
||||
server.registerMessageCallback = function (messageType, callback) {
|
||||
if (server.dispatchTable[messageType] === undefined) {
|
||||
server.dispatchTable[messageType] = [];
|
||||
}
|
||||
|
||||
server.dispatchTable[messageType].push(callback);
|
||||
};
|
||||
|
||||
server.onClose = function() {
|
||||
logger.log("Socket to server closed.");
|
||||
server.unregisterMessageCallback = function (messageType, callback) {
|
||||
if (server.dispatchTable[messageType] !== undefined) {
|
||||
for (var i = server.dispatchTable[messageType].length; i--;) {
|
||||
if (server.dispatchTable[messageType][i] === callback) {
|
||||
server.dispatchTable[messageType].splice(i, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
closedCleanup(true);
|
||||
if (server.dispatchTable[messageType].length === 0) {
|
||||
delete server.dispatchTable[messageType];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
server.send = function(message) {
|
||||
server.connect = function () {
|
||||
connectDeferred = new $.Deferred();
|
||||
logger.log("server.connect");
|
||||
var uri = context.JK.websocket_gateway_uri; // Set in index.html.erb.
|
||||
//var uri = context.gon.websocket_gateway_uri; // Leaving here for now, as we're looking for a better solution.
|
||||
|
||||
var jsMessage = JSON.stringify(message);
|
||||
server.socket = new context.WebSocket(uri);
|
||||
server.socket.onopen = server.onOpen;
|
||||
server.socket.onmessage = server.onMessage;
|
||||
server.socket.onclose = server.onClose;
|
||||
|
||||
if(message.type != context.JK.MessageType.HEARTBEAT && message.type != context.JK.MessageType.PEER_MESSAGE) {
|
||||
logger.log("server.send(" + jsMessage + ")");
|
||||
}
|
||||
if (server !== undefined && server.socket !== undefined && server.socket.send !== undefined) {
|
||||
server.socket.send(jsMessage);
|
||||
} else {
|
||||
logger.log("Dropped message because server connection is closed.");
|
||||
connectTimeout = setTimeout(function() {
|
||||
connectTimeout = null;
|
||||
if(connectDeferred.state() === 'pending') {
|
||||
connectDeferred.reject();
|
||||
}
|
||||
}, 4000);
|
||||
|
||||
return connectDeferred;
|
||||
};
|
||||
|
||||
server.loginSession = function(sessionId) {
|
||||
var loginMessage;
|
||||
server.close = function (in_error) {
|
||||
logger.log("closing websocket");
|
||||
|
||||
if (!server.signedIn) {
|
||||
logger.log("Not signed in!");
|
||||
// TODO: surface the error
|
||||
return;
|
||||
server.socket.close();
|
||||
|
||||
closedCleanup(in_error);
|
||||
}
|
||||
|
||||
server.rememberLogin = function () {
|
||||
var token, loginMessage;
|
||||
token = $.cookie("remember_token");
|
||||
var clientType = context.jamClient.IsNativeClient() ? 'client' : 'browser';
|
||||
loginMessage = msg_factory.login_with_token(token, null, clientType);
|
||||
server.send(loginMessage);
|
||||
};
|
||||
|
||||
server.onOpen = function () {
|
||||
logger.log("server.onOpen");
|
||||
server.rememberLogin();
|
||||
};
|
||||
|
||||
server.onMessage = function (e) {
|
||||
var message = JSON.parse(e.data),
|
||||
messageType = message.type.toLowerCase(),
|
||||
payload = message[messageType],
|
||||
callbacks = server.dispatchTable[message.type];
|
||||
|
||||
if (message.type != context.JK.MessageType.HEARTBEAT_ACK && message.type != context.JK.MessageType.PEER_MESSAGE) {
|
||||
logger.log("server.onMessage:" + messageType + " payload:" + JSON.stringify(payload));
|
||||
}
|
||||
|
||||
if (callbacks !== undefined) {
|
||||
var len = callbacks.length;
|
||||
for (var i = 0; i < len; i++) {
|
||||
try {
|
||||
callbacks[i](message, payload);
|
||||
} catch (ex) {
|
||||
logger.warn('exception in callback for websocket message:' + ex);
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
logger.log("Unexpected message type %s.", message.type);
|
||||
}
|
||||
};
|
||||
|
||||
loginMessage = msg_factory.login_jam_session(sessionId);
|
||||
server.send(loginMessage);
|
||||
server.onClose = function () {
|
||||
logger.log("Socket to server closed.");
|
||||
|
||||
if(connectDeferred.state() === "pending") {
|
||||
connectDeferred.reject();
|
||||
}
|
||||
|
||||
closedCleanup(true);
|
||||
};
|
||||
|
||||
server.send = function (message) {
|
||||
|
||||
var jsMessage = JSON.stringify(message);
|
||||
|
||||
if (message.type != context.JK.MessageType.HEARTBEAT && message.type != context.JK.MessageType.PEER_MESSAGE) {
|
||||
logger.log("server.send(" + jsMessage + ")");
|
||||
}
|
||||
if (server !== undefined && server.socket !== undefined && server.socket.send !== undefined) {
|
||||
server.socket.send(jsMessage);
|
||||
} else {
|
||||
logger.log("Dropped message because server connection is closed.");
|
||||
}
|
||||
};
|
||||
|
||||
server.loginSession = function (sessionId) {
|
||||
var loginMessage;
|
||||
|
||||
if (!server.signedIn) {
|
||||
logger.log("Not signed in!");
|
||||
// TODO: surface the error
|
||||
return;
|
||||
}
|
||||
|
||||
loginMessage = msg_factory.login_jam_session(sessionId);
|
||||
server.send(loginMessage);
|
||||
};
|
||||
|
||||
/** with the advent of the reliable UDP channel, this is no longer how messages are sent from client-to-clent
|
||||
|
|
@ -163,47 +513,88 @@
|
|||
* @param receiver_id client ID of message to send
|
||||
* @param message the actual message
|
||||
*/
|
||||
server.sendP2PMessage = function(receiver_id, message) {
|
||||
//logger.log("P2P message from [" + server.clientID + "] to [" + receiver_id + "]: " + message);
|
||||
//console.time('sendP2PMessage');
|
||||
var outgoing_msg = msg_factory.client_p2p_message(server.clientID, receiver_id, message);
|
||||
server.send(outgoing_msg);
|
||||
//console.timeEnd('sendP2PMessage');
|
||||
server.sendP2PMessage = function (receiver_id, message) {
|
||||
//logger.log("P2P message from [" + server.clientID + "] to [" + receiver_id + "]: " + message);
|
||||
//console.time('sendP2PMessage');
|
||||
var outgoing_msg = msg_factory.client_p2p_message(server.clientID, receiver_id, message);
|
||||
server.send(outgoing_msg);
|
||||
//console.timeEnd('sendP2PMessage');
|
||||
};
|
||||
|
||||
server.updateNotificationSeen = function(notificationId, notificationCreatedAt) {
|
||||
var time = new Date(notificationCreatedAt);
|
||||
|
||||
if(!notificationCreatedAt) {
|
||||
throw 'invalid value passed to updateNotificationSeen'
|
||||
}
|
||||
|
||||
if(!notificationLastSeenAt) {
|
||||
notificationLastSeenAt = notificationCreatedAt;
|
||||
notificationLastSeen = notificationId;
|
||||
logger.debug("updated notificationLastSeenAt with: " + notificationCreatedAt);
|
||||
}
|
||||
else if(time.getTime() > new Date(notificationLastSeenAt).getTime()) {
|
||||
notificationLastSeenAt = notificationCreatedAt;
|
||||
notificationLastSeen = notificationId;
|
||||
logger.debug("updated notificationLastSeenAt with: " + notificationCreatedAt);
|
||||
}
|
||||
else {
|
||||
logger.debug("ignored notificationLastSeenAt for: " + notificationCreatedAt);
|
||||
}
|
||||
}
|
||||
|
||||
// Message callbacks
|
||||
server.registerMessageCallback(context.JK.MessageType.LOGIN_ACK, function (header, payload) {
|
||||
server.signedIn = true;
|
||||
logger.debug("Handling LOGIN_ACK. Updating client id to " + payload.client_id);
|
||||
server.clientID = payload.client_id;
|
||||
server.publicIP = payload.public_ip;
|
||||
server.connected = true;
|
||||
|
||||
if (context.jamClient !== undefined) {
|
||||
logger.debug("... (handling LOGIN_ACK) Updating backend client, connected to true and clientID to " +
|
||||
payload.client_id);
|
||||
context.jamClient.connected = true;
|
||||
context.jamClient.clientID = server.clientID;
|
||||
}
|
||||
});
|
||||
|
||||
server.registerMessageCallback(context.JK.MessageType.PEER_MESSAGE, function (header, payload) {
|
||||
if (context.jamClient !== undefined) {
|
||||
context.jamClient.P2PMessageReceived(header.from, payload.message);
|
||||
}
|
||||
});
|
||||
|
||||
context.JK.JamServer = server;
|
||||
|
||||
// Message callbacks
|
||||
server.registerMessageCallback(context.JK.MessageType.LOGIN_ACK, function(header, payload) {
|
||||
server.signedIn = true;
|
||||
logger.debug("Handling LOGIN_ACK. Updating client id to " + payload.client_id);
|
||||
server.clientID = payload.client_id;
|
||||
server.publicIP = payload.public_ip;
|
||||
server.connected = true;
|
||||
|
||||
if (context.jamClient !== undefined)
|
||||
{
|
||||
logger.debug("... (handling LOGIN_ACK) Updating backend client, connected to true and clientID to " +
|
||||
payload.client_id);
|
||||
context.jamClient.connected = true;
|
||||
context.jamClient.clientID = server.clientID;
|
||||
}
|
||||
});
|
||||
|
||||
server.registerMessageCallback(context.JK.MessageType.PEER_MESSAGE, function(header, payload) {
|
||||
if (context.jamClient !== undefined)
|
||||
{
|
||||
context.jamClient.P2PMessageReceived(header.from, payload.message);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// Callbacks from jamClient
|
||||
if (context.jamClient !== undefined)
|
||||
{
|
||||
context.jamClient.SendP2PMessage.connect(server.sendP2PMessage);
|
||||
if (context.jamClient !== undefined) {
|
||||
context.jamClient.SendP2PMessage.connect(server.sendP2PMessage);
|
||||
}
|
||||
|
||||
function initialize() {
|
||||
registerLoginAck();
|
||||
registerHeartbeatAck();
|
||||
registerSocketClosed();
|
||||
|
||||
$inSituBanner = $('.server-connection');
|
||||
$inSituBannerHolder = $('.no-websocket-connection');
|
||||
$messageContents = $inSituBannerHolder.find('.message-contents');
|
||||
$dialog = $('#banner');
|
||||
$templateServerConnection = $('#template-server-connection');
|
||||
$templateDisconnected = $('#template-disconnected');
|
||||
|
||||
if($inSituBanner.length != 1) { throw "found wrong number of .server-connection: " + $inSituBanner.length; }
|
||||
if($inSituBannerHolder.length != 1) { throw "found wrong number of .no-websocket-connection: " + $inSituBannerHolder.length; }
|
||||
if($messageContents.length != 1) { throw "found wrong number of .message-contents: " + $messageContents.length; }
|
||||
if($dialog.length != 1) { throw "found wrong number of #banner: " + $dialog.length; }
|
||||
if($templateServerConnection.length != 1) { throw "found wrong number of #template-server-connection: " + $templateServerConnection.length; }
|
||||
if($templateDisconnected.length != 1) { throw "found wrong number of #template-disconnected: " + $templateDisconnected.length; }
|
||||
}
|
||||
|
||||
this.initialize = initialize;
|
||||
this.initiateReconnect = initiateReconnect;
|
||||
|
||||
return this;
|
||||
}
|
||||
})(window, jQuery);
|
||||
|
|
@ -25,7 +25,7 @@
|
|||
return newContent;
|
||||
}
|
||||
|
||||
$('#banner').show()
|
||||
$('#banner').attr('data-type', options.type).show()
|
||||
$('#banner_overlay').show()
|
||||
|
||||
// return the core of the banner so that caller can attach event handlers to newly created HTML
|
||||
|
|
|
|||
|
|
@ -123,6 +123,11 @@
|
|||
return false;
|
||||
}
|
||||
|
||||
if(!context.JK.JamServer.connected) {
|
||||
app.notifyAlert("Not Connected", 'To create or join a session, you must be connected to the server.');
|
||||
return false;
|
||||
}
|
||||
|
||||
// If user hasn't completed FTUE - do so now.
|
||||
if (!(context.JK.hasOneConfiguredDevice())) {
|
||||
app.afterFtue = function() { submitForm(evt); };
|
||||
|
|
|
|||
|
|
@ -342,6 +342,12 @@
|
|||
}
|
||||
|
||||
function afterShow(data) {
|
||||
if(!context.JK.JamServer.connected) {
|
||||
app.notifyAlert("Not Connected", 'To create or join a session, you must be connected to the server.');
|
||||
window.location = '/client#/home'
|
||||
return;
|
||||
}
|
||||
|
||||
clearResults();
|
||||
buildQuery();
|
||||
refreshDisplay();
|
||||
|
|
|
|||
|
|
@ -2,15 +2,26 @@
|
|||
|
||||
"use strict";
|
||||
context.JK = context.JK || {};
|
||||
context.JK.BandHoverBubble = function(bandId, position) {
|
||||
context.JK.BandHoverBubble = function(bandId, x, y) {
|
||||
|
||||
var logger = context.JK.logger;
|
||||
var rest = context.JK.Rest();
|
||||
var instrumentLogoMap = context.JK.getInstrumentIconMap24();
|
||||
var hoverSelector = "#band-hover";
|
||||
|
||||
this.showBubble = function() {
|
||||
$(hoverSelector).css({left: position.left-100, top: position.top});
|
||||
var mouseLeft = x < (document.body.clientWidth / 2);
|
||||
var mouseTop = y < (document.body.clientHeight / 2);
|
||||
var css = {};
|
||||
if (mouseLeft)
|
||||
css.left = x + 10 + 'px';
|
||||
else
|
||||
css.left = x - (7 + $(hoverSelector).width()) + 'px';
|
||||
if (mouseTop)
|
||||
css.top = y + 10 + 'px';
|
||||
else
|
||||
css.top = y - (7 + $(hoverSelector).height()) + 'px';
|
||||
|
||||
$(hoverSelector).css(css);
|
||||
$(hoverSelector).fadeIn(500);
|
||||
|
||||
rest.getBand(bandId)
|
||||
|
|
@ -28,7 +39,7 @@
|
|||
instrumentHtml = '<td><div class="nowrap">';
|
||||
if (val.instruments) { // @FIXME: edge case for Test user that has no instruments?
|
||||
$.each(val.instruments, function(index, instrument) {
|
||||
instrumentHtml += '<img src="' + instrumentLogoMap[instrument.instrument_id] + '" width="24" height="24" /> ';
|
||||
instrumentHtml += '<img src="' + context.JK.getInstrumentIcon24(instrument.instrument_id) + '" width="24" height="24" /> ';
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
"use strict";
|
||||
context.JK = context.JK || {};
|
||||
context.JK.FanHoverBubble = function(userId, position) {
|
||||
context.JK.FanHoverBubble = function(userId, x, y) {
|
||||
|
||||
var logger = context.JK.logger;
|
||||
var rest = context.JK.Rest();
|
||||
|
|
@ -10,7 +10,19 @@
|
|||
var hoverSelector = "#fan-hover";
|
||||
|
||||
this.showBubble = function() {
|
||||
$(hoverSelector).css({left: position.left-100, top: position.top});
|
||||
var mouseLeft = x < (document.body.clientWidth / 2);
|
||||
var mouseTop = y < (document.body.clientHeight / 2);
|
||||
var css = {};
|
||||
if (mouseLeft)
|
||||
css.left = x + 10 + 'px';
|
||||
else
|
||||
css.left = x - (7 + $(hoverSelector).width()) + 'px';
|
||||
if (mouseTop)
|
||||
css.top = y + 10 + 'px';
|
||||
else
|
||||
css.top = y - (7 + $(hoverSelector).height()) + 'px';
|
||||
|
||||
$(hoverSelector).css(css);
|
||||
$(hoverSelector).fadeIn(500);
|
||||
|
||||
rest.getUserDetail({id: userId})
|
||||
|
|
|
|||
|
|
@ -2,15 +2,27 @@
|
|||
|
||||
"use strict";
|
||||
context.JK = context.JK || {};
|
||||
context.JK.MusicianHoverBubble = function(userId, position) {
|
||||
context.JK.MusicianHoverBubble = function(userId, x, y) {
|
||||
|
||||
var logger = context.JK.logger;
|
||||
var rest = context.JK.Rest();
|
||||
var instrumentLogoMap = context.JK.getInstrumentIconMap24();
|
||||
var hoverSelector = "#musician-hover";
|
||||
|
||||
this.showBubble = function() {
|
||||
$(hoverSelector).css({left: position.left-100, top: position.top});
|
||||
|
||||
var mouseLeft = x < (document.body.clientWidth / 2);
|
||||
var mouseTop = y < (document.body.clientHeight / 2);
|
||||
var css = {};
|
||||
if (mouseLeft)
|
||||
css.left = x + 10 + 'px';
|
||||
else
|
||||
css.left = x - (7 + $(hoverSelector).width()) + 'px';
|
||||
if (mouseTop)
|
||||
css.top = y + 10 + 'px';
|
||||
else
|
||||
css.top = y - (7 + $(hoverSelector).height()) + 'px';
|
||||
|
||||
$(hoverSelector).css(css);
|
||||
$(hoverSelector).fadeIn(500);
|
||||
|
||||
rest.getUserDetail({id: userId})
|
||||
|
|
@ -20,7 +32,7 @@
|
|||
// instruments
|
||||
var instrumentHtml = '';
|
||||
$.each(response.instruments, function(index, val) {
|
||||
instrumentHtml += '<div class="left mr10 mb"><img src="' + instrumentLogoMap[val.instrument_id] + '" width="24" height="24" /></div>';
|
||||
instrumentHtml += '<div class="left mr10 mb"><img src="' + context.JK.getInstrumentIcon24(val.instrument_id) + '" width="24" height="24" /></div>';
|
||||
});
|
||||
|
||||
// followings
|
||||
|
|
|
|||
|
|
@ -2,10 +2,9 @@
|
|||
|
||||
"use strict";
|
||||
context.JK = context.JK || {};
|
||||
context.JK.RecordingHoverBubble = function(recordingId, position) {
|
||||
context.JK.RecordingHoverBubble = function(recordingId, x, y) {
|
||||
var logger = context.JK.logger;
|
||||
var rest = context.JK.Rest();
|
||||
var instrumentLogoMap = context.JK.getInstrumentIconMap24();
|
||||
var hoverSelector = "#recording-hover";
|
||||
|
||||
function deDupTracks(recordedTracks) {
|
||||
|
|
@ -39,7 +38,19 @@
|
|||
}
|
||||
|
||||
this.showBubble = function() {
|
||||
$(hoverSelector).css({left: position.left-100, top: position.top+20});
|
||||
var mouseLeft = x < (document.body.clientWidth / 2);
|
||||
var mouseTop = y < (document.body.clientHeight / 2);
|
||||
var css = {};
|
||||
if (mouseLeft)
|
||||
css.left = x + 10 + 'px';
|
||||
else
|
||||
css.left = x - (7 + $(hoverSelector).width()) + 'px';
|
||||
if (mouseTop)
|
||||
css.top = y + 10 + 'px';
|
||||
else
|
||||
css.top = y - (7 + $(hoverSelector).height()) + 'px';
|
||||
|
||||
$(hoverSelector).css(css);
|
||||
$(hoverSelector).fadeIn(500);
|
||||
|
||||
rest.getClaimedRecording(recordingId)
|
||||
|
|
@ -61,8 +72,8 @@
|
|||
|
||||
instrumentHtml = '<td><div class="nowrap">';
|
||||
$.each(val.instrument_ids, function(index, val) {
|
||||
instrumentHtml += '<img src="' + instrumentLogoMap[val] + '" width="24" height="24" /> ';
|
||||
})
|
||||
instrumentHtml += '<img src="' + context.JK.getInstrumentIcon24(val) + '" width="24" height="24" /> ';
|
||||
});
|
||||
instrumentHtml += '</div></td>';
|
||||
|
||||
musicianHtml += instrumentHtml;
|
||||
|
|
@ -77,7 +88,7 @@
|
|||
claimedRecordingId: claimedRecording.id,
|
||||
name: claimedRecording.name,
|
||||
genre: claimedRecording.genre_id.toUpperCase(),
|
||||
created_at: context.JK.formatDateTime(recording.created_at),
|
||||
created_at: $.timeago(recording.created_at),
|
||||
description: response.description ? response.description : "",
|
||||
play_count: recording.play_count,
|
||||
comment_count: recording.comment_count,
|
||||
|
|
|
|||
|
|
@ -2,15 +2,26 @@
|
|||
|
||||
"use strict";
|
||||
context.JK = context.JK || {};
|
||||
context.JK.SessionHoverBubble = function(sessionId, position) {
|
||||
context.JK.SessionHoverBubble = function(sessionId, x, y) {
|
||||
|
||||
var logger = context.JK.logger;
|
||||
var rest = context.JK.Rest();
|
||||
var instrumentLogoMap = context.JK.getInstrumentIconMap24();
|
||||
var hoverSelector = "#session-hover";
|
||||
|
||||
this.showBubble = function() {
|
||||
$(hoverSelector).css({left: position.left-100, top: position.top+10});
|
||||
var mouseLeft = x < (document.body.clientWidth / 2);
|
||||
var mouseTop = y < (document.body.clientHeight / 2);
|
||||
var css = {};
|
||||
if (mouseLeft)
|
||||
css.left = x + 10 + 'px';
|
||||
else
|
||||
css.left = x - (7 + $(hoverSelector).width()) + 'px';
|
||||
if (mouseTop)
|
||||
css.top = y + 10 + 'px';
|
||||
else
|
||||
css.top = y - (7 + $(hoverSelector).height()) + 'px';
|
||||
|
||||
$(hoverSelector).css(css);
|
||||
$(hoverSelector).fadeIn(500);
|
||||
|
||||
rest.getSessionHistory(sessionId)
|
||||
|
|
@ -28,7 +39,7 @@
|
|||
instrumentHtml = '<td><div class="nowrap">';
|
||||
var instruments = val.instruments.split("|");
|
||||
$.each(instruments, function(index, instrument) {
|
||||
instrumentHtml += '<img src="' + instrumentLogoMap[instrument] + '" width="24" height="24" /> ';
|
||||
instrumentHtml += '<img src="' + context.JK.getInstrumentIcon24(instrument) + '" width="24" height="24" /> ';
|
||||
});
|
||||
|
||||
instrumentHtml += '</div></td>';
|
||||
|
|
@ -45,7 +56,7 @@
|
|||
genre: response.genres.toUpperCase(),
|
||||
comment_count: response.comment_count,
|
||||
like_count: response.like_count,
|
||||
created_at: context.JK.formatDateTime(response.created_at),
|
||||
created_at: $.timeago(response.created_at),
|
||||
musicians: musicianHtml
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -20,16 +20,9 @@
|
|||
var app;
|
||||
var logger = context.JK.logger;
|
||||
var rest = context.JK.Rest();
|
||||
var heartbeatInterval = null;
|
||||
var heartbeatMS = null;
|
||||
var heartbeatMissedMS = 10000; // if 5 seconds go by and we haven't seen a heartbeat ack, get upset
|
||||
var inBadState = false;
|
||||
var lastHeartbeatAckTime = null;
|
||||
var lastHeartbeatFound = false;
|
||||
var heartbeatAckCheckInterval = null;
|
||||
var userDeferred = null;
|
||||
var notificationLastSeenAt = undefined;
|
||||
var notificationLastSeen = undefined;
|
||||
|
||||
|
||||
var opts = {
|
||||
inClient: true, // specify false if you want the app object but none of the client-oriented features
|
||||
|
|
@ -72,58 +65,6 @@
|
|||
$(routes.handler);
|
||||
}
|
||||
|
||||
function _heartbeatAckCheck() {
|
||||
|
||||
// if we've seen an ack to the latest heartbeat, don't bother with checking again
|
||||
// this makes us resilient to front-end hangs
|
||||
if (lastHeartbeatFound) {
|
||||
return;
|
||||
}
|
||||
|
||||
// check if the server is still sending heartbeat acks back down
|
||||
// this logic equates to 'if we have not received a heartbeat within heartbeatMissedMS, then get upset
|
||||
if (new Date().getTime() - lastHeartbeatAckTime.getTime() > heartbeatMissedMS) {
|
||||
logger.error("no heartbeat ack received from server after ", heartbeatMissedMS, " seconds . giving up on socket connection");
|
||||
context.JK.JamServer.close(true);
|
||||
}
|
||||
else {
|
||||
lastHeartbeatFound = true;
|
||||
}
|
||||
}
|
||||
|
||||
function _heartbeat() {
|
||||
if (app.heartbeatActive) {
|
||||
var message = context.JK.MessageFactory.heartbeat(notificationLastSeen, notificationLastSeenAt);
|
||||
notificationLastSeenAt = undefined;
|
||||
notificationLastSeen = undefined;
|
||||
context.JK.JamServer.send(message);
|
||||
lastHeartbeatFound = false;
|
||||
}
|
||||
}
|
||||
|
||||
function loggedIn(header, payload) {
|
||||
app.clientId = payload.client_id;
|
||||
|
||||
// tell the backend that we have logged in
|
||||
context.jamClient.OnLoggedIn(payload.user_id, payload.token);
|
||||
|
||||
$.cookie('client_id', payload.client_id);
|
||||
app.initAfterConnect();
|
||||
|
||||
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);
|
||||
heartbeatAckCheckInterval = context.setInterval(_heartbeatAckCheck, 1000);
|
||||
lastHeartbeatAckTime = new Date(new Date().getTime() + heartbeatMS); // add a little forgiveness to server for initial heartbeat
|
||||
}
|
||||
|
||||
function heartbeatAck(header, payload) {
|
||||
lastHeartbeatAckTime = new Date();
|
||||
|
||||
context.JK.CurrentSessionModel.trackChanges(header, payload);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* This occurs when the websocket gateway loses a connection to the backend messaging system,
|
||||
* resulting in severe loss of functionality
|
||||
|
|
@ -153,37 +94,6 @@
|
|||
context.jamClient.OnDownloadAvailable();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called whenever the websocket closes; this gives us a chance to cleanup things that should be stopped/cleared
|
||||
* @param in_error did the socket close abnormally?
|
||||
*/
|
||||
function socketClosed(in_error) {
|
||||
|
||||
// tell the backend that we have logged out
|
||||
context.jamClient.OnLoggedOut();
|
||||
|
||||
// stop future heartbeats
|
||||
if (heartbeatInterval != null) {
|
||||
clearInterval(heartbeatInterval);
|
||||
heartbeatInterval = null;
|
||||
}
|
||||
|
||||
// stop checking for heartbeat acks
|
||||
if (heartbeatAckCheckInterval != null) {
|
||||
clearTimeout(heartbeatAckCheckInterval);
|
||||
heartbeatAckCheckInterval = null;
|
||||
}
|
||||
}
|
||||
|
||||
function registerLoginAck() {
|
||||
logger.debug("register for loggedIn to set clientId");
|
||||
context.JK.JamServer.registerMessageCallback(context.JK.MessageType.LOGIN_ACK, loggedIn);
|
||||
}
|
||||
|
||||
function registerHeartbeatAck() {
|
||||
logger.debug("register for heartbeatAck");
|
||||
context.JK.JamServer.registerMessageCallback(context.JK.MessageType.HEARTBEAT_ACK, heartbeatAck);
|
||||
}
|
||||
|
||||
function registerBadStateError() {
|
||||
logger.debug("register for server_bad_state_error");
|
||||
|
|
@ -200,12 +110,6 @@
|
|||
context.JK.JamServer.registerMessageCallback(context.JK.MessageType.DOWNLOAD_AVAILABLE, downloadAvailable);
|
||||
}
|
||||
|
||||
|
||||
function registerSocketClosed() {
|
||||
logger.debug("register for socket closed");
|
||||
context.JK.JamServer.registerOnSocketClosed(socketClosed);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic error handler for Ajax calls.
|
||||
*/
|
||||
|
|
@ -259,7 +163,7 @@
|
|||
* being shown or hidden.
|
||||
* @screen is a string corresponding to the screen's layout-id attribute
|
||||
* @handler is an object with up to four optional keys:
|
||||
* beforeHide, afterHide, beforeShow, afterShow, which should all have
|
||||
* beforeHide, afterHide, beforeShow, afterShow, beforeDisconnect, which should all have
|
||||
* functions as values. If there is data provided by the screen's route
|
||||
* it will be provided to these functions.
|
||||
*/
|
||||
|
|
@ -387,26 +291,12 @@
|
|||
return userDeferred;
|
||||
}
|
||||
|
||||
this.activeElementEvent = function(evtName, data) {
|
||||
return this.layout.activeElementEvent(evtName, data);
|
||||
}
|
||||
|
||||
this.updateNotificationSeen = function(notificationId, notificationCreatedAt) {
|
||||
var time = new Date(notificationCreatedAt);
|
||||
|
||||
if(!notificationCreatedAt) {
|
||||
throw 'invalid value passed to updateNotificationSeen'
|
||||
}
|
||||
|
||||
if(!notificationLastSeenAt) {
|
||||
notificationLastSeenAt = notificationCreatedAt;
|
||||
notificationLastSeen = notificationId;
|
||||
logger.debug("updated notificationLastSeenAt with: " + notificationCreatedAt);
|
||||
}
|
||||
else if(time.getTime() > new Date(notificationLastSeenAt).getTime()) {
|
||||
notificationLastSeenAt = notificationCreatedAt;
|
||||
notificationLastSeen = notificationId;
|
||||
logger.debug("updated notificationLastSeenAt with: " + notificationCreatedAt);
|
||||
}
|
||||
else {
|
||||
logger.debug("ignored notificationLastSeenAt for: " + notificationCreatedAt);
|
||||
}
|
||||
context.JK.JamServer.updateNotificationSeen(notificationId, notificationCreatedAt);
|
||||
}
|
||||
|
||||
this.unloadFunction = function () {
|
||||
|
|
@ -437,11 +327,8 @@
|
|||
userDeferred = rest.getUserDetail();
|
||||
|
||||
if (opts.inClient) {
|
||||
registerLoginAck();
|
||||
registerHeartbeatAck();
|
||||
registerBadStateRecovered();
|
||||
registerBadStateError();
|
||||
registerSocketClosed();
|
||||
registerDownloadAvailable();
|
||||
context.JK.FaderHelpers.initialize();
|
||||
context.window.onunload = this.unloadFunction;
|
||||
|
|
|
|||
|
|
@ -27,6 +27,8 @@
|
|||
// privates
|
||||
var logger = context.JK.logger;
|
||||
|
||||
var NOT_HANDLED = "not handled";
|
||||
|
||||
var me = null; // Reference to this instance for context sanity.
|
||||
|
||||
var opts = {
|
||||
|
|
@ -36,6 +38,7 @@
|
|||
notifyGutter: 10,
|
||||
collapsedSidebar: 30,
|
||||
panelHeaderHeight: 36,
|
||||
alwaysOpenPanelHeaderHeight:78, // for the search bar
|
||||
gutter: 60, // Margin around the whole UI
|
||||
screenMargin: 0, // Margin around screens (not headers/sidebar)
|
||||
gridOuterMargin: 6, // Outer margin on Grids (added to screenMargin if screen)
|
||||
|
|
@ -74,7 +77,7 @@
|
|||
}
|
||||
|
||||
function setInitialExpandedSidebarPanel() {
|
||||
expandedPanel = $('[layout="panel"]').first().attr("layout-id");
|
||||
expandedPanel = 'panelFriends';
|
||||
}
|
||||
|
||||
function layout() {
|
||||
|
|
@ -253,10 +256,9 @@
|
|||
}
|
||||
var $expandedPanel = $('[layout-id="' + expandedPanel + '"]');
|
||||
var $expandedPanelContents = $expandedPanel.find('[layout-panel="contents"]');
|
||||
var combinedHeaderHeight = $('[layout-panel="contents"]').length * opts.panelHeaderHeight;
|
||||
var searchHeight = $('.sidebar .search').first().height();
|
||||
var combinedHeaderHeight = ($('[layout-panel="contents"]').length - 1) * opts.panelHeaderHeight + opts.alwaysOpenPanelHeaderHeight;
|
||||
var expanderHeight = $('[layout-sidebar-expander]').height();
|
||||
var expandedPanelHeight = sidebarHeight - (combinedHeaderHeight + expanderHeight + searchHeight);
|
||||
var expandedPanelHeight = sidebarHeight - (combinedHeaderHeight + expanderHeight);
|
||||
$('[layout-panel="contents"]').hide();
|
||||
$('[layout-panel="contents"]').css({"height": "1px"});
|
||||
$expandedPanelContents.show();
|
||||
|
|
@ -315,11 +317,16 @@
|
|||
// This allows dialogs to use the notification.
|
||||
$('[layout="notify"]').css({"z-index": "1001", "padding": "20px"});
|
||||
$('[layout="panel"]').css({position: 'relative'});
|
||||
$('[layout-panel="expanded"] [layout-panel="header"]').css({
|
||||
margin: "0px",
|
||||
padding: "0px",
|
||||
height: opts.panelHeaderHeight + "px"
|
||||
});
|
||||
var $headers = $('[layout-panel="expanded"] [layout-panel="header"]');
|
||||
context._.each($headers, function($header) {
|
||||
$header = $($header);
|
||||
var isAlwaysOpenHeader = $header.is('.always-open');
|
||||
$header.css({
|
||||
margin: "0px",
|
||||
padding: "0px",
|
||||
height: (isAlwaysOpenHeader ? opts.alwaysOpenPanelHeaderHeight : opts.panelHeaderHeight) + "px"
|
||||
});
|
||||
})
|
||||
$('[layout-grid]').css({
|
||||
position: "relative"
|
||||
});
|
||||
|
|
@ -436,18 +443,29 @@
|
|||
return screenBindings[screen][evtName].call(me, data);
|
||||
}
|
||||
}
|
||||
return NOT_HANDLED;
|
||||
}
|
||||
|
||||
function dialogEvent(dialog, evtName, data) {
|
||||
if (dialog && dialog in dialogBindings) {
|
||||
if (evtName in dialogBindings[dialog]) {
|
||||
var result = dialogBindings[dialog][evtName].call(me, data);
|
||||
if (result === false) {
|
||||
return false;
|
||||
}
|
||||
return dialogBindings[dialog][evtName].call(me, data);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
return NOT_HANDLED;
|
||||
}
|
||||
|
||||
function activeElementEvent(evtName, data) {
|
||||
var result = {};
|
||||
var currDialog = currentDialog();
|
||||
if(currDialog) {
|
||||
result.dialog = dialogEvent(currDialog.attr('layout-id'), evtName, data);
|
||||
}
|
||||
|
||||
if(currentScreen) {
|
||||
result.screen = screenEvent(currentScreen, evtName, data);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function onHashChange(e, postFunction) {
|
||||
|
|
@ -614,7 +632,7 @@
|
|||
}
|
||||
|
||||
function showDialog(dialog, options) {
|
||||
if (!dialogEvent(dialog, 'beforeShow', options)) {
|
||||
if (dialogEvent(dialog, 'beforeShow', options) === false) {
|
||||
return;
|
||||
}
|
||||
var $overlay = $('.dialog-overlay')
|
||||
|
|
@ -896,14 +914,30 @@
|
|||
return isNoisyNotification(payload);
|
||||
}
|
||||
|
||||
this.shouldFreezeAppOnDisconnect = function() {
|
||||
return shouldFreezeAppOnDisconnect();
|
||||
}
|
||||
|
||||
this.isDialogShowing = function() {
|
||||
return isDialogShowing();
|
||||
}
|
||||
|
||||
this.activeElementEvent = function(evtName, data) {
|
||||
return activeElementEvent(evtName, data);
|
||||
}
|
||||
|
||||
this.close = function (evt) {
|
||||
close(evt);
|
||||
};
|
||||
|
||||
this.beforeDisconnect = function() {
|
||||
fireEvents();
|
||||
}
|
||||
|
||||
this.afterReconnect = function() {
|
||||
fireEvents();
|
||||
}
|
||||
|
||||
this.closeDialog = closeDialog;
|
||||
|
||||
this.handleDialogState = handleDialogState;
|
||||
|
|
|
|||
|
|
@ -30,6 +30,18 @@
|
|||
setCount(count + 1);
|
||||
}
|
||||
|
||||
function decrementNotificationCount() {
|
||||
var count = parseInt($count.text());
|
||||
if(count > 0) {
|
||||
count = count - 1;
|
||||
setCount(count);
|
||||
if(count == 0) {
|
||||
lowlightCount();
|
||||
missedNotificationsWhileAway = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// set the element to white, and pulse it down to the un-highlighted value 2x, then set
|
||||
function pulseToDark() {
|
||||
logger.debug("pulsing notification badge")
|
||||
|
|
@ -348,6 +360,7 @@
|
|||
|
||||
|
||||
function deleteNotification(notificationId) {
|
||||
console.trace();
|
||||
var url = "/api/users/" + context.JK.currentUserId + "/notifications/" + notificationId;
|
||||
$.ajax({
|
||||
type: "DELETE",
|
||||
|
|
@ -356,8 +369,11 @@
|
|||
url: url,
|
||||
processData: false,
|
||||
success: function(response) {
|
||||
$('li[notification-id=' + notificationId + ']').hide();
|
||||
//decrementNotificationCount();
|
||||
var notification = $('li[notification-id=' + notificationId + ']');
|
||||
if(notification.length > 0) {
|
||||
decrementNotificationCount();
|
||||
}
|
||||
notification.remove();
|
||||
},
|
||||
error: app.ajaxError
|
||||
});
|
||||
|
|
|
|||
|
|
@ -77,6 +77,7 @@
|
|||
}
|
||||
|
||||
context.JK.SearchResultScreen.onSearchSuccess = function(response) {
|
||||
$('#sidebar-search-results').show();
|
||||
searchResults(response, true);
|
||||
searchResults(response, false);
|
||||
context.JK.bindHoverEvents();
|
||||
|
|
@ -183,11 +184,6 @@
|
|||
if (isSidebar) {
|
||||
// show header
|
||||
$('#sidebar-search-header').show();
|
||||
// hide panels
|
||||
$('[layout-panel="contents"]').hide();
|
||||
$('[layout-panel="contents"]').css({"height": "1px"});
|
||||
// resize search results area
|
||||
$('#sidebar-search-results').height(context.JK.Sidebar.getHeight() + 'px');
|
||||
}
|
||||
else {
|
||||
$('#result-count').html(resultCount);
|
||||
|
|
|
|||
|
|
@ -139,8 +139,11 @@
|
|||
shareDialog.initialize(context.JK.FacebookHelperInstance);
|
||||
}
|
||||
|
||||
function beforeDisconnect() {
|
||||
return { freezeInteraction: true };
|
||||
}
|
||||
|
||||
function alertCallback(type, text) {
|
||||
function alertCallback(type, text) {
|
||||
|
||||
function timeCallback() {
|
||||
var start = new Date();
|
||||
|
|
@ -275,6 +278,13 @@
|
|||
|
||||
function afterShow(data) {
|
||||
|
||||
if(!context.JK.JamServer.connected) {
|
||||
promptLeave = false;
|
||||
app.notifyAlert("Not Connected", 'To create or join a session, you must be connected to the server.');
|
||||
window.location = '/client#/home'
|
||||
return;
|
||||
}
|
||||
|
||||
if (!context.JK.hasOneConfiguredDevice() || context.JK.TrackHelpers.getUserTracks(context.jamClient).length == 0) {
|
||||
app.afterFtue = function() { initializeSession(); };
|
||||
app.cancelFtue = function() { promptLeave = false; window.location = '/client#/home' };
|
||||
|
|
@ -1516,7 +1526,8 @@
|
|||
'beforeShow': beforeShow,
|
||||
'afterShow': afterShow,
|
||||
'beforeHide': beforeHide,
|
||||
'beforeLeave' : beforeLeave
|
||||
'beforeLeave' : beforeLeave,
|
||||
'beforeDisconnect' : beforeDisconnect,
|
||||
};
|
||||
app.bindScreen('session', screenBindings);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -151,6 +151,11 @@
|
|||
var $parentRow = $('tr[id=' + session.id + ']', tbGroup);
|
||||
|
||||
$('.join-link', $parentRow).click(function(evt) {
|
||||
if(!context.JK.JamServer.connected) {
|
||||
app.notifyAlert("Not Connected", 'To create or join a session, you must be connected to the server.');
|
||||
return false;
|
||||
}
|
||||
|
||||
// If no FTUE, show that first.
|
||||
if (!context.JK.hasOneConfiguredDevice() || context.JK.TrackHelpers.getUserTracks(context.jamClient).length == 0) {
|
||||
app.afterFtue = function() { joinClick(session.id); };
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@
|
|||
// we track all the clientIDs of all the participants ever seen by this session, so that we can reliably convert a clientId from the backend into a username/avatar
|
||||
var participantsEverSeen = {};
|
||||
|
||||
function id() {
|
||||
function id() {
|
||||
return currentSession ? currentSession.id : null;
|
||||
}
|
||||
|
||||
|
|
@ -379,67 +379,11 @@
|
|||
});
|
||||
}
|
||||
|
||||
function reconnect() {
|
||||
context.JK.CurrentSessionModel.leaveCurrentSession()
|
||||
.always(function() {
|
||||
window.location.reload();
|
||||
});
|
||||
}
|
||||
|
||||
function registerReconnect(content) {
|
||||
$('a.disconnected-reconnect', content).click(function() {
|
||||
|
||||
var template = $('#template-reconnecting').html();
|
||||
var templateHtml = context.JK.fillTemplate(template, null);
|
||||
var content = context.JK.Banner.show({
|
||||
html : template
|
||||
});
|
||||
|
||||
rest.serverHealthCheck()
|
||||
.done(function() {
|
||||
reconnect();
|
||||
})
|
||||
.fail(function(xhr, textStatus, errorThrown) {
|
||||
|
||||
if(xhr && xhr.status >= 100) {
|
||||
// we could connect to the server, and it's alive
|
||||
reconnect();
|
||||
}
|
||||
else {
|
||||
var template = $('#template-could-not-reconnect').html();
|
||||
var templateHtml = context.JK.fillTemplate(template, null);
|
||||
var content = context.JK.Banner.show({
|
||||
html : template
|
||||
});
|
||||
|
||||
registerReconnect(content);
|
||||
}
|
||||
});
|
||||
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
function onWebsocketDisconnected(in_error) {
|
||||
|
||||
if(app.clientUpdating) {
|
||||
// we don't want to do a 'cover the whole screen' dialog
|
||||
// because the client update is already showing.
|
||||
return;
|
||||
}
|
||||
|
||||
// kill the streaming of the session immediately
|
||||
logger.debug("calling jamClient.LeaveSession for clientId=" + clientId);
|
||||
client.LeaveSession({ sessionID: currentSessionId });
|
||||
|
||||
if(in_error) {
|
||||
var template = $('#template-disconnected').html();
|
||||
var templateHtml = context.JK.fillTemplate(template, null);
|
||||
var content = context.JK.Banner.show({
|
||||
html : template
|
||||
}) ;
|
||||
registerReconnect(content);
|
||||
}
|
||||
}
|
||||
|
||||
// returns a deferred object
|
||||
|
|
@ -456,7 +400,6 @@
|
|||
if(foundParticipant) {
|
||||
return $.Deferred().resolve(foundParticipant.user).promise();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// TODO: find it via some REST API if not found?
|
||||
|
|
|
|||
|
|
@ -104,40 +104,14 @@
|
|||
}
|
||||
}
|
||||
|
||||
context.JK.Sidebar.getHeight = function() {
|
||||
// TODO: refactor this - copied from layout.js
|
||||
var sidebarHeight = $(context).height() - 75 - 2 * 60 + $('[layout-sidebar-expander]').height();
|
||||
var combinedHeaderHeight = $('[layout-panel="contents"]').length * 36;
|
||||
var searchHeight = $('.sidebar .search').first().height();
|
||||
var expanderHeight = $('[layout-sidebar-expander]').height();
|
||||
var expandedPanelHeight = sidebarHeight - (combinedHeaderHeight + expanderHeight + searchHeight);
|
||||
return expandedPanelHeight;
|
||||
}
|
||||
|
||||
function showFriendsPanel() {
|
||||
|
||||
var $expandedPanelContents = $('[layout-id="panelFriends"] [layout-panel="contents"]');
|
||||
var expandedPanelHeight = context.JK.Sidebar.getHeight();
|
||||
|
||||
// hide all other contents
|
||||
$('[layout-panel="contents"]').hide();
|
||||
$('[layout-panel="contents"]').css({"height": "1px"});
|
||||
|
||||
// show the appropriate contens
|
||||
$expandedPanelContents.show();
|
||||
$expandedPanelContents.animate({"height": expandedPanelHeight + "px"}, 400);
|
||||
}
|
||||
|
||||
function hideSearchResults() {
|
||||
emptySearchResults();
|
||||
$('#search-input').val('');
|
||||
$('#sidebar-search-header').hide();
|
||||
showFriendsPanel();
|
||||
}
|
||||
|
||||
function emptySearchResults() {
|
||||
$('#sidebar-search-results').empty();
|
||||
$('#sidebar-search-results').height('0px');
|
||||
}
|
||||
|
||||
var delay = (function(){
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@
|
|||
var $previousMessagesScroller = null;
|
||||
var $sendTextMessage = null;
|
||||
var $form = null;
|
||||
var $interactionBlocker = null;
|
||||
var $disconnectedMsg = null;
|
||||
var $textBox = null;
|
||||
var userLookup = null;
|
||||
var otherId = null;
|
||||
|
|
@ -48,10 +50,14 @@
|
|||
}
|
||||
|
||||
function sendMessage() {
|
||||
if(!context.JK.JamServer.connected) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var msg = $textBox.val();
|
||||
if(!msg || msg == '') {
|
||||
// don't bother the server with empty messages
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!sendingMessage) {
|
||||
|
|
@ -136,35 +142,30 @@
|
|||
$textBox.focus();
|
||||
}
|
||||
|
||||
function beforeShow(args) {
|
||||
|
||||
app.layout.closeDialog('text-message') // ensure no others are showing. this is a singleton dialog
|
||||
|
||||
function renderDialog() {
|
||||
app.user()
|
||||
.done(function(userDetail) {
|
||||
.done(function (userDetail) {
|
||||
user = userDetail;
|
||||
|
||||
var other = args.d1;
|
||||
|
||||
if(!other) throw "other must be specified in TextMessageDialog"
|
||||
otherId = other;
|
||||
|
||||
showing = true;
|
||||
|
||||
userLookup[user.id] = user;
|
||||
|
||||
rest.getUserDetail({id: otherId})
|
||||
.done(function(otherUser) {
|
||||
.done(function (otherUser) {
|
||||
|
||||
userLookup[otherUser.id] = otherUser;
|
||||
$dialog.find('.receiver-name').text(otherUser.name);
|
||||
$dialog.find('textarea').attr('placeholder', 'enter a message to ' + otherUser.name + '...');
|
||||
$dialog.find('.offline-tip').text('An email will be sent if ' + otherUser.name + ' is offline');
|
||||
$dialog.find('.offline-tip').text('An email will be sent if ' + otherUser.name + ' is offline');
|
||||
if (!context.JK.JamServer.connected) {
|
||||
renderNotConnected();
|
||||
}
|
||||
$sendTextMessage.click(sendMessage);
|
||||
|
||||
rest.getNotifications(buildParams())
|
||||
.done(function(response) {
|
||||
context._.each(response, function(textMessage) {
|
||||
.done(function (response) {
|
||||
context._.each(response, function (textMessage) {
|
||||
renderMessage(textMessage.message, textMessage.source_user_id, userLookup[textMessage.source_user_id].name, textMessage.created_at);
|
||||
})
|
||||
|
||||
|
|
@ -172,21 +173,53 @@
|
|||
fullyInitialized = true;
|
||||
drainQueue();
|
||||
})
|
||||
.fail(function(jqXHR) {
|
||||
.fail(function (jqXHR) {
|
||||
app.notifyServerError(jqXHR, 'Unable to Load Conversation')
|
||||
})
|
||||
})
|
||||
.fail(function(jqXHR) {
|
||||
.fail(function (jqXHR) {
|
||||
app.notifyServerError(jqXHR, 'Unable to Load Other User')
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function beforeShow(args) {
|
||||
|
||||
app.layout.closeDialog('text-message') // ensure no others are showing. this is a singleton dialog
|
||||
|
||||
var other = args.d1;
|
||||
|
||||
if (!other) throw "other must be specified in TextMessageDialog"
|
||||
otherId = other;
|
||||
|
||||
renderDialog();
|
||||
}
|
||||
|
||||
function afterHide() {
|
||||
showing = false;
|
||||
reset();
|
||||
}
|
||||
|
||||
function renderNotConnected() {
|
||||
$interactionBlocker.addClass('active');
|
||||
$disconnectedMsg.addClass('active');
|
||||
}
|
||||
|
||||
function renderConnected() {
|
||||
$interactionBlocker.removeClass('active');
|
||||
$disconnectedMsg.removeClass('active');
|
||||
}
|
||||
|
||||
function beforeDisconnect() {
|
||||
renderNotConnected();
|
||||
}
|
||||
|
||||
function afterConnect() {
|
||||
renderConnected();
|
||||
reset();
|
||||
renderDialog();
|
||||
}
|
||||
|
||||
function pasteIntoInput(el, text) {
|
||||
el.focus();
|
||||
if (typeof el.selectionStart == "number"
|
||||
|
|
@ -255,24 +288,13 @@
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
function showDialog(_other) {
|
||||
|
||||
app.layout.closeDialog('text-message') // this dialog is implemented as a singleton, so must enforce this
|
||||
|
||||
reset();
|
||||
|
||||
if(!_other) throw "other must be specified in TextMessageDialog"
|
||||
otherId = _other;
|
||||
|
||||
app.layout.showDialog('text-message')
|
||||
}*/
|
||||
|
||||
function initialize() {
|
||||
var dialogBindings = {
|
||||
'beforeShow' : beforeShow,
|
||||
'afterShow' : afterShow,
|
||||
'afterHide': afterHide
|
||||
'afterHide': afterHide,
|
||||
'beforeDisconnect': beforeDisconnect,
|
||||
'afterConnect': afterConnect
|
||||
};
|
||||
|
||||
|
||||
|
|
@ -284,6 +306,8 @@
|
|||
$sendTextMessage = $dialog.find('.btn-send-text-message');
|
||||
$form = $dialog.find('form');
|
||||
$textBox = $form.find('textarea');
|
||||
$interactionBlocker = $dialog.find('.interaction-blocker');
|
||||
$disconnectedMsg = $dialog.find('.disconnected-msg');
|
||||
|
||||
events();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -154,6 +154,8 @@
|
|||
}
|
||||
|
||||
context.JK.bindHoverEvents = function ($parent) {
|
||||
var timeout = 300;
|
||||
var fadeoutValue = 100;
|
||||
|
||||
if (!$parent) {
|
||||
$parent = $('body');
|
||||
|
|
@ -165,75 +167,87 @@
|
|||
}
|
||||
|
||||
function hideBubble($hoverElement) {
|
||||
|
||||
var bubbleSelector = $hoverElement.attr("bubble-id");
|
||||
|
||||
$(bubbleSelector).hover(
|
||||
function () {
|
||||
// do nothing when entering the bubble (this should never happen)
|
||||
// do nothing when entering the bubble
|
||||
},
|
||||
function () {
|
||||
$(this).fadeOut(100);
|
||||
$(this).fadeOut(fadeoutValue);
|
||||
}
|
||||
);
|
||||
|
||||
// first check to see if the user isn't hovering over the hover bubble
|
||||
if (!$(bubbleSelector).is(":hover")) {
|
||||
$(bubbleSelector).fadeOut(fadeoutValue);
|
||||
}
|
||||
}
|
||||
|
||||
// MUSICIAN
|
||||
$("[hoveraction='musician']", $parent).hoverIntent({
|
||||
over: function () {
|
||||
var bubble = new JK.MusicianHoverBubble($(this).attr('user-id'), $(this).offset());
|
||||
over: function(e) {
|
||||
var bubble = new JK.MusicianHoverBubble($(this).attr('user-id'), e.pageX, e.pageY);
|
||||
showBubble(bubble, $(this));
|
||||
},
|
||||
out: function () { // this registers for leaving the hoverable element
|
||||
hideBubble($(this));
|
||||
},
|
||||
sensitivity: 1
|
||||
sensitivity: 1,
|
||||
timeout: timeout
|
||||
});
|
||||
|
||||
// FAN
|
||||
$("[hoveraction='fan']", $parent).hoverIntent({
|
||||
over: function () {
|
||||
var bubble = new JK.FanHoverBubble($(this).attr('user-id'), $(this).offset());
|
||||
over: function(e) {
|
||||
var bubble = new JK.FanHoverBubble($(this).attr('user-id'), e.pageX, e.pageY);
|
||||
showBubble(bubble, $(this));
|
||||
},
|
||||
out: function () { // this registers for leaving the hoverable element
|
||||
hideBubble($(this));
|
||||
},
|
||||
sensitivity: 1
|
||||
sensitivity: 1,
|
||||
timeout: timeout
|
||||
});
|
||||
|
||||
// BAND
|
||||
$("[hoveraction='band']", $parent).hoverIntent({
|
||||
over: function () {
|
||||
var bubble = new JK.BandHoverBubble($(this).attr('band-id'), $(this).offset());
|
||||
over: function(e) {
|
||||
var bubble = new JK.BandHoverBubble($(this).attr('band-id'), e.pageX, e.pageY);
|
||||
showBubble(bubble, $(this));
|
||||
},
|
||||
out: function () { // this registers for leaving the hoverable element
|
||||
hideBubble($(this));
|
||||
},
|
||||
sensitivity: 1
|
||||
sensitivity: 1,
|
||||
timeout: timeout
|
||||
});
|
||||
|
||||
// SESSION
|
||||
$("[hoveraction='session']", $parent).hoverIntent({
|
||||
over: function () {
|
||||
var bubble = new JK.SessionHoverBubble($(this).attr('session-id'), $(this).offset());
|
||||
over: function(e) {
|
||||
var bubble = new JK.SessionHoverBubble($(this).attr('session-id'), e.pageX, e.pageY);
|
||||
showBubble(bubble, $(this));
|
||||
},
|
||||
out: function () { // this registers for leaving the hoverable element
|
||||
hideBubble($(this));
|
||||
},
|
||||
sensitivity: 1
|
||||
sensitivity: 1,
|
||||
timeout: timeout
|
||||
});
|
||||
|
||||
// RECORDING
|
||||
$("[hoveraction='recording']", $parent).hoverIntent({
|
||||
over: function () {
|
||||
var bubble = new JK.RecordingHoverBubble($(this).attr('recording-id'), $(this).offset());
|
||||
over: function(e) {
|
||||
var bubble = new JK.RecordingHoverBubble($(this).attr('recording-id'), e.pageX, e.pageY);
|
||||
showBubble(bubble, $(this));
|
||||
},
|
||||
out: function () { // this registers for leaving the hoverable element
|
||||
hideBubble($(this));
|
||||
},
|
||||
sensitivity: 1
|
||||
sensitivity: 1,
|
||||
timeout: timeout
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -28,7 +28,6 @@
|
|||
}
|
||||
|
||||
function togglePlay() {
|
||||
|
||||
if(playing) {
|
||||
stopPlay();
|
||||
}
|
||||
|
|
@ -51,10 +50,10 @@
|
|||
|
||||
function like() {
|
||||
rest.addRecordingLike(recordingId, claimedRecordingId, JK.currentUserId)
|
||||
.done(function(response) {
|
||||
$("#spnLikeCount").html(parseInt($("#spnLikeCount").text()) + 1);
|
||||
$("#btnLike", $scope).unbind("click");
|
||||
});
|
||||
.done(function(response) {
|
||||
$("#spnLikeCount").html(parseInt($("#spnLikeCount").text()) + 1);
|
||||
$("#btnLike", $scope).unbind("click");
|
||||
});
|
||||
}
|
||||
|
||||
function play() {
|
||||
|
|
@ -68,22 +67,35 @@
|
|||
var comment = $("#txtRecordingComment").val();
|
||||
if ($.trim(comment).length > 0) {
|
||||
rest.addRecordingComment(recordingId, JK.currentUserId, comment)
|
||||
.done(function(response) {
|
||||
$("#spnCommentCount", $scope).html(parseInt($("#spnCommentCount").text()) + 1);
|
||||
|
||||
var template = $('#template-landing-comment').html();
|
||||
var commentHtml = context.JK.fillTemplate(template, {
|
||||
avatar_url: context.JK.currentUserAvatarUrl,
|
||||
name: context.JK.currentUserName,
|
||||
comment: comment,
|
||||
timeago: $.timeago(Date.now())
|
||||
});
|
||||
|
||||
$(".landing-comment-scroller").prepend(commentHtml);
|
||||
});
|
||||
.done(function(response) {
|
||||
$("#spnCommentCount", $scope).html(parseInt($("#spnCommentCount").text()) + 1);
|
||||
renderComment(comment, context.JK.currentUserId, context.JK.currentUserName,
|
||||
context.JK.currentUserAvatarUrl, $.timeago(Date.now()), context.JK.currentUserMusician, false);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function renderComment(comment, userId, userName, userAvatarUrl, timeago, musician, append) {
|
||||
var template = $('#template-landing-comment').html();
|
||||
var commentHtml = context.JK.fillTemplate(template, {
|
||||
avatar_url: userAvatarUrl,
|
||||
user_id: userId,
|
||||
hoverAction: musician ? "musician" : "fan",
|
||||
name: userName,
|
||||
comment: comment,
|
||||
timeago: timeago
|
||||
});
|
||||
|
||||
if (append) {
|
||||
$(".landing-comment-scroller").append(commentHtml);
|
||||
}
|
||||
else {
|
||||
$(".landing-comment-scroller").prepend(commentHtml);
|
||||
}
|
||||
|
||||
context.JK.bindHoverEvents();
|
||||
}
|
||||
|
||||
function initialize(_claimedRecordingId, _recordingId) {
|
||||
recordingId = _recordingId;
|
||||
claimedRecordingId = _claimedRecordingId;
|
||||
|
|
@ -116,6 +128,32 @@
|
|||
|
||||
$("#btnLike").click(like);
|
||||
$("#btnPlay").click(play);
|
||||
|
||||
$playButton.trigger('click');
|
||||
|
||||
pollForUpdates(claimedRecordingId);
|
||||
}
|
||||
|
||||
function pollForUpdates(claimedRecordingId) {
|
||||
$(".landing-comment-scroller").empty();
|
||||
rest.getClaimedRecording(claimedRecordingId)
|
||||
.done(function(response) {
|
||||
if (response.recording && response.recording.comments) {
|
||||
$("#spnPlayCount", $scope).html(response.recording.play_count);
|
||||
$("#spnCommentCount", $scope).html(response.recording.comment_count);
|
||||
$("#spnLikeCount", $scope).html(response.recording.like_count);
|
||||
$.each(response.recording.comments, function(index, val) {
|
||||
renderComment(val.comment, val.creator.id, val.creator.name,
|
||||
context.JK.resolveAvatarUrl(val.creator.photo_url), $.timeago(val.created_at), val.creator.musician, true);
|
||||
});
|
||||
setTimeout(function() {
|
||||
pollForUpdates(claimedRecordingId);
|
||||
}, 60000);
|
||||
}
|
||||
})
|
||||
.fail(function(xhr) {
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
this.initialize = initialize;
|
||||
|
|
|
|||
|
|
@ -4,38 +4,53 @@
|
|||
var logger = context.JK.logger;
|
||||
var rest = new JK.Rest();
|
||||
var sessionId = null;
|
||||
var $scope = $(".landing-details");
|
||||
var $controls = null;
|
||||
var $status = null;
|
||||
var $playButton = $('.play-button');
|
||||
var playing = false;
|
||||
|
||||
function like() {
|
||||
rest.addSessionLike(sessionId, JK.currentUserId)
|
||||
.done(function(response) {
|
||||
$("#spnLikeCount").html(parseInt($("#spnLikeCount").text()) + 1);
|
||||
$("#btnLike").unbind("click");
|
||||
});
|
||||
.done(function(response) {
|
||||
$("#spnLikeCount").html(parseInt($("#spnLikeCount").text()) + 1);
|
||||
$("#btnLike").unbind("click");
|
||||
});
|
||||
}
|
||||
|
||||
function addComment() {
|
||||
var comment = $("#txtSessionComment").val();
|
||||
if ($.trim(comment).length > 0) {
|
||||
rest.addSessionComment(sessionId, JK.currentUserId, comment)
|
||||
.done(function(response) {
|
||||
$("#spnCommentCount").html(parseInt($("#spnCommentCount").text()) + 1);
|
||||
|
||||
var template = $('#template-landing-comment').html();
|
||||
var commentHtml = context.JK.fillTemplate(template, {
|
||||
avatar_url: context.JK.currentUserAvatarUrl,
|
||||
name: context.JK.currentUserName,
|
||||
comment: comment,
|
||||
timeago: $.timeago(Date.now())
|
||||
});
|
||||
|
||||
$(".landing-comment-scroller").prepend(commentHtml);
|
||||
});
|
||||
rest.addSessionComment(sessionId, JK.currentUserId, comment)
|
||||
.done(function(response) {
|
||||
$("#spnCommentCount").html(parseInt($("#spnCommentCount").text()) + 1);
|
||||
renderComment(comment, context.JK.currentUserId, context.JK.currentUserName,
|
||||
context.JK.currentUserAvatarUrl, $.timeago(Date.now()), context.JK.currentUserMusician, false);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function renderComment(comment, userId, userName, userAvatarUrl, timeago, musician, append) {
|
||||
var template = $('#template-landing-comment').html();
|
||||
var commentHtml = context.JK.fillTemplate(template, {
|
||||
avatar_url: userAvatarUrl,
|
||||
user_id: userId,
|
||||
hoverAction: musician ? "musician" : "fan",
|
||||
name: userName,
|
||||
comment: comment,
|
||||
timeago: timeago
|
||||
});
|
||||
|
||||
if (append) {
|
||||
$(".landing-comment-scroller").append(commentHtml);
|
||||
}
|
||||
else {
|
||||
$(".landing-comment-scroller").prepend(commentHtml);
|
||||
}
|
||||
|
||||
context.JK.bindHoverEvents();
|
||||
}
|
||||
|
||||
function stateChange(e, data) {
|
||||
if(data.displayText)
|
||||
{
|
||||
|
|
@ -88,34 +103,57 @@
|
|||
$controls.bind('statechange.listenBroadcast', stateChange);
|
||||
context.JK.prettyPrintElements($('time.duration').show());
|
||||
context.JK.TickDuration(null);
|
||||
$('.play-button').click(togglePlay);
|
||||
|
||||
$playButton.click(togglePlay);
|
||||
|
||||
sessionId = musicSessionId;
|
||||
|
||||
if (JK.currentUserId) {
|
||||
var shareDialog = new JK.ShareDialog(context.JK.app, sessionId, "session");
|
||||
shareDialog.initialize(JK.FacebookHelperInstance);
|
||||
var shareDialog = new JK.ShareDialog(context.JK.app, sessionId, "session");
|
||||
shareDialog.initialize(JK.FacebookHelperInstance);
|
||||
|
||||
$("#btnShare").click(function(e) {
|
||||
shareDialog.showDialog();
|
||||
});
|
||||
$("#btnShare").click(function(e) {
|
||||
shareDialog.showDialog();
|
||||
});
|
||||
|
||||
$("#btnPostComment").click(function(e) {
|
||||
if ($.trim($("#txtSessionComment").val()).length > 0) {
|
||||
addComment();
|
||||
$("#txtSessionComment").val('');
|
||||
$("#txtSessionComment").blur();
|
||||
}
|
||||
});
|
||||
$("#btnPostComment").click(function(e) {
|
||||
if ($.trim($("#txtSessionComment").val()).length > 0) {
|
||||
addComment();
|
||||
$("#txtSessionComment").val('');
|
||||
$("#txtSessionComment").blur();
|
||||
}
|
||||
});
|
||||
}
|
||||
else {
|
||||
$("#txtSessionComment").attr("disabled", "disabled");
|
||||
$("#txtSessionComment").val("You must be logged in to add a comment.");
|
||||
$("#txtSessionComment").attr("disabled", "disabled");
|
||||
$("#txtSessionComment").val("You must be logged in to add a comment.");
|
||||
}
|
||||
|
||||
$("#btnLike").click(like);
|
||||
|
||||
$playButton.trigger('click');
|
||||
|
||||
pollForUpdates(musicSessionId);
|
||||
}
|
||||
|
||||
function pollForUpdates(musicSessionId) {
|
||||
$(".landing-comment-scroller").empty();
|
||||
rest.getSessionHistory(musicSessionId)
|
||||
.done(function(response) {
|
||||
if (response && response.comments) {
|
||||
$("#spnCommentCount", $scope).html(response.comment_count);
|
||||
$("#spnLikeCount", $scope).html(response.like_count);
|
||||
$.each(response.comments, function(index, val) {
|
||||
renderComment(val.comment, val.creator.id, val.creator.name,
|
||||
context.JK.resolveAvatarUrl(val.creator.photo_url), $.timeago(val.created_at), val.creator.musician, true);
|
||||
});
|
||||
setTimeout(function() {
|
||||
pollForUpdates(musicSessionId);
|
||||
}, 60000);
|
||||
}
|
||||
})
|
||||
.fail(function(xhr) {
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
this.initialize = initialize;
|
||||
|
|
|
|||
|
|
@ -1,8 +1,19 @@
|
|||
#banner {
|
||||
display:none;
|
||||
|
||||
&[data-type="reconnect"] {
|
||||
height:240px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-weight:bold;
|
||||
font-size:x-large;
|
||||
}
|
||||
|
||||
.countdown {
|
||||
font-weight:bold;
|
||||
min-width:9px;
|
||||
display:inline-block;
|
||||
}
|
||||
}
|
||||
|
||||
#banner h2 {
|
||||
font-weight:bold;
|
||||
font-size:x-large;
|
||||
}
|
||||
|
|
@ -33,6 +33,7 @@
|
|||
*= require ./account
|
||||
*= require ./search
|
||||
*= require ./ftue
|
||||
*= require ./jamServer
|
||||
*= require ./gearWizard
|
||||
*= require ./whatsNextDialog
|
||||
*= require ./invitationDialog
|
||||
|
|
|
|||
|
|
@ -4,11 +4,11 @@
|
|||
background-color:#242323;
|
||||
border:solid 1px #ed3618;
|
||||
position:absolute;
|
||||
z-index:999;
|
||||
z-index:1100;
|
||||
|
||||
|
||||
&.musician-bubble {
|
||||
width:410px;
|
||||
width:425px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,65 @@
|
|||
.no-websocket-connection {
|
||||
display:none;
|
||||
text-align:center;
|
||||
width:100%;
|
||||
position:absolute;
|
||||
}
|
||||
|
||||
.server-connection {
|
||||
margin:auto;
|
||||
display:inline-block;
|
||||
zoom:1;
|
||||
text-align:center;
|
||||
padding:10px 20px;
|
||||
background-color:#404040;
|
||||
border-color:#ccc;
|
||||
border-style:solid;
|
||||
border-width:0 2px 2px;
|
||||
|
||||
|
||||
-webkit-box-shadow: 0px 0px 15px rgba(50, 50, 50, 1);
|
||||
-moz-box-shadow: 0px 0px 15px rgba(50, 50, 50, 1);
|
||||
box-shadow: 0px 0px 15px rgba(50, 50, 50, 1);
|
||||
|
||||
h2 {
|
||||
font-size:20px;
|
||||
vertical-align:baseline;
|
||||
margin-bottom:10px;
|
||||
}
|
||||
|
||||
img.alert-icon {
|
||||
top:14px;
|
||||
height:16px;
|
||||
width:16px;
|
||||
|
||||
&.left-side {
|
||||
padding-right:20px;
|
||||
}
|
||||
|
||||
&.right-side {
|
||||
padding-left:20px;
|
||||
}
|
||||
}
|
||||
|
||||
.reconnect-progress-msg {
|
||||
margin-bottom:10px;
|
||||
}
|
||||
|
||||
.reconnect-countdown {
|
||||
}
|
||||
|
||||
#reconnect-now {
|
||||
margin-top:10px;
|
||||
}
|
||||
|
||||
.countdown {
|
||||
font-weight:bold;
|
||||
min-width:9px;
|
||||
display:inline-block;
|
||||
}
|
||||
|
||||
.countdown-seconds {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -58,4 +58,39 @@
|
|||
text-align:center;
|
||||
width:50px;
|
||||
}
|
||||
|
||||
.interaction-blocker {
|
||||
display:none;
|
||||
position:absolute;
|
||||
background-color:#333;
|
||||
text-align:center;
|
||||
|
||||
&.active {
|
||||
display:block;
|
||||
top:0;
|
||||
bottom:40px;
|
||||
left:0;
|
||||
right:0;
|
||||
|
||||
opacity:0.5;
|
||||
}
|
||||
}
|
||||
|
||||
.disconnected-msg {
|
||||
color:white;
|
||||
font-size:25px;
|
||||
left:0;
|
||||
margin:auto;
|
||||
position:absolute;
|
||||
text-align:center;
|
||||
top:30%;
|
||||
width:100%;
|
||||
display:none;
|
||||
|
||||
&.active {
|
||||
display:inline-block;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -9,3 +9,8 @@ body {
|
|||
height:100%;
|
||||
margin:0 !important;
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
width:1280px;
|
||||
margin:0 auto;
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
class ExtrasController < ApplicationController
|
||||
|
||||
before_filter :signed_in_user
|
||||
before_filter :admin_user
|
||||
|
||||
def settings
|
||||
render layout: "web"
|
||||
end
|
||||
end
|
||||
|
|
@ -396,10 +396,6 @@ class UsersController < ApplicationController
|
|||
redirect_to(root_url) unless current_user?(@user)
|
||||
end
|
||||
|
||||
def admin_user
|
||||
redirect_to(root_url) unless current_user.admin?
|
||||
end
|
||||
|
||||
# the User Model expects instruments in a different format than the form submits it
|
||||
# so we have to fix it up.
|
||||
def fixup_instruments(original_instruments)
|
||||
|
|
|
|||
|
|
@ -1,8 +0,0 @@
|
|||
|
||||
class VideosController < ApplicationController
|
||||
|
||||
def show_dialog
|
||||
@video_id = @params[:video_id]
|
||||
end
|
||||
|
||||
end
|
||||
|
|
@ -57,6 +57,10 @@ module SessionsHelper
|
|||
cookies.delete(:remember_token, domain: Rails.application.config.session_cookie_domain)
|
||||
end
|
||||
|
||||
def admin_user
|
||||
redirect_to(root_url) unless current_user.admin?
|
||||
end
|
||||
|
||||
def redirect_back_or(default)
|
||||
redirect_to(session[:return_to] || default)
|
||||
session.delete(:return_to)
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ child(:recording => :recording) {
|
|||
attributes :comment, :created_at
|
||||
|
||||
child(:user => :creator) {
|
||||
attributes :id, :first_name, :last_name, :photo_url
|
||||
attributes :id, :first_name, :last_name, :name, :photo_url, :musician
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,4 +22,12 @@ child(:music_session_user_histories => :users) {
|
|||
child(:user => :user) {
|
||||
attributes :name, :photo_url
|
||||
}
|
||||
}
|
||||
|
||||
child(:comments => :comments) {
|
||||
attributes :comment, :created_at
|
||||
|
||||
child(:user => :creator) {
|
||||
attributes :id, :first_name, :last_name, :name, :photo_url, :musician
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
|
||||
<!-- generic banner for use by an code -->
|
||||
<div class="overlay" id="banner_overlay"></div>
|
||||
<div id="banner" class="dialog-overlay-sm">
|
||||
<div id="banner" class="dialog-overlay-sm" data-type="">
|
||||
|
||||
<!-- dialog header -->
|
||||
<div class="content-head">
|
||||
|
|
|
|||
|
|
@ -0,0 +1,12 @@
|
|||
.no-websocket-connection
|
||||
.server-connection
|
||||
%h2
|
||||
= image_tag( "content/icon_alert.png" , :class => "alert-icon left-side" )
|
||||
%span Disconnected from Server
|
||||
= image_tag( "content/icon_alert.png" , :class => "alert-icon right-side" )
|
||||
.message-contents
|
||||
%script{type: 'text/template', id: 'template-server-connection'}
|
||||
%div.reconnect-progress-msg
|
||||
%span.reconnect-before= 'Reconnecting automatically in '
|
||||
%span.reconnect-countdown= '{{data.countdown}}'
|
||||
%a.disconnected-reconnect.reconnect-enabled{href:'#'} RECONNECT NOW
|
||||
|
|
@ -11,28 +11,36 @@
|
|||
</div>
|
||||
|
||||
<!-- Search Box -->
|
||||
<div class="search">
|
||||
<div layout-panel="header" class="panel-header">
|
||||
<h2>search</h2>
|
||||
<div class="search" layout="panel" layout-id="panelSearch">
|
||||
<div layout-panel="collapsed">
|
||||
</div>
|
||||
<div class="searchbox">
|
||||
<form id="searchForm">
|
||||
<input id="search-input" autocomplete="off" type="text" name="search" placeholder="enter search terms" />
|
||||
</form>
|
||||
<div id="sidebar-search-header" style="margin: 4px 4px 8px 8px">
|
||||
<div class="left">
|
||||
<strong><a id="sidebar-search-expand" style="color:#fff; text-decoration:underline">« EXPAND</a></strong>
|
||||
</div>
|
||||
<!-- search filter dropdown -->
|
||||
<div class="right">
|
||||
Show: <%= select_tag(Search::SEARCH_TEXT_TYPE_ID, options_for_select(Search::SEARCH_TEXT_TYPES.collect { |ii| [ii.to_s.titleize, ii] })) %>
|
||||
<div layout-panel="expanded" class="panel expanded">
|
||||
<div layout-panel="header" class="panel-header always-open">
|
||||
<h2>search</h2>
|
||||
<div class="searchbox">
|
||||
<form id="searchForm">
|
||||
<input id="search-input" autocomplete="off" type="text" name="search" placeholder="enter search terms" />
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="clear:both;"></div><br />
|
||||
<!-- border between header and beginning of search results -->
|
||||
<div class="sidebar-search-result"></div>
|
||||
<div id="sidebar-search-results" class="results-wrapper">
|
||||
|
||||
<!-- border between header and beginning of search results -->
|
||||
<!--<div class="sidebar-search-result"></div>-->
|
||||
<div class="panelcontents" layout-panel="contents">
|
||||
<div id="sidebar-search-header" style="margin: 4px 4px 8px 8px">
|
||||
<div class="left">
|
||||
<strong><a id="sidebar-search-expand" style="color:#fff; text-decoration:underline">« EXPAND</a></strong>
|
||||
</div>
|
||||
<!-- search filter dropdown -->
|
||||
<div class="right">
|
||||
Show: <%= select_tag(Search::SEARCH_TEXT_TYPE_ID, options_for_select(Search::SEARCH_TEXT_TYPES.collect { |ii| [ii.to_s.titleize, ii] })) %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="clear:both;"></div><br />
|
||||
<div id="sidebar-search-results" class="results-wrapper"></div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -16,6 +16,9 @@
|
|||
.right
|
||||
%a.button-grey.btn-close-dialog{href:'#', 'layout-action' => 'close'} CLOSE
|
||||
%a.button-orange.btn-send-text-message{href:'#'} SEND
|
||||
.interaction-blocker
|
||||
%span.disconnected-msg DISCONNECTED FROM SERVER
|
||||
|
||||
|
||||
%script{type: 'text/template', id: 'template-previous-message'}
|
||||
.previous-message
|
||||
|
|
|
|||
|
|
@ -4,39 +4,12 @@
|
|||
<br />
|
||||
<br />
|
||||
<div align="center">
|
||||
You have been disconnected from JamKazam.
|
||||
You have been disconnected from JamKazam. <br/><br/>
|
||||
<span class="reconnect-progress-msg">Reconnecting automatically in <span class="reconnect-countdown">{countdown}</span></span>
|
||||
</div>
|
||||
<br clear="all" /><br />
|
||||
|
||||
<div class="right">
|
||||
<a href="#" class="button-orange disconnected-reconnect">RECONNECT</a>
|
||||
<a href="#" class="button-orange disconnected-reconnect">RECONNECT NOW</a>
|
||||
</div>
|
||||
|
||||
</script>
|
||||
|
||||
<script type="text/template" id="template-reconnecting">
|
||||
<h2 align="center">Reconnecting to Server</h2>
|
||||
<br />
|
||||
<br />
|
||||
<br />
|
||||
<div align="center">
|
||||
Attempting to reestablish connection to the server...
|
||||
</div>
|
||||
<br clear="all" /><br />
|
||||
|
||||
</script>
|
||||
|
||||
<script type="text/template" id="template-could-not-reconnect">
|
||||
<h2 align="center">Unable to Reconnect</h2>
|
||||
<br />
|
||||
<br />
|
||||
<br />
|
||||
<div align="center">
|
||||
We were not able to reconnect to JamKazam. <br/></br/>
|
||||
Please check your internet connection, then try again later.
|
||||
</div>
|
||||
<br clear="all" /><br />
|
||||
<div class="right">
|
||||
<a href="#" class="button-orange disconnected-reconnect">TRY AGAIN TO RECONNECT</a>
|
||||
</div>
|
||||
</script>
|
||||
|
|
@ -14,6 +14,7 @@
|
|||
<%= render "faders" %>
|
||||
<%= render "vu_meters" %>
|
||||
<%= render "ftue" %>
|
||||
<%= render "jamServer" %>
|
||||
<%= render "clients/gear/gear_wizard" %>
|
||||
<%= render "terms" %>
|
||||
<%= render "leaveSessionWarning" %>
|
||||
|
|
@ -118,8 +119,9 @@
|
|||
window.location.href = '/?redirect-to=' + encodeURIComponent(JK.locationPath());
|
||||
<% end %>
|
||||
|
||||
|
||||
// Some things can't be initialized until we're connected. Put them here.
|
||||
function _initAfterConnect() {
|
||||
function _initAfterConnect(connected) {
|
||||
if (this.didInitAfterConnect) return;
|
||||
this.didInitAfterConnect = true
|
||||
|
||||
|
|
@ -242,12 +244,17 @@
|
|||
var testBridgeScreen = new JK.TestBridgeScreen(JK.app);
|
||||
testBridgeScreen.initialize();
|
||||
|
||||
if(!connected) {
|
||||
jamServer.initiateReconnect(null, true);
|
||||
}
|
||||
|
||||
JK.app.initialRouting();
|
||||
JK.hideCurtain(300);
|
||||
}
|
||||
|
||||
JK.app = JK.JamKazam();
|
||||
JK.app.initAfterConnect = _initAfterConnect;
|
||||
var jamServer = new JK.JamServer(JK.app);
|
||||
jamServer.initialize();
|
||||
|
||||
// If no jamClient (when not running in native client)
|
||||
// create a fake one.
|
||||
|
|
@ -302,25 +309,16 @@
|
|||
|
||||
JK.TickDuration('.feed-entry.music-session-history-entry .inprogress .tick-duration');
|
||||
|
||||
JK.JamServer.connect(); // singleton here defined in JamServer.js
|
||||
JK.JamServer.connect() // singleton here defined in JamServer.js
|
||||
.done(function() {
|
||||
_initAfterConnect(true);
|
||||
})
|
||||
.fail(function() {
|
||||
_initAfterConnect(false);
|
||||
});
|
||||
|
||||
// this ensures that there is always a CurrentSessionModel, even if it's for a non-active session
|
||||
JK.CurrentSessionModel = new JK.SessionModel(JK.app, JK.JamServer, window.jamClient);
|
||||
|
||||
// Run a check to see if we're logged in yet. Only after that should
|
||||
// we initialize the other screens.
|
||||
function testConnected() {
|
||||
this.numCalls = (this.numCalls || 0) + 1;
|
||||
if (JK.clientId) {
|
||||
JK.app.initAfterConnect();
|
||||
} else {
|
||||
if (50 <= this.numCalls) { // 5 second max
|
||||
JK.notifyAlert('Server Error', 'Could not connect to server. Please try again later.')
|
||||
} else {
|
||||
window.setTimeout(testConnected, 100);
|
||||
}
|
||||
}
|
||||
}
|
||||
testConnected();
|
||||
}
|
||||
|
||||
JK.bindHoverEvents();
|
||||
|
|
|
|||
|
|
@ -0,0 +1,6 @@
|
|||
%h1 Extra Feature Settings
|
||||
|
||||
%form
|
||||
%button.launch_new_ftue{type: 'button'}
|
||||
|
||||
|
||||
|
|
@ -32,7 +32,9 @@
|
|||
<body class="jam">
|
||||
<div id="minimal-container">
|
||||
<%= javascript_include_tag "minimal/minimal" %>
|
||||
<%= yield %>
|
||||
<div class="wrapper">
|
||||
<%= yield %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
|
|
@ -56,5 +58,4 @@
|
|||
<%= render "shared/ga" %>
|
||||
<!-- version info: <%= version %> -->
|
||||
</body>
|
||||
</html>
|
||||
c
|
||||
</html>
|
||||
|
|
@ -92,10 +92,12 @@
|
|||
JK.currentUserId = '<%= current_user.id %>';
|
||||
JK.currentUserAvatarUrl = JK.resolveAvatarUrl('<%= current_user.photo_url %>');
|
||||
JK.currentUserName = '<%= current_user.name %>';
|
||||
JK.currentUserMusician = '<%= current_user.musician %>';
|
||||
<% else %>
|
||||
JK.currentUserId = null;
|
||||
JK.currentUserAvatarUrl = null;
|
||||
JK.currentUserName = null;
|
||||
JK.currentUserMusician = null;
|
||||
<% end %>
|
||||
|
||||
JK.app = JK.JamKazam();
|
||||
|
|
|
|||
|
|
@ -12,33 +12,18 @@
|
|||
<br clear="all" />
|
||||
|
||||
<div class="landing-comment-scroller">
|
||||
<% comments.each do |c| %>
|
||||
<% hoverAction = c.user.musician ? "musician" : "fan" %>
|
||||
<div user-id="<%= c.user.id %>" hoveraction="<%= hoverAction %>" class="avatar-small mr10">
|
||||
<% unless c.user.photo_url.blank? %>
|
||||
<%= image_tag "#{c.user.photo_url}", {:alt => ""} %>
|
||||
<% else %>
|
||||
<%= image_tag "shared/avatar_generic.png", {:alt => ""} %>
|
||||
<% end %>
|
||||
</div>
|
||||
<div class="w80 left p10 lightgrey mt10">
|
||||
<a user-id="<%= c.user.id %>" hoveraction="<%= hoverAction %>" href="#"><%= c.user.name %></a> <%= c.comment %>
|
||||
<br />
|
||||
<div class="f12 grey mt5"><%= timeago(c.created_at) %></div>
|
||||
</div>
|
||||
<br clear="all" />
|
||||
<% end %>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="text/template" id="template-landing-comment">
|
||||
<div class="avatar-small mr10">
|
||||
<img src="{avatar_url}" alt="" />
|
||||
<div user-id="{user_id}" hoveraction="{hoverAction}" class="avatar-small mr10">
|
||||
<img src="{avatar_url}" alt="" />
|
||||
</div>
|
||||
<div class="w80 left p10 lightgrey mt10">
|
||||
<a href="#">{name}</a> {comment}
|
||||
<br />
|
||||
<div class="f12 grey mt5">{timeago}</div>
|
||||
</div>
|
||||
<br clear="all" />
|
||||
<div class="w80 left p10 lightgrey mt10 comment-text">
|
||||
<a user-id="{user_id}" hoveraction="{hoverAction}">{name}</a> {comment}
|
||||
<br />
|
||||
<div class="f12 grey mt5 comment-timestamp">{timeago}</div>
|
||||
</div>
|
||||
<br clear="all" />
|
||||
</script>
|
||||
|
|
@ -2,8 +2,9 @@ unless $rails_rake_task
|
|||
|
||||
JamWebEventMachine.start
|
||||
|
||||
if APP_CONFIG.websocket_gateway_enable && !$rails_rake_task
|
||||
if APP_CONFIG.websocket_gateway_enable && !$rails_rake_task && ENV['NO_WEBSOCKET_GATEWAY'] != '1'
|
||||
|
||||
current = Thread.current
|
||||
Thread.new do
|
||||
JamWebsockets::Server.new.run(
|
||||
:port => APP_CONFIG.websocket_gateway_port,
|
||||
|
|
@ -11,8 +12,10 @@ unless $rails_rake_task
|
|||
:connect_time_stale => APP_CONFIG.websocket_gateway_connect_time_stale,
|
||||
:connect_time_expire => APP_CONFIG.websocket_gateway_connect_time_expire,
|
||||
:rabbitmq_host => APP_CONFIG.rabbitmq_host,
|
||||
:rabbitmq_port => APP_CONFIG.rabbitmq_port)
|
||||
:rabbitmq_port => APP_CONFIG.rabbitmq_port,
|
||||
:calling_thread => current)
|
||||
end
|
||||
|
||||
Thread.stop
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
|||
|
|
@ -63,8 +63,6 @@ SampleApp::Application.routes.draw do
|
|||
|
||||
match '/events/:slug', to: 'events#show', :via => :get, :as => 'event'
|
||||
|
||||
match '/video/dialog/:id', to: 'videos#show', :via => :get
|
||||
|
||||
match '/endorse/:id/:service', to: 'users#endorse', :as => 'endorse'
|
||||
|
||||
# temporarily allow for debugging--only allows admini n
|
||||
|
|
@ -100,6 +98,8 @@ SampleApp::Application.routes.draw do
|
|||
# vanilla forums sso
|
||||
match '/forums/sso', to: 'vanilla_forums#authenticate'
|
||||
|
||||
# admin-only page to control settings
|
||||
match '/extras/settings', to: 'extras#settings'
|
||||
|
||||
scope '/corp' do
|
||||
# about routes
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -4,12 +4,6 @@ describe "Account", :js => true, :type => :feature, :capybara_feature => true do
|
|||
|
||||
subject { page }
|
||||
|
||||
before(:all) do
|
||||
Capybara.javascript_driver = :poltergeist
|
||||
Capybara.current_driver = Capybara.javascript_driver
|
||||
Capybara.default_wait_time = 10
|
||||
end
|
||||
|
||||
let(:user) { FactoryGirl.create(:user) }
|
||||
|
||||
before(:each) do
|
||||
|
|
@ -92,7 +86,7 @@ describe "Account", :js => true, :type => :feature, :capybara_feature => true do
|
|||
before(:each) do
|
||||
fill_in "first_name", with: "Bobby"
|
||||
fill_in "last_name", with: "Toes"
|
||||
find('input[name=subscribe_email]').set(false)
|
||||
uncheck('subscribe_email')
|
||||
find("#account-edit-profile-submit").trigger(:click)
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -16,16 +16,16 @@ describe "Bands", :js => true, :type => :feature, :capybara_feature => true do
|
|||
#end
|
||||
end
|
||||
|
||||
let(:fan) { FactoryGirl.create(:fan) }
|
||||
let(:user) { FactoryGirl.create(:user) }
|
||||
let(:finder) { FactoryGirl.create(:user) }
|
||||
|
||||
before(:each) do
|
||||
UserMailer.deliveries.clear
|
||||
navigate_band_setup
|
||||
end
|
||||
|
||||
def navigate_band_setup
|
||||
sign_in_poltergeist(user)
|
||||
def navigate_band_setup login=user
|
||||
sign_in_poltergeist(login)
|
||||
wait_until_curtain_gone
|
||||
find('div.homecard.profile').trigger(:click)
|
||||
find('#profile-bands-link').trigger(:click)
|
||||
|
|
@ -33,26 +33,143 @@ describe "Bands", :js => true, :type => :feature, :capybara_feature => true do
|
|||
expect(page).to have_selector('#band-setup-title')
|
||||
end
|
||||
|
||||
it "have validation errors shown, but then can navigate past and eventually save" do
|
||||
find('#btn-band-setup-next').trigger(:click)
|
||||
find('#tdBandName .error-text li', text: "can't be blank")
|
||||
find('#tdBandBiography .error-text li', text: "can't be blank")
|
||||
find('#tdBandGenres .error-text li', text: "At least 1 genre is required.")
|
||||
def complete_band_setup_form(band, biography, params={})
|
||||
navigate_band_setup unless URI.parse(current_url).fragment == '/band/setup/new'
|
||||
params['band-name'] ||= band || "Default band name"
|
||||
params['band-biography'] ||= biography || "Default band biography"
|
||||
|
||||
within('#band-setup-form') do
|
||||
fill_in "band-name", with: "The Band"
|
||||
fill_in "band-biography", with: "Biography"
|
||||
params.each do |field, value|
|
||||
fill_in field, with: "#{value}"
|
||||
end
|
||||
first('#band-genres input[type="checkbox"]').trigger(:click)
|
||||
end
|
||||
|
||||
sleep 1 # work around race condition
|
||||
find('#btn-band-setup-next').trigger(:click)
|
||||
find('h2', text: 'Step 2: Add Band Members')
|
||||
|
||||
find('#btn-band-setup-save').trigger(:click)
|
||||
find('#band-profile-name', text: "The Band")
|
||||
find('#band-profile-biography', text: "Biography")
|
||||
end
|
||||
|
||||
context "band profile - new band setup" do
|
||||
it "displays 'Set up your band' link to user" do
|
||||
sign_in_poltergeist user
|
||||
view_profile_of user
|
||||
find('#profile-bands-link').trigger(:click)
|
||||
expect(page).to have_selector('#band-setup-link')
|
||||
end
|
||||
|
||||
it "does not display band setup link when viewed by other user" do
|
||||
in_client(fan) do
|
||||
sign_in_poltergeist fan
|
||||
view_profile_of user
|
||||
find('#profile-bands-link').trigger(:click)
|
||||
|
||||
expect(page).to_not have_selector('#band-setup-link')
|
||||
end
|
||||
end
|
||||
|
||||
it "indicates required fields and user may eventually complete" do
|
||||
navigate_band_setup
|
||||
find('#btn-band-setup-next').trigger(:click)
|
||||
expect(page).to have_selector('#tdBandName .error-text li', text: "can't be blank")
|
||||
expect(page).to have_selector('#tdBandBiography .error-text li', text: "can't be blank")
|
||||
expect(page).to have_selector('#tdBandGenres .error-text li', text: "At least 1 genre is required.")
|
||||
|
||||
complete_band_setup_form("Band name", "Band biography")
|
||||
|
||||
expect(page).to have_selector('#band-profile-name', text: "Band name")
|
||||
expect(page).to have_selector('#band-profile-biography', text: "Band biography")
|
||||
|
||||
end
|
||||
|
||||
it "limits genres to 3" do
|
||||
navigate_band_setup
|
||||
within('#band-setup-form') do
|
||||
fill_in 'band-name', with: "whatever"
|
||||
fill_in 'band-biography', with: "a good story"
|
||||
all('#band-genres input[type="checkbox"]').each_with_index do |cb, i|
|
||||
cb.trigger(:click) unless i > 3
|
||||
end
|
||||
end
|
||||
sleep 1
|
||||
find('#btn-band-setup-next').trigger(:click)
|
||||
expect(page).to have_selector('#tdBandGenres .error-text li', text: "No more than 3 genres are allowed.")
|
||||
end
|
||||
|
||||
it "handles max-length field input" do
|
||||
pending "update this after VRFS-1610 is resolved"
|
||||
max = {
|
||||
name: 1024,
|
||||
bio: 4000,
|
||||
website: 1024 # unsure what the max is, see VRFS-1610
|
||||
}
|
||||
navigate_band_setup
|
||||
band_name = 'a'*(max[:name] + 1)
|
||||
band_bio = 'b'*(max[:bio] + 1)
|
||||
band_website = 'c'*(max[:website] + 1)
|
||||
complete_band_setup_form(band_name, band_bio, 'band-website' => band_website)
|
||||
|
||||
expect(page).to have_selector('#band-profile-name', text: band_name.slice(0, max[:name]))
|
||||
expect(page).to have_selector('#band-profile-biography', text: band_bio.slice(0, max[:bio]))
|
||||
end
|
||||
|
||||
it "handles special characters in text fields" do
|
||||
pending "update this after VRFS-1609 is resolved"
|
||||
navigate_band_setup
|
||||
band_name = garbage(3) + ' ' + garbage(50)
|
||||
band_bio = garbage(500)
|
||||
band_website = garbage(500)
|
||||
complete_band_setup_form(band_name, band_bio, 'band-website' => band_website)
|
||||
|
||||
expect(page).to have_selector('#band-profile-name', text: band_name)
|
||||
expect(page).to have_selector('#band-profile-biography', text: band_bio)
|
||||
end
|
||||
|
||||
it "another user receives invite notification during Band Setup"
|
||||
end
|
||||
|
||||
|
||||
context "about view" do
|
||||
it "displays the band's information to another user"
|
||||
#photo
|
||||
#name
|
||||
#website address
|
||||
#country, state, city
|
||||
#biography/description
|
||||
#genres chosen
|
||||
#number of followers, recordings, sessions
|
||||
#actions: follow button
|
||||
|
||||
it "allows a user to follow the band"
|
||||
end
|
||||
|
||||
context "members view" do
|
||||
it "photo and name links to the musician's profile page"
|
||||
it "displays photo, name, location, instruments played"
|
||||
it "displays a hover bubble containing more info on musician"
|
||||
it "displays any pending band invitations when viewed by current band member"
|
||||
|
||||
end
|
||||
|
||||
context "history view" do
|
||||
it "shows public info"
|
||||
it "does not show private info to non-band user"
|
||||
it "shows private info to band user"
|
||||
end
|
||||
|
||||
context "social view" do
|
||||
it "displays musicians and fans who follow band"
|
||||
end
|
||||
|
||||
context "band profile - editing" do
|
||||
it "about page shows the current band's info when 'Edit Profile' is clicked"
|
||||
it "members page shows 'Edit Members' button and user can remove member"
|
||||
it "non-member cannot Edit Profile"
|
||||
it "non-member cannot Edit Members"
|
||||
end
|
||||
|
||||
it "band shows up in sidebar search result"
|
||||
end
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -4,11 +4,6 @@ describe "Notification Highlighter", :js => true, :type => :feature, :capybara_f
|
|||
|
||||
subject { page }
|
||||
|
||||
before(:all) do
|
||||
Capybara.javascript_driver = :poltergeist
|
||||
Capybara.current_driver = Capybara.javascript_driver
|
||||
Capybara.default_wait_time = 10
|
||||
end
|
||||
|
||||
let(:user) { FactoryGirl.create(:user) }
|
||||
let(:user2) { FactoryGirl.create(:user) }
|
||||
|
|
@ -138,30 +133,34 @@ describe "Notification Highlighter", :js => true, :type => :feature, :capybara_f
|
|||
end
|
||||
end
|
||||
|
||||
describe "delete notification" do
|
||||
|
||||
describe "user no unseen notifications" do
|
||||
describe "notification occurs in realtime" do
|
||||
before(:each) do
|
||||
User.delete_all
|
||||
end
|
||||
|
||||
describe "sidebar is open" do
|
||||
describe "user can see notifications" do
|
||||
it "stays deactivated" do
|
||||
it "while notification panel closed" do
|
||||
# we should see the count go to 1, but once the notification is accepted, which causes it to delete,
|
||||
# we should see the count go back down to 0.
|
||||
|
||||
end
|
||||
end
|
||||
in_client(user) do
|
||||
sign_in_poltergeist(user)
|
||||
end
|
||||
|
||||
describe "user can not see notifications" do
|
||||
describe "with dialog open" do
|
||||
it "temporarily activates" do
|
||||
in_client(user2) do
|
||||
sign_in_poltergeist(user2)
|
||||
find_musician(user)
|
||||
find(".result-list-button-wrapper[data-musician-id='#{user.id}'] .search-m-friend").trigger(:click)
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
in_client(user) do
|
||||
badge = find("#{NOTIFICATION_PANEL} .badge", text: '1')
|
||||
badge['class'].include?('highlighted').should == true
|
||||
|
||||
describe "with document blurred" do
|
||||
it "temporarily activates" do
|
||||
find('#notification #ok-button', text: 'ACCEPT').trigger(:click)
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
badge = find("#{NOTIFICATION_PANEL} .badge", text: '0')
|
||||
badge['class'].include?('highlighted').should == false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,117 @@
|
|||
require 'spec_helper'
|
||||
|
||||
# tests what happens when the websocket connection goes away
|
||||
describe "Reconnect", :js => true, :type => :feature, :capybara_feature => true do
|
||||
|
||||
subject { page }
|
||||
|
||||
let(:user1) { FactoryGirl.create(:user) }
|
||||
let(:user2) { FactoryGirl.create(:user) }
|
||||
|
||||
before(:all) do
|
||||
User.delete_all
|
||||
end
|
||||
|
||||
before(:each) do
|
||||
emulate_client
|
||||
end
|
||||
|
||||
it "websocket connection is down on initial connection" do
|
||||
|
||||
FactoryGirl.create(:friendship, :user => user1, :friend => user2)
|
||||
FactoryGirl.create(:friendship, :user => user2, :friend => user1)
|
||||
|
||||
Rails.application.config.stub(:websocket_gateway_uri).and_return('ws://localhost:99/websocket') # bogus port
|
||||
|
||||
sign_in_poltergeist(user1, validate: false)
|
||||
|
||||
page.should have_selector('.no-websocket-connection')
|
||||
|
||||
find('.homecard.createsession').trigger(:click)
|
||||
find('h1', text:'create session')
|
||||
find('#btn-create-session').trigger(:click)
|
||||
find('#notification h2', text: 'Not Connected') # get notified you can't go to create session
|
||||
page.evaluate_script('window.history.back()')
|
||||
|
||||
find('.homecard.findsession').trigger(:click)
|
||||
find('#notification h2', text: 'Not Connected') # get notified you can't go to find session
|
||||
find('h2', text: 'create session') # and be back on home screen
|
||||
|
||||
find('.homecard.feed').trigger(:click)
|
||||
find('h1', text:'feed')
|
||||
page.evaluate_script('window.history.back()')
|
||||
|
||||
find('.homecard.musicians').trigger(:click)
|
||||
find('h1', text:'musicians')
|
||||
page.evaluate_script('window.history.back()')
|
||||
|
||||
find('.homecard.profile').trigger(:click)
|
||||
find('h1', text:'profile')
|
||||
page.evaluate_script('window.history.back()')
|
||||
|
||||
find('.homecard.account').trigger(:click)
|
||||
find('h1', text:'account')
|
||||
page.evaluate_script('window.history.back()')
|
||||
|
||||
initiate_text_dialog user2
|
||||
|
||||
find('span.disconnected-msg', text: 'DISCONNECTED FROM SERVER')
|
||||
end
|
||||
|
||||
it "websocket goes down on home page" do
|
||||
|
||||
sign_in_poltergeist(user1)
|
||||
|
||||
5.times do
|
||||
close_websocket
|
||||
|
||||
# we should see indication that the websocket is down
|
||||
page.should have_selector('.no-websocket-connection')
|
||||
|
||||
# but.. after a few seconds, it should reconnect on it's own
|
||||
page.should_not have_selector('.no-websocket-connection')
|
||||
end
|
||||
|
||||
# then verify we can create a session
|
||||
|
||||
create_join_session(user1, [user2])
|
||||
|
||||
formal_leave_by user1
|
||||
|
||||
# websocket goes down while chatting
|
||||
in_client(user1) do
|
||||
initiate_text_dialog user2
|
||||
|
||||
# normal, happy dialog
|
||||
page.should_not have_selector('span.disconnected-msg', text: 'DISCONNECTED FROM SERVER')
|
||||
|
||||
close_websocket
|
||||
|
||||
# dialog-specific disconnect should show
|
||||
page.should have_selector('span.disconnected-msg', text: 'DISCONNECTED FROM SERVER')
|
||||
# and generic disconnect
|
||||
page.should have_selector('.no-websocket-connection')
|
||||
|
||||
# after a few seconds, the page should reconnect on it's own
|
||||
page.should_not have_selector('span.disconnected-msg', text: 'DISCONNECTED FROM SERVER')
|
||||
page.should_not have_selector('.no-websocket-connection')
|
||||
end
|
||||
end
|
||||
|
||||
it "websocket goes down on session page" do
|
||||
|
||||
create_session(creator: user1)
|
||||
|
||||
5.times do
|
||||
close_websocket
|
||||
|
||||
# we should see indication that the websocket is down
|
||||
page.should have_selector('h2', text: 'Disconnected from Server')
|
||||
|
||||
# but.. after a few seconds, it should reconnect on it's own
|
||||
page.should_not have_selector('h2', text: 'Disconnected from Server')
|
||||
|
||||
find('h1', text:'session')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -12,12 +12,12 @@ describe "Landing", :js => true, :type => :feature, :capybara_feature => true do
|
|||
|
||||
before(:each) do
|
||||
MusicSessionHistory.delete_all
|
||||
sign_in_poltergeist(user)
|
||||
end
|
||||
|
||||
let (:claimed_recording) { FactoryGirl.create(:claimed_recording) }
|
||||
|
||||
it "should render comments" do
|
||||
pending "weird error"
|
||||
|
||||
recording = ClaimedRecording.first
|
||||
comment = "test comment"
|
||||
|
|
@ -31,10 +31,10 @@ describe "Landing", :js => true, :type => :feature, :capybara_feature => true do
|
|||
# (1) Test a user creating a comment and ensure it displays.
|
||||
|
||||
# comment body
|
||||
find('div', text: comment)
|
||||
find('div.comment-text', text: comment)
|
||||
|
||||
# timestamp
|
||||
find('div', text: timestamp)
|
||||
find('div.comment-timestamp', text: timestamp)
|
||||
|
||||
# (2) Test a user visiting a landing page with an existing comment.
|
||||
|
||||
|
|
@ -42,9 +42,9 @@ describe "Landing", :js => true, :type => :feature, :capybara_feature => true do
|
|||
visit url
|
||||
|
||||
# comment body
|
||||
find('div', text: comment)
|
||||
find('div.comment-text', text: comment)
|
||||
|
||||
# timestamp
|
||||
find('div', text: timestamp)
|
||||
find('div.comment-timestamp', text: timestamp)
|
||||
end
|
||||
end
|
||||
|
|
@ -14,7 +14,6 @@ describe "Landing", :js => true, :type => :feature, :capybara_feature => true do
|
|||
end
|
||||
|
||||
it "should render comments" do
|
||||
pending "weird error"
|
||||
|
||||
msh = MusicSessionHistory.first
|
||||
comment = "test comment"
|
||||
|
|
@ -28,10 +27,10 @@ describe "Landing", :js => true, :type => :feature, :capybara_feature => true do
|
|||
find('#btnPostComment').trigger(:click)
|
||||
|
||||
# comment body
|
||||
find('div', text: comment)
|
||||
find('div.comment-text', text: comment)
|
||||
|
||||
# timestamp
|
||||
find('div', text: timestamp)
|
||||
find('div.comment-timestamp', text: timestamp)
|
||||
|
||||
# (2) Test a user visiting a landing page with an existing comment.
|
||||
|
||||
|
|
@ -39,9 +38,9 @@ describe "Landing", :js => true, :type => :feature, :capybara_feature => true do
|
|||
visit url
|
||||
|
||||
# comment body
|
||||
find('div', text: comment)
|
||||
find('div.comment-text', text: comment)
|
||||
|
||||
# timestamp
|
||||
find('div', text: timestamp)
|
||||
find('div.comment-timestamp', text: timestamp)
|
||||
end
|
||||
end
|
||||
|
|
@ -4,13 +4,8 @@ describe "Profile Menu", :js => true, :type => :feature, :capybara_feature => tr
|
|||
|
||||
subject { page }
|
||||
|
||||
before(:all) do
|
||||
Capybara.javascript_driver = :poltergeist
|
||||
Capybara.current_driver = Capybara.javascript_driver
|
||||
Capybara.default_wait_time = 10
|
||||
end
|
||||
|
||||
let(:user) { FactoryGirl.create(:user) }
|
||||
let(:user2) { FactoryGirl.create(:user) }
|
||||
|
||||
before(:each) do
|
||||
UserMailer.deliveries.clear
|
||||
|
|
@ -36,5 +31,19 @@ describe "Profile Menu", :js => true, :type => :feature, :capybara_feature => tr
|
|||
end
|
||||
end
|
||||
|
||||
describe "panel behavior" do
|
||||
it "search, then click notifications" do
|
||||
|
||||
notification = Notification.send_text_message("text message", user2, user)
|
||||
notification.errors.any?.should be_false
|
||||
|
||||
site_search(user2.name, validate: user2)
|
||||
|
||||
open_notifications
|
||||
|
||||
find("#sidebar-notification-list li[notification-id='#{notification.id}']")
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,3 +1,11 @@
|
|||
|
||||
# temporary to debug failing tests on the build server
|
||||
def bputs(msg)
|
||||
if ENV["BUILD_PUTS"] == "1"
|
||||
puts msg
|
||||
end
|
||||
end
|
||||
|
||||
require 'simplecov'
|
||||
require 'rubygems'
|
||||
#require 'spork'
|
||||
|
|
@ -7,18 +15,27 @@ require 'omniauth'
|
|||
|
||||
ENV["RAILS_ENV"] ||= 'test'
|
||||
|
||||
bputs "before activerecord load"
|
||||
|
||||
require 'active_record'
|
||||
require 'action_mailer'
|
||||
require 'jam_db'
|
||||
require "#{File.dirname(__FILE__)}/spec_db"
|
||||
|
||||
bputs "before db_config load"
|
||||
|
||||
# recreate test database and migrate it
|
||||
db_config = YAML::load(File.open('config/database.yml'))["test"]
|
||||
# initialize ActiveRecord's db connection\
|
||||
|
||||
|
||||
bputs "before recreate db"
|
||||
SpecDb::recreate_database(db_config)
|
||||
|
||||
bputs "before connect db"
|
||||
ActiveRecord::Base.establish_connection(YAML::load(File.open('config/database.yml'))["test"])
|
||||
|
||||
bputs "before load jam_ruby"
|
||||
require 'jam_ruby'
|
||||
|
||||
# uncomment this to see active record logs
|
||||
|
|
@ -34,13 +51,22 @@ tests_started = false
|
|||
|
||||
|
||||
Thread.new {
|
||||
sleep 30
|
||||
if ENV['BUILD_NUMBER']
|
||||
sleep 240
|
||||
else
|
||||
sleep 30
|
||||
end
|
||||
|
||||
unless tests_started
|
||||
bputs "tests are hung. exiting..."
|
||||
puts "tests are hung. exiting..."
|
||||
exit! 20
|
||||
end
|
||||
}
|
||||
|
||||
bputs "before load websocket server"
|
||||
|
||||
current = Thread.current
|
||||
Thread.new do
|
||||
ActiveRecord::Base.connection.disconnect!
|
||||
ActiveRecord::Base.establish_connection(YAML::load(File.open('config/database.yml'))["test"])
|
||||
|
|
@ -52,30 +78,47 @@ Thread.new do
|
|||
:connect_time_stale => 2,
|
||||
:connect_time_expire => 5,
|
||||
:rabbitmq_host => 'localhost',
|
||||
:rabbitmq_port => 5672)
|
||||
:rabbitmq_port => 5672,
|
||||
:calling_thread => current)
|
||||
rescue Exception => e
|
||||
puts "websocket-gateway failed: #{e}"
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
bputs "before websocket thread wait"
|
||||
Thread.stop
|
||||
|
||||
bputs "before connection reestablish"
|
||||
|
||||
ActiveRecord::Base.connection.disconnect!
|
||||
bputs "before connection reestablishing"
|
||||
ActiveRecord::Base.establish_connection(YAML::load(File.open('config/database.yml'))["test"])
|
||||
#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'
|
||||
bputs "before load environment"
|
||||
begin
|
||||
require File.expand_path("../../config/environment", __FILE__)
|
||||
rescue => e
|
||||
bputs "exception in load environment"
|
||||
bputs "e: #{e}"
|
||||
end
|
||||
|
||||
bputs "before loading rails"
|
||||
require 'rspec/rails'
|
||||
bputs "before connection autorun"
|
||||
require 'rspec/autorun'
|
||||
|
||||
|
||||
bputs "before load capybara"
|
||||
require 'capybara'
|
||||
require 'capybara/rspec'
|
||||
require 'capybara-screenshot/rspec'
|
||||
bputs "before load poltergeist"
|
||||
require 'capybara/poltergeist'
|
||||
bputs "before register capybara"
|
||||
Capybara.register_driver :poltergeist do |app|
|
||||
driver = Capybara::Poltergeist::Driver.new(app, { debug: false, phantomjs_logger: File.open('log/phantomjs.out', 'w') })
|
||||
end
|
||||
|
|
|
|||
|
|
@ -9,7 +9,10 @@ def site_search(text, options = {})
|
|||
fill_in "search-input", with: text
|
||||
end
|
||||
|
||||
find('#sidebar-search-expand').trigger(:click) if options[:expand]
|
||||
if options[:expand]
|
||||
page.driver.execute_script("jQuery('#searchForm').submit()")
|
||||
find('h1', text:'search results')
|
||||
end
|
||||
end
|
||||
|
||||
# goes to the musician tile, and tries to find a musician
|
||||
|
|
@ -37,6 +40,16 @@ def find_musician(user)
|
|||
raise "unable to find musician #{user}"
|
||||
end
|
||||
|
||||
def initiate_text_dialog(user)
|
||||
|
||||
# verify that the chat window is grayed out
|
||||
site_search(user.first_name, expand: true)
|
||||
|
||||
find("#search-results a[user-id=\"#{user.id}\"][hoveraction=\"musician\"]", text: user.name).hover_intent
|
||||
find('#musician-hover #btnMessage').trigger(:click)
|
||||
find('h1', text: 'conversation with ' + user.name)
|
||||
end
|
||||
|
||||
# sends a text message in the chat interface.
|
||||
def send_text_message(msg, options={})
|
||||
find('#text-message-dialog') # assert that the dialog is showing already
|
||||
|
|
@ -62,6 +75,7 @@ def open_notifications
|
|||
find("#{NOTIFICATION_PANEL} .panel-header").trigger(:click)
|
||||
end
|
||||
|
||||
|
||||
def hover_intent(element)
|
||||
element.hover
|
||||
element.hover
|
||||
|
|
@ -95,4 +109,8 @@ end
|
|||
# simulates focus event on window
|
||||
def window_focus
|
||||
page.evaluate_script(%{window.jQuery(window).trigger('focus');})
|
||||
end
|
||||
|
||||
def close_websocket
|
||||
page.evaluate_script("window.JK.JamServer.close(true)")
|
||||
end
|
||||
|
|
@ -4,10 +4,19 @@ include ApplicationHelper
|
|||
module Capybara
|
||||
module Node
|
||||
class Element
|
||||
|
||||
def attempt_hover
|
||||
begin
|
||||
hover
|
||||
rescue => e
|
||||
end
|
||||
end
|
||||
def hover_intent
|
||||
hover
|
||||
hover
|
||||
hover
|
||||
sleep 0.3
|
||||
attempt_hover
|
||||
sleep 0.3
|
||||
attempt_hover
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -86,13 +95,19 @@ def set_cookie(k, v)
|
|||
end
|
||||
end
|
||||
|
||||
def sign_in_poltergeist(user)
|
||||
def sign_in_poltergeist(user, options = {})
|
||||
validate = options[:validate]
|
||||
validate = true if validate.nil?
|
||||
|
||||
visit signin_path
|
||||
fill_in "Email Address:", with: user.email
|
||||
fill_in "Password:", with: user.password
|
||||
click_button "SIGN IN"
|
||||
|
||||
wait_until_curtain_gone
|
||||
|
||||
# presence of this means websocket gateway is not working
|
||||
page.should have_no_selector('.no-websocket-connection') if validate
|
||||
end
|
||||
|
||||
|
||||
|
|
@ -400,6 +415,13 @@ def assert_all_tracks_seen(users=[])
|
|||
end
|
||||
end
|
||||
|
||||
def view_profile_of user
|
||||
id = user.kind_of?(JamRuby::User) ? user.id : user
|
||||
# assume some user signed in already (allows reuse in multi-user tests)
|
||||
visit "/client#/profile/#{id}"
|
||||
wait_until_curtain_gone
|
||||
end
|
||||
|
||||
def show_user_menu
|
||||
page.execute_script("$('ul.shortcuts').show()")
|
||||
#page.execute_script("JK.UserDropdown.menuHoverIn()")
|
||||
|
|
@ -411,8 +433,19 @@ def wait_for_easydropdown(select)
|
|||
end
|
||||
|
||||
# defaults to enter key (13)
|
||||
def send_key(keycode = 13)
|
||||
keypress_script = "var e = $.Event('keydown', { keyCode: #{keycode} }); $('#search-input').trigger(e);"
|
||||
def send_key(selector, keycode = 13)
|
||||
keypress_script = "var e = $.Event('keyup', { keyCode: #{keycode} }); jQuery('#{selector}').trigger(e);"
|
||||
page.driver.execute_script(keypress_script)
|
||||
|
||||
end
|
||||
|
||||
def special_characters
|
||||
["?", "[", "]", "/", "\\", "=", "<", ">", ":", ";", ",", "'", "\"", "&", "$", "#", "*", "(", ")", "|", "~", "`", "!", "{", "}"]
|
||||
end
|
||||
|
||||
def garbage length
|
||||
output = ''
|
||||
length.times { output << special_characters.sample }
|
||||
output.gsub!(/<[\/|!|\?]/, '/<') # security risk -- avoids inputting tags until VRFS-1609 resolved
|
||||
output.slice(0, length)
|
||||
end
|
||||
|
|
@ -35,7 +35,7 @@ gem 'aasm', '3.0.16'
|
|||
gem 'carrierwave'
|
||||
gem 'devise'
|
||||
gem 'postgres-copy'
|
||||
gem 'aws-sdk', '1.29.1'
|
||||
gem 'aws-sdk' #, '1.29.1'
|
||||
gem 'bugsnag'
|
||||
gem 'postgres_ext'
|
||||
gem 'resque'
|
||||
|
|
|
|||
|
|
@ -12,13 +12,13 @@ module JamWebsockets
|
|||
end
|
||||
|
||||
def run(options={})
|
||||
|
||||
host = "0.0.0.0"
|
||||
port = options[:port]
|
||||
connect_time_stale = options[:connect_time_stale].to_i
|
||||
connect_time_expire = options[:connect_time_expire].to_i
|
||||
rabbitmq_host = options[:rabbitmq_host]
|
||||
rabbitmq_port = options[:rabbitmq_port].to_i
|
||||
calling_thread = options[:calling_thread]
|
||||
|
||||
@log.info "starting server #{host}:#{port} staleness_time=#{connect_time_stale}; reconnect time = #{connect_time_expire}, rabbitmq=#{rabbitmq_host}:#{rabbitmq_port}"
|
||||
|
||||
|
|
@ -36,6 +36,7 @@ module JamWebsockets
|
|||
start_connection_expiration(expire_time)
|
||||
start_connection_flagger(connect_time_stale)
|
||||
start_websocket_listener(host, port, options[:emwebsocket_debug])
|
||||
calling_thread.wakeup if calling_thread
|
||||
end
|
||||
|
||||
# if you don't do this, the app won't exit unless you kill -9
|
||||
|
|
@ -55,6 +56,7 @@ module JamWebsockets
|
|||
@log.info "new client #{ws}"
|
||||
@router.new_client(ws)
|
||||
end
|
||||
@log.debug("started websocket")
|
||||
end
|
||||
|
||||
def start_connection_expiration(stale_max_time)
|
||||
|
|
|
|||
Loading…
Reference in New Issue