From 414493eed2bea2ceb13c9add216d6f9e7f9c5903 Mon Sep 17 00:00:00 2001
From: marios <marios8543@gmail.com>
Date: Sun, 29 Jun 2025 21:36:48 +0300
Subject: Add Decky branch switcher and updater to error boundary (#775)

* Add Decky branch switcher and updater to error boundary

* Linting fixup

* Update DeckyErrorBoundary.tsx

Allow for plugins to trigger new behavior.

* Lint fixup
---
 frontend/src/components/DeckyErrorBoundary.tsx | 82 ++++++++++++++++++++++++++
 1 file changed, 82 insertions(+)

(limited to 'frontend/src/components/DeckyErrorBoundary.tsx')

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'}
-- 
cgit v1.2.3

