summaryrefslogtreecommitdiff
path: root/frontend/src
diff options
context:
space:
mode:
authorAAGaming <aagaming@riseup.net>2024-05-25 19:14:54 -0400
committerAAGaming <aagaming@riseup.net>2024-05-25 19:14:54 -0400
commita84a13c76d99f1e6f4505d43108a4111749e5035 (patch)
treee2826700cd371e6590818047551028d8179389bf /frontend/src
parent96cc72f2ca25ccb312b68a29aca755bb7df660ed (diff)
downloaddecky-loader-a84a13c76d99f1e6f4505d43108a4111749e5035.tar.gz
decky-loader-a84a13c76d99f1e6f4505d43108a4111749e5035.zip
Custom error handler and some misc fixes
Diffstat (limited to 'frontend/src')
-rw-r--r--frontend/src/components/DeckyErrorBoundary.tsx201
-rw-r--r--frontend/src/components/DeckyGlobalComponentsState.tsx3
-rw-r--r--frontend/src/components/DeckyRouterState.tsx3
-rw-r--r--frontend/src/components/DeckyState.tsx3
-rw-r--r--frontend/src/components/DeckyToaster.tsx5
-rw-r--r--frontend/src/components/DeckyToasterState.tsx2
-rw-r--r--frontend/src/components/Toast.tsx3
-rw-r--r--frontend/src/components/modals/DropdownMultiselect.tsx2
-rw-r--r--frontend/src/components/modals/PluginInstallModal.tsx28
-rw-r--r--frontend/src/developer.tsx6
-rw-r--r--frontend/src/errorboundary-hook.tsx69
-rw-r--r--frontend/src/plugin-loader.tsx42
-rw-r--r--frontend/src/steamfixes/index.ts4
-rw-r--r--frontend/src/steamfixes/reload.ts21
-rw-r--r--frontend/src/toaster.tsx8
-rw-r--r--frontend/src/updater.ts5
-rw-r--r--frontend/src/utils/TranslationHelper.tsx49
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 '';