summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormarios <marios8543@gmail.com>2025-10-06 23:54:49 +0300
committerGitHub <noreply@github.com>2025-10-06 23:54:49 +0300
commit6fba20239b305e92a79b360ee87850f8f2d9b62e (patch)
tree12dba98c397ba7c4adeaa2d7f4332092f1a5577b
parentc3c0e87c6fc94cfd753ea45d623849e1b3633316 (diff)
downloaddecky-loader-6fba20239b305e92a79b360ee87850f8f2d9b62e.tar.gz
decky-loader-6fba20239b305e92a79b360ee87850f8f2d9b62e.zip
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>
-rw-r--r--frontend/src/components/DeckyState.tsx15
-rw-r--r--frontend/src/components/modals/PluginDisablelModal.tsx46
-rw-r--r--frontend/src/components/settings/pages/plugin_list/index.tsx31
-rw-r--r--frontend/src/components/store/PluginCard.tsx4
-rw-r--r--frontend/src/components/store/Store.tsx2
-rw-r--r--frontend/src/plugin-loader.tsx18
-rw-r--r--frontend/src/plugin.ts4
7 files changed, 106 insertions, 14 deletions
diff --git a/frontend/src/components/DeckyState.tsx b/frontend/src/components/DeckyState.tsx
index d2ac63ae..b2f7ac33 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[];
+ disabled: 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,
+ disabled: this._disabledPlugins,
+ installedPlugins: this._disabledPlugins,
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();
}
diff --git a/frontend/src/components/modals/PluginDisablelModal.tsx b/frontend/src/components/modals/PluginDisablelModal.tsx
new file mode 100644
index 00000000..89cda293
--- /dev/null
+++ b/frontend/src/components/modals/PluginDisablelModal.tsx
@@ -0,0 +1,46 @@
+import { ConfirmModal, Spinner } from '@decky/ui';
+import { FC, useState } from 'react';
+
+import { disablePlugin, uninstallPlugin } from '../../plugin';
+
+interface PluginUninstallModalProps {
+ name: string;
+ title: string;
+ buttonText: string;
+ description: string;
+ closeModal?(): void;
+}
+
+const PluginDisableModal: FC<PluginUninstallModalProps> = ({ name, title, buttonText, description, closeModal }) => {
+ const [disabling, setDisabling] = useState<boolean>(false);
+ return (
+ <ConfirmModal
+ closeModal={closeModal}
+ onOK={async () => {
+ setDisabling(true);
+ await disablePlugin(name);
+
+ //not sure about this yet
+
+ // uninstalling a plugin resets the hidden setting for it server-side
+ // we invalidate here so if you re-install it, you won't have an out-of-date hidden filter
+ await DeckyPluginLoader.frozenPluginsService.invalidate();
+ await DeckyPluginLoader.hiddenPluginsService.invalidate();
+ closeModal?.();
+ }}
+ bOKDisabled={disabling}
+ bCancelDisabled={disabling}
+ strTitle={
+ <div style={{ display: 'flex', flexDirection: 'row', alignItems: 'center', width: '100%' }}>
+ {title}
+ {disabling && <Spinner width="24px" height="24px" style={{ marginLeft: 'auto' }} />}
+ </div>
+ }
+ strOKButtonText={buttonText}
+ >
+ {description}
+ </ConfirmModal>
+ );
+};
+
+export default PluginDisableModal;
diff --git a/frontend/src/components/settings/pages/plugin_list/index.tsx b/frontend/src/components/settings/pages/plugin_list/index.tsx
index 9a7cb076..f13cbe2b 100644
--- a/frontend/src/components/settings/pages/plugin_list/index.tsx
+++ b/frontend/src/components/settings/pages/plugin_list/index.tsx
@@ -35,6 +35,7 @@ async function reinstallPlugin(pluginName: string, currentVersion?: string) {
type PluginTableData = PluginData & {
name: string;
+ disabled: boolean;
frozen: boolean;
onFreeze(): void;
onUnfreeze(): void;
@@ -54,7 +55,7 @@ function PluginInteractables(props: { entry: ReorderableEntry<PluginTableData> }
return null;
}
- const { name, update, version, onHide, onShow, hidden, onFreeze, onUnfreeze, frozen, isDeveloper } = props.entry.data;
+ const { name, update, version, onHide, onShow, hidden, onFreeze, onUnfreeze, frozen, isDeveloper, disabled } = props.entry.data;
const showCtxMenu = (e: MouseEvent | GamepadEvent) => {
showContextMenu(
@@ -82,6 +83,22 @@ function PluginInteractables(props: { entry: ReorderableEntry<PluginTableData> }
>
{t('PluginListIndex.uninstall')}
</MenuItem>
+ {disabled ? <MenuItem
+ onSelected={() =>
+ DeckyPluginLoader.disablePlugin(
+ name,
+ t('PluginLoader.plugin_disable.title', { name }),
+ t('PluginLoader.plugin_disable.button'),
+ t('PluginLoader.plugin_disable.desc', { name }),
+ )
+ }
+ >
+ {t('PluginListIndex.plugin_disable')}
+ </MenuItem> :
+ // implement enabler
+ <>
+ </>
+ }
{hidden ? (
<MenuItem onSelected={onShow}>{t('PluginListIndex.show')}</MenuItem>
) : (
@@ -147,10 +164,11 @@ type PluginData = {
};
export default function PluginList({ isDeveloper }: { isDeveloper: boolean }) {
- const { plugins, updates, pluginOrder, setPluginOrder, frozenPlugins, hiddenPlugins } = useDeckyState();
+ const { installedPlugins, disabled, updates, pluginOrder, setPluginOrder, frozenPlugins, hiddenPlugins } = useDeckyState();
+
const [_, setPluginOrderSetting] = useSetting<string[]>(
'pluginOrder',
- plugins.map((plugin) => plugin.name),
+ installedPlugins.map((plugin) => plugin.name),
);
const { t } = useTranslation();
@@ -164,7 +182,7 @@ export default function PluginList({ isDeveloper }: { isDeveloper: boolean }) {
useEffect(() => {
setPluginEntries(
- plugins.map(({ name, version }) => {
+ installedPlugins.map(({ name, version }) => {
const frozen = frozenPlugins.includes(name);
const hidden = hiddenPlugins.includes(name);
@@ -173,6 +191,7 @@ export default function PluginList({ isDeveloper }: { isDeveloper: boolean }) {
position: pluginOrder.indexOf(name),
data: {
name,
+ disabled: disabled.some(disabledPlugin => disabledPlugin.name === name),
frozen,
hidden,
isDeveloper,
@@ -186,9 +205,9 @@ export default function PluginList({ isDeveloper }: { isDeveloper: boolean }) {
};
}),
);
- }, [plugins, updates, hiddenPlugins]);
+ }, [installedPlugins, updates, hiddenPlugins]);
- if (plugins.length === 0) {
+ if (installedPlugins.length === 0) {
return (
<div>
<p>{t('PluginListIndex.no_plugin')}</p>
diff --git a/frontend/src/components/store/PluginCard.tsx b/frontend/src/components/store/PluginCard.tsx
index f64abd09..243f846f 100644
--- a/frontend/src/components/store/PluginCard.tsx
+++ b/frontend/src/components/store/PluginCard.tsx
@@ -3,13 +3,13 @@ import { CSSProperties, FC, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { FaArrowDown, FaArrowUp, FaCheck, FaDownload, FaRecycle } from 'react-icons/fa';
-import { InstallType, Plugin } from '../../plugin';
+import { DisabledPlugin, InstallType, Plugin } from '../../plugin';
import { StorePlugin, requestPluginInstall } from '../../store';
import ExternalLink from '../ExternalLink';
interface PluginCardProps {
storePlugin: StorePlugin;
- installedPlugin: Plugin | undefined;
+ installedPlugin: Plugin | DisabledPlugin | undefined;
}
const PluginCard: FC<PluginCardProps> = ({ storePlugin, installedPlugin }) => {
diff --git a/frontend/src/components/store/Store.tsx b/frontend/src/components/store/Store.tsx
index 3209ba08..72187cbc 100644
--- a/frontend/src/components/store/Store.tsx
+++ b/frontend/src/components/store/Store.tsx
@@ -105,7 +105,7 @@ const BrowseTab: FC<{ setPluginCount: Dispatch<SetStateAction<number | null>> }>
})();
}, []);
- const { plugins: installedPlugins } = useDeckyState();
+ const { installedPlugins } = useDeckyState();
return (
<>
diff --git a/frontend/src/plugin-loader.tsx b/frontend/src/plugin-loader.tsx
index df0a6956..755c4460 100644
--- a/frontend/src/plugin-loader.tsx
+++ b/frontend/src/plugin-loader.tsx
@@ -30,7 +30,7 @@ import { FrozenPluginService } from './frozen-plugins-service';
import { HiddenPluginsService } from './hidden-plugins-service';
import Logger from './logger';
import { NotificationService } from './notification-service';
-import { InstallType, Plugin, PluginLoadType } from './plugin';
+import { DisabledPlugin, InstallType, Plugin, PluginLoadType } from './plugin';
import RouterHook from './router-hook';
import { deinitSteamFixes, initSteamFixes } from './steamfixes';
import { checkForPluginUpdates } from './store';
@@ -197,7 +197,7 @@ class PluginLoader extends Logger {
private getPluginsFromBackend = DeckyBackend.callable<
[],
- { name: string; version: string; load_type: PluginLoadType }[]
+ { name: string; version: string; load_type: PluginLoadType; disabled: boolean }[]
>('loader/get_plugins');
private restartWebhelper = DeckyBackend.callable<[], void>('utilities/restart_webhelper');
@@ -220,10 +220,16 @@ class PluginLoader extends Logger {
this.runCrashChecker();
const plugins = await this.getPluginsFromBackend();
const pluginLoadPromises = [];
+ const disabledPlugins: DisabledPlugin[] = [];
const loadStart = performance.now();
for (const plugin of plugins) {
- if (!this.hasPlugin(plugin.name))
- pluginLoadPromises.push(this.importPlugin(plugin.name, plugin.version, plugin.load_type, false));
+ if (plugin.disabled) {
+ disabledPlugins.push({ name: plugin.name, version: plugin.version });
+ this.deckyState.setDisabledPlugins(disabledPlugins);
+ } else {
+ if (!this.hasPlugin(plugin.name))
+ pluginLoadPromises.push(this.importPlugin(plugin.name, plugin.version, plugin.load_type, false));
+ }
}
await Promise.all(pluginLoadPromises);
const loadEnd = performance.now();
@@ -335,6 +341,10 @@ class PluginLoader extends Logger {
showModal(<PluginUninstallModal name={name} title={title} buttonText={buttonText} description={description} />);
}
+ public disablePlugin(name: string, title: string, buttonText: string, description: string) {
+ showModal(<PluginUninstallModal name={name} title={title} buttonText={buttonText} description={description} />);
+ }
+
public hasPlugin(name: string) {
return Boolean(this.plugins.find((plugin) => plugin.name == name));
}
diff --git a/frontend/src/plugin.ts b/frontend/src/plugin.ts
index 0035990e..32d423d5 100644
--- a/frontend/src/plugin.ts
+++ b/frontend/src/plugin.ts
@@ -14,6 +14,8 @@ export interface Plugin {
titleView?: JSX.Element;
}
+export type DisabledPlugin = Pick<Plugin, 'name' | 'version'>;
+
export enum InstallType {
INSTALL,
REINSTALL,
@@ -55,3 +57,5 @@ type installPluginsArgs = [
export let installPlugins = DeckyBackend.callable<installPluginsArgs>('utilities/install_plugins');
export let uninstallPlugin = DeckyBackend.callable<[name: string]>('utilities/uninstall_plugin');
+export let enablePlugin = DeckyBackend.callable<[name: string]>('utilities/enable_plugin');
+export let disablePlugin = DeckyBackend.callable<[name: string]>('utilities/disable_plugin');