Steam = { sm_bInitialized: false, sm_bUserInClient: false, sm_bUserInGameOverlay: false, sm_bUserInTenfootBrowser: false, sm_bUserInMobileChat: false, sm_bUserInMobileApp: false, BIsUserInSteamClient: function() { if ( !Steam.sm_bInitialized ) Steam.Init(); return Steam.sm_bUserInClient; }, BIsUserInGameOverlay: function() { if ( !Steam.sm_bInitialized ) Steam.Init(); return Steam.sm_bUserInGameOverlay }, BIsUserInSteamTenfootBrowser: function() { if ( !Steam.sm_bInitialized ) Steam.Init(); return Steam.sm_bUserInTenfootBrowser; }, BIsUserInClientOrOverlay: function() { if ( !Steam.sm_bInitialized ) Steam.Init(); return Steam.sm_bUserInClient || Steam.sm_bUserInGameOverlay; }, BIsUserInSteamMobileChat: function() { if ( !Steam.sm_bInitialized ) Steam.Init(); return Steam.sm_bUserInMobileChat; }, BIsUserInSteamMobileApp: function() { if ( !Steam.sm_bInitialized ) Steam.Init(); return Steam.sm_bUserInMobileApp; }, GetClientPackageVersion: function() { if ( !Steam.BIsUserInClientOrOverlay() ) return 0; if ( typeof navigator != 'undefined' && navigator.userAgent ) { var matches = navigator.userAgent.match( /Valve Steam [^\/]*\/([0-9]*)/ ); if ( matches && matches.length == 2 ) return matches[1]; } return 0; }, Init: function() { var fnCheckAgent = function( strUAMatch, strURLParam ) { if ( window.location.href.match( '[?&]' + strURLParam + '=' ) ) return true; if ( typeof navigator != 'undefined' && navigator.userAgent && navigator.userAgent.indexOf( strUAMatch ) != -1 ) return true; return false; }; Steam.sm_bUserInTenfootBrowser = fnCheckAgent( 'Valve Steam Tenfoot', 'force_tenfoot_client_view' ); Steam.sm_bUserInGameOverlay = fnCheckAgent( 'Valve Steam GameOverlay', 'force_overlay_view' ); Steam.sm_bUserInClient = Steam.sm_bUserInTenfootBrowser || fnCheckAgent( 'Valve Steam Client', 'force_client_view' ); Steam.sm_bUserInMobileChat = fnCheckAgent( 'Valve Steam Mobile Chat', 'force_mobile_chat_view' ); Steam.sm_bUserInMobileApp = fnCheckAgent( 'Valve Steam App', 'force_mobile_app_view' ); Steam.sm_bInitialized = true; }, LinkInNewWindow: function( $A ) { if ( Steam.BIsUserInSteamClient() && !Steam.BIsUserInSteamTenfootBrowser() ) $A.attr( 'href', 'steam://openurl_external/' + $A.attr('href') ); else $A.attr( 'target', '_blank' ); } }; function OpenFriendChat( steamid, accountid ) { if ( Steam.BIsUserInClientOrOverlay() ) { window.location = 'steam://friends/message/' + steamid; } else if ( Steam.BIsUserInSteamMobileChat() ) { window.location = 'steamchatmobile://friend/' + steamid; } else if ( Steam.BIsUserInSteamMobileApp() ) { window.location = 'https://steamcommunity.com/chat/friend/' + steamid; } else if ( typeof ClientConnectionAPI !== 'undefined' ) { ClientConnectionAPI.OpenFriendChatDialog( steamid ).then( function( result ) { if ( !result.success ) { PromptContinueToWebChat( result, function() { OpenFriendChatInWebChat( steamid, accountid ); }, 'steam://friends/message/' + steamid ); } }); } else { OpenFriendChatInWebChat( steamid, accountid ); } } function OpenFriendChatInWebChat( steamid, accountid ) { LaunchWebChat( { friend: accountid }, {command: 'ShowFriendChatDialog', steamid: steamid} ); } function OpenGroupChat( steamid ) { if ( Steam.BIsUserInSteamMobileChat() ) { window.location = 'steamchatmobile://group/' + steamid; } else if ( Steam.BIsUserInSteamMobileApp() ) { window.location = 'https://steamcommunity.com/chat/group/' + steamid; } else if ( !Steam.BIsUserInClientOrOverlay() && typeof ClientConnectionAPI !== 'undefined' ) { ClientConnectionAPI.OpenFriendChatDialog( steamid ).then( function( result ) { if ( !result.success ) { PromptContinueToWebChat( result, function() { LaunchWebChat( null, {command: 'ShowFriendChatDialog', steamid: steamid} ); }, 'steam://friends/joinchat/' + steamid ); } }); } else { window.location = 'steam://friends/joinchat/' + steamid; } } function PromptContinueToWebChat( result, fnLaunchWebchat, steamURL ) { ShowConfirmDialog( 'Got Steam?', 'We couldn\'t find Steam running on your machine. Would you like to launch web chat?', 'Use web chat' , null, 'Get Steam' ).done( function( choice ) { if ( choice == 'OK' ) fnLaunchWebchat(); else window.location = 'https://store.steampowered.com/about/' }); } // proto functions used to accept an id or an element. // This can be used to migrate them to returning jquery instead of proto-wrapped element function $JFromIDOrElement( elem ) { if ( elem instanceof jQuery ) return elem; else if ( typeof elem == 'string' ) return $J('#' + elem.replace( /\./, '\\.' ) ); else return $J( elem ); } /** Show a popup dialog like confirm(), with two buttons. Clicking ok resolves with done(), cancel or closing the window resolves with fail() * * @param strTitle Title bar text * @param strDescription Message text * @param strOKButton Text to show on OK button (default "OK") * @param strCancelButton Text to show on Cancel button (default "Cancel") * @param strSecondaryActionButton Add a secondary ok button (three buttons total). Resolves with done() like OK but passes 'SECONDARY' as argument to handler * @returns CModal */ function ShowConfirmDialog( strTitle, strDescription, strOKButton, strCancelButton, strSecondaryActionButton, rgModalParams ) { if ( !strOKButton ) strOKButton = 'OK'; if ( !strCancelButton ) strCancelButton = 'Cancel'; var deferred = new jQuery.Deferred(); var fnOK = function() { deferred.resolve( 'OK' ); }; var fnSecondary = function() { deferred.resolve( 'SECONDARY' ); }; var fnCancel = function( bWasCancelButton ) { deferred.reject( bWasCancelButton ); }; var rgButtons = []; var $OKButton = _BuildDialogButton( strOKButton, true ); $OKButton.click( fnOK ); rgButtons.push( $OKButton ); if ( strSecondaryActionButton ) { var $SecondaryActionButton = _BuildDialogButton( strSecondaryActionButton, false, {strClassName: ' btn_darkblue_white_innerfade btn_medium' } ); $SecondaryActionButton.click( fnSecondary ); rgButtons.push( $SecondaryActionButton ); } var $CancelButton = _BuildDialogButton( strCancelButton ); $CancelButton.click( function() { fnCancel( true ); } ); rgButtons.push( $CancelButton ); var Modal = _BuildDialog( strTitle, strDescription, rgButtons, fnCancel, rgModalParams ); Modal.Show(); _BindOnEnterKeyPressForDialog( Modal, deferred, fnOK ); deferred.always( function() { Modal.Dismiss(); } ); // attach the deferred's events to the modal deferred.promise( Modal ); return Modal; } /** Show a dialog with a single button, like alert(). Button click or closing the modal resolve deferred with done(). * * @param strTitle Title bar text * @param strDescription Message text * @param strOKButton Text on the OK button ("OK" by default) * @returns CModal */ function ShowAlertDialog( strTitle, strDescription, strOKButton, rgModalParams ) { if ( !strOKButton ) strOKButton = 'OK'; var deferred = new jQuery.Deferred(); var fnOK = function( bWasCancelButton ) { deferred.resolve( bWasCancelButton ); }; var $OKButton = _BuildDialogButton( strOKButton ); $OKButton.click( function() { fnOK( true ); } ); var Modal = _BuildDialog( strTitle, strDescription, [ $OKButton ], fnOK, rgModalParams ); deferred.always( function() { Modal.Dismiss(); } ); Modal.Show(); _BindOnEnterKeyPressForDialog( Modal, deferred, fnOK ); // attach the deferred's events to the modal deferred.promise( Modal ); return Modal; } /** Show a popup dialog. Has no buttons. Closing the dialog resolves deferred with done(). * * @param strTitle Title bar text * @param strDescription Message text * @param rgModalParams See CModal * @returns CModal */ function ShowDialog( strTitle, strDescription, rgModalParams ) { var deferred = new jQuery.Deferred(); var fnOK = function() { deferred.resolve(); }; var Modal = _BuildDialog( strTitle, strDescription, [], fnOK, rgModalParams ); deferred.always( function() { Modal.Dismiss(); } ); Modal.Show(); // attach the deferred's events to the modal deferred.promise( Modal ); return Modal; } /** * @returns CModal */ function ShowPromptDialog( strTitle, strDescription, strOKButton, strCancelButton, rgModalParams, defaultValue ) { if ( !strOKButton ) strOKButton = 'OK'; if ( !strCancelButton ) strCancelButton = 'Cancel'; var $Body = $J('
'); var $Input = $J('', {type: 'text', 'class': '' } ).val( defaultValue ); if ( rgModalParams && rgModalParams.inputMaxSize ) { $Input.attr( 'maxlength', rgModalParams.inputMaxSize ); } $Body.append( $J('', {'class': 'newmodal_prompt_description' } ).append( strDescription ) ); $Body.append( $J('', {'class': 'newmodal_prompt_input gray_bevel for_text_input fullwidth' } ).append( $Input ) ); var deferred = new jQuery.Deferred(); var fnOK = function() { deferred.resolve( $Input.val() ); }; var fnCancel = function() { deferred.reject(); }; $Body.submit( function( event ) { event.preventDefault(); fnOK(); } ); var elButtonLabel = $J( '' ).text( strOKButton ); var $OKButton = $J('', {type: 'submit', 'class': 'btn_green_white_innerfade btn_medium' } ).append( elButtonLabel ); $OKButton.click( fnOK ); var $CancelButton = _BuildDialogButton( strCancelButton ); $CancelButton.click( fnCancel ); var Modal = _BuildDialog( strTitle, $Body, [ $OKButton, $CancelButton ], fnCancel ); if( !rgModalParams || !rgModalParams.bNoPromiseDismiss ) deferred.always( function() { Modal.Dismiss(); } ); Modal.Show(); $Input.focus(); // attach the deferred's events to the modal deferred.promise( Modal ); return Modal; } /** * @returns CModal */ function ShowPromptWithTextAreaDialog( strTitle, strInitialText, strOKButton, strCancelButton, textAreaMaxLength, strDescription ) { if ( !strOKButton ) strOKButton = 'OK'; if ( !strCancelButton ) strCancelButton = 'Cancel'; var $Body = $J(''); var $TextArea = $J('', { 'class': 'newmodal_prompt_textarea' } ); $TextArea.text( strInitialText ); if ( textAreaMaxLength ) { $TextArea.attr( 'maxlength', textAreaMaxLength ); $TextArea.bind( "keyup change", function() { var str = $J(this).val(); var mx = parseInt($J(this).attr('maxlength')); if (str.length > mx) { $J(this).val(str.substr(0, mx)); return false; } } ); } if ( strDescription ) { $Body.append( $J('', {'class': 'newmodal_prompt_with_textarea newmodal_prompt_description' } ).text( strDescription ) ); } $Body.append( $J('', {'class': 'newmodal_prompt_with_textarea gray_bevel fullwidth' } ).append( $TextArea ) ); var deferred = new jQuery.Deferred(); var fnOK = function() { deferred.resolve( $TextArea.val() ); }; var fnCancel = function() { deferred.reject(); }; $Body.submit( function( event ) { event.preventDefault(); fnOK(); } ); var elButtonLabel = $J( '' ).text( strOKButton ); var $OKButton = $J('', {type: 'submit', 'class': 'btn_green_white_innerfade btn_medium' } ).append( elButtonLabel ); $OKButton.click( fnOK ); var $CancelButton = _BuildDialogButton( strCancelButton ); $CancelButton.click( fnCancel ); var Modal = _BuildDialog( strTitle, $Body, [ $OKButton, $CancelButton ], fnCancel ); deferred.always( function() { Modal.Dismiss(); } ); Modal.Show(); $TextArea.focus(); // attach the deferred's events to the modal deferred.promise( Modal ); return Modal; } /** * @returns CModal */ function ShowEditablePrompt( strTitle, obj, onOk, onCancel) { strEditButton = 'Edit'; strOKButton = 'OK'; strCancelButton = 'Cancel'; var $Body = $J(''); var $TextArea = $J('', {'class': 'newmodal_prompt_textarea', 'id': 'json_window'}); $TextArea[0].readOnly = true; $TextArea.text( JSON.stringify(obj, null, 2) ); $Body.append( $J('', {'class': 'newmodal_prompt_with_textarea gray_bevel fullwidth ' } ).append( $TextArea ) ); $Body.submit( function( event ) { event.preventDefault(); fnOK(); } ); var elButtonLabel = $J( '' ).text( strOKButton ); var deferredAction = new jQuery.Deferred(); var editButtonLabel = $J( '' ).text( strEditButton ); var $EditButton = $J('', {type: 'button', 'class': 'btn_darkred_white_innerfade btn_medium' } ).append( editButtonLabel ); $EditButton.click( function() { $EditButton[0].disable(); $OKButton[0].enable(); $TextArea[0].readOnly = false; $TextArea.focus(); }) var $OKButton = $J('', {type: 'submit', 'class': 'btn_green_white_innerfade btn_medium' } ).append( elButtonLabel ); $OKButton.click( function() { deferredAction.resolve( $TextArea.val() ); } ); $OKButton[0].disabled = true; fnCancel = function() { deferredAction.reject(); }; var $CancelButton = _BuildDialogButton( strCancelButton ); $CancelButton.click( fnCancel ); var Modal = _BuildDialog( strTitle, $Body, [ $EditButton, $OKButton, $CancelButton ], fnCancel ); deferredAction.always( function() { Modal.Dismiss(); } ); if (onOk) deferredAction.done(onOk); if (onCancel) deferredAction.fail(onCancel); Modal.Show(); $TextArea.focus(); // attach the deferred's events to the modal deferredAction.promise( Modal ); return Modal; } /** * @returns CModal */ function ShowBlockingWaitDialog( strTitle, strDescription ) { var deferred = new jQuery.Deferred(); var fnOK = function() { deferred.resolve(); }; var container = $J('', {'class': 'waiting_dialog_container'} ); var throbber = $J('', {'class': 'waiting_dialog_throbber'} ); container.append( throbber ); container.append( strDescription ); var Modal = _BuildDialog( strTitle, container, [], fnOK, { bExplicitDismissalOnly: true } ); deferred.always( function() { Modal.Dismiss(); } ); Modal.Show(); // attach the deferred's events to the modal deferred.promise( Modal ); return Modal; } function _BindOnEnterKeyPressForDialog( Modal, deferred, fnOnEnter ) { var fnOnKeyUp = function( event ) { if ( Modal.BIsActiveModal() && !event.isDefaultPrevented() && event.which == 13 && ( !event.target || event.target.nodeName != 'TEXTAREA' ) ) fnOnEnter(); }; $J(document).on( 'keyup.SharedConfirmDialog', fnOnKeyUp ); deferred.always( function() { $J(document).off( 'keyup.SharedConfirmDialog' ); } ); } /** * @returns CModal * @private */ function _BuildDialog( strTitle, strDescription, rgButtons, fnOnCancel, rgModalParams ) { var $Dialog = $J('', {'class': 'newmodal'} ); var $CloseButton = $J('', {'class': 'newmodal_close', 'data-panel': '{"focusable":true,"clickOnActivate":true}' } ); var $Header = ( $J('', {'class': 'newmodal_header' }) ); var $TopBar = ( $J('', {'class': 'modal_top_bar' }) ); if ( strTitle ) $Header.append( $CloseButton ).append( $J('', {'class': 'title_text' } ).text( strTitle ) ); if ( rgModalParams && rgModalParams.strSubTitle ) { var $SubTitle = (rgModalParams.strSubTitle); $Header.append( $J('', {'class': 'subtitle_text' } ).text( $SubTitle ) ) } $Header = $J('', {'class': 'newmodal_header_border'}).append( $Header ); $Dialog.append( $TopBar ).append( $Header ); var $Content = $J('', {'class': 'newmodal_content' } ); $Content.append( $J('').append( strDescription ) ); if ( rgButtons.length > 0 ) { var $Buttons = $J('', {'class': 'newmodal_buttons', 'data-panel': '{"flow-children":"row"}' } ); $Content.append( $Buttons ); for( var i = 0; i < rgButtons.length; i++ ) { var $Button = rgButtons[i]; if ( i == 0 ) $Button.attr( 'data-panel', '{"autoFocus":true,"focusable":true,"clickOnActivate":true}' ); else $Button.attr( 'data-panel', '{"focusable":true,"clickOnActivate":true}' ); $Buttons.append( $Button ); } } $Dialog.append( $J('', {'class': 'newmodal_content_border' } ).append( $Content ) ); if ( rgModalParams && rgModalParams.bExplicitDismissalOnly ) $CloseButton.hide(); var Modal = new CModal( $Dialog, rgModalParams ); if ( fnOnCancel ) { Modal.OnDismiss( fnOnCancel ); $CloseButton.click( function() { Modal.Dismiss(); } ); } // on responsive pages, the 'newmodal' element covers the whole viewable area (so that we can control scrolling // if the modal is very tall). If the modal doesn't cover the whole area, we dismiss on clicks to this background // area if ( Modal.m_fnBackgroundClick ) { $Dialog.click( function(e) { if ( e.target == this ) Modal.m_fnBackgroundClick(); } ); } Modal.SetRemoveContentOnDismissal( true ); return Modal; } function _BuildDialogButton( strText, bActive, rgOptions ) { if ( !rgOptions ) rgOptions = {}; var strClassName = bActive ? 'btn_green_steamui btn_medium' : 'btn_grey_steamui btn_medium'; if ( rgOptions.strClassName ) strClassName = rgOptions.strClassName; var elButtonLabel = $J( '' ).html( strText ); var elButton = $J('', {'class': strClassName } ).append( elButtonLabel ); return elButton; } /** Implemented for Gamepad: show content in a fullscreen, borderless, modal which gets navigation support. * Used on the app details page to show screenshots in full screen. * Closing the dialog resolves deferred with done(). * * @returns CModal */ function GPShowFullScreenModal( content ) { var deferred = new jQuery.Deferred(); var fnOK = function() { deferred.resolve(); }; var Modal = _BuilGPFullScreenModal( content, fnOK ); deferred.always( function() { Modal.Dismiss(); } ); Modal.Show(); // attach the deferred's events to the modal deferred.promise( Modal ); return Modal; } function _BuilGPFullScreenModal( content, fnOnCancel ) { var $Dialog = $J('').append( $J('', {'style': 'display:flex'} ).append( content ) ); var Modal = new CModal( $Dialog ); Modal.SetRemoveContentOnDismissal( true ); if ( fnOnCancel ) { Modal.OnDismiss( fnOnCancel ); } return Modal; } /* modal params: bExplicitDismissalOnly - by default, clicking outside of the modal dismisses it. Set this to true to override that behavior bIgnoreResizeEvents - don't resize the modal when the window resizes */ function CModal( $Content, rgParams ) { rgParams = rgParams || {}; this.m_$Content = $Content; this.m_bVisible = false; this.m_bIgnoreResizeEvents = rgParams.bIgnoreResizeEvents; this.m_fnSizing = null; this.m_fnBackgroundClick = null; this.m_fnOnResize = null; this.m_bDismissOnBackgroundClick = !rgParams.bExplicitDismissalOnly; this.m_nMaxWidth = rgParams.nMaxWidth || 0; this.m_nMaxHeight = rgParams.nMaxHeight || 0; this.m_fnOnDismiss = null; this.m_fnGPOnCloseModal = null; this.m_bRemoveContentOnDismissal = false; this.m_nInitialOffsetTop = $J(window).scrollTop(); this.m_nInitialOffsetLeft = $J(window).scrollLeft(); this.m_$Content.css( 'position', 'fixed' ); this.m_$Content.css( 'z-index', 1000 ); /* default gamepad behavior is B button closes the dialog */ if ( !this.m_$Content.attr('panel' ) ) this.m_$Content.attr( 'data-panel', '{"onCancelButton":"CModal.DismissActiveModal()"}' ); this.m_$StandardContent = null; this.m_$SizedContent = null; this.OnContentChanged(); //this will look for StandardContent and SizedContent in the modal body var _modal = this; this.m_fnBackgroundClick = function() { if ( _modal.BIsActiveModal() && _modal.m_bDismissOnBackgroundClick ) { _modal.Dismiss(); } }; this.m_fnOnEscapeKeyPress = function( event ) { if ( _modal.BIsActiveModal() && event.which == 27 ) _modal.m_fnBackgroundClick(); }; this.m_fnSizing = function() { _modal.AdjustSizing(); }; /* make sure the content is parented correctly */ $J(document.body).append( this.m_$Content ); } CModal.prototype.OnDismiss = function( fn ) { this.m_fnOnDismiss = fn; }; CModal.prototype.OnResize = function( fn ) { this.m_fnOnResize = fn; }; /** * @returns jQuery */ CModal.prototype.GetContent = function () { return this.m_$Content; }; CModal.prototype.GetBoundOnResizeEvent = function() { // in case someone outside needs to tell the modal to resize on certain events (eg images or iframes loading in the modal) return this.m_fnSizing; }; CModal.prototype.OnContentChanged = function() { // make sure we're holding the right elements this.m_$StandardContent = this.m_$Content.find( '.newmodal_content' ); if ( !this.m_$StandardContent.length ) this.m_$StandardContent = this.m_$Content; this.m_$SizedContent = this.m_$Content.find( '.newmodal_sized_content' ); }; CModal.prototype.SetRemoveContentOnDismissal = function ( bRemoveContent ) { this.m_bRemoveContentOnDismissal = bRemoveContent; }; CModal.prototype.SetDismissOnBackgroundClick = function ( bDismissOnBackgroundClick ) { this.m_bDismissOnBackgroundClick = bDismissOnBackgroundClick; }; CModal.prototype.SetMaxWidth = function ( nMaxWidth ) { this.m_nMaxWidth = nMaxWidth; if ( this.m_bVisible ) this.AdjustSizing(); }; CModal.prototype.SetMaxHeight = function ( nMaxHeight ) { this.m_nMaxHeight = nMaxHeight; if ( this.m_bVisible ) this.AdjustSizing(); }; CModal.prototype.AdjustSizing = function( duration ) { if ( !this.m_$Content ) return; var bResponsiveScreen = window.UseTouchFriendlyMode && UseTouchFriendlyMode(); var bUseTabletScreenMode = window.UseTabletScreenMode && window.UseTabletScreenMode(); var nViewportWidth = document.compatMode === 'BackCompat' ? document.body.clientWidth : $J(window).width(); var nViewportHeight = document.compatMode === 'BackCompat' ? document.body.clientHeight : $J(window).height(); var nMaxWidth = Math.max( nViewportWidth - ( bResponsiveScreen? 24 : 80 ), bResponsiveScreen ? 200 : 500 ); var nMaxHeight = Math.floor( nViewportHeight - 120 ); if ( this.m_nMaxWidth && nMaxWidth > this.m_nMaxWidth ) { nMaxWidth = this.m_nMaxWidth; } if ( this.m_nMaxHeight && nMaxHeight > this.m_nMaxHeight ) { nMaxHeight = this.m_nMaxHeight; } // if the modal has a 'newmodal_sized_content' div, it wants to be the max height, so set it now // before we compute height ( "- 18" is a fudge for a possible horizontal scrollbar ) this.m_$SizedContent.css( 'min-height', ( nMaxHeight - 18 ) + 'px' ); if ( this.m_fnOnResize ) { this.m_fnOnResize( nMaxWidth - 40, nMaxHeight ); } if ( !duration ) { // set sizes right away so we can calculate a good left and top this.m_$Content.css( 'max-width', nMaxWidth + 'px' ); if ( !bResponsiveScreen ) { this.m_$StandardContent.css( 'max-height', nMaxHeight + 'px' ); } else { this.m_$StandardContent.css( 'max-height', '' ); } } var nContentWidth = this.m_$Content.width(); var nContentHeight = this.m_$Content.height(); var nLeft = Math.max( Math.floor( ( nViewportWidth - nContentWidth ) / 2 ), 12 ); var nTop = Math.max( Math.floor( ( nViewportHeight - nContentHeight ) / 2 ), 12 ); // only use absolute position on mobile screens if ( bResponsiveScreen && !bUseTabletScreenMode ) { nLeft += this.m_nInitialOffsetLeft; nTop += this.m_nInitialOffsetTop; this.m_$Content.css( 'position', 'absolute' ); } else { this.m_$Content.css( 'position', 'fixed' ); } if ( duration ) { this.m_$Content.animate( { 'max-width': nMaxWidth, left: nLeft, top: nTop }, duration ); this.m_$StandardContent.animate( {'max-height': nMaxHeight }, duration ); } else { this.m_$Content.css( 'left', nLeft ); this.m_$Content.css( 'top', nTop ); } }; CModal.prototype.Show = function() { if ( this.m_bVisible ) return; CModal.ShowModalBackground(); if ( !this.m_bIgnoreResizeEvents ) { $J(window).on( 'resize', null, this.m_fnSizing ); } CModal.s_$Background.on( 'click.CModal', this.m_fnBackgroundClick ); $J(document).on( 'keyup.CModal', this.m_fnOnEscapeKeyPress ); this.AdjustSizing(); // if we're in gamepad, notify gamepad navigation of the modal if ( typeof GPOnShowingModalWindow === "function" ) this.m_fnGPOnCloseModal = GPOnShowingModalWindow( this.m_$Content.get( 0 ) ); this.m_$Content.show(); // resize as any child image elements load in. this.m_$Content.find('img').load( this.m_fnSizing ); this.m_bVisible = true; CModal.PushActiveModal( this ); var _this = this; if ( typeof GPNavFocusChild !== 'undefined' ) window.setTimeout( function() { GPNavFocusChild( _this.m_$Content ) }, 1 ); }; CModal.prototype.Dismiss = function() { if ( !this.m_bVisible ) return; this.m_bVisible = false; // tell gamepad navigation we're closing this modal if ( this.m_fnGPOnCloseModal ) { this.m_fnGPOnCloseModal(); this.m_fnGPOnCloseModal = null; } this.m_$Content.hide(); if ( !this.m_bIgnoreResizeEvents ) { $J(window).off( 'resize', null, this.m_fnSizing ); } if ( this.m_fnOnDismiss ) this.m_fnOnDismiss(); if ( this.m_bRemoveContentOnDismissal ) { this.m_$Content.remove(); this.m_$Content = null; } CModal.PopActiveModal( this ); if ( !CModal.s_rgModalStack.length ) { CModal.s_$Background.off( 'click.CModal', this.m_fnBackgroundClick ); $J(document).off( 'keyup.CModal', this.m_fnOnEscapeKeyPress ); CModal.HideModalBackground(); } }; CModal.prototype.BIsActiveModal = function() { return CModal.s_rgModalStack.length && CModal.s_rgModalStack[ CModal.s_rgModalStack.length - 1 ] == this; }; /* static */ CModal.ShowModalBackground = function() { if ( !CModal.s_$Background ) { CModal.s_$Background = $J('', {'class': 'newmodal_background'}); CModal.s_$Background.css( 'opacity', 0 ); $J(document.body).append( CModal.s_$Background ); } CModal.s_$Background.stop(); // stop running animations CModal.s_$Background.fadeTo( 200, 0.8 ); }; CModal.HideModalBackground = function() { if ( CModal.s_$Background ) { CModal.s_$Background.stop(); // stop running animations CModal.s_$Background.fadeOut( 200, 0 ); } }; CModal.s_rgModalStack = []; CModal.GetActiveModal = function() { if ( CModal.s_rgModalStack.length ) return CModal.s_rgModalStack[CModal.s_rgModalStack.length-1]; else return null; }; CModal.DismissActiveModal = function() { if ( CModal.s_rgModalStack.length ) CModal.s_rgModalStack[CModal.s_rgModalStack.length-1].Dismiss(); }; CModal.PushActiveModal = function( Modal ) { for ( var i = 0; i < CModal.s_rgModalStack.length; i++ ) { // push below background CModal.s_rgModalStack[i].m_$Content.css( 'z-index', 899 ); } CModal.s_rgModalStack.push( Modal ); }; CModal.PopActiveModal = function( Modal ) { for ( var i = 0; i < CModal.s_rgModalStack.length; i++ ) { if ( CModal.s_rgModalStack[i] == Modal ) { CModal.s_rgModalStack.splice( i, 1 ); break; } } if ( CModal.s_rgModalStack.length ) CModal.s_rgModalStack[ CModal.s_rgModalStack.length - 1 ].m_$Content.css( 'z-index', 1000 ); }; // this will set the right headers for a cross-domain request to community function GetDefaultCommunityAJAXParams( path, method ) { var rgParams = { url: 'https://steamcommunity.com/' + path }; if ( method ) rgParams.type = method; // if this js file was hosted off the store, add CORS request headers if ( window.location.href.indexOf( 'https://steamcommunity.com/' ) != 0 ) { rgParams.crossDomain = true; rgParams.xhrFields = { withCredentials: true }; } return rgParams; } function CrossDomainPost( url, params ) { return $J.ajax( { type: 'post', url: url, data: params, crossDomain: true, xhrFields: { withCredentials: true } } ); } // spped of the miniprofile fading in and out var MINIPROFILE_ANIM_SPEED = 150; // how long the mouse must remain over an element before we'll make an AJAX call var MINIPROFILE_DELAY_BEFORE_AJAX = 100; // the delay before we'll show the hover, must be longer than DELAY_BEFORE_AJAX var MINIPROFILE_DELAY_BEFORE_SHOW = 250; function CDelayedAJAXData( strURL, msDelayBeforeAJAX ) { this.m_$Data = null; this.m_bAJAXFailed = false; this.m_timerDelayedAJAX = null; this.m_bAJAXRequestMade = false; this.m_msDelayBeforeAJAX = msDelayBeforeAJAX; this.m_strURL = strURL; this.m_fnOnAJAXComplete = null; } CDelayedAJAXData.prototype.GetAJAXParams = function() { return GetDefaultCommunityAJAXParams( this.m_strURL, 'GET' ); }; CDelayedAJAXData.prototype.QueueAjaxRequestIfNecessary = function() { if ( !this.m_$Data && !this.m_bAJAXRequestMade ) { var _this = this; this.m_timerDelayedAJAX = window.setTimeout( function() { _this.m_timerDelayedAJAX = null; _this.m_bAJAXRequestMade = true; var rgAJAXParams = _this.GetAJAXParams(); $J.ajax( rgAJAXParams ) .done( function(data) { _this.m_$Data = $J(data); if ( _this.m_fnOnAJAXComplete ) _this.m_fnOnAJAXComplete(); }).fail( function() { _this.m_bAJAXFailed = true; }); }, this.m_msDelayBeforeAJAX ); } }; CDelayedAJAXData.prototype.CancelAJAX = function() { if ( this.m_timerDelayedAJAX ) window.clearTimeout( this.m_timerDelayedAJAX ); this.m_fnOnAJAXComplete = null; }; CDelayedAJAXData.prototype.RunWhenAJAXReady = function( fnOnReady ) { if ( this.m_$Data ) fnOnReady(); else if ( !this.m_bAJAXFailed ) { this.m_fnOnAJAXComplete = fnOnReady; this.QueueAjaxRequestIfNecessary(); } // if ajax failed we will not call fnOnReady }; CDelayedAJAXData.prototype.Show = function( $HoverContent ) { $HoverContent.children().detach(); $HoverContent.append( this.m_$Data ); }; // time the cookie preferences popup waits before appearing var COOKIE_PREFERENCES_POPUP_DELAY = 4000; function InitCookiePreferencesPopup() { var $popupDialog = $J( '#cookiePrefPopup' ); if ( $popupDialog.length == 0 ) { $AllowURL = encodeURI( 'https://store.steampowered.com/' + 'account/ajaxsetcookiepreferences'); var $CPopupContent = $J( "