';
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 34c2400d2..6ca880df5 100644
--- a/web/app/assets/javascripts/hoverFan.js
+++ b/web/app/assets/javascripts/hoverFan.js
@@ -10,7 +10,7 @@
this.showBubble = function($hoverElement) {
- rest.getUserDetail({id: userId})
+ return rest.getUserDetail({id: userId})
.done(function(response) {
$(hoverSelector).html('');
diff --git a/web/app/assets/javascripts/hoverMusician.js b/web/app/assets/javascripts/hoverMusician.js
index 7cac6517e..56042effd 100644
--- a/web/app/assets/javascripts/hoverMusician.js
+++ b/web/app/assets/javascripts/hoverMusician.js
@@ -13,14 +13,14 @@
this.showBubble = function($hoverElement) {
$templateLatency = $("#template-account-session-latency");
- rest.getUserDetail({id: userId})
+ return rest.getUserDetail({id: userId})
.done(function(response) {
$(hoverSelector).html('');
// instruments
var instrumentHtml = '';
$.each(response.instruments, function(index, val) {
- instrumentHtml += '
 + ')
';
+ instrumentHtml += '
';
});
// followings
@@ -88,7 +88,7 @@
var latencyBadge = context._.template(
$templateLatency.html(),
- $.extend(sessionUtils.createLatency(response), response),
+ $.extend(response, sessionUtils.createLatency(response)),
{variable: 'data'}
);
diff --git a/web/app/assets/javascripts/hoverRecording.js b/web/app/assets/javascripts/hoverRecording.js
index 5321df114..8ba1239c7 100644
--- a/web/app/assets/javascripts/hoverRecording.js
+++ b/web/app/assets/javascripts/hoverRecording.js
@@ -39,7 +39,7 @@
this.showBubble = function($hoverElement) {
- rest.getClaimedRecording(recordingId)
+ return rest.getClaimedRecording(recordingId)
.done(function(response) {
var claimedRecording = response;
var recording = response.recording;
@@ -58,7 +58,7 @@
instrumentHtml = '
';
$.each(val.instrument_ids, function(index, val) {
- instrumentHtml += '  + ') ';
+ instrumentHtml += '  + ') ';
});
instrumentHtml += ' | ';
diff --git a/web/app/assets/javascripts/hoverSession.js b/web/app/assets/javascripts/hoverSession.js
index d64dfee71..35a7baa5e 100644
--- a/web/app/assets/javascripts/hoverSession.js
+++ b/web/app/assets/javascripts/hoverSession.js
@@ -10,7 +10,7 @@
this.showBubble = function($hoverElement) {
- rest.getSessionHistory(sessionId)
+ return rest.getSessionHistory(sessionId)
.done(function(response) {
$(hoverSelector).html('');
@@ -25,7 +25,7 @@
instrumentHtml = '
';
var instruments = val.instruments.split("|");
$.each(instruments, function(index, instrument) {
- instrumentHtml += '  + ') ';
+ instrumentHtml += '  + ') ';
});
instrumentHtml += ' | ';
diff --git a/web/app/assets/javascripts/jam_rest.js b/web/app/assets/javascripts/jam_rest.js
index 47368278c..ad1993899 100644
--- a/web/app/assets/javascripts/jam_rest.js
+++ b/web/app/assets/javascripts/jam_rest.js
@@ -238,6 +238,10 @@
}
function cancelRsvpRequest(sessionId, rsvpRequestId, cancelAll) {
+ var cancel = "yes";
+ if (cancelAll) {
+ cancel = "all";
+ }
return $.ajax({
url: '/api/rsvp_requests/' + rsvpRequestId,
type: "DELETE",
@@ -939,7 +943,11 @@
dataType: "json",
contentType: 'application/json',
url: "/api/users/progression/certified_gear",
- data: JSON.stringify(options)
+ processData: false,
+ data: JSON.stringify({
+ success: options.success,
+ reason: options.reason
+ })
});
}
@@ -1257,6 +1265,98 @@
});
}
+ function updateAudioLatency(options) {
+ var id = getId(options);
+ return $.ajax({
+ type: "POST",
+ url: '/api/users/' + id + '/audio_latency',
+ dataType: "json",
+ contentType: 'application/json',
+ data: options,
+ });
+ }
+
+ function getJamtracks(options) {
+ return $.ajax({
+ type: "GET",
+ url: '/api/jamtracks?' + $.param(options),
+ dataType: "json",
+ contentType: 'application/json'
+ });
+ }
+
+ function addJamtrackToShoppingCart(options) {
+ return $.ajax({
+ type: "POST",
+ url: '/api/shopping_carts/add_jamtrack?' + $.param(options),
+ dataType: "json",
+ contentType: 'applications/json'
+ });
+ }
+
+ function getShoppingCarts() {
+ return $.ajax({
+ type: "GET",
+ url: '/api/shopping_carts',
+ dataType: "json",
+ contentType: 'application/json'
+ });
+ }
+
+ function removeShoppingCart(options) {
+ return $.ajax({
+ type: "DELETE",
+ url: '/api/shopping_carts?' + $.param(options),
+ dataType: "json",
+ contentType: 'application/json'
+ })
+ }
+
+ function getRecurlyAccount() {
+ return $.ajax({
+ type: "GET",
+ url: '/api/recurly/get_account',
+ dataType: "json",
+ contentType: 'application/json'
+ });
+ }
+
+ function createRecurlyAccount(options) {
+ return $.ajax({
+ type: "POST",
+ url: '/api/recurly/create_account?' + $.param(options),
+ dataType: "json",
+ contentType: 'application/json'
+ });
+ }
+
+ function getBillingInfo() {
+ return $.ajax({
+ type: "GET",
+ url: '/api/recurly/billing_info',
+ dataType: "json",
+ contentType: 'application/json'
+ });
+ }
+
+ function updateBillingInfo(options) {
+ return $.ajax({
+ type: "PUT",
+ url: '/api/recurly/update_billing_info?' + $.param(options),
+ dataType: "json",
+ contentType: 'application/json'
+ });
+ }
+
+ function placeOrder(options) {
+ return $.ajax({
+ type: "PUT",
+ url: '/api/recurly/place_order?' + $.param(options),
+ dataType: "json",
+ contentType: 'application/json'
+ });
+ }
+
function searchMusicians(query) {
return $.ajax({
type: "GET",
@@ -1375,6 +1475,16 @@
this.getChatMessages = getChatMessages;
this.createDiagnostic = createDiagnostic;
this.getLatencyTester = getLatencyTester;
+ this.updateAudioLatency = updateAudioLatency;
+ this.getJamtracks = getJamtracks;
+ this.addJamtrackToShoppingCart = addJamtrackToShoppingCart;
+ this.getShoppingCarts = getShoppingCarts;
+ this.removeShoppingCart = removeShoppingCart;
+ this.getRecurlyAccount = getRecurlyAccount;
+ this.createRecurlyAccount = createRecurlyAccount;
+ this.getBillingInfo = getBillingInfo;
+ this.updateBillingInfo = updateBillingInfo;
+ this.placeOrder = placeOrder;
this.searchMusicians = searchMusicians;
return this;
diff --git a/web/app/assets/javascripts/jamkazam.js b/web/app/assets/javascripts/jamkazam.js
index d8fb6e4aa..ba3d4e25b 100644
--- a/web/app/assets/javascripts/jamkazam.js
+++ b/web/app/assets/javascripts/jamkazam.js
@@ -22,7 +22,8 @@
var rest = context.JK.Rest();
var inBadState = false;
var userDeferred = null;
-
+ var userData = null;
+ var self = this;
var opts = {
inClient: true, // specify false if you want the app object but none of the client-oriented features
@@ -301,6 +302,7 @@
logger.debug("updating user info")
userDeferred = update; // update the global user object if this succeeded
})
+ update.done(this.updateUserCache)
return update;
}
@@ -309,6 +311,15 @@
return userDeferred;
}
+ // gets the most recent user data. can be null when app is still initializing.
+ // user app.user() if initialize sequence is unknown/asynchronous
+ this.currentUser = function() {
+ if(userData == null) {
+ throw "currentUser has null user data"
+ }
+ return userData;
+ }
+
this.activeElementEvent = function(evtName, data) {
return this.layout.activeElementEvent(evtName, data);
}
@@ -333,6 +344,10 @@
}
};
+ this.updateUserCache = function(_userData) {
+ userData = _userData
+ }
+
this.initialize = function (inOpts) {
var url, hash;
app = this;
@@ -343,6 +358,7 @@
this.layout.handleDialogState();
userDeferred = rest.getUserDetail();
+ userDeferred.done(this.updateUserCache)
if (opts.inClient) {
registerBadStateRecovered();
diff --git a/web/app/assets/javascripts/jamtrack.js b/web/app/assets/javascripts/jamtrack.js
new file mode 100644
index 000000000..6ae767514
--- /dev/null
+++ b/web/app/assets/javascripts/jamtrack.js
@@ -0,0 +1,259 @@
+(function(context,$) {
+
+ "use strict";
+ context.JK = context.JK || {};
+ context.JK.JamTrackScreen = function(app) {
+
+ var logger = context.JK.logger;
+
+ var $screen = null;
+ var $content = null;
+ var $scroller = null;
+ var $genre = null;
+ var $instrument = null;
+ var $availability = null;
+ var $nextPager = null;
+ var $noMoreJamtracks = null;
+
+ var currentQuery = defaultQuery();
+ var currentPage = 0;
+ var LIMIT = 10;
+ var next = null;
+ var instrument_logo_map = context.JK.getInstrumentIconMap24();
+
+ function beforeShow(data) {
+ refresh();
+ }
+
+ function afterShow(data) {
+ }
+
+ function events() {
+ $genre.on("change", search);
+ $instrument.on("change", search);
+ $availability.on("change", search);
+ }
+
+ function clearResults() {
+ //logger.debug("CLEARING CONTENT")
+ currentPage = 0;
+ $content.empty();
+ $noMoreJamtracks.hide();
+ next = null;
+ }
+
+ function refresh() {
+ currentQuery = buildQuery();
+ rest.getJamtracks(currentQuery)
+ .done(function(response) {
+ clearResults();
+ handleJamtrackResponse(response);
+ })
+ .fail(function(jqXHR) {
+ clearResults();
+ $noMoreJamtracks.show();
+ app.notifyServerError(jqXHR, 'Jamtrack Unavailable')
+ })
+ }
+
+ function search() {
+ logger.debug("Searching for jamtracks...");
+ refresh();
+ return false;
+ }
+
+ function defaultQuery() {
+ var query = { limit:LIMIT, page:currentPage};
+
+ if(next) {
+ query.since = next;
+ }
+
+ return query;
+ }
+
+ function buildQuery() {
+ currentQuery = defaultQuery();
+
+ // genre filter
+ var genres = $screen.find('#jamtrack_genre').val();
+ if (genres !== undefined) {
+ currentQuery.genre = genres;
+ }
+
+ // instrument filter
+ var instrument = $instrument.val();
+ if (instrument !== undefined) {
+ currentQuery.instrument = instrument;
+ }
+
+ // availability filter
+ var availability = $availability.val();
+ if (availability !== undefined) {
+ currentQuery.availability = availability;
+ }
+
+ return currentQuery;
+ }
+
+ function handleJamtrackResponse(response) {
+ //logger.debug("Handling response", JSON.stringify(response))
+ next = response.next;
+
+ renderJamtracks(response);
+
+ if(response.next == null) {
+ // if we less results than asked for, end searching
+ $scroller.infinitescroll('pause');
+ logger.debug("end of jamtracks");
+
+ if(currentPage == 0 && response.jamtracks.length == 0) {
+ $content.append("
There's no jamtracks.
") ;
+ }
+
+ if(currentPage > 0) {
+ $noMoreJamtracks.show();
+ // there are bugs with infinitescroll not removing the 'loading'.
+ // it's most noticeable at the end of the list, so whack all such entries
+ $('.infinite-scroll-loader').remove();
+ }
+ }
+ else {
+ currentPage++;
+ buildQuery();
+ registerInfiniteScroll();
+ }
+ }
+
+ function registerInfiniteScroll() {
+ $scroller.infinitescroll({
+ behavior: 'local',
+ navSelector: '#jamtrackScreen .btn-next-pager',
+ nextSelector: '#jamtrackScreen .btn-next-pager',
+ binder: $scroller,
+ dataType: 'json',
+ appendCallback: false,
+ prefill: false,
+ bufferPx: 100,
+ loading: {
+ msg: $('
Loading ...
'),
+ img: '/assets/shared/spinner.gif'
+ },
+ path: function(page) {
+ return '/api/jamtracks?' + $.param(buildQuery());
+ }
+ },function(json, opts) {
+ handleJamtrackResponse(json);
+ });
+ $scroller.infinitescroll('resume');
+ }
+
+ function playJamtrack(e) {
+ e.preventDefault();
+ }
+
+ function addToCartJamtrack(e) {
+ e.preventDefault();
+
+ var params = {id: $(e.target).attr("data-jamtrack-id")};
+
+ rest.addJamtrackToShoppingCart(params)
+ .done(function(response) {
+ context.location = "/client#/shoppingCart";
+ })
+ .fail(app.ajaxError);
+ }
+
+ function licenseUSWhy(e) {
+ e.preventDefault();
+
+ app.layout.showDialog('jamtrack-availability-dialog');
+ }
+
+ function registerEvents() {
+ $screen.find('.jamtrack-detail-btn').on("click", showJamtrackDescription);
+ $screen.find('.play-button').on('click', playJamtrack);
+ $screen.find('.jamtrack-add-cart').on('click', addToCartJamtrack);
+ $screen.find('.license-us-why').on('click', licenseUSWhy);
+ }
+
+ function renderJamtracks(data) {
+ $.each(data.jamtracks, function(i, jamtrack) {
+ $.each(jamtrack.tracks, function (index, track) {
+ var inst = '../assets/content/icon_instrument_default24.png';
+ if (track.instrument.id in instrument_logo_map) {
+ inst = instrument_logo_map[track.instrument.id].asset;
+ }
+ track.instrument_url = inst;
+
+ track.instrument_desc = track.instrument.description;
+ if (track.part != "") {
+ track.instrument_desc += " ( " + track.part + " )";
+ }
+ });
+
+ var options = {
+ jamtrack: jamtrack
+ };
+
+ var $jamtrackItem = $(
+ context._.template(
+ $('#template-jamtrack').html(),
+ options,
+ {variable: 'data'}
+ )
+ );
+ renderJamtrack($jamtrackItem );
+ });
+
+ registerEvents();
+ }
+
+ function showJamtrackDescription(e) {
+ e.preventDefault();
+
+ var $description = $(e.target).parent(".detail-arrow").next();
+ if ($description.css("display") == "none") {
+ $description.show();
+ }
+ else {
+ $description.hide();
+ }
+ }
+
+ function renderJamtrack(jamtrack) {
+ $content.append(jamtrack);
+ }
+
+ function initialize() {
+ var screenBindings = {
+ 'beforeShow': beforeShow,
+ 'afterShow': afterShow
+ };
+ app.bindScreen('jamtrack', screenBindings);
+
+ $screen = $("#jamtrack-find-form");
+ $scroller = $screen.find('.content-body-scroller');
+ $content = $screen.find(".jamtrack-content");
+ $genre = $screen.find("#jamtrack_genre");
+ $instrument = $screen.find("#jamtrack_instrument");
+ $availability = $screen.find("#jamtrack_availability");
+ $nextPager = $screen.find("a.btn-next-pager");
+ $noMoreJamtracks = $screen.find("#end-of-jamtrack-list");
+
+ if($screen.length == 0) throw "$screen must be specified";
+ if($scroller.length == 0) throw "$scroller must be specified";
+ if($content.length == 0) throw "$content must be specified";
+ if($noMoreJamtracks.length == 0) throw "$noMoreJamtracks must be specified";
+ if($genre.length == 0) throw "$genre must be specified";
+ if($instrument.length == 0) throw "$instrument must be specified";
+ if($availability.length ==0) throw "$availability must be specified";
+
+ events();
+ }
+
+ this.initialize = initialize;
+
+ return this;
+ }
+})(window,jQuery);
\ No newline at end of file
diff --git a/web/app/assets/javascripts/mods_utils.js.coffee b/web/app/assets/javascripts/mods_utils.js.coffee
index b10250c32..c4bb5321d 100644
--- a/web/app/assets/javascripts/mods_utils.js.coffee
+++ b/web/app/assets/javascripts/mods_utils.js.coffee
@@ -31,5 +31,11 @@ class ModUtils
deferred.resolve(shouldShowForName)
))
return deferred;
+
+ # returns a gear mod by name
+ getGear: (name) =>
+ gear = context.JK.app.currentUser().mods?.gear
+ if gear? then gear[name] else undefined
+
# global instance
context.JK.ModUtils = new ModUtils()
\ No newline at end of file
diff --git a/web/app/assets/javascripts/order.js b/web/app/assets/javascripts/order.js
new file mode 100644
index 000000000..b6a78bff9
--- /dev/null
+++ b/web/app/assets/javascripts/order.js
@@ -0,0 +1,511 @@
+(function(context,$) {
+
+ "use strict";
+ context.JK = context.JK || {};
+ context.JK.OrderScreen = function(app) {
+
+ var logger = context.JK.logger;
+
+ var $screen = null;
+ var $navigation = null;
+ var $billingInfo = null;
+ var $shippingInfo = null;
+ var $paymentMethod = null;
+ var $shippingAddress = null;
+ var $shippingAsBilling = null;
+ var $paymentInfoPanel = null;
+ var $orderPanel = null;
+ var $orderContent = null;
+ var userDetail = null;
+ var step = null;
+ var billing_info = null;
+ var shipping_info = null;
+ var shipping_as_billing = null;
+
+ function beforeShow() {
+ beforeShowPaymentInfo();
+// moveToOrder();
+ }
+
+ function beforeShowPaymentInfo() {
+ step = 2;
+ renderNavigation();
+ renderAccountInfo();
+ }
+
+ function renderAccountInfo() {
+ rest.getUserDetail()
+ .done(populateAccountInfo)
+ .error(app.ajaxError);
+ }
+
+ function populateAccountInfo(user) {
+ userDetail = user;
+
+ if (userDetail.has_recurly_account) {
+ rest.getBillingInfo()
+ .done(function(response) {
+ $billingInfo.find("#billing-first-name").val(response.first_name);
+ $billingInfo.find("#billing-last-name").val(response.last_name);
+ $billingInfo.find("#billing-address1").val(response.address1);
+ $billingInfo.find("#billing-address2").val(response.address2);
+ $billingInfo.find("#billing-city").val(response.city);
+ $billingInfo.find("#billing-state").val(response.state);
+ $billingInfo.find("#billing-zip").val(response.zip);
+ $billingInfo.find("#billing-country").val(response.country);
+
+ $shippingAddress.find("#shipping-first-name").val(response.first_name);
+ $shippingAddress.find("#shipping-last-name").val(response.last_name);
+ $shippingAddress.find("#shipping-address1").val(response.address1);
+ $shippingAddress.find("#shipping-address2").val(response.address2);
+ $shippingAddress.find("#shipping-city").val(response.city);
+ $shippingAddress.find("#shipping-state").val(response.state);
+ $shippingAddress.find("#shipping-zip").val(response.zip);
+ $shippingAddress.find("#shipping-country").val(response.country);
+ })
+ .error(app.ajaxError);
+ }
+ else {
+ $billingInfo.find("#billing-first-name").val(userDetail.first_name);
+ $billingInfo.find("#billing-last-name").val(userDetail.last_name);
+ $billingInfo.find("#billing-city").val(userDetail.city);
+ $billingInfo.find("#billing-state").val(userDetail.state);
+ $billingInfo.find("#billing-country").val(userDetail.country);
+
+ $shippingAddress.find("#shipping-first-name").val(userDetail.first_name);
+ $shippingAddress.find("#shipping-last-name").val(userDetail.last_name);
+ $shippingAddress.find("#shipping-city").val(userDetail.city);
+ $shippingAddress.find("#shipping-state").val(userDetail.state);
+ $shippingAddress.find("#shipping-country").val(userDetail.country);
+ }
+ }
+
+ function afterShow(data) {
+ }
+
+ function next(e) {
+ e.preventDefault();
+
+ // validation
+ var billing_first_name = $billingInfo.find("#billing-first-name").val();
+ var billing_last_name = $billingInfo.find("#billing-last-name").val();
+ var billing_address1 = $billingInfo.find("#billing-address1").val();
+ var billing_address2 = $billingInfo.find("#billing-address2").val();
+ var billing_city = $billingInfo.find("#billing-city").val();
+ var billing_state = $billingInfo.find("#billing-state").val();
+ var billing_zip = $billingInfo.find("#billing-zip").val();
+ var billing_country = $billingInfo.find("#billing-country").val();
+
+ if (!billing_first_name) {
+ $billingInfo.find('#divBillingFirstName .error-text').remove();
+ $billingInfo.find('#divBillingFirstName').addClass("error");
+ $billingInfo.find('#billing-first-name').after("
");
+
+ return false;
+ }
+ else {
+ $billingInfo.find('#divBillingFirstName').removeClass("error");
+ }
+
+ if (!billing_last_name) {
+ $billingInfo.find('#divBillingLastName .error-text').remove();
+ $billingInfo.find('#divBillingLastName').addClass("error");
+ $billingInfo.find('#billing-last-name').after("
");
+
+ return false;
+ }
+ else {
+ $billingInfo.find('#divBillingLastName').removeClass("error");
+ }
+
+ if (!billing_address1) {
+ $billingInfo.find('#divBillingAddress1 .error-text').remove();
+ $billingInfo.find('#divBillingAddress1').addClass("error");
+ $billingInfo.find('#billing-address1').after("
");
+
+ return false;
+ }
+ else {
+ $billingInfo.find('#divBillingAddress1').removeClass("error");
+ }
+
+ if (!billing_zip) {
+ $billingInfo.find('#divBillingZip .error-text').remove();
+ $billingInfo.find('#divBillingZip').addClass("error");
+ $billingInfo.find('#billing-zip').after("
");
+
+ return false;
+ }
+ else {
+ $billingInfo.find('#divBillingZip').removeClass("error");
+ }
+
+ if (!billing_state) {
+ $billingInfo.find('#divBillingState .error-text').remove();
+ $billingInfo.find('#divBillingState').addClass("error");
+ $billingInfo.find('#billing-zip').after("
");
+
+ return false;
+ }
+ else {
+ $billingInfo.find('#divBillingState').removeClass("error");
+ }
+
+ if (!billing_city) {
+ $billingInfo.find('#divBillingCity .error-text').remove();
+ $billingInfo.find('#divBillingCity').addClass("error");
+ $billingInfo.find('#billing-city').after("
");
+
+ return false;
+ }
+ else {
+ $billingInfo.find('#divBillingCity').removeClass("error");
+ }
+
+ if (!billing_country) {
+ $billingInfo.find('#divBillingCountry .error-text').remove();
+ $billingInfo.find('#divBillingCountry').addClass("error");
+ $billingInfo.find('#billing-country').after("
");
+
+ return false;
+ }
+ else {
+ $billingInfo.find('#divBillingCountry').removeClass("error");
+ }
+
+ shipping_as_billing = $shippingAsBilling.is(":checked");
+ var shipping_first_name, shipping_last_name, shipping_address1, shipping_address2;
+ var shipping_city, shipping_state, shipping_zip, shipping_country;
+
+ if (!shipping_as_billing) {
+ shipping_first_name = $shippingAddress.find("#shipping-first-name").val();
+ shipping_last_name = $shippingAddress.find("#shipping-last-name").val();
+ shipping_address1 = $shippingAddress.find("#shipping-address1").val();
+ shipping_address2 = $shippingAddress.find("#shipping-address2").val();
+ shipping_city = $shippingAddress.find("#shipping-city").val();
+ shipping_state = $shippingAddress.find("#shipping-state").val();
+ shipping_zip = $shippingAddress.find("#shipping-zip").val();
+ shipping_country = $shippingAddress.find("#shipping-country").val();
+
+ if (!shipping_first_name) {
+ $shippingAddress.find('#divShippingFirstName .error-text').remove();
+ $shippingAddress.find('#divShippingFirstName').addClass("error");
+ $shippingAddress.find('#shipping-first-name').after("
");
+
+ return false;
+ }
+ else {
+ $shippingInfo.find('#divShippingFirstName').removeClass("error");
+ }
+
+ if (!shipping_last_name) {
+ $shippingAddress.find('#divShippingLastName .error-text').remove();
+ $shippingAddress.find('#divShippingLastName').addClass("error");
+ $shippingAddress.find('#shipping-last-name').after("
");
+
+ return false;
+ }
+ else {
+ $shippingInfo.find('#divShippingLastName').removeClass("error");
+ }
+
+ if (!shipping_address1) {
+ $shippingAddress.find('#divShippingAddress1 .error-text').remove();
+ $shippingAddress.find('#divShippingAddress1').addClass("error");
+ $shippingAddress.find('#shipping-address1').after("
");
+
+ return false;
+ }
+ else {
+ $shippingInfo.find('#divShippingAddress1').removeClass("error");
+ }
+
+ if (!shipping_zip) {
+ $shippingAddress.find('#divShippingZip .error-text').remove();
+ $shippingAddress.find('#divShippingZip').addClass("error");
+ $shippingAddress.find('#shipping-zip').after("
");
+
+ return false;
+ }
+ else {
+ $shippingInfo.find('#divShippingZip').removeClass("error");
+ }
+
+ if (!shipping_state) {
+ $shippingAddress.find('#divShippingState .error-text').remove();
+ $shippingAddress.find('#divShippingState').addClass("error");
+ $shippingAddress.find('#shipping-zip').after("
");
+
+ return false;
+ }
+ else {
+ $shippingInfo.find('#divShippingState').removeClass("error");
+ }
+
+ if (!shipping_city) {
+ $shippingAddress.find('#divShippingCity .error-text').remove();
+ $shippingAddress.find('#divShippingCity').addClass("error");
+ $shippingAddress.find('#shipping-city').after("
");
+
+ return false;
+ }
+ else {
+ $shippingInfo.find('#divShippingCity').removeClass("error");
+ }
+
+ if (!shipping_country) {
+ $shippingAddress.find('#divShippingCountry .error-text').remove();
+ $shippingAddress.find('#divShippingCountry').addClass("error");
+ $shippingAddress.find('#shipping-country').after("
");
+
+ return false;
+ }
+ else {
+ $shippingAddress.find('#divShippingCountry').removeClass("error");
+ }
+ }
+
+ var card_name = $paymentMethod.find("#card-name").val();
+ var card_number = $paymentMethod.find("#card-number").val();
+ var card_year = $paymentMethod.find("#card_expire-date_3i").val();
+ var card_month = $paymentMethod.find("#card_expire-date_2i").val();
+ var card_verify = $paymentMethod.find("#card-verify").val();
+
+ if (!card_name) {
+ $paymentMethod.find('#divCardName .error-text').remove();
+ $paymentMethod.find('#divCardName').addClass("error");
+ $paymentMethod.find('#card-name').after("
");
+
+ return false;
+ }
+ else {
+ $paymentMethod.find('#divCardName').removeClass("error");
+ }
+
+ if (!card_number) {
+ $paymentMethod.find('#divCardNumber .error-text').remove();
+ $paymentMethod.find('#divCardNumber').addClass("error");
+ $paymentMethod.find('#card-number').after("
");
+
+ return false;
+ }
+ else {
+ $paymentMethod.find('#divCardNumber').removeClass("error");
+ }
+
+ if (!card_verify) {
+ $paymentMethod.find('#divCardVerify .error-text').remove();
+ $paymentMethod.find('#divCardVerify').addClass("error");
+ $paymentMethod.find('#card_verify').after("
- Card Verification Value is required
");
+
+ return false;
+ }
+ else {
+ $paymentMethod.find('#divCardVerify').removeClass("error");
+ }
+
+ billing_info = {};
+ shipping_info = {};
+ billing_info.first_name = billing_first_name;
+ billing_info.last_name = billing_last_name;
+ billing_info.address1 = billing_address1;
+ billing_info.address2 = billing_address2;
+ billing_info.city = billing_city;
+ billing_info.state = billing_state;
+ billing_info.country = billing_country;
+ billing_info.zip = billing_zip;
+ billing_info.number = card_number;
+ billing_info.month = card_month;
+ billing_info.year = card_year;
+ billing_info.verification_value = card_verify;
+
+ if (shipping_as_billing) {
+ shipping_info = billing_info;
+ delete shipping_info.number;
+ delete shipping_info.month;
+ delete shipping_info.year;
+ delete shipping_info.verification_value;
+ }
+ else {
+ shipping_info.first_name = shipping_first_name;
+ shipping_info.last_name = shipping_last_name;
+ shipping_info.address1 = shipping_address1;
+ shipping_info.address2 = shipping_address2;
+ shipping_info.city = shipping_city;
+ shipping_info.state = shipping_state;
+ shipping_info.country = shipping_country;
+ shipping_info.zip = shipping_zip;
+ }
+
+ $paymentInfoPanel.find("#payment-info-next").addClass("disabled");
+ $paymentInfoPanel.find("#payment-info-next").off("click");
+
+ if (userDetail.has_recurly_account) {
+ rest.updateBillingInfo(billing_info)
+ .done(function() {
+ })
+ .fail(errorHandling);
+ }
+ else {
+ rest.createRecurlyAccount({billing_info: billing_info})
+ .done(function() {
+ })
+ .fail(errorHandling);
+ }
+ }
+
+ function errorHandling(xhr, ajaxOptions, thrownError) {
+ $.each(xhr.responseJSON.errors, function(key, error) {
+ if (key == 'number') {
+ $paymentMethod.find('#divCardNumber .error-text').remove();
+ $paymentMethod.find('#divCardNumber').addClass("error");
+ $paymentMethod.find('#card-number').after("
");
+ }
+ else if (key == 'verification_value') {
+ $paymentMethod.find('#divCardVerify .error-text').remove();
+ $paymentMethod.find('#divCardVerify').addClass("error");
+ $paymentMethod.find('#card-verify').after("
");
+ }
+ });
+
+ $paymentInfoPanel.find("#payment-info-next").removeClass("disabled");
+ $paymentInfoPanel.find("#payment-info-next").on("click", next);
+// moveToOrder();
+ }
+
+ function beforeShowOrder() {
+ step = 3;
+ renderNavigation();
+ populateOrderPage();
+ }
+
+ function clearOrderPage() {
+ $orderContent.empty();
+ }
+
+ function populateOrderPage() {
+ clearOrderPage();
+
+ rest.getShoppingCarts()
+ .done(renderOrderPage)
+ .fail(app.ajaxError);
+ }
+
+ function renderOrderPage(carts) {
+ var data = {};
+
+ var sub_total = 0;
+ $.each(carts, function(index, cart) {
+ sub_total += parseFloat(cart.product_info.price) * parseFloat(cart.quantity);
+ });
+ data.sub_total = sub_total.toFixed(2);
+ data.taxes = 12.01;
+
+ data.carts = carts;
+ data.billing_info = billing_info;
+ data.shipping_info = shipping_info;
+ data.shipping_as_billing = shipping_as_billing;
+ var orderContentHtml = $(
+ context._.template(
+ $('#template-order-content').html(),
+ data,
+ {variable: 'data'}
+ )
+ );
+
+ $orderContent.append(orderContentHtml);
+
+ $orderPanel.find(".change-payment-info").on('click', moveToPaymentInfo);
+ $orderContent.find(".place-order").on('click', placeOrder);
+ }
+
+ function moveToOrder() {
+ $paymentInfoPanel.addClass("hidden");
+ $orderPanel.removeClass("hidden");
+ beforeShowOrder();
+ }
+
+ function moveToPaymentInfo(e) {
+ e.preventDefault();
+ $paymentInfoPanel.removeClass("hidden");
+ $orderPanel.addClass("hidden");
+ beforeShowPaymentInfo();
+ }
+
+ function toggleShippingAsBilling(e) {
+ e.preventDefault();
+
+ var shipping_as_billing = $(e.target).is(':checked');
+
+ if (!shipping_as_billing) {
+ $shippingAddress.removeClass("hidden");
+ }
+ else {
+ $shippingAddress.addClass("hidden");
+ }
+ }
+
+ function placeOrder(e) {
+ e.preventDefault();
+ }
+
+ function events() {
+ $paymentInfoPanel.find("#payment-info-next").on('click', next);
+ $shippingAsBilling.on('ifChanged', toggleShippingAsBilling);
+ }
+
+ function reset() {
+ }
+
+ function renderNavigation() {
+ $navigation.html("");
+ var navigationHtml = $(
+ context._.template(
+ $('#template-checkout-navigation').html(),
+ {current: step},
+ {variable: 'data'}
+ )
+ );
+
+ $navigation.append(navigationHtml);
+ }
+
+ function initializeControls() {
+ $("form.payment-info").iCheck({
+ checkboxClass: 'icheckbox_minimal',
+ radioClass: 'iradio_minimal',
+ inheritClass: true
+ });
+ }
+
+ function initialize() {
+ var screenBindings = {
+ 'beforeShow': beforeShow,
+ 'afterShow': afterShow
+ };
+ app.bindScreen('order', screenBindings);
+
+ $screen = $("#orderScreen");
+ $paymentInfoPanel = $screen.find(".checkout-payment-info");
+ $orderPanel = $screen.find(".order-panel");
+ $navigation = $screen.find(".checkout-navigation-bar");
+ $billingInfo = $paymentInfoPanel.find(".billing-address");
+ $shippingInfo = $paymentInfoPanel.find(".shipping-address");
+ $paymentMethod = $paymentInfoPanel.find(".payment-method");
+ $shippingAddress = $paymentInfoPanel.find(".shipping-address-detail");
+ $shippingAsBilling = $paymentInfoPanel.find("#shipping-as-billing");
+ $orderContent = $orderPanel.find(".order-content");
+
+ if($screen.length == 0) throw "$screen must be specified";
+ if($navigation.length == 0) throw "$navigation must be specified";
+
+ initializeControls();
+
+ events();
+ }
+
+ this.initialize = initialize;
+
+ return this;
+ }
+})(window,jQuery);
\ No newline at end of file
diff --git a/web/app/assets/javascripts/scheduled_session.js.erb b/web/app/assets/javascripts/scheduled_session.js.erb
index e113e4b86..1fc4d1156 100644
--- a/web/app/assets/javascripts/scheduled_session.js.erb
+++ b/web/app/assets/javascripts/scheduled_session.js.erb
@@ -1237,7 +1237,7 @@
$languageList = $screen.find('#session-language-list');
$sessionPlusMusiciansLabel = $screen.find('label[for="session-plus-musicians"]');
$editScheduledSessions = $screen.find('#edit_scheduled_sessions');
- $btnSelectFiles = $screen.find('.btn-select-files');
+ $btnSelectFiles = $screen.find('#session-notation-file-selection');
$selectedFilenames = $screen.find('#selected-filenames');
$uploadSpinner = $screen.find('#file-upload-spinner');
$policyTypes = $screen.find('input[name="session-policy-type"]');
diff --git a/web/app/assets/javascripts/session.js b/web/app/assets/javascripts/session.js
index 54efbf840..6c07ba1e3 100644
--- a/web/app/assets/javascripts/session.js
+++ b/web/app/assets/javascripts/session.js
@@ -495,9 +495,6 @@
var mixerIds = context.jamClient.SessionGetIDs();
var holder = $.extend(true, {}, {mixers: context.jamClient.SessionGetControlState(mixerIds)});
mixers = holder.mixers;
-
- //console.log("mixers", mixers)
-
// grab the first mixer, and check the mode
var newMixerMode;;
@@ -884,6 +881,7 @@
// particular client, in a particular group, and I'll need to further
// identify by track id or something similar.
+
var mixers = _groupedMixersForClientId(
participant.client_id,
[
@@ -908,6 +906,7 @@
oppositeMixer = mixers[ChannelGroupIds.PeerAudioInputMusicGroup][0]
}
}
+
if (mixer) {
usedMixers[mixer.id] = true;
myTrack = (mixer.group_id === ChannelGroupIds.AudioInputMusicGroup);
@@ -982,6 +981,7 @@
var keysToDelete = [];
for (var key in lookingForMixers) {
var clientId = lookingForMixers[key];
+<<<<<<< HEAD
var mixers = _groupedMixersForClientId(
clientId,
[
@@ -1004,6 +1004,27 @@
mixer = mixers[ChannelGroupIds.UserMusicInputGroup][0]
oppositeMixer = mixers[ChannelGroupIds.PeerAudioInputMusicGroup][0]
}
+=======
+ var mixer = null;
+ if(sessionModel.isMasterMixMode()) {
+ mixer = _mixerForClientId(
+ clientId,
+ [
+ ChannelGroupIds.AudioInputMusicGroup,
+ ChannelGroupIds.PeerAudioInputMusicGroup
+ ],
+ usedMixers);
+ }
+ else {
+ // don't pass in used mixers; we need to associate multiple tracks with the same mixer
+ mixer = _mixerForClientId(
+ clientId,
+ [
+ ChannelGroupIds.AudioInputMusicGroup,
+ ChannelGroupIds.UserMusicInputGroup
+ ],
+ {});
+>>>>>>> develop
}
if (mixer) {
var participant = (sessionModel.getParticipant(clientId) || {name:'unknown'}).name;
diff --git a/web/app/assets/javascripts/sessionList.js b/web/app/assets/javascripts/sessionList.js
index 2dae2c083..3f3ee478d 100644
--- a/web/app/assets/javascripts/sessionList.js
+++ b/web/app/assets/javascripts/sessionList.js
@@ -380,7 +380,7 @@
var track = participant.tracks[j];
logger.debug("Find:Finding instruments. Participant tracks:", participant.tracks);
var inst = context.JK.getInstrumentIcon24(track.instrument_id);
- instrumentLogoHtml += '

';
+ instrumentLogoHtml += '

';
}
var id = participant.user.id;
@@ -411,7 +411,7 @@
for (j=0; j < user.instrument_list.length; j++) {
var instrument = user.instrument_list[j];
var inst = context.JK.getInstrumentIcon24(instrument.id);
- instrumentLogoHtml += '

';
+ instrumentLogoHtml += '

';
}
}
diff --git a/web/app/assets/javascripts/shopping_cart.js b/web/app/assets/javascripts/shopping_cart.js
new file mode 100644
index 000000000..ac02b8b39
--- /dev/null
+++ b/web/app/assets/javascripts/shopping_cart.js
@@ -0,0 +1,112 @@
+(function(context,$) {
+
+ "use strict";
+ context.JK = context.JK || {};
+ context.JK.ShoppingCartScreen = function(app) {
+
+ var logger = context.JK.logger;
+
+ var $screen = null;
+ var $content = null;
+
+ function beforeShow(data) {
+ loadShoppingCarts();
+ }
+
+ function afterShow(data) {
+ }
+
+ function events() {
+ $screen.find("a.remove-cart").on('click', removeCart);
+ $screen.find("a.proceed-checkout").on('click', proceedCheckout);
+ }
+
+ function proceedCheckout(e) {
+ e.preventDefault();
+
+ if (!context.JK.currentUserId) {
+ window.location = '/client#/signin';
+ }
+ else {
+ window.location = '/client#/order';
+ }
+ }
+
+ function removeCart(e) {
+ e.preventDefault();
+
+ var options = {};
+ options.id = $(e.target).attr("cart-id");
+
+ rest.removeShoppingCart(options)
+ .done(loadShoppingCarts)
+ .fail(app.ajaxError);
+ }
+
+ function clearContent() {
+ $content.empty();
+ }
+
+ function loadShoppingCarts() {
+ clearContent();
+
+ rest.getShoppingCarts()
+ .done(renderShoppingCarts)
+ .fail(app.ajaxError);
+ }
+
+ function renderShoppingCarts(carts) {
+ var data = {};
+ var latest_cart = carts[carts.length - 1];
+
+ var $latestCartHtml = "";
+
+ if (latest_cart) {
+ $latestCartHtml = $(
+ context._.template(
+ $('#template-shopping-cart-header').html(),
+ latest_cart,
+ {variable: 'data'}
+ )
+ );
+ }
+
+ var sub_total = 0;
+ $.each(carts, function(index, cart) {
+ sub_total += parseFloat(cart.product_info.price) * parseFloat(cart.quantity);
+ });
+ data.sub_total = sub_total.toFixed(2);
+
+ data.carts = carts;
+ var $cartsHtml = $(
+ context._.template(
+ $('#template-shopping-cart-body').html(),
+ data,
+ {variable: 'data'}
+ )
+ );
+
+ $content.append($latestCartHtml).append($cartsHtml);
+
+ events();
+ }
+
+ function initialize() {
+ var screenBindings = {
+ 'beforeShow': beforeShow,
+ 'afterShow': afterShow
+ };
+ app.bindScreen('shoppingCart', screenBindings);
+
+ $screen = $("#shoppingCartScreen");
+ $content = $screen.find(".shopping-cart-content");
+
+ if($screen.length == 0) throw "$screen must be specified";
+ if($content.length == 0) throw "$content must be specified";
+ }
+
+ this.initialize = initialize;
+
+ return this;
+ }
+})(window,jQuery);
\ No newline at end of file
diff --git a/web/app/assets/javascripts/utils.js b/web/app/assets/javascripts/utils.js
index b7641dbbc..7e45b7c46 100644
--- a/web/app/assets/javascripts/utils.js
+++ b/web/app/assets/javascripts/utils.js
@@ -88,6 +88,7 @@
var instrumentIconMap24 = {};
var instrumentIconMap45 = {};
var instrumentIconMap256 = {};
+ var notSpecifiedText = "Not specified";
$.each(icon_map_base, function (instrumentId, icon) {
instrumentIconMap24[instrumentId] = {asset: "/assets/content/icon_instrument_" + icon + "24.png", name: instrumentId};
@@ -332,24 +333,28 @@
function showBubble(bubble, $hoverElement) {
$hoverElement.attr("bubble-id", bubble.id);
- bubble.showBubble($hoverElement);
+ bubble.showBubble($hoverElement)
+ .done(function() {
+
+ $(bubble.id()).hover(
+ function () {
+ $(this).data('hovering', true)
+ // do nothing when entering the bubble
+ },
+ function () {
+ $(this).data('hovering', false).fadeOut(100);
+ }
+ );
+
+ })
}
function hideBubble($hoverElement) {
var bubbleSelector = $hoverElement.attr("bubble-id");
- $(bubbleSelector).hover(
- function () {
- // do nothing when entering the bubble
- },
- function () {
- $(this).fadeOut(fadeoutValue);
- }
- );
-
// first check to see if the user isn't hovering over the hover bubble
- if (!$(bubbleSelector).is(":hover")) {
+ if (!$(bubbleSelector).data('hovering')) {
$(bubbleSelector).fadeOut(fadeoutValue);
}
}
@@ -358,8 +363,10 @@
$("[hoveraction='musician']", $parent).hoverIntent({
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));
@@ -507,6 +514,10 @@
return instrumentIconMap256["_default"].asset;
};
+ context.JK.getInstrumentId = function(instrumentId) {
+ return instrumentId ? instrumentId : notSpecifiedText;
+ }
+
// meant to pass in a bunch of images with an instrument-id attribute on them.
context.JK.setInstrumentAssetPath = function ($elements) {
diff --git a/web/app/assets/javascripts/wizard/frame_buffers.js b/web/app/assets/javascripts/wizard/frame_buffers.js
index f96dde085..a19fdfdf7 100644
--- a/web/app/assets/javascripts/wizard/frame_buffers.js
+++ b/web/app/assets/javascripts/wizard/frame_buffers.js
@@ -9,11 +9,14 @@
var $bufferIn = null;
var $bufferOut = null;
var $frameSize = null;
+ var $adjustSettingsLink = null;
var $self = $(this);
+ var logger = context.JK.logger;
var FRAMESIZE_CHANGED = 'frame_buffers.framesize_changed';
var BUFFER_IN_CHANGED = 'frame_buffers.buffer_in_changed';
var BUFFER_OUT_CHANGED = 'frame_buffers.buffer_out_changed';
+ var ADJUST_GEAR_LINK_CLICKED = 'frame_buffers.adjust_gear_settings_clicked';
function selectedFramesize() {
return parseFloat($frameSize.val());
@@ -77,6 +80,12 @@
logger.debug("buffer-out changed: " + selectedBufferOut());
$self.triggerHandler(BUFFER_OUT_CHANGED, {value: selectedBufferOut()});
});
+
+ $adjustSettingsLink.click(function() {
+ logger.debug("adjust-gear-settings clicked");
+ $self.triggerHandler(ADJUST_GEAR_LINK_CLICKED);
+ return false;
+ });
}
function initialize(_$knobs) {
@@ -89,6 +98,7 @@
$bufferIn = $knobs.find('.select-buffer-in');
$bufferOut = $knobs.find('.select-buffer-out');
$frameSize = $knobs.find('.select-frame-size');
+ $adjustSettingsLink = $knobs.find('.adjust-gear-settings')
events();
render();
@@ -97,6 +107,7 @@
this.FRAMESIZE_CHANGED = FRAMESIZE_CHANGED;
this.BUFFER_IN_CHANGED = BUFFER_IN_CHANGED;
this.BUFFER_OUT_CHANGED = BUFFER_OUT_CHANGED;
+ this.ADJUST_GEAR_LINK_CLICKED = ADJUST_GEAR_LINK_CLICKED;
this.initialize = initialize;
this.selectedFramesize = selectedFramesize;
this.selectedBufferIn = selectedBufferIn;
diff --git a/web/app/assets/javascripts/wizard/gear/gear_wizard.js b/web/app/assets/javascripts/wizard/gear/gear_wizard.js
index 349032d8c..cb0cd4317 100644
--- a/web/app/assets/javascripts/wizard/gear/gear_wizard.js
+++ b/web/app/assets/javascripts/wizard/gear/gear_wizard.js
@@ -13,6 +13,7 @@
var $wizardSteps = null;
var $templateSteps = null;
var loopbackWizard = null;
+ var adjustGearSettings = null;
var inputs = null;
var self = this;
@@ -183,9 +184,10 @@
return inputs;
}
- function initialize(_loopbackWizard) {
+ function initialize(_loopbackWizard, _adjustGearSettings) {
loopbackWizard = _loopbackWizard;
+ adjustGearSettings = _adjustGearSettings;
// on initial page load, we are not in the FTUE. so cancel the FTUE and call FTUESetStatus(true) if needed
if(context.jamClient.FTUEGetStatus() == false) {
@@ -224,6 +226,7 @@
this.createFTUEProfile = createFTUEProfile;
this.getWizard = function() {return wizard; }
this.getLoopbackWizard = function() { return loopbackWizard; };
+ this.getAdjustGearSettings = function() { return adjustGearSettings; };
self = this;
return this;
diff --git a/web/app/assets/javascripts/wizard/gear/step_direct_monitoring.js b/web/app/assets/javascripts/wizard/gear/step_direct_monitoring.js
index f76e0abaf..ac9a40c25 100644
--- a/web/app/assets/javascripts/wizard/gear/step_direct_monitoring.js
+++ b/web/app/assets/javascripts/wizard/gear/step_direct_monitoring.js
@@ -5,9 +5,11 @@
context.JK = context.JK || {};
context.JK.StepDirectMonitoring = function (app) {
+ var EVENTS = context.JK.EVENTS;
var logger = context.JK.logger;
var $step = null;
var $directMonitoringBtn = null;
+ var $adjustSettingsDirectMonitor = null;
var isPlaying = false;
var playCheckInterval = null;
var trackDurationMs = null;
@@ -83,12 +85,29 @@
}
}
+ function onAdjustGearRequested() {
+ app.layout.showDialog('adjust-gear-speed-dialog').one(EVENTS.DIALOG_CLOSED, function(e, data) {
+
+ var adjustGearTest = data.result;
+
+ if(!data.canceled) {
+ if(adjustGearTest.isGoodFtue()) {
+
+ }
+ }
+ else {
+ logger.debug("adjust-gear-speed was cancelled; ignoring")
+ }
+ })
+ }
+
function initialize(_$step) {
$step = _$step;
$directMonitoringBtn = $step.find('.test-direct-monitoring');
-
$directMonitoringBtn.on('click', togglePlay);
+ $adjustSettingsDirectMonitor = $step.find('.adjust-settings-direct-monitor');
+ $adjustSettingsDirectMonitor.on('click', onAdjustGearRequested)
}
this.handleHelp = handleHelp;
diff --git a/web/app/assets/javascripts/wizard/gear/step_select_gear.js b/web/app/assets/javascripts/wizard/gear/step_select_gear.js
index d1d53543f..9479f86c9 100644
--- a/web/app/assets/javascripts/wizard/gear/step_select_gear.js
+++ b/web/app/assets/javascripts/wizard/gear/step_select_gear.js
@@ -11,6 +11,7 @@
var VOICE_CHAT = context.JK.VOICE_CHAT;
var AUDIO_DEVICE_BEHAVIOR = context.JK.AUDIO_DEVICE_BEHAVIOR;
var gearUtils = context.JK.GearUtils;
+ var modUtils = context.JK.ModUtils;
var self = null;
var $step = null;
@@ -19,6 +20,7 @@
var frameBuffers = new context.JK.FrameBuffers(app);
var gearTest = new context.JK.GearTest(app);
var loopbackShowing = false;
+ var adjustGearSettingsShowing = false;
var wizard = null;
// the goal of lastFailureAnalytics and trackedPass are to send only a single AudioTest 'Pass' or 'Failed' event, per FTUE wizard open/close
@@ -32,6 +34,8 @@
var $inputChannels = null;
var $outputChannels = null;
var $knobs = null;
+ var $adjustSettingsLink = null;
+ var $adjustGearForIoFail = null;
var $scoreReport = null;
var $asioInputControlBtn = null;
var $asioOutputControlBtn = null;
@@ -86,7 +90,7 @@
}
function initializeNextButtonState() {
- dialog.setNextState(gearTest.isGoodFtue() || dialog.getLoopbackWizard().getGearTest().isGoodFtue());
+ dialog.setNextState(gearTest.isGoodFtue() || dialog.getLoopbackWizard().getGearTest().isGoodFtue() || dialog.getAdjustGearSettings().getGearTest().isGoodFtue());
}
function initializeBackButtonState() {
@@ -365,8 +369,9 @@
if(dialog.getLoopbackWizard().getGearTest().isGoodFtue()) {
gearTest.resetScoreReport();
gearTest.showLoopbackDone();
- context.JK.prodBubble(dialog.getWizard().getNextButton(), 'move-on-loopback-success', {}, {positions:['top']});
-
+ setTimeout(function() {
+ context.JK.prodBubble(dialog.getWizard().getNextButton(), 'can-move-on', {}, {positions:['top'], offsetParent: dialog.getWizard().getDialog()});
+ }, 300);
}
initializeNextButtonState();
@@ -379,6 +384,42 @@
})
}
+ function onAdjustGearRequested()
+ {
+ if(gearTest.isScoring()) {logger.debug("ignoring adjust-gear request while scoring"); return false;}
+
+ app.layout.showDialog('adjust-gear-speed-dialog').one(EVENTS.DIALOG_CLOSED, function(e, data) {
+ adjustGearSettingsShowing = false;
+
+ var adjustGearTest = data.result;
+
+ if(!data.canceled) {
+ if(adjustGearTest.isGoodFtue()) {
+ // update our own frame buffers to reflect any changes made by the adjust dialog
+ frameBuffers.setFramesize(context.jamClient.FTUEGetFrameSize())
+ frameBuffers.setBufferIn(context.jamClient.FTUEGetInputLatency())
+ frameBuffers.setBufferOut(context.jamClient.FTUEGetOutputLatency())
+
+ gearTest.resetScoreReport();
+ gearTest.showGearAdjustmentDone();
+
+ setTimeout(function() {
+ context.JK.prodBubble(dialog.getWizard().getNextButton(), 'can-move-on', {}, {positions:['top'], offsetParent: dialog.getWizard().getDialog()});
+ }, 300);
+ }
+
+ initializeNextButtonState();
+ initializeBackButtonState();
+ }
+ else {
+ logger.debug("adjust-gear-speed was cancelled; ignoring")
+ }
+ })
+
+ adjustGearSettingsShowing = true;
+ return false;
+ }
+
function initializeFormElements() {
if (!deviceInformation) throw "devices are not initialized";
@@ -486,6 +527,7 @@
function invalidateScore() {
gearTest.invalidateScore();
dialog.getLoopbackWizard().getGearTest().invalidateScore();
+ dialog.getAdjustGearSettings().getGearTest().invalidateScore();
initializeNextButtonState();
}
@@ -755,6 +797,7 @@
validDevice = autoSelectMinimumValidChannels();
if (!validDevice) {
+ $adjustSettingsLink.hide();
return false;
}
@@ -774,6 +817,11 @@
shownOutputProdOnce = true;
}
+ // further, check if we have both inputs and outputs defined; if so, show ? to allow launch of adjust gear settings dialog
+ if(modUtils.getGear('show_frame_options')) {
+ $adjustSettingsLink.show();
+ }
+
return true;
}
@@ -821,11 +869,12 @@
}
// handle framesize/buffers
- if (inputBehavior && (inputBehavior.showKnobs || outputBehavior.showKnobs)) {
+ if (inputBehavior && (inputBehavior.showKnobs || outputBehavior.showKnobs || modUtils.getGear('show_frame_options'))) {
$knobs.show();
}
else {
$knobs.hide();
+ $adjustSettingsLink.hide();
}
// handle ASIO visibility
@@ -865,36 +914,9 @@
jamClient.FTUESetFrameSize(frameBuffers.selectedFramesize());
}
+
function updateDefaultBuffers() {
-
- // handle specific framesize settings
- if(selectedDeviceInfo && (selectedDeviceInfo.input.info.type == 'Win32_wdm' || selectedDeviceInfo.output.info.type == 'Win32_wdm')) {
- var framesize = frameBuffers.selectedFramesize();
-
- if(framesize == 2.5) {
- logger.debug("setting default buffers to 1/1");
- frameBuffers.setBufferIn('1');
- frameBuffers.setBufferOut('1');
- }
- else if(framesize == 5) {
- logger.debug("setting default buffers to 3/2");
- frameBuffers.setBufferIn('3');
- frameBuffers.setBufferOut('2');
- }
- else {
- logger.debug("setting default buffers to 6/5");
- frameBuffers.setBufferIn('6');
- frameBuffers.setBufferOut('5');
- }
- }
- else {
- logger.debug("setting default buffers to 0/0");
- frameBuffers.setBufferIn(0);
- frameBuffers.setBufferOut(0);
- }
-
- jamClient.FTUESetInputLatency(frameBuffers.selectedBufferIn());
- jamClient.FTUESetOutputLatency(frameBuffers.selectedBufferOut());
+ gearUtils.updateDefaultBuffers(selectedDeviceInfo, frameBuffers)
}
// refocused affects how IO testing occurs.
@@ -940,11 +962,26 @@
}
}
+ function prodUserToTweakASIOSettings($btn) {
+ setTimeout(function() {
+ context.JK.prodBubble($btn, 'tweak-asio-settings', {}, {positions:['top']});
+ }, 300)
+ }
+
function onGearTestFail(e, data) {
renderScoringStopped();
gearUtils.postDiagnostic(operatingSystem, deviceInformation, selectedDeviceInfo, gearTest, frameBuffers, true);
if(data.reason == "latency") {
+
+ console.log("selectedDeviceInfo", selectedDeviceInfo)
+ if(selectedDeviceInfo.input.info.type.indexOf('Win32_asio') > -1) {
+ prodUserToTweakASIOSettings($asioInputControlBtn)
+ }
+ else if(selectedDeviceInfo.output.info.type.indexOf('Win32_asio') > -1) {
+ prodUserToTweakASIOSettings($asioOutputControlBtn)
+ }
+
storeLastFailureForAnalytics(context.JK.detectOS(), context.JK.GA.AudioTestFailReasons.latency, data.latencyScore);
}
else if(data.reason = "io") {
@@ -973,11 +1010,13 @@
var specificResolutions = [];
- if(selectedDeviceInfo.input.behavior.type.indexOf('Win32_asio') > -1 || selectedDeviceInfo.output.behavior.type.indexOf('Win32_asio') > -1) {
+ if(selectedDeviceInfo.input.info.type.indexOf('Win32_asio') > -1 || selectedDeviceInfo.output.info.type.indexOf('Win32_asio') > -1) {
+ // specificResolutions.push("Read over this
article")
specificResolutions.push("Select the ASIO SETTINGS... button and try different settings.");
}
- if(selectedDeviceInfo.input.behavior.type == 'Win32_wdm' || selectedDeviceInfo.output.behavior.type == 'Win32_wdm') {
+ if(selectedDeviceInfo.input.info.type == 'Win32_wdm' || selectedDeviceInfo.output.info.type == 'Win32_wdm') {
+ // specificResolutions.push("Read over this
article")
specificResolutions.push("Change the Frame, Buffer In, or Buffer Out settings.");
}
@@ -1063,7 +1102,7 @@
}
function onFocus() {
- if(validDevice && !loopbackShowing && !gearTest.isScoring() && getSelectedInputs().length > 0 && getSelectedOutputs().length == 2 ) {
+ if(validDevice && !loopbackShowing && !adjustGearSettingsShowing && !gearTest.isScoring() && getSelectedInputs().length > 0 && getSelectedOutputs().length == 2 ) {
scheduleRescanSystem(function() { attemptScore(true); }, 3000, false)
}
}
@@ -1160,6 +1199,8 @@
$inputChannels = $step.find('.input-ports');
$outputChannels = $step.find('.output-ports');
$knobs = $step.find('.frame-and-buffers');
+ $adjustSettingsLink = $knobs.find('.adjust-gear-settings')
+ $adjustGearForIoFail = $step.find(".adjust-gear-for-io-fail")
$scoreReport = $step.find('.results');
$asioInputControlBtn = $step.find('.asio-settings-input-btn');
$asioOutputControlBtn = $step.find('.asio-settings-output-btn');
@@ -1175,6 +1216,7 @@
.on(frameBuffers.FRAMESIZE_CHANGED, onFramesizeChanged)
.on(frameBuffers.BUFFER_IN_CHANGED, onBufferInChanged)
.on(frameBuffers.BUFFER_OUT_CHANGED, onBufferOutChanged)
+ .on(frameBuffers.ADJUST_GEAR_LINK_CLICKED, onAdjustGearRequested)
gearTest.initialize($scoreReport, true)
$(gearTest)
@@ -1182,6 +1224,7 @@
.on(gearTest.GEAR_TEST_DONE, onGearTestDone)
.on(gearTest.GEAR_TEST_FAIL, onGearTestFail)
.on(gearTest.GEAR_TEST_INVALIDATED_ASYNC, onGearTestInvalidated)
+ $adjustGearForIoFail.click(onAdjustGearRequested);
}
this.getLastAudioTestFailAnalytics = getLastAudioTestFailAnalytics;
diff --git a/web/app/assets/javascripts/wizard/gear_test.js b/web/app/assets/javascripts/wizard/gear_test.js
index 7b56534db..fc397c2f9 100644
--- a/web/app/assets/javascripts/wizard/gear_test.js
+++ b/web/app/assets/javascripts/wizard/gear_test.js
@@ -33,6 +33,8 @@
var $resultsText = null;
var $unknownText = null;
var $loopbackCompleted = null;
+ var $adjustGearSpeedCompleted = null;
+ var $adjustGearForIoFail = null;
var $ioScoreSection = null;
var $latencyScoreSection = null;
@@ -78,6 +80,10 @@
medianIOClass = 'acceptable';
}
+ // uncomment one to force a particular type of I/O failure
+ // medianIOClass = "bad";
+ // stdIOClass = "bad"
+
// take worst between median or std
var ioClassToNumber = {bad: 2, acceptable: 1, good: 0}
var aggregrateIOClass = ioClassToNumber[stdIOClass] > ioClassToNumber[medianIOClass] ? stdIOClass : medianIOClass;
@@ -240,6 +246,10 @@
latencyClass = 'unknown';
}
+ // uncomment these two lines to fail test due to latency
+ // latencyClass = "bad";
+ // validLatency = false;
+
validLatencyScore = validLatency;
if(refocused) {
@@ -300,7 +310,7 @@
logger.debug("gear_test: onInvalidAudioDevice")
asynchronousInvalidDevice = true;
$self.triggerHandler(GEAR_TEST_INVALIDATED_ASYNC);
- context.JK.Banner.showAlert('Invalid Audio Device', 'It appears this audio device is not currently connected. Attach the device to your computer and restart the application, or select a different device.')
+ context.JK.Banner.showAlert('Invalid Audio Device', 'It appears this audio device is not currently connected. Attach the device to your computer and restart the application, or select a different device.
If you think your gear is connected and working, this
support article can help.')
}
@@ -308,6 +318,10 @@
$loopbackCompleted.show();
}
+ function showGearAdjustmentDone() {
+ $adjustGearSpeedCompleted.show();
+ }
+
function resetScoreReport() {
$ioHeader.hide();
$latencyHeader.hide();
@@ -322,6 +336,7 @@
$resultsText.removeAttr('scored');
$unknownText.hide();
$loopbackCompleted.hide();
+ $adjustGearSpeedCompleted.hide();
$ioScoreSection.removeClass('good acceptable bad unknown starting skip');
$latencyScoreSection.removeClass('good acceptable bad unknown starting')
}
@@ -398,6 +413,8 @@
$resultsText = $scoreReport.find('.results-text');
$unknownText = $scoreReport.find('.unknown-text');
$loopbackCompleted = $scoreReport.find('.loopback-completed')
+ $adjustGearSpeedCompleted = $scoreReport.find('.adjust-gear-speed-completed');
+ $adjustGearForIoFail = $scoreReport.find(".adjust-gear-for-io-fail")
$latencyScoreSection = $scoreReport.find('.latency-score-section');
function onGearTestStart(e, data) {
@@ -514,6 +531,7 @@
this.attemptScore = attemptScore;
this.resetScoreReport = resetScoreReport;
this.showLoopbackDone = showLoopbackDone;
+ this.showGearAdjustmentDone = showGearAdjustmentDone;
this.invalidateScore = invalidateScore;
this.isValidLatencyScore = isValidLatencyScore;
this.isValidIOScore = isValidIOScore;
diff --git a/web/app/assets/javascripts/wizard/gear_utils.js b/web/app/assets/javascripts/wizard/gear_utils.js
index ba5dd70e0..c9d67f533 100644
--- a/web/app/assets/javascripts/wizard/gear_utils.js
+++ b/web/app/assets/javascripts/wizard/gear_utils.js
@@ -145,6 +145,54 @@
return loadedDevices;
}
+ gearUtils.updateDefaultBuffers = function(selectedDeviceInfo, frameBuffers) {
+ function hasWDMAssociated() {
+ return selectedDeviceInfo && (selectedDeviceInfo.input.info.type == 'Win32_wdm' || selectedDeviceInfo.output.info.type == 'Win32_wdm')
+ }
+
+ function hasASIOAssociated() {
+ return selectedDeviceInfo && (selectedDeviceInfo.input.info.type == 'Win32_asio' || selectedDeviceInfo.output.info.type == 'Win32_asio')
+ }
+
+ // handle specific framesize settings
+ if(hasWDMAssociated() || hasASIOAssociated()) {
+ var framesize = frameBuffers.selectedFramesize();
+
+ if(framesize == 2.5) {
+ // if there is a WDM device, start off at 1/1 due to empirically observed issues with 0/0
+ if(hasWDMAssociated()) {
+ logger.debug("setting default buffers to 1/1");
+ frameBuffers.setBufferIn('1');
+ frameBuffers.setBufferOut('1');
+ }
+ else {
+ // otherwise, it's ASIO, so go with 0/0
+ logger.debug("setting default buffers to 0/0");
+ frameBuffers.setBufferIn('0');
+ frameBuffers.setBufferOut('0');
+ }
+ }
+ else if(framesize == 5) {
+ logger.debug("setting default buffers to 3/2");
+ frameBuffers.setBufferIn('3');
+ frameBuffers.setBufferOut('2');
+ }
+ else {
+ logger.debug("setting default buffers to 6/5");
+ frameBuffers.setBufferIn('6');
+ frameBuffers.setBufferOut('5');
+ }
+ }
+ else {
+ logger.debug("setting default buffers to 0/0");
+ frameBuffers.setBufferIn(0);
+ frameBuffers.setBufferOut(0);
+ }
+
+ context.jamClient.FTUESetInputLatency(frameBuffers.selectedBufferIn());
+ context.jamClient.FTUESetOutputLatency(frameBuffers.selectedBufferOut());
+ }
+
gearUtils.ftueSummary = function(operatingSystem, deviceInformation, selectedDeviceInfo, gearTest, frameBuffers, isAutomated) {
return {
os: operatingSystem,
diff --git a/web/app/assets/javascripts/wizard/loopback/step_loopback_test.js b/web/app/assets/javascripts/wizard/loopback/step_loopback_test.js
index d22732519..ccba2ae88 100644
--- a/web/app/assets/javascripts/wizard/loopback/step_loopback_test.js
+++ b/web/app/assets/javascripts/wizard/loopback/step_loopback_test.js
@@ -58,35 +58,7 @@
}
function updateDefaultBuffers() {
-
- // handle specific framesize settings
- if(selectedDeviceInfo && (selectedDeviceInfo.input.info.type == 'Win32_wdm' || selectedDeviceInfo.output.info.type == 'Win32_wdm')) {
- var framesize = frameBuffers.selectedFramesize();
-
- if(framesize == 2.5) {
- logger.debug("setting default buffers to 1/1");
- frameBuffers.setBufferIn('1');
- frameBuffers.setBufferOut('1');
- }
- else if(framesize == 5) {
- logger.debug("setting default buffers to 3/2");
- frameBuffers.setBufferIn('3');
- frameBuffers.setBufferOut('2');
- }
- else {
- logger.debug("setting default buffers to 6/5");
- frameBuffers.setBufferIn('6');
- frameBuffers.setBufferOut('5');
- }
- }
- else {
- logger.debug("setting default buffers to 0/0");
- frameBuffers.setBufferIn(0);
- frameBuffers.setBufferOut(0);
- }
-
- jamClient.FTUESetInputLatency(frameBuffers.selectedBufferIn());
- jamClient.FTUESetOutputLatency(frameBuffers.selectedBufferOut());
+ gearUtils.updateDefaultBuffers(selectedDeviceInfo, frameBuffers)
}
function onFramesizeChanged() {
diff --git a/web/app/assets/javascripts/wizard/wizard.js b/web/app/assets/javascripts/wizard/wizard.js
index e3a6eaed3..6d8600e3e 100644
--- a/web/app/assets/javascripts/wizard/wizard.js
+++ b/web/app/assets/javascripts/wizard/wizard.js
@@ -217,6 +217,10 @@
return $currentWizardStep;
}
+ function getDialog() {
+ return $dialog;
+ }
+
function initialize(_$dialog, _$wizardSteps, _STEPS, _options) {
$dialog = _$dialog;
dialogName = $dialog.attr('layout-id');
@@ -239,6 +243,7 @@
this.onCloseDialog = onCloseDialog;
this.onBeforeShow = onBeforeShow;
this.onAfterHide = onAfterHide;
+ this.getDialog = getDialog;
this.initialize = initialize;
}
diff --git a/web/app/assets/stylesheets/client/checkout.css.scss b/web/app/assets/stylesheets/client/checkout.css.scss
new file mode 100644
index 000000000..7cb65eac0
--- /dev/null
+++ b/web/app/assets/stylesheets/client/checkout.css.scss
@@ -0,0 +1,252 @@
+.checkout-navigation {
+ padding: 20px 0px;
+ .nav-signin, .nav-payment-info, .nav-place-order {
+ width: 30%;
+ float: left;
+ }
+
+ .nav-signin {
+ margin-left: 5%;
+ }
+
+ .nav-place-order {
+ margin-right: 5%;
+ }
+
+ .nav-text {
+ font-size: 17px;
+ float: left;
+ }
+
+ .nav-text.selected {
+ font-weight: bold;
+ }
+
+ .nav-arrow {
+ float: left;
+ margin-left: 30px;
+ }
+}
+
+.checkout-signin, .checkout-payment-info, .checkout-place-order {
+ padding: 30px;
+
+ .signin-form {
+ padding: 10px;
+
+ strong {
+ font-weight: bold;
+ }
+
+ label {
+ display: inline;
+ }
+
+ .signin-password {
+ margin-left: 33px;
+ }
+
+ .login-error {
+ background-color: #330000;
+ border: 1px solid #990000;
+ padding:4px;
+
+ div.actions {
+ margin-top:10px;
+ }
+ }
+
+ .login-error-msg {
+ display:none;
+ margin-top:10px;
+ text-align:center;
+ color:#F00;
+ font-size:11px;
+ }
+
+ .login-error .login-error-msg {
+ display:block;
+ }
+ }
+
+ form.payment-info {
+ width: 100%;
+
+ input[type="text"] {
+ width: 90%;
+ }
+
+ .billing-address {
+ float: left;
+ width: 50%;
+
+ h2.billing-caption {
+ margin: 20px 5px;
+ font-size: 16px;
+ }
+
+ .billing-label {
+ padding-top: 8px;
+ width: 30%;
+ float: left;
+ text-align: right;
+ margin-right: 5px;
+ }
+
+ .billing-value {
+ width: 65%;
+ text-align: left;
+ float: left;
+ }
+ }
+
+ .payment-method {
+ float: left;
+ width: 50%;
+
+ h2.payment-method-caption {
+ margin: 20px 5px;
+ font-size: 16px;
+ }
+
+ .card-label {
+ padding-top: 8px;
+ width: 35%;
+ float: left;
+ text-align: right;
+ margin-right: 5px;
+ }
+
+ .card-value {
+ width: 60%;
+ text-align: left;
+ float: left;
+ }
+
+ .save-card-checkbox {
+ float:left;
+ display:block;
+ margin-right:5px;
+ }
+ }
+
+ .shipping-address {
+ float: left;
+ width: 50%;
+
+ h2.shipping-address-label {
+ margin: 20px 5px;
+ font-size: 16px;
+ }
+
+ .shipping-as-billing {
+ float:left;
+ display:block;
+ margin-right:5px;
+ }
+
+ .divBillingHelper {
+ padding-top: 2px;
+ }
+
+ .shipping-label {
+ padding-top: 8px;
+ width: 30%;
+ float: left;
+ text-align: right;
+ margin-right: 5px;
+ }
+
+ .shipping-value {
+ width: 65%;
+ text-align: left;
+ float: left;
+ }
+ }
+ }
+}
+
+.order-panel {
+ padding: 30px;
+
+ .order-header {
+ h2 {
+ font-size: 16px;
+ }
+ }
+
+ .order-content {
+ margin-top: 20px;
+ }
+
+ .order-left-page {
+ float: left;
+ width: 60%;
+
+ .payment-info-page {
+ padding: 5px;
+
+ .info-caption-link {
+ .caption-text {
+ float: left;
+ }
+ .caption-link {
+ float: left;
+ margin-left: 5px;
+ }
+ }
+
+ .address-info {
+ width: 50%;
+ float: left;
+ }
+
+ .payment-method-info {
+ width: 50%;
+ float: left;
+ }
+ }
+ .order-items-page {
+ padding: 5px;
+
+ .cart-item-caption {
+ width: 50%;
+ text-align: left;
+ float: left;
+ }
+
+ .cart-item-caption#header {
+ font-weight: bold;
+ }
+
+ .cart-item-price {
+ width: 25%;
+ text-align: right;
+ float: left;
+ }
+
+ .cart-item-quantity {
+ width: 25%;
+ text-align: right;
+ float: left;
+ }
+
+ .cart-items {
+ margin-top: 10px;
+ }
+
+ .cart-item {
+ margin-top: 10px;
+ }
+ }
+ }
+ .order-right-page {
+ float: right;
+ width: 35%;
+ text-align: center;
+
+ .order-total {
+ color: #ed3618;
+ }
+ }
+}
\ 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 446a19fbd..e18189462 100644
--- a/web/app/assets/stylesheets/client/client.css
+++ b/web/app/assets/stylesheets/client/client.css
@@ -49,6 +49,9 @@
*= require ./terms
*= require ./createSession
*= require ./feed
+ *= require ./jamtrack
+ *= require ./shoppingCart
+ *= require ./checkout
*= require ./genreSelector
*= require ./sessionList
*= require ./searchResults
diff --git a/web/app/assets/stylesheets/client/header.css.scss b/web/app/assets/stylesheets/client/header.css.scss
index 2812d087f..8bdedd4ea 100644
--- a/web/app/assets/stylesheets/client/header.css.scss
+++ b/web/app/assets/stylesheets/client/header.css.scss
@@ -7,6 +7,15 @@
z-index:5;
}
+.header-shopping-cart {
+ float: right;
+ margin-right: 20px;
+
+ img {
+ width: 60px;
+ }
+}
+
div[layout="header"] h1 {
cursor:pointer;
width: 247px;
diff --git a/web/app/assets/stylesheets/client/home.css.scss b/web/app/assets/stylesheets/client/home.css.scss
index 953d7f87e..d9b7d9d45 100644
--- a/web/app/assets/stylesheets/client/home.css.scss
+++ b/web/app/assets/stylesheets/client/home.css.scss
@@ -40,6 +40,9 @@
.homecard.musicians {
background-image: url(/assets/content/bkg_home_musicians.jpg);
}
+.homecard.jamtrack {
+ background-image: url(/assets/content/bkg_home_jamtracks.jpg);
+}
.homebox-info {
position: absolute;
@@ -93,6 +96,9 @@
.homecard.musicians.hover {
background-image: url(/assets/content/bkg_home_musicians_x.jpg);
}
+.homecard.jamtrack.hover {
+ background-image: url(/assets/content/bkg_home_jamtracks_x.jpg);
+}
diff --git a/web/app/assets/stylesheets/client/hoverBubble.css.scss b/web/app/assets/stylesheets/client/hoverBubble.css.scss
index b8d6ec315..fda3967c9 100644
--- a/web/app/assets/stylesheets/client/hoverBubble.css.scss
+++ b/web/app/assets/stylesheets/client/hoverBubble.css.scss
@@ -10,7 +10,7 @@
&.musician-bubble {
- width:425px;
+ width:438px;
}
h2 {
diff --git a/web/app/assets/stylesheets/client/jamtrack.css.scss b/web/app/assets/stylesheets/client/jamtrack.css.scss
new file mode 100644
index 000000000..7d69b1b6f
--- /dev/null
+++ b/web/app/assets/stylesheets/client/jamtrack.css.scss
@@ -0,0 +1,116 @@
+#jamtrackScreen {
+ a.jamtrack_help {
+ color: #fff;
+ text-decoration: none;
+ margin: 4px 0px 0px 60px;
+
+ &:hover {
+ text-decoration: underline;
+ }
+ }
+
+ .jamtrack-content {
+ text-align: center;
+ }
+
+ .no-jamtracks-msg {
+ margin-top: 10px;
+ }
+
+ .jamtrack-record {
+ border-bottom: 1px solid black;
+ text-align: left;
+ }
+
+ .jamtrack-detail {
+ float: left;
+ width: 50%;
+ padding: 10px 0px;
+
+ .detail-label {
+ width: 40%;
+ float: left;
+ margin-top: 5px;
+ }
+
+ .detail-value {
+ width: 50%;
+ float: left;
+ margin-top: 5px;
+ }
+
+ .copyright-value {
+ width: 40%;
+ float: left;
+ margin-top: 5px;
+ }
+
+ .detail-arrow {
+ float: left;
+ margin-left: 10px;
+ }
+
+ .jamtrack-description {
+ display: none;
+ }
+
+ .jamtrack-detail-btn {
+ cursor: pointer;
+ margin-top: 5px;
+ margin-right: 5px;
+ padding-top: 5px;
+ }
+ }
+
+ .jamtrack-tracks {
+ float: left;
+ width: 25%;
+ padding: 10px 0px;
+
+ .tracks-caption {
+ margin-top: 5px;
+ margin-bottom: 10px;
+ }
+
+ .track-instrument {
+ margin-top: 5px;
+ }
+
+ .instrument-image {
+ float: left;
+ }
+
+ .instrument-desc {
+ margin-top: 6px;
+ float: left;
+ margin-left: 10px;
+ }
+ }
+
+ .jamtrack-action {
+ float: left;
+ width: 25%;
+ padding: 10px 0px;
+ text-align: center;
+
+ .play-button {
+ margin-top: 5px;
+ }
+
+ .jamtrack-price {
+ margin-top: 5px;
+ font-size: 20px;
+ }
+
+ .jamtrack-add-cart, .jamtrack-add-cart-disabled {
+ margin: 8px 0px;
+ }
+
+ .jamtrack-license {
+ margin-left: 20%;
+ margin-right: 20%;
+ font-size: 13px;
+ width: 60%;
+ }
+ }
+}
\ No newline at end of file
diff --git a/web/app/assets/stylesheets/client/shoppingCart.css.scss b/web/app/assets/stylesheets/client/shoppingCart.css.scss
new file mode 100644
index 000000000..63f333543
--- /dev/null
+++ b/web/app/assets/stylesheets/client/shoppingCart.css.scss
@@ -0,0 +1,78 @@
+#shoppingCartScreen {
+
+ .content-body {
+ padding: 50px 20px 20px 20px;
+
+ .checkout-image {
+ width: 10%;
+ float: left;
+ }
+
+ .checkout-desc {
+ width: 90%;
+ float: left;
+
+ div {
+ margin-bottom: 7px;
+ }
+
+ div#note {
+ font-style: italic;
+ font-size: 13px;
+ }
+ }
+
+ .cart-item-caption {
+ width: 50%;
+ text-align: left;
+ float: left;
+ }
+
+ .cart-item-caption#header {
+ font-weight: bold;
+ }
+
+ .cart-item-price {
+ width: 15%;
+ text-align: right;
+ float: left;
+ }
+
+ .cart-item-quantity {
+ width: 15%;
+ text-align: right;
+ float: left;
+ }
+
+ .cart-item-actions {
+ width: 20%;
+ text-align: center;
+ float: left;
+ }
+
+ .cart-items {
+ width: 100%;
+ height: 300px;
+ overflow: auto;
+ margin-top: 30px;
+ }
+
+ .cart-item {
+ margin-top: 10px;
+ }
+
+ .shopping-sub-total {
+ width: 65%;
+ float: left;
+ text-align: right;
+ margin-bottom: 20px;
+ font-weight: bold;
+ font-size: 17px;
+ }
+
+ .no-cart-items {
+ margin-top: 30px;
+ text-align: center;
+ }
+ }
+}
\ No newline at end of file
diff --git a/web/app/assets/stylesheets/client/wizard/framebuffers.css.scss b/web/app/assets/stylesheets/client/wizard/framebuffers.css.scss
index b3f205d3f..abc010194 100644
--- a/web/app/assets/stylesheets/client/wizard/framebuffers.css.scss
+++ b/web/app/assets/stylesheets/client/wizard/framebuffers.css.scss
@@ -14,6 +14,10 @@
width:45%;
}
+ .adjust-gear-settings {
+ display:none;
+ margin-left:4px;
+ }
.buffers {
float:left;
diff --git a/web/app/assets/stylesheets/client/wizard/gearResults.css.scss b/web/app/assets/stylesheets/client/wizard/gearResults.css.scss
index 4710ba12c..a542212a7 100644
--- a/web/app/assets/stylesheets/client/wizard/gearResults.css.scss
+++ b/web/app/assets/stylesheets/client/wizard/gearResults.css.scss
@@ -20,6 +20,11 @@
display:inline;
}
}
+ &[data-type=adjust-gear-speed] {
+ span.conditional[data-type=adjust-gear-speed] {
+ display:inline;
+ }
+ }
.io, .latency {
display: none;
}
@@ -47,7 +52,7 @@
padding: 8px;
}
- .loopback-completed {
+ .loopback-completed, .adjust-gear-speed-completed {
display:none;
}
@@ -115,6 +120,14 @@
display: none
}
+ span.io-failure {
+ display:none;
+ }
+
+ %span.no-io-failure {
+ display:inline;
+ }
+
&[latency-score="unknown"] {
display: none;
}
@@ -159,6 +172,16 @@
}
}
+ &[io-rate-score="bad"], &[io-var-score="bad"] {
+ span.io-failure {
+ display:inline;
+ }
+
+ span.no-io-failure {
+ display:none;
+ }
+ }
+
&[latency-score="unknown"] {
li.success {
display: none;
diff --git a/web/app/assets/stylesheets/client/wizard/gearWizard.css.scss b/web/app/assets/stylesheets/client/wizard/gearWizard.css.scss
index 3a7572ec4..29d498f69 100644
--- a/web/app/assets/stylesheets/client/wizard/gearWizard.css.scss
+++ b/web/app/assets/stylesheets/client/wizard/gearWizard.css.scss
@@ -274,7 +274,7 @@
}
.watch-video {
- margin-top: 26px;
+ margin-top: 12px;
}
.help-content {
diff --git a/web/app/assets/stylesheets/dialogs/adjustGearSpeedDialog.css.scss b/web/app/assets/stylesheets/dialogs/adjustGearSpeedDialog.css.scss
new file mode 100644
index 000000000..2bdcb6d7f
--- /dev/null
+++ b/web/app/assets/stylesheets/dialogs/adjustGearSpeedDialog.css.scss
@@ -0,0 +1,160 @@
+@import "client/common.css.scss";
+@charset "UTF-8";
+#adjust-gear-speed-dialog {
+ min-height: 420px;
+ max-height: 420px;
+ width:800px;
+
+ h2 {
+ color: #FFFFFF;
+ font-size: 15px;
+ font-weight: normal;
+ margin-bottom: 6px;
+ white-space:nowrap;
+
+ }
+
+ h2.settings {
+ display:inline;
+ position:absolute;
+ left:0;
+ margin-left:0; // override global .settings from sessions.css
+ }
+
+ .ftue-box {
+ background-color: #222222;
+ font-size: 13px;
+ padding: 8px;
+ }
+
+ .dialog-inner {
+ line-height: 1.3em;
+ width:800px;
+ padding:25px;
+ font-size:15px;
+ color:#aaa;
+ @include border_box_sizing;
+
+ }
+
+ .dialog-column {
+
+ @include border_box_sizing;
+
+ &:nth-of-type(1) {
+ width:75%;
+ margin-top:38px;
+ }
+
+ &:nth-of-type(2) {
+ width:25%;
+ float:right;
+ }
+ }
+
+ .speed-options {
+ display:inline-block;
+ width:60%;
+ margin:auto;
+ margin-left:25%;
+ }
+
+ .speed-option {
+ @include border_box_sizing;
+ float:left;
+ width:33%;
+ text-align:center;
+
+ .iradio_minimal {
+ margin:auto;
+ display:inline-block;
+ }
+
+ label {
+ display:inline-block;
+ margin-left: 10px;
+ position: relative;
+ top: -3px;
+ }
+ }
+
+ .speed-text {
+ margin:10px 0;
+ }
+
+ .run-test-btn {
+ margin-top:5px;
+ margin-left:25px;
+ left:40%;
+ position:relative;
+ }
+
+ .frame-and-buffers {
+ display:inline-block;
+ margin-left:30%;
+
+ .framesize {
+ width:auto;
+ display:inline-block;
+ }
+
+ .buffers {
+ width:auto;
+ display:inline-block;
+ margin-left:20px;
+ }
+ h2 {
+ display:inline-block;
+ line-height:18px;
+ vertical-align: top;
+ }
+
+ .easydropdown-wrapper {
+ display:inline-block;
+ top:-3px;
+ margin-left:8px;
+ }
+
+
+ .select-frame-size {
+ margin-left:-2px;
+ }
+ }
+
+ .help-text {
+ margin-bottom:20px;
+ }
+
+ .basic {
+ margin:0 5px 20px 0;
+ }
+ .advanced {
+ display:none;
+ border-width:1px 0 0 0;
+ border-style:solid;
+ border-color:white;
+ padding-top:20px;
+ margin:0 5px 0 0;
+ .help-text {
+ text-align:left;
+ }
+ }
+
+ .test-results-header {
+ position:static;
+ }
+
+
+ .ftue-box.results {
+ margin-top:20px;
+ height: 268px !important;
+ padding:0;
+ @include border_box_sizing;
+ }
+
+ h2.results-text-header {
+ margin-top:5px;
+ }
+
+
+}
diff --git a/web/app/assets/stylesheets/dialogs/jamtrackAvailability.css.scss b/web/app/assets/stylesheets/dialogs/jamtrackAvailability.css.scss
new file mode 100644
index 000000000..e69de29bb
diff --git a/web/app/controllers/api_jamtracks_controller.rb b/web/app/controllers/api_jamtracks_controller.rb
new file mode 100644
index 000000000..8bbc2c179
--- /dev/null
+++ b/web/app/controllers/api_jamtracks_controller.rb
@@ -0,0 +1,15 @@
+class ApiJamtracksController < ApiController
+
+ # have to be signed in currently to see this screen
+ before_filter :api_signed_in_user
+
+ respond_to :json
+
+ def index
+ data = JamTrack.index current_user, params
+
+ @jamtracks = data[0]
+ @next = data[1]
+ end
+
+end
diff --git a/web/app/controllers/api_recurly_controller.rb b/web/app/controllers/api_recurly_controller.rb
new file mode 100644
index 000000000..4c841320d
--- /dev/null
+++ b/web/app/controllers/api_recurly_controller.rb
@@ -0,0 +1,155 @@
+class ApiRecurlyController < ApiController
+
+ before_filter :api_signed_in_user
+ respond_to :json
+
+ # create Recurly account
+ def create_account
+ logger.debug(params[:billing_info])
+ if current_user.recurly_code.nil?
+ @account = Recurly::Account.create(
+ account_code: current_user.id,
+ email: current_user.email,
+ first_name: current_user.first_name,
+ last_name: current_user.last_name,
+ address: {
+ city: current_user.city,
+ state: current_user.state,
+ country: current_user.country
+ }
+ )
+ else
+ @account = Recurly::Account.find(current_user.recurly_code)
+ end
+
+ if @account.errors.any?
+ response.status = 404
+ else
+ current_user.recurly_code = @account.account_code
+ current_user.save
+
+ @account.billing_info = params[:billing_info]
+ @account.billing_info.save
+
+ logger.debug @account
+ end
+ respond_with @account
+
+ rescue Recurly::Error, NoMethodError => e
+ render :json => { :message => e.inspect }, :status => 404
+ end
+
+ # get Recurly account
+ def get_account
+ @account = Recurly::Account.find(current_user.reculry_code)
+ respond_with @account
+ rescue Recurly::Error, NoMethodError => e
+ render :json => { message: ValidationMessages::RECURLY_ERROR}, :status => 404
+ end
+
+ # update Recurly account
+ def update_account
+ if current_user.recurly_code.nil?
+ @account = Recurly::Account.create(
+ account_code: current_user.id,
+ email: current_user.email,
+ first_name: current_user.first_name,
+ last_name: current_user.last_name,
+ address: {
+ city: current_user.city,
+ state: current_user.state,
+ country: current_user.country
+ }
+ )
+ else
+ @account = Recurly::Account.get(current_user.recurly_code)
+ end
+
+ @account.first_name = current_user.first_name
+ @account.last_name = current_user.last_name
+ @account.email = current_user.email
+ @account.update
+
+ if @account.errors.any?
+ response.status = 404
+ else
+ current_user.recurly_code = @account.account_code
+ current_user.save
+ end
+ respond_with @account
+
+ rescue Recurly::Error, NoMethodError => e
+ render :json => { message: ValidationMessages::RECURLY_ERROR}, :status => 404
+ end
+
+ # get subscription
+ def get_subscription
+ @account = Recurly::Acount.find(current_user.reculry_code)
+ respond_with @account.subscriptions.last
+ rescue Recurly::Error, NoMethodError => e
+ render :json => { message: ValidationMessages::RECURLY_GET_ACCOUNT_ERROR}, :status => 404
+ end
+
+ # create subscription
+ def create_subscription
+ end
+
+ # get Billing Information
+ def billing_info
+ if current_user.recurly_code.nil?
+ render :json => { message: ValidationMessages::RECURLY_ACCOUNT_ERROR }, :status => 404 and return
+ else
+ @account = Recurly::Account.find(current_user.recurly_code)
+ logger.debug @account
+ respond_with @account.billing_info
+ end
+ rescue Recurly::Error, NoMethodError => e
+ render :json => { message: ValidationMessages::RECURLY_ERROR}, :status => 404
+ end
+
+ # update Billing Information
+ def update_billing_info
+ if current_user.recurly_code.nil?
+ render :json => { message: ValidationMessages::RECURLY_ACCOUNT_ERROR }, :status => 404 and return
+ else
+ if params[:first_name].blank? or params[:last_name].blank? or params[:number].blank? or params[:year].blank? or params[:month].blank? or params[:verification_value].blank?
+ render :json => { message: ValidationMessages::RECURLY_PARAMETER_ERROR }, :status => 404 and return
+ end
+ @account = Recurly::Acount.find(current_user.reculry_code)
+ @account.billing_info = params
+ @account.billing_info.save
+
+ if @account.erros.any?
+ response.status = :unprocessable_entity
+ end
+
+ respond_with @account
+ end
+ rescue Recurly::Error, NoMethodError => e
+ render :json => { message: ValidationMessages::RECURLY_ERROR}, :status => 404
+ end
+
+ def place_order
+ if current_user.recurly_code.nil?
+ render :json => { message: ValidationMessages::RECURLY_ACCOUNT_ERROR }, :status => 404 and return
+ else
+ if params[:first_name].blank? or params[:last_name].blank? or params[:number].blank? or params[:year].blank? or params[:month].blank? or params[:verification_value].blank?
+ render :json => { message: ValidationMessages::RECURLY_PARAMETER_ERROR }, :status => 404 and return
+ end
+ @account = Recurly::Account.find(current_user.recurly_code)
+ @account.billing_info = params
+ @account.billing_info.save
+
+ # create subscription.
+
+ if @account.erros.any?
+ response.status = :unprocessable_entity
+ end
+
+ respond_with @account
+ end
+ rescue Recurly::Error, NoMethodError => e
+ render :json => { message: ValidationMessages::RECURLY_ERROR}, :status => 404
+ end
+
+end
\ No newline at end of file
diff --git a/web/app/controllers/api_shopping_carts_controller.rb b/web/app/controllers/api_shopping_carts_controller.rb
new file mode 100644
index 000000000..d1109e821
--- /dev/null
+++ b/web/app/controllers/api_shopping_carts_controller.rb
@@ -0,0 +1,53 @@
+class ApiShoppingCartsController < ApiController
+
+ before_filter :api_signed_in_user
+
+ respond_to :json
+
+ def index
+ @carts = current_user.shopping_carts
+ end
+
+ def add_jamtrack
+ jam_track = JamTrack.find_by_id(params[:id])
+
+ # verify JamTrack exists
+ if jam_track.nil?
+ raise StateError, "Invalid JamTrack."
+ end
+
+ @cart = ShoppingCart.create current_user, jam_track
+
+ if @cart.errors.any?
+ response.status = :unprocessable_entity
+ respond_with @cart
+ else
+ respond_with @cart, responder: ApiResponder, :statue => 201
+ end
+ end
+
+ def update_cart
+ @cart = ShoppingCart.find_by_id params[:id]
+
+ #verify Cart exists
+ raise StateError, "Invalid Cart." if @cart.nil?
+
+ @cart.quantity = params[:quantity]
+
+ if @cart.errors.any?
+ response.statue = :unprocessable_entity
+ respond_with @cart
+ else
+ respond_with @cart, responder: ApiResponder, :status => 200
+ end
+ end
+
+ def remove_cart
+ @cart = current_user.shopping_carts.find_by_id(params[:id])
+ raise StateError, "Invalid Cart." if @cart.nil?
+
+ @cart.destroy
+ respond_with responder: ApiResponder, :status => 204
+ end
+
+end
diff --git a/web/app/controllers/api_users_controller.rb b/web/app/controllers/api_users_controller.rb
index d9b473307..e475a4a70 100644
--- a/web/app/controllers/api_users_controller.rb
+++ b/web/app/controllers/api_users_controller.rb
@@ -1,7 +1,7 @@
require 'sanitize'
class ApiUsersController < ApiController
- before_filter :api_signed_in_user, :except => [:create, :show, :signup_confirm, :auth_session_create, :complete, :finalize_update_email, :isp_scoring, :add_play]
+ before_filter :api_signed_in_user, :except => [:create, :show, :signup_confirm, :auth_session_create, :complete, :finalize_update_email, :isp_scoring, :add_play, :crash_dump]
before_filter :auth_user, :only => [:session_settings_show, :session_history_index, :session_user_history_index, :update, :delete,
:liking_create, :liking_destroy, # likes
:following_create, :following_show, :following_destroy, # followings
diff --git a/web/app/helpers/music_session_helper.rb b/web/app/helpers/music_session_helper.rb
index b550e70f3..03001dc0d 100644
--- a/web/app/helpers/music_session_helper.rb
+++ b/web/app/helpers/music_session_helper.rb
@@ -25,20 +25,27 @@ module MusicSessionHelper
else
unique_users = music_session.unique_users
if sharer && unique_users.exists?(sharer)
- "LIVE SESSION: #{sharer.name}#{additional_member_count(unique_users)}"
+ "LIVE SESSION: #{sharer.name}#{additional_member_count(unique_users, sharer)}"
else
- "LIVE SESSION: #{music_session.creator.name}#{additional_member_count(unique_users)}"
+ "LIVE SESSION: #{music_session.creator.name}#{additional_member_count(unique_users, music_session.creator)}"
end
end
end
- def additional_member_count(unique_users)
+ def additional_member_count(unique_users, target_user)
length = unique_users.length
if length < 2
""
else
- " & #{length} OTHERS"
+ other_length = length - 1
+ if other_length == 1
+ other_user_in_array = unique_users - [target_user]
+ other_user = other_user_in_array[0]
+ " & #{other_user.name}"
+ else
+ " & #{length - 1} OTHERS"
+ end
end
end
diff --git a/web/app/helpers/recording_helper.rb b/web/app/helpers/recording_helper.rb
index cbad1805c..3d8a6eeec 100644
--- a/web/app/helpers/recording_helper.rb
+++ b/web/app/helpers/recording_helper.rb
@@ -23,22 +23,29 @@ module RecordingHelper
if claimed_recording.recording.band
"RECORDING: #{claimed_recording.recording.band.name}"
else
- unique_users = claimed_recording.recording.users
- if sharer && unique_users.exists?(sharer)
- "RECORDING: #{sharer.name}#{additional_member_count(unique_users)}"
+ unique_users = claimed_recording.recording.users.uniq
+ if sharer && unique_users.include?(sharer)
+ "RECORDING: #{sharer.name}#{additional_member_count(unique_users, sharer)}"
else
- "RECORDING: #{claimed_recording.user.name}#{additional_member_count(unique_users)}"
+ "RECORDING: #{claimed_recording.user.name}#{additional_member_count(unique_users, claimed_recording.user)}"
end
end
end
- def additional_member_count(unique_users)
+ def additional_member_count(unique_users, target_user)
length = unique_users.length
if length < 2
""
else
- " & #{length} OTHERS"
+ other_length = length - 1
+ if other_length == 1
+ other_user_in_array = unique_users - [target_user]
+ other_user = other_user_in_array[0]
+ " & #{other_user.name}"
+ else
+ " & #{length - 1} OTHERS"
+ end
end
end
diff --git a/web/app/views/api_jamtracks/index.rabl b/web/app/views/api_jamtracks/index.rabl
new file mode 100644
index 000000000..25c3f9da5
--- /dev/null
+++ b/web/app/views/api_jamtracks/index.rabl
@@ -0,0 +1,7 @@
+node :next do |page|
+ @next
+end
+
+node :jamtracks do |page|
+ partial "api_jamtracks/show", object: @jamtracks
+end
\ No newline at end of file
diff --git a/web/app/views/api_jamtracks/show.rabl b/web/app/views/api_jamtracks/show.rabl
new file mode 100644
index 000000000..dde7bb3dd
--- /dev/null
+++ b/web/app/views/api_jamtracks/show.rabl
@@ -0,0 +1,19 @@
+object @jamtrack
+
+attributes :id, :name, :description, :recording_type, :original_artist, :songwriter, :publisher, :sales_region, :price
+
+node :genres do |item|
+ [item.genre.description] # XXX: need to return single genre; not array
+end
+
+node :added_cart do |item|
+ current_user.shopping_carts.map(&:cart_id).include? item.id
+end
+
+child(:jam_track_tracks => :tracks) {
+ attributes :id, :part, :instrument
+}
+
+child(:licensor => :licensor) {
+ attributes :id, :name
+}
\ No newline at end of file
diff --git a/web/app/views/api_shopping_carts/add_jamtrack.rabl b/web/app/views/api_shopping_carts/add_jamtrack.rabl
new file mode 100644
index 000000000..c61e7b52b
--- /dev/null
+++ b/web/app/views/api_shopping_carts/add_jamtrack.rabl
@@ -0,0 +1 @@
+extends "api_shopping_carts/show"
\ No newline at end of file
diff --git a/web/app/views/api_shopping_carts/index.rabl b/web/app/views/api_shopping_carts/index.rabl
new file mode 100644
index 000000000..bc63bab24
--- /dev/null
+++ b/web/app/views/api_shopping_carts/index.rabl
@@ -0,0 +1,3 @@
+object @carts
+
+extends "api_shopping_carts/show"
\ No newline at end of file
diff --git a/web/app/views/api_shopping_carts/show.rabl b/web/app/views/api_shopping_carts/show.rabl
new file mode 100644
index 000000000..e371ba4c9
--- /dev/null
+++ b/web/app/views/api_shopping_carts/show.rabl
@@ -0,0 +1,3 @@
+object @cart
+
+attributes :id, :quantity, :cart_type, :product_info
\ No newline at end of file
diff --git a/web/app/views/api_users/show.rabl b/web/app/views/api_users/show.rabl
index 59d72f304..aa1030f48 100644
--- a/web/app/views/api_users/show.rabl
+++ b/web/app/views/api_users/show.rabl
@@ -21,6 +21,10 @@ if @user == current_user
user.mods_json
end
+ node :has_recurly_account do
+ @user.recurly_code == @user.id
+ end
+
elsif current_user
node :is_friend do |uu|
current_user.friends?(@user)
@@ -85,4 +89,4 @@ end
node :last_jam_audio_latency do |user|
user.last_jam_audio_latency.round if user.last_jam_audio_latency
-end
\ No newline at end of file
+end
diff --git a/web/app/views/clients/_header.html.erb b/web/app/views/clients/_header.html.erb
index 5195512ff..bad8f5229 100644
--- a/web/app/views/clients/_header.html.erb
+++ b/web/app/views/clients/_header.html.erb
@@ -11,6 +11,11 @@
<%= render "users/user_dropdown" %>
+
+
+
-
+
+