diff --git a/app/assets/javascripts/AAB_message_factory.js b/app/assets/javascripts/AAB_message_factory.js index 7159e549c..e8cff21e8 100644 --- a/app/assets/javascripts/AAB_message_factory.js +++ b/app/assets/javascripts/AAB_message_factory.js @@ -18,6 +18,7 @@ LEAVE_MUSIC_SESSION : "LEAVE_MUSIC_SESSION", LEAVE_MUSIC_SESSION_ACK : "LEAVE_MUSIC_SESSION_ACK", HEARTBEAT : "HEARTBEAT", + HEARTBEAT_ACK : "HEARTBEAT_ACK", FRIEND_UPDATE : "FRIEND_UPDATE", SESSION_INVITATION : "SESSION_INVITATION", JOIN_REQUEST : "JOIN_REQUEST", diff --git a/app/assets/javascripts/JamServer.js b/app/assets/javascripts/JamServer.js index d635bf7fd..bfbb66425 100644 --- a/app/assets/javascripts/JamServer.js +++ b/app/assets/javascripts/JamServer.js @@ -52,6 +52,15 @@ server.socket.onclose = server.onClose; }; + server.close = function(in_error) { + logger.log("closing websocket"); + + server.socket.close(); + + if(in_error) { + context.JK.CurrentSessionModel.onWebsocketDisconnected(); + } + } server.rememberLogin = function() { var token, loginMessage; token = $.cookie("remember_token"); @@ -95,7 +104,8 @@ context.jamClient.connected = false; } - // TODO: reconnect + context.JK.CurrentSessionModel.onWebsocketDisconnected(); + }; server.send = function(message) { diff --git a/app/assets/javascripts/banner.js b/app/assets/javascripts/banner.js new file mode 100644 index 000000000..d0aa78aa7 --- /dev/null +++ b/app/assets/javascripts/banner.js @@ -0,0 +1,56 @@ +(function(context,$) { + + "use strict"; + + context.JK = context.JK || {}; + context.JK.Banner = (function() { + var self = this; + var logger = context.JK.logger; + + // responsible for updating the contents of the update dialog + // as well as registering for any event handlers + function show(options) { + var text = options.text; + var html = options.html; + + var newContent = null; + if (html) { + newContent = $('#banner .dialog-inner').html(html); + } + else if(text) { + newContent = $('#banner .dialog-inner').html(text); + } + else { + console.error("unable to show banner for empty message") + return newContent; + } + + $('#banner').show() + $('#banner_overlay').show() + + // return the core of the banner so that caller can attach event handlers to newly created HTML + return newContent; + } + + function hide() { + $('#banner').hide(); + $('#banner_overlay .dialog-inner').html(""); + $('#banner_overlay').hide(); + } + + function initialize() { + + return self; + } + + // Expose publics + var me = { + initialize: initialize, + show : show, + hide : hide + } + + return me; + })(); + +})(window,jQuery); \ No newline at end of file diff --git a/app/assets/javascripts/ftue.js b/app/assets/javascripts/ftue.js index 6ccb0f8ce..9653e0126 100644 --- a/app/assets/javascripts/ftue.js +++ b/app/assets/javascripts/ftue.js @@ -76,6 +76,10 @@ function settingsInit() { jamClient.FTUEInit(); setLevels(0); + // Always reset the driver select box to "Choose..." which forces everything + // to sync properly when the user reselects their driver of choice. + // VRFS-375 and VRFS-561 + $('[layout-wizard="ftue"] [layout-wizard-step="2"] .asio-settings .settings-driver select').val(""); } function setLevels(db) { @@ -266,7 +270,29 @@ ftueSave(false); } + function videoLinkClicked(evt) { + var myOS = jamClient.GetOSAsString(); + var link; + if (myOS === 'MacOSX') { + link = $(evt.currentTarget).attr('external-link-mac'); + } else if (myOS === 'Win32') { + link = $(evt.currentTarget).attr('external-link-win'); + } + if (link) { + context.jamClient.OpenSystemBrowser(link); + } + } + function events() { + $('.ftue-video-link').hover( + function(evt) { // handlerIn + $(this).addClass('hover'); + }, + function(evt) { // handlerOut + $(this).removeClass('hover'); + } + ); + $('.ftue-video-link').on('click', videoLinkClicked); $('[layout-wizard-step="2"] .settings-driver select').on('change', audioDriverChanged); $('[layout-wizard-step="2"] .settings-controls select').on('change', audioDeviceChanged); $('#btn-asio-control-panel').on('click', openASIOControlPanel); diff --git a/app/assets/javascripts/jam_rest.js b/app/assets/javascripts/jam_rest.js index 8ba454931..3236bd28d 100644 --- a/app/assets/javascripts/jam_rest.js +++ b/app/assets/javascripts/jam_rest.js @@ -144,6 +144,15 @@ }); } + /** check if the server is alive */ + function serverHealthCheck(options) { + return $.ajax({ + url: window.host, + cache: false, + dataType: "html" + }); + } + function getId(options) { var id = options && options["id"] @@ -201,6 +210,7 @@ this.getClientDownloads = getClientDownloads this.createInvitation = createInvitation; this.postFeedback = postFeedback; + this.serverHealthCheck = serverHealthCheck; return this; }; diff --git a/app/assets/javascripts/jamkazam.js b/app/assets/javascripts/jamkazam.js index 3fcd3642a..ac241f070 100644 --- a/app/assets/javascripts/jamkazam.js +++ b/app/assets/javascripts/jamkazam.js @@ -20,7 +20,9 @@ var app; var logger = context.JK.logger; var heartbeatInterval=null; + var heartbeatMS=null; var inBadState=false; + var lastHeartbeatAckTime=null; var opts = { layoutOpts: {} @@ -59,6 +61,15 @@ if (app.heartbeatActive) { var message = context.JK.MessageFactory.heartbeat(); context.JK.JamServer.send(message); + + // check if the server is still sending heartbeat acks back down + // this logic equates to 'if we have not received a heartbeat within 2 heartbeat intervals, then get upset + if(new Date().getTime() - lastHeartbeatAckTime.getTime() > heartbeatMS * 2) { + logger.error("no heartbeat ack received from server after twice heartbeat interval. giving up"); + clearInterval(heartbeatInterval); // stop future heartbeats + context.JK.JamServer.close(true); + + } } } @@ -67,9 +78,14 @@ $.cookie('client_id', payload.client_id); // $.cookie('remember_token', payload.token); // removed per vrfs-273/403 - var heartbeatMS = payload.heartbeat_interval * 1000; + 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); + lastHeartbeatAckTime = new Date(new Date().getTime() + heartbeatMS); // add a little forgiveness to server for initial heartbeat + } + + function heartbeatAck(header, payload) { + lastHeartbeatAckTime = new Date(); } /** @@ -99,6 +115,12 @@ 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"); context.JK.JamServer.registerMessageCallback(context.JK.MessageType.SERVER_BAD_STATE_ERROR, serverBadStateError); @@ -218,6 +240,7 @@ this.layout = new context.JK.Layout(); this.layout.initialize(this.opts.layoutOpts); registerLoginAck(); + registerHeartbeatAck(); registerBadStateRecovered(); registerBadStateError(); events(); diff --git a/app/assets/javascripts/session.js b/app/assets/javascripts/session.js index f33881e68..2cd3dcf70 100644 --- a/app/assets/javascripts/session.js +++ b/app/assets/javascripts/session.js @@ -121,22 +121,37 @@ } function afterCurrentUserLoaded() { - sessionModel = new context.JK.SessionModel( + // It seems the SessionModel should be a singleton. + // a client can only be in one session at a time, + // and other parts of the code want to know at any certain times + // about the current session, if any (for example, reconnect logic) + context.JK.CurrentSessionModel = sessionModel = new context.JK.SessionModel( context.JK.JamServer, - context.jamClient, - context.JK.userMe + context.jamClient ); + sessionModel.subscribe('sessionScreen', sessionChanged); - sessionModel.joinSession(sessionId); + sessionModel.joinSession(sessionId) + .fail(function(xhr, textStatus, errorMessage) { + if(xhr.status == 404) { + // we tried to join the session, but it's already gone. kick user back to join session screen + window.location = "#/findSession" + app.notify( + { title: "Unable to Join Session", + text: "The session you attempted to join is over." + }, + { no_cancel: true }); + }else { + app.ajaxError(xhr, textStatus, errorMessage); + } + }) } function beforeHide(data) { // track that the screen is inactive, to disable body-level handlers screenActive = false; - sessionModel.leaveCurrentSession(sessionId); - // 'unregister' for callbacks - context.jamClient.SessionRegisterCallback(""); - context.jamClient.SessionSetAlertCallback(""); + sessionModel.leaveCurrentSession() + .fail(app.ajaxError) } function sessionChanged() { @@ -364,7 +379,6 @@ ]); if (mixer) { myTrack = (mixer.group_id === ChannelGroupIds.AudioInputMusicGroup); - var gainPercent = percentFromMixerValue( mixer.range_low, mixer.range_high, mixer.volume_left); var muteClass = "enabled"; @@ -412,6 +426,25 @@ }); } + function connectTrackToMixer(trackSelector, clientId, mixerId, gainPercent) { + var vuOpts = $.extend({}, trackVuOpts); + var faderOpts = $.extend({}, trackFaderOpts); + faderOpts.faderId = mixerId; + var vuLeftSelector = trackSelector + " .track-vu-left"; + var vuRightSelector = trackSelector + " .track-vu-right"; + var faderSelector = trackSelector + " .track-gain"; + var $track = $('div.track[client-id="' + clientId + '"]'); + // Set mixer-id attributes and render VU/Fader + context.JK.VuHelpers.renderVU(vuLeftSelector, vuOpts); + $track.find('.track-vu-left').attr('mixer-id', mixerId + '_vul'); + context.JK.VuHelpers.renderVU(vuRightSelector, vuOpts); + $track.find('.track-vu-right').attr('mixer-id', mixerId + '_vur'); + context.JK.FaderHelpers.renderFader(faderSelector, faderOpts); + // Set gain position + context.JK.FaderHelpers.setFaderValue(mixerId, gainPercent); + context.JK.FaderHelpers.subscribe(mixerId, faderChanged); + } + // Function called on an interval when participants change. Mixers seem to // show up later, so we render the tracks from participants, but keep track // of the ones there weren't any mixers for, and continually try to find them @@ -428,32 +461,14 @@ ChannelGroupIds.PeerAudioInputMusicGroup ]); if (mixer) { - var vuOpts = $.extend({}, trackVuOpts); - var faderOpts = $.extend({}, trackFaderOpts); - faderOpts.faderId = mixer.id; - var baseSelector = 'div.track[client-id="' + key + '"]'; - var vuLeftSelector = baseSelector + " .track-vu-left"; - var vuRightSelector = baseSelector + " .track-vu-right"; - var faderSelector = baseSelector + " .track-gain"; - keysToDelete.push(key); var gainPercent = percentFromMixerValue( mixer.range_low, mixer.range_high, mixer.volume_left); + var trackSelector = 'div.track[client-id="' + key + '"]'; + connectTrackToMixer(trackSelector, key, mixer.id, gainPercent); var $track = $('div.track[client-id="' + key + '"]'); - // Set mixer-id attributes and render VU/Fader - context.JK.VuHelpers.renderVU(vuLeftSelector, vuOpts); - $track.find('.track-vu-left').attr('mixer-id', mixer.id + '_vul'); - context.JK.VuHelpers.renderVU(vuRightSelector, vuOpts); - $track.find('.track-vu-right').attr('mixer-id', mixer.id + '_vur'); - context.JK.FaderHelpers.renderFader(faderSelector, faderOpts); - context.JK.FaderHelpers.subscribe(mixer.id, faderChanged); - $track.find('.track-icon-mute').attr('mixer-id', mixer.id); $track.find('.track-icon-settings').attr('mixer-id', mixer.id); - - // Set gain position - context.JK.FaderHelpers.setFaderValue(mixer.id, gainPercent); - // Set mute state _toggleVisualMuteControl($track.find('.track-icon-mute'), mixer.mute); } @@ -513,25 +528,9 @@ $destination.append(newTrack); // Render VU meters and gain fader - // Find the last child (just-appended): - var trackCount = $(parentSelector + ' .session-track').length; - var selectorPrefix = parentSelector + ' .session-track:nth-child(' + String(trackCount) + ')'; - var vuOpts = $.extend({}, trackVuOpts); - var faderOpts = $.extend({}, trackFaderOpts); - faderOpts.faderId = trackData.mixerId; - var vuLeftSelector = selectorPrefix + ' .track-vu-left'; - var vuRightSelector = selectorPrefix + ' .track-vu-right'; - var faderSelector = selectorPrefix + ' .track-gain'; - context.JK.VuHelpers.renderVU(vuLeftSelector, vuOpts); - context.JK.VuHelpers.renderVU(vuRightSelector, vuOpts); - context.JK.FaderHelpers.renderFader(faderSelector, faderOpts); - context.JK.FaderHelpers.subscribe(trackData.mixerId, faderChanged); - // Visually update fader to underlying mixer start value. - if (trackData.gainPercent) { - context.JK.FaderHelpers.setFaderValue(trackData.mixerId, trackData.gainPercent); - } else { - context.JK.FaderHelpers.setFaderValue(trackData.mixerId, 0); - } + var trackSelector = parentSelector + ' .session-track[client-id="' + trackData.clientId + '"]'; + var gainPercent = trackData.gainPercent || 0; + connectTrackToMixer(trackSelector, trackData.clientId, trackData.mixerId, gainPercent); var $closeButton = $('#div-track-close', 'div[track-id="' + trackData.trackId + '"]'); if (index === 0) { @@ -564,11 +563,12 @@ function handleVolumeChangeCallback(mixerId, isLeft, value) { // Visually update mixer + // There is no need to actually set the back-end mixer value as the + // back-end will already have updated the audio mixer directly prior to sending + // me this event. I simply need to visually show the new fader position. // TODO: Use mixer's range var faderValue = percentFromMixerValue(-80, 20, value); context.JK.FaderHelpers.setFaderValue(mixerId, faderValue); - fillTrackVolumeObject(mixerId, false); // don't broadcast - setMixerVolume(mixerId, faderValue); } function handleBridgeCallback() { @@ -593,6 +593,8 @@ } _updateVU(mixerId, vuVal); } else if (eventName === 'add' || eventName === 'remove') { + //logger.dbg('non-vu event: ' + eventName + ',' + mixerId + ',' + value); + // TODO - _renderSession. Note I get streams of these in // sequence, so have Nat fix, or buffer/spam protect // Note - this is already handled from websocket events. diff --git a/app/assets/javascripts/sessionModel.js b/app/assets/javascripts/sessionModel.js index 6c400ea95..859e645b4 100644 --- a/app/assets/javascripts/sessionModel.js +++ b/app/assets/javascripts/sessionModel.js @@ -7,12 +7,13 @@ context.JK = context.JK || {}; var logger = context.JK.logger; - context.JK.SessionModel = function(server, client, currentUser) { + context.JK.SessionModel = function(server, client) { var clientId = client.clientID; var currentSessionId = null; // Set on join, prior to setting currentSession. var currentSession = null; var subscribers = {}; var users = {}; // User info for session participants + var rest = context.JK.Rest(); function id() { return currentSession.id; @@ -32,24 +33,54 @@ function joinSession(sessionId) { currentSessionId = sessionId; logger.debug("SessionModel.joinSession(" + sessionId + ")"); - joinSessionRest(sessionId, function() { - refreshCurrentSession(); - }); - server.registerMessageCallback(context.JK.MessageType.MUSICIAN_SESSION_JOIN, refreshCurrentSession); - server.registerMessageCallback(context.JK.MessageType.MUSICIAN_SESSION_DEPART, refreshCurrentSession); + var deferred = joinSessionRest(sessionId); + + deferred + .done(function(){ + logger.debug("calling jamClient.JoinSession"); + client.JoinSession({ sessionID: sessionId }); + refreshCurrentSession(); + server.registerMessageCallback(context.JK.MessageType.MUSICIAN_SESSION_JOIN, refreshCurrentSession); + server.registerMessageCallback(context.JK.MessageType.MUSICIAN_SESSION_DEPART, refreshCurrentSession); + }); + + return deferred; } /** - * Leave the current session + * Leave the current session, if there is one. + * callback: called in all conditions; either after an attempt is made to tell the server that we are leaving, + * or immediately if there is no session */ function leaveCurrentSession() { - logger.debug("SessionModel.leaveCurrentSession()"); - // TODO - sessionChanged will be called with currentSession = null - server.unregisterMessageCallback(context.JK.MessageType.MUSICIAN_SESSION_JOIN, refreshCurrentSession); - server.unregisterMessageCallback(context.JK.MessageType.MUSICIAN_SESSION_DEPART, refreshCurrentSession); - leaveSessionRest(currentSessionId, sessionChanged); - currentSession = null; - currentSessionId = null; + var deferred; + + if(currentSessionId) { + logger.debug("SessionModel.leaveCurrentSession()"); + // TODO - sessionChanged will be called with currentSession = null + server.unregisterMessageCallback(context.JK.MessageType.MUSICIAN_SESSION_JOIN, refreshCurrentSession); + server.unregisterMessageCallback(context.JK.MessageType.MUSICIAN_SESSION_DEPART, refreshCurrentSession); + // leave the session right away without waiting on REST. Why? If you can't contact the server, or if it takes a long + // time, for that entire duration you'll still be sending voice data to the other users. + // this may be bad if someone decides to badmouth others in the left-session during this time + logger.debug("calling jamClient.LeaveSession for clientId=" + clientId); + client.LeaveSession({ sessionID: currentSessionId }); + deferred = leaveSessionRest(currentSessionId); + deferred.done(function() { + sessionChanged(); + }); + + // 'unregister' for callbacks + context.jamClient.SessionRegisterCallback(""); + context.jamClient.SessionSetAlertCallback(""); + currentSession = null; + currentSessionId = null; + } + else { + deferred = rest.serverHealthCheck(); + } + + return deferred; } /** @@ -268,7 +299,7 @@ * Make the server calls to join the current user to * the session provided. */ - function joinSessionRest(sessionId, callback) { + function joinSessionRest(sessionId) { var tracks = context.JK.TrackHelpers.getUserTracks(context.jamClient); var data = { client_id: clientId, @@ -277,38 +308,77 @@ tracks: tracks }; var url = "/api/sessions/" + sessionId + "/participants"; - $.ajax({ + return $.ajax({ type: "POST", dataType: "json", contentType: 'application/json', url: url, async: false, data: JSON.stringify(data), - processData:false, - success: function(response) { - logger.debug("calling jamClient.JoinSession"); - client.JoinSession({ sessionID: sessionId }); - callback(); - }, - error: ajaxError + processData:false }); } - function leaveSessionRest(sessionId, callback) { + function leaveSessionRest(sessionId) { var url = "/api/participants/" + clientId; - $.ajax({ + return $.ajax({ type: "DELETE", url: url, - async: false, - success: function (response) { - logger.debug("calling jamClient.LeaveSession for clientId=" + clientId); - client.LeaveSession({ sessionID: sessionId }); - callback(); - }, - error: ajaxError + async: false }); } + function reconnect() { + 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 + }); + + context.JK.CurrentSessionModel.leaveCurrentSession() + .done(function() { + reconnect(); + }) + .fail(function(xhr, textStatus, errorThrown) { + console.log("leaveCurrentSession failed: ", arguments); + + 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); + } + }); + + }); + } + + function onWebsocketDisconnected() { + var template = $('#template-disconnected').html(); + var templateHtml = context.JK.fillTemplate(template, null); + var content = context.JK.Banner.show({ + html : template + }) ; + + // kill the streaming of the session immediately + logger.debug("calling jamClient.LeaveSession for clientId=" + clientId); + client.LeaveSession({ sessionID: currentSessionId }); + + registerReconnect(content); + } + function ajaxError(jqXHR, textStatus, errorMessage) { logger.error("Unexpected ajax error: " + textStatus); } @@ -324,6 +394,7 @@ this.addTrack = addTrack; this.updateTrack = updateTrack; this.deleteTrack = deleteTrack; + this.onWebsocketDisconnected = onWebsocketDisconnected; this.getCurrentSession = function() { return currentSession; }; diff --git a/app/assets/javascripts/utils.js b/app/assets/javascripts/utils.js index 783bdecdc..bdb02254d 100644 --- a/app/assets/javascripts/utils.js +++ b/app/assets/javascripts/utils.js @@ -147,12 +147,16 @@ }); } + context.JK.trimString = function(str) { + return str.replace(/^\s\s*/, '').replace(/\s\s*$/, ''); + }; + context.JK.padString = function(str, max) { var retVal = '' + str; while (retVal.length < max) { retVal = '0' + retVal; } - + return retVal; } diff --git a/app/assets/stylesheets/client/banner.css.scss b/app/assets/stylesheets/client/banner.css.scss new file mode 100644 index 000000000..e3b8de209 --- /dev/null +++ b/app/assets/stylesheets/client/banner.css.scss @@ -0,0 +1,8 @@ +#banner { + display:none; +} + +#banner h2 { + font-weight:bold; + font-size:x-large; +} \ No newline at end of file diff --git a/app/assets/stylesheets/client/client.css b/app/assets/stylesheets/client/client.css index 08784199b..7fb658934 100644 --- a/app/assets/stylesheets/client/client.css +++ b/app/assets/stylesheets/client/client.css @@ -30,6 +30,7 @@ *= require ./genreSelector *= require ./sessionList *= require ./searchResults + *= require ./banner *= require ./clientUpdate *= require jquery.Jcrop */ \ No newline at end of file diff --git a/app/assets/stylesheets/client/ftue.css.scss b/app/assets/stylesheets/client/ftue.css.scss index 7dca409a3..a1cb9228d 100644 --- a/app/assets/stylesheets/client/ftue.css.scss +++ b/app/assets/stylesheets/client/ftue.css.scss @@ -18,12 +18,16 @@ div.dialog.ftue .ftue-inner div[layout-wizard-step="1"] { li { text-align:center; - width: 33%; + width: 31%; + height: 170px; margin:0px; - padding:0px; + /*padding:0px;*/ list-style: none; float:left; } + li.first { + width: 34%; + } } div.dialog.ftue .ftue-inner div[layout-wizard-step="3"] { h5 { @@ -421,3 +425,14 @@ table.audiogeartable { font-size:15px; color:#aaa; } + +.ftue-video-link { + padding:4px; + cursor:pointer; + background-color:#333; + border: 1px solid #333; +} +.ftue-video-link.hover { + background-color:#444; + border: 1px solid #555; +} diff --git a/app/controllers/api_users_controller.rb b/app/controllers/api_users_controller.rb index 48ec198a3..a663bcec2 100644 --- a/app/controllers/api_users_controller.rb +++ b/app/controllers/api_users_controller.rb @@ -53,22 +53,23 @@ class ApiUsersController < ApiController end def update - @user = User.save(params[:id], - current_user.id, - params[:first_name], - params[:last_name], - nil, # Don't allow changing email here, since updating email is something that must be done through it's own API - nil, # Don't allow changing password here, since we want to prompt again for the old password - nil, - params[:musician], - params[:gender], - params[:birth_date], - params[:internet_service_provider], - params[:city], - params[:state], - params[:country], - params[:instruments].nil? ? [] : params[:instruments], # we have to convert nil to empty []. background: http://stackoverflow.com/questions/14647731/rails-converts-empty-arrays-into-nils-in-params-of-the-request - params[:photo_url]) + + @user = User.find(params[:id]) + + + @user.first_name = params[:first_name] if params.has_key?(:first_name) + @user.last_name = params[:last_name] if params.has_key?(:last_name) + @user.gender = params[:gender] if params.has_key?(:gender) + @user.birth_date = Date.strptime(params[:birth_date], '%m-%d-%Y') if params.has_key?(:birth_date) + @user.city = params[:city] if params.has_key?(:city) + @user.state = params[:state] if params.has_key?(:state) + @user.country = params[:country] if params.has_key?(:country) + @user.musician = params[:musician] if params.has_key?(:musician) + @user.update_instruments(params[:instruments].nil? ? [] : params[:instruments]) if params.has_key?(:instruments) + + puts params[:birth_date] + @user.save + puts @user.birth_date.inspect if @user.errors.any? respond_with @user, :status => :unprocessable_entity diff --git a/app/helpers/meta_helper.rb b/app/helpers/meta_helper.rb new file mode 100644 index 000000000..0ba8cb259 --- /dev/null +++ b/app/helpers/meta_helper.rb @@ -0,0 +1,7 @@ +module MetaHelper + + def version() + "web=#{::JamWeb::VERSION} lib=#{JamRuby::VERSION} db=#{JamDb::VERSION} pb=#{Jampb::VERSION}" + end + +end diff --git a/app/views/clients/_account_profile.html.erb b/app/views/clients/_account_profile.html.erb index fb1ba66fc..bbd0e8be7 100644 --- a/app/views/clients/_account_profile.html.erb +++ b/app/views/clients/_account_profile.html.erb @@ -65,7 +65,7 @@
- Please identify which of the three types of audio gear below you are going to use with the JamKazam - service, and click either the Windows or Mac link under the appropriate gear to watch a video on how - to navigate this initial setup and testing process. After watching the video, click the 'NEXT' - button to get started. + Please identify which of the three types of audio gear below you + are going to use with the JamKazam service, and click one to + watch a video on how to navigate this initial setup and testing + process. After watching the video, click the 'NEXT' button to + get started. If you don't have your audio gear handy now, click + Cancel.
<%= image_tag "content/audio_capture_ftue.png", {:width => 243, :height => 70} %>
-<%= image_tag "content/microphone_ftue.png", {:width => 70, :height => 113} %>
-<%= image_tag "content/computer_ftue.png", {:width => 118, :height => 105} %>
-