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 @@