mirror of
https://github.com/SteamDeckHomebrew/decky-frontend-lib.git
synced 2026-05-20 10:00:08 +02:00
Compare commits
36 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aa78f4c0a2 | ||
|
|
b04044451a | ||
|
|
5d8f141114 | ||
|
|
2bfe62409f | ||
|
|
f5cefa9eea | ||
|
|
abfd2c0105 | ||
|
|
8cb7c273eb | ||
|
|
c440fa76b1 | ||
|
|
4e4859deb6 | ||
|
|
9579c41c6d | ||
|
|
4dd6afd91b | ||
|
|
b092d49bf8 | ||
|
|
7e0cb153b1 | ||
|
|
b1e503853f | ||
|
|
832ad697c7 | ||
|
|
62b454f712 | ||
|
|
ea19d62dab | ||
|
|
9f262097b9 | ||
|
|
e6e8c91ec7 | ||
|
|
688c7471cd | ||
|
|
6c1b12b95f | ||
|
|
0dfb2cedb6 | ||
|
|
2a78cc0116 | ||
|
|
1716782183 | ||
|
|
0cd498beac | ||
|
|
b728d90263 | ||
|
|
3384d2910d | ||
|
|
78c4cdb9e9 | ||
|
|
d20659e073 | ||
|
|
d91f49e728 | ||
|
|
735080fee3 | ||
|
|
86e6e4c0f5 | ||
|
|
46b1d6e8ea | ||
|
|
a7635b6305 | ||
|
|
bffd530bda | ||
|
|
9c79187d37 |
9
.github/workflows/docs.yaml
vendored
9
.github/workflows/docs.yaml
vendored
@@ -1,9 +1,10 @@
|
||||
name: Generate docs
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
workflow_dispatch:
|
||||
# push:
|
||||
# branches:
|
||||
# - main
|
||||
|
||||
jobs:
|
||||
release:
|
||||
@@ -51,4 +52,4 @@ jobs:
|
||||
ssh: true
|
||||
directory: ./wiki
|
||||
repository: SteamDeckHomebrew/wiki
|
||||
branch: main
|
||||
branch: main
|
||||
|
||||
6
.github/workflows/release.yaml
vendored
6
.github/workflows/release.yaml
vendored
@@ -4,7 +4,7 @@ on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- v4-dev
|
||||
|
||||
jobs:
|
||||
release:
|
||||
@@ -18,7 +18,7 @@ jobs:
|
||||
- name: Setup | Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16
|
||||
node-version: 20
|
||||
- name: Setup | Dependencies
|
||||
run: npm i -g pnpm && pnpm i --frozen-lockfile
|
||||
- name: Build
|
||||
@@ -29,5 +29,5 @@ jobs:
|
||||
if: github.event_name != 'pull_request'
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
NPM_TOKEN: ${{ secrets.NPM_TOKEN_DECKY_ORG }}
|
||||
run: pnpm exec semantic-release
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
{
|
||||
"branches": ["main", "dev"],
|
||||
"branches": ["main", "v4-dev"],
|
||||
"plugins": [
|
||||
[
|
||||
"@semantic-release/commit-analyzer",
|
||||
{
|
||||
"preset": "angular",
|
||||
"releaseRules": [
|
||||
{"type": "chore", "scope": "classes", "release": "patch"},
|
||||
{"type": "docs", "scope": "steamclient", "release": "patch"},
|
||||
{"type": "*", "scope": "docs", "release": false}
|
||||
{ "type": "chore", "scope": "classes", "release": "patch" },
|
||||
{ "type": "docs", "scope": "steamclient", "release": "patch" },
|
||||
{ "type": "*", "scope": "docs", "release": false }
|
||||
]
|
||||
}
|
||||
],
|
||||
|
||||
26
.vscode/tasks.json
vendored
26
.vscode/tasks.json
vendored
@@ -1,15 +1,13 @@
|
||||
{
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"type": "npm",
|
||||
"script": "dev",
|
||||
"problemMatcher": [
|
||||
"$tsc-watch"
|
||||
],
|
||||
"label": "npm: dev",
|
||||
"detail": "tsc -b -w",
|
||||
"isBackground": true
|
||||
}
|
||||
]
|
||||
}
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"type": "npm",
|
||||
"script": "dev",
|
||||
"problemMatcher": ["$tsc-watch"],
|
||||
"label": "npm: dev",
|
||||
"detail": "tsc -b -w",
|
||||
"isBackground": true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
63
CHANGELOG.md
63
CHANGELOG.md
@@ -1,3 +1,66 @@
|
||||
# [4.1.0](https://github.com/SteamDeckHomebrew/decky-frontend-lib/compare/v4.0.1...v4.1.0) (2024-06-27)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **release:** release v4.1.0 ([b040444](https://github.com/SteamDeckHomebrew/decky-frontend-lib/commit/b04044451a9dc3633fe624e47dd58c7ea206d0a3))
|
||||
|
||||
## [4.0.1](https://github.com/SteamDeckHomebrew/decky-frontend-lib/compare/v4.0.0...v4.0.1) (2024-06-27)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **release:** empty commit to bump to v4.0.1 ([2bfe624](https://github.com/SteamDeckHomebrew/decky-frontend-lib/commit/2bfe62409f775a69124e0f2e853ae0b1668a9c36))
|
||||
|
||||
# [4.0.0](https://github.com/SteamDeckHomebrew/decky-frontend-lib/compare/v3.25.0...v4.0.0) (2024-06-27)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **ci:** empty commit so semantic-release shuts up ([8cb7c27](https://github.com/SteamDeckHomebrew/decky-frontend-lib/commit/8cb7c273eb61c1f949844291b2864c11bf746058))
|
||||
* **ci:** temp remove npm publisher ([abfd2c0](https://github.com/SteamDeckHomebrew/decky-frontend-lib/commit/abfd2c010508ddf6e18149374dc52b50402ffb4c))
|
||||
* **components:** fix missing children prop ([688c747](https://github.com/SteamDeckHomebrew/decky-frontend-lib/commit/688c7471cde96bf9e9a71c47d19bd63cff7a66b3))
|
||||
* **components:** forgot one ([e6e8c91](https://github.com/SteamDeckHomebrew/decky-frontend-lib/commit/e6e8c91ec7ea711f6c147f28e300f745dcbd24f4))
|
||||
* **Field:** add children prop ([2a78cc0](https://github.com/SteamDeckHomebrew/decky-frontend-lib/commit/2a78cc011671ab123fb6356f54102e531a7953a8))
|
||||
* fixes for jun 26 beta ([7e0cb15](https://github.com/SteamDeckHomebrew/decky-frontend-lib/commit/7e0cb153b197267f49b5e1f513b54a880bf3710f))
|
||||
* **Menu:** work around ConfigContext error ([9f26209](https://github.com/SteamDeckHomebrew/decky-frontend-lib/commit/9f262097b9808a0366bb52cd24844371bbe65316))
|
||||
* **package:** fix ts memes ([ea19d62](https://github.com/SteamDeckHomebrew/decky-frontend-lib/commit/ea19d62dabfbecad97c1ab9384c676f8a018f96b))
|
||||
* **package:** pin react properly ([62b454f](https://github.com/SteamDeckHomebrew/decky-frontend-lib/commit/62b454f71255d5e1898677909a63612615e09083))
|
||||
* **package:** unbreak react ([b1e5038](https://github.com/SteamDeckHomebrew/decky-frontend-lib/commit/b1e503853ff6d39f1e86a6180f355e73b9fd6925))
|
||||
* **plugin:** shim definePlugin for now [ci skip] ([0cd498b](https://github.com/SteamDeckHomebrew/decky-frontend-lib/commit/0cd498beacbd23efa3b771880ff0a4df3f636836))
|
||||
* **utils/react:** shut it ts ([d91f49e](https://github.com/SteamDeckHomebrew/decky-frontend-lib/commit/d91f49e728b613e24b40d7e81d47fd1f3bd92372))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add v4 webpack api and port everything to it ([bffd530](https://github.com/SteamDeckHomebrew/decky-frontend-lib/commit/bffd530bda9a49aae603c323c0a2b4328eee968d))
|
||||
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
* **ci:** v4 release
|
||||
* **ci:** v4 release
|
||||
|
||||
## [3.26.1](https://github.com/SteamDeckHomebrew/decky-frontend-lib/compare/v3.26.0...v3.26.1) (2024-05-25)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **Field:** add children prop ([2a78cc0](https://github.com/SteamDeckHomebrew/decky-frontend-lib/commit/2a78cc011671ab123fb6356f54102e531a7953a8))
|
||||
* **plugin:** shim definePlugin for now [ci skip] ([0cd498b](https://github.com/SteamDeckHomebrew/decky-frontend-lib/commit/0cd498beacbd23efa3b771880ff0a4df3f636836))
|
||||
|
||||
# [3.26.0](https://github.com/SteamDeckHomebrew/decky-frontend-lib/compare/v3.25.0...v3.26.0) (2024-05-24)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **utils/react:** shut it ts ([d91f49e](https://github.com/SteamDeckHomebrew/decky-frontend-lib/commit/d91f49e728b613e24b40d7e81d47fd1f3bd92372))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add v4 webpack api and port everything to it ([bffd530](https://github.com/SteamDeckHomebrew/decky-frontend-lib/commit/bffd530bda9a49aae603c323c0a2b4328eee968d))
|
||||
|
||||
# [3.25.0](https://github.com/SteamDeckHomebrew/decky-frontend-lib/compare/v3.24.5...v3.25.0) (2024-03-09)
|
||||
|
||||
|
||||
|
||||
@@ -20,8 +20,11 @@ This library can also theoretically be used to extend existing UI elements of th
|
||||
|
||||
### Getting Started (Developers)
|
||||
|
||||
If you would like a feature added to decky-frontend-lib, please request it via a Github issue.
|
||||
If you would like a feature added to decky-frontend-lib, please request it via a Github issue.
|
||||
|
||||
If you want to start making a plugin with decky-frontend-lib, please direct your attention to the [decky-plugin-template](https://github.com/SteamDeckHomebrew/decky-plugin-template) repository.
|
||||
|
||||
This library can be found on [npm](https://www.npmjs.com/package/decky-frontend-lib) and as such you can pull it without a local copy for your project as needed.
|
||||
|
||||
Tips for fixing failing module finds after Steam updates:
|
||||
- `Object.entries(DFL)` can point out any undefined exports
|
||||
@@ -1,3 +1,3 @@
|
||||
module.exports = {
|
||||
presets: [['@babel/preset-env', { targets: { node: 'current' } }], '@babel/preset-typescript',],
|
||||
presets: [['@babel/preset-env', { targets: { node: 'current' } }], '@babel/preset-typescript'],
|
||||
};
|
||||
|
||||
5
global.d.ts
vendored
Normal file
5
global.d.ts
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
declare global {
|
||||
interface Window {
|
||||
SP_REACT: typeof React;
|
||||
}
|
||||
}
|
||||
5
globals.d.ts
vendored
5
globals.d.ts
vendored
@@ -1,5 +0,0 @@
|
||||
declare global {
|
||||
interface Window {
|
||||
SP_REACT: typeof React;
|
||||
}
|
||||
}
|
||||
51
package.json
51
package.json
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "decky-frontend-lib",
|
||||
"version": "3.25.0",
|
||||
"description": "A library for building decky plugins",
|
||||
"name": "@decky/ui",
|
||||
"version": "4.1.0",
|
||||
"description": "A library for interacting with the Steam frontend in Decky plugins and elsewhere.",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"module": "dist/index.js",
|
||||
@@ -29,7 +29,7 @@
|
||||
"steam",
|
||||
"components"
|
||||
],
|
||||
"author": "Jonas Dellinger <jonas@dellinger.dev>",
|
||||
"author": "SteamDeckHomebrew Team",
|
||||
"license": "LGPL-2.1",
|
||||
"bugs": {
|
||||
"url": "https://github.com/SteamDeckHomebrew/decky-frontend-lib/issues"
|
||||
@@ -41,29 +41,30 @@
|
||||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
"@commitlint/cli": "^17.0.2",
|
||||
"@commitlint/config-conventional": "^17.0.2",
|
||||
"@commitlint/cz-commitlint": "^17.0.0",
|
||||
"@semantic-release/changelog": "^6.0.1",
|
||||
"@commitlint/cli": "^19.3.0",
|
||||
"@commitlint/config-conventional": "^19.2.2",
|
||||
"@commitlint/cz-commitlint": "^19.2.0",
|
||||
"@semantic-release/changelog": "^6.0.3",
|
||||
"@semantic-release/git": "^10.0.1",
|
||||
"@types/jest": "^27.4.1",
|
||||
"@types/react": "16.14.0",
|
||||
"@types/react-router": "5.1.18",
|
||||
"commitizen": "^4.2.4",
|
||||
"husky": "^8.0.1",
|
||||
"@types/jest": "^29.5.12",
|
||||
"@types/react": "18.3.3",
|
||||
"@types/react-dom": "18.3.0",
|
||||
"@types/react-router": "5.1.20",
|
||||
"commitizen": "^4.3.0",
|
||||
"husky": "^9.0.11",
|
||||
"import-sort-style-module": "^6.0.0",
|
||||
"jest": "^27.5.1",
|
||||
"minimist": "^1.2.6",
|
||||
"prettier": "^2.7.1",
|
||||
"jest": "^29.7.0",
|
||||
"minimist": "^1.2.8",
|
||||
"prettier": "^3.3.2",
|
||||
"prettier-plugin-import-sort": "^0.0.7",
|
||||
"semantic-release": "^19.0.3",
|
||||
"semantic-release": "^24.0.0",
|
||||
"shx": "^0.3.4",
|
||||
"ts-jest": "^27.1.4",
|
||||
"typedoc": "^0.23.24",
|
||||
"typedoc-plugin-mdn-links": "^2.0.0",
|
||||
"typedoc-plugin-missing-exports": "^1.0.0",
|
||||
"ts-jest": "^29.1.4",
|
||||
"typedoc": "^0.25.13",
|
||||
"typedoc-plugin-mdn-links": "^3.1.29",
|
||||
"typedoc-plugin-missing-exports": "^2.3.0",
|
||||
"typedoc-wikijs-theme": "^1.0.5",
|
||||
"typescript": "^4.9.5"
|
||||
"typescript": "^5.4.5"
|
||||
},
|
||||
"pnpm": {
|
||||
"peerDependencyRules": {
|
||||
@@ -71,6 +72,12 @@
|
||||
"react",
|
||||
"react-dom"
|
||||
]
|
||||
},
|
||||
"updateConfig": {
|
||||
"ignoreDependencies": [
|
||||
"react",
|
||||
"react-dom"
|
||||
]
|
||||
}
|
||||
},
|
||||
"importSort": {
|
||||
|
||||
4891
pnpm-lock.yaml
generated
4891
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -1,34 +1,34 @@
|
||||
import { Module, findAllModules } from "./webpack";
|
||||
import { Module, findAllModules } from './webpack';
|
||||
|
||||
export interface ClassModule {
|
||||
[name: string]: string
|
||||
};
|
||||
|
||||
export const classMap: ClassModule[] = findAllModules((m: Module) => {
|
||||
if (typeof m == "object" && !m.__esModule) {
|
||||
const keys = Object.keys(m);
|
||||
// special case some libraries
|
||||
if (keys.length == 1 && m.version) return false;
|
||||
// special case localization
|
||||
if (keys.length > 1000 && m.AboutSettings) return false;
|
||||
|
||||
return keys.length > 0 && keys.every(k => !Object.getOwnPropertyDescriptor(m, k)?.get && typeof m[k] == "string")
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
export function findClass(name: string): string | void {
|
||||
return classMap.find(m => m?.[name])?.[name];
|
||||
[name: string]: string;
|
||||
}
|
||||
|
||||
export function findClassModule(filter: (module: any) => boolean) : ClassModule | void {
|
||||
return classMap.find(m => filter(m));
|
||||
|
||||
export const classMap: ClassModule[] = findAllModules((m: Module) => {
|
||||
if (typeof m == 'object' && !m.__esModule) {
|
||||
const keys = Object.keys(m);
|
||||
// special case some libraries
|
||||
if (keys.length == 1 && m.version) return false;
|
||||
// special case localization
|
||||
if (keys.length > 1000 && m.AboutSettings) return false;
|
||||
|
||||
return keys.length > 0 && keys.every((k) => !Object.getOwnPropertyDescriptor(m, k)?.get && typeof m[k] == 'string');
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
export function findClass(name: string): string | void {
|
||||
return classMap.find((m) => m?.[name])?.[name];
|
||||
}
|
||||
|
||||
export function findClassModule(filter: (module: any) => boolean): ClassModule | void {
|
||||
return classMap.find((m) => filter(m));
|
||||
}
|
||||
|
||||
export function unminifyClass(minifiedClass: string): string | void {
|
||||
for (let m of classMap) {
|
||||
for (let className of Object.keys(m)) {
|
||||
if (m[className] == minifiedClass) return className;
|
||||
}
|
||||
for (let m of classMap) {
|
||||
for (let className of Object.keys(m)) {
|
||||
if (m[className] == minifiedClass) return className;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
16
src/components/ButtonItem.ts
Normal file
16
src/components/ButtonItem.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { FC } from 'react';
|
||||
|
||||
import { CommonUIModule } from '../webpack';
|
||||
import { ItemProps } from './Item';
|
||||
import { createPropListRegex } from '../utils';
|
||||
|
||||
export interface ButtonItemProps extends ItemProps {
|
||||
onClick?(e: MouseEvent): void;
|
||||
disabled?: boolean;
|
||||
}
|
||||
const buttonItemRegex = createPropListRegex(["highlightOnFocus", "childrenContainerWidth"], false);
|
||||
export const ButtonItem = Object.values(CommonUIModule).find(
|
||||
(mod: any) =>
|
||||
(mod?.render?.toString && buttonItemRegex.test(mod.render.toString())) ||
|
||||
mod?.render?.toString?.().includes('childrenContainerWidth:"min"'),
|
||||
) as FC<ButtonItemProps>;
|
||||
@@ -1,6 +1,6 @@
|
||||
import { HTMLAttributes, ReactNode, RefAttributes, VFC } from 'react';
|
||||
import { HTMLAttributes, ReactNode, RefAttributes, FC } from 'react';
|
||||
|
||||
import { findModuleChild } from '../webpack';
|
||||
import { Export, findModuleExport } from '../webpack';
|
||||
|
||||
export interface CarouselProps extends HTMLAttributes<HTMLDivElement> {
|
||||
autoFocus?: boolean;
|
||||
@@ -20,9 +20,6 @@ export interface CarouselProps extends HTMLAttributes<HTMLDivElement> {
|
||||
scrollToAlignment?: 'center';
|
||||
}
|
||||
|
||||
export const Carousel = findModuleChild((m) => {
|
||||
if (typeof m !== 'object') return undefined;
|
||||
for (let prop in m) {
|
||||
if (m[prop]?.render?.toString().includes('setFocusedColumn:')) return m[prop];
|
||||
}
|
||||
}) as VFC<CarouselProps & RefAttributes<HTMLDivElement>>;
|
||||
export const Carousel = findModuleExport((e: Export) => e.render?.toString().includes('setFocusedColumn:')) as FC<
|
||||
CarouselProps & RefAttributes<HTMLDivElement>
|
||||
>;
|
||||
14
src/components/ControlsList.ts
Normal file
14
src/components/ControlsList.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { FC, ReactNode } from 'react';
|
||||
|
||||
import { Export, findModuleExport } from '../webpack';
|
||||
|
||||
export interface ControlsListProps {
|
||||
alignItems?: 'left' | 'right' | 'center';
|
||||
spacing?: 'standard' | 'extra';
|
||||
children?: ReactNode;
|
||||
}
|
||||
|
||||
export const ControlsList: FC<ControlsListProps> = findModuleExport(
|
||||
(e: Export) =>
|
||||
e?.toString && e.toString().includes('().ControlsListChild') && e.toString().includes('().ControlsListOuterPanel'),
|
||||
);
|
||||
@@ -1,4 +1,4 @@
|
||||
import { CSSProperties, FC, RefAttributes } from 'react';
|
||||
import { CSSProperties, FC, ReactNode, RefAttributes } from 'react';
|
||||
|
||||
import { CommonUIModule } from '../webpack';
|
||||
import { FooterLegendProps } from './FooterLegend';
|
||||
@@ -6,12 +6,13 @@ import { FooterLegendProps } from './FooterLegend';
|
||||
export interface DialogCommonProps extends RefAttributes<HTMLDivElement> {
|
||||
style?: CSSProperties;
|
||||
className?: string;
|
||||
children?: ReactNode;
|
||||
}
|
||||
|
||||
export interface DialogButtonProps extends DialogCommonProps, FooterLegendProps {
|
||||
/**
|
||||
* Enables/disables the focus around the button.
|
||||
*
|
||||
*
|
||||
* @note
|
||||
* Default value depends on context, so setting it to `false` will enable it.
|
||||
*/
|
||||
@@ -19,25 +20,25 @@ export interface DialogButtonProps extends DialogCommonProps, FooterLegendProps
|
||||
|
||||
/**
|
||||
* Disables the button - assigned `on*` methods will not be invoked if clicked.
|
||||
*
|
||||
*
|
||||
* @note
|
||||
* Depending on where it is, it might still get focus. In such case it can be
|
||||
* Depending on where it is, it might still get focus. In such case it can be
|
||||
* partially disabled separately.
|
||||
*
|
||||
*
|
||||
* @see focusable.
|
||||
*/
|
||||
disabled?: boolean;
|
||||
|
||||
/**
|
||||
* Enables/disables the navigation based focus on button - you won't be able to navigate to
|
||||
* it via the gamepad or keyboard.
|
||||
*
|
||||
* it via the gamepad or keyboard.
|
||||
*
|
||||
* @note
|
||||
* If set to `false`, it still can be clicked and **WILL** become focused until navigated away.
|
||||
* Depending on the context of where the button is, even a disabled button can focused.
|
||||
*/
|
||||
focusable?: boolean;
|
||||
|
||||
|
||||
onClick?(e: MouseEvent): void;
|
||||
onPointerDown?(e: PointerEvent): void;
|
||||
onPointerUp?(e: PointerEvent): void;
|
||||
@@ -51,13 +52,19 @@ export interface DialogButtonProps extends DialogCommonProps, FooterLegendProps
|
||||
}
|
||||
|
||||
const CommonDialogDivs = Object.values(CommonUIModule).filter(
|
||||
(m: any) => typeof m === 'object' && m?.render?.toString().includes('"div",Object.assign({},'),
|
||||
(m: any) => typeof m === 'object' && m?.render?.toString().includes('createElement("div",{...') ||
|
||||
m?.render?.toString().includes('createElement("div",Object.assign({},'),
|
||||
);
|
||||
const MappedDialogDivs = new Map(
|
||||
Object.values(CommonDialogDivs).map((m: any) => {
|
||||
const renderedDiv = m.render({});
|
||||
// Take only the first class name segment as it identifies the element we want
|
||||
return [renderedDiv.props.className.split(' ')[0], m];
|
||||
try {
|
||||
const renderedDiv = m.render({});
|
||||
// Take only the first class name segment as it identifies the element we want
|
||||
return [renderedDiv.props.className.split(' ')[0], m];
|
||||
} catch (e) {
|
||||
console.error("[DFL:Dialog]: failed to render common dialog component", e);
|
||||
return [null, null];
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -71,14 +78,11 @@ export const DialogControlsSection = MappedDialogDivs.get('DialogControlsSection
|
||||
export const DialogControlsSectionHeader = MappedDialogDivs.get('DialogControlsSectionHeader') as FC<DialogCommonProps>;
|
||||
|
||||
export const DialogButtonPrimary = Object.values(CommonUIModule).find(
|
||||
(mod: any) => mod?.render?.toString()?.includes('DialogButton') && mod?.render?.toString()?.includes('Primary'),
|
||||
(mod: any) => mod?.render?.toString()?.includes('"DialogButton","_DialogLayout","Primary"'),
|
||||
) as FC<DialogButtonProps>;
|
||||
|
||||
export const DialogButtonSecondary = Object.values(CommonUIModule).find(
|
||||
(mod: any) =>
|
||||
mod?.render?.toString()?.includes('Object.assign({type:"button"') &&
|
||||
mod?.render?.toString()?.includes('DialogButton') &&
|
||||
mod?.render?.toString()?.includes('Secondary'),
|
||||
(mod: any) => mod?.render?.toString()?.includes('"DialogButton","_DialogLayout","Secondary"')
|
||||
) as FC<DialogButtonProps>;
|
||||
|
||||
// This is the "main" button. The Primary can act as a submit button,
|
||||
@@ -18,16 +18,19 @@ export interface DialogCheckboxProps extends DialogCommonProps, FooterLegendProp
|
||||
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
|
||||
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>;
|
||||
@@ -1,7 +1,8 @@
|
||||
import { ReactNode, VFC } from 'react';
|
||||
import { ReactNode, FC } from 'react';
|
||||
|
||||
import { CommonUIModule } from '../webpack';
|
||||
import { ItemProps } from './Item';
|
||||
import { createPropListRegex } from '../utils';
|
||||
|
||||
export interface SingleDropdownOption {
|
||||
data: any;
|
||||
@@ -19,6 +20,11 @@ export interface MultiDropdownOption {
|
||||
|
||||
export type DropdownOption = SingleDropdownOption | MultiDropdownOption;
|
||||
|
||||
export interface DropdownMenuPositionOptions {
|
||||
[_: string]: unknown
|
||||
bMatchWidth?: boolean
|
||||
}
|
||||
|
||||
export interface DropdownProps {
|
||||
rgOptions: DropdownOption[];
|
||||
selectedOption: any;
|
||||
@@ -26,7 +32,7 @@ export interface DropdownProps {
|
||||
onMenuWillOpen?(showMenu: () => void): void;
|
||||
onMenuOpened?(): void;
|
||||
onChange?(data: SingleDropdownOption): void;
|
||||
contextMenuPositionOptions?: any;
|
||||
contextMenuPositionOptions?: DropdownMenuPositionOptions;
|
||||
menuLabel?: string;
|
||||
strDefaultLabel?: string;
|
||||
renderButtonValue?(element: ReactNode): ReactNode;
|
||||
@@ -35,10 +41,11 @@ export interface DropdownProps {
|
||||
|
||||
export const Dropdown = Object.values(CommonUIModule).find(
|
||||
(mod: any) => mod?.prototype?.SetSelectedOption && mod?.prototype?.BuildMenu,
|
||||
) as VFC<DropdownProps>;
|
||||
) as FC<DropdownProps>;
|
||||
|
||||
export interface DropdownItemProps extends DropdownProps, ItemProps {}
|
||||
|
||||
const dropdownItemRegex = createPropListRegex(["dropDownControlRef", "description"], false);
|
||||
export const DropdownItem = Object.values(CommonUIModule).find((mod: any) =>
|
||||
mod?.toString()?.includes('"dropDownControlRef","description"'),
|
||||
) as VFC<DropdownItemProps>;
|
||||
mod?.toString && dropdownItemRegex.test(mod.toString()),
|
||||
) as FC<DropdownItemProps>;
|
||||
@@ -1,9 +1,10 @@
|
||||
import { FC, ReactNode, RefAttributes } from 'react';
|
||||
|
||||
import { findModuleChild } from '../webpack';
|
||||
import { Export, findModuleExport } from '../webpack';
|
||||
import { FooterLegendProps } from './FooterLegend';
|
||||
|
||||
export interface FieldProps extends FooterLegendProps {
|
||||
children?: ReactNode;
|
||||
label?: ReactNode;
|
||||
bottomSeparator?: 'standard' | 'thick' | 'none';
|
||||
description?: ReactNode;
|
||||
@@ -23,9 +24,6 @@ export interface FieldProps extends FooterLegendProps {
|
||||
onClick?: (e: CustomEvent | MouseEvent) => void;
|
||||
}
|
||||
|
||||
export const Field = findModuleChild((m) => {
|
||||
if (typeof m !== 'object') return undefined;
|
||||
for (let prop in m) {
|
||||
if (m[prop]?.render?.toString().includes('"shift-children-below"')) return m[prop];
|
||||
}
|
||||
}) as FC<FieldProps & RefAttributes<HTMLDivElement>>;
|
||||
export const Field = findModuleExport((e: Export) => e?.render?.toString().includes('"shift-children-below"')) as FC<
|
||||
FieldProps & RefAttributes<HTMLDivElement>
|
||||
>;
|
||||
15
src/components/FocusRing.ts
Normal file
15
src/components/FocusRing.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { ElementType, FC, ReactNode } from 'react';
|
||||
|
||||
import { Export, findModuleExport } from '../webpack';
|
||||
|
||||
export interface FocusRingProps {
|
||||
className?: string;
|
||||
rootClassName?: string;
|
||||
render?: ElementType;
|
||||
children?: ReactNode;
|
||||
NavigationManager?: any;
|
||||
}
|
||||
|
||||
export const FocusRing = findModuleExport((e: Export) =>
|
||||
e?.toString()?.includes('.GetShowDebugFocusRing())'),
|
||||
) as FC<FocusRingProps>;
|
||||
21
src/components/Focusable.ts
Normal file
21
src/components/Focusable.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { HTMLAttributes, ReactNode, RefAttributes, FC } from 'react';
|
||||
|
||||
import { Export, findModuleExport } from '../webpack';
|
||||
import { FooterLegendProps } from './FooterLegend';
|
||||
import { createPropListRegex } from '../utils';
|
||||
|
||||
export interface FocusableProps extends HTMLAttributes<HTMLDivElement>, FooterLegendProps {
|
||||
children: ReactNode;
|
||||
'flow-children'?: string;
|
||||
focusClassName?: string;
|
||||
focusWithinClassName?: string;
|
||||
noFocusRing?: boolean;
|
||||
onActivate?: (e: CustomEvent) => void;
|
||||
onCancel?: (e: CustomEvent) => void;
|
||||
}
|
||||
|
||||
const focusableRegex = createPropListRegex(["flow-children", "onActivate", "onCancel", "focusClassName", "focusWithinClassName"]);
|
||||
|
||||
export const Focusable = findModuleExport((e: Export) =>
|
||||
e?.render?.toString && focusableRegex.test(e.render.toString())
|
||||
) as FC<FocusableProps & RefAttributes<HTMLDivElement>>;
|
||||
@@ -36,7 +36,7 @@ export declare enum NavEntryPositionPreferences {
|
||||
LAST,
|
||||
MAINTAIN_X,
|
||||
MAINTAIN_Y,
|
||||
PREFERRED_CHILD
|
||||
PREFERRED_CHILD,
|
||||
}
|
||||
export interface GamepadEventDetail {
|
||||
button: number;
|
||||
@@ -44,8 +44,8 @@ export interface GamepadEventDetail {
|
||||
source: number;
|
||||
}
|
||||
export declare type ActionDescriptionMap = {
|
||||
[key in GamepadButton]?: ReactNode
|
||||
}
|
||||
[key in GamepadButton]?: ReactNode;
|
||||
};
|
||||
export declare type GamepadEvent = CustomEvent<GamepadEventDetail>;
|
||||
export interface FooterLegendProps {
|
||||
actionDescriptionMap?: ActionDescriptionMap;
|
||||
@@ -3,6 +3,7 @@ import { ReactNode } from 'react';
|
||||
export interface ItemProps {
|
||||
label?: ReactNode;
|
||||
description?: ReactNode;
|
||||
children?: ReactNode;
|
||||
layout?: 'below' | 'inline';
|
||||
icon?: ReactNode;
|
||||
bottomSeparator?: 'standard' | 'thick' | 'none';
|
||||
20
src/components/Marquee.ts
Normal file
20
src/components/Marquee.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { CSSProperties, FC } from 'react';
|
||||
|
||||
import { Export, findModuleExport } 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> = findModuleExport(
|
||||
(e: Export) => e?.toString && e.toString().includes('.Marquee') && e.toString().includes('--fade-length'),
|
||||
);
|
||||
58
src/components/Menu.ts
Executable file
58
src/components/Menu.ts
Executable file
@@ -0,0 +1,58 @@
|
||||
import { FC, ReactNode } from 'react';
|
||||
|
||||
import { fakeRenderComponent } from '../utils';
|
||||
import { Export, findModuleExport } from '../webpack';
|
||||
import { FooterLegendProps } from './FooterLegend';
|
||||
|
||||
export const showContextMenu: (children: ReactNode, parent?: EventTarget) => void = findModuleExport(
|
||||
(e: Export) => typeof e === 'function' && e.toString().includes('GetContextMenuManagerFromWindow(')
|
||||
&& e.toString().includes('.CreateContextMenuInstance('),
|
||||
);
|
||||
|
||||
export interface MenuProps extends FooterLegendProps {
|
||||
label: string;
|
||||
onCancel?(): void;
|
||||
cancelText?: string;
|
||||
children?: ReactNode;
|
||||
}
|
||||
|
||||
export const Menu: FC<MenuProps> = findModuleExport(
|
||||
(e: Export) => e?.prototype?.HideIfSubmenu && e?.prototype?.HideMenu,
|
||||
);
|
||||
|
||||
export interface MenuGroupProps {
|
||||
label: string;
|
||||
disabled?: boolean;
|
||||
children?: ReactNode;
|
||||
}
|
||||
|
||||
export const MenuGroup: FC<MenuGroupProps> = findModuleExport(
|
||||
(e: Export) =>
|
||||
(e?.toString()?.includes?.('bInGamepadUI:') &&
|
||||
fakeRenderComponent(() => e({ overview: { appid: 7 } }), {useContext: () => ({IN_GAMEPADUI: true})})?.type?.prototype?.RenderSubMenu) ||
|
||||
(e?.prototype?.RenderSubMenu && e?.prototype?.ShowSubMenu)
|
||||
);
|
||||
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;
|
||||
}
|
||||
|
||||
export const MenuItem: FC<MenuItemProps> = findModuleExport(
|
||||
(e: Export) =>
|
||||
e?.render?.toString()?.includes('bPlayAudio:') || (e?.prototype?.OnOKButton && e?.prototype?.OnMouseEnter),
|
||||
);
|
||||
|
||||
/*
|
||||
all().map(m => {
|
||||
if (typeof m !== "object") return undefined;
|
||||
for (let prop in m) { if (m[prop]?.prototype?.OK && m[prop]?.prototype?.Cancel && m[prop]?.prototype?.render) return m[prop]}
|
||||
}).find(x => x)
|
||||
*/
|
||||
119
src/components/Modal.ts
Executable file
119
src/components/Modal.ts
Executable file
@@ -0,0 +1,119 @@
|
||||
import { FC, ReactNode } from 'react';
|
||||
|
||||
import { findSP } from '../utils';
|
||||
import { Export, findModule, findModuleByExport, findModuleExport } from '../webpack';
|
||||
|
||||
// All of the popout options + strTitle are related. Proper usage is not yet known...
|
||||
export interface ShowModalProps {
|
||||
browserContext?: unknown;
|
||||
bForcePopOut?: boolean;
|
||||
bHideActionIcons?: boolean;
|
||||
bHideMainWindowForPopouts?: boolean;
|
||||
bNeverPopOut?: boolean;
|
||||
fnOnClose?: () => void; // Seems to be the same as "closeModal" callback, but only when the modal is a popout. Will no longer work after "Update" invocation!
|
||||
popupHeight?: number;
|
||||
popupWidth?: number;
|
||||
promiseRenderComplete?: Promise<void>; // Invoked once the render is complete. Currently, it seems to be used as image loading success/error callback...
|
||||
strTitle?: string;
|
||||
}
|
||||
|
||||
export interface ShowModalResult {
|
||||
// This method will not invoke any of the variations of "closeModal" callbacks!
|
||||
Close: () => void;
|
||||
|
||||
// This method will replace the modal element completely and will not update the callback chains,
|
||||
// meaning that "closeModal" and etc. will not automatically close the modal anymore (also "fnOnClose"
|
||||
// will not be even called upon close anymore)! You have to manually call the "Close" method when, for example,
|
||||
// the "closeModal" is invoked in the newly updated modal:
|
||||
// <ModalRoot closeModal={() => { console.log("ABOUT TO CLOSE"); showModalRes.Close(); }} />
|
||||
Update: (modal: ReactNode) => void;
|
||||
}
|
||||
|
||||
const showModalRaw: (
|
||||
modal: ReactNode,
|
||||
parent?: EventTarget,
|
||||
title?: string,
|
||||
props?: ShowModalProps,
|
||||
unknown1?: unknown,
|
||||
hideActions?: { bHideActions?: boolean },
|
||||
modalManager?: unknown,
|
||||
) => ShowModalResult = findModuleExport(
|
||||
(e: Export) =>
|
||||
typeof e === 'function' && e.toString().includes('props.bDisableBackgroundDismiss') && !e?.prototype?.Cancel,
|
||||
);
|
||||
|
||||
export const showModal = (
|
||||
modal: ReactNode,
|
||||
parent?: EventTarget,
|
||||
props: ShowModalProps = {
|
||||
strTitle: 'Decky Dialog',
|
||||
bHideMainWindowForPopouts: false,
|
||||
},
|
||||
): ShowModalResult => {
|
||||
return showModalRaw(modal, parent || findSP(), props.strTitle, props, undefined, {
|
||||
bHideActions: props.bHideActionIcons,
|
||||
});
|
||||
};
|
||||
|
||||
export interface ModalRootProps {
|
||||
children?: ReactNode;
|
||||
onCancel?(): void;
|
||||
closeModal?(): void;
|
||||
onOK?(): void;
|
||||
onEscKeypress?(): void;
|
||||
className?: string;
|
||||
modalClassName?: string;
|
||||
bAllowFullSize?: boolean;
|
||||
bDestructiveWarning?: boolean;
|
||||
bDisableBackgroundDismiss?: boolean;
|
||||
bHideCloseIcon?: boolean;
|
||||
bOKDisabled?: boolean;
|
||||
bCancelDisabled?: boolean;
|
||||
}
|
||||
|
||||
export interface ConfirmModalProps extends ModalRootProps {
|
||||
onMiddleButton?(): void; // setting this prop will enable the middle button
|
||||
strTitle?: ReactNode;
|
||||
strDescription?: ReactNode;
|
||||
strOKButtonText?: ReactNode;
|
||||
strCancelButtonText?: ReactNode;
|
||||
strMiddleButtonText?: ReactNode;
|
||||
bAlertDialog?: boolean; // This will open a modal with only OK button enabled
|
||||
bMiddleDisabled?: boolean;
|
||||
}
|
||||
|
||||
export const ConfirmModal = findModuleExport(
|
||||
(e: Export) => !e?.prototype?.OK && e?.prototype?.Cancel && e?.prototype?.render,
|
||||
) as FC<ConfirmModalProps>;
|
||||
|
||||
export const ModalRoot = Object.values(
|
||||
findModule((m: any) => {
|
||||
if (typeof m !== 'object') return false;
|
||||
|
||||
for (let prop in m) {
|
||||
if (m[prop]?.m_mapModalManager && Object.values(m)?.find((x: any) => x?.type)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}) || {},
|
||||
)?.find((x: any) => x?.type?.toString()?.includes('((function(){')) as FC<ModalRootProps>;
|
||||
|
||||
interface SimpleModalProps {
|
||||
active?: boolean;
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
const ModalModule = findModuleByExport((e: Export) => e?.toString().includes('.ModalPosition,fallback:'), 5);
|
||||
|
||||
const ModalModuleProps = ModalModule ? Object.values(ModalModule) : [];
|
||||
|
||||
export const SimpleModal = ModalModuleProps.find((prop) => {
|
||||
const string = prop?.toString();
|
||||
return string?.includes('.ShowPortalModal()') && string?.includes('.OnElementReadyCallbacks.Register(');
|
||||
}) as FC<SimpleModalProps>;
|
||||
|
||||
export const ModalPosition = ModalModuleProps.find((prop) =>
|
||||
prop?.toString().includes('.ModalPosition,fallback:'),
|
||||
) as FC<SimpleModalProps>;
|
||||
26
src/components/Panel.ts
Normal file
26
src/components/Panel.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { FC, ReactNode } from 'react';
|
||||
|
||||
import { Export, findModuleDetailsByExport } from '../webpack';
|
||||
|
||||
// TODO where did this go?
|
||||
// export const Panel: FC<{ children?: ReactNode; }> = findModuleExport((e: Export) => {
|
||||
// if (typeof mod !== 'object' || !mod.__esModule) return undefined;
|
||||
// return mod.Panel;
|
||||
// });
|
||||
|
||||
export interface PanelSectionProps {
|
||||
title?: string;
|
||||
spinner?: boolean;
|
||||
children?: ReactNode;
|
||||
}
|
||||
|
||||
const [mod, panelSection] = findModuleDetailsByExport((e: Export) => e.toString()?.includes('.PanelSection'));
|
||||
|
||||
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<PanelSectionRowProps>;
|
||||
37
src/components/ProgressBar.ts
Normal file
37
src/components/ProgressBar.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import { ReactNode, FC } from 'react';
|
||||
|
||||
import { Export, findModuleExport } from '../webpack';
|
||||
import { ItemProps } from './Item';
|
||||
import { createPropListRegex } from '../utils';
|
||||
|
||||
export interface ProgressBarItemProps extends ItemProps {
|
||||
indeterminate?: boolean;
|
||||
nTransitionSec?: number;
|
||||
nProgress?: number;
|
||||
focusable?: boolean;
|
||||
}
|
||||
|
||||
export interface ProgressBarProps {
|
||||
indeterminate?: boolean;
|
||||
nTransitionSec?: number;
|
||||
nProgress?: number;
|
||||
focusable?: boolean;
|
||||
}
|
||||
|
||||
export interface ProgressBarWithInfoProps extends ProgressBarItemProps {
|
||||
sTimeRemaining?: ReactNode;
|
||||
sOperationText?: ReactNode;
|
||||
}
|
||||
|
||||
export const ProgressBar = findModuleExport((e: Export) =>
|
||||
e?.toString()?.includes('.ProgressBar,"standard"=='),
|
||||
) as FC<ProgressBarProps>;
|
||||
|
||||
export const ProgressBarWithInfo = findModuleExport((e: Export) =>
|
||||
e?.toString()?.includes('.ProgressBarFieldStatus},'),
|
||||
) as FC<ProgressBarWithInfoProps>;
|
||||
|
||||
const progressBarItemRegex = createPropListRegex(["indeterminate", "nTransitionSec", "nProgress"]);
|
||||
export const ProgressBarItem = findModuleExport((e: Export) =>
|
||||
e?.toString && progressBarItemRegex.test(e.toString()),
|
||||
) as FC<ProgressBarItemProps>;
|
||||
15
src/components/Scroll.ts
Normal file
15
src/components/Scroll.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { FC, ReactNode } from 'react';
|
||||
|
||||
import { Export, findModuleByExport, findModuleExport } from '../webpack';
|
||||
|
||||
const ScrollingModule = findModuleByExport((e: Export) => e?.render?.toString?.().includes('{case"x":'));
|
||||
|
||||
const ScrollingModuleProps = ScrollingModule ? Object.values(ScrollingModule) : [];
|
||||
|
||||
export const ScrollPanel = ScrollingModuleProps.find((prop: any) =>
|
||||
prop?.render?.toString?.().includes('{case"x":'),
|
||||
) as FC<{ children?: ReactNode }>;
|
||||
|
||||
export const ScrollPanelGroup: FC<{ children?: ReactNode }> = findModuleExport((e: Export) =>
|
||||
e?.render?.toString().includes('.FocusVisibleChild()),[])'),
|
||||
);
|
||||
@@ -1,6 +1,7 @@
|
||||
import { ReactNode, VFC } from 'react';
|
||||
import { ReactNode, FC } from 'react';
|
||||
|
||||
import { Module, findModuleChild } from '../webpack';
|
||||
import { Export, findModuleExport } from '../webpack';
|
||||
import { createPropListRegex } from '../utils';
|
||||
|
||||
export interface SidebarNavigationPage {
|
||||
title: ReactNode;
|
||||
@@ -23,11 +24,7 @@ export interface SidebarNavigationProps {
|
||||
onPageRequested?: (page: string) => void;
|
||||
}
|
||||
|
||||
export const SidebarNavigation = findModuleChild((mod: Module) => {
|
||||
for (let prop in mod) {
|
||||
if (mod[prop]?.toString()?.includes('"disableRouteReporting"')) {
|
||||
return mod[prop];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}) as VFC<SidebarNavigationProps>;
|
||||
const sidebarNavigationRegex = createPropListRegex(["pages", "fnSetNavigateToPage", "disableRouteReporting"]);
|
||||
export const SidebarNavigation = findModuleExport((e: Export) =>
|
||||
e?.toString && sidebarNavigationRegex.test(e.toString()),
|
||||
) as FC<SidebarNavigationProps>;
|
||||
8
src/components/Spinner.ts
Executable file
8
src/components/Spinner.ts
Executable file
@@ -0,0 +1,8 @@
|
||||
import { FC, SVGAttributes } from 'react';
|
||||
|
||||
import { IconsModule } from '../webpack';
|
||||
|
||||
// TODO type this and other icons?
|
||||
export const Spinner = Object.values(IconsModule)?.find(
|
||||
(mod: any) => mod?.toString && /Spinner\)}\)?,.\.createElement\(\"path\",{d:\"M18 /.test(mod.toString()),
|
||||
) as FC<SVGAttributes<SVGElement>>;
|
||||
7
src/components/SteamSpinner.ts
Executable file
7
src/components/SteamSpinner.ts
Executable file
@@ -0,0 +1,7 @@
|
||||
import { FC, SVGAttributes } from 'react';
|
||||
|
||||
import { Export, findModuleExport } from '../webpack';
|
||||
|
||||
export const SteamSpinner = findModuleExport(
|
||||
(e: Export) => e?.toString?.()?.includes('Steam Spinner') && e?.toString?.()?.includes('src'),
|
||||
) as FC<SVGAttributes<SVGElement>>;
|
||||
@@ -1,7 +1,7 @@
|
||||
import { FC, ReactNode, createElement, useEffect, useState } from 'react';
|
||||
|
||||
import { fakeRenderComponent, findInReactTree, sleep } from '../utils';
|
||||
import { findModule } from '../webpack';
|
||||
import { Export, findModuleByExport } from '../webpack';
|
||||
import { FooterLegendProps } from './FooterLegend';
|
||||
import { SteamSpinner } from './SteamSpinner';
|
||||
|
||||
@@ -98,13 +98,7 @@ const getTabs = async () => {
|
||||
let oldTabs: any;
|
||||
|
||||
try {
|
||||
const oldTabsModule = findModule((m: any) => {
|
||||
if (typeof m !== 'object') return false;
|
||||
for (let prop in m) {
|
||||
if (m[prop]?.Unbleed) return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
const oldTabsModule = findModuleByExport((e: Export) => e.Unbleed);
|
||||
if (oldTabsModule)
|
||||
oldTabs = Object.values(oldTabsModule).find((x: any) => x?.type?.toString()?.includes('((function(){'));
|
||||
} catch (e) {
|
||||
@@ -115,8 +109,7 @@ try {
|
||||
* Tabs component as used in the library and media tabs. See {@link TabsProps}.
|
||||
* Unlike other components in `decky-frontend-lib`, this requires Decky Loader to be running.
|
||||
*/
|
||||
export const Tabs =
|
||||
(oldTabs ||
|
||||
export const Tabs = (oldTabs ||
|
||||
((props: TabsProps) => {
|
||||
const found = tabsComponent;
|
||||
const [tc, setTC] = useState<FC<TabsProps>>(found);
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ChangeEventHandler, HTMLAttributes, ReactNode, VFC } from 'react';
|
||||
import { ChangeEventHandler, HTMLAttributes, ReactNode, FC } from 'react';
|
||||
|
||||
import { CommonUIModule, Module } from '../webpack';
|
||||
|
||||
@@ -25,4 +25,4 @@ export interface TextFieldProps extends HTMLAttributes<HTMLInputElement> {
|
||||
|
||||
export const TextField = Object.values(CommonUIModule).find(
|
||||
(mod: Module) => mod?.validateUrl && mod?.validateEmail,
|
||||
) as VFC<TextFieldProps>;
|
||||
) as FC<TextFieldProps>;
|
||||
25
src/components/index.ts
Executable file
25
src/components/index.ts
Executable file
@@ -0,0 +1,25 @@
|
||||
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';
|
||||
export * from './ProgressBar';
|
||||
export * from './SidebarNavigation';
|
||||
export * from './SliderField';
|
||||
export * from './Spinner';
|
||||
export * from './SteamSpinner';
|
||||
export * from './Tabs';
|
||||
export * from './TextField';
|
||||
export * from './Toggle';
|
||||
export * from './ToggleField';
|
||||
export * from './Scroll';
|
||||
@@ -1,6 +1,7 @@
|
||||
import { CSSProperties, FC, useState } from 'react';
|
||||
|
||||
import { ConfirmModal, SliderField, gamepadSliderClasses } from '../deck-components';
|
||||
import { ConfirmModal, SliderField } from '../components';
|
||||
import { gamepadSliderClasses } from '../utils/static-classes';
|
||||
|
||||
interface ColorPickerModalProps {
|
||||
closeModal: () => void;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Fragment, JSXElementConstructor, ReactElement, ReactNode, useEffect, useState } from 'react';
|
||||
|
||||
import { Field, FieldProps, Focusable, GamepadButton } from '../deck-components';
|
||||
import { Field, FieldProps, Focusable, GamepadButton } from '../components';
|
||||
|
||||
/**
|
||||
* A ReorderableList entry of type <T>.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useEffect } from 'react';
|
||||
import { FC, ImgHTMLAttributes, useState } from 'react';
|
||||
|
||||
import { Spinner } from '../deck-components';
|
||||
import { Spinner } from '../components';
|
||||
|
||||
interface SuspensefulImageProps extends ImgHTMLAttributes<HTMLImageElement> {
|
||||
suspenseWidth?: string | number;
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { getGamepadNavigationTrees } from '../utils';
|
||||
|
||||
function getQuickAccessWindow(): Window | null {
|
||||
const navTrees = getGamepadNavigationTrees();
|
||||
return navTrees.find((tree: any) => tree?.id === 'QuickAccess-NA')?.m_Root?.m_element?.ownerDocument.defaultView ?? null;
|
||||
return (
|
||||
navTrees.find((tree: any) => tree?.id === 'QuickAccess-NA')?.m_Root?.m_element?.ownerDocument.defaultView ?? null
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -12,10 +15,10 @@ function getQuickAccessWindow(): Window | null {
|
||||
* @returns `true` if quick access menu is visible and `false` otherwise.
|
||||
*
|
||||
* @example
|
||||
* import { VFC, useEffect } from "react";
|
||||
* import { FC, useEffect } from "react";
|
||||
* import { useQuickAccessVisible } from "decky-frontend-lib";
|
||||
*
|
||||
* export const PluginPanelView: VFC<{}> = ({ }) => {
|
||||
* export const PluginPanelView: FC<{}> = ({ }) => {
|
||||
* const isVisible = useQuickAccessVisible();
|
||||
*
|
||||
* useEffect(() => {
|
||||
@@ -1,16 +0,0 @@
|
||||
import { FC } from 'react';
|
||||
|
||||
import { CommonUIModule } from '../webpack';
|
||||
import { ItemProps } from './Item';
|
||||
|
||||
export interface ButtonItemProps extends ItemProps {
|
||||
onClick?(e: MouseEvent): void;
|
||||
disabled?: boolean;
|
||||
}
|
||||
export const ButtonItem =
|
||||
(CommonUIModule.ButtonField ||
|
||||
Object.values(CommonUIModule).find(
|
||||
(mod: any) =>
|
||||
mod?.render?.toString()?.includes('"highlightOnFocus","childrenContainerWidth"') ||
|
||||
mod?.render?.toString()?.includes('childrenContainerWidth:"min"'),
|
||||
)) as FC<ButtonItemProps>;
|
||||
@@ -1,17 +0,0 @@
|
||||
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;
|
||||
});
|
||||
@@ -1,19 +0,0 @@
|
||||
import { ElementType, FC, ReactNode } from 'react';
|
||||
|
||||
import { findModuleChild } from '../webpack';
|
||||
|
||||
export interface FocusRingProps {
|
||||
className?: string;
|
||||
rootClassName?: string;
|
||||
render?: ElementType;
|
||||
children?: ReactNode;
|
||||
NavigationManager?: any;
|
||||
}
|
||||
|
||||
export const FocusRing = findModuleChild((m: any) => {
|
||||
if (typeof m !== 'object') return false;
|
||||
for (let prop in m) {
|
||||
if (m[prop]?.toString()?.includes('.GetShowDebugFocusRing())')) return m[prop];
|
||||
}
|
||||
return false;
|
||||
}) as FC<FocusRingProps>;
|
||||
@@ -1,22 +0,0 @@
|
||||
import { HTMLAttributes, ReactNode, RefAttributes, VFC } from 'react';
|
||||
|
||||
import { findModuleChild } from '../webpack';
|
||||
import { FooterLegendProps } from './FooterLegend';
|
||||
|
||||
export interface FocusableProps extends HTMLAttributes<HTMLDivElement>, FooterLegendProps {
|
||||
children: ReactNode;
|
||||
'flow-children'?: string;
|
||||
focusClassName?: string;
|
||||
focusWithinClassName?: string;
|
||||
noFocusRing?: boolean;
|
||||
onActivate?: (e: CustomEvent) => void;
|
||||
onCancel?: (e: CustomEvent) => void;
|
||||
}
|
||||
|
||||
export const Focusable = findModuleChild((m) => {
|
||||
if (typeof m !== 'object') return undefined;
|
||||
for (let prop in m) {
|
||||
if (m[prop]?.render?.toString()?.includes('["flow-children","onActivate","onCancel","focusClassName",'))
|
||||
return m[prop];
|
||||
}
|
||||
}) as VFC<FocusableProps & RefAttributes<HTMLDivElement>>;
|
||||
@@ -1,26 +0,0 @@
|
||||
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;
|
||||
});
|
||||
@@ -1,84 +0,0 @@
|
||||
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;
|
||||
for (let prop in m) {
|
||||
if (typeof m[prop] === 'function' && m[prop].toString().includes('stopPropagation))')) {
|
||||
return m[prop];
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export interface MenuProps extends FooterLegendProps {
|
||||
label: string;
|
||||
onCancel?(): void;
|
||||
cancelText?: string;
|
||||
children?: ReactNode;
|
||||
}
|
||||
|
||||
export const Menu: FC<MenuProps> = findModuleChild((m) => {
|
||||
if (typeof m !== 'object') return undefined;
|
||||
|
||||
for (let prop in m) {
|
||||
if (m[prop]?.prototype?.HideIfSubmenu && m[prop]?.prototype?.HideMenu) {
|
||||
return m[prop];
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export interface MenuGroupProps {
|
||||
label: string;
|
||||
disabled?: boolean;
|
||||
children?: ReactNode;
|
||||
}
|
||||
|
||||
export const MenuGroup: FC<MenuGroupProps> = findModuleChild((m) => {
|
||||
if (typeof m !== 'object') return undefined;
|
||||
|
||||
for (let prop in m) {
|
||||
if (
|
||||
(m[prop]?.toString()?.includes?.('bInGamepadUI:') &&
|
||||
fakeRenderComponent(() => m[prop]({overview: {appid: 7}}))?.type?.prototype?.RenderSubMenu) ||
|
||||
(m[prop]?.prototype?.RenderSubMenu && m[prop]?.prototype?.ShowSubMenu)
|
||||
) {
|
||||
return m[prop];
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
export const MenuItem: FC<MenuItemProps> = findModuleChild((m) => {
|
||||
if (typeof m !== 'object') return undefined;
|
||||
|
||||
for (let prop in m) {
|
||||
if (
|
||||
m[prop]?.render?.toString()?.includes('bPlayAudio:') ||
|
||||
(m[prop]?.prototype?.OnOKButton && m[prop]?.prototype?.OnMouseEnter)
|
||||
) {
|
||||
return m[prop];
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/*
|
||||
all().map(m => {
|
||||
if (typeof m !== "object") return undefined;
|
||||
for (let prop in m) { if (m[prop]?.prototype?.OK && m[prop]?.prototype?.Cancel && m[prop]?.prototype?.render) return m[prop]}
|
||||
}).find(x => x)
|
||||
*/
|
||||
@@ -1,178 +0,0 @@
|
||||
import { FC, ReactNode } from 'react';
|
||||
|
||||
import { findSP } from '../utils';
|
||||
import { findModule, findModuleChild } from '../webpack';
|
||||
|
||||
// All of the popout options + strTitle are related. Proper usage is not yet known...
|
||||
export interface ShowModalProps {
|
||||
browserContext?: unknown; // This is another Deck Object that is yet to be found
|
||||
bForcePopOut?: boolean;
|
||||
bHideActionIcons?: boolean;
|
||||
bHideMainWindowForPopouts?: boolean;
|
||||
bNeverPopOut?: boolean;
|
||||
fnOnClose?: () => void; // Seems to be the same as "closeModal" callback, but only when the modal is a popout. Will no longer work after "Update" invocation!
|
||||
popupHeight?: number;
|
||||
popupWidth?: number;
|
||||
promiseRenderComplete?: Promise<void>; // Invoked once the render is complete. Currently, it seems to be used as image loading success/error callback...
|
||||
strTitle?: string;
|
||||
}
|
||||
|
||||
export interface ShowModalResult {
|
||||
// This method will not invoke any of the variations of "closeModal" callbacks!
|
||||
Close: () => void;
|
||||
|
||||
// This method will replace the modal element completely and will not update the callback chains,
|
||||
// meaning that "closeModal" and etc. will not automatically close the modal anymore (also "fnOnClose"
|
||||
// will not be even called upon close anymore)! You have to manually call the "Close" method when, for example,
|
||||
// the "closeModal" is invoked in the newly updated modal:
|
||||
// <ModalRoot closeModal={() => { console.log("ABOUT TO CLOSE"); showModalRes.Close(); }} />
|
||||
Update: (modal: ReactNode) => void;
|
||||
}
|
||||
|
||||
const showModalRaw:
|
||||
| ((
|
||||
modal: ReactNode,
|
||||
parent?: EventTarget,
|
||||
title?: string,
|
||||
props?: ShowModalProps,
|
||||
unknown1?: unknown,
|
||||
hideActions?: { bHideActions?: boolean },
|
||||
modalManager?: unknown,
|
||||
) => ShowModalResult)
|
||||
| void = findModuleChild((m) => {
|
||||
if (typeof m !== 'object') return undefined;
|
||||
for (let prop in m) {
|
||||
if (
|
||||
typeof m[prop] === 'function' &&
|
||||
m[prop].toString().includes('props.bDisableBackgroundDismiss') &&
|
||||
!m[prop]?.prototype?.Cancel
|
||||
) {
|
||||
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,
|
||||
parent?: EventTarget,
|
||||
props: ShowModalProps = {
|
||||
strTitle: 'Decky Dialog',
|
||||
bHideMainWindowForPopouts: false,
|
||||
},
|
||||
): ShowModalResult => {
|
||||
if (showModalRaw) {
|
||||
return showModalRaw(modal, parent || findSP(), props.strTitle, props, undefined, {
|
||||
bHideActions: props.bHideActionIcons,
|
||||
});
|
||||
} else if (oldShowModalRaw) {
|
||||
return oldShowModalRaw(modal, parent || findSP(), props);
|
||||
} else {
|
||||
throw new Error('[DFL:Modals]: Cannot find showModal function');
|
||||
}
|
||||
};
|
||||
|
||||
export interface ModalRootProps {
|
||||
children?: ReactNode;
|
||||
onCancel?(): void;
|
||||
closeModal?(): void;
|
||||
onOK?(): void;
|
||||
onEscKeypress?(): void;
|
||||
className?: string;
|
||||
modalClassName?: string;
|
||||
bAllowFullSize?: boolean;
|
||||
bDestructiveWarning?: boolean;
|
||||
bDisableBackgroundDismiss?: boolean;
|
||||
bHideCloseIcon?: boolean;
|
||||
bOKDisabled?: boolean;
|
||||
bCancelDisabled?: boolean;
|
||||
}
|
||||
|
||||
export interface ConfirmModalProps extends ModalRootProps {
|
||||
onMiddleButton?(): void; // setting this prop will enable the middle button
|
||||
strTitle?: ReactNode;
|
||||
strDescription?: ReactNode;
|
||||
strOKButtonText?: ReactNode;
|
||||
strCancelButtonText?: ReactNode;
|
||||
strMiddleButtonText?: ReactNode;
|
||||
bAlertDialog?: boolean; // This will open a modal with only OK button enabled
|
||||
bMiddleDisabled?: boolean;
|
||||
}
|
||||
|
||||
export const ConfirmModal = findModuleChild((m) => {
|
||||
if (typeof m !== 'object') return undefined;
|
||||
for (let prop in m) {
|
||||
if (!m[prop]?.prototype?.OK && m[prop]?.prototype?.Cancel && m[prop]?.prototype?.render) {
|
||||
return m[prop];
|
||||
}
|
||||
}
|
||||
}) as FC<ConfirmModalProps>;
|
||||
|
||||
// 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]?.m_mapModalManager && Object.values(m)?.find((x: any) => x?.type)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
for (let prop in m) {
|
||||
if (m[prop]?.prototype?.OK && m[prop]?.prototype?.Cancel && m[prop]?.prototype?.render) {
|
||||
return m[prop];
|
||||
}
|
||||
}
|
||||
})) as FC<ModalRootProps>;
|
||||
|
||||
interface SimpleModalProps {
|
||||
active?: boolean;
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
const ModalModule = findModule((mod: any) => {
|
||||
if (typeof mod !== 'object') return false;
|
||||
for (let prop in mod) {
|
||||
if (Object.keys(mod).length > 4 && mod[prop]?.toString().includes('.ModalPosition,fallback:')) return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
const ModalModuleProps = ModalModule ? Object.values(ModalModule) : [];
|
||||
|
||||
export const SimpleModal = ModalModuleProps.find(prop => {
|
||||
const string = prop?.toString()
|
||||
return string?.includes(".ShowPortalModal()") && string?.includes(".OnElementReadyCallbacks.Register(")
|
||||
}) as FC<SimpleModalProps>;
|
||||
|
||||
export const ModalPosition = ModalModuleProps.find(prop => prop?.toString().includes(".ModalPosition,fallback:")) as FC<SimpleModalProps>;
|
||||
@@ -1,33 +0,0 @@
|
||||
import { FC, ReactNode } from 'react';
|
||||
|
||||
import { findModuleChild } from '../webpack';
|
||||
|
||||
export const Panel: FC<{ children?: ReactNode; }> = findModuleChild((mod) => {
|
||||
if (typeof mod !== 'object' || !mod.__esModule) return undefined;
|
||||
return mod.Panel;
|
||||
})
|
||||
|
||||
export interface PanelSectionProps {
|
||||
title?: string;
|
||||
spinner?: boolean;
|
||||
children?: ReactNode;
|
||||
}
|
||||
|
||||
const [panelSection, mod] = findModuleChild((mod: any) => {
|
||||
for (let prop in mod) {
|
||||
if (mod[prop]?.toString()?.includes('.PanelSection')) {
|
||||
return [mod[prop], mod];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
export const PanelSection = panelSection as FC<PanelSectionProps>;
|
||||
|
||||
export interface PanelSectionRowProps {
|
||||
children?: ReactNode;
|
||||
}
|
||||
// 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>;
|
||||
@@ -1,44 +0,0 @@
|
||||
import { ReactNode, VFC } from 'react';
|
||||
|
||||
import { findModuleChild } from '../webpack';
|
||||
import { ItemProps } from './Item';
|
||||
|
||||
export interface ProgressBarItemProps extends ItemProps {
|
||||
indeterminate?: boolean;
|
||||
nTransitionSec?: number;
|
||||
nProgress?: number;
|
||||
focusable?: boolean;
|
||||
}
|
||||
|
||||
export interface ProgressBarProps {
|
||||
indeterminate?: boolean;
|
||||
nTransitionSec?: number;
|
||||
nProgress?: number;
|
||||
focusable?: boolean;
|
||||
}
|
||||
|
||||
export interface ProgressBarWithInfoProps extends ProgressBarItemProps {
|
||||
sTimeRemaining?: ReactNode;
|
||||
sOperationText?: ReactNode;
|
||||
}
|
||||
|
||||
export const ProgressBar = findModuleChild((m) => {
|
||||
if (typeof m !== 'object') return undefined;
|
||||
for (let prop in m) {
|
||||
if (m[prop]?.toString()?.includes('.ProgressBar,"standard"==')) return m[prop];
|
||||
}
|
||||
}) as VFC<ProgressBarProps>;
|
||||
|
||||
export const ProgressBarWithInfo = findModuleChild((m) => {
|
||||
if (typeof m !== 'object') return undefined;
|
||||
for (let prop in m) {
|
||||
if (m[prop]?.toString()?.includes('.ProgressBarFieldStatus},')) return m[prop];
|
||||
}
|
||||
}) as VFC<ProgressBarWithInfoProps>;
|
||||
|
||||
export const ProgressBarItem = findModuleChild((m) => {
|
||||
if (typeof m !== 'object') return undefined;
|
||||
for (let prop in m) {
|
||||
if (m[prop]?.toString()?.includes('"indeterminate","nTransitionSec"')) return m[prop];
|
||||
}
|
||||
}) as VFC<ProgressBarItemProps>;
|
||||
@@ -1,22 +0,0 @@
|
||||
import { FC, ReactNode } from 'react';
|
||||
|
||||
import { findModule, findModuleChild } from '../webpack';
|
||||
|
||||
const ScrollingModule = findModule((mod) => {
|
||||
if (typeof mod !== 'object') return false;
|
||||
for (let prop in mod) {
|
||||
if (mod[prop]?.render?.toString?.().includes("{case\"x\":")) return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
const ScrollingModuleProps = ScrollingModule ? Object.values(ScrollingModule) : [];
|
||||
|
||||
export const ScrollPanel = ScrollingModuleProps.find((prop: any) => prop?.render?.toString?.().includes("{case\"x\":")) as FC<{ children?: ReactNode }>;
|
||||
|
||||
export const ScrollPanelGroup: FC<{ children?: ReactNode }> = findModuleChild((mod) => {
|
||||
if (typeof mod !== 'object') return undefined;
|
||||
for (let prop in mod) {
|
||||
if (mod[prop]?.render?.toString().includes(".FocusVisibleChild()),[])")) return mod[prop];
|
||||
}
|
||||
});
|
||||
@@ -1,8 +0,0 @@
|
||||
import { FC, SVGAttributes } from 'react';
|
||||
|
||||
import { IconsModule } from '../webpack';
|
||||
|
||||
// TODO type this and other icons?
|
||||
export const Spinner = Object.values(IconsModule).find(
|
||||
(mod: any) => mod?.toString && /Spinner\)}\),.\.createElement\(\"path\",{d:\"M18 /.test(mod.toString()),
|
||||
) as FC<SVGAttributes<SVGElement>>;
|
||||
@@ -1,13 +0,0 @@
|
||||
import { FC, SVGAttributes } from 'react';
|
||||
|
||||
import { findModuleChild } from '../webpack';
|
||||
|
||||
export const SteamSpinner = findModuleChild((m) => {
|
||||
if (typeof m !== 'object') return undefined;
|
||||
for (let prop in m) {
|
||||
if (
|
||||
m[prop]?.toString?.()?.includes('Steam Spinner') && m[prop]?.toString?.()?.includes('src')
|
||||
)
|
||||
return m[prop];
|
||||
}
|
||||
}) as FC<SVGAttributes<SVGElement>>;
|
||||
@@ -1,749 +0,0 @@
|
||||
import { findModule } from '../webpack';
|
||||
|
||||
type QuickAccessMenuClasses = Record<
|
||||
| 'ActiveTab'
|
||||
| 'AllTabContents'
|
||||
| 'BatteryDetailsLabels'
|
||||
| 'BatteryIcon'
|
||||
| 'BatteryPercentageLabel'
|
||||
| 'BatteryProjectedLabel'
|
||||
| 'BatteryProjectedValue'
|
||||
| 'BatterySectionContainer'
|
||||
| 'Blocked'
|
||||
| 'ComingSoon'
|
||||
| 'Container'
|
||||
| 'ContentTransition'
|
||||
| 'Down'
|
||||
| 'EmptyNotifications'
|
||||
| 'Enter'
|
||||
| 'EnterActive'
|
||||
| 'Exit'
|
||||
| 'ExitActive'
|
||||
| 'FooterBoxShadow'
|
||||
| 'FriendsListTabPanel'
|
||||
| 'FriendsTitle'
|
||||
| 'FullHeight'
|
||||
| 'HeaderAndFooterVisible'
|
||||
| 'HeaderContainer'
|
||||
| 'ItemFocusAnim-darkGrey'
|
||||
| 'ItemFocusAnim-darkerGrey'
|
||||
| 'ItemFocusAnim-darkerGrey-nocolor'
|
||||
| 'ItemFocusAnim-green'
|
||||
| 'ItemFocusAnim-grey'
|
||||
| 'ItemFocusAnimBorder-darkGrey'
|
||||
| 'KeyboardButton'
|
||||
| 'Label'
|
||||
| 'LowBattery'
|
||||
| 'LowBatteryGauge'
|
||||
| 'Menu'
|
||||
| 'Open'
|
||||
| 'PanelExitAnchor'
|
||||
| 'PanelOuterNav'
|
||||
| 'PanelSection'
|
||||
| 'PanelSectionRow'
|
||||
| 'PanelSectionTitle'
|
||||
| 'QuickAccessMenu'
|
||||
| 'ReallyLow'
|
||||
| 'Remaining'
|
||||
| 'Selected'
|
||||
| 'Tab'
|
||||
| 'TabContentColumn'
|
||||
| 'TabGroupPanel'
|
||||
| 'TabPanelHidden'
|
||||
| 'Tabs'
|
||||
| 'Text'
|
||||
| 'Title'
|
||||
| 'TransitionMenuDelay'
|
||||
| 'Up'
|
||||
| 'ViewPlaceholder'
|
||||
| 'VoiceTab'
|
||||
| 'duration-app-launch'
|
||||
| 'focusAnimation'
|
||||
| 'hoverAnimation',
|
||||
string
|
||||
>;
|
||||
|
||||
type ScrollPanelClasses = Record<'ScrollBoth' | 'ScrollPanel' | 'ScrollX' | 'ScrollY', string>;
|
||||
|
||||
type GamepadDialogClasses = Record<
|
||||
| 'duration-app-launch'
|
||||
| 'GamepadDialogContent'
|
||||
| 'GamepadDialogContent_InnerWidth'
|
||||
| 'Field'
|
||||
| 'Button'
|
||||
| 'NoMinWidth'
|
||||
| 'ActiveAndUnfocused'
|
||||
| 'StandaloneFieldSeparator'
|
||||
| 'StandardPadding'
|
||||
| 'CompactPadding'
|
||||
| 'WithDescription'
|
||||
| 'WithBottomSeparatorStandard'
|
||||
| 'WithBottomSeparatorThick'
|
||||
| 'HighlightOnFocus'
|
||||
| 'ItemFocusAnim-darkerGrey'
|
||||
| 'ItemFocusAnim-darkGrey'
|
||||
| 'WithBottomSeparator'
|
||||
| 'Disabled'
|
||||
| 'Clickable'
|
||||
| 'FieldClickTarget'
|
||||
| 'FieldChildren'
|
||||
| 'FieldLeadIcon'
|
||||
| 'FieldLabelRow'
|
||||
| 'VerticalAlignCenter'
|
||||
| 'InlineWrapShiftsChildrenBelow'
|
||||
| 'ExtraPaddingOnChildrenBelow'
|
||||
| 'ChildrenWidthFixed'
|
||||
| 'ChildrenWidthGrow'
|
||||
| 'WithFirstRow'
|
||||
| 'WithChildrenBelow'
|
||||
| 'FieldLabel'
|
||||
| 'FieldLabelValue'
|
||||
| 'FieldDescription'
|
||||
| 'ModalPosition'
|
||||
| 'WithStandardPadding'
|
||||
| 'slideInAnimation'
|
||||
| 'BasicTextInput'
|
||||
| 'Toggle'
|
||||
| 'ToggleRail'
|
||||
| 'On'
|
||||
| 'ToggleSwitch'
|
||||
| 'LabelFieldValue'
|
||||
| 'DropDownControlButtonContents'
|
||||
| 'Spacer'
|
||||
| 'ControlsListOuterPanel'
|
||||
| 'StandardSpacing'
|
||||
| 'ExtraSpacing'
|
||||
| 'AlignRight'
|
||||
| 'AlignLeft'
|
||||
| 'AlignCenter'
|
||||
| 'ControlsListChild'
|
||||
| 'QuickAccess-Menu'
|
||||
| 'BigButtons'
|
||||
| 'BottomButtons'
|
||||
| 'ItemFocusAnim-darkerGrey-nocolor'
|
||||
| 'ItemFocusAnim-grey'
|
||||
| 'ItemFocusAnimBorder-darkGrey'
|
||||
| 'ItemFocusAnim-green'
|
||||
| 'focusAnimation'
|
||||
| 'hoverAnimation',
|
||||
string
|
||||
>;
|
||||
|
||||
type QuickAccessControlsClasses = Record<
|
||||
| 'duration-app-launch'
|
||||
| 'PanelSection'
|
||||
| 'PanelSectionTitle'
|
||||
| 'Text'
|
||||
| 'PanelSectionRow'
|
||||
| 'Label'
|
||||
| 'ComingSoon'
|
||||
| 'LowBattery'
|
||||
| 'ReallyLow'
|
||||
| 'LowBatteryGauge'
|
||||
| 'Remaining'
|
||||
| 'EmptyNotifications'
|
||||
| 'BatterySectionContainer'
|
||||
| 'BatteryIcon'
|
||||
| 'BatteryPercentageLabel'
|
||||
| 'BatteryDetailsLabels'
|
||||
| 'BatteryProjectedValue'
|
||||
| 'BatteryProjectedLabel'
|
||||
| 'ItemFocusAnim-darkerGrey-nocolor'
|
||||
| 'ItemFocusAnim-darkerGrey'
|
||||
| 'ItemFocusAnim-darkGrey'
|
||||
| 'ItemFocusAnim-grey'
|
||||
| 'ItemFocusAnimBorder-darkGrey'
|
||||
| 'ItemFocusAnim-green'
|
||||
| 'focusAnimation'
|
||||
| 'hoverAnimation',
|
||||
string
|
||||
>;
|
||||
|
||||
type UpdaterFieldClasses = Record<
|
||||
| 'duration-app-launch'
|
||||
| 'OOBEUpdateStatusContainer'
|
||||
| 'UpdateScreen'
|
||||
| 'UpdatePanel'
|
||||
| 'CurrentStatus'
|
||||
| 'TotalUpdateSize'
|
||||
| 'ProgressInfoContainer'
|
||||
| 'TimeRemaining'
|
||||
| 'BatteryLowWarning'
|
||||
| 'fadeInAnimation'
|
||||
| 'ProgressStatus'
|
||||
| 'UpdateStatusContainer'
|
||||
| 'UpdaterFieldStatusSuccess'
|
||||
| 'UpdaterFieldStatusApplying'
|
||||
| 'TextContainer'
|
||||
| 'ApplyingText'
|
||||
| 'UpdateBytesRemaining'
|
||||
| 'Label'
|
||||
| 'Numerator'
|
||||
| 'Separator'
|
||||
| 'Denominator'
|
||||
| 'PatchNotes'
|
||||
| 'PostedTime'
|
||||
| 'EventDetailTitle'
|
||||
| 'EventDetailsSubTitle'
|
||||
| 'EventDetailsBody'
|
||||
| 'InsufficientBatteryText'
|
||||
| 'UnsupportedHardwareWarning'
|
||||
| 'Title'
|
||||
| 'Text'
|
||||
| 'Body'
|
||||
| 'ItemFocusAnim-darkerGrey-nocolor'
|
||||
| 'ItemFocusAnim-darkerGrey'
|
||||
| 'ItemFocusAnim-darkGrey'
|
||||
| 'ItemFocusAnim-grey'
|
||||
| 'ItemFocusAnimBorder-darkGrey'
|
||||
| 'ItemFocusAnim-green'
|
||||
| 'focusAnimation'
|
||||
| 'hoverAnimation',
|
||||
string
|
||||
>;
|
||||
|
||||
type PlaySectionClasses = Record<
|
||||
| 'AchievementCountLabel'
|
||||
| 'AchievementProgressRow'
|
||||
| 'ActionSection'
|
||||
| 'AppButtonsContainer'
|
||||
| 'Arrow'
|
||||
| 'AvatarAndPersona'
|
||||
| 'BreakNarrow'
|
||||
| 'BreakShort'
|
||||
| 'BreakTall'
|
||||
| 'BreakUltraWide'
|
||||
| 'BreakWide'
|
||||
| 'ClickablePlayBarItem'
|
||||
| 'CloudStatusIcon'
|
||||
| 'CloudStatusLabel'
|
||||
| 'CloudStatusRow'
|
||||
| 'CloudSyncProblem'
|
||||
| 'CloudSynching'
|
||||
| 'ComingSoon'
|
||||
| 'Container'
|
||||
| 'DetailsProgressBar'
|
||||
| 'DetailsProgressContainer'
|
||||
| 'DetailsSection'
|
||||
| 'DetailsSectionExtra'
|
||||
| 'DetailsSectionStatus'
|
||||
| 'DotDotDot'
|
||||
| 'DownloadPaused'
|
||||
| 'DownloadProgressBar'
|
||||
| 'Downloading'
|
||||
| 'FavoriteButton'
|
||||
| 'Favorited'
|
||||
| 'GameInfoButton'
|
||||
| 'GameStat'
|
||||
| 'GameStatIcon'
|
||||
| 'GameStatIconForced'
|
||||
| 'GameStatRight'
|
||||
| 'GameStatsSection'
|
||||
| 'GamepadUIBreakNarrow'
|
||||
| 'GamepadUIBreakShort'
|
||||
| 'GamepadUIBreakWide'
|
||||
| 'Glassy'
|
||||
| 'HideWhenNarrow'
|
||||
| 'Icon'
|
||||
| 'Icons'
|
||||
| 'InPage'
|
||||
| 'InnerContainer'
|
||||
| 'InvalidPlatform'
|
||||
| 'ItemFocusAnim-darkGrey'
|
||||
| 'ItemFocusAnim-darkerGrey'
|
||||
| 'ItemFocusAnim-darkerGrey-nocolor'
|
||||
| 'ItemFocusAnim-green'
|
||||
| 'ItemFocusAnim-grey'
|
||||
| 'ItemFocusAnimBorder-darkGrey'
|
||||
| 'Label'
|
||||
| 'LastPlayed'
|
||||
| 'LastPlayedInfo'
|
||||
| 'MenuActive'
|
||||
| 'MenuButton'
|
||||
| 'MiniAchievements'
|
||||
| 'OfflineMode'
|
||||
| 'OnlyDownloadBar'
|
||||
| 'PermanentlyUnavailable'
|
||||
| 'PlayBar'
|
||||
| 'PlayBarCloudStatusContainer'
|
||||
| 'PlayBarDetailLabel'
|
||||
| 'PlayBarGameIcon'
|
||||
| 'PlayBarGameName'
|
||||
| 'PlayBarIconAndGame'
|
||||
| 'PlayBarLabel'
|
||||
| 'Playtime'
|
||||
| 'PlaytimeIcon'
|
||||
| 'PlaytimeIconForced'
|
||||
| 'PortraitBar'
|
||||
| 'Presale'
|
||||
| 'RecentlyUpdated'
|
||||
| 'RecentlyUpdatedIcon'
|
||||
| 'RecentlyUpdatedLink'
|
||||
| 'RecentlyUpdatedText'
|
||||
| 'RightBreakNarrow'
|
||||
| 'RightBreakUltraNarrow'
|
||||
| 'RightBreakUltraWide'
|
||||
| 'RightBreakWide'
|
||||
| 'RightControls'
|
||||
| 'Row'
|
||||
| 'SharedLibrary'
|
||||
| 'StatusAndStats'
|
||||
| 'StatusNameContainer'
|
||||
| 'StickyHeader'
|
||||
| 'StickyHeaderShadow'
|
||||
| 'SuperimposedGridItems'
|
||||
| 'SyncAnim'
|
||||
| 'Visible'
|
||||
| 'duration-app-launch'
|
||||
| 'favorited'
|
||||
| 'focusAnimation'
|
||||
| 'hoverAnimation',
|
||||
string
|
||||
>;
|
||||
|
||||
type GamepadSliderClasses = Record<
|
||||
| 'error-shake-duration'
|
||||
| 'SliderControlPanelGroup'
|
||||
| 'SliderControlAndNotches'
|
||||
| 'WithDefaultValue'
|
||||
| 'SliderControl'
|
||||
| 'Disabled'
|
||||
| 'SliderTrack'
|
||||
| 'SliderHasNotches'
|
||||
| 'SliderTrackDark'
|
||||
| 'SliderHandleContainer'
|
||||
| 'VerticalLineSliderHandleContainer'
|
||||
| 'ParenSliderHandleContainer'
|
||||
| 'SliderHandle'
|
||||
| 'SliderHandleFocusPop'
|
||||
| 'VerticalLineSliderHandle'
|
||||
| 'ParenSliderHandle'
|
||||
| 'Left'
|
||||
| 'SliderControlWithIcon'
|
||||
| 'Icon'
|
||||
| 'SliderNotchContainer'
|
||||
| 'SliderNotch'
|
||||
| 'AlignToEnds'
|
||||
| 'SliderNotchLabel'
|
||||
| 'AlignToLeft'
|
||||
| 'AlignToRight'
|
||||
| 'SliderNotchTick'
|
||||
| 'TickActive'
|
||||
| 'LabelText'
|
||||
| 'DescriptionValue'
|
||||
| 'EditableValue'
|
||||
| 'FakeEditableValue'
|
||||
| 'RedBorder'
|
||||
| 'EditableValueSuffix'
|
||||
| 'ErrorShake'
|
||||
| 'error-shake'
|
||||
| 'CompoundSlider'
|
||||
| 'CompoundSliderSubSlider'
|
||||
| 'Right'
|
||||
| 'CompoundSliderSubSliderLabelContainer'
|
||||
| 'CompoundSliderSubSliderLabelPositioner'
|
||||
| 'CompoundSliderSubSliderLabel'
|
||||
| 'CompoundSliderSubSliderLabelInternal'
|
||||
| 'DefaultValueTickContainer'
|
||||
| 'DefaultValueTick',
|
||||
string
|
||||
>;
|
||||
|
||||
type AppDetailsHeaderClasses = Record<
|
||||
| 'AddBoxSizer'
|
||||
| 'Background'
|
||||
| 'Bottom'
|
||||
| 'BottomCenter'
|
||||
| 'BottomLeft'
|
||||
| 'BottomRight'
|
||||
| 'BoxSizer'
|
||||
| 'BoxSizerButtonContainer'
|
||||
| 'BoxSizerContainer'
|
||||
| 'BoxSizerDelete'
|
||||
| 'BoxSizerDragBox'
|
||||
| 'BoxSizerEdge'
|
||||
| 'BoxSizerGridBox'
|
||||
| 'BoxSizerInfo'
|
||||
| 'BoxSizerSettings'
|
||||
| 'BoxSizerValidRegion'
|
||||
| 'CenterCenter'
|
||||
| 'DialogButton'
|
||||
| 'EdgeDown'
|
||||
| 'FallbackArt'
|
||||
| 'Features'
|
||||
| 'FullscreenEnterActive'
|
||||
| 'FullscreenEnterDone'
|
||||
| 'FullscreenEnterStart'
|
||||
| 'FullscreenExitActive'
|
||||
| 'FullscreenExitDone'
|
||||
| 'FullscreenExitStart'
|
||||
| 'HeaderBackgroundImage'
|
||||
| 'ImgBlur'
|
||||
| 'ImgBlurBackdrop'
|
||||
| 'ImgContainer'
|
||||
| 'ImgSrc'
|
||||
| 'Left'
|
||||
| 'Loaded'
|
||||
| 'Middle'
|
||||
| 'NoArt'
|
||||
| 'PinBox'
|
||||
| 'Right'
|
||||
| 'SVGTitle'
|
||||
| 'SaveBoxSizer'
|
||||
| 'TextNameSpace'
|
||||
| 'TitleImageContainer'
|
||||
| 'TitleLogo'
|
||||
| 'TitleSection'
|
||||
| 'Top'
|
||||
| 'TopCapsule'
|
||||
| 'TopGradient'
|
||||
| 'TopLeft'
|
||||
| 'TopRight'
|
||||
| 'UpperCenter'
|
||||
| 'UpperLeft'
|
||||
| 'duration-app-launch',
|
||||
string
|
||||
>;
|
||||
|
||||
type AppDetailsClasses = Record<
|
||||
| 'BreakNarrow'
|
||||
| 'BreakShort'
|
||||
| 'BreakTall'
|
||||
| 'BreakUltraWide'
|
||||
| 'BreakWide'
|
||||
| 'Container'
|
||||
| 'GamepadUIBreakNarrow'
|
||||
| 'GamepadUIBreakShort'
|
||||
| 'GamepadUIBreakWide'
|
||||
| 'Glassy'
|
||||
| 'Header'
|
||||
| 'HeaderLoaded'
|
||||
| 'InnerContainer'
|
||||
| 'ItemFocusAnim-darkGrey'
|
||||
| 'ItemFocusAnim-darkerGrey'
|
||||
| 'ItemFocusAnim-darkerGrey-nocolor'
|
||||
| 'ItemFocusAnim-green'
|
||||
| 'ItemFocusAnim-grey'
|
||||
| 'ItemFocusAnimBorder-darkGrey'
|
||||
| 'PlayBar'
|
||||
| 'PreventScrolling'
|
||||
| 'RightBreakNarrow'
|
||||
| 'RightBreakUltraNarrow'
|
||||
| 'RightBreakUltraWide'
|
||||
| 'RightBreakWide'
|
||||
| 'ScrollContainer'
|
||||
| 'ShowPlayBar'
|
||||
| 'Throbber'
|
||||
| 'duration-app-launch'
|
||||
| 'fadein'
|
||||
| 'focusAnimation'
|
||||
| 'hoverAnimation',
|
||||
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
|
||||
>;
|
||||
|
||||
type GamepadTabbedPageClasses = Record<
|
||||
| 'duration-app-launch'
|
||||
| 'headerHeight'
|
||||
| 'contentPadding'
|
||||
| 'GamepadTabbedPage'
|
||||
| 'TabHeaderRowWrapper'
|
||||
| 'Floating'
|
||||
| 'TabRow'
|
||||
| 'TabRowTabs'
|
||||
| 'BleedGlyphs'
|
||||
| 'TabsRowScroll'
|
||||
| 'FixCenterAlignScroll'
|
||||
| 'Tab'
|
||||
| 'Selected'
|
||||
| 'HasAddon'
|
||||
| 'RightAddon'
|
||||
| 'TabTitle'
|
||||
| 'LeftAddon'
|
||||
| 'TabCount'
|
||||
| 'Active'
|
||||
| 'TabBadge'
|
||||
| 'TabCountBadge'
|
||||
| 'TabRowSpacer'
|
||||
| 'Glyphs'
|
||||
| 'Show'
|
||||
| 'TabContents'
|
||||
| 'ContentTransition'
|
||||
| 'TabContentsScroll'
|
||||
| 'Right'
|
||||
| 'Enter'
|
||||
| 'EnterActive'
|
||||
| 'Exit'
|
||||
| 'ExitActive'
|
||||
| 'Left'
|
||||
| 'TabIcon',
|
||||
string
|
||||
>;
|
||||
|
||||
type GamepadContextMenuClasses = Record<
|
||||
| "duration-app-launch"
|
||||
| "BasicContextMenuModal"
|
||||
| "BasicContextMenuHeader"
|
||||
| "BasicContextMenuHeaderShrinkableSpacing"
|
||||
| "BasicContextMenuContainer"
|
||||
| "slideInAnimation"
|
||||
| "contextMenu"
|
||||
| "contextMenuContents"
|
||||
| "hasSubMenu"
|
||||
| "contextMenuFade"
|
||||
| "contextMenuItem"
|
||||
| "active"
|
||||
| "Selected"
|
||||
| "Focused"
|
||||
| "Positive"
|
||||
| "Emphasis"
|
||||
| "Destructive"
|
||||
| "Capitalized"
|
||||
| "MenuSectionHeader"
|
||||
| "UpperCase"
|
||||
| "SubMenu"
|
||||
| "ContextMenuSeparator"
|
||||
| "Label"
|
||||
| "Arrow"
|
||||
| "ItemFocusAnim-darkerGrey-nocolor"
|
||||
| "ItemFocusAnim-darkerGrey"
|
||||
| "ItemFocusAnim-darkGrey"
|
||||
| "ItemFocusAnim-grey"
|
||||
| "ItemFocusAnimBorder-darkGrey"
|
||||
| "ItemFocusAnim-green"
|
||||
| "focusAnimation"
|
||||
| "hoverAnimation",
|
||||
string
|
||||
>;
|
||||
|
||||
type AchievementClasses = Record<
|
||||
| "nAchievementHeight"
|
||||
| "nGlobalAchievementHeight"
|
||||
| "nAchievementsListTitleHeight"
|
||||
| "nAchievementGap"
|
||||
| "AchievementList"
|
||||
| "ListTitle"
|
||||
| "AchievementListItemBase"
|
||||
| "Container"
|
||||
| "Content"
|
||||
| "Right"
|
||||
| "Footer"
|
||||
| "AchievementTitle"
|
||||
| "AchievementDescription"
|
||||
| "AchievementGlobalPercentage"
|
||||
| "InBody"
|
||||
| "VerticalContent"
|
||||
| "UnlockDate"
|
||||
| "AlignEnd"
|
||||
| "ProgressBar"
|
||||
| "ProgressCount"
|
||||
| "AchievementContent"
|
||||
| "HiddenAchievementContent"
|
||||
| "FriendAchievementFooter"
|
||||
| "GlobalPercentage"
|
||||
| "UserUnlockDateTime"
|
||||
| "GlobalAchievementsListHeader"
|
||||
| "SearchField"
|
||||
| "Avatar"
|
||||
| "HeaderText"
|
||||
| "GlobalAchievementListItem"
|
||||
| "UnlockContainer"
|
||||
| "Info"
|
||||
| "Title"
|
||||
| "Description"
|
||||
| "Percent"
|
||||
| "ImageContainer"
|
||||
| "ProgressFill"
|
||||
| "SpoilerWarning"
|
||||
| "Hidden"
|
||||
| "ComparisonAchieverColumn"
|
||||
| "ComparisonAchieverInfo"
|
||||
| "ProgressContainer"
|
||||
| "ProgressLabel"
|
||||
| "Secondary"
|
||||
| "AvatarContainer"
|
||||
| "Unachieved",
|
||||
string
|
||||
>;
|
||||
|
||||
type MainMenuAppRunningClasses = Record<
|
||||
| "duration-app-launch"
|
||||
| "ScrollMask"
|
||||
| "HideMask"
|
||||
| "MainMenuAppRunning"
|
||||
| "MenuOpen"
|
||||
| "NavigationColumn"
|
||||
| "ControllerColumnFocused"
|
||||
| "NavColumnFocused"
|
||||
| "NavigationBox"
|
||||
| "NavigationMenuItem"
|
||||
| "ItemFocusAnim-darkerGrey"
|
||||
| "Active"
|
||||
| "Disabled"
|
||||
| "SwitchAppsTitle"
|
||||
| "SelectableAppWindow"
|
||||
| "ActiveDot"
|
||||
| "NavigationMenuItemSeparator"
|
||||
| "AppColumn"
|
||||
| "FocusedColumn"
|
||||
| "AppColumnContent"
|
||||
| "ActiveContent"
|
||||
| "CurrentGameBackground"
|
||||
| "CurrentGameLogo"
|
||||
| "OverlayAchievements"
|
||||
| "Container"
|
||||
| "OverlayGuides"
|
||||
| "OverlayNotes"
|
||||
| "OverlayInplaceBrowser"
|
||||
| "ItemFocusAnim-darkerGrey-nocolor"
|
||||
| "ItemFocusAnim-darkGrey"
|
||||
| "ItemFocusAnim-grey"
|
||||
| "ItemFocusAnimBorder-darkGrey"
|
||||
| "ItemFocusAnim-green"
|
||||
| "focusAnimation"
|
||||
| "hoverAnimation",
|
||||
string
|
||||
>;
|
||||
|
||||
type BasicAppDetailsSectionStylerClasses = Record<
|
||||
| "duration-app-launch"
|
||||
| "headerPadding"
|
||||
| "Header"
|
||||
| "AppDetailsContent"
|
||||
| "AppDetailsContainer"
|
||||
| "AppDetailsRoot"
|
||||
| "GameInfoContainer"
|
||||
| "GameInfoQuickLinks"
|
||||
| "GameInfoCollections"
|
||||
| "CollectionsHeader"
|
||||
| "PlaySection"
|
||||
| "ActionRow"
|
||||
| "AppDetailSectionList"
|
||||
| "AppActionButton"
|
||||
| "ActionButtonAndStatusPanel"
|
||||
| "AppButtons"
|
||||
| "InvertFocusedIcon"
|
||||
| "DeckVerifiedFeedbackContainer"
|
||||
| "DeckVerifiedFeedbackConfirmationContainer"
|
||||
| "DeckVerifiedFeedbackButton"
|
||||
| "DeckVerifiedFeedbackQuestion"
|
||||
| "DeckVerifiedFeedbackConfirmation",
|
||||
string
|
||||
>;
|
||||
|
||||
export const quickAccessMenuClasses: QuickAccessMenuClasses = findModule(
|
||||
(mod) => typeof mod === 'object' && mod?.Title && mod?.QuickAccessMenu && mod?.BatteryDetailsLabels,
|
||||
);
|
||||
/**
|
||||
* @depreciated please use quickAccessMenuClasses instead
|
||||
*/
|
||||
export const staticClasses = quickAccessMenuClasses;
|
||||
export const scrollPanelClasses: ScrollPanelClasses = findModule(
|
||||
(mod) => typeof mod === 'object' && mod?.ScrollPanel,
|
||||
);
|
||||
/**
|
||||
* @depreciated please use scrollPanelClasses instead
|
||||
*/
|
||||
export const scrollClasses = scrollPanelClasses;
|
||||
// TODO refactor to use class mapper
|
||||
export const gamepadDialogClasses: GamepadDialogClasses = findModule(
|
||||
(mod) => typeof mod === 'object' && mod?.GamepadDialogContent,
|
||||
);
|
||||
export const quickAccessControlsClasses: QuickAccessControlsClasses = findModule(
|
||||
(mod) => typeof mod === 'object' && typeof mod?.PanelSection === 'string' && mod?.PanelSection,
|
||||
);
|
||||
export const updaterFieldClasses: UpdaterFieldClasses = findModule(
|
||||
(mod) => typeof mod === 'object' && mod?.OOBEUpdateStatusContainer,
|
||||
);
|
||||
export const playSectionClasses: PlaySectionClasses = findModule(
|
||||
(mod) => typeof mod === 'object' && mod?.Container,
|
||||
);
|
||||
export const gamepadSliderClasses: GamepadSliderClasses = findModule(
|
||||
(mod) => typeof mod === 'object' && mod?.SliderControlPanelGroup,
|
||||
);
|
||||
export const appDetailsHeaderClasses: AppDetailsHeaderClasses = findModule(
|
||||
(mod) => typeof mod === 'object' && mod?.TopCapsule,
|
||||
);
|
||||
export const appDetailsClasses: AppDetailsClasses = findModule(
|
||||
(mod) => typeof mod === 'object' && mod?.HeaderLoaded,
|
||||
);
|
||||
export const gamepadUIClasses: GamepadUIClasses = findModule(
|
||||
(mod) => typeof mod === 'object' && mod?.BasicUiRoot,
|
||||
);
|
||||
export const gamepadTabbedPageClasses: GamepadTabbedPageClasses = findModule(
|
||||
(mod) => typeof mod === 'object' && mod?.GamepadTabbedPage
|
||||
);
|
||||
export const gamepadContextMenuClasses: GamepadContextMenuClasses = findModule(
|
||||
(mod) => typeof mod === 'object' && mod?.BasicContextMenuModal
|
||||
);
|
||||
export const achievementClasses: AchievementClasses = findModule(
|
||||
(mod) => typeof mod === 'object' && mod?.AchievementListItemBase
|
||||
);
|
||||
export const mainMenuAppRunningClasses: MainMenuAppRunningClasses = findModule(
|
||||
(mod) => typeof mod === 'object' && mod?.MainMenuAppRunning
|
||||
);
|
||||
export const basicAppDetailsSectionStylerClasses: BasicAppDetailsSectionStylerClasses = findModule(
|
||||
(mod) => typeof mod === 'object' && mod?.AppDetailsRoot
|
||||
);
|
||||
@@ -1,3 +1,7 @@
|
||||
declare global {
|
||||
var SteamClient: SteamClient;
|
||||
}
|
||||
|
||||
export interface Apps {
|
||||
RegisterForAppOverviewChanges: any;
|
||||
RegisterForAppDetails: any;
|
||||
@@ -223,7 +227,7 @@ export interface LogoPosition {
|
||||
pinnedPosition: LogoPinPositions;
|
||||
nWidthPct: number;
|
||||
nHeightPct: number;
|
||||
};
|
||||
}
|
||||
|
||||
export interface AppDetails {
|
||||
achievements: AppAchievements;
|
||||
2
src/globals/index.ts
Normal file
2
src/globals/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from './SteamClient';
|
||||
export * from './stores';
|
||||
34
src/deck-components/index.ts → src/globals/stores.ts
Executable file → Normal file
34
src/deck-components/index.ts → src/globals/stores.ts
Executable file → Normal file
@@ -1,37 +1,5 @@
|
||||
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';
|
||||
export * from './ProgressBar';
|
||||
export * from './Router';
|
||||
export * from './SidebarNavigation';
|
||||
export * from './SliderField';
|
||||
export * from './Spinner';
|
||||
export * from './static-classes';
|
||||
export * from './SteamSpinner';
|
||||
export * from './Tabs';
|
||||
export * from './TextField';
|
||||
export * from './Toggle';
|
||||
export * from './ToggleField';
|
||||
export * from './SteamClient';
|
||||
export * from './Scroll';
|
||||
|
||||
import { AppDetails, LogoPosition, SteamAppOverview, SteamClient } from './SteamClient';
|
||||
|
||||
import { AppDetails, LogoPosition, SteamAppOverview } from './SteamClient';
|
||||
declare global {
|
||||
var SteamClient: SteamClient;
|
||||
|
||||
interface Window {
|
||||
LocalizationManager: {
|
||||
m_mapTokens: Map<string, string>;
|
||||
15
src/index.ts
15
src/index.ts
@@ -1,9 +1,20 @@
|
||||
// export * from './deck-libs';
|
||||
export * from './custom-components';
|
||||
export * from './custom-hooks';
|
||||
export * from './deck-components';
|
||||
export * from './components';
|
||||
export * from './deck-hooks';
|
||||
export * from './plugin';
|
||||
export * from './modules';
|
||||
export * from './globals';
|
||||
export * from './webpack';
|
||||
export * from './utils';
|
||||
export * from './class-mapper';
|
||||
|
||||
/**
|
||||
* @deprecated use @decky/api instead
|
||||
*/
|
||||
export const definePlugin = (fn: any): any => {
|
||||
return (...args: any[]) => {
|
||||
// TODO: Maybe wrap this
|
||||
return fn(...args);
|
||||
};
|
||||
};
|
||||
77
src/logger.ts
Normal file
77
src/logger.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
const bgStyle1 = 'background: #16a085; color: black;';
|
||||
|
||||
export const log = (name: string, ...args: any[]) => {
|
||||
console.log(
|
||||
`%c @decky/ui %c ${name} %c`,
|
||||
bgStyle1,
|
||||
'background: #1abc9c; color: black;',
|
||||
'background: transparent;',
|
||||
...args,
|
||||
);
|
||||
};
|
||||
|
||||
export const group = (name: string, ...args: any[]) => {
|
||||
console.group(
|
||||
`%c @decky/ui %c ${name} %c`,
|
||||
bgStyle1,
|
||||
'background: #1abc9c; color: black;',
|
||||
'background: transparent;',
|
||||
...args,
|
||||
);
|
||||
};
|
||||
|
||||
export const groupEnd = (name: string, ...args: any[]) => {
|
||||
console.groupEnd();
|
||||
if (args?.length > 0)
|
||||
console.log(
|
||||
`^ %c @decky/ui %c ${name} %c`,
|
||||
bgStyle1,
|
||||
'background: #1abc9c; color: black;',
|
||||
'background: transparent;',
|
||||
...args,
|
||||
);
|
||||
};
|
||||
|
||||
export const debug = (name: string, ...args: any[]) => {
|
||||
console.debug(`%c @decky/ui %c ${name} %c`, bgStyle1, 'background: #1abc9c; color: black;', 'color: blue;', ...args);
|
||||
};
|
||||
|
||||
export const warn = (name: string, ...args: any[]) => {
|
||||
console.warn(`%c @decky/ui %c ${name} %c`, bgStyle1, 'background: #ffbb00; color: black;', 'color: blue;', ...args);
|
||||
};
|
||||
|
||||
export const error = (name: string, ...args: any[]) => {
|
||||
console.error(`%c @decky/ui %c ${name} %c`, bgStyle1, 'background: #FF0000;', 'background: transparent;', ...args);
|
||||
};
|
||||
|
||||
class Logger {
|
||||
constructor(private name: string) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
log(...args: any[]) {
|
||||
log(this.name, ...args);
|
||||
}
|
||||
|
||||
debug(...args: any[]) {
|
||||
debug(this.name, ...args);
|
||||
}
|
||||
|
||||
warn(...args: any[]) {
|
||||
warn(this.name, ...args);
|
||||
}
|
||||
|
||||
error(...args: any[]) {
|
||||
error(this.name, ...args);
|
||||
}
|
||||
|
||||
group(...args: any[]) {
|
||||
group(this.name, ...args);
|
||||
}
|
||||
|
||||
groupEnd(...args: any[]) {
|
||||
groupEnd(this.name, ...args);
|
||||
}
|
||||
}
|
||||
|
||||
export default Logger;
|
||||
@@ -1,5 +1,5 @@
|
||||
import { sleep } from '../utils';
|
||||
import { Module, findModuleChild } from '../webpack';
|
||||
import { Export, findModuleExport } from '../webpack';
|
||||
|
||||
export enum SideMenu {
|
||||
None,
|
||||
@@ -101,12 +101,7 @@ export interface Router {
|
||||
get MainRunningApp(): AppOverview | undefined;
|
||||
}
|
||||
|
||||
export const Router = findModuleChild((m: Module) => {
|
||||
if (typeof m !== 'object') return undefined;
|
||||
for (let prop in m) {
|
||||
if (m[prop]?.Navigate && m[prop]?.NavigationManager) return m[prop];
|
||||
}
|
||||
}) as Router;
|
||||
export const Router = findModuleExport((e: Export) => e.Navigate && e.NavigationManager) as Router;
|
||||
|
||||
export interface Navigation {
|
||||
Navigate(path: string): void;
|
||||
@@ -133,14 +128,7 @@ try {
|
||||
if (!Router.NavigateToAppProperties || (Router as unknown as any).deckyShim) {
|
||||
function initInternalNavigators() {
|
||||
try {
|
||||
InternalNavigators = findModuleChild((m: any) => {
|
||||
if (typeof m !== 'object') return undefined;
|
||||
for (let prop in m) {
|
||||
if (m[prop]?.GetNavigator && m[prop]?.SetNavigator) {
|
||||
return m[prop];
|
||||
}
|
||||
}
|
||||
})?.GetNavigator();
|
||||
InternalNavigators = findModuleExport((e: Export) => e.GetNavigator && e.SetNavigator)?.GetNavigator();
|
||||
} catch (e) {
|
||||
console.error('[DFL:Router]: Failed to init internal navigators, trying again');
|
||||
}
|
||||
1
src/modules/index.ts
Normal file
1
src/modules/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './Router';
|
||||
@@ -1,96 +0,0 @@
|
||||
import type { ComponentType, ReactNode } from 'react';
|
||||
import { RouteProps } from 'react-router';
|
||||
|
||||
export interface Plugin {
|
||||
title: JSX.Element;
|
||||
icon: JSX.Element;
|
||||
content?: JSX.Element;
|
||||
onDismount?(): void;
|
||||
alwaysRender?: boolean;
|
||||
}
|
||||
|
||||
interface ServerResponseSuccess<TRes> {
|
||||
success: true;
|
||||
result: TRes;
|
||||
}
|
||||
|
||||
interface ServerResponseError {
|
||||
success: false;
|
||||
result: string;
|
||||
}
|
||||
|
||||
export type ServerResponse<TRes> = ServerResponseSuccess<TRes> | ServerResponseError;
|
||||
|
||||
export type RoutePatch = (route: RouteProps) => RouteProps;
|
||||
|
||||
export interface RouterHook {
|
||||
addRoute(path: string, component: ComponentType, props?: Omit<RouteProps, 'path' | 'children'>): void;
|
||||
addPatch(path: string, patch: RoutePatch): RoutePatch;
|
||||
addGlobalComponent(name: string, component: ComponentType): void;
|
||||
removeRoute(path: string): void;
|
||||
removePatch(path: string, patch: RoutePatch): void;
|
||||
removeGlobalComponent(name: string): void;
|
||||
}
|
||||
|
||||
export interface ToastData {
|
||||
title: ReactNode;
|
||||
body: ReactNode;
|
||||
onClick?: () => void;
|
||||
logo?: ReactNode;
|
||||
icon?: ReactNode;
|
||||
className?: string;
|
||||
contentClassName?: string;
|
||||
duration?: number;
|
||||
critical?: boolean;
|
||||
eType?: number;
|
||||
sound?: number;
|
||||
playSound?: boolean;
|
||||
showToast?: boolean;
|
||||
}
|
||||
|
||||
export interface Toaster {
|
||||
toast(toast: ToastData): void;
|
||||
}
|
||||
|
||||
export interface FilePickerRes {
|
||||
path: string;
|
||||
realpath: string;
|
||||
}
|
||||
|
||||
export const enum FileSelectionType {
|
||||
FILE,
|
||||
FOLDER,
|
||||
}
|
||||
|
||||
export interface ServerAPI {
|
||||
routerHook: RouterHook;
|
||||
toaster: Toaster;
|
||||
openFilePicker(startPath: string, includeFiles?: boolean, regex?: RegExp): Promise<FilePickerRes>;
|
||||
openFilePickerV2(
|
||||
select: FileSelectionType,
|
||||
startPath: string,
|
||||
includeFiles?: boolean,
|
||||
includeFolders?: boolean,
|
||||
filter?: RegExp | ((file: File) => boolean),
|
||||
extensions?: string[],
|
||||
showHiddenFiles?: boolean,
|
||||
allowAllFiles?: boolean,
|
||||
max?: number,
|
||||
): Promise<FilePickerRes>;
|
||||
callPluginMethod<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>>;
|
||||
executeInTab(tab: string, runAsync: boolean, code: string): Promise<unknown>;
|
||||
injectCssIntoTab<TRes = string>(tab: string, style: string): Promise<ServerResponse<TRes>>;
|
||||
removeCssFromTab(tab: string, cssId: string): Promise<unknown>;
|
||||
}
|
||||
|
||||
type DefinePluginFn = (serverAPI: ServerAPI) => Plugin;
|
||||
|
||||
// TypeScript helper function
|
||||
export const definePlugin = (fn: DefinePluginFn): DefinePluginFn => {
|
||||
return (...args) => {
|
||||
// TODO: Maybe wrap this
|
||||
return fn(...args);
|
||||
};
|
||||
};
|
||||
@@ -1,5 +1,6 @@
|
||||
export * from './patcher';
|
||||
export * from './react';
|
||||
export * from './static-classes';
|
||||
|
||||
declare global {
|
||||
var FocusNavController: any;
|
||||
|
||||
@@ -8,6 +8,28 @@ declare global {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a Regular Expression to search for a React component that uses certain props in order.
|
||||
*
|
||||
* @export
|
||||
* @param {string[]} propList Ordererd list of properties to search for
|
||||
* @returns {RegExp} RegEx to call .test(component.toString()) on
|
||||
*/
|
||||
export function createPropListRegex(propList: string[], fromStart: boolean = true): RegExp {
|
||||
let regexString = fromStart ? "const\{" : "";
|
||||
propList.forEach((prop: any, propIdx) => {
|
||||
regexString += `"?${prop}"?:[a-zA-Z_$]{1,2}`;
|
||||
if (propIdx < propList.length - 1) {
|
||||
regexString += ",";
|
||||
}
|
||||
});
|
||||
|
||||
// TODO provide a way to enable this
|
||||
// console.debug(`[DFL:Utils] createPropListRegex generated regex "${regexString}" for props`, propList);
|
||||
|
||||
return new RegExp(regexString);
|
||||
}
|
||||
|
||||
export function fakeRenderComponent(fun: Function, customHooks: any = {}): any {
|
||||
const hooks = (window.SP_REACT as any).__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentDispatcher
|
||||
.current;
|
||||
@@ -67,14 +89,18 @@ export function wrapReactClass(node: any, prop: any = 'type') {
|
||||
|
||||
export function getReactRoot(o: HTMLElement | Element | Node) {
|
||||
return (
|
||||
// @ts-expect-error 7053
|
||||
o[Object.keys(o).find((k) => k.startsWith('__reactContainer$')) as string] ||
|
||||
// @ts-expect-error 7053
|
||||
o['_reactRootContainer']?._internalRoot?.current
|
||||
);
|
||||
}
|
||||
|
||||
export function getReactInstance(o: HTMLElement | Element | Node) {
|
||||
return (
|
||||
// @ts-expect-error 7053
|
||||
o[Object.keys(o).find((k) => k.startsWith('__reactFiber')) as string] ||
|
||||
// @ts-expect-error 7053
|
||||
o[Object.keys(o).find((k) => k.startsWith('__reactInternalInstance')) as string]
|
||||
);
|
||||
}
|
||||
|
||||
1104
src/utils/static-classes.ts
Normal file
1104
src/utils/static-classes.ts
Normal file
File diff suppressed because it is too large
Load Diff
121
src/webpack.ts
121
src/webpack.ts
@@ -1,65 +1,104 @@
|
||||
import Logger from './logger';
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
webpackJsonp: any;
|
||||
webpackChunksteamui: any;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO
|
||||
const logger = new Logger('Webpack');
|
||||
|
||||
// In most case an object with getters for each property. Look for the first call to r.d in the module, usually near or at the top.
|
||||
export type Module = any;
|
||||
export type Export = any;
|
||||
type FilterFn = (module: any) => boolean;
|
||||
type ExportFilterFn = (moduleExport: any, exportName?: any) => boolean;
|
||||
type FindFn = (module: any) => any;
|
||||
|
||||
export let webpackCache: any = {};
|
||||
let hasWebpack5 = false;
|
||||
export let modules: any = [];
|
||||
|
||||
if (window.webpackJsonp && !window.webpackJsonp.deckyShimmed) {
|
||||
// Webpack 4, currently on stable
|
||||
const wpRequire = window.webpackJsonp.push([
|
||||
[],
|
||||
{ get_require: (mod: any, _exports: any, wpRequire: any) => (mod.exports = wpRequire) },
|
||||
[['get_require']],
|
||||
]);
|
||||
|
||||
delete wpRequire.m.get_require;
|
||||
delete wpRequire.c.get_require;
|
||||
webpackCache = wpRequire.c;
|
||||
} else {
|
||||
function initModuleCache() {
|
||||
const startTime = performance.now();
|
||||
logger.group('Webpack Module Init');
|
||||
// Webpack 5, currently on beta
|
||||
hasWebpack5 = true;
|
||||
const id = Math.random();
|
||||
let initReq: any;
|
||||
// Generate a fake module ID
|
||||
const id = Math.random(); // really should be an int and not a float but who cares
|
||||
let webpackRequire!: ((id: any) => Module) & { m: object };
|
||||
// Insert our module in a new chunk.
|
||||
// The module will then be called with webpack's internal require function as its first argument
|
||||
window.webpackChunksteamui.push([
|
||||
[id],
|
||||
{},
|
||||
(r: any) => {
|
||||
initReq = r;
|
||||
webpackRequire = r;
|
||||
},
|
||||
]);
|
||||
for (let i of Object.keys(initReq.m)) {
|
||||
|
||||
logger.log(
|
||||
'Initializing all modules. Errors here likely do not matter, as they are usually just failing module side effects.',
|
||||
);
|
||||
|
||||
// Loop over every module ID
|
||||
for (let i of Object.keys(webpackRequire.m)) {
|
||||
try {
|
||||
webpackCache[i] = initReq(i);
|
||||
const module = webpackRequire(i);
|
||||
if (module) {
|
||||
modules.push(module);
|
||||
}
|
||||
} catch (e) {
|
||||
console.debug("[DFL:Webpack]: Ignoring require error for module", i, e);
|
||||
logger.debug('Ignoring require error for module', i, e);
|
||||
}
|
||||
}
|
||||
|
||||
logger.groupEnd(`Modules initialized in ${performance.now() - startTime}ms...`);
|
||||
}
|
||||
|
||||
export const allModules: Module[] = hasWebpack5
|
||||
? Object.values(webpackCache).filter((x) => x)
|
||||
: Object.keys(webpackCache)
|
||||
.map((x) => webpackCache[x].exports)
|
||||
.filter((x) => x);
|
||||
initModuleCache();
|
||||
|
||||
export const findModule = (filter: FilterFn) => {
|
||||
for (const m of allModules) {
|
||||
for (const m of modules) {
|
||||
if (m.default && filter(m.default)) return m.default;
|
||||
if (filter(m)) return m;
|
||||
}
|
||||
};
|
||||
|
||||
export const findModuleDetailsByExport = (
|
||||
filter: ExportFilterFn,
|
||||
minExports?: number,
|
||||
): [module: Module | undefined, moduleExport: any, exportName: any] => {
|
||||
for (const m of modules) {
|
||||
if (!m) continue;
|
||||
for (const mod of [m.default, m]) {
|
||||
if (typeof mod !== 'object') continue;
|
||||
if (minExports && Object.keys(mod).length < minExports) continue;
|
||||
for (let exportName in mod) {
|
||||
if (mod?.[exportName]) {
|
||||
const filterRes = filter(mod[exportName], exportName);
|
||||
if (filterRes) {
|
||||
return [mod, mod[exportName], exportName];
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return [undefined, undefined, undefined];
|
||||
};
|
||||
|
||||
export const findModuleByExport = (filter: ExportFilterFn, minExports?: number) => {
|
||||
return findModuleDetailsByExport(filter, minExports)?.[0];
|
||||
};
|
||||
|
||||
export const findModuleExport = (filter: ExportFilterFn, minExports?: number) => {
|
||||
return findModuleDetailsByExport(filter, minExports)?.[1];
|
||||
};
|
||||
|
||||
/**
|
||||
* @deprecated use findModuleExport instead
|
||||
*/
|
||||
export const findModuleChild = (filter: FindFn) => {
|
||||
for (const m of allModules) {
|
||||
for (const m of modules) {
|
||||
for (const mod of [m.default, m]) {
|
||||
const filterRes = filter(mod);
|
||||
if (filterRes) {
|
||||
@@ -74,7 +113,7 @@ export const findModuleChild = (filter: FindFn) => {
|
||||
export const findAllModules = (filter: FilterFn) => {
|
||||
const out = [];
|
||||
|
||||
for (const m of allModules) {
|
||||
for (const m of modules) {
|
||||
if (m.default && filter(m.default)) out.push(m.default);
|
||||
if (filter(m)) out.push(m);
|
||||
}
|
||||
@@ -82,7 +121,7 @@ export const findAllModules = (filter: FilterFn) => {
|
||||
return out;
|
||||
};
|
||||
|
||||
export const CommonUIModule = allModules.find((m: Module) => {
|
||||
export const CommonUIModule = modules.find((m: Module) => {
|
||||
if (typeof m !== 'object') return false;
|
||||
for (let prop in m) {
|
||||
if (m[prop]?.contextType?._currentValue && Object.keys(m).length > 60) return true;
|
||||
@@ -90,18 +129,8 @@ export const CommonUIModule = allModules.find((m: Module) => {
|
||||
return false;
|
||||
});
|
||||
|
||||
export const IconsModule = findModule((m: Module) => {
|
||||
if (typeof m !== 'object') return false;
|
||||
for (let prop in m) {
|
||||
if (m[prop]?.toString && /Spinner\)}\),.\.createElement\(\"path\",{d:\"M18 /.test(m[prop].toString())) return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
export const IconsModule = findModuleByExport(
|
||||
(e) => e?.toString && /Spinner\)}\)?,.\.createElement\(\"path\",{d:\"M18 /.test(e.toString()),
|
||||
);
|
||||
|
||||
export const ReactRouter = allModules.find((m: Module) => {
|
||||
if (typeof m !== 'object') return undefined;
|
||||
for (let prop in m) {
|
||||
if (m[prop]?.computeRootMatch) return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
export const ReactRouter = findModuleByExport((e) => e.computeRootMatch);
|
||||
|
||||
@@ -14,10 +14,9 @@
|
||||
"noImplicitThis": true,
|
||||
"noImplicitAny": true,
|
||||
"strict": true,
|
||||
"suppressImplicitAnyIndexErrors": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"skipLibCheck": true
|
||||
"removeComments": true,
|
||||
"allowSyntheticDefaultImports": true
|
||||
},
|
||||
"include": ["src", "globals.d.ts"],
|
||||
"include": ["src"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"categorizeByGroup": false,
|
||||
"excludeExternals": true
|
||||
}
|
||||
"categorizeByGroup": false,
|
||||
"excludeExternals": true
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user