diff --git a/src/deck-components/Tabs.tsx b/src/deck-components/Tabs.tsx index 7ad78a5..b26f136 100644 --- a/src/deck-components/Tabs.tsx +++ b/src/deck-components/Tabs.tsx @@ -1,74 +1,119 @@ -// import { FC, ReactNode } from 'react'; -// import { findModule } from '../webpack'; -// import { FooterLegendProps } from './FooterLegend'; +import { createElement, FC, ReactNode, useEffect, useState } from 'react'; -// /** -// * Individual tab objects for the Tabs component -// * -// * `id` ID of this tab, can be used with activeTab to auto-focus a given tab -// * `title` Title shown in the header bar -// * `renderTabAddon` Return a {@link ReactNode} to render it next to the tab title, i.e. the counts for each tab on the Media page -// * `content` Content of the tab -// * `footer` Sets up button handlers and labels -// */ -// export interface Tab { -// id: string; -// title: string; -// renderTabAddon?: () => ReactNode; -// content: ReactNode; -// footer?: FooterLegendProps; -// } +import { fakeRenderComponent, findInReactTree, sleep } from '../utils'; +import { FooterLegendProps } from './FooterLegend'; +import { SteamSpinner } from './SteamSpinner'; -// /** -// * Props for the {@link Tabs} -// * -// * `tabs` array of {@link Tab} -// * `activeTab` tab currently active, needs to be one of the tabs {@link Tab.id}, must be set using a `useState` in the `onShowTab` handler -// * `onShowTab` Called when the active tab should change, needs to set `activeTab`. See example. -// * `autoFocusContents` Whether to automatically focus the tab contents or not. -// * `footer` Sets up button handlers and labels -// * -// * @example -// * const Component: FC = () => { -// * const [currentTab, setCurrentTab] = useState("Tab1"); -// * -// * return ( -// * { -// * setCurrentTabRoute(tabID); -// * }} -// * tabs={[ -// * { -// * title: "Tab 1", -// * content: , -// * id: "Tab1", -// * }, -// * { -// * title: "Tab 2", -// * content: , -// * id: "Tab2", -// * }, -// * ]} -// * /> -// * ); -// * }; -// */ -// export interface TabsProps { -// tabs: Tab[]; -// activeTab: string; -// onShowTab: (tab: string) => void; -// autoFocusContents?: boolean; -// } +/** + * Individual tab objects for the Tabs component + * + * `id` ID of this tab, can be used with activeTab to auto-focus a given tab + * `title` Title shown in the header bar + * `renderTabAddon` Return a {@link ReactNode} to render it next to the tab title, i.e. the counts for each tab on the Media page + * `content` Content of the tab + * `footer` Sets up button handlers and labels + */ +export interface Tab { + id: string; + title: string; + renderTabAddon?: () => ReactNode; + content: ReactNode; + footer?: FooterLegendProps; +} -// /** -// * Tabs component as used in the library and media tabs. See {@link TabsProps} -// */ -// export const Tabs = Object.values(findModule((m) => { -// if (typeof m !== 'object') return false; -// for (let prop in m) { -// if (m[prop]?.Unbleed) return true; -// } -// return false; -// })).find((x: any) => x?.type?.toString()?.includes("((function(") && x?.type?.toString()?.includes("[\"tabs\"")) as FC; \ No newline at end of file +/** + * Props for the {@link Tabs} + * + * `tabs` array of {@link Tab} + * `activeTab` tab currently active, needs to be one of the tabs {@link Tab.id}, must be set using a `useState` in the `onShowTab` handler + * `onShowTab` Called when the active tab should change, needs to set `activeTab`. See example. + * `autoFocusContents` Whether to automatically focus the tab contents or not. + * `footer` Sets up button handlers and labels + * + * @example + * const Component: FC = () => { + * const [currentTab, setCurrentTab] = useState("Tab1"); + * + * return ( + * { + * setCurrentTabRoute(tabID); + * }} + * tabs={[ + * { + * title: "Tab 1", + * content: , + * id: "Tab1", + * }, + * { + * title: "Tab 2", + * content: , + * id: "Tab2", + * }, + * ]} + * /> + * ); + * }; + */ +export interface TabsProps { + tabs: Tab[]; + activeTab: string; + onShowTab: (tab: string) => void; + autoFocusContents?: boolean; +} + +declare global { + interface Window { + DeckyPluginLoader: any; + } +} + +let tabsComponent: any; + +const getTabs = async () => { + if (tabsComponent) return tabsComponent + while (!window?.DeckyPluginLoader?.routerHook?.routes) { + console.debug("[DFL:Tabs]: Waiting for Decky router...") + await sleep(500); + } + return tabsComponent = fakeRenderComponent( + () => { + return findInReactTree( + findInReactTree( + window.DeckyPluginLoader.routerHook.routes + .find((x: any) => x.props.path == '/library/app/:appid/achievements') + .props.children.type(), + (x) => x?.props?.scrollTabsTop, + ).type({ appid: 1 }), + (x) => x?.props?.tabs, + ).type; + }, + { + useRef: () => ({ current: { reaction: { track: () => {} } } }), + useContext: () => ({ match: { params: { appid: 1 } } }), + useMemo: () => ({ data: {} }), + }, + ); +} + +/** + * 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 = ((props: TabsProps) => { + const found = tabsComponent; + const [tc, setTC] = useState>(found); + useEffect(() => { + if (found) return; + (async()=> { + console.debug("[DFL:Tabs]: Finding component...") + const t = await getTabs(); + console.debug("[DFL:Tabs]: Found!") + setTC(t); + })(); + }, []) + console.log("tc", tc); + return tc ? createElement(tc, props) : ; +}) as FC; diff --git a/src/deck-components/index.ts b/src/deck-components/index.ts index 0c73744..490c033 100755 --- a/src/deck-components/index.ts +++ b/src/deck-components/index.ts @@ -17,7 +17,7 @@ export * from './SliderField'; export * from './Spinner'; export * from './static-classes'; export * from './SteamSpinner'; -//export * from './Tabs'; +export * from './Tabs'; export * from './TextField'; export * from './Toggle'; export * from './ToggleField';