From 9f586a1b97cf9069fbfbeee17e3909baf9e95f66 Mon Sep 17 00:00:00 2001
From: jbofill <74568881+jessebofill@users.noreply.github.com>
Date: Tue, 30 Dec 2025 12:29:08 -0700
Subject: Feat: Disable plugins (#850)

* implement base frontend changes necessary for plugin disabling

* implement frontend diisable functions/ modal

* plugin disable boilerplate / untested

* Feat disable plugins (#810)

* implement base frontend changes necessary for plugin disabling

* implement frontend diisable functions/ modal

---------

Co-authored-by: Jesse Bofill <jesse_bofill@yahoo.com>

* fix mistakes

* add frontend

* working plugin disable, not tested extensively

* fix uninstalled hidden plugins remaining in list

* hide plugin irrelevant plugin setting menu option when disabled

* fix hidden plugin issues

* reset disabled plugin on uninstall

* fix plugin load on reenable

* move disable settings uninstall cleanup

* add engilsh tranlsations for enable/ disable elements

* fix bug where wrong loadType can get passed to importPlugin

* show correct number of hidden plugins if plugin is both hidden and disabled

* fix: get fresh list of plugin updates when changed in settings plugin list

* fix: fix invalid semver plugin version from preventing latest updates

* retain x position when changing focus in list items  that have multiple horizontal focusables

* correction to pluging version checking validation

* make sure disabled plugins get checked for updates

* show number of disabled plugins at bottom of plugin view

* add notice to update modals that disabled plugins will be enabled upon installation

* run formatter

* Update backend/decky_loader/locales/en-US.json

Co-authored-by: EMERALD <hudson.samuels@gmail.com>

* chore: correct filename typo

* chore: change disabled icon

* chore: revert accidental defsettings changes

* format

* add timeout to frontend importPlugin

if a request hangs this prevent it from blocking other plugin loads.
backend diaptch_plugin which calls this for individual plugin load (as opposed to batch) is set to 15s.
other callers of importPlugin are not using timeout, same as before.

* fix plugin update checking loop

---------

Co-authored-by: marios <marios8543@gmail.com>
Co-authored-by: EMERALD <hudson.samuels@gmail.com>
---
 frontend/src/components/PluginView.tsx | 46 ++++++++++++++++++++++++++--------
 1 file changed, 36 insertions(+), 10 deletions(-)

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

diff --git a/frontend/src/components/PluginView.tsx b/frontend/src/components/PluginView.tsx
index 1d39972e..ffaa176a 100644
--- a/frontend/src/components/PluginView.tsx
+++ b/frontend/src/components/PluginView.tsx
@@ -1,7 +1,7 @@
 import { ButtonItem, ErrorBoundary, Focusable, PanelSection, PanelSectionRow } from '@decky/ui';
 import { FC, useMemo } from 'react';
 import { useTranslation } from 'react-i18next';
-import { FaEyeSlash } from 'react-icons/fa';
+import { FaBan, FaEyeSlash } from 'react-icons/fa';
 
 import { useDeckyState } from './DeckyState';
 import NotificationBadge from './NotificationBadge';
@@ -9,8 +9,16 @@ import { useQuickAccessVisible } from './QuickAccessVisibleState';
 import TitleView from './TitleView';
 
 const PluginView: FC = () => {
-  const { plugins, hiddenPlugins, updates, activePlugin, pluginOrder, setActivePlugin, closeActivePlugin } =
-    useDeckyState();
+  const {
+    plugins,
+    disabledPlugins,
+    hiddenPlugins,
+    updates,
+    activePlugin,
+    pluginOrder,
+    setActivePlugin,
+    closeActivePlugin,
+  } = useDeckyState();
   const visible = useQuickAccessVisible();
   const { t } = useTranslation();
 
@@ -21,7 +29,9 @@ const PluginView: FC = () => {
       .sort((a, b) => pluginOrder.indexOf(a.name) - pluginOrder.indexOf(b.name))
       .filter((p) => p.content)
       .filter(({ name }) => !hiddenPlugins.includes(name));
-  }, [plugins, pluginOrder]);
+  }, [plugins, pluginOrder, hiddenPlugins]);
+
+  const numberOfHidden = hiddenPlugins.filter((name) => !!plugins.find((p) => p.name === name)).length;
 
   if (activePlugin) {
     return (
@@ -53,12 +63,28 @@ const PluginView: FC = () => {
               </ButtonItem>
             </PanelSectionRow>
           ))}
-          {hiddenPlugins.length > 0 && (
-            <div style={{ display: 'flex', alignItems: 'center', gap: '10px', fontSize: '0.8rem', marginTop: '10px' }}>
-              <FaEyeSlash />
-              <div>{t('PluginView.hidden', { count: hiddenPlugins.length })}</div>
-            </div>
-          )}
+          <div
+            style={{
+              display: 'flex',
+              flexDirection: 'column',
+              position: 'absolute',
+              justifyContent: 'center',
+              padding: '5px 0px',
+            }}
+          >
+            {numberOfHidden > 0 && (
+              <div style={{ display: 'flex', alignItems: 'center', gap: '10px', fontSize: '0.8rem' }}>
+                <FaEyeSlash />
+                <div>{t('PluginView.hidden', { count: numberOfHidden })}</div>
+              </div>
+            )}
+            {disabledPlugins.length > 0 && (
+              <div style={{ display: 'flex', alignItems: 'center', gap: '10px', fontSize: '0.8rem' }}>
+                <FaBan />
+                <div>{t('PluginView.disabled', { count: disabledPlugins.length })}</div>
+              </div>
+            )}
+          </div>
         </PanelSection>
       </div>
     </>
-- 
cgit v1.2.3

