From 010feddf36646fb9695106bd64eab41e47e962fe Mon Sep 17 00:00:00 2001 From: Jonas Dellinger Date: Mon, 29 May 2023 18:29:36 +0200 Subject: Add update all button to plugin list (#466) --- .../modals/MultiplePluginsInstallModal.tsx | 82 ++++++++++++++++++++++ .../settings/pages/plugin_list/index.tsx | 49 +++++++++++-- 2 files changed, 125 insertions(+), 6 deletions(-) create mode 100644 frontend/src/components/modals/MultiplePluginsInstallModal.tsx (limited to 'frontend/src/components') diff --git a/frontend/src/components/modals/MultiplePluginsInstallModal.tsx b/frontend/src/components/modals/MultiplePluginsInstallModal.tsx new file mode 100644 index 00000000..febdc38d --- /dev/null +++ b/frontend/src/components/modals/MultiplePluginsInstallModal.tsx @@ -0,0 +1,82 @@ +import { ConfirmModal, Navigation, QuickAccessTab } from 'decky-frontend-lib'; +import { FC, useMemo, useState } from 'react'; +import { useTranslation } from 'react-i18next'; + +import { InstallType } from '../../plugin'; + +interface MultiplePluginsInstallModalProps { + requests: { name: string; version: string; hash: string; install_type: InstallType }[]; + onOK(): void | Promise; + onCancel(): void | Promise; + closeModal?(): void; +} + +// values are the JSON keys used in the translation file +const InstallTypeTranslationMapping = { + [InstallType.INSTALL]: 'install', + [InstallType.REINSTALL]: 'reinstall', + [InstallType.UPDATE]: 'update', +} as const satisfies Record; + +type TitleTranslationMapping = 'mixed' | (typeof InstallTypeTranslationMapping)[InstallType]; + +const MultiplePluginsInstallModal: FC = ({ + requests, + onOK, + onCancel, + closeModal, +}) => { + const [loading, setLoading] = useState(false); + const { t } = useTranslation(); + + // used as part of the title translation + // if we know all operations are of a specific type, we can show so in the title to make decision easier + const installTypeGrouped = useMemo((): TitleTranslationMapping => { + if (requests.every(({ install_type }) => install_type === InstallType.INSTALL)) return 'install'; + if (requests.every(({ install_type }) => install_type === InstallType.REINSTALL)) return 'reinstall'; + if (requests.every(({ install_type }) => install_type === InstallType.UPDATE)) return 'update'; + return 'mixed'; + }, [requests]); + + return ( + { + setLoading(true); + await onOK(); + setTimeout(() => Navigation.OpenQuickAccessMenu(QuickAccessTab.Decky), 250); + setTimeout(() => window.DeckyPluginLoader.checkPluginUpdates(), 1000); + }} + onCancel={async () => { + await onCancel(); + }} + strTitle={
{t(`MultiplePluginsInstallModal.title.${installTypeGrouped}`, { count: requests.length })}
} + strOKButtonText={t(`MultiplePluginsInstallModal.ok_button.${loading ? 'loading' : 'idle'}`)} + > +
+ {t('MultiplePluginsInstallModal.confirm')} +
    + {requests.map(({ name, version, install_type, hash }, i) => { + const installTypeStr = InstallTypeTranslationMapping[install_type]; + const description = t(`MultiplePluginsInstallModal.description.${installTypeStr}`, { + name, + version, + }); + + return ( +
  • +
    {description}
    + {hash === 'False' && ( +
    {t('PluginInstallModal.no_hash')}
    + )} +
  • + ); + })} +
+
+
+ ); +}; + +export default MultiplePluginsInstallModal; diff --git a/frontend/src/components/settings/pages/plugin_list/index.tsx b/frontend/src/components/settings/pages/plugin_list/index.tsx index d7ff7bd9..ce349393 100644 --- a/frontend/src/components/settings/pages/plugin_list/index.tsx +++ b/frontend/src/components/settings/pages/plugin_list/index.tsx @@ -14,7 +14,12 @@ import { useTranslation } from 'react-i18next'; import { FaDownload, FaEllipsisH, FaRecycle } from 'react-icons/fa'; import { InstallType } from '../../../../plugin'; -import { StorePluginVersion, getPluginList, requestPluginInstall } from '../../../../store'; +import { + StorePluginVersion, + getPluginList, + requestMultiplePluginInstalls, + requestPluginInstall, +} from '../../../../store'; import { useSetting } from '../../../../utils/hooks/useSetting'; import { useDeckyState } from '../../../DeckyState'; @@ -67,9 +72,9 @@ function PluginInteractables(props: { entry: ReorderableEntry }) { onClick={() => requestPluginInstall(pluginName, data?.update as StorePluginVersion, InstallType.UPDATE)} onOKButton={() => requestPluginInstall(pluginName, data?.update as StorePluginVersion, InstallType.UPDATE)} > -
+
{t('PluginListIndex.update_to', { name: data?.update?.name })} - +
) : ( @@ -78,14 +83,22 @@ function PluginInteractables(props: { entry: ReorderableEntry }) { onClick={() => reinstallPlugin(pluginName, data?.version)} onOKButton={() => reinstallPlugin(pluginName, data?.version)} > -
+
{t('PluginListIndex.reinstall')} - +
)} @@ -146,6 +159,30 @@ export default function PluginList() { return ( + {updates && updates.size > 0 && ( + + requestMultiplePluginInstalls( + [...updates.entries()].map(([plugin, selectedVer]) => ({ + installType: InstallType.UPDATE, + plugin, + selectedVer, + })), + ) + } + style={{ + position: 'absolute', + top: '57px', + right: '2.8vw', + width: 'auto', + display: 'flex', + alignItems: 'center', + }} + > + {t('PluginListIndex.update_all', { count: updates.size })} + + + )} entries={pluginEntries} onSave={onSave} interactables={PluginInteractables} /> -- cgit v1.2.3