mirror of
https://github.com/SteamDeckHomebrew/decky-loader.git
synced 2026-06-17 08:47:49 +00:00
Custom error handler and some misc fixes
This commit is contained in:
@@ -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;
|
||||
@@ -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> = ({
|
||||
|
||||
@@ -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 }) => {
|
||||
|
||||
@@ -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 }) => {
|
||||
|
||||
@@ -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(
|
||||
() => {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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),
|
||||
);
|
||||
|
||||
@@ -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 && (
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
@@ -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,
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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 () => {};
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
|
||||
@@ -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 '';
|
||||
|
||||
Reference in New Issue
Block a user