mirror of
https://github.com/SteamDeckHomebrew/decky-loader.git
synced 2026-06-17 00:37:49 +00:00
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
This commit is contained in:
@@ -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<string>('browse');
|
||||
const [data, setData] = useState<StorePlugin[] | null>(null);
|
||||
const { TabCount } = findModule((m) => {
|
||||
if (m?.TabCount && m?.TabTitle) return true;
|
||||
return false;
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
@@ -19,19 +34,12 @@ const StorePage: FC<{}> = () => {
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
marginTop: '40px',
|
||||
height: 'calc( 100% - 40px )',
|
||||
overflowY: 'scroll',
|
||||
}}
|
||||
>
|
||||
<>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexWrap: 'nowrap',
|
||||
flexDirection: 'column',
|
||||
height: '100%',
|
||||
marginTop: '40px',
|
||||
height: 'calc( 100% - 40px )',
|
||||
background: '#0005',
|
||||
}}
|
||||
>
|
||||
{!data ? (
|
||||
@@ -39,13 +47,193 @@ const StorePage: FC<{}> = () => {
|
||||
<SteamSpinner />
|
||||
</div>
|
||||
) : (
|
||||
<div>
|
||||
{data.map((plugin: StorePlugin) => (
|
||||
<PluginCard plugin={plugin} />
|
||||
))}
|
||||
</div>
|
||||
<Tabs
|
||||
activeTab={currentTabRoute}
|
||||
onShowTab={(tabId: string) => {
|
||||
setCurrentTabRoute(tabId);
|
||||
}}
|
||||
tabs={[
|
||||
{
|
||||
title: 'Browse',
|
||||
content: <BrowseTab children={{ data: data }} />,
|
||||
id: 'browse',
|
||||
renderTabAddon: () => <span className={TabCount}>{data.length}</span>,
|
||||
},
|
||||
{
|
||||
title: 'About',
|
||||
content: <AboutTab />,
|
||||
id: 'about',
|
||||
},
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
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<number>(sortOptions[0].data);
|
||||
// const [selectedFilter, setFilter] = useState<number>(filterOptions[0].data);
|
||||
const [searchFieldValue, setSearchValue] = useState<string>('');
|
||||
|
||||
return (
|
||||
<>
|
||||
<style>{`
|
||||
.deckyStoreCardInstallContainer > .Panel {
|
||||
padding: 0;
|
||||
}
|
||||
`}</style>
|
||||
{/* This should be used once filtering is added
|
||||
|
||||
<PanelSectionRow>
|
||||
<Focusable style={{ display: 'flex', maxWidth: '100%' }}>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
width: '47.5%',
|
||||
}}
|
||||
>
|
||||
<span className="DialogLabel">Sort</span>
|
||||
<Dropdown
|
||||
menuLabel="Sort"
|
||||
rgOptions={sortOptions}
|
||||
strDefaultLabel="Last Updated (Newest)"
|
||||
selectedOption={selectedSort}
|
||||
onChange={(e) => setSort(e.data)}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
width: '47.5%',
|
||||
marginLeft: 'auto',
|
||||
}}
|
||||
>
|
||||
<span className="DialogLabel">Filter</span>
|
||||
<Dropdown
|
||||
menuLabel="Filter"
|
||||
rgOptions={filterOptions}
|
||||
strDefaultLabel="All"
|
||||
selectedOption={selectedFilter}
|
||||
onChange={(e) => setFilter(e.data)}
|
||||
/>
|
||||
</div>
|
||||
</Focusable>
|
||||
</PanelSectionRow>
|
||||
<div style={{ justifyContent: 'center', display: 'flex' }}>
|
||||
<Focusable style={{ display: 'flex', alignItems: 'center', width: '96%' }}>
|
||||
<div style={{ width: '100%' }}>
|
||||
<TextField label="Search" value={searchFieldValue} onChange={(e) => setSearchValue(e.target.value)} />
|
||||
</div>
|
||||
</Focusable>
|
||||
</div>
|
||||
*/}
|
||||
<PanelSectionRow>
|
||||
<Focusable style={{ display: 'flex', maxWidth: '100%' }}>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
minWidth: '100%',
|
||||
maxWidth: '100%',
|
||||
}}
|
||||
>
|
||||
<span className="DialogLabel">Sort</span>
|
||||
<Dropdown
|
||||
menuLabel="Sort"
|
||||
rgOptions={sortOptions}
|
||||
strDefaultLabel="Last Updated (Newest)"
|
||||
selectedOption={selectedSort}
|
||||
onChange={(e) => setSort(e.data)}
|
||||
/>
|
||||
</div>
|
||||
</Focusable>
|
||||
</PanelSectionRow>
|
||||
<div style={{ justifyContent: 'center', display: 'flex' }}>
|
||||
<Focusable style={{ display: 'flex', alignItems: 'center', width: '96%' }}>
|
||||
<div style={{ width: '100%' }}>
|
||||
<TextField label="Search" value={searchFieldValue} onChange={(e) => setSearchValue(e.target.value)} />
|
||||
</div>
|
||||
</Focusable>
|
||||
</div>
|
||||
<div>
|
||||
{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) => (
|
||||
<PluginCard plugin={plugin} />
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const AboutTab: FC<{}> = () => {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
}}
|
||||
>
|
||||
<style>{`
|
||||
.deckyStoreAboutHeader {
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
margin-top: 20px;
|
||||
}
|
||||
`}</style>
|
||||
<img
|
||||
src={logo}
|
||||
style={{
|
||||
width: '256px',
|
||||
height: 'auto',
|
||||
alignSelf: 'center',
|
||||
}}
|
||||
/>
|
||||
<span className="deckyStoreAboutHeader">Testing</span>
|
||||
<span>
|
||||
Please consider testing new plugins to help the Decky Loader team!{' '}
|
||||
<a
|
||||
href="https://deckbrew.xyz/testing"
|
||||
target="_blank"
|
||||
style={{
|
||||
textDecoration: 'none',
|
||||
}}
|
||||
>
|
||||
deckbrew.xyz/testing
|
||||
</a>
|
||||
</span>
|
||||
<span className="deckyStoreAboutHeader">Contributing</span>
|
||||
<span>
|
||||
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.
|
||||
</span>
|
||||
<span className="deckyStoreAboutHeader">Source Code</span>
|
||||
<span>All plugin source code is available on SteamDeckHomebrew/decky-plugin-database repository on GitHub.</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user