diff --git a/admin/Gemfile b/admin/Gemfile
index b121b5991..c8b1d1772 100644
--- a/admin/Gemfile
+++ b/admin/Gemfile
@@ -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'
diff --git a/ruby/Gemfile b/ruby/Gemfile
index 6ad832f29..c1f1b078e 100644
--- a/ruby/Gemfile
+++ b/ruby/Gemfile
@@ -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'
diff --git a/ruby/lib/jam_ruby/lib/em_helper.rb b/ruby/lib/jam_ruby/lib/em_helper.rb
index 2e8c46c55..c310cf2bf 100644
--- a/ruby/lib/jam_ruby/lib/em_helper.rb
+++ b/ruby/lib/jam_ruby/lib/em_helper.rb
@@ -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
diff --git a/ruby/lib/jam_ruby/models/email_batch.rb b/ruby/lib/jam_ruby/models/email_batch.rb
index bdd3f2e7d..309b63305 100644
--- a/ruby/lib/jam_ruby/models/email_batch.rb
+++ b/ruby/lib/jam_ruby/models/email_batch.rb
@@ -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
diff --git a/ruby/lib/jam_ruby/models/notification.rb b/ruby/lib/jam_ruby/models/notification.rb
index 37180190d..4318619d2 100644
--- a/ruby/lib/jam_ruby/models/notification.rb
+++ b/ruby/lib/jam_ruby/models/notification.rb
@@ -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
diff --git a/ruby/spec/jam_ruby/models/recorded_track_spec.rb b/ruby/spec/jam_ruby/models/recorded_track_spec.rb
index 7fb0add63..42c197bc9 100644
--- a/ruby/spec/jam_ruby/models/recorded_track_spec.rb
+++ b/ruby/spec/jam_ruby/models/recorded_track_spec.rb
@@ -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'
diff --git a/ruby/spec/spec_helper.rb b/ruby/spec/spec_helper.rb
index 1d65f5330..c29b007e6 100644
--- a/ruby/spec/spec_helper.rb
+++ b/ruby/spec/spec_helper.rb
@@ -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
diff --git a/ruby/spec/support/utilities.rb b/ruby/spec/support/utilities.rb
index 564ec7b34..0f78e56c4 100644
--- a/ruby/spec/support/utilities.rb
+++ b/ruby/spec/support/utilities.rb
@@ -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
diff --git a/web/Gemfile b/web/Gemfile
index 04415dab0..2595343ce 100644
--- a/web/Gemfile
+++ b/web/Gemfile
@@ -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'
diff --git a/web/app/assets/javascripts/JamServer.js b/web/app/assets/javascripts/JamServer.js
index 0544f4c7c..d1126adf1 100644
--- a/web/app/assets/javascripts/JamServer.js
+++ b/web/app/assets/javascripts/JamServer.js
@@ -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 $('' + secs + ' ' + (secs == 1 ? ' second.s' : 'seconds.') + '');
+ }
+
+ 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);
\ No newline at end of file
diff --git a/web/app/assets/javascripts/banner.js b/web/app/assets/javascripts/banner.js
index d0aa78aa7..535d841c3 100644
--- a/web/app/assets/javascripts/banner.js
+++ b/web/app/assets/javascripts/banner.js
@@ -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
diff --git a/web/app/assets/javascripts/createSession.js.erb b/web/app/assets/javascripts/createSession.js.erb
index b572c2ca7..e5744cbfe 100644
--- a/web/app/assets/javascripts/createSession.js.erb
+++ b/web/app/assets/javascripts/createSession.js.erb
@@ -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); };
diff --git a/web/app/assets/javascripts/findSession.js b/web/app/assets/javascripts/findSession.js
index 11001f3e3..e9b2dd964 100644
--- a/web/app/assets/javascripts/findSession.js
+++ b/web/app/assets/javascripts/findSession.js
@@ -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();
diff --git a/web/app/assets/javascripts/hoverBand.js b/web/app/assets/javascripts/hoverBand.js
index 2b98e8e62..d6498f91a 100644
--- a/web/app/assets/javascripts/hoverBand.js
+++ b/web/app/assets/javascripts/hoverBand.js
@@ -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 = '
';
if (val.instruments) { // @FIXME: edge case for Test user that has no instruments?
$.each(val.instruments, function(index, instrument) {
- instrumentHtml += '  ';
+ instrumentHtml += '  + ') ';
});
}
diff --git a/web/app/assets/javascripts/hoverFan.js b/web/app/assets/javascripts/hoverFan.js
index 1aa510721..f5b9926d0 100644
--- a/web/app/assets/javascripts/hoverFan.js
+++ b/web/app/assets/javascripts/hoverFan.js
@@ -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})
diff --git a/web/app/assets/javascripts/hoverMusician.js b/web/app/assets/javascripts/hoverMusician.js
index 0cee5cb0f..6ce0368a6 100644
--- a/web/app/assets/javascripts/hoverMusician.js
+++ b/web/app/assets/javascripts/hoverMusician.js
@@ -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 += '  ';
+ instrumentHtml += '  + ') ';
});
// followings
diff --git a/web/app/assets/javascripts/hoverRecording.js b/web/app/assets/javascripts/hoverRecording.js
index 6d32b8ffa..3555b626e 100644
--- a/web/app/assets/javascripts/hoverRecording.js
+++ b/web/app/assets/javascripts/hoverRecording.js
@@ -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 = ' ';
$.each(val.instrument_ids, function(index, val) {
- instrumentHtml += '  ';
- })
+ instrumentHtml += '  + ') ';
+ });
instrumentHtml += ' | ';
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,
diff --git a/web/app/assets/javascripts/hoverSession.js b/web/app/assets/javascripts/hoverSession.js
index 373bd0cc6..f6a9843c5 100644
--- a/web/app/assets/javascripts/hoverSession.js
+++ b/web/app/assets/javascripts/hoverSession.js
@@ -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 = ' ';
var instruments = val.instruments.split("|");
$.each(instruments, function(index, instrument) {
- instrumentHtml += '  ';
+ instrumentHtml += '  + ') ';
});
instrumentHtml += ' | ';
@@ -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
});
diff --git a/web/app/assets/javascripts/jamkazam.js b/web/app/assets/javascripts/jamkazam.js
index f00e9b02d..4891c3509 100644
--- a/web/app/assets/javascripts/jamkazam.js
+++ b/web/app/assets/javascripts/jamkazam.js
@@ -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;
diff --git a/web/app/assets/javascripts/layout.js b/web/app/assets/javascripts/layout.js
index 0d4ec3dbe..a32df7763 100644
--- a/web/app/assets/javascripts/layout.js
+++ b/web/app/assets/javascripts/layout.js
@@ -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;
diff --git a/web/app/assets/javascripts/notificationPanel.js b/web/app/assets/javascripts/notificationPanel.js
index 9e183324b..454c11b04 100644
--- a/web/app/assets/javascripts/notificationPanel.js
+++ b/web/app/assets/javascripts/notificationPanel.js
@@ -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
});
diff --git a/web/app/assets/javascripts/searchResults.js b/web/app/assets/javascripts/searchResults.js
index 7cb7a0f9d..ee6f34981 100644
--- a/web/app/assets/javascripts/searchResults.js
+++ b/web/app/assets/javascripts/searchResults.js
@@ -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);
diff --git a/web/app/assets/javascripts/session.js b/web/app/assets/javascripts/session.js
index aef2240f7..e667e27c0 100644
--- a/web/app/assets/javascripts/session.js
+++ b/web/app/assets/javascripts/session.js
@@ -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);
};
diff --git a/web/app/assets/javascripts/sessionList.js b/web/app/assets/javascripts/sessionList.js
index 37539b3b0..368b56ffe 100644
--- a/web/app/assets/javascripts/sessionList.js
+++ b/web/app/assets/javascripts/sessionList.js
@@ -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); };
diff --git a/web/app/assets/javascripts/sessionModel.js b/web/app/assets/javascripts/sessionModel.js
index 31a057e14..4cecf82bb 100644
--- a/web/app/assets/javascripts/sessionModel.js
+++ b/web/app/assets/javascripts/sessionModel.js
@@ -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?
diff --git a/web/app/assets/javascripts/sidebar.js b/web/app/assets/javascripts/sidebar.js
index bac8a80cf..57253d053 100644
--- a/web/app/assets/javascripts/sidebar.js
+++ b/web/app/assets/javascripts/sidebar.js
@@ -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(){
diff --git a/web/app/assets/javascripts/textMessageDialog.js b/web/app/assets/javascripts/textMessageDialog.js
index 3e3049880..8099da112 100644
--- a/web/app/assets/javascripts/textMessageDialog.js
+++ b/web/app/assets/javascripts/textMessageDialog.js
@@ -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();
}
diff --git a/web/app/assets/javascripts/utils.js b/web/app/assets/javascripts/utils.js
index 657ab1ed8..057ffd3f7 100644
--- a/web/app/assets/javascripts/utils.js
+++ b/web/app/assets/javascripts/utils.js
@@ -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
});
}
diff --git a/web/app/assets/javascripts/web/recordings.js b/web/app/assets/javascripts/web/recordings.js
index 1793b2609..5a901a81e 100644
--- a/web/app/assets/javascripts/web/recordings.js
+++ b/web/app/assets/javascripts/web/recordings.js
@@ -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;
diff --git a/web/app/assets/javascripts/web/sessions.js b/web/app/assets/javascripts/web/sessions.js
index 070333da5..55a569142 100644
--- a/web/app/assets/javascripts/web/sessions.js
+++ b/web/app/assets/javascripts/web/sessions.js
@@ -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;
diff --git a/web/app/assets/stylesheets/client/banner.css.scss b/web/app/assets/stylesheets/client/banner.css.scss
index e3b8de209..c036c5dbd 100644
--- a/web/app/assets/stylesheets/client/banner.css.scss
+++ b/web/app/assets/stylesheets/client/banner.css.scss
@@ -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;
-}
\ No newline at end of file
diff --git a/web/app/assets/stylesheets/client/client.css b/web/app/assets/stylesheets/client/client.css
index acdbd914a..8dfded0bf 100644
--- a/web/app/assets/stylesheets/client/client.css
+++ b/web/app/assets/stylesheets/client/client.css
@@ -33,6 +33,7 @@
*= require ./account
*= require ./search
*= require ./ftue
+ *= require ./jamServer
*= require ./gearWizard
*= require ./whatsNextDialog
*= require ./invitationDialog
diff --git a/web/app/assets/stylesheets/client/hoverBubble.css.scss b/web/app/assets/stylesheets/client/hoverBubble.css.scss
index 5016310ac..3d2efd754 100644
--- a/web/app/assets/stylesheets/client/hoverBubble.css.scss
+++ b/web/app/assets/stylesheets/client/hoverBubble.css.scss
@@ -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 {
diff --git a/web/app/assets/stylesheets/client/jamServer.css.scss b/web/app/assets/stylesheets/client/jamServer.css.scss
new file mode 100644
index 000000000..95e1ecf34
--- /dev/null
+++ b/web/app/assets/stylesheets/client/jamServer.css.scss
@@ -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 {
+
+ }
+}
+
diff --git a/web/app/assets/stylesheets/client/textMessageDialog.css.scss b/web/app/assets/stylesheets/client/textMessageDialog.css.scss
index 5605adfce..4a60bb78d 100644
--- a/web/app/assets/stylesheets/client/textMessageDialog.css.scss
+++ b/web/app/assets/stylesheets/client/textMessageDialog.css.scss
@@ -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;
+ }
+ }
+
+
}
\ No newline at end of file
diff --git a/web/app/assets/stylesheets/minimal/minimal_main.css.scss b/web/app/assets/stylesheets/minimal/minimal_main.css.scss
index 5a170e128..50feaf50f 100644
--- a/web/app/assets/stylesheets/minimal/minimal_main.css.scss
+++ b/web/app/assets/stylesheets/minimal/minimal_main.css.scss
@@ -9,3 +9,8 @@ body {
height:100%;
margin:0 !important;
}
+
+.wrapper {
+ width:1280px;
+ margin:0 auto;
+}
\ No newline at end of file
diff --git a/web/app/controllers/extras_controller.rb b/web/app/controllers/extras_controller.rb
new file mode 100644
index 000000000..131a316d0
--- /dev/null
+++ b/web/app/controllers/extras_controller.rb
@@ -0,0 +1,9 @@
+class ExtrasController < ApplicationController
+
+ before_filter :signed_in_user
+ before_filter :admin_user
+
+ def settings
+ render layout: "web"
+ end
+end
diff --git a/web/app/controllers/users_controller.rb b/web/app/controllers/users_controller.rb
index cea76d118..d31bb4094 100644
--- a/web/app/controllers/users_controller.rb
+++ b/web/app/controllers/users_controller.rb
@@ -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)
diff --git a/web/app/controllers/videos_controller.rb b/web/app/controllers/videos_controller.rb
deleted file mode 100644
index a63343776..000000000
--- a/web/app/controllers/videos_controller.rb
+++ /dev/null
@@ -1,8 +0,0 @@
-
-class VideosController < ApplicationController
-
- def show_dialog
- @video_id = @params[:video_id]
- end
-
-end
\ No newline at end of file
diff --git a/web/app/helpers/sessions_helper.rb b/web/app/helpers/sessions_helper.rb
index 9966731a6..98b0c9774 100644
--- a/web/app/helpers/sessions_helper.rb
+++ b/web/app/helpers/sessions_helper.rb
@@ -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)
diff --git a/web/app/views/api_claimed_recordings/show.rabl b/web/app/views/api_claimed_recordings/show.rabl
index 683c1bed9..5210fcbc9 100644
--- a/web/app/views/api_claimed_recordings/show.rabl
+++ b/web/app/views/api_claimed_recordings/show.rabl
@@ -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
}
}
}
diff --git a/web/app/views/api_music_sessions/history_show.rabl b/web/app/views/api_music_sessions/history_show.rabl
index 841e427d5..d29de5af3 100644
--- a/web/app/views/api_music_sessions/history_show.rabl
+++ b/web/app/views/api_music_sessions/history_show.rabl
@@ -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
+ }
}
\ No newline at end of file
diff --git a/web/app/views/clients/_banner.html.erb b/web/app/views/clients/_banner.html.erb
index 9a932e3a9..4a7cd1d6e 100644
--- a/web/app/views/clients/_banner.html.erb
+++ b/web/app/views/clients/_banner.html.erb
@@ -1,7 +1,7 @@
-
+
diff --git a/web/app/views/clients/_jamServer.html.haml b/web/app/views/clients/_jamServer.html.haml
new file mode 100644
index 000000000..46d9d4c9b
--- /dev/null
+++ b/web/app/views/clients/_jamServer.html.haml
@@ -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
\ No newline at end of file
diff --git a/web/app/views/clients/_sidebar.html.erb b/web/app/views/clients/_sidebar.html.erb
index 7b276eac4..4cf8d0804 100644
--- a/web/app/views/clients/_sidebar.html.erb
+++ b/web/app/views/clients/_sidebar.html.erb
@@ -11,28 +11,36 @@
-
- |