import { DialogButton, Dropdown, DropdownOption, Focusable, PanelSectionRow, SteamSpinner, Tabs, TextField, findModule, } from '@decky/ui'; import { Dispatch, FC, SetStateAction, useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import logo from '../../../assets/plugin_store.png'; import Logger from '../../logger'; import { SortDirections, SortOptions, Store, StorePlugin, StoreRequestError, getPluginList, getStore, } from '../../store'; import { useDeckyState } from '../DeckyState'; import ExternalLink from '../ExternalLink'; import PluginCard from './PluginCard'; const logger = new Logger('Store'); const StorePage: FC<{}> = () => { const [currentTabRoute, setCurrentTabRoute] = useState('browse'); const [pluginCount, setPluginCount] = useState(null); const { TabCount } = findModule((m) => { if (m?.TabCount && m?.TabTitle) return true; return false; }); const { t } = useTranslation(); return ( <>
{ setCurrentTabRoute(tabId); }} tabs={[ { title: t('Store.store_tabs.title'), content: , id: 'browse', renderTabAddon: () => {pluginCount}, }, { title: t('Store.store_tabs.about'), content: , id: 'about', }, ]} />
); }; const BrowseTab: FC<{ setPluginCount: Dispatch> }> = ({ setPluginCount }) => { const { t } = useTranslation(); const dropdownSortOptions = useMemo( (): DropdownOption[] => [ // ascending and descending order are the wrong way around for the alphabetical sort // this is because it was initially done incorrectly for i18n and 'fixing' it would // make all the translations incorrect { data: [SortOptions.name, SortDirections.ascending], label: t('Store.store_tabs.alph_desc') }, { data: [SortOptions.name, SortDirections.descending], label: t('Store.store_tabs.alph_asce') }, { data: [SortOptions.date, SortDirections.ascending], label: t('Store.store_tabs.date_asce') }, { data: [SortOptions.date, SortDirections.descending], label: t('Store.store_tabs.date_desc') }, { data: [SortOptions.downloads, SortDirections.descending], label: t('Store.store_tabs.downloads_desc') }, { data: [SortOptions.downloads, SortDirections.ascending], label: t('Store.store_tabs.downloads_asce') }, ], [], ); // const filterOptions = useMemo((): DropdownOption[] => [{ data: 1, label: 'All' }], []); const [selectedSort, setSort] = useState<[SortOptions, SortDirections]>(dropdownSortOptions[0].data); // const [selectedFilter, setFilter] = useState(filterOptions[0].data); const [searchFieldValue, setSearchValue] = useState(''); const [pluginList, setPluginList] = useState(null); const [loadError, setLoadError] = useState(null); const [retryCount, setRetryCount] = useState(0); const [isTesting, setIsTesting] = useState(false); useEffect(() => { const controller = new AbortController(); setPluginList(null); setPluginCount(null); setLoadError(null); (async () => { try { const res = await getPluginList(selectedSort[0], selectedSort[1], controller.signal); if (controller.signal.aborted) return; logger.debug('got data!', res); setPluginList(res); setPluginCount(res.length); } catch (error) { if (controller.signal.aborted) return; logger.error('Failed to load the plugin store', error); setLoadError(error instanceof Error ? error : new Error(String(error))); } })(); return () => controller.abort(); }, [selectedSort, retryCount, setPluginCount]); useEffect(() => { (async () => { try { const storeRes = await getStore(); logger.debug(`store is ${storeRes}, isTesting is ${storeRes === Store.Testing}`); setIsTesting(storeRes === Store.Testing); } catch (error) { logger.error('Failed to read the store channel', error); } })(); }, []); const { installedPlugins } = useDeckyState(); return ( <> {/* This should be used once filtering is added
{t("Store.store_sort.label")} setSort(e.data)} />
{t("Store.store_filter.label")} setFilter(e.data)} />
setSearchValue(e.target.value)} />
*/}
{t('Store.store_sort.label')} setSort(e.data)} />
setSearchValue(e.target.value)} />
{isTesting && (

{t('Store.store_testing_warning.label')}

{`${t('Store.store_testing_warning.desc')} `} decky.xyz/testing
)}
{loadError ? (

{loadError instanceof StoreRequestError && loadError.status === 426 ? t('Store.errors.update_required') : t('Store.errors.load_failed')}

setRetryCount((count) => count + 1)}>{t('Store.retry')}
) : !pluginList ? (
) : ( pluginList .filter((plugin: StorePlugin) => { return ( plugin.name.toLowerCase().includes(searchFieldValue.toLowerCase()) || plugin.description.toLowerCase().includes(searchFieldValue.toLowerCase()) || plugin.author.toLowerCase().includes(searchFieldValue.toLowerCase()) || plugin.tags.some((tag: string) => tag.toLowerCase().includes(searchFieldValue.toLowerCase())) ); }) .map((plugin: StorePlugin) => ( installedPlugin.name === plugin.name)} /> )) )}
); }; const AboutTab: FC<{}> = () => { const { t } = useTranslation(); return (
Testing {t('Store.store_testing_cta')}{' '} decky.xyz/testing {t('Store.store_contrib.label')} {t('Store.store_contrib.desc')} {t('Store.store_source.label')} {t('Store.store_source.desc')}
); }; export default StorePage;