mirror of
https://github.com/SteamDeckHomebrew/decky-loader.git
synced 2026-07-11 18:51:59 +00:00
initial implementation of new router and qam hooks
This commit is contained in:
@@ -22,9 +22,7 @@ class ErrorBoundaryHook extends Logger {
|
|||||||
this.log('Initialized');
|
this.log('Initialized');
|
||||||
window.__ERRORBOUNDARY_HOOK_INSTANCE?.deinit?.();
|
window.__ERRORBOUNDARY_HOOK_INSTANCE?.deinit?.();
|
||||||
window.__ERRORBOUNDARY_HOOK_INSTANCE = this;
|
window.__ERRORBOUNDARY_HOOK_INSTANCE = this;
|
||||||
}
|
|
||||||
|
|
||||||
init() {
|
|
||||||
// valve writes only the sanest of code
|
// valve writes only the sanest of code
|
||||||
const exp = /^\(\)=>\(.\|\|.\(new .\),.\)$/;
|
const exp = /^\(\)=>\(.\|\|.\(new .\),.\)$/;
|
||||||
const initErrorReportingStore = findModuleExport(
|
const initErrorReportingStore = findModuleExport(
|
||||||
@@ -76,6 +74,11 @@ class ErrorBoundaryHook extends Logger {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.errorBoundaryPatch = replacePatch(ErrorBoundary.prototype, 'render', function (this: any) {
|
this.errorBoundaryPatch = replacePatch(ErrorBoundary.prototype, 'render', function (this: any) {
|
||||||
|
if (this.state._deckyForceRerender) {
|
||||||
|
const stateClone = {...this.state, _deckyForceRerender: null};
|
||||||
|
this.setState(stateClone);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
if (this.state.error) {
|
if (this.state.error) {
|
||||||
const store = Object.getPrototypeOf(this)?.constructor?.sm_ErrorReportingStore || errorReportingStore;
|
const store = Object.getPrototypeOf(this)?.constructor?.sm_ErrorReportingStore || errorReportingStore;
|
||||||
return (
|
return (
|
||||||
@@ -89,6 +92,10 @@ class ErrorBoundaryHook extends Logger {
|
|||||||
}
|
}
|
||||||
return callOriginal;
|
return callOriginal;
|
||||||
});
|
});
|
||||||
|
// Small hack that gives us a lot more flexibility to force rerenders.
|
||||||
|
ValveErrorBoundary.prototype._deckyForceRerender = function (this: any) {
|
||||||
|
this.setState({...this.state, _deckyForceRerender: true});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public temporarilyDisableReporting() {
|
public temporarilyDisableReporting() {
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ import Toaster from './toaster';
|
|||||||
import { getVersionInfo } from './updater';
|
import { getVersionInfo } from './updater';
|
||||||
import { getSetting, setSetting } from './utils/settings';
|
import { getSetting, setSetting } from './utils/settings';
|
||||||
import TranslationHelper, { TranslationClass } from './utils/TranslationHelper';
|
import TranslationHelper, { TranslationClass } from './utils/TranslationHelper';
|
||||||
|
import AppHook from './app-hook';
|
||||||
|
|
||||||
const StorePage = lazy(() => import('./components/store/Store'));
|
const StorePage = lazy(() => import('./components/store/Store'));
|
||||||
const SettingsPage = lazy(() => import('./components/settings'));
|
const SettingsPage = lazy(() => import('./components/settings'));
|
||||||
@@ -82,8 +83,6 @@ class PluginLoader extends Logger {
|
|||||||
constructor() {
|
constructor() {
|
||||||
super(PluginLoader.name);
|
super(PluginLoader.name);
|
||||||
|
|
||||||
this.errorBoundaryHook.init();
|
|
||||||
|
|
||||||
DeckyBackend.addEventListener('loader/notify_updates', this.notifyUpdates.bind(this));
|
DeckyBackend.addEventListener('loader/notify_updates', this.notifyUpdates.bind(this));
|
||||||
DeckyBackend.addEventListener('loader/import_plugin', this.importPlugin.bind(this));
|
DeckyBackend.addEventListener('loader/import_plugin', this.importPlugin.bind(this));
|
||||||
DeckyBackend.addEventListener('loader/unload_plugin', this.unloadPlugin.bind(this));
|
DeckyBackend.addEventListener('loader/unload_plugin', this.unloadPlugin.bind(this));
|
||||||
|
|||||||
+131
-112
@@ -1,4 +1,4 @@
|
|||||||
import { ErrorBoundary, Focusable, Patch, afterPatch } from '@decky/ui';
|
import { ErrorBoundary, Focusable, Patch, afterPatch, beforePatch, findInReactTree, findModuleByExport, findModuleExport, getReactRoot, sleep } from '@decky/ui';
|
||||||
import { FC, ReactElement, ReactNode, cloneElement, createElement, memo } from 'react';
|
import { FC, ReactElement, ReactNode, cloneElement, createElement, memo } from 'react';
|
||||||
import type { Route } from 'react-router';
|
import type { Route } from 'react-router';
|
||||||
|
|
||||||
@@ -25,13 +25,13 @@ declare global {
|
|||||||
const isPatched = Symbol('is patched');
|
const isPatched = Symbol('is patched');
|
||||||
|
|
||||||
class RouterHook extends Logger {
|
class RouterHook extends Logger {
|
||||||
private router: any;
|
|
||||||
private memoizedRouter: any;
|
|
||||||
private gamepadWrapper: any;
|
|
||||||
private routerState: DeckyRouterState = new DeckyRouterState();
|
private routerState: DeckyRouterState = new DeckyRouterState();
|
||||||
private globalComponentsState: DeckyGlobalComponentsState = new DeckyGlobalComponentsState();
|
private globalComponentsState: DeckyGlobalComponentsState = new DeckyGlobalComponentsState();
|
||||||
private wrapperPatch: Patch;
|
private renderedComponents: ReactElement[] = [];
|
||||||
private routerPatch?: Patch;
|
private Route: any;
|
||||||
|
private DeckyWrapper = this.routerWrapper.bind(this);
|
||||||
|
private DeckyGlobalComponentsWrapper = this.globalComponentsWrapper.bind(this);
|
||||||
|
private toReplace = new Map<string, ReactNode>();
|
||||||
public routes?: any[];
|
public routes?: any[];
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
@@ -41,115 +41,134 @@ class RouterHook extends Logger {
|
|||||||
window.__ROUTER_HOOK_INSTANCE?.deinit?.();
|
window.__ROUTER_HOOK_INSTANCE?.deinit?.();
|
||||||
window.__ROUTER_HOOK_INSTANCE = this;
|
window.__ROUTER_HOOK_INSTANCE = this;
|
||||||
|
|
||||||
this.gamepadWrapper = Focusable;
|
(async()=> {
|
||||||
|
const root = getReactRoot(document.getElementById('root') as any);
|
||||||
let Route: new () => Route;
|
// TODO be more specific, this is horrible and very very slow
|
||||||
// Used to store the new replicated routes we create to allow routes to be unpatched.
|
const findRouterNode = () =>findInReactTree(root, node => typeof node?.pendingProps?.loggedIn == "undefined" && node?.type?.toString().includes("Settings.Root()"));
|
||||||
const processList = (
|
let routerNode = findRouterNode();
|
||||||
routeList: any[],
|
while (!routerNode) {
|
||||||
routes: Map<string, RouterEntry> | null,
|
this.warn(
|
||||||
routePatches: Map<string, Set<RoutePatch>>,
|
'Failed to find Router node, reattempting in 5 seconds.',
|
||||||
save: boolean,
|
);
|
||||||
) => {
|
await sleep(5000);
|
||||||
this.debug('Route list: ', routeList);
|
routerNode = findRouterNode();
|
||||||
if (save) this.routes = routeList;
|
|
||||||
let routerIndex = routeList.length;
|
|
||||||
if (routes) {
|
|
||||||
if (!routeList[routerIndex - 1]?.length || routeList[routerIndex - 1]?.length !== routes.size) {
|
|
||||||
if (routeList[routerIndex - 1]?.length && routeList[routerIndex - 1].length !== routes.size) routerIndex--;
|
|
||||||
const newRouterArray: (ReactElement | JSX.Element)[] = [];
|
|
||||||
routes.forEach(({ component, props }, path) => {
|
|
||||||
newRouterArray.push(
|
|
||||||
<Route path={path} {...props}>
|
|
||||||
<ErrorBoundary>{createElement(component)}</ErrorBoundary>
|
|
||||||
</Route>,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
routeList[routerIndex] = newRouterArray;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
routeList.forEach((route: Route, index: number) => {
|
if (routerNode) {
|
||||||
const replaced = toReplace.get(route?.props?.path as string);
|
this.debug("routerNode", routerNode);
|
||||||
if (replaced) {
|
// Patch the component globally
|
||||||
routeList[index].props.children = replaced;
|
afterPatch(routerNode.elementType, "type", this.handleRouterRender.bind(this));
|
||||||
toReplace.delete(route?.props?.path as string);
|
// Swap out the current instance
|
||||||
|
routerNode.type = routerNode.elementType.type;
|
||||||
|
if (routerNode?.alternate) {
|
||||||
|
routerNode.alternate.type = routerNode.type;
|
||||||
}
|
}
|
||||||
if (route?.props?.path && routePatches.has(route.props.path as string)) {
|
// Force a full rerender via our custom error boundary
|
||||||
toReplace.set(
|
routerNode?.return?.stateNode?._deckyForceRerender?.();
|
||||||
route?.props?.path as string,
|
|
||||||
// @ts-ignore
|
|
||||||
routeList[index].props.children,
|
|
||||||
);
|
|
||||||
routePatches.get(route.props.path as string)?.forEach((patch) => {
|
|
||||||
const oType = routeList[index].props.children.type;
|
|
||||||
routeList[index].props.children = patch({
|
|
||||||
...routeList[index].props,
|
|
||||||
children: {
|
|
||||||
...cloneElement(routeList[index].props.children),
|
|
||||||
type: routeList[index].props.children[isPatched] ? oType : (props) => createElement(oType, props),
|
|
||||||
},
|
|
||||||
}).children;
|
|
||||||
routeList[index].props.children[isPatched] = true;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
let toReplace = new Map<string, ReactNode>();
|
|
||||||
const DeckyWrapper = ({ children }: { children: ReactElement }) => {
|
|
||||||
const { routes, routePatches } = useDeckyRouterState();
|
|
||||||
const mainRouteList = children.props.children[0].props.children;
|
|
||||||
const ingameRouteList = children.props.children[1].props.children; // /appoverlay and /apprunning
|
|
||||||
processList(mainRouteList, routes, routePatches, true);
|
|
||||||
processList(ingameRouteList, null, routePatches, false);
|
|
||||||
|
|
||||||
this.debug('Rerendered routes list');
|
|
||||||
return children;
|
|
||||||
};
|
|
||||||
|
|
||||||
let renderedComponents: ReactElement[] = [];
|
|
||||||
|
|
||||||
const DeckyGlobalComponentsWrapper = () => {
|
|
||||||
const { components } = useDeckyGlobalComponentsState();
|
|
||||||
if (renderedComponents.length != components.size) {
|
|
||||||
this.debug('Rerendering global components');
|
|
||||||
renderedComponents = Array.from(components.values()).map((GComponent) => <GComponent />);
|
|
||||||
}
|
}
|
||||||
return <>{renderedComponents}</>;
|
})();
|
||||||
};
|
|
||||||
|
|
||||||
this.wrapperPatch = afterPatch(this.gamepadWrapper, 'render', (_: any, ret: any) => {
|
|
||||||
if (ret?.props?.children?.props?.children?.length == 5 || ret?.props?.children?.props?.children?.length == 4) {
|
|
||||||
const idx = ret?.props?.children?.props?.children?.length == 4 ? 1 : 2;
|
|
||||||
const potentialSettingsRootString =
|
|
||||||
ret.props.children.props.children[idx]?.props?.children?.[0]?.type?.type?.toString() || '';
|
|
||||||
if (potentialSettingsRootString?.includes('Settings.Root()')) {
|
|
||||||
if (!this.router) {
|
|
||||||
this.router = ret.props.children.props.children[idx]?.props?.children?.[0]?.type;
|
|
||||||
this.routerPatch = afterPatch(this.router, 'type', (_: any, ret: any) => {
|
|
||||||
if (!Route)
|
|
||||||
Route = ret.props.children[0].props.children.find((x: any) => x.props.path == '/createaccount').type;
|
|
||||||
const returnVal = (
|
|
||||||
<DeckyRouterStateContextProvider deckyRouterState={this.routerState}>
|
|
||||||
<DeckyWrapper>{ret}</DeckyWrapper>
|
|
||||||
</DeckyRouterStateContextProvider>
|
|
||||||
);
|
|
||||||
return returnVal;
|
|
||||||
});
|
|
||||||
this.memoizedRouter = memo(this.router.type);
|
|
||||||
this.memoizedRouter.isDeckyRouter = true;
|
|
||||||
}
|
|
||||||
ret.props.children.props.children.push(
|
|
||||||
<DeckyGlobalComponentsStateContextProvider deckyGlobalComponentsState={this.globalComponentsState}>
|
|
||||||
<DeckyGlobalComponentsWrapper />
|
|
||||||
</DeckyGlobalComponentsStateContextProvider>,
|
|
||||||
);
|
|
||||||
ret.props.children.props.children[idx].props.children[0].type = this.memoizedRouter;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public handleRouterRender(_: any, ret: any) {
|
||||||
|
const DeckyWrapper = this.DeckyWrapper;
|
||||||
|
const DeckyGlobalComponentsWrapper = this.DeckyGlobalComponentsWrapper;
|
||||||
|
if (!this.Route)
|
||||||
|
// TODO make more redundant
|
||||||
|
this.Route = ret.props.children[0].props.children.find((x: any) => x.props.path == '/createaccount').type;
|
||||||
|
if (ret._decky) {
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
const returnVal = (
|
||||||
|
<>
|
||||||
|
<DeckyRouterStateContextProvider deckyRouterState={this.routerState}>
|
||||||
|
<DeckyWrapper>{ret}</DeckyWrapper>
|
||||||
|
</DeckyRouterStateContextProvider>
|
||||||
|
<DeckyGlobalComponentsStateContextProvider deckyGlobalComponentsState={this.globalComponentsState}>
|
||||||
|
<DeckyGlobalComponentsWrapper />
|
||||||
|
</DeckyGlobalComponentsStateContextProvider>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
(returnVal as any)._decky = true;
|
||||||
|
return returnVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
private globalComponentsWrapper () {
|
||||||
|
const { components } = useDeckyGlobalComponentsState();
|
||||||
|
if (this.renderedComponents.length != components.size) {
|
||||||
|
this.debug('Rerendering global components');
|
||||||
|
this.renderedComponents = Array.from(components.values()).map((GComponent) => <GComponent />);
|
||||||
|
}
|
||||||
|
return <>{this.renderedComponents}</>;
|
||||||
|
};
|
||||||
|
|
||||||
|
private routerWrapper({ children }: { children: ReactElement }) {
|
||||||
|
// Used to store the new replicated routes we create to allow routes to be unpatched.
|
||||||
|
|
||||||
|
const { routes, routePatches } = useDeckyRouterState();
|
||||||
|
// TODO make more redundant
|
||||||
|
if (!children?.props?.children?.[0]?.props?.children) {
|
||||||
|
console.log("routerWrapper wrong component?", children)
|
||||||
|
return children;
|
||||||
|
}
|
||||||
|
const mainRouteList = children.props.children[0].props.children;
|
||||||
|
const ingameRouteList = children.props.children[1].props.children; // /appoverlay and /apprunning
|
||||||
|
this.processList(mainRouteList, routes, routePatches, true);
|
||||||
|
this.processList(ingameRouteList, null, routePatches, false);
|
||||||
|
|
||||||
|
this.debug('Rerendered routes list');
|
||||||
|
return children;
|
||||||
|
};
|
||||||
|
|
||||||
|
private processList(
|
||||||
|
routeList: any[],
|
||||||
|
routes: Map<string, RouterEntry> | null,
|
||||||
|
routePatches: Map<string, Set<RoutePatch>>,
|
||||||
|
save: boolean,
|
||||||
|
) {
|
||||||
|
const Route = this.Route;
|
||||||
|
this.debug('Route list: ', routeList);
|
||||||
|
if (save) this.routes = routeList;
|
||||||
|
let routerIndex = routeList.length;
|
||||||
|
if (routes) {
|
||||||
|
if (!routeList[routerIndex - 1]?.length || routeList[routerIndex - 1]?.length !== routes.size) {
|
||||||
|
if (routeList[routerIndex - 1]?.length && routeList[routerIndex - 1].length !== routes.size) routerIndex--;
|
||||||
|
const newRouterArray: (ReactElement | JSX.Element)[] = [];
|
||||||
|
routes.forEach(({ component, props }, path) => {
|
||||||
|
newRouterArray.push(
|
||||||
|
<Route path={path} {...props}>
|
||||||
|
<ErrorBoundary>{createElement(component)}</ErrorBoundary>
|
||||||
|
</Route>,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
routeList[routerIndex] = newRouterArray;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
routeList.forEach((route: Route, index: number) => {
|
||||||
|
const replaced = this.toReplace.get(route?.props?.path as string);
|
||||||
|
if (replaced) {
|
||||||
|
routeList[index].props.children = replaced;
|
||||||
|
this.toReplace.delete(route?.props?.path as string);
|
||||||
|
}
|
||||||
|
if (route?.props?.path && routePatches.has(route.props.path as string)) {
|
||||||
|
this.toReplace.set(
|
||||||
|
route?.props?.path as string,
|
||||||
|
// @ts-ignore
|
||||||
|
routeList[index].props.children,
|
||||||
|
);
|
||||||
|
routePatches.get(route.props.path as string)?.forEach((patch) => {
|
||||||
|
const oType = routeList[index].props.children.type;
|
||||||
|
routeList[index].props.children = patch({
|
||||||
|
...routeList[index].props,
|
||||||
|
children: {
|
||||||
|
...cloneElement(routeList[index].props.children),
|
||||||
|
type: routeList[index].props.children[isPatched] ? oType : (props) => createElement(oType, props),
|
||||||
|
},
|
||||||
|
}).children;
|
||||||
|
routeList[index].props.children[isPatched] = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
addRoute(path: string, component: RouterEntry['component'], props: RouterEntry['props'] = {}) {
|
addRoute(path: string, component: RouterEntry['component'], props: RouterEntry['props'] = {}) {
|
||||||
this.routerState.addRoute(path, component, props);
|
this.routerState.addRoute(path, component, props);
|
||||||
}
|
}
|
||||||
@@ -175,8 +194,8 @@ class RouterHook extends Logger {
|
|||||||
}
|
}
|
||||||
|
|
||||||
deinit() {
|
deinit() {
|
||||||
this.wrapperPatch.unpatch();
|
// this.wrapperPatch.unpatch();
|
||||||
this.routerPatch?.unpatch();
|
// this.routerPatch?.unpatch();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+26
-75
@@ -1,5 +1,5 @@
|
|||||||
// TabsHook for versions after the Desktop merge
|
// TabsHook for versions after the Desktop merge
|
||||||
import { ErrorBoundary, Patch, QuickAccessTab, afterPatch, findInReactTree, getReactRoot, sleep } from '@decky/ui';
|
import { ErrorBoundary, Patch, QuickAccessTab, afterPatch, createReactTreePatcher, findInReactTree, findModuleByExport, getReactRoot, setReactPatcherLoggingEnabled, sleep } from '@decky/ui';
|
||||||
|
|
||||||
import { QuickAccessVisibleStateProvider } from './components/QuickAccessVisibleState';
|
import { QuickAccessVisibleStateProvider } from './components/QuickAccessVisibleState';
|
||||||
import Logger from './logger';
|
import Logger from './logger';
|
||||||
@@ -22,6 +22,7 @@ class TabsHook extends Logger {
|
|||||||
tabs: Tab[] = [];
|
tabs: Tab[] = [];
|
||||||
private qAMRoot?: any;
|
private qAMRoot?: any;
|
||||||
private qamPatch?: Patch;
|
private qamPatch?: Patch;
|
||||||
|
private cachedTabs: any;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super('TabsHook');
|
super('TabsHook');
|
||||||
@@ -32,87 +33,37 @@ class TabsHook extends Logger {
|
|||||||
}
|
}
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
const tree = getReactRoot(document.getElementById('root') as any);
|
// TODO patch the "embedded" renderer in this module too (seems to be for VR? unsure)
|
||||||
let qAMRoot: any;
|
const qamModule = findModuleByExport(e => e?.type?.toString()?.includes("QuickAccessMenuBrowserView"));
|
||||||
const findQAMRoot = (currentNode: any, iters: number): any => {
|
const qamRenderer = Object.values(qamModule).find((e: any) => e?.type?.toString()?.includes("QuickAccessMenuBrowserView"))
|
||||||
if (iters >= 80) {
|
|
||||||
// currently 67
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
(typeof currentNode?.memoizedProps?.visible == 'boolean' ||
|
|
||||||
typeof currentNode?.memoizedProps?.active == 'boolean') &&
|
|
||||||
currentNode?.type?.toString()?.includes('QuickAccessMenuBrowserView')
|
|
||||||
) {
|
|
||||||
this.log(`QAM root was found in ${iters} recursion cycles`);
|
|
||||||
return currentNode;
|
|
||||||
}
|
|
||||||
if (currentNode.child) {
|
|
||||||
let node = findQAMRoot(currentNode.child, iters + 1);
|
|
||||||
if (node !== null) return node;
|
|
||||||
}
|
|
||||||
if (currentNode.sibling) {
|
|
||||||
let node = findQAMRoot(currentNode.sibling, iters + 1);
|
|
||||||
if (node !== null) return node;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
(async () => {
|
|
||||||
qAMRoot = findQAMRoot(tree, 0);
|
|
||||||
while (!qAMRoot) {
|
|
||||||
this.error(
|
|
||||||
'Failed to find QAM root node, reattempting in 5 seconds. A developer may need to increase the recursion limit.',
|
|
||||||
);
|
|
||||||
await sleep(5000);
|
|
||||||
qAMRoot = findQAMRoot(tree, 0);
|
|
||||||
}
|
|
||||||
this.qAMRoot = qAMRoot;
|
|
||||||
let patchedInnerQAM: any;
|
|
||||||
this.qamPatch = afterPatch(qAMRoot.return, 'type', (_: any, ret: any) => {
|
|
||||||
try {
|
|
||||||
if (!qAMRoot?.child) {
|
|
||||||
qAMRoot = findQAMRoot(tree, 0);
|
|
||||||
this.qAMRoot = qAMRoot;
|
|
||||||
}
|
|
||||||
if (qAMRoot?.child && !qAMRoot?.child?.type?.decky) {
|
|
||||||
afterPatch(qAMRoot.child, 'type', (_: any, ret: any) => {
|
|
||||||
try {
|
|
||||||
const qamTabsRenderer = findInReactTree(ret, (x) => x?.props?.onFocusNavDeactivated);
|
|
||||||
if (patchedInnerQAM) {
|
|
||||||
qamTabsRenderer.type = patchedInnerQAM;
|
|
||||||
} else {
|
|
||||||
afterPatch(qamTabsRenderer, 'type', (innerArgs: any, ret: any) => {
|
|
||||||
const tabs = findInReactTree(ret, (x) => x?.props?.tabs);
|
|
||||||
this.render(tabs.props.tabs, innerArgs[0].visible);
|
|
||||||
return ret;
|
|
||||||
});
|
|
||||||
patchedInnerQAM = qamTabsRenderer.type;
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
this.error('Error patching QAM inner', e);
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
});
|
|
||||||
qAMRoot.child.type.decky = true;
|
|
||||||
qAMRoot.child.alternate.type = qAMRoot.child.type;
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
this.error('Error patching QAM', e);
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret;
|
const patchHandler = createReactTreePatcher([
|
||||||
});
|
tree => findInReactTree(tree, node => node?.props?.onFocusNavDeactivated)
|
||||||
|
], (args, ret) => {
|
||||||
|
this.log("qam render", args, ret);
|
||||||
|
const tabs = findInReactTree(ret, (x) => x?.props?.tabs);
|
||||||
|
this.render(tabs.props.tabs, args[0].visible);
|
||||||
|
return ret;
|
||||||
|
}, "TabsHook");
|
||||||
|
|
||||||
if (qAMRoot.return.alternate) {
|
this.qamPatch = afterPatch(qamRenderer, "type", patchHandler);
|
||||||
qAMRoot.return.alternate.type = qAMRoot.return.type;
|
|
||||||
|
// Patch already rendered qam
|
||||||
|
const root = getReactRoot(document.getElementById('root') as any);
|
||||||
|
const qamNode = root && findInReactTree(root, (n: any) => n.elementType == qamRenderer); // need elementType, because type is actually mobx wrapper
|
||||||
|
if (qamNode) {
|
||||||
|
this.debug("qamNode", qamNode);
|
||||||
|
// Only affects this fiber node so we don't need to unpatch here
|
||||||
|
qamNode.type = qamNode.elementType.type;
|
||||||
|
if (qamNode?.alternate) {
|
||||||
|
qamNode.alternate.type = qamNode.type;
|
||||||
}
|
}
|
||||||
this.log('Finished initial injection');
|
}
|
||||||
})();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
deinit() {
|
deinit() {
|
||||||
this.qamPatch?.unpatch();
|
this.qamPatch?.unpatch();
|
||||||
this.qAMRoot.return.alternate.type = this.qAMRoot.return.type;
|
// this.qAMRoot.return.alternate.type = this.qAMRoot.return.type;
|
||||||
}
|
}
|
||||||
|
|
||||||
add(tab: Tab) {
|
add(tab: Tab) {
|
||||||
|
|||||||
Reference in New Issue
Block a user