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
+143 -156
View File
@@ -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>
);
};