mirror of
https://github.com/redphx/better-xcloud.git
synced 2025-06-06 15:47:18 +02:00
Stop using MutationObserver in root-dialog
This commit is contained in:
parent
585ee82776
commit
2f8c776133
134
dist/better-xcloud.pretty.user.js
vendored
134
dist/better-xcloud.pretty.user.js
vendored
@ -5707,10 +5707,25 @@ ${subsVar} = subs;
|
|||||||
if (index > -1 && (index = PatcherUtils.lastIndexOf(str, "return", index, 200)), index < 0) return !1;
|
if (index > -1 && (index = PatcherUtils.lastIndexOf(str, "return", index, 200)), index < 0) return !1;
|
||||||
return PatcherUtils.injectUseEffect(str, index, "Stream", "ui.streamMenu.rendered");
|
return PatcherUtils.injectUseEffect(str, index, "Stream", "ui.streamMenu.rendered");
|
||||||
},
|
},
|
||||||
|
injectGuideHomeUseEffect(str) {
|
||||||
|
let index = str.indexOf('"HomeLandingPage-module__authenticatedContentContainer');
|
||||||
|
if (index > -1 && (index = PatcherUtils.lastIndexOf(str, "return", index, 200)), index < 0) return !1;
|
||||||
|
return PatcherUtils.injectUseEffect(str, index, "Script", "ui.guideHome.rendered");
|
||||||
|
},
|
||||||
injectCreatePortal(str) {
|
injectCreatePortal(str) {
|
||||||
let index = str.indexOf(".createPortal=function");
|
let index = str.indexOf(".createPortal=function");
|
||||||
if (index > -1 && (index = PatcherUtils.indexOf(str, "{", index, 50, !0)), index < 0) return !1;
|
if (index > -1 && (index = PatcherUtils.indexOf(str, "{", index, 50, !0)), index < 0) return !1;
|
||||||
return str = PatcherUtils.insertAt(str, index, create_portal_default), str;
|
return str = PatcherUtils.insertAt(str, index, create_portal_default), str;
|
||||||
|
},
|
||||||
|
injectAchievementsProgressUseEffect(str) {
|
||||||
|
let index = str.indexOf('"AchievementsButton-module__progressBarContainer');
|
||||||
|
if (index > -1 && (index = PatcherUtils.lastIndexOf(str, "return", index, 200)), index < 0) return !1;
|
||||||
|
return PatcherUtils.injectUseEffect(str, index, "Script", "ui.guideAchievementProgress.rendered");
|
||||||
|
},
|
||||||
|
injectAchievementsDetailUseEffect(str) {
|
||||||
|
let index = str.indexOf("GuideAchievementDetail.useParams()");
|
||||||
|
if (index > -1 && (index = PatcherUtils.lastIndexOf(str, "const", index, 200)), index < 0) return !1;
|
||||||
|
return PatcherUtils.injectUseEffect(str, index, "Script", "ui.guideAchievementDetail.rendered");
|
||||||
}
|
}
|
||||||
}, PATCH_ORDERS = PatcherUtils.filterPatches([
|
}, PATCH_ORDERS = PatcherUtils.filterPatches([
|
||||||
...AppInterface && getGlobalPref("nativeMkb.mode") === "on" ? [
|
...AppInterface && getGlobalPref("nativeMkb.mode") === "on" ? [
|
||||||
@ -5719,8 +5734,11 @@ ${subsVar} = subs;
|
|||||||
] : [],
|
] : [],
|
||||||
"exposeReactCreateComponent",
|
"exposeReactCreateComponent",
|
||||||
"injectCreatePortal",
|
"injectCreatePortal",
|
||||||
|
"injectGuideHomeUseEffect",
|
||||||
"injectHeaderUseEffect",
|
"injectHeaderUseEffect",
|
||||||
"injectErrorPageUseEffect",
|
"injectErrorPageUseEffect",
|
||||||
|
"injectAchievementsProgressUseEffect",
|
||||||
|
"injectAchievementsDetailUseEffect",
|
||||||
"gameCardCustomIcons",
|
"gameCardCustomIcons",
|
||||||
...getGlobalPref("ui.imageQuality") < 90 ? [
|
...getGlobalPref("ui.imageQuality") < 90 ? [
|
||||||
"setImageQuality"
|
"setImageQuality"
|
||||||
@ -8830,10 +8848,8 @@ class GuideMenu {
|
|||||||
return this.$renderedButtons = $div, $div;
|
return this.$renderedButtons = $div, $div;
|
||||||
}
|
}
|
||||||
injectHome($root, isPlaying = !1) {
|
injectHome($root, isPlaying = !1) {
|
||||||
{
|
let $buttons = this.renderButtons();
|
||||||
let $achievementsProgress = $root.querySelector("button[class*=AchievementsButton-module__progressBarContainer]");
|
if ($root.contains($buttons)) return;
|
||||||
if ($achievementsProgress) TrueAchievements.getInstance().injectAchievementsProgress($achievementsProgress);
|
|
||||||
}
|
|
||||||
let $target = null;
|
let $target = null;
|
||||||
if (isPlaying) {
|
if (isPlaying) {
|
||||||
$target = $root.querySelector("a[class*=QuitGameButton]");
|
$target = $root.querySelector("a[class*=QuitGameButton]");
|
||||||
@ -8844,42 +8860,8 @@ class GuideMenu {
|
|||||||
if ($dividers) $target = $dividers[$dividers.length - 1];
|
if ($dividers) $target = $dividers[$dividers.length - 1];
|
||||||
}
|
}
|
||||||
if (!$target) return !1;
|
if (!$target) return !1;
|
||||||
let $buttons = this.renderButtons();
|
|
||||||
$buttons.dataset.isPlaying = isPlaying.toString(), $target.insertAdjacentElement("afterend", $buttons);
|
$buttons.dataset.isPlaying = isPlaying.toString(), $target.insertAdjacentElement("afterend", $buttons);
|
||||||
}
|
}
|
||||||
onShown = async (e) => {
|
|
||||||
if (e.where === "home") {
|
|
||||||
let $root = document.querySelector("#gamepass-dialog-root div[role=dialog] div[role=tabpanel] div[class*=HomeLandingPage]");
|
|
||||||
$root && this.injectHome($root, STATES.isPlaying);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
addEventListeners() {
|
|
||||||
window.addEventListener(BxEvent.XCLOUD_GUIDE_MENU_SHOWN, this.onShown);
|
|
||||||
}
|
|
||||||
observe($addedElm) {
|
|
||||||
let className = $addedElm.className;
|
|
||||||
if (!className) className = $addedElm.firstElementChild?.className ?? "";
|
|
||||||
if (!className || className.startsWith("bx-")) return;
|
|
||||||
if (className.includes("AchievementsButton-module__progressBarContainer")) {
|
|
||||||
TrueAchievements.getInstance().injectAchievementsProgress($addedElm);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!className.startsWith("NavigationAnimation") && !className.startsWith("DialogRoutes") && !className.startsWith("Dialog-module__container")) return;
|
|
||||||
{
|
|
||||||
let $achievDetailPage = $addedElm.querySelector("div[class*=AchievementDetailPage]");
|
|
||||||
if ($achievDetailPage) {
|
|
||||||
TrueAchievements.getInstance().injectAchievementDetailPage($achievDetailPage);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let $selectedTab = $addedElm.querySelector("div[class^=NavigationMenu] button[aria-selected=true");
|
|
||||||
if ($selectedTab) {
|
|
||||||
let $elm = $selectedTab, index;
|
|
||||||
for (index = 0;$elm = $elm?.previousElementSibling; index++)
|
|
||||||
;
|
|
||||||
if (index === 0) BxEvent.dispatch(window, BxEvent.XCLOUD_GUIDE_MENU_SHOWN, { where: "home" });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
class StreamBadges {
|
class StreamBadges {
|
||||||
static instance;
|
static instance;
|
||||||
@ -10334,68 +10316,6 @@ class StreamUiHandler {
|
|||||||
StreamUiHandler.$btnStreamSettings = void 0, StreamUiHandler.$btnStreamStats = void 0, StreamUiHandler.$btnRefresh = void 0, StreamUiHandler.$btnHome = void 0;
|
StreamUiHandler.$btnStreamSettings = void 0, StreamUiHandler.$btnStreamStats = void 0, StreamUiHandler.$btnRefresh = void 0, StreamUiHandler.$btnHome = void 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
class RootDialogObserver {
|
|
||||||
static $btnShortcut = AppInterface && createButton({
|
|
||||||
icon: BxIcon.CREATE_SHORTCUT,
|
|
||||||
label: t("create-shortcut"),
|
|
||||||
style: 64 | 8 | 128 | 4096 | 8192,
|
|
||||||
onClick: (e) => {
|
|
||||||
window.BX_EXPOSED.dialogRoutes?.closeAll();
|
|
||||||
let $btn = e.target.closest("button");
|
|
||||||
AppInterface.createShortcut($btn?.dataset.path);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
static $btnWallpaper = AppInterface && createButton({
|
|
||||||
icon: BxIcon.DOWNLOAD,
|
|
||||||
label: t("wallpaper"),
|
|
||||||
style: 64 | 8 | 128 | 4096 | 8192,
|
|
||||||
onClick: (e) => {
|
|
||||||
window.BX_EXPOSED.dialogRoutes?.closeAll();
|
|
||||||
let $btn = e.target.closest("button"), details = parseDetailsPath($btn.dataset.path);
|
|
||||||
details && AppInterface.downloadWallpapers(details.titleSlug, details.productId);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
static handleGameCardMenu($root) {
|
|
||||||
let $detail = $root.querySelector('a[href^="/play/"]');
|
|
||||||
if (!$detail) return;
|
|
||||||
let path = $detail.getAttribute("href");
|
|
||||||
RootDialogObserver.$btnShortcut.dataset.path = path, RootDialogObserver.$btnWallpaper.dataset.path = path, $root.append(RootDialogObserver.$btnShortcut, RootDialogObserver.$btnWallpaper);
|
|
||||||
}
|
|
||||||
static handleAddedElement($root, $addedElm) {
|
|
||||||
if (AppInterface && $addedElm.className.startsWith("SlideSheet-module__container")) {
|
|
||||||
let $gameCardMenu = $addedElm.querySelector("div[class^=MruContextMenu],div[class^=GameCardContextMenu]");
|
|
||||||
if ($gameCardMenu) return RootDialogObserver.handleGameCardMenu($gameCardMenu), !0;
|
|
||||||
} else if ($root.querySelector("div[class*=GuideDialog]")) return GuideMenu.getInstance().observe($addedElm), !0;
|
|
||||||
return !1;
|
|
||||||
}
|
|
||||||
static observe($root) {
|
|
||||||
let beingShown = !1;
|
|
||||||
new MutationObserver((mutationList) => {
|
|
||||||
for (let mutation of mutationList) {
|
|
||||||
if (mutation.type !== "childList") continue;
|
|
||||||
if (BX_FLAGS.Debug && BxLogger.warning("RootDialog", "added", mutation.addedNodes), mutation.addedNodes.length === 1) {
|
|
||||||
let $addedElm = mutation.addedNodes[0];
|
|
||||||
if ($addedElm instanceof HTMLElement) RootDialogObserver.handleAddedElement($root, $addedElm);
|
|
||||||
}
|
|
||||||
let shown = !!($root.firstElementChild && $root.firstElementChild.childElementCount > 0);
|
|
||||||
if (shown !== beingShown) beingShown = shown, BxEventBus.Script.emit(shown ? "dialog.shown" : "dialog.dismissed", {});
|
|
||||||
}
|
|
||||||
}).observe($root, { subtree: !0, childList: !0 });
|
|
||||||
}
|
|
||||||
static waitForRootDialog() {
|
|
||||||
let observer = new MutationObserver((mutationList) => {
|
|
||||||
for (let mutation of mutationList) {
|
|
||||||
if (mutation.type !== "childList") continue;
|
|
||||||
let $target = mutation.target;
|
|
||||||
if ($target.id && $target.id === "gamepass-dialog-root") {
|
|
||||||
observer.disconnect(), RootDialogObserver.observe($target);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
observer.observe(document.documentElement, { subtree: !0, childList: !0 });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
SettingsManager.getInstance();
|
SettingsManager.getInstance();
|
||||||
if (window.location.pathname.includes("/auth/msa")) {
|
if (window.location.pathname.includes("/auth/msa")) {
|
||||||
let nativePushState = window.history.pushState;
|
let nativePushState = window.history.pushState;
|
||||||
@ -10472,6 +10392,18 @@ BxEventBus.Stream.on("state.playing", (payload) => {
|
|||||||
BxEventBus.Script.on("ui.error.rendered", () => {
|
BxEventBus.Script.on("ui.error.rendered", () => {
|
||||||
BxEventBus.Stream.emit("state.stopped", {});
|
BxEventBus.Stream.emit("state.stopped", {});
|
||||||
});
|
});
|
||||||
|
BxEventBus.Script.on("ui.guideHome.rendered", () => {
|
||||||
|
let $root = document.querySelector("#gamepass-dialog-root div[role=dialog] div[role=tabpanel] div[class*=HomeLandingPage]");
|
||||||
|
$root && GuideMenu.getInstance().injectHome($root, STATES.isPlaying);
|
||||||
|
});
|
||||||
|
BxEventBus.Script.on("ui.guideAchievementProgress.rendered", () => {
|
||||||
|
let $elm = document.querySelector("#gamepass-dialog-root button[class*=AchievementsButton-module__progressBarContainer]");
|
||||||
|
if ($elm) TrueAchievements.getInstance().injectAchievementsProgress($elm);
|
||||||
|
});
|
||||||
|
BxEventBus.Script.on("ui.guideAchievementDetail.rendered", () => {
|
||||||
|
let $elm = document.querySelector("#gamepass-dialog-root div[class^=AchievementDetailPage-module]");
|
||||||
|
if ($elm) TrueAchievements.getInstance().injectAchievementDetailPage($elm);
|
||||||
|
});
|
||||||
BxEventBus.Stream.on("ui.streamMenu.rendered", async () => {
|
BxEventBus.Stream.on("ui.streamMenu.rendered", async () => {
|
||||||
await StreamUiHandler.handleStreamMenu();
|
await StreamUiHandler.handleStreamMenu();
|
||||||
});
|
});
|
||||||
@ -10516,7 +10448,7 @@ function main() {
|
|||||||
BX_FLAGS.ForceNativeMkbTitles.push(...customList);
|
BX_FLAGS.ForceNativeMkbTitles.push(...customList);
|
||||||
}
|
}
|
||||||
if (StreamSettings.setup(), patchRtcPeerConnection(), patchRtcCodecs(), interceptHttpRequests(), patchVideoApi(), patchCanvasContext(), AppInterface && patchPointerLockApi(), getGlobalPref("audio.volume.booster.enabled") && patchAudioContext(), getGlobalPref("block.tracking")) patchMeControl(), disableAdobeAudienceManager();
|
if (StreamSettings.setup(), patchRtcPeerConnection(), patchRtcCodecs(), interceptHttpRequests(), patchVideoApi(), patchCanvasContext(), AppInterface && patchPointerLockApi(), getGlobalPref("audio.volume.booster.enabled") && patchAudioContext(), getGlobalPref("block.tracking")) patchMeControl(), disableAdobeAudienceManager();
|
||||||
if (RootDialogObserver.waitForRootDialog(), addCss(), GuideMenu.getInstance().addEventListeners(), StreamStatsCollector.setupEvents(), StreamBadges.setupEvents(), StreamStats.setupEvents(), WebGPUPlayer.prepare(), STATES.userAgent.capabilities.touch && TouchController.updateCustomList(), DeviceVibrationManager.getInstance(), BX_FLAGS.CheckForUpdate && checkForUpdate(), Patcher.init(), disablePwa(), getGlobalPref("xhome.enabled")) RemotePlayManager.detect();
|
if (addCss(), StreamStatsCollector.setupEvents(), StreamBadges.setupEvents(), StreamStats.setupEvents(), WebGPUPlayer.prepare(), STATES.userAgent.capabilities.touch && TouchController.updateCustomList(), DeviceVibrationManager.getInstance(), BX_FLAGS.CheckForUpdate && checkForUpdate(), Patcher.init(), disablePwa(), getGlobalPref("xhome.enabled")) RemotePlayManager.detect();
|
||||||
if (getGlobalPref("touchController.mode") === "all") TouchController.setup();
|
if (getGlobalPref("touchController.mode") === "all") TouchController.setup();
|
||||||
if (AppInterface && (getGlobalPref("mkb.enabled") || getGlobalPref("nativeMkb.mode") === "on")) STATES.pointerServerPort = AppInterface.startPointerServer() || 9269, BxLogger.info("startPointerServer", "Port", STATES.pointerServerPort.toString());
|
if (AppInterface && (getGlobalPref("mkb.enabled") || getGlobalPref("nativeMkb.mode") === "on")) STATES.pointerServerPort = AppInterface.startPointerServer() || 9269, BxLogger.info("startPointerServer", "Port", STATES.pointerServerPort.toString());
|
||||||
if (getGlobalPref("ui.gameCard.waitTime.show") && GameTile.setup(), EmulatedMkbHandler.setupEvents(), getGlobalPref("ui.controllerStatus.show")) window.addEventListener("gamepadconnected", (e) => showGamepadToast(e.gamepad)), window.addEventListener("gamepaddisconnected", (e) => showGamepadToast(e.gamepad));
|
if (getGlobalPref("ui.gameCard.waitTime.show") && GameTile.setup(), EmulatedMkbHandler.setupEvents(), getGlobalPref("ui.controllerStatus.show")) window.addEventListener("gamepadconnected", (e) => showGamepadToast(e.gamepad)), window.addEventListener("gamepaddisconnected", (e) => showGamepadToast(e.gamepad));
|
||||||
|
10
dist/better-xcloud.user.js
vendored
10
dist/better-xcloud.user.js
vendored
File diff suppressed because one or more lines are too long
26
src/index.ts
26
src/index.ts
@ -25,6 +25,7 @@ import { BxLogger } from "@utils/bx-logger";
|
|||||||
import { GameBar } from "./modules/game-bar/game-bar";
|
import { GameBar } from "./modules/game-bar/game-bar";
|
||||||
import { ScreenshotManager } from "./utils/screenshot-manager";
|
import { ScreenshotManager } from "./utils/screenshot-manager";
|
||||||
import { NativeMkbHandler } from "./modules/mkb/native-mkb-handler";
|
import { NativeMkbHandler } from "./modules/mkb/native-mkb-handler";
|
||||||
|
import { GuideMenu } from "./modules/ui/guide-menu";
|
||||||
import { updateVideoPlayer } from "./modules/stream/stream-settings-utils";
|
import { updateVideoPlayer } from "./modules/stream/stream-settings-utils";
|
||||||
import { BlockFeature, NativeMkbMode, TouchControllerMode, UiSection } from "./enums/pref-values";
|
import { BlockFeature, NativeMkbMode, TouchControllerMode, UiSection } from "./enums/pref-values";
|
||||||
import { HeaderSection } from "./modules/ui/header";
|
import { HeaderSection } from "./modules/ui/header";
|
||||||
@ -45,8 +46,7 @@ import { SettingsManager } from "./modules/settings-manager";
|
|||||||
import { Toast } from "./utils/toast";
|
import { Toast } from "./utils/toast";
|
||||||
import { WebGPUPlayer } from "./modules/player/webgpu/webgpu-player";
|
import { WebGPUPlayer } from "./modules/player/webgpu/webgpu-player";
|
||||||
import { StreamUiHandler } from "./modules/stream/stream-ui";
|
import { StreamUiHandler } from "./modules/stream/stream-ui";
|
||||||
import { RootDialogObserver } from "./utils/root-dialog-observer";
|
import { TrueAchievements } from "./utils/true-achievements";
|
||||||
import { GuideMenu } from "./modules/ui/guide-menu";
|
|
||||||
|
|
||||||
SettingsManager.getInstance();
|
SettingsManager.getInstance();
|
||||||
|
|
||||||
@ -264,6 +264,25 @@ BxEventBus.Script.on('ui.error.rendered', () => {
|
|||||||
BxEventBus.Stream.emit('state.stopped', {});
|
BxEventBus.Stream.emit('state.stopped', {});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
BxEventBus.Script.on('ui.guideHome.rendered', () => {
|
||||||
|
const $root = document.querySelector<HTMLElement>('#gamepass-dialog-root div[role=dialog] div[role=tabpanel] div[class*=HomeLandingPage]');
|
||||||
|
$root && GuideMenu.getInstance().injectHome($root, STATES.isPlaying);
|
||||||
|
});
|
||||||
|
|
||||||
|
BxEventBus.Script.on('ui.guideAchievementProgress.rendered', () => {
|
||||||
|
const $elm = document.querySelector('#gamepass-dialog-root button[class*=AchievementsButton-module__progressBarContainer]');
|
||||||
|
if ($elm) {
|
||||||
|
TrueAchievements.getInstance().injectAchievementsProgress($elm as HTMLElement);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
BxEventBus.Script.on('ui.guideAchievementDetail.rendered', () => {
|
||||||
|
const $elm = document.querySelector('#gamepass-dialog-root div[class^=AchievementDetailPage-module]');
|
||||||
|
if ($elm) {
|
||||||
|
TrueAchievements.getInstance().injectAchievementDetailPage($elm as HTMLElement);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
BxEventBus.Stream.on('ui.streamMenu.rendered', async () => {
|
BxEventBus.Stream.on('ui.streamMenu.rendered', async () => {
|
||||||
await StreamUiHandler.handleStreamMenu();
|
await StreamUiHandler.handleStreamMenu();
|
||||||
});
|
});
|
||||||
@ -403,12 +422,9 @@ function main() {
|
|||||||
disableAdobeAudienceManager();
|
disableAdobeAudienceManager();
|
||||||
}
|
}
|
||||||
|
|
||||||
RootDialogObserver.waitForRootDialog();
|
|
||||||
|
|
||||||
// Setup UI
|
// Setup UI
|
||||||
addCss();
|
addCss();
|
||||||
|
|
||||||
GuideMenu.getInstance().addEventListeners();
|
|
||||||
StreamStatsCollector.setupEvents();
|
StreamStatsCollector.setupEvents();
|
||||||
StreamBadges.setupEvents();
|
StreamBadges.setupEvents();
|
||||||
StreamStats.setupEvents();
|
StreamStats.setupEvents();
|
||||||
|
@ -1164,6 +1164,16 @@ ${subsVar} = subs;
|
|||||||
return PatcherUtils.injectUseEffect(str, index, 'Stream', 'ui.streamMenu.rendered');
|
return PatcherUtils.injectUseEffect(str, index, 'Stream', 'ui.streamMenu.rendered');
|
||||||
},
|
},
|
||||||
|
|
||||||
|
injectGuideHomeUseEffect(str: string) {
|
||||||
|
let index = str.indexOf('"HomeLandingPage-module__authenticatedContentContainer');
|
||||||
|
index > -1 && (index = PatcherUtils.lastIndexOf(str, 'return', index, 200));
|
||||||
|
if (index < 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return PatcherUtils.injectUseEffect(str, index, 'Script', 'ui.guideHome.rendered');
|
||||||
|
},
|
||||||
|
|
||||||
injectCreatePortal(str: string) {
|
injectCreatePortal(str: string) {
|
||||||
let index = str.indexOf('.createPortal=function');
|
let index = str.indexOf('.createPortal=function');
|
||||||
index > -1 && (index = PatcherUtils.indexOf(str, '{', index, 50, true));
|
index > -1 && (index = PatcherUtils.indexOf(str, '{', index, 50, true));
|
||||||
@ -1174,6 +1184,26 @@ ${subsVar} = subs;
|
|||||||
str = PatcherUtils.insertAt(str, index, codeCreatePortal);
|
str = PatcherUtils.insertAt(str, index, codeCreatePortal);
|
||||||
return str;
|
return str;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
injectAchievementsProgressUseEffect(str: string) {
|
||||||
|
let index = str.indexOf('"AchievementsButton-module__progressBarContainer');
|
||||||
|
index > -1 && (index = PatcherUtils.lastIndexOf(str, 'return', index, 200));
|
||||||
|
if (index < 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return PatcherUtils.injectUseEffect(str, index, 'Script', 'ui.guideAchievementProgress.rendered');
|
||||||
|
},
|
||||||
|
|
||||||
|
injectAchievementsDetailUseEffect(str: string) {
|
||||||
|
let index = str.indexOf('GuideAchievementDetail.useParams()');
|
||||||
|
index > -1 && (index = PatcherUtils.lastIndexOf(str, 'const', index, 200));
|
||||||
|
if (index < 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return PatcherUtils.injectUseEffect(str, index, 'Script', 'ui.guideAchievementDetail.rendered');
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
let PATCH_ORDERS = PatcherUtils.filterPatches([
|
let PATCH_ORDERS = PatcherUtils.filterPatches([
|
||||||
@ -1185,8 +1215,11 @@ let PATCH_ORDERS = PatcherUtils.filterPatches([
|
|||||||
'exposeReactCreateComponent',
|
'exposeReactCreateComponent',
|
||||||
|
|
||||||
'injectCreatePortal',
|
'injectCreatePortal',
|
||||||
|
'injectGuideHomeUseEffect',
|
||||||
'injectHeaderUseEffect',
|
'injectHeaderUseEffect',
|
||||||
'injectErrorPageUseEffect',
|
'injectErrorPageUseEffect',
|
||||||
|
'injectAchievementsProgressUseEffect',
|
||||||
|
'injectAchievementsDetailUseEffect',
|
||||||
|
|
||||||
'gameCardCustomIcons',
|
'gameCardCustomIcons',
|
||||||
// 'gameCardPassTitle',
|
// 'gameCardPassTitle',
|
||||||
|
@ -1,11 +1,7 @@
|
|||||||
import { isFullVersion } from "@macros/build" with { type: "macro" };
|
|
||||||
|
|
||||||
import { BxEvent } from "@/utils/bx-event";
|
|
||||||
import { AppInterface, STATES } from "@/utils/global";
|
import { AppInterface, STATES } from "@/utils/global";
|
||||||
import { createButton, ButtonStyle, CE } from "@/utils/html";
|
import { createButton, ButtonStyle, CE } from "@/utils/html";
|
||||||
import { t } from "@/utils/translation";
|
import { t } from "@/utils/translation";
|
||||||
import { SettingsDialog } from "./dialog/settings-dialog";
|
import { SettingsDialog } from "./dialog/settings-dialog";
|
||||||
import { TrueAchievements } from "@/utils/true-achievements";
|
|
||||||
import { BxIcon } from "@/utils/bx-icon";
|
import { BxIcon } from "@/utils/bx-icon";
|
||||||
import { BxEventBus } from "@/utils/bx-event-bus";
|
import { BxEventBus } from "@/utils/bx-event-bus";
|
||||||
import { getGlobalPref } from "@/utils/pref-utils";
|
import { getGlobalPref } from "@/utils/pref-utils";
|
||||||
@ -141,11 +137,9 @@ export class GuideMenu {
|
|||||||
}
|
}
|
||||||
|
|
||||||
injectHome($root: HTMLElement, isPlaying = false) {
|
injectHome($root: HTMLElement, isPlaying = false) {
|
||||||
if (isFullVersion()) {
|
const $buttons = this.renderButtons();
|
||||||
const $achievementsProgress = $root.querySelector('button[class*=AchievementsButton-module__progressBarContainer]');
|
if ($root.contains($buttons)) {
|
||||||
if ($achievementsProgress) {
|
return;
|
||||||
TrueAchievements.getInstance().injectAchievementsProgress($achievementsProgress as HTMLElement);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find the element to add buttons to
|
// Find the element to add buttons to
|
||||||
@ -169,67 +163,7 @@ export class GuideMenu {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const $buttons = this.renderButtons();
|
|
||||||
$buttons.dataset.isPlaying = isPlaying.toString();
|
$buttons.dataset.isPlaying = isPlaying.toString();
|
||||||
$target.insertAdjacentElement('afterend', $buttons);
|
$target.insertAdjacentElement('afterend', $buttons);
|
||||||
}
|
}
|
||||||
|
|
||||||
private onShown = async (e: Event) => {
|
|
||||||
const where = (e as any).where as GuideMenuTab;
|
|
||||||
|
|
||||||
if (where === GuideMenuTab.HOME) {
|
|
||||||
const $root = document.querySelector<HTMLElement>('#gamepass-dialog-root div[role=dialog] div[role=tabpanel] div[class*=HomeLandingPage]');
|
|
||||||
$root && this.injectHome($root, STATES.isPlaying);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
addEventListeners() {
|
|
||||||
window.addEventListener(BxEvent.XCLOUD_GUIDE_MENU_SHOWN, this.onShown);
|
|
||||||
}
|
|
||||||
|
|
||||||
observe($addedElm: HTMLElement) {
|
|
||||||
let className = $addedElm.className;
|
|
||||||
|
|
||||||
// Fix custom buttons disappearing in Guide Menu (#551)
|
|
||||||
if (!className) {
|
|
||||||
className = $addedElm.firstElementChild?.className ?? '';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!className || className.startsWith('bx-')) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TrueAchievements
|
|
||||||
if (isFullVersion() && className.includes('AchievementsButton-module__progressBarContainer')) {
|
|
||||||
TrueAchievements.getInstance().injectAchievementsProgress($addedElm);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!className.startsWith('NavigationAnimation') &&
|
|
||||||
!className.startsWith('DialogRoutes') &&
|
|
||||||
!className.startsWith('Dialog-module__container')) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Achievement Details page
|
|
||||||
if (isFullVersion()) {
|
|
||||||
const $achievDetailPage = $addedElm.querySelector('div[class*=AchievementDetailPage]');
|
|
||||||
if ($achievDetailPage) {
|
|
||||||
TrueAchievements.getInstance().injectAchievementDetailPage($achievDetailPage as HTMLElement);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find navigation bar
|
|
||||||
const $selectedTab = $addedElm.querySelector('div[class^=NavigationMenu] button[aria-selected=true');
|
|
||||||
if ($selectedTab) {
|
|
||||||
let $elm: Element | null = $selectedTab;
|
|
||||||
let index;
|
|
||||||
for (index = 0; ($elm = $elm?.previousElementSibling); index++);
|
|
||||||
|
|
||||||
if (index === 0) {
|
|
||||||
BxEvent.dispatch(window, BxEvent.XCLOUD_GUIDE_MENU_SHOWN, { where: GuideMenuTab.HOME });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -38,6 +38,10 @@ type ScriptEvents = {
|
|||||||
|
|
||||||
'ui.header.rendered': {},
|
'ui.header.rendered': {},
|
||||||
'ui.error.rendered': {},
|
'ui.error.rendered': {},
|
||||||
|
|
||||||
|
'ui.guideHome.rendered': {},
|
||||||
|
'ui.guideAchievementProgress.rendered': {},
|
||||||
|
'ui.guideAchievementDetail.rendered': {},
|
||||||
};
|
};
|
||||||
|
|
||||||
type StreamEvents = {
|
type StreamEvents = {
|
||||||
|
@ -1,112 +0,0 @@
|
|||||||
import { GuideMenu } from "@/modules/ui/guide-menu";
|
|
||||||
import { BX_FLAGS } from "./bx-flags";
|
|
||||||
import { BxLogger } from "./bx-logger";
|
|
||||||
import { BxIcon } from "./bx-icon";
|
|
||||||
import { AppInterface } from "./global";
|
|
||||||
import { createButton, ButtonStyle } from "./html";
|
|
||||||
import { t } from "./translation";
|
|
||||||
import { parseDetailsPath } from "./utils";
|
|
||||||
import { BxEventBus } from "./bx-event-bus";
|
|
||||||
|
|
||||||
|
|
||||||
export class RootDialogObserver {
|
|
||||||
private static $btnShortcut = AppInterface && createButton({
|
|
||||||
icon: BxIcon.CREATE_SHORTCUT,
|
|
||||||
label: t('create-shortcut'),
|
|
||||||
style: ButtonStyle.FOCUSABLE | ButtonStyle.GHOST | ButtonStyle.FULL_WIDTH | ButtonStyle.NORMAL_CASE | ButtonStyle.NORMAL_LINK,
|
|
||||||
onClick: e => {
|
|
||||||
window.BX_EXPOSED.dialogRoutes?.closeAll();
|
|
||||||
|
|
||||||
const $btn = (e.target as HTMLElement).closest('button');
|
|
||||||
AppInterface.createShortcut($btn?.dataset.path);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
private static $btnWallpaper = AppInterface && createButton({
|
|
||||||
icon: BxIcon.DOWNLOAD,
|
|
||||||
label: t('wallpaper'),
|
|
||||||
style: ButtonStyle.FOCUSABLE | ButtonStyle.GHOST | ButtonStyle.FULL_WIDTH | ButtonStyle.NORMAL_CASE | ButtonStyle.NORMAL_LINK,
|
|
||||||
onClick: e => {
|
|
||||||
window.BX_EXPOSED.dialogRoutes?.closeAll();
|
|
||||||
|
|
||||||
const $btn = (e.target as HTMLElement).closest('button');
|
|
||||||
const details = parseDetailsPath($btn!.dataset.path!);
|
|
||||||
details && AppInterface.downloadWallpapers(details.titleSlug, details.productId);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
private static handleGameCardMenu($root: HTMLElement) {
|
|
||||||
const $detail = $root.querySelector('a[href^="/play/"]') as HTMLAnchorElement;
|
|
||||||
if (!$detail) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const path = $detail.getAttribute('href')!;
|
|
||||||
RootDialogObserver.$btnShortcut.dataset.path = path;
|
|
||||||
RootDialogObserver.$btnWallpaper.dataset.path = path;
|
|
||||||
|
|
||||||
$root.append(RootDialogObserver.$btnShortcut, RootDialogObserver.$btnWallpaper);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static handleAddedElement($root: HTMLElement, $addedElm: HTMLElement): boolean {
|
|
||||||
if (AppInterface && $addedElm.className.startsWith('SlideSheet-module__container')) {
|
|
||||||
// Game card's context menu
|
|
||||||
const $gameCardMenu = $addedElm.querySelector<HTMLElement>('div[class^=MruContextMenu],div[class^=GameCardContextMenu]');
|
|
||||||
if ($gameCardMenu) {
|
|
||||||
RootDialogObserver.handleGameCardMenu($gameCardMenu);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
} else if ($root.querySelector('div[class*=GuideDialog]')) {
|
|
||||||
// Guide menu
|
|
||||||
GuideMenu.getInstance().observe($addedElm);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static observe($root: HTMLElement) {
|
|
||||||
let beingShown = false;
|
|
||||||
|
|
||||||
const observer = new MutationObserver(mutationList => {
|
|
||||||
for (const mutation of mutationList) {
|
|
||||||
if (mutation.type !== 'childList') {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
BX_FLAGS.Debug && BxLogger.warning('RootDialog', 'added', mutation.addedNodes);
|
|
||||||
if (mutation.addedNodes.length === 1) {
|
|
||||||
const $addedElm = mutation.addedNodes[0];
|
|
||||||
if ($addedElm instanceof HTMLElement) {
|
|
||||||
RootDialogObserver.handleAddedElement($root, $addedElm);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const shown = !!($root.firstElementChild && $root.firstElementChild.childElementCount > 0);
|
|
||||||
if (shown !== beingShown) {
|
|
||||||
beingShown = shown;
|
|
||||||
BxEventBus.Script.emit(shown ? 'dialog.shown' : 'dialog.dismissed', {});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
observer.observe($root, { subtree: true, childList: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
public static waitForRootDialog() {
|
|
||||||
const observer = new MutationObserver(mutationList => {
|
|
||||||
for (const mutation of mutationList) {
|
|
||||||
if (mutation.type !== 'childList') {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const $target = mutation.target as HTMLElement;
|
|
||||||
if ($target.id && $target.id === 'gamepass-dialog-root') {
|
|
||||||
observer.disconnect();
|
|
||||||
RootDialogObserver.observe($target);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
});
|
|
||||||
observer.observe(document.documentElement, { subtree: true, childList: true });
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
x
Reference in New Issue
Block a user