diff options
Diffstat (limited to 'frontend/src')
| -rw-r--r-- | frontend/src/components/DeckyErrorBoundary.tsx | 201 | ||||
| -rw-r--r-- | frontend/src/components/DeckyGlobalComponentsState.tsx | 3 | ||||
| -rw-r--r-- | frontend/src/components/DeckyRouterState.tsx | 3 | ||||
| -rw-r--r-- | frontend/src/components/DeckyState.tsx | 3 | ||||
| -rw-r--r-- | frontend/src/components/DeckyToaster.tsx | 5 | ||||
| -rw-r--r-- | frontend/src/components/DeckyToasterState.tsx | 2 | ||||
| -rw-r--r-- | frontend/src/components/Toast.tsx | 3 | ||||
| -rw-r--r-- | frontend/src/components/modals/DropdownMultiselect.tsx | 2 | ||||
| -rw-r--r-- | frontend/src/components/modals/PluginInstallModal.tsx | 28 | ||||
| -rw-r--r-- | frontend/src/developer.tsx | 6 | ||||
| -rw-r--r-- | frontend/src/errorboundary-hook.tsx | 69 | ||||
| -rw-r--r-- | frontend/src/plugin-loader.tsx | 42 | ||||
| -rw-r--r-- | frontend/src/steamfixes/index.ts | 4 | ||||
| -rw-r--r-- | frontend/src/steamfixes/reload.ts | 21 | ||||
| -rw-r--r-- | frontend/src/toaster.tsx | 8 | ||||
| -rw-r--r-- | frontend/src/updater.ts | 5 | ||||
| -rw-r--r-- | frontend/src/utils/TranslationHelper.tsx | 49 |
17 files changed, 355 insertions, 99 deletions
diff --git a/frontend/src/components/DeckyErrorBoundary.tsx b/frontend/src/components/DeckyErrorBoundary.tsx new file mode 100644 index 00000000..a851b2e1 --- /dev/null +++ b/frontend/src/components/DeckyErrorBoundary.tsx @@ -0,0 +1,201 @@ +import { sleep } from '@decky/ui'; +import { ErrorInfo, FunctionComponent, useReducer, useState } from 'react'; + +import { uninstallPlugin } from '../plugin'; +import { doRestart, doShutdown } from '../updater'; + +interface ReactErrorInfo { + error: Error; + info: ErrorInfo; +} + +interface DeckyErrorBoundaryProps { + error: ReactErrorInfo; + errorKey: string; + reset: () => void; +} + +declare global { + interface Window { + SystemNetworkStore?: any; + } +} + +const pluginErrorRegex = /\(http:\/\/localhost:1337\/plugins\/(.*)\//; +const pluginSourceMapErrorRegex = /\(decky:\/\/decky\/plugin\/(.*)\//; +const legacyPluginErrorRegex = /\(decky:\/\/decky\/legacy_plugin\/(.*)\/index.js/; + +function getLikelyErrorSource(error: ReactErrorInfo): [source: string, wasPlugin: boolean] { + const pluginMatch = error.error.stack?.match(pluginErrorRegex); + if (pluginMatch) { + return [decodeURIComponent(pluginMatch[1]), true]; + } + + const pluginMatchViaMap = error.error.stack?.match(pluginSourceMapErrorRegex); + if (pluginMatchViaMap) { + return [decodeURIComponent(pluginMatchViaMap[1]), true]; + } + + const legacyPluginMatch = error.error.stack?.match(legacyPluginErrorRegex); + if (legacyPluginMatch) { + return [decodeURIComponent(legacyPluginMatch[1]), true]; + } + + if (error.error.stack?.includes('http://localhost:1337/')) { + return ['the Decky frontend', false]; + } + return ['Steam', false]; +} + +export const startSSH = DeckyBackend.callable('utilities/start_ssh'); +export const starrCEFForwarding = DeckyBackend.callable('utilities/allow_remote_debugging'); + +function ipToString(ip: number) { + return [(ip >>> 24) & 255, (ip >>> 16) & 255, (ip >>> 8) & 255, (ip >>> 0) & 255].join('.'); +} + +// Intentionally not localized since we can't really trust React here +const DeckyErrorBoundary: FunctionComponent<DeckyErrorBoundaryProps> = ({ error, reset }) => { + const [actionLog, addLogLine] = useReducer((log: string, line: string) => (log += '\n' + line), ''); + const [actionsEnabled, setActionsEnabled] = useState<boolean>(true); + const [debugAllowed, setDebugAllowed] = useState<boolean>(true); + const [errorSource, wasCausedByPlugin] = getLikelyErrorSource(error); + + return ( + <div + style={{ + overflow: 'scroll', + marginLeft: '15px', + color: 'white', + fontSize: '16px', + userSelect: 'auto', + backgroundColor: 'black', + marginTop: '48px', // Incase this is a page + }} + > + <h1 + style={{ + fontSize: '20px', + display: 'inline-block', + marginTop: '15px', + userSelect: 'auto', + }} + > + ⚠️ An error occured rendering this content. + </h1> + <p>This error likely occured in {getLikelyErrorSource(error)}.</p> + {actionLog?.length > 0 && ( + <pre> + <code> + Running actions... + {actionLog} + </code> + </pre> + )} + {actionsEnabled && ( + <> + <h3>Actions: </h3> + <p>Use the touch screen.</p> + <div style={{ display: 'block', marginBottom: '5px' }}> + <button style={{ marginRight: '5px', padding: '5px' }} onClick={reset}> + Retry + </button> + <button style={{ marginRight: '5px', padding: '5px' }} onClick={() => SteamClient.User.StartRestart()}> + Restart Steam + </button> + </div> + <div style={{ display: 'block', marginBottom: '5px' }}> + <button + style={{ marginRight: '5px', padding: '5px' }} + onClick={async () => { + setActionsEnabled(false); + addLogLine('Restarting Decky...'); + doRestart(); + await sleep(2000); + addLogLine('Reloading UI...'); + }} + > + Restart Decky + </button> + <button + style={{ marginRight: '5px', padding: '5px' }} + onClick={async () => { + setActionsEnabled(false); + addLogLine('Stopping Decky...'); + doShutdown(); + await sleep(5000); + addLogLine('Restarting Steam...'); + SteamClient.User.StartRestart(); + }} + > + Disable Decky until next boot + </button> + </div> + {debugAllowed && ( + <div style={{ display: 'block', marginBottom: '5px' }}> + <button + style={{ marginRight: '5px', padding: '5px' }} + onClick={async () => { + setDebugAllowed(false); + addLogLine('Enabling CEF debugger forwarding...'); + await starrCEFForwarding(); + addLogLine('Enabling SSH...'); + await startSSH(); + addLogLine('Ready for debugging!'); + if (window?.SystemNetworkStore?.wirelessNetworkDevice?.ip4?.addresses?.[0]?.ip) { + const ip = ipToString(window.SystemNetworkStore.wirelessNetworkDevice.ip4.addresses[0].ip); + addLogLine(`CEF Debugger: http://${ip}:8081`); + addLogLine(`SSH: deck@${ip}`); + } + }} + > + Allow remote debugging and SSH until next boot + </button> + </div> + )} + {wasCausedByPlugin && ( + <div style={{ display: 'block', marginBottom: '5px' }}> + {'\n'} + <button + style={{ marginRight: '5px', padding: '5px' }} + onClick={async () => { + setActionsEnabled(false); + addLogLine(`Uninstalling ${errorSource}...`); + await uninstallPlugin(errorSource); + await DeckyPluginLoader.frozenPluginsService.invalidate(); + await DeckyPluginLoader.hiddenPluginsService.invalidate(); + await sleep(1000); + addLogLine('Restarting Decky...'); + doRestart(); + await sleep(2000); + addLogLine('Restarting Steam...'); + await sleep(500); + SteamClient.User.StartRestart(); + }} + > + Uninstall {errorSource} and restart Decky + </button> + </div> + )} + </> + )} + + <pre + style={{ + marginTop: '15px', + opacity: 0.7, + userSelect: 'auto', + }} + > + <code> + {error.error.stack} + {'\n\n'} + Component Stack: + {error.info.componentStack} + </code> + </pre> + </div> + ); +}; + +export default DeckyErrorBoundary; diff --git a/frontend/src/components/DeckyGlobalComponentsState.tsx b/frontend/src/components/DeckyGlobalComponentsState.tsx index fe45588b..475d1e4a 100644 --- a/frontend/src/components/DeckyGlobalComponentsState.tsx +++ b/frontend/src/components/DeckyGlobalComponentsState.tsx @@ -1,4 +1,4 @@ -import { FC, createContext, useContext, useEffect, useState } from 'react'; +import { FC, ReactNode, createContext, useContext, useEffect, useState } from 'react'; interface PublicDeckyGlobalComponentsState { components: Map<string, FC>; @@ -40,6 +40,7 @@ export const useDeckyGlobalComponentsState = () => useContext(DeckyGlobalCompone interface Props { deckyGlobalComponentsState: DeckyGlobalComponentsState; + children: ReactNode; } export const DeckyGlobalComponentsStateContextProvider: FC<Props> = ({ diff --git a/frontend/src/components/DeckyRouterState.tsx b/frontend/src/components/DeckyRouterState.tsx index 0c8bb1ba..426ed731 100644 --- a/frontend/src/components/DeckyRouterState.tsx +++ b/frontend/src/components/DeckyRouterState.tsx @@ -1,4 +1,4 @@ -import { ComponentType, FC, createContext, useContext, useEffect, useState } from 'react'; +import { ComponentType, FC, ReactNode, createContext, useContext, useEffect, useState } from 'react'; import type { RouteProps } from 'react-router'; export interface RouterEntry { @@ -71,6 +71,7 @@ export const useDeckyRouterState = () => useContext(DeckyRouterStateContext); interface Props { deckyRouterState: DeckyRouterState; + children: ReactNode; } export const DeckyRouterStateContextProvider: FC<Props> = ({ children, deckyRouterState }) => { diff --git a/frontend/src/components/DeckyState.tsx b/frontend/src/components/DeckyState.tsx index 749e27ce..75106e62 100644 --- a/frontend/src/components/DeckyState.tsx +++ b/frontend/src/components/DeckyState.tsx @@ -1,4 +1,4 @@ -import { FC, createContext, useContext, useEffect, useState } from 'react'; +import { FC, ReactNode, createContext, useContext, useEffect, useState } from 'react'; import { DEFAULT_NOTIFICATION_SETTINGS, NotificationSettings } from '../notification-service'; import { Plugin } from '../plugin'; @@ -134,6 +134,7 @@ export const useDeckyState = () => useContext(DeckyStateContext); interface Props { deckyState: DeckyState; + children?: ReactNode; } export const DeckyStateContextProvider: FC<Props> = ({ children, deckyState }) => { diff --git a/frontend/src/components/DeckyToaster.tsx b/frontend/src/components/DeckyToaster.tsx index 00b7b4db..1cb51d65 100644 --- a/frontend/src/components/DeckyToaster.tsx +++ b/frontend/src/components/DeckyToaster.tsx @@ -1,4 +1,5 @@ -import { ToastData, joinClassNames } from '@decky/ui'; +import type { ToastData } from '@decky/api'; +import { joinClassNames } from '@decky/ui'; import { FC, useEffect, useState } from 'react'; import { ReactElement } from 'react-markdown/lib/react-markdown'; @@ -28,7 +29,7 @@ const DeckyToaster: FC<DeckyToasterProps> = () => { } useEffect(() => { // not actually node but TS is shit - let interval: NodeJS.Timer | null; + let interval: NodeJS.Timeout | number | null; if (renderedToast) { interval = setTimeout( () => { diff --git a/frontend/src/components/DeckyToasterState.tsx b/frontend/src/components/DeckyToasterState.tsx index d6c3871f..8d0a5d45 100644 --- a/frontend/src/components/DeckyToasterState.tsx +++ b/frontend/src/components/DeckyToasterState.tsx @@ -1,4 +1,4 @@ -import { ToastData } from '@decky/ui'; +import type { ToastData } from '@decky/api'; import { FC, createContext, useContext, useEffect, useState } from 'react'; interface PublicDeckyToasterState { diff --git a/frontend/src/components/Toast.tsx b/frontend/src/components/Toast.tsx index ab01671a..79e3d864 100644 --- a/frontend/src/components/Toast.tsx +++ b/frontend/src/components/Toast.tsx @@ -1,4 +1,5 @@ -import { ToastData, findModule, joinClassNames } from '@decky/ui'; +import type { ToastData } from '@decky/api'; +import { findModule, joinClassNames } from '@decky/ui'; import { FunctionComponent } from 'react'; interface ToastProps { diff --git a/frontend/src/components/modals/DropdownMultiselect.tsx b/frontend/src/components/modals/DropdownMultiselect.tsx index 4c5cf7b1..255c6fa0 100644 --- a/frontend/src/components/modals/DropdownMultiselect.tsx +++ b/frontend/src/components/modals/DropdownMultiselect.tsx @@ -59,7 +59,7 @@ const DropdownMultiselect: FC<{ const [itemsSelected, setItemsSelected] = useState<any>(selected); const { t } = useTranslation(); - const handleItemSelect = useCallback((checked, value) => { + const handleItemSelect = useCallback((checked: boolean, value: any) => { setItemsSelected((x: any) => checked ? [...x.filter((y: any) => y !== value), value] : x.filter((y: any) => y !== value), ); diff --git a/frontend/src/components/modals/PluginInstallModal.tsx b/frontend/src/components/modals/PluginInstallModal.tsx index 8b3128a1..c6c90264 100644 --- a/frontend/src/components/modals/PluginInstallModal.tsx +++ b/frontend/src/components/modals/PluginInstallModal.tsx @@ -60,10 +60,10 @@ const PluginInstallModal: FC<PluginInstallModalProps> = ({ strTitle={ <div> <TranslationHelper - trans_class={TranslationClass.PLUGIN_INSTALL_MODAL} - trans_text="title" - i18n_args={{ artifact: artifact }} - install_type={installType} + transClass={TranslationClass.PLUGIN_INSTALL_MODAL} + transText="title" + i18nArgs={{ artifact: artifact }} + installType={installType} /> </div> } @@ -71,17 +71,17 @@ const PluginInstallModal: FC<PluginInstallModalProps> = ({ loading ? ( <div> <TranslationHelper - trans_class={TranslationClass.PLUGIN_INSTALL_MODAL} - trans_text="button_processing" - install_type={installType} + transClass={TranslationClass.PLUGIN_INSTALL_MODAL} + transText="button_processing" + installType={installType} /> </div> ) : ( <div> <TranslationHelper - trans_class={TranslationClass.PLUGIN_INSTALL_MODAL} - trans_text="button_idle" - install_type={installType} + transClass={TranslationClass.PLUGIN_INSTALL_MODAL} + transText="button_idle" + installType={installType} /> </div> ) @@ -89,13 +89,13 @@ const PluginInstallModal: FC<PluginInstallModalProps> = ({ > <div> <TranslationHelper - trans_class={TranslationClass.PLUGIN_INSTALL_MODAL} - trans_text="desc" - i18n_args={{ + transClass={TranslationClass.PLUGIN_INSTALL_MODAL} + transText="desc" + i18nArgs={{ artifact: artifact, version: version, }} - install_type={installType} + installType={installType} /> </div> {loading && ( diff --git a/frontend/src/developer.tsx b/frontend/src/developer.tsx index 576dd73d..5df6de07 100644 --- a/frontend/src/developer.tsx +++ b/frontend/src/developer.tsx @@ -40,11 +40,11 @@ export async function setShowValveInternal(show: boolean) { export async function setShouldConnectToReactDevTools(enable: boolean) { DeckyPluginLoader.toaster.toast({ title: enable ? ( - <TranslationHelper trans_class={TranslationClass.DEVELOPER} trans_text={'enabling'} /> + <TranslationHelper transClass={TranslationClass.DEVELOPER} transText={'enabling'} /> ) : ( - <TranslationHelper trans_class={TranslationClass.DEVELOPER} trans_text={'disabling'} /> + <TranslationHelper transClass={TranslationClass.DEVELOPER} transText={'disabling'} /> ), - body: <TranslationHelper trans_class={TranslationClass.DEVELOPER} trans_text={'5secreload'} />, + body: <TranslationHelper transClass={TranslationClass.DEVELOPER} transText={'5secreload'} />, icon: <FaReact />, }); await sleep(5000); diff --git a/frontend/src/errorboundary-hook.tsx b/frontend/src/errorboundary-hook.tsx new file mode 100644 index 00000000..175b3ff6 --- /dev/null +++ b/frontend/src/errorboundary-hook.tsx @@ -0,0 +1,69 @@ +import { Patch, callOriginal, findModuleExport, replacePatch } from '@decky/ui'; + +import DeckyErrorBoundary from './components/DeckyErrorBoundary'; +import Logger from './logger'; + +declare global { + interface Window { + __ERRORBOUNDARY_HOOK_INSTANCE: any; + } +} + +class ErrorBoundaryHook extends Logger { + private errorBoundaryPatch?: Patch; + + constructor() { + super('ErrorBoundaryHook'); + + this.log('Initialized'); + window.__ERRORBOUNDARY_HOOK_INSTANCE?.deinit?.(); + window.__ERRORBOUNDARY_HOOK_INSTANCE = this; + } + + init() { + // valve writes only the sanest of code + const exp = /^\(\)=>\(.\|\|.\(new .\),.\)$/; + const initErrorReportingStore = findModuleExport( + (e) => typeof e == 'function' && e?.toString && exp.test(e.toString()), + ); + + if (!initErrorReportingStore) { + this.error('could not find initErrorReportingStore! error boundary hook disabled!'); + return; + } + // will replace the existing one for us seemingly? doesnt matter anyway lol + const errorReportingStore = initErrorReportingStore(); + + // NUH UH. + Object.defineProperty(Object.getPrototypeOf(errorReportingStore), 'reporting_enabled', { + get: () => false, + }); + errorReportingStore.m_bEnabled = false; + + // @ts-ignore + // window.errorStore = errorReportingStore; + + const ValveErrorBoundary = findModuleExport( + (e) => e.InstallErrorReportingStore && e?.prototype?.Reset && e?.prototype?.componentDidCatch, + ); + if (!ValveErrorBoundary) { + this.error('could not find ValveErrorBoundary'); + return; + } + + this.errorBoundaryPatch = replacePatch(ValveErrorBoundary.prototype, 'render', function (this: any) { + if (this.state.error) { + return ( + <DeckyErrorBoundary error={this.state.error} errorKey={this.state.errorKey} reset={() => this.Reset()} /> + ); + } + return callOriginal; + }); + } + + deinit() { + this.errorBoundaryPatch?.unpatch(); + } +} + +export default ErrorBoundaryHook; diff --git a/frontend/src/plugin-loader.tsx b/frontend/src/plugin-loader.tsx index df3a9609..9726b3f2 100644 --- a/frontend/src/plugin-loader.tsx +++ b/frontend/src/plugin-loader.tsx @@ -21,6 +21,7 @@ import PluginUninstallModal from './components/modals/PluginUninstallModal'; import NotificationBadge from './components/NotificationBadge'; import PluginView from './components/PluginView'; import WithSuspense from './components/WithSuspense'; +import ErrorBoundaryHook from './errorboundary-hook'; import { FrozenPluginService } from './frozen-plugins-service'; import { HiddenPluginsService } from './hidden-plugins-service'; import Logger from './logger'; @@ -61,6 +62,7 @@ const callPluginMethod = DeckyBackend.callable<[pluginName: string, method: stri class PluginLoader extends Logger { private plugins: Plugin[] = []; + private errorBoundaryHook: ErrorBoundaryHook = new ErrorBoundaryHook(); private tabsHook: TabsHook = new TabsHook(); private routerHook: RouterHook = new RouterHook(); public toaster: Toaster = new Toaster(); @@ -79,6 +81,8 @@ class PluginLoader extends Logger { constructor() { super(PluginLoader.name); + this.errorBoundaryHook.init(); + DeckyBackend.addEventListener('loader/notify_updates', this.notifyUpdates.bind(this)); DeckyBackend.addEventListener('loader/import_plugin', this.importPlugin.bind(this)); DeckyBackend.addEventListener('loader/unload_plugin', this.unloadPlugin.bind(this)); @@ -185,12 +189,12 @@ class PluginLoader extends Logger { this.deckyState.setHasLoaderUpdate(true); if (this.notificationService.shouldNotify('deckyUpdates')) { this.toaster.toast({ - title: <TranslationHelper trans_class={TranslationClass.PLUGIN_LOADER} trans_text="decky_title" />, + title: <TranslationHelper transClass={TranslationClass.PLUGIN_LOADER} transText="decky_title" />, body: ( <TranslationHelper - trans_class={TranslationClass.PLUGIN_LOADER} - trans_text="decky_update_available" - i18n_args={{ tag_name: versionInfo?.remote?.tag_name }} + transClass={TranslationClass.PLUGIN_LOADER} + transText="decky_update_available" + i18nArgs={{ tag_name: versionInfo?.remote?.tag_name }} /> ), onClick: () => Router.Navigate('/decky/settings'), @@ -213,12 +217,12 @@ class PluginLoader extends Logger { const updates = await this.checkPluginUpdates(); if (updates?.size > 0 && this.notificationService.shouldNotify('pluginUpdates')) { this.toaster.toast({ - title: <TranslationHelper trans_class={TranslationClass.PLUGIN_LOADER} trans_text="decky_title" />, + title: <TranslationHelper transClass={TranslationClass.PLUGIN_LOADER} transText="decky_title" />, body: ( <TranslationHelper - trans_class={TranslationClass.PLUGIN_LOADER} - trans_text="plugin_update" - i18n_args={{ count: updates.size }} + transClass={TranslationClass.PLUGIN_LOADER} + transText="plugin_update" + i18nArgs={{ count: updates.size }} /> ), onClick: () => Router.Navigate('/decky/settings/plugins'), @@ -294,6 +298,10 @@ class PluginLoader extends Logger { this.routerHook.removeRoute('/decky/settings'); deinitSteamFixes(); deinitFilepickerPatches(); + this.routerHook.deinit(); + this.tabsHook.deinit(); + this.toaster.deinit(); + this.errorBoundaryHook.deinit(); } public unloadPlugin(name: string) { @@ -365,7 +373,9 @@ class PluginLoader extends Logger { }, }); if (res.ok) { - let plugin_export: (serverAPI: any) => Plugin = await eval(await res.text()); + let plugin_export: (serverAPI: any) => Plugin = await eval( + (await res.text()) + `\n//# sourceURL=decky://decky/legacy_plugin/${encodeURIComponent(name)}/index.js`, + ); let plugin = plugin_export(this.createLegacyPluginAPI(name)); this.plugins.push({ ...plugin, @@ -384,7 +394,7 @@ class PluginLoader extends Logger { <PanelSection> <PanelSectionRow> <div className={quickAccessMenuClasses.FriendsTitle} style={{ display: 'flex', justifyContent: 'center' }}> - <TranslationHelper trans_class={TranslationClass.PLUGIN_LOADER} trans_text="error" /> + <TranslationHelper transClass={TranslationClass.PLUGIN_LOADER} transText="error" /> </div> </PanelSectionRow> <PanelSectionRow> @@ -395,9 +405,9 @@ class PluginLoader extends Logger { <PanelSectionRow> <div className={quickAccessMenuClasses.Text}> <TranslationHelper - trans_class={TranslationClass.PLUGIN_LOADER} - trans_text="plugin_error_uninstall" - i18n_args={{ name: name }} + transClass={TranslationClass.PLUGIN_LOADER} + transText="plugin_error_uninstall" + i18nArgs={{ name: name }} /> </div> </PanelSectionRow> @@ -412,9 +422,9 @@ class PluginLoader extends Logger { this.toaster.toast({ title: ( <TranslationHelper - trans_class={TranslationClass.PLUGIN_LOADER} - trans_text="plugin_load_error.toast" - i18n_args={{ name: name }} + transClass={TranslationClass.PLUGIN_LOADER} + transText="plugin_load_error.toast" + i18nArgs={{ name: name }} /> ), body: '' + e, diff --git a/frontend/src/steamfixes/index.ts b/frontend/src/steamfixes/index.ts index 45f07b2a..e3f2b284 100644 --- a/frontend/src/steamfixes/index.ts +++ b/frontend/src/steamfixes/index.ts @@ -1,5 +1,5 @@ // import reloadFix from './reload'; -// import restartFix from './restart'; +import restartFix from './restart'; let fixes: Function[] = []; export function deinitSteamFixes() { @@ -8,5 +8,5 @@ export function deinitSteamFixes() { export async function initSteamFixes() { // fixes.push(await reloadFix()); - // fixes.push(await restartFix()); + fixes.push(await restartFix()); } diff --git a/frontend/src/steamfixes/reload.ts b/frontend/src/steamfixes/reload.ts deleted file mode 100644 index a986cc17..00000000 --- a/frontend/src/steamfixes/reload.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { getFocusNavController, sleep } from '@decky/ui'; - -import Logger from '../logger'; - -const logger = new Logger('ReloadSteamFix'); - -declare global { - var GamepadNavTree: any; -} - -export default async function reloadFix() { - // Hack to unbreak the ui when reloading it - await sleep(4000); - if (getFocusNavController()?.m_rgAllContexts?.length == 0) { - SteamClient.URL.ExecuteSteamURL('steam://open/settings'); - logger.log('Applied UI reload fix.'); - } - - // This steamfix does not need to deinit. - return () => {}; -} diff --git a/frontend/src/toaster.tsx b/frontend/src/toaster.tsx index c6b15347..7f08cb01 100644 --- a/frontend/src/toaster.tsx +++ b/frontend/src/toaster.tsx @@ -1,7 +1,7 @@ +import type { ToastData } from '@decky/api'; import { Export, Patch, - ToastData, afterPatch, findClass, findInReactTree, @@ -124,12 +124,12 @@ class Toaster extends Logger { this.node.alternate.type = this.node.type; } }; - const oRender = this.rNode.stateNode.__proto__.render; - let int: NodeJS.Timer | undefined; + const oRender = Object.getPrototypeOf(this.rNode.stateNode).render; + let int: NodeJS.Timeout | undefined; this.rNode.stateNode.render = (...args: any[]) => { const ret = oRender.call(this.rNode.stateNode, ...args); if (ret && !this?.node?.return?.return) { - clearInterval(int); + int && clearInterval(int); int = setInterval(() => { const n = findToasterRoot(tree, 0); if (n?.return) { diff --git a/frontend/src/updater.ts b/frontend/src/updater.ts index c8e96521..a8853b18 100644 --- a/frontend/src/updater.ts +++ b/frontend/src/updater.ts @@ -25,9 +25,6 @@ export interface VerInfo { export const doUpdate = DeckyBackend.callable('updater/do_update'); export const doRestart = DeckyBackend.callable('updater/do_restart'); +export const doShutdown = DeckyBackend.callable('updater/do_shutdown'); export const getVersionInfo = DeckyBackend.callable<[], VerInfo>('updater/get_version_info'); export const checkForUpdates = DeckyBackend.callable<[], VerInfo>('updater/check_for_updates'); - -DeckyBackend.addEventListener('updater/finish_download', async () => { - await doRestart(); -}); diff --git a/frontend/src/utils/TranslationHelper.tsx b/frontend/src/utils/TranslationHelper.tsx index 99584d6b..61bd24bf 100644 --- a/frontend/src/utils/TranslationHelper.tsx +++ b/frontend/src/utils/TranslationHelper.tsx @@ -11,47 +11,42 @@ export enum TranslationClass { } interface TranslationHelperProps { - trans_class: TranslationClass; - trans_text: string; - i18n_args?: {}; - install_type?: number; + transClass: TranslationClass; + transText: string; + i18nArgs?: {}; + installType?: number; } const logger = new Logger('TranslationHelper'); -const TranslationHelper: FC<TranslationHelperProps> = ({ - trans_class, - trans_text, - i18n_args = null, - install_type = 0, -}) => { +const TranslationHelper: FC<TranslationHelperProps> = ({ transClass, transText, i18nArgs = null, installType = 0 }) => { return ( <Translation> {(t, {}) => { - switch (trans_class) { + switch (transClass) { case TranslationClass.PLUGIN_LOADER: - return i18n_args - ? t(TranslationClass.PLUGIN_LOADER + '.' + trans_text, i18n_args) - : t(TranslationClass.PLUGIN_LOADER + '.' + trans_text); + return i18nArgs + ? t(TranslationClass.PLUGIN_LOADER + '.' + transText, i18nArgs) + : t(TranslationClass.PLUGIN_LOADER + '.' + transText); case TranslationClass.PLUGIN_INSTALL_MODAL: - switch (install_type) { + switch (installType) { case InstallType.INSTALL: - return i18n_args - ? t(TranslationClass.PLUGIN_INSTALL_MODAL + '.install.' + trans_text, i18n_args) - : t(TranslationClass.PLUGIN_INSTALL_MODAL + '.install.' + trans_text); + return i18nArgs + ? t(TranslationClass.PLUGIN_INSTALL_MODAL + '.install.' + transText, i18nArgs) + : t(TranslationClass.PLUGIN_INSTALL_MODAL + '.install.' + transText); case InstallType.REINSTALL: - return i18n_args - ? t(TranslationClass.PLUGIN_INSTALL_MODAL + '.reinstall.' + trans_text, i18n_args) - : t(TranslationClass.PLUGIN_INSTALL_MODAL + '.reinstall.' + trans_text); + return i18nArgs + ? t(TranslationClass.PLUGIN_INSTALL_MODAL + '.reinstall.' + transText, i18nArgs) + : t(TranslationClass.PLUGIN_INSTALL_MODAL + '.reinstall.' + transText); case InstallType.UPDATE: - return i18n_args - ? t(TranslationClass.PLUGIN_INSTALL_MODAL + '.update.' + trans_text, i18n_args) - : t(TranslationClass.PLUGIN_INSTALL_MODAL + '.update.' + trans_text); + return i18nArgs + ? t(TranslationClass.PLUGIN_INSTALL_MODAL + '.update.' + transText, i18nArgs) + : t(TranslationClass.PLUGIN_INSTALL_MODAL + '.update.' + transText); } case TranslationClass.DEVELOPER: - return i18n_args - ? t(TranslationClass.DEVELOPER + '.' + trans_text, i18n_args) - : t(TranslationClass.DEVELOPER + '.' + trans_text); + return i18nArgs + ? t(TranslationClass.DEVELOPER + '.' + transText, i18nArgs) + : t(TranslationClass.DEVELOPER + '.' + transText); default: logger.error('We should never fall in the default case!'); return ''; |
