mirror of
https://github.com/SteamDeckHomebrew/decky-frontend-lib.git
synced 2026-05-19 17:40:08 +02:00
Merge branch 'SteamDeckHomebrew:main' into main
This commit is contained in:
@@ -6,7 +6,8 @@ declare global {
|
||||
|
||||
function getQuickAccessWindow(): Window | null {
|
||||
try {
|
||||
const navTrees = FocusNavController?.m_ActiveContext?.m_rgGamepadNavigationTrees || FocusNavController?.m_rgGamepadNavigationTrees;
|
||||
const context = FocusNavController?.m_ActiveContext || FocusNavController?.m_LastActiveContext;
|
||||
const navTrees = context?.m_rgGamepadNavigationTrees || FocusNavController?.m_rgGamepadNavigationTrees;
|
||||
return navTrees?.find((tree: any) => tree?.id === "QuickAccess-NA")?.m_Root?.m_element?.ownerDocument.defaultView ?? null;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
|
||||
17
src/deck-components/ControlsList.tsx
Normal file
17
src/deck-components/ControlsList.tsx
Normal file
@@ -0,0 +1,17 @@
|
||||
import { findModuleChild } from '../webpack';
|
||||
import { FC } from 'react';
|
||||
|
||||
export interface ControlsListProps {
|
||||
alignItems?: 'left' | 'right' | 'center';
|
||||
spacing?: 'standard' | 'extra';
|
||||
}
|
||||
|
||||
export const ControlsList: FC<ControlsListProps> = findModuleChild((m) => {
|
||||
if (typeof m !== 'object') return;
|
||||
for (const prop in m) {
|
||||
if (m[prop]?.toString && m[prop].toString().includes('().ControlsListChild') && m[prop].toString().includes('().ControlsListOuterPanel')) {
|
||||
return m[prop];
|
||||
}
|
||||
}
|
||||
return;
|
||||
});
|
||||
33
src/deck-components/DialogCheckbox.tsx
Normal file
33
src/deck-components/DialogCheckbox.tsx
Normal file
@@ -0,0 +1,33 @@
|
||||
import { FC, ReactNode } from 'react';
|
||||
|
||||
import { findModule } from '../webpack';
|
||||
import { DialogCommonProps } from './Dialog';
|
||||
import { FooterLegendProps } from './FooterLegend';
|
||||
|
||||
export interface DialogCheckboxProps extends DialogCommonProps, FooterLegendProps {
|
||||
onChange?(checked: boolean): void;
|
||||
label?: ReactNode;
|
||||
description?: ReactNode;
|
||||
disabled?: boolean;
|
||||
tooltip?: string;
|
||||
color?: string;
|
||||
highlightColor?: string;
|
||||
bottomSeparator?: 'standard' | 'thick' | 'none';
|
||||
controlled?: boolean;
|
||||
checked?: boolean;
|
||||
onClick?(evt: Event): void;
|
||||
}
|
||||
|
||||
export const DialogCheckbox = Object.values(findModule((m: any) => {
|
||||
if (typeof m !== 'object') return false;
|
||||
for (const prop in m) {
|
||||
if (m[prop]?.prototype?.GetPanelElementProps) return true;
|
||||
}
|
||||
return false;
|
||||
})).find((m: any) =>
|
||||
m.contextType &&
|
||||
m.prototype?.render.toString().includes('fallback:') &&
|
||||
m?.prototype?.SetChecked &&
|
||||
m?.prototype?.Toggle &&
|
||||
m?.prototype?.GetPanelElementProps
|
||||
) as FC<DialogCheckboxProps>;
|
||||
@@ -8,6 +8,7 @@ export interface FocusableProps extends HTMLAttributes<HTMLDivElement>, FooterLe
|
||||
'flow-children'?: string;
|
||||
focusClassName?: string;
|
||||
focusWithinClassName?: string;
|
||||
noFocusRing?: boolean;
|
||||
onActivate?: (e: CustomEvent) => void;
|
||||
onCancel?: (e: CustomEvent) => void;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { ReactNode } from 'react';
|
||||
|
||||
export enum GamepadButton {
|
||||
INVALID,
|
||||
OK,
|
||||
@@ -42,16 +44,16 @@ export interface GamepadEventDetail {
|
||||
source: number;
|
||||
}
|
||||
export declare type ActionDescriptionMap = {
|
||||
[key in GamepadButton]?: string
|
||||
[key in GamepadButton]?: ReactNode
|
||||
}
|
||||
export declare type GamepadEvent = CustomEvent<GamepadEventDetail>;
|
||||
export interface FooterLegendProps {
|
||||
actionDescriptionMap?: ActionDescriptionMap;
|
||||
onOKActionDescription?: string;
|
||||
onCancelActionDescription?: string;
|
||||
onSecondaryActionDescription?: string;
|
||||
onOptionsActionDescription?: string;
|
||||
onMenuActionDescription?: string;
|
||||
onOKActionDescription?: ReactNode;
|
||||
onCancelActionDescription?: ReactNode;
|
||||
onSecondaryActionDescription?: ReactNode;
|
||||
onOptionsActionDescription?: ReactNode;
|
||||
onMenuActionDescription?: ReactNode;
|
||||
onButtonDown?: (evt: GamepadEvent) => void;
|
||||
onButtonUp?: (evt: GamepadEvent) => void;
|
||||
onOKButton?: (evt: GamepadEvent) => void;
|
||||
|
||||
26
src/deck-components/Marquee.tsx
Normal file
26
src/deck-components/Marquee.tsx
Normal file
@@ -0,0 +1,26 @@
|
||||
import { CSSProperties, FC } from 'react';
|
||||
|
||||
import { findModuleChild } from '../webpack';
|
||||
|
||||
export interface MarqueeProps {
|
||||
play?: boolean;
|
||||
direction?: 'left' | 'right';
|
||||
speed?: number;
|
||||
delay?: number;
|
||||
fadeLength?: number;
|
||||
center?: boolean;
|
||||
resetOnPause?: boolean;
|
||||
style?: CSSProperties;
|
||||
className?: string;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export const Marquee: FC<MarqueeProps> = findModuleChild((m) => {
|
||||
if (typeof m !== 'object') return;
|
||||
for (const prop in m) {
|
||||
if (m[prop]?.toString && m[prop].toString().includes('.Marquee') && m[prop].toString().includes('--fade-length')) {
|
||||
return m[prop];
|
||||
}
|
||||
}
|
||||
return;
|
||||
});
|
||||
@@ -2,6 +2,7 @@ import { FC, ReactNode } from 'react';
|
||||
|
||||
import { fakeRenderComponent } from '../utils';
|
||||
import { findModuleChild } from '../webpack';
|
||||
import { FooterLegendProps } from './FooterLegend';
|
||||
|
||||
export const showContextMenu: (children: ReactNode, parent?: EventTarget) => void = findModuleChild((m) => {
|
||||
if (typeof m !== 'object') return undefined;
|
||||
@@ -12,7 +13,7 @@ export const showContextMenu: (children: ReactNode, parent?: EventTarget) => voi
|
||||
}
|
||||
});
|
||||
|
||||
export interface MenuProps {
|
||||
export interface MenuProps extends FooterLegendProps {
|
||||
label: string;
|
||||
onCancel?(): void;
|
||||
cancelText?: string;
|
||||
@@ -49,9 +50,16 @@ export const MenuGroup: FC<MenuGroupProps> = findModuleChild((m) => {
|
||||
}
|
||||
});
|
||||
|
||||
export interface MenuItemProps {
|
||||
onSelected?(): void;
|
||||
export interface MenuItemProps extends FooterLegendProps {
|
||||
bInteractableItem?: boolean;
|
||||
onClick?(evt: Event): void;
|
||||
onSelected?(evt: Event): void;
|
||||
onMouseEnter?(evt: MouseEvent): void;
|
||||
onMoveRight?(): void;
|
||||
selected?: boolean;
|
||||
disabled?: boolean;
|
||||
bPlayAudio?: boolean;
|
||||
tone?: 'positive' | 'emphasis' | 'destructive';
|
||||
children?: ReactNode;
|
||||
}
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ const showModalRaw:
|
||||
unknown1?: unknown,
|
||||
hideActions?: { bHideActions?: boolean },
|
||||
modalManager?: unknown,
|
||||
) => Promise<ShowModalResult>)
|
||||
) => ShowModalResult)
|
||||
| void = findModuleChild((m) => {
|
||||
if (typeof m !== 'object') return undefined;
|
||||
for (let prop in m) {
|
||||
@@ -52,16 +52,15 @@ const showModalRaw:
|
||||
}
|
||||
});
|
||||
|
||||
const oldShowModalRaw:
|
||||
| ((modal: ReactNode, parent?: EventTarget, props?: ShowModalProps) => Promise<ShowModalResult>)
|
||||
| void = findModuleChild((m) => {
|
||||
if (typeof m !== 'object') return undefined;
|
||||
for (let prop in m) {
|
||||
if (typeof m[prop] === 'function' && m[prop].toString().includes('bHideMainWindowForPopouts:!0')) {
|
||||
return m[prop];
|
||||
const oldShowModalRaw: ((modal: ReactNode, parent?: EventTarget, props?: ShowModalProps) => ShowModalResult) | void =
|
||||
findModuleChild((m) => {
|
||||
if (typeof m !== 'object') return undefined;
|
||||
for (let prop in m) {
|
||||
if (typeof m[prop] === 'function' && m[prop].toString().includes('bHideMainWindowForPopouts:!0')) {
|
||||
return m[prop];
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
export const showModal = (
|
||||
modal: ReactNode,
|
||||
@@ -70,7 +69,7 @@ export const showModal = (
|
||||
strTitle: 'Decky Dialog',
|
||||
bHideMainWindowForPopouts: false,
|
||||
},
|
||||
): Promise<ShowModalResult> => {
|
||||
): ShowModalResult => {
|
||||
if (showModalRaw) {
|
||||
return showModalRaw(modal, parent || findSP(), props.strTitle, props, undefined, {
|
||||
bHideActions: props.bHideActionIcons,
|
||||
@@ -118,13 +117,13 @@ export const ConfirmModal = findModuleChild((m) => {
|
||||
}
|
||||
}) as FC<ConfirmModalProps>;
|
||||
|
||||
// new
|
||||
// new as of december 2022 on beta
|
||||
export const ModalRoot = (Object.values(
|
||||
findModule((m: any) => {
|
||||
if (typeof m !== 'object') return false;
|
||||
|
||||
for (let prop in m) {
|
||||
if (m[prop]?.toString()?.includes('"ModalManager","DialogWrapper"')) {
|
||||
if (m[prop]?.m_mapModalManager) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -132,6 +131,20 @@ export const ModalRoot = (Object.values(
|
||||
return false;
|
||||
}) || {},
|
||||
)?.find((x: any) => x?.type?.toString()?.includes('((function(){')) ||
|
||||
// before december 2022 beta
|
||||
Object.values(
|
||||
findModule((m: any) => {
|
||||
if (typeof m !== 'object') return false;
|
||||
|
||||
for (let prop in m) {
|
||||
if (m[prop]?.toString()?.includes('"ModalManager","DialogWrapper"')) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}) || {},
|
||||
)?.find((x: any) => x?.type?.toString()?.includes('((function(){')) ||
|
||||
// old
|
||||
findModuleChild((m) => {
|
||||
if (typeof m !== 'object') return undefined;
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { FC } from 'react';
|
||||
import { FC, ReactNode } from 'react';
|
||||
|
||||
import { findModuleChild } from '../webpack';
|
||||
|
||||
export interface PanelSectionProps {
|
||||
title?: string;
|
||||
spinner?: boolean;
|
||||
children?: ReactNode
|
||||
}
|
||||
|
||||
const [panelSection, mod] = findModuleChild((mod: any) => {
|
||||
@@ -18,6 +19,10 @@ const [panelSection, mod] = findModuleChild((mod: any) => {
|
||||
|
||||
export const PanelSection = panelSection as FC<PanelSectionProps>;
|
||||
|
||||
export interface PanelSectionRowProps {
|
||||
children?: ReactNode
|
||||
}
|
||||
|
||||
export const PanelSectionRow = Object.values(mod).filter(
|
||||
(exp: any) => !exp?.toString()?.includes('.PanelSection'),
|
||||
)[0] as FC;
|
||||
)[0] as FC<PanelSectionRowProps>;
|
||||
|
||||
@@ -63,28 +63,38 @@ export type AppOverview = {
|
||||
sort_as: string;
|
||||
};
|
||||
|
||||
export interface Router {
|
||||
CloseSideMenus(): void;
|
||||
OpenQuickAccessMenu(quickAccessTab?: QuickAccessTab): void;
|
||||
GetQuickAccessTab(): QuickAccessTab;
|
||||
Navigate(path: string): void;
|
||||
NavigateBackOrOpenMenu(): void;
|
||||
NavigateToAppProperties(): void;
|
||||
NavigateToBugForum(): void;
|
||||
NavigateToExternalWeb(url: string): void;
|
||||
NavigateToHelp(): void;
|
||||
NavigateToInvites(): void;
|
||||
NavigateToRunningApp(replace?: boolean): void;
|
||||
NavigateToStorage(): void;
|
||||
NavigateToStore(): void;
|
||||
NavigateToStoreApp(appId: number | string): void;
|
||||
NavigateToStoreFreeToPlay(): void;
|
||||
NavigateToStoreManual(): void;
|
||||
NavigateToStoreNewReleases(): void;
|
||||
NavigateToStoreOnSale(): void;
|
||||
ToggleSideMenu(sideMenu: SideMenu): void;
|
||||
CloseSideMenus(): void;
|
||||
export interface MenuStore {
|
||||
OpenSideMenu(sideMenu: SideMenu): void;
|
||||
OpenQuickAccessMenu(quickAccessTab?: QuickAccessTab): void;
|
||||
OpenMainMenu(): void;
|
||||
}
|
||||
|
||||
export interface WindowRouter {
|
||||
BrowserWindow: Window;
|
||||
MenuStore: MenuStore;
|
||||
Navigate(path: string): void;
|
||||
NavigateToChat(): void;
|
||||
NavigateToSteamWeb(url: string): void;
|
||||
NavigateBack(): void;
|
||||
NavigateToWebRoute(unknown?: any, unknown2?: any): void;
|
||||
}
|
||||
|
||||
export interface WindowStore {
|
||||
GamepadUIMainWindowInstance?: WindowRouter; // Current
|
||||
SteamUIWindows: WindowRouter[];
|
||||
OverlayWindows: WindowRouter[]; // Used by desktop GamepadUI
|
||||
}
|
||||
|
||||
export interface Router {
|
||||
WindowStore?: WindowStore;
|
||||
CloseSideMenus(): void;
|
||||
Navigate(path: string): void;
|
||||
NavigateToAppProperties(): void;
|
||||
NavigateToExternalWeb(url: string): void;
|
||||
NavigateToInvites(): void;
|
||||
NavigateToChat(): void;
|
||||
NavigateToLibraryTab(): void;
|
||||
NavigateToLayoutPreview(e: unknown): void;
|
||||
OpenPowerMenu(unknown?: any): void;
|
||||
get RunningApps(): AppOverview[];
|
||||
get MainRunningApp(): AppOverview | undefined;
|
||||
@@ -96,3 +106,51 @@ export const Router = findModuleChild((m: Module) => {
|
||||
if (m[prop]?.Navigate && m[prop]?.NavigationManager) return m[prop];
|
||||
}
|
||||
}) as Router;
|
||||
|
||||
export interface Navigation {
|
||||
Navigate(path: string): void;
|
||||
NavigateBack(): void;
|
||||
NavigateToAppProperties(): void;
|
||||
NavigateToExternalWeb(url: string): void;
|
||||
NavigateToInvites(): void;
|
||||
NavigateToChat(): void;
|
||||
NavigateToLibraryTab(): void;
|
||||
NavigateToLayoutPreview(e: unknown): void;
|
||||
NavigateToSteamWeb(url: string): void;
|
||||
NavigateToWebRoute(unknown?: any, unknown2?: any): void;
|
||||
OpenSideMenu(sideMenu: SideMenu): void;
|
||||
OpenQuickAccessMenu(quickAccessTab?: QuickAccessTab): void;
|
||||
OpenMainMenu(): void;
|
||||
OpenPowerMenu(unknown?: any): void;
|
||||
CloseSideMenus(): void;
|
||||
}
|
||||
|
||||
export const Navigation = {
|
||||
Navigate: Router.Navigate.bind(Router),
|
||||
NavigateBack: Router.WindowStore?.GamepadUIMainWindowInstance?.NavigateBack.bind(
|
||||
Router.WindowStore.GamepadUIMainWindowInstance,
|
||||
),
|
||||
NavigateToAppProperties: Router.NavigateToAppProperties.bind(Router),
|
||||
NavigateToExternalWeb: Router.NavigateToExternalWeb.bind(Router),
|
||||
NavigateToInvites: Router.NavigateToInvites.bind(Router),
|
||||
NavigateToChat: Router.NavigateToChat.bind(Router),
|
||||
NavigateToLibraryTab: Router.NavigateToLibraryTab.bind(Router),
|
||||
NavigateToLayoutPreview: Router.NavigateToLayoutPreview.bind(Router),
|
||||
NavigateToSteamWeb: Router.WindowStore?.GamepadUIMainWindowInstance?.NavigateToSteamWeb.bind(
|
||||
Router.WindowStore.GamepadUIMainWindowInstance,
|
||||
),
|
||||
NavigateToWebRoute: Router.WindowStore?.GamepadUIMainWindowInstance?.NavigateToWebRoute.bind(
|
||||
Router.WindowStore.GamepadUIMainWindowInstance,
|
||||
),
|
||||
OpenSideMenu: Router.WindowStore?.GamepadUIMainWindowInstance?.MenuStore.OpenSideMenu.bind(
|
||||
Router.WindowStore.GamepadUIMainWindowInstance.MenuStore,
|
||||
),
|
||||
OpenQuickAccessMenu: Router.WindowStore?.GamepadUIMainWindowInstance?.MenuStore.OpenQuickAccessMenu.bind(
|
||||
Router.WindowStore.GamepadUIMainWindowInstance.MenuStore,
|
||||
),
|
||||
OpenMainMenu: Router.WindowStore?.GamepadUIMainWindowInstance?.MenuStore.OpenMainMenu.bind(
|
||||
Router.WindowStore.GamepadUIMainWindowInstance.MenuStore,
|
||||
),
|
||||
CloseSideMenus: Router.CloseSideMenus.bind(Router),
|
||||
OpenPowerMenu: Router.OpenPowerMenu.bind(Router),
|
||||
} as Navigation;
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
export * from './Button';
|
||||
export * from './ButtonItem';
|
||||
export * from './Carousel';
|
||||
export * from './ControlsList';
|
||||
export * from './Dialog';
|
||||
export * from './DialogCheckbox';
|
||||
export * from './Dropdown';
|
||||
export * from './Field';
|
||||
export * from './Focusable';
|
||||
export * from './FocusRing';
|
||||
export * from './FooterLegend';
|
||||
export * from './Marquee';
|
||||
export * from './Menu';
|
||||
export * from './Modal';
|
||||
export * from './Panel';
|
||||
|
||||
@@ -42,6 +42,10 @@ export interface ToastData {
|
||||
contentClassName?: string;
|
||||
duration?: number;
|
||||
critical?: boolean;
|
||||
eType?: number;
|
||||
sound?: number;
|
||||
playSound?: boolean;
|
||||
showToast?: boolean;
|
||||
}
|
||||
|
||||
export interface Toaster {
|
||||
|
||||
@@ -16,6 +16,7 @@ export function findSP(): Window {
|
||||
// old (SP as host)
|
||||
if (document.title == 'SP') return window;
|
||||
// new (SP as popup)
|
||||
return FocusNavController.m_ActiveContext.m_rgGamepadNavigationTrees.find((x: any) => x.m_ID == 'root_1_').Root
|
||||
const context = FocusNavController.m_ActiveContext || FocusNavController.m_LastActiveContext;
|
||||
return context.m_rgGamepadNavigationTrees.find((x: any) => x.m_ID == 'root_1_').Root
|
||||
.Element.ownerDocument.defaultView;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user