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:
EMERALD
2023-01-19 20:00:42 -06:00
committed by GitHub
parent cbbd564860
commit 3ebaac6752
10 changed files with 408 additions and 202 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

+2
View File
@@ -0,0 +1,2 @@
declare module '*.png';
declare module '*.jpg';
+2 -1
View File
@@ -12,6 +12,7 @@
}, },
"devDependencies": { "devDependencies": {
"@rollup/plugin-commonjs": "^21.1.0", "@rollup/plugin-commonjs": "^21.1.0",
"@rollup/plugin-image": "^3.0.1",
"@rollup/plugin-json": "^4.1.0", "@rollup/plugin-json": "^4.1.0",
"@rollup/plugin-node-resolve": "^13.3.0", "@rollup/plugin-node-resolve": "^13.3.0",
"@rollup/plugin-replace": "^4.0.0", "@rollup/plugin-replace": "^4.0.0",
@@ -41,7 +42,7 @@
} }
}, },
"dependencies": { "dependencies": {
"decky-frontend-lib": "^3.18.9", "decky-frontend-lib": "^3.18.10",
"react-file-icon": "^1.2.0", "react-file-icon": "^1.2.0",
"react-icons": "^4.4.0", "react-icons": "^4.4.0",
"react-markdown": "^8.0.3", "react-markdown": "^8.0.3",
+40 -4
View File
@@ -2,6 +2,7 @@ lockfileVersion: 5.4
specifiers: specifiers:
'@rollup/plugin-commonjs': ^21.1.0 '@rollup/plugin-commonjs': ^21.1.0
'@rollup/plugin-image': ^3.0.1
'@rollup/plugin-json': ^4.1.0 '@rollup/plugin-json': ^4.1.0
'@rollup/plugin-node-resolve': ^13.3.0 '@rollup/plugin-node-resolve': ^13.3.0
'@rollup/plugin-replace': ^4.0.0 '@rollup/plugin-replace': ^4.0.0
@@ -10,7 +11,7 @@ specifiers:
'@types/react-file-icon': ^1.0.1 '@types/react-file-icon': ^1.0.1
'@types/react-router': 5.1.18 '@types/react-router': 5.1.18
'@types/webpack': ^5.28.0 '@types/webpack': ^5.28.0
decky-frontend-lib: ^3.18.9 decky-frontend-lib: ^3.18.10
husky: ^8.0.1 husky: ^8.0.1
import-sort-style-module: ^6.0.0 import-sort-style-module: ^6.0.0
inquirer: ^8.2.4 inquirer: ^8.2.4
@@ -30,7 +31,7 @@ specifiers:
typescript: ^4.7.4 typescript: ^4.7.4
dependencies: dependencies:
decky-frontend-lib: 3.18.9 decky-frontend-lib: 3.18.10
react-file-icon: 1.2.0_wcqkhtmu7mswc6yz4uyexck3ty react-file-icon: 1.2.0_wcqkhtmu7mswc6yz4uyexck3ty
react-icons: 4.4.0_react@16.14.0 react-icons: 4.4.0_react@16.14.0
react-markdown: 8.0.3_vshvapmxg47tngu7tvrsqpq55u react-markdown: 8.0.3_vshvapmxg47tngu7tvrsqpq55u
@@ -38,6 +39,7 @@ dependencies:
devDependencies: devDependencies:
'@rollup/plugin-commonjs': 21.1.0_rollup@2.76.0 '@rollup/plugin-commonjs': 21.1.0_rollup@2.76.0
'@rollup/plugin-image': 3.0.1_rollup@2.76.0
'@rollup/plugin-json': 4.1.0_rollup@2.76.0 '@rollup/plugin-json': 4.1.0_rollup@2.76.0
'@rollup/plugin-node-resolve': 13.3.0_rollup@2.76.0 '@rollup/plugin-node-resolve': 13.3.0_rollup@2.76.0
'@rollup/plugin-replace': 4.0.0_rollup@2.76.0 '@rollup/plugin-replace': 4.0.0_rollup@2.76.0
@@ -339,6 +341,20 @@ packages:
rollup: 2.76.0 rollup: 2.76.0
dev: true dev: true
/@rollup/plugin-image/3.0.1_rollup@2.76.0:
resolution: {integrity: sha512-F50Sko4Xcc576x7HG9f3MvJKKnBfSmqfVFWJkJgyIEkI8YxZxux28lDbuy0+GsAK6BFl9Gn+TRXOUgHHJbFh3w==}
engines: {node: '>=14.0.0'}
peerDependencies:
rollup: ^1.20.0||^2.0.0||^3.0.0
peerDependenciesMeta:
rollup:
optional: true
dependencies:
'@rollup/pluginutils': 5.0.2_rollup@2.76.0
mini-svg-data-uri: 1.4.4
rollup: 2.76.0
dev: true
/@rollup/plugin-inject/4.0.4_rollup@2.76.0: /@rollup/plugin-inject/4.0.4_rollup@2.76.0:
resolution: {integrity: sha512-4pbcU4J/nS+zuHk+c+OL3WtmEQhqxlZ9uqfjQMQDOHOPld7PsCd8k5LWs8h5wjwJN7MgnAn768F2sDxEP4eNFQ==} resolution: {integrity: sha512-4pbcU4J/nS+zuHk+c+OL3WtmEQhqxlZ9uqfjQMQDOHOPld7PsCd8k5LWs8h5wjwJN7MgnAn768F2sDxEP4eNFQ==}
peerDependencies: peerDependencies:
@@ -422,6 +438,21 @@ packages:
picomatch: 2.3.1 picomatch: 2.3.1
dev: true dev: true
/@rollup/pluginutils/5.0.2_rollup@2.76.0:
resolution: {integrity: sha512-pTd9rIsP92h+B6wWwFbW8RkZv4hiR/xKsqre4SIuAOaOEQRxi0lqLke9k2/7WegC85GgUs9pjmOjCUi3In4vwA==}
engines: {node: '>=14.0.0'}
peerDependencies:
rollup: ^1.20.0||^2.0.0||^3.0.0
peerDependenciesMeta:
rollup:
optional: true
dependencies:
'@types/estree': 1.0.0
estree-walker: 2.0.2
picomatch: 2.3.1
rollup: 2.76.0
dev: true
/@types/debug/4.1.7: /@types/debug/4.1.7:
resolution: {integrity: sha512-9AonUzyTjXXhEOa0DnqpzZi6VHlqKMswga9EXjpXnnqxwLtdvPPtlO8evrI5D9S6asFRCQ6v+wpiUKbw+vKqyg==} resolution: {integrity: sha512-9AonUzyTjXXhEOa0DnqpzZi6VHlqKMswga9EXjpXnnqxwLtdvPPtlO8evrI5D9S6asFRCQ6v+wpiUKbw+vKqyg==}
dependencies: dependencies:
@@ -944,8 +975,8 @@ packages:
dependencies: dependencies:
ms: 2.1.2 ms: 2.1.2
/decky-frontend-lib/3.18.9: /decky-frontend-lib/3.18.10:
resolution: {integrity: sha512-QNMHDDAHfL+JpvVVte4Vj8iyOqvz/2iyFEknbJ1/Kz7aPTygFUsJp5mq1FDVvVNjfCYfF3fYAaZVqZu3d7pCEA==} resolution: {integrity: sha512-2mgbA3sSkuwQR/FnmhXVrcW6LyTS95IuL6muJAmQCruhBvXapDtjk1TcgxqMZxFZwGD1IPnemPYxHZll6IgnZw==}
dev: false dev: false
/decode-named-character-reference/1.0.2: /decode-named-character-reference/1.0.2:
@@ -1936,6 +1967,11 @@ packages:
engines: {node: '>=6'} engines: {node: '>=6'}
dev: true dev: true
/mini-svg-data-uri/1.4.4:
resolution: {integrity: sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg==}
hasBin: true
dev: true
/minimatch/3.1.2: /minimatch/3.1.2:
resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
dependencies: dependencies:
+2
View File
@@ -1,4 +1,5 @@
import commonjs from '@rollup/plugin-commonjs'; import commonjs from '@rollup/plugin-commonjs';
import image from '@rollup/plugin-image';
import json from '@rollup/plugin-json'; import json from '@rollup/plugin-json';
import { nodeResolve } from '@rollup/plugin-node-resolve'; import { nodeResolve } from '@rollup/plugin-node-resolve';
import replace from '@rollup/plugin-replace'; import replace from '@rollup/plugin-replace';
@@ -29,6 +30,7 @@ export default defineConfig({
preventAssignment: false, preventAssignment: false,
'process.env.NODE_ENV': JSON.stringify('production'), 'process.env.NODE_ENV': JSON.stringify('production'),
}), }),
image(),
], ],
preserveEntrySignatures: false, preserveEntrySignatures: false,
output: { output: {
@@ -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'; import { FC, useState } from 'react';
interface PluginInstallModalProps { interface PluginInstallModalProps {
@@ -26,15 +26,14 @@ const PluginInstallModal: FC<PluginInstallModalProps> = ({ artifact, version, ha
onCancel={async () => { onCancel={async () => {
await onCancel(); await onCancel();
}} }}
strTitle={`Install ${artifact}`}
strOKButtonText={loading ? 'Installing' : 'Install'}
> >
<div className={staticClasses.Title} style={{ flexDirection: 'column' }}> {hash == 'False' ? (
{hash == 'False' ? <h3 style={{ color: 'red' }}>!!!!NO HASH PROVIDED!!!!</h3> : null} <h3 style={{ color: 'red' }}>!!!!NO HASH PROVIDED!!!!</h3>
<div style={{ flexDirection: 'row' }}> ) : (
{loading && <Spinner style={{ width: '20px' }} />} {loading ? 'Installing' : 'Install'} {artifact} `Are you sure you want to install ${artifact} ${version}?`
{version ? ' version ' + version : null} )}
{!loading && '?'}
</div>
</div>
</ConfirmModal> </ConfirmModal>
); );
}; };
+143 -156
View File
@@ -1,15 +1,12 @@
import { import {
DialogButton, ButtonItem,
Dropdown, Dropdown,
Focusable, Focusable,
Navigation, PanelSectionRow,
QuickAccessTab,
SingleDropdownOption, SingleDropdownOption,
SuspensefulImage, SuspensefulImage,
joinClassNames,
staticClasses,
} from 'decky-frontend-lib'; } from 'decky-frontend-lib';
import { FC, useRef, useState } from 'react'; import { FC, useState } from 'react';
import { StorePlugin, StorePluginVersion, requestPluginInstall } from '../../store'; import { StorePlugin, StorePluginVersion, requestPluginInstall } from '../../store';
@@ -19,172 +16,162 @@ interface PluginCardProps {
const PluginCard: FC<PluginCardProps> = ({ plugin }) => { const PluginCard: FC<PluginCardProps> = ({ plugin }) => {
const [selectedOption, setSelectedOption] = useState<number>(0); const [selectedOption, setSelectedOption] = useState<number>(0);
const buttonRef = useRef<HTMLDivElement>(null); const root: boolean = plugin.tags.some((tag) => tag === 'root');
const containerRef = useRef<HTMLDivElement>(null);
return ( return (
<div <div
className="deckyStoreCard"
style={{ style={{
padding: '30px', marginLeft: '20px',
paddingTop: '10px', marginRight: '20px',
paddingBottom: '10px', marginBottom: '20px',
display: 'flex',
alignItems: 'center',
}} }}
> >
{/* TODO: abstract this messy focus hackiness into a custom component in lib */} <div
<Focusable className="deckyStoreCardImageContainer"
className="deckyStoreCard"
ref={containerRef}
onActivate={(_: CustomEvent) => {
buttonRef.current!.focus();
}}
onCancel={(_: CustomEvent) => {
if (containerRef.current!.querySelectorAll('* :focus').length === 0) {
Navigation.NavigateBack();
setTimeout(() => Navigation.OpenQuickAccessMenu(QuickAccessTab.Decky), 1000);
} else {
containerRef.current!.focus();
}
}}
style={{ style={{
display: 'flex', width: '320px',
flexDirection: 'column', height: '200px',
background: '#ACB2C924', position: 'relative',
height: 'unset',
marginBottom: 'unset',
// boxShadow: var(--gpShadow-Medium);
scrollSnapAlign: 'start',
boxSizing: 'border-box',
}} }}
> >
<div className="deckyStoreCardHeader" style={{ display: 'flex', alignItems: 'center' }}> <SuspensefulImage
<div className="deckyStoreCardImage"
style={{ fontSize: '18pt', padding: '10px' }} suspenseHeight="200px"
className={joinClassNames(staticClasses.Text)} suspenseWidth="320px"
// onClick={() => Router.NavigateToExternalWeb('https://github.com/' + plugin.artifact)}
>
{plugin.name}
</div>
</div>
<div
style={{ style={{
display: 'flex', width: '320px',
flexDirection: 'row', height: '200px',
margin: '0 0 0 10px', objectFit: 'cover',
}} }}
className="deckyStoreCardBody" src={plugin.image_url}
> />
<SuspensefulImage </div>
className="deckyStoreCardImage" <div
suspenseWidth="256px" className="deckyStoreCardInfo"
style={{ style={{
width: 'auto', width: 'calc(100% - 320px)', // The calc is here so that the info section doesn't expand into the image
height: '160px', display: 'flex',
borderRadius: '5px', flexDirection: 'column',
}} height: '100%',
src={plugin.image_url} marginLeft: '1em',
/> justifyContent: 'center',
<div }}
style={{ >
display: 'flex', <span
flexDirection: 'column', className="deckyStoreCardTitle"
}}
className="deckyStoreCardInfo"
>
<p
className={joinClassNames(staticClasses.PanelSectionRow)}
style={{ marginTop: '0px', marginLeft: '16px' }}
>
<span style={{ paddingLeft: '0px' }}>Author: {plugin.author}</span>
</p>
<p
className={joinClassNames(staticClasses.PanelSectionRow)}
style={{
marginLeft: '16px',
marginTop: '0px',
marginBottom: '0px',
marginRight: '16px',
}}
>
<span style={{ paddingLeft: '0px' }}>{plugin.description}</span>
</p>
<p
className={joinClassNames('deckyStoreCardTagsContainer', staticClasses.PanelSectionRow)}
style={{
padding: '0 16px',
display: 'flex',
flexWrap: 'wrap',
gap: '5px 10px',
}}
>
<span style={{ padding: '5px 0' }}>Tags:</span>
{plugin.tags.map((tag: string) => (
<span
className="deckyStoreCardTag"
style={{
padding: '5px',
borderRadius: '5px',
background: tag == 'root' ? '#842029' : '#ACB2C947',
}}
>
{tag == 'root' ? 'Requires root' : tag}
</span>
))}
</p>
</div>
</div>
<div
className="deckyStoreCardActionsContainer"
style={{ style={{
fontSize: '1.25em',
fontWeight: 'bold',
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
width: '90%',
}}
>
{plugin.name}
</span>
<span
className="deckyStoreCardAuthor"
style={{
marginRight: 'auto',
fontSize: '1em',
}}
>
{plugin.author}
</span>
<span
className="deckyStoreCardDescription"
style={{
fontSize: '13px',
color: '#969696',
WebkitLineClamp: root ? '2' : '3',
WebkitBoxOrient: 'vertical',
overflow: 'hidden',
display: '-webkit-box',
}}
>
{plugin.description ? (
plugin.description
) : (
<span>
<i style={{ color: '#666' }}>No description provided.</i>
</span>
)}
</span>
{root && (
<span
className="deckyStoreCardDescription deckyStoreCardDescriptionRoot"
style={{
fontSize: '13px',
color: '#fee75c',
}}
>
<i>This plugin has full access to your Steam Deck.</i>{' '}
<a
className="deckyStoreCardDescriptionRootLink"
href="https://deckbrew.xyz/root"
target="_blank"
style={{
color: '#fee75c',
textDecoration: 'none',
}}
>
deckbrew.xyz/root
</a>
</span>
)}
<div
className="deckyStoreCardButtonRow"
style={{
marginTop: '1em',
width: '100%', width: '100%',
alignSelf: 'flex-end', overflow: 'hidden',
display: 'flex',
flexDirection: 'row',
}} }}
> >
<Focusable <PanelSectionRow>
className="deckyStoreCardActions" <Focusable style={{ display: 'flex', maxWidth: '100%' }}>
style={{ <div
display: 'flex', className="deckyStoreCardInstallContainer"
flexDirection: 'row', style={{
width: '100%', paddingTop: '0px',
margin: '10px', paddingBottom: '0px',
}} width: '40%',
> }}
<div
className="deckyStoreCardInstallButtonContainer"
style={{
flex: '1',
margin: '0 10px 0 0',
}}
>
<DialogButton
className="deckyStoreCardInstallButton"
ref={buttonRef}
onClick={() => requestPluginInstall(plugin.name, plugin.versions[selectedOption])}
> >
Install <ButtonItem
</DialogButton> bottomSeparator="none"
</div> layout="below"
<div onClick={() => requestPluginInstall(plugin.name, plugin.versions[selectedOption])}
className="deckyStoreCardVersionDropdownContainer" >
style={{ <span className="deckyStoreCardInstallText">Install</span>
flex: '0.2', </ButtonItem>
}} </div>
> <div
<Dropdown className="deckyStoreCardVersionContainer"
rgOptions={ style={{
plugin.versions.map((version: StorePluginVersion, index) => ({ marginLeft: '5%',
data: index, width: '30%',
label: version.name, }}
})) as SingleDropdownOption[] >
} <Dropdown
strDefaultLabel={'Select a version'} rgOptions={
selectedOption={selectedOption} plugin.versions.map((version: StorePluginVersion, index) => ({
onChange={({ data }) => setSelectedOption(data)} data: index,
/> label: version.name,
</div> })) as SingleDropdownOption[]
</Focusable> }
menuLabel="Plugin Version"
selectedOption={selectedOption}
onChange={({ data }) => setSelectedOption(data)}
/>
</div>
</Focusable>
</PanelSectionRow>
</div> </div>
</Focusable> </div>
</div> </div>
); );
}; };
+206 -18
View File
@@ -1,6 +1,16 @@
import { SteamSpinner } from 'decky-frontend-lib'; import {
import { FC, useEffect, useState } from 'react'; 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 Logger from '../../logger';
import { StorePlugin, getPluginList } from '../../store'; import { StorePlugin, getPluginList } from '../../store';
import PluginCard from './PluginCard'; import PluginCard from './PluginCard';
@@ -8,7 +18,12 @@ import PluginCard from './PluginCard';
const logger = new Logger('FilePicker'); const logger = new Logger('FilePicker');
const StorePage: FC<{}> = () => { const StorePage: FC<{}> = () => {
const [currentTabRoute, setCurrentTabRoute] = useState<string>('browse');
const [data, setData] = useState<StorePlugin[] | null>(null); const [data, setData] = useState<StorePlugin[] | null>(null);
const { TabCount } = findModule((m) => {
if (m?.TabCount && m?.TabTitle) return true;
return false;
});
useEffect(() => { useEffect(() => {
(async () => { (async () => {
@@ -19,19 +34,12 @@ const StorePage: FC<{}> = () => {
}, []); }, []);
return ( return (
<div <>
style={{
marginTop: '40px',
height: 'calc( 100% - 40px )',
overflowY: 'scroll',
}}
>
<div <div
style={{ style={{
display: 'flex', marginTop: '40px',
flexWrap: 'nowrap', height: 'calc( 100% - 40px )',
flexDirection: 'column', background: '#0005',
height: '100%',
}} }}
> >
{!data ? ( {!data ? (
@@ -39,13 +47,193 @@ const StorePage: FC<{}> = () => {
<SteamSpinner /> <SteamSpinner />
</div> </div>
) : ( ) : (
<div> <Tabs
{data.map((plugin: StorePlugin) => ( activeTab={currentTabRoute}
<PluginCard plugin={plugin} /> onShowTab={(tabId: string) => {
))} setCurrentTabRoute(tabId);
</div> }}
tabs={[
{
title: 'Browse',
content: <BrowseTab children={{ data: data }} />,
id: 'browse',
renderTabAddon: () => <span className={TabCount}>{data.length}</span>,
},
{
title: 'About',
content: <AboutTab />,
id: 'about',
},
]}
/>
)} )}
</div> </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> </div>
); );
}; };
+4 -13
View File
@@ -1,13 +1,4 @@
import { import { ConfirmModal, ModalRoot, Patch, QuickAccessTab, Router, showModal, sleep } from 'decky-frontend-lib';
ConfirmModal,
ModalRoot,
Patch,
QuickAccessTab,
Router,
showModal,
sleep,
staticClasses,
} from 'decky-frontend-lib';
import { FC, lazy } from 'react'; import { FC, lazy } from 'react';
import { FaCog, FaExclamationCircle, FaPlug } from 'react-icons/fa'; import { FaCog, FaExclamationCircle, FaPlug } from 'react-icons/fa';
@@ -155,10 +146,10 @@ class PluginLoader extends Logger {
onCancel={() => { onCancel={() => {
// do nothing // do nothing
}} }}
strTitle={`Uninstall ${name}`}
strOKButtonText={'Uninstall'}
> >
<div className={staticClasses.Title} style={{ flexDirection: 'column' }}> Are you sure you want to uninstall {name}?
Uninstall {name}?
</div>
</ConfirmModal>, </ConfirmModal>,
); );
} }
+1 -1
View File
@@ -18,6 +18,6 @@
"allowSyntheticDefaultImports": true, "allowSyntheticDefaultImports": true,
"skipLibCheck": true "skipLibCheck": true
}, },
"include": ["src"], "include": ["src", "index.d.ts"],
"exclude": ["node_modules"] "exclude": ["node_modules"]
} }