summaryrefslogtreecommitdiff
path: root/frontend
diff options
context:
space:
mode:
authorAAGaming <aagaming@riseup.net>2025-07-01 14:49:48 -0400
committerGitHub <noreply@github.com>2025-07-01 14:49:48 -0400
commit7b8a18d02d8e6b098f3399d015cf22dd04bc561c (patch)
tree7139a04320c70caaaea7a53b77e7b4ff1132ab1a /frontend
parentf327c6c792cb6beb1f32f70045e577a542a48070 (diff)
parentcbea1518ed1e88fde34e40a16a0e9c20e954a9f5 (diff)
downloaddecky-loader-7b8a18d02d8e6b098f3399d015cf22dd04bc561c.tar.gz
decky-loader-7b8a18d02d8e6b098f3399d015cf22dd04bc561c.zip
Merge branch 'main' into aa/fix-updater-reload
Diffstat (limited to 'frontend')
-rw-r--r--frontend/.prettierrc.js2
-rw-r--r--frontend/package.json2
-rw-r--r--frontend/pnpm-lock.yaml10
-rw-r--r--frontend/src/components/DeckyErrorBoundary.tsx82
-rw-r--r--frontend/src/components/Markdown.tsx9
-rw-r--r--frontend/src/components/modals/filepicker/patches/library.ts2
-rw-r--r--frontend/src/components/settings/pages/developer/index.tsx11
-rw-r--r--frontend/src/components/settings/pages/general/Updater.tsx48
-rw-r--r--frontend/src/plugin-loader.tsx7
-rw-r--r--frontend/src/router-hook.tsx10
10 files changed, 145 insertions, 38 deletions
diff --git a/frontend/.prettierrc.js b/frontend/.prettierrc.js
index 7a0331c8..df3e6cde 100644
--- a/frontend/.prettierrc.js
+++ b/frontend/.prettierrc.js
@@ -7,4 +7,4 @@ export default {
tabWidth: 2,
endOfLine: 'auto',
plugins: [importSort],
-} \ No newline at end of file
+};
diff --git a/frontend/package.json b/frontend/package.json
index 842ec869..5c16e017 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -47,7 +47,7 @@
}
},
"dependencies": {
- "@decky/ui": "^4.8.3",
+ "@decky/ui": "^4.10.2",
"compare-versions": "^6.1.1",
"filesize": "^10.1.2",
"i18next": "^23.11.5",
diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml
index 711af51f..d3685cba 100644
--- a/frontend/pnpm-lock.yaml
+++ b/frontend/pnpm-lock.yaml
@@ -9,8 +9,8 @@ importers:
.:
dependencies:
'@decky/ui':
- specifier: ^4.8.3
- version: 4.8.3
+ specifier: ^4.10.2
+ version: 4.10.2
compare-versions:
specifier: ^6.1.1
version: 6.1.1
@@ -218,8 +218,8 @@ packages:
'@decky/api@1.1.1':
resolution: {integrity: sha512-R5fkBRHBt5QIQY7Q0AlbVIhlIZ/nTzwBOoi8Rt4Go2fjFnoMKPInCJl6cPjXzimGwl2pyqKJgY6VnH6ar0XrHQ==}
- '@decky/ui@4.8.3':
- resolution: {integrity: sha512-Y1KciazgvKqMEVBGrWFCTGOqgVi5sHbcQNoCZRMbPpcI0U3j7udl6mkfe/NBa16oRDZ03ljS41SmrAgKAAt/pA==}
+ '@decky/ui@4.10.2':
+ resolution: {integrity: sha512-dfY/OEI/rhG4d3Tvx4Y3TLmBrJ+nAm2RTbkoOJ3VAglql3Lu7RY2ixeDFbs21ZWWzWh/C+9dAQKjQ4lx4d1f2g==}
'@esbuild/aix-ppc64@0.20.2':
resolution: {integrity: sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==}
@@ -2295,7 +2295,7 @@ snapshots:
'@decky/api@1.1.1': {}
- '@decky/ui@4.8.3': {}
+ '@decky/ui@4.10.2': {}
'@esbuild/aix-ppc64@0.20.2':
optional: true
diff --git a/frontend/src/components/DeckyErrorBoundary.tsx b/frontend/src/components/DeckyErrorBoundary.tsx
index 654db8a0..7a8d2b31 100644
--- a/frontend/src/components/DeckyErrorBoundary.tsx
+++ b/frontend/src/components/DeckyErrorBoundary.tsx
@@ -4,6 +4,8 @@ import { FunctionComponent, useEffect, useReducer, useState } from 'react';
import { uninstallPlugin } from '../plugin';
import { VerInfo, doRestart, doShutdown } from '../updater';
import { ValveReactErrorInfo, getLikelyErrorSourceFromValveReactError } from '../utils/errors';
+import { useSetting } from '../utils/hooks/useSetting';
+import { UpdateBranch } from './settings/pages/general/BranchSelect';
interface DeckyErrorBoundaryProps {
error: ValveReactErrorInfo;
@@ -37,6 +39,27 @@ const DeckyErrorBoundary: FunctionComponent<DeckyErrorBoundaryProps> = ({ error,
if (!shouldReportToValve) DeckyPluginLoader.errorBoundaryHook.temporarilyDisableReporting();
DeckyPluginLoader.updateVersion().then(setVersionInfo);
}, []);
+
+ const [selectedBranch, setSelectedBranch] = useSetting<UpdateBranch>('branch', UpdateBranch.Stable);
+ const [isChecking, setIsChecking] = useState<boolean>(false);
+ const [updateProgress, setUpdateProgress] = useState<number>(-1);
+ const [versionToUpdateTo, setSetVersionToUpdateTo] = useState<string>('');
+
+ useEffect(() => {
+ const a = DeckyBackend.addEventListener('updater/update_download_percentage', (percentage) => {
+ setUpdateProgress(percentage);
+ });
+
+ const b = DeckyBackend.addEventListener('updater/finish_download', () => {
+ setUpdateProgress(-2);
+ });
+
+ return () => {
+ DeckyBackend.removeEventListener('updater/update_download_percentage', a);
+ DeckyBackend.removeEventListener('updater/finish_download', b);
+ };
+ }, []);
+
return (
<>
<style>
@@ -149,6 +172,65 @@ const DeckyErrorBoundary: FunctionComponent<DeckyErrorBoundaryProps> = ({ error,
</button>
</div>
)}
+ {
+ <div style={{ display: 'block', marginBottom: '5px' }}>
+ {updateProgress > -1
+ ? 'Update in progress... ' + updateProgress + '%'
+ : updateProgress == -2
+ ? 'Update complete. Restarting...'
+ : 'Changing your Decky Loader branch and/or \n checking for updates might help!\n'}
+ {updateProgress == -1 && (
+ <div style={{ height: '30px' }}>
+ <select
+ style={{ height: '100%' }}
+ onChange={async (e) => {
+ const branch = parseInt(e.target.value);
+ setSelectedBranch(branch);
+ setSetVersionToUpdateTo('');
+ }}
+ >
+ <option value="0" selected={selectedBranch == UpdateBranch.Stable}>
+ Stable
+ </option>
+ <option value="1" selected={selectedBranch == UpdateBranch.Prerelease}>
+ Pre-Release
+ </option>
+ <option value="2" selected={selectedBranch == UpdateBranch.Testing}>
+ Testing
+ </option>
+ </select>
+ <button
+ style={{ height: '100%' }}
+ disabled={updateProgress != -1 || isChecking}
+ onClick={async () => {
+ if (versionToUpdateTo == '') {
+ setIsChecking(true);
+ const versionInfo = (await DeckyBackend.callable(
+ 'updater/check_for_updates',
+ )()) as unknown as VerInfo;
+ setIsChecking(false);
+ if (versionInfo?.remote && versionInfo?.remote?.tag_name != versionInfo?.current) {
+ setSetVersionToUpdateTo(versionInfo.remote.tag_name);
+ } else {
+ setSetVersionToUpdateTo('');
+ }
+ } else {
+ DeckyBackend.callable('updater/do_update')();
+ setUpdateProgress(0);
+ }
+ }}
+ >
+ {' '}
+ {isChecking
+ ? 'Checking for updates...'
+ : versionToUpdateTo != ''
+ ? 'Update to ' + versionToUpdateTo
+ : 'Check for updates'}
+ </button>
+ </div>
+ )}
+ </div>
+ }
{wasCausedByPlugin && (
<div style={{ display: 'block', marginBottom: '5px' }}>
{'\n'}
diff --git a/frontend/src/components/Markdown.tsx b/frontend/src/components/Markdown.tsx
index cf6657aa..9842750d 100644
--- a/frontend/src/components/Markdown.tsx
+++ b/frontend/src/components/Markdown.tsx
@@ -1,4 +1,4 @@
-import { Focusable, Navigation } from '@decky/ui';
+import { Focusable, Navigation, findClass, findClassByName } from '@decky/ui';
import { FunctionComponent, useRef } from 'react';
import ReactMarkdown, { Options as ReactMarkdownOptions } from 'react-markdown';
import remarkGfm from 'remark-gfm';
@@ -8,6 +8,9 @@ interface MarkdownProps extends ReactMarkdownOptions {
}
const Markdown: FunctionComponent<MarkdownProps> = (props) => {
+ const eventDetailsBodyClassName = findClassByName('EventDetailsBody') || undefined;
+ const eventLinkClassName = findClass('43088', 'Link');
+
return (
<Focusable>
<ReactMarkdown
@@ -25,8 +28,10 @@ const Markdown: FunctionComponent<MarkdownProps> = (props) => {
Navigation.NavigateToExternalWeb(aRef.current!.href);
}}
style={{ display: 'inline' }}
+ focusClassName="steam-focus"
+ className={eventDetailsBodyClassName}
>
- <a ref={aRef} {...nodeProps.node.properties}>
+ <a ref={aRef} {...nodeProps.node.properties} className={eventLinkClassName}>
{nodeProps.children}
</a>
</Focusable>
diff --git a/frontend/src/components/modals/filepicker/patches/library.ts b/frontend/src/components/modals/filepicker/patches/library.ts
index 3b7fa679..b0930a5e 100644
--- a/frontend/src/components/modals/filepicker/patches/library.ts
+++ b/frontend/src/components/modals/filepicker/patches/library.ts
@@ -47,7 +47,7 @@ export default async function libraryPatch() {
}
const unlisten = History.listen(() => {
- if (window.SteamClient.Apps.PromptToChangeShortcut !== patch.patchedFunction) {
+ if ((window.SteamClient.Apps as any).PromptToChangeShortcut !== patch.patchedFunction) {
rePatch();
}
});
diff --git a/frontend/src/components/settings/pages/developer/index.tsx b/frontend/src/components/settings/pages/developer/index.tsx
index 099f2610..05989806 100644
--- a/frontend/src/components/settings/pages/developer/index.tsx
+++ b/frontend/src/components/settings/pages/developer/index.tsx
@@ -72,7 +72,16 @@ export default function DeveloperSettings() {
}
icon={<FaLink style={{ display: 'block' }} />}
>
- <DialogButton disabled={pluginURL.length == 0} onClick={() => installFromURL(pluginURL)}>
+ <DialogButton
+ disabled={pluginURL.length == 0}
+ onClick={() => {
+ if (/^https?:\/\//.test(pluginURL)) {
+ installFromURL(pluginURL);
+ } else {
+ installFromURL('https://' + pluginURL);
+ }
+ }}
+ >
{t('SettingsDeveloperIndex.third_party_plugins.button_install')}
</DialogButton>
</Field>
diff --git a/frontend/src/components/settings/pages/general/Updater.tsx b/frontend/src/components/settings/pages/general/Updater.tsx
index 59756a57..3cd58ab6 100644
--- a/frontend/src/components/settings/pages/general/Updater.tsx
+++ b/frontend/src/components/settings/pages/general/Updater.tsx
@@ -1,14 +1,4 @@
-import {
- Carousel,
- DialogButton,
- Field,
- FocusRing,
- Focusable,
- ProgressBarWithInfo,
- Spinner,
- findSP,
- showModal,
-} from '@decky/ui';
+import { Carousel, DialogButton, Field, Focusable, ProgressBarWithInfo, Spinner, findSP, showModal } from '@decky/ui';
import { Suspense, lazy, useCallback, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { FaExclamation } from 'react-icons/fa';
@@ -23,9 +13,31 @@ const MarkdownRenderer = lazy(() => import('../../../Markdown'));
function PatchNotesModal({ versionInfo, closeModal }: { versionInfo: VerInfo | null; closeModal?: () => {} }) {
const SP = findSP();
const { t } = useTranslation();
+
return (
- <Focusable onCancelButton={closeModal}>
- <FocusRing>
+ <>
+ <style>
+ {`
+.steam-focus {
+ outline-offset: 3px;
+ outline: 2px solid rgba(255, 255, 255, 0.6);
+ animation: pulseOutline 1.2s infinite ease-in-out;
+}
+
+@keyframes pulseOutline {
+ 0% {
+ outline: 2px solid rgba(255, 255, 255, 0.6);
+ }
+ 50% {
+ outline: 2px solid rgba(255, 255, 255, 1);
+ }
+ 100% {
+ outline: 2px solid rgba(255, 255, 255, 0.6);
+ }
+}`}
+ </style>
+
+ <Focusable onCancelButton={closeModal}>
<Carousel
fnItemRenderer={(id: number) => (
<Focusable
@@ -35,7 +47,9 @@ function PatchNotesModal({ versionInfo, closeModal }: { versionInfo: VerInfo | n
overflowY: 'scroll',
display: 'flex',
justifyContent: 'center',
- margin: '40px',
+ margin: '30px',
+ padding: '0 15px',
+ backgroundColor: 'rgba(37, 40, 46, 0.5)',
}}
>
<div>
@@ -57,11 +71,11 @@ function PatchNotesModal({ versionInfo, closeModal }: { versionInfo: VerInfo | n
nItemMarginX={0}
initialColumn={0}
autoFocus={true}
- fnGetColumnWidth={() => SP.innerWidth}
+ fnGetColumnWidth={() => SP.innerWidth - SP.innerWidth * (10 / 100)}
name={t('Updater.decky_updates') as string}
/>
- </FocusRing>
- </Focusable>
+ </Focusable>
+ </>
);
}
diff --git a/frontend/src/plugin-loader.tsx b/frontend/src/plugin-loader.tsx
index 88e85a48..df0a6956 100644
--- a/frontend/src/plugin-loader.tsx
+++ b/frontend/src/plugin-loader.tsx
@@ -1,5 +1,6 @@
import { ToastNotification } from '@decky/api';
import {
+ EUIMode,
ModalRoot,
Navigation,
PanelSection,
@@ -30,7 +31,7 @@ import { HiddenPluginsService } from './hidden-plugins-service';
import Logger from './logger';
import { NotificationService } from './notification-service';
import { InstallType, Plugin, PluginLoadType } from './plugin';
-import RouterHook, { UIMode } from './router-hook';
+import RouterHook from './router-hook';
import { deinitSteamFixes, initSteamFixes } from './steamfixes';
import { checkForPluginUpdates } from './store';
import TabsHook from './tabs-hook';
@@ -205,12 +206,12 @@ class PluginLoader extends Logger {
let registration: any;
const uiMode = await new Promise(
(r) =>
- (registration = SteamClient.UI.RegisterForUIModeChanged((mode: UIMode) => {
+ (registration = SteamClient.UI.RegisterForUIModeChanged((mode: EUIMode) => {
r(mode);
registration.unregister();
})),
);
- if (uiMode == UIMode.BigPicture) {
+ if (uiMode == EUIMode.GamePad) {
// wait for SP window to exist before loading plugins
while (!findSP()) {
await sleep(100);
diff --git a/frontend/src/router-hook.tsx b/frontend/src/router-hook.tsx
index 9612793a..b3355d76 100644
--- a/frontend/src/router-hook.tsx
+++ b/frontend/src/router-hook.tsx
@@ -1,4 +1,5 @@
import {
+ EUIMode,
ErrorBoundary,
Patch,
afterPatch,
@@ -31,11 +32,6 @@ declare global {
}
}
-export enum UIMode {
- BigPicture = 4,
- Desktop = 7,
-}
-
const isPatched = Symbol('is patched');
class RouterHook extends Logger {
@@ -76,13 +72,13 @@ class RouterHook extends Logger {
this.error('Failed to find router stack module');
}
- this.modeChangeRegistration = SteamClient.UI.RegisterForUIModeChanged((mode: UIMode) => {
+ this.modeChangeRegistration = SteamClient.UI.RegisterForUIModeChanged((mode: EUIMode) => {
this.debug(`UI mode changed to ${mode}`);
if (this.patchedModes.has(mode)) return;
this.patchedModes.add(mode);
this.debug(`Patching router for UI mode ${mode}`);
switch (mode) {
- case UIMode.BigPicture:
+ case EUIMode.GamePad:
this.debug('Patching gamepad router');
this.patchGamepadRouter();
break;