mirror of
https://github.com/SteamDeckHomebrew/decky-frontend-lib.git
synced 2026-05-20 10:00:08 +02:00
Compare commits
10 Commits
aa/patcher
...
0.0.4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1e92d5c754 | ||
|
|
668ab618fc | ||
|
|
5d59bf348c | ||
|
|
a5ac702820 | ||
|
|
f882636be7 | ||
|
|
815129d03f | ||
|
|
ef41d17e43 | ||
|
|
6d4ca5dc92 | ||
|
|
0303724794 | ||
|
|
98c226b8e6 |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -35,3 +35,6 @@ Thumbs.db
|
||||
dist/
|
||||
|
||||
research/
|
||||
|
||||
# PNPM lockfile
|
||||
pnpm-lock.yaml
|
||||
|
||||
5
globals.d.ts
vendored
Normal file
5
globals.d.ts
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
declare global {
|
||||
interface Window {
|
||||
SP_REACT: typeof React;
|
||||
}
|
||||
}
|
||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "decky-frontend-lib",
|
||||
"version": "0.0.1",
|
||||
"version": "0.0.4",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "decky-frontend-lib",
|
||||
"version": "0.0.1",
|
||||
"version": "0.0.4",
|
||||
"license": "GPL-2.0-or-later",
|
||||
"devDependencies": {
|
||||
"@types/jest": "^27.4.1",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "decky-frontend-lib",
|
||||
"version": "0.0.2",
|
||||
"version": "0.0.4",
|
||||
"description": "A library for building decky plugins",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
|
||||
2899
pnpm-lock.yaml
generated
2899
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -3,14 +3,27 @@ import { FC } from 'react';
|
||||
import { CommonUIModule } from '../webpack';
|
||||
|
||||
interface ButtonProps {
|
||||
label?: string;
|
||||
description?: string;
|
||||
layout?: 'below';
|
||||
onClick?(e: MouseEvent): void;
|
||||
className?: string;
|
||||
noFocusRing?: boolean;
|
||||
disabled?: boolean;
|
||||
bottomSeparator?: boolean;
|
||||
onClick?(e: MouseEvent): void;
|
||||
onPointerDown?(e: PointerEvent): void;
|
||||
onPointerUp?(e: PointerEvent): void;
|
||||
onPointerCancel?(e: PointerEvent): void;
|
||||
onMouseDown?(e: PointerEvent): void;
|
||||
onMouseUp?(e: MouseEvent): void;
|
||||
onTouchStart?(e: TouchEvent): void;
|
||||
onTouchEnd?(e: TouchEvent): void;
|
||||
onTouchCancel?(e: TouchEvent): void;
|
||||
onSubmit?(e: SubmitEvent): void;
|
||||
}
|
||||
|
||||
export const Button = Object.values(CommonUIModule).find((mod: any) =>
|
||||
mod?.render?.toString()?.includes('childrenContainerWidth:"min"'),
|
||||
) as FC<ButtonProps>;
|
||||
export const DialogButton = Object.values(CommonUIModule).find(
|
||||
(mod: any) =>
|
||||
mod?.render?.toString()?.includes('Object.assign({type:"button"') &&
|
||||
mod?.render?.toString()?.includes('DialogButton'),
|
||||
) as any;
|
||||
|
||||
// Button isn't exported, so call DialogButton to grab it
|
||||
|
||||
export const Button = DialogButton!.render({}).type as FC<ButtonProps>; // its actually a forwarded ref but that doesn't really matter in usage
|
||||
|
||||
17
src/deck-components/ButtonItem.tsx
Normal file
17
src/deck-components/ButtonItem.tsx
Normal file
@@ -0,0 +1,17 @@
|
||||
import { FC } from 'react';
|
||||
|
||||
import { CommonUIModule } from '../webpack';
|
||||
|
||||
interface ButtonItemProps {
|
||||
label?: string;
|
||||
description?: string;
|
||||
layout?: 'below';
|
||||
icon?: JSX.Element;
|
||||
onClick?(e: MouseEvent): void;
|
||||
disabled?: boolean;
|
||||
bottomSeparator?: boolean;
|
||||
}
|
||||
|
||||
export const ButtonItem = Object.values(CommonUIModule).find((mod: any) =>
|
||||
mod?.render?.toString()?.includes('childrenContainerWidth:"min"'),
|
||||
) as FC<ButtonItemProps>;
|
||||
23
src/deck-components/Panel.tsx
Normal file
23
src/deck-components/Panel.tsx
Normal file
@@ -0,0 +1,23 @@
|
||||
import { FC } from 'react';
|
||||
|
||||
import { findModuleChild } from '../webpack';
|
||||
|
||||
interface PanelSectionProps {
|
||||
title?: string;
|
||||
spinner?: boolean;
|
||||
}
|
||||
|
||||
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 const PanelSectionRow = Object.values(mod).filter(
|
||||
(exp: any) => !exp?.toString()?.includes('.PanelSection'),
|
||||
)[0] as FC;
|
||||
16
src/deck-components/Spinner.tsx
Normal file
16
src/deck-components/Spinner.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
import { FC } from 'react';
|
||||
|
||||
import { IconsModule } from '../webpack';
|
||||
|
||||
// interface ButtonProps {
|
||||
// label?: string;
|
||||
// description?: string;
|
||||
// layout?: 'below';
|
||||
// onClick?(e: MouseEvent): void;
|
||||
// disabled?: boolean;
|
||||
// bottomSeparator?: boolean;
|
||||
// }
|
||||
|
||||
export const Spinner = Object.values(IconsModule).find((mod: any) =>
|
||||
mod?.toString && /Spinner\)}\),.\.createElement\(\"path\",{d:\"M18 /.test(mod.toString())
|
||||
) as FC<{}>;
|
||||
@@ -1,6 +1,9 @@
|
||||
export * from './Button';
|
||||
export * from './ButtonItem';
|
||||
export * from './Menu';
|
||||
export * from './Modal';
|
||||
export * from './Panel';
|
||||
export * from './Slider';
|
||||
export * from './Spinner';
|
||||
export * from './static-classes';
|
||||
export * from './Toggle';
|
||||
|
||||
@@ -2,3 +2,4 @@
|
||||
export * from './deck-components';
|
||||
export * from './plugin';
|
||||
export * from './webpack';
|
||||
export * from './utils';
|
||||
|
||||
87
src/utils.ts
Normal file
87
src/utils.ts
Normal file
@@ -0,0 +1,87 @@
|
||||
import * as React from "react";
|
||||
|
||||
// this shouldn't need to be redeclared but it does for some reason
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
SP_REACT: typeof React;
|
||||
}
|
||||
}
|
||||
|
||||
export function fakeRenderComponent(fun: Function): any {
|
||||
const hooks = (window.SP_REACT as any).__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentDispatcher.current;
|
||||
|
||||
// TODO: add more hooks
|
||||
|
||||
let oldHooks = {
|
||||
useContext: hooks.useContext,
|
||||
useCallback: hooks.useCallback,
|
||||
useLayoutEffect: hooks.useLayoutEffect,
|
||||
useEffect: hooks.useEffect,
|
||||
useMemo: hooks.useMemo,
|
||||
useRef: hooks.useRef,
|
||||
useState: hooks.useState,
|
||||
}
|
||||
|
||||
hooks.useCallback = (cb: Function) => cb;
|
||||
hooks.useContext = (cb: any) => cb._currentValue;
|
||||
hooks.useLayoutEffect = (_: Function) => {}//cb();
|
||||
hooks.useMemo = (cb: Function, _: any[]) => cb;
|
||||
hooks.useEffect = (_: Function) => {}//cb();
|
||||
hooks.useRef = (val: any) => ({current: val || {}});
|
||||
hooks.useState = (v: any) => {
|
||||
let val = v;
|
||||
|
||||
return [val, (n: any) => val = n];
|
||||
};
|
||||
|
||||
const res = fun(hooks);
|
||||
|
||||
Object.assign(hooks, oldHooks);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
export function beforePatch(obj: any, name: string, fnc: Function): void {
|
||||
const orig = obj[name];
|
||||
obj[name] = function (...args: any[]) {
|
||||
fnc.call(this, args);
|
||||
return orig.call(this, ...args);
|
||||
}
|
||||
Object.assign(obj[name], orig);
|
||||
obj[name].toString = () => orig.toString();
|
||||
obj[name].__deckyOrig = orig;
|
||||
}
|
||||
|
||||
export function afterPatch(obj: any, name: string, fnc: Function): void {
|
||||
const orig = obj[name];
|
||||
obj[name] = function (...args: any[]) {
|
||||
let ret = orig.call(this, ...args);
|
||||
ret = fnc.call(this, args, ret);
|
||||
return ret;
|
||||
}
|
||||
Object.assign(obj[name], orig);
|
||||
obj[name].toString = () => orig.toString();
|
||||
obj[name].__deckyOrig = orig;
|
||||
}
|
||||
|
||||
export function replacePatch(obj: any, name: string, fnc: Function): void {
|
||||
const orig = obj[name];
|
||||
obj[name] = function (...args: any[]) {
|
||||
const ret = fnc.call(this, args);
|
||||
if (ret == 'CALL_ORIGINAL') return orig.call(this, ...args);
|
||||
return ret;
|
||||
};
|
||||
Object.assign(obj[name], orig);
|
||||
obj[name].toString = () => orig.toString();
|
||||
obj[name].__deckyOrig = orig;
|
||||
}
|
||||
|
||||
// TODO allow one method to be patched and unpatched multiple times independently using IDs in a Map or something
|
||||
export function unpatch(obj: any, name: any): void {
|
||||
obj[name] = obj[name].__deckyOrig;
|
||||
}
|
||||
|
||||
export function getReactInstance(o: HTMLElement | Element | Node) {
|
||||
return o[Object.keys(o).find(k => k.startsWith('__reactInternalInstance')) as string]
|
||||
}
|
||||
@@ -58,7 +58,24 @@ 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 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]}
|
||||
for (let prop in m) {
|
||||
if (m[prop]?.Navigate && m[prop]?.NavigationManager) return m[prop]
|
||||
}
|
||||
})
|
||||
|
||||
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;
|
||||
})
|
||||
@@ -3,7 +3,8 @@
|
||||
"outDir": "dist",
|
||||
"module": "ESNext",
|
||||
"target": "ES2020",
|
||||
"jsx": "react-jsx",
|
||||
"jsx": "react",
|
||||
"jsxFactory": "window.SP_REACT.createElement",
|
||||
"declaration": true,
|
||||
"moduleResolution": "node",
|
||||
"noUnusedLocals": true,
|
||||
@@ -17,6 +18,6 @@
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"skipLibCheck": true
|
||||
},
|
||||
"include": ["src"],
|
||||
"include": ["src", "globals.d.ts"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user