diff options
Diffstat (limited to 'frontend')
| -rw-r--r-- | frontend/.gitignore | 2 | ||||
| -rw-r--r-- | frontend/package.json | 1 | ||||
| -rw-r--r-- | frontend/pnpm-lock.yaml | 94 | ||||
| -rw-r--r-- | frontend/rollup.config.js | 6 | ||||
| -rw-r--r-- | frontend/src/components/DeckyState.tsx | 18 | ||||
| -rw-r--r-- | frontend/src/components/PluginView.tsx | 11 | ||||
| -rw-r--r-- | frontend/src/components/modals/PluginUninstallModal.tsx | 30 | ||||
| -rw-r--r-- | frontend/src/components/settings/pages/plugin_list/PluginListLabel.tsx | 34 | ||||
| -rw-r--r-- | frontend/src/components/settings/pages/plugin_list/index.tsx | 74 | ||||
| -rw-r--r-- | frontend/src/hidden-plugins-service.tsx | 34 | ||||
| -rw-r--r-- | frontend/src/plugin-loader.tsx | 23 |
11 files changed, 275 insertions, 52 deletions
diff --git a/frontend/.gitignore b/frontend/.gitignore index 5861baa5..58855fb2 100644 --- a/frontend/.gitignore +++ b/frontend/.gitignore @@ -2,3 +2,5 @@ node_modules/ .yalc yalc.lock + +stats.html diff --git a/frontend/package.json b/frontend/package.json index 35d98c65..de0fd5a2 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -33,6 +33,7 @@ "rollup-plugin-delete": "^2.0.0", "rollup-plugin-external-globals": "^0.6.1", "rollup-plugin-polyfill-node": "^0.10.2", + "rollup-plugin-visualizer": "^5.9.0", "tslib": "^2.5.2", "typescript": "^4.9.5" }, diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml index 0f91050b..ad7bb42c 100644 --- a/frontend/pnpm-lock.yaml +++ b/frontend/pnpm-lock.yaml @@ -93,6 +93,9 @@ devDependencies: rollup-plugin-polyfill-node: specifier: ^0.10.2 version: 0.10.2(rollup@2.79.1) + rollup-plugin-visualizer: + specifier: ^5.9.0 + version: 5.9.0(rollup@2.79.1) tslib: specifier: ^2.5.2 version: 2.5.2 @@ -1235,6 +1238,15 @@ packages: engines: {node: '>= 10'} dev: true + /cliui@8.0.1: + resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} + engines: {node: '>=12'} + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + dev: true + /clone-buffer@1.0.0: resolution: {integrity: sha512-KLLTJWrvwIP+OPfMn0x2PheDEP20RPUcGXj/ERegTgdmPEZylALQldygiqrPPu8P45uNuPs7ckmReLY6v/iA5g==} engines: {node: '>= 0.10'} @@ -1414,6 +1426,11 @@ packages: clone: 1.0.4 dev: true + /define-lazy-prop@2.0.0: + resolution: {integrity: sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==} + engines: {node: '>=8'} + dev: true + /define-properties@1.2.0: resolution: {integrity: sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==} engines: {node: '>= 0.4'} @@ -1770,6 +1787,11 @@ packages: engines: {node: '>=6.9.0'} dev: true + /get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + dev: true + /get-intrinsic@1.2.1: resolution: {integrity: sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==} dependencies: @@ -2126,6 +2148,12 @@ packages: engines: {node: '>=0.10.0'} dev: true + /is-docker@2.2.1: + resolution: {integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==} + engines: {node: '>=8'} + hasBin: true + dev: true + /is-extglob@2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} @@ -2222,6 +2250,13 @@ packages: engines: {node: '>=0.10.0'} dev: true + /is-wsl@2.2.0: + resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==} + engines: {node: '>=8'} + dependencies: + is-docker: 2.2.1 + dev: true + /isarray@1.0.0: resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} dev: true @@ -2891,6 +2926,15 @@ packages: mimic-fn: 2.1.0 dev: true + /open@8.4.2: + resolution: {integrity: sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==} + engines: {node: '>=12'} + dependencies: + define-lazy-prop: 2.0.0 + is-docker: 2.2.1 + is-wsl: 2.2.0 + dev: true + /ora@5.4.1: resolution: {integrity: sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==} engines: {node: '>=10'} @@ -3237,6 +3281,11 @@ packages: engines: {node: '>= 10'} dev: true + /require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + dev: true + /resolve-from@3.0.0: resolution: {integrity: sha512-GnlH6vxLymXJNMBo7XP1fJIzBFbdYt49CuTwmB/6N53t+kMPRMFKz783LlQ4tv28XoQfMWinAJX6WCGf2IlaIw==} engines: {node: '>=4'} @@ -3318,6 +3367,23 @@ packages: rollup: 2.79.1 dev: true + /rollup-plugin-visualizer@5.9.0(rollup@2.79.1): + resolution: {integrity: sha512-bbDOv47+Bw4C/cgs0czZqfm8L82xOZssk4ayZjG40y9zbXclNk7YikrZTDao6p7+HDiGxrN0b65SgZiVm9k1Cg==} + engines: {node: '>=14'} + hasBin: true + peerDependencies: + rollup: 2.x || 3.x + peerDependenciesMeta: + rollup: + optional: true + dependencies: + open: 8.4.2 + picomatch: 2.3.1 + rollup: 2.79.1 + source-map: 0.7.4 + yargs: 17.7.2 + dev: true + /rollup@2.79.1: resolution: {integrity: sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw==} engines: {node: '>=10.0.0'} @@ -3425,6 +3491,11 @@ packages: engines: {node: '>=0.10.0'} dev: true + /source-map@0.7.4: + resolution: {integrity: sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==} + engines: {node: '>= 8'} + dev: true + /sourcemap-codec@1.4.8: resolution: {integrity: sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==} deprecated: Please use @jridgewell/sourcemap-codec instead @@ -3958,10 +4029,33 @@ packages: engines: {node: '>=0.4'} dev: true + /y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + dev: true + /yallist@3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} dev: true + /yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + dev: true + + /yargs@17.7.2: + resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} + engines: {node: '>=12'} + dependencies: + cliui: 8.0.1 + escalade: 3.1.1 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 21.1.1 + dev: true + /zwitch@2.0.4: resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} dev: false diff --git a/frontend/rollup.config.js b/frontend/rollup.config.js index 2573b8a0..ac9ff58c 100644 --- a/frontend/rollup.config.js +++ b/frontend/rollup.config.js @@ -7,6 +7,7 @@ import typescript from '@rollup/plugin-typescript'; import { defineConfig } from 'rollup'; import del from 'rollup-plugin-delete'; import externalGlobals from 'rollup-plugin-external-globals'; +import { visualizer } from 'rollup-plugin-visualizer'; const hiddenWarnings = ['THIS_IS_UNDEFINED', 'EVAL']; @@ -16,7 +17,7 @@ export default defineConfig({ del({ targets: '../backend/static/*', force: true }), commonjs(), nodeResolve({ - browser: true + browser: true, }), externalGlobals({ react: 'SP_REACT', @@ -33,6 +34,7 @@ export default defineConfig({ 'process.env.NODE_ENV': JSON.stringify('production'), }), image(), + visualizer(), ], preserveEntrySignatures: false, output: { @@ -46,4 +48,4 @@ export default defineConfig({ if (hiddenWarnings.some((warning) => message.code === warning)) return; handleWarning(message); }, -});
\ No newline at end of file +}); diff --git a/frontend/src/components/DeckyState.tsx b/frontend/src/components/DeckyState.tsx index 67d4b78e..53ef6d2d 100644 --- a/frontend/src/components/DeckyState.tsx +++ b/frontend/src/components/DeckyState.tsx @@ -7,6 +7,7 @@ import { VerInfo } from '../updater'; interface PublicDeckyState { plugins: Plugin[]; pluginOrder: string[]; + hiddenPlugins: string[]; activePlugin: Plugin | null; updates: PluginUpdateMapping | null; hasLoaderUpdate?: boolean; @@ -17,6 +18,7 @@ interface PublicDeckyState { export class DeckyState { private _plugins: Plugin[] = []; private _pluginOrder: string[] = []; + private _hiddenPlugins: string[] = []; private _activePlugin: Plugin | null = null; private _updates: PluginUpdateMapping | null = null; private _hasLoaderUpdate: boolean = false; @@ -29,6 +31,7 @@ export class DeckyState { return { plugins: this._plugins, pluginOrder: this._pluginOrder, + hiddenPlugins: this._hiddenPlugins, activePlugin: this._activePlugin, updates: this._updates, hasLoaderUpdate: this._hasLoaderUpdate, @@ -52,6 +55,11 @@ export class DeckyState { this.notifyUpdate(); } + setHiddenPlugins(hiddenPlugins: string[]) { + this._hiddenPlugins = hiddenPlugins; + this.notifyUpdate(); + } + setActivePlugin(name: string) { this._activePlugin = this._plugins.find((plugin) => plugin.name === name) ?? null; this.notifyUpdate(); @@ -111,11 +119,11 @@ export const DeckyStateContextProvider: FC<Props> = ({ children, deckyState }) = return () => deckyState.eventBus.removeEventListener('update', onUpdate); }, []); - const setIsLoaderUpdating = (hasUpdate: boolean) => deckyState.setIsLoaderUpdating(hasUpdate); - const setVersionInfo = (versionInfo: VerInfo) => deckyState.setVersionInfo(versionInfo); - const setActivePlugin = (name: string) => deckyState.setActivePlugin(name); - const closeActivePlugin = () => deckyState.closeActivePlugin(); - const setPluginOrder = (pluginOrder: string[]) => deckyState.setPluginOrder(pluginOrder); + const setIsLoaderUpdating = deckyState.setIsLoaderUpdating.bind(deckyState); + const setVersionInfo = deckyState.setVersionInfo.bind(deckyState); + const setActivePlugin = deckyState.setActivePlugin.bind(deckyState); + const closeActivePlugin = deckyState.closeActivePlugin.bind(deckyState); + const setPluginOrder = deckyState.setPluginOrder.bind(deckyState); return ( <DeckyStateContext.Provider diff --git a/frontend/src/components/PluginView.tsx b/frontend/src/components/PluginView.tsx index 3ecc2c86..b53035f7 100644 --- a/frontend/src/components/PluginView.tsx +++ b/frontend/src/components/PluginView.tsx @@ -8,6 +8,8 @@ import { staticClasses, } from 'decky-frontend-lib'; import { VFC, useEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { FaEyeSlash } from 'react-icons/fa'; import { Plugin } from '../plugin'; import { useDeckyState } from './DeckyState'; @@ -16,8 +18,10 @@ import { useQuickAccessVisible } from './QuickAccessVisibleState'; import TitleView from './TitleView'; const PluginView: VFC = () => { + const { hiddenPlugins } = useDeckyState(); const { plugins, updates, activePlugin, pluginOrder, setActivePlugin, closeActivePlugin } = useDeckyState(); const visible = useQuickAccessVisible(); + const { t } = useTranslation(); const [pluginList, setPluginList] = useState<Plugin[]>( plugins.sort((a, b) => pluginOrder.indexOf(a.name) - pluginOrder.indexOf(b.name)), @@ -48,6 +52,7 @@ const PluginView: VFC = () => { <PanelSection> {pluginList .filter((p) => p.content) + .filter(({ name }) => !hiddenPlugins.includes(name)) .map(({ name, icon }) => ( <PanelSectionRow key={name}> <ButtonItem layout="below" onClick={() => setActivePlugin(name)}> @@ -59,6 +64,12 @@ const PluginView: VFC = () => { </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> + )} </PanelSection> </div> </> diff --git a/frontend/src/components/modals/PluginUninstallModal.tsx b/frontend/src/components/modals/PluginUninstallModal.tsx new file mode 100644 index 00000000..e7ecbc99 --- /dev/null +++ b/frontend/src/components/modals/PluginUninstallModal.tsx @@ -0,0 +1,30 @@ +import { ConfirmModal } from 'decky-frontend-lib'; +import { FC } from 'react'; + +interface PluginUninstallModalProps { + name: string; + title: string; + buttonText: string; + description: string; + closeModal?(): void; +} + +const PluginUninstallModal: FC<PluginUninstallModalProps> = ({ name, title, buttonText, description, closeModal }) => { + return ( + <ConfirmModal + closeModal={closeModal} + onOK={async () => { + await window.DeckyPluginLoader.callServerMethod('uninstall_plugin', { name }); + // 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 window.DeckyPluginLoader.hiddenPluginsService.invalidate(); + }} + strTitle={title} + strOKButtonText={buttonText} + > + {description} + </ConfirmModal> + ); +}; + +export default PluginUninstallModal; diff --git a/frontend/src/components/settings/pages/plugin_list/PluginListLabel.tsx b/frontend/src/components/settings/pages/plugin_list/PluginListLabel.tsx new file mode 100644 index 00000000..a49f808f --- /dev/null +++ b/frontend/src/components/settings/pages/plugin_list/PluginListLabel.tsx @@ -0,0 +1,34 @@ +import { FC } from 'react'; +import { useTranslation } from 'react-i18next'; +import { FaEyeSlash } from 'react-icons/fa'; + +interface PluginListLabelProps { + hidden: boolean; + name: string; + version?: string; +} + +const PluginListLabel: FC<PluginListLabelProps> = ({ name, hidden, version }) => { + const { t } = useTranslation(); + return ( + <div style={{ display: 'flex', flexDirection: 'column', gap: '6px' }}> + <div>{version ? `${name} - ${version}` : name}</div> + {hidden && ( + <div + style={{ + fontSize: '0.8rem', + color: '#dcdedf', + display: 'flex', + alignItems: 'center', + gap: '10px', + }} + > + <FaEyeSlash /> + {t('PluginListLabel.hidden')} + </div> + )} + </div> + ); +}; + +export default PluginListLabel; diff --git a/frontend/src/components/settings/pages/plugin_list/index.tsx b/frontend/src/components/settings/pages/plugin_list/index.tsx index fab8ec2b..09d06d48 100644 --- a/frontend/src/components/settings/pages/plugin_list/index.tsx +++ b/frontend/src/components/settings/pages/plugin_list/index.tsx @@ -22,10 +22,7 @@ import { } from '../../../../store'; import { useSetting } from '../../../../utils/hooks/useSetting'; import { useDeckyState } from '../../../DeckyState'; - -function labelToName(pluginLabel: string, pluginVersion?: string): string { - return pluginVersion ? pluginLabel.substring(0, pluginLabel.indexOf(` - ${pluginVersion}`)) : pluginLabel; -} +import PluginListLabel from './PluginListLabel'; async function reinstallPlugin(pluginName: string, currentVersion?: string) { const serverData = await getPluginList(); @@ -36,10 +33,17 @@ async function reinstallPlugin(pluginName: string, currentVersion?: string) { } } -function PluginInteractables(props: { entry: ReorderableEntry<PluginData> }) { - const data = props.entry.data; +type PluginTableData = PluginData & { name: string; hidden: boolean; onHide(): void; onShow(): void }; + +function PluginInteractables(props: { entry: ReorderableEntry<PluginTableData> }) { const { t } = useTranslation(); - let pluginName = labelToName(props.entry.label, data?.version); + + // nothing to display without this data... + if (!props.entry.data) { + return null; + } + + const { name, update, version, onHide, onShow, hidden } = props.entry.data; const showCtxMenu = (e: MouseEvent | GamepadEvent) => { showContextMenu( @@ -47,7 +51,7 @@ function PluginInteractables(props: { entry: ReorderableEntry<PluginData> }) { <MenuItem onSelected={() => { try { - fetch(`http://127.0.0.1:1337/plugins/${pluginName}/reload`, { + fetch(`http://127.0.0.1:1337/plugins/${name}/reload`, { method: 'POST', credentials: 'include', headers: { @@ -58,7 +62,7 @@ function PluginInteractables(props: { entry: ReorderableEntry<PluginData> }) { console.error('Error Reloading Plugin Backend', err); } - window.DeckyPluginLoader.importPlugin(pluginName, data?.version); + window.DeckyPluginLoader.importPlugin(name, version); }} > {t('PluginListIndex.reload')} @@ -66,15 +70,20 @@ function PluginInteractables(props: { entry: ReorderableEntry<PluginData> }) { <MenuItem onSelected={() => window.DeckyPluginLoader.uninstallPlugin( - pluginName, - t('PluginLoader.plugin_uninstall.title', { name: pluginName }), + name, + t('PluginLoader.plugin_uninstall.title', { name }), t('PluginLoader.plugin_uninstall.button'), - t('PluginLoader.plugin_uninstall.desc', { name: pluginName }), + t('PluginLoader.plugin_uninstall.desc', { name }), ) } > {t('PluginListIndex.uninstall')} </MenuItem> + {hidden ? ( + <MenuItem onSelected={onShow}>{t('PluginListIndex.show')}</MenuItem> + ) : ( + <MenuItem onSelected={onHide}>{t('PluginListIndex.hide')}</MenuItem> + )} </Menu>, e.currentTarget ?? window, ); @@ -82,22 +91,22 @@ function PluginInteractables(props: { entry: ReorderableEntry<PluginData> }) { return ( <> - {data?.update ? ( + {update ? ( <DialogButton style={{ height: '40px', minWidth: '60px', marginRight: '10px' }} - onClick={() => requestPluginInstall(pluginName, data?.update as StorePluginVersion, InstallType.UPDATE)} - onOKButton={() => requestPluginInstall(pluginName, data?.update as StorePluginVersion, InstallType.UPDATE)} + onClick={() => requestPluginInstall(name, update, InstallType.UPDATE)} + onOKButton={() => requestPluginInstall(name, update, InstallType.UPDATE)} > <div style={{ display: 'flex', minWidth: '180px', justifyContent: 'space-between', alignItems: 'center' }}> - {t('PluginListIndex.update_to', { name: data?.update?.name })} + {t('PluginListIndex.update_to', { name: update.name })} <FaDownload style={{ paddingLeft: '1rem' }} /> </div> </DialogButton> ) : ( <DialogButton style={{ height: '40px', minWidth: '60px', marginRight: '10px' }} - onClick={() => reinstallPlugin(pluginName, data?.version)} - onOKButton={() => reinstallPlugin(pluginName, data?.version)} + onClick={() => reinstallPlugin(name, version)} + onOKButton={() => reinstallPlugin(name, version)} > <div style={{ display: 'flex', minWidth: '180px', justifyContent: 'space-between', alignItems: 'center' }}> {t('PluginListIndex.reinstall')} @@ -130,7 +139,7 @@ type PluginData = { }; export default function PluginList() { - const { plugins, updates, pluginOrder, setPluginOrder } = useDeckyState(); + const { plugins, updates, pluginOrder, setPluginOrder, hiddenPlugins } = useDeckyState(); const [_, setPluginOrderSetting] = useSetting<string[]>( 'pluginOrder', plugins.map((plugin) => plugin.name), @@ -141,22 +150,29 @@ export default function PluginList() { window.DeckyPluginLoader.checkPluginUpdates(); }, []); - const [pluginEntries, setPluginEntries] = useState<ReorderableEntry<PluginData>[]>([]); + const [pluginEntries, setPluginEntries] = useState<ReorderableEntry<PluginTableData>[]>([]); + const hiddenPluginsService = window.DeckyPluginLoader.hiddenPluginsService; useEffect(() => { setPluginEntries( - plugins.map((plugin) => { + plugins.map(({ name, version }) => { + const hidden = hiddenPlugins.includes(name); + return { - label: plugin.version ? `${plugin.name} - ${plugin.version}` : plugin.name, + label: <PluginListLabel name={name} hidden={hidden} version={version} />, + position: pluginOrder.indexOf(name), data: { - update: updates?.get(plugin.name), - version: plugin.version, + name, + hidden, + version, + update: updates?.get(name), + onHide: () => hiddenPluginsService.update([...hiddenPlugins, name]), + onShow: () => hiddenPluginsService.update(hiddenPlugins.filter((pluginName) => name !== pluginName)), }, - position: pluginOrder.indexOf(plugin.name), }; }), ); - }, [plugins, updates]); + }, [plugins, updates, hiddenPlugins]); if (plugins.length === 0) { return ( @@ -166,8 +182,8 @@ export default function PluginList() { ); } - function onSave(entries: ReorderableEntry<PluginData>[]) { - const newOrder = entries.map((entry) => labelToName(entry.label, entry?.data?.version)); + function onSave(entries: ReorderableEntry<PluginTableData>[]) { + const newOrder = entries.map((entry) => entry.data!.name); console.log(newOrder); setPluginOrder(newOrder); setPluginOrderSetting(newOrder); @@ -200,7 +216,7 @@ export default function PluginList() { </DialogButton> )} <DialogControlsSection style={{ marginTop: 0 }}> - <ReorderableList<PluginData> entries={pluginEntries} onSave={onSave} interactables={PluginInteractables} /> + <ReorderableList<PluginTableData> entries={pluginEntries} onSave={onSave} interactables={PluginInteractables} /> </DialogControlsSection> </DialogBody> ); diff --git a/frontend/src/hidden-plugins-service.tsx b/frontend/src/hidden-plugins-service.tsx new file mode 100644 index 00000000..0501c453 --- /dev/null +++ b/frontend/src/hidden-plugins-service.tsx @@ -0,0 +1,34 @@ +import { DeckyState } from './components/DeckyState'; +import { getSetting, setSetting } from './utils/settings'; + +/** + * A Service class for managing the state and actions related to the hidden plugins feature + * + * It's mostly responsible for sending setting updates to the server and keeping the local state in sync. + */ +export class HiddenPluginsService { + constructor(private deckyState: DeckyState) {} + + init() { + getSetting<string[]>('hiddenPlugins', []).then((hiddenPlugins) => { + this.deckyState.setHiddenPlugins(hiddenPlugins); + }); + } + + /** + * Sends the new hidden plugins list to the server and persists it locally in the decky state + * + * @param hiddenPlugins The new list of hidden plugins + */ + async update(hiddenPlugins: string[]) { + await setSetting('hiddenPlugins', hiddenPlugins); + this.deckyState.setHiddenPlugins(hiddenPlugins); + } + + /** + * Refreshes the state of hidden plugins in the local state + */ + async invalidate() { + this.deckyState.setHiddenPlugins(await getSetting('hiddenPlugins', [])); + } +} diff --git a/frontend/src/plugin-loader.tsx b/frontend/src/plugin-loader.tsx index 57483293..6d20c2f0 100644 --- a/frontend/src/plugin-loader.tsx +++ b/frontend/src/plugin-loader.tsx @@ -1,5 +1,4 @@ import { - ConfirmModal, ModalRoot, PanelSection, PanelSectionRow, @@ -18,9 +17,11 @@ import LegacyPlugin from './components/LegacyPlugin'; import { deinitFilepickerPatches, initFilepickerPatches } from './components/modals/filepicker/patches'; import MultiplePluginsInstallModal from './components/modals/MultiplePluginsInstallModal'; import PluginInstallModal from './components/modals/PluginInstallModal'; +import PluginUninstallModal from './components/modals/PluginUninstallModal'; import NotificationBadge from './components/NotificationBadge'; import PluginView from './components/PluginView'; import WithSuspense from './components/WithSuspense'; +import { HiddenPluginsService } from './hidden-plugins-service'; import Logger from './logger'; import { InstallType, Plugin } from './plugin'; import RouterHook from './router-hook'; @@ -45,6 +46,7 @@ class PluginLoader extends Logger { private routerHook: RouterHook = new RouterHook(); public toaster: Toaster = new Toaster(); private deckyState: DeckyState = new DeckyState(); + public hiddenPluginsService = new HiddenPluginsService(this.deckyState); private reloadLock: boolean = false; // stores a list of plugin names which requested to be reloaded @@ -182,21 +184,8 @@ class PluginLoader extends Logger { ); } - public uninstallPlugin(name: string, title: string, button_text: string, description: string) { - showModal( - <ConfirmModal - onOK={async () => { - await this.callServerMethod('uninstall_plugin', { name }); - }} - onCancel={() => { - // do nothing - }} - strTitle={title} - strOKButtonText={button_text} - > - {description} - </ConfirmModal>, - ); + public uninstallPlugin(name: string, title: string, buttonText: string, description: string) { + showModal(<PluginUninstallModal name={name} title={title} buttonText={buttonText} description={description} />); } public hasPlugin(name: string) { @@ -220,6 +209,8 @@ class PluginLoader extends Logger { console.log(pluginOrder); this.deckyState.setPluginOrder(pluginOrder); }); + + this.hiddenPluginsService.init(); } public deinit() { |
