mirror of
https://github.com/redphx/better-xcloud.git
synced 2025-07-04 13:21:43 +02:00
Compare commits
16 Commits
Author | SHA1 | Date | |
---|---|---|---|
6a8eecab06 | |||
640dd2fb5a | |||
30bb8cfbeb | |||
42b57a2cf8 | |||
210fdfbabe | |||
dbbdc48aab | |||
66123bc4ef | |||
2ecd995e47 | |||
0e03d4dc32 | |||
5b4088cc81 | |||
1f3e4b8250 | |||
daf3f72736 | |||
fbebb12965 | |||
43ef2b7cd0 | |||
e1eca20792 | |||
c2d8f1fbf7 |
2
dist/better-xcloud.meta.js
vendored
2
dist/better-xcloud.meta.js
vendored
@ -1,5 +1,5 @@
|
||||
// ==UserScript==
|
||||
// @name Better xCloud
|
||||
// @namespace https://github.com/redphx
|
||||
// @version 5.4.0
|
||||
// @version 5.4.1
|
||||
// ==/UserScript==
|
||||
|
182
dist/better-xcloud.user.js
vendored
182
dist/better-xcloud.user.js
vendored
File diff suppressed because one or more lines are too long
@ -69,7 +69,7 @@
|
||||
height: var(--bx-button-height);
|
||||
|
||||
&:not(:only-child) {
|
||||
margin-right: 4px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
@ -117,3 +117,7 @@ button.bx-inactive {
|
||||
opacity: 0.2;
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
.bx-button-shortcut {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
@ -203,3 +203,23 @@
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.bx-debug-info {
|
||||
button {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
pre {
|
||||
margin-top: 10px;
|
||||
cursor: copy;
|
||||
color: white;
|
||||
padding: 8px;
|
||||
border: 1px solid #2d2d2d;
|
||||
background: #212121;
|
||||
white-space: break-spaces;
|
||||
|
||||
&:hover {
|
||||
background: #272727;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,7 @@
|
||||
--bx-monospaced-font: Consolas, "Courier New", Courier, monospace;
|
||||
--bx-promptfont-font: promptfont;
|
||||
|
||||
--bx-button-height: 36px;
|
||||
--bx-button-height: 40px;
|
||||
|
||||
--bx-default-button-color: #2d3036;
|
||||
--bx-default-button-hover-color: #515863;
|
||||
|
@ -1,6 +1,9 @@
|
||||
.bx-stream-settings-dialog {
|
||||
display: flex;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: var(--bx-stream-settings-z-index);
|
||||
opacity: 0.98;
|
||||
user-select: none;
|
||||
@ -22,10 +25,8 @@
|
||||
}
|
||||
|
||||
.bx-stream-settings-tabs {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 420px;
|
||||
display: flex;
|
||||
position: fixed;
|
||||
flex-direction: column;
|
||||
border-radius: 0 0 0 8px;
|
||||
box-shadow: 0px 0px 6px #000;
|
||||
@ -60,10 +61,6 @@
|
||||
|
||||
.bx-stream-settings-tab-contents {
|
||||
flex-direction: column;
|
||||
position: fixed;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
padding: 14px 14px 0;
|
||||
width: 420px;
|
||||
background: #1a1b1e;
|
||||
@ -74,6 +71,8 @@
|
||||
text-align: center;
|
||||
box-shadow: 0px 0px 6px #000;
|
||||
overflow: overlay;
|
||||
margin-left: 56px;
|
||||
z-index: 1;
|
||||
|
||||
> div[data-tab-group=mkb] {
|
||||
display: flex;
|
||||
@ -108,6 +107,11 @@
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 500px) {
|
||||
.bx-stream-settings-tab-contents {
|
||||
width: calc(100vw - 56px);
|
||||
}
|
||||
}
|
||||
|
||||
.bx-stream-settings-row {
|
||||
display: flex;
|
||||
|
4
src/assets/svg/create-shortcut.svg
Normal file
4
src/assets/svg/create-shortcut.svg
Normal file
@ -0,0 +1,4 @@
|
||||
<svg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='#fff' fill-rule='evenodd' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' viewBox='0 0 32 32'>
|
||||
<path d="M13.253 3.639c0-.758-.615-1.373-1.373-1.373H3.639c-.758 0-1.373.615-1.373 1.373v8.241c0 .758.615 1.373 1.373 1.373h8.241c.758 0 1.373-.615 1.373-1.373V3.639zm0 16.481c0-.758-.615-1.373-1.373-1.373H3.639c-.758 0-1.373.615-1.373 1.373v8.241c0 .758.615 1.373 1.373 1.373h8.241c.758 0 1.373-.615 1.373-1.373V20.12zm16.481 0c0-.758-.615-1.373-1.373-1.373H20.12c-.758 0-1.373.615-1.373 1.373v8.241c0 .758.615 1.373 1.373 1.373h8.241c.758 0 1.373-.615 1.373-1.373V20.12zM19.262 7.76h9.957"/>
|
||||
<path d="M24.24 2.781v9.957"/>
|
||||
</svg>
|
After Width: | Height: | Size: 711 B |
@ -35,6 +35,7 @@ import { updateVideoPlayer } from "./modules/stream/stream-settings-utils";
|
||||
import { UiSection } from "./enums/ui-sections";
|
||||
import { HeaderSection } from "./modules/ui/header";
|
||||
import { GameTile } from "./modules/ui/game-tile";
|
||||
import { ProductDetailsPage } from "./modules/ui/product-details";
|
||||
|
||||
|
||||
// Handle login page
|
||||
@ -198,6 +199,13 @@ window.addEventListener(BxEvent.STREAM_ERROR_PAGE, e => {
|
||||
BxEvent.dispatch(window, BxEvent.STREAM_STOPPED);
|
||||
});
|
||||
|
||||
window.addEventListener(BxEvent.XCLOUD_RENDERING_COMPONENT, e => {
|
||||
const component = (e as any).component;
|
||||
if (component === 'product-details') {
|
||||
ProductDetailsPage.injectShortcutButton();
|
||||
}
|
||||
});
|
||||
|
||||
function unload() {
|
||||
if (!STATES.isPlaying) {
|
||||
return;
|
||||
|
@ -183,14 +183,14 @@ export class ControllerShortcut {
|
||||
$fragment.appendChild($option);
|
||||
}
|
||||
|
||||
$container.dataset.hasGamepad = hasGamepad.toString();
|
||||
if (hasGamepad) {
|
||||
$select.appendChild($fragment);
|
||||
|
||||
$select.selectedIndex = 0;
|
||||
$select.dispatchEvent(new Event('change'));
|
||||
$select.dispatchEvent(new Event('input'));
|
||||
}
|
||||
|
||||
$container.dataset.hasGamepad = hasGamepad.toString();
|
||||
}
|
||||
|
||||
static #switchProfile(profile: string) {
|
||||
@ -205,9 +205,9 @@ export class ControllerShortcut {
|
||||
const $select = ControllerShortcut.#$selectActions[button as GamepadKey]!;
|
||||
$select.value = actions[button] || '';
|
||||
|
||||
BxEvent.dispatch($select, 'change', {
|
||||
ignoreOnChange: true,
|
||||
});
|
||||
BxEvent.dispatch($select, 'input', {
|
||||
ignoreOnChange: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { SCRIPT_VERSION, STATES } from "@utils/global";
|
||||
import { AppInterface, SCRIPT_VERSION, STATES } from "@utils/global";
|
||||
import { BX_FLAGS } from "@utils/bx-flags";
|
||||
import { getPref, PrefKey } from "@utils/preferences";
|
||||
import { VibrationManager } from "@modules/vibration-manager";
|
||||
@ -799,6 +799,22 @@ if (this.baseStorageKey in window.BX_EXPOSED.overrideSettings) {
|
||||
str = str.substring(0, index) + codeSetCurrentlyFocusedInteractable + str.substring(index);
|
||||
return str;
|
||||
},
|
||||
|
||||
// product-details-page.js#2388, 24.17.20
|
||||
detectProductDetailsPage(str: string) {
|
||||
let index = str.indexOf('{location:"ProductDetailPage",');
|
||||
if (index === -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
index = str.indexOf('return', index - 40);
|
||||
if (index === -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
str = str.substring(0, index) + 'BxEvent.dispatch(window, BxEvent.XCLOUD_RENDERING_COMPONENT, {component: "product-details"});' + str.substring(index);
|
||||
return str;
|
||||
},
|
||||
};
|
||||
|
||||
let PATCH_ORDERS: PatchArray = [
|
||||
@ -820,6 +836,7 @@ let PATCH_ORDERS: PatchArray = [
|
||||
'exposeDialogRoutes',
|
||||
|
||||
'enableTvRoutes',
|
||||
AppInterface && 'detectProductDetailsPage',
|
||||
|
||||
'overrideStorageGetSettings',
|
||||
getPref(PrefKey.UI_GAME_CARD_SHOW_WAIT_TIME) && 'patchSetCurrentlyFocusedInteractable',
|
||||
|
@ -1,6 +1,7 @@
|
||||
import vertClarityBoost from "./shaders/clarity_boost.vert" with { type: "text" };
|
||||
import fsClarityBoost from "./shaders/clarity_boost.fs" with { type: "text" };
|
||||
import { BxLogger } from "@/utils/bx-logger";
|
||||
import { getPref, PrefKey } from "@/utils/preferences";
|
||||
|
||||
|
||||
const LOG_TAG = 'WebGL2Player';
|
||||
@ -120,11 +121,13 @@ export class WebGL2Player {
|
||||
}
|
||||
|
||||
#setupShaders() {
|
||||
BxLogger.info(LOG_TAG, 'Setting up', getPref(PrefKey.VIDEO_POWER_PREFERENCE));
|
||||
|
||||
const gl = this.#$canvas.getContext('webgl2', {
|
||||
isBx: true,
|
||||
antialias: true,
|
||||
alpha: false,
|
||||
powerPreference: 'high-performance',
|
||||
powerPreference: getPref(PrefKey.VIDEO_POWER_PREFERENCE),
|
||||
}) as WebGL2RenderingContext;
|
||||
this.#gl = gl;
|
||||
|
||||
|
@ -260,9 +260,20 @@ export class StreamPlayer {
|
||||
this.#resizePlayer();
|
||||
}
|
||||
|
||||
reloadPlayer() {
|
||||
this.#cleanUpWebGL2Player();
|
||||
|
||||
this.#playerType = StreamPlayerType.VIDEO;
|
||||
this.setPlayerType(StreamPlayerType.WEBGL2, false);
|
||||
}
|
||||
|
||||
#cleanUpWebGL2Player() {
|
||||
// Clean up WebGL2 Player
|
||||
this.#webGL2Player?.destroy();
|
||||
this.#webGL2Player = null;
|
||||
}
|
||||
|
||||
destroy() {
|
||||
// Cleanup WebGL2 Player
|
||||
this.#webGL2Player?.destroy();
|
||||
this.#webGL2Player = null;
|
||||
this.#cleanUpWebGL2Player();
|
||||
}
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ export function onChangeVideoPlayerType() {
|
||||
const playerType = getPref(PrefKey.VIDEO_PLAYER_TYPE);
|
||||
const $videoProcessing = document.getElementById('bx_setting_video_processing') as HTMLSelectElement;
|
||||
const $videoSharpness = document.getElementById('bx_setting_video_sharpness') as HTMLElement;
|
||||
const $videoPowerPreference = document.getElementById('bx_setting_video_power_preference') as HTMLElement;
|
||||
|
||||
let isDisabled = false;
|
||||
|
||||
@ -28,6 +29,9 @@ export function onChangeVideoPlayerType() {
|
||||
$videoProcessing.disabled = isDisabled;
|
||||
$videoSharpness.dataset.disabled = isDisabled.toString();
|
||||
|
||||
// Hide Power Preference setting if renderer isn't WebGL2
|
||||
$videoPowerPreference.closest('.bx-stream-settings-row')!.classList.toggle('bx-gone', playerType !== StreamPlayerType.WEBGL2);
|
||||
|
||||
updateVideoPlayer();
|
||||
}
|
||||
|
||||
|
@ -112,6 +112,17 @@ export class StreamSettings {
|
||||
}, {
|
||||
pref: PrefKey.VIDEO_PROCESSING,
|
||||
onChange: updateVideoPlayer,
|
||||
}, {
|
||||
pref: PrefKey.VIDEO_POWER_PREFERENCE,
|
||||
onChange: () => {
|
||||
const streamPlayer = STATES.currentStream.streamPlayer;
|
||||
if (!streamPlayer) {
|
||||
return;
|
||||
}
|
||||
|
||||
streamPlayer.reloadPlayer();
|
||||
updateVideoPlayer();
|
||||
},
|
||||
}, {
|
||||
pref: PrefKey.VIDEO_SHARPNESS,
|
||||
onChange: updateVideoPlayer,
|
||||
@ -324,6 +335,9 @@ export class StreamSettings {
|
||||
(window as any).BX_EXPOSED.disableGamepadPolling = true;
|
||||
|
||||
BxEvent.dispatch(window, BxEvent.XCLOUD_DIALOG_SHOWN);
|
||||
|
||||
// Update video's settings
|
||||
onChangeVideoPlayerType();
|
||||
}
|
||||
|
||||
hide() {
|
||||
@ -476,6 +490,11 @@ export class StreamSettings {
|
||||
$sibling && $sibling.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
// If it's the first/last item -> loop around
|
||||
const pseudo = direction === NavigationDirection.UP ? 'last-of-type' : 'first-of-type';
|
||||
const $target = this.$tabs!.querySelector(`svg:not(.bx-gone):${pseudo}`);
|
||||
$target && ($target as HTMLElement).focus();
|
||||
} else if (direction === NavigationDirection.RIGHT) {
|
||||
this.#focusFirstVisibleSetting();
|
||||
}
|
||||
@ -513,6 +532,12 @@ export class StreamSettings {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// If it's the first/last item -> loop around
|
||||
// TODO: bugged if pseudo is "first-of-type" and the first setting is disabled
|
||||
const pseudo = direction === NavigationDirection.UP ? ':last-of-type' : '';
|
||||
const $target = this.$settings!.querySelector(`div[data-tab-group]:not(.bx-gone) div[data-focus-container]${pseudo} [tabindex="0"]:not(:disabled):last-of-type`);
|
||||
$target && ($target as HTMLElement).focus();
|
||||
} else if (direction === NavigationDirection.LEFT || direction === NavigationDirection.RIGHT) {
|
||||
// Find all child elements with tabindex
|
||||
const children = Array.from($parent.querySelectorAll('[tabindex="0"]'));
|
||||
@ -643,6 +668,15 @@ export class StreamSettings {
|
||||
this.hide();
|
||||
});
|
||||
|
||||
// Close dialog when not clicking on any child elements in the dialog
|
||||
$container.addEventListener('click', e => {
|
||||
if (e.target === $container) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.hide();
|
||||
}
|
||||
});
|
||||
|
||||
for (const settingTab of this.SETTINGS_UI) {
|
||||
if (!settingTab) {
|
||||
continue;
|
||||
@ -753,8 +787,5 @@ export class StreamSettings {
|
||||
|
||||
document.documentElement.appendChild($overlay);
|
||||
document.documentElement.appendChild($container);
|
||||
|
||||
// Update video's settings
|
||||
onChangeVideoPlayerType();
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { STATES, AppInterface, SCRIPT_VERSION } from "@utils/global";
|
||||
import { STATES, AppInterface, SCRIPT_VERSION, deepClone } from "@utils/global";
|
||||
import { CE, createButton, ButtonStyle } from "@utils/html";
|
||||
import { BxIcon } from "@utils/bx-icon";
|
||||
import { getPreferredServerRegion } from "@utils/region";
|
||||
@ -9,6 +9,8 @@ import { PatcherCache } from "../patcher";
|
||||
import { UserAgentProfile } from "@enums/user-agent";
|
||||
import { BxSelectElement } from "@/web-components/bx-select";
|
||||
import { StreamSettings } from "../stream/stream-settings";
|
||||
import { BX_FLAGS } from "@/utils/bx-flags";
|
||||
import { Toast } from "@/utils/toast";
|
||||
|
||||
const SETTINGS_UI = {
|
||||
'Better xCloud': {
|
||||
@ -455,6 +457,47 @@ export function setupSettingsUi() {
|
||||
$wrapper.appendChild(CE('div', {'class': 'bx-settings-app-version'}, `xCloud website version ${appVersion} (${appDate})`));
|
||||
} catch (e) {}
|
||||
|
||||
// Show Debug info
|
||||
const debugInfo = deepClone(BX_FLAGS.DeviceInfo);
|
||||
const debugSettings = [
|
||||
PrefKey.STREAM_TARGET_RESOLUTION,
|
||||
PrefKey.STREAM_CODEC_PROFILE,
|
||||
|
||||
PrefKey.VIDEO_PLAYER_TYPE,
|
||||
PrefKey.VIDEO_PROCESSING,
|
||||
PrefKey.VIDEO_SHARPNESS,
|
||||
];
|
||||
|
||||
debugInfo['settings'] = {};
|
||||
for (const key of debugSettings) {
|
||||
debugInfo['settings'][key] = getPref(key);
|
||||
}
|
||||
|
||||
const $debugInfo = CE('div', {class: 'bx-debug-info'},
|
||||
createButton({
|
||||
label: 'Debug info',
|
||||
style: ButtonStyle.GHOST | ButtonStyle.FULL_WIDTH | ButtonStyle.FOCUSABLE,
|
||||
onClick: e => {
|
||||
console.log(e);
|
||||
(e.target as HTMLElement).closest('button')?.nextElementSibling?.classList.toggle('bx-gone');
|
||||
},
|
||||
}),
|
||||
CE('pre', {
|
||||
class: 'bx-gone',
|
||||
on: {
|
||||
click: async (e: Event) => {
|
||||
try {
|
||||
await navigator.clipboard.writeText((e.target as HTMLElement).innerText);
|
||||
Toast.show('Copied to clipboard', '', {instant: true});
|
||||
} catch (err) {
|
||||
console.error('Failed to copy: ', err);
|
||||
}
|
||||
},
|
||||
},
|
||||
}, '```\n' + JSON.stringify(debugInfo, null, ' ') + '\n```'),
|
||||
);
|
||||
$wrapper.appendChild($debugInfo);
|
||||
|
||||
$container.appendChild($wrapper);
|
||||
|
||||
// Add Settings UI to the web page
|
||||
|
@ -89,15 +89,15 @@ export class GuideMenu {
|
||||
// "Stream settings" button
|
||||
buttons.push(GuideMenu.#BUTTONS.streamSetting);
|
||||
|
||||
// "App settings" & "Close app" buttons
|
||||
if (AppInterface) {
|
||||
buttons.push(GuideMenu.#BUTTONS.appSettings);
|
||||
buttons.push(GuideMenu.#BUTTONS.closeApp);
|
||||
}
|
||||
// "App settings" button
|
||||
AppInterface && buttons.push(GuideMenu.#BUTTONS.appSettings);
|
||||
|
||||
// Reload page
|
||||
// "Reload page" button
|
||||
buttons.push(GuideMenu.#BUTTONS.reloadPage);
|
||||
|
||||
// "Close app" buttons
|
||||
AppInterface && buttons.push(GuideMenu.#BUTTONS.closeApp);
|
||||
|
||||
const $buttons = GuideMenu.#renderButtons(buttons);
|
||||
|
||||
const $lastDivider = $dividers[$dividers.length - 1];
|
||||
|
36
src/modules/ui/product-details.ts
Normal file
36
src/modules/ui/product-details.ts
Normal file
@ -0,0 +1,36 @@
|
||||
import { BX_FLAGS } from "@/utils/bx-flags";
|
||||
import { BxIcon } from "@/utils/bx-icon";
|
||||
import { AppInterface } from "@/utils/global";
|
||||
import { ButtonStyle, createButton } from "@/utils/html";
|
||||
import { t } from "@/utils/translation";
|
||||
|
||||
export class ProductDetailsPage {
|
||||
private static $btnShortcut = createButton({
|
||||
classes: ['bx-button-shortcut'],
|
||||
icon: BxIcon.CREATE_SHORTCUT,
|
||||
label: t('create-shortcut'),
|
||||
style: ButtonStyle.FOCUSABLE,
|
||||
tabIndex: 0,
|
||||
onClick: e => {
|
||||
AppInterface && AppInterface.createShortcut(window.location.pathname.substring(6));
|
||||
},
|
||||
});
|
||||
|
||||
private static shortcutTimeoutId: number | null = null;
|
||||
|
||||
static injectShortcutButton() {
|
||||
if (!AppInterface || BX_FLAGS.DeviceInfo?.deviceType !== 'android') {
|
||||
return;
|
||||
}
|
||||
|
||||
ProductDetailsPage.shortcutTimeoutId && clearTimeout(ProductDetailsPage.shortcutTimeoutId);
|
||||
ProductDetailsPage.shortcutTimeoutId = window.setTimeout(() => {
|
||||
// Find action buttons container
|
||||
const $container = document.querySelector('div[class*=ActionButtons-module__container]');
|
||||
if ($container) {
|
||||
this.$btnShortcut.style.width = $container.getBoundingClientRect().width + 'px';
|
||||
$container.parentElement?.appendChild(ProductDetailsPage.$btnShortcut);
|
||||
}
|
||||
}, 500);
|
||||
}
|
||||
}
|
@ -48,6 +48,8 @@ export enum BxEvent {
|
||||
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 {
|
||||
|
@ -11,6 +11,11 @@ type BxFlags = Partial<{
|
||||
FeatureGates: {[key: string]: boolean} | null,
|
||||
|
||||
IsSupportedTvBrowser: boolean,
|
||||
|
||||
DeviceInfo: Partial<{
|
||||
deviceType: 'android' | 'android-tv' | 'webos' | 'unknown',
|
||||
userAgent?: string,
|
||||
}>,
|
||||
}>
|
||||
|
||||
// Setup flags
|
||||
@ -25,6 +30,10 @@ const DEFAULT_FLAGS: BxFlags = {
|
||||
|
||||
ForceNativeMkbTitles: [],
|
||||
FeatureGates: null,
|
||||
|
||||
DeviceInfo: {
|
||||
deviceType: 'unknown',
|
||||
},
|
||||
}
|
||||
|
||||
export const BX_FLAGS: BxFlags = Object.assign(DEFAULT_FLAGS, window.BX_FLAGS || {});
|
||||
@ -32,4 +41,8 @@ try {
|
||||
delete window.BX_FLAGS;
|
||||
} catch (e) {}
|
||||
|
||||
if (!BX_FLAGS.DeviceInfo!.userAgent) {
|
||||
BX_FLAGS.DeviceInfo!.userAgent = window.navigator.userAgent;
|
||||
}
|
||||
|
||||
export const NATIVE_FETCH = window.fetch;
|
||||
|
@ -1,6 +1,7 @@
|
||||
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" };
|
||||
import iconCreateShortcut from "@assets/svg/create-shortcut.svg" with { type: "text" };
|
||||
import iconCursorText from "@assets/svg/cursor-text.svg" with { type: "text" };
|
||||
import iconDisplay from "@assets/svg/display.svg" with { type: "text" };
|
||||
import iconHome from "@assets/svg/home.svg" with { type: "text" };
|
||||
@ -11,9 +12,9 @@ import iconRefresh from "@assets/svg/refresh.svg" with { type: "text" };
|
||||
import iconRemotePlay from "@assets/svg/remote-play.svg" with { type: "text" };
|
||||
import iconStreamSettings from "@assets/svg/stream-settings.svg" with { type: "text" };
|
||||
import iconStreamStats from "@assets/svg/stream-stats.svg" with { type: "text" };
|
||||
import iconTrash from "@assets/svg/trash.svg" with { type: "text" };
|
||||
import iconTouchControlEnable from "@assets/svg/touch-control-enable.svg" with { type: "text" };
|
||||
import iconTouchControlDisable from "@assets/svg/touch-control-disable.svg" with { type: "text" };
|
||||
import iconTouchControlEnable from "@assets/svg/touch-control-enable.svg" with { type: "text" };
|
||||
import iconTrash from "@assets/svg/trash.svg" with { type: "text" };
|
||||
import iconVirtualController from "@assets/svg/virtual-controller.svg" with { type: "text" };
|
||||
|
||||
// Game Bar
|
||||
@ -37,6 +38,7 @@ export const BxIcon = {
|
||||
STREAM_STATS: iconStreamStats,
|
||||
COMMAND: iconCommand,
|
||||
CONTROLLER: iconController,
|
||||
CREATE_SHORTCUT: iconCreateShortcut,
|
||||
DISPLAY: iconDisplay,
|
||||
HOME: iconHome,
|
||||
NATIVE_MKB: iconNativeMkb,
|
||||
|
@ -4,6 +4,8 @@ import { getPref, PrefKey } from "./preferences";
|
||||
export let FeatureGates: {[key: string]: boolean} = {
|
||||
'PwaPrompt': false,
|
||||
'EnableWifiWarnings': false,
|
||||
'EnableUpdateRequiredPage': false,
|
||||
'ShowForcedUpdateScreen': false,
|
||||
};
|
||||
|
||||
// Disable context menu in Home page
|
||||
|
@ -35,18 +35,23 @@ function createElement<T=HTMLElement>(elmName: string, props: {[index: string]:
|
||||
if (hasNs) {
|
||||
$elm.setAttributeNS(null, key, props[key]);
|
||||
} else {
|
||||
$elm.setAttribute(key, props[key]);
|
||||
if (key === 'on') {
|
||||
for (const eventName in props[key]) {
|
||||
$elm.addEventListener(eventName, props[key][eventName]);
|
||||
}
|
||||
} else {
|
||||
$elm.setAttribute(key, props[key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 2, size = arguments.length; i < size; i++) {
|
||||
const arg = arguments[i];
|
||||
const argType = typeof arg;
|
||||
|
||||
if (argType === 'string' || argType === 'number') {
|
||||
$elm.appendChild(document.createTextNode(arg));
|
||||
} else if (arg) {
|
||||
if (arg instanceof Node) {
|
||||
$elm.appendChild(arg);
|
||||
} else if (arg !== null && typeof arg !== 'undefined') {
|
||||
$elm.appendChild(document.createTextNode(arg));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -9,6 +9,7 @@ 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";
|
||||
|
||||
export enum PrefKey {
|
||||
LAST_UPDATE_CHECK = 'version_last_check',
|
||||
@ -82,6 +83,7 @@ export enum PrefKey {
|
||||
|
||||
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',
|
||||
@ -557,7 +559,7 @@ export class Preferences {
|
||||
|
||||
[PrefKey.UI_CONTROLLER_FRIENDLY]: {
|
||||
label: t('controller-friendly-ui'),
|
||||
default: false,
|
||||
default: !STATES.browser.capabilities.touch || BX_FLAGS.DeviceInfo?.deviceType === "android-tv",
|
||||
},
|
||||
|
||||
[PrefKey.UI_LAYOUT]: {
|
||||
@ -610,7 +612,7 @@ export class Preferences {
|
||||
[PrefKey.USER_AGENT_PROFILE]: {
|
||||
label: t('user-agent-profile'),
|
||||
note: '⚠️ ' + t('unexpected-behavior'),
|
||||
default: 'default',
|
||||
default: BX_FLAGS.DeviceInfo?.deviceType === 'android-tv' ? UserAgentProfile.VR_OCULUS : 'default',
|
||||
options: {
|
||||
[UserAgentProfile.DEFAULT]: t('default'),
|
||||
[UserAgentProfile.WINDOWS_EDGE]: 'Edge + Windows',
|
||||
@ -637,6 +639,15 @@ export class Preferences {
|
||||
[StreamVideoProcessing.CAS]: t('amd-fidelity-cas'),
|
||||
},
|
||||
},
|
||||
[PrefKey.VIDEO_POWER_PREFERENCE]: {
|
||||
label: t('gpu-configuration'),
|
||||
default: 'default',
|
||||
options: {
|
||||
'default': t('default'),
|
||||
'high-performance': t('high-performance'),
|
||||
'low-power': t('low-power'),
|
||||
},
|
||||
},
|
||||
[PrefKey.VIDEO_SHARPNESS]: {
|
||||
label: t('sharpness'),
|
||||
type: SettingElementType.NUMBER_STEPPER,
|
||||
|
@ -76,6 +76,7 @@ const Texts = {
|
||||
"controller-shortcuts-xbox-note": "Button to open the Guide menu",
|
||||
"controller-vibration": "Controller vibration",
|
||||
"copy": "Copy",
|
||||
"create-shortcut": "Create shortcut",
|
||||
"custom": "Custom",
|
||||
"deadzone-counterweight": "Deadzone counterweight",
|
||||
"decrease": "Decrease",
|
||||
@ -109,6 +110,7 @@ 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",
|
||||
@ -116,6 +118,7 @@ const Texts = {
|
||||
"hide-sections": "Hide sections",
|
||||
"hide-system-menu-icon": "Hide System menu's icon",
|
||||
"hide-touch-controller": "Hide touch controller",
|
||||
"high-performance": "High performance",
|
||||
"horizontal-scroll-sensitivity": "Horizontal scroll sensitivity",
|
||||
"horizontal-sensitivity": "Horizontal sensitivity",
|
||||
"ignore": "Ignore",
|
||||
@ -129,6 +132,7 @@ const Texts = {
|
||||
"left-stick": "Left stick",
|
||||
"loading-screen": "Loading screen",
|
||||
"local-co-op": "Local co-op",
|
||||
"low-power": "Low power",
|
||||
"map-mouse-to": "Map mouse to",
|
||||
"may-not-work-properly": "May not work properly!",
|
||||
"menu": "Menu",
|
||||
|
@ -22,7 +22,6 @@ export class BxSelectElement {
|
||||
});
|
||||
|
||||
const isMultiple = $select.multiple;
|
||||
let visibleIndex = $select.selectedIndex;
|
||||
let $checkBox: HTMLInputElement;
|
||||
let $label: HTMLElement;
|
||||
|
||||
@ -42,7 +41,7 @@ export class BxSelectElement {
|
||||
});
|
||||
|
||||
$checkBox.addEventListener('input', e => {
|
||||
const $option = getOptionAtIndex(visibleIndex);
|
||||
const $option = getOptionAtIndex($select.selectedIndex);
|
||||
$option && ($option.selected = (e.target as HTMLInputElement).checked);
|
||||
|
||||
$select.dispatchEvent(new Event('input'));
|
||||
@ -61,7 +60,7 @@ export class BxSelectElement {
|
||||
const render = () => {
|
||||
// console.log('options', this.options, 'selectedIndices', this.selectedIndices, 'selectedOptions', this.selectedOptions);
|
||||
|
||||
visibleIndex = normalizeIndex(visibleIndex);
|
||||
const visibleIndex = normalizeIndex($select.selectedIndex);
|
||||
|
||||
const $option = getOptionAtIndex(visibleIndex);
|
||||
let content = '';
|
||||
@ -108,11 +107,10 @@ export class BxSelectElement {
|
||||
const onPrevNext = (e: Event) => {
|
||||
const goNext = e.target === $btnNext;
|
||||
|
||||
const currentIndex = visibleIndex;
|
||||
const currentIndex = $select.selectedIndex;
|
||||
let newIndex = goNext ? currentIndex + 1 : currentIndex - 1;
|
||||
newIndex = normalizeIndex(newIndex);
|
||||
|
||||
visibleIndex = newIndex;
|
||||
if (!isMultiple && newIndex !== currentIndex) {
|
||||
$select.selectedIndex = newIndex;
|
||||
}
|
||||
|
Reference in New Issue
Block a user