From 87a7361dc76949325421d9579e1e1813e84dcc1e Mon Sep 17 00:00:00 2001 From: AAGaming Date: Fri, 14 Oct 2022 23:33:16 -0400 Subject: Allow B button to close active plugin and return to menu. (#218) --- frontend/src/components/PluginView.tsx | 58 ++++---- .../src/components/QuickAccessVisibleState.tsx | 13 ++ frontend/src/plugin-loader.tsx | 2 - frontend/src/tabs-hook.ts | 150 -------------------- frontend/src/tabs-hook.tsx | 151 +++++++++++++++++++++ 5 files changed, 198 insertions(+), 176 deletions(-) create mode 100644 frontend/src/components/QuickAccessVisibleState.tsx delete mode 100644 frontend/src/tabs-hook.ts create mode 100644 frontend/src/tabs-hook.tsx (limited to 'frontend/src') diff --git a/frontend/src/components/PluginView.tsx b/frontend/src/components/PluginView.tsx index 0d0a52cf..2ae0b555 100644 --- a/frontend/src/components/PluginView.tsx +++ b/frontend/src/components/PluginView.tsx @@ -1,5 +1,6 @@ import { ButtonItem, + Focusable, PanelSection, PanelSectionRow, joinClassNames, @@ -10,38 +11,47 @@ import { VFC } from 'react'; import { useDeckyState } from './DeckyState'; import NotificationBadge from './NotificationBadge'; +import { useQuickAccessVisible } from './QuickAccessVisibleState'; +import TitleView from './TitleView'; const PluginView: VFC = () => { - const { plugins, updates, activePlugin, setActivePlugin } = useDeckyState(); + const { plugins, updates, activePlugin, setActivePlugin, closeActivePlugin } = useDeckyState(); + const visible = useQuickAccessVisible(); if (activePlugin) { return ( -
- {activePlugin.content} -
+ + +
+ {visible && activePlugin.content} +
+
); } return ( -
- - {plugins - .filter((p) => p.content) - .map(({ name, icon }) => ( - - setActivePlugin(name)}> -
- {icon} -
{name}
- -
-
-
- ))} -
-
+ <> + +
+ + {plugins + .filter((p) => p.content) + .map(({ name, icon }) => ( + + setActivePlugin(name)}> +
+ {icon} +
{name}
+ +
+
+
+ ))} +
+
+ ); }; diff --git a/frontend/src/components/QuickAccessVisibleState.tsx b/frontend/src/components/QuickAccessVisibleState.tsx new file mode 100644 index 00000000..b5ee3b98 --- /dev/null +++ b/frontend/src/components/QuickAccessVisibleState.tsx @@ -0,0 +1,13 @@ +import { FC, createContext, useContext } from 'react'; + +const QuickAccessVisibleState = createContext(true); + +export const useQuickAccessVisible = () => useContext(QuickAccessVisibleState); + +interface Props { + visible: boolean; +} + +export const QuickAccessVisibleStateProvider: FC = ({ children, visible }) => { + return {children}; +}; diff --git a/frontend/src/plugin-loader.tsx b/frontend/src/plugin-loader.tsx index a1414b3a..6eee9bc0 100644 --- a/frontend/src/plugin-loader.tsx +++ b/frontend/src/plugin-loader.tsx @@ -17,7 +17,6 @@ import { deinitFilepickerPatches, initFilepickerPatches } from './components/mod import PluginInstallModal from './components/modals/PluginInstallModal'; import NotificationBadge from './components/NotificationBadge'; import PluginView from './components/PluginView'; -import TitleView from './components/TitleView'; import WithSuspense from './components/WithSuspense'; import Logger from './logger'; import { Plugin } from './plugin'; @@ -64,7 +63,6 @@ class PluginLoader extends Logger { title: null, content: ( - ), diff --git a/frontend/src/tabs-hook.ts b/frontend/src/tabs-hook.ts deleted file mode 100644 index e75e043d..00000000 --- a/frontend/src/tabs-hook.ts +++ /dev/null @@ -1,150 +0,0 @@ -import { Patch, QuickAccessTab, afterPatch, sleep } from 'decky-frontend-lib'; -import { memo } from 'react'; - -import Logger from './logger'; - -declare global { - interface Window { - __TABS_HOOK_INSTANCE: any; - } - interface Array { - __filter: any; - } -} - -const isTabsArray = (tabs: any) => { - const length = tabs.length; - return length >= 7 && tabs[length - 1]?.tab; -}; - -interface Tab { - id: QuickAccessTab | number; - title: any; - content: any; - icon: any; -} - -class TabsHook extends Logger { - // private keys = 7; - tabs: Tab[] = []; - private quickAccess: any; - private tabRenderer: any; - private memoizedQuickAccess: any; - private cNode: any; - - private qAPTree: any; - private rendererTree: any; - - private cNodePatch?: Patch; - - constructor() { - super('TabsHook'); - - this.log('Initialized'); - window.__TABS_HOOK_INSTANCE?.deinit?.(); - window.__TABS_HOOK_INSTANCE = this; - - const self = this; - const tree = (document.getElementById('root') as any)._reactRootContainer._internalRoot.current; - let scrollRoot: any; - async function findScrollRoot(currentNode: any, iters: number): Promise { - if (iters >= 30) { - self.error( - 'Scroll root was not found before hitting the recursion limit, a developer will need to increase the limit.', - ); - return null; - } - currentNode = currentNode?.child; - if (currentNode?.type?.prototype?.RemoveSmartScrollContainer) { - self.log(`Scroll root was found in ${iters} recursion cycles`); - return currentNode; - } - if (!currentNode) return null; - if (currentNode.sibling) { - let node = await findScrollRoot(currentNode.sibling, iters + 1); - if (node !== null) return node; - } - return await findScrollRoot(currentNode, iters + 1); - } - (async () => { - scrollRoot = await findScrollRoot(tree, 0); - while (!scrollRoot) { - this.log('Failed to find scroll root node, reattempting in 5 seconds'); - await sleep(5000); - scrollRoot = await findScrollRoot(tree, 0); - } - let newQA: any; - let newQATabRenderer: any; - this.cNodePatch = afterPatch(scrollRoot.stateNode, 'render', (_: any, ret: any) => { - if (!this.quickAccess && ret.props.children.props.children[4]) { - this.quickAccess = ret?.props?.children?.props?.children[4].type; - newQA = (...args: any) => { - const ret = this.quickAccess.type(...args); - if (ret) { - if (!newQATabRenderer) { - this.tabRenderer = ret.props.children[1].children.type; - newQATabRenderer = (...args: any) => { - const oFilter = Array.prototype.filter; - Array.prototype.filter = function (...args: any[]) { - if (isTabsArray(this)) { - self.render(this); - } - // @ts-ignore - return oFilter.call(this, ...args); - }; - // TODO remove array hack entirely and use this instead const tabs = ret.props.children.props.children[0].props.children[1].props.children[0].props.children[0].props.tabs - const ret = this.tabRenderer(...args); - Array.prototype.filter = oFilter; - return ret; - }; - } - this.rendererTree = ret.props.children[1].children; - ret.props.children[1].children.type = newQATabRenderer; - } - return ret; - }; - this.memoizedQuickAccess = memo(newQA); - this.memoizedQuickAccess.isDeckyQuickAccess = true; - } - if (ret.props.children.props.children[4]) { - this.qAPTree = ret.props.children.props.children[4]; - ret.props.children.props.children[4].type = this.memoizedQuickAccess; - } - return ret; - }); - this.cNode = scrollRoot; - this.cNode.stateNode.forceUpdate(); - this.log('Finished initial injection'); - })(); - } - - deinit() { - this.cNodePatch?.unpatch(); - if (this.qAPTree) this.qAPTree.type = this.quickAccess; - if (this.rendererTree) this.rendererTree.type = this.tabRenderer; - if (this.cNode) this.cNode.stateNode.forceUpdate(); - } - - 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[]) { - for (const { title, icon, content, id } of this.tabs) { - existingTabs.push({ - key: id, - title, - tab: icon, - panel: content, - }); - } - } -} - -export default TabsHook; diff --git a/frontend/src/tabs-hook.tsx b/frontend/src/tabs-hook.tsx new file mode 100644 index 00000000..c5072e27 --- /dev/null +++ b/frontend/src/tabs-hook.tsx @@ -0,0 +1,151 @@ +import { Patch, QuickAccessTab, afterPatch, sleep } from 'decky-frontend-lib'; +import { memo } from 'react'; + +import { QuickAccessVisibleStateProvider } from './components/QuickAccessVisibleState'; +import Logger from './logger'; + +declare global { + interface Window { + __TABS_HOOK_INSTANCE: any; + } + interface Array { + __filter: any; + } +} + +const isTabsArray = (tabs: any) => { + const length = tabs.length; + return length >= 7 && tabs[length - 1]?.tab; +}; + +interface Tab { + id: QuickAccessTab | number; + title: any; + content: any; + icon: any; +} + +class TabsHook extends Logger { + // private keys = 7; + tabs: Tab[] = []; + private quickAccess: any; + private tabRenderer: any; + private memoizedQuickAccess: any; + private cNode: any; + + private qAPTree: any; + private rendererTree: any; + + private cNodePatch?: Patch; + + constructor() { + super('TabsHook'); + + this.log('Initialized'); + window.__TABS_HOOK_INSTANCE?.deinit?.(); + window.__TABS_HOOK_INSTANCE = this; + + const self = this; + const tree = (document.getElementById('root') as any)._reactRootContainer._internalRoot.current; + let scrollRoot: any; + async function findScrollRoot(currentNode: any, iters: number): Promise { + if (iters >= 30) { + self.error( + 'Scroll root was not found before hitting the recursion limit, a developer will need to increase the limit.', + ); + return null; + } + currentNode = currentNode?.child; + if (currentNode?.type?.prototype?.RemoveSmartScrollContainer) { + self.log(`Scroll root was found in ${iters} recursion cycles`); + return currentNode; + } + if (!currentNode) return null; + if (currentNode.sibling) { + let node = await findScrollRoot(currentNode.sibling, iters + 1); + if (node !== null) return node; + } + return await findScrollRoot(currentNode, iters + 1); + } + (async () => { + scrollRoot = await findScrollRoot(tree, 0); + while (!scrollRoot) { + this.log('Failed to find scroll root node, reattempting in 5 seconds'); + await sleep(5000); + scrollRoot = await findScrollRoot(tree, 0); + } + let newQA: any; + let newQATabRenderer: any; + this.cNodePatch = afterPatch(scrollRoot.stateNode, 'render', (_: any, ret: any) => { + if (!this.quickAccess && ret.props.children.props.children[4]) { + this.quickAccess = ret?.props?.children?.props?.children[4].type; + newQA = (...args: any) => { + const ret = this.quickAccess.type(...args); + if (ret) { + if (!newQATabRenderer) { + this.tabRenderer = ret.props.children[1].children.type; + newQATabRenderer = (...qamArgs: any[]) => { + const oFilter = Array.prototype.filter; + Array.prototype.filter = function (...args: any[]) { + if (isTabsArray(this)) { + self.render(this, qamArgs[0].visible); + } + // @ts-ignore + return oFilter.call(this, ...args); + }; + // TODO remove array hack entirely and use this instead const tabs = ret.props.children.props.children[0].props.children[1].props.children[0].props.children[0].props.tabs + const ret = this.tabRenderer(...qamArgs); + Array.prototype.filter = oFilter; + return ret; + }; + } + this.rendererTree = ret.props.children[1].children; + ret.props.children[1].children.type = newQATabRenderer; + } + return ret; + }; + this.memoizedQuickAccess = memo(newQA); + this.memoizedQuickAccess.isDeckyQuickAccess = true; + } + if (ret.props.children.props.children[4]) { + this.qAPTree = ret.props.children.props.children[4]; + ret.props.children.props.children[4].type = this.memoizedQuickAccess; + } + return ret; + }); + this.cNode = scrollRoot; + this.cNode.stateNode.forceUpdate(); + this.log('Finished initial injection'); + })(); + } + + deinit() { + this.cNodePatch?.unpatch(); + if (this.qAPTree) this.qAPTree.type = this.quickAccess; + if (this.rendererTree) this.rendererTree.type = this.tabRenderer; + if (this.cNode) this.cNode.stateNode.forceUpdate(); + } + + 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) { + for (const { title, icon, content, id } of this.tabs) { + existingTabs.push({ + key: id, + title, + tab: icon, + panel: {content}, + }); + } + } +} + +export default TabsHook; -- cgit v1.2.3