@ -1,7 +1,7 @@
// ==UserScript==
// @name Better xCloud
// @namespace https://github.com/redphx
// @version 3.2.4
// @version 3.3.0
// @description Improve Xbox Cloud Gaming (xCloud) experience
// @author redphx
// @license MIT
@ -14,12 +14,23 @@
// ==/UserScript==
'use strict' ;
const SCRIPT _VERSION = '3.2.4' ;
/* ADDITIONAL CODE */
const SCRIPT _VERSION = '3.3.0' ;
const SCRIPT _HOME = 'https://github.com/redphx/better-xcloud' ;
const ENABLE _XCLOUD _LOGGER = false ;
const ENABLE _PRELOAD _BX _UI = false ;
const USE _DEV _TOUCH _LAYOUT = false ;
// Setup flags
const DEFAULT _FLAGS = {
PreloadUi : false ,
EnableXcloudLogging : false ,
UseDevTouchLayout : false ,
}
const BX _FLAGS = Object . assign ( DEFAULT _FLAGS , window . BX _FLAGS || { } ) ;
delete window . BX _FLAGS ;
const AppInterface = window . AppInterface ;
let REMOTE _PLAY _SERVER ;
@ -89,7 +100,7 @@ const BxEvent = {
}
}
window . AppInterface && window . AppInterface . onEvent ( eventName ) ;
AppInterface && AppInterface . onEvent ( eventName ) ;
target . dispatchEvent ( event ) ;
} ,
} ;
@ -558,7 +569,7 @@ const Translations = {
"Cancel" ,
"Cancelar" ,
,
,
"Cancella" ,
"キャンセル" ,
"취소" ,
"Anuluj" ,
@ -779,7 +790,7 @@ const Translations = {
"Controller shortcuts" ,
"Habilitar atajos del Joystick" ,
,
,
"Abilita scrociatorie da controller" ,
"コントローラーショートカット" ,
,
"Skróty kontrolera" ,
@ -881,7 +892,7 @@ const Translations = {
"Delete" ,
"Borrar" ,
,
,
"Elimina" ,
"削除" ,
"삭제" ,
"Usuń" ,
@ -1041,7 +1052,7 @@ const Translations = {
"Desconectado" ,
"Отключен" ,
"Bağlı değil" ,
"Роз’ єднано" ,
"Від' єднано" ,
"Đã ngắt kết nối" ,
"已断开连接" ,
] ,
@ -1051,7 +1062,7 @@ const Translations = {
"Edit" ,
"Editar" ,
,
,
"Modifica" ,
"編集" ,
"편집" ,
"Edytuj" ,
@ -1136,7 +1147,7 @@ const Translations = {
"Emulate controller with Mouse & Keyboard" ,
"Emular mandos con teclado y ratón" ,
,
"Abilitare il supporto di mouse e tastiera" ,
"Abilita il supporto per mouse e tastiera" ,
"マウス&キーボード操作をコントローラー化" ,
"마우스 & 키보드 활성화" ,
"Emuluj kontroler za pomocą myszy i klawiatury" ,
@ -1179,7 +1190,7 @@ const Translations = {
"\"Uzaktan Oynama\" özelliğini aktive et" ,
"Увімкнути функцію \"Remote Play\"" ,
"Bật tính năng \"Chơi Từ Xa\"" ,
"启用\"远程播放\"功能 " ,
"启用\"Remote Play\"主机串流 " ,
] ,
"enable-volume-control" : [
"Lautstärkeregelung aktivieren" ,
@ -1272,7 +1283,7 @@ const Translations = {
"Fortnite: force console version" ,
"Fortnite: forzar versión de consola" ,
,
,
"Fortnite: Foza la versione console" ,
"Fortnite: 強制的にコンソール版を起動する" ,
,
"Fortnite: wymuś wersję konsolową" ,
@ -1394,10 +1405,10 @@ const Translations = {
,
"Android用のBetter xCloudをインストール" ,
,
,
"Zainstaluj aplikację Better xCloud na Androida" ,
"Instalar o aplicativo Better xCloud para Android" ,
"Установите приложение Better xCloud для Android" ,
,
"Better xCloud'un Android uygulaması nı indir" ,
"Встановити додаток Better xCloud для Android" ,
"Cài đặt ứng dụng Better xCloud cho Android" ,
"安装Better xCloud安卓客户端" ,
@ -1612,7 +1623,7 @@ const Translations = {
"You may also need to adjust the in-game sensitivity & deadzone settings" ,
"También puede que necesites ajustar la sensibilidad del juego y la configuración de la zona muerta" ,
,
,
"Potrebbe anche essere necessario regolare le impostazioni della sensibilità e deadzone del gioco" ,
"ゲーム内の設定で感度とデッドゾーンの調整が必要な場合があります" ,
,
"Może być również konieczne dostosowanie czułości w grze i ustawienia 'martwej strefy' urządzenia" ,
@ -1629,7 +1640,7 @@ const Translations = {
"Click to activate" ,
"Haz clic para activar" ,
,
,
"Fare clic per attivare" ,
"マウスクリックで開始" ,
,
"Kliknij, aby aktywować" ,
@ -1646,7 +1657,7 @@ const Translations = {
"Using this feature when playing online could be viewed as cheating" ,
"Usar esta función al jugar en línea podría ser visto como trampas" ,
,
,
"L'utilizzo di questa funzione quando si gioca online potrebbe essere considerato un baro" ,
"オンラインプレイでこの機能を使用すると不正行為と判定される可能性があります" ,
,
"Używanie tej funkcji podczas grania online może być postrzegane jako oszukiwanie" ,
@ -1850,7 +1861,7 @@ const Translations = {
"Playing" ,
"Jugando" ,
,
,
"Installa l'applicazione Better xCloud per Android" ,
"プレイ中" ,
"플레이 중" ,
"W grze" ,
@ -2097,7 +2108,7 @@ const Translations = {
"Uzaktan Bağlanma" ,
"Віддалена г р а " ,
"Chơi Từ Xa" ,
"远程游玩 " ,
"远程串流 " ,
] ,
"rename" : [
"Umbenennen" ,
@ -2269,6 +2280,23 @@ const Translations = {
"Lưu" ,
"保存" ,
] ,
"screenshot-apply-filters" : [
,
,
"Applies video filters to screenshots" ,
,
,
,
"スクリーンショットにビデオフィルターを適用" ,
,
,
,
"Применяет фильтры видео к скриншотам" ,
,
"Застосовує відеофільтри до знімків екрана" ,
"Áp dụng hiệu ứng video vào ảnh chụp màn hình" ,
,
] ,
"screenshot-button-position" : [
"Position des Screenshot-Buttons" ,
"Posisi tombol Screenshot" ,
@ -2282,7 +2310,7 @@ const Translations = {
"Posição do botão de captura de tela" ,
"Расположение кнопки скриншота" ,
"Ekran görüntüsü düğmesi konumu" ,
"Позиція кнопки скриншоту " ,
"Позиція кнопки знімка екрана " ,
"Vị trí của nút Chụp màn hình" ,
"截图按钮位置" ,
] ,
@ -2299,7 +2327,7 @@ const Translations = {
"Separar o Controle por Toque e o Controle #1" ,
"Раздельный сенсорный контроллер и контроллер #1" ,
"Dokunmatik kumandayı ve birincil kumandayı ayrı tut" ,
"О кремо Сенсорний контролер та Контролер #1" ,
"Відо кремити Сенсорний контролер та Контролер #1" ,
"Tách biệt Bộ điều khiển cảm ứng và Tay cầm #1" ,
"虚拟摇杆和手柄分别控制不同角色" ,
] ,
@ -2768,7 +2796,7 @@ const Translations = {
"Swap buttons" ,
"Intercambiar botones" ,
,
,
"Inverti i pulsanti" ,
"ボタン入れ替え" ,
"버튼 바꾸기" ,
"Zamień przyciski" ,
@ -2945,7 +2973,7 @@ const Translations = {
"Superior-centralizado" ,
"Сверху" ,
"Orta üst" ,
"Зверху прав о р у ч " ,
"Зверху по цент р у " ,
"Chính giữa phía trên" ,
"顶部居中" ,
] ,
@ -2989,7 +3017,7 @@ const Translations = {
"Touch control layout" ,
"Diseño de control táctil" ,
,
,
"Controller Touch" ,
"タッチコントロールレイアウト" ,
,
"Układ sterowania dotykowego" ,
@ -3142,7 +3170,7 @@ const Translations = {
"Vertical sensitivity" ,
"Sensibilidad Vertical" ,
,
,
"Sensibilità Verticale" ,
"上下方向の感度" ,
,
"Czułość pionowa" ,
@ -3302,7 +3330,7 @@ const Translations = {
"Tempo estimado de conclusão" ,
"Примерное время запуска" ,
"Tahminî bitiş süresi" ,
"Розрахунк овий час завершення" ,
"Орієнт овн ий час завершення" ,
"Thời gian hoàn thành dự kiến" ,
"预计等待时间" ,
] ,
@ -3960,7 +3988,7 @@ class LoadingScreen {
LoadingScreen . # orgWebTitle && ( document . title = LoadingScreen . # orgWebTitle ) ;
LoadingScreen . # $waitTimeBox && LoadingScreen . # $waitTimeBox . classList . add ( 'bx-gone' ) ;
if ( LoadingScreen . # $bgStyle ) {
if ( getPref ( Preferences . UI _LOADING _SCREEN _GAME _ART ) && LoadingScreen. # $bgStyle ) {
const $rocketBg = document . querySelector ( '#game-stream rect[width="800"]' ) ;
$rocketBg && $rocketBg . addEventListener ( 'transitionend' , e => {
LoadingScreen . # $bgStyle . textContent += `
@ -4091,7 +4119,7 @@ class TouchController {
return ;
}
const baseUrl = ` https://raw.githubusercontent.com/redphx/better-xcloud/gh-pages/touch-layouts ${ USE _DEV _TOUCH _LAYOUT ? '/dev' : '' } ` ;
const baseUrl = ` https://raw.githubusercontent.com/redphx/better-xcloud/gh-pages/touch-layouts ${ BX _FLAGS . UseDevTouchLayout ? '/dev' : '' } ` ;
const url = ` ${ baseUrl } / ${ xboxTitleId } .json ` ;
// Get layout info
@ -6191,7 +6219,7 @@ class VibrationManager {
static # playDeviceVibration ( data ) {
// console.log(+new Date, data);
if ( ' AppInterface' in window ) {
if ( AppInterface) {
AppInterface . vibrate ( JSON . stringify ( data ) , window . BX _VIBRATION _INTENSITY ) ;
return ;
}
@ -6917,13 +6945,22 @@ class UserAgent {
}
static spoof ( ) {
let newUserAgent ;
const profile = getPref ( Preferences . USER _AGENT _PROFILE ) ;
if ( profile === UserAgent . PROFILE _DEFAULT ) {
// Fix Kiwi 124
if ( window . navigator . userAgent . includes ( 'Chrome/124.0.0.0' ) ) {
newUserAgent = window . navigator . userAgent . replace ( 'Chrome/124.0.0.0' , 'Chrome/122.0.0.0' )
} else {
return ;
}
const defaultUserAgent = window . navigator . userAgent ;
const userAgent = UserAgent . get ( profile ) || defaultUserAgent ;
}
if ( ! newUserAgent ) {
newUserAgent = UserAgent . get ( profile ) || defaultUserAgent ;
}
// Clear data of navigator.userAgentData, force xCloud to detect browser based on navigator.userAgent
Object . defineProperty ( window . navigator , 'userAgentData' , { } ) ;
@ -6931,10 +6968,10 @@ class UserAgent {
// Override navigator.userAgent
window . navigator . orgUserAgent = window . navigator . userAgent ;
Object . defineProperty ( window . navigator , 'userAgent' , {
value : u serAgent,
value : newU serAgent,
} ) ;
return u serAgent;
return newU serAgent;
}
}
@ -7011,6 +7048,8 @@ class Preferences {
static get MKB _DEFAULT _PRESET _ID ( ) { return 'mkb_default_preset_id' ; }
static get SCREENSHOT _BUTTON _POSITION ( ) { return 'screenshot_button_position' ; }
static get SCREENSHOT _APPLY _FILTERS ( ) { return 'screenshot_apply_filters' ; }
static get BLOCK _TRACKING ( ) { return 'block_tracking' ; }
static get BLOCK _SOCIAL _FEATURES ( ) { return 'block_social_features' ; }
static get SKIP _SPLASH _VIDEO ( ) { return 'skip_splash_video' ; }
@ -7155,13 +7194,14 @@ class Preferences {
}
}
if ( hasLow Codec ) {
if ( ! hasNormal Codec && ! hasHigh Codec ) {
options . default = ` ${ t ( 'visual-quality-low ' ) } ( ${ t ( 'default' ) } ) ` ;
if ( hasHigh Codec ) {
if ( ! hasLow Codec && ! hasNormal Codec ) {
options . default = ` ${ t ( 'visual-quality-high ' ) } ( ${ t ( 'default' ) } ) ` ;
} else {
options . low = t ( 'visual-quality-low ' ) ;
options . high = t ( 'visual-quality-high ' ) ;
}
}
if ( hasNormalCodec ) {
if ( ! hasLowCodec && ! hasHighCodec ) {
options . default = ` ${ t ( 'visual-quality-normal' ) } ( ${ t ( 'default' ) } ) ` ;
@ -7169,11 +7209,12 @@ class Preferences {
options . normal = t ( 'visual-quality-normal' ) ;
}
}
if ( hasHighCodec ) {
if ( ! hasLowCodec && ! hasNormalCodec ) {
options . default = ` ${ t ( 'visual-quality-high' ) } ( ${ t ( 'default' ) } ) ` ;
if ( hasLowCodec ) {
if ( ! hasNormalCodec && ! hasHighCodec ) {
options . default = ` ${ t ( 'visual-quality-low' ) } ( ${ t ( 'default' ) } ) ` ;
} else {
options . high = t ( 'visual-quality-high ' ) ;
options . low = t ( 'visual-quality-low ' ) ;
}
}
@ -7196,6 +7237,7 @@ class Preferences {
[ Preferences . PREFER _IPV6 _SERVER ] : {
'default' : false ,
} ,
[ Preferences . SCREENSHOT _BUTTON _POSITION ] : {
'default' : 'bottom-left' ,
'options' : {
@ -7204,6 +7246,10 @@ class Preferences {
'none' : t ( 'disable' ) ,
} ,
} ,
[ Preferences . SCREENSHOT _APPLY _FILTERS ] : {
'default' : false ,
} ,
[ Preferences . SKIP _SPLASH _VIDEO ] : {
'default' : false ,
} ,
@ -7216,6 +7262,7 @@ class Preferences {
'options' : {
'default' : t ( 'default' ) ,
'all' : t ( 'tc-all-games' ) ,
'off' : t ( 'off' ) ,
} ,
'unsupported' : ! HAS _TOUCH _SUPPORT ,
'ready' : ( ) => {
@ -7393,6 +7440,7 @@ class Preferences {
'default' : '16:9' ,
'options' : {
'16:9' : '16:9' ,
'18:9' : '18:9' ,
'21:9' : '21:9' ,
'16:10' : '16:10' ,
'4:3' : '4:3' ,
@ -8004,7 +8052,11 @@ if (window.BX_VIBRATION_INTENSITY && window.BX_VIBRATION_INTENSITY < 1) {
return false ;
}
cons t newCode = `
le t newCode = '' ;
if ( getPref ( Preferences . STREAM _TOUCH _CONTROLLER ) === 'off' ) {
newCode = 'return;' ;
} else {
newCode = `
const gamepads = window.navigator.getGamepads();
let gamepadFound = false;
@ -8019,6 +8071,7 @@ if (gamepadFound) {
return;
}
` ;
}
funcStr = funcStr . replace ( text , newCode + text ) ;
return funcStr ;
@ -8035,7 +8088,7 @@ if (gamepadFound) {
getPref ( Preferences . UI _LAYOUT ) === 'tv' && [ 'tvLayout' ] ,
ENABLE _XCLOUD _LOGGER && [
BX _FLAGS . EnableXcloudLogging && [
'enableConsoleLogging' ,
'enableXcloudLogger' ,
] ,
@ -8068,9 +8121,9 @@ if (gamepadFound) {
[ 'playVibration' ] ,
HAS _TOUCH _SUPPORT && getPref ( Preferences . STREAM _TOUCH _CONTROLLER ) === 'all' && [ 'exposeTouchLayoutManager' ] ,
HAS _TOUCH _SUPPORT && getPref ( Preferences . STREAM _TOUCH _CONTROLLER _AUTO _OFF ) && [ 'disableTakRenderer' ] ,
HAS _TOUCH _SUPPORT && ( getPref ( Preferences . STREAM _TOUCH _CONTROLLER ) === 'off' || getPref ( Preferences . STREAM _TOUCH _CONTROLLER _AUTO _OFF ) ) && [ 'disableTakRenderer' ] ,
ENABLE _XCLOUD _LOGGER && [ 'enableConsoleLogging' ] ,
BX _FLAGS . EnableXcloudLogging && [ 'enableConsoleLogging' ] ,
getPref ( Preferences . BLOCK _TRACKING ) && [ 'blockGamepadStatsCollector' ] ,
@ -8621,6 +8674,10 @@ a.bx-button.bx-full-width {
color: #828282;
}
.bx-settings-group-label b {
margin-bottom: 8px;
}
@media not (hover: hover) {
.bx-settings-row:focus-within {
background-color: #242424;
@ -8720,12 +8777,9 @@ div[class*=StreamMenu-module__menuContainer] > div[class*=Menu-module] {
position: fixed;
bottom: 0;
box-sizing: border-box;
width: 16vh ;
height: 16vh ;
max-width : 128 px;
max-height: 128px;
padding: 2vh;
padding: 24px 24px 12px 12px;
width: 60px ;
height: 60px ;
padding : 16 px;
background-size: cover;
background-repeat: no-repeat;
background-origin: content-box;
@ -8742,7 +8796,7 @@ div[class*=StreamMenu-module__menuContainer] > div[class*=Menu-module] {
}
.bx-screenshot-button[data-capturing=true] {
padding: 1vh ;
padding: 8px ;
}
.bx-screenshot-canvas {
@ -10251,7 +10305,7 @@ function injectSettingsButton($parent) {
}
// Show link to Android app
if ( ! window . AppInterface ) {
if ( ! AppInterface ) {
const userAgent = UserAgent . getDefault ( ) . toLowerCase ( ) ;
if ( userAgent . includes ( 'android' ) ) {
const $btn = createButton ( {
@ -10284,6 +10338,9 @@ function injectSettingsButton($parent) {
[ Preferences . AUDIO _ENABLE _VOLUME _CONTROL ] : t ( 'enable-volume-control' ) ,
[ Preferences . AUDIO _MIC _ON _PLAYING ] : t ( 'enable-mic-on-startup' ) ,
[ Preferences . STREAM _DISABLE _FEEDBACK _DIALOG ] : t ( 'disable-post-stream-feedback-dialog' ) ,
[ Preferences . SCREENSHOT _BUTTON _POSITION ] : t ( 'screenshot-button-position' ) ,
[ Preferences . SCREENSHOT _APPLY _FILTERS ] : t ( 'screenshot-apply-filters' ) ,
} ,
[ t ( 'local-co-op' ) ] : {
@ -10292,8 +10349,6 @@ function injectSettingsButton($parent) {
} ,
[ t ( 'mouse-and-keyboard' ) ] : {
// '_note': '⚠️ ' + t('may-not-work-properly'),
// [Preferences.MKB_ENABLED]: [t('enable-mkb'), t('only-supports-some-games')],
[ Preferences . MKB _ENABLED ] : t ( 'enable-mkb' ) ,
[ Preferences . MKB _HIDE _IDLE _CURSOR ] : t ( 'hide-idle-cursor' ) ,
} ,
@ -10306,6 +10361,7 @@ function injectSettingsButton($parent) {
[ t ( 'touch-controller' ) ] : {
_note : ! HAS _TOUCH _SUPPORT ? '⚠️ ' + t ( 'device-unsupported-touch' ) : null ,
_unsupported : ! HAS _TOUCH _SUPPORT ,
[ Preferences . STREAM _TOUCH _CONTROLLER ] : t ( 'tc-availability' ) ,
[ Preferences . STREAM _TOUCH _CONTROLLER _AUTO _OFF ] : t ( 'tc-auto-off' ) ,
[ Preferences . STREAM _TOUCH _CONTROLLER _STYLE _STANDARD ] : t ( 'tc-standard-layout-style' ) ,
@ -10323,7 +10379,6 @@ function injectSettingsButton($parent) {
[ Preferences . SKIP _SPLASH _VIDEO ] : t ( 'skip-splash-video' ) ,
[ Preferences . HIDE _DOTS _ICON ] : t ( 'hide-system-menu-icon' ) ,
[ Preferences . REDUCE _ANIMATIONS ] : t ( 'reduce-animations' ) ,
[ Preferences . SCREENSHOT _BUTTON _POSITION ] : t ( 'screenshot-button-position' ) ,
} ,
[ t ( 'other' ) ] : {
[ Preferences . BLOCK _SOCIAL _FEATURES ] : t ( 'disable-social-features' ) ,
@ -10345,6 +10400,11 @@ function injectSettingsButton($parent) {
$wrapper . appendChild ( $group ) ;
// Don't render settings if this is an unsupported feature
if ( SETTINGS _UI [ groupLabel ] . _unsupported ) {
continue ;
}
let onChange = e => {
if ( ! $reloadBtnWrapper ) {
return ;
@ -10363,6 +10423,7 @@ function injectSettingsButton($parent) {
} ;
for ( let settingId in SETTINGS _UI [ groupLabel ] ) {
// Don't render custom settings
if ( settingId . startsWith ( '_' ) ) {
continue ;
}
@ -10557,6 +10618,11 @@ function updateVideoPlayerCss() {
videoCss += ` filter: ${ filters } !important; ` ;
}
// Apply video filters to screenshots
if ( getPref ( Preferences . SCREENSHOT _APPLY _FILTERS ) ) {
$SCREENSHOT _CANVAS . getContext ( '2d' ) . filter = filters ;
}
const PREF _RATIO = getPref ( Preferences . VIDEO _RATIO ) ;
if ( PREF _RATIO && PREF _RATIO !== '16:9' ) {
if ( PREF _RATIO . includes ( ':' ) ) {
@ -11309,6 +11375,15 @@ function takeScreenshot(callback) {
const $canvasContext = $SCREENSHOT _CANVAS . getContext ( '2d' ) ;
$canvasContext . drawImage ( $STREAM _VIDEO , 0 , 0 , $SCREENSHOT _CANVAS . width , $SCREENSHOT _CANVAS . height ) ;
// Get data URL and pass to parent app
if ( AppInterface ) {
const data = $SCREENSHOT _CANVAS . toDataURL ( 'image/png' ) . split ( ';base64,' ) [ 1 ] ;
AppInterface . saveScreenshot ( GAME _TITLE _ID , data ) ;
callback && callback ( ) ;
return ;
}
$SCREENSHOT _CANVAS . toBlob ( blob => {
// Download screenshot
const now = + new Date ;
@ -11427,20 +11502,17 @@ function disablePwa() {
}
function setupBxUi ( ) {
updateVideoPlayerCss ( ) ;
// Prevent initializing multiple times
if ( document . querySelector ( '.bx-quick-settings-bar' ) ) {
return ;
}
if ( ! document . querySelector ( '.bx-quick-settings-bar' ) ) {
window . addEventListener ( 'resize' , updateVideoPlayerCss ) ;
setupQuickSettingsBar ( ) ;
setupScreenshotButton ( ) ;
StreamStats . render ( ) ;
}
updateVideoPlayerCss ( ) ;
}
// Hide Settings UI when navigate to another page
window . addEventListener ( BxEvent . POPSTATE , onHistoryChanged ) ;
@ -11470,7 +11542,7 @@ window.addEventListener(BxEvent.STREAM_LOADING, e => {
window . addEventListener ( BxEvent . STREAM _STARTING , e => {
// Hide loading screen
getPref ( Preferences . UI _LOADING _SCREEN _GAME _ART ) && LoadingScreen. hide ( ) ;
LoadingScreen . hide ( ) ;
} ) ;
window . addEventListener ( BxEvent . STREAM _PLAYING , e => {
@ -11488,6 +11560,7 @@ window.addEventListener(BxEvent.STREAM_PLAYING, e => {
const PREF _SCREENSHOT _BUTTON _POSITION = getPref ( Preferences . SCREENSHOT _BUTTON _POSITION ) ;
$SCREENSHOT _CANVAS . width = $video . videoWidth ;
$SCREENSHOT _CANVAS . height = $video . videoHeight ;
updateVideoPlayerCss ( ) ;
// Setup screenshot button
if ( PREF _SCREENSHOT _BUTTON _POSITION !== 'none' ) {
@ -11623,7 +11696,7 @@ patchVideoApi();
// Setup UI
addCss ( ) ;
Toast . setup ( ) ;
ENABLE _PRELOAD _BX _UI && setupBxUi ( ) ;
BX _FLAGS . PreloadUi && setupBxUi ( ) ;
disablePwa ( ) ;