Compare commits

..

1 Commits

Author SHA1 Message Date
AAGaming
d6e399fa46 feat(plugin): add menu and overlay patching api 2022-12-31 21:48:31 -05:00
25 changed files with 1560 additions and 2286 deletions

View File

@@ -1,192 +1,3 @@
## [3.21.5](https://github.com/SteamDeckHomebrew/decky-frontend-lib/compare/v3.21.4...v3.21.5) (2023-06-22)
### Bug Fixes
* missing parameter ([8b54ee9](https://github.com/SteamDeckHomebrew/decky-frontend-lib/commit/8b54ee990ee4d9b51174737979c35ab7ad92ed7a))
## [3.21.4](https://github.com/SteamDeckHomebrew/decky-frontend-lib/compare/v3.21.3...v3.21.4) (2023-06-22)
### Bug Fixes
* move the new file picker api as v2 ([8bfeae4](https://github.com/SteamDeckHomebrew/decky-frontend-lib/commit/8bfeae4b3593b2efa0aa075a0d9e0b5926cdf169))
## [3.21.3](https://github.com/SteamDeckHomebrew/decky-frontend-lib/compare/v3.21.2...v3.21.3) (2023-06-22)
### Bug Fixes
* total is not needed as a return value to plugins ([979a630](https://github.com/SteamDeckHomebrew/decky-frontend-lib/commit/979a630f2b02ac4a1ac19e38002244f9dfe97177))
## [3.21.2](https://github.com/SteamDeckHomebrew/decky-frontend-lib/compare/v3.21.1...v3.21.2) (2023-06-18)
### Bug Fixes
* **SidebarNavigation:** allow ReactNode for page title ([44d9b90](https://github.com/SteamDeckHomebrew/decky-frontend-lib/commit/44d9b90cdcfb9e6441fdb1e4b21fe844f1f29fd5))
## [3.21.1](https://github.com/SteamDeckHomebrew/decky-frontend-lib/compare/v3.21.0...v3.21.1) (2023-05-28)
### Bug Fixes
* **reorderable-list:** open label type from string to ReactNode ([fb5f043](https://github.com/SteamDeckHomebrew/decky-frontend-lib/commit/fb5f043ba9eeed0209960c8ae4ae597c2831b8c8))
# [3.21.0](https://github.com/SteamDeckHomebrew/decky-frontend-lib/compare/v3.20.7...v3.21.0) (2023-05-19)
### Bug Fixes
* **Utils:** return null when SP not found ([18d341f](https://github.com/SteamDeckHomebrew/decky-frontend-lib/commit/18d341f82acf843ff830d1bf3d44678f3f2eda52))
### Features
* **StaticClasses:** add new GamepadUI classes ([3ca8c43](https://github.com/SteamDeckHomebrew/decky-frontend-lib/commit/3ca8c43a542b1049c4e203ad186e555401fbfbfe))
## [3.20.7](https://github.com/SteamDeckHomebrew/decky-frontend-lib/compare/v3.20.6...v3.20.7) (2023-05-10)
### Bug Fixes
* **decky:** fix decky on latest beta ([74a7cba](https://github.com/SteamDeckHomebrew/decky-frontend-lib/commit/74a7cbaf94538c68a01f5fa707935c4d21570c5f))
## [3.20.6](https://github.com/SteamDeckHomebrew/decky-frontend-lib/compare/v3.20.5...v3.20.6) (2023-04-29)
### Bug Fixes
* **QuickAccessTab:** set decky tab ID to 999 ([5d5cb31](https://github.com/SteamDeckHomebrew/decky-frontend-lib/commit/5d5cb31638070deae9970a93c587b447d5e56559))
## [3.20.5](https://github.com/SteamDeckHomebrew/decky-frontend-lib/compare/v3.20.4...v3.20.5) (2023-04-04)
### Bug Fixes
* reorderable list no longer toggles on backout ([7e1182a](https://github.com/SteamDeckHomebrew/decky-frontend-lib/commit/7e1182a83f7fafbec6fe115a72f8b64b71c119a4))
## [3.20.4](https://github.com/SteamDeckHomebrew/decky-frontend-lib/compare/v3.20.3...v3.20.4) (2023-04-04)
### Bug Fixes
* **SteamSpinner:** oh apparently the class was moved outside the component for some reason?????? ([b8ddf3d](https://github.com/SteamDeckHomebrew/decky-frontend-lib/commit/b8ddf3d927401d04c0fde8ebc8960639369b8ad3))
## [3.20.3](https://github.com/SteamDeckHomebrew/decky-frontend-lib/compare/v3.20.2...v3.20.3) (2023-04-04)
### Bug Fixes
* **SteamSpinner:** fix the fix ([79d229b](https://github.com/SteamDeckHomebrew/decky-frontend-lib/commit/79d229be50d26e2510af0ea16cdf6644371a5967))
## [3.20.2](https://github.com/SteamDeckHomebrew/decky-frontend-lib/compare/v3.20.1...v3.20.2) (2023-04-04)
### Bug Fixes
* **SteamSpinner:** dont error on latest desktop beta ([19819b7](https://github.com/SteamDeckHomebrew/decky-frontend-lib/commit/19819b7a5bc9434fa802f4e8dca4f4cb6921df07))
## [3.20.1](https://github.com/SteamDeckHomebrew/decky-frontend-lib/compare/v3.20.0...v3.20.1) (2023-04-03)
### Bug Fixes
* **useQuickAccessVisible:** make it work again ([7dacb23](https://github.com/SteamDeckHomebrew/decky-frontend-lib/commit/7dacb23e8be7b1f076cdd0869a4e3a3902b07ec5))
# [3.20.0](https://github.com/SteamDeckHomebrew/decky-frontend-lib/compare/v3.19.2...v3.20.0) (2023-04-03)
### Bug Fixes
* **Field:** remove style ([53faf55](https://github.com/SteamDeckHomebrew/decky-frontend-lib/commit/53faf55df1484204e276cd21a32703c2d7809332))
* fixed missing export ([b480d39](https://github.com/SteamDeckHomebrew/decky-frontend-lib/commit/b480d397c4251f42c1f24ff5e74322d22e313f05))
* list didn't update on prop change ([b146eab](https://github.com/SteamDeckHomebrew/decky-frontend-lib/commit/b146eab8d7e1338afa54a168a2fd895e716c2bb2))
### Features
* added doc comments ([dfcb3be](https://github.com/SteamDeckHomebrew/decky-frontend-lib/commit/dfcb3bec19900e099ae3766771e120dbd4f229f5))
* added reorderable list and updated fieldProps ([3c171cf](https://github.com/SteamDeckHomebrew/decky-frontend-lib/commit/3c171cfb8ff18ed02eeb569a183c9d43fd0b4f57))
* changed ReorderableList to working version ([5b166d6](https://github.com/SteamDeckHomebrew/decky-frontend-lib/commit/5b166d6db879200b049e872cba327957ba5fb705))
* made requested changes and ran prettier ([b1b2f4f](https://github.com/SteamDeckHomebrew/decky-frontend-lib/commit/b1b2f4fa2da755efd65b82b15b52196f89fb09c0))
* refactoring mostly complete ([cea315a](https://github.com/SteamDeckHomebrew/decky-frontend-lib/commit/cea315a52c285b31ad4e5d0a03104c674ae4f7f5))
* reorderable list now saves on backout ([b1591f8](https://github.com/SteamDeckHomebrew/decky-frontend-lib/commit/b1591f86bbd36e160818626760e0717ee50878e0))
* **ReorderableList:** add animations, clean up ([26fae13](https://github.com/SteamDeckHomebrew/decky-frontend-lib/commit/26fae13c8ebd3f11f134c3bc0edfc971afd42fff))
* support for non-interactable reordering ([dbd01b1](https://github.com/SteamDeckHomebrew/decky-frontend-lib/commit/dbd01b11cafe9b102cc371b9812f99aec718d106))
* support for user specified icon ([47a6fdd](https://github.com/SteamDeckHomebrew/decky-frontend-lib/commit/47a6fddc89b8f9110252c5e19a6e95152c367dbf))
## [3.19.2](https://github.com/SteamDeckHomebrew/decky-frontend-lib/compare/v3.19.1...v3.19.2) (2023-03-07)
### Bug Fixes
* **Item:** add highlightOnFocus prop ([c53f87b](https://github.com/SteamDeckHomebrew/decky-frontend-lib/commit/c53f87b4a9273b377853bfff1d27474ebd6e564a))
## [3.19.1](https://github.com/SteamDeckHomebrew/decky-frontend-lib/compare/v3.19.0...v3.19.1) (2023-02-23)
### Bug Fixes
* refactoring to fix for feb 22 2023 beta ([0b6dc24](https://github.com/SteamDeckHomebrew/decky-frontend-lib/commit/0b6dc24c0da2d7644e185425e975787657f8bba1))
# [3.19.0](https://github.com/SteamDeckHomebrew/decky-frontend-lib/compare/v3.18.11...v3.19.0) (2023-02-22)
### Features
* added reorderable list and updated fieldProps ([#57](https://github.com/SteamDeckHomebrew/decky-frontend-lib/issues/57)) ([5a074b5](https://github.com/SteamDeckHomebrew/decky-frontend-lib/commit/5a074b5bb68c675c484a7b693f67a67488be9bcf))
## [3.18.11](https://github.com/SteamDeckHomebrew/decky-frontend-lib/compare/v3.18.10...v3.18.11) (2023-02-18)
### Bug Fixes
* **Navigation:** fix NavigateToExternalWeb ([82214fe](https://github.com/SteamDeckHomebrew/decky-frontend-lib/commit/82214fef4c0a383776631fbb754550fe69f9000d))
* **Navigation:** fix NavigateToExternalWeb ([c53d7f8](https://github.com/SteamDeckHomebrew/decky-frontend-lib/commit/c53d7f8448d8aad76dc699f1f309bdd547ee14df))
## [3.18.10](https://github.com/SteamDeckHomebrew/decky-frontend-lib/compare/v3.18.9...v3.18.10) (2023-01-17)
### Bug Fixes
* **SuspensefulImage:** fix changing src ([9723854](https://github.com/SteamDeckHomebrew/decky-frontend-lib/commit/9723854ddca53d7708e1effbddec9e5ead22d5de))
## [3.18.9](https://github.com/SteamDeckHomebrew/decky-frontend-lib/compare/v3.18.8...v3.18.9) (2023-01-16)
### Bug Fixes
* **Navigation:** fix on stable ([4affd4a](https://github.com/SteamDeckHomebrew/decky-frontend-lib/commit/4affd4aaec088f01d0f30af48cb4daa34acf26b1))
## [3.18.8](https://github.com/SteamDeckHomebrew/decky-frontend-lib/compare/v3.18.7...v3.18.8) (2023-01-16)
### Bug Fixes
* **Navigation:** fix timing issue in decky-loader ([58b69f0](https://github.com/SteamDeckHomebrew/decky-frontend-lib/commit/58b69f0d6c43356c4f0ed183802d5bf7fb80e978))
## [3.18.7](https://github.com/SteamDeckHomebrew/decky-frontend-lib/compare/v3.18.6...v3.18.7) (2023-01-16)
### Bug Fixes
* un-break navigation on stable ([2e66e5a](https://github.com/SteamDeckHomebrew/decky-frontend-lib/commit/2e66e5a555f44009d24e332eca82453ba930baf7))
## [3.18.6](https://github.com/SteamDeckHomebrew/decky-frontend-lib/compare/v3.18.5...v3.18.6) (2023-01-13)
### Bug Fixes
* **Router:** fix Navigation for the millionth time ([aac2d52](https://github.com/SteamDeckHomebrew/decky-frontend-lib/commit/aac2d520a68b1074ba1ae988d6c92f7881a296d7))
## [3.18.5](https://github.com/SteamDeckHomebrew/decky-frontend-lib/compare/v3.18.4...v3.18.5) (2022-12-21)
### Bug Fixes
* fixed prop interfaces ([#70](https://github.com/SteamDeckHomebrew/decky-frontend-lib/issues/70)) ([0b50f2c](https://github.com/SteamDeckHomebrew/decky-frontend-lib/commit/0b50f2cf0baa76fc00aa0a41a8435d7a512bff19))
## [3.18.4](https://github.com/SteamDeckHomebrew/decky-frontend-lib/compare/v3.18.3...v3.18.4) (2022-12-16) ## [3.18.4](https://github.com/SteamDeckHomebrew/decky-frontend-lib/compare/v3.18.3...v3.18.4) (2022-12-16)

View File

@@ -2,9 +2,9 @@
Library used to develop plugins used for use with [decky-loader](https://github.com/SteamDeckHomebrew/decky-loader). Library used to develop plugins used for use with [decky-loader](https://github.com/SteamDeckHomebrew/decky-loader).
## Decky Loader Discord [![Chat](https://img.shields.io/discord/960281551428522045?color=%235865F2&label=discord)](https://deckbrew.xyz/discord) ## Decky Loader Discord [![Chat](https://img.shields.io/badge/chat-on%20discord-7289da.svg)](https://discord.gg/ZU74G2NJzk)
Please [contact the developers here](https://deckbrew.xyz/discord) for questions and support that cannot be addressed via a Github issue. Please contact the developers here for questions and support that cannot be addressed via a Github issue.
## Developers and Contributors ## Developers and Contributors

View File

@@ -1,6 +1,6 @@
{ {
"name": "decky-frontend-lib", "name": "decky-frontend-lib",
"version": "3.21.5", "version": "3.18.4",
"description": "A library for building decky plugins", "description": "A library for building decky plugins",
"main": "dist/index.js", "main": "dist/index.js",
"types": "dist/index.d.ts", "types": "dist/index.d.ts",
@@ -9,7 +9,7 @@
"scripts": { "scripts": {
"build": "shx rm -rf dist && tsc -b", "build": "shx rm -rf dist && tsc -b",
"dev": "tsc -b -w", "dev": "tsc -b -w",
"docs": "typedoc --theme wiki-js --tsconfig ./tsconfig.json src/**/*", "docs": "typedoc --tsconfig ./tsconfig.json src/**/*",
"test": "echo 'No tests for now!'", "test": "echo 'No tests for now!'",
"prepare": "husky install", "prepare": "husky install",
"commit": "git-cz" "commit": "git-cz"
@@ -59,11 +59,11 @@
"semantic-release": "^19.0.3", "semantic-release": "^19.0.3",
"shx": "^0.3.4", "shx": "^0.3.4",
"ts-jest": "^27.1.4", "ts-jest": "^27.1.4",
"typedoc": "^0.23.24", "typedoc": "^0.23.15",
"typedoc-plugin-markdown": "^3.13.6",
"typedoc-plugin-mdn-links": "^2.0.0", "typedoc-plugin-mdn-links": "^2.0.0",
"typedoc-plugin-missing-exports": "^1.0.0", "typedoc-plugin-missing-exports": "^1.0.0",
"typedoc-wikijs-theme": "^1.0.5", "typescript": "^4.6.3"
"typescript": "^4.9.5"
}, },
"pnpm": { "pnpm": {
"peerDependencyRules": { "peerDependencyRules": {

3072
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,183 +0,0 @@
import { Fragment, JSXElementConstructor, ReactElement, ReactNode, useEffect, useState } from 'react';
import { Field, FieldProps, Focusable, GamepadButton } from '../deck-components';
/**
* A ReorderableList entry of type <T>.
* @param label The name of this entry in the list.
* @param data Optional data to connect to this entry.
* @param position The position of this entry in the list.
*/
export type ReorderableEntry<T> = {
label: ReactNode;
data?: T;
position: number;
};
/**
* Properties for a ReorderableList component of type <T>.
*
* @param animate If the list should animate. @default true
*/
export type ReorderableListProps<T> = {
entries: ReorderableEntry<T>[];
onSave: (entries: ReorderableEntry<T>[]) => void;
interactables?: JSXElementConstructor<{ entry: ReorderableEntry<T> }>;
fieldProps?: FieldProps;
animate?: boolean;
};
/**
* A component for creating reorderable lists.
*
* See an example implementation {@linkplain https://github.com/Tormak9970/Component-Testing-Plugin/blob/main/src/testing-window/ReorderableListTest.tsx here}.
*/
export function ReorderableList<T>(props: ReorderableListProps<T>) {
if (props.animate === undefined) props.animate = true;
const [entryList, setEntryList] = useState<ReorderableEntry<T>[]>(
props.entries.sort((a: ReorderableEntry<T>, b: ReorderableEntry<T>) => a.position - b.position),
);
const [reorderEnabled, setReorderEnabled] = useState<boolean>(false);
useEffect(() => {
setEntryList(props.entries.sort((a: ReorderableEntry<T>, b: ReorderableEntry<T>) => a.position - b.position));
}, [props.entries]);
function toggleReorderEnabled(): void {
let newReorderValue = !reorderEnabled;
setReorderEnabled(newReorderValue);
if (!newReorderValue) {
props.onSave(entryList);
}
}
function saveOnBackout(e: Event) {
const event = e as CustomEvent;
if (event.detail.button == GamepadButton.CANCEL && reorderEnabled) {
setReorderEnabled(!reorderEnabled);
props.onSave(entryList);
}
}
return (
<Fragment>
<div
style={{
width: 'inherit',
height: 'inherit',
flex: '1 1 1px',
scrollPadding: '48px 0px',
display: 'flex',
flexDirection: 'column',
justifyContent: 'flex-start',
alignContent: 'stretch',
}}
>
<Focusable
onSecondaryButton={toggleReorderEnabled}
onSecondaryActionDescription={reorderEnabled ? 'Save Order' : 'Reorder'}
onClick={toggleReorderEnabled}
onButtonDown={saveOnBackout}
>
{entryList.map((entry: ReorderableEntry<T>) => (
<ReorderableItem
animate={props.animate!}
listData={entryList}
entryData={entry}
reorderEntryFunc={setEntryList}
reorderEnabled={reorderEnabled}
fieldProps={props.fieldProps}
>
{props.interactables ? <props.interactables entry={entry} /> : null}
</ReorderableItem>
))}
</Focusable>
</div>
</Fragment>
);
}
/**
* Properties for a ReorderableItem component of type <T>
*/
export type ReorderableListEntryProps<T> = {
fieldProps?: FieldProps;
listData: ReorderableEntry<T>[];
entryData: ReorderableEntry<T>;
reorderEntryFunc: CallableFunction;
reorderEnabled: boolean;
animate: boolean;
children: ReactElement | null;
};
function ReorderableItem<T>(props: ReorderableListEntryProps<T>) {
const [isSelected, _setIsSelected] = useState<boolean>(false);
const [isSelectedLastFrame, setIsSelectedLastFrame] = useState<boolean>(false);
const listEntries = props.listData;
function onReorder(e: Event): void {
if (!props.reorderEnabled) return;
const event = e as CustomEvent;
const currentIdx = listEntries.findIndex((entryData: ReorderableEntry<T>) => entryData === props.entryData);
const currentIdxValue = listEntries[currentIdx];
if (currentIdx < 0) return;
let targetPosition: number = -1;
if (event.detail.button == GamepadButton.DIR_DOWN) {
targetPosition = currentIdxValue.position + 1;
} else if (event.detail.button == GamepadButton.DIR_UP) {
targetPosition = currentIdxValue.position - 1;
}
if (targetPosition >= listEntries.length || targetPosition < 0) return;
let otherToUpdate = listEntries.find((entryData: ReorderableEntry<T>) => entryData.position === targetPosition);
if (!otherToUpdate) return;
let currentPosition = currentIdxValue.position;
currentIdxValue.position = otherToUpdate.position;
otherToUpdate.position = currentPosition;
props.reorderEntryFunc(
[...listEntries].sort((a: ReorderableEntry<T>, b: ReorderableEntry<T>) => a.position - b.position),
);
}
async function setIsSelected(val: boolean) {
_setIsSelected(val);
// Wait 3 frames, then set. I have no idea why, but if you dont wait long enough it doesn't work.
for (let i = 0; i < 3; i++) await new Promise((res) => requestAnimationFrame(res));
setIsSelectedLastFrame(val);
}
return (
<div
style={
props.animate
? {
transition:
isSelected || isSelectedLastFrame
? ''
: 'transform 0.3s cubic-bezier(0.25, 1, 0.5, 1), opacity 0.3s cubic-bezier(0.25, 1, 0.5, 1)', // easeOutQuart https://easings.net/#easeOutQuart
transform: !props.reorderEnabled || isSelected ? 'scale(1)' : 'scale(0.9)',
opacity: !props.reorderEnabled || isSelected ? 1 : 0.7,
}
: {}
}
>
<Field
label={props.entryData.label}
{...props.fieldProps}
focusable={!props.children}
onButtonDown={onReorder}
onGamepadBlur={() => setIsSelected(false)}
onGamepadFocus={() => setIsSelected(true)}
>
<Focusable style={{ display: 'flex', width: '100%', position: 'relative' }}>{props.children}</Focusable>
</Field>
</div>
);
}

View File

@@ -13,8 +13,6 @@ export const SuspensefulImage: FC<SuspensefulImageProps> = (props) => {
const [error, setError] = useState(false); const [error, setError] = useState(false);
useEffect(() => { useEffect(() => {
setLoading(true);
setError(false);
const img = new Image(); const img = new Image();
img.src = props.src || ''; img.src = props.src || '';
img.addEventListener('load', () => { img.addEventListener('load', () => {
@@ -23,7 +21,7 @@ export const SuspensefulImage: FC<SuspensefulImageProps> = (props) => {
img.addEventListener('error', () => { img.addEventListener('error', () => {
setError(true); setError(true);
}); });
}, [props.src]); }, []);
return loading ? ( return loading ? (
<div <div

View File

@@ -1,3 +1,2 @@
export * from './SuspensefulImage'; export * from './SuspensefulImage';
export * from './ColorPickerModal'; export * from './ColorPickerModal';
export * from './ReorderableList';

View File

@@ -1,9 +1,18 @@
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { getGamepadNavigationTrees } from '../utils';
declare global {
var FocusNavController: any;
}
function getQuickAccessWindow(): Window | null { function getQuickAccessWindow(): Window | null {
const navTrees = getGamepadNavigationTrees(); try {
return navTrees.find((tree: any) => tree?.id === 'QuickAccess-NA')?.m_Root?.m_element?.ownerDocument.defaultView ?? null; 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);
return null;
}
} }
/** /**

View File

@@ -7,10 +7,9 @@ export interface ButtonItemProps extends ItemProps {
onClick?(e: MouseEvent): void; onClick?(e: MouseEvent): void;
disabled?: boolean; disabled?: boolean;
} }
export const ButtonItem =
CommonUIModule.ButtonField || export const ButtonItem = Object.values(CommonUIModule).find(
(Object.values(CommonUIModule).find( (mod: any) =>
(mod: any) => mod?.render?.toString()?.includes('"highlightOnFocus","childrenContainerWidth"') ||
mod?.render?.toString()?.includes('"highlightOnFocus","childrenContainerWidth"') || mod?.render?.toString()?.includes('childrenContainerWidth:"min"'),
mod?.render?.toString()?.includes('childrenContainerWidth:"min"'), ) as FC<ButtonItemProps>;
) as FC<ButtonItemProps>);

View File

@@ -8,5 +8,4 @@ export interface ItemProps {
bottomSeparator?: 'standard' | 'thick' | 'none'; bottomSeparator?: 'standard' | 'thick' | 'none';
indentLevel?: number; indentLevel?: number;
tooltip?: string; tooltip?: string;
highlightOnFocus?: boolean;
} }

View File

@@ -1,4 +1,4 @@
import { CSSProperties, FC } from 'react'; import { CSSProperties, FC, ReactNode } from 'react';
import { findModuleChild } from '../webpack'; import { findModuleChild } from '../webpack';
@@ -12,7 +12,7 @@ export interface MarqueeProps {
resetOnPause?: boolean; resetOnPause?: boolean;
style?: CSSProperties; style?: CSSProperties;
className?: string; className?: string;
children: React.ReactNode; children: ReactNode;
} }
export const Marquee: FC<MarqueeProps> = findModuleChild((m) => { export const Marquee: FC<MarqueeProps> = findModuleChild((m) => {

View File

@@ -41,8 +41,8 @@ export const MenuGroup: FC<MenuGroupProps> = findModuleChild((m) => {
for (let prop in m) { for (let prop in m) {
if ( if (
(m[prop]?.toString()?.includes?.('bInGamepadUI:') && (m[prop]?.toString()?.includes('bInGamepadUI:') &&
fakeRenderComponent(() => m[prop]({overview: {appid: 7}}))?.type?.prototype?.RenderSubMenu) || fakeRenderComponent(() => m[prop]())?.type?.prototype?.RenderSubMenu) ||
(m[prop]?.prototype?.RenderSubMenu && m[prop]?.prototype?.ShowSubMenu) (m[prop]?.prototype?.RenderSubMenu && m[prop]?.prototype?.ShowSubMenu)
) { ) {
return m[prop]; return m[prop];

View File

@@ -123,7 +123,7 @@ export const ModalRoot = (Object.values(
if (typeof m !== 'object') return false; if (typeof m !== 'object') return false;
for (let prop in m) { for (let prop in m) {
if (m[prop]?.m_mapModalManager && Object.values(m)?.find((x: any) => x?.type)) { if (m[prop]?.m_mapModalManager) {
return true; return true;
} }
} }

View File

@@ -1,11 +1,10 @@
import { FC, ReactNode } from 'react'; import { FC } from 'react';
import { findModuleChild } from '../webpack'; import { findModuleChild } from '../webpack';
export interface PanelSectionProps { export interface PanelSectionProps {
title?: string; title?: string;
spinner?: boolean; spinner?: boolean;
children?: ReactNode;
} }
const [panelSection, mod] = findModuleChild((mod: any) => { const [panelSection, mod] = findModuleChild((mod: any) => {
@@ -19,10 +18,6 @@ const [panelSection, mod] = findModuleChild((mod: any) => {
export const PanelSection = panelSection as FC<PanelSectionProps>; export const PanelSection = panelSection as FC<PanelSectionProps>;
export interface PanelSectionRowProps { export const PanelSectionRow = Object.values(mod).filter(
children?: ReactNode; (exp: any) => !exp?.toString()?.includes('.PanelSection'),
} )[0] as FC;
// New as of Feb 22 2023 Beta || Old
export const PanelSectionRow =
mod.PanelSectionRow ||
(Object.values(mod).filter((exp: any) => !exp?.toString()?.includes('.PanelSection'))[0] as FC<PanelSectionRowProps>);

View File

@@ -1,4 +1,3 @@
import { sleep } from '../utils';
import { Module, findModuleChild } from '../webpack'; import { Module, findModuleChild } from '../webpack';
export enum SideMenu { export enum SideMenu {
@@ -15,8 +14,7 @@ export enum QuickAccessTab {
Settings, Settings,
Perf, Perf,
Help, Help,
Music, Decky,
Decky = 999,
} }
export enum DisplayStatus { export enum DisplayStatus {
@@ -78,6 +76,7 @@ export interface WindowRouter {
NavigateToChat(): void; NavigateToChat(): void;
NavigateToSteamWeb(url: string): void; NavigateToSteamWeb(url: string): void;
NavigateBack(): void; NavigateBack(): void;
NavigateToWebRoute(unknown?: any, unknown2?: any): void;
} }
export interface WindowStore { export interface WindowStore {
@@ -118,6 +117,7 @@ export interface Navigation {
NavigateToLibraryTab(): void; NavigateToLibraryTab(): void;
NavigateToLayoutPreview(e: unknown): void; NavigateToLayoutPreview(e: unknown): void;
NavigateToSteamWeb(url: string): void; NavigateToSteamWeb(url: string): void;
NavigateToWebRoute(unknown?: any, unknown2?: any): void;
OpenSideMenu(sideMenu: SideMenu): void; OpenSideMenu(sideMenu: SideMenu): void;
OpenQuickAccessMenu(quickAccessTab?: QuickAccessTab): void; OpenQuickAccessMenu(quickAccessTab?: QuickAccessTab): void;
OpenMainMenu(): void; OpenMainMenu(): void;
@@ -125,62 +125,32 @@ export interface Navigation {
CloseSideMenus(): void; CloseSideMenus(): void;
} }
export let Navigation = {} as Navigation; export const Navigation = {
Navigate: Router.Navigate.bind(Router),
try { NavigateBack: Router.WindowStore?.GamepadUIMainWindowInstance?.NavigateBack.bind(
(async () => { Router.WindowStore.GamepadUIMainWindowInstance,
let InternalNavigators: any = {}; ),
if (!Router.NavigateToAppProperties || (Router as unknown as any).deckyShim) { NavigateToAppProperties: Router.NavigateToAppProperties.bind(Router),
function initInternalNavigators() { NavigateToExternalWeb: Router.NavigateToExternalWeb.bind(Router),
try { NavigateToInvites: Router.NavigateToInvites.bind(Router),
InternalNavigators = findModuleChild((m: any) => { NavigateToChat: Router.NavigateToChat.bind(Router),
if (typeof m !== 'object') return undefined; NavigateToLibraryTab: Router.NavigateToLibraryTab.bind(Router),
for (let prop in m) { NavigateToLayoutPreview: Router.NavigateToLayoutPreview.bind(Router),
if (m[prop]?.GetNavigator) { NavigateToSteamWeb: Router.WindowStore?.GamepadUIMainWindowInstance?.NavigateToSteamWeb.bind(
return m[prop]; Router.WindowStore.GamepadUIMainWindowInstance,
} ),
} NavigateToWebRoute: Router.WindowStore?.GamepadUIMainWindowInstance?.NavigateToWebRoute.bind(
})?.GetNavigator(); Router.WindowStore.GamepadUIMainWindowInstance,
} catch (e) { ),
console.error('[DFL:Router]: Failed to init internal navigators, trying again'); OpenSideMenu: Router.WindowStore?.GamepadUIMainWindowInstance?.MenuStore.OpenSideMenu.bind(
} Router.WindowStore.GamepadUIMainWindowInstance.MenuStore,
} ),
initInternalNavigators(); OpenQuickAccessMenu: Router.WindowStore?.GamepadUIMainWindowInstance?.MenuStore.OpenQuickAccessMenu.bind(
while (!InternalNavigators?.AppProperties) { Router.WindowStore.GamepadUIMainWindowInstance.MenuStore,
console.log('[DFL:Router]: Trying to init internal navigators again'); ),
await sleep(100); OpenMainMenu: Router.WindowStore?.GamepadUIMainWindowInstance?.MenuStore.OpenMainMenu.bind(
initInternalNavigators(); Router.WindowStore.GamepadUIMainWindowInstance.MenuStore,
} ),
} CloseSideMenus: Router.CloseSideMenus.bind(Router),
const newNavigation = { OpenPowerMenu: Router.OpenPowerMenu.bind(Router),
Navigate: Router.Navigate?.bind(Router), } as Navigation;
NavigateBack: Router.WindowStore?.GamepadUIMainWindowInstance?.NavigateBack?.bind(
Router.WindowStore.GamepadUIMainWindowInstance,
),
NavigateToAppProperties: InternalNavigators?.AppProperties || Router.NavigateToAppProperties?.bind(Router),
NavigateToExternalWeb: InternalNavigators?.ExternalWeb || Router.NavigateToExternalWeb?.bind(Router),
NavigateToInvites: InternalNavigators?.Invites || Router.NavigateToInvites?.bind(Router),
NavigateToChat: Router.NavigateToChat?.bind(Router),
NavigateToLibraryTab: InternalNavigators?.LibraryTab || Router.NavigateToLibraryTab?.bind(Router),
NavigateToLayoutPreview: Router.NavigateToLayoutPreview?.bind(Router),
NavigateToSteamWeb: Router.WindowStore?.GamepadUIMainWindowInstance?.NavigateToSteamWeb?.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;
Object.assign(Navigation, newNavigation);
})();
} catch (e) {
console.error('[DFL:Router]: Error initializing Navigation interface', e);
}

View File

@@ -3,7 +3,7 @@ import { ReactNode, VFC } from 'react';
import { Module, findModuleChild } from '../webpack'; import { Module, findModuleChild } from '../webpack';
export interface SidebarNavigationPage { export interface SidebarNavigationPage {
title: ReactNode; title: string;
content: ReactNode; content: ReactNode;
icon?: ReactNode; icon?: ReactNode;
visible?: boolean; visible?: boolean;
@@ -16,7 +16,7 @@ export interface SidebarNavigationPage {
export interface SidebarNavigationProps { export interface SidebarNavigationProps {
title?: string; title?: string;
pages: (SidebarNavigationPage | 'separator')[]; pages: SidebarNavigationPage[];
showTitle?: boolean; showTitle?: boolean;
disableRouteReporting?: boolean; disableRouteReporting?: boolean;
page?: string; page?: string;

View File

@@ -25,7 +25,6 @@ export interface SliderFieldProps extends ItemProps {
valueSuffix?: string; valueSuffix?: string;
minimumDpadGranularity?: number; minimumDpadGranularity?: number;
onChange?(value: number): void; onChange?(value: number): void;
className?: string;
} }
export const SliderField = Object.values(CommonUIModule).find((mod: any) => export const SliderField = Object.values(CommonUIModule).find((mod: any) =>

View File

@@ -217,14 +217,6 @@ export type AppLanguages = {
strShortName: string; strShortName: string;
}; };
export type LogoPinPositions = 'BottomLeft' | 'UpperLeft' | 'CenterCenter' | 'UpperCenter' | 'BottomCenter';
export interface LogoPosition {
pinnedPosition: LogoPinPositions;
nWidthPct: number;
nHeightPct: number;
};
export interface AppDetails { export interface AppDetails {
achievements: AppAchievements; achievements: AppAchievements;
bCanMoveInstallFolder: boolean; bCanMoveInstallFolder: boolean;
@@ -300,18 +292,9 @@ export interface AppDetails {
vecMusicAlbums: any[]; vecMusicAlbums: any[];
vecPlatforms: string[]; vecPlatforms: string[];
vecScreenShots: any[]; vecScreenShots: any[];
libraryAssets?: {
logoPosition?: LogoPosition;
};
} }
export interface SteamAppOverview { export interface SteamAppOverview {
display_name: string; display_name: string;
gameid: string; gameid: string;
appid: number;
icon_hash: string;
third_party_mod?: boolean;
selected_clientid?: string;
BIsModOrShortcut: () => boolean;
BIsShortcut: () => boolean;
} }

View File

@@ -5,9 +5,7 @@ import { findModuleChild } from '../webpack';
export const SteamSpinner = findModuleChild((m) => { export const SteamSpinner = findModuleChild((m) => {
if (typeof m !== 'object') return undefined; if (typeof m !== 'object') return undefined;
for (let prop in m) { for (let prop in m) {
if ( if (m[prop]?.toString()?.includes('Steam Spinner') && m[prop].toString().includes('PreloadThrobber'))
m[prop]?.toString?.()?.includes('Steam Spinner') && m[prop]?.toString?.()?.includes('src')
)
return m[prop]; return m[prop];
} }
}) as FC<SVGAttributes<SVGElement>>; }) as FC<SVGAttributes<SVGElement>>;

View File

@@ -4,7 +4,6 @@ import { CommonUIModule } from '../webpack';
import { ItemProps } from './Item'; import { ItemProps } from './Item';
export interface ToggleFieldProps extends ItemProps { export interface ToggleFieldProps extends ItemProps {
highlightOnFocus?: boolean;
checked: boolean; checked: boolean;
disabled?: boolean; disabled?: boolean;
onChange?(checked: boolean): void; onChange?(checked: boolean): void;

View File

@@ -26,56 +26,8 @@ export * from './Toggle';
export * from './ToggleField'; export * from './ToggleField';
export * from './SteamClient'; export * from './SteamClient';
import { AppDetails, LogoPosition, SteamAppOverview, SteamClient } from './SteamClient'; import {SteamClient} from './SteamClient'
declare global { declare global {
var SteamClient: SteamClient; var SteamClient: SteamClient;
interface Window {
LocalizationManager: {
m_mapTokens: Map<string, string>;
m_mapFallbackTokens: Map<string, string>;
m_rgLocalesToUse: string[];
};
App: {
m_CurrentUser: {
bIsLimited: boolean;
bIsOfflineMode: boolean;
bSupportAlertActive: boolean;
bCanInviteFriends: boolean;
NotificationCounts: {
comments: number;
inventory_items: number;
invites: number;
gifts: number;
offline_messages: number;
trade_offers: number;
async_game_updates: number;
moderator_messages: number;
help_request_replies: number;
};
strAccountBalance: string;
strAccountName: string;
strSteamID: string;
};
};
appStore: {
GetAppOverviewByAppID: (appId: number) => SteamAppOverview | null;
GetCustomVerticalCapsuleURLs: (app: SteamAppOverview) => string[];
GetCustomLandcapeImageURLs: (app: SteamAppOverview) => string[];
GetCustomHeroImageURLs: (app: SteamAppOverview) => string[];
GetCustomLogoImageURLs: (app: SteamAppOverview) => string[];
GetLandscapeImageURLForApp: (app: SteamAppOverview) => string;
GetVerticalCapsuleURLForApp: (app: SteamAppOverview) => string;
GetCachedLandscapeImageURLForApp: (app: SteamAppOverview) => string;
GetCachedVerticalImageURLForApp: (app: SteamAppOverview) => string;
GetPregeneratedVerticalCapsuleForApp: (app: SteamAppOverview) => string;
GetIconURLForApp: (app: SteamAppOverview) => string;
};
appDetailsStore: {
GetAppDetails: (appId: number) => AppDetails | null;
GetCustomLogoPosition: (app: SteamAppOverview) => LogoPosition | null;
SaveCustomLogoPosition: (app: SteamAppOverview, logoPositions: LogoPosition) => any;
};
}
} }

View File

@@ -441,69 +441,6 @@ type AppDetailsClasses = Record<
string string
>; >;
type GamepadUIClasses = Record<
| 'duration-app-launch'
| 'TransitionMenuDelay'
| 'PanelSection'
| 'PanelSectionTitle'
| 'Text'
| 'PanelSectionRow'
| 'Label'
| 'ComingSoon'
| 'LowBattery'
| 'ReallyLow'
| 'LowBatteryGauge'
| 'Remaining'
| 'EmptyNotifications'
| 'BatterySectionContainer'
| 'BatteryIcon'
| 'BatteryPercentageLabel'
| 'BatteryDetailsLabels'
| 'BatteryProjectedValue'
| 'BatteryProjectedLabel'
| 'ViewPlaceholder'
| 'FullHeight'
| 'Title'
| 'Container'
| 'Open'
| 'QuickAccessMenu'
| 'HeaderContainer'
| 'Menu'
| 'HeaderAndFooterVisible'
| 'TabContentColumn'
| 'Tabs'
| 'Tab'
| 'Selected'
| 'ItemFocusAnim-darkerGrey'
| 'ItemFocusAnim-darkerGrey-nocolor'
| 'VoiceTab'
| 'ItemFocusAnim-green'
| 'Blocked'
| 'TabPanelHidden'
| 'FriendsTitle'
| 'FriendsListTabPanel'
| 'PanelOuterNav'
| 'PanelExitAnchor'
| 'TabGroupPanel'
| 'FooterBoxShadow'
| 'AllTabContents'
| 'ContentTransition'
| 'ActiveTab'
| 'Up'
| 'Enter'
| 'EnterActive'
| 'Exit'
| 'ExitActive'
| 'Down'
| 'KeyboardButton'
| 'ItemFocusAnim-darkGrey'
| 'ItemFocusAnim-grey'
| 'ItemFocusAnimBorder-darkGrey'
| 'focusAnimation'
| 'hoverAnimation',
string
>;
export const quickAccessMenuClasses: QuickAccessMenuClasses = findModule( export const quickAccessMenuClasses: QuickAccessMenuClasses = findModule(
(mod) => typeof mod === 'object' && mod?.Title?.includes('quickaccessmenu'), (mod) => typeof mod === 'object' && mod?.Title?.includes('quickaccessmenu'),
); );
@@ -522,7 +459,7 @@ export const gamepadDialogClasses: GamepadDialogClasses = findModule(
(mod) => typeof mod === 'object' && mod?.GamepadDialogContent?.includes('gamepaddialog'), (mod) => typeof mod === 'object' && mod?.GamepadDialogContent?.includes('gamepaddialog'),
); );
export const quickAccessControlsClasses: QuickAccessControlsClasses = findModule( export const quickAccessControlsClasses: QuickAccessControlsClasses = findModule(
(mod) => typeof mod === 'object' && typeof mod?.PanelSection === 'string' && mod?.PanelSection?.includes('quickaccesscontrols'), (mod) => typeof mod === 'object' && mod?.PanelSection?.includes('quickaccesscontrols'),
); );
export const updaterFieldClasses: UpdaterFieldClasses = findModule( export const updaterFieldClasses: UpdaterFieldClasses = findModule(
(mod) => typeof mod === 'object' && mod?.OOBEUpdateStatusContainer?.includes('updaterfield'), (mod) => typeof mod === 'object' && mod?.OOBEUpdateStatusContainer?.includes('updaterfield'),
@@ -539,6 +476,3 @@ export const appDetailsHeaderClasses: AppDetailsHeaderClasses = findModule(
export const appDetailsClasses: AppDetailsClasses = findModule( export const appDetailsClasses: AppDetailsClasses = findModule(
(mod) => typeof mod === 'object' && mod?.HeaderLoaded?.includes('appdetails_'), (mod) => typeof mod === 'object' && mod?.HeaderLoaded?.includes('appdetails_'),
); );
export const gamepadUIClasses: GamepadUIClasses = findModule(
(mod) => typeof mod === 'object' && mod?.BasicUiRoot?.includes('gamepadui_'),
);

View File

@@ -21,6 +21,52 @@ interface ServerResponseError {
export type ServerResponse<TRes> = ServerResponseSuccess<TRes> | ServerResponseError; export type ServerResponse<TRes> = ServerResponseSuccess<TRes> | ServerResponseError;
export interface MainMenuItem {
/** Called when clicking this menu item, useful if you do not want to navigate to a route.
* For navigating to a route, use route instead. */
action?: () => void;
/** The route this menu item navigates to @example "/steamweb" */
route?: string;
/** Props for the route this item navigates to. Currently only used for /steamweb to determine what webpack to navigate to @example {url: 'https://store.steampowered.com/'} */
routeState?: any;
/** Label for this menu item, @example "Store" */
label: ReactNode;
/** Runs when this item is selected. Defaults to setting the focused app to null.
* If you override this you must also call the original to retain proper behaivour */
onFocus?: () => void;
/** Icon of this menu item, you probably want a react SVG node */
children: ReactNode;
}
export interface CustomMainMenuItem extends MainMenuItem {
index: number;
}
export interface MainMenuItemReactNode {
props: MainMenuItem;
type: ComponentType;
key: any;
}
export interface OverlayReactNode {
props: MainMenuItem;
type: ComponentType;
}
export type ItemPatch = (item: MainMenuItemReactNode) => MainMenuItemReactNode;
export type OverlayPatch = (overlay: OverlayReactNode) => OverlayReactNode;
export interface MenuHook {
addItem(item: CustomMainMenuItem): CustomMainMenuItem;
addPatch(path: string, patch: ItemPatch): ItemPatch;
addOverlayPatch(patch: OverlayPatch): OverlayPatch;
addOverlayComponent(component: ReactNode): ReactNode;
removePatch(path: string, patch: ItemPatch): void;
removeOverlayPatch(patch: OverlayPatch): void;
removeOverlayComponent(component: ReactNode): void;
removeItem(item: CustomMainMenuItem): void;
}
export type RoutePatch = (route: RouteProps) => RouteProps; export type RoutePatch = (route: RouteProps) => RouteProps;
export interface RouterHook { export interface RouterHook {
@@ -57,26 +103,11 @@ export interface FilePickerRes {
realpath: string; realpath: string;
} }
export enum FileSelectionType {
FILE,
FOLDER,
}
export interface ServerAPI { export interface ServerAPI {
routerHook: RouterHook; routerHook: RouterHook;
menuHook: MenuHook;
toaster: Toaster; toaster: Toaster;
openFilePicker(startPath: string, includeFiles?: boolean, regex?: RegExp): Promise<FilePickerRes>; openFilePicker(startPath: string, includeFiles?: boolean, regex?: RegExp): Promise<FilePickerRes>;
openFilePickerV2(
startPath: string,
includeFiles?: boolean,
includeFolders?: boolean,
filter?: RegExp | ((file: File) => boolean),
extensions?: string[],
showHiddenFiles?: boolean,
allowAllFiles?: boolean,
max?: number,
select?: FileSelectionType,
): Promise<FilePickerRes>;
callPluginMethod<TArgs = {}, TRes = {}>(methodName: string, args: TArgs): Promise<ServerResponse<TRes>>; callPluginMethod<TArgs = {}, TRes = {}>(methodName: string, args: TArgs): Promise<ServerResponse<TRes>>;
callServerMethod<TArgs = {}, TRes = {}>(methodName: string, args: TArgs): Promise<ServerResponse<TRes>>; callServerMethod<TArgs = {}, TRes = {}>(methodName: string, args: TArgs): Promise<ServerResponse<TRes>>;
fetchNoCors<TRes = {}>(url: RequestInfo, request?: RequestInit): Promise<ServerResponse<TRes>>; fetchNoCors<TRes = {}>(url: RequestInfo, request?: RequestInit): Promise<ServerResponse<TRes>>;

View File

@@ -1,11 +1,6 @@
export * from './patcher'; export * from './patcher';
export * from './react'; export * from './react';
declare global {
var FocusNavController: any;
var GamepadNavTree: any;
}
export function joinClassNames(...classes: string[]): string { export function joinClassNames(...classes: string[]): string {
return classes.join(' '); return classes.join(' ');
} }
@@ -21,22 +16,7 @@ export function findSP(): Window {
// old (SP as host) // old (SP as host)
if (document.title == 'SP') return window; if (document.title == 'SP') return window;
// new (SP as popup) // new (SP as popup)
const navTrees = getGamepadNavigationTrees(); const context = FocusNavController.m_ActiveContext || FocusNavController.m_LastActiveContext;
return navTrees?.find((x: any) => x.m_ID == 'root_1_').Root.Element.ownerDocument.defaultView; return context.m_rgGamepadNavigationTrees.find((x: any) => x.m_ID == 'root_1_').Root
} .Element.ownerDocument.defaultView;
/**
* Gets the correct FocusNavController, as the Feb 22 2023 beta has two for some reason.
*/
export function getFocusNavController(): any {
return window.GamepadNavTree?.m_context?.m_controller || window.FocusNavController;
}
/**
* Gets the gamepad navigation trees as Valve seems to be moving them.
*/
export function getGamepadNavigationTrees(): any {
const focusNav = getFocusNavController();
const context = focusNav.m_ActiveContext || focusNav.m_LastActiveContext;
return context?.m_rgGamepadNavigationTrees;
} }

View File

@@ -1,4 +1,5 @@
{ {
"githubPages": false,
"categorizeByGroup": false, "categorizeByGroup": false,
"excludeExternals": true "excludeExternals": true
} }