mirror of
https://github.com/redphx/better-xcloud.git
synced 2025-06-03 06:07:19 +02:00
Add "Suggest settings" feature
This commit is contained in:
parent
070113b764
commit
4f7e0a4f7f
@ -10,7 +10,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/bun": "^1.1.6",
|
||||
"@types/node": "^20.14.15",
|
||||
"@types/node": "^20.16.1",
|
||||
"@types/stylus": "^0.48.42",
|
||||
"eslint": "^9.9.0",
|
||||
"eslint-plugin-compat": "^6.0.0",
|
||||
|
@ -59,7 +59,7 @@ div[class^=HUDButton-module__hiddenContainer] ~ div:not([class^=HUDButton-module
|
||||
}
|
||||
|
||||
/* Remove the "Cloud Gaming" text in header when the screen is too small */
|
||||
@media screen and (max-width: 600px) {
|
||||
@media screen and (max-width: 640px) {
|
||||
header a[href="/play"] {
|
||||
display: none;
|
||||
}
|
||||
|
@ -31,6 +31,42 @@
|
||||
font-weight: normal;
|
||||
height: var(--bx-button-height);
|
||||
}
|
||||
|
||||
input {
|
||||
accent-color: var(--bx-primary-button-color);
|
||||
|
||||
&:focus {
|
||||
accent-color: var(--bx-danger-button-color);
|
||||
}
|
||||
}
|
||||
|
||||
select:disabled {
|
||||
-webkit-appearance: none;
|
||||
background: transparent;
|
||||
text-align-last: right;
|
||||
border: none;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
select option:disabled {
|
||||
display: none;
|
||||
}
|
||||
|
||||
input[type=checkbox],
|
||||
select {
|
||||
&:focus {
|
||||
filter: drop-shadow(1px 0 0 #fff) drop-shadow(-1px 0 0 #fff) drop-shadow(0 1px 0 #fff) drop-shadow(0 -1px 0 #fff);
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
color: #5dc21e;
|
||||
text-decoration: none;
|
||||
|
||||
&:hover, &:focus {
|
||||
color: #128112;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bx-settings-tabs-container {
|
||||
@ -258,13 +294,6 @@
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
input[type=checkbox],
|
||||
select {
|
||||
&:focus {
|
||||
filter: drop-shadow(1px 0 0 #fff) drop-shadow(-1px 0 0 #fff) drop-shadow(0 1px 0 #fff) drop-shadow(0 -1px 0 #fff);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
&:has(input:focus), &:has(select:focus), &:has(button:focus) {
|
||||
border-left-color: white;
|
||||
@ -282,26 +311,6 @@
|
||||
margin: 0 0 0 auto;
|
||||
}
|
||||
}
|
||||
|
||||
input {
|
||||
accent-color: var(--bx-primary-button-color);
|
||||
|
||||
&:focus {
|
||||
accent-color: var(--bx-danger-button-color);
|
||||
}
|
||||
}
|
||||
|
||||
select:disabled {
|
||||
-webkit-appearance: none;
|
||||
background: transparent;
|
||||
text-align-last: right;
|
||||
border: none;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
select option:disabled {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.bx-settings-dialog-note {
|
||||
@ -335,15 +344,6 @@
|
||||
line-height: 20px;
|
||||
font-size: 14px;
|
||||
margin-top: 10px;
|
||||
color: #5dc21e;
|
||||
|
||||
&:hover {
|
||||
color: #6dd72b;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
.bx-debug-info {
|
||||
@ -404,3 +404,166 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bx-suggest-toggler {
|
||||
text-align: left;
|
||||
display: flex;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
background: #003861;
|
||||
|
||||
label {
|
||||
flex: 1;
|
||||
margin-bottom: 0;
|
||||
padding: 10px;
|
||||
background: #004f87;
|
||||
}
|
||||
|
||||
span {
|
||||
display: inline-block;
|
||||
align-self: center;
|
||||
padding: 10px;
|
||||
width: 40px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
&:hover, &:focus {
|
||||
cursor: pointer;
|
||||
background: #005da1;
|
||||
|
||||
label {
|
||||
background: #006fbe;
|
||||
}
|
||||
}
|
||||
|
||||
&[bx-open] {
|
||||
span {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
&+ .bx-suggest-box {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bx-suggest-box {
|
||||
display: none;
|
||||
background: #161616;
|
||||
padding: 10px;
|
||||
box-shadow: 0px 0px 12px #0f0f0f inset;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.bx-suggest-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
.bx-suggest-note {
|
||||
font-size: 11px;
|
||||
color: #8c8c8c;
|
||||
font-style: italic;
|
||||
font-weight: 100;
|
||||
}
|
||||
|
||||
.bx-suggest-link {
|
||||
font-size: 12px;
|
||||
display: inline-block;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.bx-suggest-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 10px;
|
||||
|
||||
label {
|
||||
flex: 1;
|
||||
overflow: overlay;
|
||||
border-radius: 4px;
|
||||
|
||||
.bx-suggest-label {
|
||||
background: #323232;
|
||||
padding: 4px 10px;
|
||||
font-size: 12px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.bx-suggest-value {
|
||||
padding: 6px;
|
||||
font-size: 14px;
|
||||
|
||||
&.bx-suggest-change {
|
||||
background-color: var(--bx-warning-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.bx-suggest-ok {
|
||||
input {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.bx-suggest-label {
|
||||
background-color: #008114;
|
||||
}
|
||||
|
||||
.bx-suggest-value {
|
||||
background-color: #13a72a;
|
||||
}
|
||||
}
|
||||
|
||||
&.bx-suggest-change {
|
||||
.bx-suggest-label {
|
||||
background-color: #a65e08;
|
||||
}
|
||||
|
||||
.bx-suggest-value {
|
||||
background-color: #d57f18;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
label {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.bx-suggest-label {
|
||||
background-color: #995707;
|
||||
}
|
||||
|
||||
.bx-suggest-value {
|
||||
background-color: #bd7115;
|
||||
}
|
||||
}
|
||||
|
||||
// Unchecked setting
|
||||
input:not(:checked) + label {
|
||||
opacity: 0.5;
|
||||
|
||||
.bx-suggest-label {
|
||||
background-color: #2a2a2a;
|
||||
}
|
||||
|
||||
.bx-suggest-value {
|
||||
background-color: #393939;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
input:not(:checked) + label {
|
||||
opacity: 1;
|
||||
|
||||
.bx-suggest-label {
|
||||
background-color: #202020;
|
||||
}
|
||||
|
||||
.bx-suggest-value {
|
||||
background-color: #303030;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -34,7 +34,7 @@ import { GameTile } from "./modules/ui/game-tile";
|
||||
import { ProductDetailsPage } from "./modules/ui/product-details";
|
||||
import { NavigationDialogManager } from "./modules/ui/dialog/navigation-dialog";
|
||||
import { PrefKey } from "./enums/pref-keys";
|
||||
import { getPref } from "./utils/settings-storages/global-settings-storage";
|
||||
import { getPref, StreamTouchController } from "./utils/settings-storages/global-settings-storage";
|
||||
import { compressCss } from "@macros/build" with {type: "macro"};
|
||||
import { SettingsNavigationDialog } from "./modules/ui/dialog/settings-dialog";
|
||||
import { StreamUiHandler } from "./modules/stream/stream-ui";
|
||||
@ -359,7 +359,7 @@ function main() {
|
||||
RemotePlay.detect();
|
||||
}
|
||||
|
||||
if (getPref(PrefKey.STREAM_TOUCH_CONTROLLER) === 'all') {
|
||||
if (getPref(PrefKey.STREAM_TOUCH_CONTROLLER) === StreamTouchController.ALL) {
|
||||
TouchController.setup();
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { Screenshot } from "@utils/screenshot";
|
||||
import { GamepadKey } from "@enums/mkb";
|
||||
import { PrompFont } from "@enums/prompt-font";
|
||||
import { CE } from "@utils/html";
|
||||
import { CE, removeChildElements } from "@utils/html";
|
||||
import { t } from "@utils/translation";
|
||||
import { EmulatedMkbHandler } from "./mkb/mkb-handler";
|
||||
import { StreamStats } from "./stream/stream-stats";
|
||||
@ -174,9 +174,7 @@ export class ControllerShortcut {
|
||||
const $fragment = document.createDocumentFragment();
|
||||
|
||||
// Remove old profiles
|
||||
while ($select.firstElementChild) {
|
||||
$select.firstElementChild.remove();
|
||||
}
|
||||
removeChildElements($select);
|
||||
|
||||
const gamepads = navigator.getGamepads();
|
||||
let hasGamepad = false;
|
||||
|
@ -7,7 +7,7 @@ import type { BaseGameBarAction } from "./action-base";
|
||||
import { STATES } from "@utils/global";
|
||||
import { MicrophoneAction } from "./action-microphone";
|
||||
import { PrefKey } from "@/enums/pref-keys";
|
||||
import { getPref } from "@/utils/settings-storages/global-settings-storage";
|
||||
import { getPref, StreamTouchController } from "@/utils/settings-storages/global-settings-storage";
|
||||
|
||||
|
||||
export class GameBar {
|
||||
@ -42,7 +42,7 @@ export class GameBar {
|
||||
|
||||
this.actions = [
|
||||
new ScreenshotAction(),
|
||||
...(STATES.userAgent.capabilities.touch && (getPref(PrefKey.STREAM_TOUCH_CONTROLLER) !== 'off') ? [new TouchControlAction()] : []),
|
||||
...(STATES.userAgent.capabilities.touch && (getPref(PrefKey.STREAM_TOUCH_CONTROLLER) !== StreamTouchController.OFF) ? [new TouchControlAction()] : []),
|
||||
new MicrophoneAction(),
|
||||
];
|
||||
|
||||
|
@ -15,7 +15,7 @@ import codeVibrationAdjust from "./patches/vibration-adjust.js" with { type: "te
|
||||
import { FeatureGates } from "@/utils/feature-gates.js";
|
||||
import { UiSection } from "@/enums/ui-sections.js";
|
||||
import { PrefKey } from "@/enums/pref-keys.js";
|
||||
import { getPref } from "@/utils/settings-storages/global-settings-storage";
|
||||
import { getPref, StreamTouchController } from "@/utils/settings-storages/global-settings-storage";
|
||||
import { GamePassCloudGallery } from "@/enums/game-pass-gallery.js";
|
||||
|
||||
type PatchArray = (keyof typeof PATCHES)[];
|
||||
@ -393,7 +393,7 @@ if (window.BX_EXPOSED.stopTakRendering) {
|
||||
}
|
||||
|
||||
let autoOffCode = '';
|
||||
if (getPref(PrefKey.STREAM_TOUCH_CONTROLLER) === 'off') {
|
||||
if (getPref(PrefKey.STREAM_TOUCH_CONTROLLER) === StreamTouchController.OFF) {
|
||||
autoOffCode = 'return;';
|
||||
} else if (getPref(PrefKey.STREAM_TOUCH_CONTROLLER_AUTO_OFF)) {
|
||||
autoOffCode = `
|
||||
@ -450,7 +450,7 @@ e.guideUI = null;
|
||||
`;
|
||||
|
||||
// Remove the TAK Edit button when the touch controller is disabled
|
||||
if (getPref(PrefKey.STREAM_TOUCH_CONTROLLER) === 'off') {
|
||||
if (getPref(PrefKey.STREAM_TOUCH_CONTROLLER) === StreamTouchController.OFF) {
|
||||
newCode += 'e.canShowTakHUD = false;';
|
||||
}
|
||||
|
||||
@ -804,7 +804,7 @@ true` + text;
|
||||
const PREF_HIDE_SECTIONS = getPref(PrefKey.UI_HIDE_SECTIONS) as UiSection[];
|
||||
const siglIds: GamePassCloudGallery[] = [];
|
||||
|
||||
const sections: Partial<Record<UiSection, GamePassCloudGallery>> = {
|
||||
const sections: PartialRecord<UiSection, GamePassCloudGallery> = {
|
||||
[UiSection.NATIVE_MKB]: GamePassCloudGallery.NATIVE_MKB,
|
||||
[UiSection.MOST_POPULAR]: GamePassCloudGallery.MOST_POPULAR,
|
||||
};
|
||||
@ -991,9 +991,9 @@ let PLAYING_PATCH_ORDERS: PatchArray = [
|
||||
getPref(PrefKey.STREAM_DISABLE_FEEDBACK_DIALOG) && 'skipFeedbackDialog',
|
||||
|
||||
...(STATES.userAgent.capabilities.touch ? [
|
||||
getPref(PrefKey.STREAM_TOUCH_CONTROLLER) === 'all' && 'patchShowSensorControls',
|
||||
getPref(PrefKey.STREAM_TOUCH_CONTROLLER) === 'all' && 'exposeTouchLayoutManager',
|
||||
(getPref(PrefKey.STREAM_TOUCH_CONTROLLER) === 'off' || getPref(PrefKey.STREAM_TOUCH_CONTROLLER_AUTO_OFF) || !STATES.userAgent.capabilities.touch) && 'disableTakRenderer',
|
||||
getPref(PrefKey.STREAM_TOUCH_CONTROLLER) === StreamTouchController.ALL && 'patchShowSensorControls',
|
||||
getPref(PrefKey.STREAM_TOUCH_CONTROLLER) === StreamTouchController.ALL && 'exposeTouchLayoutManager',
|
||||
(getPref(PrefKey.STREAM_TOUCH_CONTROLLER) === StreamTouchController.OFF || getPref(PrefKey.STREAM_TOUCH_CONTROLLER_AUTO_OFF) || !STATES.userAgent.capabilities.touch) && 'disableTakRenderer',
|
||||
getPref(PrefKey.STREAM_TOUCH_CONTROLLER_DEFAULT_OPACITY) !== 100 && 'patchTouchControlDefaultOpacity',
|
||||
'patchBabylonRendererClass',
|
||||
] : []),
|
||||
|
@ -544,7 +544,7 @@ export class NavigationDialogManager {
|
||||
const children = Array.from($elm.children);
|
||||
|
||||
// Search from right to left if the orientation is horizontal
|
||||
const orientation = ($elm as NavigationElement).nearby?.orientation;
|
||||
const orientation = ($elm as NavigationElement).nearby?.orientation || 'vertical';
|
||||
if (orientation === 'horizontal' || (orientation === 'vertical' && direction === NavigationDirection.UP)) {
|
||||
children.reverse();
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { onChangeVideoPlayerType, updateVideoPlayer } from "@/modules/stream/stream-settings-utils";
|
||||
import { ButtonStyle, CE, createButton, createSvgIcon } from "@/utils/html";
|
||||
import { ButtonStyle, CE, createButton, createSvgIcon, removeChildElements } from "@/utils/html";
|
||||
import { NavigationDialog, NavigationDirection } from "./navigation-dialog";
|
||||
import { ControllerShortcut } from "@/modules/controller-shortcut";
|
||||
import { MkbRemapper } from "@/modules/mkb/mkb-remapper";
|
||||
@ -17,13 +17,13 @@ import { setNearby } from "@/utils/navigation-utils";
|
||||
import { PatcherCache } from "@/modules/patcher";
|
||||
import { UserAgentProfile } from "@/enums/user-agent";
|
||||
import { UserAgent } from "@/utils/user-agent";
|
||||
import { BX_FLAGS } from "@/utils/bx-flags";
|
||||
import { BX_FLAGS, NATIVE_FETCH } from "@/utils/bx-flags";
|
||||
import { copyToClipboard } from "@/utils/utils";
|
||||
import { GamepadKey } from "@/enums/mkb";
|
||||
import { PrefKey, StorageKey } from "@/enums/pref-keys";
|
||||
import { getPref, getPrefDefinition, setPref } from "@/utils/settings-storages/global-settings-storage";
|
||||
import { SettingElement } from "@/utils/setting-element";
|
||||
import type { SettingDefinition } from "@/types/setting-definition";
|
||||
import { ControllerDeviceVibration, getPref, getPrefDefinition, setPref, StreamTouchController } from "@/utils/settings-storages/global-settings-storage";
|
||||
import { SettingElement, type BxHtmlSettingElement } from "@/utils/setting-element";
|
||||
import type { RecommendedSettings, SettingDefinition, SuggestedSettingCategory as SuggestedSettingProfile } from "@/types/setting-definition";
|
||||
import { FullscreenText } from "../fullscreen-text";
|
||||
|
||||
|
||||
@ -72,9 +72,19 @@ export class SettingsNavigationDialog extends NavigationDialog {
|
||||
private $btnReload!: HTMLElement;
|
||||
private $btnGlobalReload!: HTMLButtonElement;
|
||||
private $noteGlobalReload!: HTMLElement;
|
||||
private $btnSuggestion!: HTMLButtonElement;
|
||||
|
||||
private renderFullSettings: boolean;
|
||||
|
||||
private suggestedSettings: Record<SuggestedSettingProfile, PartialRecord<PrefKey, any>> = {
|
||||
recommended: {},
|
||||
default: {},
|
||||
lowest: {},
|
||||
highest: {},
|
||||
};
|
||||
private suggestedSettingLabels: PartialRecord<PrefKey, string> = {};
|
||||
private settingElements: PartialRecord<PrefKey, HTMLElement> = {};
|
||||
|
||||
private readonly TAB_GLOBAL_ITEMS: Array<SettingTabContent | false> = [{
|
||||
group: 'general',
|
||||
label: t('better-xcloud'),
|
||||
@ -135,6 +145,17 @@ export class SettingsNavigationDialog extends NavigationDialog {
|
||||
}, t('settings-reload-note'));
|
||||
topButtons.push(this.$noteGlobalReload);
|
||||
|
||||
// Suggestion
|
||||
this.$btnSuggestion = CE('div', {
|
||||
class: 'bx-suggest-toggler bx-focusable',
|
||||
tabindex: 0,
|
||||
}, CE('label', {}, t('suggest-settings')),
|
||||
CE('span', {}, '❯'),
|
||||
);
|
||||
this.$btnSuggestion.addEventListener('click', this.renderSuggestions.bind(this));
|
||||
|
||||
topButtons.push(this.$btnSuggestion);
|
||||
|
||||
// Add buttons to parent
|
||||
const $div = CE('div', {
|
||||
class: 'bx-top-buttons',
|
||||
@ -306,7 +327,10 @@ export class SettingsNavigationDialog extends NavigationDialog {
|
||||
label: 'Debug info',
|
||||
style: ButtonStyle.GHOST | ButtonStyle.FULL_WIDTH | ButtonStyle.FOCUSABLE,
|
||||
onClick: e => {
|
||||
(e.target as HTMLElement).closest('button')?.nextElementSibling?.classList.toggle('bx-gone');
|
||||
const $pre = (e.target as HTMLElement).closest('button')?.nextElementSibling!;
|
||||
|
||||
$pre.classList.toggle('bx-gone');
|
||||
$pre.scrollIntoView();
|
||||
},
|
||||
}),
|
||||
CE('pre', {
|
||||
@ -617,6 +641,291 @@ export class SettingsNavigationDialog extends NavigationDialog {
|
||||
window.location.reload();
|
||||
}
|
||||
|
||||
private async getRecommendedSettings(deviceCode: string): Promise<string | null> {
|
||||
// Get recommended settings from GitHub
|
||||
try {
|
||||
const response = await NATIVE_FETCH(`https://raw.githubusercontent.com/redphx/better-xcloud/gh-pages/devices/${deviceCode.toLowerCase()}.json`);
|
||||
const json = (await response.json()) as RecommendedSettings;
|
||||
const recommended: PartialRecord<PrefKey, any> = {};
|
||||
|
||||
// Only supports schema version 1
|
||||
if (json.schema_version !== 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const scriptSettings = json.settings.script;
|
||||
|
||||
// Set base settings
|
||||
if (scriptSettings._base) {
|
||||
let base = typeof scriptSettings._base === 'string' ? [scriptSettings._base] : scriptSettings._base;
|
||||
for (const profile of base) {
|
||||
Object.assign(recommended, this.suggestedSettings[profile]);
|
||||
}
|
||||
|
||||
delete scriptSettings._base;
|
||||
}
|
||||
|
||||
// Override settings
|
||||
let key: Exclude<keyof typeof scriptSettings, '_base'>;
|
||||
// @ts-ignore
|
||||
for (key in scriptSettings) {
|
||||
recommended[key] = scriptSettings[key];
|
||||
}
|
||||
|
||||
// Update device type in BxFlags
|
||||
BX_FLAGS.DeviceInfo.deviceType = json.device_type;
|
||||
|
||||
this.suggestedSettings.recommended = recommended;
|
||||
|
||||
return json.device_name;
|
||||
} catch (e) {}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private addDefaultSuggestedSetting(prefKey: PrefKey, value: any) {
|
||||
let key: keyof typeof this.suggestedSettings;
|
||||
for (key in this.suggestedSettings) {
|
||||
if (key !== 'default' && !(prefKey in this.suggestedSettings)) {
|
||||
this.suggestedSettings[key][prefKey] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private generateDefaultSuggestedSettings() {
|
||||
let key: keyof typeof this.suggestedSettings;
|
||||
for (key in this.suggestedSettings) {
|
||||
if (key === 'default') {
|
||||
continue;
|
||||
}
|
||||
|
||||
let prefKey: PrefKey;
|
||||
for (prefKey in this.suggestedSettings[key]) {
|
||||
if (!(prefKey in this.suggestedSettings.default)) {
|
||||
this.suggestedSettings.default[prefKey] = getPrefDefinition(prefKey).default;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async renderSuggestions(e: Event) {
|
||||
const $btnSuggest = (e.target as HTMLElement).closest('div')!;
|
||||
$btnSuggest.toggleAttribute('bx-open');
|
||||
|
||||
let $content = $btnSuggest.nextElementSibling as HTMLElement;
|
||||
if ($content) {
|
||||
BxEvent.dispatch($content.querySelector('select'), 'input');
|
||||
return;
|
||||
}
|
||||
|
||||
// Get labels
|
||||
for (const settingTab of this.SETTINGS_UI) {
|
||||
if (!settingTab || !settingTab.items) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const settingTabContent of settingTab.items) {
|
||||
if (!settingTabContent || !settingTabContent.items) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const setting of settingTabContent.items) {
|
||||
let prefKey: PrefKey | undefined;
|
||||
|
||||
if (typeof setting === 'string') {
|
||||
prefKey = setting;
|
||||
} else if (typeof setting === 'object') {
|
||||
prefKey = setting.pref as PrefKey;
|
||||
}
|
||||
|
||||
if (prefKey) {
|
||||
this.suggestedSettingLabels[prefKey] = settingTabContent.label;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get recommended settings for Android devices
|
||||
let recommendedDevice: string | null = '';
|
||||
|
||||
if (BX_FLAGS.DeviceInfo.deviceType.includes('android')) {
|
||||
if (BX_FLAGS.DeviceInfo.androidInfo) {
|
||||
const deviceCode = BX_FLAGS.DeviceInfo.androidInfo.board;
|
||||
recommendedDevice = await this.getRecommendedSettings(deviceCode);
|
||||
}
|
||||
}
|
||||
// recommendedDevice = await this.getRecommendedSettings('foster_e');
|
||||
|
||||
const hasRecommendedSettings = Object.keys(this.suggestedSettings.recommended).length > 0;
|
||||
|
||||
// Add some specific setings based on device type
|
||||
const deviceType = BX_FLAGS.DeviceInfo.deviceType;
|
||||
if (deviceType === 'android-handheld') {
|
||||
// Disable touch
|
||||
this.addDefaultSuggestedSetting(PrefKey.STREAM_TOUCH_CONTROLLER, StreamTouchController.OFF);
|
||||
// Enable device vibration
|
||||
this.addDefaultSuggestedSetting(PrefKey.CONTROLLER_DEVICE_VIBRATION, ControllerDeviceVibration.ON);
|
||||
} else if (deviceType === 'android') {
|
||||
// Enable device vibration
|
||||
this.addDefaultSuggestedSetting(PrefKey.CONTROLLER_DEVICE_VIBRATION, ControllerDeviceVibration.AUTO);
|
||||
} else if (deviceType === 'android-tv') {
|
||||
// Disable touch
|
||||
this.addDefaultSuggestedSetting(PrefKey.STREAM_TOUCH_CONTROLLER, StreamTouchController.OFF);
|
||||
}
|
||||
|
||||
// Set value for Default profile
|
||||
this.generateDefaultSuggestedSettings();
|
||||
|
||||
// Start rendering
|
||||
const $suggestedSettings = CE('div', {class: 'bx-suggest-wrapper'});
|
||||
const $select = CE<HTMLSelectElement>('select', {},
|
||||
hasRecommendedSettings && CE('option', {value: 'recommended'}, t('recommended')),
|
||||
!hasRecommendedSettings && CE('option', {value: 'highest'}, t('highest-quality')),
|
||||
CE('option', {value: 'default'}, t('default')),
|
||||
CE('option', {value: 'lowest'}, t('lowest-quality')),
|
||||
);
|
||||
$select.addEventListener('input', e => {
|
||||
const profile = $select.value as SuggestedSettingProfile;
|
||||
|
||||
// Empty children
|
||||
removeChildElements($suggestedSettings);
|
||||
const fragment = document.createDocumentFragment();
|
||||
|
||||
let note: HTMLElement | string | undefined;
|
||||
if (profile === 'recommended') {
|
||||
note = t('recommended-settings-for-device', {device: recommendedDevice});
|
||||
} else if (profile === 'highest') {
|
||||
// Add note for "Highest quality" profile
|
||||
note = '⚠️ ' + t('highest-quality-note');
|
||||
}
|
||||
|
||||
note && fragment.appendChild(CE('div', {class: 'bx-suggest-note'}, note));
|
||||
|
||||
const settings = this.suggestedSettings[profile];
|
||||
let prefKey: PrefKey;
|
||||
for (prefKey in settings) {
|
||||
const currentValue = getPref(prefKey, false);
|
||||
const suggestedValue = settings[prefKey];
|
||||
const currentValueText = STORAGE.Global.getValueText(prefKey, currentValue);
|
||||
const isSameValue = currentValue === suggestedValue;
|
||||
|
||||
let $child: HTMLElement;
|
||||
let $value: HTMLElement | string;
|
||||
if (isSameValue) {
|
||||
// No changes
|
||||
$value = currentValueText;
|
||||
} else {
|
||||
const suggestedValueText = STORAGE.Global.getValueText(prefKey, suggestedValue);
|
||||
$value = currentValueText + ' ➔ ' + suggestedValueText;
|
||||
}
|
||||
|
||||
let $checkbox: HTMLInputElement;
|
||||
const breadcrumb = this.suggestedSettingLabels[prefKey] + ' ❯ ' + STORAGE.Global.getLabel(prefKey);
|
||||
|
||||
$child = CE('div', {
|
||||
class: `bx-suggest-row ${isSameValue ? 'bx-suggest-ok' : 'bx-suggest-change'}`,
|
||||
},
|
||||
$checkbox = CE('input', {
|
||||
type: 'checkbox',
|
||||
tabindex: 0,
|
||||
checked: true,
|
||||
id: `bx_suggest_${prefKey}`,
|
||||
}),
|
||||
CE('label', {
|
||||
for: `bx_suggest_${prefKey}`,
|
||||
},
|
||||
CE('div', {
|
||||
class: 'bx-suggest-label',
|
||||
}, breadcrumb),
|
||||
CE('div', {
|
||||
class: 'bx-suggest-value',
|
||||
}, $value),
|
||||
),
|
||||
);
|
||||
|
||||
if (isSameValue) {
|
||||
$checkbox.disabled = true;
|
||||
$checkbox.checked = true;
|
||||
}
|
||||
|
||||
fragment.appendChild($child);
|
||||
}
|
||||
|
||||
$suggestedSettings.appendChild(fragment);
|
||||
});
|
||||
|
||||
BxEvent.dispatch($select, 'input');
|
||||
|
||||
const onClickApply = () => {
|
||||
const profile = $select.value as SuggestedSettingProfile;
|
||||
const settings = this.suggestedSettings[profile];
|
||||
|
||||
let prefKey: PrefKey;
|
||||
for (prefKey in settings) {
|
||||
const suggestedValue = settings[prefKey];
|
||||
const $checkBox = $content.querySelector(`#bx_suggest_${prefKey}`) as HTMLInputElement;
|
||||
if (!$checkBox.checked || $checkBox.disabled) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const $control = this.settingElements[prefKey] as HTMLElement;
|
||||
|
||||
// Set value directly if the control element is not available
|
||||
if (!$control) {
|
||||
setPref(prefKey, suggestedValue);
|
||||
continue;
|
||||
}
|
||||
|
||||
if ('setValue' in $control) {
|
||||
($control as BxHtmlSettingElement).setValue(suggestedValue);
|
||||
} else {
|
||||
($control as HTMLInputElement).value = suggestedValue;
|
||||
}
|
||||
|
||||
BxEvent.dispatch($control, 'input', {
|
||||
manualTrigger: true,
|
||||
});
|
||||
}
|
||||
|
||||
// Refresh suggested settings
|
||||
BxEvent.dispatch($select, 'input');
|
||||
};
|
||||
|
||||
// Apply button
|
||||
const $btnApply = createButton({
|
||||
label: t('apply'),
|
||||
style: ButtonStyle.FULL_WIDTH | ButtonStyle.FOCUSABLE,
|
||||
onClick: onClickApply,
|
||||
});
|
||||
|
||||
$content = CE('div', {
|
||||
class: 'bx-suggest-box',
|
||||
_nearby: {
|
||||
orientation: 'vertical',
|
||||
}
|
||||
},
|
||||
BxSelectElement.wrap($select),
|
||||
$suggestedSettings,
|
||||
$btnApply,
|
||||
|
||||
BX_FLAGS.DeviceInfo.deviceType.includes('android') && CE('a', {
|
||||
class: 'bx-suggest-link bx-focusable',
|
||||
href: 'https://better-xcloud.github.io/guide/android-webview-tweaks/',
|
||||
target: '_blank',
|
||||
tabindex: 0,
|
||||
}, t('how-to-improve-app-performance')),
|
||||
|
||||
BX_FLAGS.DeviceInfo.deviceType.includes('android') && !hasRecommendedSettings && CE('a', {
|
||||
class: 'bx-suggest-link bx-focusable',
|
||||
href: 'https://github.com/redphx/better-xcloud-devices',
|
||||
target: '_blank',
|
||||
tabindex: 0,
|
||||
}, t('suggest-settings-link')),
|
||||
);
|
||||
|
||||
$btnSuggest?.insertAdjacentElement('afterend', $content);
|
||||
}
|
||||
|
||||
private renderTab(settingTab: SettingTab) {
|
||||
const $svg = createSvgIcon(settingTab.icon as any);
|
||||
$svg.dataset.group = settingTab.group;
|
||||
@ -771,6 +1080,8 @@ export class SettingsNavigationDialog extends NavigationDialog {
|
||||
if ($control instanceof HTMLSelectElement && getPref(PrefKey.UI_CONTROLLER_FRIENDLY)) {
|
||||
$control = BxSelectElement.wrap($control);
|
||||
}
|
||||
|
||||
pref && (this.settingElements[pref] = $control);
|
||||
}
|
||||
|
||||
let prefDefinition: SettingDefinition | null = null;
|
||||
@ -782,6 +1093,13 @@ export class SettingsNavigationDialog extends NavigationDialog {
|
||||
let note = prefDefinition?.note || setting.note;
|
||||
const experimental = prefDefinition?.experimental || setting.experimental;
|
||||
|
||||
if (settingTabContent.label && setting.pref) {
|
||||
if (prefDefinition?.suggest) {
|
||||
typeof prefDefinition.suggest.lowest !== 'undefined' && (this.suggestedSettings.lowest[setting.pref] = prefDefinition.suggest.lowest);
|
||||
typeof prefDefinition.suggest.highest !== 'undefined' && (this.suggestedSettings.highest[setting.pref] = prefDefinition.suggest.highest);
|
||||
}
|
||||
}
|
||||
|
||||
// Add Experimental text
|
||||
if (experimental) {
|
||||
label = '🧪 ' + label;
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { AppInterface } from "@utils/global";
|
||||
import { BxEvent } from "@utils/bx-event";
|
||||
import { PrefKey } from "@/enums/pref-keys";
|
||||
import { getPref } from "@/utils/settings-storages/global-settings-storage";
|
||||
import { ControllerDeviceVibration, getPref } from "@/utils/settings-storages/global-settings-storage";
|
||||
|
||||
const VIBRATION_DATA_MAP = {
|
||||
'gamepadIndex': 8,
|
||||
@ -69,9 +69,9 @@ export class VibrationManager {
|
||||
const value = getPref(PrefKey.CONTROLLER_DEVICE_VIBRATION);
|
||||
let enabled;
|
||||
|
||||
if (value === 'on') {
|
||||
if (value === ControllerDeviceVibration.ON) {
|
||||
enabled = true;
|
||||
} else if (value === 'auto') {
|
||||
} else if (value === ControllerDeviceVibration.AUTO) {
|
||||
enabled = true;
|
||||
const gamepads = window.navigator.getGamepads();
|
||||
for (const gamepad of gamepads) {
|
||||
|
2
src/types/index.d.ts
vendored
2
src/types/index.d.ts
vendored
@ -70,8 +70,6 @@ type BxStates = {
|
||||
pointerServerPort: number;
|
||||
}
|
||||
|
||||
type DualEnum = {[index: string]: number} & {[index: number]: string};
|
||||
|
||||
type XcloudTitleInfo = {
|
||||
titleId: string,
|
||||
|
||||
|
18
src/types/setting-definition.d.ts
vendored
18
src/types/setting-definition.d.ts
vendored
@ -1,3 +1,19 @@
|
||||
import type { PrefKey } from "@/enums/pref-keys";
|
||||
import type { SettingElementType } from "@/utils/setting-element";
|
||||
|
||||
export type SuggestedSettingCategory = 'recommended' | 'lowest' | 'highest' | 'default';
|
||||
export type RecommendedSettings = {
|
||||
schema_version: 1,
|
||||
device_name: string,
|
||||
device_type: 'android' | 'android-tv' | 'android-handheld' | 'webos',
|
||||
settings: {
|
||||
app: any,
|
||||
script: {
|
||||
_base?: 'lowest' | 'highest',
|
||||
} & PartialRecord<PrefKey, any>,
|
||||
},
|
||||
};
|
||||
|
||||
export type SettingDefinition = {
|
||||
default: any;
|
||||
} & Partial<{
|
||||
@ -5,7 +21,9 @@ export type SettingDefinition = {
|
||||
note: string | HTMLElement;
|
||||
experimental: boolean;
|
||||
unsupported: string | boolean;
|
||||
suggest: PartialRecord<SuggestedSettingCategory, any>,
|
||||
ready: (setting: SettingDefinition) => void;
|
||||
type: SettingElementType,
|
||||
// migrate?: (this: Preferences, savedPrefs: any, value: any) => void;
|
||||
}> & (
|
||||
{} | {
|
||||
|
@ -5,7 +5,7 @@ import { BxLogger } from "./bx-logger";
|
||||
import { BX_FLAGS } from "./bx-flags";
|
||||
import { NavigationDialogManager } from "@/modules/ui/dialog/navigation-dialog";
|
||||
import { PrefKey } from "@/enums/pref-keys";
|
||||
import { getPref } from "./settings-storages/global-settings-storage";
|
||||
import { getPref, StreamTouchController } from "./settings-storages/global-settings-storage";
|
||||
|
||||
export enum SupportedInputType {
|
||||
CONTROLLER = 'Controller',
|
||||
@ -41,7 +41,7 @@ export const BxExposed = {
|
||||
let touchControllerAvailability = getPref(PrefKey.STREAM_TOUCH_CONTROLLER);
|
||||
|
||||
// Disable touch control when gamepad found
|
||||
if (touchControllerAvailability !== 'off' && getPref(PrefKey.STREAM_TOUCH_CONTROLLER_AUTO_OFF)) {
|
||||
if (touchControllerAvailability !== StreamTouchController.OFF && getPref(PrefKey.STREAM_TOUCH_CONTROLLER_AUTO_OFF)) {
|
||||
const gamepads = window.navigator.getGamepads();
|
||||
let gamepadFound = false;
|
||||
|
||||
@ -52,10 +52,10 @@ export const BxExposed = {
|
||||
}
|
||||
}
|
||||
|
||||
gamepadFound && (touchControllerAvailability = 'off');
|
||||
gamepadFound && (touchControllerAvailability = StreamTouchController.OFF);
|
||||
}
|
||||
|
||||
if (touchControllerAvailability === 'off') {
|
||||
if (touchControllerAvailability === StreamTouchController.OFF) {
|
||||
// Disable touch on all games (not native touch)
|
||||
supportedInputTypes = supportedInputTypes.filter(i => i !== SupportedInputType.CUSTOM_TOUCH_OVERLAY && i !== SupportedInputType.GENERIC_TOUCH);
|
||||
// Empty TABs
|
||||
@ -68,7 +68,7 @@ export const BxExposed = {
|
||||
supportedInputTypes.includes(SupportedInputType.CUSTOM_TOUCH_OVERLAY) ||
|
||||
supportedInputTypes.includes(SupportedInputType.GENERIC_TOUCH);
|
||||
|
||||
if (!titleInfo.details.hasTouchSupport && touchControllerAvailability === 'all') {
|
||||
if (!titleInfo.details.hasTouchSupport && touchControllerAvailability === StreamTouchController.ALL) {
|
||||
// Add generic touch support for non touch-supported games
|
||||
titleInfo.details.hasFakeTouchSupport = true;
|
||||
supportedInputTypes.push(SupportedInputType.GENERIC_TOUCH);
|
||||
|
@ -1,3 +1,5 @@
|
||||
import { BxLogger } from "./bx-logger";
|
||||
|
||||
type BxFlags = {
|
||||
CheckForUpdate: boolean;
|
||||
EnableXcloudLogging: boolean;
|
||||
@ -7,8 +9,12 @@ type BxFlags = {
|
||||
FeatureGates: {[key: string]: boolean} | null,
|
||||
|
||||
DeviceInfo: {
|
||||
deviceType: 'android' | 'android-tv' | 'webos' | 'unknown',
|
||||
deviceType: 'android' | 'android-tv' | 'android-handheld' | 'webos' | 'unknown',
|
||||
userAgent?: string,
|
||||
|
||||
androidInfo?: {
|
||||
board: string,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@ -35,4 +41,6 @@ if (!BX_FLAGS.DeviceInfo.userAgent) {
|
||||
BX_FLAGS.DeviceInfo.userAgent = window.navigator.userAgent;
|
||||
}
|
||||
|
||||
BxLogger.info('BxFlags', BX_FLAGS);
|
||||
|
||||
export const NATIVE_FETCH = window.fetch;
|
||||
|
@ -2,8 +2,36 @@ import type { BxIcon } from "@utils/bx-icon";
|
||||
import { setNearby } from "./navigation-utils";
|
||||
import type { NavigationNearbyElements } from "@/modules/ui/dialog/navigation-dialog";
|
||||
|
||||
export enum ButtonStyle {
|
||||
PRIMARY = 1,
|
||||
DANGER = 2,
|
||||
GHOST = 4,
|
||||
FROSTED = 8,
|
||||
DROP_SHADOW = 16,
|
||||
FOCUSABLE = 32,
|
||||
FULL_WIDTH = 64,
|
||||
FULL_HEIGHT = 128,
|
||||
TALL = 256,
|
||||
CIRCULAR = 512,
|
||||
NORMAL_CASE = 1024,
|
||||
}
|
||||
|
||||
const ButtonStyleClass = {
|
||||
[ButtonStyle.PRIMARY]: 'bx-primary',
|
||||
[ButtonStyle.DANGER]: 'bx-danger',
|
||||
[ButtonStyle.GHOST]: 'bx-ghost',
|
||||
[ButtonStyle.FROSTED]: 'bx-frosted',
|
||||
[ButtonStyle.DROP_SHADOW]: 'bx-drop-shadow',
|
||||
[ButtonStyle.FOCUSABLE]: 'bx-focusable',
|
||||
[ButtonStyle.FULL_WIDTH]: 'bx-full-width',
|
||||
[ButtonStyle.FULL_HEIGHT]: 'bx-full-height',
|
||||
[ButtonStyle.TALL]: 'bx-tall',
|
||||
[ButtonStyle.CIRCULAR]: 'bx-circular',
|
||||
[ButtonStyle.NORMAL_CASE]: 'bx-normal-case',
|
||||
}
|
||||
|
||||
type BxButton = {
|
||||
style?: number | string | ButtonStyle;
|
||||
style?: ButtonStyle;
|
||||
url?: string;
|
||||
classes?: string[];
|
||||
icon?: typeof BxIcon;
|
||||
@ -15,8 +43,6 @@ type BxButton = {
|
||||
attributes?: {[key: string]: any},
|
||||
}
|
||||
|
||||
type ButtonStyle = {[index: string]: number} & {[index: number]: string};
|
||||
|
||||
// Quickly create a tree of elements without having to use innerHTML
|
||||
type CreateElementOptions = {
|
||||
[index: string]: any;
|
||||
@ -80,20 +106,7 @@ export const createSvgIcon = (icon: typeof BxIcon) => {
|
||||
return svgParser(icon.toString());
|
||||
}
|
||||
|
||||
export const ButtonStyle: DualEnum = {};
|
||||
ButtonStyle[ButtonStyle.PRIMARY = 1] = 'bx-primary';
|
||||
ButtonStyle[ButtonStyle.DANGER = 2] = 'bx-danger';
|
||||
ButtonStyle[ButtonStyle.GHOST = 4] = 'bx-ghost';
|
||||
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));
|
||||
const ButtonStyleIndices = Object.keys(ButtonStyleClass).map(i => parseInt(i));
|
||||
|
||||
export const createButton = <T=HTMLButtonElement>(options: BxButton): T => {
|
||||
let $btn;
|
||||
@ -106,8 +119,8 @@ export const createButton = <T=HTMLButtonElement>(options: BxButton): T => {
|
||||
}
|
||||
|
||||
const style = (options.style || 0) as number;
|
||||
style && ButtonStyleIndices.forEach(index => {
|
||||
(style & index) && $btn.classList.add(ButtonStyle[index] as string);
|
||||
style && ButtonStyleIndices.forEach((index: keyof typeof ButtonStyleClass) => {
|
||||
(style & index) && $btn.classList.add(ButtonStyleClass[index] as string);
|
||||
});
|
||||
|
||||
options.classes && $btn.classList.add(...options.classes);
|
||||
@ -153,3 +166,9 @@ export function isElementVisible($elm: HTMLElement): boolean {
|
||||
|
||||
export const CTN = document.createTextNode.bind(document);
|
||||
window.BX_CE = createElement;
|
||||
|
||||
export function removeChildElements($parent: HTMLElement) {
|
||||
while ($parent.firstElementChild) {
|
||||
$parent.firstElementChild.remove();
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ import { setNearby } from "./navigation-utils";
|
||||
import type { PrefKey } from "@/enums/pref-keys";
|
||||
import type { BaseSettingsStore } from "./settings-storages/base-settings-storage";
|
||||
import { type MultipleOptionsParams, type NumberStepperParams } from "@/types/setting-definition";
|
||||
import { BxEvent } from "./bx-event";
|
||||
|
||||
export enum SettingElementType {
|
||||
OPTIONS = 'options',
|
||||
@ -13,16 +14,26 @@ export enum SettingElementType {
|
||||
CHECKBOX = 'checkbox',
|
||||
}
|
||||
|
||||
interface BxBaseSettingElement {
|
||||
setValue: (value: any) => void,
|
||||
}
|
||||
|
||||
export interface BxHtmlSettingElement extends HTMLElement, BxBaseSettingElement {};
|
||||
|
||||
export interface BxSelectSettingElement extends HTMLSelectElement, BxBaseSettingElement {}
|
||||
|
||||
export class SettingElement {
|
||||
static #renderOptions(key: string, setting: PreferenceSetting, currentValue: any, onChange: any) {
|
||||
const $control = CE<HTMLSelectElement>('select', {
|
||||
static #renderOptions(key: string, setting: PreferenceSetting, currentValue: any, onChange: any): BxSelectSettingElement {
|
||||
const $control = CE<BxSelectSettingElement>('select', {
|
||||
// title: setting.label,
|
||||
tabindex: 0,
|
||||
}) as HTMLSelectElement;
|
||||
});
|
||||
|
||||
let $parent: HTMLElement;
|
||||
if (setting.optionsGroup) {
|
||||
$parent = CE('optgroup', {'label': setting.optionsGroup});
|
||||
$parent = CE('optgroup', {
|
||||
label: setting.optionsGroup,
|
||||
});
|
||||
$control.appendChild($parent);
|
||||
} else {
|
||||
$parent = $control;
|
||||
@ -44,19 +55,20 @@ export class SettingElement {
|
||||
});
|
||||
|
||||
// Custom method
|
||||
($control as any).setValue = (value: any) => {
|
||||
$control.setValue = (value: any) => {
|
||||
$control.value = value;
|
||||
};
|
||||
|
||||
return $control;
|
||||
}
|
||||
|
||||
static #renderMultipleOptions(key: string, setting: PreferenceSetting, currentValue: any, onChange: any, params: MultipleOptionsParams={}) {
|
||||
const $control = CE<HTMLSelectElement>('select', {
|
||||
static #renderMultipleOptions(key: string, setting: PreferenceSetting, currentValue: any, onChange: any, params: MultipleOptionsParams={}): BxSelectSettingElement {
|
||||
const $control = CE<BxSelectSettingElement>('select', {
|
||||
// title: setting.label,
|
||||
multiple: true,
|
||||
tabindex: 0,
|
||||
});
|
||||
|
||||
if (params && params.size) {
|
||||
$control.setAttribute('size', params.size.toString());
|
||||
}
|
||||
@ -75,7 +87,7 @@ export class SettingElement {
|
||||
|
||||
const $parent = target.parentElement!;
|
||||
$parent.focus();
|
||||
$parent.dispatchEvent(new Event('input'));
|
||||
BxEvent.dispatch($parent, 'input');
|
||||
});
|
||||
|
||||
$control.appendChild($option);
|
||||
@ -100,9 +112,15 @@ export class SettingElement {
|
||||
}
|
||||
|
||||
static #renderNumber(key: string, setting: PreferenceSetting, currentValue: any, onChange: any) {
|
||||
const $control = CE('input', {'tabindex': 0, 'type': 'number', 'min': setting.min, 'max': setting.max}) as HTMLInputElement;
|
||||
const $control = CE<HTMLInputElement>('input', {
|
||||
tabindex: 0,
|
||||
type: 'number',
|
||||
min: setting.min,
|
||||
max: setting.max,
|
||||
});
|
||||
|
||||
$control.value = currentValue;
|
||||
onChange && $control.addEventListener('change', (e: Event) => {
|
||||
onChange && $control.addEventListener('input', (e: Event) => {
|
||||
const target = e.target as HTMLInputElement;
|
||||
|
||||
const value = Math.max(setting.min!, Math.min(setting.max!, parseInt(target.value)));
|
||||
@ -118,7 +136,7 @@ export class SettingElement {
|
||||
const $control = CE('input', {'type': 'checkbox', 'tabindex': 0}) as HTMLInputElement;
|
||||
$control.checked = currentValue;
|
||||
|
||||
onChange && $control.addEventListener('change', e => {
|
||||
onChange && $control.addEventListener('input', e => {
|
||||
!(e as any).ignoreOnChange && onChange(e, (e.target as HTMLInputElement).checked);
|
||||
});
|
||||
|
||||
@ -162,77 +180,21 @@ export class SettingElement {
|
||||
$btnInc.classList.toggle('bx-inactive', controlValue === MAX);
|
||||
}
|
||||
|
||||
const $wrapper = CE('div', {'class': 'bx-number-stepper', id: `bx_setting_${key}`},
|
||||
$btnDec = CE('button', {
|
||||
'data-type': 'dec',
|
||||
type: 'button',
|
||||
class: options.hideSlider ? 'bx-focusable' : '',
|
||||
tabindex: options.hideSlider ? 0 : -1,
|
||||
}, '-') as HTMLButtonElement,
|
||||
$text = CE('span', {}, renderTextValue(value)) as HTMLSpanElement,
|
||||
$btnInc = CE('button', {
|
||||
'data-type': 'inc',
|
||||
type: 'button',
|
||||
class: options.hideSlider ? 'bx-focusable' : '',
|
||||
tabindex: options.hideSlider ? 0 : -1,
|
||||
}, '+') as HTMLButtonElement,
|
||||
);
|
||||
|
||||
if (options.disabled) {
|
||||
($wrapper as any).disabled = true;
|
||||
}
|
||||
|
||||
if (!options.disabled && !options.hideSlider) {
|
||||
$range = CE('input', {
|
||||
id: `bx_setting_${key}`,
|
||||
type: 'range',
|
||||
min: MIN,
|
||||
max: MAX,
|
||||
value: value,
|
||||
step: STEPS,
|
||||
tabindex: 0,
|
||||
}) as HTMLInputElement;
|
||||
|
||||
$range.addEventListener('input', e => {
|
||||
value = parseInt((e.target as HTMLInputElement).value);
|
||||
const valueChanged = controlValue !== value;
|
||||
|
||||
if (!valueChanged) {
|
||||
return;
|
||||
}
|
||||
|
||||
controlValue = value;
|
||||
updateButtonsVisibility();
|
||||
$text.textContent = renderTextValue(value);
|
||||
|
||||
!(e as any).ignoreOnChange && onChange && onChange(e, value);
|
||||
});
|
||||
|
||||
$wrapper.appendChild($range);
|
||||
|
||||
if (options.ticks || options.exactTicks) {
|
||||
const markersId = `markers-${key}`;
|
||||
const $markers = CE('datalist', {'id': markersId});
|
||||
$range.setAttribute('list', markersId);
|
||||
|
||||
if (options.exactTicks) {
|
||||
let start = Math.max(Math.floor(MIN / options.exactTicks), 1) * options.exactTicks;
|
||||
|
||||
if (start === MIN) {
|
||||
start += options.exactTicks;
|
||||
}
|
||||
|
||||
for (let i = start; i < MAX; i += options.exactTicks) {
|
||||
$markers.appendChild(CE<HTMLOptionElement>('option', {'value': i}));
|
||||
}
|
||||
} else {
|
||||
for (let i = MIN + options.ticks!; i < MAX; i += options.ticks!) {
|
||||
$markers.appendChild(CE<HTMLOptionElement>('option', {'value': i}));
|
||||
}
|
||||
}
|
||||
$wrapper.appendChild($markers);
|
||||
}
|
||||
}
|
||||
const $wrapper = CE<BxHtmlSettingElement>('div', {'class': 'bx-number-stepper', id: `bx_setting_${key}`},
|
||||
$btnDec = CE('button', {
|
||||
'data-type': 'dec',
|
||||
type: 'button',
|
||||
class: options.hideSlider ? 'bx-focusable' : '',
|
||||
tabindex: options.hideSlider ? 0 : -1,
|
||||
}, '-') as HTMLButtonElement,
|
||||
$text = CE('span', {}, renderTextValue(value)) as HTMLSpanElement,
|
||||
$btnInc = CE('button', {
|
||||
'data-type': 'inc',
|
||||
type: 'button',
|
||||
class: options.hideSlider ? 'bx-focusable' : '',
|
||||
tabindex: options.hideSlider ? 0 : -1,
|
||||
}, '+') as HTMLButtonElement,
|
||||
);
|
||||
|
||||
if (options.disabled) {
|
||||
$btnInc.disabled = true;
|
||||
@ -240,9 +202,66 @@ export class SettingElement {
|
||||
|
||||
$btnDec.disabled = true;
|
||||
$btnDec.classList.add('bx-inactive');
|
||||
|
||||
($wrapper as any).disabled = true;
|
||||
return $wrapper;
|
||||
}
|
||||
|
||||
$range = CE<HTMLInputElement>('input', {
|
||||
id: `bx_setting_${key}`,
|
||||
type: 'range',
|
||||
min: MIN,
|
||||
max: MAX,
|
||||
value: value,
|
||||
step: STEPS,
|
||||
tabindex: 0,
|
||||
});
|
||||
|
||||
options.hideSlider && $range.classList.add('bx-gone');
|
||||
|
||||
$range.addEventListener('input', e => {
|
||||
value = parseInt((e.target as HTMLInputElement).value);
|
||||
const valueChanged = controlValue !== value;
|
||||
|
||||
if (!valueChanged) {
|
||||
return;
|
||||
}
|
||||
|
||||
controlValue = value;
|
||||
updateButtonsVisibility();
|
||||
$text.textContent = renderTextValue(value);
|
||||
|
||||
!(e as any).ignoreOnChange && onChange && onChange(e, value);
|
||||
});
|
||||
|
||||
$wrapper.addEventListener('input', e => {
|
||||
BxEvent.dispatch($range, 'input');
|
||||
});
|
||||
$wrapper.appendChild($range);
|
||||
|
||||
if (options.ticks || options.exactTicks) {
|
||||
const markersId = `markers-${key}`;
|
||||
const $markers = CE('datalist', {'id': markersId});
|
||||
$range.setAttribute('list', markersId);
|
||||
|
||||
if (options.exactTicks) {
|
||||
let start = Math.max(Math.floor(MIN / options.exactTicks), 1) * options.exactTicks;
|
||||
|
||||
if (start === MIN) {
|
||||
start += options.exactTicks;
|
||||
}
|
||||
|
||||
for (let i = start; i < MAX; i += options.exactTicks) {
|
||||
$markers.appendChild(CE<HTMLOptionElement>('option', {'value': i}));
|
||||
}
|
||||
} else {
|
||||
for (let i = MIN + options.ticks!; i < MAX; i += options.ticks!) {
|
||||
$markers.appendChild(CE<HTMLOptionElement>('option', {'value': i}));
|
||||
}
|
||||
}
|
||||
$wrapper.appendChild($markers);
|
||||
}
|
||||
|
||||
updateButtonsVisibility();
|
||||
|
||||
let interval: number;
|
||||
@ -278,16 +297,14 @@ export class SettingElement {
|
||||
|
||||
const onMouseDown = (e: PointerEvent) => {
|
||||
e.preventDefault();
|
||||
|
||||
isHolding = true;
|
||||
|
||||
const args = arguments;
|
||||
interval && clearInterval(interval);
|
||||
interval = window.setInterval(() => {
|
||||
const event = new Event('click');
|
||||
(event as any).arguments = args;
|
||||
|
||||
e.target?.dispatchEvent(event);
|
||||
e.target && BxEvent.dispatch(e.target as HTMLElement, 'click', {
|
||||
arguments: args,
|
||||
});
|
||||
}, 200);
|
||||
};
|
||||
|
||||
@ -301,11 +318,9 @@ export class SettingElement {
|
||||
const onContextMenu = (e: Event) => e.preventDefault();
|
||||
|
||||
// Custom method
|
||||
($wrapper as any).setValue = (value: any) => {
|
||||
controlValue = parseInt(value);
|
||||
|
||||
$wrapper.setValue = (value: any) => {
|
||||
$text.textContent = renderTextValue(value);
|
||||
$range && ($range.value = value);
|
||||
$range.value = value;
|
||||
};
|
||||
|
||||
$btnDec.addEventListener('click', onClick);
|
||||
|
@ -1,6 +1,7 @@
|
||||
import type { PrefKey } from "@/enums/pref-keys";
|
||||
import type { SettingDefinitions } from "@/types/setting-definition";
|
||||
import type { NumberStepperParams, SettingDefinitions } from "@/types/setting-definition";
|
||||
import { BxEvent } from "../bx-event";
|
||||
import { SettingElementType } from "../setting-element";
|
||||
|
||||
export class BaseSettingsStore {
|
||||
private storage: Storage;
|
||||
@ -12,7 +13,8 @@ export class BaseSettingsStore {
|
||||
this.storage = window.localStorage;
|
||||
this.storageKey = storageKey;
|
||||
|
||||
for (const settingId in definitions) {
|
||||
let settingId: keyof typeof definitions
|
||||
for (settingId in definitions) {
|
||||
const setting = definitions[settingId];
|
||||
|
||||
/*
|
||||
@ -49,14 +51,14 @@ export class BaseSettingsStore {
|
||||
return this.definitions[key];
|
||||
}
|
||||
|
||||
getSetting(key: PrefKey) {
|
||||
getSetting(key: PrefKey, checkUnsupported = true) {
|
||||
if (typeof key === 'undefined') {
|
||||
debugger;
|
||||
return;
|
||||
}
|
||||
|
||||
// Return default value if the feature is not supported
|
||||
if (this.definitions[key].unsupported) {
|
||||
if (checkUnsupported && this.definitions[key].unsupported) {
|
||||
return this.definitions[key].default;
|
||||
}
|
||||
|
||||
@ -121,4 +123,30 @@ export class BaseSettingsStore {
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
getLabel(key: PrefKey): string {
|
||||
return this.definitions[key].label || key;
|
||||
}
|
||||
|
||||
getValueText(key: PrefKey, value: any): string {
|
||||
const definition = this.definitions[key];
|
||||
if (definition.type === SettingElementType.NUMBER_STEPPER) {
|
||||
const params = (definition as any).params as NumberStepperParams;
|
||||
if (params.customTextValue) {
|
||||
const text = params.customTextValue(value);
|
||||
if (text) {
|
||||
return text;
|
||||
}
|
||||
}
|
||||
|
||||
return value.toString();
|
||||
} else if ('options' in definition) {
|
||||
const options = (definition as any).options;
|
||||
if (value in options) {
|
||||
return options[value];
|
||||
}
|
||||
}
|
||||
|
||||
return value.toString();
|
||||
}
|
||||
}
|
||||
|
@ -4,8 +4,7 @@ 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 { type SettingDefinition, type SettingDefinitions } from "@/types/setting-definition";
|
||||
import { BX_FLAGS } from "../bx-flags";
|
||||
import { STATES, AppInterface, STORAGE } from "../global";
|
||||
import { CE } from "../html";
|
||||
@ -15,8 +14,33 @@ import { BaseSettingsStore as BaseSettingsStorage } from "./base-settings-storag
|
||||
import { SettingElementType } from "../setting-element";
|
||||
|
||||
|
||||
export const enum StreamResolution {
|
||||
DIM_720P = '720p',
|
||||
DIM_1080P = '1080p',
|
||||
}
|
||||
|
||||
export const enum CodecProfile {
|
||||
DEFAULT = 'default',
|
||||
LOW = 'low',
|
||||
NORMAL = 'normal',
|
||||
HIGH = 'high',
|
||||
};
|
||||
|
||||
export const enum StreamTouchController {
|
||||
DEFAULT = 'default',
|
||||
ALL = 'all',
|
||||
OFF = 'off',
|
||||
}
|
||||
|
||||
export const enum ControllerDeviceVibration {
|
||||
ON = 'on',
|
||||
AUTO = 'auto',
|
||||
OFF = 'off',
|
||||
}
|
||||
|
||||
|
||||
function getSupportedCodecProfiles() {
|
||||
const options: {[index: string]: string} = {
|
||||
const options: PartialRecord<CodecProfile, string> = {
|
||||
default: t('default'),
|
||||
};
|
||||
|
||||
@ -46,25 +70,25 @@ function getSupportedCodecProfiles() {
|
||||
|
||||
if (hasLowCodec) {
|
||||
if (!hasNormalCodec && !hasHighCodec) {
|
||||
options.default = `${t('visual-quality-low')} (${t('default')})`;
|
||||
options[CodecProfile.DEFAULT] = `${t('visual-quality-low')} (${t('default')})`;
|
||||
} else {
|
||||
options.low = t('visual-quality-low');
|
||||
options[CodecProfile.LOW] = t('visual-quality-low');
|
||||
}
|
||||
}
|
||||
|
||||
if (hasNormalCodec) {
|
||||
if (!hasLowCodec && !hasHighCodec) {
|
||||
options.default = `${t('visual-quality-normal')} (${t('default')})`;
|
||||
options[CodecProfile.DEFAULT] = `${t('visual-quality-normal')} (${t('default')})`;
|
||||
} else {
|
||||
options.normal = t('visual-quality-normal');
|
||||
options[CodecProfile.NORMAL] = t('visual-quality-normal');
|
||||
}
|
||||
}
|
||||
|
||||
if (hasHighCodec) {
|
||||
if (!hasLowCodec && !hasNormalCodec) {
|
||||
options.default = `${t('visual-quality-high')} (${t('default')})`;
|
||||
options[CodecProfile.DEFAULT] = `${t('visual-quality-high')} (${t('default')})`;
|
||||
} else {
|
||||
options.high = t('visual-quality-high');
|
||||
options[CodecProfile.HIGH] = t('visual-quality-high');
|
||||
}
|
||||
}
|
||||
|
||||
@ -140,25 +164,31 @@ export class GlobalSettingsStorage extends BaseSettingsStorage {
|
||||
default: 'auto',
|
||||
options: {
|
||||
auto: t('default'),
|
||||
'720p': '720p',
|
||||
'1080p': '1080p',
|
||||
[StreamResolution.DIM_720P]: '720p',
|
||||
[StreamResolution.DIM_1080P]: '1080p',
|
||||
},
|
||||
suggest: {
|
||||
lowest: StreamResolution.DIM_720P,
|
||||
highest: StreamResolution.DIM_1080P,
|
||||
},
|
||||
},
|
||||
[PrefKey.STREAM_CODEC_PROFILE]: {
|
||||
label: t('visual-quality'),
|
||||
default: 'default',
|
||||
options: getSupportedCodecProfiles(),
|
||||
ready: (setting: PreferenceSetting) => {
|
||||
const options: any = setting.options;
|
||||
ready: (setting: SettingDefinition) => {
|
||||
const options = (setting as any).options;
|
||||
const keys = Object.keys(options);
|
||||
|
||||
if (keys.length <= 1) { // Unsupported
|
||||
setting.unsupported = true;
|
||||
setting.note = '⚠️ ' + t('browser-unsupported-feature');
|
||||
} else {
|
||||
// Set default value to the best codec profile
|
||||
// setting.default = keys[keys.length - 1];
|
||||
}
|
||||
|
||||
setting.suggest = {
|
||||
lowest: keys.length === 1 ? keys[0] : keys[1],
|
||||
highest: keys[keys.length - 1],
|
||||
};
|
||||
},
|
||||
},
|
||||
[PrefKey.PREFER_IPV6_SERVER]: {
|
||||
@ -189,16 +219,16 @@ export class GlobalSettingsStorage extends BaseSettingsStorage {
|
||||
|
||||
[PrefKey.STREAM_TOUCH_CONTROLLER]: {
|
||||
label: t('tc-availability'),
|
||||
default: 'all',
|
||||
default: StreamTouchController.ALL,
|
||||
options: {
|
||||
default: t('default'),
|
||||
all: t('tc-all-games'),
|
||||
off: t('off'),
|
||||
[StreamTouchController.DEFAULT]: t('default'),
|
||||
[StreamTouchController.ALL]: t('tc-all-games'),
|
||||
[StreamTouchController.OFF]: t('off'),
|
||||
},
|
||||
unsupported: !STATES.userAgent.capabilities.touch,
|
||||
ready: (setting: PreferenceSetting) => {
|
||||
ready: (setting: SettingDefinition) => {
|
||||
if (setting.unsupported) {
|
||||
setting.default = 'default';
|
||||
setting.default = StreamTouchController.DEFAULT;
|
||||
}
|
||||
},
|
||||
},
|
||||
@ -274,6 +304,9 @@ export class GlobalSettingsStorage extends BaseSettingsStorage {
|
||||
}
|
||||
},
|
||||
},
|
||||
suggest: {
|
||||
highest: 0,
|
||||
}
|
||||
},
|
||||
|
||||
[PrefKey.GAME_BAR_POSITION]: {
|
||||
@ -318,11 +351,11 @@ export class GlobalSettingsStorage extends BaseSettingsStorage {
|
||||
|
||||
[PrefKey.CONTROLLER_DEVICE_VIBRATION]: {
|
||||
label: t('device-vibration'),
|
||||
default: 'off',
|
||||
default: ControllerDeviceVibration.OFF,
|
||||
options: {
|
||||
on: t('on'),
|
||||
auto: t('device-vibration-not-using-gamepad'),
|
||||
off: t('off'),
|
||||
[ControllerDeviceVibration.ON]: t('on'),
|
||||
[ControllerDeviceVibration.AUTO]: t('device-vibration-not-using-gamepad'),
|
||||
[ControllerDeviceVibration.OFF]: t('off'),
|
||||
},
|
||||
},
|
||||
|
||||
@ -346,7 +379,7 @@ export class GlobalSettingsStorage extends BaseSettingsStorage {
|
||||
const userAgent = ((window.navigator as any).orgUserAgent || window.navigator.userAgent || '').toLowerCase();
|
||||
return !AppInterface && userAgent.match(/(android|iphone|ipad)/) ? t('browser-unsupported-feature') : false;
|
||||
})(),
|
||||
ready: (setting: PreferenceSetting) => {
|
||||
ready: (setting: SettingDefinition) => {
|
||||
let note;
|
||||
let url;
|
||||
if (setting.unsupported) {
|
||||
@ -372,16 +405,15 @@ export class GlobalSettingsStorage extends BaseSettingsStorage {
|
||||
on: t('on'),
|
||||
off: t('off'),
|
||||
},
|
||||
ready: (setting: PreferenceSetting) => {
|
||||
ready: (setting: SettingDefinition) => {
|
||||
if (AppInterface) {
|
||||
|
||||
} else if (UserAgent.isMobile()) {
|
||||
setting.unsupported = true;
|
||||
setting.default = 'off';
|
||||
delete setting.options!['default'];
|
||||
delete setting.options!['on'];
|
||||
delete (setting as any).options['default'];
|
||||
delete (setting as any).options['on'];
|
||||
} else {
|
||||
delete setting.options!['on'];
|
||||
delete (setting as any).options['on'];
|
||||
}
|
||||
},
|
||||
},
|
||||
@ -530,6 +562,10 @@ export class GlobalSettingsStorage extends BaseSettingsStorage {
|
||||
[StreamPlayerType.VIDEO]: t('default'),
|
||||
[StreamPlayerType.WEBGL2]: t('webgl2'),
|
||||
},
|
||||
suggest: {
|
||||
lowest: StreamPlayerType.VIDEO,
|
||||
highest: StreamPlayerType.WEBGL2,
|
||||
},
|
||||
},
|
||||
[PrefKey.VIDEO_PROCESSING]: {
|
||||
label: t('clarity-boost'),
|
||||
@ -538,6 +574,10 @@ export class GlobalSettingsStorage extends BaseSettingsStorage {
|
||||
[StreamVideoProcessing.USM]: t('unsharp-masking'),
|
||||
[StreamVideoProcessing.CAS]: t('amd-fidelity-cas'),
|
||||
},
|
||||
suggest: {
|
||||
lowest: StreamVideoProcessing.USM,
|
||||
highest: StreamVideoProcessing.CAS,
|
||||
},
|
||||
},
|
||||
[PrefKey.VIDEO_POWER_PREFERENCE]: {
|
||||
label: t('renderer-configuration'),
|
||||
@ -547,6 +587,9 @@ export class GlobalSettingsStorage extends BaseSettingsStorage {
|
||||
'low-power': t('low-power'),
|
||||
'high-performance': t('high-performance'),
|
||||
},
|
||||
suggest: {
|
||||
highest: 'low-power',
|
||||
},
|
||||
},
|
||||
[PrefKey.VIDEO_SHARPNESS]: {
|
||||
label: t('sharpness'),
|
||||
@ -561,6 +604,10 @@ export class GlobalSettingsStorage extends BaseSettingsStorage {
|
||||
return value === 0 ? t('off') : value.toString();
|
||||
},
|
||||
},
|
||||
suggest: {
|
||||
lowest: 0,
|
||||
highest: 4,
|
||||
},
|
||||
},
|
||||
[PrefKey.VIDEO_RATIO]: {
|
||||
label: t('aspect-ratio'),
|
||||
@ -701,10 +748,10 @@ export class GlobalSettingsStorage extends BaseSettingsStorage {
|
||||
},
|
||||
|
||||
[PrefKey.REMOTE_PLAY_RESOLUTION]: {
|
||||
default: '1080p',
|
||||
default: StreamResolution.DIM_1080P,
|
||||
options: {
|
||||
'1080p': '1080p',
|
||||
'720p': '720p',
|
||||
[StreamResolution.DIM_1080P]: '1080p',
|
||||
[StreamResolution.DIM_720P]: '720p',
|
||||
},
|
||||
},
|
||||
|
||||
|
@ -121,8 +121,11 @@ const Texts = {
|
||||
"hide-system-menu-icon": "Hide System menu's icon",
|
||||
"hide-touch-controller": "Hide touch controller",
|
||||
"high-performance": "High performance",
|
||||
"highest-quality": "Highest quality",
|
||||
"highest-quality-note": "Your device may not be powerful enough to use these settings",
|
||||
"horizontal-scroll-sensitivity": "Horizontal scroll sensitivity",
|
||||
"horizontal-sensitivity": "Horizontal sensitivity",
|
||||
"how-to-improve-app-performance": "How to improve app's performance",
|
||||
"ignore": "Ignore",
|
||||
"import": "Import",
|
||||
"increase": "Increase",
|
||||
@ -137,6 +140,7 @@ const Texts = {
|
||||
"loading-screen": "Loading screen",
|
||||
"local-co-op": "Local co-op",
|
||||
"low-power": "Low power",
|
||||
"lowest-quality": "Lowest quality",
|
||||
"map-mouse-to": "Map mouse to",
|
||||
"may-not-work-properly": "May not work properly!",
|
||||
"menu": "Menu",
|
||||
@ -189,6 +193,28 @@ const Texts = {
|
||||
],
|
||||
"press-to-bind": "Press a key or do a mouse click to bind...",
|
||||
"prompt-preset-name": "Preset's name:",
|
||||
"recommended": "Recommended",
|
||||
"recommended-settings-for-device": [
|
||||
(e: any) => `Recommended settings for ${e.device}`,
|
||||
,
|
||||
,
|
||||
,
|
||||
,
|
||||
(e: any) => `Ajustes recomendados para ${e.device}`,
|
||||
,
|
||||
(e: any) => `Configurazioni consigliate per ${e.device}`,
|
||||
,
|
||||
(e: any) => `다음 기기에서 권장되는 설정: ${e.device}`,
|
||||
,
|
||||
,
|
||||
,
|
||||
,
|
||||
,
|
||||
(e: any) => `Рекомендовані налаштування для ${e.device}`,
|
||||
(e: any) => `Cấu hình được đề xuất cho ${e.device}`,
|
||||
,
|
||||
,
|
||||
],
|
||||
"reduce-animations": "Reduce UI animations",
|
||||
"region": "Region",
|
||||
"reload-page": "Reload page",
|
||||
@ -250,6 +276,8 @@ const Texts = {
|
||||
"stream-settings": "Stream settings",
|
||||
"stream-stats": "Stream stats",
|
||||
"stretch": "Stretch",
|
||||
"suggest-settings": "Suggest settings",
|
||||
"suggest-settings-link": "Suggest recommended settings for this device",
|
||||
"support-better-xcloud": "Support Better xCloud",
|
||||
"swap-buttons": "Swap buttons",
|
||||
"take-screenshot": "Take screenshot",
|
||||
@ -314,6 +342,7 @@ const Texts = {
|
||||
"volume": "Volume",
|
||||
"wait-time-countdown": "Countdown",
|
||||
"wait-time-estimated": "Estimated finish time",
|
||||
"wallpaper": "Wallpaper",
|
||||
"webgl2": "WebGL2",
|
||||
};
|
||||
|
||||
|
@ -9,7 +9,7 @@ import { patchIceCandidates } from "./network";
|
||||
import { getPreferredServerRegion } from "./region";
|
||||
import { BypassServerIps } from "@/enums/bypass-servers";
|
||||
import { PrefKey } from "@/enums/pref-keys";
|
||||
import { getPref } from "./settings-storages/global-settings-storage";
|
||||
import { getPref, StreamResolution, StreamTouchController } from "./settings-storages/global-settings-storage";
|
||||
|
||||
export
|
||||
class XcloudInterceptor {
|
||||
@ -111,7 +111,7 @@ class XcloudInterceptor {
|
||||
|
||||
// Force stream's resolution
|
||||
if (PREF_STREAM_TARGET_RESOLUTION !== 'auto') {
|
||||
const osName = (PREF_STREAM_TARGET_RESOLUTION === '720p') ? 'android' : 'windows';
|
||||
const osName = (PREF_STREAM_TARGET_RESOLUTION === StreamResolution.DIM_720P) ? 'android' : 'windows';
|
||||
body.settings.osName = osName;
|
||||
}
|
||||
|
||||
@ -147,7 +147,7 @@ class XcloudInterceptor {
|
||||
}
|
||||
|
||||
// Touch controller for all games
|
||||
if (getPref(PrefKey.STREAM_TOUCH_CONTROLLER) === 'all') {
|
||||
if (getPref(PrefKey.STREAM_TOUCH_CONTROLLER) === StreamTouchController.ALL) {
|
||||
const titleInfo = STATES.currentStream.titleInfo;
|
||||
if (titleInfo?.details.hasTouchSupport) {
|
||||
TouchController.disable();
|
||||
|
@ -6,7 +6,7 @@ import { NATIVE_FETCH } from "./bx-flags";
|
||||
import { STATES } from "./global";
|
||||
import { patchIceCandidates } from "./network";
|
||||
import { PrefKey } from "@/enums/pref-keys";
|
||||
import { getPref } from "./settings-storages/global-settings-storage";
|
||||
import { getPref, StreamResolution, StreamTouchController } from "./settings-storages/global-settings-storage";
|
||||
import type { RemotePlayConsoleAddresses } from "@/types/network";
|
||||
|
||||
export class XhomeInterceptor {
|
||||
@ -70,7 +70,7 @@ export class XhomeInterceptor {
|
||||
static async #handleInputConfigs(request: Request | URL, opts: {[index: string]: any}) {
|
||||
const response = await NATIVE_FETCH(request);
|
||||
|
||||
if (getPref(PrefKey.STREAM_TOUCH_CONTROLLER) !== 'all') {
|
||||
if (getPref(PrefKey.STREAM_TOUCH_CONTROLLER) !== StreamTouchController.ALL) {
|
||||
return response;
|
||||
}
|
||||
|
||||
@ -150,7 +150,7 @@ export class XhomeInterceptor {
|
||||
|
||||
// Patch resolution
|
||||
const deviceInfo = RemotePlay.BASE_DEVICE_INFO;
|
||||
if (getPref(PrefKey.REMOTE_PLAY_RESOLUTION) === '720p') {
|
||||
if (getPref(PrefKey.REMOTE_PLAY_RESOLUTION) === StreamResolution.DIM_720P) {
|
||||
deviceInfo.dev.os.name = 'android';
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,6 @@
|
||||
import type { NavigationElement } from "@/modules/ui/dialog/navigation-dialog";
|
||||
import { BxEvent } from "@/utils/bx-event";
|
||||
import type { BxSelectSettingElement } from "@/utils/setting-element";
|
||||
import { ButtonStyle, CE, createButton } from "@utils/html";
|
||||
|
||||
export class BxSelectElement {
|
||||
@ -40,7 +42,7 @@ export class BxSelectElement {
|
||||
const $option = getOptionAtIndex(visibleIndex);
|
||||
$option && ($option.selected = (e.target as HTMLInputElement).checked);
|
||||
|
||||
$select.dispatchEvent(new Event('input'));
|
||||
BxEvent.dispatch($select, 'input');
|
||||
});
|
||||
} else {
|
||||
$content = CE('div', {},
|
||||
@ -122,7 +124,7 @@ export class BxSelectElement {
|
||||
if (isMultiple) {
|
||||
render();
|
||||
} else {
|
||||
$select.dispatchEvent(new Event('input'));
|
||||
BxEvent.dispatch($select, 'input');
|
||||
}
|
||||
};
|
||||
|
||||
@ -178,7 +180,15 @@ export class BxSelectElement {
|
||||
$div.dispatchEvent = function() {
|
||||
// @ts-ignore
|
||||
return $select.dispatchEvent.apply($select, arguments);
|
||||
}
|
||||
};
|
||||
|
||||
($div as any).setValue = (value: any) => {
|
||||
if ('setValue' in $select) {
|
||||
($select as BxSelectSettingElement).setValue(value);
|
||||
} else {
|
||||
$select.value = value;
|
||||
}
|
||||
};
|
||||
|
||||
return $div;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user