mirror of
https://github.com/redphx/better-xcloud.git
synced 2025-06-06 15:47:18 +02:00
Android: add Shortcut & Wallpaper menu to Game Card's context menu
This commit is contained in:
parent
63aaca7d61
commit
03efa528c8
52
src/index.ts
52
src/index.ts
@ -42,6 +42,7 @@ import { StreamUiHandler } from "./modules/stream/stream-ui";
|
|||||||
import { UserAgent } from "./utils/user-agent";
|
import { UserAgent } from "./utils/user-agent";
|
||||||
import { XboxApi } from "./utils/xbox-api";
|
import { XboxApi } from "./utils/xbox-api";
|
||||||
import { StreamStatsCollector } from "./utils/stream-stats-collector";
|
import { StreamStatsCollector } from "./utils/stream-stats-collector";
|
||||||
|
import { RootDialogObserver } from "./utils/root-dialog-observer";
|
||||||
|
|
||||||
// Handle login page
|
// Handle login page
|
||||||
if (window.location.pathname.includes('/auth/msa')) {
|
if (window.location.pathname.includes('/auth/msa')) {
|
||||||
@ -328,55 +329,6 @@ isFullVersion() && window.addEventListener(BxEvent.CAPTURE_SCREENSHOT, e => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
function observeRootDialog($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 && $addedElm.className) {
|
|
||||||
// Make sure it's Guide dialog
|
|
||||||
if ($root.querySelector('div[class*=GuideDialog]')) {
|
|
||||||
GuideMenu.observe($addedElm);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const shown = !!($root.firstElementChild && $root.firstElementChild.childElementCount > 0);
|
|
||||||
if (shown !== beingShown) {
|
|
||||||
beingShown = shown;
|
|
||||||
BxEvent.dispatch(window, shown ? BxEvent.XCLOUD_DIALOG_SHOWN : BxEvent.XCLOUD_DIALOG_DISMISSED);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
observer.observe($root, {subtree: true, childList: true});
|
|
||||||
}
|
|
||||||
|
|
||||||
function 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();
|
|
||||||
observeRootDialog($target);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
});
|
|
||||||
observer.observe(document.documentElement, {subtree: true, childList: true});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function main() {
|
function main() {
|
||||||
if (getPref(PrefKey.GAME_MSFS2020_FORCE_NATIVE_MKB)) {
|
if (getPref(PrefKey.GAME_MSFS2020_FORCE_NATIVE_MKB)) {
|
||||||
BX_FLAGS.ForceNativeMkbTitles.push('9PMQDM08SNK9');
|
BX_FLAGS.ForceNativeMkbTitles.push('9PMQDM08SNK9');
|
||||||
@ -397,7 +349,7 @@ function main() {
|
|||||||
disableAdobeAudienceManager();
|
disableAdobeAudienceManager();
|
||||||
}
|
}
|
||||||
|
|
||||||
waitForRootDialog();
|
RootDialogObserver.waitForRootDialog();
|
||||||
|
|
||||||
// Setup UI
|
// Setup UI
|
||||||
addCss();
|
addCss();
|
||||||
|
@ -3,6 +3,7 @@ import { BxIcon } from "@/utils/bx-icon";
|
|||||||
import { AppInterface } from "@/utils/global";
|
import { AppInterface } from "@/utils/global";
|
||||||
import { ButtonStyle, CE, createButton } from "@/utils/html";
|
import { ButtonStyle, CE, createButton } from "@/utils/html";
|
||||||
import { t } from "@/utils/translation";
|
import { t } from "@/utils/translation";
|
||||||
|
import { parseDetailsPath } from "@/utils/utils";
|
||||||
|
|
||||||
export class ProductDetailsPage {
|
export class ProductDetailsPage {
|
||||||
private static $btnShortcut = AppInterface && createButton({
|
private static $btnShortcut = AppInterface && createButton({
|
||||||
@ -20,17 +21,9 @@ export class ProductDetailsPage {
|
|||||||
label: t('wallpaper'),
|
label: t('wallpaper'),
|
||||||
style: ButtonStyle.FOCUSABLE,
|
style: ButtonStyle.FOCUSABLE,
|
||||||
tabIndex: 0,
|
tabIndex: 0,
|
||||||
onClick: async e => {
|
onClick: e => {
|
||||||
try {
|
const details = parseDetailsPath(window.location.pathname);
|
||||||
const matches = /\/games\/(?<titleSlug>[^\/]+)\/(?<productId>\w+)/.exec(window.location.pathname);
|
details && AppInterface.downloadWallpapers(details.titleSlug, details.productId);
|
||||||
if (!matches?.groups) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const titleSlug = matches.groups.titleSlug.replaceAll('\%' + '7C', '-');
|
|
||||||
const productId = matches.groups.productId;
|
|
||||||
AppInterface.downloadWallpapers(titleSlug, productId);
|
|
||||||
} catch (e) {}
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
114
src/utils/root-dialog-observer.ts
Normal file
114
src/utils/root-dialog-observer.ts
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
import { GuideMenu } from "@/modules/ui/guide-menu";
|
||||||
|
import { BxEvent } from "./bx-event";
|
||||||
|
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";
|
||||||
|
|
||||||
|
|
||||||
|
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,
|
||||||
|
tabIndex: 0,
|
||||||
|
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,
|
||||||
|
tabIndex: 0,
|
||||||
|
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.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 && $addedElm.className) {
|
||||||
|
RootDialogObserver.handleAddedElement($root, $addedElm);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const shown = !!($root.firstElementChild && $root.firstElementChild.childElementCount > 0);
|
||||||
|
if (shown !== beingShown) {
|
||||||
|
beingShown = shown;
|
||||||
|
BxEvent.dispatch(window, shown ? BxEvent.XCLOUD_DIALOG_SHOWN : BxEvent.XCLOUD_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});
|
||||||
|
}
|
||||||
|
}
|
@ -120,3 +120,15 @@ export function productTitleToSlug(title: string): string {
|
|||||||
.replace(/ /g, '-')
|
.replace(/ /g, '-')
|
||||||
.toLowerCase();
|
.toLowerCase();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function parseDetailsPath(path: string) {
|
||||||
|
const matches = /\/games\/(?<titleSlug>[^\/]+)\/(?<productId>\w+)/.exec(path);
|
||||||
|
if (!matches?.groups) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const titleSlug = matches.groups.titleSlug.replaceAll('\%' + '7C', '-');
|
||||||
|
const productId = matches.groups.productId;
|
||||||
|
|
||||||
|
return {titleSlug, productId};
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user