diff --git a/web/app/assets/javascripts/accounts_audio_profile.js b/web/app/assets/javascripts/accounts_audio_profile.js index b9cdfc95b..5c3b93897 100644 --- a/web/app/assets/javascripts/accounts_audio_profile.js +++ b/web/app/assets/javascripts/accounts_audio_profile.js @@ -1,112 +1,127 @@ -(function(context,$) { +(function (context, $) { - "use strict"; + "use strict"; - context.JK = context.JK || {}; - context.JK.AccountAudioProfile = function(app) { - var self = this; - var logger = context.JK.logger; - var rest = context.JK.Rest(); - var userId; - var user = {}; - var tmpUploadPath = null; - var userDetail = null; - var avatar; - var selection = null; - var targetCropSize = 88; - var updatingAvatar = false; + context.JK = context.JK || {}; + context.JK.AccountAudioProfile = function (app) { + var self = this; + var logger = context.JK.logger; + var rest = context.JK.Rest(); + var userId; + var user = {}; + var tmpUploadPath = null; + var userDetail = null; + var avatar; + var selection = null; + var targetCropSize = 88; + var updatingAvatar = false; - function beforeShow(data) { - userId = data.id; + function beforeShow(data) { + userId = data.id; - registerFtueSuccess(); - } + registerFtueSuccess(); + } - function afterShow(data) { - resetForm(); - renderAudioProfileScreen() - } + function afterShow(data) { + resetForm(); + renderAudioProfileScreen() + } - function beforeHide() { - unregisterFtueSuccess(); + function beforeHide() { + unregisterFtueSuccess(); - } + } - function renderAudioProfileScreen() { - populateAccountAudio() - } + function renderAudioProfileScreen() { + populateAccountAudio() + } - function populateAccountAudio() { + function populateAccountAudio() { - $('#account-audio-content-scroller').empty(); + // load Audio Driver dropdown + var devices = context.jamClient.TrackGetDevices(); - // load Audio Driver dropdown - var devices = context.jamClient.TrackGetDevices(); + var options = { + devices: devices + } - var options = { - devices: devices - } + var template = context._.template($('#template-account-audio').html(), options, {variable: 'data'}); - var template = context._.template($('#template-account-audio').html(), options, {variable: 'data'}); + appendAudio(template); - appendAudio(template); - } + } - function appendAudio(template) { - $('#account-audio-content-scroller').html(template); - } + function appendAudio(template) { + $('#account-audio-content-scroller table tbody').replaceWith(template); + } - function resetForm() { - } + function resetForm() { + } - function handleDeleteAudioProfile(audioProfileId) { - console.log("deleting audio profile: " + audioProfileId); + function handleDeleteAudioProfile(audioProfileId) { + console.log("deleting audio profile: " + audioProfileId); - context.jamClient.TrackDeleteProfile(audioProfileId); + context.jamClient.TrackDeleteProfile(audioProfileId); - // redraw after deletion of profile - populateAccountAudio(); - } + // redraw after deletion of profile + populateAccountAudio(); + } - function handleStartAudioQualification() { - app.setWizardStep(1); - app.layout.showDialog('ftue'); - } + function handleStartAudioQualification() { - function registerFtueSuccess() { - $('div[layout-id=ftue]').on("ftue_success", ftueSuccessHandler); - } + var useNewFtue = true; - function unregisterFtueSuccess() { - $('div[layout-id=ftue]').off("ftue_success", ftueSuccessHandler); - } + if(useNewFtue) { + app.layout.startNewFtue(); + } + else { + app.setWizardStep(1); + app.layout.showDialog('ftue'); + } + } - function ftueSuccessHandler() { - populateAccountAudio(); - } + function registerFtueSuccess() { + $('div[layout-id=ftue]').on("ftue_success", ftueSuccessHandler); + } - // events for main screen - function events() { - // wire up main panel clicks - $('#account-audio-content-scroller').on('click', 'a[data-purpose=delete-audio-profile]', function(evt) { evt.stopPropagation(); handleDeleteAudioProfile($(this).attr('data-id')); return false; } ); - $('#account-audio-content-scroller').on('click', 'a[data-purpose=add-profile]', function(evt) { evt.stopPropagation(); handleStartAudioQualification(); return false; } ); - } + function unregisterFtueSuccess() { + $('div[layout-id=ftue]').off("ftue_success", ftueSuccessHandler); + } - function initialize() { - var screenBindings = { - 'beforeShow': beforeShow, - 'afterShow': afterShow - }; - app.bindScreen('account/audio', screenBindings); - events(); - } + function ftueSuccessHandler() { + populateAccountAudio(); + } - this.initialize = initialize; - this.beforeShow = beforeShow; - this.afterShow = afterShow; - this.beforeHide = beforeHide; - return this; - }; + // events for main screen + function events() { + // wire up main panel clicks + $('#account-audio-content-scroller').on('click', 'a[data-purpose=delete-audio-profile]', function (evt) { + evt.stopPropagation(); + handleDeleteAudioProfile($(this).attr('data-id')); + return false; + }); + $('#account-audio-content-scroller').on('click', 'a[data-purpose=add-profile]', function (evt) { + evt.stopPropagation(); + handleStartAudioQualification(); + return false; + }); + } -})(window,jQuery); \ No newline at end of file + function initialize() { + var screenBindings = { + 'beforeShow': beforeShow, + 'afterShow': afterShow + }; + app.bindScreen('account/audio', screenBindings); + events(); + } + + this.initialize = initialize; + this.beforeShow = beforeShow; + this.afterShow = afterShow; + this.beforeHide = beforeHide; + return this; + }; + +})(window, jQuery); \ No newline at end of file diff --git a/web/app/assets/javascripts/createSession.js.erb b/web/app/assets/javascripts/createSession.js.erb index cbbace6ae..dfec928f2 100644 --- a/web/app/assets/javascripts/createSession.js.erb +++ b/web/app/assets/javascripts/createSession.js.erb @@ -119,7 +119,7 @@ // If user hasn't completed FTUE - do so now. if (!(context.jamClient.FTUEGetStatus())) { app.afterFtue = function() { submitForm(evt); }; - app.layout.showDialog('ftue'); + app.layout.startNewFtue(); return; } diff --git a/web/app/assets/javascripts/ftue.js b/web/app/assets/javascripts/ftue.js index 9587649c7..1178d53aa 100644 --- a/web/app/assets/javascripts/ftue.js +++ b/web/app/assets/javascripts/ftue.js @@ -18,6 +18,9 @@ var jamClient = context.jamClient; var win32 = true; + // tracks in the loopback FTUe what the currently chosen audio driver is + var currentAudioDriverId = null; + var deviceSetMap = { 'audio-input': jamClient.FTUESetMusicInput, 'audio-output': jamClient.FTUESetMusicOutput, @@ -32,6 +35,14 @@ 'ftue-audio-output-fader': jamClient.FTUESetOutputVolume }; + var faderReadMap = { + 'ftue-2-audio-input-fader': jamClient.FTUEGetInputVolume, + 'ftue-2-voice-input-fader': jamClient.FTUEGetOutputVolume, + 'ftue-audio-input-fader': jamClient.FTUEGetInputVolume, + 'ftue-voice-input-fader': jamClient.FTUEGetChatInputVolume, + 'ftue-audio-output-fader': jamClient.FTUEGetOutputVolume + }; + function latencyTimeoutCheck() { if (context.JK.FtueWizard.latencyTimeout) { jamClient.FTUERegisterLatencyCallback(''); @@ -72,6 +83,20 @@ } function afterShow(data) { + + } + + // 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); + //faderChange(faderId, faderPct); + }); } function faderChange(faderId, newValue, dragging) { @@ -81,15 +106,67 @@ setFunction(mixerLevel); } + function setSaveButtonState($save, enabled) { + if (enabled) { + $save.removeClass('disabled'); + } + else { + $save.addClass('disabled'); + } + } + + function checkValidStateForTesting() { + var reqMissing = !musicInAndOutSet() || currentAudioDriverId == null || currentAudioDriverId == ''; + + if(reqMissing ) { + renderDisableTest(); + } + else { + renderEnableTest(); + } + } + + function renderDisableTest() { + $('#btn-ftue-test').addClass('disabled'); + } + + function renderEnableTest() { + $('#btn-ftue-test').removeClass('disabled'); + } + + function renderStartNewFtueLatencyTesting() { + setSaveButtonState($('#btn-ftue-2-save'), false); + $('#ftue-2-spinner').show().css('display', 'inline-block'); + } + + function renderStopNewFtueLatencyTesting() { + $('#ftue-2-spinner').hide(); + } + function settingsInit() { jamClient.FTUEInit(); - setLevels(0); + //setLevels(0); + resetFtueLatencyView(); + setSaveButtonState($('#btn-ftue-2-save'), false); + if (jamClient.GetOSAsString() !== "Win32") { + $('#btn-ftue-2-asio-control-panel').hide(); + } + renderDisableTest(); + // Always reset the driver select box to "Choose..." which forces everything // to sync properly when the user reselects their driver of choice. // VRFS-375 and VRFS-561 $('[layout-wizard="ftue"] [layout-wizard-step="0"] .settings-2-device select').val(""); $('[layout-wizard="ftue"] [layout-wizard-step="0"] .settings-2-voice select').val(""); + $('[layout-wizard="ftue"] [layout-wizard-step="0"] #ftue-2-asio-framesize').val("").easyDropDown('disable'); + $('[layout-wizard="ftue"] [layout-wizard-step="0"] #ftue-2-asio-input-latency').val("0").easyDropDown('disable'); + $('[layout-wizard="ftue"] [layout-wizard-step="0"] #ftue-2-asio-output-latency').val("0").easyDropDown('disable'); + + // with the old ftue, this is pretty annoying to reset these everytime $('[layout-wizard="ftue"] [layout-wizard-step="2"] .asio-settings .settings-driver select').val(""); + $('[layout-wizard="ftue"] [layout-wizard-step="2"] .settings-controls select[data-device="audio-input"]').val(""); + $('[layout-wizard="ftue"] [layout-wizard-step="2"] .settings-controls select[data-device="audio-output"]').val(""); + $('[layout-wizard="ftue"] [layout-wizard-step="2"] .settings-controls select[data-device="voice-chat-output"]').val(""); } function setLevels(db) { @@ -251,6 +328,8 @@ }); if (musicInAndOutSet()) { + renderVolumes(); + // If there is no voice-chat-input selected, let the back-end know // that we're using music for voice-chat. if ($('[layout-wizard-step="2"] select[data-device="voice-chat-input"]').val()) { @@ -265,7 +344,7 @@ logger.debug("Calling FTUESave(" + persist + ")"); var response = jamClient.FTUESave(persist); - setLevels(0); + //setLevels(0); if (response) { logger.warn(response); // TODO - we may need to do something about errors on save. @@ -307,6 +386,15 @@ ftueSave(false); } + function testRequested(evt) { + evt.preventDefault(); + var $testButton = $('#btn-ftue-test'); + if (!$testButton.hasClass('disabled')) { + app.setWizardStep(3); + } + return false; + } + function videoLinkClicked(evt) { var myOS = jamClient.GetOSAsString(); var link; @@ -337,6 +425,8 @@ $('#asio-framesize').on('change', setAsioFrameSize); $('#asio-input-latency').on('change', setAsioInputLatency); $('#asio-output-latency').on('change', setAsioOutputLatency); + $('#btn-ftue-test').on('click', testRequested) + // New FTUE events $('.ftue-new .settings-2-device select').on('change', newFtueAudioDeviceChanged); $('.ftue-new .settings-2-voice select').on('change', newFtueAudioDeviceChanged); @@ -457,6 +547,8 @@ jamClient.FTUESave(true); jamClient.FTUESetStatus(true); // No FTUE wizard next time rest.userCertifiedGear({success: true}); + // notify anyone curious about how it went + $('div[layout-id=ftue]').trigger('ftue_success'); app.layout.closeDialog('ftue'); if (app.afterFtue) { // If there's a function to invoke, invoke it. @@ -472,6 +564,7 @@ // We also call jamClient.FTUEGetExpectedLatency, which returns a structure like: // { latency: 11.1875, latencyknown: true, latencyvar: 1} function newFtueAudioDeviceChanged(evt) { + renderStartNewFtueLatencyTesting(); var $select = $(evt.currentTarget); var $audioSelect = $('.ftue-new .settings-2-device select'); @@ -486,6 +579,7 @@ jamClient.TrackSetChatEnable(false); } if (!audioDriverId) { + renderStopNewFtueLatencyTesting(); // reset back to 'Choose...' newFtueEnableControls(false); return; @@ -516,13 +610,25 @@ // TODO FIXME - how to handle a driver selection where we are unable to // autoset both inputs and outputs? (I'd think this could happen if either // the input or output side returned no values) + renderStopNewFtueLatencyTesting(); return; } newFtueEnableControls(true); newFtueOsSpecificSettings(); - setLevels(0); + // make sure whatever the user sees in the frontend is what the backend thinks + // this is necesasry because if you do a FTUE pass, close the client, and pick the same device + // the backend will *silently* use values from before, because the frontend does not query the backend + // for these values anywhere. + newFtueAsioFrameSizeToBackend($('#ftue-2-asio-framesize')); + newFtueAsioInputLatencyToBackend($('#ftue-2-asio-input-latency')); + newFtueAsioOutputLatencyToBackend($('#ftue-2-asio-output-latency')); + + //setLevels(0); + renderVolumes(); newFtueUpdateLatencyView('loading'); + + jamClient.FTUESave(false); setVuCallbacks(); @@ -545,33 +651,67 @@ newFtueSave(false); } - function newFtueSetAsioFrameSize(evt) { - var val = parseFloat($(evt.currentTarget).val(), 10); + // simply tells backend what frontend shows in the UI + function newFtueAsioFrameSizeToBackend($input) { + var val = parseFloat($input.val(), 10); if (isNaN(val)) { - return; + logger.warn("unable to get value from framesize input: %o", $input.val()); + return false; } logger.debug("Calling FTUESetFrameSize(" + val + ")"); jamClient.FTUESetFrameSize(val); + return true; + } + + function newFtueAsioInputLatencyToBackend($input) { + var val = parseInt($input.val(), 10); + if (isNaN(val)) { + logger.warn("unable to get value from input latency input: %o", $input.val()); + return false; + } + logger.debug("Calling FTUESetInputLatency(" + val + ")"); + jamClient.FTUESetInputLatency(val); + return true; + } + + function newFtueAsioOutputLatencyToBackend($input) { + var val = parseInt($input.val(), 10); + if (isNaN(val)) { + logger.warn("unable to get value from output latency input: %o", $input.val()); + return false; + } + logger.debug("Calling FTUESetOutputLatency(" + val + ")"); + jamClient.FTUESetOutputLatency(val); + return true; + } + + function newFtueSetAsioFrameSize(evt) { + renderStartNewFtueLatencyTesting(); + + if(!newFtueAsioFrameSizeToBackend($(evt.currentTarget))) { + renderStopNewFtueLatencyTesting(); + return; + } newFtueSave(false); } function newFtueSetAsioInputLatency(evt) { - var val = parseInt($(evt.currentTarget).val(), 10); - if (isNaN(val)) { + renderStartNewFtueLatencyTesting(); + + if(!newFtueAsioInputLatencyToBackend($(evt.currentTarget))) { + renderStopNewFtueLatencyTesting(); return; } - logger.debug("Calling FTUESetInputLatency(" + val + ")"); - jamClient.FTUESetInputLatency(val); newFtueSave(false); } function newFtueSetAsioOutputLatency(evt) { - var val = parseInt($(evt.currentTarget).val(), 10); - if (isNaN(val)) { + renderStartNewFtueLatencyTesting(); + + if(!newFtueAsioOutputLatencyToBackend($(evt.currentTarget))) { + renderStopNewFtueLatencyTesting(); return; } - logger.debug("Calling FTUESetOutputLatency(" + val + ")"); - jamClient.FTUESetOutputLatency(val); newFtueSave(false); } @@ -626,6 +766,27 @@ } + function resetFtueLatencyView() { + + var $report = $('.ftue-new .latency .report'); + var $instructions = $('.ftue-new .latency .instructions'); + + $('.ms-label', $report).empty(); + $('p', $report).empty(); + + var latencyClass = 'start'; + + $report.removeClass('good acceptable bad'); + $report.addClass(latencyClass); + + var instructionClasses = ['neutral', 'good', 'acceptable', 'bad', 'start', 'loading']; + $.each(instructionClasses, function (idx, val) { + $('p.' + val, $instructions).hide(); + }); + + $('p.' + latencyClass, $instructions).show(); + } + // Given a latency structure, update the view. function newFtueUpdateLatencyView(latency) { var $report = $('.ftue-new .latency .report'); @@ -639,26 +800,26 @@ latencyValue = Math.round(latencyValue * 100) / 100; if (latency.latency <= 10) { latencyClass = "good"; - $saveButton.removeClass('disabled'); + setSaveButtonState($saveButton, true); } else if (latency.latency <= 20) { latencyClass = "acceptable"; - $saveButton.removeClass('disabled'); + setSaveButtonState($saveButton, true); } else { latencyClass = "bad"; - $saveButton.addClass('disabled'); + setSaveButtonState($saveButton, false); } } else { latencyClass = "unknown"; - $saveButton.addClass('disabled'); + setSaveButtonState($saveButton, false); } $('.ms-label', $report).html(latencyValue); $('p', $report).html('milliseconds'); - $report.removeClass('good acceptable bad'); + $report.removeClass('good acceptable bad unknown'); $report.addClass(latencyClass); - var instructionClasses = ['neutral', 'good', 'acceptable', 'bad', 'start', 'loading']; + var instructionClasses = ['neutral', 'good', 'acceptable', 'unknown', 'bad', 'start', 'loading']; $.each(instructionClasses, function (idx, val) { $('p.' + val, $instructions).hide(); }); @@ -666,15 +827,17 @@ $('p.loading', $instructions).show(); } else { $('p.' + latencyClass, $instructions).show(); + renderStopNewFtueLatencyTesting(); } } function audioDriverChanged(evt) { var $select = $(evt.currentTarget); - var driverId = $select.val(); - jamClient.FTUESetMusicDevice(driverId); + currentAudioDriverId = $select.val(); + jamClient.FTUESetMusicDevice(currentAudioDriverId); loadAudioDevices(); setAsioSettingsVisibility(); + checkValidStateForTesting(); } function audioDeviceChanged(evt) { @@ -684,6 +847,7 @@ ftueSave(false); setVuCallbacks(); } + checkValidStateForTesting(); } function setAudioDevice($select) { diff --git a/web/app/assets/javascripts/layout.js b/web/app/assets/javascripts/layout.js index ef88e9f06..f4e1eeda0 100644 --- a/web/app/assets/javascripts/layout.js +++ b/web/app/assets/javascripts/layout.js @@ -7,813 +7,830 @@ * * See the layout-example.html file for a simple working example. */ -(function(context,$) { +(function (context, $) { - "use strict"; + "use strict"; - context.JK = context.JK || {}; + context.JK = context.JK || {}; - // Static function to hide the 'curtain' which hides the underlying - // stuff until we can get it laid out. Called from both the main - // client as well as the landing page. - context.JK.hideCurtain = function(duration) { - context.setTimeout(function() { - $('.curtain').fadeOut(2*duration); - }, duration); + // Static function to hide the 'curtain' which hides the underlying + // stuff until we can get it laid out. Called from both the main + // client as well as the landing page. + context.JK.hideCurtain = function (duration) { + context.setTimeout(function () { + $('.curtain').fadeOut(2 * duration); + }, duration); + }; + + context.JK.Layout = function () { + + // privates + var logger = context.JK.logger; + + var me = null; // Reference to this instance for context sanity. + + var opts = { + headerHeight: 75, + sidebarWidth: 300, + notifyHeight: 150, + notifyGutter: 10, + collapsedSidebar: 30, + panelHeaderHeight: 36, + gutter: 60, // Margin around the whole UI + screenMargin: 0, // Margin around screens (not headers/sidebar) + gridOuterMargin: 6, // Outer margin on Grids (added to screenMargin if screen) + gridPadding: 8, // Padding around grid cells. Added to outer margin. + animationDuration: 400, + allowBodyOverflow: false, // Allow tests to disable the body-no-scroll policy + sizeOverlayToContent: false // if true, use the size of
tag to decide overlay size everytime overlay is shown. should be used in non-client settings }; - context.JK.Layout = function() { + var width = $(context).width(); + var height = $(context).height(); + var resizing = null; + var sidebarVisible = true; + var expandedPanel = null; + var previousScreen = null; + var currentScreen = null; - // privates - var logger = context.JK.logger; + var screenBindings = {}; + var dialogBindings = {}; + var wizardShowFunctions = {}; - var me = null; // Reference to this instance for context sanity. + var openDialogs = []; // FIFO stack - var opts = { - headerHeight: 75, - sidebarWidth: 300, - notifyHeight: 150, - notifyGutter: 10, - collapsedSidebar: 30, - panelHeaderHeight: 36, - gutter: 60, // Margin around the whole UI - screenMargin: 0, // Margin around screens (not headers/sidebar) - gridOuterMargin: 6, // Outer margin on Grids (added to screenMargin if screen) - gridPadding: 8, // Padding around grid cells. Added to outer margin. - animationDuration: 400, - allowBodyOverflow: false, // Allow tests to disable the body-no-scroll policy - sizeOverlayToContent: false // if true, use the size of tag to decide overlay size everytime overlay is shown. should be used in non-client settings - }; + function setup() { + requiredStyles(); + hideAll(); + setInitialExpandedSidebarPanel(); + sizeScreens(width, height, '[layout="screen"]', true); + positionOffscreens(width, height); + $('[layout="sidebar"]').show(); + $('[layout="panel"]').show(); + layout(); + } - var width = $(context).width(); - var height = $(context).height(); - var resizing = null; - var sidebarVisible = true; - var expandedPanel = null; - var previousScreen = null; - var currentScreen = null; + function setInitialExpandedSidebarPanel() { + expandedPanel = $('[layout="panel"]').first().attr("layout-id"); + } - var screenBindings = {}; - var dialogBindings = {}; - var wizardShowFunctions = {}; + function layout() { + width = $(context).width(); + height = $(context).height(); + // TODO + // Work on naming. File is layout, class is Layout, this method + // is layout and every other method starts with 'layoutX'. Perhaps + // a little redundant? + layoutCurtain(width, height); + layoutDialogOverlay(width, height); + layoutScreens(width, height); + layoutSidebar(width, height); + layoutHeader(width, height); + layoutNotify(width, height); + layoutFooter(width, height); + } - var openDialogs = []; // FIFO stack + function layoutCurtain(screenWidth, screenHeight) { + var curtainStyle = { + position: 'absolute', + width: screenWidth + 'px', + height: screenHeight + 'px' + }; + $('.curtain').css(curtainStyle); + } - function setup() { - requiredStyles(); - hideAll(); - setInitialExpandedSidebarPanel(); - sizeScreens(width, height, '[layout="screen"]', true); - positionOffscreens(width, height); - $('[layout="sidebar"]').show(); - $('[layout="panel"]').show(); - layout(); + function layoutDialogOverlay(screenWidth, screenHeight) { + var style = { + position: 'absolute', + width: screenWidth + 'px', + height: screenHeight + 'px' + }; + $('.dialog-overlay').css(style); + } + + function layoutScreens(screenWidth, screenHeight) { + var previousScreenSelector = '[layout-id="' + previousScreen + '"]'; + var currentScreenSelector = '[layout-id="' + currentScreen + '"]'; + $(currentScreenSelector).show(); + + var width = screenWidth - (2 * opts.gutter + 2 * opts.screenMargin); + var left = -1 * width - 100; + + if (currentScreenSelector === previousScreenSelector) { + left = $(currentScreenSelector).css("left"); + if (left) { + left = left.split("px")[0]; } + } + $(previousScreenSelector).animate({left: left}, {duration: opts.animationDuration, queue: false}); + sizeScreens(screenWidth, screenHeight, '[layout="screen"]'); + positionOffscreens(screenWidth, screenHeight); + positionOnscreen(screenWidth, screenHeight); + } - function setInitialExpandedSidebarPanel() { - expandedPanel = $('[layout="panel"]').first().attr("layout-id"); + function sizeScreens(screenWidth, screenHeight, selector, immediate) { + var duration = opts.animationDuration; + if (immediate) { + duration = 0; + } + + var width = screenWidth - (2 * opts.gutter + 2 * opts.screenMargin); + if (sidebarVisible) { + width -= (opts.sidebarWidth + 2 * opts.gridPadding); + } else { + width -= opts.collapsedSidebar + 2 * opts.gridPadding; + width += opts.gutter; // Add back in the right gutter width. + } + var height = screenHeight - opts.headerHeight - (2 * opts.gutter + 2 * opts.screenMargin); + var css = { + width: width, + height: height + }; + var $screens = $(selector); + $screens.animate(css, {duration: duration, queue: false}); + layoutHomeScreen(width, height); + } + + /** + * Postition all screens that are not the current screen. + */ + function positionOffscreens(screenWidth, screenHeight) { + var top = opts.headerHeight + opts.gutter + opts.screenMargin; + var left = -1 * (screenWidth + 2 * opts.gutter); + var $screens = $('[layout="screen"]').not('[layout-id="' + currentScreen + '"]'); + $screens.css({ + top: top, + left: left + }); + } + + /** + * Position the current screen + */ + function positionOnscreen(screenWidth, screenHeight, immediate) { + var duration = opts.animationDuration; + if (immediate) { + duration = 0; + } + var top = opts.headerHeight + opts.gutter + opts.screenMargin; + var left = opts.gutter + opts.screenMargin; + var $screen = $('[layout-id="' + currentScreen + '"]'); + $screen.animate({ + top: top, + left: left, + overflow: 'auto' + }, duration); + } + + function layoutHomeScreen(homeScreenWidth, homeScreenHeight) { + var $grid = $('[layout-grid]'); + var gridWidth = homeScreenWidth; + var gridHeight = homeScreenHeight; + $grid.css({width: gridWidth, height: gridHeight}); + var layout = $grid.attr('layout-grid'); + if (!layout) + return; + var gridRows = layout.split('x')[0]; + var gridCols = layout.split('x')[1]; + + $grid.children().each(function () { + var childPosition = $(this).attr("layout-grid-position"); + var childRow = childPosition.split(',')[1]; + var childCol = childPosition.split(',')[0]; + var childRowspan = $(this).attr("layout-grid-rows"); + var childColspan = $(this).attr("layout-grid-columns"); + var childLayout = me.getCardLayout(gridWidth, gridHeight, gridRows, gridCols, + childRow, childCol, childRowspan, childColspan); + + $(this).animate({ + width: childLayout.width, + height: childLayout.height, + top: childLayout.top, + left: childLayout.left + }, opts.animationDuration); + }); + } + + function layoutSidebar(screenWidth, screenHeight) { + var width = opts.sidebarWidth; + var expanderHeight = $('[layout-sidebar-expander]').height(); + var height = screenHeight - opts.headerHeight - 2 * opts.gutter + expanderHeight; + var right = opts.gutter; + if (!sidebarVisible) { + // Negative right to hide most of sidebar + right = (0 - opts.sidebarWidth) + opts.collapsedSidebar; + } + var top = opts.headerHeight + opts.gutter - expanderHeight; + var css = { + width: width, + height: height, + top: top, + right: right + }; + $('[layout="sidebar"]').animate(css, opts.animationDuration); + layoutPanels(width, height); + if (sidebarVisible) { + $('[layout-panel="collapsed"]').hide(); + $('[layout-panel="expanded"]').show(); + $('[layout-sidebar-expander="hidden"]').hide(); + $('[layout-sidebar-expander="visible"]').show(); + } else { + $('[layout-panel="collapsed"]').show(); + $('[layout-panel="expanded"]').hide(); + $('[layout-sidebar-expander="hidden"]').show(); + $('[layout-sidebar-expander="visible"]').hide(); + } + } + + function layoutPanels(sidebarWidth, sidebarHeight) { + // TODO - don't like the accordian - poor usability. Requires longest mouse + // reach when switching panels. Probably better to do tabs. + if (!sidebarVisible) { + return; + } + var $expandedPanelContents = $('[layout-id="' + expandedPanel + '"] [layout-panel="contents"]'); + var combinedHeaderHeight = $('[layout-panel="contents"]').length * opts.panelHeaderHeight; + var searchHeight = $('.sidebar .search').first().height(); + var expanderHeight = $('[layout-sidebar-expander]').height(); + var expandedPanelHeight = sidebarHeight - (combinedHeaderHeight + expanderHeight + searchHeight); + $('[layout-panel="contents"]').hide(); + $('[layout-panel="contents"]').css({"height": "1px"}); + $expandedPanelContents.show(); + $expandedPanelContents.animate({"height": expandedPanelHeight + "px"}, opts.animationDuration); + } + + function layoutHeader(screenWidth, screenHeight) { + var width = screenWidth - 2 * opts.gutter; + var height = opts.headerHeight - opts.gutter; + var top = opts.gutter; + var left = opts.gutter; + var css = { + width: width + "px", + height: height + "px", + top: top + "px", + left: left + "px" + }; + $('[layout="header"]').css(css); + } + + function layoutNotify(screenWidth, screenHeight) { + var $notify = $('[layout="notify"]'); + var nHeight = $notify.height(); + var notifyStyle = { + bottom: '0px', + position: 'fixed' + }; + $notify.css(notifyStyle); + } + + function layoutFooter(screenWidth, screenHeight) { + + if (!opts.layoutFooter) { + return; + } + var $footer = $('#footer'); + $footer.show(); + var nHeight = $footer.height(); + var footerStyle = { + top: (screenHeight - 80) + 'px' + }; + var width = screenWidth - (2 * opts.gutter + 2 * opts.screenMargin); + var left = -1 * width - 100; + $footer.animate({ "left": opts.gutter, "width": width, "top": (screenHeight - 78) + "px"}, opts.animationDuration); + } + + function requiredLayoutStyles() { + var layoutStyle = { + position: 'absolute', + margin: '0px', + padding: '0px' + }; + $('[layout]').css(layoutStyle); + // JW: Setting z-index of notify to 1001, so it will appear above the dialog overlay. + // This allows dialogs to use the notification. + $('[layout="notify"]').css({"z-index": "1001", "padding": "20px"}); + $('[layout="panel"]').css({position: 'relative'}); + $('[layout-panel="expanded"] [layout-panel="header"]').css({ + margin: "0px", + padding: "0px", + height: opts.panelHeaderHeight + "px" + }); + $('[layout-grid]').css({ + position: "relative" + }); + $('[layout-grid]').children().css({ + position: "absolute" + }); + } + + function requiredStyles() { + var bodyStyle = { + margin: '0px', + padding: '0px', + overflow: 'hidden' + }; + if (opts.allowBodyOverflow) { + delete bodyStyle.overflow; + } + $('body').css(bodyStyle); + + requiredLayoutStyles(); + + var curtainStyle = { + position: "absolute", + margin: '0px', + padding: '0px', + overflow: 'hidden', + zIndex: 100 + }; + $('.curtain').css(curtainStyle); + } + + function hideAll() { + $('[layout]').hide(); + $('[layout="header"]').show(); + } + + function showSidebar() { + sidebarVisible = true; + layout(); + } + + function hideSidebar() { + sidebarVisible = false; + layout(); + } + + function toggleSidebar() { + if (sidebarVisible) { + hideSidebar(); + } else { + showSidebar(); + } + } + + function hideDialogs() { + // TODO - may need dialogEvents here for specific dialogs. + $('[layout="dialog"]').hide(); + $('.dialog-overlay').hide(); + } + + function tabClicked(evt) { + evt.preventDefault(); + var destination = $(evt.currentTarget).attr('tab-target'); + $('[tab-target]').removeClass('selected'); + $(evt.currentTarget).addClass('selected'); + $('.tab').hide(); + $('[tab-id="' + destination + '"]').show(); + } + + function linkClicked(evt) { + evt.preventDefault(); + var $currentTarget = $(evt.currentTarget); + + // allow links to be disabled + if ($currentTarget.hasClass("disabled")) { + return; + } + + // If link requires FTUE, show that first. + if ($currentTarget.hasClass("requires-ftue")) { + if (!(context.jamClient.FTUEGetStatus())) { + app.layout.showDialog('ftue'); } + } - function layout() { - width = $(context).width(); - height = $(context).height(); - // TODO - // Work on naming. File is layout, class is Layout, this method - // is layout and every other method starts with 'layoutX'. Perhaps - // a little redundant? - layoutCurtain(width, height); - layoutDialogOverlay(width, height); - layoutScreens(width, height); - layoutSidebar(width, height); - layoutHeader(width, height); - layoutNotify(width, height); - layoutFooter(width, height); + var destination = $(evt.currentTarget).attr('layout-link'); + var destinationType = $('[layout-id="' + destination + '"]').attr("layout"); + if (destinationType === "screen") { + context.location = '/client#/' + destination; + } else if (destinationType === "dialog") { + showDialog(destination); + } + } + + function close(evt) { + var $target = $(evt.currentTarget).closest('[layout]'); + var layoutId = $target.attr('layout-id'); + var isDialog = ($target.attr('layout') === 'dialog'); + if (isDialog) { + closeDialog(layoutId); + } else { + $target.hide(); + } + return false; + } + + function closeDialog(dialog) { + console.log("closing dialog: " + dialog); + var $dialog = $('[layout-id="' + dialog + '"]'); + dialogEvent(dialog, 'beforeHide'); + var $overlay = $('.dialog-overlay'); + unstackDialogs($overlay); + $dialog.hide(); + dialogEvent(dialog, 'afterHide'); + } + + function screenEvent(screen, evtName, data) { + if (screen && screen in screenBindings) { + if (evtName in screenBindings[screen]) { + screenBindings[screen][evtName].call(me, data); } + } + } - function layoutCurtain(screenWidth, screenHeight) { - var curtainStyle = { - position: 'absolute', - width: screenWidth + 'px', - height: screenHeight + 'px' - }; - $('.curtain').css(curtainStyle); - } - - function layoutDialogOverlay(screenWidth, screenHeight) { - var style = { - position: 'absolute', - width: screenWidth + 'px', - height: screenHeight + 'px' - }; - $('.dialog-overlay').css(style); - } - - function layoutScreens(screenWidth, screenHeight) { - var previousScreenSelector = '[layout-id="' + previousScreen + '"]'; - var currentScreenSelector = '[layout-id="' + currentScreen + '"]'; - $(currentScreenSelector).show(); - - var width = screenWidth - (2 * opts.gutter + 2 * opts.screenMargin); - var left = -1 * width - 100; - - if (currentScreenSelector === previousScreenSelector) { - left = $(currentScreenSelector).css("left"); - if (left) { - left = left.split("px")[0]; - } - } - $(previousScreenSelector).animate({left: left}, {duration: opts.animationDuration, queue: false}); - sizeScreens(screenWidth, screenHeight, '[layout="screen"]'); - positionOffscreens(screenWidth, screenHeight); - positionOnscreen(screenWidth, screenHeight); - } - - function sizeScreens(screenWidth, screenHeight, selector, immediate) { - var duration = opts.animationDuration; - if (immediate) { - duration = 0; - } - - var width = screenWidth - (2 * opts.gutter + 2 * opts.screenMargin); - if (sidebarVisible) { - width -= (opts.sidebarWidth + 2*opts.gridPadding); - } else { - width -= opts.collapsedSidebar + 2*opts.gridPadding; - width += opts.gutter; // Add back in the right gutter width. - } - var height = screenHeight - opts.headerHeight - (2 * opts.gutter + 2 * opts.screenMargin); - var css = { - width: width, - height: height - }; - var $screens = $(selector); - $screens.animate(css, {duration:duration, queue:false}); - layoutHomeScreen(width, height); - } - - /** - * Postition all screens that are not the current screen. - */ - function positionOffscreens(screenWidth, screenHeight) { - var top = opts.headerHeight + opts.gutter + opts.screenMargin; - var left = -1 * (screenWidth + 2*opts.gutter); - var $screens = $('[layout="screen"]').not('[layout-id="' + currentScreen + '"]'); - $screens.css({ - top: top, - left: left - }); - } - - /** - * Position the current screen - */ - function positionOnscreen(screenWidth, screenHeight, immediate) { - var duration = opts.animationDuration; - if (immediate) { - duration = 0; - } - var top = opts.headerHeight + opts.gutter + opts.screenMargin; - var left = opts.gutter + opts.screenMargin; - var $screen = $('[layout-id="' + currentScreen + '"]'); - $screen.animate({ - top: top, - left: left, - overflow: 'auto' - }, duration); - } - - function layoutHomeScreen(homeScreenWidth, homeScreenHeight) { - var $grid = $('[layout-grid]'); - var gridWidth = homeScreenWidth; - var gridHeight = homeScreenHeight; - $grid.css({width:gridWidth, height: gridHeight}); - var layout = $grid.attr('layout-grid'); - if (!layout) - return; - var gridRows = layout.split('x')[0]; - var gridCols = layout.split('x')[1]; - - $grid.children().each(function() { - var childPosition = $(this).attr("layout-grid-position"); - var childRow = childPosition.split(',')[1]; - var childCol = childPosition.split(',')[0]; - var childRowspan = $(this).attr("layout-grid-rows"); - var childColspan = $(this).attr("layout-grid-columns"); - var childLayout = me.getCardLayout(gridWidth, gridHeight, gridRows, gridCols, - childRow, childCol, childRowspan, childColspan); - - $(this).animate({ - width: childLayout.width, - height: childLayout.height, - top: childLayout.top, - left: childLayout.left - }, opts.animationDuration); - }); - } - - function layoutSidebar(screenWidth, screenHeight) { - var width = opts.sidebarWidth; - var expanderHeight = $('[layout-sidebar-expander]').height(); - var height = screenHeight - opts.headerHeight - 2 * opts.gutter + expanderHeight; - var right = opts.gutter; - if (!sidebarVisible) { - // Negative right to hide most of sidebar - right = (0 - opts.sidebarWidth) + opts.collapsedSidebar; - } - var top = opts.headerHeight + opts.gutter - expanderHeight; - var css = { - width: width, - height: height, - top: top, - right: right - }; - $('[layout="sidebar"]').animate(css, opts.animationDuration); - layoutPanels(width, height); - if (sidebarVisible) { - $('[layout-panel="collapsed"]').hide(); - $('[layout-panel="expanded"]').show(); - $('[layout-sidebar-expander="hidden"]').hide(); - $('[layout-sidebar-expander="visible"]').show(); - } else { - $('[layout-panel="collapsed"]').show(); - $('[layout-panel="expanded"]').hide(); - $('[layout-sidebar-expander="hidden"]').show(); - $('[layout-sidebar-expander="visible"]').hide(); - } - } - - function layoutPanels(sidebarWidth, sidebarHeight) { - // TODO - don't like the accordian - poor usability. Requires longest mouse - // reach when switching panels. Probably better to do tabs. - if (!sidebarVisible) { - return; - } - var $expandedPanelContents = $('[layout-id="' + expandedPanel + '"] [layout-panel="contents"]'); - var combinedHeaderHeight = $('[layout-panel="contents"]').length * opts.panelHeaderHeight; - var searchHeight = $('.sidebar .search').first().height(); - var expanderHeight = $('[layout-sidebar-expander]').height(); - var expandedPanelHeight = sidebarHeight - (combinedHeaderHeight + expanderHeight + searchHeight); - $('[layout-panel="contents"]').hide(); - $('[layout-panel="contents"]').css({"height": "1px"}); - $expandedPanelContents.show(); - $expandedPanelContents.animate({"height": expandedPanelHeight + "px"}, opts.animationDuration); - } - - function layoutHeader(screenWidth, screenHeight) { - var width = screenWidth - 2*opts.gutter; - var height = opts.headerHeight - opts.gutter; - var top = opts.gutter; - var left = opts.gutter; - var css = { - width: width + "px", - height: height + "px", - top: top + "px", - left: left + "px" - }; - $('[layout="header"]').css(css); - } - - function layoutNotify(screenWidth, screenHeight) { - var $notify = $('[layout="notify"]'); - var nHeight = $notify.height(); - var notifyStyle = { - bottom: '0px', - position: 'fixed' - }; - $notify.css(notifyStyle); - } - - function layoutFooter(screenWidth, screenHeight) { - - if(!opts.layoutFooter) { return; } - var $footer = $('#footer'); - $footer.show(); - var nHeight = $footer.height(); - var footerStyle = { - top: (screenHeight - 80) + 'px' - }; - var width = screenWidth - (2 * opts.gutter + 2 * opts.screenMargin); - var left = -1 * width - 100; - $footer.animate({ "left" : opts.gutter, "width" : width, "top": (screenHeight - 78) + "px"}, opts.animationDuration); - } - - function requiredLayoutStyles() { - var layoutStyle = { - position: 'absolute', - margin: '0px', - padding: '0px' - }; - $('[layout]').css(layoutStyle); - // JW: Setting z-index of notify to 1001, so it will appear above the dialog overlay. - // This allows dialogs to use the notification. - $('[layout="notify"]').css({"z-index": "1001", "padding": "20px"}); - $('[layout="panel"]').css({position: 'relative'}); - $('[layout-panel="expanded"] [layout-panel="header"]').css({ - margin: "0px", - padding: "0px", - height: opts.panelHeaderHeight + "px" - }); - $('[layout-grid]').css({ - position: "relative" - }); - $('[layout-grid]').children().css({ - position: "absolute" - }); - } - - function requiredStyles() { - var bodyStyle = { - margin: '0px', - padding: '0px', - overflow: 'hidden' - }; - if (opts.allowBodyOverflow) { - delete bodyStyle.overflow; - } - $('body').css(bodyStyle); - - requiredLayoutStyles(); - - var curtainStyle = { - position: "absolute", - margin: '0px', - padding: '0px', - overflow: 'hidden', - zIndex: 100 - }; - $('.curtain').css(curtainStyle); - } - - function hideAll() { - $('[layout]').hide(); - $('[layout="header"]').show(); - } - - function showSidebar() { - sidebarVisible = true; - layout(); - } - - function hideSidebar() { - sidebarVisible = false; - layout(); - } - - function toggleSidebar() { - if (sidebarVisible) { - hideSidebar(); - } else { - showSidebar(); - } - } - - function hideDialogs() { - // TODO - may need dialogEvents here for specific dialogs. - $('[layout="dialog"]').hide(); - $('.dialog-overlay').hide(); - } - - function tabClicked(evt) { - evt.preventDefault(); - var destination = $(evt.currentTarget).attr('tab-target'); - $('[tab-target]').removeClass('selected'); - $(evt.currentTarget).addClass('selected'); - $('.tab').hide(); - $('[tab-id="' + destination + '"]').show(); - } - - function linkClicked(evt) { - evt.preventDefault(); - var $currentTarget = $(evt.currentTarget); - - // allow links to be disabled - if($currentTarget.hasClass("disabled") ) { - return; - } - - // If link requires FTUE, show that first. - if ($currentTarget.hasClass("requires-ftue")) { - if (!(context.jamClient.FTUEGetStatus())) { - app.layout.showDialog('ftue'); - } - } - - var destination = $(evt.currentTarget).attr('layout-link'); - var destinationType = $('[layout-id="' + destination + '"]').attr("layout"); - if (destinationType === "screen") { - context.location = '/client#/' + destination; - } else if (destinationType === "dialog") { - showDialog(destination); - } - } - - function close(evt) { - var $target = $(evt.currentTarget).closest('[layout]'); - var layoutId = $target.attr('layout-id'); - var isDialog = ($target.attr('layout') === 'dialog'); - if (isDialog) { - closeDialog(layoutId); - } else { - $target.hide(); - } + function dialogEvent(dialog, evtName, data) { + if (dialog && dialog in dialogBindings) { + if (evtName in dialogBindings[dialog]) { + var result = dialogBindings[dialog][evtName].call(me, data); + if (result === false) { return false; + } } + } + return true; + } - function closeDialog(dialog) { - console.log("closing dialog: " + dialog); - var $dialog = $('[layout-id="' + dialog + '"]'); - dialogEvent(dialog, 'beforeHide'); - var $overlay = $('.dialog-overlay'); - unstackDialogs($overlay); - $dialog.hide(); - dialogEvent(dialog, 'afterHide'); + function changeToScreen(screen, data) { + previousScreen = currentScreen; + currentScreen = screen; + + screenEvent(previousScreen, 'beforeHide', data); + screenEvent(currentScreen, 'beforeShow', data); + + // For now -- it seems we want it open always. + // TODO - support user preference here? Remember how they left it? + sidebarVisible = true; + /* + var openSidebarScreens = [ + 'home', 'session', 'createSession', + 'findSession', 'searchResults' + ]; + $.each(openSidebarScreens, function() { + logger.debug("comparing " + this + " to " + currentScreen); + if (this === currentScreen) { + sidebarVisible = true; + return false; + } + }); + */ + layout(); + + screenEvent(previousScreen, 'afterHide', data); + screenEvent(currentScreen, 'afterShow', data); + + // Show any requested dialog + if ("d" in data) { + showDialog(data.d); + } + } + + /** + * Responsible for keeping N dialogs in correct stacked order, + * also moves the .dialog-overlay such that it hides/obscures all dialogs except the highest one + */ + function stackDialogs($dialog, $overlay) { + // don't push a dialog on the stack that is already on there; remove it from where ever it is currently + // and the rest of the code will make it end up at the top + var layoutId = $dialog.attr('layout-id'); + for (var i = openDialogs.length - 1; i >= 0; i--) { + if (openDialogs[i].attr('layout-id') === layoutId) { + openDialogs.splice(i, 1); } + } - function screenEvent(screen, evtName, data) { - if (screen && screen in screenBindings) { - if (evtName in screenBindings[screen]) { - screenBindings[screen][evtName].call(me, data); - } - } + openDialogs.push($dialog); + var zIndex = 1000; + for (var i in openDialogs) { + var $openDialog = openDialogs[i]; + $openDialog.css('zIndex', zIndex); + zIndex++; + } + $overlay.css('zIndex', zIndex - 1); + } + + function unstackDialogs($overlay) { + if (openDialogs.length > 0) { + openDialogs.pop(); + } + + var zIndex = 1000 + openDialogs.length; + $overlay.css('zIndex', zIndex - 1); + + if (openDialogs.length == 0) { + $overlay.hide(); + } + } + + function showDialog(dialog) { + if (!dialogEvent(dialog, 'beforeShow')) { + return; + } + var $overlay = $('.dialog-overlay') + + if (opts.sizeOverlayToContent) { + var $body = $('body') + $('.dialog-overlay').css({ + width: $body.width() + 'px', + height: $body.height() + 'px' + }); + } + + $overlay.show(); + centerDialog(dialog); + var $dialog = $('[layout-id="' + dialog + '"]'); + stackDialogs($dialog, $overlay); + $dialog.show(); + dialogEvent(dialog, 'afterShow'); + } + + function centerDialog(dialog) { + var $dialog = $('[layout-id="' + dialog + '"]'); + $dialog.css({ + left: width / 2 - ($dialog.width() / 2) + "px", + top: height / 2 - ($dialog.height() / 2) + "px" + }); + } + + + function panelHeaderClicked(evt) { + evt.preventDefault(); + expandedPanel = $(evt.currentTarget).closest('[layout="panel"]').attr("layout-id"); + layout(); + return false; + } + + function wizardLinkClicked(evt) { + evt.preventDefault(); + var targetStepId = $(evt.currentTarget).attr("layout-wizard-link"); + setWizardStep(targetStepId); + return false; + } + + function startNewFtue() { + var step = 0; + setWizardStep(step); + wizardShowFunctions[step](); + showDialog('ftue'); + } + + function setWizardStep(targetStepId) { + var selector = '[layout-wizard-step="' + targetStepId + '"]'; + var $targetStep = $(selector); + var stepDialogTitle = $targetStep.attr("dialog-title"); + if (stepDialogTitle) { + var $myDialog = $targetStep.closest('[layout="dialog"]'); + var $myTitle = $('.content-head h1', $myDialog); + $myTitle.html(stepDialogTitle); + } + // Hide all steps: + // Invoke the 'show' function, if present prior to actually showing. + if (context._.contains(context._.keys(wizardShowFunctions), targetStepId)) { + wizardShowFunctions[targetStepId](); + } + $('[layout-wizard-step]').hide(); + $targetStep.show(); + + var ftuePurpose = $targetStep.attr("dialog-purpose"); + context.JK.GA.trackFTUECompletion(ftuePurpose, context.JK.detectOS()); + } + + function trackLocationChange(e) { + context.JK.GA.virtualPageView(location.pathname + location.search + location.hash); + } + + function handleDialogState() { + var rawDialogState = $.cookie('dialog_state'); + try { + var dialogState = JSON.parse(rawDialogState); + if (!dialogState) { + $.removeCookie('dialog_state'); + return; } + } + catch (e) { + $.removeCookie('dialog_state'); + return; + } - function dialogEvent(dialog, evtName, data) { - if (dialog && dialog in dialogBindings) { - if (evtName in dialogBindings[dialog]) { - var result = dialogBindings[dialog][evtName].call(me, data); - if(result === false) { - return false; - } - } - } - return true; + var dialogName = dialogState['name']; + if (dialogName) { + setTimeout(function () { + // TODO: we need a 'everything is initialized' event + showDialog(dialogName); + }, 0); + } + $.removeCookie('dialog_state'); + } + + // on next page load, a dialog of this name will show + function queueDialog(name) { + $.cookie('dialog_state', JSON.stringify({name: name})) + } + + function events() { + $(context).resize(function () { + if (resizing) { + context.clearTimeout(resizing); } + resizing = context.setTimeout(layout, 80); + }); + $('body').on('click', '[layout-link]', linkClicked); + $('[layout-action="close"]').on('click', close); + $('[layout-sidebar-expander]').on('click', toggleSidebar); + $('[layout-panel="expanded"] [layout-panel="header"]').on('click', panelHeaderClicked); + $('[layout-wizard-link]').on('click', wizardLinkClicked); + $('[tab-target]').on('click', tabClicked); + $(context).on('hashchange', trackLocationChange); + } - function changeToScreen(screen, data) { - previousScreen = currentScreen; - currentScreen = screen; - - screenEvent(previousScreen, 'beforeHide', data); - screenEvent(currentScreen, 'beforeShow', data); - - // For now -- it seems we want it open always. - // TODO - support user preference here? Remember how they left it? - sidebarVisible = true; - /* - var openSidebarScreens = [ - 'home', 'session', 'createSession', - 'findSession', 'searchResults' - ]; - $.each(openSidebarScreens, function() { - logger.debug("comparing " + this + " to " + currentScreen); - if (this === currentScreen) { - sidebarVisible = true; - return false; - } - }); - */ - layout(); - - screenEvent(previousScreen, 'afterHide', data); - screenEvent(currentScreen, 'afterShow', data); - - // Show any requested dialog - if ("d" in data) { - showDialog(data.d); - } - } - - /** - * Responsible for keeping N dialogs in correct stacked order, - * also moves the .dialog-overlay such that it hides/obscures all dialogs except the highest one - */ - function stackDialogs($dialog, $overlay) { - // don't push a dialog on the stack that is already on there; remove it from where ever it is currently - // and the rest of the code will make it end up at the top - var layoutId = $dialog.attr('layout-id'); - for(var i = openDialogs.length - 1; i >= 0; i--) { - if(openDialogs[i].attr('layout-id') === layoutId) { - openDialogs.splice(i, 1); - } - } - - openDialogs.push($dialog); - var zIndex = 1000; - for(var i in openDialogs) { - var $openDialog = openDialogs[i]; - $openDialog.css('zIndex', zIndex); - zIndex++; - } - $overlay.css('zIndex', zIndex - 1); - } - - function unstackDialogs($overlay) { - if(openDialogs.length > 0) { - openDialogs.pop(); - } - - var zIndex = 1000 + openDialogs.length; - $overlay.css('zIndex', zIndex - 1); - - if(openDialogs.length == 0) { - $overlay.hide(); - } - } - - function showDialog(dialog) { - if(!dialogEvent(dialog, 'beforeShow')) {return;} - var $overlay = $('.dialog-overlay') - - if(opts.sizeOverlayToContent) { - var $body = $('body') - $('.dialog-overlay').css({ - width: $body.width() + 'px', - height: $body.height() + 'px' - }); - } - - $overlay.show(); - centerDialog(dialog); - var $dialog = $('[layout-id="' + dialog + '"]'); - stackDialogs($dialog, $overlay); - $dialog.show(); - dialogEvent(dialog, 'afterShow'); - } - - function centerDialog(dialog) { - var $dialog = $('[layout-id="' + dialog + '"]'); - $dialog.css({ - left: width/2 - ($dialog.width()/2) + "px", - top: height/2 - ($dialog.height()/2) + "px" - }); - } - - - function panelHeaderClicked(evt) { - evt.preventDefault(); - expandedPanel = $(evt.currentTarget).closest('[layout="panel"]').attr("layout-id"); - layout(); - return false; - } - - function wizardLinkClicked(evt) { - evt.preventDefault(); - var targetStepId = $(evt.currentTarget).attr("layout-wizard-link"); - setWizardStep(targetStepId); - return false; - } - - function setWizardStep(targetStepId) { - var selector = '[layout-wizard-step="' + targetStepId + '"]'; - var $targetStep = $(selector); - var stepDialogTitle = $targetStep.attr("dialog-title"); - if (stepDialogTitle) { - var $myDialog = $targetStep.closest('[layout="dialog"]'); - var $myTitle = $('.content-head h1', $myDialog); - $myTitle.html(stepDialogTitle); - } - // Hide all steps: - // Invoke the 'show' function, if present prior to actually showing. - if (context._.contains(context._.keys(wizardShowFunctions), targetStepId)) { - wizardShowFunctions[targetStepId](); - } - $('[layout-wizard-step]').hide(); - $targetStep.show(); - - var ftuePurpose = $targetStep.attr("dialog-purpose"); - context.JK.GA.trackFTUECompletion(ftuePurpose, context.JK.detectOS()); - } - - function trackLocationChange(e) { - context.JK.GA.virtualPageView(location.pathname + location.search + location.hash); - } - - function handleDialogState() { - var rawDialogState = $.cookie('dialog_state'); - try { - var dialogState = JSON.parse(rawDialogState); - if(!dialogState) { $.removeCookie('dialog_state'); return; } - } - catch (e) {$.removeCookie('dialog_state'); return; } - - var dialogName = dialogState['name']; - if(dialogName) { - setTimeout(function() { - // TODO: we need a 'everything is initialized' event - showDialog(dialogName); - }, 0); - } - $.removeCookie('dialog_state'); - } - - // on next page load, a dialog of this name will show - function queueDialog(name) { - $.cookie('dialog_state', JSON.stringify({name:name})) - } - - function events() { - $(context).resize(function() { - if (resizing) { - context.clearTimeout(resizing); - } - resizing = context.setTimeout(layout, 80); - }); - $('body').on('click', '[layout-link]', linkClicked); - $('[layout-action="close"]').on('click', close); - $('[layout-sidebar-expander]').on('click', toggleSidebar); - $('[layout-panel="expanded"] [layout-panel="header"]').on('click', panelHeaderClicked); - $('[layout-wizard-link]').on('click', wizardLinkClicked); - $('[tab-target]').on('click', tabClicked); - $(context).on('hashchange', trackLocationChange); - } - - // public functions - this.getOpts = function() { - return opts; - }; - - // used for concurrent notifications - var notifyQueue = []; - var firstNotification = false; - var notifyDetails; - - this.notify = function(message, descriptor) { - var $notify = $('[layout="notify"]'); - - if (notifyQueue.length === 0) { - firstNotification = true; - setNotificationInfo(message, descriptor); - } - - notifyQueue.push({message: message, descriptor: descriptor}); - // JW - speeding up the in/out parts of notify. Extending non-moving time. - $notify.slideDown(250) - .delay(4000) - .slideUp({ - duration: 400, - queue: true, - complete: function() { - notifyDetails = notifyQueue.shift(); - - // shift 1 more time if this is first notification being displayed - if (firstNotification) { - notifyDetails = notifyQueue.shift(); - firstNotification = false; - } - - if (notifyDetails !== undefined) { - setNotificationInfo(notifyDetails.message, notifyDetails.descriptor); - } - } - }); - }; - - function setNotificationInfo(message, descriptor) { - var $notify = $('[layout="notify"]'); - $('h2', $notify).text(message.title); - if(message.text instanceof jQuery) { - $('p', $notify).append(message.text) - } - else { - $('p', $notify).html( message.text); - } - - if (message.icon_url) { - $('#avatar', $notify).attr('src', message.icon_url); - $('#notify-avatar', $notify).show(); - } - else { - $('#notify-avatar', $notify).hide(); - } - - if (message.detail) { - $('div.detail', $notify).html(message.detail).show(); - } - else { - $('div.detail', $notify).hide(); - } - - if (descriptor) { - if (descriptor.ok_text) { - $('#ok-button', $notify).html(descriptor.ok_text); - } - else { - $('#ok-button', $notify).html("OKAY"); - } - - if (descriptor.ok_callback !== undefined) { - $('#ok-button', $notify).click(function() { - if (descriptor.ok_callback_args) { - logger.debug("descriptor.ok_callback_args=" + descriptor.ok_callback_args); - descriptor.ok_callback(descriptor.ok_callback_args); - return false; - } - else { - descriptor.ok_callback(); - return false; - } - }); - } - - if (descriptor.cancel_text) { - $('#cancel-button', $notify).html(descriptor.cancel_text); - } - else { - if(descriptor.no_cancel) { - $('#cancel-button', $notify).hide(); - } - else { - $('#cancel-button', $notify).html("CANCEL"); - } - } - - if (descriptor.cancel_callback !== undefined) { - $('#cancel-button', $notify).click(function() { - if (descriptor.cancel_callback_args) { - logger.debug("descriptor.cancel_callback_args=" + descriptor.cancel_callback_args); - descriptor.cancel_callback(descriptor.cancel_callback_args); - return false; - } - else { - descriptor.cancel_callback(); - return false; - } - }); - } - } - else { - $('#ok-button', $notify).html("OKAY"); - $('#cancel-button', $notify).html("CANCEL"); - } - } - - this.setWizardStep = setWizardStep; - - this.changeToScreen = function(screen, data) { - changeToScreen(screen, data); - }; - - this.showDialog = function(dialog) { - showDialog(dialog); - }; - - this.close = function(evt) { - close(evt); - }; - - this.closeDialog = closeDialog; - - this.handleDialogState = handleDialogState; - this.queueDialog = queueDialog; - - /** - * Given information on a grid, and a given card's grid settings, use the - * margin options and return a list of [top, left, width, height] - * for the cell. - */ - this.getCardLayout = function(gridWidth, gridHeight, gridRows, gridCols, - row, col, rowspan, colspan) { - - var _gridWidth = gridWidth + 3*opts.gridPadding; - var _gridHeight = gridHeight + 3*opts.gridPadding; - var cellWidth, cellHeight, top, left, width, height; - - cellWidth = Math.floor((_gridWidth-2*opts.gridOuterMargin) / gridCols); - cellHeight = Math.floor((_gridHeight-2*opts.gridOuterMargin) / gridRows); - width = colspan * cellWidth - 2*opts.gridPadding; - height = rowspan * cellHeight - 2*opts.gridPadding; - top = row * cellHeight;// + opts.gridOuterMargin; // + opts.gridPadding; - left = col * cellWidth;// + opts.gridOuterMargin; // + opts.gridPadding; - - return { - top: top, - left: left, - width: width, - height: height - }; - }; - - this.bindScreen = function(screen, handler) { - screenBindings[screen] = handler; - }; - - this.bindDialog = function(dialog, handler) { - dialogBindings[dialog] = handler; - }; - - this.registerWizardStepFunction = function(stepId, showFunction) { - wizardShowFunctions[stepId] = showFunction; - }; - - this.initialize = function(inOpts) { - me = this; - opts = $.extend(opts, inOpts); - setup(); - events(); - }; - - return this; - + // public functions + this.getOpts = function () { + return opts; }; + // used for concurrent notifications + var notifyQueue = []; + var firstNotification = false; + var notifyDetails; + + this.notify = function (message, descriptor) { + var $notify = $('[layout="notify"]'); + + if (notifyQueue.length === 0) { + firstNotification = true; + setNotificationInfo(message, descriptor); + } + + notifyQueue.push({message: message, descriptor: descriptor}); + // JW - speeding up the in/out parts of notify. Extending non-moving time. + $notify.slideDown(250) + .delay(4000) + .slideUp({ + duration: 400, + queue: true, + complete: function () { + notifyDetails = notifyQueue.shift(); + + // shift 1 more time if this is first notification being displayed + if (firstNotification) { + notifyDetails = notifyQueue.shift(); + firstNotification = false; + } + + if (notifyDetails !== undefined) { + setNotificationInfo(notifyDetails.message, notifyDetails.descriptor); + } + } + }); + }; + + function setNotificationInfo(message, descriptor) { + var $notify = $('[layout="notify"]'); + $('h2', $notify).text(message.title); + if (message.text instanceof jQuery) { + $('p', $notify).append(message.text) + } + else { + $('p', $notify).html(message.text); + } + + if (message.icon_url) { + $('#avatar', $notify).attr('src', message.icon_url); + $('#notify-avatar', $notify).show(); + } + else { + $('#notify-avatar', $notify).hide(); + } + + if (message.detail) { + $('div.detail', $notify).html(message.detail).show(); + } + else { + $('div.detail', $notify).hide(); + } + + if (descriptor) { + if (descriptor.ok_text) { + $('#ok-button', $notify).html(descriptor.ok_text); + } + else { + $('#ok-button', $notify).html("OKAY"); + } + + if (descriptor.ok_callback !== undefined) { + $('#ok-button', $notify).click(function () { + if (descriptor.ok_callback_args) { + logger.debug("descriptor.ok_callback_args=" + descriptor.ok_callback_args); + descriptor.ok_callback(descriptor.ok_callback_args); + return false; + } + else { + descriptor.ok_callback(); + return false; + } + }); + } + + if (descriptor.cancel_text) { + $('#cancel-button', $notify).html(descriptor.cancel_text); + } + else { + if (descriptor.no_cancel) { + $('#cancel-button', $notify).hide(); + } + else { + $('#cancel-button', $notify).html("CANCEL"); + } + } + + if (descriptor.cancel_callback !== undefined) { + $('#cancel-button', $notify).click(function () { + if (descriptor.cancel_callback_args) { + logger.debug("descriptor.cancel_callback_args=" + descriptor.cancel_callback_args); + descriptor.cancel_callback(descriptor.cancel_callback_args); + return false; + } + else { + descriptor.cancel_callback(); + return false; + } + }); + } + } + else { + $('#ok-button', $notify).html("OKAY"); + $('#cancel-button', $notify).html("CANCEL"); + } + } + + this.setWizardStep = setWizardStep; + this.startNewFtue = startNewFtue; + + this.changeToScreen = function (screen, data) { + changeToScreen(screen, data); + }; + + this.showDialog = function (dialog) { + showDialog(dialog); + }; + + this.close = function (evt) { + close(evt); + }; + + this.closeDialog = closeDialog; + + this.handleDialogState = handleDialogState; + this.queueDialog = queueDialog; + + /** + * Given information on a grid, and a given card's grid settings, use the + * margin options and return a list of [top, left, width, height] + * for the cell. + */ + this.getCardLayout = function (gridWidth, gridHeight, gridRows, gridCols, row, col, rowspan, colspan) { + + var _gridWidth = gridWidth + 3 * opts.gridPadding; + var _gridHeight = gridHeight + 3 * opts.gridPadding; + var cellWidth, cellHeight, top, left, width, height; + + cellWidth = Math.floor((_gridWidth - 2 * opts.gridOuterMargin) / gridCols); + cellHeight = Math.floor((_gridHeight - 2 * opts.gridOuterMargin) / gridRows); + width = colspan * cellWidth - 2 * opts.gridPadding; + height = rowspan * cellHeight - 2 * opts.gridPadding; + top = row * cellHeight;// + opts.gridOuterMargin; // + opts.gridPadding; + left = col * cellWidth;// + opts.gridOuterMargin; // + opts.gridPadding; + + return { + top: top, + left: left, + width: width, + height: height + }; + }; + + this.bindScreen = function (screen, handler) { + screenBindings[screen] = handler; + }; + + this.bindDialog = function (dialog, handler) { + dialogBindings[dialog] = handler; + }; + + this.registerWizardStepFunction = function (stepId, showFunction) { + wizardShowFunctions[stepId] = showFunction; + }; + + this.initialize = function (inOpts) { + me = this; + opts = $.extend(opts, inOpts); + setup(); + events(); + }; + + return this; + + }; + }(window, jQuery)); \ No newline at end of file diff --git a/web/app/assets/javascripts/sessionList.js b/web/app/assets/javascripts/sessionList.js index c275321ac..5c4ea6de8 100644 --- a/web/app/assets/javascripts/sessionList.js +++ b/web/app/assets/javascripts/sessionList.js @@ -143,7 +143,7 @@ // If no FTUE, show that first. if (!(context.jamClient.FTUEGetStatus())) { app.afterFtue = function() { joinClick(session.id); }; - app.layout.showDialog('ftue'); + app.layout.startNewFtue(); return; } else { joinClick(session.id); diff --git a/web/app/assets/stylesheets/client/account.css.scss b/web/app/assets/stylesheets/client/account.css.scss index 301d62411..341969dac 100644 --- a/web/app/assets/stylesheets/client/account.css.scss +++ b/web/app/assets/stylesheets/client/account.css.scss @@ -151,6 +151,19 @@ margin-right:0; } + .account-audio { + + table.generaltable td { + vertical-align:middle; + } + + .button-orange, .button-grey { + margin-bottom:9px; + } + } + + + .button-grey { margin-right:6px; } diff --git a/web/app/assets/stylesheets/client/ftue.css.scss b/web/app/assets/stylesheets/client/ftue.css.scss index 1a582efc4..958cec7e6 100644 --- a/web/app/assets/stylesheets/client/ftue.css.scss +++ b/web/app/assets/stylesheets/client/ftue.css.scss @@ -42,8 +42,9 @@ div.dialog.ftue .ftue-inner div[layout-wizard-step="3"] { } } +div.dialog.ftue .ftue-inner div[layout-wizard-step="0"] { - +} #ftue-latency-needle { position:absolute; width: 6px; @@ -188,6 +189,7 @@ div.dialog.ftue { } .instructions p.bad { margin-top: -6px; + line-height: 16px; } .instructions p a { font-weight: bold; @@ -383,6 +385,13 @@ div.dialog.ftue { color: darken(#fff, 5); text-decoration: none; } + + .spinner-small { + display:none; + margin-top: 3px; + position: relative; + top: 12px; + } } } diff --git a/web/app/assets/stylesheets/client/jamkazam.css.scss b/web/app/assets/stylesheets/client/jamkazam.css.scss index f4c7d0623..dad78e959 100644 --- a/web/app/assets/stylesheets/client/jamkazam.css.scss +++ b/web/app/assets/stylesheets/client/jamkazam.css.scss @@ -476,6 +476,16 @@ input[type="text"], input[type="password"]{ height:128px; } +.spinner-small { + background-image: url('/assets/shared/spinner.gif'); + background-repeat:no-repeat; + background-position:center; + width:32px; + height:32px; + background-size: 50% 50%; +} + + // disable text selection for the in-session panel, ftue, and arbitrary elements marked with .no-selection-range div[layout-id=session], div[layout-id=ftue], .no-selection-range { -webkit-touch-callout: none; diff --git a/web/app/assets/stylesheets/client/screen_common.css.scss b/web/app/assets/stylesheets/client/screen_common.css.scss index 24a8a0635..7f233ddca 100644 --- a/web/app/assets/stylesheets/client/screen_common.css.scss +++ b/web/app/assets/stylesheets/client/screen_common.css.scss @@ -201,6 +201,14 @@ small, .small {font-size:11px;} cursor:pointer; color:#FC9; text-decoration:none; + + &.disabled { + @extend .button-grey; + } + + &.disabled:hover { + @extend .button-grey:hover; + } } .smallbutton { diff --git a/web/app/assets/stylesheets/easydropdown_jk.css.scss b/web/app/assets/stylesheets/easydropdown_jk.css.scss index e90fba225..ed51ce5a2 100644 --- a/web/app/assets/stylesheets/easydropdown_jk.css.scss +++ b/web/app/assets/stylesheets/easydropdown_jk.css.scss @@ -57,6 +57,7 @@ body.jam { right: 0; bottom: 0; height: 50px; + z-index:-1; box-shadow: inset 0 -50px 30px -35px #fff; } diff --git a/web/app/views/clients/_account_audio_profile.html.erb b/web/app/views/clients/_account_audio_profile.html.erb index d1e5fc947..c16a0394b 100644 --- a/web/app/views/clients/_account_audio_profile.html.erb +++ b/web/app/views/clients/_account_audio_profile.html.erb @@ -15,26 +15,35 @@