diff options
| author | marios <marios8543@gmail.com> | 2025-10-06 23:54:49 +0300 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-10-06 23:54:49 +0300 |
| commit | 6fba20239b305e92a79b360ee87850f8f2d9b62e (patch) | |
| tree | 12dba98c397ba7c4adeaa2d7f4332092f1a5577b | |
| parent | c3c0e87c6fc94cfd753ea45d623849e1b3633316 (diff) | |
| download | decky-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.tsx | 15 | ||||
| -rw-r--r-- | frontend/src/components/modals/PluginDisablelModal.tsx | 46 | ||||
| -rw-r--r-- | frontend/src/components/settings/pages/plugin_list/index.tsx | 31 | ||||
| -rw-r--r-- | frontend/src/components/store/PluginCard.tsx | 4 | ||||
| -rw-r--r-- | frontend/src/components/store/Store.tsx | 2 | ||||
| -rw-r--r-- | frontend/src/plugin-loader.tsx | 18 | ||||
| -rw-r--r-- | frontend/src/plugin.ts | 4 |
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'); |
