mirror of
https://github.com/SteamDeckHomebrew/decky-loader.git
synced 2026-06-13 04:05:04 +03: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:
Binary file not shown.
|
After Width: | Height: | Size: 55 KiB |
Vendored
+2
@@ -0,0 +1,2 @@
|
||||
declare module '*.png';
|
||||
declare module '*.jpg';
|
||||
@@ -12,6 +12,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rollup/plugin-commonjs": "^21.1.0",
|
||||
"@rollup/plugin-image": "^3.0.1",
|
||||
"@rollup/plugin-json": "^4.1.0",
|
||||
"@rollup/plugin-node-resolve": "^13.3.0",
|
||||
"@rollup/plugin-replace": "^4.0.0",
|
||||
@@ -41,7 +42,7 @@
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"decky-frontend-lib": "^3.18.9",
|
||||
"decky-frontend-lib": "^3.18.10",
|
||||
"react-file-icon": "^1.2.0",
|
||||
"react-icons": "^4.4.0",
|
||||
"react-markdown": "^8.0.3",
|
||||
|
||||
Generated
+40
-4
@@ -2,6 +2,7 @@ lockfileVersion: 5.4
|
||||
|
||||
specifiers:
|
||||
'@rollup/plugin-commonjs': ^21.1.0
|
||||
'@rollup/plugin-image': ^3.0.1
|
||||
'@rollup/plugin-json': ^4.1.0
|
||||
'@rollup/plugin-node-resolve': ^13.3.0
|
||||
'@rollup/plugin-replace': ^4.0.0
|
||||
@@ -10,7 +11,7 @@ specifiers:
|
||||
'@types/react-file-icon': ^1.0.1
|
||||
'@types/react-router': 5.1.18
|
||||
'@types/webpack': ^5.28.0
|
||||
decky-frontend-lib: ^3.18.9
|
||||
decky-frontend-lib: ^3.18.10
|
||||
husky: ^8.0.1
|
||||
import-sort-style-module: ^6.0.0
|
||||
inquirer: ^8.2.4
|
||||
@@ -30,7 +31,7 @@ specifiers:
|
||||
typescript: ^4.7.4
|
||||
|
||||
dependencies:
|
||||
decky-frontend-lib: 3.18.9
|
||||
decky-frontend-lib: 3.18.10
|
||||
react-file-icon: 1.2.0_wcqkhtmu7mswc6yz4uyexck3ty
|
||||
react-icons: 4.4.0_react@16.14.0
|
||||
react-markdown: 8.0.3_vshvapmxg47tngu7tvrsqpq55u
|
||||
@@ -38,6 +39,7 @@ dependencies:
|
||||
|
||||
devDependencies:
|
||||
'@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-node-resolve': 13.3.0_rollup@2.76.0
|
||||
'@rollup/plugin-replace': 4.0.0_rollup@2.76.0
|
||||
@@ -339,6 +341,20 @@ packages:
|
||||
rollup: 2.76.0
|
||||
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:
|
||||
resolution: {integrity: sha512-4pbcU4J/nS+zuHk+c+OL3WtmEQhqxlZ9uqfjQMQDOHOPld7PsCd8k5LWs8h5wjwJN7MgnAn768F2sDxEP4eNFQ==}
|
||||
peerDependencies:
|
||||
@@ -422,6 +438,21 @@ packages:
|
||||
picomatch: 2.3.1
|
||||
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:
|
||||
resolution: {integrity: sha512-9AonUzyTjXXhEOa0DnqpzZi6VHlqKMswga9EXjpXnnqxwLtdvPPtlO8evrI5D9S6asFRCQ6v+wpiUKbw+vKqyg==}
|
||||
dependencies:
|
||||
@@ -944,8 +975,8 @@ packages:
|
||||
dependencies:
|
||||
ms: 2.1.2
|
||||
|
||||
/decky-frontend-lib/3.18.9:
|
||||
resolution: {integrity: sha512-QNMHDDAHfL+JpvVVte4Vj8iyOqvz/2iyFEknbJ1/Kz7aPTygFUsJp5mq1FDVvVNjfCYfF3fYAaZVqZu3d7pCEA==}
|
||||
/decky-frontend-lib/3.18.10:
|
||||
resolution: {integrity: sha512-2mgbA3sSkuwQR/FnmhXVrcW6LyTS95IuL6muJAmQCruhBvXapDtjk1TcgxqMZxFZwGD1IPnemPYxHZll6IgnZw==}
|
||||
dev: false
|
||||
|
||||
/decode-named-character-reference/1.0.2:
|
||||
@@ -1936,6 +1967,11 @@ packages:
|
||||
engines: {node: '>=6'}
|
||||
dev: true
|
||||
|
||||
/mini-svg-data-uri/1.4.4:
|
||||
resolution: {integrity: sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg==}
|
||||
hasBin: true
|
||||
dev: true
|
||||
|
||||
/minimatch/3.1.2:
|
||||
resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
|
||||
dependencies:
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import commonjs from '@rollup/plugin-commonjs';
|
||||
import image from '@rollup/plugin-image';
|
||||
import json from '@rollup/plugin-json';
|
||||
import { nodeResolve } from '@rollup/plugin-node-resolve';
|
||||
import replace from '@rollup/plugin-replace';
|
||||
@@ -29,6 +30,7 @@ export default defineConfig({
|
||||
preventAssignment: false,
|
||||
'process.env.NODE_ENV': JSON.stringify('production'),
|
||||
}),
|
||||
image(),
|
||||
],
|
||||
preserveEntrySignatures: false,
|
||||
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';
|
||||
|
||||
interface PluginInstallModalProps {
|
||||
@@ -26,15 +26,14 @@ const PluginInstallModal: FC<PluginInstallModalProps> = ({ artifact, version, ha
|
||||
onCancel={async () => {
|
||||
await onCancel();
|
||||
}}
|
||||
strTitle={`Install ${artifact}`}
|
||||
strOKButtonText={loading ? 'Installing' : 'Install'}
|
||||
>
|
||||
<div className={staticClasses.Title} style={{ flexDirection: 'column' }}>
|
||||
{hash == 'False' ? <h3 style={{ color: 'red' }}>!!!!NO HASH PROVIDED!!!!</h3> : null}
|
||||
<div style={{ flexDirection: 'row' }}>
|
||||
{loading && <Spinner style={{ width: '20px' }} />} {loading ? 'Installing' : 'Install'} {artifact}
|
||||
{version ? ' version ' + version : null}
|
||||
{!loading && '?'}
|
||||
</div>
|
||||
</div>
|
||||
{hash == 'False' ? (
|
||||
<h3 style={{ color: 'red' }}>!!!!NO HASH PROVIDED!!!!</h3>
|
||||
) : (
|
||||
`Are you sure you want to install ${artifact} ${version}?`
|
||||
)}
|
||||
</ConfirmModal>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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<PluginCardProps> = ({ plugin }) => {
|
||||
const [selectedOption, setSelectedOption] = useState<number>(0);
|
||||
const buttonRef = useRef<HTMLDivElement>(null);
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const root: boolean = plugin.tags.some((tag) => tag === 'root');
|
||||
|
||||
return (
|
||||
<div
|
||||
className="deckyStoreCard"
|
||||
style={{
|
||||
padding: '30px',
|
||||
paddingTop: '10px',
|
||||
paddingBottom: '10px',
|
||||
marginLeft: '20px',
|
||||
marginRight: '20px',
|
||||
marginBottom: '20px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
{/* TODO: abstract this messy focus hackiness into a custom component in lib */}
|
||||
<Focusable
|
||||
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();
|
||||
}
|
||||
}}
|
||||
<div
|
||||
className="deckyStoreCardImageContainer"
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
background: '#ACB2C924',
|
||||
height: 'unset',
|
||||
marginBottom: 'unset',
|
||||
// boxShadow: var(--gpShadow-Medium);
|
||||
scrollSnapAlign: 'start',
|
||||
boxSizing: 'border-box',
|
||||
width: '320px',
|
||||
height: '200px',
|
||||
position: 'relative',
|
||||
}}
|
||||
>
|
||||
<div className="deckyStoreCardHeader" style={{ display: 'flex', alignItems: 'center' }}>
|
||||
<div
|
||||
style={{ fontSize: '18pt', padding: '10px' }}
|
||||
className={joinClassNames(staticClasses.Text)}
|
||||
// onClick={() => Router.NavigateToExternalWeb('https://github.com/' + plugin.artifact)}
|
||||
>
|
||||
{plugin.name}
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
<SuspensefulImage
|
||||
className="deckyStoreCardImage"
|
||||
suspenseHeight="200px"
|
||||
suspenseWidth="320px"
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
margin: '0 0 0 10px',
|
||||
width: '320px',
|
||||
height: '200px',
|
||||
objectFit: 'cover',
|
||||
}}
|
||||
className="deckyStoreCardBody"
|
||||
>
|
||||
<SuspensefulImage
|
||||
className="deckyStoreCardImage"
|
||||
suspenseWidth="256px"
|
||||
style={{
|
||||
width: 'auto',
|
||||
height: '160px',
|
||||
borderRadius: '5px',
|
||||
}}
|
||||
src={plugin.image_url}
|
||||
/>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
}}
|
||||
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"
|
||||
src={plugin.image_url}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className="deckyStoreCardInfo"
|
||||
style={{
|
||||
width: 'calc(100% - 320px)', // The calc is here so that the info section doesn't expand into the image
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
height: '100%',
|
||||
marginLeft: '1em',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
<span
|
||||
className="deckyStoreCardTitle"
|
||||
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%',
|
||||
alignSelf: 'flex-end',
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
overflow: 'hidden',
|
||||
}}
|
||||
>
|
||||
<Focusable
|
||||
className="deckyStoreCardActions"
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
width: '100%',
|
||||
margin: '10px',
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className="deckyStoreCardInstallButtonContainer"
|
||||
style={{
|
||||
flex: '1',
|
||||
margin: '0 10px 0 0',
|
||||
}}
|
||||
>
|
||||
<DialogButton
|
||||
className="deckyStoreCardInstallButton"
|
||||
ref={buttonRef}
|
||||
onClick={() => requestPluginInstall(plugin.name, plugin.versions[selectedOption])}
|
||||
<PanelSectionRow>
|
||||
<Focusable style={{ display: 'flex', maxWidth: '100%' }}>
|
||||
<div
|
||||
className="deckyStoreCardInstallContainer"
|
||||
style={{
|
||||
paddingTop: '0px',
|
||||
paddingBottom: '0px',
|
||||
width: '40%',
|
||||
}}
|
||||
>
|
||||
Install
|
||||
</DialogButton>
|
||||
</div>
|
||||
<div
|
||||
className="deckyStoreCardVersionDropdownContainer"
|
||||
style={{
|
||||
flex: '0.2',
|
||||
}}
|
||||
>
|
||||
<Dropdown
|
||||
rgOptions={
|
||||
plugin.versions.map((version: StorePluginVersion, index) => ({
|
||||
data: index,
|
||||
label: version.name,
|
||||
})) as SingleDropdownOption[]
|
||||
}
|
||||
strDefaultLabel={'Select a version'}
|
||||
selectedOption={selectedOption}
|
||||
onChange={({ data }) => setSelectedOption(data)}
|
||||
/>
|
||||
</div>
|
||||
</Focusable>
|
||||
<ButtonItem
|
||||
bottomSeparator="none"
|
||||
layout="below"
|
||||
onClick={() => requestPluginInstall(plugin.name, plugin.versions[selectedOption])}
|
||||
>
|
||||
<span className="deckyStoreCardInstallText">Install</span>
|
||||
</ButtonItem>
|
||||
</div>
|
||||
<div
|
||||
className="deckyStoreCardVersionContainer"
|
||||
style={{
|
||||
marginLeft: '5%',
|
||||
width: '30%',
|
||||
}}
|
||||
>
|
||||
<Dropdown
|
||||
rgOptions={
|
||||
plugin.versions.map((version: StorePluginVersion, index) => ({
|
||||
data: index,
|
||||
label: version.name,
|
||||
})) as SingleDropdownOption[]
|
||||
}
|
||||
menuLabel="Plugin Version"
|
||||
selectedOption={selectedOption}
|
||||
onChange={({ data }) => setSelectedOption(data)}
|
||||
/>
|
||||
</div>
|
||||
</Focusable>
|
||||
</PanelSectionRow>
|
||||
</div>
|
||||
</Focusable>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,13 +1,4 @@
|
||||
import {
|
||||
ConfirmModal,
|
||||
ModalRoot,
|
||||
Patch,
|
||||
QuickAccessTab,
|
||||
Router,
|
||||
showModal,
|
||||
sleep,
|
||||
staticClasses,
|
||||
} from 'decky-frontend-lib';
|
||||
import { ConfirmModal, ModalRoot, Patch, QuickAccessTab, Router, showModal, sleep } from 'decky-frontend-lib';
|
||||
import { FC, lazy } from 'react';
|
||||
import { FaCog, FaExclamationCircle, FaPlug } from 'react-icons/fa';
|
||||
|
||||
@@ -155,10 +146,10 @@ class PluginLoader extends Logger {
|
||||
onCancel={() => {
|
||||
// do nothing
|
||||
}}
|
||||
strTitle={`Uninstall ${name}`}
|
||||
strOKButtonText={'Uninstall'}
|
||||
>
|
||||
<div className={staticClasses.Title} style={{ flexDirection: 'column' }}>
|
||||
Uninstall {name}?
|
||||
</div>
|
||||
Are you sure you want to uninstall {name}?
|
||||
</ConfirmModal>,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -18,6 +18,6 @@
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"skipLibCheck": true
|
||||
},
|
||||
"include": ["src"],
|
||||
"include": ["src", "index.d.ts"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user