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/DeckyState.tsx | 18 +++++++++++++++++-
 1 file changed, 17 insertions(+), 1 deletion(-)

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

diff --git a/frontend/src/components/DeckyState.tsx b/frontend/src/components/DeckyState.tsx
index d2ac63ae..d1b558c1 100644
--- a/frontend/src/components/DeckyState.tsx
+++ b/frontend/src/components/DeckyState.tsx
@@ -1,12 +1,14 @@
 import { FC, ReactNode, createContext, useContext, useEffect, useState } from 'react';
 
 import { DEFAULT_NOTIFICATION_SETTINGS, NotificationSettings } from '../notification-service';
-import { Plugin } from '../plugin';
+import { DisabledPlugin, Plugin } from '../plugin';
 import { PluginUpdateMapping } from '../store';
 import { VerInfo } from '../updater';
 
 interface PublicDeckyState {
   plugins: Plugin[];
+  disabledPlugins: DisabledPlugin[];
+  installedPlugins: (Plugin | DisabledPlugin)[];
   pluginOrder: string[];
   frozenPlugins: string[];
   hiddenPlugins: string[];
@@ -26,6 +28,8 @@ export interface UserInfo {
 
 export class DeckyState {
   private _plugins: Plugin[] = [];
+  private _disabledPlugins: DisabledPlugin[] = [];
+  private _installedPlugins: (Plugin | DisabledPlugin)[] = [];
   private _pluginOrder: string[] = [];
   private _frozenPlugins: string[] = [];
   private _hiddenPlugins: string[] = [];
@@ -42,6 +46,8 @@ export class DeckyState {
   publicState(): PublicDeckyState {
     return {
       plugins: this._plugins,
+      disabledPlugins: this._disabledPlugins,
+      installedPlugins: this._installedPlugins,
       pluginOrder: this._pluginOrder,
       frozenPlugins: this._frozenPlugins,
       hiddenPlugins: this._hiddenPlugins,
@@ -62,6 +68,13 @@ export class DeckyState {
 
   setPlugins(plugins: Plugin[]) {
     this._plugins = plugins;
+    this._installedPlugins = [...plugins, ...this._disabledPlugins];
+    this.notifyUpdate();
+  }
+
+  setDisabledPlugins(disabledPlugins: DisabledPlugin[]) {
+    this._disabledPlugins = disabledPlugins;
+    this._installedPlugins = [...this._plugins, ...disabledPlugins];
     this.notifyUpdate();
   }
 
@@ -125,6 +138,7 @@ interface DeckyStateContext extends PublicDeckyState {
   setIsLoaderUpdating(hasUpdate: boolean): void;
   setActivePlugin(name: string): void;
   setPluginOrder(pluginOrder: string[]): void;
+  setDisabledPlugins(disabled: DisabledPlugin[]): void;
   closeActivePlugin(): void;
 }
 
@@ -163,6 +177,7 @@ export const DeckyStateContextProvider: FC<Props> = ({ children, deckyState }) =
   const setActivePlugin = deckyState.setActivePlugin.bind(deckyState);
   const closeActivePlugin = deckyState.closeActivePlugin.bind(deckyState);
   const setPluginOrder = deckyState.setPluginOrder.bind(deckyState);
+  const setDisabledPlugins = deckyState.setDisabledPlugins.bind(deckyState);
 
   return (
     <DeckyStateContext.Provider
@@ -173,6 +188,7 @@ export const DeckyStateContextProvider: FC<Props> = ({ children, deckyState }) =
         setActivePlugin,
         closeActivePlugin,
         setPluginOrder,
+        setDisabledPlugins,
       }}
     >
       {children}
-- 
cgit v1.2.3

