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": {
|
"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",
|
||||||
|
|||||||
Generated
+40
-4
@@ -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:
|
||||||
|
|||||||
@@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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>,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"]
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user