diff options
| author | AAGaming <aagaming@riseup.net> | 2024-07-18 01:20:29 -0400 |
|---|---|---|
| committer | AAGaming <aagaming@riseup.net> | 2024-08-03 14:04:19 -0400 |
| commit | 88e7919a12fd56b297e73afb3fb05483f5893f4d (patch) | |
| tree | 4b6920d6f8675e6d6de8f2de0df4b4477746a879 /frontend/src/toaster.tsx | |
| parent | 28c7254ef6952d9504472ebcbb05238b50aa6086 (diff) | |
| download | decky-loader-88e7919a12fd56b297e73afb3fb05483f5893f4d.tar.gz decky-loader-88e7919a12fd56b297e73afb3fb05483f5893f4d.zip | |
implement new toaster hook
this also supports the notification list and probably also desktop toasts (UI wip, read location enum prop from toast component probably)
Diffstat (limited to 'frontend/src/toaster.tsx')
| -rw-r--r-- | frontend/src/toaster.tsx | 172 |
1 files changed, 36 insertions, 136 deletions
diff --git a/frontend/src/toaster.tsx b/frontend/src/toaster.tsx index 4bc08772..611806d2 100644 --- a/frontend/src/toaster.tsx +++ b/frontend/src/toaster.tsx @@ -1,19 +1,16 @@ import type { ToastData } from '@decky/api'; -import { - Export, - Patch, - afterPatch, - findClassByName, - findInReactTree, - findModuleExport, - getReactRoot, - sleep, -} from '@decky/ui'; -import { ReactNode } from 'react'; +import { Patch, callOriginal, findModuleExport, injectFCTrampoline, replacePatch } from '@decky/ui'; import Toast from './components/Toast'; import Logger from './logger'; +// TODO export +enum ToastType { + New, + Update, + Remove, +} + declare global { interface Window { __TOASTER_INSTANCE: any; @@ -23,18 +20,12 @@ declare global { } class Toaster extends Logger { - // private routerHook: RouterHook; - // private toasterState: DeckyToasterState = new DeckyToasterState(); - private node: any; - private rNode: any; - private audioModule: any; private finishStartup?: () => void; private ready: Promise<void> = new Promise((res) => (this.finishStartup = res)); - private toasterPatch?: Patch; + private toastPatch?: Patch; constructor() { super('Toaster'); - // this.routerHook = routerHook; window.__TOASTER_INSTANCE?.deinit?.(); window.__TOASTER_INSTANCE = this; @@ -42,115 +33,18 @@ class Toaster extends Logger { } async init() { - // this.routerHook.addGlobalComponent('DeckyToaster', () => ( - // <DeckyToasterStateContextProvider deckyToasterState={this.toasterState}> - // <DeckyToaster /> - // </DeckyToasterStateContextProvider> - // )); - let instance: any; - const tree = getReactRoot(document.getElementById('root') as any); - const toasterClass1 = findClassByName('GamepadToastPlaceholder'); - const toasterClass2 = findClassByName('ToastPlaceholder'); - const toasterClass3 = findClassByName('ToastPopup'); - const toasterClass4 = findClassByName('GamepadToastPopup'); - const findToasterRoot = (currentNode: any, iters: number): any => { - if (iters >= 80) { - // currently 66 - return null; - } - if ( - currentNode?.memoizedProps?.className?.startsWith?.(toasterClass1) || - currentNode?.memoizedProps?.className?.startsWith?.(toasterClass2) || - currentNode?.memoizedProps?.className?.startsWith?.(toasterClass3) || - currentNode?.memoizedProps?.className?.startsWith?.(toasterClass4) - ) { - this.log(`Toaster root was found in ${iters} recursion cycles`); - return currentNode; - } - if (currentNode.sibling) { - let node = findToasterRoot(currentNode.sibling, iters + 1); - if (node !== null) return node; - } - if (currentNode.child) { - let node = findToasterRoot(currentNode.child, iters + 1); - if (node !== null) return node; - } - return null; - }; - instance = findToasterRoot(tree, 0); - while (!instance) { - this.warn( - 'Failed to find Toaster root node, reattempting in 5 seconds. A developer may need to increase the recursion limit.', - ); - await sleep(5000); - instance = findToasterRoot(tree, 0); - } - this.node = instance.return; - this.rNode = findInReactTree( - this.node.return.return, - (node) => node?.stateNode && node.type?.InstallErrorReportingStore, - ); - let toast: any; - let renderedToast: ReactNode = null; - let innerPatched: any; - const repatch = () => { - if (this.node && !this.node.type.decky) { - this.toasterPatch = afterPatch(this.node, 'type', (_: any, ret: any) => { - const inner = findInReactTree(ret.props.children, (x) => x?.props?.onDismiss); - if (innerPatched) { - inner.type = innerPatched; - } else { - afterPatch(inner, 'type', (innerArgs: any, ret: any) => { - const currentToast = innerArgs[0]?.notification; - if (currentToast?.decky) { - if (currentToast == toast) { - ret.props.children = renderedToast; - } else { - toast = currentToast; - renderedToast = <Toast toast={toast.data} />; - ret.props.children = renderedToast; - } - } else { - toast = null; - renderedToast = null; - } - return ret; - }); - innerPatched = inner.type; - } - return ret; - }); - this.node.type.decky = true; - this.node.alternate.type = this.node.type; - } - }; - const oRender = Object.getPrototypeOf(this.rNode.stateNode).render; - let int: number | undefined; - this.rNode.stateNode.render = (...args: any[]) => { - const ret = oRender.call(this.rNode.stateNode, ...args); - if (ret && !this?.node?.return?.return) { - int && clearInterval(int); - int = setInterval(() => { - const n = findToasterRoot(tree, 0); - if (n?.return) { - clearInterval(int); - this.node = n.return; - this.rNode = this.node.return; - repatch(); - } else { - this.error('Failed to re-grab Toaster node, trying again...'); - } - }, 1200); + const ToastRenderer = findModuleExport((e) => e?.toString()?.includes(`controller:"notification",method:`)); + this.debug('toastrenderer', ToastRenderer); + // TODO find a way to undo this if possible? + const patchedRenderer = injectFCTrampoline(ToastRenderer); + this.toastPatch = replacePatch(patchedRenderer, 'component', (args: any[]) => { + this.debug('render toast', args); + if (args?.[0]?.group?.decky || args?.[0]?.group?.notifications?.[0]?.decky) { + this.debug('rendering decky toast'); + return args[0].group.notifications.map((notification: any) => <Toast toast={notification.data} />); } - repatch(); - return ret; - }; - - this.rNode.stateNode.shouldComponentUpdate = () => true; - this.rNode.stateNode.forceUpdate(); - delete this.rNode.stateNode.shouldComponentUpdate; - - this.audioModule = findModuleExport((e: Export) => e.PlayNavSound && e.RegisterCallbackOnPlaySound); + return callOriginal; + }); this.log('Initialized'); this.finishStartup?.(); @@ -168,8 +62,6 @@ class Toaster extends Logger { data: toast, decky: true, }; - // @ts-ignore - toastData.data.appid = () => 0; if (toast.sound === undefined) toast.sound = 6; if (toast.playSound === undefined) toast.playSound = true; if (toast.showToast === undefined) toast.showToast = true; @@ -180,19 +72,27 @@ class Toaster extends Logger { window.NotificationStore.BIsUserInGame()) ) return; - if (toast.playSound) this.audioModule?.PlayNavSound(toast.sound); if (toast.showToast) { - window.NotificationStore.m_rgNotificationToasts.push(toastData); - window.NotificationStore.DispatchNextToast(); + function fnTray(toast: any, tray: any) { + let group = { + eType: toast.eType, + notifications: [toast], + }; + tray.unshift(group); + } + const info = { + showToast: toast.showToast, + sound: toast.sound, + eFeature: 0, + toastDurationMS: toastData.nToastDurationMS, + fnTray, + }; + window.NotificationStore.ProcessNotification(info, toastData, ToastType.New); } } deinit() { - this.toasterPatch?.unpatch(); - this.node.alternate.type = this.node.type; - delete this.rNode.stateNode.render; - this.ready = new Promise((res) => (this.finishStartup = res)); - // this.routerHook.removeGlobalComponent('DeckyToaster'); + this.toastPatch?.unpatch(); } } |
