import { ConfirmModal, Navigation, ProgressBarWithInfo, QuickAccessTab } from '@decky/ui'; import { FC, useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { FaCheck, FaDownload } from 'react-icons/fa'; import { InstallType, InstallTypeTranslationMapping } from '../../plugin'; interface MultiplePluginsInstallModalProps { requests: { name: string; version: string; hash: string; install_type: InstallType }[]; onOK(): void | Promise; onCancel(): void | Promise; closeModal?(): void; } // IMPORTANT! Keep in sync with `t(...)` comments below type TitleTranslationMapping = 'mixed' | (typeof InstallTypeTranslationMapping)[InstallType]; const MultiplePluginsInstallModal: FC = ({ requests, onOK, onCancel, closeModal, }) => { const [loading, setLoading] = useState(false); const [percentage, setPercentage] = useState(0); const [pluginsCompleted, setPluginsCompleted] = useState([]); const [pluginInProgress, setInProgress] = useState(); const [downloadInfo, setDownloadInfo] = useState(null); const { t } = useTranslation(); function updateDownloadState(percent: number, trans_text: string | undefined, trans_info: Record) { setPercentage(percent); if (trans_text === undefined) { setDownloadInfo(null); } else { setDownloadInfo(t(trans_text, trans_info)); } } function startDownload(name: string) { setInProgress(name); setPercentage(0); } function finishDownload(name: string) { setPluginsCompleted((list) => [...list, name]); } useEffect(() => { DeckyBackend.addEventListener('loader/plugin_download_info', updateDownloadState); DeckyBackend.addEventListener('loader/plugin_download_start', startDownload); DeckyBackend.addEventListener('loader/plugin_download_finish', finishDownload); return () => { DeckyBackend.removeEventListener('loader/plugin_download_info', updateDownloadState); DeckyBackend.removeEventListener('loader/plugin_download_start', startDownload); DeckyBackend.removeEventListener('loader/plugin_download_finish', finishDownload); }; }, []); // 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'; if (requests.every(({ install_type }) => install_type === InstallType.DOWNGRADE)) return 'downgrade'; if (requests.every(({ install_type }) => install_type === InstallType.OVERWRITE)) return 'overwrite'; return 'mixed'; }, [requests]); return ( { setLoading(true); await onOK(); setTimeout(() => Navigation.OpenQuickAccessMenu(QuickAccessTab.Decky), 250); setTimeout(() => DeckyPluginLoader.checkPluginUpdates(), 1000); }} onCancel={async () => { await onCancel(); }} strTitle={
{ // IMPORTANT! These comments are not cosmetic and are needed for `extracttext` task to work // t('MultiplePluginsInstallModal.title.install', { count: n }) // t('MultiplePluginsInstallModal.title.reinstall', { count: n }) // t('MultiplePluginsInstallModal.title.update', { count: n }) // t('MultiplePluginsInstallModal.title.downgrade', { count: n }) // t('MultiplePluginsInstallModal.title.overwrite', { count: n }) // t('MultiplePluginsInstallModal.title.mixed', { count: n }) t(`MultiplePluginsInstallModal.title.${installTypeGrouped}`, { count: requests.length }) }
} strOKButtonText={ loading ? t('MultiplePluginsInstallModal.ok_button.loading') : t('MultiplePluginsInstallModal.ok_button.idle') } >
{t('MultiplePluginsInstallModal.confirm')}
    {requests.map(({ name, version, install_type, hash }, i) => { const installTypeStr = InstallTypeTranslationMapping[install_type]; // IMPORTANT! These comments are not cosmetic and are needed for `extracttext` task to work // t('MultiplePluginsInstallModal.description.install') // t('MultiplePluginsInstallModal.description.reinstall') // t('MultiplePluginsInstallModal.description.update') // t('MultiplePluginsInstallModal.description.downgrade') // t('MultiplePluginsInstallModal.description.overwrite') const description = t(`MultiplePluginsInstallModal.description.${installTypeStr}`, { name, version, }); return (
  • {description}{' '} {(pluginsCompleted.includes(name) && ) || (name === pluginInProgress && )} {hash === 'False' && (
    {t('PluginInstallModal.no_hash')}
    )}
  • ); })}
{/* TODO: center the progress bar and make it 80% width */} {loading && ( )}
); }; export default MultiplePluginsInstallModal;