summaryrefslogtreecommitdiff
path: root/frontend/src/hooks/tabs.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'frontend/src/hooks/tabs.tsx')
-rw-r--r--frontend/src/hooks/tabs.tsx131
1 files changed, 131 insertions, 0 deletions
diff --git a/frontend/src/hooks/tabs.tsx b/frontend/src/hooks/tabs.tsx
new file mode 100644
index 00000000..4d917b6c
--- /dev/null
+++ b/frontend/src/hooks/tabs.tsx
@@ -0,0 +1,131 @@
+// TabsHook for versions after the Desktop merge
+import {
+ ErrorBoundary,
+ Patch,
+ QuickAccessTab,
+ afterPatch,
+ createReactTreePatcher,
+ findInReactTree,
+ findModuleByExport,
+ getReactRoot,
+} from '@decky/ui';
+
+import { QuickAccessVisibleStateProvider } from '../components/QuickAccessVisibleState';
+import Logger from '../logger';
+
+declare global {
+ interface Window {
+ __TABS_HOOK_INSTANCE: any;
+ }
+}
+
+interface Tab {
+ id: QuickAccessTab | number;
+ title: any;
+ content: any;
+ icon: any;
+}
+
+class TabsHook extends Logger {
+ // private keys = 7;
+ tabs: Tab[] = [];
+ private qamBrowserViewPatch?: Patch;
+ private qamEmbeddedPatch?: Patch;
+
+ constructor() {
+ super('TabsHook');
+
+ this.log('Initialized');
+ window.__TABS_HOOK_INSTANCE?.deinit?.();
+ window.__TABS_HOOK_INSTANCE = this;
+ }
+
+ init() {
+ const qamModule = findModuleByExport((e) => e?.type?.toString?.()?.includes('QuickAccessMenuBrowserView'));
+ const qamBrowserViewRenderer = Object.values(qamModule).find((e: any) =>
+ e?.type?.toString?.()?.includes('QuickAccessMenuBrowserView'),
+ );
+ const qamEmbeddedRenderer = Object.values(qamModule).find((e: any) =>
+ e?.type?.toString?.()?.includes('QuickAccessMenuEmbedded'),
+ );
+
+ const patchHandler = createReactTreePatcher(
+ [(tree) => findInReactTree(tree, (node) => node?.props?.onFocusNavDeactivated)],
+ (args, ret) => {
+ const tabs = findInReactTree(ret, (x) => x?.props?.tabs);
+ this.render(tabs.props.tabs, args[0].visible);
+ return ret;
+ },
+ 'TabsHook',
+ );
+
+ this.qamBrowserViewPatch = afterPatch(qamBrowserViewRenderer, 'type', patchHandler);
+ if (qamEmbeddedRenderer) this.qamEmbeddedPatch = afterPatch(qamEmbeddedRenderer, 'type', patchHandler);
+
+ // Patch already rendered qam
+ const root = getReactRoot(document.getElementById('root') as any);
+ const qamNode =
+ root &&
+ findInReactTree(
+ root,
+ (n: any) =>
+ n.elementType == qamBrowserViewRenderer ||
+ (qamEmbeddedRenderer != null && n.elementType == qamEmbeddedRenderer),
+ ); // need elementType, because type is actually mobx wrapper
+ if (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;
+ }
+ }
+ }
+
+ deinit() {
+ this.qamBrowserViewPatch?.unpatch();
+ this.qamEmbeddedPatch?.unpatch();
+ }
+
+ add(tab: Tab) {
+ this.debug('Adding tab', tab.id, 'to render array');
+ this.tabs.push(tab);
+ }
+
+ removeById(id: number) {
+ this.debug('Removing tab', id);
+ this.tabs = this.tabs.filter((tab) => tab.id !== id);
+ }
+
+ render(existingTabs: any[], visible: boolean) {
+ let deckyTabAmount = existingTabs.reduce((prev: any, cur: any) => (cur.decky ? prev + 1 : prev), 0);
+ if (deckyTabAmount == this.tabs.length) {
+ for (let tab of existingTabs) {
+ if (tab?.decky) {
+ if (tab?.qAMVisibilitySetter) {
+ tab?.qAMVisibilitySetter(visible);
+ } else {
+ tab.initialVisibility = visible;
+ }
+ }
+ }
+ return;
+ }
+ for (const { title, icon, content, id } of this.tabs) {
+ const tab: any = {
+ key: id,
+ title,
+ tab: icon,
+ decky: true,
+ initialVisibility: visible,
+ };
+ tab.panel = (
+ <ErrorBoundary>
+ <QuickAccessVisibleStateProvider tab={tab}>{content}</QuickAccessVisibleStateProvider>
+ </ErrorBoundary>
+ );
+ existingTabs.push(tab);
+ }
+ }
+}
+
+export default TabsHook;