diff options
| author | Álvaro Cuesta <1827495+alvaro-cuesta@users.noreply.github.com> | 2025-01-02 20:38:40 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-01-02 11:38:40 -0800 |
| commit | f6144f9634482d6bd0ed88a31495c6c6c88add96 (patch) | |
| tree | 21797e504d62c33c1514870a457e36d3b4f3cbac | |
| parent | 79bb62a3c4f69dbd57dc037da1269285cae1b26d (diff) | |
| download | decky-loader-3.1.0-pre1.tar.gz decky-loader-3.1.0-pre1.zip | |
feat: sync with local plugin status in store (#733)v3.1.0-pre1
* fix: useDeckyState proper type and safety
* refactor: plugin list
Avoids unneeded re-renders. See https://react.dev/learn/you-might-not-need-an-effect#caching-expensive-calculations
* feat: sync with local plugin status in store
Adds some QoL changes to the plugin store browser:
- Add ✓ icon to currently installed plugin version in version selector
- Change install button label depending on the install type that the
button would trigger
- Adds icon to install button for clarity
The goal is to make it clear to the user what the current state of the
installed plugin is, and what would be the impact of installing the
selected version.
Resolves #360
* lint: prettier
* fix: add missing translations
* refactor: safer translation strings on install
Prefer using `t(...)` instead of `TranslationHelper` since it ensures
that the translation keys are not missing in the locale files when
running the `extractext` task.
By adding comments with `t(...)` calls, `i18next-parser` will generate
the strings as if they were present as literals in the code (see
https://github.com/i18next/i18next-parser#caveats).
This does _not_ suppress the warnings (since `i18next-parser` does not
have access to TS types, so it cannot infer template literals) but it at
least makes it less likely that a translation will be missed by mistake,
have typos, etc.
| -rw-r--r-- | backend/decky_loader/browser.py | 4 | ||||
| -rw-r--r-- | backend/decky_loader/locales/en-US.json | 25 | ||||
| -rw-r--r-- | frontend/src/components/DeckyState.tsx | 12 | ||||
| -rw-r--r-- | frontend/src/components/PluginView.tsx | 44 | ||||
| -rw-r--r-- | frontend/src/components/modals/MultiplePluginsInstallModal.tsx | 37 | ||||
| -rw-r--r-- | frontend/src/components/modals/PluginInstallModal.tsx | 66 | ||||
| -rw-r--r-- | frontend/src/components/store/PluginCard.tsx | 73 | ||||
| -rw-r--r-- | frontend/src/components/store/Store.tsx | 10 | ||||
| -rw-r--r-- | frontend/src/plugin-loader.tsx | 8 | ||||
| -rw-r--r-- | frontend/src/plugin.ts | 12 | ||||
| -rw-r--r-- | frontend/src/utils/TranslationHelper.tsx | 19 |
11 files changed, 211 insertions, 99 deletions
diff --git a/backend/decky_loader/browser.py b/backend/decky_loader/browser.py index aa108c59..64ecfb78 100644 --- a/backend/decky_loader/browser.py +++ b/backend/decky_loader/browser.py @@ -29,6 +29,8 @@ class PluginInstallType(IntEnum): INSTALL = 0 REINSTALL = 1 UPDATE = 2 + DOWNGRADE = 3 + OVERWRITE = 4 class PluginInstallRequest(TypedDict): name: str @@ -323,5 +325,5 @@ class PluginBrowser: if name in plugin_order: plugin_order.remove(name) self.settings.setSetting("pluginOrder", plugin_order) - + logger.debug("Removed any settings for plugin %s", name) diff --git a/backend/decky_loader/locales/en-US.json b/backend/decky_loader/locales/en-US.json index 23566026..7cbc2a8b 100644 --- a/backend/decky_loader/locales/en-US.json +++ b/backend/decky_loader/locales/en-US.json @@ -52,7 +52,9 @@ "MultiplePluginsInstallModal": { "confirm": "Are you sure you want to make the following modifications?", "description": { + "downgrade": "Downgrade {{name}} to {{version}}", "install": "Install {{name}} {{version}}", + "overwrite": "Overwrite {{name}} with {{version}}", "reinstall": "Reinstall {{name}} {{version}}", "update": "Update {{name}} to {{version}}" }, @@ -61,10 +63,14 @@ "loading": "Working" }, "title": { + "downgrade_one": "Downgrade 1 plugin", + "downgrade_other": "Downgrade {{count}} plugins", "install_one": "Install 1 plugin", "install_other": "Install {{count}} plugins", "mixed_one": "Modify {{count}} plugin", "mixed_other": "Modify {{count}} plugins", + "overwrite_one": "Overwrite 1 plugin", + "overwrite_other": "Overwrite {{count}} plugins", "reinstall_one": "Reinstall 1 plugin", "reinstall_other": "Reinstall {{count}} plugins", "update_one": "Update 1 plugin", @@ -72,12 +78,22 @@ } }, "PluginCard": { + "plugin_downgrade": "Downgrade", "plugin_full_access": "This plugin has full access to your Steam Deck.", "plugin_install": "Install", "plugin_no_desc": "No description provided.", + "plugin_overwrite": "Overwrite", + "plugin_reinstall": "Reinstall", + "plugin_update": "Update", "plugin_version_label": "Plugin Version" }, "PluginInstallModal": { + "downgrade": { + "button_idle": "Downgrade", + "button_processing": "Downgrading", + "desc": "Are you sure you want to downgrade {{artifact}} to version {{version}}?", + "title": "Downgrade {{artifact}}" + }, "install": { "button_idle": "Install", "button_processing": "Installing", @@ -85,6 +101,13 @@ "title": "Install {{artifact}}" }, "no_hash": "This plugin does not have a hash, you are installing it at your own risk.", + "not_installed": "(not installed)", + "overwrite": { + "button_idle": "Overwrite", + "button_processing": "Overwriting", + "desc": "Are you sure you want to overwrite {{artifact}} with version {{version}}?", + "title": "Overwrite {{artifact}}" + }, "reinstall": { "button_idle": "Reinstall", "button_processing": "Reinstalling", @@ -94,7 +117,7 @@ "update": { "button_idle": "Update", "button_processing": "Updating", - "desc": "Are you sure you want to update {{artifact}} {{version}}?", + "desc": "Are you sure you want to update {{artifact}} to version {{version}}?", "title": "Update {{artifact}}" } }, diff --git a/frontend/src/components/DeckyState.tsx b/frontend/src/components/DeckyState.tsx index 75106e62..d2ac63ae 100644 --- a/frontend/src/components/DeckyState.tsx +++ b/frontend/src/components/DeckyState.tsx @@ -128,9 +128,17 @@ interface DeckyStateContext extends PublicDeckyState { closeActivePlugin(): void; } -const DeckyStateContext = createContext<DeckyStateContext>(null as any); +const DeckyStateContext = createContext<DeckyStateContext | null>(null); -export const useDeckyState = () => useContext(DeckyStateContext); +export const useDeckyState = () => { + const deckyState = useContext(DeckyStateContext); + + if (deckyState === null) { + throw new Error('useDeckyState needs a parent DeckyStateContext'); + } + + return deckyState; +}; interface Props { deckyState: DeckyState; diff --git a/frontend/src/components/PluginView.tsx b/frontend/src/components/PluginView.tsx index 19afbca5..1d39972e 100644 --- a/frontend/src/components/PluginView.tsx +++ b/frontend/src/components/PluginView.tsx @@ -1,27 +1,26 @@ import { ButtonItem, ErrorBoundary, Focusable, PanelSection, PanelSectionRow } from '@decky/ui'; -import { FC, useEffect, useState } from 'react'; +import { FC, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { FaEyeSlash } from 'react-icons/fa'; -import { Plugin } from '../plugin'; import { useDeckyState } from './DeckyState'; import NotificationBadge from './NotificationBadge'; import { useQuickAccessVisible } from './QuickAccessVisibleState'; import TitleView from './TitleView'; const PluginView: FC = () => { - const { hiddenPlugins } = useDeckyState(); - const { plugins, updates, activePlugin, pluginOrder, setActivePlugin, closeActivePlugin } = useDeckyState(); + const { plugins, hiddenPlugins, 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)), - ); - - useEffect(() => { - setPluginList(plugins.sort((a, b) => pluginOrder.indexOf(a.name) - pluginOrder.indexOf(b.name))); + const pluginList = useMemo(() => { console.log('updating PluginView after changes'); + + return [...plugins] + .sort((a, b) => pluginOrder.indexOf(a.name) - pluginOrder.indexOf(b.name)) + .filter((p) => p.content) + .filter(({ name }) => !hiddenPlugins.includes(name)); }, [plugins, pluginOrder]); if (activePlugin) { @@ -43,20 +42,17 @@ const PluginView: FC = () => { }} > <PanelSection> - {pluginList - .filter((p) => p.content) - .filter(({ name }) => !hiddenPlugins.includes(name)) - .map(({ name, icon }) => ( - <PanelSectionRow key={name}> - <ButtonItem layout="below" onClick={() => setActivePlugin(name)}> - <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}> - {icon} - <div>{name}</div> - <NotificationBadge show={updates?.has(name)} style={{ top: '-5px', right: '-5px' }} /> - </div> - </ButtonItem> - </PanelSectionRow> - ))} + {pluginList.map(({ name, icon }) => ( + <PanelSectionRow key={name}> + <ButtonItem layout="below" onClick={() => setActivePlugin(name)}> + <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}> + {icon} + <div>{name}</div> + <NotificationBadge show={updates?.has(name)} style={{ top: '-5px', right: '-5px' }} /> + </div> + </ButtonItem> + </PanelSectionRow> + ))} {hiddenPlugins.length > 0 && ( <div style={{ display: 'flex', alignItems: 'center', gap: '10px', fontSize: '0.8rem', marginTop: '10px' }}> <FaEyeSlash /> diff --git a/frontend/src/components/modals/MultiplePluginsInstallModal.tsx b/frontend/src/components/modals/MultiplePluginsInstallModal.tsx index ba49ba92..9c86f3db 100644 --- a/frontend/src/components/modals/MultiplePluginsInstallModal.tsx +++ b/frontend/src/components/modals/MultiplePluginsInstallModal.tsx @@ -3,7 +3,7 @@ import { FC, useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { FaCheck, FaDownload } from 'react-icons/fa'; -import { InstallType } from '../../plugin'; +import { InstallType, InstallTypeTranslationMapping } from '../../plugin'; interface MultiplePluginsInstallModalProps { requests: { name: string; version: string; hash: string; install_type: InstallType }[]; @@ -12,13 +12,7 @@ interface MultiplePluginsInstallModalProps { 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<InstallType, string>; - +// IMPORTANT! Keep in sync with `t(...)` comments below type TitleTranslationMapping = 'mixed' | (typeof InstallTypeTranslationMapping)[InstallType]; const MultiplePluginsInstallModal: FC<MultiplePluginsInstallModalProps> = ({ @@ -70,6 +64,8 @@ const MultiplePluginsInstallModal: FC<MultiplePluginsInstallModalProps> = ({ 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]); @@ -86,14 +82,35 @@ const MultiplePluginsInstallModal: FC<MultiplePluginsInstallModalProps> = ({ onCancel={async () => { await onCancel(); }} - strTitle={<div>{t(`MultiplePluginsInstallModal.title.${installTypeGrouped}`, { count: requests.length })}</div>} - strOKButtonText={t(`MultiplePluginsInstallModal.ok_button.${loading ? 'loading' : 'idle'}`)} + strTitle={ + <div> + { + // 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 }) + } + </div> + } + strOKButtonText={ + loading ? t('MultiplePluginsInstallModal.ok_button.loading') : t('MultiplePluginsInstallModal.ok_button.idle') + } > <div> {t('MultiplePluginsInstallModal.confirm')} <ul style={{ listStyle: 'none', display: 'flex', flexDirection: 'column', gap: '4px' }}> {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, diff --git a/frontend/src/components/modals/PluginInstallModal.tsx b/frontend/src/components/modals/PluginInstallModal.tsx index 227bd818..16419d91 100644 --- a/frontend/src/components/modals/PluginInstallModal.tsx +++ b/frontend/src/components/modals/PluginInstallModal.tsx @@ -2,13 +2,13 @@ import { ConfirmModal, Navigation, ProgressBarWithInfo, QuickAccessTab } from '@ import { FC, useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; -import TranslationHelper, { TranslationClass } from '../../utils/TranslationHelper'; +import { InstallType, InstallTypeTranslationMapping } from '../../plugin'; interface PluginInstallModalProps { artifact: string; version: string; hash: string; - installType: number; + installType: InstallType; onOK(): void; onCancel(): void; closeModal?(): void; @@ -44,6 +44,8 @@ const PluginInstallModal: FC<PluginInstallModalProps> = ({ }; }, []); + const installTypeTranslationKey = InstallTypeTranslationMapping[installType]; + return ( <ConfirmModal bOKDisabled={loading} @@ -59,12 +61,15 @@ const PluginInstallModal: FC<PluginInstallModalProps> = ({ }} strTitle={ <div style={{ display: 'flex', flexDirection: 'row', alignItems: 'center', width: '100%' }}> - <TranslationHelper - transClass={TranslationClass.PLUGIN_INSTALL_MODAL} - transText="title" - i18nArgs={{ artifact: artifact }} - installType={installType} - /> + { + // IMPORTANT! These comments are not cosmetic and are needed for `extracttext` task to work + // t('PluginInstallModal.install.title') + // t('PluginInstallModal.reinstall.title') + // t('PluginInstallModal.update.title') + // t('PluginInstallModal.downgrade.title') + // t('PluginInstallModal.overwrite.title') + t(`PluginInstallModal.${installTypeTranslationKey}.title`, { artifact: artifact }) + } {loading && ( <div style={{ marginLeft: 'auto' }}> <ProgressBarWithInfo @@ -80,33 +85,44 @@ const PluginInstallModal: FC<PluginInstallModalProps> = ({ strOKButtonText={ loading ? ( <div> - <TranslationHelper - transClass={TranslationClass.PLUGIN_INSTALL_MODAL} - transText="button_processing" - installType={installType} - /> + { + // IMPORTANT! These comments are not cosmetic and are needed for `extracttext` task to work + // t('PluginInstallModal.install.button_processing') + // t('PluginInstallModal.reinstall.button_processing') + // t('PluginInstallModal.update.button_processing') + // t('PluginInstallModal.downgrade.button_processing') + // t('PluginInstallModal.overwrite.button_processing') + t(`PluginInstallModal.${installTypeTranslationKey}.button_processing`) + } </div> ) : ( <div> - <TranslationHelper - transClass={TranslationClass.PLUGIN_INSTALL_MODAL} - transText="button_idle" - installType={installType} - /> + { + // IMPORTANT! These comments are not cosmetic and are needed for `extracttext` task to work + // t('PluginInstallModal.install.button_idle') + // t('PluginInstallModal.reinstall.button_idle') + // t('PluginInstallModal.update.button_idle') + // t('PluginInstallModal.downgrade.button_idle') + // t('PluginInstallModal.overwrite.button_idle') + t(`PluginInstallModal.${installTypeTranslationKey}.button_idle`) + } </div> ) } > <div> - <TranslationHelper - transClass={TranslationClass.PLUGIN_INSTALL_MODAL} - transText="desc" - i18nArgs={{ + { + // IMPORTANT! These comments are not cosmetic and are needed for `extracttext` task to work + // t('PluginInstallModal.install.desc') + // t('PluginInstallModal.reinstall.desc') + // t('PluginInstallModal.update.desc') + // t('PluginInstallModal.downgrade.desc') + // t('PluginInstallModal.overwrite.desc') + t(`PluginInstallModal.${installTypeTranslationKey}.desc`, { artifact: artifact, version: version, - }} - installType={installType} - /> + }) + } </div> {hash == 'False' && <span style={{ color: 'red' }}>{t('PluginInstallModal.no_hash')}</span>} </ConfirmModal> diff --git a/frontend/src/components/store/PluginCard.tsx b/frontend/src/components/store/PluginCard.tsx index 6e2a3510..f64abd09 100644 --- a/frontend/src/components/store/PluginCard.tsx +++ b/frontend/src/components/store/PluginCard.tsx @@ -1,18 +1,32 @@ import { ButtonItem, Dropdown, Focusable, PanelSectionRow, SingleDropdownOption, SuspensefulImage } from '@decky/ui'; import { CSSProperties, FC, useState } from 'react'; import { useTranslation } from 'react-i18next'; +import { FaArrowDown, FaArrowUp, FaCheck, FaDownload, FaRecycle } from 'react-icons/fa'; -import { InstallType } from '../../plugin'; -import { StorePlugin, StorePluginVersion, requestPluginInstall } from '../../store'; +import { InstallType, Plugin } from '../../plugin'; +import { StorePlugin, requestPluginInstall } from '../../store'; import ExternalLink from '../ExternalLink'; interface PluginCardProps { - plugin: StorePlugin; + storePlugin: StorePlugin; + installedPlugin: Plugin | undefined; } -const PluginCard: FC<PluginCardProps> = ({ plugin }) => { +const PluginCard: FC<PluginCardProps> = ({ storePlugin, installedPlugin }) => { const [selectedOption, setSelectedOption] = useState<number>(0); - const root = plugin.tags.some((tag) => tag === 'root'); + const installedVersionIndex = storePlugin.versions.findIndex((version) => version.name === installedPlugin?.version); + const installType = // This assumes index in options is inverse to update order (i.e. newer updates are first) + installedPlugin && selectedOption < installedVersionIndex + ? InstallType.UPDATE + : installedPlugin && selectedOption === installedVersionIndex + ? InstallType.REINSTALL + : installedPlugin && selectedOption > installedVersionIndex + ? InstallType.DOWNGRADE + : installedPlugin // can happen if installed version is not in store + ? InstallType.OVERWRITE + : InstallType.INSTALL; + + const root = storePlugin.tags.some((tag) => tag === 'root'); const { t } = useTranslation(); @@ -43,7 +57,7 @@ const PluginCard: FC<PluginCardProps> = ({ plugin }) => { height: '200px', objectFit: 'cover', }} - src={plugin.image_url} + src={storePlugin.image_url} /> </div> <div @@ -69,7 +83,7 @@ const PluginCard: FC<PluginCardProps> = ({ plugin }) => { width: '90%', }} > - {plugin.name} + {storePlugin.name} </span> <span className="deckyStoreCardAuthor" @@ -78,7 +92,7 @@ const PluginCard: FC<PluginCardProps> = ({ plugin }) => { fontSize: '1em', }} > - {plugin.author} + {storePlugin.author} </span> <span className="deckyStoreCardDescription" @@ -91,8 +105,8 @@ const PluginCard: FC<PluginCardProps> = ({ plugin }) => { display: '-webkit-box', }} > - {plugin.description ? ( - plugin.description + {storePlugin.description ? ( + storePlugin.description ) : ( <span> <i style={{ color: '#666' }}>{t('PluginCard.plugin_no_desc')}</i> @@ -141,18 +155,49 @@ const PluginCard: FC<PluginCardProps> = ({ plugin }) => { bottomSeparator="none" layout="below" onClick={() => - requestPluginInstall(plugin.name, plugin.versions[selectedOption], InstallType.INSTALL) + requestPluginInstall(storePlugin.name, storePlugin.versions[selectedOption], installType) } > - <span className="deckyStoreCardInstallText">{t('PluginCard.plugin_install')}</span> + <span + className="deckyStoreCardInstallText" + style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', gap: '5px' }} + > + {installType === InstallType.UPDATE ? ( + <> + <FaArrowUp /> {t('PluginCard.plugin_update')} + </> + ) : installType === InstallType.REINSTALL ? ( + <> + <FaRecycle /> {t('PluginCard.plugin_reinstall')} + </> + ) : installType === InstallType.DOWNGRADE ? ( + <> + <FaArrowDown /> {t('PluginCard.plugin_downgrade')} + </> + ) : installType === InstallType.OVERWRITE ? ( + <> + <FaDownload /> {t('PluginCard.plugin_overwrite')} + </> + ) : ( + // installType === InstallType.INSTALL (also fallback) + <> + <FaDownload /> {t('PluginCard.plugin_install')} + </> + )} + </span> </ButtonItem> </div> <div className="deckyStoreCardVersionContainer" style={{ minWidth: '130px' }}> <Dropdown rgOptions={ - plugin.versions.map((version: StorePluginVersion, index) => ({ + storePlugin.versions.map((version, index) => ({ data: index, - label: version.name, + label: ( + <div style={{ display: 'flex', alignItems: 'center', gap: '5px' }}> + {version.name} + {installedPlugin && installedVersionIndex === index ? <FaCheck /> : null} + </div> + ), })) as SingleDropdownOption[] } menuLabel={t('PluginCard.plugin_version_label') as string} diff --git a/frontend/src/components/store/Store.tsx b/frontend/src/components/store/Store.tsx index 1094b243..3209ba08 100644 --- a/frontend/src/components/store/Store.tsx +++ b/frontend/src/components/store/Store.tsx @@ -14,6 +14,7 @@ import { useTranslation } from 'react-i18next'; import logo from '../../../assets/plugin_store.png'; import Logger from '../../logger'; import { SortDirections, SortOptions, Store, StorePlugin, getPluginList, getStore } from '../../store'; +import { useDeckyState } from '../DeckyState'; import ExternalLink from '../ExternalLink'; import PluginCard from './PluginCard'; @@ -104,6 +105,8 @@ const BrowseTab: FC<{ setPluginCount: Dispatch<SetStateAction<number | null>> }> })(); }, []); + const { plugins: installedPlugins } = useDeckyState(); + return ( <> <style>{` @@ -235,7 +238,12 @@ const BrowseTab: FC<{ setPluginCount: Dispatch<SetStateAction<number | null>> }> plugin.tags.some((tag: string) => tag.toLowerCase().includes(searchFieldValue.toLowerCase())) ); }) - .map((plugin: StorePlugin) => <PluginCard plugin={plugin} />) + .map((plugin: StorePlugin) => ( + <PluginCard + storePlugin={plugin} + installedPlugin={installedPlugins.find((installedPlugin) => installedPlugin.name === plugin.name)} + /> + )) )} </div> </> diff --git a/frontend/src/plugin-loader.tsx b/frontend/src/plugin-loader.tsx index cb1bb270..88e85a48 100644 --- a/frontend/src/plugin-loader.tsx +++ b/frontend/src/plugin-loader.tsx @@ -146,9 +146,11 @@ class PluginLoader extends Logger { }); this.routerHook.addRoute('/decky/store', () => ( - <WithSuspense route={true}> - <StorePage /> - </WithSuspense> + <DeckyStateContextProvider deckyState={this.deckyState}> + <WithSuspense route={true}> + <StorePage /> + </WithSuspense> + </DeckyStateContextProvider> )); this.routerHook.addRoute('/decky/settings', () => { return ( diff --git a/frontend/src/plugin.ts b/frontend/src/plugin.ts index 0a6ae7aa..0035990e 100644 --- a/frontend/src/plugin.ts +++ b/frontend/src/plugin.ts @@ -18,8 +18,20 @@ export enum InstallType { INSTALL, REINSTALL, UPDATE, + DOWNGRADE, + OVERWRITE, } +// values are the JSON keys used in the translation file +// IMPORTANT! keep in sync with `t(...)` comments where this is used +export const InstallTypeTranslationMapping = { + [InstallType.INSTALL]: 'install', + [InstallType.REINSTALL]: 'reinstall', + [InstallType.UPDATE]: 'update', + [InstallType.DOWNGRADE]: 'downgrade', + [InstallType.OVERWRITE]: 'overwrite', +} as const satisfies Record<InstallType, string>; + type installPluginArgs = [ artifact: string, name?: string, diff --git a/frontend/src/utils/TranslationHelper.tsx b/frontend/src/utils/TranslationHelper.tsx index 61bd24bf..4596060d 100644 --- a/frontend/src/utils/TranslationHelper.tsx +++ b/frontend/src/utils/TranslationHelper.tsx @@ -2,11 +2,9 @@ import { FC } from 'react'; import { Translation } from 'react-i18next'; import Logger from '../logger'; -import { InstallType } from '../plugin'; export enum TranslationClass { PLUGIN_LOADER = 'PluginLoader', - PLUGIN_INSTALL_MODAL = 'PluginInstallModal', DEVELOPER = 'Developer', } @@ -19,7 +17,7 @@ interface TranslationHelperProps { const logger = new Logger('TranslationHelper'); -const TranslationHelper: FC<TranslationHelperProps> = ({ transClass, transText, i18nArgs = null, installType = 0 }) => { +const TranslationHelper: FC<TranslationHelperProps> = ({ transClass, transText, i18nArgs = null }) => { return ( <Translation> {(t, {}) => { @@ -28,21 +26,6 @@ const TranslationHelper: FC<TranslationHelperProps> = ({ transClass, transText, return i18nArgs ? t(TranslationClass.PLUGIN_LOADER + '.' + transText, i18nArgs) : t(TranslationClass.PLUGIN_LOADER + '.' + transText); - case TranslationClass.PLUGIN_INSTALL_MODAL: - switch (installType) { - case InstallType.INSTALL: - return i18nArgs - ? t(TranslationClass.PLUGIN_INSTALL_MODAL + '.install.' + transText, i18nArgs) - : t(TranslationClass.PLUGIN_INSTALL_MODAL + '.install.' + transText); - case InstallType.REINSTALL: - return i18nArgs - ? t(TranslationClass.PLUGIN_INSTALL_MODAL + '.reinstall.' + transText, i18nArgs) - : t(TranslationClass.PLUGIN_INSTALL_MODAL + '.reinstall.' + transText); - case InstallType.UPDATE: - return i18nArgs - ? t(TranslationClass.PLUGIN_INSTALL_MODAL + '.update.' + transText, i18nArgs) - : t(TranslationClass.PLUGIN_INSTALL_MODAL + '.update.' + transText); - } case TranslationClass.DEVELOPER: return i18nArgs ? t(TranslationClass.DEVELOPER + '.' + transText, i18nArgs) |
