From afa21de689d61393a9c216b30147fcbe92652209 Mon Sep 17 00:00:00 2001 From: Scott Comer Date: Sat, 31 May 2014 15:22:21 -0500 Subject: [PATCH 01/32] make score info available to template and make visible on the screen --- web/app/assets/javascripts/findMusician.js | 32 ++++++++++++++++++++-- web/app/views/clients/_musicians.html.erb | 17 ++++++------ 2 files changed, 39 insertions(+), 10 deletions(-) diff --git a/web/app/assets/javascripts/findMusician.js b/web/app/assets/javascripts/findMusician.js index 38bf79976..f29dc9afb 100644 --- a/web/app/assets/javascripts/findMusician.js +++ b/web/app/assets/javascripts/findMusician.js @@ -78,6 +78,30 @@ } } + function score_to_text(score) { + // these are raw scores as reported by client (round trip times) + if (score == null) return "n/a"; + return Math.round(score / 2) + " ms"; + } + + function score_to_color(score) { + // these are raw scores as reported by client (round trip times) + if (score == null) return "purple"; + if (0 < score && score <= 40) return "green"; + if (40 < score && score <= 80) return "yellow"; + if (80 < score && score <= 120) return "red"; + return "blue"; + } + + function score_to_color_alt(score) { + // these are raw scores as reported by client (round trip times) + if (score == null) return "missing"; + if (0 < score && score <= 40) return "good"; + if (40 < score && score <= 80) return "moderate"; + if (80 < score && score <= 120) return "poor"; + return "unacceptable"; + } + function renderMusicians() { var ii, len; var mTemplate = $('#template-find-musician-row').html(); @@ -108,7 +132,7 @@ user_id: aFollow.user_id, musician_name: aFollow.name, profile_url: '/client#/profile/' + aFollow.user_id, - avatar_url: context.JK.resolveAvatarUrl(aFollow.photo_url), + avatar_url: context.JK.resolveAvatarUrl(aFollow.photo_url) }; follows += context.JK.fillTemplate(fTemplate, followVals); if (2 == jj) break; @@ -125,6 +149,7 @@ }; var musician_actions = context.JK.fillTemplate(aTemplate, actionVals); + var joined_score = musician['joined_score'] mVals = { avatar_url: context.JK.resolveAvatarUrl(musician.photo_url), profile_url: "/client#/profile/" + musician.id, @@ -138,7 +163,10 @@ session_count: musician['session_count'], musician_id: musician['id'], musician_follow_template: follows, - musician_action_template: musician_actions + musician_action_template: musician_actions, + musician_one_way_score: score_to_text(joined_score), + musician_score_color: score_to_color(joined_score), + musician_score_color_alt: score_to_color_alt(joined_score) }; var musician_row = context.JK.fillTemplate(mTemplate, mVals); renderings += musician_row; diff --git a/web/app/views/clients/_musicians.html.erb b/web/app/views/clients/_musicians.html.erb index 599710a6c..47da7d626 100644 --- a/web/app/views/clients/_musicians.html.erb +++ b/web/app/views/clients/_musicians.html.erb @@ -32,12 +32,12 @@
FOLLOWING:
@@ -58,10 +58,11 @@
- {friend_count} - {follow_count} - {recording_count} - {session_count} + {friend_count} friends + {follow_count} follows + {recording_count} recordings + {session_count} sessions + {musician_one_way_score} {musician_score_color_alt} score
{musician_action_template} From c0d08ebe27b7b84c84ac73e83885184fbc080e77 Mon Sep 17 00:00:00 2001 From: Scott Comer Date: Sat, 31 May 2014 18:07:25 -0500 Subject: [PATCH 02/32] scores integrated into display where i could, not exactly where david wanted. this finishes vrfs-1455 --- ruby/lib/jam_ruby/models/search.rb | 4 ++-- ruby/lib/jam_ruby/models/user.rb | 3 ++- web/app/assets/stylesheets/client/band.css.scss | 4 ++-- web/app/assets/stylesheets/client/profile.css.scss | 4 ++-- web/app/controllers/api_search_controller.rb | 1 + 5 files changed, 9 insertions(+), 7 deletions(-) diff --git a/ruby/lib/jam_ruby/models/search.rb b/ruby/lib/jam_ruby/models/search.rb index acb82fdc0..88000f782 100644 --- a/ruby/lib/jam_ruby/models/search.rb +++ b/ruby/lib/jam_ruby/models/search.rb @@ -179,7 +179,7 @@ module JamRuby unless locidispid.nil? # score_join of left allows for null scores, whereas score_join of inner requires a score however good or bad # this is ANY_SCORE: - score_join = 'left' # or 'inner' + score_join = 'left outer' # or 'inner' score_min = nil score_max = nil case score_limit @@ -214,7 +214,7 @@ module JamRuby end rel = rel.joins("#{score_join} join scores on scores.alocidispid = users.last_jam_locidispid") - .where(['scores.blocidispid = ?', locidispid]) + .where(['(scores.blocidispid = ? or scores.blocidispid is null)', locidispid]) rel = rel.where(['scores.score > ?', score_min]) unless score_min.nil? rel = rel.where(['scores.score <= ?', score_max]) unless score_max.nil? diff --git a/ruby/lib/jam_ruby/models/user.rb b/ruby/lib/jam_ruby/models/user.rb index 59b34c405..44712bbb7 100644 --- a/ruby/lib/jam_ruby/models/user.rb +++ b/ruby/lib/jam_ruby/models/user.rb @@ -292,7 +292,8 @@ module JamRuby def joined_score nil unless has_attribute?(:score) - read_attribute(:score).to_i + a = read_attribute(:score) + a.nil? ? nil : a.to_i end # mods comes back as text; so give ourselves a parsed version diff --git a/web/app/assets/stylesheets/client/band.css.scss b/web/app/assets/stylesheets/client/band.css.scss index 25f45e9a2..4da4130d3 100644 --- a/web/app/assets/stylesheets/client/band.css.scss +++ b/web/app/assets/stylesheets/client/band.css.scss @@ -162,11 +162,11 @@ } } .lcol { - width: 148px; + width: 200px; } .whitespace { // equal to lcol width. - padding-left: 148px; + padding-left: 200px; } .instruments { width:128px; diff --git a/web/app/assets/stylesheets/client/profile.css.scss b/web/app/assets/stylesheets/client/profile.css.scss index f1c207801..746f783e3 100644 --- a/web/app/assets/stylesheets/client/profile.css.scss +++ b/web/app/assets/stylesheets/client/profile.css.scss @@ -231,11 +231,11 @@ } } .lcol { - width: 148px; + width: 200px; } .whitespace { // equal to lcol width. - padding-left: 148px; + padding-left: 200px; } .instruments { width:128px; diff --git a/web/app/controllers/api_search_controller.rb b/web/app/controllers/api_search_controller.rb index 468371a79..3d57aeffc 100644 --- a/web/app/controllers/api_search_controller.rb +++ b/web/app/controllers/api_search_controller.rb @@ -15,6 +15,7 @@ class ApiSearchController < ApiController conn = (clientid ? Connection.where(client_id: clientid, user_id: current_user.id).first : nil) # puts "================== query #{query.inspect}" @search = Search.musician_filter(query, current_user, conn) + # puts "================== search #{@search.inspect}" else @search = Search.band_filter(query, current_user) end From 477fe5994a178ef91aa0bc9ae692acec8289ed33 Mon Sep 17 00:00:00 2001 From: Anthony Davis Date: Sat, 31 May 2014 20:38:39 -0500 Subject: [PATCH 03/32] VRFS-1103 - ruby coverage is now logged during web tests --- web/.simplecov | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/web/.simplecov b/web/.simplecov index 1091e3c7a..f7df5462a 100644 --- a/web/.simplecov +++ b/web/.simplecov @@ -10,14 +10,23 @@ if ENV['COVERAGE'] == "1" SimpleCov.formatter = SimpleCov::Formatter::MergedFormatter - SimpleCov.start do - add_filter "/test/" - add_filter "/bin/" - add_filter "/scripts/" - add_filter "/tmp/" - add_filter "/vendor/" - add_filter "/spec/" - add_filter "/features/" + SimpleCov.start :rails do + # remove the :root_filter so that we can see coverage of external dependencies (i.e., jam_ruby) + filters.clear + + # ignore Ruby itself (...not to be confused with jam_ruby) + add_filter "/lib/ruby/" + + # ignore Rails subfolders which don't contain app code + %w{config coverage db doc features log script spec test tmp}.each do |dir| + add_filter "#{dir}/" + end + + # ignore all gem code except our jam gems + add_filter {|src| src.filename =~ /ruby.*\/gems\// unless src.filename =~ /\/gems\/jam/ } + + # categorize JamRuby in the coverage report: + add_group 'Jam Ruby', 'jam_ruby' end all_files = Dir['**/*.rb'] From 1d19bf613cb48184cbb5a67fd43b9c4daafe2a91 Mon Sep 17 00:00:00 2001 From: Scott Comer Date: Sun, 1 Jun 2014 16:03:32 -0500 Subject: [PATCH 04/32] removed users isp field, use last_jam_locidispid%1000000 instead; added fields for state and country codes. --- db/manifest | 1 + db/up/fix_users_location_fields.sql | 5 +++++ 2 files changed, 6 insertions(+) create mode 100644 db/up/fix_users_location_fields.sql diff --git a/db/manifest b/db/manifest index f10027f6e..fdd6e190c 100755 --- a/db/manifest +++ b/db/manifest @@ -160,3 +160,4 @@ periodic_emails.sql remember_extra_scoring_data.sql indexing_for_regions.sql latency_tester.sql +fix_users_location_fields.sql diff --git a/db/up/fix_users_location_fields.sql b/db/up/fix_users_location_fields.sql new file mode 100644 index 000000000..04c822a5c --- /dev/null +++ b/db/up/fix_users_location_fields.sql @@ -0,0 +1,5 @@ +ALTER TABLE users DROP COLUMN addr; +ALTER TABLE users DROP COLUMN locidispid; +ALTER TABLE users DROP COLUMN internet_service_provider; +ALTER TABLE users ADD COLUMN statecode varchar(2); +ALTER TABLE users ADD COLUMN countrycode varchar(2); From 84ed2949f6795fef1e6ae25523faf788e2ce0778 Mon Sep 17 00:00:00 2001 From: Jonathan Kolyer Date: Tue, 3 Jun 2014 04:21:00 +0000 Subject: [PATCH 05/32] VRFS-736 refactoring tests --- .../models/email_batch_progression.rb | 1 + ruby/spec/jam_ruby/models/email_batch_spec.rb | 36 ++++++++----------- 2 files changed, 16 insertions(+), 21 deletions(-) diff --git a/ruby/lib/jam_ruby/models/email_batch_progression.rb b/ruby/lib/jam_ruby/models/email_batch_progression.rb index cce4a9f5c..51aa4957b 100644 --- a/ruby/lib/jam_ruby/models/email_batch_progression.rb +++ b/ruby/lib/jam_ruby/models/email_batch_progression.rb @@ -4,6 +4,7 @@ module JamRuby BATCH_SIZE = 500 SINCE_WEEKS = 2 + NUM_IDX = 3 SUBTYPES = [:client_notdl, # Registered Musician Has Not Downloaded Client :client_dl_notrun, # Registered Musician Has Downloaded Client But Not Yet Run It diff --git a/ruby/spec/jam_ruby/models/email_batch_spec.rb b/ruby/spec/jam_ruby/models/email_batch_spec.rb index 8cac2b44c..d18e413aa 100644 --- a/ruby/spec/jam_ruby/models/email_batch_spec.rb +++ b/ruby/spec/jam_ruby/models/email_batch_spec.rb @@ -61,28 +61,22 @@ describe EmailBatch do # before { pending } def handles_new_users(ebatch, user) - 3.times { |nn| expect(ebatch.fetch_recipients(nn).count).to eq(0) } + EmailBatchProgression::NUM_IDX.times { |nn| expect(ebatch.fetch_recipients(nn).count).to eq(0) } - dd = user.created_at + ebatch.days_past_for_trigger_index(0).days - Timecop.travel(dd) - vals = [1,0,0] - 3.times { |nn| expect(ebatch.fetch_recipients(nn).count).to eq(vals[nn]) } - ebatch.make_set(user, 0) - 3.times { |nn| expect(ebatch.fetch_recipients(nn).count).to eq(0) } - - dd = dd + ebatch.days_past_for_trigger_index(1).days - Timecop.travel(dd) - vals = [0,1,0] - 3.times { |nn| expect(ebatch.fetch_recipients(nn).count).to eq(vals[nn]) } - ebatch.make_set(user, 1) - 3.times { |nn| expect(ebatch.fetch_recipients(nn).count).to eq(0) } - - dd = dd + ebatch.days_past_for_trigger_index(2).days - Timecop.travel(dd) - vals = [0,0,1] - 3.times { |nn| expect(ebatch.fetch_recipients(nn).count).to eq(vals[nn]) } - ebatch.make_set(user, 2) - 3.times { |nn| expect(ebatch.fetch_recipients(nn).count).to eq(0) } + dd = user.created_at + EmailBatchProgression::NUM_IDX.times do |idx| + dd = dd + ebatch.days_past_for_trigger_index(idx).days + Timecop.travel(dd) + vals = Array.new(3,0) + vals[idx] = 1 + EmailBatchProgression::NUM_IDX.times do |nn| + expect(ebatch.fetch_recipients(nn).count).to eq(vals[nn]) + end + ebatch.make_set(user, idx) + EmailBatchProgression::NUM_IDX.times do |nn| + expect(ebatch.fetch_recipients(nn).count).to eq(0) + end + end end def handles_existing_users(ebatch, user) From a94c5b325b56f661c4f75ca1e02e0a497011c38e Mon Sep 17 00:00:00 2001 From: Seth Call Date: Tue, 3 Jun 2014 16:19:39 -0500 Subject: [PATCH 06/32] * VRFS-1575 - reworking loopback --- .../javascripts/accounts_audio_profile.js | 30 +- web/app/assets/javascripts/utils.js | 105 ---- .../javascripts/wizard/frame_buffers.js | 117 ++++ .../javascripts/wizard/gear/gear_wizard.js | 7 +- .../wizard/gear/step_configure_voice_chat.js | 5 +- .../wizard/gear/step_select_gear.js | 545 ++++-------------- .../assets/javascripts/wizard/gear_test.js | 457 +++++++++++++++ .../assets/javascripts/wizard/gear_utils.js | 172 ++++++ .../wizard/loopback/loopback_wizard.js | 21 +- .../wizard/loopback/step_loopback_intro.js | 37 ++ .../wizard/loopback/step_loopback_result.js | 22 + .../wizard/loopback/step_loopback_test.js | 373 ++++++++++++ web/app/assets/javascripts/wizard/wizard.js | 16 +- web/app/assets/stylesheets/client/client.css | 3 + .../client/wizard/framebuffers.css.scss | 40 ++ .../client/wizard/gearResults.css.scss | 160 +++++ .../client/wizard/gearWizard.css.scss | 171 ------ .../client/wizard/loopbackWizard.css.scss | 148 +++++ .../clients/_account_audio_profile.html.erb | 3 + web/app/views/clients/_help.html.erb | 4 + web/app/views/clients/gear/_buttons.html.haml | 20 - web/app/views/clients/index.html.erb | 15 +- .../views/clients/wizard/_buttons.html.haml | 10 + .../clients/wizard/_framebuffers.html.haml | 33 ++ .../views/clients/wizard/_gear_test.html.haml | 41 ++ .../{ => wizard}/gear/_gear_wizard.html.haml | 71 +-- .../loopback/_loopback_wizard.html.haml | 81 +++ 27 files changed, 1890 insertions(+), 817 deletions(-) create mode 100644 web/app/assets/javascripts/wizard/frame_buffers.js create mode 100644 web/app/assets/javascripts/wizard/gear_test.js create mode 100644 web/app/assets/javascripts/wizard/gear_utils.js create mode 100644 web/app/assets/javascripts/wizard/loopback/step_loopback_intro.js create mode 100644 web/app/assets/javascripts/wizard/loopback/step_loopback_result.js create mode 100644 web/app/assets/javascripts/wizard/loopback/step_loopback_test.js create mode 100644 web/app/assets/stylesheets/client/wizard/framebuffers.css.scss create mode 100644 web/app/assets/stylesheets/client/wizard/gearResults.css.scss create mode 100644 web/app/assets/stylesheets/client/wizard/loopbackWizard.css.scss delete mode 100644 web/app/views/clients/gear/_buttons.html.haml create mode 100644 web/app/views/clients/wizard/_buttons.html.haml create mode 100644 web/app/views/clients/wizard/_framebuffers.html.haml create mode 100644 web/app/views/clients/wizard/_gear_test.html.haml rename web/app/views/clients/{ => wizard}/gear/_gear_wizard.html.haml (79%) create mode 100644 web/app/views/clients/wizard/loopback/_loopback_wizard.html.haml diff --git a/web/app/assets/javascripts/accounts_audio_profile.js b/web/app/assets/javascripts/accounts_audio_profile.js index 3b1b7ee60..26a6e36a1 100644 --- a/web/app/assets/javascripts/accounts_audio_profile.js +++ b/web/app/assets/javascripts/accounts_audio_profile.js @@ -56,7 +56,7 @@ } } - var template = context._.template($('#template-account-audio').html(), {profiles: profiles}, {variable: 'data'}); + var template = context._.template($('#template-account-audio').html(), {is_admin: context.JK.currentUserAdmin, profiles: profiles}, {variable: 'data'}); appendAudio(template); } @@ -77,6 +77,20 @@ populateAccountAudio(); } + function handleLoopbackAudioProfile(audioProfileId) { + + if(audioProfileId != context.jamClient.FTUEGetMusicProfileName()) { + var result = context.jamClient.FTUELoadAudioConfiguration(audioProfileId); + + if(!result) { + logger.error("unable to activate audio configuration: " + audioProfileId); + context.JK.alertSupportedNeeded("Unable to activate audio configuration for profile named: " + audioProfileId); + } + } + + app.layout.showDialog('loopback-wizard'); + } + function handleActivateAudioProfile(audioProfileId) { logger.debug("activating audio profile: " + audioProfileId); @@ -143,6 +157,20 @@ return false; }); + + $root.on('click', 'a[data-purpose=loopback-audio-profile]', function (evt) { + evt.stopPropagation(); + var $btn = $(this); + var status = $btn.closest('tr').attr('data-status'); + if(status == "good") { + handleLoopbackAudioProfile($btn.attr('data-id')); + } + else { + context.JK.Banner.showAlert("Unable to loopback test this profile. Please verify that the devices associated are connected."); + } + return false; + }); + $root.on('click', 'a[data-purpose=add-profile]', function (evt) { evt.stopPropagation(); handleStartAudioQualification(); diff --git a/web/app/assets/javascripts/utils.js b/web/app/assets/javascripts/utils.js index 7e482aeb9..78f373a60 100644 --- a/web/app/assets/javascripts/utils.js +++ b/web/app/assets/javascripts/utils.js @@ -568,111 +568,6 @@ return keys; }; - context.JK.createProfileName = function(deviceInfo, chatName) { - var isSameInOut = deviceInfo.input.id == deviceInfo.output.id; - - var name = null; - if(isSameInOut) { - name = "In/Out: " + deviceInfo.input.info.displayName; - } - else { - name = "In: " + deviceInfo.input.info.displayName + ", Out: " + deviceInfo.output.info.displayName - } - - logger.debug("creating profile name: " + name); - return name; - } - - context.JK.selectedDeviceInfo = function(audioInputDeviceId, audioOutputDeviceId, deviceInformation) { - - if(!deviceInformation) { - deviceInformation = context.JK.loadDeviceInfo(); - } - - var input = deviceInformation[audioInputDeviceId]; - var output = deviceInformation[audioOutputDeviceId]; - - var inputBehavior = AUDIO_DEVICE_BEHAVIOR[input.type]; - var outputBehavior = AUDIO_DEVICE_BEHAVIOR[output.type]; - - return { - input: { - id: audioInputDeviceId, - info: input, - behavior: inputBehavior - }, - output: { - id: audioOutputDeviceId, - info: output, - behavior: outputBehavior - } - } - } - - context.JK.loadDeviceInfo = function() { - - var operatingSystem = context.JK.GetOSAsString(); - // should return one of: - // * MacOSX_builtin - // * MACOSX_interface - // * Win32_wdm - // * Win32_asio - // * Win32_asio4all - // * Linux - function determineDeviceType(deviceId, displayName) { - if (operatingSystem == "MacOSX") { - if (displayName.toLowerCase().trim() == "built-in") { - return "MacOSX_builtin"; - } - else { - return "MacOSX_interface"; - } - } - else if (operatingSystem == "Win32") { - if (context.jamClient.FTUEIsMusicDeviceWDM(deviceId)) { - return "Win32_wdm"; - } - else if (displayName.toLowerCase().indexOf("asio4all") > -1) { - return "Win32_asio4all" - } - else { - return "Win32_asio"; - } - } - else { - return "Linux"; - } - } - - var devices = context.jamClient.FTUEGetAudioDevices(); - logger.debug("FTUEGetAudioDevices: " + JSON.stringify(devices)); - - var loadedDevices = {}; - - // augment these devices by determining their type - context._.each(devices.devices, function (device) { - - if (device.name == "JamKazam Virtual Monitor") { - return; - } - - var deviceInfo = {}; - - deviceInfo.id = device.guid; - deviceInfo.type = determineDeviceType(device.guid, device.display_name); - deviceInfo.displayType = AUDIO_DEVICE_BEHAVIOR[deviceInfo.type].display; - deviceInfo.displayName = device.display_name; - deviceInfo.inputCount = device.input_count; - deviceInfo.outputCount = device.output_count; - - loadedDevices[device.guid] = deviceInfo; - }) - - logger.debug(context.JK.dlen(loadedDevices) + " devices loaded.", loadedDevices); - - return loadedDevices; - } - /** * Finds the first error associated with the field. * @param fieldName the name of the field diff --git a/web/app/assets/javascripts/wizard/frame_buffers.js b/web/app/assets/javascripts/wizard/frame_buffers.js new file mode 100644 index 000000000..f96dde085 --- /dev/null +++ b/web/app/assets/javascripts/wizard/frame_buffers.js @@ -0,0 +1,117 @@ +(function (context, $) { + + "use strict"; + + context.JK = context.JK || {}; + context.JK.FrameBuffers = function (app) { + + var $knobs = null; + var $bufferIn = null; + var $bufferOut = null; + var $frameSize = null; + var $self = $(this); + + 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'; + + function selectedFramesize() { + return parseFloat($frameSize.val()); + } + + function selectedBufferIn() { + return parseFloat($bufferIn.val()); + } + + function selectedBufferOut() { + return parseFloat($bufferOut.val()); + } + + function setFramesize(value) { + context.JK.dropdown($frameSize.val(value).easyDropDown('select', value.toString(), true)) + } + + function setBufferIn(value) { + context.JK.dropdown($bufferIn.val(value).easyDropDown('select', value.toString(), true)); + } + + function setBufferOut(value) { + context.JK.dropdown($bufferOut.val(value).easyDropDown('select', value.toString(), true)) + } + + function render() { + context.JK.dropdown($frameSize); + context.JK.dropdown($bufferIn); + context.JK.dropdown($bufferOut); + } + + function disable() { + $frameSize.attr("disabled", "disabled").easyDropDown('disable'); + $bufferIn.attr("disabled", "disabled").easyDropDown('disable'); + $bufferOut.attr("disabled", "disabled").easyDropDown('disable'); + } + + function enable() { + $frameSize.removeAttr("disabled").easyDropDown('enable'); + $bufferIn.removeAttr("disabled").easyDropDown('enable'); + $bufferOut.removeAttr("disabled").easyDropDown('enable'); + } + + function resetValues() { + $frameSize.val('2.5'); + $bufferIn.val('0'); + $bufferOut.val('0'); + } + + function events() { + $frameSize.unbind('change').change(function () { + $self.triggerHandler(FRAMESIZE_CHANGED, {value: selectedFramesize()}); + }); + + $bufferIn.unbind('change').change(function () { + logger.debug("buffer-in changed: " + selectedBufferIn()); + $self.triggerHandler(BUFFER_IN_CHANGED, {value: selectedBufferIn()}); + }); + + $bufferOut.unbind('change').change(function () { + logger.debug("buffer-out changed: " + selectedBufferOut()); + $self.triggerHandler(BUFFER_OUT_CHANGED, {value: selectedBufferOut()}); + }); + } + + function initialize(_$knobs) { + + $knobs = _$knobs; + if(!$knobs.is('.frame-and-buffers')) { + throw "$knobs != .frame-and-buffers" + } + + $bufferIn = $knobs.find('.select-buffer-in'); + $bufferOut = $knobs.find('.select-buffer-out'); + $frameSize = $knobs.find('.select-frame-size'); + + events(); + render(); + } + + this.FRAMESIZE_CHANGED = FRAMESIZE_CHANGED; + this.BUFFER_IN_CHANGED = BUFFER_IN_CHANGED; + this.BUFFER_OUT_CHANGED = BUFFER_OUT_CHANGED; + this.initialize = initialize; + this.selectedFramesize = selectedFramesize; + this.selectedBufferIn = selectedBufferIn; + this.selectedBufferOut = selectedBufferOut; + this.setFramesize = setFramesize; + this.setBufferIn = setBufferIn; + this.setBufferOut = setBufferOut; + this.render = render; + this.enable = enable; + this.disable = disable; + this.resetValues = resetValues; + + + return this; + } + + +})(window, jQuery); \ No newline at end of file diff --git a/web/app/assets/javascripts/wizard/gear/gear_wizard.js b/web/app/assets/javascripts/wizard/gear/gear_wizard.js index 69bbc9026..c2b5f3101 100644 --- a/web/app/assets/javascripts/wizard/gear/gear_wizard.js +++ b/web/app/assets/javascripts/wizard/gear/gear_wizard.js @@ -11,6 +11,7 @@ var wizard = null; var $wizardSteps = null; var $templateSteps = null; + var loopbackWizard = null; var self = this; @@ -145,7 +146,9 @@ } - function initialize() { + function initialize(_loopbackWizard) { + + loopbackWizard = _loopbackWizard; // 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) { @@ -179,6 +182,8 @@ this.setBackState = setBackState; this.initialize = initialize; this.createFTUEProfile = createFTUEProfile; + this.getWizard = function() {return wizard; } + this.getLoopbackWizard = function() { return loopbackWizard; }; self = this; return this; diff --git a/web/app/assets/javascripts/wizard/gear/step_configure_voice_chat.js b/web/app/assets/javascripts/wizard/gear/step_configure_voice_chat.js index fddc56cf0..dd5c3874f 100644 --- a/web/app/assets/javascripts/wizard/gear/step_configure_voice_chat.js +++ b/web/app/assets/javascripts/wizard/gear/step_configure_voice_chat.js @@ -7,6 +7,7 @@ var ASSIGNMENT = context.JK.ASSIGNMENT; var VOICE_CHAT = context.JK.VOICE_CHAT; + var gearUtils = context.JK.GearUtils; var logger = context.JK.logger; var $step = null; @@ -134,13 +135,13 @@ } function handleNext() { - var selectedDeviceInfo = context.JK.selectedDeviceInfo(context.jamClient.FTUEGetInputMusicDevice(), context.jamClient.FTUEGetOutputMusicDevice()); + var selectedDeviceInfo = gearUtils.selectedDeviceInfo(context.jamClient.FTUEGetInputMusicDevice(), context.jamClient.FTUEGetOutputMusicDevice()); var chatName = null; if(isChatEnabled()) { chatName = $selectedChatInput.attr('data-channel-name'); } - context.jamClient.FTUESetMusicProfileName(context.JK.createProfileName(selectedDeviceInfo, chatName)); + context.jamClient.FTUESetMusicProfileName(gearUtils.createProfileName(selectedDeviceInfo, chatName)); return true; } 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 a254f6c07..a18c3f4e2 100644 --- a/web/app/assets/javascripts/wizard/gear/step_select_gear.js +++ b/web/app/assets/javascripts/wizard/gear/step_select_gear.js @@ -3,40 +3,29 @@ "use strict"; context.JK = context.JK || {}; - context.JK.StepSelectGear = function (app, $dialog) { + context.JK.StepSelectGear = function (app, dialog) { var ASSIGNMENT = context.JK.ASSIGNMENT; var VOICE_CHAT = context.JK.VOICE_CHAT; var AUDIO_DEVICE_BEHAVIOR = context.JK.AUDIO_DEVICE_BEHAVIOR; + var gearUtils = context.JK.GearUtils; var self = null; var $step = null; var logger = context.JK.logger; var rest = context.JK.Rest(); + var frameBuffers = new context.JK.FrameBuffers(app); + var gearTest = new context.JK.GearTest(app); + var loopbackShowing = false; + var $watchVideoInput = null; var $watchVideoOutput = null; var $audioInput = null; var $audioOutput = null; - var $bufferIn = null; - var $bufferOut = null; - var $frameSize = null; var $inputChannels = null; var $outputChannels = null; var $knobs = null; var $scoreReport = null; - var $latencyScoreSection = null; - var $latencyScore = null; - var $latencyHeader = null; - var $ioHeader = null; - var $ioScoreSection = null; - var $ioRate = null; - var $ioRateScore = null; - var $ioVar = null; - var $ioVarScore = null; - var $ioCountdown = null; - var $ioCountdownSecs = null; - var $resultsText = null; - var $unknownText = null; var $asioInputControlBtn = null; var $asioOutputControlBtn = null; var $resyncBtn = null; @@ -46,7 +35,6 @@ var operatingSystem = null; var iCheckIgnore = false; - var scoring = false; // are we currently scoring var validDevice = false; // do we currently have a device selected that we can score against? // cached values between @@ -57,20 +45,8 @@ var selectedDeviceInfo = null; var musicPorts = null; - var validLatencyScore = false; - var validIOScore = false; - var lastLatencyScore = null; - var ioScore = null; - var latencyScore = null; - var savedProfile = false; - - - function isGoodFtue() { - return validLatencyScore && validIOScore; - } - // returns a deviceInfo hash for the device matching the deviceId, or undefined. function findDevice(deviceId) { return deviceInformation[deviceId]; @@ -84,30 +60,6 @@ return $audioOutput.val(); } - function selectedFramesize() { - return parseFloat($frameSize.val()); - } - - function selectedBufferIn() { - return parseFloat($bufferIn.val()); - } - - function selectedBufferOut() { - return parseFloat($bufferOut.val()); - } - - function setFramesize(value) { - context.JK.dropdown($frameSize.val(value).easyDropDown('select', value.toString(), true)) - } - - function setBufferIn(value) { - context.JK.dropdown($bufferIn.val(value).easyDropDown('select', value.toString(), true)); - } - - function setBufferOut(value) { - context.JK.dropdown($bufferOut.val(value).easyDropDown('select', value.toString(), true)) - } - function setInputAudioDevice(value) { context.JK.dropdown($audioInput.val(value).easyDropDown('select', value.toString(), true)) } @@ -117,11 +69,11 @@ } function initializeNextButtonState() { - $dialog.setNextState(isGoodFtue()); + dialog.setNextState(gearTest.isGoodFtue() || dialog.getLoopbackWizard().getGearTest().isGoodFtue()); } function initializeBackButtonState() { - $dialog.setBackState(!scoring); + dialog.setBackState(!gearTest.isScoring()); } function initializeAudioInput() { @@ -133,7 +85,6 @@ } }); - console.log("INITIALIZE AUDIO INPUT: " + optionsHtml) $audioInput.html(optionsHtml); context.JK.dropdown($audioInput); $audioInput.easyDropDown('enable') @@ -156,16 +107,6 @@ initializeAudioOutputChanged(); } - function initializeFramesize() { - context.JK.dropdown($frameSize); - } - - function initializeBuffers() { - context.JK.dropdown($bufferIn); - context.JK.dropdown($bufferOut); - } - - // reloads the backend's channel state for the currently selected audio devices, // and update's the UI accordingly function initializeChannels() { @@ -246,7 +187,7 @@ function newInputAssignment() { var assigned = 0; context._.each(musicPorts.inputs, function (inputChannel) { - if (isChannelAssigned(inputChannel)) { + if (gearUtils.isChannelAssigned(inputChannel)) { assigned += 1; } }); @@ -324,18 +265,13 @@ initializeChannels(); } - // checks if it's an assigned OUTPUT or ASSIGNED CHAT - function isChannelAssigned(channel) { - return channel.assignment == ASSIGNMENT.CHAT || channel.assignment == ASSIGNMENT.OUTPUT || channel.assignment > 0; - } - function initializeInputPorts(musicPorts) { $inputChannels.empty(); var inputPorts = musicPorts.inputs; context._.each(inputPorts, function (inputChannel) { var $inputChannel = $(context._.template($templateAudioPort.html(), inputChannel, { variable: 'data' })); var $checkbox = $inputChannel.find('input'); - if (isChannelAssigned(inputChannel)) { + if (gearUtils.isChannelAssigned(inputChannel)) { $checkbox.attr('checked', 'checked'); } context.JK.checkbox($checkbox); @@ -350,7 +286,7 @@ context._.each(outputChannels, function (outputChannel) { var $outputPort = $(context._.template($templateAudioPort.html(), outputChannel, { variable: 'data' })); var $checkbox = $outputPort.find('input'); - if (isChannelAssigned(outputChannel)) { + if (gearUtils.isChannelAssigned(outputChannel)) { $checkbox.attr('checked', 'checked'); } context.JK.checkbox($checkbox); @@ -361,8 +297,23 @@ function initializeLoopback() { $launchLoopbackBtn.unbind('click').click(function() { - app.setWizardStep(1); - app.layout.showDialog('ftue'); + + $(dialog.getLoopbackWizard()).one('dialog_closed', function() { + loopbackShowing = false; + + if(dialog.getLoopbackWizard().getGearTest().isGoodFtue()) { + gearTest.resetScoreReport(); + gearTest.showLoopbackDone(); + context.JK.prodBubble(dialog.getWizard().getNextButton(), 'move-on-loopback-success', {}, {positions:['top']}); + + } + + initializeNextButtonState(); + initializeBackButtonState(); + }) + + loopbackShowing = true; + app.layout.showDialog('loopback-wizard') return false; }) } @@ -372,17 +323,9 @@ initializeAudioInput(); initializeAudioOutput(); - initializeFramesize(); - initializeBuffers(); initializeLoopback(); } - function resetFrameBuffers() { - $frameSize.val('2.5'); - $bufferIn.val('0'); - $bufferOut.val('0'); - } - function clearInputPorts() { $inputChannels.empty(); } @@ -391,92 +334,6 @@ $outputChannels.empty(); } - function resetScoreReport() { - $ioHeader.hide(); - $latencyHeader.hide(); - $ioRate.hide(); - $ioRateScore.empty(); - $ioVar.hide(); - $ioVarScore.empty(); - $latencyScore.empty(); - $resultsText.removeAttr('latency-score'); - $resultsText.removeAttr('io-var-score'); - $resultsText.removeAttr('io-rate-score'); - $resultsText.removeAttr('scored'); - $unknownText.hide(); - $ioScoreSection.removeClass('good acceptable bad unknown starting skip'); - $latencyScoreSection.removeClass('good acceptable bad unknown starting') - } - - function renderLatencyScore(latencyValue, latencyClass) { - // latencyValue == null implies starting condition - if (latencyValue) { - $latencyScore.text(latencyValue + ' ms'); - } - else { - $latencyScore.text(''); - } - - - if(latencyClass == 'unknown') { - $latencyScore.text('Unknown'); - $unknownText.show(); - } - - $latencyHeader.show(); - $resultsText.attr('latency-score', latencyClass); - $latencyScoreSection.removeClass('good acceptable bad unknown starting').addClass(latencyClass); - } - - // std deviation is the worst value between in/out - // media is the worst value between in/out - // io is the value returned by the backend, which has more info - // ioClass is the pre-computed rollup class describing the result in simple terms of 'good', 'acceptable', bad' - function renderIOScore(std, median, ioData, ioClass, ioRateClass, ioVarClass) { - $ioRateScore.text(median !== null ? median : ''); - $ioVarScore.text(std !== null ? std : ''); - if (ioClass && ioClass != "starting" && ioClass != "skip") { - $ioRate.show(); - $ioVar.show(); - } - if(ioClass == 'starting' || ioClass == 'skip') { - $ioHeader.show(); - } - $resultsText.attr('io-rate-score', ioRateClass); - $resultsText.attr('io-var-score', ioVarClass); - $ioScoreSection.removeClass('good acceptable bad unknown starting skip') - if (ioClass) { - $ioScoreSection.addClass(ioClass); - } - // TODO: show help bubble of all data in IO data - } - - function updateScoreReport(latencyResult) { - var latencyClass = "neutral"; - var latencyValue = null; - var validLatency = false; - if (latencyResult && latencyResult.latencyknown) { - var latencyValue = latencyResult.latency; - latencyValue = Math.round(latencyValue * 100) / 100; - if (latencyValue <= 10) { - latencyClass = "good"; - validLatency = true; - } else if (latencyValue <= gon.ftue_maximum_gear_latency) { - latencyClass = "acceptable"; - validLatency = true; - } else { - latencyClass = "bad"; - } - } - else { - latencyClass = 'unknown'; - } - - validLatencyScore = validLatency; - - renderLatencyScore(latencyValue, latencyClass); - } - function audioInputDeviceUnselected() { validDevice = false; setOutputAudioDevice(''); @@ -484,28 +341,22 @@ } function renderScoringStarted() { - resetScoreReport(); + initializeBackButtonState(); initializeNextButtonState(); freezeAudioInteraction(); - renderLatencyScore(null, 'starting'); - renderIOScore(null, null, null, null, null, null); } function renderScoringStopped() { initializeNextButtonState(); - unfreezeAudioInteraction(); - $resultsText.attr('scored', 'complete'); - scoring = false; initializeBackButtonState(); + unfreezeAudioInteraction(); } function freezeAudioInteraction() { logger.debug("freezing audio interaction"); $audioInput.attr("disabled", "disabled").easyDropDown('disable'); $audioOutput.attr("disabled", "disabled").easyDropDown('disable'); - $frameSize.attr("disabled", "disabled").easyDropDown('disable'); - $bufferIn.attr("disabled", "disabled").easyDropDown('disable'); - $bufferOut.attr("disabled", "disabled").easyDropDown('disable'); + frameBuffers.disable(); $asioInputControlBtn.on("click", false); $asioOutputControlBtn.on("click", false); $resyncBtn.on('click', false); @@ -518,9 +369,7 @@ logger.debug("unfreezing audio interaction"); $audioInput.removeAttr("disabled").easyDropDown('enable'); $audioOutput.removeAttr("disabled").easyDropDown('enable'); - $frameSize.removeAttr("disabled").easyDropDown('enable'); - $bufferIn.removeAttr("disabled").easyDropDown('enable'); - $bufferOut.removeAttr("disabled").easyDropDown('enable'); + frameBuffers.enable(); $asioInputControlBtn.off("click", false); $asioOutputControlBtn.off("click", false); $resyncBtn.off('click', false); @@ -574,9 +423,8 @@ } function invalidateScore() { - validLatencyScore = false; - validIOScore = false; - resetScoreReport(); + gearTest.invalidateScore(); + dialog.getLoopbackWizard().getGearTest().invalidateScore(); initializeNextButtonState(); } @@ -589,61 +437,23 @@ }); } - function initializeKnobs() { - $frameSize.unbind('change').change(function () { - logger.debug("frameize changed: " + selectedFramesize()); - context.JK.prodBubble($resyncBtn, 'push-resync-when-done', {}, {positions:['top']}); - updateDefaultBuffers(); - jamClient.FTUESetFrameSize(selectedFramesize()); - invalidateScore(); - }); - - $bufferIn.unbind('change').change(function () { - logger.debug("buffer-in changed: " + selectedBufferIn()); - context.JK.prodBubble($resyncBtn, 'push-resync-when-done', {}, {positions:['top']}); - jamClient.FTUESetInputLatency(selectedBufferIn()); - invalidateScore(); - }); - - $bufferOut.unbind('change').change(function () { - logger.debug("buffer-out changed: " + selectedBufferOut()); - context.JK.prodBubble($resyncBtn, 'push-resync-when-done', {}, {positions:['top']}); - jamClient.FTUESetOutputLatency(selectedBufferOut()); - invalidateScore(); - }); + function onFramesizeChanged() { + context.JK.prodBubble($resyncBtn, 'push-resync-when-done', {}, {positions:['top']}); + updateDefaultBuffers(); + jamClient.FTUESetFrameSize(frameBuffers.selectedFramesize()); + invalidateScore(); } - - function ftueSummary() { - return { - os: operatingSystem, - version: context.jamClient.ClientUpdateVersion(), - success: isGoodFtue(), - score: { - validLatencyScore: validLatencyScore, - validIOScore: validIOScore, - latencyScore: latencyScore, - ioScore : ioScore, - }, - audioParameters: { - frameSize: selectedFramesize(), - bufferIn: selectedBufferIn(), - bufferOut: selectedBufferOut(), - }, - devices: deviceInformation, - selectedDevice: selectedDeviceInfo - } + + function onBufferInChanged() { + context.JK.prodBubble($resyncBtn, 'push-resync-when-done', {}, {positions:['top']}); + jamClient.FTUESetInputLatency(frameBuffers.selectedBufferIn()); + invalidateScore(); } - - function postDiagnostic() { - rest.createDiagnostic({ - type: 'GEAR_SELECTION', - data: { - logs: logger.logCache, - client_type: context.JK.clientType(), - client_id: - context.JK.JamServer.clientID, - summary:ftueSummary()} - }); + + function onBufferOutChanged() { + context.JK.prodBubble($resyncBtn, 'push-resync-when-done', {}, {positions:['top']}); + jamClient.FTUESetOutputLatency(frameBuffers.selectedBufferOut()); + invalidateScore(); } function getSelectedInputs() { @@ -672,19 +482,6 @@ }) } - function renderIOScoringStarted(secondsLeft) { - $ioCountdownSecs.text(secondsLeft); - $ioCountdown.show(); - } - - function renderIOScoringStopped() { - $ioCountdown.hide(); - } - - function renderIOCountdown(secondsLeft) { - $ioCountdownSecs.text(secondsLeft); - } - // sets selectedDeviceInfo, which contains id, behavior, and info for input and output device function cacheCurrentAudioInfo() { @@ -717,7 +514,7 @@ var profileName = context.jamClient.FTUEGetMusicProfileName(); logger.debug("invaliding previously saved profile: " + profileName); - $dialog.createFTUEProfile(); + dialog.createFTUEProfile(); // restore user selections because newSession is called by createFTUEProfile(), invalidating dropdowns setInputAudioDevice(audioInputDeviceId); setOutputAudioDevice(audioOutputDeviceId); @@ -731,7 +528,7 @@ lastSelectedDeviceInfo = selectedDeviceInfo; - selectedDeviceInfo = context.JK.selectedDeviceInfo(audioInputDeviceId, audioOutputDeviceId, deviceInformation) + selectedDeviceInfo = gearUtils.selectedDeviceInfo(audioInputDeviceId, audioOutputDeviceId, deviceInformation) return true; } @@ -761,9 +558,21 @@ return false; } - jamClient.FTUESetInputLatency(selectedBufferIn()); - jamClient.FTUESetOutputLatency(selectedBufferOut()); - jamClient.FTUESetFrameSize(selectedFramesize()); + jamClient.FTUESetInputLatency(frameBuffers.selectedBufferIn()); + jamClient.FTUESetOutputLatency(frameBuffers.selectedBufferOut()); + jamClient.FTUESetFrameSize(frameBuffers.selectedFramesize()); + + // prod user to watch video if the previous type and new type changed + if(!shownInputProdOnce && isInputAudioTypeDifferentFromLastTime()) { + context.JK.prodBubble($watchVideoInput, 'ftue-watch-video', {}, {positions:['top', 'right']}); + shownInputProdOnce = true; + } + + // prod user to watch video if the previous type and new type changed + if(!shownOutputProdOnce && isOutputAudioTypeDifferentFromLastTime()) { + context.JK.prodBubble($watchVideoOutput, 'ftue-watch-video', {}, {positions:['top', 'right']}); + shownOutputProdOnce = true; + } return true; } @@ -798,7 +607,6 @@ else { var inputBehavior = null; var outputBehavior = null; - } // deal with watch video @@ -820,7 +628,7 @@ $knobs.hide(); } - // handle ASIO + // handle ASIO visibility if (inputBehavior) { if (inputBehavior.showASIO) { $asioInputControlBtn.show(); @@ -856,103 +664,45 @@ function updateDefaultFrameSize() { if(selectedDeviceInfo && (selectedDeviceInfo.input.info.type == 'Win32_wdm' || selectedDeviceInfo.output.info.type == 'Win32_wdm')) { - setFramesize('10'); + frameBuffers.setFramesize('10'); } else { - setFramesize('2.5') + frameBuffers.setFramesize('2.5') } - jamClient.FTUESetFrameSize(selectedFramesize()); + 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 = selectedFramesize(); + var framesize = frameBuffers.selectedFramesize(); if(framesize == 2.5) { logger.debug("setting default buffers to 1/1"); - setBufferIn('1'); - setBufferOut('1'); + frameBuffers.setBufferIn('1'); + frameBuffers.setBufferOut('1'); } else if(framesize == 5) { logger.debug("setting default buffers to 3/2"); - setBufferIn('3'); - setBufferOut('2'); + frameBuffers.setBufferIn('3'); + frameBuffers.setBufferOut('2'); } else { logger.debug("setting default buffers to 6/5"); - setBufferIn('6'); - setBufferOut('5'); + frameBuffers.setBufferIn('6'); + frameBuffers.setBufferOut('5'); } } else { logger.debug("setting default buffers to 0/0"); - setBufferIn(0); - setBufferOut(0); + frameBuffers.setBufferIn(0); + frameBuffers.setBufferOut(0); } - jamClient.FTUESetInputLatency(selectedBufferIn()); - jamClient.FTUESetOutputLatency(selectedBufferOut()); - } - - function processIOScore(io) { - // take the higher variance, which is apparently actually std dev - var std = io.in_var > io.out_var ? io.in_var : io.out_var; - std = Math.round(std * 100) / 100; - // take the furthest-off-from-target io rate - var median = Math.abs(io.in_median - io.in_target) > Math.abs(io.out_median - io.out_target) ? [io.in_median, io.in_target] : [io.out_median, io.out_target]; - var medianTarget = median[1]; - median = Math.round(median[0]); - - var stdIOClass = 'bad'; - if (std <= 0.50) { - stdIOClass = 'good'; - } - else if (std <= 1.00) { - stdIOClass = 'acceptable'; - } - - var medianIOClass = 'bad'; - if (Math.abs(median - medianTarget) <= 1) { - medianIOClass = 'good'; - } - else if (Math.abs(median - medianTarget) <= 2) { - medianIOClass = 'acceptable'; - } - - // take worst between median or std - var ioClassToNumber = {bad: 2, acceptable: 1, good: 0} - var aggregrateIOClass = ioClassToNumber[stdIOClass] > ioClassToNumber[medianIOClass] ? stdIOClass : medianIOClass; - - // now base the overall IO score based on both values. - renderIOScore(std, median, io, aggregrateIOClass, medianIOClass, stdIOClass); - - if(aggregrateIOClass == "bad") { - validIOScore = false; - } - else { - validIOScore = true; - } - - if(isGoodFtue()) { - onSuccessfulScore(); - } - else { - onFailedScore(); - } - - renderScoringStopped(); - postDiagnostic(); - } - - function onFailedScore() { - rest.userCertifiedGear({success: false}); - } - - function onSuccessfulScore() { - rest.userCertifiedGear({success: true}); + jamClient.FTUESetInputLatency(frameBuffers.selectedBufferIn()); + jamClient.FTUESetOutputLatency(frameBuffers.selectedBufferOut()); } // refocused affects how IO testing occurs. @@ -960,85 +710,7 @@ // * reuse IO score if it was good/acceptable // * rescore IO if it was bad or skipped from previous try function attemptScore(refocused) { - if(scoring) {return;} - scoring = true; - initializeBackButtonState(); - validLatencyScore = false; - latencyScore = null; - if(!refocused) { - // don't reset a valid IO score on refocus - validIOScore = false; - ioScore = null; - } - - renderScoringStarted(); - - // this timer exists to give UI time to update for renderScoringStarted before blocking nature of jamClient.FTUESave(save) kicks in - setTimeout(function () { - logger.debug("Calling FTUESave(false)"); - jamClient.FTUESave(false); - - var latency = jamClient.FTUEGetExpectedLatency(); - latencyScore = latency; - - // prod user to watch video if the previous type and new type changed - if(!shownInputProdOnce && isInputAudioTypeDifferentFromLastTime()) { - context.JK.prodBubble($watchVideoInput, 'ftue-watch-video', {}, {positions:['top', 'right']}); - shownInputProdOnce = true; - } - - // prod user to watch video if the previous type and new type changed - if(!shownOutputProdOnce && isOutputAudioTypeDifferentFromLastTime()) { - context.JK.prodBubble($watchVideoOutput, 'ftue-watch-video', {}, {positions:['top', 'right']}); - shownOutputProdOnce = true; - } - - updateScoreReport(latency); - - if(refocused) { - context.JK.prodBubble($scoreReport, 'refocus-rescore', {validIOScore: validIOScore}, {positions:['top', 'left']}); - } - - // if there was a valid latency score, go on to the next step - if (validLatencyScore) { - // reuse valid IO score if this is on refocus - if(refocused && validIOScore) { - renderIOScore(null, null, null, 'starting', 'starting', 'starting'); - processIOScore(ioScore); - } - else { - renderIOScore(null, null, null, 'starting', 'starting', 'starting'); - var testTimeSeconds = gon.ftue_io_wait_time; // allow time for IO to establish itself - var startTime = testTimeSeconds / 2; // start measuring half way through the test, to get past IO oddities - renderIOScoringStarted(testTimeSeconds); - renderIOCountdown(testTimeSeconds); - var interval = setInterval(function () { - testTimeSeconds -= 1; - renderIOCountdown(testTimeSeconds); - - if(testTimeSeconds == startTime) { - logger.debug("Starting IO Perf Test starting at " + startTime + "s in") - context.jamClient.FTUEStartIoPerfTest(); - } - - if (testTimeSeconds == 0) { - clearInterval(interval); - renderIOScoringStopped(); - logger.debug("Ending IO Perf Test at " + testTimeSeconds + "s in") - var io = context.jamClient.FTUEGetIoPerfData(); - ioScore = io; - processIOScore(io); - } - }, 1000); - } - } - else { - onFailedScore(); - renderIOScore(null, null, null, 'skip', 'skip', 'skip'); - renderScoringStopped(); - postDiagnostic(); - } - }, 250); + gearTest.attemptScore(refocused); } function initializeAudioInputChanged() { @@ -1049,6 +721,20 @@ $audioOutput.unbind('change').change(audioDeviceChanged); } + function onGearTestStarted(e, data) { + renderScoringStarted(); + } + + function onGearTestDone(e, data) { + renderScoringStopped(); + gearUtils.postDiagnostic(operatingSystem, deviceInformation, selectedDeviceInfo, gearTest, frameBuffers, true); + } + + function onGearTestFail(e, data) { + renderScoringStopped(); + gearUtils.postDiagnostic(operatingSystem, deviceInformation, selectedDeviceInfo, gearTest, frameBuffers, true); + } + function handleNext() { if(!savedProfile) { @@ -1075,7 +761,7 @@ return false; } else { - context.jamClient.FTUESetMusicProfileName(context.JK.createProfileName(selectedDeviceInfo)); + context.jamClient.FTUESetMusicProfileName(gearUtils.createProfileName(selectedDeviceInfo)); context.jamClient.FTUESave(true); savedProfile = true; context.JK.GA.trackAudioTestCompletion(context.JK.detectOS()); @@ -1085,22 +771,19 @@ } function onFocus() { - if(!scoring && validDevice && getSelectedInputs().length > 0 && getSelectedOutputs().length == 2 ) { - // in the case the user has been unselecting ports, re-enforce minimum viable channels - validDevice = autoSelectMinimumValidChannels(); + if(validDevice && !loopbackShowing && getSelectedInputs().length > 0 && getSelectedOutputs().length == 2 ) { attemptScore(true); } } function newSession() { savedProfile = false; - deviceInformation = context.JK.loadDeviceInfo(); + deviceInformation = gearUtils.loadDeviceInfo(); resetState(); initializeFormElements(); initializeNextButtonState(); initializeWatchVideo(); initializeASIOButtons(); - initializeKnobs(); initializeResync(); } @@ -1118,7 +801,7 @@ invalidateScore(); clearInputPorts(); clearOutputPorts(); - resetFrameBuffers(); + frameBuffers.resetValues(); updateDialogForCurrentDevices(); } @@ -1129,26 +812,11 @@ $watchVideoOutput = $step.find('.watch-video.audio-output'); $audioInput = $step.find('.select-audio-input-device'); $audioOutput = $step.find('.select-audio-output-device'); - $bufferIn = $step.find('.select-buffer-in'); - $bufferOut = $step.find('.select-buffer-out'); - $frameSize = $step.find('.select-frame-size'); + $inputChannels = $step.find('.input-ports'); $outputChannels = $step.find('.output-ports'); $knobs = $step.find('.frame-and-buffers'); $scoreReport = $step.find('.results'); - $latencyScoreSection = $scoreReport.find('.latency-score-section'); - $latencyScore = $scoreReport.find('.latency-score'); - $latencyHeader = $scoreReport.find('.latency'); - $ioHeader = $scoreReport.find('.io'); - $ioScoreSection = $scoreReport.find('.io-score-section'); - $ioRate = $scoreReport.find('.io-rate'); - $ioRateScore = $scoreReport.find('.io-rate-score'); - $ioVar = $scoreReport.find('.io-var'); - $ioVarScore = $scoreReport.find('.io-var-score'); - $ioCountdown = $scoreReport.find('.io-countdown'); - $ioCountdownSecs = $scoreReport.find('.io-countdown .secs'); - $resultsText = $scoreReport.find('.results-text'); - $unknownText = $scoreReport.find('.unknown-text'); $asioInputControlBtn = $step.find('.asio-settings-input-btn'); $asioOutputControlBtn = $step.find('.asio-settings-output-btn'); $resyncBtn = $step.find('.resync-btn'); @@ -1156,6 +824,17 @@ $launchLoopbackBtn = $('.loopback-test'); $instructions = $('.instructions'); operatingSystem = context.JK.GetOSAsString(); + frameBuffers.initialize($knobs); + $(frameBuffers) + .on(frameBuffers.FRAMESIZE_CHANGED, onFramesizeChanged) + .on(frameBuffers.BUFFER_IN_CHANGED, onBufferInChanged) + .on(frameBuffers.BUFFER_OUT_CHANGED, onBufferOutChanged) + + gearTest.initialize($scoreReport, true) + $(gearTest) + .on(gearTest.GEAR_TEST_START, onGearTestStarted) + .on(gearTest.GEAR_TEST_DONE, onGearTestDone) + .on(gearTest.GEAR_TEST_FAIL, onGearTestFail) } this.handleNext = handleNext; diff --git a/web/app/assets/javascripts/wizard/gear_test.js b/web/app/assets/javascripts/wizard/gear_test.js new file mode 100644 index 000000000..9b1a1e4f9 --- /dev/null +++ b/web/app/assets/javascripts/wizard/gear_test.js @@ -0,0 +1,457 @@ +(function (context, $) { + + "use strict"; + + context.JK = context.JK || {}; + context.JK.GearTest = function (app) { + + var logger = context.JK.logger; + + var isAutomated = false; + var drawUI = false; + var scoring = false; + var validLatencyScore = false; + var validIOScore = false; + var latencyScore = null; + var ioScore = null; + + var $scoreReport = null; + var $ioHeader = null; + var $latencyHeader = null; + var $ioRate = null; + var $ioRateScore = null; + var $ioVar = null; + var $ioVarScore = null; + var $ioCountdown = null; + var $ioCountdownSecs = null; + var $latencyScore = null; + var $resultsText = null; + var $unknownText = null; + var $loopbackCompleted = null; + var $ioScoreSection = null; + var $latencyScoreSection = null; + + var $self = $(this); + + var GEAR_TEST_START = "gear_test.start"; + var GEAR_TEST_IO_START = "gear_test.io_start"; + var GEAR_TEST_IO_DONE = "gear_test.io_done"; + var GEAR_TEST_LATENCY_START = "gear_test.latency_start"; + var GEAR_TEST_LATENCY_DONE = "gear_test.latency_done"; + var GEAR_TEST_DONE = "gear_test.done"; + var GEAR_TEST_FAIL = "gear_test.fail"; + var GEAR_TEST_IO_PROGRESS = "gear_test.io_progress"; + + function isGoodFtue() { + return validLatencyScore && validIOScore; + } + + function processIOScore(io) { + // take the higher variance, which is apparently actually std dev + var std = io.in_var > io.out_var ? io.in_var : io.out_var; + std = Math.round(std * 100) / 100; + // take the furthest-off-from-target io rate + var median = Math.abs(io.in_median - io.in_target) > Math.abs(io.out_median - io.out_target) ? [io.in_median, io.in_target] : [io.out_median, io.out_target]; + var medianTarget = median[1]; + median = Math.round(median[0]); + + var stdIOClass = 'bad'; + if (std <= 0.50) { + stdIOClass = 'good'; + } + else if (std <= 1.00) { + stdIOClass = 'acceptable'; + } + + var medianIOClass = 'bad'; + if (Math.abs(median - medianTarget) <= 1) { + medianIOClass = 'good'; + } + else if (Math.abs(median - medianTarget) <= 2) { + medianIOClass = 'acceptable'; + } + + // take worst between median or std + var ioClassToNumber = {bad: 2, acceptable: 1, good: 0} + var aggregrateIOClass = ioClassToNumber[stdIOClass] > ioClassToNumber[medianIOClass] ? stdIOClass : medianIOClass; + + // now base the overall IO score based on both values. + + $self.triggerHandler(GEAR_TEST_IO_DONE, {std:std, median:median, io:io, aggregrateIOClass: aggregrateIOClass, medianIOClass : medianIOClass, stdIOClass: stdIOClass}) + //renderIOScore(std, median, io, aggregrateIOClass, medianIOClass, stdIOClass); + + if(aggregrateIOClass == "bad") { + validIOScore = false; + } + else { + validIOScore = true; + } + + scoring = false; + + if(isGoodFtue()) { + $self.triggerHandler(GEAR_TEST_DONE) + } + else { + $self.triggerHandler(GEAR_TEST_FAIL, {reason:'io'}); + } + } + + function automaticScore() { + logger.debug("automaticScore: calling FTUESave(false)"); + jamClient.FTUESave(false); + var latency = jamClient.FTUEGetExpectedLatency(); + context.JK.GearTest.testDeferred.resolve(latency); + } + + function loopbackScore() { + var cbFunc = 'JK.loopbackLatencyCallback'; + logger.debug("Registering loopback latency callback: " + cbFunc); + context.jamClient.FTUERegisterLatencyCallback('JK.loopbackLatencyCallback'); + var now = new Date(); + logger.debug("Starting Latency Test..." + now); + context.jamClient.FTUEStartLatency(); + } + + // refocused affects how IO testing occurs. + // on refocus=true: + // * reuse IO score if it was good/acceptable + // * rescore IO if it was bad or skipped from previous try + function attemptScore(refocused) { + if(scoring) { + logger.debug("gear-test: already scoring"); + return; + } + scoring = true; + $self.triggerHandler(GEAR_TEST_START); + $self.triggerHandler(GEAR_TEST_LATENCY_START); + validLatencyScore = false; + latencyScore = null; + if(!refocused) { + // don't reset a valid IO score on refocus + validIOScore = false; + ioScore = null; + } + + // this timer exists to give UI time to update for renderScoringStarted before blocking nature of jamClient.FTUESave(save) kicks in + setTimeout(function () { + + context.JK.GearTest.testDeferred = new $.Deferred(); + if(isAutomated) { + // use automated latency score mechanism + automaticScore() + } + else { + // use loopback score mechanism + loopbackScore(); + } + + context.JK.GearTest.testDeferred.done(function(latency) { + latencyScore = latency; + + if(isAutomated) { + // uncomment to do a manual loopback test + //latency.latencyknown = false; + } + + updateScoreReport(latency, refocused); + + // if there was a valid latency score, go on to the next step + if (validLatencyScore) { + $self.triggerHandler(GEAR_TEST_IO_START); + // reuse valid IO score if this is on refocus + if(refocused && validIOScore) { + processIOScore(ioScore); + } + else { + var testTimeSeconds = gon.ftue_io_wait_time; // allow time for IO to establish itself + var startTime = testTimeSeconds / 2; // start measuring half way through the test, to get past IO oddities + $self.trigger(GEAR_TEST_IO_PROGRESS, {countdown:testTimeSeconds, first:true}) + + var interval = setInterval(function () { + testTimeSeconds -= 1; + $self.trigger(GEAR_TEST_IO_PROGRESS, {countdown:testTimeSeconds, first:false}) + + if(testTimeSeconds == startTime) { + logger.debug("Starting IO Perf Test starting at " + startTime + "s in") + context.jamClient.FTUEStartIoPerfTest(); + } + + if (testTimeSeconds == 0) { + clearInterval(interval); + logger.debug("Ending IO Perf Test at " + testTimeSeconds + "s in") + var io = context.jamClient.FTUEGetIoPerfData(); + ioScore = io; + processIOScore(io); + } + }, 1000); + } + } + else { + scoring = false; + $self.triggerHandler(GEAR_TEST_FAIL, {reason:'latency'}) + } + }) + }, 250); + } + + function updateScoreReport(latencyResult, refocused) { + var latencyClass = "neutral"; + var latencyValue = null; + var validLatency = false; + if (latencyResult && latencyResult.latencyknown) { + var latencyValue = latencyResult.latency; + latencyValue = Math.round(latencyValue * 100) / 100; + if (latencyValue <= 10) { + latencyClass = "good"; + validLatency = true; + } else if (latencyValue <= gon.ftue_maximum_gear_latency) { + latencyClass = "acceptable"; + validLatency = true; + } else { + latencyClass = "bad"; + } + } + else { + latencyClass = 'unknown'; + } + + validLatencyScore = validLatency; + + if(refocused) { + context.JK.prodBubble($scoreReport, 'refocus-rescore', {validIOScore: validIOScore}, {positions:['top', 'left']}); + } + + $self.triggerHandler(GEAR_TEST_LATENCY_DONE, {latencyValue: latencyValue, latencyClass: latencyClass, refocused: refocused}); + } + + // std deviation is the worst value between in/out + // media is the worst value between in/out + // io is the value returned by the backend, which has more info + // ioClass is the pre-computed rollup class describing the result in simple terms of 'good', 'acceptable', bad' + function renderIOScore(std, median, ioData, ioClass, ioRateClass, ioVarClass) { + $ioRateScore.text(median !== null ? median : ''); + $ioVarScore.text(std !== null ? std : ''); + if (ioClass && ioClass != "starting" && ioClass != "skip") { + $ioRate.show(); + $ioVar.show(); + } + if(ioClass == 'starting' || ioClass == 'skip') { + $ioHeader.show(); + } + $resultsText.attr('io-rate-score', ioRateClass); + $resultsText.attr('io-var-score', ioVarClass); + $ioScoreSection.removeClass('good acceptable bad unknown starting skip') + if (ioClass) { + $ioScoreSection.addClass(ioClass); + } + // TODO: show help bubble of all data in IO data + } + + function isScoring() { + return scoring; + } + + function isValidLatencyScore() { + return validLatencyScore; + } + + function isValidIOScore() { + return validIOScore; + } + + function getLatencyScore() { + return latencyScore; + } + + function getIOScore() { + return ioScore; + } + + function showLoopbackDone() { + $loopbackCompleted.show(); + } + + function resetScoreReport() { + $ioHeader.hide(); + $latencyHeader.hide(); + $ioRate.hide(); + $ioRateScore.empty(); + $ioVar.hide(); + $ioVarScore.empty(); + $latencyScore.empty(); + $resultsText.removeAttr('latency-score'); + $resultsText.removeAttr('io-var-score'); + $resultsText.removeAttr('io-rate-score'); + $resultsText.removeAttr('scored'); + $unknownText.hide(); + $loopbackCompleted.hide(); + $ioScoreSection.removeClass('good acceptable bad unknown starting skip'); + $latencyScoreSection.removeClass('good acceptable bad unknown starting') + } + + function invalidateScore() { + validLatencyScore = false; + validIOScore = false; + resetScoreReport(); + } + + function renderLatencyScore(latencyValue, latencyClass) { + // latencyValue == null implies starting condition + if (latencyValue) { + $latencyScore.text(latencyValue + ' ms'); + } + else { + $latencyScore.text(''); + } + + + if(latencyClass == 'unknown') { + $latencyScore.text('Unknown'); + $unknownText.show(); + } + + $latencyHeader.show(); + $resultsText.attr('latency-score', latencyClass); + $latencyScoreSection.removeClass('good acceptable bad unknown starting').addClass(latencyClass); + } + + function renderIOScoringStarted(secondsLeft) { + $ioCountdownSecs.text(secondsLeft); + $ioCountdown.show(); + } + + function renderIOScoringStopped() { + $ioCountdown.hide(); + } + + function renderIOCountdown(secondsLeft) { + $ioCountdownSecs.text(secondsLeft); + } + + function handleUI($testResults) { + + if(!$testResults.is('.ftue-box.results')) { + throw "GearTest != .ftue-box.results" + } + + $scoreReport = $testResults; + $ioHeader = $scoreReport.find('.io');; + $latencyHeader = $scoreReport.find('.latency'); + $ioRate = $scoreReport.find('.io-rate'); + $ioRateScore = $scoreReport.find('.io-rate-score'); + $ioVar = $scoreReport.find('.io-var'); + $ioVarScore = $scoreReport.find('.io-var-score'); + $ioScoreSection = $scoreReport.find('.io-score-section'); + $latencyScore = $scoreReport.find('.latency-score'); + $ioCountdown = $scoreReport.find('.io-countdown'); + $ioCountdownSecs = $scoreReport.find('.io-countdown .secs'); + $resultsText = $scoreReport.find('.results-text'); + $unknownText = $scoreReport.find('.unknown-text'); + $loopbackCompleted = $scoreReport.find('.loopback-completed') + $latencyScoreSection = $scoreReport.find('.latency-score-section'); + + function onGearTestStart(e, data) { + renderIOScore(null, null, null, null, null, null); + } + + function onGearTestIOStart(e, data) { + renderIOScore(null, null, null, 'starting', 'starting', 'starting'); + } + + function onGearTestLatencyStart(e, data) { + resetScoreReport(); + renderLatencyScore(null, 'starting'); + } + + function onGearTestLatencyDone(e, data) { + renderLatencyScore(data.latencyValue, data.latencyClass); + } + + function onGearTestIOProgress(e, data) { + if(data.first) { + renderIOScoringStarted(data.countdown); + } + + renderIOCountdown(data.countdown); + + if(data.countdown == 0) { + renderIOScoringStopped(); + } + } + + function onGearTestIODone(e, data) { + renderIOScore(data.std, data.median, data.io, data.aggregrateIOClass, data.medianIOClass, data.stdIOClass); + } + + function onGearTestDone(e, data) { + $resultsText.attr('scored', 'complete'); + rest.userCertifiedGear({success: true}); + } + + function onGearTestFail(e, data) { + $resultsText.attr('scored', 'complete'); + + if(data.reason == "latency") { + renderIOScore(null, null, null, 'skip', 'skip', 'skip'); + } + + rest.userCertifiedGear({success: false}); + } + + $self + .on(GEAR_TEST_START, onGearTestStart) + .on(GEAR_TEST_IO_START, onGearTestIOStart) + .on(GEAR_TEST_LATENCY_START, onGearTestLatencyStart) + .on(GEAR_TEST_LATENCY_DONE, onGearTestLatencyDone) + .on(GEAR_TEST_IO_PROGRESS, onGearTestIOProgress) + .on(GEAR_TEST_IO_DONE, onGearTestIODone) + .on(GEAR_TEST_DONE, onGearTestDone) + .on(GEAR_TEST_FAIL, onGearTestFail); + } + + function initialize($testResults, automated, noUI) { + + isAutomated = automated; + drawUI = !noUI; + + if(drawUI) { + handleUI($testResults); + } + } + + // Latency Test Callback + context.JK.loopbackLatencyCallback = function (latencyMS) { + // Unregister callback: + context.jamClient.FTUERegisterLatencyCallback(''); + + logger.debug("loopback test done: " + latencyMS); + context.JK.GearTest.testDeferred.resolve({latency: latencyMS, latencyknown:true}) + }; + + this.GEAR_TEST_START = GEAR_TEST_START; + this.GEAR_TEST_IO_START = GEAR_TEST_IO_START; + this.GEAR_TEST_IO_DONE = GEAR_TEST_IO_DONE; + this.GEAR_TEST_LATENCY_START = GEAR_TEST_LATENCY_START; + this.GEAR_TEST_LATENCY_DONE = GEAR_TEST_LATENCY_DONE; + this.GEAR_TEST_DONE = GEAR_TEST_DONE; + this.GEAR_TEST_FAIL = GEAR_TEST_FAIL; + this.GEAR_TEST_IO_PROGRESS = GEAR_TEST_IO_PROGRESS; + + this.initialize = initialize; + this.isScoring = isScoring; + this.attemptScore = attemptScore; + this.resetScoreReport = resetScoreReport; + this.showLoopbackDone = showLoopbackDone; + this.invalidateScore = invalidateScore; + this.isValidLatencyScore = isValidLatencyScore; + this.isValidIOScore = isValidIOScore; + this.isGoodFtue = isGoodFtue; + this.getLatencyScore = getLatencyScore; + this.getIOScore = getIOScore; + + return this; + } + + +})(window, jQuery); \ No newline at end of file diff --git a/web/app/assets/javascripts/wizard/gear_utils.js b/web/app/assets/javascripts/wizard/gear_utils.js new file mode 100644 index 000000000..874fb8e63 --- /dev/null +++ b/web/app/assets/javascripts/wizard/gear_utils.js @@ -0,0 +1,172 @@ +/** + * Common utility functions. + */ +(function (context, $) { + + "use strict"; + + context.JK = context.JK || {}; + var gearUtils = {}; + context.JK.GearUtils = gearUtils; + var logger = context.JK.logger; + var ASSIGNMENT = context.JK.ASSIGNMENT; + var VOICE_CHAT = context.JK.VOICE_CHAT; + var AUDIO_DEVICE_BEHAVIOR = context.JK.AUDIO_DEVICE_BEHAVIOR; + + // checks if it's an assigned OUTPUT or ASSIGNED CHAT + gearUtils.isChannelAssigned = function (channel) { + return channel.assignment == ASSIGNMENT.CHAT || channel.assignment == ASSIGNMENT.OUTPUT || channel.assignment > 0; + } + + + gearUtils.createProfileName = function(deviceInfo, chatName) { + var isSameInOut = deviceInfo.input.id == deviceInfo.output.id; + + var name = null; + if(isSameInOut) { + name = "In/Out: " + deviceInfo.input.info.displayName; + } + else { + name = "In: " + deviceInfo.input.info.displayName + ", Out: " + deviceInfo.output.info.displayName + } + + logger.debug("creating profile name: " + name); + return name; + } + + + gearUtils.selectedDeviceInfo = function(audioInputDeviceId, audioOutputDeviceId, deviceInformation) { + + + if(!audioInputDeviceId) { + logger.debug("gearUtils.selectedDeviceInfo: no active input device"); + return null; + } + if(!audioOutputDeviceId) { + logger.debug("gearUtils.selectedDeviceInfo: no active output device"); + return null; + } + + if(!deviceInformation) { + deviceInformation = gearUtils.loadDeviceInfo(); + } + + var input = deviceInformation[audioInputDeviceId]; + var output = deviceInformation[audioOutputDeviceId]; + + var inputBehavior = AUDIO_DEVICE_BEHAVIOR[input.type]; + var outputBehavior = AUDIO_DEVICE_BEHAVIOR[output.type]; + + return { + input: { + id: audioInputDeviceId, + info: input, + behavior: inputBehavior + }, + output: { + id: audioOutputDeviceId, + info: output, + behavior: outputBehavior + } + } + } + + gearUtils.loadDeviceInfo = function() { + + var operatingSystem = context.JK.GetOSAsString(); + // should return one of: + // * MacOSX_builtin + // * MACOSX_interface + // * Win32_wdm + // * Win32_asio + // * Win32_asio4all + // * Linux + function determineDeviceType(deviceId, displayName) { + if (operatingSystem == "MacOSX") { + if (displayName.toLowerCase().trim() == "built-in") { + return "MacOSX_builtin"; + } + else { + return "MacOSX_interface"; + } + } + else if (operatingSystem == "Win32") { + if (context.jamClient.FTUEIsMusicDeviceWDM(deviceId)) { + return "Win32_wdm"; + } + else if (displayName.toLowerCase().indexOf("asio4all") > -1) { + return "Win32_asio4all" + } + else { + return "Win32_asio"; + } + } + else { + return "Linux"; + } + } + + var devices = context.jamClient.FTUEGetAudioDevices(); + logger.debug("FTUEGetAudioDevices: " + JSON.stringify(devices)); + + var loadedDevices = {}; + + // augment these devices by determining their type + context._.each(devices.devices, function (device) { + + if (device.name == "JamKazam Virtual Monitor") { + return; + } + + var deviceInfo = {}; + + deviceInfo.id = device.guid; + deviceInfo.type = determineDeviceType(device.guid, device.display_name); + deviceInfo.displayType = AUDIO_DEVICE_BEHAVIOR[deviceInfo.type].display; + deviceInfo.displayName = device.display_name; + deviceInfo.inputCount = device.input_count; + deviceInfo.outputCount = device.output_count; + + loadedDevices[device.guid] = deviceInfo; + }) + + logger.debug(context.JK.dlen(loadedDevices) + " devices loaded.", loadedDevices); + + return loadedDevices; + } + + gearUtils.ftueSummary = function(operatingSystem, deviceInformation, selectedDeviceInfo, gearTest, frameBuffers, isAutomated) { + return { + os: operatingSystem, + version: context.jamClient.ClientUpdateVersion(), + success: gearTest.isGoodFtue(), + automated: isAutomated, + score: { + validLatencyScore: gearTest.isValidLatencyScore(), + validIOScore: gearTest.isValidIOScore(), + latencyScore: gearTest.getLatencyScore(), + ioScore : gearTest.getIOScore(), + }, + audioParameters: { + frameSize: frameBuffers.selectedFramesize(), + bufferIn: frameBuffers.selectedBufferIn(), + bufferOut: frameBuffers.selectedBufferOut(), + }, + devices: deviceInformation, + selectedDevice: selectedDeviceInfo + } + } + + gearUtils.postDiagnostic = function(operatingSystem, deviceInformation, selectedDeviceInfo, gearTest, frameBuffers, isAutomated) { + rest.createDiagnostic({ + type: 'GEAR_SELECTION', + data: { + logs: logger.logCache, + client_type: context.JK.clientType(), + client_id: + context.JK.JamServer.clientID, + summary:gearUtils.ftueSummary(operatingSystem, deviceInformation, selectedDeviceInfo, gearTest, frameBuffers, isAutomated)} + }); + } + +})(window, jQuery); \ No newline at end of file diff --git a/web/app/assets/javascripts/wizard/loopback/loopback_wizard.js b/web/app/assets/javascripts/wizard/loopback/loopback_wizard.js index 6202cb76f..7b25c96d2 100644 --- a/web/app/assets/javascripts/wizard/loopback/loopback_wizard.js +++ b/web/app/assets/javascripts/wizard/loopback/loopback_wizard.js @@ -2,7 +2,6 @@ "use strict"; - context.JK = context.JK || {}; context.JK.LoopbackWizard = function (app) { @@ -11,10 +10,11 @@ var $dialog = null; var wizard = null; var $wizardSteps = null; + var $self = $(this); - var step1 = new context.JK.Step1(app, this); - var step2 = new context.JK.Step2(app, this); - var step3 = new context.JK.Step3(app, this); + var step1 = new context.JK.StepLoopbackIntro(app, this); + var step2 = new context.JK.StepLoopbackTest(app, this); + var step3 = new context.JK.StepLoopbackResult(app, this); var STEPS = { 0: step1, @@ -32,7 +32,8 @@ function closeDialog() { wizard.onCloseDialog(); - app.layout.closeDialog('your-wizard'); + $self.triggerHandler('dialog_closed'); + app.layout.closeDialog('loopback-wizard'); } function setNextState(enabled) { @@ -60,6 +61,9 @@ return false; } + function getGearTest() { + return step2.getGearTest(); + } function events() { $(wizard).on('step_changed', onStepChanged); @@ -70,16 +74,14 @@ function initialize() { - var dialogBindings = { beforeShow: beforeShow, afterHide: afterHide }; - app.bindDialog('your-wizard', dialogBindings); - $dialog = $('#your-wizard'); + app.bindDialog('loopback-wizard', dialogBindings); + $dialog = $('#loopback-wizard-dialog'); $wizardSteps = $dialog.find('.wizard-step'); step1.initialize($wizardSteps.filter($('[layout-wizard-step=0]'))); step2.initialize($wizardSteps.filter($('[layout-wizard-step=1]'))); - step3.initialize($wizardSteps.filter($('[layout-wizard-step=2]'))); wizard = new context.JK.Wizard(app); wizard.initialize($dialog, $wizardSteps, STEPS); @@ -90,6 +92,7 @@ this.setNextState = setNextState; this.setBackState = setBackState; this.initialize = initialize; + this.getGearTest = getGearTest; return this; diff --git a/web/app/assets/javascripts/wizard/loopback/step_loopback_intro.js b/web/app/assets/javascripts/wizard/loopback/step_loopback_intro.js new file mode 100644 index 000000000..f6f8a4cf9 --- /dev/null +++ b/web/app/assets/javascripts/wizard/loopback/step_loopback_intro.js @@ -0,0 +1,37 @@ +(function (context, $) { + + "use strict"; + + context.JK = context.JK || {}; + context.JK.StepLoopbackIntro = function (app, $dialog) { + + var $step = null; + + 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 beforeShow() { + } + + function initialize(_$step) { + $step = _$step; + + $step.find('.ftue-video-link').on('click', videoLinkClicked); + } + + this.beforeShow = beforeShow; + this.initialize = initialize; + + return this; + } +})(window, jQuery); \ No newline at end of file diff --git a/web/app/assets/javascripts/wizard/loopback/step_loopback_result.js b/web/app/assets/javascripts/wizard/loopback/step_loopback_result.js new file mode 100644 index 000000000..f0adaa2ba --- /dev/null +++ b/web/app/assets/javascripts/wizard/loopback/step_loopback_result.js @@ -0,0 +1,22 @@ +(function (context, $) { + + "use strict"; + + context.JK = context.JK || {}; + context.JK.StepLoopbackResult = function (app, $dialog) { + + var logger = context.JK.logger; + var $step = null; + + function beforeShow() { + } + + function initialize(_$step) { + $step = _$step; + } + this.beforeShow = beforeShow; + this.initialize = initialize; + + return this; + } +})(window, jQuery); \ No newline at end of file diff --git a/web/app/assets/javascripts/wizard/loopback/step_loopback_test.js b/web/app/assets/javascripts/wizard/loopback/step_loopback_test.js new file mode 100644 index 000000000..be6843dad --- /dev/null +++ b/web/app/assets/javascripts/wizard/loopback/step_loopback_test.js @@ -0,0 +1,373 @@ +(function (context, $) { + + "use strict"; + + context.JK = context.JK || {}; + context.JK.StepLoopbackTest = function (app, dialog) { + + var ASSIGNMENT = context.JK.ASSIGNMENT; + var AUDIO_DEVICE_BEHAVIOR = context.JK.AUDIO_DEVICE_BEHAVIOR; + var gearUtils = context.JK.GearUtils; + + var logger = context.JK.logger; + var $step = null; + var frameBuffers = new context.JK.FrameBuffers(app); + var gearTest = new context.JK.GearTest(app); + var deviceInformation = null; + var operatingSystem = null; + var selectedDeviceInfo = null; + var musicPorts = null; + + var $asioInputControlBtn = null; + var $asioOutputControlBtn = null; + var $resyncBtn = null; + var $runTestBtn = null; + var $audioInputDevice = null; + var $audioOutputDevice = null; + var $inputChannels = null; + var $outputChannels = null; + var $templateAudioPort = null; + var $scoreReport = null; + + var faderMap = { + 'loopback-audio-input-fader': jamClient.FTUESetInputVolume, + 'loopback-audio-output-fader': jamClient.FTUESetOutputVolume + }; + + var faderReadMap = { + 'loopback-audio-input-fader': jamClient.FTUEGetInputVolume, + 'loopback-audio-output-fader': jamClient.FTUEGetOutputVolume + }; + + + + function attemptScore() { + gearTest.attemptScore(); + } + + + function invalidateScore() { + gearTest.invalidateScore(); + initializeNextButtonState(); + } + + 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()); + } + + function onFramesizeChanged() { + context.JK.prodBubble($resyncBtn, 'push-resync-when-done', {}, {positions:['top']}); + updateDefaultBuffers(); + jamClient.FTUESetFrameSize(frameBuffers.selectedFramesize()); + invalidateScore(); + } + + function onBufferInChanged() { + context.JK.prodBubble($resyncBtn, 'push-resync-when-done', {}, {positions:['top']}); + jamClient.FTUESetInputLatency(frameBuffers.selectedBufferIn()); + invalidateScore(); + } + + function onBufferOutChanged() { + context.JK.prodBubble($resyncBtn, 'push-resync-when-done', {}, {positions:['top']}); + jamClient.FTUESetOutputLatency(frameBuffers.selectedBufferOut()); + invalidateScore(); + } + + function freezeAudioInteraction() { + logger.debug("loopback: freezing audio interaction"); + frameBuffers.disable(); + $asioInputControlBtn.on("click", false); + $asioOutputControlBtn.on("click", false); + $resyncBtn.on('click', false); + $runTestBtn.on('click', false); + } + + function unfreezeAudioInteraction() { + logger.debug("unfreezing audio interaction"); + frameBuffers.enable(); + $asioInputControlBtn.off("click", false); + $asioOutputControlBtn.off("click", false); + $resyncBtn.off('click', false); + $runTestBtn.off('click', false); + } + + function getGearTest() { + return gearTest; + } + + function handleNext() { + return true; + } + + function handleBack() { + return true; + } + + + function render(){ + + if(selectedDeviceInfo) { + var inputBehavior = selectedDeviceInfo.input.behavior; + var outputBehavior = selectedDeviceInfo.output.behavior; + } + else { + var inputBehavior = null; + var outputBehavior = null; + + } + + // handle ASIO visibility + if (inputBehavior) { + if (inputBehavior.showASIO) { + $asioInputControlBtn.show(); + } + else { + $asioInputControlBtn.hide(); + } + if(outputBehavior.showASIO && (selectedDeviceInfo.input.id != selectedDeviceInfo.output.id)) { + $asioOutputControlBtn.show(); + } + else { + $asioOutputControlBtn.hide(); + } + } + else { + // show no ASIO buttons + $asioInputControlBtn.hide(); + $asioOutputControlBtn.hide(); + } + + if(selectedDeviceInfo) { + $audioInputDevice.text(selectedDeviceInfo.input.info.displayName); + $audioOutputDevice.text(selectedDeviceInfo.output.info.displayName); + } + else { + $audioInputDevice.text('Unassigned'); + $audioOutputDevice.text('Unassigned'); + } + + $inputChannels.empty(); + context._.each(musicPorts.inputs, function (inputChannel) { + var $inputChannel = $(context._.template($templateAudioPort.html(), inputChannel, { variable: 'data' })); + var $checkbox = $inputChannel.find('input'); + if (gearUtils.isChannelAssigned(inputChannel)) { + $checkbox.attr('checked', 'checked'); + } + context.JK.checkbox($checkbox).iCheck('disable'); + $inputChannels.append($inputChannel); + }); + + $outputChannels.empty(); + context._.each(musicPorts.outputs, function (outputChannel) { + var $outputPort = $(context._.template($templateAudioPort.html(), outputChannel, { variable: 'data' })); + var $checkbox = $outputPort.find('input'); + if (gearUtils.isChannelAssigned(outputChannel)) { + $checkbox.attr('checked', 'checked'); + } + context.JK.checkbox($checkbox).iCheck('disable'); + $outputChannels.append($outputPort); + }); + + initializeVUMeters(); + renderVolumes(); + registerVuCallbacks(); + } + + function initializeASIOButtons() { + $asioInputControlBtn.unbind('click').click(function () { + context.jamClient.FTUEOpenControlPanel(selectedDeviceInfo.input.id); + }); + $asioOutputControlBtn.unbind('click').click(function () { + context.jamClient.FTUEOpenControlPanel(selectedDeviceInfo.output.id); + }); + } + + + function initializeResync() { + $resyncBtn.unbind('click').click(function () { + attemptScore(); + return false; + }) + } + + function initializeVUMeters() { + var vuMeters = [ + '#loopback-audio-input-vu-left', + '#loopback-audio-input-vu-right', + '#loopback-audio-output-vu-left', + '#loopback-audio-output-vu-right' + ]; + $.each(vuMeters, function () { + context.JK.VuHelpers.renderVU(this, + {vuType: "horizontal", lightCount: 12, lightWidth: 15, lightHeight: 3}); + }); + + var faders = context._.keys(faderMap); + $.each(faders, function () { + var fid = this; + context.JK.FaderHelpers.renderFader('#' + fid, + {faderId: fid, faderType: "horizontal", width: 163}); + context.JK.FaderHelpers.subscribe(fid, faderChange); + }); + } + + // renders volumes based on what the backend says + function renderVolumes() { + $.each(context._.keys(faderReadMap), function (index, faderId) { + // faderChange takes a value from 0-100 + var $fader = $('[fader-id="' + faderId + '"]'); + + var db = faderReadMap[faderId](); + var faderPct = db + 80; + context.JK.FaderHelpers.setHandlePosition($fader, faderPct); + }); + } + + function faderChange(faderId, newValue, dragging) { + var setFunction = faderMap[faderId]; + // TODO - using hardcoded range of -80 to 20 for output levels. + var mixerLevel = newValue - 80; // Convert our [0-100] to [-80 - +20] range + setFunction(mixerLevel); + } + + function registerVuCallbacks() { + logger.debug("loopback-wizard: registering vu callbacks"); + jamClient.FTUERegisterVUCallbacks( + "JK.loopbackAudioOutputVUCallback", + "JK.loopbackAudioInputVUCallback", + "JK.loopbackChatInputVUCallback" + ); + jamClient.SetVURefreshRate(200); + } + + function initializeNextButtonState() { + dialog.setNextState(gearTest.isGoodFtue()); + } + + function initializeBackButtonState() { + dialog.setBackState(!gearTest.isScoring()); + } + + function renderScoringStarted() { + initializeBackButtonState(); + initializeNextButtonState(); + freezeAudioInteraction(); + } + + function renderScoringStopped() { + initializeNextButtonState(); + initializeBackButtonState(); + unfreezeAudioInteraction(); + } + + function onGearTestStarted(e, data) { + renderScoringStarted(); + } + + function onGearTestDone(e, data) { + renderScoringStopped(); + gearUtils.postDiagnostic(operatingSystem, deviceInformation, selectedDeviceInfo, gearTest, frameBuffers, true); + } + + function onGearTestFail(e, data) { + renderScoringStopped(); + gearUtils.postDiagnostic(operatingSystem, deviceInformation, selectedDeviceInfo, gearTest, frameBuffers, true); + } + + function beforeShow() { + selectedDeviceInfo = gearUtils.selectedDeviceInfo(context.jamClient.FTUEGetInputMusicDevice(), context.jamClient.FTUEGetOutputMusicDevice()); + deviceInformation = gearUtils.loadDeviceInfo(); + musicPorts = jamClient.FTUEGetChannels(); + + render(); + } + + function beforeHide() { + logger.debug("loopback-wizard: unregistering vu callbacks"); + jamClient.FTUERegisterVUCallbacks('', '', ''); + } + + function initialize(_$step) { + $step = _$step; + + $asioInputControlBtn = $step.find('.asio-settings-input-btn'); + $asioOutputControlBtn = $step.find('.asio-settings-output-btn'); + $resyncBtn = $step.find('.resync-btn'); + $runTestBtn = $step.find('.run-test-btn'); + $audioInputDevice = $step.find('.audio-device.input') + $audioOutputDevice = $step.find('.audio-device.output') + $inputChannels = $step.find('.input-ports') + $outputChannels = $step.find('.output-ports') + $templateAudioPort = $('#template-audio-port'); + $scoreReport = $step.find('.results'); + operatingSystem = context.JK.GetOSAsString(); + + frameBuffers.initialize($step.find('.frame-and-buffers')); + $(frameBuffers) + .on(frameBuffers.FRAMESIZE_CHANGED, onFramesizeChanged) + .on(frameBuffers.BUFFER_IN_CHANGED, onBufferInChanged) + .on(frameBuffers.BUFFER_OUT_CHANGED, onBufferOutChanged) + + + gearTest.initialize($scoreReport, false) + $(gearTest) + .on(gearTest.GEAR_TEST_START, onGearTestStarted) + .on(gearTest.GEAR_TEST_DONE, onGearTestDone) + .on(gearTest.GEAR_TEST_FAIL, onGearTestFail) + + $runTestBtn.click(attemptScore); + + initializeASIOButtons(); + initializeResync(); + } + + context.JK.loopbackAudioInputVUCallback = function (dbValue) { + context.JK.ftueVUCallback(dbValue, '#loopback-audio-input-vu-left'); + context.JK.ftueVUCallback(dbValue, '#loopback-audio-input-vu-right'); + }; + context.JK.loopbackAudioOutputVUCallback = function (dbValue) { + context.JK.ftueVUCallback(dbValue, '#loopback-audio-output-vu-left'); + context.JK.ftueVUCallback(dbValue, '#loopback-audio-output-vu-right'); + }; + context.JK.loopbackChatInputVUCallback = function (dbValue) { + }; + + this.getGearTest = getGearTest; + this.handleNext = handleNext; + this.handleBack = handleBack; + this.beforeShow = beforeShow; + this.beforeHide = beforeHide; + this.initialize = initialize; + + return this; + } +})(window, jQuery); \ No newline at end of file diff --git a/web/app/assets/javascripts/wizard/wizard.js b/web/app/assets/javascripts/wizard/wizard.js index 88e53a630..05a30fd8c 100644 --- a/web/app/assets/javascripts/wizard/wizard.js +++ b/web/app/assets/javascripts/wizard/wizard.js @@ -13,11 +13,13 @@ var $wizardSteps = null; var $currentWizardStep = null; var $wizardButtons = null; + var options = {}; var $btnHelp = null; var $btnNext = null; var $btnBack = null; var $btnClose = null; var $btnCancel = null; + var dialogName = null; var self = this; var $self = $(this); @@ -83,7 +85,7 @@ $currentWizardStep = $nextWizardStep; - context.JK.GA.virtualPageView(location.pathname + location.search + location.hash + '/d1=' + step, $currentWizardStep.attr('dialog-title')); + context.JK.GA.virtualPageView('/client/' + dialogName, dialogName + ": " + $currentWizardStep.attr('dialog-title')); $self.triggerHandler('step_changed', {step:step}); @@ -150,6 +152,10 @@ step = null; } + function getNextButton() { + return $btnNext; + } + function setNextState(enabled) { if(!$btnNext) return; @@ -192,15 +198,21 @@ return $currentWizardStep; } - function initialize(_$dialog, _$wizardSteps, _STEPS) { + function initialize(_$dialog, _$wizardSteps, _STEPS, _options) { $dialog = _$dialog; + dialogName = $dialog.attr('layout-id'); + if (!dialogName) throw "no dialog name (layout-id) in wizard"; $wizardSteps = _$wizardSteps; STEPS = _STEPS; $wizardButtons = $dialog.find('.wizard-buttons'); $templateButtons = $('#template-wizard-buttons'); + + if(_options) {_options = {}}; + options = _options; } + this.getNextButton = getNextButton; this.setNextState = setNextState; this.setBackState = setBackState; this.getCurrentStep = getCurrentStep; diff --git a/web/app/assets/stylesheets/client/client.css b/web/app/assets/stylesheets/client/client.css index c267797f0..e1d7a51fe 100644 --- a/web/app/assets/stylesheets/client/client.css +++ b/web/app/assets/stylesheets/client/client.css @@ -34,8 +34,11 @@ *= require ./search *= require ./ftue *= require ./jamServer + *= require ./wizard/gearResults + *= require ./wizard/framebuffers *= require ./wizard/wizard *= require ./wizard/gearWizard + *= require ./wizard/loopbackWizard *= require ./whatsNextDialog *= require ./invitationDialog *= require ./shareDialog diff --git a/web/app/assets/stylesheets/client/wizard/framebuffers.css.scss b/web/app/assets/stylesheets/client/wizard/framebuffers.css.scss new file mode 100644 index 000000000..120196378 --- /dev/null +++ b/web/app/assets/stylesheets/client/wizard/framebuffers.css.scss @@ -0,0 +1,40 @@ +.dialog { + + .framesize { + float:left; + margin-right:10px; + width:30%; + } + + .select-buffer-in { + width:45%; + } + + .select-buffer-out { + width:45%; + } + + + .buffers { + float:left; + width:60%; + + h2 { + margin-left:5px; + } + + .dropdown-container { + width:48px; + } + .easydropdown-wrapper:nth-of-type(1) { + left:5px; + } + .easydropdown-wrapper:nth-of-type(2) { + left:35px; + } + .easydropdown, .easydropdown-wrapper { + width:30px; + } + } + +} \ No newline at end of file diff --git a/web/app/assets/stylesheets/client/wizard/gearResults.css.scss b/web/app/assets/stylesheets/client/wizard/gearResults.css.scss new file mode 100644 index 000000000..44be59140 --- /dev/null +++ b/web/app/assets/stylesheets/client/wizard/gearResults.css.scss @@ -0,0 +1,160 @@ +@import "client/common.css.scss"; +@charset "UTF-8"; + +.dialog { + .ftue-box.results { + height: 268px !important; + padding: 0; + @include border_box_sizing; + + span.conditional { + display:none; + } + &[data-type=automated] { + span.conditional[data-type=automated] { + display:inline; + } + } + &[data-type=loopback] { + span.conditional[data-type=loopback] { + display:inline; + } + } + .io, .latency { + display: none; + } + + .spinner-small { + display:none; + margin:auto; + } + + &[data-type="loopback"] .latency-score-section.starting { + .spinner-small { + display:block; + } + } + .loopback-button-holder { + width: 100%; + text-align: center; + } + a.loopback-test { + margin-top: 10px; + } + + .unknown-text { + display: none; + padding: 8px; + } + + .loopback-completed { + display:none; + } + + .scoring-section { + font-size: 15px; + @include border_box_sizing; + height: 64px; + + &.good { + background-color: #72a43b; + } + &.acceptable { + background-color: #cc9900; + } + &.bad { + background-color: #660000; + } + &.unknown, &.skip { + background-color: #999; + } + &.skip { + .io-skip-msg { + display: inline; + } + } + } + + .io-countdown { + display: none; + padding-left: 19px; + position: relative; + + .secs { + position: absolute; + width: 19px; + left: 0; + } + } + + .io-skip-msg { + display: none; + } + + .io-rate { + display: none; + } + .io-var { + display: none; + } + + ul.results-text { + padding: 10px 8px; + + li { + display: none + } + + &[latency-score="unknown"] { + display: none; + } + &[latency-score="good"] li.latency-good { + display: list-item; + } + &[latency-score="acceptable"] li.latency-acceptable { + display: list-item; + } + &[latency-score="bad"] li.latency-bad { + display: list-item; + } + &[io-var-score="good"] li.io-var-good { + display: list-item; + } + &[io-var-score="acceptable"] li.io-var-acceptable { + display: list-item; + } + &[io-var-score="bad"] li.io-var-bad { + display: list-item; + } + &[io-rate-score="good"] li.io-rate-good { + display: list-item; + } + &[io-rate-score="acceptable"] li.io-rate-acceptable { + display: list-item; + } + &[io-rate-score="bad"] li.io-rate-bad { + display: list-item; + } + &[scored="complete"] { + li.success { + display: list-item; + } + + &[io-rate-score="bad"], &[io-var-score="bad"], &[latency-score="bad"] { + li.success { + display: none; + } + li.failure { + display: list-item; + } + } + + &[latency-score="unknown"] { + li.success { + display: none; + } + } + } + } + } +} \ No newline at end of file diff --git a/web/app/assets/stylesheets/client/wizard/gearWizard.css.scss b/web/app/assets/stylesheets/client/wizard/gearWizard.css.scss index 9264961d2..d73f4016e 100644 --- a/web/app/assets/stylesheets/client/wizard/gearWizard.css.scss +++ b/web/app/assets/stylesheets/client/wizard/gearWizard.css.scss @@ -1,9 +1,6 @@ -/* Custom Styles for the FTUE Dialogs */ - @import "client/common.css.scss"; @charset "UTF-8"; - .dialog.gear-wizard { min-height: 500px; max-height: 500px; @@ -160,43 +157,6 @@ margin-top:5px; } - .framesize { - float:left; - margin-right:10px; - width:30%; - } - - .select-buffer-in { - width:45%; - } - - .select-buffer-out { - width:45%; - } - - - .buffers { - float:left; - width:60%; - - h2 { - margin-left:5px; - } - - .dropdown-container { - width:48px; - } - .easydropdown-wrapper:nth-of-type(1) { - left:5px; - } - .easydropdown-wrapper:nth-of-type(2) { - left:35px; - } - .easydropdown, .easydropdown-wrapper { - width:30px; - } - } - .audio-port { white-space: nowrap; } @@ -205,137 +165,6 @@ margin-top:15px; } - - - .ftue-box.results { - height: 268px !important; - padding:0; - @include border_box_sizing; - - .io, .latency { - display:none; - } - - .loopback-button-holder { - width:100%; - text-align:center; - } - a.loopback-test { - margin-top:10px; - } - - .unknown-text { - display:none; - padding:8px; - } - - .scoring-section { - font-size:15px; - @include border_box_sizing; - height:64px; - - &.good { - background-color:#72a43b; - } - &.acceptable { - background-color:#cc9900; - } - &.bad { - background-color:#660000; - } - &.unknown, &.skip { - background-color:#999; - } - &.skip { - .io-skip-msg { - display:inline; - } - } - } - - .io-countdown { - display:none; - padding-left:19px; - position:relative; - - .secs { - position:absolute; - width:19px; - left:0; - } - } - - .io-skip-msg { - display:none; - } - - .io-rate { - display:none; - } - .io-var { - display:none; - } - - ul.results-text { - padding:10px 8px; - - li { - display:none - } - - &[latency-score="unknown"] { - display:none; - } - &[latency-score="good"] li.latency-good { - display:list-item; - } - &[latency-score="acceptable"] li.latency-acceptable { - display:list-item; - } - &[latency-score="bad"] li.latency-bad { - display:list-item; - } - &[io-var-score="good"] li.io-var-good { - display:list-item; - } - &[io-var-score="acceptable"] li.io-var-acceptable { - display:list-item; - } - &[io-var-score="bad"] li.io-var-bad { - display:list-item; - } - &[io-rate-score="good"] li.io-rate-good { - display:list-item; - } - &[io-rate-score="acceptable"] li.io-rate-acceptable { - display:list-item; - } - &[io-rate-score="bad"] li.io-rate-bad { - display:list-item; - } - &[scored="complete"] { - li.success { - display:list-item; - } - - &[io-rate-score="bad"], &[io-var-score="bad"], &[latency-score="bad"]{ - li.success { - display:none; - } - li.failure { - display:list-item; - } - } - - &[latency-score="unknown"] { - li.success { - display:none; - } - } - } - } - } - .audio-output { display:none; } diff --git a/web/app/assets/stylesheets/client/wizard/loopbackWizard.css.scss b/web/app/assets/stylesheets/client/wizard/loopbackWizard.css.scss new file mode 100644 index 000000000..1be48b04d --- /dev/null +++ b/web/app/assets/stylesheets/client/wizard/loopbackWizard.css.scss @@ -0,0 +1,148 @@ +@import "client/common.css.scss"; +@charset "UTF-8"; + + +.dialog.loopback-wizard { + min-height: 500px; + max-height: 500px; + width:800px; + + h2 { + color: #FFFFFF; + font-size: 15px; + font-weight: normal; + margin-bottom: 6px; + white-space:nowrap; + } + .ftue-box { + background-color: #222222; + font-size: 13px; + padding: 8px; + } + + .ftue-inner { + line-height: 1.3em; + width:800px; + padding:25px; + font-size:15px; + color:#aaa; + @include border_box_sizing; + + } + + .wizard-step[layout-wizard-step="0"] { + p.intro { + margin-top:0px; + } + + ul { + margin:0; + padding:0px; + margin-top:70px; + } + + li { + text-align:center; + width: 31%; + height: 170px; + margin:0px; + /*padding:0px;*/ + list-style: none; + float:left; + } + li.first { + width: 34%; + } + } + + .wizard-step[layout-wizard-step="1"] { + + .wizard-step-content .wizard-step-column { + + &:nth-of-type(1), &:nth-of-type(2) { + width:31%; + } + + &:nth-of-type(3) { + width:25%; + float:right; + } + } + + a.button-orange { + margin-left:3px; + } + + .ftue-controls { + margin-top: 16px; + position:relative; + height: 48px; + width: 220px; + background-color: #222; + } + .ftue-vu-left { + position:relative; + top: 0px; + } + .ftue-vu-right { + position:relative; + top: 22px; + } + .ftue-fader { + position:relative; + top: 14px; + left: 8px; + } + .gain-label { + color: $ColorScreenPrimary; + position:absolute; + top: 14px; + right: 6px; + } + + .ports-header { + margin-top:20px; + } + + .ports { + height:100px; + } + + .asio-settings-input-btn, .asio-settings-output-btn { + position:absolute; + right:5px; + } + + .resync-btn { + margin-top:35px; + } + + .run-test-btn { + text-align:center; + display:inline-block; + margin-top:10px; + } + + .frame-and-buffers { + margin-top:10px; + } + .test-results-header { + + } + + .select-frame-size { + margin-left:-2px; + } + + .ftue-box.results { + margin-top:20px; + height: 200px !important; + padding:0; + @include border_box_sizing; + } + + h2.results-text-header { + margin-top:5px; + } + } +} diff --git a/web/app/views/clients/_account_audio_profile.html.erb b/web/app/views/clients/_account_audio_profile.html.erb index 72570a67f..59181ca9e 100644 --- a/web/app/views/clients/_account_audio_profile.html.erb +++ b/web/app/views/clients/_account_audio_profile.html.erb @@ -50,6 +50,9 @@ {{profile.id}} {{profile.active_text}} ACTIVATE + {% if(data.is_admin) { %} + LOOPBACK (admin only) + {% } %} DELETE {% } %} diff --git a/web/app/views/clients/_help.html.erb b/web/app/views/clients/_help.html.erb index 8da09fa38..4d6d5ad1c 100644 --- a/web/app/views/clients/_help.html.erb +++ b/web/app/views/clients/_help.html.erb @@ -28,4 +28,8 @@ + + \ No newline at end of file diff --git a/web/app/views/clients/gear/_buttons.html.haml b/web/app/views/clients/gear/_buttons.html.haml deleted file mode 100644 index 3ab9f4629..000000000 --- a/web/app/views/clients/gear/_buttons.html.haml +++ /dev/null @@ -1,20 +0,0 @@ - -- show_back ||= local_assigns[:show_back] = local_assigns.fetch(:show_back, true) -- show_next ||= local_assigns[:show_next] = local_assigns.fetch(:show_next, true) - -- total_steps = 7 - -.wizard-buttons - .wizard-buttons-holder - %a.button-grey.btn-help{href: '#'} HELP - - if step > 0 && step != total_steps - %a.button-orange.btn-back{href:'#'} BACK - - if step != total_steps - %a.button-orange.btn-next{href:'#'} NEXT - - if step == total_steps - %a.button-orange.btn-close{href:'#', 'layout-action' => 'close'} CLOSE - - if step == total_steps - %a.button-orange.btn-cancel{href:'#', 'layout-action' => 'close'} CANCEL - - - diff --git a/web/app/views/clients/index.html.erb b/web/app/views/clients/index.html.erb index 513d49d75..65f761cf2 100644 --- a/web/app/views/clients/index.html.erb +++ b/web/app/views/clients/index.html.erb @@ -19,7 +19,9 @@ <%= render "ftue" %> <%= render "jamServer" %> <%= render "iconInstrumentSelect" %> -<%= render "clients/gear/gear_wizard" %> +<%= render "clients/wizard/buttons" %> +<%= render "clients/wizard/gear/gear_wizard" %> +<%= render "clients/wizard/loopback/loopback_wizard" %> <%= render "terms" %> <%= render "leaveSessionWarning" %> <%= render "rateSession" %> @@ -235,11 +237,16 @@ var whatsNextDialog = new JK.WhatsNextDialog(JK.app); whatsNextDialog.initialize(invitationDialog); - var ftueWizard = new JK.FtueWizard(JK.app); - ftueWizard.initialize(); + //var ftueWizard = new JK.FtueWizard(JK.app); + //ftueWizard.initialize(); + + + var loopbackWizard = new JK.LoopbackWizard(JK.app); + loopbackWizard.initialize(); var gearWizard = new JK.GearWizard(JK.app); - gearWizard.initialize(); + gearWizard.initialize(loopbackWizard); + var testBridgeScreen = new JK.TestBridgeScreen(JK.app); testBridgeScreen.initialize(); diff --git a/web/app/views/clients/wizard/_buttons.html.haml b/web/app/views/clients/wizard/_buttons.html.haml new file mode 100644 index 000000000..dbc90ae54 --- /dev/null +++ b/web/app/views/clients/wizard/_buttons.html.haml @@ -0,0 +1,10 @@ +%script{type: 'text/template', id: 'template-wizard-buttons'} + .wizard-buttons-holder + %a.button-grey.btn-cancel{href:'#', 'layout-action' => 'close'} CANCEL + %a.button-grey.btn-help{href: '#'} HELP + %a.button-orange.btn-back{href:'#'} BACK + %a.button-orange.btn-next{href:'#'} NEXT + %a.button-orange.btn-close{href:'#', 'layout-action' => 'close'} CLOSE + + + diff --git a/web/app/views/clients/wizard/_framebuffers.html.haml b/web/app/views/clients/wizard/_framebuffers.html.haml new file mode 100644 index 000000000..f8ca37dd0 --- /dev/null +++ b/web/app/views/clients/wizard/_framebuffers.html.haml @@ -0,0 +1,33 @@ +.frame-and-buffers + .framesize + %h2 Frame + %select.select-frame-size + %option{val:'2.5'} 2.5 + %option{val:'5'} 5 + %option{val:'10'} 10 + .buffers + %h2 Buffer In/Out + %select.select-buffer-in + %option{val:'0'} 0 + %option{val:'1'} 1 + %option{val:'2'} 2 + %option{val:'3'} 3 + %option{val:'4'} 4 + %option{val:'5'} 5 + %option{val:'6'} 6 + %option{val:'7'} 7 + %option{val:'8'} 8 + %option{val:'9'} 9 + %option{val:'10'} 10 + %select.select-buffer-out + %option{val:'0'} 0 + %option{val:'1'} 1 + %option{val:'2'} 2 + %option{val:'3'} 3 + %option{val:'4'} 4 + %option{val:'5'} 5 + %option{val:'6'} 6 + %option{val:'7'} 7 + %option{val:'8'} 8 + %option{val:'9'} 9 + %option{val:'10'} 10 \ No newline at end of file diff --git a/web/app/views/clients/wizard/_gear_test.html.haml b/web/app/views/clients/wizard/_gear_test.html.haml new file mode 100644 index 000000000..5d79bcfe4 --- /dev/null +++ b/web/app/views/clients/wizard/_gear_test.html.haml @@ -0,0 +1,41 @@ +.ftue-box.results{'data-type' => test_type} + .left.w50.center.white.scoring-section.latency-score-section + .p5 + .latency LATENCY + %span.latency-score + .spinner-small + .left.w50.center.white.scoring-section.io-score-section + .p5 + .io I/O + %span.io-skip-msg + Not Tested + %span.io-countdown + %span.secs + seconds left + %span.io-rate< + Rate= + %span.io-rate-score> + %span.io-var< + Var= + %span.io-var-score> + .clearall + %ul.results-text + %li.latency-good Your latency is good. + %li.latency-acceptable Your latency is acceptable. + %li.latency-bad Your latency is poor. + %li.io-rate-good Your I/O rate is good. + %li.io-rate-acceptable Your I/O rate is acceptable. + %li.io-rate-bad Your I/O rate is poor. + %li.io-var-good Your I/O variance is good. + %li.io-var-acceptable Your I/O variance is acceptable. + %li.io-var-bad Your I/O variance is poor. + %li.success You may proceed to the next step. + %li.failure + %span.conditional{'data-type' => 'automated'} We're sorry, but your audio gear has failed. Please watch video or click HELP button below. + %span.conditional{'data-type' => 'loopback'} We're sorry, but your audio gear has failed. Please watch videos on the previous screen. + .unknown-text + %div We cannot accurately predict the latency of your audio gear. To proceed, you must run an audio loopback test. Click button below to do this. + %div.loopback-button-holder + %a.button-orange.loopback-test{href:'#'} RUN LOOPBACK TEST + .loopback-completed + You have completed the loopback test successfully. Click Next to continue. \ No newline at end of file diff --git a/web/app/views/clients/gear/_gear_wizard.html.haml b/web/app/views/clients/wizard/gear/_gear_wizard.html.haml similarity index 79% rename from web/app/views/clients/gear/_gear_wizard.html.haml rename to web/app/views/clients/wizard/gear/_gear_wizard.html.haml index 56fd3cd0a..ea9fc4dd5 100644 --- a/web/app/views/clients/gear/_gear_wizard.html.haml +++ b/web/app/views/clients/wizard/gear/_gear_wizard.html.haml @@ -54,77 +54,10 @@ %h2.audio-channels Audio Output Ports .ftue-box.list.ports.output-ports %a.button-orange.asio-settings-output-btn ASIO SETTINGS... - .frame-and-buffers - .framesize - %h2 Frame - %select.select-frame-size - %option{val:'2.5'} 2.5 - %option{val:'5'} 5 - %option{val:'10'} 10 - .buffers - %h2 Buffer In/Out - %select.select-buffer-in - %option{val:'0'} 0 - %option{val:'1'} 1 - %option{val:'2'} 2 - %option{val:'3'} 3 - %option{val:'4'} 4 - %option{val:'5'} 5 - %option{val:'6'} 6 - %option{val:'7'} 7 - %option{val:'8'} 8 - %option{val:'9'} 9 - %option{val:'10'} 10 - %select.select-buffer-out - %option{val:'0'} 0 - %option{val:'1'} 1 - %option{val:'2'} 2 - %option{val:'3'} 3 - %option{val:'4'} 4 - %option{val:'5'} 5 - %option{val:'6'} 6 - %option{val:'7'} 7 - %option{val:'8'} 8 - %option{val:'9'} 9 - %option{val:'10'} 10 + = render :partial => "/clients/wizard/framebuffers" .wizard-step-column %h2 Test Results - .ftue-box.results - .left.w50.center.white.scoring-section.latency-score-section - .p5 - .latency LATENCY - %span.latency-score - .left.w50.center.white.scoring-section.io-score-section - .p5 - .io I/O - %span.io-skip-msg - Not Tested - %span.io-countdown - %span.secs - seconds left - %span.io-rate< - Rate= - %span.io-rate-score> - %span.io-var< - Var= - %span.io-var-score> - .clearall - %ul.results-text - %li.latency-good Your latency is good. - %li.latency-acceptable Your latency is acceptable. - %li.latency-bad Your latency is poor. - %li.io-rate-good Your I/O rate is good. - %li.io-rate-acceptable Your I/O rate is acceptable. - %li.io-rate-bad Your I/O rate is poor. - %li.io-var-good Your I/O variance is good. - %li.io-var-acceptable Your I/O variance is acceptable. - %li.io-var-bad Your I/O variance is poor. - %li.success You may proceed to the next step. - %li.failure We're sorry, but your audio gear has failed. Please watch video or click HELP button below. - .unknown-text - %div We cannot accurately predict the latency of your audio gear. To proceed, you must run an audio loopback test. Click button below to do this. - %div.loopback-button-holder - %a.button-orange.loopback-test{href:'#'} RUN LOOPBACK TEST + = render :partial => "/clients/wizard/gear_test", locals: {test_type: 'automated'} .clearall .wizard-step{ 'layout-wizard-step' => "2", 'dialog-title' => "Configure Tracks", 'dialog-purpose' => "ConfigureTracks" } diff --git a/web/app/views/clients/wizard/loopback/_loopback_wizard.html.haml b/web/app/views/clients/wizard/loopback/_loopback_wizard.html.haml new file mode 100644 index 000000000..3d9b18483 --- /dev/null +++ b/web/app/views/clients/wizard/loopback/_loopback_wizard.html.haml @@ -0,0 +1,81 @@ +.dialog.loopback-wizard{ layout: 'dialog', 'layout-id' => 'loopback-wizard', id: 'loopback-wizard-dialog'} + .content-head + %h1 loopback latency test + + .ftue-inner{ 'layout-wizard' => 'loopback-wizard' } + .wizard-step{ 'layout-wizard-step' => "0", 'dialog-title' => "Welcome", 'dialog-purpose' => "Intro"} + .help-text + 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. + + + %ul.device_type + %li.ftue-video-link.first{'external-link-win'=>"http://www.youtube.com/watch?v=b1JrwGeUcOo", 'external-link-mac'=>"http://www.youtube.com/watch?v=TRzb7OTlO-Q"} + AUDIO DEVICE WITH PORTS FOR INSTRUMENT OR MIC INPUT JACKS + %br + %p + = image_tag "content/audio_capture_ftue.png", {:width => 243, :height => 70} + + %li.ftue-video-link{'external-link-win'=>"http://www.youtube.com/watch?v=IDrLa8TOXwQ",'external-link-mac'=>"http://www.youtube.com/watch?v=vIs7ArrjMpE"} + USB MICROPHONE + %br + %p + = image_tag "content/microphone_ftue.png", {:width => 70, :height => 113} + %li.ftue-video-link{'external-link-win'=>"http://www.youtube.com/watch?v=PCri4Xed4CA",'external-link-mac'=>"http://www.youtube.com/watch?v=Gatmd_ja47U"} + COMPUTER'S BUILT-IN MIC & SPEAKERS/HEADPHONES + %br + %p + = image_tag "content/computer_ftue.png", {:width => 118, :height => 105} + + + .wizard-step{ 'layout-wizard-step' => "1", 'dialog-title' => "Verify Audio", 'dialog-purpose' => "VerifyAudio" } + .help-text + Your currently configured audio gear is shown below. Before proceeding to the next step of the loopback test, play and speak and use the gain sliders + so that you can clearly hear both your instrument and/or voice through your headphones. When done click Next. + + .wizard-step-content + .wizard-step-column + %h2 + Audio Input + %a.button-orange.asio-settings-input-btn ASIO SETTINGS + .audio-device.input Unknown + %h2.ports-header Audio Input Ports + .ftue-box.input-ports.ports + .ftue-controls + .ftue-vu-left#loopback-audio-input-vu-left + .ftue-fader#loopback-audio-input-fader + .gain-label GAIN + .ftue-vu-right#loopback-audio-input-vu-right + = render :partial => "/clients/wizard/framebuffers" + .wizard-step-column + %h2 + Audio Output + %a.button-orange.asio-settings-output-btn ASIO SETTINGS + .audio-device.output Unknown + %h2.ports-header Audio Output Ports + .ftue-box.output-ports.ports + .ftue-controls + .ftue-vu-left#loopback-audio-output-vu-left + .ftue-fader#loopback-audio-output-fader + .gain-label GAIN + .ftue-vu-right#loopback-audio-output-vu-right + %a.button-orange.resync-btn RESYNC + .wizard-step-column + %h2.test-results-header + Test & Results + %a.button-orange.run-test-btn RUN TEST + = render :partial => "/clients/wizard/gear_test", locals: {test_type: 'loopback'} + .clearall + + .wizard-step{ 'layout-wizard-step' => "2", 'dialog-title' => "Congratulations", 'dialog-purpose' => "Congratulations" } + .help-text + Congratulations! + %br + %br + You have successfully scored your audio gear using the loopback test. Close the dialog to proceed. + + .wizard-buttons \ No newline at end of file From 7fbc48f61e43010a9a8a1c6ce6798f0810de5ac1 Mon Sep 17 00:00:00 2001 From: Seth Call Date: Tue, 3 Jun 2014 16:29:09 -0500 Subject: [PATCH 07/32] * VRFS-1579 - check if getOperatingMode exists before calling it --- web/app/views/clients/index.html.erb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web/app/views/clients/index.html.erb b/web/app/views/clients/index.html.erb index 65f761cf2..f07fd3a8e 100644 --- a/web/app/views/clients/index.html.erb +++ b/web/app/views/clients/index.html.erb @@ -265,8 +265,8 @@ JK.initJamClient(); - // latency_tester does not want to be here - if(window.jamClient.getOperatingMode() == "server") { + // latency_tester does not want to be here - redirect it + if(window.jamClient.getOperatingMode && window.jamClient.getOperatingMode() == "server") { window.location = "/latency_tester"; return; } From 26f5006bc94a23c2dd57b9230299778abcaec515 Mon Sep 17 00:00:00 2001 From: Seth Call Date: Tue, 3 Jun 2014 16:47:20 -0500 Subject: [PATCH 08/32] * pinning rspec to compatible version --- websocket-gateway/Gemfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/websocket-gateway/Gemfile b/websocket-gateway/Gemfile index 912e4475f..7f62f3149 100644 --- a/websocket-gateway/Gemfile +++ b/websocket-gateway/Gemfile @@ -57,7 +57,7 @@ group :test do gem 'simplecov', '~> 0.7.1' gem 'simplecov-rcov' gem 'cucumber' - gem 'rspec' + gem 'rspec', '2.14.1' # can go to 3.0, but requires changes to tests due to should removal, be_truthy, and other changes gem 'factory_girl' #gem 'spork', '0.9.0' gem 'database_cleaner', '0.7.0' From 49e03a967c91b8e40928f39aa679714312324f36 Mon Sep 17 00:00:00 2001 From: Seth Call Date: Tue, 3 Jun 2014 16:57:13 -0500 Subject: [PATCH 09/32] * pinning rspec in web to fix tests --- web/Gemfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/Gemfile b/web/Gemfile index e8a9e2c7e..1c88e8a59 100644 --- a/web/Gemfile +++ b/web/Gemfile @@ -78,7 +78,7 @@ gem 'multi_json', '1.9.0' gem 'rest_client' group :development, :test do - gem 'rspec-rails' + gem 'rspec-rails', '2.14.2' gem "activerecord-import", "~> 0.4.1" gem 'guard-rspec', '0.5.5' gem 'jasmine', '1.3.1' From 84f1e7922378e59adc9e2d670d2c5ba7fb462842 Mon Sep 17 00:00:00 2001 From: Seth Call Date: Tue, 3 Jun 2014 17:45:48 -0500 Subject: [PATCH 10/32] * pin admin tests --- admin/Gemfile | 2 +- .../javascripts/configureTrackDialog.js | 119 ++++++++++++++++++ web/app/assets/stylesheets/client/client.css | 1 + .../client/configureTracksDialog.css.scss | 5 + .../_configure_tracks_dialog.html.haml | 24 ++++ web/app/views/clients/index.html.erb | 3 + 6 files changed, 153 insertions(+), 1 deletion(-) create mode 100644 web/app/assets/javascripts/configureTrackDialog.js create mode 100644 web/app/assets/stylesheets/client/configureTracksDialog.css.scss create mode 100644 web/app/views/clients/_configure_tracks_dialog.html.haml diff --git a/admin/Gemfile b/admin/Gemfile index 567f0e491..965a9d6fa 100644 --- a/admin/Gemfile +++ b/admin/Gemfile @@ -99,7 +99,7 @@ gem 'debugger' group :development, :test do gem 'capybara' - gem 'rspec-rails' + gem 'rspec-rails', '2.14.2' gem 'guard-rspec', '0.5.5' gem 'jasmine', '1.3.1' gem 'execjs', '1.4.0' diff --git a/web/app/assets/javascripts/configureTrackDialog.js b/web/app/assets/javascripts/configureTrackDialog.js new file mode 100644 index 000000000..0a144ad0f --- /dev/null +++ b/web/app/assets/javascripts/configureTrackDialog.js @@ -0,0 +1,119 @@ +(function (context, $) { + + "use strict"; + + context.JK = context.JK || {}; + context.JK.ConfigureTracksDialog = function (app) { + var logger = context.JK.logger; + var ASSIGNMENT = context.JK.ASSIGNMENT; + var VOICE_CHAT = context.JK.VOICE_CHAT; + + var $dialog = null; + var $instructions = null; + var $musicAudioTab = null; + var $musicAudioTabSelector = null; + var $voiceChatTab = null; + var $voiceChatTabSelector = null; + + var configure_audio_instructions = { + "Win32": "Choose the audio device you would like to use for this session. If needed, use arrow buttons to assign audio inputs " + + "to your tracks, to indicate what instrument you are playing on each track, and to assign audio outputs for listening. " + + "If you want to use a new audio device you have not tested/certified for latency using JamKazam, click the Add New Audio " + + "Gear button to test that device.", + + "MacOSX": "Choose the audio device you would like to use for this session. If needed, use arrow buttons to assign audio inputs " + + "to your tracks, to indicate what instrument you are playing on each track, and to assign audio outputs for listening. " + + "If you want to use a new audio device you have not tested/certified for latency using JamKazam, click the Add New Audio " + + "Gear button to test that device.", + + "Unix": "Choose the audio device you would like to use for this session. If needed, use arrow buttons to assign audio inputs " + + "to your tracks, to indicate what instrument you are playing on each track, and to assign audio outputs for listening. " + + "If you want to use a new audio device you have not tested/certified for latency using JamKazam, click the Add New Audio " + + "Gear button to test that device." + }; + + var configure_voice_instructions = "If you are using a microphone to capture your instrumental or vocal audio, you can simply use that mic " + + "for both music and chat. Otherwise, choose a device to use for voice chat, and use arrow buttons to " + + "select an input on that device."; + + function setInstructions(type) { + if (type === 'audio') { + var os = context.jamClient.GetOSAsString(); + $instructions.html(configure_audio_instructions[os]); + } + else if (type === 'voice') { + $instructions.html(configure_voice_instructions); + } + else { + throw "unknown type in setInstructions(" + type + ')'; + } + } + + function activateTab(type) { + if (type === 'voice') { + $musicAudioTab.hide(); + $voiceChatTab.show(); + + $musicAudioTabSelector.removeClass('selected'); + $voiceChatTabSelector.addClass('selected'); + } + else { + $musicAudioTab.show(); + $voiceChatTab.hide(); + + $musicAudioTabSelector.addClass('selected'); + $voiceChatTabSelector.removeClass('selected'); + } + } + + function validateVoiceChatSettings() { + return true; + } + + function showMusicAudioPanel() { + setInstructions('audio'); + activateTab('audio'); + + } + + function validateAudioSettings() { + return true; + } + + function showVoiceChatPanel() { + + } + + function events() { + $musicAudioTabSelector.click(function () { + // validate voice chat settings + if (validateVoiceChatSettings(true)) { + showMusicAudioPanel(false); + } + }); + + $voiceChatTabSelector.click(function () { + // validate audio settings + if (validateAudioSettings(true)) { + showVoiceChatPanel(false); + } + }); + } + + function initialize() { + + $dialog = $('#configure-tracks-dialog'); + $instructions = $dialog.find('.instructions span'); + $musicAudioTab = $dialog.find('div[tab-id="music-audio"]'); + $musicAudioTabSelector = $dialog.find('.tab-configure-audio'); + $voiceChatTab = $dialog.find('div[tab-id="voice-chat"]'); + $voiceChatTabSelector = $dialog.find('.tab-configure-voice'); + events(); + } + + this.initialize = initialize; + + return this; + }; + +})(window, jQuery); \ 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 e1d7a51fe..54747b4e1 100644 --- a/web/app/assets/stylesheets/client/client.css +++ b/web/app/assets/stylesheets/client/client.css @@ -43,6 +43,7 @@ *= require ./invitationDialog *= require ./shareDialog *= require ./hoverBubble + *= require ./configureTracksDialog *= require ./recordingFinishedDialog *= require ./localRecordingsDialog *= require ./serverErrorDialog diff --git a/web/app/assets/stylesheets/client/configureTracksDialog.css.scss b/web/app/assets/stylesheets/client/configureTracksDialog.css.scss new file mode 100644 index 000000000..e04e64176 --- /dev/null +++ b/web/app/assets/stylesheets/client/configureTracksDialog.css.scss @@ -0,0 +1,5 @@ +#configure-tracks-dialog { + min-height: 500px; + max-height: 500px; + width:800px; +} \ No newline at end of file diff --git a/web/app/views/clients/_configure_tracks_dialog.html.haml b/web/app/views/clients/_configure_tracks_dialog.html.haml new file mode 100644 index 000000000..ec437ff32 --- /dev/null +++ b/web/app/views/clients/_configure_tracks_dialog.html.haml @@ -0,0 +1,24 @@ +.dialog{ layout: 'dialog', 'layout-id' => 'configure-tracks', id: 'configure-tracks-dialog'} + .content-head + = image_tag "content/icon_add.png", {:width => 19, :height => 19, :class => 'content-icon' } + %h1 configure tracks + .dialog-inner + .dialog-tabs + %a.selected.tab-configure-audio Music Audio + %a.tab-configure-voice Voice Chat + + .instructions + %span + + .clearall + + .tab{'tab-id' => 'music-audio'} + eat it + + + .clearall + + + .tab{'tab-id' => 'voice-chat'} + + .clearall diff --git a/web/app/views/clients/index.html.erb b/web/app/views/clients/index.html.erb index f07fd3a8e..4093b3f72 100644 --- a/web/app/views/clients/index.html.erb +++ b/web/app/views/clients/index.html.erb @@ -47,6 +47,7 @@ <%= render "friendSelector" %> <%= render "account_profile_avatar" %> <%= render "account_audio_profile" %> +<%= render "configure_tracks_dialog" %> <%= render "invitationDialog" %> <%= render "inviteMusicians" %> <%= render "hoverBand" %> @@ -247,6 +248,8 @@ var gearWizard = new JK.GearWizard(JK.app); gearWizard.initialize(loopbackWizard); + var configureTracksDialog = new JK.ConfigureTracksDialog(JK.app); + configureTracksDialog.initialize(); var testBridgeScreen = new JK.TestBridgeScreen(JK.app); testBridgeScreen.initialize(); From 60c72f29dd6c20f17596ae25e80d2d28ad440fdb Mon Sep 17 00:00:00 2001 From: Seth Call Date: Wed, 4 Jun 2014 14:47:05 -0500 Subject: [PATCH 11/32] * VRFS-1753 - mostly done aside from prompt with dialog on create/find session when appropriate --- .../javascripts/configureTrackDialog.js | 18 +- web/app/assets/javascripts/networkTest.js | 515 +++++ .../assets/javascripts/networkTestDialog.js | 94 + web/app/assets/javascripts/user_dropdown.js | 6 +- .../wizard/gear/step_network_test.js | 440 +---- web/app/assets/stylesheets/client/client.css | 1 + .../stylesheets/client/content-orig.css.scss | 2 +- .../stylesheets/client/content.css.scss | 7 +- .../client/networkTestDialog.css.scss | 7 + .../client/wizard/gearWizard.css.scss | 1679 +++++++++-------- .../_configure_tracks_dialog.html.haml | 2 - .../clients/_networkTestDialog.html.haml | 10 + web/app/views/clients/_network_test.html.haml | 30 + web/app/views/clients/index.html.erb | 4 + .../wizard/gear/_gear_wizard.html.haml | 33 +- web/app/views/users/_user_dropdown.html.erb | 3 + web/spec/factories.rb | 20 + web/spec/features/gear_wizard_spec.rb | 3 +- web/spec/features/network_test_spec.rb | 41 + web/spec/support/utilities.rb | 6 +- 20 files changed, 1625 insertions(+), 1296 deletions(-) create mode 100644 web/app/assets/javascripts/networkTest.js create mode 100644 web/app/assets/javascripts/networkTestDialog.js create mode 100644 web/app/assets/stylesheets/client/networkTestDialog.css.scss create mode 100644 web/app/views/clients/_networkTestDialog.html.haml create mode 100644 web/app/views/clients/_network_test.html.haml create mode 100644 web/spec/features/network_test_spec.rb diff --git a/web/app/assets/javascripts/configureTrackDialog.js b/web/app/assets/javascripts/configureTrackDialog.js index 0a144ad0f..543ab3e32 100644 --- a/web/app/assets/javascripts/configureTrackDialog.js +++ b/web/app/assets/javascripts/configureTrackDialog.js @@ -81,7 +81,8 @@ } function showVoiceChatPanel() { - + setInstructions('voice'); + activateTab('voice'); } function events() { @@ -100,8 +101,23 @@ }); } + function beforeShow() { + showMusicAudioPanel(); + } + + function afterHide() { + + } + function initialize() { + var dialogBindings = { + 'beforeShow' : beforeShow, + 'afterHide': afterHide + }; + + app.bindDialog('configure-tracks', dialogBindings); + $dialog = $('#configure-tracks-dialog'); $instructions = $dialog.find('.instructions span'); $musicAudioTab = $dialog.find('div[tab-id="music-audio"]'); diff --git a/web/app/assets/javascripts/networkTest.js b/web/app/assets/javascripts/networkTest.js new file mode 100644 index 000000000..b9b8db12e --- /dev/null +++ b/web/app/assets/javascripts/networkTest.js @@ -0,0 +1,515 @@ +(function (context, $) { + + "use strict"; + + context.JK = context.JK || {}; + context.JK.NetworkTest = function (app) { + + var NETWORK_TEST_TYPES = { + RestPhase : 0, + PktTest100NormalLatency : 1, + PktTest200MediumLatency : 2, + PktTest400LowLatency : 3, + PktTestRateSweep : 4, + RcvOnly : 5 + } + var STARTING_NUM_CLIENTS = 4; + var PAYLOAD_SIZE = 100; + var MINIMUM_ACCEPTABLE_SESSION_SIZE = 2; + + var rest = context.JK.Rest(); + var logger = context.JK.logger; + var $step = null; + var TEST_SUCCESS_CALLBACK = 'JK.HandleNetworkTestSuccess'; + var TEST_TIMEOUT_CALLBACK = 'JK.HandleNetworkTestTimeout'; + + var $startNetworkTestBtn = null; + var $testResults = null; + var $testScore = null; + var $testText = null; + var testedSuccessfully = false; + var $scoringBar = null; + var $goodMarker = null; + var $goodLine = null; + var $currentScore = null; + var $scoredClients = null; + var $subscore = null; + var backendGuardTimeout = null; + + var serverClientId = ''; + var scoring = false; + var numClientsToTest = STARTING_NUM_CLIENTS; + var testSummary = {attempts : [], final:null} + var $self = $(this); + var scoringZoneWidth = 100;//px + var inGearWizard = false; + + var NETWORK_TEST_START = 'network_test.start'; + var NETWORK_TEST_DONE = 'network_test.done'; + var NETWORK_TEST_FAIL = 'network_test.fail'; + + function createSuccessCallbackName() { + if(inGearWizard) { + return TEST_SUCCESS_CALLBACK + 'ForGearWizard'; + } + else { + return TEST_SUCCESS_CALLBACK + 'ForDialog'; + } + } + + function createTimeoutCallbackName() { + if(inGearWizard) { + return TEST_TIMEOUT_CALLBACK + 'ForGearWizard'; + } + else { + return TEST_TIMEOUT_CALLBACK + 'ForDialog'; + } + } + + function reset() { + serverClientId = ''; + scoring = false; + numClientsToTest = STARTING_NUM_CLIENTS; + testSummary = {attempts : []}; + configureStartButton(); + $scoredClients.empty(); + $testResults.removeClass('good acceptable bad testing'); + $testText.empty(); + $subscore.empty(); + $currentScore.width(0); + } + + function renderStartTest() { + configureStartButton(); + $testResults.addClass('testing'); + $goodLine.css('left', (gon.ftue_packet_rate_treshold * 100) + '%'); + $goodMarker.css('left', (gon.ftue_packet_rate_treshold * 100) + '%'); + } + + function renderStopTest(score, text) { + $scoredClients.html(score); + $testText.html(text); + $testResults.removeClass('testing'); + } + + function postDiagnostic() { + rest.createDiagnostic({ + type: 'NETWORK_TEST_RESULT', + data: {client_type: context.JK.clientType(), client_id: context.JK.JamServer.clientID, summary:testSummary} + }); + } + + function appendContextualStatement() { + if(inGearWizard) { + return "You can skip this step for now." + } + else { + return ''; + } + } + function testFinished() { + var attempt = getCurrentAttempt(); + + if(!testSummary.final) { + testSummary.final = {reason : attempt.reason}; + } + + var reason = testSummary.final.reason; + var success = false; + + if(reason == "success") { + renderStopTest(attempt.num_clients, "Your router and Internet service will support sessions of up to " + attempt.num_clients + " JamKazam musicians.") + testedSuccessfully = true; + if(!testSummary.final.num_clients) { + testSummary.final.num_clients = attempt.num_clients; + } + context.JK.GA.trackNetworkTest(context.JK.detectOS(), testSummary.final.num_clients); + context.jamClient.SetNetworkTestScore(attempt.num_clients); + if(testSummary.final.num_clients == 2) { + $testResults.addClass('acceptable'); + } + else { + $testResults.addClass('good'); + } + success = true; + } + else if(reason == "minimum_client_threshold") { + context.jamClient.SetNetworkTestScore(0); + renderStopTest('', "We're sorry, but your router and Internet service will not effectively support JamKazam sessions. Please click the HELP button for more information.") + } + else if(reason == "unreachable") { + context.jamClient.SetNetworkTestScore(0); + renderStopTest('', "We're sorry, but your router will not support JamKazam in its current configuration. Please click the HELP button for more information."); + } + else if(reason == "internal_error") { + context.JK.alertSupportedNeeded("The JamKazam client software had an unexpected problem while scoring your Internet connection."); + renderStopTest('', ''); + } + else if(reason == "remote_peer_cant_test") { + context.JK.alertSupportedNeeded("The JamKazam service is experiencing technical difficulties."); + renderStopTest('', ''); + } + else if(reason == 'backend_gone') { + context.JK.alertSupportedNeeded("The JamKazam client is experiencing technical difficulties."); + renderStopTest('', ''); + } + else if(reason == "invalid_response") { + context.JK.alertSupportedNeeded("The JamKazam client software had an unexpected problem while scoring your Internet connection. Reason=" + attempt.backend_data.reason + '.'); + renderStopTest('', ''); + } + else if(reason == 'no_servers') { + context.JK.alertSupportedNeeded("No network test servers are available." + appendContextualStatement()); + renderStopTest('', ''); + testedSuccessfully = true; + } + else if(reason == 'no_network') { + context.JK.Banner.showAlert("Please try again later. Your network appears down."); + renderStopTest('', ''); + } + else if(reason == "rest_api_error") { + context.JK.alertSupportedNeeded("Unable to acquire a network test server." + appendContextualStatement()); + testedSuccessfully = true; + renderStopTest('', ''); + } + else if(reason == "timeout") { + context.JK.alertSupportedNeeded("Communication with a network test servers timed out." + appendContextualStatement()); + testedSuccessfully = true; + renderStopTest('', ''); + } + else { + context.JK.alertSupportedNeeded("The JamKazam client software had a logic error while scoring your Internet connection."); + renderStopTest('', ''); + } + + numClientsToTest = STARTING_NUM_CLIENTS; + scoring = false; + configureStartButton(); + postDiagnostic(); + + if(success) { + $self.triggerHandler(NETWORK_TEST_DONE) + } + else { + $self.triggerHandler(NETWORK_TEST_FAIL) + } + } + + function getCurrentAttempt() { + return testSummary.attempts[testSummary.attempts.length - 1]; + } + + function backendTimedOut() { + testSummary.final = {reason: 'backend_gone'} + testFinished(); + } + + function cancel() { + clearBackendGuard(); + } + function clearBackendGuard() { + if(backendGuardTimeout) { + clearTimeout(backendGuardTimeout); + backendGuardTimeout = null; + } + } + + function attemptTestPass() { + + var attempt = {}; + attempt.payload_size = PAYLOAD_SIZE; + attempt.duration = gon.ftue_network_test_duration; + attempt.test_type = 'PktTest400LowLatency'; + attempt.num_clients = numClientsToTest; + attempt.server_client_id = serverClientId; + attempt.received_progress = false; + testSummary.attempts.push(attempt); + + //context.jamClient.StopNetworkTest(''); + + $testText.text("Simulating the network traffic of a " + numClientsToTest + "-person session."); + + updateProgress(0, false); + + backendGuardTimeout = setTimeout(function(){backendTimedOut()}, (gon.ftue_network_test_duration + 1) * 1000); + + context.jamClient.TestNetworkPktBwRate(serverClientId, createSuccessCallbackName(), createTimeoutCallbackName(), + NETWORK_TEST_TYPES.PktTest400LowLatency, + gon.ftue_network_test_duration, + numClientsToTest, + PAYLOAD_SIZE); + } + + + function startNetworkTest(checkWireless) { + + if(scoring) return false; + + if(checkWireless) { + // check if on Wifi 1st + var isWireless = context.jamClient.IsMyNetworkWireless(); + if(isWireless == -1) { + logger.warn("unable to determine if the user is on wireless or not for network test. skipping prompt.") + } + else if(isWireless == 1) { + context.JK.Banner.showAlert({buttons: [ + {name: 'RUN NETWORK TEST ANYWAY', click: function() {startNetworkTest(false)}}, + {name: 'CANCEL', click: function() {}}], + html: "

It appears that your computer is connected to your network using WiFi.

" + + "

We strongly advise against running the JamKazam application on a WiFi connection. " + + "We recommend using a wired Ethernet connection from your computer to your router. " + + "A WiFi connection is likely to cause significant issues in both latency and audio quality.

"}) + return false; + } + } + + reset(); + scoring = true; + $self.triggerHandler(NETWORK_TEST_START); + renderStartTest(); + rest.getLatencyTester() + .done(function(response) { + // ensure there are no tests ongoing + + serverClientId = response.client_id; + + + testSummary.serverClientId = serverClientId; + + logger.info("beginning network test against client_id: " + serverClientId); + + attemptTestPass(); + }) + .fail(function(jqXHR) { + if(jqXHR.status == 404) { + // means there are no network testers available. + // we have to skip this part of the UI + testSummary.final = {reason: 'no_servers'} + } + else { + if(context.JK.isNetworkError(arguments)) { + testSummary.final = {reason: 'no_network'} + } + else { + testSummary.final = {reason: 'rest_api_error'} + } + } + testFinished(); + }) + logger.info("starting network test"); + return false; + } + + function updateProgress(throughput, showSubscore) { + var width = throughput * 100; + + $currentScore.stop().data('showSubscore', showSubscore); + + if(!showSubscore) { + $subscore.text(''); + } + + $currentScore.animate({ + duration: 1000, + width: width + '%' + }, { + step: function (now, fx) { + if(showSubscore) { + var newWidth = ( 100 * parseFloat($currentScore.css('width')) / parseFloat($currentScore.parent().css('width')) ); + $subscore.text((Math.round(newWidth * 10) / 10) + '%'); + } + } + }).css('overflow', 'visible'); + ; + } + + function networkTestSuccess(data) { + clearBackendGuard(); + + var attempt = getCurrentAttempt(); + + function refineTest(up) { + if(up) { + if(numClientsToTest == gon.ftue_network_test_max_clients) { + attempt.reason = "success"; + testFinished(); + } + else { + numClientsToTest++; + logger.debug("increasing number of clients to " + numClientsToTest); + setTimeout(attemptTestPass, 500); // wait a second to avoid race conditions with client/server comm + } + } + else { + // reduce numclients if we can + if(numClientsToTest == MINIMUM_ACCEPTABLE_SESSION_SIZE) { + // we are too low already. fail the user + attempt.reason = "minimum_client_threshold"; + testFinished(); + } + else if(numClientsToTest > STARTING_NUM_CLIENTS) { + // this means we've gone up before... so don't go back down (i.e., creating a loop) + attempt.reason = "success"; + testSummary.final = { reason:'success', num_clients: numClientsToTest - 1 } + testFinished(); + } + else { + numClientsToTest--; + logger.debug("reducing number of clients to " + numClientsToTest); + setTimeout(attemptTestPass, 500); // wait a second to avoid race conditions with client/server comm + } + } + } + + attempt.backend_data = data; + + if(data.progress === true) { + + var animate = true; + if(data.downthroughput && data.upthroughput) { + + if(data.downthroughput > 0 || data.upthroughput > 0) { + attempt.received_progress = true; + animate = true; + } + + if(attempt.received_progress) { + // take the lower + var throughput= data.downthroughput < data.upthroughput ? data.downthroughput : data.upthroughput; + + updateProgress(throughput, true); + } + } + } + else { + + logger.debug("network test pass success. data: ", data); + + if(data.reason == "unreachable") { + // STUN + logger.debug("network test: unreachable (STUN issue or similar)"); + attempt.reason = data.reason; + testFinished(); + } + else if(data.reason == "internal_error") { + // oops + logger.debug("network test: internal_error (client had a unexpected problem)"); + attempt.reason = data.reason; + testFinished(); + } + else if(data.reason == "remote_peer_cant_test") { + // old client + logger.debug("network test: remote_peer_cant_test (old client)") + attempt.reason = data.reason; + testFinished(); + } + else { + if(!data.downthroughput || !data.upthroughput) { + // we have to assume this is bad. just not a reason we know about in code + logger.debug("network test: no test data (unknown issue? " + data.reason + ")") + attempt.reason = "invalid_response"; + testFinished(); + } + else { + // success... but we still have to verify if this data is within threshold + if(data.downthroughput < gon.ftue_packet_rate_treshold) { + logger.debug("network test: downthroughput too low. downthroughput: " + data.downthroughput + ", threshold: " + gon.ftue_packet_rate_treshold); + refineTest(false); + } + else if(data.upthroughput < gon.ftue_packet_rate_treshold) { + logger.debug("network test: upthroughput too low. upthroughput: " + data.upthroughput + ", threshold: " + gon.ftue_packet_rate_treshold); + refineTest(false); + } + else { + // true success. we can accept this score + logger.debug("network test: success") + refineTest(true); + } + } + } + + // VRFS-1742 + // context.jamClient.StopNetworkTest(serverClientId); + } + + } + + function networkTestTimeout(data) { + clearBackendGuard(); + + logger.warn("network timeout when testing latency test: " + data); + + var attempt = getCurrentAttempt(); + attempt.reason = 'timeout'; + attempt.backend_data = data; + testFinished(); + } + + function hasScoredNetworkSuccessfully() { + return testedSuccessfully; + } + + function configureStartButton() { + if(scoring) { + $startNetworkTestBtn.text('NETWORK TEST RUNNING...').removeClass('button-orange').addClass('button-grey') + } + else { + $startNetworkTestBtn.text('START NETWORK TEST').removeClass('button-grey').addClass('button-orange'); + } + + } + + function initializeNextButtonState() { + $dialog.setNextState(hasScoredNetworkSuccessfully() && !scoring); + } + + function initializeBackButtonState() { + $dialog.setBackState(!scoring); + } + + function beforeHide() { + clearBackendGuard(); + } + + function initialize(_$step, _inGearWizard) { + $step = _$step; + inGearWizard = _inGearWizard; + + $startNetworkTestBtn = $step.find('.start-network-test'); + + if($startNetworkTestBtn.length == 0) throw 'no start network test button found in network-test' + + $testResults = $step.find('.network-test-results'); + $testScore = $step.find('.network-test-score'); + $testText = $step.find('.network-test-text'); + $scoringBar = $step.find('.scoring-bar'); + $goodMarker = $step.find('.good-marker'); + $goodLine =$step.find('.good-line'); + $currentScore = $step.find('.current-score'); + $scoredClients= $step.find('.scored-clients'); + $subscore = $step.find('.subscore'); + $startNetworkTestBtn.on('click', startNetworkTest); + + // if this network test is instantiated anywhere else than the gearWizard, or a dialog, then this will have to be expanded + if(inGearWizard) { + context.JK.HandleNetworkTestSuccessForGearWizard = function(data) { networkTestSuccess(data)}; // pin to global for bridge callback + context.JK.HandleNetworkTestTimeoutForGearWizard = function(data) { networkTestTimeout(data)}; // pin to global for bridge callback + } + else { + context.JK.HandleNetworkTestSuccessForDialog = function(data) { networkTestSuccess(data)}; // pin to global for bridge callback + context.JK.HandleNetworkTestTimeoutForDialog = function(data) { networkTestTimeout(data)}; // pin to global for bridge callback + } + } + + this.isScoring = function () { return scoring; }; + this.hasScoredNetworkSuccessfully = hasScoredNetworkSuccessfully; + this.initialize = initialize; + this.reset = reset; + this.cancel = cancel; + + this.NETWORK_TEST_START = NETWORK_TEST_START; + this.NETWORK_TEST_DONE = NETWORK_TEST_DONE; + this.NETWORK_TEST_FAIL = NETWORK_TEST_FAIL; + + return this; + } +})(window, jQuery); \ No newline at end of file diff --git a/web/app/assets/javascripts/networkTestDialog.js b/web/app/assets/javascripts/networkTestDialog.js new file mode 100644 index 000000000..c739355a6 --- /dev/null +++ b/web/app/assets/javascripts/networkTestDialog.js @@ -0,0 +1,94 @@ +(function (context, $) { + + "use strict"; + + context.JK = context.JK || {}; + context.JK.NetworkTestDialog = function (app) { + var logger = context.JK.logger; + + var $dialog = null; + var $btnCancel = null; + var $btnClose = null; + var $btnHelp = null; + var networkTest = new context.JK.NetworkTest(app); + + function networkTestDone() { + unfreezeInteractions(); + } + + function networkTestFail() { + unfreezeInteractions(); + } + + function networkTestStart() { + freezeInteractions(); + } + + + function freezeInteractions() { + $btnClose.removeClass('button-orange').addClass('button-grey'); + } + + function unfreezeInteractions() { + $btnClose.removeClass('button-grey').addClass('button-orange'); + } + + function events() { + $btnCancel.on('click', function() { + // should we stop this if the test is going? + app.layout.closeDialog('network-test') + return false; + }) + + $btnClose.on('click', function(e) { + if(!networkTest.isScoring()) { + app.layout.closeDialog('network-test'); + } + return false; + }) + + $btnHelp.on('click', function(e) { + context.JK.Banner.showAlert('No help is yet available for the network test.'); + return false; + }) + } + + function beforeShow() { + if(!networkTest.isScoring()) { + networkTest.reset(); + } + } + + function afterHide() { + + } + + function initialize() { + + var dialogBindings = { + 'beforeShow' : beforeShow, + 'afterHide': afterHide + }; + + app.bindDialog('network-test', dialogBindings); + + $dialog = $('#network-test-dialog'); + $btnCancel = $dialog.find('.btn-cancel'); + $btnHelp = $dialog.find('.btn-help'); + $btnClose = $dialog.find('.btn-close'); + + networkTest.initialize($dialog, false); + $(networkTest) + .on(networkTest.NETWORK_TEST_DONE, networkTestDone) + .on(networkTest.NETWORK_TEST_FAIL, networkTestFail) + .on(networkTest.NETWORK_TEST_START, networkTestStart) + + events(); + } + + this.initialize = initialize; + + return this; + }; + +})(window, jQuery); \ No newline at end of file diff --git a/web/app/assets/javascripts/user_dropdown.js b/web/app/assets/javascripts/user_dropdown.js index 053ebf23c..d8868a058 100644 --- a/web/app/assets/javascripts/user_dropdown.js +++ b/web/app/assets/javascripts/user_dropdown.js @@ -50,6 +50,11 @@ invitationDialog.showFacebookDialog(e); }); + $('.shortcuts .test-network').on('click', function(e) { + app.layout.showDialog('network-test'); + return false; + }) + $('#header-avatar').on('avatar_changed', function (event, newAvatarUrl) { updateAvatar(newAvatarUrl); event.preventDefault(); @@ -84,7 +89,6 @@ } } - // initially show avatar function showAvatar() { var photoUrl = context.JK.resolveAvatarUrl(userMe.photo_url); diff --git a/web/app/assets/javascripts/wizard/gear/step_network_test.js b/web/app/assets/javascripts/wizard/gear/step_network_test.js index 2669d0289..030441276 100644 --- a/web/app/assets/javascripts/wizard/gear/step_network_test.js +++ b/web/app/assets/javascripts/wizard/gear/step_network_test.js @@ -3,454 +3,59 @@ "use strict"; context.JK = context.JK || {}; - context.JK.StepNetworkTest = function (app, $dialog) { + context.JK.StepNetworkTest = function (app, dialog) { - var NETWORK_TEST_TYPES = { - RestPhase : 0, - PktTest100NormalLatency : 1, - PktTest200MediumLatency : 2, - PktTest400LowLatency : 3, - PktTestRateSweep : 4, - RcvOnly : 5 - } - var STARTING_NUM_CLIENTS = 4; - var PAYLOAD_SIZE = 100; - var MINIMUM_ACCEPTABLE_SESSION_SIZE = 2; - var rest = context.JK.Rest(); var logger = context.JK.logger; + var networkTest = new context.JK.NetworkTest(app); var $step = null; - var TEST_SUCCESS_CALLBACK = 'JK.HandleNetworkTestSuccess'; - var TEST_TIMEOUT_CALLBACK = 'JK.HandleNetworkTestTimeout'; - - var $startNetworkTestBtn = null; - var $testResults = null; - var $testScore = null; - var $testText = null; - var testedSuccessfully = false; - var $scoringBar = null; - var $goodMarker = null; - var $goodLine = null; - var $currentScore = null; - var $scoredClients = null; - var $subscore = null; - var backendGuardTimeout = null; - - var serverClientId = ''; - var isScoring = false; - var numClientsToTest = STARTING_NUM_CLIENTS; - var testSummary = {attempts : [], final:null} - - var scoringZoneWidth = 100;//px - function reset() { - serverClientId = ''; - isScoring = false; - numClientsToTest = STARTING_NUM_CLIENTS; - testSummary = {attempts : []}; - updateControlsState(); + function networkTestDone() { + updateButtons(); } - function renderStartTest() { - $scoredClients.empty(); - $testResults.removeClass('good acceptable bad').addClass('testing'); - $testText.empty(); - $subscore.empty(); - updateControlsState(); - $currentScore.width(0); - $goodLine.css('left', (gon.ftue_packet_rate_treshold * 100) + '%'); - $goodMarker.css('left', (gon.ftue_packet_rate_treshold * 100) + '%'); + function networkTestFail() { + updateButtons(); } - function renderStopTest(score, text) { - $scoredClients.html(score); - $testText.html(text); - $testResults.removeClass('testing'); + function networkTestStart() { + updateButtons(); } - function postDiagnostic() { - rest.createDiagnostic({ - type: 'NETWORK_TEST_RESULT', - data: {client_type: context.JK.clientType(), client_id: context.JK.JamServer.clientID, summary:testSummary} - }); - } - - function testFinished() { - var attempt = getCurrentAttempt(); - - if(!testSummary.final) { - testSummary.final = {reason : attempt.reason}; - } - - var reason = testSummary.final.reason; - - if(reason == "success") { - renderStopTest(attempt.num_clients, "Your router and Internet service will support sessions of up to " + attempt.num_clients + " JamKazam musicians.") - testedSuccessfully = true; - if(!testSummary.final.num_clients) { - testSummary.final.num_clients = attempt.num_clients; - } - context.JK.GA.trackNetworkTest(context.JK.detectOS(), testSummary.final.num_clients); - context.jamClient.SetNetworkTestScore(attempt.num_clients); - if(testSummary.final.num_clients == 2) { - $testResults.addClass('acceptable'); - } - else { - $testResults.addClass('good'); - } - } - else if(reason == "minimum_client_threshold") { - context.jamClient.SetNetworkTestScore(0); - renderStopTest('', "We're sorry, but your router and Internet service will not effectively support JamKazam sessions. Please click the HELP button for more information.") - } - else if(reason == "unreachable") { - context.jamClient.SetNetworkTestScore(0); - renderStopTest('', "We're sorry, but your router will not support JamKazam in its current configuration. Please click the HELP button for more information."); - } - else if(reason == "internal_error") { - context.JK.alertSupportedNeeded("The JamKazam client software had an unexpected problem while scoring your Internet connection."); - renderStopTest('', ''); - } - else if(reason == "remote_peer_cant_test") { - context.JK.alertSupportedNeeded("The JamKazam service is experiencing technical difficulties."); - renderStopTest('', ''); - } - else if(reason == 'backend_gone') { - context.JK.alertSupportedNeeded("The JamKazam client is experiencing technical difficulties."); - renderStopTest('', ''); - } - else if(reason == "invalid_response") { - context.JK.alertSupportedNeeded("The JamKazam client software had an unexpected problem while scoring your Internet connection. Reason=" + attempt.backend_data.reason + '.'); - renderStopTest('', ''); - } - else if(reason == 'no_servers') { - context.JK.alertSupportedNeeded("No network test servers are available. You can skip this step for now."); - renderStopTest('', ''); - testedSuccessfully = true; - } - else if(reason == 'no_network') { - context.JK.Banner.showAlert("Please try again later. Your network appears down."); - renderStopTest('', ''); - } - else if(reason == "rest_api_error") { - context.JK.alertSupportedNeeded("Unable to acquire a network test server. You can skip this step for now."); - testedSuccessfully = true; - renderStopTest('', ''); - } - else if(reason == "timeout") { - context.JK.alertSupportedNeeded("Communication with a network test servers timed out. You can skip this step for now."); - testedSuccessfully = true; - renderStopTest('', ''); - } - else { - context.JK.alertSupportedNeeded("The JamKazam client software had a logic error while scoring your Internet connection."); - renderStopTest('', ''); - } - - numClientsToTest = STARTING_NUM_CLIENTS; - isScoring = false; - updateControlsState(); - postDiagnostic(); - } - - function getCurrentAttempt() { - return testSummary.attempts[testSummary.attempts.length - 1]; - } - - function backendTimedOut() { - testSummary.final = {reason: 'backend_gone'} - testFinished(); - } - - function clearBackendGuard() { - if(backendGuardTimeout) { - clearTimeout(backendGuardTimeout); - backendGuardTimeout = null; - } - } - - function attemptTestPass() { - - var attempt = {}; - attempt.payload_size = PAYLOAD_SIZE; - attempt.duration = gon.ftue_network_test_duration; - attempt.test_type = 'PktTest400LowLatency'; - attempt.num_clients = numClientsToTest; - attempt.server_client_id = serverClientId; - attempt.received_progress = false; - testSummary.attempts.push(attempt); - - //context.jamClient.StopNetworkTest(''); - - $testText.text("Simulating the network traffic of a " + numClientsToTest + "-person session."); - - updateProgress(0, false); - - backendGuardTimeout = setTimeout(function(){backendTimedOut()}, (gon.ftue_network_test_duration + 1) * 1000); - - context.jamClient.TestNetworkPktBwRate(serverClientId, TEST_SUCCESS_CALLBACK, TEST_TIMEOUT_CALLBACK, - NETWORK_TEST_TYPES.PktTest400LowLatency, - gon.ftue_network_test_duration, - numClientsToTest, - PAYLOAD_SIZE); - } - - - function startNetworkTest(checkWireless) { - - if(isScoring) return false; - - if(checkWireless) { - // check if on Wifi 1st - var isWireless = context.jamClient.IsMyNetworkWireless(); - if(isWireless == -1) { - logger.warn("unable to determine if the user is on wireless or not for network test. skipping prompt.") - } - else if(isWireless == 1) { - context.JK.Banner.showAlert({buttons: [ - {name: 'RUN NETWORK TEST ANYWAY', click: function() {startNetworkTest(false)}}, - {name: 'CANCEL', click: function() {}}], - html: "

It appears that your computer is connected to your network using WiFi.

" + - "

We strongly advise against running the JamKazam application on a WiFi connection. " + - "We recommend using a wired Ethernet connection from your computer to your router. " + - "A WiFi connection is likely to cause significant issues in both latency and audio quality.

"}) - return false; - } - } - - reset(); - isScoring = true; - renderStartTest(); - rest.getLatencyTester() - .done(function(response) { - // ensure there are no tests ongoing - - serverClientId = response.client_id; - - logger.info("beginning network test against client_id: " + serverClientId); - - attemptTestPass(); - }) - .fail(function(jqXHR) { - if(jqXHR.status == 404) { - // means there are no network testers available. - // we have to skip this part of the UI - testSummary.final = {reason: 'no_servers'} - } - else { - if(context.JK.isNetworkError(arguments)) { - testSummary.final = {reason: 'no_network'} - } - else { - testSummary.final = {reason: 'rest_api_error'} - } - } - testFinished(); - }) - logger.info("starting network test"); - return false; - } - - function updateProgress(throughput, showSubscore) { - var width = throughput * 100; - - $currentScore.stop().data('showSubscore', showSubscore); - - if(!showSubscore) { - $subscore.text(''); - } - - $currentScore.animate({ - duration: 1000, - width: width + '%' - }, { - step: function (now, fx) { - if(showSubscore) { - var newWidth = ( 100 * parseFloat($currentScore.css('width')) / parseFloat($currentScore.parent().css('width')) ); - $subscore.text((Math.round(newWidth * 10) / 10) + '%'); - } - } - }).css('overflow', 'visible'); - ; - } - - function networkTestSuccess(data) { - clearBackendGuard(); - - var attempt = getCurrentAttempt(); - - function refineTest(up) { - if(up) { - if(numClientsToTest == gon.ftue_network_test_max_clients) { - attempt.reason = "success"; - testFinished(); - } - else { - numClientsToTest++; - logger.debug("increasing number of clients to " + numClientsToTest); - setTimeout(attemptTestPass, 500); // wait a second to avoid race conditions with client/server comm - } - } - else { - // reduce numclients if we can - if(numClientsToTest == MINIMUM_ACCEPTABLE_SESSION_SIZE) { - // we are too low already. fail the user - attempt.reason = "minimum_client_threshold"; - testFinished(); - } - else if(numClientsToTest > STARTING_NUM_CLIENTS) { - // this means we've gone up before... so don't go back down (i.e., creating a loop) - attempt.reason = "success"; - testSummary.final = { reason:'success', num_clients: numClientsToTest - 1 } - testFinished(); - } - else { - numClientsToTest--; - logger.debug("reducing number of clients to " + numClientsToTest); - setTimeout(attemptTestPass, 500); // wait a second to avoid race conditions with client/server comm - } - } - } - - attempt.backend_data = data; - - if(data.progress === true) { - - var animate = true; - if(data.downthroughput && data.upthroughput) { - - if(data.downthroughput > 0 || data.upthroughput > 0) { - attempt.received_progress = true; - animate = true; - } - - if(attempt.received_progress) { - // take the lower - var throughput= data.downthroughput < data.upthroughput ? data.downthroughput : data.upthroughput; - - updateProgress(throughput, true); - } - } - } - else { - - logger.debug("network test pass success. data: ", data); - - if(data.reason == "unreachable") { - // STUN - logger.debug("network test: unreachable (STUN issue or similar)"); - attempt.reason = data.reason; - testFinished(); - } - else if(data.reason == "internal_error") { - // oops - logger.debug("network test: internal_error (client had a unexpected problem)"); - attempt.reason = data.reason; - testFinished(); - } - else if(data.reason == "remote_peer_cant_test") { - // old client - logger.debug("network test: remote_peer_cant_test (old client)") - attempt.reason = data.reason; - testFinished(); - } - else { - if(!data.downthroughput || !data.upthroughput) { - // we have to assume this is bad. just not a reason we know about in code - logger.debug("network test: no test data (unknown issue? " + data.reason + ")") - attempt.reason = "invalid_response"; - testFinished(); - } - else { - // success... but we still have to verify if this data is within threshold - if(data.downthroughput < gon.ftue_packet_rate_treshold) { - logger.debug("network test: downthroughput too low. downthroughput: " + data.downthroughput + ", threshold: " + gon.ftue_packet_rate_treshold); - refineTest(false); - } - else if(data.upthroughput < gon.ftue_packet_rate_treshold) { - logger.debug("network test: upthroughput too low. upthroughput: " + data.upthroughput + ", threshold: " + gon.ftue_packet_rate_treshold); - refineTest(false); - } - else { - // true success. we can accept this score - logger.debug("network test: success") - refineTest(true); - } - } - } - - // VRFS-1742 - // context.jamClient.StopNetworkTest(serverClientId); - } - - } - - function networkTestTimeout(data) { - clearBackendGuard(); - - logger.warn("network timeout when testing latency test: " + data); - - var attempt = getCurrentAttempt(); - attempt.reason = 'timeout'; - attempt.backend_data = data; - testFinished(); - } - - function hasScoredNetworkSuccessfully() { - return testedSuccessfully; - } - - function configureStartButton() { - if(isScoring) { - $startNetworkTestBtn.text('NETWORK TEST RUNNING...').removeClass('button-orange').addClass('button-grey') - } - else { - $startNetworkTestBtn.text('START NETWORK TEST').removeClass('button-grey').addClass('button-orange'); - } - - } - function updateControlsState() { + function updateButtons() { initializeNextButtonState(); initializeBackButtonState(); - configureStartButton(); } - function initializeNextButtonState() { - $dialog.setNextState(hasScoredNetworkSuccessfully() && !isScoring); + dialog.setNextState(networkTest.hasScoredNetworkSuccessfully() && !networkTest.isScoring()); } function initializeBackButtonState() { - $dialog.setBackState(!isScoring); + dialog.setBackState(!networkTest.isScoring()); } function newSession() { - reset(); - //context.jamClient.StopNetworkTest(''); + networkTest.reset(); + updateButtons(); } function beforeShow() { - reset(); + networkTest.cancel(); + updateButtons(); } function beforeHide() { - clearBackendGuard(); + networkTest.cancel(); } function initialize(_$step) { $step = _$step; - - $startNetworkTestBtn = $step.find('.start-network-test'); - $testResults = $step.find('.network-test-results'); - $testScore = $step.find('.network-test-score'); - $testText = $step.find('.network-test-text'); - $scoringBar = $step.find('.scoring-bar'); - $goodMarker = $step.find('.good-marker'); - $goodLine =$step.find('.good-line'); - $currentScore = $step.find('.current-score'); - $scoredClients= $step.find('.scored-clients'); - $subscore = $step.find('.subscore'); - $startNetworkTestBtn.on('click', startNetworkTest); + networkTest.initialize($step, true); + $(networkTest) + .on(networkTest.NETWORK_TEST_DONE, networkTestDone) + .on(networkTest.NETWORK_TEST_FAIL, networkTestFail) + .on(networkTest.NETWORK_TEST_START, networkTestStart) } this.newSession = newSession; @@ -458,9 +63,6 @@ this.beforeShow = beforeShow; this.initialize = initialize; - context.JK.HandleNetworkTestSuccess = networkTestSuccess; // pin to global for bridge callback - context.JK.HandleNetworkTestTimeout = networkTestTimeout; // pin to global for bridge callback - return this; } })(window, jQuery); \ 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 54747b4e1..382e6ca7b 100644 --- a/web/app/assets/stylesheets/client/client.css +++ b/web/app/assets/stylesheets/client/client.css @@ -39,6 +39,7 @@ *= require ./wizard/wizard *= require ./wizard/gearWizard *= require ./wizard/loopbackWizard + *= require ./networkTestDialog *= require ./whatsNextDialog *= require ./invitationDialog *= require ./shareDialog diff --git a/web/app/assets/stylesheets/client/content-orig.css.scss b/web/app/assets/stylesheets/client/content-orig.css.scss index f0be5b7a0..0d176ded4 100644 --- a/web/app/assets/stylesheets/client/content-orig.css.scss +++ b/web/app/assets/stylesheets/client/content-orig.css.scss @@ -305,7 +305,7 @@ ul.shortcuts { padding:2px; } - .account-home, .band-setup, .audio, .get-help, .download-app, .invite-friends { + .account-home, .band-setup, .audio, .get-help, .invite-friends, .test-network{ border-bottom:1px; border-style:solid; border-color:#ED3618; diff --git a/web/app/assets/stylesheets/client/content.css.scss b/web/app/assets/stylesheets/client/content.css.scss index 6685d883d..7d76ca24b 100644 --- a/web/app/assets/stylesheets/client/content.css.scss +++ b/web/app/assets/stylesheets/client/content.css.scss @@ -429,12 +429,17 @@ ul.shortcuts { padding:2px; } - .account-home, .band-setup, .account-menu-group, .get-help, .download-app, .community-forum, .invite-friends { + .account-home, .band-setup, .account-menu-group, .get-help, .community-forum, .invite-friends { border-bottom:1px; border-style:solid; border-color:#ED3618; } + .community-forum { + border-top:1px; + border-style:solid; + border-color:#ED3618; + } span.arrow-right { diff --git a/web/app/assets/stylesheets/client/networkTestDialog.css.scss b/web/app/assets/stylesheets/client/networkTestDialog.css.scss new file mode 100644 index 000000000..749965fc4 --- /dev/null +++ b/web/app/assets/stylesheets/client/networkTestDialog.css.scss @@ -0,0 +1,7 @@ +#network-test-dialog { + .buttons { + bottom: 25px; + position: absolute; + right: 25px; + } +} \ No newline at end of file diff --git a/web/app/assets/stylesheets/client/wizard/gearWizard.css.scss b/web/app/assets/stylesheets/client/wizard/gearWizard.css.scss index d73f4016e..59936c3e3 100644 --- a/web/app/assets/stylesheets/client/wizard/gearWizard.css.scss +++ b/web/app/assets/stylesheets/client/wizard/gearWizard.css.scss @@ -1,959 +1,962 @@ @import "client/common.css.scss"; + @charset "UTF-8"; -.dialog.gear-wizard { +.dialog.gear-wizard, .dialog.network-test { min-height: 500px; max-height: 500px; - width:800px; + width: 800px; .ftue-inner { line-height: 1.3em; - width:800px; - padding:25px; - font-size:15px; - color:#aaa; + width: 800px; + padding: 25px; + font-size: 15px; + color: #aaa; @include border_box_sizing; + } - .ftue-stepnumber { - display: block; - float: left; - padding: 12px; - padding-top: 6px; - width: 12px; - height: 18px; - border: solid 1px #ed3618; - background-color: #333; - -webkit-border-radius: 18px; - -moz-border-radius: 18px; - border-radius: 18px; - color: #ed3618 !important; - font-size: 22px; - margin-right: 10px; - font-family: helvetica; - text-decoration: none; - line-height: 27px; + .ftue-stepnumber { + display: block; + float: left; + padding: 12px; + padding-top: 6px; + width: 12px; + height: 18px; + border: solid 1px #ed3618; + background-color: #333; + -webkit-border-radius: 18px; + -moz-border-radius: 18px; + border-radius: 18px; + color: #ed3618 !important; + font-size: 22px; + margin-right: 10px; + font-family: helvetica; + text-decoration: none; + line-height: 27px; - &.active { - background-color:#ed3618; - color:#333; + &.active { + background-color: #ed3618; + color: #333; + } + } + + .ftue-step-title { + float: left; + color: #ccc; + font-size: 28px; + width: 350px; + margin-top: 3px; + line-height: 30px; + display: none; + } + + .help-text { + position: relative; + clear: both; + margin-top: 20px; + } + + h2 { + color: #FFFFFF; + font-size: 15px; + font-weight: normal; + margin-bottom: 6px; + white-space: nowrap; + } + .ftue-box { + background-color: #222222; + font-size: 13px; + padding: 8px; + + &.list { + + } + + &.list.ports { + height: 100px; + overflow: auto; + } + + &.instructions { + height: 268px !important; + line-height: 16px; + @include border_box_sizing; + + .video-type { + font-size: 10px; + display: none; } } - .ftue-step-title { - float: left; - color: #ccc; - font-size: 28px; - width: 350px; - margin-top: 3px; - line-height:30px; - display:none; + ul li { + margin-left: 20px; + list-style-type: disc; } - .help-text { - position:relative; - clear:both; - margin-top:20px; + .watch-video { + margin: 8px 0 3px 0; + } + } + + .wizard-step[layout-wizard-step="0"] { + .video-button-holder { + margin-top: 100px; + } + } + + .wizard-step[layout-wizard-step="1"] { + + .wizard-step-content .wizard-step-column { + width: 25%; } - h2 { - color: #FFFFFF; - font-size: 15px; - font-weight: normal; - margin-bottom: 6px; - white-space:nowrap; + .watch-video.audio-input { + margin-top: 29px; + + &.audio-output-showing { + margin-top: 10px; + } } - .ftue-box { - background-color: #222222; - font-size: 13px; - padding: 8px; - &.list { + .watch-video.audio-output { + margin-top: 10px; + } + .select-audio-input-device { + margin-bottom: 20px; + } + + .select-audio-output-device { + margin-bottom: 20px; + } + + .asio-settings-input-btn, .asio-settings-output-btn, .resync-btn { + width: 80%; + display: inline-block; + text-align: center; + } + + .asio-settings-input-btn, .asio-settings-output-btn { + margin-top: 10px; + } + + .asio-settings-input-btn { + display: none; + } + + .asio-settings-output-btn { + display: none; + } + + .resync-btn { + margin-top: 10px; + visibility: hidden; + } + + .frame-and-buffers { + display: none; + margin-top: 5px; + } + + .audio-port { + white-space: nowrap; + } + + .audio-channels { + margin-top: 15px; + } + + .audio-output { + display: none; + } + } + + .wizard-step[layout-wizard-step="2"] { + .wizard-step-content .wizard-step-column { + width: 25%; + + &:nth-of-type(2) { + width: 21%; } - &.list.ports { - height:100px; - overflow:auto; + &:nth-of-type(3) { + width: 45%; } - &.instructions { - height: 268px !important; - line-height:16px; - @include border_box_sizing; + &:nth-of-type(4) { + width: 9%; + } + } - .video-type { - font-size:10px; - display:none; + .watch-video { + margin-top: 45px; + } + + .icon-instrument-select { + padding: 3px 0; // to combine 24 of .current-instrument + 3x on either side + margin: 0 0 15px 25px; // 15 margin-bottom to match tracks on the left + width: 30px; + } + + .unassigned-channels { + min-height: 240px; + overflow-y: auto; + //padding-right:18px; // to keep draggables off of scrollbar. maybe necessary + + &.drag-in-progress { + overflow-y: visible; + overflow-x: visible; + } + + &.possible-target { + border: solid 1px white; + + &.drag-hovering { + border: solid 1px #ED3618; } } - - ul li { - margin-left:20px; - list-style-type: disc; - } - - .watch-video { - margin:8px 0 3px 0; - } } - .wizard-step[layout-wizard-step="0"] { - .video-button-holder { - margin-top:100px; - } + .num { + position: absolute; + height: 29px; + line-height: 29px; } - - .wizard-step[layout-wizard-step="1"] { - - .wizard-step-content .wizard-step-column { - width:25%; - } - - .watch-video.audio-input { - margin-top:29px; - - &.audio-output-showing { - margin-top:10px; - } - } - - .watch-video.audio-output { - margin-top:10px; - } - - .select-audio-input-device { - margin-bottom:20px; - } - - .select-audio-output-device { - margin-bottom:20px; - } - - .asio-settings-input-btn, .asio-settings-output-btn, .resync-btn { - width:80%; - display:inline-block; - text-align:center; - } - - .asio-settings-input-btn, .asio-settings-output-btn { - margin-top:10px; - } - - .asio-settings-input-btn { - display:none; - } - - .asio-settings-output-btn { - display:none; - } - - .resync-btn { - margin-top:10px; - visibility:hidden; - } - - .frame-and-buffers { - display:none; - margin-top:5px; - } - - .audio-port { - white-space: nowrap; - } - - .audio-channels { - margin-top:15px; - } - - .audio-output { - display:none; - } - } - - .wizard-step[layout-wizard-step="2"] { - .wizard-step-content .wizard-step-column { - width:25%; - - &:nth-of-type(2) { - width:21%; - } - - - &:nth-of-type(3) { - width:45%; - } - - - &:nth-of-type(4) { - width:9%; - } - } - - .watch-video { - margin-top:45px; - } - - .icon-instrument-select { - padding:3px 0; // to combine 24 of .current-instrument + 3x on either side - margin:0 0 15px 25px; // 15 margin-bottom to match tracks on the left - width:30px; - } - - .unassigned-channels { - min-height:240px; - overflow-y:auto; - //padding-right:18px; // to keep draggables off of scrollbar. maybe necessary - - &.drag-in-progress { - overflow-y:visible; - overflow-x:visible; - } - + .track { + margin-bottom: 15px; + .track-target { &.possible-target { - border: solid 1px white; - - &.drag-hovering { - border: solid 1px #ED3618; - } + border-color: white; + } + &.drag-hovering { + border-color: #ED3618; } } + } - .num { + .ftue-input { + font-size: 12px; + cursor: move; + padding: 4px; + border: solid 1px #999; + margin-bottom: 15px; + white-space: nowrap; + overflow: hidden; + text-align: left; + direction: rtl; + &.ui-draggable-dragging { + margin-bottom: 0; + } + + &:hover { + color: white; + font-weight: bold; + } + + /** + &:hover { + color:white; + overflow:visible; + background-color:#333; + width: auto !important; + direction:ltr; position:absolute; - height:29px; - line-height:29px; - } - .track { - margin-bottom: 15px; - .track-target { - &.possible-target { - border-color:white; - } - &.drag-hovering { - border-color:#ED3618; - } - } - } + line-height:19.5px; + }*/ + } + .track-target { + + cursor: move; + padding: 4px; + border: solid 1px #999; + margin-left: 15px; + height: 20px; + overflow: hidden; + + &.drag-in-progress { + overflow: visible; + } .ftue-input { - font-size:12px; - cursor: move; - padding: 4px; - border: solid 1px #999; - margin-bottom: 15px; - white-space: nowrap; - overflow:hidden; - text-align:left; - direction:rtl; - &.ui-draggable-dragging{ - margin-bottom:0; + padding: 0; + border: 0; + margin-bottom: 0; + &.ui-draggable-dragging { + padding: 4px; + border: solid 1px #999; + overflow: visible; } - - &:hover { - color:white; - font-weight: bold; - } - - /** - &:hover { - color:white; - overflow:visible; - background-color:#333; - width: auto !important; - direction:ltr; - position:absolute; - line-height:19.5px; - }*/ } - .track-target { - - cursor: move; - padding: 4px; - border: solid 1px #999; - margin-left: 15px; - height:20px; - overflow:hidden; - - &.drag-in-progress { - overflow:visible; - } - - .ftue-input { - padding:0; - border:0; - margin-bottom:0; - &.ui-draggable-dragging { - padding:4px; - border: solid 1px #999; - overflow:visible; - } - } + .placeholder { + display: none; + font-size: 12px; + } + &[track-count="0"] { .placeholder { - display:none; - font-size:12px; + display: inline; } + } - &[track-count="0"] { - .placeholder { - display:inline; - } - } + &[track-count="2"] { + .ftue-input { + width: 49%; + display: inline-block; - &[track-count="2"] { - .ftue-input { - width:49%; - display:inline-block; - - &:nth-of-type(1) { - float:left; - /**&:after { - float:right; - content: ','; - padding-right:3px; - }*/ - } - &:nth-of-type(2) { + &:nth-of-type(1) { + float: left; + /**&:after { float:right; - } + content: ','; + padding-right:3px; + }*/ + } + &:nth-of-type(2) { + float: right; } } } } + } - .wizard-step[layout-wizard-step="3"] { + .wizard-step[layout-wizard-step="3"] { - .wizard-step-content .wizard-step-column { - width:25%; + .wizard-step-content .wizard-step-column { + width: 25%; + + &:nth-of-type(2) { + width: 50%; + } + } + + .watch-video { + margin-top: 97px; + } + + .voicechat-option { + + position: relative; + + div { - &:nth-of-type(2) { - width:50%; - } } - .watch-video { - margin-top:97px; + h3 { + padding-left: 30px; + margin-top: 14px; + font-weight: bold; + display: inline-block; } - .voicechat-option { + p { + padding-left: 30px; + margin-top: 5px; + display: inline-block; + } - position:relative; + input { + position: absolute; + margin: auto; + width: 30px; + } - div{ + .iradio_minimal { + margin-top: 15px; + display: inline-block; + } + } - } - - h3 { - padding-left:30px; - margin-top:14px; - font-weight:bold; - display:inline-block; - } + .ftue-box { + &.chat-inputs { + height: 230px !important; + overflow: auto; p { - padding-left:30px; - margin-top:5px; - display:inline-block; + white-space: nowrap; + display: inline-block; + height: 32px; + vertical-align: middle; } - input { - position:absolute; - margin:auto; - width:30px; - } + .chat-input { + white-space: nowrap; - .iradio_minimal { - margin-top:15px; - display:inline-block; - } - } - - .ftue-box { - &.chat-inputs { - height: 230px !important; - overflow:auto; - - p { - white-space: nowrap; - display:inline-block; - height:32px; - vertical-align: middle; + .iradio_minimal { + display: inline-block; } - - .chat-input { - white-space:nowrap; - - .iradio_minimal { - display:inline-block; - } - } - } - - .watch-video { - margin-top:25px; - } - } - } - - .wizard-step[layout-wizard-step="4"] { - .wizard-step-content .wizard-step-column { - width:25%; - - &:nth-of-type(2) { - width:75%; } } .watch-video { - margin-top:12px; + margin-top: 25px; } + } + } - .help-content { - margin-top:22px; - margin-left:20px; - } + .wizard-step[layout-wizard-step="4"] { + .wizard-step-content .wizard-step-column { + width: 25%; - .test-direct-monitoring { - margin-top:40px; - display:inline-block; - text-decoration: none; - width:90px; - - &.paused { - .playing { - display:none; - } - } - &.playing { - .paused { - display:none; - } - } - - .direct-monitoring-btn { - padding-left:5px; - font-size:16px; - } - - * { - vertical-align:middle; - } + &:nth-of-type(2) { + width: 75%; } } - .wizard-step[layout-wizard-step="5"] { - - .wizard-step-content .wizard-step-column { - &:nth-of-type(1) { - width:25%; - } - &:nth-of-type(2) { - width:50%; - } - &:nth-of-type(3) { - width:25%; - } - } - - .summary { - margin-left:20px; - margin-top:22px; - } - - .watch-video { - margin-top:90px; - } - - a.start-network-test { - margin-top:20px; - } - - .network-test-score { - height:24px; - padding:10px; - color:white; - font-size:20px; - background-color:#222; - text-align:center; - margin-bottom:20px; - - &.good { - background-color: #72a43b; - } - &.acceptable { - background-color: #D6A800; - } - &.bad { - background-color: #7B0C00; - } - } - - .scoring-bar { - width:100%; - height:20px; - left:0; - position:relative; - //display:inline-block; - display:none; - - .current-score { - background-color:gray; - border-right:1px solid white; - border-top:1px solid #ccc; - border-bottom:1px solid #ccc; - border-left:1px solid #ccc; - height:20px; - display:inline-block; - position:relative; - left:0; - min-width:55px; - text-align:left; - padding-left:5px; - @include border_box_sizing; - font-size:12px; - color:white; - - .subscore { - font-size:10px; - color:white; - bottom:-15px; - right:-16px; - position:absolute; - } - } - - .good-marker { - position:absolute; - text-align:center; - left:95%; - width: 0; - height: 0; - border-left: 5px solid transparent; - border-right: 5px solid transparent; - border-top: 5px solid #72a43b; - margin-left:-5px; - top:-7px; - } - - .good-line { - position:absolute; - height:100%; - left:95%; - width:1px; - background-color: #72a43b; - margin-left:-0.5px; - top:0; - } - } - .network-test-text { - - } - - .network-test-results { - height: 248px ! important; - @include border_box_sizing; - &.testing { - - text-align:left; - .network-test-score { - display:none; - } - - .scoring-bar { - display:inline-block; - margin-bottom:10px; - } - - .network-test-text { - //background-image: url('/assets/shared/spinner.gif'); - //background-repeat:no-repeat; - //background-position:center; - //width:128px; - //height:128px; - } - } - - &.good { - .network-test-score { - background-color: #72a43b; - } - } - - &.acceptable { - .network-test-score { - background-color: #D6A800; - } - } - - &.bad { - .network-test-score { - background-color: #7B0C00; - } - } - } - } - - .wizard-step[layout-wizard-step="6"] { - .wizard-step-content .wizard-step-column { - &:nth-of-type(1) { - width:50%; - height:350px; - } - &:nth-of-type(2) { - width:50%; - } - - ul { - margin-bottom:20px; - } - } - } - - p { + .watch-video { margin-top: 12px; } - .device_type ul { - list-style:disc; + .help-content { + margin-top: 22px; + margin-left: 20px; } - .device_type li { - margin: 15px 12px 15px 36px; - } + .test-direct-monitoring { + margin-top: 40px; + display: inline-block; + text-decoration: none; + width: 90px; - select { - max-width: 220px; - } - - .settings-profile { - margin-top: 12px; - } - - p.intro { - margin-top:0px; - } - - .easydropdown-wrapper { - width:100%; - max-width:220px; - } - .ftue-new { - clear:both; - position:relative; - width:100%; - height: 54px; - margin-top: 12px; - select { - font-size: 15px; - padding: 3px; - } - .latency { - position: absolute; - top: 120px; - font-size: 12px; - .report { - color:#fff; - position: absolute; - top: 20px; - left: 0px; - width: 105px; - height: 50px; - background-color: #72a43b; - padding: 10px; - text-align: center; - .ms-label { - padding-top: 10px; - font-size: 34px; - font-family: Arial, sans-serif; - } - p { - margin-top: 4px; - } - } - .report.neutral, .report.start, .report.unknown { - background-color: #666; - } - .report.good { - background-color: #72a43b; - } - .report.acceptable { - background-color: #D6A800; - } - .report.bad { - background-color: #7B0C00; - } - .instructions { - color:#fff; - position: absolute; - top: 20px; - left: 125px; - width: 595px; - height: 50px; - padding: 10px; - background-color: #666; - } - .instructions p.start, .instructions p.neutral { - padding-top: 4px; - } - .instructions p.unknown { - margin-top:0px; - padding-top: 4px; - } - .instructions p.good { - padding-top: 4px; - } - .instructions p.acceptable { - margin-top: -6px; - } - .instructions p.bad { - margin-top: -6px; - line-height: 16px; - } - .instructions p a { - font-weight: bold; + &.paused { + .playing { + display: none; } } - .column { - position:absolute; - width: 220px; - height: 50px; - margin-right:8px; - } - .settings-2-device { - left:0px; - } - .settings-2-center { - left:50%; - margin-left: -110px; - .buttons { - margin-top: 14px; - a { - margin-right: 0px; - } + &.playing { + .paused { + display: none; } } - .settings-2-voice { - top: 0px; - right:0px; - } - .controls { - margin-top: 16px; - background-color: #222; - height: 48px; - width: 220px; - } - .ftue-vu-left { - position:relative; - top: 0px; - } - .ftue-vu-right { - position:relative; - top: 22px; - } - .ftue-fader { - position:relative; - top: 14px; - left: 8px; - } - .gain-label { - color: $ColorScreenPrimary; - position:absolute; - top: 76px; - right: 6px; - } - .subcolumn { - position:absolute; - top: 60px; - font-size: 12px !important; - width: 68px; - height: 48px; + .direct-monitoring-btn { + padding-left: 5px; + font-size: 16px; } - .subcolumn select { - width: 68px; + + * { + vertical-align: middle; } - .subcolumn.first { - left:0px; + } + } + + .network-test { + .network-test-results { + height:268px ! important; + } + } + .wizard-step[layout-wizard-step="5"], .network-test { + + .wizard-step-content .wizard-step-column { + &:nth-of-type(1) { + width: 25%; } - .subcolumn.second { - left:50%; - margin-left:-34px; + &:nth-of-type(2) { + width: 50%; } - .subcolumn.third { - right:0px; + &:nth-of-type(3) { + width: 25%; } } - .asio-settings { - clear:both; - position:relative; - width:100%; - height: 54px; - margin-top: 8px; - .column { - position:absolute; - width: 220px; - height: 50px; - margin-right:8px; + .summary { + margin-left: 20px; + margin-top: 22px; + } + + .watch-video { + margin-top: 90px; + } + + a.start-network-test { + margin-top: 20px; + } + + .network-test-score { + height: 24px; + padding: 10px; + color: white; + font-size: 20px; + background-color: #222; + text-align: center; + margin-bottom: 20px; + + &.good { + background-color: #72a43b; } - .settings-driver { - left:0px; + &.acceptable { + background-color: #D6A800; } - .settings-asio { - left:50%; - margin-left: -110px; - } - .settings-asio.mac { - left:0px; - margin-left: 0px; - } - .settings-asio-button { - right:0px; - height: 45px; - .bottom { - position:absolute; - bottom:0px; - } - } - .settings-asio-button.mac { - right:auto; - left:50%; - margin-left: -110px; - } - .subcolumn { - position:absolute; - width: 68px; - height: 48px; - } - .subcolumn select { - width: 68px; - } - .subcolumn.first { - left:0px; - } - .subcolumn.second { - left:50%; - margin-left:-34px; - } - .subcolumn.third { - right:0px; + &.bad { + background-color: #7B0C00; } } - .settings-controls { - - clear:both; - position:relative; + .scoring-bar { width: 100%; + height: 20px; + left: 0; + position: relative; + //display:inline-block; + display: none; + + .current-score { + background-color: gray; + border-right: 1px solid white; + border-top: 1px solid #ccc; + border-bottom: 1px solid #ccc; + border-left: 1px solid #ccc; + height: 20px; + display: inline-block; + position: relative; + left: 0; + min-width: 55px; + text-align: left; + padding-left: 5px; + @include border_box_sizing; + font-size: 12px; + color: white; + line-height:18px; + + .subscore { + font-size: 10px; + color: white; + bottom: -15px; + right: -16px; + position: absolute; + } + } + + .good-marker { + position: absolute; + text-align: center; + left: 95%; + width: 0; + height: 0; + border-left: 5px solid transparent; + border-right: 5px solid transparent; + border-top: 5px solid #72a43b; + margin-left: -5px; + top: -7px; + } + + .good-line { + position: absolute; + height: 100%; + left: 95%; + width: 1px; + background-color: #72a43b; + margin-left: -0.5px; + top: 0; + } + } + .network-test-text { + + } + + .network-test-results { + height: 248px; + @include border_box_sizing; + &.testing { + + text-align: left; + .network-test-score { + display: none; + } + + .scoring-bar { + display: inline-block; + margin-bottom: 10px; + } + + .network-test-text { + //background-image: url('/assets/shared/spinner.gif'); + //background-repeat:no-repeat; + //background-position:center; + //width:128px; + //height:128px; + } + } + + &.good { + .network-test-score { + background-color: #72a43b; + } + } + + &.acceptable { + .network-test-score { + background-color: #D6A800; + } + } + + &.bad { + .network-test-score { + background-color: #7B0C00; + } + } + } + } + + .wizard-step[layout-wizard-step="6"] { + .wizard-step-content .wizard-step-column { + &:nth-of-type(1) { + width: 50%; + height: 350px; + } + &:nth-of-type(2) { + width: 50%; + } + + ul { + margin-bottom: 20px; + } + } + } + + p { + margin-top: 12px; + } + + .device_type ul { + list-style: disc; + } + + .device_type li { + margin: 15px 12px 15px 36px; + } + + select { + max-width: 220px; + } + + .settings-profile { + margin-top: 12px; + } + + p.intro { + margin-top: 0px; + } + + .easydropdown-wrapper { + width: 100%; + max-width: 220px; + } + .ftue-new { + clear: both; + position: relative; + width: 100%; + height: 54px; + margin-top: 12px; + select { + font-size: 15px; + padding: 3px; + } + .latency { + position: absolute; + top: 120px; + font-size: 12px; + .report { + color: #fff; + position: absolute; + top: 20px; + left: 0px; + width: 105px; + height: 50px; + background-color: #72a43b; + padding: 10px; + text-align: center; + .ms-label { + padding-top: 10px; + font-size: 34px; + font-family: Arial, sans-serif; + } + p { + margin-top: 4px; + } + } + .report.neutral, .report.start, .report.unknown { + background-color: #666; + } + .report.good { + background-color: #72a43b; + } + .report.acceptable { + background-color: #D6A800; + } + .report.bad { + background-color: #7B0C00; + } + .instructions { + color: #fff; + position: absolute; + top: 20px; + left: 125px; + width: 595px; + height: 50px; + padding: 10px; + background-color: #666; + } + .instructions p.start, .instructions p.neutral { + padding-top: 4px; + } + .instructions p.unknown { + margin-top: 0px; + padding-top: 4px; + } + .instructions p.good { + padding-top: 4px; + } + .instructions p.acceptable { + margin-top: -6px; + } + .instructions p.bad { + margin-top: -6px; + line-height: 16px; + } + .instructions p a { + font-weight: bold; + } + } + .column { + position: absolute; + width: 220px; + height: 50px; + margin-right: 8px; + } + .settings-2-device { + left: 0px; + } + .settings-2-center { + left: 50%; + margin-left: -110px; + .buttons { + margin-top: 14px; + a { + margin-right: 0px; + } + } + } + .settings-2-voice { + top: 0px; + right: 0px; + } + .controls { + margin-top: 16px; + background-color: #222; + height: 48px; + width: 220px; + } + .ftue-vu-left { + position: relative; + top: 0px; + } + .ftue-vu-right { + position: relative; + top: 22px; + } + .ftue-fader { + position: relative; + top: 14px; + left: 8px; + } + .gain-label { + color: $ColorScreenPrimary; + position: absolute; + top: 76px; + right: 6px; + } + + .subcolumn { + position: absolute; + top: 60px; + font-size: 12px !important; + width: 68px; + height: 48px; + } + .subcolumn select { + width: 68px; + } + .subcolumn.first { + left: 0px; + } + .subcolumn.second { + left: 50%; + margin-left: -34px; + } + .subcolumn.third { + right: 0px; + } + } + + .asio-settings { + clear: both; + position: relative; + width: 100%; + height: 54px; + margin-top: 8px; + .column { + position: absolute; + width: 220px; + height: 50px; + margin-right: 8px; + } + .settings-driver { + left: 0px; + } + .settings-asio { + left: 50%; + margin-left: -110px; + } + .settings-asio.mac { + left: 0px; + margin-left: 0px; + } + .settings-asio-button { + right: 0px; + height: 45px; + .bottom { + position: absolute; + bottom: 0px; + } + } + .settings-asio-button.mac { + right: auto; + left: 50%; + margin-left: -110px; + } + .subcolumn { + position: absolute; + width: 68px; + height: 48px; + } + .subcolumn select { + width: 68px; + } + .subcolumn.first { + left: 0px; + } + .subcolumn.second { + left: 50%; + margin-left: -34px; + } + .subcolumn.third { + right: 0px; + } + } + + .settings-controls { + + clear: both; + position: relative; + width: 100%; + height: 100px; + + div.section { + position: absolute; + width: 220px; height: 100px; - div.section { - position:absolute; - width: 220px; - height: 100px; - - select { - display:block; - width: 100%; - } - } - .audio-input { - left:0px; - margin-top:30px; - - &.audio-output-showing { - margin-top:0; - } - } - .voice-chat-input { - left:50%; - margin-left: -110px; - } - .audio-output { - right:0px; - } - .ftue-controls { - margin-top: 16px; - position:relative; - height: 48px; - width: 220px; - background-color: #222; - } - .ftue-vu-left { - position:relative; - top: 0px; - } - .ftue-vu-right { - position:relative; - top: 22px; - } - .ftue-fader { - position:relative; - top: 14px; - left: 8px; - } - .gain-label { - color: $ColorScreenPrimary; - position:absolute; - top: 14px; - right: 6px; + select { + display: block; + width: 100%; } } + .audio-input { + left: 0px; + margin-top: 30px; - .buttonbar { - position:absolute; - bottom: 24px; + &.audio-output-showing { + margin-top: 0; + } + } + .voice-chat-input { + left: 50%; + margin-left: -110px; + } + .audio-output { right: 0px; - a { - color: darken(#fff, 5); - text-decoration: none; - } - - .spinner-small { - display:none; - margin-top: 3px; - position: relative; - top: 12px; - } } - - input[type=text], input[type=password] { - padding:3px; - font-size:13px; - width:145px; + .ftue-controls { + margin-top: 16px; + position: relative; + height: 48px; + width: 220px; + background-color: #222; } - - select.audiodropdown { - width:223px; - color:#666; + .ftue-vu-left { + position: relative; + top: 0px; } + .ftue-vu-right { + position: relative; + top: 22px; + } + .ftue-fader { + position: relative; + top: 14px; + left: 8px; + } + .gain-label { + color: $ColorScreenPrimary; + position: absolute; + top: 14px; + right: 6px; + } + } + .buttonbar { + position: absolute; + bottom: 24px; + right: 0px; a { - color:#ccc; + color: darken(#fff, 5); + text-decoration: none; } - a:hover { - color:#fff; + .spinner-small { + display: none; + margin-top: 3px; + position: relative; + top: 12px; } + } - .ftue-instrumentlist { - width:340px; - height:178px; - background-color:#c5c5c5; - border:none; - -webkit-box-shadow: inset 2px 2px 3px 0px #888; - box-shadow: inset 2px 2px 3px 0px #888; - color:#666; - overflow:auto; - font-size:14px; - } + input[type=text], input[type=password] { + padding: 3px; + font-size: 13px; + width: 145px; + } - .ftue-instrumentlist select, .ftue-instrumentlist .easydropdown { - width:100%; - color:#666; - } + select.audiodropdown { + width: 223px; + color: #666; + } + a { + color: #ccc; + } + + a:hover { + color: #fff; + } + + .ftue-instrumentlist { + width: 340px; + height: 178px; + background-color: #c5c5c5; + border: none; + -webkit-box-shadow: inset 2px 2px 3px 0px #888; + box-shadow: inset 2px 2px 3px 0px #888; + color: #666; + overflow: auto; + font-size: 14px; + } + + .ftue-instrumentlist select, .ftue-instrumentlist .easydropdown { + width: 100%; + color: #666; } } \ No newline at end of file diff --git a/web/app/views/clients/_configure_tracks_dialog.html.haml b/web/app/views/clients/_configure_tracks_dialog.html.haml index ec437ff32..9d8248c56 100644 --- a/web/app/views/clients/_configure_tracks_dialog.html.haml +++ b/web/app/views/clients/_configure_tracks_dialog.html.haml @@ -13,8 +13,6 @@ .clearall .tab{'tab-id' => 'music-audio'} - eat it - .clearall diff --git a/web/app/views/clients/_networkTestDialog.html.haml b/web/app/views/clients/_networkTestDialog.html.haml new file mode 100644 index 000000000..d09c03585 --- /dev/null +++ b/web/app/views/clients/_networkTestDialog.html.haml @@ -0,0 +1,10 @@ +.dialog.network-test{ layout: 'dialog', 'layout-id' => 'network-test', id: 'network-test-dialog'} + .content-head + %h1 Test Router & Network + .dialog-inner + = render :partial => '/clients/network_test' + .clearall + .buttons + %a.button-grey.btn-cancel{href:'#'} CANCEL + %a.button-orange.btn-help{href: '#'} HELP + %a.button-orange.btn-close{href:'#'} CLOSE \ No newline at end of file diff --git a/web/app/views/clients/_network_test.html.haml b/web/app/views/clients/_network_test.html.haml new file mode 100644 index 000000000..373d4c748 --- /dev/null +++ b/web/app/views/clients/_network_test.html.haml @@ -0,0 +1,30 @@ +.network-test + .help-text In this step, you will test your router and Internet connection to ensure that you can play in online sessions, and to see how many musicians can be in a session with you based on your internet connection. + .wizard-step-content + .wizard-step-column + %h2 Instructions + .ftue-box.instructions + %ul + %li Check that computer is connected to router using Ethernet cable. + %li Click Start Network Test button. + %li View test results. + .center + %a.button-orange.watch-video{href:'#'} WATCH VIDEO + .wizard-step-column + .summary + %p Ensure that your computer is connected to your home router using an Ethernet cable rather than using Wi-Fi wireless access. If necessary, find or purchase a long Ethernet cable, up to 100 ft. + %p Then click on the Start Network Test button below. + .center + %a.button-orange.start-network-test{href:'#'} START NETWORK TEST + .wizard-step-column + %h2 Test Results + .network-test-results.ftue-box + .scoring-bar + .current-score + testing... + .subscore + .good-marker + .good-line + .network-test-score + .scored-clients + .network-test-text \ No newline at end of file diff --git a/web/app/views/clients/index.html.erb b/web/app/views/clients/index.html.erb index 4093b3f72..3c946bcb3 100644 --- a/web/app/views/clients/index.html.erb +++ b/web/app/views/clients/index.html.erb @@ -57,6 +57,7 @@ <%= render "hoverSession" %> <%= render "whatsNextDialog" %> <%= render "shareDialog" %> +<%= render "networkTestDialog" %> <%= render "recordingFinishedDialog" %> <%= render "localRecordingsDialog" %> <%= render "showServerErrorDialog" %> @@ -251,6 +252,9 @@ var configureTracksDialog = new JK.ConfigureTracksDialog(JK.app); configureTracksDialog.initialize(); + var networkTestDialog = new JK.NetworkTestDialog(JK.app); + networkTestDialog.initialize(); + var testBridgeScreen = new JK.TestBridgeScreen(JK.app); testBridgeScreen.initialize(); diff --git a/web/app/views/clients/wizard/gear/_gear_wizard.html.haml b/web/app/views/clients/wizard/gear/_gear_wizard.html.haml index ea9fc4dd5..c703f7128 100644 --- a/web/app/views/clients/wizard/gear/_gear_wizard.html.haml +++ b/web/app/views/clients/wizard/gear/_gear_wizard.html.haml @@ -142,39 +142,10 @@ %span.direct-monitoring-btn Play - .wizard-step{ 'layout-wizard-step' => "5", 'dialog-title' => "Test Router & Network", 'dialog-purpose' => "TestRouterNetwork" } + .wizard-step.network-test{ 'layout-wizard-step' => "5", 'dialog-title' => "Test Router & Network", 'dialog-purpose' => "TestRouterNetwork" } .ftuesteps .clearall - .help-text In this step, you will test your router and Internet connection to ensure that you can play in online sessions, and to see how many musicians can be in a session with you based on your internet connection. - .wizard-step-content - .wizard-step-column - %h2 Instructions - .ftue-box.instructions - %ul - %li Check that computer is connected to router using Ethernet cable. - %li Click Start Network Test button. - %li View test results. - .center - %a.button-orange.watch-video{href:'#'} WATCH VIDEO - .wizard-step-column - .summary - %p Ensure that your computer is connected to your home router using an Ethernet cable rather than using Wi-Fi wireless access. If necessary, find or purchase a long Ethernet cable, up to 100 ft. - %p Then click on the Start Network Test button below. - .center - %a.button-orange.start-network-test{href:'#'} START NETWORK TEST - .wizard-step-column - %h2 Test Results - .network-test-results.ftue-box - .scoring-bar - .current-score - testing... - .subscore - .good-marker - .good-line - .network-test-score - .scored-clients - .network-test-text - + = render :partial => '/clients/network_test' .wizard-step{ 'layout-wizard-step' => "6", 'dialog-title' => "Success!", 'dialog-purpose' => "Success" } .ftuesteps diff --git a/web/app/views/users/_user_dropdown.html.erb b/web/app/views/users/_user_dropdown.html.erb index 5504c5b0f..dd694bdb8 100644 --- a/web/app/views/users/_user_dropdown.html.erb +++ b/web/app/views/users/_user_dropdown.html.erb @@ -37,6 +37,9 @@
  • <%= link_to "Download App", downloads_path, :rel => "external" %>
  • + <% if @nativeClient %> +
  • <%= link_to "Test Network", '#' %>
  • + <% end %>
  • <%= link_to "Community Forum", Rails.application.config.vanilla_url, :rel => "external" %>
  • <%= link_to "Get Help", 'https://jamkazam.desk.com/', :rel => "external" %>
  • <%= link_to "Sign Out", signout_path, method: "delete" %>
  • diff --git a/web/spec/factories.rb b/web/spec/factories.rb index 6c5598e9a..f3bdd0ed4 100644 --- a/web/spec/factories.rb +++ b/web/spec/factories.rb @@ -469,5 +469,25 @@ FactoryGirl.define do end end + factory :diagnostic, :class => JamRuby::Diagnostic do + type JamRuby::Diagnostic::NO_HEARTBEAT_ACK + creator JamRuby::Diagnostic::CLIENT + data Faker::Lorem.sentence + end + + factory :latency_tester, :class => JamRuby::LatencyTester do + ignore do + connection nil + make_connection true + end + + sequence(:client_id) { |n| "LatencyTesterClientId-#{n}" } + + after(:create) do |latency_tester, evaluator| + latency_tester.connection = evaluator.connection if evaluator.connection + latency_tester.connection = FactoryGirl.create(:connection, client_type: Connection::TYPE_LATENCY_TESTER, client_id: latency_tester.client_id) if evaluator.make_connection + latency_tester.save + end + end end diff --git a/web/spec/features/gear_wizard_spec.rb b/web/spec/features/gear_wizard_spec.rb index c94f7891e..1acc2848a 100644 --- a/web/spec/features/gear_wizard_spec.rb +++ b/web/spec/features/gear_wizard_spec.rb @@ -7,10 +7,12 @@ describe "Gear Wizard", :js => true, :type => :feature, :capybara_feature => tru let(:user) { FactoryGirl.create(:user) } before(:each) do + LatencyTester.delete_all sign_in_poltergeist user end it "success path" do + FactoryGirl.create(:latency_tester) visit '/client#/account/audio' # step 1 - intro find("div.account-audio a[data-purpose='add-profile']").trigger(:click) @@ -37,7 +39,6 @@ describe "Gear Wizard", :js => true, :type => :feature, :capybara_feature => tru find('.ftue-step-title', text: 'Test Router & Network') find('.button-orange.start-network-test').trigger(:click) find('.user-btn', text: 'RUN NETWORK TEST ANYWAY').trigger(:click) - find('.button-grey.start-network-test').trigger(:click) find('.button-orange.start-network-test') find('.btn-next.button-orange').trigger(:click) diff --git a/web/spec/features/network_test_spec.rb b/web/spec/features/network_test_spec.rb new file mode 100644 index 000000000..b9cb2f92b --- /dev/null +++ b/web/spec/features/network_test_spec.rb @@ -0,0 +1,41 @@ +require 'spec_helper' + +describe "Network Test", :js => true, :type => :feature, :capybara_feature => true, :slow => true do + + subject { page } + + let(:user) { FactoryGirl.create(:user) } + + describe "native client" do + + before(:each) do + LatencyTester.delete_all + emulate_client + sign_in_poltergeist user + end + + it "success" do + FactoryGirl.create(:latency_tester) + + open_user_dropdown + page.find('.test-network').trigger(:click) + find('h1', text: 'Test Router & Network') + + # start the test + find('.start-network-test').trigger(:click) + find('.user-btn', text: 'RUN NETWORK TEST ANYWAY').trigger(:click) + find('.scored-clients', text: '5') + end + end + + describe "normal browser" do + before(:each) do + sign_in_poltergeist user + end + + it "not available in regular browser" do + open_user_dropdown + page.should_not have_selector('.test-network') + end + end +end diff --git a/web/spec/support/utilities.rb b/web/spec/support/utilities.rb index 998b75962..78259a1b3 100644 --- a/web/spec/support/utilities.rb +++ b/web/spec/support/utilities.rb @@ -120,11 +120,15 @@ def sign_out() end def sign_out_poltergeist(options = {}) - find('.userinfo').hover() + open_user_dropdown click_link 'Sign Out' should_be_at_root if options[:validate] end +def open_user_dropdown + find('.userinfo').hover() +end + def should_be_at_root find('h1', text: 'Play music together over the Internet as if in the same room') end From 653b03bea116f0c9ebbd4f5fc00162d330f9c997 Mon Sep 17 00:00:00 2001 From: Seth Call Date: Sun, 8 Jun 2014 22:26:50 -0500 Subject: [PATCH 12/32] * VRFS-1754 - new configure tracks dialog --- .../javascripts/accounts_audio_profile.js | 74 ++- web/app/assets/javascripts/addNewGear.js | 2 +- .../javascripts/configureTrackDialog.js | 120 ++++- .../javascripts/configureTracksHelper.js | 422 ++++++++++++++++++ web/app/assets/javascripts/fakeJamClient.js | 11 +- web/app/assets/javascripts/globals.js | 5 + web/app/assets/javascripts/layout.js | 20 +- .../assets/javascripts/notificationPanel.js | 3 +- web/app/assets/javascripts/utils.js | 9 + web/app/assets/javascripts/voiceChatHelper.js | 222 +++++++++ .../wizard/gear/step_configure_tracks.js | 234 +--------- .../wizard/gear/step_configure_voice_chat.js | 128 +----- .../wizard/gear/step_select_gear.js | 3 +- .../assets/javascripts/wizard/gear_utils.js | 68 ++- .../wizard/loopback/loopback_wizard.js | 6 +- web/app/assets/stylesheets/client/client.css | 2 + .../client/configureTracksDialog.css.scss | 120 ++++- .../client/dragDropTracks.css.scss | 162 +++++++ .../client/networkTestDialog.css.scss | 7 + .../client/voiceChatHelper.css.scss | 60 +++ .../client/wizard/gearWizard.css.scss | 196 +------- .../clients/_account_audio_profile.html.erb | 1 + .../_configure_tracks_dialog.html.haml | 51 ++- web/app/views/clients/_session.html.erb | 2 +- .../wizard/gear/_gear_wizard.html.haml | 41 +- 25 files changed, 1363 insertions(+), 606 deletions(-) create mode 100644 web/app/assets/javascripts/configureTracksHelper.js create mode 100644 web/app/assets/javascripts/voiceChatHelper.js create mode 100644 web/app/assets/stylesheets/client/dragDropTracks.css.scss create mode 100644 web/app/assets/stylesheets/client/voiceChatHelper.css.scss diff --git a/web/app/assets/javascripts/accounts_audio_profile.js b/web/app/assets/javascripts/accounts_audio_profile.js index 26a6e36a1..e6e5efcbd 100644 --- a/web/app/assets/javascripts/accounts_audio_profile.js +++ b/web/app/assets/javascripts/accounts_audio_profile.js @@ -4,9 +4,12 @@ context.JK = context.JK || {}; context.JK.AccountAudioProfile = function (app) { - var self = this; + + var EVENTS = context.JK.EVENTS; + var gearUtils = context.JK.GearUtils; var logger = context.JK.logger; var rest = context.JK.Rest(); + var self = this; var userId; function beforeShow(data) { @@ -35,28 +38,26 @@ } function populateAccountAudio() { - var all = context.jamClient.FTUEGetAllAudioConfigurations(); - var good = context.jamClient.FTUEGetGoodAudioConfigurations(); - var current = context.jamClient.FTUEGetMusicProfileName(); + var profiles = gearUtils.getProfiles(); - var profiles = []; - context._.each(all, function(item) { - profiles.push({id: item, good: false, class:'bad', current: current == item, active_text: current == item ? '(active)' : ''}) + context._.each(profiles, function(profile) { + profile.active_text = profile.current ? '(active)' : ''; }); - if(good) { - for(var i = 0; i < good.length; i++) { - for(var j = 0; j < profiles.length; j++) { - if(good[i] == profiles[j].id) { - profiles[j].good = true; - profiles[j].class = 'good'; - break; - } - } + // If you are in the FTUE, and you close the client and/or it crashes + // then you will have 'FTUE' (incomplete) profiles. This is the only time + // the user might see them, so we clean them before they get to see it + var cleansedProfiles = []; + context._.each(profiles, function(profile) { + if(profile.id.indexOf('FTUE') == 0) { + context.jamClient.TrackDeleteProfile(profile.id); } - } + else { + cleansedProfiles.push(profile) + } + }); - var template = context._.template($('#template-account-audio').html(), {is_admin: context.JK.currentUserAdmin, profiles: profiles}, {variable: 'data'}); + var template = context._.template($('#template-account-audio').html(), {is_admin: context.JK.currentUserAdmin, profiles: cleansedProfiles}, {variable: 'data'}); appendAudio(template); } @@ -86,11 +87,34 @@ logger.error("unable to activate audio configuration: " + audioProfileId); context.JK.alertSupportedNeeded("Unable to activate audio configuration for profile named: " + audioProfileId); } + else { + // redraw after activation of profile + populateAccountAudio(); + } } app.layout.showDialog('loopback-wizard'); } + function handleConfigureAudioProfile(audioProfileId) { + + if(audioProfileId != context.jamClient.FTUEGetMusicProfileName()) { + var result = context.jamClient.FTUELoadAudioConfiguration(audioProfileId); + + if(!result) { + logger.error("unable to activate audio configuration: " + audioProfileId); + context.JK.alertSupportedNeeded("Unable to activate audio configuration for profile named: " + audioProfileId); + } + else { + // redraw after activation of profile + populateAccountAudio(); + } + } + + app.layout.showDialog('configure-tracks') + .one(EVENTS.DIALOG_CLOSED, populateAccountAudio) + } + function handleActivateAudioProfile(audioProfileId) { logger.debug("activating audio profile: " + audioProfileId); @@ -171,6 +195,20 @@ return false; }); + $root.on('click', 'a[data-purpose=configure-audio-profile]', function (evt) { + evt.stopPropagation(); + var $btn = $(this); + var status = $btn.closest('tr').attr('data-status'); + if(status == "good") { + handleConfigureAudioProfile($btn.attr('data-id')); + } + else { + context.JK.Banner.showAlert("Unable to configure this profile. Please verify that the devices associated are connected."); + } + return false; + }); + + $root.on('click', 'a[data-purpose=add-profile]', function (evt) { evt.stopPropagation(); handleStartAudioQualification(); diff --git a/web/app/assets/javascripts/addNewGear.js b/web/app/assets/javascripts/addNewGear.js index 26af81510..cbe9f7d50 100644 --- a/web/app/assets/javascripts/addNewGear.js +++ b/web/app/assets/javascripts/addNewGear.js @@ -11,7 +11,7 @@ sessionScreen.setPromptLeave(false); - app.layout.closeDialog('configure-audio'); + app.layout.closeDialog('configure-tracks'); context.location = "/client#/home"; diff --git a/web/app/assets/javascripts/configureTrackDialog.js b/web/app/assets/javascripts/configureTrackDialog.js index 543ab3e32..42887ad90 100644 --- a/web/app/assets/javascripts/configureTrackDialog.js +++ b/web/app/assets/javascripts/configureTrackDialog.js @@ -7,13 +7,24 @@ var logger = context.JK.logger; var ASSIGNMENT = context.JK.ASSIGNMENT; var VOICE_CHAT = context.JK.VOICE_CHAT; + var gearUtils = context.JK.GearUtils; + + var $dialog = null; + var $instructions = null; + var $musicAudioTab = null; + var $musicAudioTabSelector = null; + var $voiceChatTab = null; + var $voiceChatTabSelector = null; + var $certifiedAudioProfile = null; + var $btnCancel = null; + var $btnAddNewGear = null; + var $btnUpdateTrackSettings = null; + + var configureTracksHelper = null; + var voiceChatHelper = null; + var profiles = null; + var currentProfile = null; - var $dialog = null; - var $instructions = null; - var $musicAudioTab = null; - var $musicAudioTabSelector = null; - var $voiceChatTab = null; - var $voiceChatTabSelector = null; var configure_audio_instructions = { "Win32": "Choose the audio device you would like to use for this session. If needed, use arrow buttons to assign audio inputs " + @@ -38,6 +49,8 @@ function setInstructions(type) { if (type === 'audio') { + $instructions.html('Choose your audio device. Drag and drop to assign input ports to tracks, and specify the instrument for each track. Drag and drop to assign a pair of output ports for session stereo audio monitoring.') + return; var os = context.jamClient.GetOSAsString(); $instructions.html(configure_audio_instructions[os]); } @@ -67,17 +80,16 @@ } function validateVoiceChatSettings() { - return true; + return voiceChatHelper.trySave(); } function showMusicAudioPanel() { setInstructions('audio'); activateTab('audio'); - } function validateAudioSettings() { - return true; + return configureTracksHelper.trySave(); } function showVoiceChatPanel() { @@ -88,21 +100,92 @@ function events() { $musicAudioTabSelector.click(function () { // validate voice chat settings - if (validateVoiceChatSettings(true)) { - showMusicAudioPanel(false); + if (validateVoiceChatSettings()) { + configureTracksHelper.reset(); + voiceChatHelper.reset(); + showMusicAudioPanel(); } }); $voiceChatTabSelector.click(function () { // validate audio settings - if (validateAudioSettings(true)) { - showVoiceChatPanel(false); + if (validateAudioSettings()) { + configureTracksHelper.reset(); + voiceChatHelper.reset(); + showVoiceChatPanel(); } }); + + $btnCancel.click(function() { + app.layout.closeDialog('configure-tracks') + return false; + }); + + + $btnAddNewGear.click(function() { + + return false; + }); + + $btnUpdateTrackSettings.click(function() { + if(configureTracksHelper.trySave() && voiceChatHelper.trySave()) { + app.layout.closeDialog('configure-tracks'); + } + + return false; + }); + + $certifiedAudioProfile.change(deviceChanged); } + function renderCertifiedGearDropdown() { + + var optionsHtml = ''; + context._.each(profiles, function (profile) { + if(profile.good) { + optionsHtml += ''; + } + }); + $certifiedAudioProfile.html(optionsHtml); + + context.JK.dropdown($certifiedAudioProfile); + } + + function deviceChanged() { + var profile = $certifiedAudioProfile.val(); + + if(currentProfile == profile) { + return; // just bail early, because the easydropdown fires change events even when you select the same value + } + logger.debug("activating audio profile: " + profile); + + var result = context.jamClient.FTUELoadAudioConfiguration(profile); + + if(!result) { + logger.error("unable to activate audio configuration: " + profile); + context.JK.alertSupportedNeeded("Unable to activate audio configuration for profile named: " + profile); + renderCertifiedGearDropdown(); // force the dropdown to be reflective of the actually active profile + } + else { + configureTracksHelper.reset(); + } + + } function beforeShow() { + profiles = gearUtils.getProfiles(); + renderCertifiedGearDropdown(); showMusicAudioPanel(); + + currentProfile = context.jamClient.FTUEGetMusicProfileName(); + + if(currentProfile != $certifiedAudioProfile.val()) { + logger.error("the currently active profile (" + currentProfile + ") is not the same as the Certified Audio Gear dropdown (" + $certifiedAudioProfile.val() + ")"); + context.JK.alertSupportedNeeded("Unable to determine the current profile."); + } + + configureTracksHelper.reset(); + voiceChatHelper.reset(); + } function afterHide() { @@ -124,6 +207,17 @@ $musicAudioTabSelector = $dialog.find('.tab-configure-audio'); $voiceChatTab = $dialog.find('div[tab-id="voice-chat"]'); $voiceChatTabSelector = $dialog.find('.tab-configure-voice'); + $certifiedAudioProfile = $dialog.find('.certified-audio-profile'); + $btnCancel = $dialog.find('.btn-cancel'); + $btnAddNewGear = $dialog.find('.btn-add-new-audio-gear'); + $btnUpdateTrackSettings = $dialog.find('.btn-update-settings'); + + configureTracksHelper = new JK.ConfigureTracksHelper(app); + configureTracksHelper.initialize($dialog); + + voiceChatHelper = new JK.VoiceChatHelper(app); + voiceChatHelper.initialize($dialog, false); + events(); } diff --git a/web/app/assets/javascripts/configureTracksHelper.js b/web/app/assets/javascripts/configureTracksHelper.js new file mode 100644 index 000000000..62c3ee168 --- /dev/null +++ b/web/app/assets/javascripts/configureTracksHelper.js @@ -0,0 +1,422 @@ +(function (context, $) { + + "use strict"; + + context.JK = context.JK || {}; + context.JK.ConfigureTracksHelper = function (app) { + var logger = context.JK.logger; + var ASSIGNMENT = context.JK.ASSIGNMENT; + var VOICE_CHAT = context.JK.VOICE_CHAT; + var MAX_TRACKS = context.JK.MAX_TRACKS; + var MAX_OUTPUTS = context.JK.MAX_OUTPUTS; + var gearUtils = context.JK.GearUtils; + + var $parent = null; + var $templateAssignablePort = null; + var $templateTrackTarget = null; + var $templateOutputTarget = null; + var $unassignedInputsHolder = null; + var $unassignedOutputsHolder = null; + var $tracksHolder = null; + var $outputChannelHolder = null; + var $instrumentsHolder = null; + + function loadChannels() { + var musicPorts = jamClient.FTUEGetChannels(); + + $unassignedInputsHolder.empty(); + $unassignedOutputsHolder.empty(); + $tracksHolder.find('.ftue-input').remove(); + $outputChannelHolder.find('.ftue-input').remove(); + + var inputChannels = musicPorts.inputs; + var outputChannels = musicPorts.outputs; + + context._.each(inputChannels, function (inputChannel) { + var $channel = $(context._.template($templateAssignablePort.html(), inputChannel, { variable: 'data' })); + + if(inputChannel.assignment == ASSIGNMENT.UNASSIGNED) { + unassignInputChannel($channel); + } + else if(inputChannel.assignment == ASSIGNMENT.CHAT) { + // well, we can't show it as unused... if there were a place to show chat inputs, we would put it there. + // but we don't have it, so just skip + logger.debug("skipping channel ", inputChannel) + return; + } + else { + // find the track this belongs in + + var trackNumber = inputChannel.assignment - 1; + + var $track = $tracksHolder.find('.track[data-num="' + trackNumber + '"]') + + if($track.length == 0) { + context.JK.alertSupportedNeeded('Unable to find a track for channel with assignment ' + inputChannel.assignment); + return false; + } + addChannelToTrack($channel, $track.find('.track-target')); + } + + $channel.draggable({ + helper: 'clone', + start: function() { + var $channel = $(this); + var $track = $channel.closest('.track-target'); + var isUnassigned = $track.length == 0; + if(isUnassigned) { + $tracksHolder.find('.track-target').addClass('possible-target'); + } + else { + $tracksHolder.find('.track-target').addClass('possible-target'); + $unassignedInputsHolder.addClass('possible-target'); + } + }, + stop: function() { + $tracksHolder.find('.track-target').removeClass('possible-target'); + $unassignedInputsHolder.removeClass('possible-target') + } + }); + }) + + var outputAssignment = 0; + context._.each(outputChannels, function (outputChannel, index) { + var $channel = $(context._.template($templateAssignablePort.html(), outputChannel, { variable: 'data' })); + + if(outputChannel.assignment == ASSIGNMENT.UNASSIGNED) { + unassignOutputChannel($channel); + } + else { + var $output = $outputChannelHolder.find('.output[data-num="' + index + '"]') + + if($output.length == 0) { + context.JK.alertSupportedNeeded('Unable to find an output for channel with assignment ' + outputChannel.assignment); + return false; + } + addChannelToOutput($channel, $output.find('.output-target')); + } + + $channel.draggable({ + helper: 'clone', + start: function() { + var $channel = $(this); + var $output = $channel.closest('.output-target'); + var isUnassigned = $output.length == 0; + if(isUnassigned) { + $outputChannelHolder.find('.output-target').addClass('possible-target'); + } + else { + $outputChannelHolder.find('.output-target').addClass('possible-target'); + $unassignedOutputsHolder.addClass('possible-target'); + } + }, + stop: function() { + $outputChannelHolder.find('.output-target').removeClass('possible-target'); + $unassignedOutputsHolder.removeClass('possible-target') + } + }); + }); + } + + // iterates through the dom and returns a pure data structure for track associations and output channels + function getCurrentState() { + + var state = {}; + state.tracks = []; + state.unassignedChannels = []; + state.outputs = []; + var $unassignedInputChannels = $unassignedInputsHolder.find('.ftue-input'); + var $unassignedOutputChannels = $unassignedOutputsHolder.find('.ftue-input'); + var $tracks = $tracksHolder.find('.track-target'); + var $outputs = $outputChannelHolder.find('.output-target'); + + context._.each($unassignedInputChannels, function($unassignedInput) { + $unassignedInput = $($unassignedInput); + var channelId = $unassignedInput.attr('data-input-id'); + state.unassignedChannels.push(channelId); + }) + + context._.each($unassignedOutputChannels, function($unassignedOutput) { + $unassignedOutput = $($unassignedOutput); + var channelId = $unassignedOutput.attr('data-input-id'); + state.unassignedChannels.push(channelId); + }) + + context._.each($tracks, function($track, index) { + $track = $($track); + var $assignedChannels = $track.find('.ftue-input'); + + var track = {index: index, channels:[]}; + context._.each($assignedChannels, function($assignedChannel) { + $assignedChannel = $($assignedChannel); + track.channels.push($assignedChannel.attr('data-input-id')) + }); + + // sparse array + if(track.channels.length > 0) { + state.tracks.push(track); + } + var $instrument = $instrumentsHolder.find('[data-num="' + index + '"]').find('.icon-instrument-select'); + track.instrument_id = $instrument.data('instrument_id'); + }) + + context._.each($outputs, function($output, index) { + $output = $($output); + var $assignedChannel = $output.find('.ftue-input'); + + // this is overkill since there should only be 1 or 0 .ftue-inputs in a given .output + var outputSlot = {index: index, channels:[]}; + context._.each($assignedChannel, function($assignedChannel) { + $assignedChannel = $($assignedChannel); + outputSlot.channels.push($assignedChannel.attr('data-input-id')) + }); + + // sparse array + if(outputSlot.channels.length > 0) { + state.outputs.push(outputSlot); + } + }) + return state; + } + + function validate(tracks) { + // there must be at least one assigned channel + if(tracks.tracks.length == 0) { + logger.debug("ConfigureTracks validation error: must have assigned at least one input port to a track."); + context.JK.Banner.showAlert('Must have assigned at least one input port to a track.'); + return false; + } + + // there must be some instruments + context._.each(tracks.tracks, function(track) { + if(!track.instrument_id) { + logger.debug("ConfigureTracks validation error: all tracks with ports assigned must specify an instrument."); + context.JK.Banner.showAlert('All tracks with ports assigned must specify an instrument.'); + return false; + } + }); + + // there must be exactly 2 output channels assigned + if(tracks.outputs.length != 2 || (tracks.outputs[0].channels.length != 1 && track.outputs[1].channels.length != 1)) { + logger.debug("ConfigureTracks validation error: must have assigned exactly two output ports"); + context.JK.Banner.showAlert('Must have assigned exactly 2 output ports.'); + return false; + } + + return true; + } + + function save(state) { + + context._.each(state.unassignedChannels, function(unassignedChannelId) { + context.jamClient.TrackSetAssignment(unassignedChannelId, true, ASSIGNMENT.UNASSIGNED); + }); + + // save input/tracks + context._.each(state.tracks, function(track, index) { + + var trackNumber = index + 1; + + context._.each(track.channels, function(channelId) { + context.jamClient.TrackSetAssignment(channelId, true, trackNumber); + + }); + logger.debug("context.jamClient.TrackSetInstrument(trackNumber, track.instrument_id)", trackNumber, track.instrument_id); + context.jamClient.TrackSetInstrument(trackNumber, context.JK.instrument_id_to_instrument[track.instrument_id].client_id); + }); + + // save outputs + context._.each(state.outputs, function(output, index) { + context._.each(output.channels, function(channelId) { + context.jamClient.TrackSetAssignment(channelId, true, ASSIGNMENT.OUTPUT); + }); + }); + + var result = context.jamClient.TrackSaveAssignments(); + + if(!result || result.length == 0) { + // success + return true; + } + else { + context.JK.Banner.showAlert('Unable to save assignments. ' + result); + return false; + } + } + + function loadTrackInstruments() { + var $trackInstruments = $instrumentsHolder.find('.track-instrument'); + + context._.each($trackInstruments, function(trackInstrument) { + var $trackInstrument = $(trackInstrument); + + var trackIndex = parseInt($trackInstrument.attr('data-num')) + 1; + + var clientInstrument = context.jamClient.TrackGetInstrument(trackIndex); + + var instrument = context.JK.client_to_server_instrument_map[clientInstrument]; + + $trackInstrument.instrumentSelectorSet(instrument ? instrument.server_id : instrument); + }); + } + + function trySave() { + var state = getCurrentState(); + + if(!validate(state)) { + return false; + } + + var saved = save(state); + + if(saved) { + context.JK.GA.trackConfigureTracksCompletion(context.JK.detectOS()); + } + + return saved; + } + + function reset() { + loadChannels(); + loadTrackInstruments(); + } + + function unassignOutputChannel($channel) { + var $originallyAssignedTrack = $channel.closest('.output-target'); + $unassignedOutputsHolder.append($channel); + $originallyAssignedTrack.attr('output-count', $originallyAssignedTrack.find('.ftue-input:not(.ui-draggable-dragging)').length); + } + + function unassignInputChannel($channel) { + var $originallyAssignedTrack = $channel.closest('.track-target'); + $unassignedInputsHolder.append($channel); + $originallyAssignedTrack.attr('track-count', $originallyAssignedTrack.find('.ftue-input:not(.ui-draggable-dragging)').length); + + } + + function addChannelToTrack($channel, $track) { + var $originallyAssignedTrack = $channel.closest('.track-target'); + $track.append($channel); + $track.attr('track-count', $track.find('.ftue-input:not(.ui-draggable-dragging)').length); + $originallyAssignedTrack.attr('track-count', $originallyAssignedTrack.find('.ftue-input:not(.ui-draggable-dragging)').length) + } + + function addChannelToOutput($channel, $slot) { + var $originallyAssignedTrack = $channel.closest('.output-target'); + $slot.append($channel); + $slot.attr('output-count', $slot.find('.ftue-input:not(.ui-draggable-dragging)').length); + $originallyAssignedTrack.attr('output-count', $originallyAssignedTrack.find('.ftue-input:not(.ui-draggable-dragging)').length) + } + + + function initializeUnassignedOutputDroppable() { + $unassignedOutputsHolder.droppable( + { + activeClass: 'drag-in-progress', + hoverClass: 'drag-hovering', + drop: function( event, ui ) { + var $channel = ui.draggable; + + //$channel.css('left', '0').css('top', '0'); + unassignOutputChannel($channel); + } + }); + } + + function initializeUnassignedInputDroppable() { + $unassignedInputsHolder.droppable( + { + activeClass: 'drag-in-progress', + hoverClass: 'drag-hovering', + drop: function( event, ui ) { + var $channel = ui.draggable; + //$channel.css('left', '0').css('top', '0'); + unassignInputChannel($channel); + } + }); + } + + function initializeOutputDroppables() { + var i; + for(i = 0; i < MAX_OUTPUTS; i++) { + var $target = $(context._.template($templateOutputTarget.html(), {num: i }, { variable: 'data' })); + $outputChannelHolder.append($target); + $target.find('.output-target').droppable( + { + activeClass: 'drag-in-progress', + hoverClass: 'drag-hovering', + drop: function( event, ui ) { + var $slot = $(this); + if($slot.attr('output-count') == 1) { + return false; // max of 1 output per slot + } + var $channel = ui.draggable; + //$channel.css('left', '0').css('top', '0'); + addChannelToOutput($channel, $slot); + } + }); + } + } + + function initializeTrackDroppables() { + var i; + for(i = 0; i < MAX_TRACKS; i++) { + var $target = $(context._.template($templateTrackTarget.html(), {num: i }, { variable: 'data' })); + $tracksHolder.append($target); + $target.find('.track-target').droppable( + { + activeClass: 'drag-in-progress', + hoverClass: 'drag-hovering', + drop: function( event, ui ) { + var $track = $(this); + if($track.attr('track-count') == 2) { + return false; // max of 2 inputs per track + } + + var $channel = ui.draggable; + //$channel.css('left', '0').css('top', '0'); + addChannelToTrack($channel, $track); + } + }); + } + } + + function initializeInstrumentDropdown() { + var i; + for(i = 0; i < MAX_TRACKS; i++) { + var $root = $('
    '); + $root.instrumentSelector().attr('data-num', i); + $instrumentsHolder.append($root); + } + } + + + function initialize(_$parent) { + $parent = _$parent; + + $templateAssignablePort = $('#template-assignable-port'); + $templateTrackTarget = $('#template-track-target'); + $templateOutputTarget = $('#template-output-target'); + $unassignedInputsHolder = $parent.find('.unassigned-input-channels') + $unassignedOutputsHolder = $parent.find('.unassigned-output-channels'); + $tracksHolder = $parent.find('.tracks'); + $instrumentsHolder = $parent.find('.instruments'); + $outputChannelHolder = $parent.find('.output-channels'); + + + initializeUnassignedInputDroppable(); + initializeTrackDroppables(); + initializeInstrumentDropdown(); + + initializeUnassignedOutputDroppable(); + initializeOutputDroppables(); + } + + this.initialize = initialize; + this.trySave = trySave; + this.reset = reset; + + return this; + }; + +})(window, jQuery); \ No newline at end of file diff --git a/web/app/assets/javascripts/fakeJamClient.js b/web/app/assets/javascripts/fakeJamClient.js index 0f580bca0..6c4596517 100644 --- a/web/app/assets/javascripts/fakeJamClient.js +++ b/web/app/assets/javascripts/fakeJamClient.js @@ -235,21 +235,25 @@ } function FTUEGetGoodConfigurationList() { - return ['a']; + return ['default']; } function FTUEGetAllAudioConfigurations() { - return ['a']; + return ['default']; } function FTUEGetGoodAudioConfigurations() { - return ['a']; + return ['default']; } function FTUEGetConfigurationDevice() { return 'Good Device'; } + function FTUELoadAudioConfiguration() { + return true; + } + function FTUEIsMusicDeviceWDM() { return false; } @@ -850,6 +854,7 @@ this.FTUEGetGoodAudioConfigurations = FTUEGetGoodAudioConfigurations; this.FTUEGetConfigurationDevice = FTUEGetConfigurationDevice; this.FTUEIsMusicDeviceWDM = FTUEIsMusicDeviceWDM; + this.FTUELoadAudioConfiguration = FTUELoadAudioConfiguration; // Session this.SessionAddTrack = SessionAddTrack; diff --git a/web/app/assets/javascripts/globals.js b/web/app/assets/javascripts/globals.js index 9f32fb61d..623670af6 100644 --- a/web/app/assets/javascripts/globals.js +++ b/web/app/assets/javascripts/globals.js @@ -27,7 +27,12 @@ CHAT: "1" }; + context.JK.EVENTS = { + DIALOG_CLOSED : 'dialog_closed' + } + context.JK.MAX_TRACKS = 6; + context.JK.MAX_OUTPUTS = 2; // TODO: store these client_id values in instruments table, or store // server_id as the client_id to prevent maintenance nightmares. As it's diff --git a/web/app/assets/javascripts/layout.js b/web/app/assets/javascripts/layout.js index 60a87ee4d..5c4fc72df 100644 --- a/web/app/assets/javascripts/layout.js +++ b/web/app/assets/javascripts/layout.js @@ -27,6 +27,7 @@ // privates var logger = context.JK.logger; + var EVENTS = context.JK.EVENTS; var NOT_HANDLED = "not handled"; var me = null; // Reference to this instance for context sanity. @@ -433,8 +434,9 @@ var $overlay = $('.dialog-overlay'); unstackDialogs($overlay); $dialog.hide(); + $dialog.triggerHandler(EVENTS.DIALOG_CLOSED, {name: dialog, dialogCount: openDialogs.length}); + $(context).triggerHandler(EVENTS.DIALOG_CLOSED, {name: dialog, dialogCount: openDialogs.length}) dialogEvent(dialog, 'afterHide'); - $(me).triggerHandler('dialog_closed', {dialogCount: openDialogs.length}) } function screenEvent(screen, evtName, data) { @@ -538,6 +540,11 @@ */ layout(); + // add an attribute to any dialogs, which let's it know it's current screen (useful for contextual styling) + context._.each(openDialogs, function(dialog) { + addScreenContextToDialog($(dialog)); + }) + screenEvent(previousScreen, 'afterHide', data); screenEvent(currentScreen, 'afterShow', data); @@ -631,6 +638,10 @@ } } + function addScreenContextToDialog($dialog) { + $dialog.attr('current-screen', currentScreen); // useful for contextual styling of dialogs + } + function showDialog(dialog, options) { if (dialogEvent(dialog, 'beforeShow', options) === false) { return; @@ -649,8 +660,10 @@ centerDialog(dialog); var $dialog = $('[layout-id="' + dialog + '"]'); stackDialogs($dialog, $overlay); + addScreenContextToDialog($dialog) $dialog.show(); dialogEvent(dialog, 'afterShow', options); + return $dialog; } function centerDialog(dialog) { @@ -903,7 +916,7 @@ } this.showDialog = function (dialog, options) { - showDialog(dialog, options); + return showDialog(dialog, options); }; this.dialogObscuredNotification = function(payload) { @@ -926,6 +939,9 @@ return activeElementEvent(evtName, data); } + this.getCurrentScreen = function() { + return currentScreen; // will be a string of the layout-id of the active screen + } this.close = function (evt) { close(evt); }; diff --git a/web/app/assets/javascripts/notificationPanel.js b/web/app/assets/javascripts/notificationPanel.js index 454c11b04..3bd5c7722 100644 --- a/web/app/assets/javascripts/notificationPanel.js +++ b/web/app/assets/javascripts/notificationPanel.js @@ -4,6 +4,7 @@ context.JK = context.JK || {}; context.JK.NotificationPanel = function(app) { + var EVENTS = context.JK.EVENTS; var logger = context.JK.logger; var friends = []; var rest = context.JK.Rest(); @@ -138,7 +139,7 @@ } function events() { - $(app.layout).on('dialog_closed', function(e, data) {if(data.dialogCount == 0) userCameBack(); }); + $(context).on(EVENTS.DIALOG_CLOSED, function(e, data) {if(data.dialogCount == 0) userCameBack(); }); $(window).focus(userCameBack); $(window).blur(windowBlurred); app.user() diff --git a/web/app/assets/javascripts/utils.js b/web/app/assets/javascripts/utils.js index 78f373a60..c8788b791 100644 --- a/web/app/assets/javascripts/utils.js +++ b/web/app/assets/javascripts/utils.js @@ -142,6 +142,15 @@ $element.data("prodTimer", null); $element.btOff(); }, options['duration'])); + + $element.on('remove', function() { + var timer = $element.data('prodTimer') + if(timer) { + clearTimeout(timer); + $element.data("prodTimer", null); + $element.btOff(); + } + }) } /** * Associates a bubble on hover (by default) with the specified $element, using jquery.bt.js (BeautyTips) diff --git a/web/app/assets/javascripts/voiceChatHelper.js b/web/app/assets/javascripts/voiceChatHelper.js new file mode 100644 index 000000000..ea5b5dc26 --- /dev/null +++ b/web/app/assets/javascripts/voiceChatHelper.js @@ -0,0 +1,222 @@ +(function (context, $) { + + "use strict"; + + context.JK = context.JK || {}; + context.JK.VoiceChatHelper = function (app) { + var logger = context.JK.logger; + var ASSIGNMENT = context.JK.ASSIGNMENT; + var VOICE_CHAT = context.JK.VOICE_CHAT; + var MAX_TRACKS = context.JK.MAX_TRACKS; + var MAX_OUTPUTS = context.JK.MAX_OUTPUTS; + var gearUtils = context.JK.GearUtils; + + var $parent = null; + + var $reuseAudioInputRadio = null; + var $useChatInputRadio = null; + var $chatInputs = null; + var $templateChatInput = null; + var $selectedChatInput = null;// should only be used if isChatEnabled = true + var saveImmediate = null; // if true, then every action by the user results in a save to the backend immediately, false means you have to call trySave to persist + + function defaultReuse() { + $reuseAudioInputRadio.iCheck('check').attr('checked', 'checked'); + $useChatInputRadio.removeAttr('checked'); + } + + function isChatEnabled() { + + return $useChatInputRadio.is(':checked'); + } + + function reset() { + + $selectedChatInput = null; + + if(context.jamClient.TrackGetChatEnable()) { + enableChat(false); + } + else { + disableChat(false); + } + + $chatInputs.empty(); + + var chatInputs = gearUtils.getChatInputs(); + + context._.each(chatInputs, function(chatInput) { + if(chatInput.assignment > 0) { + return; + } + var chatChannelName = chatInput.name; + var chatChannelId = chatInput.id; + var isCurrentlyChat = chatInput.assignment == ASSIGNMENT.CHAT; + var $chat = $(context._.template($templateChatInput.html(), {id: chatChannelId, name: chatChannelName}, { variable: 'data' })); + var $chatInput = $chat.find('input'); + if(isCurrentlyChat) { + $selectedChatInput = $chatInput; + $selectedChatInput.attr('checked', 'checked'); + } + $chat.hide(); // we'll show it once it's styled with iCheck + $chatInputs.append($chat); + }); + + var $radioButtons = $chatInputs.find('input[name="chat-device"]'); + context.JK.checkbox($radioButtons).on('ifChecked', function(e) { + var $input = $(e.currentTarget); + $selectedChatInput = $input; // for use in handleNext + if(saveImmediate) { + var channelId = $input.attr('data-channel-id'); + context.jamClient.TrackSetAssignment(channelId, true, ASSIGNMENT.CHAT); + var result = context.jamClient.TrackSaveAssignments(); + + if(!result || result.length == 0) { + // success + } + else { + context.JK.Banner.showAlert('Unable to save assignments. ' + result); + return false; + } + } + }); + + if(!isChatEnabled()) { + $radioButtons.iCheck('disable'); + } + + $chatInputs.find('.chat-input').show().on('click', function() { + if(!isChatEnabled()) { + context.JK.prodBubble($parent.find('.use-chat-input h3'), 'chat-not-enabled', {}, { positions:['left']}); + } + }) + } + + function disableChat(applyToBackend) { + if(saveImmediate && applyToBackend) { + logger.debug("voiceChatHelper: disabling chat to backend"); + context.jamClient.TrackSetChatEnable(false); + var result = context.jamClient.TrackSaveAssignments(); + + if(!result || result.length == 0) { + // success + $reuseAudioInputRadio.iCheck('check').attr('checked', 'checked'); + $useChatInputRadio.removeAttr('checked'); + + } + else { + context.JK.Banner.showAlert('Unable to disable chat. ' + result); + return false; + } + } + else { + logger.debug("voiceChatHelper: disabling chat UI only"); + $reuseAudioInputRadio.iCheck('check').attr('checked', 'checked'); + $useChatInputRadio.removeAttr('checked'); + } + var $radioButtons = $chatInputs.find('input[name="chat-device"]'); + $radioButtons.iCheck('disable'); + } + + function enableChat(applyToBackend) { + if(saveImmediate && applyToBackend) { + logger.debug("voiceChatHelper: enabling chat to backend"); + context.jamClient.TrackSetChatEnable(true); + var result = context.jamClient.TrackSaveAssignments(); + + if(!result || result.length == 0) { + // success + $useChatInputRadio.iCheck('check').attr('checked', 'checked'); + $reuseAudioInputRadio.removeAttr('checked'); + + } + else { + context.JK.Banner.showAlert('Unable to enable chat. ' + result); + return false; + } + } + else { + logger.debug("voiceChatHelper: enabling chat UI only"); + $useChatInputRadio.iCheck('check').attr('checked', 'checked'); + $reuseAudioInputRadio.removeAttr('checked'); + } + + var $radioButtons = $chatInputs.find('input[name="chat-device"]'); + $radioButtons.iCheck('enable'); + } + + function handleChatEnabledToggle() { + context.JK.checkbox($reuseAudioInputRadio); + context.JK.checkbox($useChatInputRadio); + + // plugin sets to relative on the element; have to do this as an override + $reuseAudioInputRadio.closest('.iradio_minimal').css('position', 'absolute'); + $useChatInputRadio.closest('.iradio_minimal').css('position', 'absolute'); + + $reuseAudioInputRadio.on('ifChecked', function() { disableChat(true) }); + $useChatInputRadio.on('ifChecked', function() { enableChat(true) }); + } + + // gets the state of the UI + function getCurrentState() { + var state = { + enabled:null, + chat_channel:null + }; + + state.enabled = $useChatInputRadio.is(':checked'); + state.chat_channel = $selectedChatInput && $selectedChatInput.attr('data-channel-id'); + logger.debug("desired chat state: enabled=" + state.enabled + ", chat_channel=" + state.chat_channel) + return state; + } + + function trySave() { + + var state = getCurrentState(); + + if(state.enabled && state.chat_channel) { + logger.debug("enabling chat. chat_channel=" + state.chat_channel); + context.jamClient.TrackSetChatEnable(true); + context.jamClient.FTUESetChatInput(state.chat_channel); + //context.jamClient.TrackSetAssignment(state.chat_channel, true, ASSIGNMENT.CHAT); + } + else { + logger.debug("disabling chat."); + context.jamClient.TrackSetChatEnable(false); + if(state.chat_channel) { + context.jamClient.TrackSetAssignment(state.chat_channel, true, ASSIGNMENT.UNASSIGNED); + } + } + + var result = context.jamClient.TrackSaveAssignments(); + + if(!result || result.length == 0) { + // success + return true; + } + else { + context.JK.Banner.showAlert('Unable to save chat assignments. ' + result); + return false; + } + } + + function initialize(_$step, _saveImmediate) { + $parent = _$step; + saveImmediate = _saveImmediate; + + $reuseAudioInputRadio = $parent.find('.reuse-audio-input input'); + $useChatInputRadio = $parent.find('.use-chat-input input'); + $chatInputs = $parent.find('.chat-inputs'); + $templateChatInput = $('#template-chat-input'); + + handleChatEnabledToggle(); + } + + this.reset = reset; + this.trySave = trySave; + this.initialize = initialize; + + return this; + }; + +})(window, jQuery); \ No newline at end of file diff --git a/web/app/assets/javascripts/wizard/gear/step_configure_tracks.js b/web/app/assets/javascripts/wizard/gear/step_configure_tracks.js index cfcabc988..2b8698846 100644 --- a/web/app/assets/javascripts/wizard/gear/step_configure_tracks.js +++ b/web/app/assets/javascripts/wizard/gear/step_configure_tracks.js @@ -10,175 +10,13 @@ var MAX_TRACKS = context.JK.MAX_TRACKS; var logger = context.JK.logger; + var configureTracksHelper = new context.JK.ConfigureTracksHelper(app); var $step = null; - var $templateAssignablePort = null; - var $templateTrackTarget = null; - var $unassignedChannelsHolder = null; - var $tracksHolder = null; - var $instrumentsHolder = null; - function loadChannels() { - var musicPorts = jamClient.FTUEGetChannels(); - - $unassignedChannelsHolder.empty(); - $tracksHolder.find('.ftue-input').remove(); - - var inputChannels = musicPorts.inputs; - - context._.each(inputChannels, function (inputChannel) { - if(inputChannel.assignment == ASSIGNMENT.UNASSIGNED) { - var $channel = $(context._.template($templateAssignablePort.html(), inputChannel, { variable: 'data' })); - unassignChannel($channel); - } - else { - var $channel = $(context._.template($templateAssignablePort.html(), inputChannel, { variable: 'data' })); - - // find the track this belongs in - - var trackNumber = inputChannel.assignment - 1; - - var $track = $tracksHolder.find('.track[data-num="' + trackNumber + '"]') - - if($track.length == 0) { - context.JK.alertSupportedNeeded('Unable to find a track for channel with assignment ' + inputChannel.assignment); - return false; - } - addChannelToTrack($channel, $track.find('.track-target')); - } - - $channel.draggable({ - helper: 'clone', - start: function() { - var $channel = $(this); - var $track = $channel.closest('.track-target'); - var isUnassigned = $track.length == 0; - if(isUnassigned) { - $tracksHolder.find('.track-target').addClass('possible-target'); - } - else { - $tracksHolder.find('.track-target').addClass('possible-target'); - $unassignedChannelsHolder.addClass('possible-target'); - } - }, - stop: function() { - $tracksHolder.find('.track-target').removeClass('possible-target'); - $unassignedChannelsHolder.removeClass('possible-target') - } - }); - }) - } - - // iterates through the dom and returns a pure data structure for track associations - function trackAssociations() { - - var tracks = {}; - tracks.tracks = []; - tracks.unassignedChannels = []; - var $unassignedChannels = $unassignedChannelsHolder.find('.ftue-input'); - var $tracks = $tracksHolder.find('.track-target'); - - context._.each($unassignedChannels, function($unassignedTrack) { - $unassignedTrack = $($unassignedTrack); - var channelId = $unassignedTrack.attr('data-input-id'); - tracks.unassignedChannels.push(channelId); - }) - - context._.each($tracks, function($track, index) { - $track = $($track); - var $assignedChannels = $track.find('.ftue-input'); - - var track = {index: index, channels:[]}; - context._.each($assignedChannels, function($assignedChannel) { - $assignedChannel = $($assignedChannel); - track.channels.push($assignedChannel.attr('data-input-id')) - }); - - // sparse array - if(track.channels.length > 0) { - tracks.tracks.push(track); - } - var $instrument = $instrumentsHolder.find('[data-num="' + index + '"]').find('.icon-instrument-select'); - track.instrument_id = $instrument.data('instrument_id'); - }) - return tracks; - } - - function validate(tracks) { - // there must be at least one assigned channel - - if(tracks.tracks.length == 0) { - logger.debug("ConfigureTracks validation error: must have assigned at least one input port to a track."); - context.JK.Banner.showAlert('Must have assigned at least one input port to a track.'); - return false; - } - - context._.each(tracks.tracks, function(track) { - if(!track.instrument_id) { - logger.debug("ConfigureTracks validation error: all tracks with ports assigned must specify an instrument."); - context.JK.Banner.showAlert('All tracks with ports assigned must specify an instrument.'); - return false; - } - }); - - return true; - } - - function save(tracks) { - - context._.each(tracks.unassignedChannels, function(unassignedChannelId) { - context.jamClient.TrackSetAssignment(unassignedChannelId, true, ASSIGNMENT.UNASSIGNED); - }); - - context._.each(tracks.tracks, function(track, index) { - - var trackNumber = index + 1; - - context._.each(track.channels, function(channelId) { - context.jamClient.TrackSetAssignment(channelId, true, trackNumber); - - }); - logger.debug("context.jamClient.TrackSetInstrument(trackNumber, track.instrument_id)", trackNumber, track.instrument_id); - context.jamClient.TrackSetInstrument(trackNumber, context.JK.instrument_id_to_instrument[track.instrument_id].client_id); - }); - - var result = context.jamClient.TrackSaveAssignments(); - - if(!result || result.length == 0) { - // success - return true; - } - else { - context.JK.Banner.showAlert('Unable to save assignments. ' + result); - return false; - } - } - - function loadTrackInstruments() { - var $trackInstruments = $instrumentsHolder.find('.track-instrument'); - - context._.each($trackInstruments, function(trackInstrument) { - var $trackInstrument = $(trackInstrument); - - var trackIndex = parseInt($trackInstrument.attr('data-num')) + 1; - - var clientInstrument = context.jamClient.TrackGetInstrument(trackIndex); - - var instrument = context.JK.client_to_server_instrument_map[clientInstrument]; - - $trackInstrument.instrumentSelectorSet(instrument ? instrument.server_id : instrument); - }); - } - function handleNext() { - var tracks = trackAssociations(); - - if(!validate(tracks)) { - return false; - } - - var saved = save(tracks); + var saved = configureTracksHelper.trySave(); if(saved) { context.JK.GA.trackConfigureTracksCompletion(context.JK.detectOS()); @@ -188,77 +26,13 @@ } function beforeShow() { - loadChannels(); - loadTrackInstruments(); - } - - function unassignChannel($channel) { - var $originallyAssignedTrack = $channel.closest('.track-target'); - $unassignedChannelsHolder.append($channel); - $originallyAssignedTrack.attr('track-count', $originallyAssignedTrack.find('.ftue-input:not(.ui-draggable-dragging)').length); - - } - function addChannelToTrack($channel, $track) { - var $originallyAssignedTrack = $channel.closest('.track-target'); - $track.append($channel); - $track.attr('track-count', $track.find('.ftue-input:not(.ui-draggable-dragging)').length); - $originallyAssignedTrack.attr('track-count', $originallyAssignedTrack.find('.ftue-input:not(.ui-draggable-dragging)').length) - } - - function initializeUnassignedDroppable() { - $unassignedChannelsHolder.droppable( - { - activeClass: 'drag-in-progress', - hoverClass: 'drag-hovering', - drop: function( event, ui ) { - var $channel = ui.draggable; - //$channel.css('left', '0').css('top', '0'); - unassignChannel($channel); - } - }); - } - - function initializeTrackDroppables() { - var i; - for(i = 0; i < MAX_TRACKS; i++) { - var $target = $(context._.template($templateTrackTarget.html(), {num: i }, { variable: 'data' })); - $tracksHolder.append($target); - $target.find('.track-target').droppable( - { - activeClass: 'drag-in-progress', - hoverClass: 'drag-hovering', - drop: function( event, ui ) { - var $track = $(this); - var $channel = ui.draggable; - //$channel.css('left', '0').css('top', '0'); - addChannelToTrack($channel, $track); - } - }); - } - } - - function initializeInstrumentDropdown() { - var i; - for(i = 0; i < MAX_TRACKS; i++) { - var $root = $('
    '); - $root.instrumentSelector().attr('data-num', i); - $instrumentsHolder.append($root); - } + configureTracksHelper.reset(); } function initialize(_$step) { $step = _$step; - $templateAssignablePort = $('#template-assignable-port'); - $templateTrackTarget = $('#template-track-target'); - $unassignedChannelsHolder = $step.find('.unassigned-channels'); - $tracksHolder = $step.find('.tracks'); - $instrumentsHolder = $step.find('.instruments'); - - - initializeUnassignedDroppable(); - initializeTrackDroppables(); - initializeInstrumentDropdown(); + configureTracksHelper.initialize($step); } this.handleNext = handleNext; diff --git a/web/app/assets/javascripts/wizard/gear/step_configure_voice_chat.js b/web/app/assets/javascripts/wizard/gear/step_configure_voice_chat.js index dd5c3874f..c48e83a2a 100644 --- a/web/app/assets/javascripts/wizard/gear/step_configure_voice_chat.js +++ b/web/app/assets/javascripts/wizard/gear/step_configure_voice_chat.js @@ -17,144 +17,24 @@ var $templateChatInput = null; var $selectedChatInput = null;// should only be used if isChatEnabled = true + var voiceChatHelper = new context.JK.VoiceChatHelper(app); + function newSession() { - $reuseAudioInputRadio.attr('checked', 'checked').iCheck('check'); - } - - function isChannelAvailableForChat(chatChannelId, musicPorts) { - var result = true; - context._.each(musicPorts.input, function(inputChannel) { - // if the channel is currently assigned to a track, it not unassigned - if(inputChannel.id == chatChannelId && (inputChannel.assignment > 0)) { - result = false; - return false; // break - } - }); - - return result; - } - - function isChatEnabled() { - return $useChatInputRadio.is(':checked'); + voiceChatHelper.reset(); } function beforeShow() { - - if(isChatEnabled()) { - enableChat(); - } - else { - disableChat(); - } - - var musicPorts = jamClient.FTUEGetChannels(); - var chatInputs = context.jamClient.FTUEGetChatInputs(); - - $chatInputs.empty(); - - context._.each(chatInputs, function(chatChannelName, chatChannelId) { - if(isChannelAvailableForChat(chatChannelId, musicPorts)) { - var $chat = $(context._.template($templateChatInput.html(), {id: chatChannelId, name: chatChannelName}, { variable: 'data' })); - $chat.hide(); // we'll show it once it's styled with iCheck - $chatInputs.append($chat); - } - }); - - var $radioButtons = $chatInputs.find('input[name="chat-device"]'); - context.JK.checkbox($radioButtons).on('ifChecked', function(e) { - var $input = $(e.currentTarget); - $selectedChatInput = $input; // for use in handleNext - var channelId = $input.attr('data-channel-id'); - context.jamClient.TrackSetAssignment(channelId, true, ASSIGNMENT.CHAT); - var result = context.jamClient.TrackSaveAssignments(); - - if(!result || result.length == 0) { - // success - } - else { - context.JK.Banner.showAlert('Unable to save assignments. ' + result); - return false; - } - }); - - if(!isChatEnabled()) { - $radioButtons.iCheck('disable'); - } - - $chatInputs.find('.chat-input').show().on('click', function() { - if(!isChatEnabled()) { - context.JK.prodBubble($step.find('.use-chat-input h3'), 'chat-not-enabled', {}, { positions:['left']}); - } - }) - } - - function disableChat() { - logger.debug("FTUE: disabling chat"); - context.jamClient.TrackSetChatEnable(false); - var result = context.jamClient.TrackSaveAssignments(); - - if(!result || result.length == 0) { - // success - } - else { - context.JK.Banner.showAlert('Unable to disable chat. ' + result); - return false; - } - - var $radioButtons = $chatInputs.find('input[name="chat-device"]'); - $radioButtons.iCheck('disable'); - } - - function enableChat() { - logger.debug("FTUE: enabling chat"); - context.jamClient.TrackSetChatEnable(true); - var result = context.jamClient.TrackSaveAssignments(); - - if(!result || result.length == 0) { - // success - } - else { - context.JK.Banner.showAlert('Unable to enable chat. ' + result); - return false; - } - - var $radioButtons = $chatInputs.find('input[name="chat-device"]'); - $radioButtons.iCheck('enable'); - } - - function handleChatEnabledToggle() { - context.JK.checkbox($reuseAudioInputRadio); - context.JK.checkbox($useChatInputRadio); - - // plugin sets to relative on the element; have to do this as an override - $reuseAudioInputRadio.closest('.iradio_minimal').css('position', 'absolute'); - $useChatInputRadio.closest('.iradio_minimal').css('position', 'absolute'); - - $reuseAudioInputRadio.on('ifChecked', disableChat); - $useChatInputRadio.on('ifChecked', enableChat) } function handleNext() { - var selectedDeviceInfo = gearUtils.selectedDeviceInfo(context.jamClient.FTUEGetInputMusicDevice(), context.jamClient.FTUEGetOutputMusicDevice()); - - var chatName = null; - if(isChatEnabled()) { - chatName = $selectedChatInput.attr('data-channel-name'); - } - context.jamClient.FTUESetMusicProfileName(gearUtils.createProfileName(selectedDeviceInfo, chatName)); - return true; } function initialize(_$step) { $step = _$step; - $reuseAudioInputRadio = $step.find('.reuse-audio-input input'); - $useChatInputRadio = $step.find('.use-chat-input input'); - $chatInputs = $step.find('.chat-inputs'); - $templateChatInput = $('#template-chat-input'); + voiceChatHelper.initialize($step, true); - handleChatEnabledToggle(); } this.handleNext = handleNext; 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 a18c3f4e2..bfd75d343 100644 --- a/web/app/assets/javascripts/wizard/gear/step_select_gear.js +++ b/web/app/assets/javascripts/wizard/gear/step_select_gear.js @@ -5,6 +5,7 @@ context.JK = context.JK || {}; context.JK.StepSelectGear = function (app, dialog) { + var EVENTS = context.JK.DIALOG_CLOSED; var ASSIGNMENT = context.JK.ASSIGNMENT; var VOICE_CHAT = context.JK.VOICE_CHAT; var AUDIO_DEVICE_BEHAVIOR = context.JK.AUDIO_DEVICE_BEHAVIOR; @@ -298,7 +299,7 @@ function initializeLoopback() { $launchLoopbackBtn.unbind('click').click(function() { - $(dialog.getLoopbackWizard()).one('dialog_closed', function() { + $(dialog.getLoopbackWizard().getDialog()).one(EVENTS.DIALOG_CLOSED, function() { loopbackShowing = false; if(dialog.getLoopbackWizard().getGearTest().isGoodFtue()) { diff --git a/web/app/assets/javascripts/wizard/gear_utils.js b/web/app/assets/javascripts/wizard/gear_utils.js index 874fb8e63..7129c9e38 100644 --- a/web/app/assets/javascripts/wizard/gear_utils.js +++ b/web/app/assets/javascripts/wizard/gear_utils.js @@ -83,7 +83,7 @@ // * Linux function determineDeviceType(deviceId, displayName) { if (operatingSystem == "MacOSX") { - if (displayName.toLowerCase().trim() == "built-in") { + if (displayName.toLowerCase().trim().indexOf("built-in") == 0) { return "MacOSX_builtin"; } else { @@ -157,6 +157,36 @@ } } + /** + * Lists all profiles, but marks profiles good: true/false. + * Also, current:true/false indicates which profile is active. (at most 1 profile will be marked current) + * This is to provide a unified view of FTUEGetAllAudioConfigurations & FTUEGetGoodAudioConfigurations + * @returns an array of profiles, where each profile is: {id: profile-name, good: boolean, class: 'bad' | 'good', current: boolean } + */ + gearUtils.getProfiles = function() { + var all = context.jamClient.FTUEGetAllAudioConfigurations(); + var good = context.jamClient.FTUEGetGoodAudioConfigurations(); + var current = context.jamClient.FTUEGetMusicProfileName(); + + var profiles = []; + context._.each(all, function(item) { + profiles.push({id: item, good: false, class:'bad', current: current == item}) + }); + + if(good) { + for(var i = 0; i < good.length; i++) { + for(var j = 0; j < profiles.length; j++) { + if(good[i] == profiles[j].id) { + profiles[j].good = true; + profiles[j].class = 'good'; + break; + } + } + } + } + + return profiles; + } gearUtils.postDiagnostic = function(operatingSystem, deviceInformation, selectedDeviceInfo, gearTest, frameBuffers, isAutomated) { rest.createDiagnostic({ type: 'GEAR_SELECTION', @@ -169,4 +199,40 @@ }); } + // complete list of possibly chatInputs, whether currently assigned as the chat channel or not + // each item should be {id: channelId, name: channelName, assignment: channel assignment} + + gearUtils.getChatInputs = function(){ + + var musicPorts = jamClient.FTUEGetChannels(); + var chatsOnOtherDevices = context.jamClient.FTUEGetChatInputs(false); + + var chatInputs = []; + context._.each(musicPorts.inputs, function(input) { + chatInputs.push({id: input.id, name: input.name, assignment:input.assignment}); + }); + + context._.each(chatsOnOtherDevices, function(chatChannelName, chatChannelId) { + var chatInput = {id: chatChannelId, name: chatChannelName, assignment: null}; + var assignment = context.jamClient.TrackGetAssignment(chatChannelId, true); + chatInput.assignment = assignment; + chatInputs.push(chatInput); + }) + + return chatInputs; + } + + gearUtils.isChannelAvailableForChat = function(chatChannelId, musicPorts) { + var result = true; + context._.each(musicPorts.inputs, function(inputChannel) { + // if the channel is currently assigned to a track, it not unassigned + if(inputChannel.id == chatChannelId && (inputChannel.assignment > 0)) { + result = false; + return false; // break + } + }); + + return result; + } + })(window, jQuery); \ No newline at end of file diff --git a/web/app/assets/javascripts/wizard/loopback/loopback_wizard.js b/web/app/assets/javascripts/wizard/loopback/loopback_wizard.js index 7b25c96d2..bf16da14c 100644 --- a/web/app/assets/javascripts/wizard/loopback/loopback_wizard.js +++ b/web/app/assets/javascripts/wizard/loopback/loopback_wizard.js @@ -5,6 +5,7 @@ context.JK = context.JK || {}; context.JK.LoopbackWizard = function (app) { + var EVENTS = context.JK.DIALOG_CLOSED; var logger = context.JK.logger; var $dialog = null; @@ -32,7 +33,6 @@ function closeDialog() { wizard.onCloseDialog(); - $self.triggerHandler('dialog_closed'); app.layout.closeDialog('loopback-wizard'); } @@ -71,6 +71,9 @@ $(wizard).on('wizard_close', onClosed); } + function getDialog() { + return $dialog; + } function initialize() { @@ -93,6 +96,7 @@ this.setBackState = setBackState; this.initialize = initialize; this.getGearTest = getGearTest; + this.getDialog = getDialog; return this; diff --git a/web/app/assets/stylesheets/client/client.css b/web/app/assets/stylesheets/client/client.css index 382e6ca7b..e9a515cc4 100644 --- a/web/app/assets/stylesheets/client/client.css +++ b/web/app/assets/stylesheets/client/client.css @@ -34,6 +34,8 @@ *= require ./search *= require ./ftue *= require ./jamServer + *= require ./dragDropTracks + *= require ./voiceChatHelper *= require ./wizard/gearResults *= require ./wizard/framebuffers *= require ./wizard/wizard diff --git a/web/app/assets/stylesheets/client/configureTracksDialog.css.scss b/web/app/assets/stylesheets/client/configureTracksDialog.css.scss index e04e64176..18ad08458 100644 --- a/web/app/assets/stylesheets/client/configureTracksDialog.css.scss +++ b/web/app/assets/stylesheets/client/configureTracksDialog.css.scss @@ -1,5 +1,121 @@ +@import "client/common.css.scss"; +@charset "UTF-8"; + #configure-tracks-dialog { - min-height: 500px; - max-height: 500px; + min-height: 700px; + max-height: 700px; width:800px; + + &[current-screen="account/audio"] { + .btn-add-new-audio-gear { + display:none; + } + } + + .sub-header { + color:white; + margin-bottom:10px; + } + .certified-audio-profile-section { + + height:53px; + + .easydropdown { + width:120px; + } + + .easydropdown-wrapper { + width:120px; + } + + .dropdown-container { + min-width:138px; + } + } + + .column { + position:relative; + float:left; + vertical-align:top; + @include border_box_sizing; + padding: 20px 20px 0 0; + } + + .sub-column { + position:relative; + float:left; + vertical-align:top; + @include border_box_sizing; + } + + .tab[tab-id="music-audio"] { + .column { + &:nth-of-type(1) { + width: 30%; + } + + &:nth-of-type(2) { + width: 70%; + } + } + + .sub-column { + &:nth-of-type(1) { + width: 80%; + } + + &:nth-of-type(2) { + width: 20%; + } + } + } + + .tab[tab-id="voice-chat"] { + .column { + &:nth-of-type(1) { + width: 50%; + } + + &:nth-of-type(2) { + width: 50%; + } + } + } + + .unused-audio-inputs-section { + margin-top:20px; + height:270px; + } + + .unused-audio-outputs-section { + margin-top:20px; + height:80px; + } + + .input-tracks-section { + height:363px; + } + + .output-channels-section { + height:80px; + } + + .buttons { + bottom: 25px; + position: absolute; + right: 25px; + left:25px; + } + + .btn-add-new-audio-gear { + float:left; + } + + .btn-cancel { + float:right; + } + + .btn-update-settings { + float:right; + } } \ No newline at end of file diff --git a/web/app/assets/stylesheets/client/dragDropTracks.css.scss b/web/app/assets/stylesheets/client/dragDropTracks.css.scss new file mode 100644 index 000000000..8fc30bcf9 --- /dev/null +++ b/web/app/assets/stylesheets/client/dragDropTracks.css.scss @@ -0,0 +1,162 @@ +@import "client/common.css.scss"; +@charset "UTF-8"; + +.dialog.gear-wizard, #configure-tracks-dialog { + + .icon-instrument-select { + padding: 3px 0; // to combine 24 of .current-instrument + 3x on either side + margin: 0 0 15px 25px; // 15 margin-bottom to match tracks on the left + width: 30px; + } + + .unassigned-output-channels { + min-height: 80px; + overflow-y: auto; + //padding-right:18px; // to keep draggables off of scrollbar. maybe necessary + + &.drag-in-progress { + overflow-y: visible; + overflow-x: visible; + } + + &.possible-target { + border: solid 1px white; + + &.drag-hovering { + border: solid 1px #ED3618; + } + } + } + + .unassigned-input-channels { + min-height: 240px; + overflow-y: auto; + //padding-right:18px; // to keep draggables off of scrollbar. maybe necessary + + &.drag-in-progress { + overflow-y: visible; + overflow-x: visible; + } + + &.possible-target { + border: solid 1px white; + + &.drag-hovering { + border: solid 1px #ED3618; + } + } + } + + .num { + position: absolute; + height: 29px; + line-height: 29px; + } + .track, .output { + margin-bottom: 15px; + .track-target, .output-target { + &.possible-target { + border-color: white; + } + &.drag-hovering { + border-color: #ED3618; + } + } + } + + // do not show tracks with 2 channels as a possible target + .track .track-target.possible-target[track-count="2"] { + border-color:#999; + } + + // do now show output slots with 1 channel as a possible target + .output .output-target.possible-target[output-count="1"] { + border-color:#999; + } + + .ftue-input { + font-size: 12px; + cursor: move; + padding: 4px; + border: solid 1px #999; + margin-bottom: 15px; + white-space: nowrap; + overflow: hidden; + text-align: left; + direction: rtl; + &.ui-draggable-dragging { + margin-bottom: 0; + } + + &:hover { + color: white; + font-weight: bold; + } + + /** + &:hover { + color:white; + overflow:visible; + background-color:#333; + width: auto !important; + direction:ltr; + position:absolute; + line-height:19.5px; + }*/ + } + + .track-target, .output-target { + + cursor: move; + padding: 4px; + border: solid 1px #999; + margin-left: 15px; + height: 20px; + overflow: hidden; + + &.drag-in-progress { + overflow: visible; + } + + .ftue-input { + padding: 0; + border: 0; + margin-bottom: 0; + &.ui-draggable-dragging { + padding: 4px; + border: solid 1px #999; + overflow: visible; + } + } + + .placeholder { + display: none; + font-size: 12px; + } + + &[track-count="0"], &[output-count="0"] { + .placeholder { + display: inline; + } + } + + &[track-count="2"] { + .ftue-input { + width: 49%; + display: inline-block; + + &:nth-of-type(1) { + float: left; + /**&:after { + float:right; + content: ','; + padding-right:3px; + }*/ + } + &:nth-of-type(2) { + float: right; + } + } + } + } +} \ No newline at end of file diff --git a/web/app/assets/stylesheets/client/networkTestDialog.css.scss b/web/app/assets/stylesheets/client/networkTestDialog.css.scss index 749965fc4..1c799915b 100644 --- a/web/app/assets/stylesheets/client/networkTestDialog.css.scss +++ b/web/app/assets/stylesheets/client/networkTestDialog.css.scss @@ -4,4 +4,11 @@ position: absolute; right: 25px; } + + + .network-test { + .network-test-results { + height:268px ! important; + } + } } \ No newline at end of file diff --git a/web/app/assets/stylesheets/client/voiceChatHelper.css.scss b/web/app/assets/stylesheets/client/voiceChatHelper.css.scss new file mode 100644 index 000000000..17a721813 --- /dev/null +++ b/web/app/assets/stylesheets/client/voiceChatHelper.css.scss @@ -0,0 +1,60 @@ +.dialog.configure-tracks, .dialog.gear-wizard { + + .voicechat-option { + + position: relative; + + div { + + } + + h3 { + padding-left: 30px; + margin-top: 14px; + font-weight: bold; + display: inline-block; + } + + p { + padding-left: 30px; + margin-top: 5px; + display: inline-block; + } + + input { + position: absolute; + margin: auto; + width: 30px; + } + + .iradio_minimal { + margin-top: 15px; + display: inline-block; + } + } + + .ftue-box { + background-color: #222222; + font-size: 13px; + padding: 8px; + &.chat-inputs { + height: 230px !important; + overflow: auto; + + p { + white-space: nowrap; + display: inline-block; + height: 32px; + vertical-align: middle; + } + + .chat-input { + white-space: nowrap; + + .iradio_minimal { + display: inline-block; + } + } + } + } +} \ No newline at end of file diff --git a/web/app/assets/stylesheets/client/wizard/gearWizard.css.scss b/web/app/assets/stylesheets/client/wizard/gearWizard.css.scss index 59936c3e3..c3d7c4012 100644 --- a/web/app/assets/stylesheets/client/wizard/gearWizard.css.scss +++ b/web/app/assets/stylesheets/client/wizard/gearWizard.css.scss @@ -1,5 +1,4 @@ @import "client/common.css.scss"; - @charset "UTF-8"; .dialog.gear-wizard, .dialog.network-test { @@ -193,132 +192,8 @@ margin-top: 45px; } - .icon-instrument-select { - padding: 3px 0; // to combine 24 of .current-instrument + 3x on either side - margin: 0 0 15px 25px; // 15 margin-bottom to match tracks on the left - width: 30px; - } - - .unassigned-channels { - min-height: 240px; - overflow-y: auto; - //padding-right:18px; // to keep draggables off of scrollbar. maybe necessary - - &.drag-in-progress { - overflow-y: visible; - overflow-x: visible; - } - - &.possible-target { - border: solid 1px white; - - &.drag-hovering { - border: solid 1px #ED3618; - } - } - } - - .num { - position: absolute; - height: 29px; - line-height: 29px; - } - .track { - margin-bottom: 15px; - .track-target { - &.possible-target { - border-color: white; - } - &.drag-hovering { - border-color: #ED3618; - } - } - } - - .ftue-input { - font-size: 12px; - cursor: move; - padding: 4px; - border: solid 1px #999; - margin-bottom: 15px; - white-space: nowrap; - overflow: hidden; - text-align: left; - direction: rtl; - &.ui-draggable-dragging { - margin-bottom: 0; - } - - &:hover { - color: white; - font-weight: bold; - } - - /** - &:hover { - color:white; - overflow:visible; - background-color:#333; - width: auto !important; - direction:ltr; - position:absolute; - line-height:19.5px; - }*/ - } - - .track-target { - - cursor: move; - padding: 4px; - border: solid 1px #999; - margin-left: 15px; - height: 20px; - overflow: hidden; - - &.drag-in-progress { - overflow: visible; - } - - .ftue-input { - padding: 0; - border: 0; - margin-bottom: 0; - &.ui-draggable-dragging { - padding: 4px; - border: solid 1px #999; - overflow: visible; - } - } - - .placeholder { - display: none; - font-size: 12px; - } - - &[track-count="0"] { - .placeholder { - display: inline; - } - } - - &[track-count="2"] { - .ftue-input { - width: 49%; - display: inline-block; - - &:nth-of-type(1) { - float: left; - /**&:after { - float:right; - content: ','; - padding-right:3px; - }*/ - } - &:nth-of-type(2) { - float: right; - } - } - } + .output-channels, .unassigned-output-channels { + display:none; } } @@ -333,67 +208,9 @@ } .watch-video { - margin-top: 97px; + margin-top: 25px; } - .voicechat-option { - - position: relative; - - div { - - } - - h3 { - padding-left: 30px; - margin-top: 14px; - font-weight: bold; - display: inline-block; - } - - p { - padding-left: 30px; - margin-top: 5px; - display: inline-block; - } - - input { - position: absolute; - margin: auto; - width: 30px; - } - - .iradio_minimal { - margin-top: 15px; - display: inline-block; - } - } - - .ftue-box { - &.chat-inputs { - height: 230px !important; - overflow: auto; - - p { - white-space: nowrap; - display: inline-block; - height: 32px; - vertical-align: middle; - } - - .chat-input { - white-space: nowrap; - - .iradio_minimal { - display: inline-block; - } - } - } - - .watch-video { - margin-top: 25px; - } - } } .wizard-step[layout-wizard-step="4"] { @@ -442,11 +259,6 @@ } } - .network-test { - .network-test-results { - height:268px ! important; - } - } .wizard-step[layout-wizard-step="5"], .network-test { .wizard-step-content .wizard-step-column { @@ -557,7 +369,7 @@ } .network-test-results { - height: 248px; + height: 248px !important; @include border_box_sizing; &.testing { diff --git a/web/app/views/clients/_account_audio_profile.html.erb b/web/app/views/clients/_account_audio_profile.html.erb index 59181ca9e..28afacc86 100644 --- a/web/app/views/clients/_account_audio_profile.html.erb +++ b/web/app/views/clients/_account_audio_profile.html.erb @@ -53,6 +53,7 @@ {% if(data.is_admin) { %} LOOPBACK (admin only) {% } %} + CONFIGURE DELETE {% } %} diff --git a/web/app/views/clients/_configure_tracks_dialog.html.haml b/web/app/views/clients/_configure_tracks_dialog.html.haml index 9d8248c56..8c1dcdb84 100644 --- a/web/app/views/clients/_configure_tracks_dialog.html.haml +++ b/web/app/views/clients/_configure_tracks_dialog.html.haml @@ -1,4 +1,4 @@ -.dialog{ layout: 'dialog', 'layout-id' => 'configure-tracks', id: 'configure-tracks-dialog'} +.dialog.configure-tracks{ layout: 'dialog', 'layout-id' => 'configure-tracks', id: 'configure-tracks-dialog'} .content-head = image_tag "content/icon_add.png", {:width => 19, :height => 19, :class => 'content-icon' } %h1 configure tracks @@ -9,14 +9,63 @@ .instructions %span + Choose your audio device. Drag and drop to assign input ports to tracks, and specify the instrument + for each track. Drag and drop to assign a pair of output ports for session stereo audio monitoring. .clearall .tab{'tab-id' => 'music-audio'} + .column + .certified-audio-profile-section + .sub-header Certified Audio Profile + %select.certified-audio-profile + .clearall + + .unused-audio-inputs-section.no-selection-range + .sub-header Unused Input Ports + .unassigned-input-channels + + .unused-audio-outputs-section.no-selection-range + .sub-header Unused Output Ports + .unassigned-output-channels + + .column + .input-tracks-section + .sub-column + .sub-header Track Input Port(s) + .input-tracks.tracks.no-selection-range + .sub-column + .sub-header Instrument + .instruments.no-selection-range + + .output-channels-section + .sub-header Audio Output Port + .output-channels.no-selection-range .clearall + .tab{'tab-id' => 'voice-chat'} + .column + %form.select-voice-chat-option.section + .sub-header Select Voice Chat Option + .voicechat-option.reuse-audio-input + %input{type:"radio", name: "voicechat", checked:"checked"} + %h3 Use Music Microphone + %p I am already using a microphone to capture my vocal or instrumental music, so I can talk with other musicians using that microphone + .voicechat-option.use-chat-input + %input{type:"radio", name: "voicechat"} + %h3 Use Chat Microphone + %p I am not using a microphone for acoustic instruments or vocals, so use the input selected to the right for voice chat during my sessions + .column + .select-voice-chat + .sub-header Voice Chat Input + .ftue-box.chat-inputs .clearall + + .buttons + %a.btn-add-new-audio-gear.button-grey{'layout-link' => 'add-new-audio-gear'} ADD NEW AUDIO GEAR + %a.button-orange.btn-update-settings{href:'#'} UPDATE SETTINGS + %a.button-grey.btn-cancel{href:'#'} CANCEL diff --git a/web/app/views/clients/_session.html.erb b/web/app/views/clients/_session.html.erb index a43de781f..04cdd236d 100644 --- a/web/app/views/clients/_session.html.erb +++ b/web/app/views/clients/_session.html.erb @@ -52,7 +52,7 @@

    my tracks

    -
    +
    <%= image_tag "content/icon_settings_lg.png", {:width => 18, :height => 18} %>  Settings
    diff --git a/web/app/views/clients/wizard/gear/_gear_wizard.html.haml b/web/app/views/clients/wizard/gear/_gear_wizard.html.haml index c703f7128..367349565 100644 --- a/web/app/views/clients/wizard/gear/_gear_wizard.html.haml +++ b/web/app/views/clients/wizard/gear/_gear_wizard.html.haml @@ -74,15 +74,17 @@ %li Select the instrument for each track. .center %a.button-orange.watch-video{href:'#'} WATCH VIDEO - .wizard-step-column.no-selection-range + .wizard-step-column %h2 Unassigned Ports - .unassigned-channels - .wizard-step-column.no-selection-range + .unassigned-input-channels.no-selection-range + .wizard-step-column %h2 Track Input Port(s) - .tracks - .wizard-step-column.no-selection-range + .tracks.no-selection-range + .wizard-step-column %h2 Instrument - .instruments + .instruments.no-selection-range + .output-channels + .unassigned-output-channels .wizard-step{ 'layout-wizard-step' => "3", 'dialog-title' => "Configure Voice Chat", 'dialog-purpose' => "ConfigureVoiceChat" } .ftuesteps @@ -98,14 +100,15 @@ %a.button-orange.watch-video{href:'#'} WATCH VIDEO .wizard-step-column %h2 Select Voice Chat Option - .voicechat-option.reuse-audio-input - %input{type:"radio", name: "voicechat", checked:"checked"} - %h3 Use Music Microphone - %p I am already using a microphone to capture my vocal or instrumental music, so I can talk with other musicians using that microphone - .voicechat-option.use-chat-input - %input{type:"radio", name: "voicechat"} - %h3 Use Chat Microphone - %p I am not using a microphone for acoustic instruments or vocals, so use the input selected to the right for voice chat during my sessions + %form + .voicechat-option.reuse-audio-input + %input{type:"radio", name: "voicechat", checked:"checked"} + %h3 Use Music Microphone + %p I am already using a microphone to capture my vocal or instrumental music, so I can talk with other musicians using that microphone + .voicechat-option.use-chat-input + %input{type:"radio", name: "voicechat"} + %h3 Use Chat Microphone + %p I am not using a microphone for acoustic instruments or vocals, so use the input selected to the right for voice chat during my sessions .wizard-step-column %h2 Voice Chat Input .ftue-box.chat-inputs @@ -220,8 +223,16 @@ .track-target{'data-num' => '{{data.num}}', 'track-count' => 0} %span.placeholder None + +%script{type: 'text/template', id: 'template-output-target'} + .output{'data-num' => '{{data.num}}'} + .num {{data.num + 1}}: + .output-target{'data-num' => '{{data.num}}', 'output-count' => 0} + %span.placeholder None + + %script{type: 'text/template', id: 'template-chat-input'} .chat-input %input{type:"radio", name: "chat-device", 'data-channel-id' => '{{data.id}}', 'data-channel-name' => '{{data.name}}'} %p - = '{{data.name}}' + %span {{data.name}} \ No newline at end of file From 9368d2f045fce947b4cb400aaef16d5baa13d8f1 Mon Sep 17 00:00:00 2001 From: Seth Call Date: Sun, 8 Jun 2014 22:32:07 -0500 Subject: [PATCH 13/32] * fix selector in test --- web/spec/features/in_session_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/spec/features/in_session_spec.rb b/web/spec/features/in_session_spec.rb index 2e57bacb5..d9fa56316 100644 --- a/web/spec/features/in_session_spec.rb +++ b/web/spec/features/in_session_spec.rb @@ -49,7 +49,7 @@ describe "In a Session", :js => true, :type => :feature, :capybara_feature => tr wait_for_ajax expect(page).to have_selector('h1', text: 'configure tracks') - find('#btn-add-new-audio-gear').trigger(:click) + find('.btn-add-new-audio-gear').trigger(:click) wait_for_ajax expect(page).to have_selector('h1', text: 'add new audio gear') From 9421ded0da72e975a8456251d2a5b45c20012f0b Mon Sep 17 00:00:00 2001 From: Seth Call Date: Mon, 9 Jun 2014 08:42:14 -0500 Subject: [PATCH 14/32] * fix add new gear click handler --- web/app/assets/javascripts/configureTrackDialog.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/web/app/assets/javascripts/configureTrackDialog.js b/web/app/assets/javascripts/configureTrackDialog.js index 42887ad90..495d62e0b 100644 --- a/web/app/assets/javascripts/configureTrackDialog.js +++ b/web/app/assets/javascripts/configureTrackDialog.js @@ -122,10 +122,10 @@ }); - $btnAddNewGear.click(function() { + //$btnAddNewGear.click(function() { - return false; - }); + // return false; + //}); $btnUpdateTrackSettings.click(function() { if(configureTracksHelper.trySave() && voiceChatHelper.trySave()) { From 4a603cbaa2a0b3f21ca9c1cb3f1a4c641356c50a Mon Sep 17 00:00:00 2001 From: Seth Call Date: Mon, 9 Jun 2014 15:43:16 -0500 Subject: [PATCH 15/32] * VRFS-1764 - fix for content falling out of bounds; VRFS-1762 - tracking audio latency now; need to still make sure this works with new create session flow when it arrives --- db/manifest | 1 + db/up/audio_latency.sql | 2 + ruby/lib/jam_ruby/connection_manager.rb | 4 +- ruby/lib/jam_ruby/models/connection.rb | 5 +- ruby/lib/jam_ruby/models/user.rb | 13 +++++ ruby/spec/jam_ruby/connection_manager_spec.rb | 28 +++++------ .../jam_ruby/models/claimed_recording_spec.rb | 2 +- ruby/spec/jam_ruby/models/connection_spec.rb | 23 +++++++++ ruby/spec/jam_ruby/models/mix_spec.rb | 2 +- .../jam_ruby/models/music_session_spec.rb | 2 +- .../jam_ruby/models/musician_search_spec.rb | 4 +- ruby/spec/jam_ruby/models/recording_spec.rb | 4 +- ruby/spec/jam_ruby/models/user_spec.rb | 47 ++++++++++++++----- .../javascripts/accounts_audio_profile.js | 2 +- web/app/assets/javascripts/chatPanel.js | 2 +- .../javascripts/configureTracksHelper.js | 3 ++ .../assets/javascripts/createSession.js.erb | 1 + web/app/assets/javascripts/jam_rest.js | 12 +++++ web/app/assets/javascripts/sessionModel.js | 3 +- .../assets/javascripts/wizard/gear_test.js | 2 +- .../assets/javascripts/wizard/gear_utils.js | 8 ++++ .../client/dragDropTracks.css.scss | 7 ++- .../api_music_sessions_controller.rb | 6 ++- web/app/controllers/api_users_controller.rb | 21 +++++++-- web/app/views/api_music_sessions/show.rabl | 2 +- web/app/views/api_users/audio_latency.rabl | 3 ++ web/app/views/api_users/show.rabl | 2 +- web/config/routes.rb | 3 ++ web/lib/music_session_manager.rb | 8 ++-- .../api_claimed_recordings_spec.rb | 2 +- .../controllers/api_users_controller_spec.rb | 36 ++++++++++++++ web/spec/controllers/users_controller_spec.rb | 0 web/spec/features/social_meta_spec.rb | 4 +- web/spec/helpers/recording_helper_spec.rb | 2 +- .../managers/music_session_manager_spec.rb | 6 +-- web/spec/requests/music_sessions_api_spec.rb | 2 +- web/spec/requests/users_api_spec.rb | 4 +- 37 files changed, 214 insertions(+), 64 deletions(-) create mode 100644 db/up/audio_latency.sql create mode 100644 web/app/views/api_users/audio_latency.rabl create mode 100644 web/spec/controllers/api_users_controller_spec.rb delete mode 100644 web/spec/controllers/users_controller_spec.rb diff --git a/db/manifest b/db/manifest index fdd6e190c..28ce6b527 100755 --- a/db/manifest +++ b/db/manifest @@ -161,3 +161,4 @@ remember_extra_scoring_data.sql indexing_for_regions.sql latency_tester.sql fix_users_location_fields.sql +audio_latency.sql diff --git a/db/up/audio_latency.sql b/db/up/audio_latency.sql new file mode 100644 index 000000000..dd891322c --- /dev/null +++ b/db/up/audio_latency.sql @@ -0,0 +1,2 @@ +ALTER TABLE connections ADD COLUMN last_jam_audio_latency double precision; +ALTER TABLE users RENAME COLUMN audio_latency TO last_jam_audio_latency; \ No newline at end of file diff --git a/ruby/lib/jam_ruby/connection_manager.rb b/ruby/lib/jam_ruby/connection_manager.rb index efd6dd12b..4916da854 100644 --- a/ruby/lib/jam_ruby/connection_manager.rb +++ b/ruby/lib/jam_ruby/connection_manager.rb @@ -339,7 +339,7 @@ SQL end end - def join_music_session(user, client_id, music_session, as_musician, tracks) + def join_music_session(user, client_id, music_session, as_musician, tracks, audio_latency) connection = nil ConnectionManager.active_record_transaction do |connection_manager| @@ -347,7 +347,7 @@ SQL connection = Connection.find_by_client_id_and_user_id!(client_id, user.id) - connection.join_the_session(music_session, as_musician, tracks, user) + connection.join_the_session(music_session, as_musician, tracks, user, audio_latency) # connection.music_session_id = music_session.id # connection.as_musician = as_musician # connection.joining_session = true diff --git a/ruby/lib/jam_ruby/models/connection.rb b/ruby/lib/jam_ruby/models/connection.rb index 9def6a7c1..fc3d43682 100644 --- a/ruby/lib/jam_ruby/models/connection.rb +++ b/ruby/lib/jam_ruby/models/connection.rb @@ -19,6 +19,7 @@ module JamRuby validates :as_musician, :inclusion => {:in => [true, false]} validates :client_type, :inclusion => {:in => [TYPE_CLIENT, TYPE_BROWSER, TYPE_LATENCY_TESTER]} + validates_numericality_of :last_jam_audio_latency, greater_than:0, :allow_nil => true validate :can_join_music_session, :if => :joining_session? validate :user_or_latency_tester_present @@ -152,7 +153,7 @@ module JamRuby true end - def join_the_session(music_session, as_musician, tracks, user) + def join_the_session(music_session, as_musician, tracks, user, audio_latency) self.music_session_id = music_session.id self.as_musician = as_musician self.joining_session = true @@ -163,6 +164,7 @@ module JamRuby # if user joins the session as a musician, update their addr and location if as_musician user.update_addr_loc(self, 'j') + user.update_audio_latency(self, audio_latency) end end @@ -185,7 +187,6 @@ module JamRuby end end - private def require_at_least_one_track_when_in_session diff --git a/ruby/lib/jam_ruby/models/user.rb b/ruby/lib/jam_ruby/models/user.rb index 44712bbb7..23d72ace6 100644 --- a/ruby/lib/jam_ruby/models/user.rb +++ b/ruby/lib/jam_ruby/models/user.rb @@ -129,6 +129,7 @@ module JamRuby validates :musician, :inclusion => {:in => [true, false]} validates :show_whats_next, :inclusion => {:in => [nil, true, false]} validates :mods, json: true + validates_numericality_of :last_jam_audio_latency, greater_than:0, :allow_nil => true # custom validators validate :validate_musician_instruments @@ -1169,6 +1170,18 @@ module JamRuby self.save end + def update_audio_latency(connection, audio_latency) + + # updating the connection is best effort + if connection + connection.last_jam_audio_latency = audio_latency + connection.save + end + + self.last_jam_audio_latency = audio_latency + self.save + end + def top_followings @topf ||= User.joins("INNER JOIN follows ON follows.followable_id = users.id AND follows.followable_type = '#{self.class.to_s}'") .where(['follows.user_id = ?', self.id]) diff --git a/ruby/spec/jam_ruby/connection_manager_spec.rb b/ruby/spec/jam_ruby/connection_manager_spec.rb index 1e74c6369..bdb397262 100644 --- a/ruby/spec/jam_ruby/connection_manager_spec.rb +++ b/ruby/spec/jam_ruby/connection_manager_spec.rb @@ -283,7 +283,7 @@ describe ConnectionManager do user = User.find(user_id) @connman.create_connection(user_id, client_id, "1.1.1.1", 'client', STALE_TIME, EXPIRE_TIME) - connection = @connman.join_music_session(user, client_id, music_session, true, TRACKS) + connection = @connman.join_music_session(user, client_id, music_session, true, TRACKS, 10) connection.errors.any?.should be_false @@ -308,7 +308,7 @@ describe ConnectionManager do user = User.find(user_id) - expect { @connman.join_music_session(user, client_id, music_session, true, TRACKS) }.to raise_error(ActiveRecord::RecordNotFound) + expect { @connman.join_music_session(user, client_id, music_session, true, TRACKS, 10) }.to raise_error(ActiveRecord::RecordNotFound) end @@ -325,11 +325,11 @@ describe ConnectionManager do music_session_id = music_session.id user = User.find(user_id) - @connman.join_music_session(user, client_id, music_session, true, TRACKS) + @connman.join_music_session(user, client_id, music_session, true, TRACKS, 10) user = User.find(user_id2) - connection = @connman.join_music_session(user, client_id2, music_session, true, TRACKS) + connection = @connman.join_music_session(user, client_id2, music_session, true, TRACKS, 10) connection.errors.size.should == 1 connection.errors.get(:as_musician).should == [ValidationMessages::FAN_CAN_NOT_JOIN_AS_MUSICIAN] end @@ -343,7 +343,7 @@ describe ConnectionManager do music_session = FactoryGirl.create(:active_music_session, user_id: user_id) user = User.find(user_id) - connection = @connman.join_music_session(user, client_id, music_session, 'blarg', TRACKS) + connection = @connman.join_music_session(user, client_id, music_session, 'blarg', TRACKS, 10) connection.errors.size.should == 0 connection.as_musician.should be_false end @@ -362,11 +362,11 @@ describe ConnectionManager do user = User.find(musician_id) - @connman.join_music_session(user, musician_client_id, music_session, true, TRACKS) + @connman.join_music_session(user, musician_client_id, music_session, true, TRACKS, 10) # now join the session as a fan, bt fan_access = false user = User.find(fan_id) - connection = @connman.join_music_session(user, fan_client_id, music_session, false, TRACKS) + connection = @connman.join_music_session(user, fan_client_id, music_session, false, TRACKS, 10) connection.errors.size.should == 1 end @@ -381,7 +381,7 @@ describe ConnectionManager do @connman.create_connection(user_id, client_id, "1.1.1.1", 'client', STALE_TIME, EXPIRE_TIME) # specify real user id, but not associated with this session - expect { @connman.join_music_session(user, client_id, music_session, true, TRACKS) } .to raise_error(ActiveRecord::RecordNotFound) + expect { @connman.join_music_session(user, client_id, music_session, true, TRACKS, 10) } .to raise_error(ActiveRecord::RecordNotFound) end it "join_music_session fails if no music_session" do @@ -392,7 +392,7 @@ describe ConnectionManager do music_session = ActiveMusicSession.new @connman.create_connection(user_id, client_id, "1.1.1.1", 'client', STALE_TIME, EXPIRE_TIME) - connection = @connman.join_music_session(user, client_id, music_session, true, TRACKS) + connection = @connman.join_music_session(user, client_id, music_session, true, TRACKS, 10) connection.errors.size.should == 1 connection.errors.get(:music_session).should == [ValidationMessages::MUSIC_SESSION_MUST_BE_SPECIFIED] end @@ -407,7 +407,7 @@ describe ConnectionManager do @connman.create_connection(user_id, client_id, "1.1.1.1", 'client', STALE_TIME, EXPIRE_TIME) # specify real user id, but not associated with this session - expect { @connman.join_music_session(user, client_id, music_session, true, TRACKS) } .to raise_error(ActiveRecord::RecordNotFound) + expect { @connman.join_music_session(user, client_id, music_session, true, TRACKS, 10) } .to raise_error(ActiveRecord::RecordNotFound) end @@ -435,7 +435,7 @@ describe ConnectionManager do dummy_music_session = ActiveMusicSession.new @connman.create_connection(user_id, client_id, "1.1.1.1", 'client', STALE_TIME, EXPIRE_TIME) - @connman.join_music_session(user, client_id, music_session, true, TRACKS) + @connman.join_music_session(user, client_id, music_session, true, TRACKS, 10) expect { @connman.leave_music_session(user, Connection.find_by_client_id(client_id), dummy_music_session) }.to raise_error(JamRuby::StateError) end @@ -448,7 +448,7 @@ describe ConnectionManager do user = User.find(user_id) @connman.create_connection(user_id, client_id, "1.1.1.1", 'client', STALE_TIME, EXPIRE_TIME) - @connman.join_music_session(user, client_id, music_session, true, TRACKS) + @connman.join_music_session(user, client_id, music_session, true, TRACKS, 10) assert_session_exists(music_session_id, true) @@ -492,11 +492,11 @@ describe ConnectionManager do client_id1 = Faker::Number.number(20) @connman.create_connection(user_id, client_id1, "1.1.1.1", 'client', STALE_TIME, EXPIRE_TIME) music_session1 = FactoryGirl.create(:active_music_session, :user_id => user_id) - connection1 = @connman.join_music_session(user, client_id1, music_session1, true, TRACKS) + connection1 = @connman.join_music_session(user, client_id1, music_session1, true, TRACKS, 10) connection1.errors.size.should == 0 music_session2 = FactoryGirl.create(:active_music_session, :user_id => user_id) - connection2 = @connman.join_music_session(user, client_id1, music_session2, true, TRACKS) + connection2 = @connman.join_music_session(user, client_id1, music_session2, true, TRACKS, 10) connection2.errors.size.should == 1 connection2.errors.get(:music_session).should == [ValidationMessages::CANT_JOIN_MULTIPLE_SESSIONS] diff --git a/ruby/spec/jam_ruby/models/claimed_recording_spec.rb b/ruby/spec/jam_ruby/models/claimed_recording_spec.rb index 4bbbeea17..5f771d63a 100644 --- a/ruby/spec/jam_ruby/models/claimed_recording_spec.rb +++ b/ruby/spec/jam_ruby/models/claimed_recording_spec.rb @@ -21,7 +21,7 @@ describe ClaimedRecording do @music_session = FactoryGirl.create(:active_music_session, :creator => @user, :musician_access => true) # @music_session.connections << @connection @music_session.save - @connection.join_the_session(@music_session, true, nil, @user) + @connection.join_the_session(@music_session, true, nil, @user, 10) @recording = Recording.start(@music_session, @user) @recording.stop @recording.reload diff --git a/ruby/spec/jam_ruby/models/connection_spec.rb b/ruby/spec/jam_ruby/models/connection_spec.rb index 377bbdb1d..9435cf9bd 100644 --- a/ruby/spec/jam_ruby/models/connection_spec.rb +++ b/ruby/spec/jam_ruby/models/connection_spec.rb @@ -3,6 +3,11 @@ require 'spec_helper' describe JamRuby::Connection do let(:user) { FactoryGirl.create(:user) } let (:music_session) { FactoryGirl.create(:active_music_session, :creator => user) } + let (:conn) { FactoryGirl.create(:connection, + :user => user, + :music_session => music_session, + :ip_address => "1.1.1.1", + :client_id => "1") } it 'starts in the correct state' do connection = FactoryGirl.create(:connection, @@ -48,4 +53,22 @@ describe JamRuby::Connection do user.lng.should == geocode.lng end + describe "audio latency" do + it "allow update" do + conn.last_jam_audio_latency = 1 + conn.save! + end + + it "prevent negative or 0" do + conn.last_jam_audio_latency = 0 + conn.save.should be_false + conn.errors[:last_jam_audio_latency].should == ['must be greater than 0'] + end + + it "prevent non numerical" do + conn.last_jam_audio_latency = 'a' + conn.save.should be_false + conn.errors[:last_jam_audio_latency].should == ['is not a number'] + end + end end diff --git a/ruby/spec/jam_ruby/models/mix_spec.rb b/ruby/spec/jam_ruby/models/mix_spec.rb index 904c036a4..5989abb85 100755 --- a/ruby/spec/jam_ruby/models/mix_spec.rb +++ b/ruby/spec/jam_ruby/models/mix_spec.rb @@ -10,7 +10,7 @@ describe Mix do @music_session = FactoryGirl.create(:active_music_session, :creator => @user, :musician_access => true) # @music_session.connections << @connection @music_session.save - @connection.join_the_session(@music_session, true, nil, @user) + @connection.join_the_session(@music_session, true, nil, @user, 10) @recording = Recording.start(@music_session, @user) @recording.stop @recording.claim(@user, "name", "description", Genre.first, true) diff --git a/ruby/spec/jam_ruby/models/music_session_spec.rb b/ruby/spec/jam_ruby/models/music_session_spec.rb index 44b14fe27..4f2a1122a 100644 --- a/ruby/spec/jam_ruby/models/music_session_spec.rb +++ b/ruby/spec/jam_ruby/models/music_session_spec.rb @@ -361,7 +361,7 @@ describe ActiveMusicSession do @music_session = FactoryGirl.create(:active_music_session, :creator => @user1, :musician_access => true) # @music_session.connections << @connection @music_session.save! - @connection.join_the_session(@music_session, true, nil, @user1) + @connection.join_the_session(@music_session, true, nil, @user1, 10) end describe "not recording" do diff --git a/ruby/spec/jam_ruby/models/musician_search_spec.rb b/ruby/spec/jam_ruby/models/musician_search_spec.rb index 1b60aae49..a98985b17 100644 --- a/ruby/spec/jam_ruby/models/musician_search_spec.rb +++ b/ruby/spec/jam_ruby/models/musician_search_spec.rb @@ -171,7 +171,7 @@ describe 'Musician search' do music_session = FactoryGirl.create(:active_music_session, :creator => usr, :musician_access => true) # music_session.connections << connection # music_session.save - connection.join_the_session(music_session, true, nil, usr) + connection.join_the_session(music_session, true, nil, usr, 10) recording = Recording.start(music_session, usr) recording.stop recording.reload @@ -186,7 +186,7 @@ describe 'Musician search' do music_session = FactoryGirl.create(:active_music_session, :creator => usr, :musician_access => true) # music_session.connections << connection # music_session.save - connection.join_the_session(music_session, true, nil, usr) + connection.join_the_session(music_session, true, nil, usr, 10) end context 'musician stat counters' do diff --git a/ruby/spec/jam_ruby/models/recording_spec.rb b/ruby/spec/jam_ruby/models/recording_spec.rb index fa666cbea..d61a1eecd 100644 --- a/ruby/spec/jam_ruby/models/recording_spec.rb +++ b/ruby/spec/jam_ruby/models/recording_spec.rb @@ -80,7 +80,7 @@ describe Recording do @track2 = FactoryGirl.create(:track, :connection => @connection2, :instrument => @instrument2) # @music_session.connections << @connection2 - @connection2.join_the_session(@music_session, true, nil, @user2) + @connection2.join_the_session(@music_session, true, nil, @user2, 10) @recording = Recording.start(@music_session, @user) @user.recordings.length.should == 0 @@ -179,7 +179,7 @@ describe Recording do @track = FactoryGirl.create(:track, :connection => @connection2, :instrument => @instrument) # @music_session.connections << @connection2 @music_session.save - @connection2.join_the_session(@music_session, true, nil, @user2) + @connection2.join_the_session(@music_session, true, nil, @user2, 10) @recording = Recording.start(@music_session, @user) @recording.stop @recording.reload diff --git a/ruby/spec/jam_ruby/models/user_spec.rb b/ruby/spec/jam_ruby/models/user_spec.rb index 0e0396fa3..4bf872a3c 100644 --- a/ruby/spec/jam_ruby/models/user_spec.rb +++ b/ruby/spec/jam_ruby/models/user_spec.rb @@ -12,19 +12,21 @@ describe User do subject { @user } - it { should respond_to(:first_name) } - it { should respond_to(:last_name) } - it { should respond_to(:email) } - it { should respond_to(:password) } - it { should respond_to(:password_confirmation) } - it { should respond_to(:remember_token) } - it { should respond_to(:admin) } - it { should respond_to(:valid_password?) } - it { should respond_to(:can_invite) } - it { should respond_to(:mods) } - - it { should be_valid } - it { should_not be_admin } + it { + should respond_to(:first_name) + should respond_to(:last_name) + should respond_to(:email) + should respond_to(:password) + should respond_to(:password_confirmation) + should respond_to(:remember_token) + should respond_to(:admin) + should respond_to(:valid_password?) + should respond_to(:can_invite) + should respond_to(:mods) + should respond_to(:last_jam_audio_latency) + should be_valid + should_not be_admin + } describe "accessible attributes" do it "should not allow access to admin" do @@ -471,6 +473,25 @@ describe User do @user.connection_expire_time_client.should == 5 end end + + describe "audio latency" do + it "allow update" do + @user.last_jam_audio_latency = 1 + @user.save! + end + + it "prevent negative or 0" do + @user.last_jam_audio_latency = 0 + @user.save.should be_false + @user.errors[:last_jam_audio_latency].should == ['must be greater than 0'] + end + + it "prevent non numerical" do + @user.last_jam_audio_latency = 'a' + @user.save.should be_false + @user.errors[:last_jam_audio_latency].should == ['is not a number'] + end + end =begin describe "update avatar" do diff --git a/web/app/assets/javascripts/accounts_audio_profile.js b/web/app/assets/javascripts/accounts_audio_profile.js index e6e5efcbd..d6c2ed709 100644 --- a/web/app/assets/javascripts/accounts_audio_profile.js +++ b/web/app/assets/javascripts/accounts_audio_profile.js @@ -46,7 +46,7 @@ // If you are in the FTUE, and you close the client and/or it crashes // then you will have 'FTUE' (incomplete) profiles. This is the only time - // the user might see them, so we clean them before they get to see it + var cleansedProfiles = []; context._.each(profiles, function(profile) { if(profile.id.indexOf('FTUE') == 0) { diff --git a/web/app/assets/javascripts/chatPanel.js b/web/app/assets/javascripts/chatPanel.js index 7d3f995fc..bf5b05eca 100644 --- a/web/app/assets/javascripts/chatPanel.js +++ b/web/app/assets/javascripts/chatPanel.js @@ -39,7 +39,7 @@ message['message'] = $textBox.val(); message['music_session'] = $sessionId; - message['client_id'] = context.JK.clientId; + message['client_id'] = app.clientId; return message; } diff --git a/web/app/assets/javascripts/configureTracksHelper.js b/web/app/assets/javascripts/configureTracksHelper.js index 62c3ee168..b834e99c3 100644 --- a/web/app/assets/javascripts/configureTracksHelper.js +++ b/web/app/assets/javascripts/configureTracksHelper.js @@ -32,6 +32,9 @@ var inputChannels = musicPorts.inputs; var outputChannels = musicPorts.outputs; + // uncomment to add a bunch of bogus channels + //inputChannels = inputChannels.concat(inputChannels.concat(inputChannels.concat(inputChannels))) + context._.each(inputChannels, function (inputChannel) { var $channel = $(context._.template($templateAssignablePort.html(), inputChannel, { variable: 'data' })); diff --git a/web/app/assets/javascripts/createSession.js.erb b/web/app/assets/javascripts/createSession.js.erb index 8532d162c..bb9d5f017 100644 --- a/web/app/assets/javascripts/createSession.js.erb +++ b/web/app/assets/javascripts/createSession.js.erb @@ -172,6 +172,7 @@ if ($('#band-list option:selected').val() !== '') { data.band = $('#band-list option:selected').val(); } + data.audio_latency = context.jamClient.FTUEGetExpectedLatency().latency; // 1. If no previous session data, a single stereo track with the // top instrument in the user's profile. diff --git a/web/app/assets/javascripts/jam_rest.js b/web/app/assets/javascripts/jam_rest.js index 859fc82b1..a76df6cf0 100644 --- a/web/app/assets/javascripts/jam_rest.js +++ b/web/app/assets/javascripts/jam_rest.js @@ -1015,6 +1015,17 @@ }); } + 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 initialize() { return self; } @@ -1103,6 +1114,7 @@ this.getChatMessages = getChatMessages; this.createDiagnostic = createDiagnostic; this.getLatencyTester = getLatencyTester; + this.updateAudioLatency = updateAudioLatency; return this; }; diff --git a/web/app/assets/javascripts/sessionModel.js b/web/app/assets/javascripts/sessionModel.js index 06cf02d0f..ef5a65ac9 100644 --- a/web/app/assets/javascripts/sessionModel.js +++ b/web/app/assets/javascripts/sessionModel.js @@ -368,7 +368,8 @@ ip_address: server.publicIP, as_musician: true, tracks: tracks, - session_id: sessionId + session_id: sessionId, + audio_latency: context.jamClient.FTUEGetExpectedLatency().latency }; return rest.legacyJoinSession(data); diff --git a/web/app/assets/javascripts/wizard/gear_test.js b/web/app/assets/javascripts/wizard/gear_test.js index 9b1a1e4f9..a92a14434 100644 --- a/web/app/assets/javascripts/wizard/gear_test.js +++ b/web/app/assets/javascripts/wizard/gear_test.js @@ -386,7 +386,7 @@ function onGearTestDone(e, data) { $resultsText.attr('scored', 'complete'); - rest.userCertifiedGear({success: true}); + rest.userCertifiedGear({success: true, client_id: app.clientId, audio_latency: getLatencyScore()}); } function onGearTestFail(e, data) { diff --git a/web/app/assets/javascripts/wizard/gear_utils.js b/web/app/assets/javascripts/wizard/gear_utils.js index 7129c9e38..f3b045cae 100644 --- a/web/app/assets/javascripts/wizard/gear_utils.js +++ b/web/app/assets/javascripts/wizard/gear_utils.js @@ -7,6 +7,7 @@ context.JK = context.JK || {}; var gearUtils = {}; + var rest = new context.JK.Rest(); context.JK.GearUtils = gearUtils; var logger = context.JK.logger; var ASSIGNMENT = context.JK.ASSIGNMENT; @@ -235,4 +236,11 @@ return result; } + gearUtils.updateAudioLatency = function(app) { + var latency = jamClient.FTUEGetExpectedLatency().latency; + return rest.updateAudioLatency({client_id: app.clientId, audio_latency: latency}) + .fail(function(jqXHR) { + app.notifyServerError(jqXHR, "Unable to sync audio latency") + }); + } })(window, jQuery); \ No newline at end of file diff --git a/web/app/assets/stylesheets/client/dragDropTracks.css.scss b/web/app/assets/stylesheets/client/dragDropTracks.css.scss index 8fc30bcf9..24cc6e5d5 100644 --- a/web/app/assets/stylesheets/client/dragDropTracks.css.scss +++ b/web/app/assets/stylesheets/client/dragDropTracks.css.scss @@ -10,8 +10,9 @@ } .unassigned-output-channels { - min-height: 80px; + min-height: 90px; overflow-y: auto; + max-height: 90px; //padding-right:18px; // to keep draggables off of scrollbar. maybe necessary &.drag-in-progress { @@ -31,8 +32,12 @@ .unassigned-input-channels { min-height: 240px; overflow-y: auto; + max-height:93%; //padding-right:18px; // to keep draggables off of scrollbar. maybe necessary + .ftue-input { + + } &.drag-in-progress { overflow-y: visible; overflow-x: visible; diff --git a/web/app/controllers/api_music_sessions_controller.rb b/web/app/controllers/api_music_sessions_controller.rb index 1f5929394..2b4289c1b 100644 --- a/web/app/controllers/api_music_sessions_controller.rb +++ b/web/app/controllers/api_music_sessions_controller.rb @@ -110,7 +110,8 @@ class ApiMusicSessionsController < ApiController band, params[:genres], params[:tracks], - params[:legal_terms]) + params[:legal_terms], + params[:audio_latency]) if @music_session.errors.any? response.status = :unprocessable_entity @@ -159,7 +160,8 @@ class ApiMusicSessionsController < ApiController params[:id], params[:client_id], params[:as_musician], - params[:tracks]) + params[:tracks], + params[:audio_latency]) if @connection.errors.any? response.status = :unprocessable_entity diff --git a/web/app/controllers/api_users_controller.rb b/web/app/controllers/api_users_controller.rb index 56b408ea5..99601d900 100644 --- a/web/app/controllers/api_users_controller.rb +++ b/web/app/controllers/api_users_controller.rb @@ -12,7 +12,7 @@ class ApiUsersController < ApiController :band_invitation_index, :band_invitation_show, :band_invitation_update, # band invitations :set_password, :begin_update_email, :update_avatar, :delete_avatar, :generate_filepicker_policy, :share_session, :share_recording, - :affiliate_report] + :affiliate_report, :audio_latency] respond_to :json @@ -527,6 +527,15 @@ class ApiUsersController < ApiController @user = current_user if params[:success] @user.update_progression_field(:first_certified_gear_at) + + connection = Connection.find_by_client_id(params[:client_id]) + # update last_jam location information + @user.update_addr_loc(connection, 'g') if connection + + if !@user.errors.any? + # update audio gear latency information + @user.update_audio_latency(connection, params[:audio_latency]) + end else @user.failed_qualification(params[:reason]) end @@ -643,10 +652,16 @@ class ApiUsersController < ApiController if play.errors.any? render :json => { :message => "Unexpected error occurred" }, :status => 500 - return else render :json => {}, :status => 201 - return + end + end + + # updates audio latency on the user, and associated connection + def audio_latency + Connection.transaction do + @user.update_audio_latency(Connection.find_by_client_id(params[:client_id]), params[:audio_latency]) + respond_with_model(@user) end end diff --git a/web/app/views/api_music_sessions/show.rabl b/web/app/views/api_music_sessions/show.rabl index 9e24bb634..deed7e064 100644 --- a/web/app/views/api_music_sessions/show.rabl +++ b/web/app/views/api_music_sessions/show.rabl @@ -33,7 +33,7 @@ else child(:connections => :participants) { collection @music_sessions, :object_root => false - attributes :ip_address, :client_id, :joined_session_at + attributes :ip_address, :client_id, :joined_session_at, :audio_latency node :user do |connection| { :id => connection.user.id, :photo_url => connection.user.photo_url, :name => connection.user.name, :is_friend => connection.user.friends?(current_user), :connection_state => connection.aasm_state } diff --git a/web/app/views/api_users/audio_latency.rabl b/web/app/views/api_users/audio_latency.rabl new file mode 100644 index 000000000..924eff588 --- /dev/null +++ b/web/app/views/api_users/audio_latency.rabl @@ -0,0 +1,3 @@ +object @user + +attributes :id \ 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 eff5b3c5c..02be0b8a5 100644 --- a/web/app/views/api_users/show.rabl +++ b/web/app/views/api_users/show.rabl @@ -1,6 +1,6 @@ object @user -attributes :id, :first_name, :last_name, :name, :city, :state, :country, :location, :online, :photo_url, :musician, :gender, :birth_date, :internet_service_provider, :friend_count, :liker_count, :like_count, :follower_count, :following_count, :recording_count, :session_count, :biography, :favorite_count +attributes :id, :first_name, :last_name, :name, :city, :state, :country, :location, :online, :photo_url, :musician, :gender, :birth_date, :internet_service_provider, :friend_count, :liker_count, :like_count, :follower_count, :following_count, :recording_count, :session_count, :biography, :favorite_count, :audio_latency if @user.musician? node :location do @user.location end diff --git a/web/config/routes.rb b/web/config/routes.rb index d4ee8c1b0..98744d193 100644 --- a/web/config/routes.rb +++ b/web/config/routes.rb @@ -258,6 +258,9 @@ SampleApp::Application.routes.draw do match '/users/progression/certified_gear' => 'api_users#qualified_gear', :via => :post match '/users/progression/social_promoted' => 'api_users#social_promoted', :via => :post + # audio latency + match '/users/:id/audio_latency' => 'api_users#audio_latency', :via => :post + # social match '/users/:id/share/session/:provider' => 'api_users#share_session', :via => :get match '/users/:id/share/recording/:provider' => 'api_users#share_recording', :via => :get diff --git a/web/lib/music_session_manager.rb b/web/lib/music_session_manager.rb index 8cc8144e8..498a6d8c4 100644 --- a/web/lib/music_session_manager.rb +++ b/web/lib/music_session_manager.rb @@ -9,7 +9,7 @@ MusicSessionManager < BaseManager @log = Logging.logger[self] end - def create(music_session, user, client_id, description, musician_access, approval_required, fan_chat, fan_access, band, genres, tracks, legal_terms) + def create(music_session, user, client_id, description, musician_access, approval_required, fan_chat, fan_access, band, genres, tracks, legal_terms, audio_latency) return_value = nil time = Benchmark.realtime do @@ -38,7 +38,7 @@ MusicSessionManager < BaseManager # auto-join this user into the newly created session as_musician = true - connection = ConnectionManager.new.join_music_session(user, client_id, active_music_session, as_musician, tracks) + connection = ConnectionManager.new.join_music_session(user, client_id, active_music_session, as_musician, tracks, audio_latency) unless connection.errors.any? user.update_progression_field(:first_music_session_at) @@ -83,7 +83,7 @@ MusicSessionManager < BaseManager music_session end - def participant_create(user, music_session_id, client_id, as_musician, tracks) + def participant_create(user, music_session_id, client_id, as_musician, tracks, audio_latency) connection = nil music_session = nil ActiveRecord::Base.transaction do @@ -92,7 +92,7 @@ MusicSessionManager < BaseManager music_session.with_lock do # VRFS-1297 music_session.tick_track_changes - connection = ConnectionManager.new.join_music_session(user, client_id, music_session, as_musician, tracks) + connection = ConnectionManager.new.join_music_session(user, client_id, music_session, as_musician, tracks, audio_latency) if connection.errors.any? # rollback the transaction to make sure nothing is disturbed in the database diff --git a/web/spec/controllers/api_claimed_recordings_spec.rb b/web/spec/controllers/api_claimed_recordings_spec.rb index 8778d81e4..540ff2cf5 100644 --- a/web/spec/controllers/api_claimed_recordings_spec.rb +++ b/web/spec/controllers/api_claimed_recordings_spec.rb @@ -11,7 +11,7 @@ describe ApiClaimedRecordingsController do @music_session = FactoryGirl.create(:active_music_session, :creator => @user, :musician_access => true) # @music_session.connections << @connection @music_session.save - @connection.join_the_session(@music_session, true, nil, @user) + @connection.join_the_session(@music_session, true, nil, @user, 10) @recording = Recording.start(@music_session, @user) @recording.stop @recording.reload diff --git a/web/spec/controllers/api_users_controller_spec.rb b/web/spec/controllers/api_users_controller_spec.rb new file mode 100644 index 000000000..3cefceb4b --- /dev/null +++ b/web/spec/controllers/api_users_controller_spec.rb @@ -0,0 +1,36 @@ +require 'spec_helper' + +describe ApiUsersController do + render_views + + let(:user) { FactoryGirl.create(:user) } + let (:conn) { FactoryGirl.create(:connection, :user => user) } + + + before(:each) do + controller.current_user = user + end + + describe "audio_latency" do + it "updates both connection and user" do + + post :audio_latency, id: user.id, client_id: conn.client_id, audio_latency: 1.5, :format => 'json' + response.should be_success + + conn.reload + conn.last_jam_audio_latency.should == 1.5 + + user.reload + conn.user.last_jam_audio_latency.should == 1.5 + end + + it "if connection does not exist, user is still updated" do + + post :audio_latency, id: user.id, client_id: 'nothingness', audio_latency: 1.5, :format => 'json' + response.should be_success + + user.reload + conn.user.last_jam_audio_latency.should == 1.5 + end + end +end diff --git a/web/spec/controllers/users_controller_spec.rb b/web/spec/controllers/users_controller_spec.rb deleted file mode 100644 index e69de29bb..000000000 diff --git a/web/spec/features/social_meta_spec.rb b/web/spec/features/social_meta_spec.rb index 43a11f23b..9df062b8e 100644 --- a/web/spec/features/social_meta_spec.rb +++ b/web/spec/features/social_meta_spec.rb @@ -65,7 +65,7 @@ describe "social metadata" do ms = FactoryGirl.create(:active_music_session, :creator => user, :musician_access => true) # ms.connections << connection ms.save! - connection.join_the_session(ms, true, nil, user) + connection.join_the_session(ms, true, nil, user, 10) ms } @@ -93,7 +93,7 @@ describe "social metadata" do @music_session = FactoryGirl.create(:active_music_session, :creator => @user, :musician_access => true) # @music_session.connections << @connection @music_session.save - @connection.join_the_session(@music_session, true, nil, @user) + @connection.join_the_session(@music_session, true, nil, @user, 10) @recording = Recording.start(@music_session, @user) @recording.stop @recording.reload diff --git a/web/spec/helpers/recording_helper_spec.rb b/web/spec/helpers/recording_helper_spec.rb index 95091ccf0..2b7603102 100644 --- a/web/spec/helpers/recording_helper_spec.rb +++ b/web/spec/helpers/recording_helper_spec.rb @@ -10,7 +10,7 @@ describe MusicSessionHelper do @music_session = FactoryGirl.create(:active_music_session, :creator => @user, :musician_access => true) # @music_session.connections << @connection @music_session.save - @connection.join_the_session(@music_session, true, nil, @user) + @connection.join_the_session(@music_session, true, nil, @user, 10) @recording = Recording.start(@music_session, @user) @recording.stop @recording.reload diff --git a/web/spec/managers/music_session_manager_spec.rb b/web/spec/managers/music_session_manager_spec.rb index 4ede47c94..94b7e4411 100644 --- a/web/spec/managers/music_session_manager_spec.rb +++ b/web/spec/managers/music_session_manager_spec.rb @@ -20,13 +20,13 @@ describe MusicSessionManager do end it "creates a session properly" do - active_music_session = @music_session_manager.create(music_session, @user, @connection.client_id, "description", true, false, true, true, @band, [@genre], @tracks, true) + active_music_session = @music_session_manager.create(music_session, @user, @connection.client_id, "description", true, false, true, true, @band, [@genre], @tracks, true, 10) ActiveMusicSession.find(active_music_session.id) # shouldn't throw an exception end it "updates a session properly" do - active_music_session = @music_session_manager.create(music_session, @user, @connection.client_id, "description", true, false, true, true, @band, [@genre], @tracks, true) + active_music_session = @music_session_manager.create(music_session, @user, @connection.client_id, "description", true, false, true, true, @band, [@genre], @tracks, true, 10) @music_session_manager.update(music_session, "updated description", nil, nil, nil, nil, nil) music_session.reload music_session.description.should == "updated description" @@ -40,7 +40,7 @@ describe MusicSessionManager do end it "deletes a session properly" do - active_music_session = @music_session_manager.create(music_session, @user, @connection.client_id, "description", true, false, true, true, @band, [@genre], @tracks, true) + active_music_session = @music_session_manager.create(music_session, @user, @connection.client_id, "description", true, false, true, true, @band, [@genre], @tracks, true, 10) music_session = MusicSession.find_by_music_session_id(active_music_session.id) music_session.should_not be_nil active_music_session.destroy diff --git a/web/spec/requests/music_sessions_api_spec.rb b/web/spec/requests/music_sessions_api_spec.rb index dfd06511f..42f9a9817 100755 --- a/web/spec/requests/music_sessions_api_spec.rb +++ b/web/spec/requests/music_sessions_api_spec.rb @@ -701,7 +701,7 @@ describe "Music Session API ", :type => :api do music_session = FactoryGirl.create(:active_music_session, :creator => user, :musician_access => true) # music_session.connections << connection music_session.save - connection.join_the_session(music_session, true, nil, user) + connection.join_the_session(music_session, true, nil, user, 10) recording = Recording.start(music_session, user) recording.stop recording.reload diff --git a/web/spec/requests/users_api_spec.rb b/web/spec/requests/users_api_spec.rb index ba450e85a..dec2cd384 100644 --- a/web/spec/requests/users_api_spec.rb +++ b/web/spec/requests/users_api_spec.rb @@ -985,7 +985,7 @@ describe "User API", :type => :api do ms = FactoryGirl.create(:active_music_session, :creator => user, :musician_access => true) # ms.connections << connection ms.save! - connection.join_the_session(ms, true, nil, user) + connection.join_the_session(ms, true, nil, user, 10) ms } @@ -1123,7 +1123,7 @@ describe "User API", :type => :api do @music_session = FactoryGirl.create(:active_music_session, :creator => user, :musician_access => true) # @music_session.connections << @connection @music_session.save - @connection.join_the_session(@music_session, true, nil, user) + @connection.join_the_session(@music_session, true, nil, user, 10) @recording = Recording.start(@music_session, user) @recording.stop @recording.reload From 507eba6fbcc4bedc206d9b0d9e78da5843063784 Mon Sep 17 00:00:00 2001 From: Seth Call Date: Mon, 9 Jun 2014 16:33:34 -0500 Subject: [PATCH 16/32] * VRFS-1759 - found another getOperatingMode that needed to be protected --- web/app/assets/javascripts/JamServer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/app/assets/javascripts/JamServer.js b/web/app/assets/javascripts/JamServer.js index c61b8b6fe..ffd1142f6 100644 --- a/web/app/assets/javascripts/JamServer.js +++ b/web/app/assets/javascripts/JamServer.js @@ -453,7 +453,7 @@ clientType = context.JK.clientType(); } if(!mode) { - mode = context.jamClient.getOperatingMode(); + mode = context.jamClient.getOperatingMode ? context.jamClient.getOperatingMode() : 'client'; } connectDeferred = new $.Deferred(); From 4219b158150962631534682bd406f726b0762654 Mon Sep 17 00:00:00 2001 From: Seth Call Date: Mon, 9 Jun 2014 23:28:31 -0500 Subject: [PATCH 17/32] * VRFS-1772 - prevent wrapping of frame-buffers --- .../wizard/gear/step_select_gear.js | 2 +- .../client/wizard/framebuffers.css.scss | 4 ++-- .../features/configure_tracks_dialog_spec.rb | 18 ++++++++++++++++++ 3 files changed, 21 insertions(+), 3 deletions(-) create mode 100644 web/spec/features/configure_tracks_dialog_spec.rb 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 bfd75d343..915747c6e 100644 --- a/web/app/assets/javascripts/wizard/gear/step_select_gear.js +++ b/web/app/assets/javascripts/wizard/gear/step_select_gear.js @@ -626,7 +626,7 @@ $knobs.show(); } else { - $knobs.hide(); + $knobs.show(); } // handle ASIO visibility diff --git a/web/app/assets/stylesheets/client/wizard/framebuffers.css.scss b/web/app/assets/stylesheets/client/wizard/framebuffers.css.scss index 120196378..b3f205d3f 100644 --- a/web/app/assets/stylesheets/client/wizard/framebuffers.css.scss +++ b/web/app/assets/stylesheets/client/wizard/framebuffers.css.scss @@ -30,10 +30,10 @@ left:5px; } .easydropdown-wrapper:nth-of-type(2) { - left:35px; + left:30px; } .easydropdown, .easydropdown-wrapper { - width:30px; + width:30px !important; } } diff --git a/web/spec/features/configure_tracks_dialog_spec.rb b/web/spec/features/configure_tracks_dialog_spec.rb new file mode 100644 index 000000000..3a42ca695 --- /dev/null +++ b/web/spec/features/configure_tracks_dialog_spec.rb @@ -0,0 +1,18 @@ +require 'spec_helper' + +describe "Gear Wizard", :js => true, :type => :feature, :capybara_feature => true, :slow => true do + + subject { page } + + let(:user) { FactoryGirl.create(:user) } + + before(:each) do + emulate_client + sign_in_poltergeist user + end + + it "launches from audio profile screen" do + + end + +end From 60a1618c9b84a68e1a06040847d647a6b44c9b00 Mon Sep 17 00:00:00 2001 From: Seth Call Date: Mon, 9 Jun 2014 23:30:02 -0500 Subject: [PATCH 18/32] * oops left in manual show of frame-buffers for VRFS-1772 --- web/app/assets/javascripts/wizard/gear/step_select_gear.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 915747c6e..bfd75d343 100644 --- a/web/app/assets/javascripts/wizard/gear/step_select_gear.js +++ b/web/app/assets/javascripts/wizard/gear/step_select_gear.js @@ -626,7 +626,7 @@ $knobs.show(); } else { - $knobs.show(); + $knobs.hide(); } // handle ASIO visibility From 169c9b0ab103b0491e3cc26d33b05f5c66fff16f Mon Sep 17 00:00:00 2001 From: Seth Call Date: Tue, 10 Jun 2014 09:02:51 -0500 Subject: [PATCH 19/32] * VRFS-1774 - de-dup channels repeated * VRFS-1774 - filter out assigned channels * VRFS-1773 - no pre-assignment on step 3 (configure tracks) * VRFS-1774 - make radiobuttons appear more disabled; no more prod * VRFS-1767 - probably gone; but there is a more fundamental issue --- .../javascripts/configureTracksHelper.js | 16 ++-- web/app/assets/javascripts/voiceChatHelper.js | 83 +++++++++++++------ .../wizard/gear/step_configure_tracks.js | 12 ++- .../wizard/gear/step_configure_voice_chat.js | 2 +- .../wizard/gear/step_select_gear.js | 1 + .../assets/javascripts/wizard/gear_utils.js | 32 +++++-- .../client/voiceChatHelper.css.scss | 4 + .../wizard/gear/_gear_wizard.html.haml | 2 +- web/spec/features/gear_wizard_spec.rb | 6 ++ 9 files changed, 116 insertions(+), 42 deletions(-) diff --git a/web/app/assets/javascripts/configureTracksHelper.js b/web/app/assets/javascripts/configureTracksHelper.js index b834e99c3..4438770ef 100644 --- a/web/app/assets/javascripts/configureTracksHelper.js +++ b/web/app/assets/javascripts/configureTracksHelper.js @@ -21,11 +21,15 @@ var $outputChannelHolder = null; var $instrumentsHolder = null; - function loadChannels() { + function loadChannels(forceInputsToUnassign) { var musicPorts = jamClient.FTUEGetChannels(); $unassignedInputsHolder.empty(); $unassignedOutputsHolder.empty(); + + // reset all counts + $unassignedInputsHolder.find('.track-target').attr('track-count', '0'); + $unassignedOutputsHolder.find('.output-target').attr('output-count', '0'); $tracksHolder.find('.ftue-input').remove(); $outputChannelHolder.find('.ftue-input').remove(); @@ -38,7 +42,7 @@ context._.each(inputChannels, function (inputChannel) { var $channel = $(context._.template($templateAssignablePort.html(), inputChannel, { variable: 'data' })); - if(inputChannel.assignment == ASSIGNMENT.UNASSIGNED) { + if(forceInputsToUnassign || inputChannel.assignment == ASSIGNMENT.UNASSIGNED) { unassignInputChannel($channel); } else if(inputChannel.assignment == ASSIGNMENT.CHAT) { @@ -247,7 +251,7 @@ } } - function loadTrackInstruments() { + function loadTrackInstruments(forceInputsToUnassign) { var $trackInstruments = $instrumentsHolder.find('.track-instrument'); context._.each($trackInstruments, function(trackInstrument) { @@ -279,9 +283,9 @@ return saved; } - function reset() { - loadChannels(); - loadTrackInstruments(); + function reset(forceInputsToUnassign) { + loadChannels(forceInputsToUnassign); + loadTrackInstruments(forceInputsToUnassign); } function unassignOutputChannel($channel) { diff --git a/web/app/assets/javascripts/voiceChatHelper.js b/web/app/assets/javascripts/voiceChatHelper.js index ea5b5dc26..57d8d5d7d 100644 --- a/web/app/assets/javascripts/voiceChatHelper.js +++ b/web/app/assets/javascripts/voiceChatHelper.js @@ -20,21 +20,25 @@ var $selectedChatInput = null;// should only be used if isChatEnabled = true var saveImmediate = null; // if true, then every action by the user results in a save to the backend immediately, false means you have to call trySave to persist + // needed because iCheck fires iChecked event even when you programmatically change it, unlike when using .val(x) + var ignoreICheckeEvent = false; + function defaultReuse() { - $reuseAudioInputRadio.iCheck('check').attr('checked', 'checked'); - $useChatInputRadio.removeAttr('checked'); + suppressChange(function(){ + $reuseAudioInputRadio.iCheck('check').attr('checked', 'checked'); + $useChatInputRadio.removeAttr('checked'); + }) } function isChatEnabled() { - return $useChatInputRadio.is(':checked'); } - function reset() { + function reset(forceDisabledChat) { $selectedChatInput = null; - if(context.jamClient.TrackGetChatEnable()) { + if(!forceDisabledChat && context.jamClient.TrackGetChatEnable()) { enableChat(false); } else { @@ -82,16 +86,41 @@ }); if(!isChatEnabled()) { - $radioButtons.iCheck('disable'); + disableChatButtonsUI(); } $chatInputs.find('.chat-input').show().on('click', function() { - if(!isChatEnabled()) { - context.JK.prodBubble($parent.find('.use-chat-input h3'), 'chat-not-enabled', {}, { positions:['left']}); - } + // obnoxious; remove soon XXX + // if(!isChatEnabled()) { + // context.JK.prodBubble($parent.find('.use-chat-input h3'), 'chat-not-enabled', {}, { positions:['left']}); + // } }) } + function disableChatButtonsUI() { + var $radioButtons = $chatInputs.find('input[name="chat-device"]'); + $radioButtons.iCheck('disable') + $chatInputs.addClass('disabled'); + } + + function enableChatButtonsUI() { + var $radioButtons = $chatInputs.find('input[name="chat-device"]'); + $radioButtons.iCheck('enable') + $chatInputs.removeClass('disabled'); + + } + function suppressChange(proc) { + + ignoreICheckeEvent = true; + + try { + proc(); + } + finally { + ignoreICheckeEvent = false; + } + } + function disableChat(applyToBackend) { if(saveImmediate && applyToBackend) { logger.debug("voiceChatHelper: disabling chat to backend"); @@ -100,9 +129,10 @@ if(!result || result.length == 0) { // success - $reuseAudioInputRadio.iCheck('check').attr('checked', 'checked'); - $useChatInputRadio.removeAttr('checked'); - + suppressChange(function() { + $reuseAudioInputRadio.iCheck('check').attr('checked', 'checked'); + $useChatInputRadio.removeAttr('checked'); + }) } else { context.JK.Banner.showAlert('Unable to disable chat. ' + result); @@ -111,11 +141,12 @@ } else { logger.debug("voiceChatHelper: disabling chat UI only"); - $reuseAudioInputRadio.iCheck('check').attr('checked', 'checked'); - $useChatInputRadio.removeAttr('checked'); + suppressChange(function() { + $reuseAudioInputRadio.iCheck('check').attr('checked', 'checked'); + $useChatInputRadio.removeAttr('checked'); + }) } - var $radioButtons = $chatInputs.find('input[name="chat-device"]'); - $radioButtons.iCheck('disable'); + disableChatButtonsUI() } function enableChat(applyToBackend) { @@ -126,9 +157,10 @@ if(!result || result.length == 0) { // success - $useChatInputRadio.iCheck('check').attr('checked', 'checked'); - $reuseAudioInputRadio.removeAttr('checked'); - + suppressChange(function() { + $useChatInputRadio.iCheck('check').attr('checked', 'checked'); + $reuseAudioInputRadio.removeAttr('checked'); + }) } else { context.JK.Banner.showAlert('Unable to enable chat. ' + result); @@ -137,12 +169,13 @@ } else { logger.debug("voiceChatHelper: enabling chat UI only"); - $useChatInputRadio.iCheck('check').attr('checked', 'checked'); - $reuseAudioInputRadio.removeAttr('checked'); + suppressChange(function() { + $useChatInputRadio.iCheck('check').attr('checked', 'checked'); + $reuseAudioInputRadio.removeAttr('checked'); + }) } - var $radioButtons = $chatInputs.find('input[name="chat-device"]'); - $radioButtons.iCheck('enable'); + enableChatButtonsUI(); } function handleChatEnabledToggle() { @@ -153,8 +186,8 @@ $reuseAudioInputRadio.closest('.iradio_minimal').css('position', 'absolute'); $useChatInputRadio.closest('.iradio_minimal').css('position', 'absolute'); - $reuseAudioInputRadio.on('ifChecked', function() { disableChat(true) }); - $useChatInputRadio.on('ifChecked', function() { enableChat(true) }); + $reuseAudioInputRadio.on('ifChecked', function() { if(!ignoreICheckeEvent) disableChat(true) }); + $useChatInputRadio.on('ifChecked', function() { if(!ignoreICheckeEvent) enableChat(true) }); } // gets the state of the UI diff --git a/web/app/assets/javascripts/wizard/gear/step_configure_tracks.js b/web/app/assets/javascripts/wizard/gear/step_configure_tracks.js index 2b8698846..495bf07e3 100644 --- a/web/app/assets/javascripts/wizard/gear/step_configure_tracks.js +++ b/web/app/assets/javascripts/wizard/gear/step_configure_tracks.js @@ -13,20 +13,27 @@ var configureTracksHelper = new context.JK.ConfigureTracksHelper(app); var $step = null; - + var successfullyAssignedOnce = false; function handleNext() { var saved = configureTracksHelper.trySave(); if(saved) { context.JK.GA.trackConfigureTracksCompletion(context.JK.detectOS()); + successfullyAssignedOnce = true; } return saved; } + function newSession() { + successfullyAssignedOnce = false; + } + function beforeShow() { - configureTracksHelper.reset(); + var forceInputsToUnassigned = !successfullyAssignedOnce; + + configureTracksHelper.reset(forceInputsToUnassigned) } function initialize(_$step) { @@ -35,6 +42,7 @@ configureTracksHelper.initialize($step); } + this.newSession = newSession; this.handleNext = handleNext; this.beforeShow = beforeShow; this.initialize = initialize; diff --git a/web/app/assets/javascripts/wizard/gear/step_configure_voice_chat.js b/web/app/assets/javascripts/wizard/gear/step_configure_voice_chat.js index c48e83a2a..b1b936d6e 100644 --- a/web/app/assets/javascripts/wizard/gear/step_configure_voice_chat.js +++ b/web/app/assets/javascripts/wizard/gear/step_configure_voice_chat.js @@ -20,7 +20,7 @@ var voiceChatHelper = new context.JK.VoiceChatHelper(app); function newSession() { - voiceChatHelper.reset(); + voiceChatHelper.reset(true); } function beforeShow() { 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 bfd75d343..e0e138cab 100644 --- a/web/app/assets/javascripts/wizard/gear/step_select_gear.js +++ b/web/app/assets/javascripts/wizard/gear/step_select_gear.js @@ -81,6 +81,7 @@ var optionsHtml = ''; optionsHtml = ''; context._.each(deviceInformation, function (deviceInfo, deviceId) { + if(deviceInfo.inputCount > 0) { optionsHtml += ''; } diff --git a/web/app/assets/javascripts/wizard/gear_utils.js b/web/app/assets/javascripts/wizard/gear_utils.js index f3b045cae..46e93e469 100644 --- a/web/app/assets/javascripts/wizard/gear_utils.js +++ b/web/app/assets/javascripts/wizard/gear_utils.js @@ -205,21 +205,39 @@ gearUtils.getChatInputs = function(){ - var musicPorts = jamClient.FTUEGetChannels(); + //var musicPorts = jamClient.FTUEGetChannels(); + var chatsOnCurrentDevice = context.jamClient.FTUEGetChatInputs(true); var chatsOnOtherDevices = context.jamClient.FTUEGetChatInputs(false); var chatInputs = []; - context._.each(musicPorts.inputs, function(input) { - chatInputs.push({id: input.id, name: input.name, assignment:input.assignment}); - }); + //context._.each(musicPorts.inputs, function(input) { + // chatInputs.push({id: input.id, name: input.name, assignment:input.assignment}); + //}); + + var deDupper = {}; + context._.each(chatsOnCurrentDevice, function(chatChannelName, chatChannelId) { + var chatInput = {id: chatChannelId, name: chatChannelName, assignment: ASSIGNMENT.UNASSIGNED}; + if(!deDupper[chatInput.id]) { + var assignment = context.jamClient.TrackGetAssignment(chatChannelId, true); + if(assignment != ASSIGNMENT.CHAT) { + chatInputs.push(chatInput); + deDupper[chatInput.id] = chatInput; + } + } + }) context._.each(chatsOnOtherDevices, function(chatChannelName, chatChannelId) { var chatInput = {id: chatChannelId, name: chatChannelName, assignment: null}; - var assignment = context.jamClient.TrackGetAssignment(chatChannelId, true); - chatInput.assignment = assignment; - chatInputs.push(chatInput); + if(!deDupper[chatInput.id]) { + var assignment = context.jamClient.TrackGetAssignment(chatChannelId, true); + chatInput.assignment = assignment; + + chatInputs.push(chatInput); + deDupper[chatInput.id] = chatInput; + } }) + logger.debug("chatInputs:", chatInputs) return chatInputs; } diff --git a/web/app/assets/stylesheets/client/voiceChatHelper.css.scss b/web/app/assets/stylesheets/client/voiceChatHelper.css.scss index 17a721813..8fd6b3e81 100644 --- a/web/app/assets/stylesheets/client/voiceChatHelper.css.scss +++ b/web/app/assets/stylesheets/client/voiceChatHelper.css.scss @@ -40,7 +40,11 @@ &.chat-inputs { height: 230px !important; overflow: auto; + color:white; + &.disabled { + color:gray; + } p { white-space: nowrap; display: inline-block; diff --git a/web/app/views/clients/wizard/gear/_gear_wizard.html.haml b/web/app/views/clients/wizard/gear/_gear_wizard.html.haml index 367349565..7aa5bff56 100644 --- a/web/app/views/clients/wizard/gear/_gear_wizard.html.haml +++ b/web/app/views/clients/wizard/gear/_gear_wizard.html.haml @@ -100,7 +100,7 @@ %a.button-orange.watch-video{href:'#'} WATCH VIDEO .wizard-step-column %h2 Select Voice Chat Option - %form + %form.voice .voicechat-option.reuse-audio-input %input{type:"radio", name: "voicechat", checked:"checked"} %h3 Use Music Microphone diff --git a/web/spec/features/gear_wizard_spec.rb b/web/spec/features/gear_wizard_spec.rb index 1acc2848a..ba5e2b222 100644 --- a/web/spec/features/gear_wizard_spec.rb +++ b/web/spec/features/gear_wizard_spec.rb @@ -25,6 +25,12 @@ describe "Gear Wizard", :js => true, :type => :feature, :capybara_feature => tru # step 3 - configure tracks find('.ftue-step-title', text: 'Configure Tracks') + + # drag one input over to tracks area http://rubydoc.info/github/jnicklas/capybara/master/Capybara/Node/Element#drag_to-instance_method + input = first('.ftue-input') + track_slot = first('.track-target') + input.drag_to(track_slot) + find('.btn-next.button-orange').trigger(:click) # step 4 - configure voice chat From 07fcef684418b2d903c401c3a021678bc7d7a71e Mon Sep 17 00:00:00 2001 From: Seth Call Date: Tue, 10 Jun 2014 09:44:50 -0500 Subject: [PATCH 20/32] * fix case where unassigned chat doesn't relinquish it --- web/app/assets/javascripts/fakeJamClient.js | 5 ++++ web/app/assets/javascripts/voiceChatHelper.js | 26 ++++++++++++------- .../javascripts/wizard/gear/gear_wizard.js | 1 + .../wizard/gear/step_configure_voice_chat.js | 8 +++++- .../assets/javascripts/wizard/gear_utils.js | 22 ++++++++++++---- 5 files changed, 46 insertions(+), 16 deletions(-) diff --git a/web/app/assets/javascripts/fakeJamClient.js b/web/app/assets/javascripts/fakeJamClient.js index 6c4596517..98eeb39d8 100644 --- a/web/app/assets/javascripts/fakeJamClient.js +++ b/web/app/assets/javascripts/fakeJamClient.js @@ -168,6 +168,9 @@ ] }; } + + function FTUEClearChannelAssignments(){} + function FTUEClearChatInput(){} function FTUEGetAudioDevices() { return { "devices": [ @@ -855,6 +858,8 @@ this.FTUEGetConfigurationDevice = FTUEGetConfigurationDevice; this.FTUEIsMusicDeviceWDM = FTUEIsMusicDeviceWDM; this.FTUELoadAudioConfiguration = FTUELoadAudioConfiguration; + this.FTUEClearChannelAssignments = FTUEClearChannelAssignments; + this.FTUEClearChatInput = FTUEClearChatInput; // Session this.SessionAddTrack = SessionAddTrack; diff --git a/web/app/assets/javascripts/voiceChatHelper.js b/web/app/assets/javascripts/voiceChatHelper.js index 57d8d5d7d..55b670e8d 100644 --- a/web/app/assets/javascripts/voiceChatHelper.js +++ b/web/app/assets/javascripts/voiceChatHelper.js @@ -21,7 +21,7 @@ var saveImmediate = null; // if true, then every action by the user results in a save to the backend immediately, false means you have to call trySave to persist // needed because iCheck fires iChecked event even when you programmatically change it, unlike when using .val(x) - var ignoreICheckeEvent = false; + var ignoreICheckEvent = false; function defaultReuse() { suppressChange(function(){ @@ -111,20 +111,25 @@ } function suppressChange(proc) { - ignoreICheckeEvent = true; + ignoreICheckEvent = true; try { proc(); } finally { - ignoreICheckeEvent = false; + ignoreICheckEvent = false; } } function disableChat(applyToBackend) { if(saveImmediate && applyToBackend) { logger.debug("voiceChatHelper: disabling chat to backend"); - context.jamClient.TrackSetChatEnable(false); + var state = getCurrentState(); + //context.jamClient.TrackSetChatEnable(false); + //if(state.chat_channel) { + // context.jamClient.TrackSetAssignment(state.chat_channel, true, ASSIGNMENT.UNASSIGNED); + //} + context.jamClient.FTUEClearChatInput(); var result = context.jamClient.TrackSaveAssignments(); if(!result || result.length == 0) { @@ -186,8 +191,8 @@ $reuseAudioInputRadio.closest('.iradio_minimal').css('position', 'absolute'); $useChatInputRadio.closest('.iradio_minimal').css('position', 'absolute'); - $reuseAudioInputRadio.on('ifChecked', function() { if(!ignoreICheckeEvent) disableChat(true) }); - $useChatInputRadio.on('ifChecked', function() { if(!ignoreICheckeEvent) enableChat(true) }); + $reuseAudioInputRadio.on('ifChecked', function() { if(!ignoreICheckEvent) disableChat(true) }); + $useChatInputRadio.on('ifChecked', function() { if(!ignoreICheckEvent) enableChat(true) }); } // gets the state of the UI @@ -215,10 +220,11 @@ } else { logger.debug("disabling chat."); - context.jamClient.TrackSetChatEnable(false); - if(state.chat_channel) { - context.jamClient.TrackSetAssignment(state.chat_channel, true, ASSIGNMENT.UNASSIGNED); - } + context.jamClient.FTUEClearChatInput(); + //context.jamClient.TrackSetChatEnable(false); + //if(state.chat_channel) { + //context.jamClient.TrackSetAssignment(state.chat_channel, true, ASSIGNMENT.UNASSIGNED); + //} } var result = context.jamClient.TrackSaveAssignments(); diff --git a/web/app/assets/javascripts/wizard/gear/gear_wizard.js b/web/app/assets/javascripts/wizard/gear/gear_wizard.js index c2b5f3101..7fdddf1e6 100644 --- a/web/app/assets/javascripts/wizard/gear/gear_wizard.js +++ b/web/app/assets/javascripts/wizard/gear/gear_wizard.js @@ -75,6 +75,7 @@ var newProfileName = 'FTUEAttempt-' + new Date().getTime().toString(); logger.debug("setting FTUE-prefixed profile name to: " + newProfileName); context.jamClient.FTUESetMusicProfileName(newProfileName); + context.jamClient.FTUEClearChannelAssignments(); newSession(); var profileName = context.jamClient.FTUEGetMusicProfileName(); diff --git a/web/app/assets/javascripts/wizard/gear/step_configure_voice_chat.js b/web/app/assets/javascripts/wizard/gear/step_configure_voice_chat.js index b1b936d6e..df656b42a 100644 --- a/web/app/assets/javascripts/wizard/gear/step_configure_voice_chat.js +++ b/web/app/assets/javascripts/wizard/gear/step_configure_voice_chat.js @@ -18,12 +18,18 @@ var $selectedChatInput = null;// should only be used if isChatEnabled = true var voiceChatHelper = new context.JK.VoiceChatHelper(app); + var firstTimeShown = false; function newSession() { - voiceChatHelper.reset(true); + firstTimeShown = true; } function beforeShow() { + var forceDisabledChat = firstTimeShown; + + voiceChatHelper.reset(forceDisabledChat); + + firstTimeShown = false; } function handleNext() { diff --git a/web/app/assets/javascripts/wizard/gear_utils.js b/web/app/assets/javascripts/wizard/gear_utils.js index 46e93e469..dd5d52ed2 100644 --- a/web/app/assets/javascripts/wizard/gear_utils.js +++ b/web/app/assets/javascripts/wizard/gear_utils.js @@ -205,8 +205,8 @@ gearUtils.getChatInputs = function(){ - //var musicPorts = jamClient.FTUEGetChannels(); - var chatsOnCurrentDevice = context.jamClient.FTUEGetChatInputs(true); + var musicPorts = jamClient.FTUEGetChannels(); + //var chatsOnCurrentDevice = context.jamClient.FTUEGetChatInputs(true); var chatsOnOtherDevices = context.jamClient.FTUEGetChatInputs(false); var chatInputs = []; @@ -215,16 +215,28 @@ //}); var deDupper = {}; - context._.each(chatsOnCurrentDevice, function(chatChannelName, chatChannelId) { + + context._.each(musicPorts.inputs, function(input) { + + var chatInput = {id: input.id, name: input.name, assignment:input.assignment}; + if(!deDupper[input.id]) { + if(input.assignment <= 0) { + chatInputs.push(chatInput); + deDupper[input.id] = chatInput; + } + } + }); + + /**context._.each(chatsOnCurrentDevice, function(chatChannelName, chatChannelId) { var chatInput = {id: chatChannelId, name: chatChannelName, assignment: ASSIGNMENT.UNASSIGNED}; if(!deDupper[chatInput.id]) { var assignment = context.jamClient.TrackGetAssignment(chatChannelId, true); - if(assignment != ASSIGNMENT.CHAT) { + if(assignment <= 0) { chatInputs.push(chatInput); deDupper[chatInput.id] = chatInput; } } - }) + })*/ context._.each(chatsOnOtherDevices, function(chatChannelName, chatChannelId) { var chatInput = {id: chatChannelId, name: chatChannelName, assignment: null}; From cad7044488e8377927f6536051017e25843668bb Mon Sep 17 00:00:00 2001 From: Seth Call Date: Tue, 10 Jun 2014 10:12:24 -0500 Subject: [PATCH 21/32] * deal with the None placeholder not showing in successive FTUE attempts --- web/app/assets/javascripts/configureTracksHelper.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web/app/assets/javascripts/configureTracksHelper.js b/web/app/assets/javascripts/configureTracksHelper.js index 4438770ef..5f511873a 100644 --- a/web/app/assets/javascripts/configureTracksHelper.js +++ b/web/app/assets/javascripts/configureTracksHelper.js @@ -28,8 +28,8 @@ $unassignedOutputsHolder.empty(); // reset all counts - $unassignedInputsHolder.find('.track-target').attr('track-count', '0'); - $unassignedOutputsHolder.find('.output-target').attr('output-count', '0'); + $tracksHolder.find('.track-target').attr('track-count', '0'); + $outputChannelHolder.find('.output-target').attr('output-count', '0'); $tracksHolder.find('.ftue-input').remove(); $outputChannelHolder.find('.ftue-input').remove(); From dbc17a89c6363a1dd34fe50286ddfd320bce5a03 Mon Sep 17 00:00:00 2001 From: Seth Call Date: Tue, 10 Jun 2014 13:26:14 -0500 Subject: [PATCH 22/32] * VRFS-1775 - yellow links on success page --- .../javascripts/wizard/gear/step_select_gear.js | 16 ++++++++++++---- web/app/assets/javascripts/wizard/gear_test.js | 14 +++++++++++--- .../client/wizard/gearWizard.css.scss | 8 ++++++++ .../clients/wizard/gear/_gear_wizard.html.haml | 16 ++++++++-------- 4 files changed, 39 insertions(+), 15 deletions(-) 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 e0e138cab..b6d268747 100644 --- a/web/app/assets/javascripts/wizard/gear/step_select_gear.js +++ b/web/app/assets/javascripts/wizard/gear/step_select_gear.js @@ -764,10 +764,18 @@ } else { context.jamClient.FTUESetMusicProfileName(gearUtils.createProfileName(selectedDeviceInfo)); - context.jamClient.FTUESave(true); - savedProfile = true; - context.JK.GA.trackAudioTestCompletion(context.JK.detectOS()); - return true; + var result = context.jamClient.FTUESave(true); + if(result != "") { + // failed + logger.warn("unable to FTUESave(true). reason:" + result); + context.JK.Banner.alertSupportedNeeded("Unable to persist the audio profile. " + result); + return false; + } + else { + savedProfile = true; + context.JK.GA.trackAudioTestCompletion(context.JK.detectOS()); + return true; + } } } } diff --git a/web/app/assets/javascripts/wizard/gear_test.js b/web/app/assets/javascripts/wizard/gear_test.js index a92a14434..b793eccf9 100644 --- a/web/app/assets/javascripts/wizard/gear_test.js +++ b/web/app/assets/javascripts/wizard/gear_test.js @@ -99,9 +99,17 @@ function automaticScore() { logger.debug("automaticScore: calling FTUESave(false)"); - jamClient.FTUESave(false); - var latency = jamClient.FTUEGetExpectedLatency(); - context.JK.GearTest.testDeferred.resolve(latency); + var result = jamClient.FTUESave(false); + if(result != "") { + logger.debug("unable to FTUESave(false). reason=" + result); + return false; + } + else { + var latency = jamClient.FTUEGetExpectedLatency(); + context.JK.GearTest.testDeferred.resolve(latency); + } + + return true; } function loopbackScore() { diff --git a/web/app/assets/stylesheets/client/wizard/gearWizard.css.scss b/web/app/assets/stylesheets/client/wizard/gearWizard.css.scss index c3d7c4012..89752175c 100644 --- a/web/app/assets/stylesheets/client/wizard/gearWizard.css.scss +++ b/web/app/assets/stylesheets/client/wizard/gearWizard.css.scss @@ -424,6 +424,14 @@ ul { margin-bottom: 20px; + + a { + color:$ColorLink; + } + + a:hover { + color:$ColorLinkHover; + } } } } diff --git a/web/app/views/clients/wizard/gear/_gear_wizard.html.haml b/web/app/views/clients/wizard/gear/_gear_wizard.html.haml index 7aa5bff56..467ad5801 100644 --- a/web/app/views/clients/wizard/gear/_gear_wizard.html.haml +++ b/web/app/views/clients/wizard/gear/_gear_wizard.html.haml @@ -163,23 +163,23 @@ %h2 Tutorial Videos %ul %li - %a Creating a Session + %a{href: '#'} Creating a Session %li - %a Finding a Session + %a{href: '#'} Finding a Session %li - %a Playing in a Session + %a{href: '#'} Playing in a Session %li - %a Connecting with Other Musicians + %a{href: '#'} Connecting with Other Musicians %li - %a Making and Sharing Recordings + %a{href: '#'} Making and Sharing Recordings %li - %a Broadcasting Your Sessions + %a{href: '#'} Broadcasting Your Sessions %h2 Other Valuable Resource Links %ul %li - %a JamKazam Support Center + %a{href: '#'} JamKazam Support Center %li - %a JamKazam Community Forum + %a{href: '#'} JamKazam Community Forum .wizard-buttons %script{type: 'text/template', id: 'template-ftuesteps'} From e5b1aa0e6a8ac23a4d3aa2dcdb69aedf0c316086 Mon Sep 17 00:00:00 2001 From: Seth Call Date: Tue, 10 Jun 2014 13:30:21 -0500 Subject: [PATCH 23/32] * VRFS-1771 - add no-selection-range in a larger area --- .../views/clients/_configure_tracks_dialog.html.haml | 12 ++++++------ .../views/clients/wizard/gear/_gear_wizard.html.haml | 8 ++++---- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/web/app/views/clients/_configure_tracks_dialog.html.haml b/web/app/views/clients/_configure_tracks_dialog.html.haml index 8c1dcdb84..763e5143d 100644 --- a/web/app/views/clients/_configure_tracks_dialog.html.haml +++ b/web/app/views/clients/_configure_tracks_dialog.html.haml @@ -14,7 +14,7 @@ .clearall - .tab{'tab-id' => 'music-audio'} + .tab.no-selection-range{'tab-id' => 'music-audio'} .column .certified-audio-profile-section @@ -22,11 +22,11 @@ %select.certified-audio-profile .clearall - .unused-audio-inputs-section.no-selection-range + .unused-audio-inputs-section .sub-header Unused Input Ports .unassigned-input-channels - .unused-audio-outputs-section.no-selection-range + .unused-audio-outputs-section .sub-header Unused Output Ports .unassigned-output-channels @@ -34,14 +34,14 @@ .input-tracks-section .sub-column .sub-header Track Input Port(s) - .input-tracks.tracks.no-selection-range + .input-tracks.tracks .sub-column .sub-header Instrument - .instruments.no-selection-range + .instruments .output-channels-section .sub-header Audio Output Port - .output-channels.no-selection-range + .output-channels .clearall diff --git a/web/app/views/clients/wizard/gear/_gear_wizard.html.haml b/web/app/views/clients/wizard/gear/_gear_wizard.html.haml index 467ad5801..db90e19ef 100644 --- a/web/app/views/clients/wizard/gear/_gear_wizard.html.haml +++ b/web/app/views/clients/wizard/gear/_gear_wizard.html.haml @@ -64,7 +64,7 @@ .ftuesteps .clearall .help-text In this step, you will select, configure, and test your audio gear. Please watch the video for best instructions. - .wizard-step-content + .wizard-step-content.no-selection-range .wizard-step-column %h2 Instructions .ftue-box.instructions @@ -76,13 +76,13 @@ %a.button-orange.watch-video{href:'#'} WATCH VIDEO .wizard-step-column %h2 Unassigned Ports - .unassigned-input-channels.no-selection-range + .unassigned-input-channels .wizard-step-column %h2 Track Input Port(s) - .tracks.no-selection-range + .tracks .wizard-step-column %h2 Instrument - .instruments.no-selection-range + .instruments .output-channels .unassigned-output-channels From 5f889d5af69c2e7afbd2962d511b54e7bf7943b7 Mon Sep 17 00:00:00 2001 From: Seth Call Date: Tue, 10 Jun 2014 14:20:57 -0500 Subject: [PATCH 24/32] * fix tests --- web/app/assets/javascripts/wizard/gear/step_select_gear.js | 2 +- web/app/assets/javascripts/wizard/gear_test.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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 b6d268747..4dee7efaf 100644 --- a/web/app/assets/javascripts/wizard/gear/step_select_gear.js +++ b/web/app/assets/javascripts/wizard/gear/step_select_gear.js @@ -765,7 +765,7 @@ else { context.jamClient.FTUESetMusicProfileName(gearUtils.createProfileName(selectedDeviceInfo)); var result = context.jamClient.FTUESave(true); - if(result != "") { + if(result && result != "") { // failed logger.warn("unable to FTUESave(true). reason:" + result); context.JK.Banner.alertSupportedNeeded("Unable to persist the audio profile. " + result); diff --git a/web/app/assets/javascripts/wizard/gear_test.js b/web/app/assets/javascripts/wizard/gear_test.js index b793eccf9..e84e85454 100644 --- a/web/app/assets/javascripts/wizard/gear_test.js +++ b/web/app/assets/javascripts/wizard/gear_test.js @@ -100,7 +100,7 @@ function automaticScore() { logger.debug("automaticScore: calling FTUESave(false)"); var result = jamClient.FTUESave(false); - if(result != "") { + if(result && result != "") { logger.debug("unable to FTUESave(false). reason=" + result); return false; } From c70a76cbbe87aafde75762506d0e9475d5853bf4 Mon Sep 17 00:00:00 2001 From: Seth Call Date: Tue, 10 Jun 2014 15:16:10 -0500 Subject: [PATCH 25/32] * VRFS-1768 - updated position and color of controlbuttons --- .../wizard/gear/step_select_gear.js | 1 + .../wizard/gear/step_understand_gear.js | 4 ++-- web/app/assets/javascripts/wizard/wizard.js | 21 +++++++--------- .../assets/stylesheets/client/ftue.css.scss | 3 +++ .../stylesheets/client/screen_common.css.scss | 13 ++++++---- .../client/wizard/gearWizard.css.scss | 2 ++ .../stylesheets/client/wizard/wizard.css.scss | 24 ++++++++++++++++++- .../views/clients/wizard/_buttons.html.haml | 13 ++++++---- 8 files changed, 57 insertions(+), 24 deletions(-) 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 4dee7efaf..49314a30f 100644 --- a/web/app/assets/javascripts/wizard/gear/step_select_gear.js +++ b/web/app/assets/javascripts/wizard/gear/step_select_gear.js @@ -799,6 +799,7 @@ function beforeShow() { $(window).on('focus', onFocus); + initializeNextButtonState(); } function beforeHide() { diff --git a/web/app/assets/javascripts/wizard/gear/step_understand_gear.js b/web/app/assets/javascripts/wizard/gear/step_understand_gear.js index c4c235cf1..648b741dd 100644 --- a/web/app/assets/javascripts/wizard/gear/step_understand_gear.js +++ b/web/app/assets/javascripts/wizard/gear/step_understand_gear.js @@ -10,9 +10,9 @@ function beforeShow() { var $watchVideo = $step.find('.watch-video'); - var videoUrl = 'https://www.youtube.com/watch?v=VexH4834o9I'; + var videoUrl = 'https://www.youtube.com/watch?v=NiELWY769Tw'; if (operatingSystem == "Win32") { - $watchVideo.attr('href', 'https://www.youtube.com/watch?v=VexH4834o9I'); + $watchVideo.attr('href', 'https://www.youtube.com/watch?v=Z1GxCljtdCY'); } $watchVideo.attr('href', videoUrl); } diff --git a/web/app/assets/javascripts/wizard/wizard.js b/web/app/assets/javascripts/wizard/wizard.js index 05a30fd8c..8005930c2 100644 --- a/web/app/assets/javascripts/wizard/wizard.js +++ b/web/app/assets/javascripts/wizard/wizard.js @@ -53,7 +53,7 @@ } function back() { - if ($(this).is('.button-grey')) return false; + if ($(this).is('.disabled')) return false; previousStep = step; step = step - 1; moveToStep(); @@ -61,7 +61,7 @@ } function next() { - if ($(this).is('.button-grey')) return false; + if ($(this).is('.disabled')) return false; var stepInfo = STEPS[step]; if(stepInfo.handleNext) { @@ -160,27 +160,24 @@ if(!$btnNext) return; - $btnNext.removeClass('button-orange button-grey'); - - if (enabled) { - $btnNext.addClass('button-orange'); + if(enabled) { + $btnNext.removeClass('disabled'); } else { - $btnNext.addClass('button-grey'); + $btnNext.addClass('disabled'); } + } function setBackState(enabled) { if(!$btnBack) return; - $btnBack.removeClass('button-orange button-grey'); - - if (enabled) { - $btnBack.addClass('button-orange'); + if(enabled) { + $btnBack.removeClass('disabled'); } else { - $btnBack.addClass('button-grey'); + $btnBack.addClass('disabled'); } } diff --git a/web/app/assets/stylesheets/client/ftue.css.scss b/web/app/assets/stylesheets/client/ftue.css.scss index 88e1230fd..933d01a6b 100644 --- a/web/app/assets/stylesheets/client/ftue.css.scss +++ b/web/app/assets/stylesheets/client/ftue.css.scss @@ -546,6 +546,8 @@ div[layout-id="ftue3"] { float:left; } +/** + .ftue-inner a { color:#ccc; } @@ -553,6 +555,7 @@ div[layout-id="ftue3"] { .ftue-inner a:hover { color:#fff; } +*/ .ftue-instrumentlist { width:340px; diff --git a/web/app/assets/stylesheets/client/screen_common.css.scss b/web/app/assets/stylesheets/client/screen_common.css.scss index deffd0dfe..f3a66d06b 100644 --- a/web/app/assets/stylesheets/client/screen_common.css.scss +++ b/web/app/assets/stylesheets/client/screen_common.css.scss @@ -193,9 +193,9 @@ small, .small {font-size:11px;} .button-orange { margin:0px 8px 0px 8px; - background-color: #ED3618; + background-color: $ColorScreenPrimary; border: solid 1px #F27861; - outline: solid 2px #ED3618; + outline: solid 2px $ColorScreenPrimary; padding:3px 10px; font-size:12px; font-weight:300; @@ -205,11 +205,16 @@ small, .small {font-size:11px;} line-height:12px; &.disabled { - @extend .button-grey; + background-color: #b03420; + border: solid 1px darken(#F27861, 20%); + outline: solid 2px #b03420; } &.disabled:hover { - @extend .button-grey:hover; + //background-color:darken(#f16750, 20%); + //color:#FFF; + background-color: #b03420; + color:#FC9; } } diff --git a/web/app/assets/stylesheets/client/wizard/gearWizard.css.scss b/web/app/assets/stylesheets/client/wizard/gearWizard.css.scss index 89752175c..dd947294f 100644 --- a/web/app/assets/stylesheets/client/wizard/gearWizard.css.scss +++ b/web/app/assets/stylesheets/client/wizard/gearWizard.css.scss @@ -755,6 +755,7 @@ color: #666; } + /** a { color: #ccc; } @@ -763,6 +764,7 @@ color: #fff; } +*/ .ftue-instrumentlist { width: 340px; height: 178px; diff --git a/web/app/assets/stylesheets/client/wizard/wizard.css.scss b/web/app/assets/stylesheets/client/wizard/wizard.css.scss index 02ef8e802..725301f78 100644 --- a/web/app/assets/stylesheets/client/wizard/wizard.css.scss +++ b/web/app/assets/stylesheets/client/wizard/wizard.css.scss @@ -13,7 +13,29 @@ } .wizard-buttons-holder { - float:right; + position:absolute; + width:100%; + } + + .left-buttons { + position:absolute; + left:-6px; + bottom:0; + } + + .middle-buttons { + position:absolute; + width:100%; + text-align: center; + a {margin:auto;} + bottom:0; + left:-24px; + } + + .right-buttons { + position:absolute; + right:56px; + bottom:0; } .wizard-step { diff --git a/web/app/views/clients/wizard/_buttons.html.haml b/web/app/views/clients/wizard/_buttons.html.haml index dbc90ae54..ac1e8bebd 100644 --- a/web/app/views/clients/wizard/_buttons.html.haml +++ b/web/app/views/clients/wizard/_buttons.html.haml @@ -1,10 +1,13 @@ %script{type: 'text/template', id: 'template-wizard-buttons'} .wizard-buttons-holder - %a.button-grey.btn-cancel{href:'#', 'layout-action' => 'close'} CANCEL - %a.button-grey.btn-help{href: '#'} HELP - %a.button-orange.btn-back{href:'#'} BACK - %a.button-orange.btn-next{href:'#'} NEXT - %a.button-orange.btn-close{href:'#', 'layout-action' => 'close'} CLOSE + .left-buttons + %a.button-grey.btn-help{href: '#'} HELP + .middle-buttons + %a.button-grey.btn-cancel{href:'#', 'layout-action' => 'close'} CANCEL + .right-buttons + %a.button-grey.btn-back{href:'#'} BACK + %a.button-orange.btn-next{href:'#'} NEXT + %a.button-orange.btn-close{href:'#', 'layout-action' => 'close'} CLOSE From 5d926ee65191652e921f42338695d35bc1731d14 Mon Sep 17 00:00:00 2001 From: Seth Call Date: Tue, 10 Jun 2014 15:28:29 -0500 Subject: [PATCH 26/32] * fix 'Same as Input' problem VRFS-1768; also fixed yellow links on play button --- web/app/assets/javascripts/wizard/gear/step_select_gear.js | 3 +++ web/app/assets/stylesheets/client/wizard/gearWizard.css.scss | 5 +++++ 2 files changed, 8 insertions(+) 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 49314a30f..ba646d67f 100644 --- a/web/app/assets/javascripts/wizard/gear/step_select_gear.js +++ b/web/app/assets/javascripts/wizard/gear/step_select_gear.js @@ -66,6 +66,9 @@ } function setOutputAudioDevice(value) { + if(value != "" && value == selectedAudioInput()) { + value = ''; // to force Same as Input + } context.JK.dropdown($audioOutput.val(value).easyDropDown('select', value.toString(), true)) } diff --git a/web/app/assets/stylesheets/client/wizard/gearWizard.css.scss b/web/app/assets/stylesheets/client/wizard/gearWizard.css.scss index dd947294f..ecabff486 100644 --- a/web/app/assets/stylesheets/client/wizard/gearWizard.css.scss +++ b/web/app/assets/stylesheets/client/wizard/gearWizard.css.scss @@ -236,6 +236,11 @@ display: inline-block; text-decoration: none; width: 90px; + color: #ccc; + + &:hover { + color: #fff; + } &.paused { .playing { From 02b85b3676cdb27034c75bfa277675fd02e28d86 Mon Sep 17 00:00:00 2001 From: Seth Call Date: Tue, 10 Jun 2014 15:58:46 -0500 Subject: [PATCH 27/32] * reorg buttons and colors --- .../assets/stylesheets/client/screen_common.css.scss | 11 ++++++----- .../assets/stylesheets/client/wizard/wizard.css.scss | 9 --------- web/app/views/clients/wizard/_buttons.html.haml | 1 - 3 files changed, 6 insertions(+), 15 deletions(-) diff --git a/web/app/assets/stylesheets/client/screen_common.css.scss b/web/app/assets/stylesheets/client/screen_common.css.scss index f3a66d06b..f5b5dec93 100644 --- a/web/app/assets/stylesheets/client/screen_common.css.scss +++ b/web/app/assets/stylesheets/client/screen_common.css.scss @@ -205,16 +205,17 @@ small, .small {font-size:11px;} line-height:12px; &.disabled { - background-color: #b03420; - border: solid 1px darken(#F27861, 20%); - outline: solid 2px #b03420; + background-color: transparent; + border: solid 1px #868686; + outline: solid 2px transparent; + color:#ccc; } &.disabled:hover { //background-color:darken(#f16750, 20%); //color:#FFF; - background-color: #b03420; - color:#FC9; + background-color: #515151; + color:#ccc; } } diff --git a/web/app/assets/stylesheets/client/wizard/wizard.css.scss b/web/app/assets/stylesheets/client/wizard/wizard.css.scss index 725301f78..4e974eb15 100644 --- a/web/app/assets/stylesheets/client/wizard/wizard.css.scss +++ b/web/app/assets/stylesheets/client/wizard/wizard.css.scss @@ -23,15 +23,6 @@ bottom:0; } - .middle-buttons { - position:absolute; - width:100%; - text-align: center; - a {margin:auto;} - bottom:0; - left:-24px; - } - .right-buttons { position:absolute; right:56px; diff --git a/web/app/views/clients/wizard/_buttons.html.haml b/web/app/views/clients/wizard/_buttons.html.haml index ac1e8bebd..438d06536 100644 --- a/web/app/views/clients/wizard/_buttons.html.haml +++ b/web/app/views/clients/wizard/_buttons.html.haml @@ -2,7 +2,6 @@ .wizard-buttons-holder .left-buttons %a.button-grey.btn-help{href: '#'} HELP - .middle-buttons %a.button-grey.btn-cancel{href:'#', 'layout-action' => 'close'} CANCEL .right-buttons %a.button-grey.btn-back{href:'#'} BACK From cb0c8f2007627004f712d69acfee33f5a4a0c8c1 Mon Sep 17 00:00:00 2001 From: Seth Call Date: Tue, 10 Jun 2014 16:09:04 -0500 Subject: [PATCH 28/32] * switch back to left-to-right text in track assignment; and a little bit of padding VRFS-1777 --- web/app/assets/stylesheets/client/dragDropTracks.css.scss | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/web/app/assets/stylesheets/client/dragDropTracks.css.scss b/web/app/assets/stylesheets/client/dragDropTracks.css.scss index 24cc6e5d5..4c43e7662 100644 --- a/web/app/assets/stylesheets/client/dragDropTracks.css.scss +++ b/web/app/assets/stylesheets/client/dragDropTracks.css.scss @@ -87,8 +87,9 @@ margin-bottom: 15px; white-space: nowrap; overflow: hidden; + text-overflow: ellipsis; text-align: left; - direction: rtl; + //direction: rtl; &.ui-draggable-dragging { margin-bottom: 0; } @@ -149,9 +150,12 @@ .ftue-input { width: 49%; display: inline-block; + @include border_box_sizing; + &:nth-of-type(1) { float: left; + padding-right:2px; /**&:after { float:right; content: ','; @@ -159,6 +163,7 @@ }*/ } &:nth-of-type(2) { + padding-left:2px; float: right; } } From 161f75a126e920f4f92bc09080351b80956337ed Mon Sep 17 00:00:00 2001 From: Scott Comer Date: Tue, 10 Jun 2014 20:50:59 -0500 Subject: [PATCH 29/32] use current_scores instead of scores --- ruby/lib/jam_ruby/models/search.rb | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/ruby/lib/jam_ruby/models/search.rb b/ruby/lib/jam_ruby/models/search.rb index 88000f782..616829360 100644 --- a/ruby/lib/jam_ruby/models/search.rb +++ b/ruby/lib/jam_ruby/models/search.rb @@ -213,14 +213,14 @@ module JamRuby # the default of ANY setup above applies end - rel = rel.joins("#{score_join} join scores on scores.alocidispid = users.last_jam_locidispid") - .where(['(scores.blocidispid = ? or scores.blocidispid is null)', locidispid]) + rel = rel.joins("#{score_join} join current_scores on current_scores.alocidispid = users.last_jam_locidispid") + .where(['(current_scores.blocidispid = ? or current_scores.blocidispid is null)', locidispid]) - rel = rel.where(['scores.score > ?', score_min]) unless score_min.nil? - rel = rel.where(['scores.score <= ?', score_max]) unless score_max.nil? + rel = rel.where(['current_scores.score > ?', score_min]) unless score_min.nil? + rel = rel.where(['current_scores.score <= ?', score_max]) unless score_max.nil? - rel = rel.select('scores.score') - rel = rel.group('scores.score') + rel = rel.select('current_scores.score') + rel = rel.group('current_scores.score') end ordering = self.order_param(params) @@ -242,7 +242,7 @@ module JamRuby end unless locidispid.nil? - rel = rel.order('scores.score ASC NULLS LAST') + rel = rel.order('current_scores.score ASC NULLS LAST') end rel = rel.order('users.created_at DESC') @@ -391,10 +391,10 @@ module JamRuby rel = User.musicians_geocoded .where(['created_at >= ? AND users.id != ?', since_date, usr.id]) - .joins('inner join scores on users.last_jam_locidispid = scores.alocidispid') - .where(['scores.blocidispid = ?', locidispid]) - .where(['scores.score <= ?', score_limit]) - .order('scores.score') # best scores first + .joins('inner join current_scores on users.last_jam_locidispid = current_scores.alocidispid') + .where(['current_scores.blocidispid = ?', locidispid]) + .where(['current_scores.score <= ?', score_limit]) + .order('current_scores.score') # best scores first .order('users.created_at DESC') # then most recent .limit(limit) From f644a124872f3e6e1b00600fb14613dece7f2191 Mon Sep 17 00:00:00 2001 From: Seth Call Date: Tue, 10 Jun 2014 22:07:35 -0500 Subject: [PATCH 30/32] * VRFS-1777 hovering working for all but unassigned ports --- .../javascripts/configureTracksHelper.js | 105 +++++++++++++++++- .../client/dragDropTracks.css.scss | 18 +-- .../_configure_tracks_dialog.html.haml | 4 +- .../wizard/gear/_gear_wizard.html.haml | 8 +- 4 files changed, 120 insertions(+), 15 deletions(-) diff --git a/web/app/assets/javascripts/configureTracksHelper.js b/web/app/assets/javascripts/configureTracksHelper.js index 5f511873a..a1f8d2b85 100644 --- a/web/app/assets/javascripts/configureTracksHelper.js +++ b/web/app/assets/javascripts/configureTracksHelper.js @@ -20,6 +20,91 @@ var $tracksHolder = null; var $outputChannelHolder = null; var $instrumentsHolder = null; + var isDragging = false; + + function hoverIn($channel) { + if(isDragging) return; + + $channel.css('color', 'white') + + var $container = $channel.closest('.target'); + var inTarget = $container.length > 0; + if(!inTarget) { + $container = $channel.closest('.channels-holder') + } + + $channel.data('container', $container) + $channel.addClass('hovering'); + var $inputs = $container.find('.ftue-input'); + + var index = $inputs.index($channel); + $channel.css('background-color', '#333'); + // $channel.css('padding', '0 5px'); + if(inTarget) { + $channel.css('border', '#333'); + $channel.css('border-radius', '2px'); + $channel.css('min-width', '49%'); + $channel.css('width', 'auto'); + $channel.css('position', 'absolute'); + $container.css('overflow', 'visible'); + } + else { + // TODO: make the unassigned work + // $channel.css('min-width', $channel.css('width')); + // $channel.css('position', 'absolute'); + // $container.addClass('compensate'); + } + + $channel.css('z-index', 10000) + if(inTarget && $inputs.length == 2) { + + if(index == 0) { + $channel.css('right', '50%') + } + else { + $channel.css('left', '51%') + } + } + + } + + function hoverOut($channel) { + $channel + .removeClass('hovering') + .css('color', '') + .css('font-weight', '') + .css('position', '') + .css('width', '') + .css('background-color', '') + .css('padding', '') + .css('border', '') + .css('border-radius', '') + .css('right', '') + .css('left', '') + .css('min-width', '') + .css('z-index', '') + .css('margin-left', '') + .css('max-width', 'auto'); + + //var $container = $channel.closest('.target'); + var $container = $channel.data('container'); + $container.css('overflow', '') + $container.removeClass('compensate'); + + } + + function fixClone($clone) { + $clone + .css('color', '') + .css('font-weight', '') + .css('width', 'auto') + .css('background-color', '') + .css('padding', '') + .css('border', '') + .css('border-radius', '') + .css('right', '') + .css('min-width', '') + } function loadChannels(forceInputsToUnassign) { var musicPorts = jamClient.FTUEGetChannels(); @@ -42,6 +127,11 @@ context._.each(inputChannels, function (inputChannel) { var $channel = $(context._.template($templateAssignablePort.html(), inputChannel, { variable: 'data' })); + $channel.hover( + function() { hoverIn ($(this)) }, + function() { hoverOut($(this)) } + ); + if(forceInputsToUnassign || inputChannel.assignment == ASSIGNMENT.UNASSIGNED) { unassignInputChannel($channel); } @@ -67,8 +157,10 @@ $channel.draggable({ helper: 'clone', - start: function() { + start: function(e, ui) { + isDragging = true; var $channel = $(this); + fixClone(ui.helper); var $track = $channel.closest('.track-target'); var isUnassigned = $track.length == 0; if(isUnassigned) { @@ -80,6 +172,7 @@ } }, stop: function() { + isDragging = false; $tracksHolder.find('.track-target').removeClass('possible-target'); $unassignedInputsHolder.removeClass('possible-target') } @@ -90,6 +183,11 @@ context._.each(outputChannels, function (outputChannel, index) { var $channel = $(context._.template($templateAssignablePort.html(), outputChannel, { variable: 'data' })); + $channel.hover( + function() { hoverIn ($(this)) }, + function() { hoverOut($(this)) } + ); + if(outputChannel.assignment == ASSIGNMENT.UNASSIGNED) { unassignOutputChannel($channel); } @@ -105,8 +203,10 @@ $channel.draggable({ helper: 'clone', - start: function() { + start: function(e,ui) { + isDragging = true; var $channel = $(this); + fixClone(ui.helper); var $output = $channel.closest('.output-target'); var isUnassigned = $output.length == 0; if(isUnassigned) { @@ -118,6 +218,7 @@ } }, stop: function() { + isDragging = false; $outputChannelHolder.find('.output-target').removeClass('possible-target'); $unassignedOutputsHolder.removeClass('possible-target') } diff --git a/web/app/assets/stylesheets/client/dragDropTracks.css.scss b/web/app/assets/stylesheets/client/dragDropTracks.css.scss index 4c43e7662..e67703ea4 100644 --- a/web/app/assets/stylesheets/client/dragDropTracks.css.scss +++ b/web/app/assets/stylesheets/client/dragDropTracks.css.scss @@ -33,11 +33,16 @@ min-height: 240px; overflow-y: auto; max-height:93%; + //padding-right:18px; // to keep draggables off of scrollbar. maybe necessary - .ftue-input { - + &.compensate { + .ftue-input:not('.hovering') { + } } + .ftue-input { + } + &.drag-in-progress { overflow-y: visible; overflow-x: visible; @@ -89,14 +94,12 @@ overflow: hidden; text-overflow: ellipsis; text-align: left; + line-height:20px; //direction: rtl; &.ui-draggable-dragging { margin-bottom: 0; - } - - &:hover { - color: white; - font-weight: bold; + z-index:10000; + background-color:#333; } /** @@ -113,6 +116,7 @@ .track-target, .output-target { + position:relative; cursor: move; padding: 4px; border: solid 1px #999; diff --git a/web/app/views/clients/_configure_tracks_dialog.html.haml b/web/app/views/clients/_configure_tracks_dialog.html.haml index 763e5143d..252ad6066 100644 --- a/web/app/views/clients/_configure_tracks_dialog.html.haml +++ b/web/app/views/clients/_configure_tracks_dialog.html.haml @@ -24,11 +24,11 @@ .unused-audio-inputs-section .sub-header Unused Input Ports - .unassigned-input-channels + .unassigned-input-channels.channels-holder .unused-audio-outputs-section .sub-header Unused Output Ports - .unassigned-output-channels + .unassigned-output-channels.channels-holder .column .input-tracks-section diff --git a/web/app/views/clients/wizard/gear/_gear_wizard.html.haml b/web/app/views/clients/wizard/gear/_gear_wizard.html.haml index db90e19ef..28d4accef 100644 --- a/web/app/views/clients/wizard/gear/_gear_wizard.html.haml +++ b/web/app/views/clients/wizard/gear/_gear_wizard.html.haml @@ -76,7 +76,7 @@ %a.button-orange.watch-video{href:'#'} WATCH VIDEO .wizard-step-column %h2 Unassigned Ports - .unassigned-input-channels + .unassigned-input-channels.channels-holder .wizard-step-column %h2 Track Input Port(s) .tracks @@ -84,7 +84,7 @@ %h2 Instrument .instruments .output-channels - .unassigned-output-channels + .unassigned-output-channels.channels-holder .wizard-step{ 'layout-wizard-step' => "3", 'dialog-title' => "Configure Voice Chat", 'dialog-purpose' => "ConfigureVoiceChat" } .ftuesteps @@ -220,14 +220,14 @@ %script{type: 'text/template', id: 'template-track-target'} .track{'data-num' => '{{data.num}}'} .num {{data.num + 1}}: - .track-target{'data-num' => '{{data.num}}', 'track-count' => 0} + .track-target.target{'data-num' => '{{data.num}}', 'track-count' => 0} %span.placeholder None %script{type: 'text/template', id: 'template-output-target'} .output{'data-num' => '{{data.num}}'} .num {{data.num + 1}}: - .output-target{'data-num' => '{{data.num}}', 'output-count' => 0} + .output-target.target{'data-num' => '{{data.num}}', 'output-count' => 0} %span.placeholder None From ff96f8e199d5accf7f5db61b8ff208a88d5963fe Mon Sep 17 00:00:00 2001 From: Seth Call Date: Tue, 10 Jun 2014 22:11:36 -0500 Subject: [PATCH 31/32] * VRFS-1764 - fix case where a drag start changes scroll --- web/app/assets/stylesheets/client/dragDropTracks.css.scss | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/web/app/assets/stylesheets/client/dragDropTracks.css.scss b/web/app/assets/stylesheets/client/dragDropTracks.css.scss index e67703ea4..a85a659a0 100644 --- a/web/app/assets/stylesheets/client/dragDropTracks.css.scss +++ b/web/app/assets/stylesheets/client/dragDropTracks.css.scss @@ -16,8 +16,6 @@ //padding-right:18px; // to keep draggables off of scrollbar. maybe necessary &.drag-in-progress { - overflow-y: visible; - overflow-x: visible; } &.possible-target { @@ -30,9 +28,9 @@ } .unassigned-input-channels { - min-height: 240px; + min-height: 22px; overflow-y: auto; - max-height:93%; + max-height:23%; //padding-right:18px; // to keep draggables off of scrollbar. maybe necessary @@ -44,8 +42,6 @@ } &.drag-in-progress { - overflow-y: visible; - overflow-x: visible; } &.possible-target { From d38e5d155af439c546a4e7e30f0c8bf5a87e1b5b Mon Sep 17 00:00:00 2001 From: Seth Call Date: Tue, 10 Jun 2014 22:27:37 -0500 Subject: [PATCH 32/32] * fix for bad reference to context.JK.EVENTS --- web/app/assets/javascripts/wizard/gear/step_select_gear.js | 2 +- web/app/assets/javascripts/wizard/loopback/loopback_wizard.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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 ba646d67f..4efede27e 100644 --- a/web/app/assets/javascripts/wizard/gear/step_select_gear.js +++ b/web/app/assets/javascripts/wizard/gear/step_select_gear.js @@ -5,7 +5,7 @@ context.JK = context.JK || {}; context.JK.StepSelectGear = function (app, dialog) { - var EVENTS = context.JK.DIALOG_CLOSED; + var EVENTS = context.JK.EVENTS; var ASSIGNMENT = context.JK.ASSIGNMENT; var VOICE_CHAT = context.JK.VOICE_CHAT; var AUDIO_DEVICE_BEHAVIOR = context.JK.AUDIO_DEVICE_BEHAVIOR; diff --git a/web/app/assets/javascripts/wizard/loopback/loopback_wizard.js b/web/app/assets/javascripts/wizard/loopback/loopback_wizard.js index bf16da14c..f7d3de1b9 100644 --- a/web/app/assets/javascripts/wizard/loopback/loopback_wizard.js +++ b/web/app/assets/javascripts/wizard/loopback/loopback_wizard.js @@ -5,7 +5,7 @@ context.JK = context.JK || {}; context.JK.LoopbackWizard = function (app) { - var EVENTS = context.JK.DIALOG_CLOSED; + var EVENTS = context.JK.EVENTS; var logger = context.JK.logger; var $dialog = null;