From 67fdf5ea12f0062efc5b5bc4fe50b3d22f6cfc5e Mon Sep 17 00:00:00 2001 From: redphx <96280+redphx@users.noreply.github.com> Date: Sat, 21 Jun 2025 16:00:46 +0700 Subject: [PATCH] Fix clearAllData() not working properly (#708) --- dist/better-xcloud.pretty.user.js | 4 ++-- dist/better-xcloud.user.js | 4 ++-- src/modules/ui/dialog/settings-dialog.ts | 2 +- src/utils/utils.ts | 3 ++- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/dist/better-xcloud.pretty.user.js b/dist/better-xcloud.pretty.user.js index 732812b..82429c2 100644 --- a/dist/better-xcloud.pretty.user.js +++ b/dist/better-xcloud.pretty.user.js @@ -2760,7 +2760,7 @@ function parseDetailsPath(path) { return { titleSlug, productId }; } function clearAllData() { - for (let i = 0;i < localStorage.length; i++) { + for (let i = localStorage.length - 1;i >= 0; i--) { let key = localStorage.key(i); if (!key) continue; if (key.startsWith("BetterXcloud") || key.startsWith("better_xcloud")) localStorage.removeItem(key); @@ -7387,7 +7387,7 @@ class SettingsDialog extends NavigationDialog { ($parent) => { $parent.appendChild(createButton({ label: t("clear-data"), - style: 8 | 128 | 64, + style: 4 | 16 | 128 | 64, onClick: (e) => { if (confirm(t("clear-data-confirm"))) clearAllData(); } diff --git a/dist/better-xcloud.user.js b/dist/better-xcloud.user.js index fb6e53f..b504eee 100755 --- a/dist/better-xcloud.user.js +++ b/dist/better-xcloud.user.js @@ -105,7 +105,7 @@ function floorToNearest(value, interval) {return Math.floor(value / interval) * async function copyToClipboard(text, showToast = !0) {try {return await navigator.clipboard.writeText(text), showToast && Toast.show("Copied to clipboard", "", { instant: !0 }), !0;} catch (err) {console.error("Failed to copy: ", err), showToast && Toast.show("Failed to copy", "", { instant: !0 });}return !1;} function productTitleToSlug(title) {return title.replace(/[;,/?:@&=+_`~$%#^*()!^™\xae\xa9]/g, "").replace(/\|/g, "-").replace(/ {2,}/g, " ").trim().substr(0, 50).replace(/ /g, "-").toLowerCase();} function parseDetailsPath(path) {let matches = /\/games\/(?[^\/]+)\/(?\w+)/.exec(path);if (!matches?.groups) return {};let titleSlug = matches.groups.titleSlug.replaceAll("|", "-"), productId = matches.groups.productId;return { titleSlug, productId };} -function clearAllData() {for (let i = 0;i < localStorage.length; i++) {let key = localStorage.key(i);if (!key) continue;if (key.startsWith("BetterXcloud") || key.startsWith("better_xcloud")) localStorage.removeItem(key);}try {indexedDB.deleteDatabase(LocalDb.DB_NAME);} catch (e) {}alert(t("clear-data-success"));} +function clearAllData() {for (let i = localStorage.length - 1;i >= 0; i--) {let key = localStorage.key(i);if (!key) continue;if (key.startsWith("BetterXcloud") || key.startsWith("better_xcloud")) localStorage.removeItem(key);}try {indexedDB.deleteDatabase(LocalDb.DB_NAME);} catch (e) {}alert(t("clear-data-success"));} function containsAll(arr, values) {return values.every((val) => arr.includes(val));} function blockAllNotifications() {let blockFeatures = getGlobalPref("block.features");return containsAll(blockFeatures, ["friends", "notifications-achievements", "notifications-invites"]);} function blockSomeNotifications() {let blockFeatures = getGlobalPref("block.features");if (blockAllNotifications()) return !1;return ["friends", "notifications-achievements", "notifications-invites"].some((value) => blockFeatures.includes(value));} @@ -214,7 +214,7 @@ class KeyBindingDialog {static instance;static getInstance = () => KeyBindingDia class MkbMappingManagerDialog extends BaseProfileManagerDialog {static instance;static getInstance = () => MkbMappingManagerDialog.instance ?? (MkbMappingManagerDialog.instance = new MkbMappingManagerDialog(t("virtual-controller")));KEYS_PER_BUTTON = 2;BUTTONS_ORDER = [16,12,13,14,15,0,1,2,3,4,5,6,7,8,9,10,100,101,102,103,11,200,201,202,203];allKeyElements = [];$mouseMapTo;$mouseSensitivityX;$mouseSensitivityY;$mouseDeadzone;$unbindNote;constructor(title) {super(title, MkbMappingPresetsTable.getInstance());this.render();}onBindingKey = (e) => {if (e.target.disabled) return;if (e.button !== 0) return;};parseDataset($btn) {let dataset = $btn.dataset;return {keySlot: parseInt(dataset.keySlot),buttonIndex: parseInt(dataset.buttonIndex)};}onKeyChanged = (e) => {let $current = e.target, keyInfo = $current.keyInfo;if (keyInfo) {for (let $elm of this.allKeyElements)if ($elm !== $current && $elm.keyInfo?.code === keyInfo.code) $elm.unbindKey(!0);}this.savePreset();};render() {let $rows = CE("div", !1, this.$unbindNote = CE("i", { class: "bx-mkb-note" }, t("right-click-to-unbind")));for (let buttonIndex of this.BUTTONS_ORDER) {let [buttonName, buttonPrompt] = GamepadKeyName[buttonIndex], $elm, $fragment = document.createDocumentFragment();for (let i = 0;i < this.KEYS_PER_BUTTON; i++)$elm = BxKeyBindingButton.create({title: buttonPrompt,isPrompt: !0,allowedFlags: [1, 4, 8],onChanged: this.onKeyChanged}), $elm.dataset.buttonIndex = buttonIndex.toString(), $elm.dataset.keySlot = i.toString(), $elm.addEventListener("mouseup", this.onBindingKey), $fragment.appendChild($elm), this.allKeyElements.push($elm);let $keyRow = CE("div", {class: "bx-mkb-key-row",_nearby: { orientation: "horizontal" }}, CE("label", { title: buttonName }, buttonPrompt), $fragment);$rows.appendChild($keyRow);}let savePreset = () => this.savePreset(), $extraSettings = CE("div", !1, createSettingRow(t("map-mouse-to"), this.$mouseMapTo = BxSelectElement.create(CE("select", { _on: { input: savePreset } }, CE("option", { value: 2 }, t("right-stick")), CE("option", { value: 1 }, t("left-stick")), CE("option", { value: 0 }, t("off"))))), createSettingRow(t("horizontal-sensitivity"), this.$mouseSensitivityX = BxNumberStepper.create("hor_sensitivity", 0, 1, 300, {suffix: "%",exactTicks: 50}, savePreset)), createSettingRow(t("vertical-sensitivity"), this.$mouseSensitivityY = BxNumberStepper.create("ver_sensitivity", 0, 1, 300, {suffix: "%",exactTicks: 50}, savePreset)), createSettingRow(t("deadzone-counterweight"), this.$mouseDeadzone = BxNumberStepper.create("deadzone_counterweight", 0, 1, 50, {suffix: "%",exactTicks: 10}, savePreset)));this.$content = CE("div", !1, $rows, $extraSettings);}switchPreset(id) {let preset = this.allPresets.data[id];if (!preset) {this.currentPresetId = 0;return;}let presetData = preset.data;this.currentPresetId = id;let isDefaultPreset = id <= 0;this.updateButtonStates(), this.$unbindNote.classList.toggle("bx-gone", isDefaultPreset);for (let $elm of this.allKeyElements) {let { buttonIndex, keySlot } = this.parseDataset($elm), buttonKeys = presetData.mapping[buttonIndex];if (buttonKeys && buttonKeys[keySlot]) $elm.bindKey({code: buttonKeys[keySlot]}, !0);else $elm.unbindKey(!0);$elm.disabled = isDefaultPreset;}let mouse = presetData.mouse;this.$mouseMapTo.value = mouse.mapTo.toString(), this.$mouseSensitivityX.value = mouse.sensitivityX.toString(), this.$mouseSensitivityY.value = mouse.sensitivityY.toString(), this.$mouseDeadzone.value = mouse.deadzoneCounterweight.toString(), this.$mouseMapTo.disabled = isDefaultPreset, this.$mouseSensitivityX.dataset.disabled = isDefaultPreset.toString(), this.$mouseSensitivityY.dataset.disabled = isDefaultPreset.toString(), this.$mouseDeadzone.dataset.disabled = isDefaultPreset.toString();}savePreset() {let presetData = deepClone(this.presetsDb.BLANK_PRESET_DATA);for (let $elm of this.allKeyElements) {let { buttonIndex, keySlot } = this.parseDataset($elm), mapping = presetData.mapping;if (!mapping[buttonIndex]) mapping[buttonIndex] = [];if (!$elm.keyInfo) delete mapping[buttonIndex][keySlot];else mapping[buttonIndex][keySlot] = $elm.keyInfo.code;}let mouse = presetData.mouse;mouse.mapTo = parseInt(this.$mouseMapTo.value), mouse.sensitivityX = parseInt(this.$mouseSensitivityX.value), mouse.sensitivityY = parseInt(this.$mouseSensitivityY.value), mouse.deadzoneCounterweight = parseInt(this.$mouseDeadzone.value);let oldPreset = this.allPresets.data[this.currentPresetId], newPreset = {id: this.currentPresetId,name: oldPreset.name,data: presetData};this.presetsDb.updatePreset(newPreset), this.allPresets.data[this.currentPresetId] = newPreset;}onBeforeUnmount() {StreamSettings.refreshMkbSettings(), super.onBeforeUnmount();}} class KeyboardShortcutsManagerDialog extends BaseProfileManagerDialog {static instance;static getInstance = () => KeyboardShortcutsManagerDialog.instance ?? (KeyboardShortcutsManagerDialog.instance = new KeyboardShortcutsManagerDialog(t("keyboard-shortcuts")));$content;$unbindNote;allKeyElements = [];constructor(title) {super(title, KeyboardShortcutsTable.getInstance());let $rows = CE("div", { class: "bx-keyboard-shortcuts-manager-container" });for (let groupLabel in SHORTCUT_ACTIONS) {let items = SHORTCUT_ACTIONS[groupLabel];if (!items) continue;let $fieldSet = CE("fieldset", !1, CE("legend", !1, groupLabel));for (let action in items) {let crumbs = items[action];if (!crumbs) continue;let label = crumbs.join(" ❯ "), $btn = BxKeyBindingButton.create({title: label,isPrompt: !1,onChanged: this.onKeyChanged,allowedFlags: [1, 2]});$btn.classList.add("bx-full-width"), $btn.dataset.action = action, this.allKeyElements.push($btn);let $row = createSettingRow(label, CE("div", { class: "bx-binding-button-wrapper" }, $btn));$fieldSet.appendChild($row);}if ($fieldSet.childElementCount > 1) $rows.appendChild($fieldSet);}this.$content = CE("div", !1, this.$unbindNote = CE("i", { class: "bx-mkb-note" }, t("right-click-to-unbind")), $rows);}onKeyChanged = (e) => {let $current = e.target, keyInfo = $current.keyInfo;if (keyInfo) for (let $elm of this.allKeyElements) {if ($elm === $current) continue;if ($elm.keyInfo?.code === keyInfo.code && $elm.keyInfo?.modifiers === keyInfo.modifiers) $elm.unbindKey(!0);}this.savePreset();};parseDataset($btn) {return {action: $btn.dataset.action};}switchPreset(id) {let preset = this.allPresets.data[id];if (!preset) {this.currentPresetId = 0;return;}let presetData = preset.data;this.currentPresetId = id;let isDefaultPreset = id <= 0;this.updateButtonStates(), this.$unbindNote.classList.toggle("bx-gone", isDefaultPreset);for (let $elm of this.allKeyElements) {let { action } = this.parseDataset($elm), keyInfo = presetData.mapping[action];if (keyInfo) $elm.bindKey(keyInfo, !0);else $elm.unbindKey(!0);$elm.disabled = isDefaultPreset;}}savePreset() {let presetData = deepClone(this.presetsDb.BLANK_PRESET_DATA);for (let $elm of this.allKeyElements) {let { action } = this.parseDataset($elm), mapping = presetData.mapping;if ($elm.keyInfo) mapping[action] = $elm.keyInfo;}let oldPreset = this.allPresets.data[this.currentPresetId], newPreset = {id: this.currentPresetId,name: oldPreset.name,data: presetData};this.presetsDb.updatePreset(newPreset), this.allPresets.data[this.currentPresetId] = newPreset;}onBeforeUnmount() {StreamSettings.refreshKeyboardShortcuts(), super.onBeforeUnmount();}} class MkbExtraSettings extends HTMLElement {$mappingPresets;$shortcutsPresets;updateLayout;saveMkbSettings;saveShortcutsSettings;static renderSettings() {let $container = document.createDocumentFragment();$container.updateLayout = MkbExtraSettings.updateLayout.bind($container), $container.saveMkbSettings = MkbExtraSettings.saveMkbSettings.bind($container), $container.saveShortcutsSettings = MkbExtraSettings.saveShortcutsSettings.bind($container);let $mappingPresets = BxSelectElement.create(CE("select", {autocomplete: "off",_on: {input: $container.saveMkbSettings}})), $shortcutsPresets = BxSelectElement.create(CE("select", {autocomplete: "off",_on: {input: $container.saveShortcutsSettings}}));return $container.append(...getGlobalPref("mkb.enabled") ? [createSettingRow(t("virtual-controller"), CE("div", {class: "bx-preset-row",_nearby: {orientation: "horizontal"}}, $mappingPresets, createButton({title: t("manage"),icon: BxIcon.MANAGE,style: 64 | 1 | 512,onClick: () => MkbMappingManagerDialog.getInstance().show({id: parseInt($container.$mappingPresets.value)})})), {multiLines: !0,onContextMenu: this.boundOnContextMenu,pref: "mkb.p1.preset.mappingId"}),createSettingRow(t("virtual-controller-slot"), this.settingsManager.getElement("mkb.p1.slot"), {onContextMenu: this.boundOnContextMenu,pref: "mkb.p1.slot"})] : [], createSettingRow(t("in-game-keyboard-shortcuts"), CE("div", {class: "bx-preset-row",_nearby: {orientation: "horizontal"}}, $shortcutsPresets, createButton({title: t("manage"),icon: BxIcon.MANAGE,style: 64 | 1 | 512,onClick: () => KeyboardShortcutsManagerDialog.getInstance().show({id: parseInt($container.$shortcutsPresets.value)})})), {multiLines: !0,onContextMenu: this.boundOnContextMenu,pref: "keyboardShortcuts.preset.inGameId"})), $container.$mappingPresets = $mappingPresets, $container.$shortcutsPresets = $shortcutsPresets, this.settingsManager.setElement("keyboardShortcuts.preset.inGameId", $shortcutsPresets), this.settingsManager.setElement("mkb.p1.preset.mappingId", $mappingPresets), $container.updateLayout(), this.onMountedCallbacks.push(() => {$container.updateLayout();}), $container;}static async updateLayout() {let mappingPresets = await MkbMappingPresetsTable.getInstance().getPresets();renderPresetsList(this.$mappingPresets, mappingPresets, getStreamPref("mkb.p1.preset.mappingId"));let shortcutsPresets = await KeyboardShortcutsTable.getInstance().getPresets();renderPresetsList(this.$shortcutsPresets, shortcutsPresets, getStreamPref("keyboardShortcuts.preset.inGameId"), { addOffValue: !0 });}static async saveMkbSettings() {let presetId = parseInt(this.$mappingPresets.value);setStreamPref("mkb.p1.preset.mappingId", presetId, "ui");}static async saveShortcutsSettings() {let presetId = parseInt(this.$shortcutsPresets.value);setStreamPref("keyboardShortcuts.preset.inGameId", presetId, "ui");}} -class SettingsDialog extends NavigationDialog {static instance;static getInstance = () => SettingsDialog.instance ?? (SettingsDialog.instance = new SettingsDialog);LOG_TAG = "SettingsNavigationDialog";$container;$tabs;$tabContents;$btnReload;$btnGlobalReload;$noteGlobalReload;$btnSuggestion;$streamSettingsSelection;renderFullSettings;boundOnContextMenu;suggestedSettings = {recommended: {},default: {},lowest: {},highest: {}};settingLabels = {};settingsManager;TAB_GLOBAL_ITEMS = [{group: "general",label: t("better-xcloud"),helpUrl: "https://better-xcloud.github.io/features/",items: [($parent) => {let PREF_LATEST_VERSION = getGlobalPref("version.latest"), topButtons = [];if (!SCRIPT_VERSION.includes("beta") && PREF_LATEST_VERSION && PREF_LATEST_VERSION != SCRIPT_VERSION) {let opts = {label: "🌟 " + t("new-version-available", { version: PREF_LATEST_VERSION }),style: 1 | 64 | 128};if (AppInterface && AppInterface.updateLatestScript) opts.onClick = (e) => AppInterface.updateLatestScript();else opts.url = "https://github.com/redphx/better-xcloud/releases/latest";topButtons.push(createButton(opts));}if (AppInterface) topButtons.push(createButton({label: t("app-settings"),icon: BxIcon.STREAM_SETTINGS,style: 128 | 64,onClick: (e) => {AppInterface.openAppSettings && AppInterface.openAppSettings(), this.hide();}}));else if (UserAgent.getDefault().toLowerCase().includes("android")) topButtons.push(createButton({label: "🔥 " + t("install-android"),style: 128 | 64,url: "https://better-xcloud.github.io/android"}));this.$btnGlobalReload = createButton({label: t("settings-reload"),classes: ["bx-settings-reload-button", "bx-gone"],style: 64 | 128,onClick: (e) => {this.reloadPage();}}), topButtons.push(this.$btnGlobalReload), this.$noteGlobalReload = CE("span", {class: "bx-settings-reload-note"}, t("settings-reload-note")), topButtons.push(this.$noteGlobalReload), this.$btnSuggestion = CE("div", {class: "bx-suggest-toggler bx-focusable",tabindex: 0}, CE("label", !1, t("suggest-settings")), CE("span", !1, "❯")), this.$btnSuggestion.addEventListener("click", SuggestionsSetting.renderSuggestions.bind(this)), topButtons.push(this.$btnSuggestion);let $div = CE("div", {class: "bx-top-buttons",_nearby: {orientation: "vertical"}}, ...topButtons);$parent.appendChild($div);},{pref: "bx.locale",multiLines: !0},"server.bypassRestriction","ui.controllerFriendly"]}, {group: "server",label: t("server"),items: [{pref: "server.region",multiLines: !0},{pref: "stream.locale",multiLines: !0},"server.ipv6.prefer"]}, {group: "stream",label: t("stream"),items: ["stream.video.resolution","stream.video.codecProfile","stream.video.maxBitrate","audio.volume.booster.enabled","screenshot.applyFilters","audio.mic.onPlaying","game.fortnite.forceConsole","stream.video.combineAudio"]}, {requiredVariants: "full",group: "mkb",label: t("mouse-and-keyboard"),items: ["nativeMkb.mode",{pref: "nativeMkb.forcedGames",multiLines: !0,note: CE("a", { href: "https://github.com/redphx/better-xcloud/discussions/574", target: "_blank" }, t("unofficial-game-list"))},"mkb.enabled","mkb.cursor.hideIdle"],...!STATES.browser.capabilities.emulatedNativeMkb && (!STATES.userAgent.capabilities.mkb || !STATES.browser.capabilities.mkb) ? {unsupported: !0,unsupportedNote: CE("a", {href: "https://github.com/redphx/better-xcloud/issues/206#issuecomment-1920475657",target: "_blank"}, "⚠️ " + t("browser-unsupported-feature"))} : {}}, {requiredVariants: "full",group: "touch-control",label: t("touch-controller"),items: [{pref: "touchController.mode",note: CE("a", { href: "https://github.com/redphx/better-xcloud/discussions/241", target: "_blank" }, t("unofficial-game-list"))},"touchController.autoOff","touchController.opacity.default","touchController.style.standard","touchController.style.custom"],...!STATES.userAgent.capabilities.touch ? {unsupported: !0,unsupportedNote: "⚠️ " + t("device-unsupported-touch")} : {}}, {group: "ui",label: t("ui"),items: ["ui.layout","ui.theme","ui.imageQuality","ui.gameCard.waitTime.show","ui.controllerStatus.show","ui.streamMenu.simplify","ui.splashVideo.skip",!AppInterface && "ui.hideScrollbar","ui.systemMenu.hideHandle","ui.feedbackDialog.disabled","ui.reduceAnimations",{pref: "ui.hideSections",multiLines: !0},{pref: "block.features",multiLines: !0}]}, {requiredVariants: "full",group: "game-bar",label: t("game-bar"),items: ["gameBar.position"]}, {group: "loading-screen",label: t("loading-screen"),items: ["loadingScreen.gameArt.show","loadingScreen.waitTime.show","loadingScreen.rocket"]}, {group: "other",label: t("other"),items: ["block.tracking"]}, {group: "advanced",label: t("advanced"),items: [{pref: "userAgent.profile",multiLines: !0,onCreated: (setting, $control) => {let defaultUserAgent = window.navigator.orgUserAgent || window.navigator.userAgent, $inpCustomUserAgent = CE("input", {type: "text",placeholder: defaultUserAgent,autocomplete: "off",class: "bx-settings-custom-user-agent",tabindex: 0});$inpCustomUserAgent.addEventListener("input", (e) => {let profile = $control.value, custom = e.target.value.trim();UserAgent.updateStorage(profile, custom), this.onGlobalSettingChanged(e);}), $control.insertAdjacentElement("afterend", $inpCustomUserAgent), setNearby($inpCustomUserAgent.parentElement, {orientation: "vertical"});}}]}, {group: "footer",items: [($parent) => {try {let appVersion = document.querySelector("meta[name=gamepass-app-version]").content, appDate = new Date(document.querySelector("meta[name=gamepass-app-date]").content).toISOString().substring(0, 10);$parent.appendChild(CE("div", {class: "bx-settings-app-version"}, `xCloud website version ${appVersion} (${appDate})`));} catch (e) {}},($parent) => {$parent.appendChild(CE("a", {class: "bx-donation-link",href: "https://ko-fi.com/redphx",target: "_blank",tabindex: 0}, `❤️ ${t("support-better-xcloud")}`));},($parent) => {$parent.appendChild(createButton({label: t("clear-data"),style: 8 | 128 | 64,onClick: (e) => {if (confirm(t("clear-data-confirm"))) clearAllData();}}));},($parent) => {$parent.appendChild(CE("div", { class: "bx-debug-info" }, createButton({label: "Debug info",style: 8 | 128 | 64,onClick: (e) => {let $button = e.target.closest("button");if (!$button) return;let $pre = $button.nextElementSibling;if (!$pre) {let debugInfo = deepClone(BX_FLAGS.DeviceInfo);debugInfo.settings = JSON.parse(window.localStorage.getItem("BetterXcloud") || "{}"), $pre = CE("pre", {class: "bx-focusable bx-gone",tabindex: 0,_on: {click: async (e2) => {await copyToClipboard(e2.target.innerText);}}}, "```\n" + JSON.stringify(debugInfo, null, " ") + "\n```"), $button.insertAdjacentElement("afterend", $pre);}$pre.classList.toggle("bx-gone"), $pre.scrollIntoView();}})));}]}];TAB_DISPLAY_ITEMS = [{requiredVariants: "full",group: "audio",label: t("audio"),helpUrl: "https://better-xcloud.github.io/ingame-features/#audio",items: [{pref: "audio.volume",params: {disabled: !getGlobalPref("audio.volume.booster.enabled")},onCreated: (setting, $elm) => {let $range = $elm.querySelector("input[type=range");BxEventBus.Stream.on("setting.changed", (payload) => {let { settingKey } = payload;if (settingKey === "audio.volume") $range.value = getStreamPref(settingKey).toString(), BxEvent.dispatch($range, "input", { ignoreOnChange: !0 });});}}]}, {group: "video",label: t("video"),helpUrl: "https://better-xcloud.github.io/ingame-features/#video",items: ["video.player.type","video.maxFps","video.player.powerPreference","video.processing","video.processing.mode","video.ratio","video.position","video.processing.sharpness","video.saturation","video.contrast","video.brightness"]}];TAB_CONTROLLER_ITEMS = [{group: "controller",label: t("controller"),helpUrl: "https://better-xcloud.github.io/ingame-features/#controller",items: ["localCoOp.enabled","controller.pollingRate",($parent) => {$parent.appendChild(ControllerExtraSettings.renderSettings.apply(this));}]},STATES.userAgent.capabilities.touch && {group: "touch-control",label: t("touch-controller"),items: [{label: t("layout"),content: CE("select", {disabled: !0}, CE("option", !1, t("default"))),onCreated: (setting, $elm) => {$elm.addEventListener("input", (e) => {TouchController.applyCustomLayout($elm.value, 1000);}), window.addEventListener(BxEvent.CUSTOM_TOUCH_LAYOUTS_LOADED, (e) => {let customLayouts = TouchController.getCustomLayouts();while ($elm.firstChild)$elm.removeChild($elm.firstChild);if ($elm.disabled = !customLayouts, !customLayouts) {$elm.appendChild(CE("option", { value: "" }, t("default"))), $elm.value = "", $elm.dispatchEvent(new Event("input"));return;}let $fragment = document.createDocumentFragment();for (let key in customLayouts.layouts) {let layout = customLayouts.layouts[key], name;if (layout.author) name = `${layout.name} (${layout.author})`;else name = layout.name;let $option = CE("option", { value: key }, name);$fragment.appendChild($option);}$elm.appendChild($fragment), $elm.value = customLayouts.default_layout;});}}]},STATES.browser.capabilities.deviceVibration && {group: "device",label: t("device"),items: [{pref: "deviceVibration.mode",multiLines: !0,unsupported: !STATES.browser.capabilities.deviceVibration}, {pref: "deviceVibration.intensity",unsupported: !STATES.browser.capabilities.deviceVibration}]}];TAB_MKB_ITEMS = [{requiredVariants: "full",group: "mkb",label: t("mouse-and-keyboard"),helpUrl: "https://better-xcloud.github.io/mouse-and-keyboard/",items: [($parent) => {$parent.appendChild(MkbExtraSettings.renderSettings.apply(this));}]},NativeMkbHandler.isAllowed() && {requiredVariants: "full",group: "native-mkb",label: t("native-mkb"),items: ["nativeMkb.scroll.sensitivityY","nativeMkb.scroll.sensitivityX"]}];TAB_STATS_ITEMS = [{group: "stats",label: t("stream-stats"),helpUrl: "https://better-xcloud.github.io/stream-stats/",items: ["stats.showWhenPlaying","stats.quickGlance.enabled","stats.items","stats.position","stats.textSize","stats.opacity.all","stats.opacity.background","stats.colors"]}];SETTINGS_UI = {global: {group: "global",icon: BxIcon.HOME,items: this.TAB_GLOBAL_ITEMS},stream: {group: "stream",icon: BxIcon.DISPLAY,items: this.TAB_DISPLAY_ITEMS},controller: {group: "controller",icon: BxIcon.CONTROLLER,items: this.TAB_CONTROLLER_ITEMS,requiredVariants: "full"},mkb: {group: "mkb",icon: BxIcon.NATIVE_MKB,items: this.TAB_MKB_ITEMS,requiredVariants: "full"},stats: {group: "stats",icon: BxIcon.STREAM_STATS,items: this.TAB_STATS_ITEMS}};constructor() {super();BxLogger.info(this.LOG_TAG, "constructor()"), this.boundOnContextMenu = this.onContextMenu.bind(this), this.settingsManager = SettingsManager.getInstance(), this.renderFullSettings = STATES.supportedRegion && STATES.isSignedIn, this.setupDialog(), this.onMountedCallbacks.push(() => {if (onChangeVideoPlayerType(), STATES.userAgent.capabilities.touch) BxEvent.dispatch(window, BxEvent.CUSTOM_TOUCH_LAYOUTS_LOADED);let $selectUserAgent = document.querySelector(`#bx_setting_${escapeCssSelector("userAgent.profile")}`);if ($selectUserAgent) $selectUserAgent.disabled = !0, BxEvent.dispatch($selectUserAgent, "input", {}), $selectUserAgent.disabled = !1;}), BxEventBus.Stream.on("gameSettings.switched", ({ id }) => {this.$tabContents.dataset.gameId = id.toString();});}getDialog() {return this;}getContent() {return this.$container;}onMounted() {super.onMounted();}isOverlayVisible() {return !STATES.isPlaying;}reloadPage() {this.$btnGlobalReload.disabled = !0, this.$btnGlobalReload.firstElementChild.textContent = t("settings-reloading"), this.hide(), FullscreenText.getInstance().show(t("settings-reloading")), window.location.reload();}isSupportedVariant(requiredVariants) {if (typeof requiredVariants === "undefined") return !0;return requiredVariants = typeof requiredVariants === "string" ? [requiredVariants] : requiredVariants, requiredVariants.includes(SCRIPT_VARIANT);}onTabClicked = (e) => {let $svg = e.target.closest("svg"), $child, children = Array.from(this.$tabContents.children);for ($child of children)if ($child.dataset.tabGroup === $svg.dataset.group) $child.classList.remove("bx-gone"), calculateSelectBoxes($child);else if ($child.dataset.tabGroup) $child.classList.add("bx-gone");this.$streamSettingsSelection.classList.toggle("bx-gone", $svg.dataset.group === "global");for (let $child2 of Array.from(this.$tabs.children))$child2.classList.remove("bx-active");$svg.classList.add("bx-active");};renderTab(settingTab) {let $svg = createSvgIcon(settingTab.icon);return $svg.dataset.group = settingTab.group, $svg.tabIndex = 0, $svg.addEventListener("click", this.onTabClicked), $svg;}onGlobalSettingChanged = (e) => {PatcherCache.getInstance().clear(), this.$btnReload.classList.add("bx-danger"), this.$noteGlobalReload.classList.add("bx-gone"), this.$btnGlobalReload.classList.remove("bx-gone"), this.$btnGlobalReload.classList.add("bx-danger");};onContextMenu(e) {e.preventDefault();let $elm = e.target;$elm instanceof HTMLElement && this.resetHighlightedSetting($elm);}renderServerSetting(setting) {let selectedValue = getGlobalPref("server.region"), continents = {"america-north": {label: t("continent-north-america")},"america-south": {label: t("continent-south-america")},asia: {label: t("continent-asia")},australia: {label: t("continent-australia")},europe: {label: t("continent-europe")},other: {label: t("other")}}, $control = CE("select", {id: `bx_setting_${escapeCssSelector(setting.pref)}`,tabindex: 0});$control.name = $control.id, $control.addEventListener("input", (e) => {setGlobalPref(setting.pref, e.target.value, "ui"), this.onGlobalSettingChanged(e);}), setting.options = {};for (let regionName in STATES.serverRegions) {let region = STATES.serverRegions[regionName], value = regionName, label = `${region.shortName} - ${regionName}`;if (region.isDefault) {if (label += ` (${t("default")})`, value = "default", selectedValue === regionName) selectedValue = "default";}setting.options[value] = label;let $option = CE("option", { value }, label), continent = continents[region.contintent];if (!continent.children) continent.children = [];continent.children.push($option);}let fragment = document.createDocumentFragment(), key;for (key in continents) {let continent = continents[key];if (!continent.children) continue;fragment.appendChild(CE("optgroup", {label: continent.label}, ...continent.children));}return $control.appendChild(fragment), $control.disabled = Object.keys(STATES.serverRegions).length === 0, $control.value = selectedValue, $control;}renderSettingRow(settingTab, $tabContent, settingTabContent, setting) {if (typeof setting === "string") setting = {pref: setting};let pref = setting.pref, $control;if (setting.content) if (typeof setting.content === "function") $control = setting.content.apply(this);else $control = setting.content;else if (!setting.unsupported) {if (pref === "server.region") $control = this.renderServerSetting(setting);else if (pref === "bx.locale") $control = SettingElement.fromPref(pref, async (e) => {let newLocale = e.target.value;if (getGlobalPref("ui.controllerFriendly")) {let timeoutId = e.target.timeoutId;timeoutId && window.clearTimeout(timeoutId), e.target.timeoutId = window.setTimeout(() => {Translations.refreshLocale(newLocale), Translations.updateTranslations();}, 500);} else Translations.refreshLocale(newLocale), Translations.updateTranslations();this.onGlobalSettingChanged(e);});else if (pref === "userAgent.profile") $control = SettingElement.fromPref("userAgent.profile", (e) => {let value = e.target.value, isCustom = value === "custom", userAgent2 = UserAgent.get(value);UserAgent.updateStorage(value);let $inp = $control.nextElementSibling;$inp.value = userAgent2, $inp.readOnly = !isCustom, $inp.disabled = !isCustom, !e.target.disabled && this.onGlobalSettingChanged(e);});else if ($control = this.settingsManager.getElement(pref, setting.params), settingTab.group === "global") $control.addEventListener("input", this.onGlobalSettingChanged);if ($control instanceof HTMLSelectElement) $control = BxSelectElement.create($control);}let prefDefinition = null;if (pref) prefDefinition = getPrefInfo(pref).definition;if (prefDefinition && !this.isSupportedVariant(prefDefinition.requiredVariants)) return;let label = prefDefinition?.label || setting.label || "", note = prefDefinition?.note || setting.note, unsupportedNote = prefDefinition?.unsupportedNote || setting.unsupportedNote, experimental = prefDefinition?.experimental || setting.experimental;if (typeof note === "function") note = note();if (typeof unsupportedNote === "function") unsupportedNote = unsupportedNote();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);}if (experimental) if (label = "🧪 " + label, !note) note = t("experimental");else note = `${t("experimental")}: ${note}`;let $note;if (unsupportedNote) $note = CE("div", { class: "bx-settings-dialog-note" }, unsupportedNote);else if (note) $note = CE("div", { class: "bx-settings-dialog-note" }, note);let $row = createSettingRow(label, !prefDefinition?.unsupported && $control, {$note,multiLines: setting.multiLines,icon: prefDefinition?.labelIcon,onContextMenu: this.boundOnContextMenu,pref});if (pref) $row.htmlFor = `bx_setting_${escapeCssSelector(pref)}`;if ($row.dataset.type = settingTabContent.group, !STATES.supportedRegion && setting.pref === "server.bypassRestriction") $row.classList.add("bx-settings-important-row");$tabContent.appendChild($row), !prefDefinition?.unsupported && setting.onCreated && setting.onCreated(setting, $control);}renderSettingsSection(settingTab, sections) {let $tabContent = CE("div", {class: "bx-gone",_dataset: {tabGroup: settingTab.group}});for (let section of sections) {if (!section) continue;if (section instanceof HTMLElement) {$tabContent.appendChild(section);continue;}if (!this.isSupportedVariant(section.requiredVariants)) continue;if (!this.renderFullSettings && settingTab.group === "global" && section.group !== "general" && section.group !== "footer") continue;let label = section.label;if (label === t("better-xcloud")) {if (label += " " + SCRIPT_VERSION, SCRIPT_VARIANT === "lite") label += " (Lite)";label = createButton({label,url: "https://github.com/redphx/better-xcloud/releases",style: 4096 | 16 | 64});}if (label) {let $title = CE("h2", {_nearby: {orientation: "horizontal"}}, CE("span", !1, label), section.helpUrl && createButton({icon: BxIcon.QUESTION,style: 8 | 64,url: section.helpUrl,title: t("help")}));$tabContent.appendChild($title);}if (section.unsupportedNote) {let $note = CE("b", { class: "bx-note-unsupported" }, section.unsupportedNote);$tabContent.appendChild($note);}if (section.unsupported) continue;if (section.content) {$tabContent.appendChild(section.content);continue;}section.items = section.items || [];for (let setting of section.items) {if (setting === !1) continue;if (typeof setting === "function") {setting.apply(this, [$tabContent]);continue;}this.renderSettingRow(settingTab, $tabContent, section, setting);}}return $tabContent;}setupDialog() {let $tabs, $tabContents, $container = CE("div", {class: "bx-settings-dialog",_nearby: {orientation: "horizontal"}}, CE("div", {class: "bx-settings-tabs-container",_nearby: {orientation: "vertical",focus: () => {return this.dialogManager.focus($tabs);},loop: (direction) => {if (direction === 1 || direction === 3) return this.focusVisibleTab(direction === 1 ? "last" : "first"), !0;return !1;}}}, $tabs = CE("div", {class: "bx-settings-tabs bx-hide-scroll-bar",_nearby: {focus: () => this.focusActiveTab()}}), CE("div", !1, this.$btnReload = createButton({icon: BxIcon.REFRESH,style: 64 | 32,onClick: (e) => {this.reloadPage();}}), createButton({icon: BxIcon.CLOSE,style: 64 | 32,onClick: (e) => {this.dialogManager.hide();}}))), CE("div", {class: "bx-settings-tab-contents",_nearby: {orientation: "vertical",loop: (direction) => {if (direction === 1 || direction === 3) return this.focusVisibleSetting(direction === 1 ? "last" : "first"), !0;return !1;}}}, this.$streamSettingsSelection = SettingsManager.getInstance().getStreamSettingsSelection(), $tabContents = CE("div", {class: "bx-settings-tab-content",_nearby: {orientation: "vertical",focus: () => this.jumpToSettingGroup("next")}})));this.$container = $container, this.$tabs = $tabs, this.$tabContents = $tabContents, $container.addEventListener("click", (e) => {if (e.target === $container) e.preventDefault(), e.stopPropagation(), this.hide();});let settingTabGroup;for (settingTabGroup in this.SETTINGS_UI) {let settingTab = this.SETTINGS_UI[settingTabGroup];if (!settingTab) continue;if (!this.isSupportedVariant(settingTab.requiredVariants)) continue;if (settingTab.group !== "global" && !this.renderFullSettings) continue;let $svg = this.renderTab(settingTab);$tabs.appendChild($svg);let $tabContent = this.renderSettingsSection.call(this, settingTab, settingTab.items);$tabContents.appendChild($tabContent);}$tabs.firstElementChild.dispatchEvent(new Event("click"));}focusTab(tabId) {let $tab = this.$container.querySelector(`.bx-settings-tabs svg[data-group=${tabId}]`);$tab && $tab.dispatchEvent(new Event("click"));}focusIfNeeded() {this.jumpToSettingGroup("next");}focusActiveTab() {let $currentTab = this.$tabs.querySelector(".bx-active");return $currentTab && $currentTab.focus(), !0;}focusVisibleSetting(type = "first") {let controls = Array.from(this.$tabContents.querySelectorAll("div[data-tab-group]:not(.bx-gone) > *"));if (!controls.length) return !1;if (type === "last") controls.reverse();for (let $control of controls) {if (!($control instanceof HTMLElement)) continue;let $focusable = this.dialogManager.findFocusableElement($control);if ($focusable) {if (this.dialogManager.focus($focusable)) return !0;}}return !1;}focusVisibleTab(type = "first") {let tabs = Array.from(this.$tabs.querySelectorAll("svg:not(.bx-gone)"));if (!tabs.length) return !1;if (type === "last") tabs.reverse();for (let $tab of tabs)if (this.dialogManager.focus($tab)) return !0;return !1;}jumpToSettingGroup(direction) {let $tabContent = this.$tabContents.querySelector("div[data-tab-group]:not(.bx-gone)");if (!$tabContent) return !1;let $header, $focusing = document.activeElement;if (!$focusing || !$tabContent.contains($focusing)) $header = $tabContent.querySelector("h2");else {let $parent = $focusing.closest("[data-tab-group] > *"), siblingProperty = direction === "next" ? "nextSibling" : "previousSibling", $tmp = $parent, times = 0;while (!0) {if (!$tmp) break;if ($tmp.tagName === "H2") {if ($header = $tmp, !$tmp.nextElementSibling?.classList.contains("bx-note-unsupported")) {if (++times, direction === "next" || times >= 2) break;}}$tmp = $tmp[siblingProperty];}}let $target;if ($header) $target = this.dialogManager.findNextTarget($header, 3, !1);if ($target) return this.dialogManager.focus($target);return !1;}resetHighlightedSetting($elm) {let targetGameId = SettingsManager.getInstance().getTargetGameId();if (targetGameId < 0) return;if (!$elm) $elm = document.activeElement instanceof HTMLElement ? document.activeElement : void 0;let $row = $elm?.closest("div[data-tab-group] > .bx-settings-row");if (!$row) return;let pref = $row.prefKey;if (!pref) alert("Pref not found: " + $row.id);if (!isStreamPref(pref)) return;let deleted = STORAGE.Stream.deleteSettingByGame(targetGameId, pref);if (deleted) BxEventBus.Stream.emit("setting.changed", {storageKey: `${"BetterXcloud.Stream"}.${targetGameId}`,settingKey: pref});return deleted;}handleKeyPress(key) {let handled = !0;switch (key) {case "Tab":this.focusActiveTab();break;case "Home":this.focusVisibleSetting("first");break;case "End":this.focusVisibleSetting("last");break;case "PageUp":this.jumpToSettingGroup("previous");break;case "PageDown":this.jumpToSettingGroup("next");break;case "KeyQ":this.resetHighlightedSetting();break;default:handled = !1;break;}return handled;}handleGamepad(button) {let handled = !0;switch (button) {case 1:let $focusing = document.activeElement;if ($focusing && this.$tabs.contains($focusing)) this.hide();else this.focusActiveTab();break;case 4:case 5:this.focusActiveTab();break;case 6:this.jumpToSettingGroup("previous");break;case 7:this.jumpToSettingGroup("next");break;case 2:this.resetHighlightedSetting();break;default:handled = !1;break;}return handled;}} +class SettingsDialog extends NavigationDialog {static instance;static getInstance = () => SettingsDialog.instance ?? (SettingsDialog.instance = new SettingsDialog);LOG_TAG = "SettingsNavigationDialog";$container;$tabs;$tabContents;$btnReload;$btnGlobalReload;$noteGlobalReload;$btnSuggestion;$streamSettingsSelection;renderFullSettings;boundOnContextMenu;suggestedSettings = {recommended: {},default: {},lowest: {},highest: {}};settingLabels = {};settingsManager;TAB_GLOBAL_ITEMS = [{group: "general",label: t("better-xcloud"),helpUrl: "https://better-xcloud.github.io/features/",items: [($parent) => {let PREF_LATEST_VERSION = getGlobalPref("version.latest"), topButtons = [];if (!SCRIPT_VERSION.includes("beta") && PREF_LATEST_VERSION && PREF_LATEST_VERSION != SCRIPT_VERSION) {let opts = {label: "🌟 " + t("new-version-available", { version: PREF_LATEST_VERSION }),style: 1 | 64 | 128};if (AppInterface && AppInterface.updateLatestScript) opts.onClick = (e) => AppInterface.updateLatestScript();else opts.url = "https://github.com/redphx/better-xcloud/releases/latest";topButtons.push(createButton(opts));}if (AppInterface) topButtons.push(createButton({label: t("app-settings"),icon: BxIcon.STREAM_SETTINGS,style: 128 | 64,onClick: (e) => {AppInterface.openAppSettings && AppInterface.openAppSettings(), this.hide();}}));else if (UserAgent.getDefault().toLowerCase().includes("android")) topButtons.push(createButton({label: "🔥 " + t("install-android"),style: 128 | 64,url: "https://better-xcloud.github.io/android"}));this.$btnGlobalReload = createButton({label: t("settings-reload"),classes: ["bx-settings-reload-button", "bx-gone"],style: 64 | 128,onClick: (e) => {this.reloadPage();}}), topButtons.push(this.$btnGlobalReload), this.$noteGlobalReload = CE("span", {class: "bx-settings-reload-note"}, t("settings-reload-note")), topButtons.push(this.$noteGlobalReload), this.$btnSuggestion = CE("div", {class: "bx-suggest-toggler bx-focusable",tabindex: 0}, CE("label", !1, t("suggest-settings")), CE("span", !1, "❯")), this.$btnSuggestion.addEventListener("click", SuggestionsSetting.renderSuggestions.bind(this)), topButtons.push(this.$btnSuggestion);let $div = CE("div", {class: "bx-top-buttons",_nearby: {orientation: "vertical"}}, ...topButtons);$parent.appendChild($div);},{pref: "bx.locale",multiLines: !0},"server.bypassRestriction","ui.controllerFriendly"]}, {group: "server",label: t("server"),items: [{pref: "server.region",multiLines: !0},{pref: "stream.locale",multiLines: !0},"server.ipv6.prefer"]}, {group: "stream",label: t("stream"),items: ["stream.video.resolution","stream.video.codecProfile","stream.video.maxBitrate","audio.volume.booster.enabled","screenshot.applyFilters","audio.mic.onPlaying","game.fortnite.forceConsole","stream.video.combineAudio"]}, {requiredVariants: "full",group: "mkb",label: t("mouse-and-keyboard"),items: ["nativeMkb.mode",{pref: "nativeMkb.forcedGames",multiLines: !0,note: CE("a", { href: "https://github.com/redphx/better-xcloud/discussions/574", target: "_blank" }, t("unofficial-game-list"))},"mkb.enabled","mkb.cursor.hideIdle"],...!STATES.browser.capabilities.emulatedNativeMkb && (!STATES.userAgent.capabilities.mkb || !STATES.browser.capabilities.mkb) ? {unsupported: !0,unsupportedNote: CE("a", {href: "https://github.com/redphx/better-xcloud/issues/206#issuecomment-1920475657",target: "_blank"}, "⚠️ " + t("browser-unsupported-feature"))} : {}}, {requiredVariants: "full",group: "touch-control",label: t("touch-controller"),items: [{pref: "touchController.mode",note: CE("a", { href: "https://github.com/redphx/better-xcloud/discussions/241", target: "_blank" }, t("unofficial-game-list"))},"touchController.autoOff","touchController.opacity.default","touchController.style.standard","touchController.style.custom"],...!STATES.userAgent.capabilities.touch ? {unsupported: !0,unsupportedNote: "⚠️ " + t("device-unsupported-touch")} : {}}, {group: "ui",label: t("ui"),items: ["ui.layout","ui.theme","ui.imageQuality","ui.gameCard.waitTime.show","ui.controllerStatus.show","ui.streamMenu.simplify","ui.splashVideo.skip",!AppInterface && "ui.hideScrollbar","ui.systemMenu.hideHandle","ui.feedbackDialog.disabled","ui.reduceAnimations",{pref: "ui.hideSections",multiLines: !0},{pref: "block.features",multiLines: !0}]}, {requiredVariants: "full",group: "game-bar",label: t("game-bar"),items: ["gameBar.position"]}, {group: "loading-screen",label: t("loading-screen"),items: ["loadingScreen.gameArt.show","loadingScreen.waitTime.show","loadingScreen.rocket"]}, {group: "other",label: t("other"),items: ["block.tracking"]}, {group: "advanced",label: t("advanced"),items: [{pref: "userAgent.profile",multiLines: !0,onCreated: (setting, $control) => {let defaultUserAgent = window.navigator.orgUserAgent || window.navigator.userAgent, $inpCustomUserAgent = CE("input", {type: "text",placeholder: defaultUserAgent,autocomplete: "off",class: "bx-settings-custom-user-agent",tabindex: 0});$inpCustomUserAgent.addEventListener("input", (e) => {let profile = $control.value, custom = e.target.value.trim();UserAgent.updateStorage(profile, custom), this.onGlobalSettingChanged(e);}), $control.insertAdjacentElement("afterend", $inpCustomUserAgent), setNearby($inpCustomUserAgent.parentElement, {orientation: "vertical"});}}]}, {group: "footer",items: [($parent) => {try {let appVersion = document.querySelector("meta[name=gamepass-app-version]").content, appDate = new Date(document.querySelector("meta[name=gamepass-app-date]").content).toISOString().substring(0, 10);$parent.appendChild(CE("div", {class: "bx-settings-app-version"}, `xCloud website version ${appVersion} (${appDate})`));} catch (e) {}},($parent) => {$parent.appendChild(CE("a", {class: "bx-donation-link",href: "https://ko-fi.com/redphx",target: "_blank",tabindex: 0}, `❤️ ${t("support-better-xcloud")}`));},($parent) => {$parent.appendChild(createButton({label: t("clear-data"),style: 4 | 16 | 128 | 64,onClick: (e) => {if (confirm(t("clear-data-confirm"))) clearAllData();}}));},($parent) => {$parent.appendChild(CE("div", { class: "bx-debug-info" }, createButton({label: "Debug info",style: 8 | 128 | 64,onClick: (e) => {let $button = e.target.closest("button");if (!$button) return;let $pre = $button.nextElementSibling;if (!$pre) {let debugInfo = deepClone(BX_FLAGS.DeviceInfo);debugInfo.settings = JSON.parse(window.localStorage.getItem("BetterXcloud") || "{}"), $pre = CE("pre", {class: "bx-focusable bx-gone",tabindex: 0,_on: {click: async (e2) => {await copyToClipboard(e2.target.innerText);}}}, "```\n" + JSON.stringify(debugInfo, null, " ") + "\n```"), $button.insertAdjacentElement("afterend", $pre);}$pre.classList.toggle("bx-gone"), $pre.scrollIntoView();}})));}]}];TAB_DISPLAY_ITEMS = [{requiredVariants: "full",group: "audio",label: t("audio"),helpUrl: "https://better-xcloud.github.io/ingame-features/#audio",items: [{pref: "audio.volume",params: {disabled: !getGlobalPref("audio.volume.booster.enabled")},onCreated: (setting, $elm) => {let $range = $elm.querySelector("input[type=range");BxEventBus.Stream.on("setting.changed", (payload) => {let { settingKey } = payload;if (settingKey === "audio.volume") $range.value = getStreamPref(settingKey).toString(), BxEvent.dispatch($range, "input", { ignoreOnChange: !0 });});}}]}, {group: "video",label: t("video"),helpUrl: "https://better-xcloud.github.io/ingame-features/#video",items: ["video.player.type","video.maxFps","video.player.powerPreference","video.processing","video.processing.mode","video.ratio","video.position","video.processing.sharpness","video.saturation","video.contrast","video.brightness"]}];TAB_CONTROLLER_ITEMS = [{group: "controller",label: t("controller"),helpUrl: "https://better-xcloud.github.io/ingame-features/#controller",items: ["localCoOp.enabled","controller.pollingRate",($parent) => {$parent.appendChild(ControllerExtraSettings.renderSettings.apply(this));}]},STATES.userAgent.capabilities.touch && {group: "touch-control",label: t("touch-controller"),items: [{label: t("layout"),content: CE("select", {disabled: !0}, CE("option", !1, t("default"))),onCreated: (setting, $elm) => {$elm.addEventListener("input", (e) => {TouchController.applyCustomLayout($elm.value, 1000);}), window.addEventListener(BxEvent.CUSTOM_TOUCH_LAYOUTS_LOADED, (e) => {let customLayouts = TouchController.getCustomLayouts();while ($elm.firstChild)$elm.removeChild($elm.firstChild);if ($elm.disabled = !customLayouts, !customLayouts) {$elm.appendChild(CE("option", { value: "" }, t("default"))), $elm.value = "", $elm.dispatchEvent(new Event("input"));return;}let $fragment = document.createDocumentFragment();for (let key in customLayouts.layouts) {let layout = customLayouts.layouts[key], name;if (layout.author) name = `${layout.name} (${layout.author})`;else name = layout.name;let $option = CE("option", { value: key }, name);$fragment.appendChild($option);}$elm.appendChild($fragment), $elm.value = customLayouts.default_layout;});}}]},STATES.browser.capabilities.deviceVibration && {group: "device",label: t("device"),items: [{pref: "deviceVibration.mode",multiLines: !0,unsupported: !STATES.browser.capabilities.deviceVibration}, {pref: "deviceVibration.intensity",unsupported: !STATES.browser.capabilities.deviceVibration}]}];TAB_MKB_ITEMS = [{requiredVariants: "full",group: "mkb",label: t("mouse-and-keyboard"),helpUrl: "https://better-xcloud.github.io/mouse-and-keyboard/",items: [($parent) => {$parent.appendChild(MkbExtraSettings.renderSettings.apply(this));}]},NativeMkbHandler.isAllowed() && {requiredVariants: "full",group: "native-mkb",label: t("native-mkb"),items: ["nativeMkb.scroll.sensitivityY","nativeMkb.scroll.sensitivityX"]}];TAB_STATS_ITEMS = [{group: "stats",label: t("stream-stats"),helpUrl: "https://better-xcloud.github.io/stream-stats/",items: ["stats.showWhenPlaying","stats.quickGlance.enabled","stats.items","stats.position","stats.textSize","stats.opacity.all","stats.opacity.background","stats.colors"]}];SETTINGS_UI = {global: {group: "global",icon: BxIcon.HOME,items: this.TAB_GLOBAL_ITEMS},stream: {group: "stream",icon: BxIcon.DISPLAY,items: this.TAB_DISPLAY_ITEMS},controller: {group: "controller",icon: BxIcon.CONTROLLER,items: this.TAB_CONTROLLER_ITEMS,requiredVariants: "full"},mkb: {group: "mkb",icon: BxIcon.NATIVE_MKB,items: this.TAB_MKB_ITEMS,requiredVariants: "full"},stats: {group: "stats",icon: BxIcon.STREAM_STATS,items: this.TAB_STATS_ITEMS}};constructor() {super();BxLogger.info(this.LOG_TAG, "constructor()"), this.boundOnContextMenu = this.onContextMenu.bind(this), this.settingsManager = SettingsManager.getInstance(), this.renderFullSettings = STATES.supportedRegion && STATES.isSignedIn, this.setupDialog(), this.onMountedCallbacks.push(() => {if (onChangeVideoPlayerType(), STATES.userAgent.capabilities.touch) BxEvent.dispatch(window, BxEvent.CUSTOM_TOUCH_LAYOUTS_LOADED);let $selectUserAgent = document.querySelector(`#bx_setting_${escapeCssSelector("userAgent.profile")}`);if ($selectUserAgent) $selectUserAgent.disabled = !0, BxEvent.dispatch($selectUserAgent, "input", {}), $selectUserAgent.disabled = !1;}), BxEventBus.Stream.on("gameSettings.switched", ({ id }) => {this.$tabContents.dataset.gameId = id.toString();});}getDialog() {return this;}getContent() {return this.$container;}onMounted() {super.onMounted();}isOverlayVisible() {return !STATES.isPlaying;}reloadPage() {this.$btnGlobalReload.disabled = !0, this.$btnGlobalReload.firstElementChild.textContent = t("settings-reloading"), this.hide(), FullscreenText.getInstance().show(t("settings-reloading")), window.location.reload();}isSupportedVariant(requiredVariants) {if (typeof requiredVariants === "undefined") return !0;return requiredVariants = typeof requiredVariants === "string" ? [requiredVariants] : requiredVariants, requiredVariants.includes(SCRIPT_VARIANT);}onTabClicked = (e) => {let $svg = e.target.closest("svg"), $child, children = Array.from(this.$tabContents.children);for ($child of children)if ($child.dataset.tabGroup === $svg.dataset.group) $child.classList.remove("bx-gone"), calculateSelectBoxes($child);else if ($child.dataset.tabGroup) $child.classList.add("bx-gone");this.$streamSettingsSelection.classList.toggle("bx-gone", $svg.dataset.group === "global");for (let $child2 of Array.from(this.$tabs.children))$child2.classList.remove("bx-active");$svg.classList.add("bx-active");};renderTab(settingTab) {let $svg = createSvgIcon(settingTab.icon);return $svg.dataset.group = settingTab.group, $svg.tabIndex = 0, $svg.addEventListener("click", this.onTabClicked), $svg;}onGlobalSettingChanged = (e) => {PatcherCache.getInstance().clear(), this.$btnReload.classList.add("bx-danger"), this.$noteGlobalReload.classList.add("bx-gone"), this.$btnGlobalReload.classList.remove("bx-gone"), this.$btnGlobalReload.classList.add("bx-danger");};onContextMenu(e) {e.preventDefault();let $elm = e.target;$elm instanceof HTMLElement && this.resetHighlightedSetting($elm);}renderServerSetting(setting) {let selectedValue = getGlobalPref("server.region"), continents = {"america-north": {label: t("continent-north-america")},"america-south": {label: t("continent-south-america")},asia: {label: t("continent-asia")},australia: {label: t("continent-australia")},europe: {label: t("continent-europe")},other: {label: t("other")}}, $control = CE("select", {id: `bx_setting_${escapeCssSelector(setting.pref)}`,tabindex: 0});$control.name = $control.id, $control.addEventListener("input", (e) => {setGlobalPref(setting.pref, e.target.value, "ui"), this.onGlobalSettingChanged(e);}), setting.options = {};for (let regionName in STATES.serverRegions) {let region = STATES.serverRegions[regionName], value = regionName, label = `${region.shortName} - ${regionName}`;if (region.isDefault) {if (label += ` (${t("default")})`, value = "default", selectedValue === regionName) selectedValue = "default";}setting.options[value] = label;let $option = CE("option", { value }, label), continent = continents[region.contintent];if (!continent.children) continent.children = [];continent.children.push($option);}let fragment = document.createDocumentFragment(), key;for (key in continents) {let continent = continents[key];if (!continent.children) continue;fragment.appendChild(CE("optgroup", {label: continent.label}, ...continent.children));}return $control.appendChild(fragment), $control.disabled = Object.keys(STATES.serverRegions).length === 0, $control.value = selectedValue, $control;}renderSettingRow(settingTab, $tabContent, settingTabContent, setting) {if (typeof setting === "string") setting = {pref: setting};let pref = setting.pref, $control;if (setting.content) if (typeof setting.content === "function") $control = setting.content.apply(this);else $control = setting.content;else if (!setting.unsupported) {if (pref === "server.region") $control = this.renderServerSetting(setting);else if (pref === "bx.locale") $control = SettingElement.fromPref(pref, async (e) => {let newLocale = e.target.value;if (getGlobalPref("ui.controllerFriendly")) {let timeoutId = e.target.timeoutId;timeoutId && window.clearTimeout(timeoutId), e.target.timeoutId = window.setTimeout(() => {Translations.refreshLocale(newLocale), Translations.updateTranslations();}, 500);} else Translations.refreshLocale(newLocale), Translations.updateTranslations();this.onGlobalSettingChanged(e);});else if (pref === "userAgent.profile") $control = SettingElement.fromPref("userAgent.profile", (e) => {let value = e.target.value, isCustom = value === "custom", userAgent2 = UserAgent.get(value);UserAgent.updateStorage(value);let $inp = $control.nextElementSibling;$inp.value = userAgent2, $inp.readOnly = !isCustom, $inp.disabled = !isCustom, !e.target.disabled && this.onGlobalSettingChanged(e);});else if ($control = this.settingsManager.getElement(pref, setting.params), settingTab.group === "global") $control.addEventListener("input", this.onGlobalSettingChanged);if ($control instanceof HTMLSelectElement) $control = BxSelectElement.create($control);}let prefDefinition = null;if (pref) prefDefinition = getPrefInfo(pref).definition;if (prefDefinition && !this.isSupportedVariant(prefDefinition.requiredVariants)) return;let label = prefDefinition?.label || setting.label || "", note = prefDefinition?.note || setting.note, unsupportedNote = prefDefinition?.unsupportedNote || setting.unsupportedNote, experimental = prefDefinition?.experimental || setting.experimental;if (typeof note === "function") note = note();if (typeof unsupportedNote === "function") unsupportedNote = unsupportedNote();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);}if (experimental) if (label = "🧪 " + label, !note) note = t("experimental");else note = `${t("experimental")}: ${note}`;let $note;if (unsupportedNote) $note = CE("div", { class: "bx-settings-dialog-note" }, unsupportedNote);else if (note) $note = CE("div", { class: "bx-settings-dialog-note" }, note);let $row = createSettingRow(label, !prefDefinition?.unsupported && $control, {$note,multiLines: setting.multiLines,icon: prefDefinition?.labelIcon,onContextMenu: this.boundOnContextMenu,pref});if (pref) $row.htmlFor = `bx_setting_${escapeCssSelector(pref)}`;if ($row.dataset.type = settingTabContent.group, !STATES.supportedRegion && setting.pref === "server.bypassRestriction") $row.classList.add("bx-settings-important-row");$tabContent.appendChild($row), !prefDefinition?.unsupported && setting.onCreated && setting.onCreated(setting, $control);}renderSettingsSection(settingTab, sections) {let $tabContent = CE("div", {class: "bx-gone",_dataset: {tabGroup: settingTab.group}});for (let section of sections) {if (!section) continue;if (section instanceof HTMLElement) {$tabContent.appendChild(section);continue;}if (!this.isSupportedVariant(section.requiredVariants)) continue;if (!this.renderFullSettings && settingTab.group === "global" && section.group !== "general" && section.group !== "footer") continue;let label = section.label;if (label === t("better-xcloud")) {if (label += " " + SCRIPT_VERSION, SCRIPT_VARIANT === "lite") label += " (Lite)";label = createButton({label,url: "https://github.com/redphx/better-xcloud/releases",style: 4096 | 16 | 64});}if (label) {let $title = CE("h2", {_nearby: {orientation: "horizontal"}}, CE("span", !1, label), section.helpUrl && createButton({icon: BxIcon.QUESTION,style: 8 | 64,url: section.helpUrl,title: t("help")}));$tabContent.appendChild($title);}if (section.unsupportedNote) {let $note = CE("b", { class: "bx-note-unsupported" }, section.unsupportedNote);$tabContent.appendChild($note);}if (section.unsupported) continue;if (section.content) {$tabContent.appendChild(section.content);continue;}section.items = section.items || [];for (let setting of section.items) {if (setting === !1) continue;if (typeof setting === "function") {setting.apply(this, [$tabContent]);continue;}this.renderSettingRow(settingTab, $tabContent, section, setting);}}return $tabContent;}setupDialog() {let $tabs, $tabContents, $container = CE("div", {class: "bx-settings-dialog",_nearby: {orientation: "horizontal"}}, CE("div", {class: "bx-settings-tabs-container",_nearby: {orientation: "vertical",focus: () => {return this.dialogManager.focus($tabs);},loop: (direction) => {if (direction === 1 || direction === 3) return this.focusVisibleTab(direction === 1 ? "last" : "first"), !0;return !1;}}}, $tabs = CE("div", {class: "bx-settings-tabs bx-hide-scroll-bar",_nearby: {focus: () => this.focusActiveTab()}}), CE("div", !1, this.$btnReload = createButton({icon: BxIcon.REFRESH,style: 64 | 32,onClick: (e) => {this.reloadPage();}}), createButton({icon: BxIcon.CLOSE,style: 64 | 32,onClick: (e) => {this.dialogManager.hide();}}))), CE("div", {class: "bx-settings-tab-contents",_nearby: {orientation: "vertical",loop: (direction) => {if (direction === 1 || direction === 3) return this.focusVisibleSetting(direction === 1 ? "last" : "first"), !0;return !1;}}}, this.$streamSettingsSelection = SettingsManager.getInstance().getStreamSettingsSelection(), $tabContents = CE("div", {class: "bx-settings-tab-content",_nearby: {orientation: "vertical",focus: () => this.jumpToSettingGroup("next")}})));this.$container = $container, this.$tabs = $tabs, this.$tabContents = $tabContents, $container.addEventListener("click", (e) => {if (e.target === $container) e.preventDefault(), e.stopPropagation(), this.hide();});let settingTabGroup;for (settingTabGroup in this.SETTINGS_UI) {let settingTab = this.SETTINGS_UI[settingTabGroup];if (!settingTab) continue;if (!this.isSupportedVariant(settingTab.requiredVariants)) continue;if (settingTab.group !== "global" && !this.renderFullSettings) continue;let $svg = this.renderTab(settingTab);$tabs.appendChild($svg);let $tabContent = this.renderSettingsSection.call(this, settingTab, settingTab.items);$tabContents.appendChild($tabContent);}$tabs.firstElementChild.dispatchEvent(new Event("click"));}focusTab(tabId) {let $tab = this.$container.querySelector(`.bx-settings-tabs svg[data-group=${tabId}]`);$tab && $tab.dispatchEvent(new Event("click"));}focusIfNeeded() {this.jumpToSettingGroup("next");}focusActiveTab() {let $currentTab = this.$tabs.querySelector(".bx-active");return $currentTab && $currentTab.focus(), !0;}focusVisibleSetting(type = "first") {let controls = Array.from(this.$tabContents.querySelectorAll("div[data-tab-group]:not(.bx-gone) > *"));if (!controls.length) return !1;if (type === "last") controls.reverse();for (let $control of controls) {if (!($control instanceof HTMLElement)) continue;let $focusable = this.dialogManager.findFocusableElement($control);if ($focusable) {if (this.dialogManager.focus($focusable)) return !0;}}return !1;}focusVisibleTab(type = "first") {let tabs = Array.from(this.$tabs.querySelectorAll("svg:not(.bx-gone)"));if (!tabs.length) return !1;if (type === "last") tabs.reverse();for (let $tab of tabs)if (this.dialogManager.focus($tab)) return !0;return !1;}jumpToSettingGroup(direction) {let $tabContent = this.$tabContents.querySelector("div[data-tab-group]:not(.bx-gone)");if (!$tabContent) return !1;let $header, $focusing = document.activeElement;if (!$focusing || !$tabContent.contains($focusing)) $header = $tabContent.querySelector("h2");else {let $parent = $focusing.closest("[data-tab-group] > *"), siblingProperty = direction === "next" ? "nextSibling" : "previousSibling", $tmp = $parent, times = 0;while (!0) {if (!$tmp) break;if ($tmp.tagName === "H2") {if ($header = $tmp, !$tmp.nextElementSibling?.classList.contains("bx-note-unsupported")) {if (++times, direction === "next" || times >= 2) break;}}$tmp = $tmp[siblingProperty];}}let $target;if ($header) $target = this.dialogManager.findNextTarget($header, 3, !1);if ($target) return this.dialogManager.focus($target);return !1;}resetHighlightedSetting($elm) {let targetGameId = SettingsManager.getInstance().getTargetGameId();if (targetGameId < 0) return;if (!$elm) $elm = document.activeElement instanceof HTMLElement ? document.activeElement : void 0;let $row = $elm?.closest("div[data-tab-group] > .bx-settings-row");if (!$row) return;let pref = $row.prefKey;if (!pref) alert("Pref not found: " + $row.id);if (!isStreamPref(pref)) return;let deleted = STORAGE.Stream.deleteSettingByGame(targetGameId, pref);if (deleted) BxEventBus.Stream.emit("setting.changed", {storageKey: `${"BetterXcloud.Stream"}.${targetGameId}`,settingKey: pref});return deleted;}handleKeyPress(key) {let handled = !0;switch (key) {case "Tab":this.focusActiveTab();break;case "Home":this.focusVisibleSetting("first");break;case "End":this.focusVisibleSetting("last");break;case "PageUp":this.jumpToSettingGroup("previous");break;case "PageDown":this.jumpToSettingGroup("next");break;case "KeyQ":this.resetHighlightedSetting();break;default:handled = !1;break;}return handled;}handleGamepad(button) {let handled = !0;switch (button) {case 1:let $focusing = document.activeElement;if ($focusing && this.$tabs.contains($focusing)) this.hide();else this.focusActiveTab();break;case 4:case 5:this.focusActiveTab();break;case 6:this.jumpToSettingGroup("previous");break;case 7:this.jumpToSettingGroup("next");break;case 2:this.resetHighlightedSetting();break;default:handled = !1;break;}return handled;}} class ScreenshotManager {static instance;static getInstance = () => ScreenshotManager.instance ?? (ScreenshotManager.instance = new ScreenshotManager);LOG_TAG = "ScreenshotManager";$download;$canvas;canvasContext;constructor() {BxLogger.info(this.LOG_TAG, "constructor()"), this.$download = CE("a"), this.$canvas = CE("canvas", { class: "bx-gone" }), this.canvasContext = this.$canvas.getContext("2d", {alpha: !1,willReadFrequently: !1});}updateCanvasSize(width, height) {this.$canvas.width = width, this.$canvas.height = height;}updateCanvasFilters(filters) {this.canvasContext.filter = filters;}onAnimationEnd(e) {e.target.classList.remove("bx-taking-screenshot");}takeScreenshot(callback) {let currentStream = STATES.currentStream, streamPlayerManager = currentStream.streamPlayerManager, $canvas = this.$canvas;if (!streamPlayerManager || !$canvas) return;let $player;if (getGlobalPref("screenshot.applyFilters")) $player = streamPlayerManager.getPlayerElement();else $player = streamPlayerManager.getPlayerElement("video");if (!$player || !$player.isConnected) return;let canvasContext = this.canvasContext;if ($player instanceof HTMLCanvasElement) streamPlayerManager.getCanvasPlayer()?.updateFrame();canvasContext.drawImage($player, 0, 0);let $gameStream = $player.closest("#game-stream");if ($gameStream) $gameStream.addEventListener("animationend", this.onAnimationEnd, { once: !0 }), $gameStream.classList.add("bx-taking-screenshot");if (AppInterface) {let data = $canvas.toDataURL("image/png").split(";base64,")[1];AppInterface.saveScreenshot(currentStream.titleSlug, data), canvasContext.clearRect(0, 0, $canvas.width, $canvas.height), callback && callback();return;}$canvas.toBlob((blob) => {if (!blob) return;let now = +new Date, $download = this.$download;$download.download = `${currentStream.titleSlug}-${now}.png`, $download.href = URL.createObjectURL(blob), $download.click(), URL.revokeObjectURL($download.href), $download.href = "", $download.download = "", canvasContext.clearRect(0, 0, $canvas.width, $canvas.height), callback && callback();}, "image/png");}} class RendererShortcut {static toggleVisibility() {let $mediaContainer = document.querySelector('#game-stream div[data-testid="media-container"]');if (!$mediaContainer) {BxEventBus.Stream.emit("video.visibility.changed", { isVisible: !0 });return;}$mediaContainer.classList.toggle("bx-gone");let isVisible = !$mediaContainer.classList.contains("bx-gone");limitVideoPlayerFps(isVisible ? getStreamPref("video.maxFps") : 0), BxEventBus.Stream.emit("video.visibility.changed", { isVisible });}} class TrueAchievements {static instance;static getInstance = () => TrueAchievements.instance ?? (TrueAchievements.instance = new TrueAchievements);LOG_TAG = "TrueAchievements";$link;$button;$hiddenLink;constructor() {BxLogger.info(this.LOG_TAG, "constructor()"), this.$link = createButton({label: t("true-achievements"),url: "#",icon: BxIcon.TRUE_ACHIEVEMENTS,style: 64 | 8 | 128 | 8192,onClick: this.onClick}), this.$button = createButton({label: t("true-achievements"),title: t("true-achievements"),icon: BxIcon.TRUE_ACHIEVEMENTS,style: 64,onClick: this.onClick}), this.$hiddenLink = CE("a", {target: "_blank"});}onClick = (e) => {e.preventDefault(), window.BX_EXPOSED.dialogRoutes?.closeAll();let dataset = this.$link.dataset;this.open(!0, dataset.xboxTitleId, dataset.id);};updateIds(xboxTitleId, id) {let $link = this.$link, $button = this.$button;if (clearDataSet($link), clearDataSet($button), xboxTitleId) $link.dataset.xboxTitleId = xboxTitleId, $button.dataset.xboxTitleId = xboxTitleId;if (id) $link.dataset.id = id, $button.dataset.id = id;}injectAchievementsProgress($elm) {if (SCRIPT_VARIANT !== "full") return;let $parent = $elm.parentElement, $div = CE("div", {class: "bx-guide-home-achievements-progress"}, $elm), xboxTitleId;try {let $container = $parent.closest("div[class*=AchievementsPreview-module__container]");if ($container) xboxTitleId = getReactProps($container).children.props.data.data.xboxTitleId;} catch (e) {}if (!xboxTitleId) xboxTitleId = this.getStreamXboxTitleId();if (typeof xboxTitleId !== "undefined") xboxTitleId = xboxTitleId.toString();if (this.updateIds(xboxTitleId), document.body.dataset.mediaType === "tv") $div.appendChild(this.$link);else $div.appendChild(this.$button);$parent.appendChild($div);}injectAchievementDetailPage($parent) {if (SCRIPT_VARIANT !== "full") return;let props = getReactProps($parent);if (!props) return;try {let achievementList = props.children.props.data.data, $header = $parent.querySelector("div[class*=AchievementDetailHeader]"), achievementName = getReactProps($header).children[0].props.achievementName, id, xboxTitleId;for (let achiev of achievementList)if (achiev.name === achievementName) {id = achiev.id, xboxTitleId = achiev.title.id;break;}if (id) this.updateIds(xboxTitleId, id), $parent.appendChild(this.$link);} catch (e) {}}getStreamXboxTitleId() {return STATES.currentStream.xboxTitleId || STATES.currentStream.titleInfo?.details.xboxTitleId;}open(override, xboxTitleId, id) {if (!xboxTitleId || xboxTitleId === "undefined") xboxTitleId = this.getStreamXboxTitleId();if (AppInterface?.openTrueAchievementsLink) {AppInterface.openTrueAchievementsLink(override, xboxTitleId?.toString(), id?.toString());return;}let url = "https://www.trueachievements.com";if (xboxTitleId) {if (url += `/deeplink/${xboxTitleId}`, id) url += `/${id}`;}this.$hiddenLink.href = url, this.$hiddenLink.click();}} diff --git a/src/modules/ui/dialog/settings-dialog.ts b/src/modules/ui/dialog/settings-dialog.ts index 0d12c42..593d10c 100755 --- a/src/modules/ui/dialog/settings-dialog.ts +++ b/src/modules/ui/dialog/settings-dialog.ts @@ -373,7 +373,7 @@ export class SettingsDialog extends NavigationDialog { $parent => { $parent.appendChild(createButton({ label: t('clear-data'), - style: ButtonStyle.GHOST | ButtonStyle.FULL_WIDTH | ButtonStyle.FOCUSABLE, + style: ButtonStyle.DANGER | ButtonStyle.FROSTED | ButtonStyle.FULL_WIDTH | ButtonStyle.FOCUSABLE, onClick: e => { if (confirm(t('clear-data-confirm'))) { clearAllData(); diff --git a/src/utils/utils.ts b/src/utils/utils.ts index 6a354a1..184f679 100755 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -136,7 +136,8 @@ export function parseDetailsPath(path: string) { export function clearAllData() { // Delete localStorage items - for (let i = 0; i < localStorage.length; i++) { + + for (let i = localStorage.length - 1; i >= 0; i--) { const key = localStorage.key(i); if (!key) { continue;