mirror of
https://github.com/redphx/better-xcloud.git
synced 2025-06-29 02:41:44 +02:00
Compare commits
6 Commits
Author | SHA1 | Date | |
---|---|---|---|
714178a82d | |||
5c2c13e0e6 | |||
3f423325b9 | |||
870a205ead | |||
421af05882 | |||
756d105f74 |
2
dist/better-xcloud.meta.js
vendored
2
dist/better-xcloud.meta.js
vendored
@ -1,5 +1,5 @@
|
||||
// ==UserScript==
|
||||
// @name Better xCloud
|
||||
// @namespace https://github.com/redphx
|
||||
// @version 5.7.1
|
||||
// @version 5.7.2
|
||||
// ==/UserScript==
|
||||
|
129
dist/better-xcloud.user.js
vendored
129
dist/better-xcloud.user.js
vendored
File diff suppressed because one or more lines are too long
@ -1,10 +1,40 @@
|
||||
.bx-guide-home-achievements-progress {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
flex-direction: row;
|
||||
|
||||
.bx-button {
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
|
||||
html[data-xds-platform=tv] & {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
html:not([data-xds-platform=tv]) & {
|
||||
flex-direction: row;
|
||||
|
||||
> button:first-of-type {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
> button:last-of-type {
|
||||
width: 40px;
|
||||
|
||||
span {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bx-guide-home-buttons {
|
||||
> div {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 12px;
|
||||
|
||||
body[data-media-type=tv] & {
|
||||
html[data-xds-platform=tv] & {
|
||||
flex-direction: column;
|
||||
|
||||
button {
|
||||
@ -12,7 +42,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
body:not([data-media-type=tv]) & {
|
||||
html:not([data-xds-platform=tv]) & {
|
||||
button {
|
||||
span {
|
||||
display: none;
|
||||
|
3
src/assets/svg/speaker-slash.svg
Normal file
3
src/assets/svg/speaker-slash.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg xmlns='http://www.w3.org/2000/svg' fill='#fff' stroke='none' fill-rule='evenodd' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' viewBox='0 0 32 32'>
|
||||
<path d='M5.462 3.4c-.205-.23-.499-.363-.808-.363-.592 0-1.079.488-1.079 1.08a1.08 1.08 0 0 0 .289.736l4.247 4.672H2.504a2.17 2.17 0 0 0-2.16 2.16v8.637a2.17 2.17 0 0 0 2.16 2.16h6.107l9.426 7.33a1.08 1.08 0 0 0 .662.227c.592 0 1.08-.487 1.08-1.079v-6.601l5.679 6.247a1.08 1.08 0 0 0 .808.363c.592 0 1.08-.487 1.08-1.079a1.08 1.08 0 0 0-.29-.736L5.462 3.4zm-2.958 8.285h5.398v8.637H2.504v-8.637zM17.62 26.752l-7.558-5.878V11.67l7.558 8.313v6.769zm5.668-8.607c1.072-1.218 1.072-3.063 0-4.281a1.08 1.08 0 0 1-.293-.74c0-.592.487-1.079 1.079-1.079a1.08 1.08 0 0 1 .834.393 5.42 5.42 0 0 1 0 7.137 1.08 1.08 0 0 1-.81.365c-.593 0-1.08-.488-1.08-1.08 0-.263.096-.517.27-.715zM12.469 7.888c-.147-.19-.228-.423-.228-.663a1.08 1.08 0 0 1 .417-.853l5.379-4.184a1.08 1.08 0 0 1 .662-.227c.593 0 1.08.488 1.08 1.08v10.105c0 .593-.487 1.08-1.08 1.08s-1.079-.487-1.079-1.08V5.255l-3.636 2.834c-.469.362-1.153.273-1.515-.196v-.005zm19.187 8.115a10.79 10.79 0 0 1-2.749 7.199 1.08 1.08 0 0 1-.793.347c-.593 0-1.08-.487-1.08-1.079 0-.26.094-.511.264-.708 2.918-3.262 2.918-8.253 0-11.516-.184-.2-.287-.461-.287-.733 0-.592.487-1.08 1.08-1.08a1.08 1.08 0 0 1 .816.373 10.78 10.78 0 0 1 2.749 7.197z' fill-rule='nonzero'/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.4 KiB |
@ -1,7 +1,6 @@
|
||||
import { BxEvent } from "@utils/bx-event";
|
||||
import { BxIcon } from "@utils/bx-icon";
|
||||
import { createButton, ButtonStyle, CE } from "@utils/html";
|
||||
import { t } from "@utils/translation";
|
||||
import { BaseGameBarAction } from "./action-base";
|
||||
import { MicrophoneShortcut, MicrophoneState } from "../shortcuts/shortcut-microphone";
|
||||
|
||||
@ -24,7 +23,6 @@ export class MicrophoneAction extends BaseGameBarAction {
|
||||
const $btnDefault = createButton({
|
||||
style: ButtonStyle.GHOST,
|
||||
icon: BxIcon.MICROPHONE,
|
||||
title: t('show-touch-controller'),
|
||||
onClick: onClick,
|
||||
classes: ['bx-activated'],
|
||||
});
|
||||
@ -32,7 +30,6 @@ export class MicrophoneAction extends BaseGameBarAction {
|
||||
const $btnMuted = createButton({
|
||||
style: ButtonStyle.GHOST,
|
||||
icon: BxIcon.MICROPHONE_MUTED,
|
||||
title: t('hide-touch-controller'),
|
||||
onClick: onClick,
|
||||
});
|
||||
|
||||
|
54
src/modules/game-bar/action-speaker.ts
Normal file
54
src/modules/game-bar/action-speaker.ts
Normal file
@ -0,0 +1,54 @@
|
||||
import { BxEvent } from "@utils/bx-event";
|
||||
import { BxIcon } from "@utils/bx-icon";
|
||||
import { createButton, ButtonStyle, CE } from "@utils/html";
|
||||
import { BaseGameBarAction } from "./action-base";
|
||||
import { SoundShortcut, SpeakerState } from "../shortcuts/shortcut-sound";
|
||||
|
||||
|
||||
export class SpeakerAction extends BaseGameBarAction {
|
||||
$content: HTMLElement;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
const onClick = (e: Event) => {
|
||||
BxEvent.dispatch(window, BxEvent.GAME_BAR_ACTION_ACTIVATED);
|
||||
SoundShortcut.muteUnmute();
|
||||
};
|
||||
|
||||
const $btnEnable = createButton({
|
||||
style: ButtonStyle.GHOST,
|
||||
icon: BxIcon.AUDIO,
|
||||
onClick: onClick,
|
||||
});
|
||||
|
||||
const $btnMuted = createButton({
|
||||
style: ButtonStyle.GHOST,
|
||||
icon: BxIcon.SPEAKER_MUTED,
|
||||
onClick: onClick,
|
||||
classes: ['bx-activated'],
|
||||
});
|
||||
|
||||
this.$content = CE('div', {},
|
||||
$btnEnable,
|
||||
$btnMuted,
|
||||
);
|
||||
|
||||
this.reset();
|
||||
|
||||
window.addEventListener(BxEvent.SPEAKER_STATE_CHANGED, e => {
|
||||
const speakerState = (e as any).speakerState;
|
||||
const enabled = speakerState === SpeakerState.ENABLED;
|
||||
|
||||
this.$content.dataset.enabled = enabled.toString();
|
||||
});
|
||||
}
|
||||
|
||||
render(): HTMLElement {
|
||||
return this.$content;
|
||||
}
|
||||
|
||||
reset(): void {
|
||||
this.$content.dataset.enabled = 'true';
|
||||
}
|
||||
}
|
@ -26,7 +26,6 @@ export class TouchControlAction extends BaseGameBarAction {
|
||||
icon: BxIcon.TOUCH_CONTROL_ENABLE,
|
||||
title: t('show-touch-controller'),
|
||||
onClick: onClick,
|
||||
classes: ['bx-activated'],
|
||||
});
|
||||
|
||||
const $btnDisable = createButton({
|
||||
@ -34,6 +33,7 @@ export class TouchControlAction extends BaseGameBarAction {
|
||||
icon: BxIcon.TOUCH_CONTROL_DISABLE,
|
||||
title: t('hide-touch-controller'),
|
||||
onClick: onClick,
|
||||
classes: ['bx-activated'],
|
||||
});
|
||||
|
||||
this.$content = CE('div', {},
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { CE, createSvgIcon } from "@utils/html";
|
||||
import { CE, clearFocus, createSvgIcon } from "@utils/html";
|
||||
import { ScreenshotAction } from "./action-screenshot";
|
||||
import { TouchControlAction } from "./action-touch-control";
|
||||
import { BxEvent } from "@utils/bx-event";
|
||||
@ -9,6 +9,7 @@ import { MicrophoneAction } from "./action-microphone";
|
||||
import { PrefKey } from "@/enums/pref-keys";
|
||||
import { getPref, StreamTouchController } from "@/utils/settings-storages/global-settings-storage";
|
||||
import { TrueAchievementsAction } from "./action-true-achievements";
|
||||
import { SpeakerAction } from "./action-speaker";
|
||||
|
||||
|
||||
export class GameBar {
|
||||
@ -43,8 +44,9 @@ export class GameBar {
|
||||
this.actions = [
|
||||
new ScreenshotAction(),
|
||||
...(STATES.userAgent.capabilities.touch && (getPref(PrefKey.STREAM_TOUCH_CONTROLLER) !== StreamTouchController.OFF) ? [new TouchControlAction()] : []),
|
||||
new TrueAchievementsAction(),
|
||||
new SpeakerAction(),
|
||||
new MicrophoneAction(),
|
||||
new TrueAchievementsAction(),
|
||||
];
|
||||
|
||||
// Reverse the action list if Game Bar's position is on the right side
|
||||
@ -133,6 +135,9 @@ export class GameBar {
|
||||
}
|
||||
|
||||
hideBar() {
|
||||
// Stop focusing Game Bar
|
||||
clearFocus();
|
||||
|
||||
if (!this.$container) {
|
||||
return;
|
||||
}
|
||||
|
@ -4,6 +4,12 @@ import { Toast } from "@utils/toast";
|
||||
import { ceilToNearest, floorToNearest } from "@/utils/utils";
|
||||
import { PrefKey } from "@/enums/pref-keys";
|
||||
import { getPref, setPref } from "@/utils/settings-storages/global-settings-storage";
|
||||
import { BxEvent } from "@/utils/bx-event";
|
||||
|
||||
export enum SpeakerState {
|
||||
ENABLED,
|
||||
MUTED,
|
||||
}
|
||||
|
||||
export class SoundShortcut {
|
||||
static adjustGainNodeVolume(amount: number): number {
|
||||
@ -64,6 +70,10 @@ export class SoundShortcut {
|
||||
|
||||
SoundShortcut.setGainNodeVolume(targetValue);
|
||||
Toast.show(`${t('stream')} ❯ ${t('volume')}`, status, {instant: true});
|
||||
|
||||
BxEvent.dispatch(window, BxEvent.SPEAKER_STATE_CHANGED, {
|
||||
speakerState: targetValue === 0 ? SpeakerState.MUTED : SpeakerState.ENABLED,
|
||||
})
|
||||
return;
|
||||
}
|
||||
|
||||
@ -79,6 +89,10 @@ export class SoundShortcut {
|
||||
|
||||
const status = $media.muted ? t('muted') : t('unmuted');
|
||||
Toast.show(`${t('stream')} ❯ ${t('volume')}`, status, {instant: true});
|
||||
|
||||
BxEvent.dispatch(window, BxEvent.SPEAKER_STATE_CHANGED, {
|
||||
speakerState: $media.muted ? SpeakerState.MUTED : SpeakerState.ENABLED,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -88,7 +88,6 @@ export class GuideMenu {
|
||||
const buttons = [
|
||||
GuideMenu.#BUTTONS.scriptSettings,
|
||||
[
|
||||
TrueAchievements.$button,
|
||||
GuideMenu.#BUTTONS.backToHome,
|
||||
GuideMenu.#BUTTONS.reloadPage,
|
||||
GuideMenu.#BUTTONS.closeApp,
|
||||
@ -116,6 +115,11 @@ export class GuideMenu {
|
||||
}
|
||||
|
||||
static #injectHome($root: HTMLElement, isPlaying = false) {
|
||||
const $achievementsProgress = $root.querySelector('button[class*=AchievementsButton-module__progressBarContainer]');
|
||||
if ($achievementsProgress) {
|
||||
TrueAchievements.injectAchievementsProgress($achievementsProgress as HTMLElement);
|
||||
}
|
||||
|
||||
// Find the element to add buttons to
|
||||
let $target: HTMLElement | null = null;
|
||||
if (isPlaying) {
|
||||
@ -157,6 +161,12 @@ export class GuideMenu {
|
||||
|
||||
static observe($addedElm: HTMLElement) {
|
||||
const className = $addedElm.className;
|
||||
|
||||
if (className.includes('AchievementsButton-module__progressBarContainer')) {
|
||||
TrueAchievements.injectAchievementsProgress($addedElm);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!className.startsWith('NavigationAnimation') &&
|
||||
!className.startsWith('DialogRoutes') &&
|
||||
!className.startsWith('Dialog-module__container')) {
|
||||
|
@ -37,6 +37,7 @@ export namespace BxEvent {
|
||||
|
||||
export const GAME_BAR_ACTION_ACTIVATED = 'bx-game-bar-action-activated';
|
||||
export const MICROPHONE_STATE_CHANGED = 'bx-microphone-state-changed';
|
||||
export const SPEAKER_STATE_CHANGED = 'bx-speaker-state-changed';
|
||||
|
||||
export const CAPTURE_SCREENSHOT = 'bx-capture-screenshot';
|
||||
|
||||
|
@ -14,6 +14,7 @@ import iconPower from "@assets/svg/power.svg" with { type: "text" };
|
||||
import iconQuestion from "@assets/svg/question.svg" with { type: "text" };
|
||||
import iconRefresh from "@assets/svg/refresh.svg" with { type: "text" };
|
||||
import iconRemotePlay from "@assets/svg/remote-play.svg" with { type: "text" };
|
||||
import iconSpeakerSlash from "@assets/svg/speaker-slash.svg" with { type: "text" };
|
||||
import iconStreamSettings from "@assets/svg/stream-settings.svg" with { type: "text" };
|
||||
import iconStreamStats from "@assets/svg/stream-stats.svg" with { type: "text" };
|
||||
import iconTouchControlDisable from "@assets/svg/touch-control-disable.svg" with { type: "text" };
|
||||
@ -64,6 +65,7 @@ export const BxIcon = {
|
||||
CARET_LEFT: iconCaretLeft,
|
||||
CARET_RIGHT: iconCaretRight,
|
||||
SCREENSHOT: iconCamera,
|
||||
SPEAKER_MUTED: iconSpeakerSlash,
|
||||
TOUCH_CONTROL_ENABLE: iconTouchControlEnable,
|
||||
TOUCH_CONTROL_DISABLE: iconTouchControlDisable,
|
||||
|
||||
|
@ -174,3 +174,16 @@ export function removeChildElements($parent: HTMLElement) {
|
||||
$parent.firstElementChild.remove();
|
||||
}
|
||||
}
|
||||
|
||||
export function clearFocus() {
|
||||
if (document.activeElement instanceof HTMLElement) {
|
||||
document.activeElement.blur();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export function clearDataSet($elm: HTMLElement) {
|
||||
Object.keys($elm.dataset).forEach(key => {
|
||||
delete $elm.dataset[key];
|
||||
});
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { BxIcon } from "./bx-icon";
|
||||
import { AppInterface, STATES } from "./global";
|
||||
import { ButtonStyle, CE, createButton, getReactProps } from "./html";
|
||||
import { ButtonStyle, CE, clearDataSet, createButton, getReactProps } from "./html";
|
||||
import { t } from "./translation";
|
||||
|
||||
export class TrueAchievements {
|
||||
@ -16,7 +16,7 @@ export class TrueAchievements {
|
||||
label: t('true-achievements'),
|
||||
title: t('true-achievements'),
|
||||
icon: BxIcon.TRUE_ACHIEVEMENTS,
|
||||
style: ButtonStyle.FOCUSABLE | ButtonStyle.FULL_WIDTH,
|
||||
style: ButtonStyle.FOCUSABLE,
|
||||
onClick: TrueAchievements.onClick,
|
||||
}) as HTMLAnchorElement;
|
||||
|
||||
@ -25,18 +25,67 @@ export class TrueAchievements {
|
||||
|
||||
const dataset = TrueAchievements.$link.dataset;
|
||||
TrueAchievements.open(true, dataset.xboxTitleId, dataset.id);
|
||||
|
||||
// Close all xCloud's dialogs
|
||||
window.BX_EXPOSED.dialogRoutes.closeAll();
|
||||
}
|
||||
|
||||
private static $hiddenLink = CE<HTMLAnchorElement>('a', {
|
||||
target: '_blank',
|
||||
});
|
||||
|
||||
private static updateLinks(xboxTitleId?: string, id?: string) {
|
||||
TrueAchievements.$link.dataset.xboxTitleId = xboxTitleId;
|
||||
TrueAchievements.$link.dataset.id = id;
|
||||
private static updateIds(xboxTitleId?: string, id?: string) {
|
||||
const $link = TrueAchievements.$link;
|
||||
const $button = TrueAchievements.$button;
|
||||
|
||||
TrueAchievements.$button.dataset.xboxTitleId = xboxTitleId;
|
||||
TrueAchievements.$button.dataset.id = id;
|
||||
clearDataSet($link);
|
||||
clearDataSet($button);
|
||||
|
||||
if (xboxTitleId) {
|
||||
$link.dataset.xboxTitleId = xboxTitleId;
|
||||
$button.dataset.xboxTitleId = xboxTitleId;
|
||||
}
|
||||
|
||||
if (id) {
|
||||
$link.dataset.id = id;
|
||||
$button.dataset.id = id;
|
||||
}
|
||||
}
|
||||
|
||||
static injectAchievementsProgress($elm: HTMLElement) {
|
||||
const $parent = $elm.parentElement!;
|
||||
|
||||
// Wrap xCloud's element with our own
|
||||
const $div = CE('div', {
|
||||
class: 'bx-guide-home-achievements-progress',
|
||||
}, $elm);
|
||||
|
||||
// Get xboxTitleId of the game
|
||||
let xboxTitleId: string | number | undefined;
|
||||
try {
|
||||
const $container = $parent.closest('div[class*=AchievementsPreview-module__container]') as HTMLElement;
|
||||
if ($container) {
|
||||
const props = getReactProps($container);
|
||||
xboxTitleId = props.children.props.data.data.xboxTitleId;
|
||||
}
|
||||
} catch (e) {}
|
||||
|
||||
if (!xboxTitleId) {
|
||||
xboxTitleId = TrueAchievements.getStreamXboxTitleId();
|
||||
}
|
||||
|
||||
if (typeof xboxTitleId !== 'undefined') {
|
||||
xboxTitleId = xboxTitleId.toString();
|
||||
}
|
||||
TrueAchievements.updateIds(xboxTitleId);
|
||||
|
||||
if (document.documentElement.dataset.xdsPlatform === 'tv') {
|
||||
$div.appendChild(TrueAchievements.$link);
|
||||
} else {
|
||||
$div.appendChild(TrueAchievements.$button);
|
||||
}
|
||||
|
||||
$parent.appendChild($div);
|
||||
}
|
||||
|
||||
static injectAchievementDetailPage($parent: HTMLElement) {
|
||||
@ -66,15 +115,19 @@ export class TrueAchievements {
|
||||
|
||||
// Found achievement -> add TrueAchievements button
|
||||
if (id) {
|
||||
TrueAchievements.updateLinks(xboxTitleId, id);
|
||||
TrueAchievements.updateIds(xboxTitleId, id);
|
||||
$parent.appendChild(TrueAchievements.$link);
|
||||
}
|
||||
} catch (e) {};
|
||||
}
|
||||
|
||||
private static getStreamXboxTitleId() : number | undefined {
|
||||
return STATES.currentStream.xboxTitleId || STATES.currentStream.titleInfo?.details.xboxTitleId;
|
||||
}
|
||||
|
||||
static open(override: boolean, xboxTitleId?: number | string, id?: number | string) {
|
||||
if (!xboxTitleId || xboxTitleId === 'undefined') {
|
||||
xboxTitleId = STATES.currentStream.xboxTitleId || STATES.currentStream.titleInfo?.details.xboxTitleId;
|
||||
xboxTitleId = TrueAchievements.getStreamXboxTitleId();
|
||||
}
|
||||
|
||||
if (AppInterface && AppInterface.openTrueAchievementsLink) {
|
||||
@ -84,10 +137,10 @@ export class TrueAchievements {
|
||||
|
||||
let url = 'https://www.trueachievements.com';
|
||||
if (xboxTitleId) {
|
||||
if (id && id !== 'undefined') {
|
||||
url += `/deeplink/${xboxTitleId}/${id}`;
|
||||
} else {
|
||||
url += `/deeplink/${xboxTitleId}`;
|
||||
url += `/deeplink/${xboxTitleId}`;
|
||||
|
||||
if (id) {
|
||||
url += `/${id}`;
|
||||
}
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user