mirror of
https://github.com/redphx/better-xcloud.git
synced 2025-08-05 12:56:42 +02:00
Merge Global settings and Stream settings into one dialog
This commit is contained in:
@@ -1,63 +1,63 @@
|
||||
import { AppInterface } from "@utils/global";
|
||||
|
||||
export enum BxEvent {
|
||||
JUMP_BACK_IN_READY = 'bx-jump-back-in-ready',
|
||||
POPSTATE = 'bx-popstate',
|
||||
|
||||
TITLE_INFO_READY = 'bx-title-info-ready',
|
||||
|
||||
STREAM_LOADING = 'bx-stream-loading',
|
||||
STREAM_STARTING = 'bx-stream-starting',
|
||||
STREAM_STARTED = 'bx-stream-started',
|
||||
STREAM_PLAYING = 'bx-stream-playing',
|
||||
STREAM_STOPPED = 'bx-stream-stopped',
|
||||
STREAM_ERROR_PAGE = 'bx-stream-error-page',
|
||||
|
||||
STREAM_WEBRTC_CONNECTED = 'bx-stream-webrtc-connected',
|
||||
STREAM_WEBRTC_DISCONNECTED = 'bx-stream-webrtc-disconnected',
|
||||
|
||||
// STREAM_EVENT_TARGET_READY = 'bx-stream-event-target-ready',
|
||||
STREAM_SESSION_READY = 'bx-stream-session-ready',
|
||||
|
||||
CUSTOM_TOUCH_LAYOUTS_LOADED = 'bx-custom-touch-layouts-loaded',
|
||||
TOUCH_LAYOUT_MANAGER_READY = 'bx-touch-layout-manager-ready',
|
||||
|
||||
REMOTE_PLAY_READY = 'bx-remote-play-ready',
|
||||
REMOTE_PLAY_FAILED = 'bx-remote-play-failed',
|
||||
|
||||
XCLOUD_SERVERS_READY = 'bx-servers-ready',
|
||||
XCLOUD_SERVERS_UNAVAILABLE = 'bx-servers-unavailable',
|
||||
|
||||
DATA_CHANNEL_CREATED = 'bx-data-channel-created',
|
||||
|
||||
GAME_BAR_ACTION_ACTIVATED = 'bx-game-bar-action-activated',
|
||||
MICROPHONE_STATE_CHANGED = 'bx-microphone-state-changed',
|
||||
|
||||
CAPTURE_SCREENSHOT = 'bx-capture-screenshot',
|
||||
GAINNODE_VOLUME_CHANGED = 'bx-gainnode-volume-changed',
|
||||
|
||||
POINTER_LOCK_REQUESTED = 'bx-pointer-lock-requested',
|
||||
POINTER_LOCK_EXITED = 'bx-pointer-lock-exited',
|
||||
|
||||
NAVIGATION_FOCUS_CHANGED = 'bx-nav-focus-changed',
|
||||
|
||||
// xCloud Dialog events
|
||||
XCLOUD_DIALOG_SHOWN = 'bx-xcloud-dialog-shown',
|
||||
XCLOUD_DIALOG_DISMISSED = 'bx-xcloud-dialog-dismissed',
|
||||
|
||||
XCLOUD_GUIDE_MENU_SHOWN = 'bx-xcloud-guide-menu-shown',
|
||||
|
||||
XCLOUD_POLLING_MODE_CHANGED = 'bx-xcloud-polling-mode-changed',
|
||||
|
||||
XCLOUD_RENDERING_COMPONENT = 'bx-xcloud-rendering-page',
|
||||
}
|
||||
|
||||
export enum XcloudEvent {
|
||||
MICROPHONE_STATE_CHANGED = 'microphoneStateChanged',
|
||||
}
|
||||
|
||||
export namespace BxEvent {
|
||||
export function dispatch(target: HTMLElement | Window, eventName: string, data?: any) {
|
||||
export const JUMP_BACK_IN_READY = 'bx-jump-back-in-ready';
|
||||
export const POPSTATE = 'bx-popstate';
|
||||
|
||||
export const TITLE_INFO_READY = 'bx-title-info-ready';
|
||||
|
||||
export const SETTINGS_CHANGED = 'bx-settings-changed';
|
||||
|
||||
export const STREAM_LOADING = 'bx-stream-loading';
|
||||
export const STREAM_STARTING = 'bx-stream-starting';
|
||||
export const STREAM_STARTED = 'bx-stream-started';
|
||||
export const STREAM_PLAYING = 'bx-stream-playing';
|
||||
export const STREAM_STOPPED = 'bx-stream-stopped';
|
||||
export const STREAM_ERROR_PAGE = 'bx-stream-error-page';
|
||||
|
||||
export const STREAM_WEBRTC_CONNECTED = 'bx-stream-webrtc-connected';
|
||||
export const STREAM_WEBRTC_DISCONNECTED = 'bx-stream-webrtc-disconnected';
|
||||
|
||||
// export const STREAM_EVENT_TARGET_READY = 'bx-stream-event-target-ready';
|
||||
export const STREAM_SESSION_READY = 'bx-stream-session-ready';
|
||||
|
||||
export const CUSTOM_TOUCH_LAYOUTS_LOADED = 'bx-custom-touch-layouts-loaded';
|
||||
export const TOUCH_LAYOUT_MANAGER_READY = 'bx-touch-layout-manager-ready';
|
||||
|
||||
export const REMOTE_PLAY_READY = 'bx-remote-play-ready';
|
||||
export const REMOTE_PLAY_FAILED = 'bx-remote-play-failed';
|
||||
|
||||
export const XCLOUD_SERVERS_READY = 'bx-servers-ready';
|
||||
export const XCLOUD_SERVERS_UNAVAILABLE = 'bx-servers-unavailable';
|
||||
|
||||
export const DATA_CHANNEL_CREATED = 'bx-data-channel-created';
|
||||
|
||||
export const GAME_BAR_ACTION_ACTIVATED = 'bx-game-bar-action-activated';
|
||||
export const MICROPHONE_STATE_CHANGED = 'bx-microphone-state-changed';
|
||||
|
||||
export const CAPTURE_SCREENSHOT = 'bx-capture-screenshot';
|
||||
|
||||
export const POINTER_LOCK_REQUESTED = 'bx-pointer-lock-requested';
|
||||
export const POINTER_LOCK_EXITED = 'bx-pointer-lock-exited';
|
||||
|
||||
export const NAVIGATION_FOCUS_CHANGED = 'bx-nav-focus-changed';
|
||||
|
||||
// xCloud Dialog events
|
||||
export const XCLOUD_DIALOG_SHOWN = 'bx-xcloud-dialog-shown';
|
||||
export const XCLOUD_DIALOG_DISMISSED = 'bx-xcloud-dialog-dismissed';
|
||||
|
||||
export const XCLOUD_GUIDE_MENU_SHOWN = 'bx-xcloud-guide-menu-shown';
|
||||
|
||||
export const XCLOUD_POLLING_MODE_CHANGED = 'bx-xcloud-polling-mode-changed';
|
||||
|
||||
export const XCLOUD_RENDERING_COMPONENT = 'bx-xcloud-rendering-page';
|
||||
|
||||
export function dispatch(target: Element | Window | null, eventName: string, data?: any) {
|
||||
if (!target) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!eventName) {
|
||||
alert('BxEvent.dispatch(): eventName is null');
|
||||
return;
|
||||
|
@@ -1,19 +1,21 @@
|
||||
import { ControllerShortcut } from "@/modules/controller-shortcut";
|
||||
import { BxEvent } from "@utils/bx-event";
|
||||
import { deepClone, STATES } from "@utils/global";
|
||||
import { getPref, PrefKey } from "@utils/preferences";
|
||||
import { BxLogger } from "./bx-logger";
|
||||
import { BX_FLAGS } from "./bx-flags";
|
||||
import { StreamSettings } from "@/modules/stream/stream-settings";
|
||||
import { NavigationDialogManager } from "@/modules/ui/dialog/navigation-dialog";
|
||||
import { PrefKey } from "@/enums/pref-keys";
|
||||
import { getPref } from "./settings-storages/global-settings-storage";
|
||||
|
||||
export enum InputType {
|
||||
export enum SupportedInputType {
|
||||
CONTROLLER = 'Controller',
|
||||
MKB = 'MKB',
|
||||
CUSTOM_TOUCH_OVERLAY = 'CustomTouchOverlay',
|
||||
GENERIC_TOUCH = 'GenericTouch',
|
||||
NATIVE_TOUCH = 'NativeTouch',
|
||||
BATIVE_SENSOR = 'NativeSensor',
|
||||
}
|
||||
};
|
||||
export type SupportedInputTypeValue = (typeof SupportedInputType)[keyof typeof SupportedInputType];
|
||||
|
||||
export const BxExposed = {
|
||||
getTitleInfo: () => STATES.currentStream.titleInfo,
|
||||
@@ -25,15 +27,15 @@ export const BxExposed = {
|
||||
let supportedInputTypes = titleInfo.details.supportedInputTypes;
|
||||
|
||||
if (BX_FLAGS.ForceNativeMkbTitles?.includes(titleInfo.details.productId)) {
|
||||
supportedInputTypes.push(InputType.MKB);
|
||||
supportedInputTypes.push(SupportedInputType.MKB);
|
||||
}
|
||||
|
||||
// Remove native MKB support on mobile browsers or by user's choice
|
||||
if (getPref(PrefKey.NATIVE_MKB_ENABLED) === 'off') {
|
||||
supportedInputTypes = supportedInputTypes.filter(i => i !== InputType.MKB);
|
||||
supportedInputTypes = supportedInputTypes.filter(i => i !== SupportedInputType.MKB);
|
||||
}
|
||||
|
||||
titleInfo.details.hasMkbSupport = supportedInputTypes.includes(InputType.MKB);
|
||||
titleInfo.details.hasMkbSupport = supportedInputTypes.includes(SupportedInputType.MKB);
|
||||
|
||||
if (STATES.userAgent.capabilities.touch) {
|
||||
let touchControllerAvailability = getPref(PrefKey.STREAM_TOUCH_CONTROLLER);
|
||||
@@ -55,21 +57,21 @@ export const BxExposed = {
|
||||
|
||||
if (touchControllerAvailability === 'off') {
|
||||
// Disable touch on all games (not native touch)
|
||||
supportedInputTypes = supportedInputTypes.filter(i => i !== InputType.CUSTOM_TOUCH_OVERLAY && i !== InputType.GENERIC_TOUCH);
|
||||
supportedInputTypes = supportedInputTypes.filter(i => i !== SupportedInputType.CUSTOM_TOUCH_OVERLAY && i !== SupportedInputType.GENERIC_TOUCH);
|
||||
// Empty TABs
|
||||
titleInfo.details.supportedTabs = [];
|
||||
}
|
||||
|
||||
// Pre-check supported input types
|
||||
titleInfo.details.hasNativeTouchSupport = supportedInputTypes.includes(InputType.NATIVE_TOUCH);
|
||||
titleInfo.details.hasNativeTouchSupport = supportedInputTypes.includes(SupportedInputType.NATIVE_TOUCH);
|
||||
titleInfo.details.hasTouchSupport = titleInfo.details.hasNativeTouchSupport ||
|
||||
supportedInputTypes.includes(InputType.CUSTOM_TOUCH_OVERLAY) ||
|
||||
supportedInputTypes.includes(InputType.GENERIC_TOUCH);
|
||||
supportedInputTypes.includes(SupportedInputType.CUSTOM_TOUCH_OVERLAY) ||
|
||||
supportedInputTypes.includes(SupportedInputType.GENERIC_TOUCH);
|
||||
|
||||
if (!titleInfo.details.hasTouchSupport && touchControllerAvailability === 'all') {
|
||||
// Add generic touch support for non touch-supported games
|
||||
titleInfo.details.hasFakeTouchSupport = true;
|
||||
supportedInputTypes.push(InputType.GENERIC_TOUCH);
|
||||
supportedInputTypes.push(SupportedInputType.GENERIC_TOUCH);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,9 +122,9 @@ export const BxExposed = {
|
||||
disableGamepadPolling: false,
|
||||
|
||||
backButtonPressed: () => {
|
||||
const streamSettings = StreamSettings.getInstance();
|
||||
if (streamSettings.isShowing()) {
|
||||
streamSettings.hide();
|
||||
const navigationDialogManager = NavigationDialogManager.getInstance();
|
||||
if (navigationDialogManager.isShowing()) {
|
||||
navigationDialogManager.hide();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@@ -5,8 +5,6 @@ type BxFlags = Partial<{
|
||||
EnableXcloudLogging: boolean;
|
||||
SafariWorkaround: boolean;
|
||||
|
||||
UseDevTouchLayout: boolean;
|
||||
|
||||
ForceNativeMkbTitles: string[];
|
||||
FeatureGates: {[key: string]: boolean} | null,
|
||||
|
||||
@@ -26,8 +24,6 @@ const DEFAULT_FLAGS: BxFlags = {
|
||||
EnableXcloudLogging: false,
|
||||
SafariWorkaround: true,
|
||||
|
||||
UseDevTouchLayout: false,
|
||||
|
||||
ForceNativeMkbTitles: [],
|
||||
FeatureGates: null,
|
||||
|
||||
|
@@ -1,3 +1,5 @@
|
||||
import iconBetterXcloud from "@assets/svg/better-xcloud.svg" with { type: "text" };
|
||||
import iconClose from "@assets/svg/close.svg" with { type: "text" };
|
||||
import iconCommand from "@assets/svg/command.svg" with { type: "text" };
|
||||
import iconController from "@assets/svg/controller.svg" with { type: "text" };
|
||||
import iconCopy from "@assets/svg/copy.svg" with { type: "text" };
|
||||
@@ -34,8 +36,10 @@ import iconUpload from "@assets/svg/upload.svg" with { type: "text" };
|
||||
|
||||
|
||||
export const BxIcon = {
|
||||
BETTER_XCLOUD: iconBetterXcloud,
|
||||
STREAM_SETTINGS: iconStreamSettings,
|
||||
STREAM_STATS: iconStreamStats,
|
||||
CLOSE: iconClose,
|
||||
COMMAND: iconCommand,
|
||||
CONTROLLER: iconController,
|
||||
CREATE_SHORTCUT: iconCreateShortcut,
|
||||
|
@@ -1,4 +1,4 @@
|
||||
enum TextColor {
|
||||
const enum TextColor {
|
||||
INFO = '#008746',
|
||||
WARNING = '#c1a404',
|
||||
ERROR = '#c10404',
|
||||
@@ -19,7 +19,7 @@ export class BxLogger {
|
||||
BxLogger.#log(TextColor.ERROR, tag, ...args);
|
||||
}
|
||||
|
||||
static #log(color: TextColor, tag: string, ...args: any) {
|
||||
static #log(color: string, tag: string, ...args: any) {
|
||||
console.log(`%c${BxLogger.#PREFIX}`, `color:${color};font-weight:bold;`, tag, '//', ...args);
|
||||
}
|
||||
}
|
||||
|
@@ -1,7 +1,8 @@
|
||||
import { CE } from "@utils/html";
|
||||
import { PrefKey, getPref } from "@utils/preferences";
|
||||
import { renderStylus } from "@macros/build" with {type: "macro"};
|
||||
import { compressCss, renderStylus } from "@macros/build" with {type: "macro"};
|
||||
import { UiSection } from "@/enums/ui-sections";
|
||||
import { PrefKey } from "@/enums/pref-keys";
|
||||
import { getPref } from "./settings-storages/global-settings-storage";
|
||||
|
||||
|
||||
export function addCss() {
|
||||
@@ -19,6 +20,17 @@ export function addCss() {
|
||||
// Hide "All games" section
|
||||
if (PREF_HIDE_SECTIONS.includes(UiSection.ALL_GAMES)) {
|
||||
selectorToHide.push('#BodyContent div[class*=AllGamesRow-module__gridContainer]');
|
||||
selectorToHide.push('#BodyContent div[class*=AllGamesRow-module__rowHeader]');
|
||||
}
|
||||
|
||||
// Hide "Most popular" section
|
||||
if (PREF_HIDE_SECTIONS.includes(UiSection.MOST_POPULAR)) {
|
||||
selectorToHide.push('#BodyContent div[class*=HomePage-module__bottomSpacing]:has(a[href="/play/gallery/popular"])');
|
||||
}
|
||||
|
||||
// Hide "Play with touch" section
|
||||
if (PREF_HIDE_SECTIONS.includes(UiSection.TOUCH)) {
|
||||
selectorToHide.push('#BodyContent div[class*=HomePage-module__bottomSpacing]:has(a[href="/play/gallery/touch"])');
|
||||
}
|
||||
|
||||
// Hide "Start a party" button in the Guide menu
|
||||
@@ -32,18 +44,18 @@ export function addCss() {
|
||||
|
||||
// Reduce animations
|
||||
if (getPref(PrefKey.REDUCE_ANIMATIONS)) {
|
||||
css += `
|
||||
css += compressCss(`
|
||||
div[class*=GameCard-module__gameTitleInnerWrapper],
|
||||
div[class*=GameCard-module__card],
|
||||
div[class*=ScrollArrows-module] {
|
||||
transition: none !important;
|
||||
}
|
||||
`;
|
||||
`);
|
||||
}
|
||||
|
||||
// Hide the top-left dots icon while playing
|
||||
if (getPref(PrefKey.HIDE_DOTS_ICON)) {
|
||||
css += `
|
||||
css += compressCss(`
|
||||
div[class*=Grip-module__container] {
|
||||
visibility: hidden;
|
||||
}
|
||||
@@ -65,18 +77,18 @@ button[class*=GripHandle-module__container][aria-expanded=false] {
|
||||
div[class*=StreamHUD-module__buttonsContainer] {
|
||||
padding: 0px !important;
|
||||
}
|
||||
`;
|
||||
`);
|
||||
}
|
||||
|
||||
css += `
|
||||
css += compressCss(`
|
||||
div[class*=StreamMenu-module__menu] {
|
||||
min-width: 100vw !important;
|
||||
}
|
||||
`;
|
||||
`);
|
||||
|
||||
// Simplify Stream's menu
|
||||
if (getPref(PrefKey.STREAM_SIMPLIFY_MENU)) {
|
||||
css += `
|
||||
css += compressCss(`
|
||||
div[class*=Menu-module__scrollable] {
|
||||
--bxStreamMenuItemSize: 80px;
|
||||
--streamMenuItemSize: calc(var(--bxStreamMenuItemSize) + 40px) !important;
|
||||
@@ -107,9 +119,9 @@ svg[class*=MenuItem-module__icon] {
|
||||
padding: 0 !important;
|
||||
margin: 0 !important;
|
||||
}
|
||||
`;
|
||||
`);
|
||||
} else {
|
||||
css += `
|
||||
css += compressCss(`
|
||||
body[data-media-type=tv] .bx-badges {
|
||||
top: calc(var(--streamMenuItemSize) + 30px);
|
||||
}
|
||||
@@ -131,12 +143,12 @@ body:not([data-media-type=tv]) div[class*=MenuItem-module__label] {
|
||||
margin-left: 8px !important;
|
||||
margin-right: 8px !important;
|
||||
}
|
||||
`;
|
||||
`);
|
||||
}
|
||||
|
||||
// Hide scrollbar
|
||||
if (getPref(PrefKey.UI_SCROLLBAR_HIDE)) {
|
||||
css += `
|
||||
css += compressCss(`
|
||||
html {
|
||||
scrollbar-width: none;
|
||||
}
|
||||
@@ -144,7 +156,7 @@ html {
|
||||
body::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
`;
|
||||
`);
|
||||
}
|
||||
|
||||
const $style = CE('style', {}, css);
|
||||
|
@@ -1,5 +1,6 @@
|
||||
import { PrefKey } from "@/enums/pref-keys";
|
||||
import { BX_FLAGS } from "./bx-flags";
|
||||
import { getPref, PrefKey } from "./preferences";
|
||||
import { getPref } from "./settings-storages/global-settings-storage";
|
||||
|
||||
export let FeatureGates: {[key: string]: boolean} = {
|
||||
'PwaPrompt': false,
|
||||
|
@@ -1,8 +1,9 @@
|
||||
import { EmulatedMkbHandler } from "@modules/mkb/mkb-handler";
|
||||
import { PrefKey, getPref } from "@utils/preferences";
|
||||
import { t } from "@utils/translation";
|
||||
import { Toast } from "@utils/toast";
|
||||
import { BxLogger } from "@utils/bx-logger";
|
||||
import { PrefKey } from "@/enums/pref-keys";
|
||||
import { getPref } from "./settings-storages/global-settings-storage";
|
||||
|
||||
// Show a toast when connecting/disconecting controller
|
||||
export function showGamepadToast(gamepad: Gamepad) {
|
||||
|
@@ -1,3 +1,4 @@
|
||||
import type { BaseSettingsStore } from "./settings-storages/base-settings-storage";
|
||||
import { UserAgent } from "./user-agent";
|
||||
|
||||
export const SCRIPT_VERSION = Bun.env.SCRIPT_VERSION!;
|
||||
@@ -42,6 +43,8 @@ export const STATES: BxStates = {
|
||||
pointerServerPort: 9269,
|
||||
};
|
||||
|
||||
export const STORAGE: {[key: string]: BaseSettingsStore} = {};
|
||||
|
||||
export function deepClone(obj: any): any {
|
||||
if ('structuredClone' in window) {
|
||||
return structuredClone(obj);
|
||||
|
@@ -2,7 +2,7 @@ import { BxEvent } from "@utils/bx-event";
|
||||
import { LoadingScreen } from "@modules/loading-screen";
|
||||
import { RemotePlay } from "@modules/remote-play";
|
||||
import { HeaderSection } from "@/modules/ui/header";
|
||||
import { StreamSettings } from "@/modules/stream/stream-settings";
|
||||
import { NavigationDialogManager } from "@/modules/ui/dialog/navigation-dialog";
|
||||
|
||||
export function patchHistoryMethod(type: 'pushState' | 'replaceState') {
|
||||
const orig = window.history[type];
|
||||
@@ -32,10 +32,8 @@ export function onHistoryChanged(e: PopStateEvent) {
|
||||
$settings.classList.add('bx-gone');
|
||||
}
|
||||
|
||||
// Hide Stream settings
|
||||
if (document.querySelector('.' + StreamSettings.MAIN_CLASS)) {
|
||||
StreamSettings.getInstance().hide();
|
||||
}
|
||||
// Hide Navigation dialog
|
||||
NavigationDialogManager.getInstance().hide();
|
||||
|
||||
// Hide Remote Play popup
|
||||
RemotePlay.detachPopup();
|
||||
|
@@ -1,4 +1,6 @@
|
||||
import type { BxIcon } from "@utils/bx-icon";
|
||||
import { setNearby } from "./navigation-utils";
|
||||
import type { NavigationNearbyElements } from "@/modules/ui/dialog/navigation-dialog";
|
||||
|
||||
type BxButton = {
|
||||
style?: number | string | ButtonStyle;
|
||||
@@ -16,7 +18,13 @@ type BxButton = {
|
||||
type ButtonStyle = {[index: string]: number} & {[index: number]: string};
|
||||
|
||||
// Quickly create a tree of elements without having to use innerHTML
|
||||
function createElement<T=HTMLElement>(elmName: string, props: {[index: string]: any}={}, ..._: any): T {
|
||||
type CreateElementOptions = {
|
||||
[index: string]: any;
|
||||
_nearby?: NavigationNearbyElements;
|
||||
};
|
||||
|
||||
|
||||
function createElement<T=HTMLElement>(elmName: string, props: CreateElementOptions={}, ..._: any): T {
|
||||
let $elm;
|
||||
const hasNs = 'xmlns' in props;
|
||||
|
||||
@@ -27,6 +35,11 @@ function createElement<T=HTMLElement>(elmName: string, props: {[index: string]:
|
||||
$elm = document.createElement(elmName);
|
||||
}
|
||||
|
||||
if (props['_nearby']) {
|
||||
setNearby($elm, props['_nearby']);
|
||||
delete props['_nearby'];
|
||||
}
|
||||
|
||||
for (const key in props) {
|
||||
if ($elm.hasOwnProperty(key)) {
|
||||
continue;
|
||||
@@ -71,10 +84,14 @@ export const ButtonStyle: DualEnum = {};
|
||||
ButtonStyle[ButtonStyle.PRIMARY = 1] = 'bx-primary';
|
||||
ButtonStyle[ButtonStyle.DANGER = 2] = 'bx-danger';
|
||||
ButtonStyle[ButtonStyle.GHOST = 4] = 'bx-ghost';
|
||||
ButtonStyle[ButtonStyle.FOCUSABLE = 8] = 'bx-focusable';
|
||||
ButtonStyle[ButtonStyle.FULL_WIDTH = 16] = 'bx-full-width';
|
||||
ButtonStyle[ButtonStyle.FULL_HEIGHT = 32] = 'bx-full-height';
|
||||
ButtonStyle[ButtonStyle.TALL = 64] = 'bx-tall';
|
||||
ButtonStyle[ButtonStyle.FROSTED = 8] = 'bx-frosted';
|
||||
ButtonStyle[ButtonStyle.DROP_SHADOW = 16] = 'bx-drop-shadow';
|
||||
ButtonStyle[ButtonStyle.FOCUSABLE = 32] = 'bx-focusable';
|
||||
ButtonStyle[ButtonStyle.FULL_WIDTH = 64] = 'bx-full-width';
|
||||
ButtonStyle[ButtonStyle.FULL_HEIGHT = 128] = 'bx-full-height';
|
||||
ButtonStyle[ButtonStyle.TALL = 256] = 'bx-tall';
|
||||
ButtonStyle[ButtonStyle.CIRCULAR = 512] = 'bx-circular';
|
||||
ButtonStyle[ButtonStyle.NORMAL_CASE = 1024] = 'bx-normal-case';
|
||||
|
||||
const ButtonStyleIndices = Object.keys(ButtonStyle).splice(0, Object.keys(ButtonStyle).length / 2).map(i => parseInt(i));
|
||||
|
||||
@@ -100,7 +117,7 @@ export const createButton = <T=HTMLButtonElement>(options: BxButton): T => {
|
||||
options.title && $btn.setAttribute('title', options.title);
|
||||
options.disabled && (($btn as HTMLButtonElement).disabled = true);
|
||||
options.onClick && $btn.addEventListener('click', options.onClick);
|
||||
typeof options.tabIndex === 'number' && ($btn.tabIndex = options.tabIndex!);
|
||||
$btn.tabIndex = typeof options.tabIndex === 'number' ? options.tabIndex : 0;
|
||||
|
||||
for (const key in options.attributes) {
|
||||
if (!$btn.hasOwnProperty(key)) {
|
||||
|
@@ -1,7 +1,8 @@
|
||||
import { MkbPreset } from "@modules/mkb/mkb-preset";
|
||||
import { PrefKey, setPref } from "@utils/preferences";
|
||||
import { t } from "@utils/translation";
|
||||
import type { MkbStoredPreset, MkbStoredPresets } from "@/types/mkb";
|
||||
import { PrefKey } from "@/enums/pref-keys";
|
||||
import { setPref } from "./settings-storages/global-settings-storage";
|
||||
|
||||
export class LocalDb {
|
||||
static #instance: LocalDb;
|
||||
|
@@ -1,9 +1,10 @@
|
||||
import { BxEvent } from "@utils/bx-event";
|
||||
import { getPref, PrefKey } from "@utils/preferences";
|
||||
import { STATES } from "@utils/global";
|
||||
import { BxLogger } from "@utils/bx-logger";
|
||||
import { patchSdpBitrate, setCodecPreferences } from "./sdp";
|
||||
import { StreamPlayer, type StreamPlayerOptions } from "@/modules/stream-player";
|
||||
import { PrefKey } from "@/enums/pref-keys";
|
||||
import { getPref } from "./settings-storages/global-settings-storage";
|
||||
|
||||
export function patchVideoApi() {
|
||||
const PREF_SKIP_SPLASH_VIDEO = getPref(PrefKey.SKIP_SPLASH_VIDEO);
|
||||
@@ -99,8 +100,6 @@ export function patchRtcPeerConnection() {
|
||||
BxLogger.error('setLocalDescription', e);
|
||||
}
|
||||
|
||||
BxLogger.info('setLocalDescription', arguments[0].sdp);
|
||||
|
||||
// @ts-ignore
|
||||
return nativeSetLocalDescription.apply(this, arguments);
|
||||
};
|
||||
|
14
src/utils/navigation-utils.ts
Normal file
14
src/utils/navigation-utils.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import type { NavigationElement, NavigationNearbyElements } from "@/modules/ui/dialog/navigation-dialog";
|
||||
|
||||
export class NavigationUtils {
|
||||
static setNearby($elm: NavigationElement, nearby: NavigationNearbyElements) {
|
||||
$elm.nearby = $elm.nearby || {};
|
||||
|
||||
let key: keyof typeof nearby;
|
||||
for (key in nearby) {
|
||||
$elm.nearby[key] = nearby[key] as any;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const setNearby = NavigationUtils.setNearby;
|
@@ -1,6 +1,5 @@
|
||||
import { BxEvent } from "@utils/bx-event";
|
||||
import { BX_FLAGS, NATIVE_FETCH } from "@utils/bx-flags";
|
||||
import { PrefKey, getPref } from "@utils/preferences";
|
||||
import { TouchController } from "@modules/touch-controller";
|
||||
import { STATES } from "@utils/global";
|
||||
import { GamePassCloudGallery } from "../enums/game-pass-gallery";
|
||||
@@ -8,11 +7,10 @@ import { FeatureGates } from "./feature-gates";
|
||||
import { BxLogger } from "./bx-logger";
|
||||
import { XhomeInterceptor } from "./xhome-interceptor";
|
||||
import { XcloudInterceptor } from "./xcloud-interceptor";
|
||||
import { PrefKey } from "@/enums/pref-keys";
|
||||
import { getPref } from "./settings-storages/global-settings-storage";
|
||||
|
||||
enum RequestType {
|
||||
XCLOUD = 'xcloud',
|
||||
XHOME = 'xhome',
|
||||
};
|
||||
type RequestType = 'xcloud' | 'xhome';
|
||||
|
||||
function clearApplicationInsightsBuffers() {
|
||||
window.sessionStorage.removeItem('AI_buffer');
|
||||
@@ -258,12 +256,12 @@ export function interceptHttpRequests() {
|
||||
|
||||
let requestType: RequestType;
|
||||
if (url.includes('/sessions/home') || url.includes('xhome.') || (STATES.remotePlay.isPlaying && url.endsWith('/inputconfigs'))) {
|
||||
requestType = RequestType.XHOME;
|
||||
requestType = 'xhome';
|
||||
} else {
|
||||
requestType = RequestType.XCLOUD;
|
||||
requestType = 'xcloud';
|
||||
}
|
||||
|
||||
if (requestType === RequestType.XHOME) {
|
||||
if (requestType === 'xhome') {
|
||||
return XhomeInterceptor.handle(request as Request);
|
||||
}
|
||||
|
||||
|
@@ -2,8 +2,9 @@ import { deepClone, STATES } from "@utils/global";
|
||||
import { BxLogger } from "./bx-logger";
|
||||
import { TouchController } from "@modules/touch-controller";
|
||||
import { GamePassCloudGallery } from "../enums/game-pass-gallery";
|
||||
import { getPref, PrefKey } from "./preferences";
|
||||
import { BX_FLAGS } from "./bx-flags";
|
||||
import { PrefKey } from "@/enums/pref-keys";
|
||||
import { getPref } from "./settings-storages/global-settings-storage";
|
||||
|
||||
const LOG_TAG = 'PreloadState';
|
||||
|
||||
|
@@ -1,8 +1,9 @@
|
||||
import { getPref, PrefKey } from "@utils/preferences";
|
||||
import { STATES } from "@utils/global";
|
||||
import { PrefKey } from "@/enums/pref-keys";
|
||||
import { getPref } from "./settings-storages/global-settings-storage";
|
||||
|
||||
|
||||
export function getPreferredServerRegion(shortName = false) {
|
||||
export function getPreferredServerRegion(shortName = false): string | null {
|
||||
let preferredRegion = getPref(PrefKey.SERVER_REGION);
|
||||
if (preferredRegion in STATES.serverRegions) {
|
||||
if (shortName && STATES.serverRegions[preferredRegion].shortName) {
|
||||
@@ -25,5 +26,5 @@ export function getPreferredServerRegion(shortName = false) {
|
||||
}
|
||||
}
|
||||
|
||||
return '???';
|
||||
return null;
|
||||
}
|
||||
|
@@ -1,7 +1,8 @@
|
||||
import { StreamPlayerType } from "@enums/stream-player";
|
||||
import { AppInterface, STATES } from "./global";
|
||||
import { CE } from "./html";
|
||||
import { getPref, PrefKey } from "./preferences";
|
||||
import { PrefKey } from "@/enums/pref-keys";
|
||||
import { getPref } from "./settings-storages/global-settings-storage";
|
||||
|
||||
|
||||
export class Screenshot {
|
||||
|
@@ -1,5 +1,8 @@
|
||||
import type { PreferenceSetting } from "@/types/preferences";
|
||||
import { CE } from "@utils/html";
|
||||
import { setNearby } from "./navigation-utils";
|
||||
import type { PrefKey } from "@/enums/pref-keys";
|
||||
import type { BaseSettingsStore } from "./settings-storages/base-settings-storage";
|
||||
|
||||
type MultipleOptionsParams = {
|
||||
size?: number;
|
||||
@@ -50,7 +53,8 @@ export class SettingElement {
|
||||
onChange && $control.addEventListener('input', e => {
|
||||
const target = e.target as HTMLSelectElement;
|
||||
const value = (setting.type && setting.type === 'number') ? parseInt(target.value) : target.value;
|
||||
onChange(e, value);
|
||||
|
||||
!(e as any).ignoreOnChange && onChange(e, value);
|
||||
});
|
||||
|
||||
// Custom method
|
||||
@@ -102,7 +106,8 @@ export class SettingElement {
|
||||
onChange && $control.addEventListener('input', (e: Event) => {
|
||||
const target = e.target as HTMLSelectElement
|
||||
const values = Array.from(target.selectedOptions).map(i => i.value);
|
||||
onChange(e, values);
|
||||
|
||||
!(e as any).ignoreOnChange && onChange(e, values);
|
||||
});
|
||||
|
||||
return $control;
|
||||
@@ -117,7 +122,7 @@ export class SettingElement {
|
||||
const value = Math.max(setting.min!, Math.min(setting.max!, parseInt(target.value)));
|
||||
target.value = value.toString();
|
||||
|
||||
onChange(e, value);
|
||||
!(e as any).ignoreOnChange && onChange(e, value);
|
||||
});
|
||||
|
||||
return $control;
|
||||
@@ -128,7 +133,7 @@ export class SettingElement {
|
||||
$control.checked = currentValue;
|
||||
|
||||
onChange && $control.addEventListener('change', e => {
|
||||
onChange(e, (e.target as HTMLInputElement).checked);
|
||||
!(e as any).ignoreOnChange && onChange(e, (e.target as HTMLInputElement).checked);
|
||||
});
|
||||
|
||||
return $control;
|
||||
@@ -143,7 +148,7 @@ export class SettingElement {
|
||||
let $text: HTMLSpanElement;
|
||||
let $btnDec: HTMLButtonElement;
|
||||
let $btnInc: HTMLButtonElement;
|
||||
let $range: HTMLInputElement;
|
||||
let $range: HTMLInputElement | null = null;
|
||||
|
||||
let controlValue = value;
|
||||
|
||||
@@ -187,6 +192,10 @@ export class SettingElement {
|
||||
}, '+') as HTMLButtonElement,
|
||||
);
|
||||
|
||||
if (options.disabled) {
|
||||
($wrapper as any).disabled = true;
|
||||
}
|
||||
|
||||
if (!options.disabled && !options.hideSlider) {
|
||||
$range = CE('input', {
|
||||
id: `bx_setting_${key}`,
|
||||
@@ -212,6 +221,7 @@ export class SettingElement {
|
||||
|
||||
!(e as any).ignoreOnChange && onChange && onChange(e, value);
|
||||
});
|
||||
|
||||
$wrapper.appendChild($range);
|
||||
|
||||
if (options.ticks || options.exactTicks) {
|
||||
@@ -277,7 +287,7 @@ export class SettingElement {
|
||||
$range && ($range.value = value.toString());
|
||||
|
||||
isHolding = false;
|
||||
onChange && onChange(e, value);
|
||||
!(e as any).ignoreOnChange && onChange && onChange(e, value);
|
||||
}
|
||||
|
||||
const onMouseDown = (e: PointerEvent) => {
|
||||
@@ -322,6 +332,10 @@ export class SettingElement {
|
||||
$btnInc.addEventListener('pointerup', onMouseUp);
|
||||
$btnInc.addEventListener('contextmenu', onContextMenu);
|
||||
|
||||
setNearby($wrapper, {
|
||||
focus: $range || $btnInc,
|
||||
})
|
||||
|
||||
return $wrapper;
|
||||
}
|
||||
|
||||
@@ -349,4 +363,34 @@ export class SettingElement {
|
||||
|
||||
return $control;
|
||||
}
|
||||
|
||||
static fromPref(key: PrefKey, storage: BaseSettingsStore, onChange: any, overrideParams={}) {
|
||||
const definition = storage.getDefinition(key);
|
||||
let currentValue = storage.getSetting(key);
|
||||
|
||||
let type;
|
||||
if ('type' in definition) {
|
||||
type = definition.type;
|
||||
} else if ('options' in definition) {
|
||||
type = SettingElementType.OPTIONS;
|
||||
} else if ('multipleOptions' in definition) {
|
||||
type = SettingElementType.MULTIPLE_OPTIONS;
|
||||
} else if (typeof definition.default === 'number') {
|
||||
type = SettingElementType.NUMBER;
|
||||
} else {
|
||||
type = SettingElementType.CHECKBOX;
|
||||
}
|
||||
|
||||
const params = Object.assign(overrideParams, definition.params || {});
|
||||
if (params.disabled) {
|
||||
currentValue = definition.default;
|
||||
}
|
||||
|
||||
const $control = SettingElement.render(type!, key as string, definition, currentValue, (e: any, value: any) => {
|
||||
storage.setSetting(key, value);
|
||||
onChange && onChange(e, value);
|
||||
}, params);
|
||||
|
||||
return $control;
|
||||
}
|
||||
}
|
124
src/utils/settings-storages/base-settings-storage.ts
Normal file
124
src/utils/settings-storages/base-settings-storage.ts
Normal file
@@ -0,0 +1,124 @@
|
||||
import type { PrefKey } from "@/enums/pref-keys";
|
||||
import type { SettingDefinitions } from "@/types/setting-definition";
|
||||
import { BxEvent } from "../bx-event";
|
||||
|
||||
export class BaseSettingsStore {
|
||||
private storage: Storage;
|
||||
private storageKey: string;
|
||||
private _settings: object | null;
|
||||
private definitions: SettingDefinitions;
|
||||
|
||||
constructor(storageKey: string, definitions: SettingDefinitions) {
|
||||
this.storage = window.localStorage;
|
||||
this.storageKey = storageKey;
|
||||
|
||||
for (const settingId in definitions) {
|
||||
const setting = definitions[settingId];
|
||||
|
||||
/*
|
||||
if (setting.migrate && settingId in savedPrefs) {
|
||||
setting.migrate.call(this, savedPrefs, savedPrefs[settingId]);
|
||||
}
|
||||
*/
|
||||
|
||||
setting.ready && setting.ready.call(this, setting);
|
||||
}
|
||||
this.definitions = definitions;
|
||||
|
||||
this._settings = null;
|
||||
}
|
||||
|
||||
get settings() {
|
||||
if (this._settings) {
|
||||
return this._settings;
|
||||
}
|
||||
|
||||
const settings = JSON.parse(this.storage.getItem(this.storageKey) || '{}');
|
||||
this._settings = settings;
|
||||
|
||||
return settings;
|
||||
}
|
||||
|
||||
getDefinition(key: PrefKey) {
|
||||
if (!this.definitions[key]) {
|
||||
const error = 'Request invalid definition: ' + key;
|
||||
alert(error);
|
||||
throw Error(error);
|
||||
}
|
||||
|
||||
return this.definitions[key];
|
||||
}
|
||||
|
||||
getSetting(key: PrefKey) {
|
||||
if (typeof key === 'undefined') {
|
||||
debugger;
|
||||
return;
|
||||
}
|
||||
|
||||
// Return default value if the feature is not supported
|
||||
if (this.definitions[key].unsupported) {
|
||||
return this.definitions[key].default;
|
||||
}
|
||||
|
||||
if (!(key in this.settings)) {
|
||||
this.settings[key] = this.validateValue(key, null);
|
||||
}
|
||||
|
||||
return this.settings[key];
|
||||
}
|
||||
|
||||
setSetting(key: PrefKey, value: any, emitEvent = false) {
|
||||
value = this.validateValue(key, value);
|
||||
|
||||
this.settings[key] = value;
|
||||
this.saveSettings();
|
||||
|
||||
emitEvent && BxEvent.dispatch(window, BxEvent.SETTINGS_CHANGED, {
|
||||
storageKey: this.storageKey,
|
||||
settingKey: key,
|
||||
settingValue: value,
|
||||
});
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
saveSettings() {
|
||||
this.storage.setItem(this.storageKey, JSON.stringify(this.settings));
|
||||
}
|
||||
|
||||
private validateValue(key: PrefKey, value: any) {
|
||||
const def = this.definitions[key];
|
||||
if (!def) {
|
||||
return value;
|
||||
}
|
||||
|
||||
if (typeof value === 'undefined' || value === null) {
|
||||
value = def.default;
|
||||
}
|
||||
|
||||
if ('min' in def) {
|
||||
value = Math.max(def.min!, value);
|
||||
}
|
||||
|
||||
if ('max' in def) {
|
||||
value = Math.min(def.max!, value);
|
||||
}
|
||||
|
||||
if ('options' in def && !(value in def.options!)) {
|
||||
value = def.default;
|
||||
} else if ('multipleOptions' in def) {
|
||||
if (value.length) {
|
||||
const validOptions = Object.keys(def.multipleOptions!);
|
||||
value.forEach((item: any, idx: number) => {
|
||||
(validOptions.indexOf(item) === -1) && value.splice(idx, 1);
|
||||
});
|
||||
}
|
||||
|
||||
if (!value.length) {
|
||||
value = def.default;
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
}
|
@@ -1,116 +1,78 @@
|
||||
import { CE } from "@utils/html";
|
||||
import { SUPPORTED_LANGUAGES, t} from "@utils/translation";
|
||||
import { SettingElement, SettingElementType } from "@utils/settings";
|
||||
import { UserAgent } from "@utils/user-agent";
|
||||
import { StreamStat } from "@modules/stream/stream-stats";
|
||||
import type { PreferenceSetting, PreferenceSettings } from "@/types/preferences";
|
||||
import { AppInterface, STATES } from "@utils/global";
|
||||
import { StreamPlayerType, StreamVideoProcessing } from "@enums/stream-player";
|
||||
import { UserAgentProfile } from "@/enums/user-agent";
|
||||
import { UiSection } from "@/enums/ui-sections";
|
||||
import { BypassServers } from "@/enums/bypass-servers";
|
||||
import { BX_FLAGS } from "./bx-flags";
|
||||
import { PrefKey, StorageKey } from "@/enums/pref-keys";
|
||||
import { StreamPlayerType, StreamVideoProcessing } from "@/enums/stream-player";
|
||||
import { UiSection } from "@/enums/ui-sections";
|
||||
import { UserAgentProfile } from "@/enums/user-agent";
|
||||
import { StreamStat } from "@/modules/stream/stream-stats";
|
||||
import type { PreferenceSetting } from "@/types/preferences";
|
||||
import type { SettingDefinitions } from "@/types/setting-definition";
|
||||
import { BX_FLAGS } from "../bx-flags";
|
||||
import { STATES, AppInterface, STORAGE } from "../global";
|
||||
import { CE } from "../html";
|
||||
import { SettingElementType } from "../setting-element";
|
||||
import { t, SUPPORTED_LANGUAGES } from "../translation";
|
||||
import { UserAgent } from "../user-agent";
|
||||
import { BaseSettingsStore as BaseSettingsStorage } from "./base-settings-storage";
|
||||
|
||||
export enum PrefKey {
|
||||
LAST_UPDATE_CHECK = 'version_last_check',
|
||||
LATEST_VERSION = 'version_latest',
|
||||
CURRENT_VERSION = 'version_current',
|
||||
|
||||
BETTER_XCLOUD_LOCALE = 'bx_locale',
|
||||
function getSupportedCodecProfiles() {
|
||||
const options: {[index: string]: string} = {
|
||||
default: t('default'),
|
||||
};
|
||||
|
||||
SERVER_REGION = 'server_region',
|
||||
SERVER_BYPASS_RESTRICTION = 'server_bypass_restriction',
|
||||
if (!('getCapabilities' in RTCRtpReceiver)) {
|
||||
return options;
|
||||
}
|
||||
|
||||
PREFER_IPV6_SERVER = 'prefer_ipv6_server',
|
||||
STREAM_TARGET_RESOLUTION = 'stream_target_resolution',
|
||||
STREAM_PREFERRED_LOCALE = 'stream_preferred_locale',
|
||||
STREAM_CODEC_PROFILE = 'stream_codec_profile',
|
||||
let hasLowCodec = false;
|
||||
let hasNormalCodec = false;
|
||||
let hasHighCodec = false;
|
||||
|
||||
USER_AGENT_PROFILE = 'user_agent_profile',
|
||||
STREAM_SIMPLIFY_MENU = 'stream_simplify_menu',
|
||||
const codecs = RTCRtpReceiver.getCapabilities('video')!.codecs;
|
||||
for (let codec of codecs) {
|
||||
if (codec.mimeType.toLowerCase() !== 'video/h264' || !codec.sdpFmtpLine) {
|
||||
continue;
|
||||
}
|
||||
|
||||
STREAM_COMBINE_SOURCES = 'stream_combine_sources',
|
||||
const fmtp = codec.sdpFmtpLine.toLowerCase();
|
||||
if (fmtp.includes('profile-level-id=4d')) {
|
||||
hasHighCodec = true;
|
||||
} else if (fmtp.includes('profile-level-id=42e')) {
|
||||
hasNormalCodec = true;
|
||||
} else if (fmtp.includes('profile-level-id=420')) {
|
||||
hasLowCodec = true;
|
||||
}
|
||||
}
|
||||
|
||||
STREAM_TOUCH_CONTROLLER = 'stream_touch_controller',
|
||||
STREAM_TOUCH_CONTROLLER_AUTO_OFF = 'stream_touch_controller_auto_off',
|
||||
STREAM_TOUCH_CONTROLLER_DEFAULT_OPACITY = 'stream_touch_controller_default_opacity',
|
||||
STREAM_TOUCH_CONTROLLER_STYLE_STANDARD = 'stream_touch_controller_style_standard',
|
||||
STREAM_TOUCH_CONTROLLER_STYLE_CUSTOM = 'stream_touch_controller_style_custom',
|
||||
if (hasHighCodec) {
|
||||
if (!hasLowCodec && !hasNormalCodec) {
|
||||
options.default = `${t('visual-quality-high')} (${t('default')})`;
|
||||
} else {
|
||||
options.high = t('visual-quality-high');
|
||||
}
|
||||
}
|
||||
|
||||
STREAM_DISABLE_FEEDBACK_DIALOG = 'stream_disable_feedback_dialog',
|
||||
if (hasNormalCodec) {
|
||||
if (!hasLowCodec && !hasHighCodec) {
|
||||
options.default = `${t('visual-quality-normal')} (${t('default')})`;
|
||||
} else {
|
||||
options.normal = t('visual-quality-normal');
|
||||
}
|
||||
}
|
||||
|
||||
BITRATE_VIDEO_MAX = 'bitrate_video_max',
|
||||
if (hasLowCodec) {
|
||||
if (!hasNormalCodec && !hasHighCodec) {
|
||||
options.default = `${t('visual-quality-low')} (${t('default')})`;
|
||||
} else {
|
||||
options.low = t('visual-quality-low');
|
||||
}
|
||||
}
|
||||
|
||||
GAME_BAR_POSITION = 'game_bar_position',
|
||||
|
||||
LOCAL_CO_OP_ENABLED = 'local_co_op_enabled',
|
||||
// LOCAL_CO_OP_SEPARATE_TOUCH_CONTROLLER = 'local_co_op_separate_touch_controller',
|
||||
|
||||
CONTROLLER_ENABLE_SHORTCUTS = 'controller_enable_shortcuts',
|
||||
CONTROLLER_ENABLE_VIBRATION = 'controller_enable_vibration',
|
||||
CONTROLLER_DEVICE_VIBRATION = 'controller_device_vibration',
|
||||
CONTROLLER_VIBRATION_INTENSITY = 'controller_vibration_intensity',
|
||||
CONTROLLER_SHOW_CONNECTION_STATUS = 'controller_show_connection_status',
|
||||
|
||||
NATIVE_MKB_ENABLED = 'native_mkb_enabled',
|
||||
NATIVE_MKB_SCROLL_HORIZONTAL_SENSITIVITY = 'native_mkb_scroll_x_sensitivity',
|
||||
NATIVE_MKB_SCROLL_VERTICAL_SENSITIVITY = 'native_mkb_scroll_y_sensitivity',
|
||||
|
||||
MKB_ENABLED = 'mkb_enabled',
|
||||
MKB_HIDE_IDLE_CURSOR = 'mkb_hide_idle_cursor',
|
||||
MKB_ABSOLUTE_MOUSE = 'mkb_absolute_mouse',
|
||||
MKB_DEFAULT_PRESET_ID = 'mkb_default_preset_id',
|
||||
|
||||
SCREENSHOT_APPLY_FILTERS = 'screenshot_apply_filters',
|
||||
|
||||
BLOCK_TRACKING = 'block_tracking',
|
||||
BLOCK_SOCIAL_FEATURES = 'block_social_features',
|
||||
SKIP_SPLASH_VIDEO = 'skip_splash_video',
|
||||
HIDE_DOTS_ICON = 'hide_dots_icon',
|
||||
REDUCE_ANIMATIONS = 'reduce_animations',
|
||||
|
||||
UI_LOADING_SCREEN_GAME_ART = 'ui_loading_screen_game_art',
|
||||
UI_LOADING_SCREEN_WAIT_TIME = 'ui_loading_screen_wait_time',
|
||||
UI_LOADING_SCREEN_ROCKET = 'ui_loading_screen_rocket',
|
||||
|
||||
UI_CONTROLLER_FRIENDLY = 'ui_controller_friendly',
|
||||
UI_LAYOUT = 'ui_layout',
|
||||
UI_SCROLLBAR_HIDE = 'ui_scrollbar_hide',
|
||||
UI_HIDE_SECTIONS = 'ui_hide_sections',
|
||||
|
||||
UI_HOME_CONTEXT_MENU_DISABLED = 'ui_home_context_menu_disabled',
|
||||
UI_GAME_CARD_SHOW_WAIT_TIME = 'ui_game_card_show_wait_time',
|
||||
|
||||
VIDEO_PLAYER_TYPE = 'video_player_type',
|
||||
VIDEO_PROCESSING = 'video_processing',
|
||||
VIDEO_POWER_PREFERENCE = 'video_power_preference',
|
||||
VIDEO_SHARPNESS = 'video_sharpness',
|
||||
VIDEO_RATIO = 'video_ratio',
|
||||
VIDEO_BRIGHTNESS = 'video_brightness',
|
||||
VIDEO_CONTRAST = 'video_contrast',
|
||||
VIDEO_SATURATION = 'video_saturation',
|
||||
|
||||
AUDIO_MIC_ON_PLAYING = 'audio_mic_on_playing',
|
||||
AUDIO_ENABLE_VOLUME_CONTROL = 'audio_enable_volume_control',
|
||||
AUDIO_VOLUME = 'audio_volume',
|
||||
|
||||
STATS_ITEMS = 'stats_items',
|
||||
STATS_SHOW_WHEN_PLAYING = 'stats_show_when_playing',
|
||||
STATS_QUICK_GLANCE = 'stats_quick_glance',
|
||||
STATS_POSITION = 'stats_position',
|
||||
STATS_TEXT_SIZE = 'stats_text_size',
|
||||
STATS_TRANSPARENT = 'stats_transparent',
|
||||
STATS_OPACITY = 'stats_opacity',
|
||||
STATS_CONDITIONAL_FORMATTING = 'stats_conditional_formatting',
|
||||
|
||||
REMOTE_PLAY_ENABLED = 'xhome_enabled',
|
||||
REMOTE_PLAY_RESOLUTION = 'xhome_resolution',
|
||||
|
||||
GAME_FORTNITE_FORCE_CONSOLE = 'game_fortnite_force_console',
|
||||
return options;
|
||||
}
|
||||
|
||||
export class Preferences {
|
||||
static SETTINGS: PreferenceSettings = {
|
||||
export class GlobalSettingsStorage extends BaseSettingsStorage {
|
||||
private static readonly DEFINITIONS: SettingDefinitions = {
|
||||
[PrefKey.LAST_UPDATE_CHECK]: {
|
||||
default: 0,
|
||||
},
|
||||
@@ -185,61 +147,7 @@ export class Preferences {
|
||||
[PrefKey.STREAM_CODEC_PROFILE]: {
|
||||
label: t('visual-quality'),
|
||||
default: 'default',
|
||||
options: (() => {
|
||||
const options: {[index: string]: string} = {
|
||||
default: t('default'),
|
||||
};
|
||||
|
||||
if (!('getCapabilities' in RTCRtpReceiver)) {
|
||||
return options;
|
||||
}
|
||||
|
||||
let hasLowCodec = false;
|
||||
let hasNormalCodec = false;
|
||||
let hasHighCodec = false;
|
||||
|
||||
const codecs = RTCRtpReceiver.getCapabilities('video')!.codecs;
|
||||
for (let codec of codecs) {
|
||||
if (codec.mimeType.toLowerCase() !== 'video/h264' || !codec.sdpFmtpLine) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const fmtp = codec.sdpFmtpLine.toLowerCase();
|
||||
if (!hasHighCodec && fmtp.includes('profile-level-id=4d')) {
|
||||
hasHighCodec = true;
|
||||
} else if (!hasNormalCodec && fmtp.includes('profile-level-id=42e')) {
|
||||
hasNormalCodec = true;
|
||||
} else if (!hasLowCodec && fmtp.includes('profile-level-id=420')) {
|
||||
hasLowCodec = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (hasHighCodec) {
|
||||
if (!hasLowCodec && !hasNormalCodec) {
|
||||
options.default = `${t('visual-quality-high')} (${t('default')})`;
|
||||
} else {
|
||||
options.high = t('visual-quality-high');
|
||||
}
|
||||
}
|
||||
|
||||
if (hasNormalCodec) {
|
||||
if (!hasLowCodec && !hasHighCodec) {
|
||||
options.default = `${t('visual-quality-normal')} (${t('default')})`;
|
||||
} else {
|
||||
options.normal = t('visual-quality-normal');
|
||||
}
|
||||
}
|
||||
|
||||
if (hasLowCodec) {
|
||||
if (!hasNormalCodec && !hasHighCodec) {
|
||||
options.default = `${t('visual-quality-low')} (${t('default')})`;
|
||||
} else {
|
||||
options.low = t('visual-quality-low');
|
||||
}
|
||||
}
|
||||
|
||||
return options;
|
||||
})(),
|
||||
options: getSupportedCodecProfiles(),
|
||||
ready: (setting: PreferenceSetting) => {
|
||||
const options: any = setting.options;
|
||||
const keys = Object.keys(options);
|
||||
@@ -366,16 +274,6 @@ export class Preferences {
|
||||
}
|
||||
},
|
||||
},
|
||||
migrate: function(savedPrefs: any, value: any) {
|
||||
try {
|
||||
value = parseInt(value);
|
||||
if (value !== 0 && value < 100) {
|
||||
value *= 1024 * 1000;
|
||||
}
|
||||
this.set(PrefKey.BITRATE_VIDEO_MAX, value, true);
|
||||
savedPrefs[PrefKey.BITRATE_VIDEO_MAX] = value;
|
||||
} catch (e) {}
|
||||
},
|
||||
},
|
||||
|
||||
[PrefKey.GAME_BAR_POSITION]: {
|
||||
@@ -559,7 +457,7 @@ export class Preferences {
|
||||
|
||||
[PrefKey.UI_CONTROLLER_FRIENDLY]: {
|
||||
label: t('controller-friendly-ui'),
|
||||
default: !STATES.browser.capabilities.touch || BX_FLAGS.DeviceInfo?.deviceType === "android-tv",
|
||||
default: BX_FLAGS.DeviceInfo!.deviceType !== 'unknown',
|
||||
},
|
||||
|
||||
[PrefKey.UI_LAYOUT]: {
|
||||
@@ -588,11 +486,13 @@ export class Preferences {
|
||||
multipleOptions: {
|
||||
[UiSection.NEWS]: t('section-news'),
|
||||
[UiSection.FRIENDS]: t('section-play-with-friends'),
|
||||
// [UiSection.MOST_POPULAR]: t('section-most-popular'),
|
||||
[UiSection.NATIVE_MKB]: t('section-native-mkb'),
|
||||
[UiSection.TOUCH]: t('section-touch'),
|
||||
[UiSection.MOST_POPULAR]: t('section-most-popular'),
|
||||
[UiSection.ALL_GAMES]: t('section-all-games'),
|
||||
},
|
||||
params: {
|
||||
size: 3,
|
||||
size: 6,
|
||||
},
|
||||
},
|
||||
|
||||
@@ -612,14 +512,14 @@ export class Preferences {
|
||||
[PrefKey.USER_AGENT_PROFILE]: {
|
||||
label: t('user-agent-profile'),
|
||||
note: '⚠️ ' + t('unexpected-behavior'),
|
||||
default: BX_FLAGS.DeviceInfo?.deviceType === 'android-tv' ? UserAgentProfile.VR_OCULUS : 'default',
|
||||
default: BX_FLAGS.DeviceInfo!.deviceType === 'android-tv' ? UserAgentProfile.VR_OCULUS : 'default',
|
||||
options: {
|
||||
[UserAgentProfile.DEFAULT]: t('default'),
|
||||
[UserAgentProfile.WINDOWS_EDGE]: 'Edge + Windows',
|
||||
[UserAgentProfile.MACOS_SAFARI]: 'Safari + macOS',
|
||||
[UserAgentProfile.VR_OCULUS]: 'Android TV',
|
||||
[UserAgentProfile.SMART_TV_GENERIC]: 'Smart TV',
|
||||
[UserAgentProfile.SMART_TV_TIZEN]: 'Samsung Smart TV',
|
||||
[UserAgentProfile.VR_OCULUS]: 'Meta Quest VR',
|
||||
[UserAgentProfile.CUSTOM]: t('custom'),
|
||||
},
|
||||
},
|
||||
@@ -640,7 +540,7 @@ export class Preferences {
|
||||
},
|
||||
},
|
||||
[PrefKey.VIDEO_POWER_PREFERENCE]: {
|
||||
label: t('gpu-configuration'),
|
||||
label: t('renderer-configuration'),
|
||||
default: 'default',
|
||||
options: {
|
||||
'default': t('default'),
|
||||
@@ -725,7 +625,7 @@ export class Preferences {
|
||||
default: 100,
|
||||
min: 0,
|
||||
max: 600,
|
||||
steps: 20,
|
||||
steps: 10,
|
||||
params: {
|
||||
suffix: '%',
|
||||
ticks: 100,
|
||||
@@ -813,169 +713,16 @@ export class Preferences {
|
||||
default: false,
|
||||
note: t('fortnite-allow-stw-mode'),
|
||||
},
|
||||
|
||||
// Deprecated
|
||||
/*
|
||||
[Preferences.DEPRECATED_CONTROLLER_SUPPORT_LOCAL_CO_OP]: {
|
||||
default: false,
|
||||
'migrate': function(savedPrefs, value) {
|
||||
this.set(Preferences.LOCAL_CO_OP_ENABLED, value);
|
||||
savedPrefs[Preferences.LOCAL_CO_OP_ENABLED] = value;
|
||||
},
|
||||
},
|
||||
*/
|
||||
}
|
||||
|
||||
#storage = localStorage;
|
||||
#key = 'better_xcloud';
|
||||
#prefs: {[index: string]: any} = {};
|
||||
};
|
||||
|
||||
constructor() {
|
||||
let savedPrefsStr = this.#storage.getItem(this.#key);
|
||||
if (savedPrefsStr == null) {
|
||||
savedPrefsStr = '{}';
|
||||
}
|
||||
|
||||
const savedPrefs = JSON.parse(savedPrefsStr);
|
||||
|
||||
for (let settingId in Preferences.SETTINGS) {
|
||||
const setting = Preferences.SETTINGS[settingId];
|
||||
|
||||
if (setting.migrate && settingId in savedPrefs) {
|
||||
setting.migrate.call(this, savedPrefs, savedPrefs[settingId]);
|
||||
}
|
||||
|
||||
setting.ready && setting.ready.call(this, setting);
|
||||
}
|
||||
|
||||
for (let settingId in Preferences.SETTINGS) {
|
||||
const setting = Preferences.SETTINGS[settingId];
|
||||
if (!setting) {
|
||||
alert(`Undefined setting key: ${settingId}`);
|
||||
console.log('Undefined setting key');
|
||||
continue;
|
||||
}
|
||||
|
||||
// Ignore deprecated/migrated settings
|
||||
if (setting.migrate) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (settingId in savedPrefs) {
|
||||
this.#prefs[settingId] = this.#validateValue(settingId, savedPrefs[settingId]);
|
||||
} else {
|
||||
this.#prefs[settingId] = setting.default;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#validateValue(key: keyof typeof Preferences.SETTINGS, value: any) {
|
||||
const config = Preferences.SETTINGS[key];
|
||||
if (!config) {
|
||||
return value;
|
||||
}
|
||||
|
||||
if (typeof value === 'undefined' || value === null) {
|
||||
value = config.default;
|
||||
}
|
||||
|
||||
if ('min' in config) {
|
||||
value = Math.max(config.min!, value);
|
||||
}
|
||||
|
||||
if ('max' in config) {
|
||||
value = Math.min(config.max!, value);
|
||||
}
|
||||
|
||||
if ('options' in config && !(value in config.options!)) {
|
||||
value = config.default;
|
||||
} else if ('multipleOptions' in config) {
|
||||
if (value.length) {
|
||||
const validOptions = Object.keys(config.multipleOptions!);
|
||||
value.forEach((item: any, idx: number) => {
|
||||
(validOptions.indexOf(item) === -1) && value.splice(idx, 1);
|
||||
});
|
||||
}
|
||||
|
||||
if (!value.length) {
|
||||
value = config.default;
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
get(key: PrefKey) {
|
||||
if (typeof key === 'undefined') {
|
||||
debugger;
|
||||
return;
|
||||
}
|
||||
|
||||
// Return default value if the feature is not supported
|
||||
if (Preferences.SETTINGS[key].unsupported) {
|
||||
return Preferences.SETTINGS[key].default;
|
||||
}
|
||||
|
||||
if (!(key in this.#prefs)) {
|
||||
this.#prefs[key] = this.#validateValue(key, null);
|
||||
}
|
||||
|
||||
return this.#prefs[key];
|
||||
}
|
||||
|
||||
set(key: PrefKey, value: any, skipSave?: boolean): any {
|
||||
value = this.#validateValue(key, value);
|
||||
|
||||
this.#prefs[key] = value;
|
||||
!skipSave && this.#updateStorage();
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
#updateStorage() {
|
||||
this.#storage.setItem(this.#key, JSON.stringify(this.#prefs));
|
||||
}
|
||||
|
||||
toElement(key: keyof typeof Preferences.SETTINGS, onChange: any, overrideParams={}) {
|
||||
const setting = Preferences.SETTINGS[key];
|
||||
let currentValue = this.get(key);
|
||||
|
||||
let type;
|
||||
if ('type' in setting) {
|
||||
type = setting.type;
|
||||
} else if ('options' in setting) {
|
||||
type = SettingElementType.OPTIONS;
|
||||
} else if ('multipleOptions' in setting) {
|
||||
type = SettingElementType.MULTIPLE_OPTIONS;
|
||||
} else if (typeof setting.default === 'number') {
|
||||
type = SettingElementType.NUMBER;
|
||||
} else {
|
||||
type = SettingElementType.CHECKBOX;
|
||||
}
|
||||
|
||||
const params = Object.assign(overrideParams, setting.params || {});
|
||||
if (params.disabled) {
|
||||
currentValue = Preferences.SETTINGS[key].default;
|
||||
}
|
||||
|
||||
const $control = SettingElement.render(type!, key as string, setting, currentValue, (e: any, value: any) => {
|
||||
this.set(key, value);
|
||||
onChange && onChange(e, value);
|
||||
}, params);
|
||||
|
||||
return $control;
|
||||
}
|
||||
|
||||
toNumberStepper(key: keyof typeof Preferences.SETTINGS, onChange: any, options={}) {
|
||||
return SettingElement.render(SettingElementType.NUMBER_STEPPER, key, Preferences.SETTINGS[key], this.get(key), (e: any, value: any) => {
|
||||
this.set(key, value);
|
||||
onChange && onChange(e, value);
|
||||
}, options);
|
||||
super(StorageKey.GLOBAL, GlobalSettingsStorage.DEFINITIONS);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const prefs = new Preferences();
|
||||
export const getPref = prefs.get.bind(prefs);
|
||||
export const setPref = prefs.set.bind(prefs);
|
||||
export const toPrefElement = prefs.toElement.bind(prefs);
|
||||
const globalSettings = new GlobalSettingsStorage();
|
||||
export const getPrefDefinition = globalSettings.getDefinition.bind(globalSettings);
|
||||
export const getPref = globalSettings.getSetting.bind(globalSettings);
|
||||
export const setPref = globalSettings.setSetting.bind(globalSettings);
|
||||
STORAGE.Global = globalSettings;
|
@@ -46,10 +46,12 @@ const Texts = {
|
||||
"badge-playtime": "Playtime",
|
||||
"badge-server": "Server",
|
||||
"badge-video": "Video",
|
||||
"better-xcloud": "Better xCloud",
|
||||
"bitrate-audio-maximum": "Maximum audio bitrate",
|
||||
"bitrate-video-maximum": "Maximum video bitrate",
|
||||
"bottom-left": "Bottom-left",
|
||||
"bottom-right": "Bottom-right",
|
||||
"brazil": "Brazil",
|
||||
"brightness": "Brightness",
|
||||
"browser-unsupported-feature": "Your browser doesn't support this feature",
|
||||
"bypass-region-restriction": "Bypass region restriction",
|
||||
@@ -76,7 +78,7 @@ const Texts = {
|
||||
"controller-shortcuts-xbox-note": "Button to open the Guide menu",
|
||||
"controller-vibration": "Controller vibration",
|
||||
"copy": "Copy",
|
||||
"create-shortcut": "Create shortcut",
|
||||
"create-shortcut": "Shortcut",
|
||||
"custom": "Custom",
|
||||
"deadzone-counterweight": "Deadzone counterweight",
|
||||
"decrease": "Decrease",
|
||||
@@ -110,7 +112,6 @@ const Texts = {
|
||||
"fortnite-force-console-version": "Fortnite: force console version",
|
||||
"game-bar": "Game Bar",
|
||||
"getting-consoles-list": "Getting the list of consoles...",
|
||||
"gpu-configuration": "GPU configuration",
|
||||
"help": "Help",
|
||||
"hide": "Hide",
|
||||
"hide-idle-cursor": "Hide mouse cursor on idle",
|
||||
@@ -124,7 +125,8 @@ const Texts = {
|
||||
"ignore": "Ignore",
|
||||
"import": "Import",
|
||||
"increase": "Increase",
|
||||
"install-android": "Install Better xCloud app for Android",
|
||||
"install-android": "Better xCloud app for Android",
|
||||
"japan": "Japan",
|
||||
"keyboard-shortcuts": "Keyboard shortcuts",
|
||||
"language": "Language",
|
||||
"large": "Large",
|
||||
@@ -154,6 +156,7 @@ const Texts = {
|
||||
"opacity": "Opacity",
|
||||
"other": "Other",
|
||||
"playing": "Playing",
|
||||
"poland": "Poland",
|
||||
"position": "Position",
|
||||
"powered-off": "Powered off",
|
||||
"powered-on": "Powered on",
|
||||
@@ -163,9 +166,9 @@ const Texts = {
|
||||
"press-esc-to-cancel": "Press Esc to cancel",
|
||||
"press-key-to-toggle-mkb": [
|
||||
(e: any) => `Press ${e.key} to toggle this feature`,
|
||||
,
|
||||
(e: any) => `Premeu ${e.key} per alternar aquesta funció`,
|
||||
(e: any) => `${e.key}: Funktion an-/ausschalten`,
|
||||
,
|
||||
(e: any) => `Tekan ${e.key} untuk mengaktifkan fitur ini`,
|
||||
(e: any) => `Pulsa ${e.key} para alternar esta función`,
|
||||
(e: any) => `Appuyez sur ${e.key} pour activer cette fonctionnalité`,
|
||||
(e: any) => `Premi ${e.key} per attivare questa funzionalità`,
|
||||
@@ -174,7 +177,7 @@ const Texts = {
|
||||
(e: any) => `Naciśnij ${e.key} aby przełączyć tę funkcję`,
|
||||
(e: any) => `Pressione ${e.key} para alternar este recurso`,
|
||||
(e: any) => `Нажмите ${e.key} для переключения этой функции`,
|
||||
,
|
||||
(e: any) => `กด ${e.key} เพื่อสลับคุณสมบัตินี้`,
|
||||
(e: any) => `Etkinleştirmek için ${e.key} tuşuna basın`,
|
||||
(e: any) => `Натисніть ${e.key} щоб перемкнути цю функцію`,
|
||||
(e: any) => `Nhấn ${e.key} để bật/tắt tính năng này`,
|
||||
@@ -189,6 +192,7 @@ const Texts = {
|
||||
"remote-play": "Remote Play",
|
||||
"rename": "Rename",
|
||||
"renderer": "Renderer",
|
||||
"renderer-configuration": "Renderer configuration",
|
||||
"right-click-to-unbind": "Right-click on a key to unbind it",
|
||||
"right-stick": "Right stick",
|
||||
"rocket-always-hide": "Always hide",
|
||||
@@ -202,12 +206,15 @@ const Texts = {
|
||||
"screenshot-apply-filters": "Applies video filters to screenshots",
|
||||
"section-all-games": "All games",
|
||||
"section-most-popular": "Most popular",
|
||||
"section-native-mkb": "Play with mouse & keyboard",
|
||||
"section-news": "News",
|
||||
"section-play-with-friends": "Play with friends",
|
||||
"section-touch": "Play with touch",
|
||||
"separate-touch-controller": "Separate Touch controller & Controller #1",
|
||||
"separate-touch-controller-note": "Touch controller is Player 1, Controller #1 is Player 2",
|
||||
"server": "Server",
|
||||
"settings": "Settings",
|
||||
"settings-reload-note": "Settings in this tab only go into effect on the next page load",
|
||||
"settings-reload": "Reload page to reflect changes",
|
||||
"settings-reloading": "Reloading...",
|
||||
"sharpness": "Sharpness",
|
||||
@@ -271,7 +278,7 @@ const Texts = {
|
||||
(e: any) => `Układ sterowania dotykowego stworzony przez ${e.name}`,
|
||||
(e: any) => `Disposição de controle por toque feito por ${e.name}`,
|
||||
(e: any) => `Сенсорная раскладка по ${e.name}`,
|
||||
,
|
||||
(e: any) => `รูปแบบการควบคุมแบบสัมผัสโดย ${e.name}`,
|
||||
(e: any) => `${e.name} kişisinin dokunmatik kontrolcü tuş şeması`,
|
||||
(e: any) => `Розташування сенсорного керування від ${e.name}`,
|
||||
(e: any) => `Bố cục điều khiển cảm ứng tạo bởi ${e.name}`,
|
||||
@@ -282,6 +289,7 @@ const Texts = {
|
||||
"transparent-background": "Transparent background",
|
||||
"ui": "UI",
|
||||
"unexpected-behavior": "May cause unexpected behavior",
|
||||
"united-states": "United States",
|
||||
"unknown": "Unknown",
|
||||
"unlimited": "Unlimited",
|
||||
"unmuted": "Unmuted",
|
||||
@@ -320,14 +328,20 @@ export class Translations {
|
||||
static async init() {
|
||||
Translations.#enUsIndex = Translations.#supportedLocales.indexOf(Translations.#EN_US);
|
||||
|
||||
Translations.refreshCurrentLocale();
|
||||
Translations.refreshLocale();
|
||||
await Translations.#loadTranslations();
|
||||
}
|
||||
|
||||
static refreshCurrentLocale() {
|
||||
static refreshLocale(newLocale?: string) {
|
||||
let locale;
|
||||
if (newLocale) {
|
||||
localStorage.setItem(Translations.#KEY_LOCALE, newLocale);
|
||||
locale = newLocale;
|
||||
} else {
|
||||
locale = localStorage.getItem(Translations.#KEY_LOCALE);
|
||||
}
|
||||
const supportedLocales = Translations.#supportedLocales;
|
||||
|
||||
let locale = localStorage.getItem(Translations.#KEY_LOCALE);
|
||||
if (!locale) {
|
||||
// Get browser's locale
|
||||
locale = window.navigator.language || Translations.#EN_US;
|
||||
@@ -417,6 +431,10 @@ export class Translations {
|
||||
Translations.#foreignTranslations = translations;
|
||||
});
|
||||
}
|
||||
|
||||
static switchLocale(locale: string) {
|
||||
localStorage.setItem(Translations.#KEY_LOCALE, locale);
|
||||
}
|
||||
}
|
||||
|
||||
export const t = Translations.get;
|
||||
|
@@ -1,5 +1,4 @@
|
||||
import { UserAgentProfile } from "@enums/user-agent";
|
||||
import { deepClone } from "./global";
|
||||
import { BX_FLAGS } from "./bx-flags";
|
||||
|
||||
type UserAgentConfig = {
|
||||
@@ -48,14 +47,14 @@ export class UserAgent {
|
||||
}
|
||||
|
||||
static updateStorage(profile: UserAgentProfile, custom?: string) {
|
||||
const clonedConfig = deepClone(UserAgent.#config);
|
||||
clonedConfig.profile = profile;
|
||||
const config = UserAgent.#config;
|
||||
config.profile = profile;
|
||||
|
||||
if (typeof custom !== 'undefined') {
|
||||
clonedConfig.custom = custom;
|
||||
if (profile === UserAgentProfile.CUSTOM && typeof custom !== 'undefined') {
|
||||
config.custom = custom;
|
||||
}
|
||||
|
||||
window.localStorage.setItem(UserAgent.STORAGE_KEY, JSON.stringify(clonedConfig));
|
||||
window.localStorage.setItem(UserAgent.STORAGE_KEY, JSON.stringify(config));
|
||||
}
|
||||
|
||||
static getDefault(): string {
|
||||
|
@@ -1,7 +1,9 @@
|
||||
import { PrefKey, getPref, setPref } from "@utils/preferences";
|
||||
import { AppInterface, SCRIPT_VERSION } from "@utils/global";
|
||||
import { UserAgent } from "@utils/user-agent";
|
||||
import { Translations } from "./translation";
|
||||
import { Toast } from "./toast";
|
||||
import { PrefKey } from "@/enums/pref-keys";
|
||||
import { getPref, setPref } from "./settings-storages/global-settings-storage";
|
||||
|
||||
/**
|
||||
* Check for update
|
||||
@@ -95,3 +97,16 @@ export function floorToNearest(value: number, interval: number): number {
|
||||
export function roundToNearest(value: number, interval: number): number {
|
||||
return Math.round(value / interval) * interval;
|
||||
}
|
||||
|
||||
export async function copyToClipboard(text: string, showToast=true): Promise<boolean> {
|
||||
try {
|
||||
await navigator.clipboard.writeText(text);
|
||||
showToast && Toast.show('Copied to clipboard', '', {instant: true});
|
||||
return true;
|
||||
} catch (err) {
|
||||
console.error('Failed to copy: ', err);
|
||||
showToast && Toast.show('Failed to copy', '', {instant: true});
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
@@ -6,9 +6,10 @@ import { BxEvent } from "./bx-event";
|
||||
import { NATIVE_FETCH, BX_FLAGS } from "./bx-flags";
|
||||
import { STATES } from "./global";
|
||||
import { patchIceCandidates } from "./network";
|
||||
import { getPref, PrefKey } from "./preferences";
|
||||
import { getPreferredServerRegion } from "./region";
|
||||
import { BypassServerIps } from "@/enums/bypass-servers";
|
||||
import { PrefKey } from "@/enums/pref-keys";
|
||||
import { getPref } from "./settings-storages/global-settings-storage";
|
||||
|
||||
export
|
||||
class XcloudInterceptor {
|
||||
@@ -74,7 +75,7 @@ class XcloudInterceptor {
|
||||
BxEvent.dispatch(window, BxEvent.XCLOUD_SERVERS_READY);
|
||||
|
||||
const preferredRegion = getPreferredServerRegion();
|
||||
if (preferredRegion in STATES.serverRegions) {
|
||||
if (preferredRegion && preferredRegion in STATES.serverRegions) {
|
||||
const tmp = Object.assign({}, STATES.serverRegions[preferredRegion]);
|
||||
tmp.isDefault = true;
|
||||
|
||||
|
@@ -1,11 +1,12 @@
|
||||
import { RemotePlay } from "@/modules/remote-play";
|
||||
import { TouchController } from "@/modules/touch-controller";
|
||||
import { BxEvent } from "./bx-event";
|
||||
import { InputType } from "./bx-exposed";
|
||||
import { SupportedInputType } from "./bx-exposed";
|
||||
import { NATIVE_FETCH } from "./bx-flags";
|
||||
import { STATES } from "./global";
|
||||
import { getPref, PrefKey } from "./preferences";
|
||||
import { patchIceCandidates } from "./network";
|
||||
import { PrefKey } from "@/enums/pref-keys";
|
||||
import { getPref } from "./settings-storages/global-settings-storage";
|
||||
|
||||
export class XhomeInterceptor {
|
||||
static #consoleAddrs: {[index: string]: number} = {};
|
||||
@@ -67,14 +68,14 @@ export class XhomeInterceptor {
|
||||
const obj = await response.clone().json() as any;
|
||||
|
||||
const xboxTitleId = JSON.parse(opts.body).titleIds[0];
|
||||
STATES.currentStream.xboxTitleId = xboxTitleId;
|
||||
TouchController.setXboxTitleId(xboxTitleId);
|
||||
|
||||
const inputConfigs = obj[0];
|
||||
|
||||
let hasTouchSupport = inputConfigs.supportedTabs.length > 0;
|
||||
if (!hasTouchSupport) {
|
||||
const supportedInputTypes = inputConfigs.supportedInputTypes;
|
||||
hasTouchSupport = supportedInputTypes.includes(InputType.NATIVE_TOUCH) || supportedInputTypes.includes(InputType.CUSTOM_TOUCH_OVERLAY);
|
||||
hasTouchSupport = supportedInputTypes.includes(SupportedInputType.NATIVE_TOUCH) || supportedInputTypes.includes(SupportedInputType.CUSTOM_TOUCH_OVERLAY);
|
||||
}
|
||||
|
||||
if (hasTouchSupport) {
|
||||
@@ -85,7 +86,7 @@ export class XhomeInterceptor {
|
||||
});
|
||||
} else {
|
||||
TouchController.enable();
|
||||
TouchController.getCustomLayouts(xboxTitleId);
|
||||
TouchController.requestCustomLayouts(xboxTitleId);
|
||||
}
|
||||
|
||||
response.json = () => Promise.resolve(obj);
|
||||
|
Reference in New Issue
Block a user