diff options
| author | AAGaming <aa@mail.catvibers.me> | 2022-08-09 21:52:03 -0400 |
|---|---|---|
| committer | AAGaming <aa@mail.catvibers.me> | 2022-08-09 21:52:03 -0400 |
| commit | 67426af3ef73e788d99b6d2e0c730c270daea273 (patch) | |
| tree | 30c1f4b33e63d38d8d5cc26f26af655f1b5a44ba | |
| parent | 0dbdb4a143f6e4f2b08c5a38a597d5a1c49a109c (diff) | |
| download | decky-loader-67426af3ef73e788d99b6d2e0c730c270daea273.tar.gz decky-loader-67426af3ef73e788d99b6d2e0c730c270daea273.zip | |
Add api for showing toast notificationsv2.0.4-67426af-pre
| -rw-r--r-- | backend/updater.py | 6 | ||||
| -rw-r--r-- | frontend/package.json | 2 | ||||
| -rw-r--r-- | frontend/pnpm-lock.yaml | 8 | ||||
| -rw-r--r-- | frontend/src/components/Toast.tsx | 54 | ||||
| -rw-r--r-- | frontend/src/components/settings/pages/general/Updater.tsx | 18 | ||||
| -rw-r--r-- | frontend/src/index.tsx | 3 | ||||
| -rw-r--r-- | frontend/src/plugin-loader.tsx | 14 | ||||
| -rw-r--r-- | frontend/src/toaster.tsx | 93 | ||||
| -rw-r--r-- | frontend/src/updater.ts | 16 |
9 files changed, 190 insertions, 24 deletions
diff --git a/backend/updater.py b/backend/updater.py index 4c3cd715..4fca0496 100644 --- a/backend/updater.py +++ b/backend/updater.py @@ -62,7 +62,7 @@ class Updater: "updatable": self.localVer != None } else: - return {"current": "unknown", "updatable": False} + return {"current": "unknown", "remote": self.remoteVer, "updatable": False} async def check_for_updates(self): async with ClientSession() as web: @@ -70,6 +70,8 @@ class Updater: remoteVersions = await res.json() self.remoteVer = next(filter(lambda ver: ver["prerelease"] and ver["tag_name"].startswith("v") and ver["tag_name"].endswith("-pre"), remoteVersions), None) logger.info("Updated remote version information") + tab = await get_tab("SP") + await tab.evaluate_js(f"window.DeckyPluginLoader.notifyUpdates()", False, True, False) return await self.get_version() async def version_reloader(self): @@ -79,7 +81,7 @@ class Updater: await self.check_for_updates() except: pass - await sleep(60 * 60) # 1 hour + await sleep(60 * 60 * 6) # 6 hours async def do_update(self): version = self.remoteVer["tag_name"] diff --git a/frontend/package.json b/frontend/package.json index 422ba3c3..3765e94b 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -37,7 +37,7 @@ } }, "dependencies": { - "decky-frontend-lib": "^1.2.4", + "decky-frontend-lib": "^1.5.0", "react-icons": "^4.4.0" } } diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml index 932f5e18..85cf84e0 100644 --- a/frontend/pnpm-lock.yaml +++ b/frontend/pnpm-lock.yaml @@ -9,7 +9,7 @@ specifiers: '@types/react': 16.14.0 '@types/react-router': 5.1.18 '@types/webpack': ^5.28.0 - decky-frontend-lib: ^1.2.4 + decky-frontend-lib: ^1.5.0 husky: ^8.0.1 import-sort-style-module: ^6.0.0 inquirer: ^8.2.4 @@ -23,7 +23,7 @@ specifiers: typescript: ^4.7.4 dependencies: - decky-frontend-lib: 1.2.4 + decky-frontend-lib: 1.5.0 react-icons: 4.4.0_react@16.14.0 devDependencies: @@ -806,8 +806,8 @@ packages: ms: 2.1.2 dev: true - /decky-frontend-lib/1.2.4: - resolution: {integrity: sha512-r3mLEey9KUkF68geJVSjNlOz/Fg4vpMKUzoutSceyd8o/J5l+QR+Vf0b3gwK3UN9Sp4Pj4XQ1eB82+/W0ApsFg==} + /decky-frontend-lib/1.5.0: + resolution: {integrity: sha512-BT/txV7Q1NJfbnT99jt0OdUE2Qv4Gm1jhrevctx84D+pp7TfpCYau+Khnq2B9agEQQo7CWh5hOJW/SX5n2dKnQ==} dev: false /deepmerge/4.2.2: diff --git a/frontend/src/components/Toast.tsx b/frontend/src/components/Toast.tsx new file mode 100644 index 00000000..559c37c6 --- /dev/null +++ b/frontend/src/components/Toast.tsx @@ -0,0 +1,54 @@ +import { ToastData, findModule, joinClassNames } from 'decky-frontend-lib'; +import { FunctionComponent } from 'react'; + +interface ToastProps { + toast: { + data: ToastData; + nToastDurationMS: number; + }; +} + +const toastClasses = findModule((mod) => { + if (typeof mod !== 'object') return false; + + if (mod.ToastPlaceholder) { + return true; + } + + return false; +}); + +const templateClasses = findModule((mod) => { + if (typeof mod !== 'object') return false; + + if (mod.ShortTemplate) { + return true; + } + + return false; +}); + +const Toast: FunctionComponent<ToastProps> = ({ toast }) => { + return ( + <div + style={{ '--toast-duration': `${toast.nToastDurationMS}ms` } as React.CSSProperties} + className={joinClassNames(toastClasses.ToastPopup, toastClasses.toastEnter)} + > + <div + onClick={toast.data.onClick} + className={joinClassNames(templateClasses.ShortTemplate, toast.data.className || '')} + > + {toast.data.logo && <div className={templateClasses.StandardLogoDimensions}>{toast.data.logo}</div>} + <div className={joinClassNames(templateClasses.Content, toast.data.contentClassName || '')}> + <div className={templateClasses.Header}> + {toast.data.icon && <div className={templateClasses.Icon}>{toast.data.icon}</div>} + <div className={templateClasses.Title}>{toast.data.title}</div> + </div> + <div className={templateClasses.Body}>{toast.data.body}</div> + </div> + </div> + </div> + ); +}; + +export default Toast; diff --git a/frontend/src/components/settings/pages/general/Updater.tsx b/frontend/src/components/settings/pages/general/Updater.tsx index 106af6f3..3d137d7a 100644 --- a/frontend/src/components/settings/pages/general/Updater.tsx +++ b/frontend/src/components/settings/pages/general/Updater.tsx @@ -2,23 +2,7 @@ import { DialogButton, Field, ProgressBarWithInfo, Spinner } from 'decky-fronten import { useEffect, useState } from 'react'; import { FaArrowDown } from 'react-icons/fa'; -import { callUpdaterMethod, finishUpdate } from '../../../../updater'; - -interface VerInfo { - current: string; - remote: { - assets: { - browser_download_url: string; - created_at: string; - }[]; - name: string; - body: string; - prerelease: boolean; - published_at: string; - tag_name: string; - } | null; - updatable: boolean; -} +import { VerInfo, callUpdaterMethod, finishUpdate } from '../../../../updater'; export default function UpdaterSettings() { const [versionInfo, setVersionInfo] = useState<VerInfo | null>(null); diff --git a/frontend/src/index.tsx b/frontend/src/index.tsx index 20f71766..3df18093 100644 --- a/frontend/src/index.tsx +++ b/frontend/src/index.tsx @@ -1,3 +1,5 @@ +import { sleep } from 'decky-frontend-lib'; + import PluginLoader from './plugin-loader'; import { DeckyUpdater } from './updater'; @@ -12,6 +14,7 @@ declare global { } } (async () => { + await sleep(1000); window.deckyAuthToken = await fetch('http://127.0.0.1:1337/auth/token').then((r) => r.text()); window.DeckyPluginLoader?.dismountAll(); diff --git a/frontend/src/plugin-loader.tsx b/frontend/src/plugin-loader.tsx index 29ca326f..df3b220a 100644 --- a/frontend/src/plugin-loader.tsx +++ b/frontend/src/plugin-loader.tsx @@ -12,6 +12,8 @@ import Logger from './logger'; import { Plugin } from './plugin'; import RouterHook from './router-hook'; import TabsHook from './tabs-hook'; +import Toaster from './toaster'; +import { VerInfo, callUpdaterMethod } from './updater'; declare global { interface Window {} @@ -22,6 +24,7 @@ class PluginLoader extends Logger { private tabsHook: TabsHook = new TabsHook(); // private windowHook: WindowHook = new WindowHook(); private routerHook: RouterHook = new RouterHook(); + private toaster: Toaster = new Toaster(); private deckyState: DeckyState = new DeckyState(); private reloadLock: boolean = false; @@ -54,6 +57,16 @@ class PluginLoader extends Logger { }); } + public async notifyUpdates() { + const versionInfo = (await callUpdaterMethod('get_version')).result as VerInfo; + if (versionInfo?.remote && versionInfo?.remote?.tag_name != versionInfo?.current) { + this.toaster.toast({ + title: 'Decky', + body: `Update to ${versionInfo?.remote?.tag_name} availiable!`, + }); + } + } + public addPluginInstallPrompt(artifact: string, version: string, request_id: string, hash: string) { showModal( <PluginInstallModal @@ -189,6 +202,7 @@ class PluginLoader extends Logger { createPluginAPI(pluginName: string) { return { routerHook: this.routerHook, + toaster: this.toaster, callServerMethod: this.callServerMethod, async callPluginMethod(methodName: string, args = {}) { const response = await fetch(`http://127.0.0.1:1337/plugins/${pluginName}/methods/${methodName}`, { diff --git a/frontend/src/toaster.tsx b/frontend/src/toaster.tsx new file mode 100644 index 00000000..b6901ed2 --- /dev/null +++ b/frontend/src/toaster.tsx @@ -0,0 +1,93 @@ +import { ToastData, afterPatch, findInReactTree, findModuleChild, sleep, unpatch } from 'decky-frontend-lib'; + +import Toast from './components/Toast'; +import Logger from './logger'; + +declare global { + interface Window { + __TOASTER_INSTANCE: any; + NotificationStore: any; + } +} + +class Toaster extends Logger { + private instanceRet: any; + private node: any; + private settingsModule: any; + + constructor() { + super('Toaster'); + + window.__TOASTER_INSTANCE?.deinit?.(); + window.__TOASTER_INSTANCE = this; + this.init(); + } + + async init() { + this.settingsModule = findModuleChild((m) => { + if (typeof m !== 'object') return undefined; + for (let prop in m) { + if (typeof m[prop]?.settings?.bDisableToastsInGame !== 'undefined') return m[prop]; + } + }); + + let instance: any; + while (true) { + instance = findInReactTree( + (document.getElementById('root') as any)._reactRootContainer._internalRoot.current, + (x) => x?.memoizedProps?.className?.startsWith('toastmanager_ToastPlaceholder'), + ); + if (instance) break; + this.debug('finding instance'); + await sleep(2000); + } + + this.node = instance.return.return; + this.node.stateNode.render = (...args: any[]) => { + const ret = this.node.stateNode.__proto__.render.call(this.node.stateNode, ...args); + if (ret) { + this.instanceRet = ret; + afterPatch(ret, 'type', (_: any, ret: any) => { + if (ret?.props?.children[1]?.children?.props?.notification?.decky) { + const toast = ret.props.children[1].children.props.notification; + ret.props.children[1].children.type = () => <Toast toast={toast} />; + } + return ret; + }); + } + return ret; + }; + this.node.stateNode.forceUpdate(); + this.log('Initialized'); + } + + toast(toast: ToastData) { + const settings = this.settingsModule.settings; + let toastData = { + nNotificationID: window.NotificationStore.m_nNextTestNotificationID++, + rtCreated: Date.now(), + eType: 15, + nToastDurationMS: toast.duration || 5e3, + data: toast, + decky: true, + }; + // @ts-ignore + toastData.data.appid = () => 0; + if ( + (settings.bDisableAllToasts && !toast.critical) || + (settings.bDisableToastsInGame && !toast.critical && window.NotificationStore.BIsUserInGame()) + ) + return; + window.NotificationStore.m_rgNotificationToasts.push(toastData); + window.NotificationStore.DispatchNextToast(); + window.NotificationStore.m_rgNotificationToasts.pop(); + } + + deinit() { + unpatch(this.instanceRet, 'type'); + delete this.node.stateNode.render; + this.node.stateNode.forceUpdate(); + } +} + +export default Toaster; diff --git a/frontend/src/updater.ts b/frontend/src/updater.ts index f499d030..dd37f0b4 100644 --- a/frontend/src/updater.ts +++ b/frontend/src/updater.ts @@ -11,6 +11,22 @@ export interface DeckyUpdater { finish: () => void; } +export interface VerInfo { + current: string; + remote: { + assets: { + browser_download_url: string; + created_at: string; + }[]; + name: string; + body: string; + prerelease: boolean; + published_at: string; + tag_name: string; + } | null; + updatable: boolean; +} + export async function callUpdaterMethod(methodName: string, args = {}) { const response = await fetch(`http://127.0.0.1:1337/updater/${methodName}`, { method: 'POST', |
