From 3ebaac6752cb2b13ee5bfb6274cd7ae60b0d6bcb Mon Sep 17 00:00:00 2001 From: EMERALD Date: Thu, 19 Jan 2023 20:00:42 -0600 Subject: Store and plugin installation visual improvements (#343) * Redesign store, add comments for filtering * Improve installation/uninstallation modals * Fix store comment to be easier to fix * Add source code info to about page --- .../src/components/modals/PluginInstallModal.tsx | 17 +- frontend/src/components/store/PluginCard.tsx | 277 ++++++++++----------- frontend/src/components/store/Store.tsx | 224 +++++++++++++++-- 3 files changed, 346 insertions(+), 172 deletions(-) (limited to 'frontend/src/components') diff --git a/frontend/src/components/modals/PluginInstallModal.tsx b/frontend/src/components/modals/PluginInstallModal.tsx index dfddc199..f2f13bbf 100644 --- a/frontend/src/components/modals/PluginInstallModal.tsx +++ b/frontend/src/components/modals/PluginInstallModal.tsx @@ -1,4 +1,4 @@ -import { ConfirmModal, Navigation, QuickAccessTab, Spinner, staticClasses } from 'decky-frontend-lib'; +import { ConfirmModal, Navigation, QuickAccessTab } from 'decky-frontend-lib'; import { FC, useState } from 'react'; interface PluginInstallModalProps { @@ -26,15 +26,14 @@ const PluginInstallModal: FC = ({ artifact, version, ha onCancel={async () => { await onCancel(); }} + strTitle={`Install ${artifact}`} + strOKButtonText={loading ? 'Installing' : 'Install'} > -
- {hash == 'False' ?

!!!!NO HASH PROVIDED!!!!

: null} -
- {loading && } {loading ? 'Installing' : 'Install'} {artifact} - {version ? ' version ' + version : null} - {!loading && '?'} -
-
+ {hash == 'False' ? ( +

!!!!NO HASH PROVIDED!!!!

+ ) : ( + `Are you sure you want to install ${artifact} ${version}?` + )} ); }; diff --git a/frontend/src/components/store/PluginCard.tsx b/frontend/src/components/store/PluginCard.tsx index aa5fd1d6..828d3ae9 100644 --- a/frontend/src/components/store/PluginCard.tsx +++ b/frontend/src/components/store/PluginCard.tsx @@ -1,15 +1,12 @@ import { - DialogButton, + ButtonItem, Dropdown, Focusable, - Navigation, - QuickAccessTab, + PanelSectionRow, SingleDropdownOption, SuspensefulImage, - joinClassNames, - staticClasses, } from 'decky-frontend-lib'; -import { FC, useRef, useState } from 'react'; +import { FC, useState } from 'react'; import { StorePlugin, StorePluginVersion, requestPluginInstall } from '../../store'; @@ -19,172 +16,162 @@ interface PluginCardProps { const PluginCard: FC = ({ plugin }) => { const [selectedOption, setSelectedOption] = useState(0); - const buttonRef = useRef(null); - const containerRef = useRef(null); + const root: boolean = plugin.tags.some((tag) => tag === 'root'); + return (
- {/* TODO: abstract this messy focus hackiness into a custom component in lib */} - { - buttonRef.current!.focus(); - }} - onCancel={(_: CustomEvent) => { - if (containerRef.current!.querySelectorAll('* :focus').length === 0) { - Navigation.NavigateBack(); - setTimeout(() => Navigation.OpenQuickAccessMenu(QuickAccessTab.Decky), 1000); - } else { - containerRef.current!.focus(); - } +
+ +
+
-
-
Router.NavigateToExternalWeb('https://github.com/' + plugin.artifact)} - > - {plugin.name} -
-
-
- -
+ + {plugin.author} + + + {plugin.description ? ( + plugin.description + ) : ( + + No description provided. + + )} + + {root && ( + -

- Author: {plugin.author} -

-

This plugin has full access to your Steam Deck.{' '} + - {plugin.description} -

-

- Tags: - {plugin.tags.map((tag: string) => ( - - {tag == 'root' ? 'Requires root' : tag} - - ))} -

-
-
+ deckbrew.xyz/root + + + )}
- -
- requestPluginInstall(plugin.name, plugin.versions[selectedOption])} + + +
- Install - -
-
- ({ - data: index, - label: version.name, - })) as SingleDropdownOption[] - } - strDefaultLabel={'Select a version'} - selectedOption={selectedOption} - onChange={({ data }) => setSelectedOption(data)} - /> -
-
+ requestPluginInstall(plugin.name, plugin.versions[selectedOption])} + > + Install + +
+
+ ({ + data: index, + label: version.name, + })) as SingleDropdownOption[] + } + menuLabel="Plugin Version" + selectedOption={selectedOption} + onChange={({ data }) => setSelectedOption(data)} + /> +
+
+
- +
); }; diff --git a/frontend/src/components/store/Store.tsx b/frontend/src/components/store/Store.tsx index 2ffd8efb..7a9c0e33 100644 --- a/frontend/src/components/store/Store.tsx +++ b/frontend/src/components/store/Store.tsx @@ -1,6 +1,16 @@ -import { SteamSpinner } from 'decky-frontend-lib'; -import { FC, useEffect, useState } from 'react'; +import { + Dropdown, + DropdownOption, + Focusable, + PanelSectionRow, + SteamSpinner, + Tabs, + TextField, + findModule, +} from 'decky-frontend-lib'; +import { FC, useEffect, useMemo, useState } from 'react'; +import logo from '../../../assets/plugin_store.png'; import Logger from '../../logger'; import { StorePlugin, getPluginList } from '../../store'; import PluginCard from './PluginCard'; @@ -8,7 +18,12 @@ import PluginCard from './PluginCard'; const logger = new Logger('FilePicker'); const StorePage: FC<{}> = () => { + const [currentTabRoute, setCurrentTabRoute] = useState('browse'); const [data, setData] = useState(null); + const { TabCount } = findModule((m) => { + if (m?.TabCount && m?.TabTitle) return true; + return false; + }); useEffect(() => { (async () => { @@ -19,19 +34,12 @@ const StorePage: FC<{}> = () => { }, []); return ( -
+ <>
{!data ? ( @@ -39,13 +47,193 @@ const StorePage: FC<{}> = () => {
) : ( -
- {data.map((plugin: StorePlugin) => ( - - ))} -
+ { + setCurrentTabRoute(tabId); + }} + tabs={[ + { + title: 'Browse', + content: , + id: 'browse', + renderTabAddon: () => {data.length}, + }, + { + title: 'About', + content: , + id: 'about', + }, + ]} + /> )}
+ + ); +}; + +const BrowseTab: FC<{ children: { data: StorePlugin[] } }> = (data) => { + const sortOptions = useMemo( + (): DropdownOption[] => [ + { data: 1, label: 'Alphabetical (A to Z)' }, + { data: 2, label: 'Alphabetical (Z to A)' }, + ], + [], + ); + + // const filterOptions = useMemo((): DropdownOption[] => [{ data: 1, label: 'All' }], []); + + const [selectedSort, setSort] = useState(sortOptions[0].data); + // const [selectedFilter, setFilter] = useState(filterOptions[0].data); + const [searchFieldValue, setSearchValue] = useState(''); + + return ( + <> + + {/* This should be used once filtering is added + + + +
+ Sort + setSort(e.data)} + /> +
+
+ Filter + setFilter(e.data)} + /> +
+
+
+
+ +
+ setSearchValue(e.target.value)} /> +
+
+
+ */} + + +
+ Sort + setSort(e.data)} + /> +
+
+
+
+ +
+ setSearchValue(e.target.value)} /> +
+
+
+
+ {data.children.data + .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())) + ); + }) + .sort((a, b) => { + if (selectedSort % 2 === 1) return a.name.localeCompare(b.name); + else return b.name.localeCompare(a.name); + }) + .map((plugin: StorePlugin) => ( + + ))} +
+ + ); +}; + +const AboutTab: FC<{}> = () => { + return ( +
+ + + Testing + + Please consider testing new plugins to help the Decky Loader team!{' '} + + deckbrew.xyz/testing + + + Contributing + + If you would like to contribute to the Decky Plugin Store, check the SteamDeckHomebrew/decky-plugin-template + repository on GitHub. Information on development and distribution is available in the README. + + Source Code + All plugin source code is available on SteamDeckHomebrew/decky-plugin-database repository on GitHub.
); }; -- cgit v1.2.3