diff --git a/backend/main.py b/backend/main.py index ce503e8e2..b52180ab0 100644 --- a/backend/main.py +++ b/backend/main.py @@ -83,8 +83,8 @@ async def load_plugins(self): async def loader_reinjector(self): while True: - await sleep(1) - if not await tab_has_global_var("SP", "DeckyPluginLoader"): + await sleep(5) + if not await tab_has_global_var("SP", "deckyHasLoaded"): logger.info("Plugin loader isn't present in Steam anymore, reinjecting...") await self.inject_javascript() diff --git a/frontend/src/components/DeckyRouterState.tsx b/frontend/src/components/DeckyRouterState.tsx index 4aab7abda..c7da4034b 100644 --- a/frontend/src/components/DeckyRouterState.tsx +++ b/frontend/src/components/DeckyRouterState.tsx @@ -6,17 +6,21 @@ export interface RouterEntry { component: ComponentType; } +export type RoutePatch = (route: RouteProps) => RouteProps; + interface PublicDeckyRouterState { routes: Map; + routePatches: Map>; } export class DeckyRouterState { private _routes = new Map(); + private _routePatches = new Map>(); public eventBus = new EventTarget(); publicState(): PublicDeckyRouterState { - return { routes: this._routes }; + return { routes: this._routes, routePatches: this._routePatches }; } addRoute(path: string, component: RouterEntry['component'], props: RouterEntry['props'] = {}) { @@ -24,6 +28,26 @@ export class DeckyRouterState { this.notifyUpdate(); } + addPatch(path: string, patch: RoutePatch) { + let patchList = this._routePatches.get(path); + if (!patchList) { + patchList = new Set(); + this._routePatches.set(path, patchList); + } + patchList.add(patch); + this.notifyUpdate(); + return patch; + } + + removePatch(path: string, patch: RoutePatch) { + const patchList = this._routePatches.get(path); + patchList?.delete(patch); + if (patchList?.size == 0) { + this._routePatches.delete(path); + } + this.notifyUpdate(); + } + removeRoute(path: string) { this._routes.delete(path); this.notifyUpdate(); @@ -36,6 +60,8 @@ export class DeckyRouterState { interface DeckyRouterStateContext extends PublicDeckyRouterState { addRoute(path: string, component: RouterEntry['component'], props: RouterEntry['props']): void; + addPatch(path: string, patch: RoutePatch): RoutePatch; + removePatch(path: string, patch: RoutePatch): void; removeRoute(path: string): void; } @@ -54,6 +80,7 @@ export const DeckyRouterStateContextProvider: FC = ({ children, deckyRout useEffect(() => { function onUpdate() { + console.log('test', deckyRouterState.publicState()); setPublicDeckyRouterState({ ...deckyRouterState.publicState() }); } @@ -62,12 +89,15 @@ export const DeckyRouterStateContextProvider: FC = ({ children, deckyRout return () => deckyRouterState.eventBus.removeEventListener('update', onUpdate); }, []); - const addRoute = (path: string, component: RouterEntry['component'], props: RouterEntry['props'] = {}) => - deckyRouterState.addRoute(path, component, props); - const removeRoute = (path: string) => deckyRouterState.removeRoute(path); + const addRoute = deckyRouterState.addRoute.bind(deckyRouterState); + const addPatch = deckyRouterState.addPatch.bind(deckyRouterState); + const removePatch = deckyRouterState.removePatch.bind(deckyRouterState); + const removeRoute = deckyRouterState.removeRoute.bind(deckyRouterState); return ( - + {children} ); diff --git a/frontend/src/index.tsx b/frontend/src/index.tsx index 188c13302..4045751f5 100644 --- a/frontend/src/index.tsx +++ b/frontend/src/index.tsx @@ -7,6 +7,7 @@ declare global { DeckyUpdater?: DeckyUpdater; importDeckyPlugin: Function; syncDeckyPlugins: Function; + deckyHasLoaded: boolean; } } @@ -26,3 +27,5 @@ window.syncDeckyPlugins = async function () { }; setTimeout(() => window.syncDeckyPlugins(), 5000); + +window.deckyHasLoaded = true; diff --git a/frontend/src/logger.ts b/frontend/src/logger.ts index 9eb515a3f..220363623 100644 --- a/frontend/src/logger.ts +++ b/frontend/src/logger.ts @@ -8,6 +8,16 @@ export const log = (name: string, ...args: any[]) => { ); }; +export const debug = (name: string, ...args: any[]) => { + console.debug( + `%c Decky %c ${name} %c`, + 'background: #16a085; color: black;', + 'background: #1abc9c; color: black;', + 'color: blue;', + ...args, + ); +}; + export const error = (name: string, ...args: any[]) => { console.log( `%c Decky %c ${name} %c`, @@ -28,7 +38,7 @@ class Logger { } debug(...args: any[]) { - log(this.name, ...args); + debug(this.name, ...args); } } diff --git a/frontend/src/router-hook.tsx b/frontend/src/router-hook.tsx index 4e23658e7..ca2a70397 100644 --- a/frontend/src/router-hook.tsx +++ b/frontend/src/router-hook.tsx @@ -1,10 +1,11 @@ import { afterPatch, findModuleChild, unpatch } from 'decky-frontend-lib'; import { ReactElement, createElement, memo } from 'react'; -import type { Route } from 'react-router'; +import type { Route, RouteProps } from 'react-router'; import { DeckyRouterState, DeckyRouterStateContextProvider, + RoutePatch, RouterEntry, useDeckyRouterState, } from './components/DeckyRouterState'; @@ -38,19 +39,16 @@ class RouterHook extends Logger { }); let Route: new () => Route; + // Used to store the new replicated routes we create to allow routes to be unpatched. + let toReplace = new Map(); const DeckyWrapper = ({ children }: { children: ReactElement }) => { - const { routes } = useDeckyRouterState(); + const { routes, routePatches } = useDeckyRouterState(); - let routerIndex = children.props.children[0].props.children.length; - if ( - !children.props.children[0].props.children[routerIndex - 1]?.length || - children.props.children[0].props.children[routerIndex - 1]?.length !== routes.size - ) { - if ( - children.props.children[0].props.children[routerIndex - 1]?.length && - children.props.children[0].props.children[routerIndex - 1].length !== routes.size - ) - routerIndex--; + const routeList = children.props.children[0].props.children; + + let routerIndex = routeList.length; + 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[] = []; routes.forEach(({ component, props }, path) => { newRouterArray.push( @@ -59,8 +57,26 @@ class RouterHook extends Logger { , ); }); - children.props.children[0].props.children[routerIndex] = newRouterArray; + routeList[routerIndex] = newRouterArray; } + routeList.forEach((route: Route, index: number) => { + const replaced = toReplace.get(route?.props?.path as string); + if (replaced) { + routeList[index] = replaced; + toReplace.delete(route?.props?.path as string); + } + if (route?.props?.path && routePatches.has(route.props.path as string)) { + toReplace.set( + route?.props?.path as string, + // @ts-ignore + createElement(routeList[index].type, routeList[index].props, routeList[index].props.children), + ); + routePatches.get(route.props.path as string)?.forEach((patch) => { + routeList[index].props = patch(routeList[index].props); + }); + } + }); + this.debug('Rerendered routes list'); return children; }; @@ -97,6 +113,14 @@ class RouterHook extends Logger { this.routerState.addRoute(path, component, props); } + addPatch(path: string, patch: RoutePatch) { + return this.routerState.addPatch(path, patch); + } + + removePatch(path: string, patch: RoutePatch) { + this.routerState.removePatch(path, patch); + } + removeRoute(path: string) { this.routerState.removeRoute(path); } diff --git a/frontend/src/tabs-hook.ts b/frontend/src/tabs-hook.ts index 667fc9738..b83a1e976 100644 --- a/frontend/src/tabs-hook.ts +++ b/frontend/src/tabs-hook.ts @@ -110,12 +110,12 @@ class TabsHook extends Logger { } add(tab: Tab) { - this.log('Adding tab', tab.id, 'to render array'); + this.debug('Adding tab', tab.id, 'to render array'); this.tabs.push(tab); } removeById(id: number) { - this.log('Removing tab', id); + this.debug('Removing tab', id); this.tabs = this.tabs.filter((tab) => tab.id !== id); }