From 7e0cb153b197267f49b5e1f513b54a880bf3710f Mon Sep 17 00:00:00 2001 From: AAGaming Date: Wed, 26 Jun 2024 22:28:58 -0400 Subject: [PATCH] fix(*): fixes for jun 26 beta --- README.md | 3 +++ src/components/ButtonItem.ts | 6 ++++-- src/components/Dialog.ts | 21 ++++++++++++--------- src/components/Dropdown.ts | 4 +++- src/components/Focusable.ts | 5 ++++- src/components/Menu.ts | 3 ++- src/components/ProgressBar.ts | 4 +++- src/components/SidebarNavigation.ts | 4 +++- src/components/Spinner.ts | 4 ++-- src/index.ts | 8 ++++---- src/utils/react.ts | 22 ++++++++++++++++++++++ src/webpack.ts | 2 +- 12 files changed, 63 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index 5dce244..8a4c308 100644 --- a/README.md +++ b/README.md @@ -25,3 +25,6 @@ If you would like a feature added to decky-frontend-lib, please request it via a 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 \ No newline at end of file diff --git a/src/components/ButtonItem.ts b/src/components/ButtonItem.ts index 76657af..51b2219 100644 --- a/src/components/ButtonItem.ts +++ b/src/components/ButtonItem.ts @@ -2,13 +2,15 @@ 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()?.includes('"highlightOnFocus","childrenContainerWidth"') || - mod?.render?.toString()?.includes('childrenContainerWidth:"min"'), + (mod?.render?.toString && buttonItemRegex.test(mod.render.toString())) || + mod?.render?.toString?.().includes('childrenContainerWidth:"min"'), ) as FC; diff --git a/src/components/Dialog.ts b/src/components/Dialog.ts index 6eeba71..c5a6b29 100644 --- a/src/components/Dialog.ts +++ b/src/components/Dialog.ts @@ -52,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]; + } }), ); @@ -72,14 +78,11 @@ export const DialogControlsSection = MappedDialogDivs.get('DialogControlsSection export const DialogControlsSectionHeader = MappedDialogDivs.get('DialogControlsSectionHeader') as FC; 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; 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; // This is the "main" button. The Primary can act as a submit button, diff --git a/src/components/Dropdown.ts b/src/components/Dropdown.ts index 96afdb6..8e63c0c 100644 --- a/src/components/Dropdown.ts +++ b/src/components/Dropdown.ts @@ -2,6 +2,7 @@ import { ReactNode, FC } from 'react'; import { CommonUIModule } from '../webpack'; import { ItemProps } from './Item'; +import { createPropListRegex } from '../utils'; export interface SingleDropdownOption { data: any; @@ -44,6 +45,7 @@ export const Dropdown = Object.values(CommonUIModule).find( 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"'), + mod?.toString && dropdownItemRegex.test(mod.toString()), ) as FC; diff --git a/src/components/Focusable.ts b/src/components/Focusable.ts index f605d1d..dc9a357 100644 --- a/src/components/Focusable.ts +++ b/src/components/Focusable.ts @@ -2,6 +2,7 @@ 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, FooterLegendProps { children: ReactNode; @@ -13,6 +14,8 @@ export interface FocusableProps extends HTMLAttributes, FooterLe onCancel?: (e: CustomEvent) => void; } +const focusableRegex = createPropListRegex(["flow-children", "onActivate", "onCancel", "focusClassName", "focusWithinClassName"]); + export const Focusable = findModuleExport((e: Export) => - e?.render?.toString()?.includes('["flow-children","onActivate","onCancel","focusClassName",'), + e?.render?.toString && focusableRegex.test(e.render.toString()) ) as FC>; diff --git a/src/components/Menu.ts b/src/components/Menu.ts index 5aae6ff..6b7cbbf 100755 --- a/src/components/Menu.ts +++ b/src/components/Menu.ts @@ -5,7 +5,8 @@ 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('stopPropagation))'), + (e: Export) => typeof e === 'function' && e.toString().includes('GetContextMenuManagerFromWindow(') + && e.toString().includes('.CreateContextMenuInstance('), ); export interface MenuProps extends FooterLegendProps { diff --git a/src/components/ProgressBar.ts b/src/components/ProgressBar.ts index 4799889..4f02e38 100644 --- a/src/components/ProgressBar.ts +++ b/src/components/ProgressBar.ts @@ -2,6 +2,7 @@ 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; @@ -30,6 +31,7 @@ export const ProgressBarWithInfo = findModuleExport((e: Export) => e?.toString()?.includes('.ProgressBarFieldStatus},'), ) as FC; +const progressBarItemRegex = createPropListRegex(["indeterminate", "nTransitionSec", "nProgress"]); export const ProgressBarItem = findModuleExport((e: Export) => - e?.toString()?.includes('"indeterminate","nTransitionSec"'), + e?.toString && progressBarItemRegex.test(e.toString()), ) as FC; diff --git a/src/components/SidebarNavigation.ts b/src/components/SidebarNavigation.ts index 42361d9..3459844 100644 --- a/src/components/SidebarNavigation.ts +++ b/src/components/SidebarNavigation.ts @@ -1,6 +1,7 @@ import { ReactNode, FC } from 'react'; import { Export, findModuleExport } from '../webpack'; +import { createPropListRegex } from '../utils'; export interface SidebarNavigationPage { title: ReactNode; @@ -23,6 +24,7 @@ export interface SidebarNavigationProps { onPageRequested?: (page: string) => void; } +const sidebarNavigationRegex = createPropListRegex(["pages", "fnSetNavigateToPage", "disableRouteReporting"]); export const SidebarNavigation = findModuleExport((e: Export) => - e?.toString()?.includes('"disableRouteReporting"'), + e?.toString && sidebarNavigationRegex.test(e.toString()), ) as FC; diff --git a/src/components/Spinner.ts b/src/components/Spinner.ts index ec3d688..7108374 100755 --- a/src/components/Spinner.ts +++ b/src/components/Spinner.ts @@ -3,6 +3,6 @@ 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()), +export const Spinner = Object.values(IconsModule)?.find( + (mod: any) => mod?.toString && /Spinner\)}\)?,.\.createElement\(\"path\",{d:\"M18 /.test(mod.toString()), ) as FC>; diff --git a/src/index.ts b/src/index.ts index c4f8b08..be7f00b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -13,8 +13,8 @@ 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); - }; + return (...args: any[]) => { + // TODO: Maybe wrap this + return fn(...args); + }; }; \ No newline at end of file diff --git a/src/utils/react.ts b/src/utils/react.ts index 82c2141..745c14c 100644 --- a/src/utils/react.ts +++ b/src/utils/react.ts @@ -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; diff --git a/src/webpack.ts b/src/webpack.ts index 5862c62..1bf31a1 100644 --- a/src/webpack.ts +++ b/src/webpack.ts @@ -130,7 +130,7 @@ export const CommonUIModule = modules.find((m: Module) => { }); export const IconsModule = findModuleByExport( - (e) => e?.toString && /Spinner\)}\),.\.createElement\(\"path\",{d:\"M18 /.test(e.toString()), + (e) => e?.toString && /Spinner\)}\)?,.\.createElement\(\"path\",{d:\"M18 /.test(e.toString()), ); export const ReactRouter = findModuleByExport((e) => e.computeRootMatch);