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,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>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user