summaryrefslogtreecommitdiff
path: root/frontend/src
diff options
context:
space:
mode:
authorAAGaming <aagaming00@protonmail.com>2022-06-17 18:43:53 -0400
committerGitHub <noreply@github.com>2022-06-17 18:43:53 -0400
commit99b4b939bdd2140aecf19ddb09a59b44e9cd117d (patch)
treed1a4c154101cb43b34c782a310e9c0699c9cf005 /frontend/src
parenta95bf94d878f61869895bb22cbff1b4f524c5dca (diff)
downloaddecky-loader-99b4b939bdd2140aecf19ddb09a59b44e9cd117d.tar.gz
decky-loader-99b4b939bdd2140aecf19ddb09a59b44e9cd117d.zip
Implement React-based plugin store (#81)
Co-authored-by: TrainDoctor <11465594+TrainDoctor@users.noreply.github.com> Co-authored-by: WerWolv <werwolv98@gmail.com>
Diffstat (limited to 'frontend/src')
-rw-r--r--frontend/src/components/PluginView.tsx2
-rw-r--r--frontend/src/components/store/PluginCard.tsx172
-rw-r--r--frontend/src/components/store/Store.tsx55
-rw-r--r--frontend/src/index.tsx1
-rw-r--r--frontend/src/plugin-loader.tsx7
-rw-r--r--frontend/src/router-hook.tsx4
6 files changed, 240 insertions, 1 deletions
diff --git a/frontend/src/components/PluginView.tsx b/frontend/src/components/PluginView.tsx
index 78bb22c2..92650fec 100644
--- a/frontend/src/components/PluginView.tsx
+++ b/frontend/src/components/PluginView.tsx
@@ -9,7 +9,7 @@ const PluginView: VFC = () => {
const onStoreClick = () => {
Router.CloseSideMenus();
- Router.NavigateToExternalWeb('http://127.0.0.1:1337/browser/redirect');
+ Router.Navigate('/decky/store');
};
if (activePlugin) {
diff --git a/frontend/src/components/store/PluginCard.tsx b/frontend/src/components/store/PluginCard.tsx
new file mode 100644
index 00000000..7816d1bb
--- /dev/null
+++ b/frontend/src/components/store/PluginCard.tsx
@@ -0,0 +1,172 @@
+import {
+ DialogButton,
+ Dropdown,
+ Focusable,
+ Router,
+ SingleDropdownOption,
+ SuspensefulImage,
+ staticClasses,
+} from 'decky-frontend-lib';
+import { FC, useRef, useState } from 'react';
+
+import { StorePlugin } from './Store';
+
+interface PluginCardProps {
+ plugin: StorePlugin;
+}
+
+const classNames = (...classes: string[]) => {
+ return classes.join(' ');
+};
+
+async function requestPluginInstall(plugin: StorePlugin, selectedVer: string) {
+ const formData = new FormData();
+ formData.append('artifact', plugin.artifact);
+ formData.append('version', selectedVer);
+ formData.append('hash', plugin.versions[selectedVer]);
+ await fetch('http://localhost:1337/browser/install_plugin', {
+ method: 'POST',
+ body: formData,
+ });
+}
+
+const PluginCard: FC<PluginCardProps> = ({ plugin }) => {
+ const [selectedOption, setSelectedOption] = useState<number>(0);
+ const buttonRef = useRef<HTMLDivElement>(null);
+ const containerRef = useRef<HTMLDivElement>(null);
+ return (
+ <div
+ style={{
+ padding: '30px',
+ paddingTop: '10px',
+ paddingBottom: '10px',
+ }}
+ >
+ {/* TODO: abstract this messy focus hackiness into a custom component in lib */}
+ <Focusable
+ // className="Panel Focusable"
+ ref={containerRef}
+ onActivate={(e: CustomEvent) => {
+ buttonRef.current!.focus();
+ }}
+ onCancel={(e: CustomEvent) => {
+ containerRef.current!.querySelectorAll('* :focus').length === 0
+ ? Router.NavigateBackOrOpenMenu()
+ : containerRef.current!.focus();
+ }}
+ style={{
+ display: 'flex',
+ flexDirection: 'column',
+ background: '#ACB2C924',
+ height: 'unset',
+ marginBottom: 'unset',
+ // boxShadow: var(--gpShadow-Medium);
+ scrollSnapAlign: 'start',
+ boxSizing: 'border-box',
+ }}
+ >
+ <div style={{ display: 'flex', alignItems: 'center' }}>
+ <a
+ style={{ fontSize: '18pt', padding: '10px' }}
+ className={classNames(staticClasses.Text)}
+ onClick={() => Router.NavigateToExternalWeb('https://github.com/' + plugin.artifact)}
+ >
+ <span style={{ color: 'grey' }}>{plugin.artifact.split('/')[0]}/</span>
+ {plugin.artifact.split('/')[1]}
+ </a>
+ </div>
+ <div
+ style={{
+ display: 'flex',
+ flexDirection: 'row',
+ }}
+ >
+ <SuspensefulImage
+ suspenseWidth="256px"
+ style={{
+ width: 'auto',
+ height: '160px',
+ }}
+ src={`https://cdn.tzatzikiweeb.moe/file/steam-deck-homebrew/artifact_images/${plugin.artifact.replace(
+ '/',
+ '_',
+ )}.png`}
+ />
+ <div
+ style={{
+ display: 'flex',
+ flexDirection: 'column',
+ }}
+ >
+ <p className={classNames(staticClasses.PanelSectionRow)}>
+ <span>Author: {plugin.author}</span>
+ </p>
+ <p className={classNames(staticClasses.PanelSectionRow)}>
+ <span>Tags:</span>
+ {plugin.tags.map((tag: string) => (
+ <span
+ style={{
+ padding: '5px',
+ marginRight: '10px',
+ borderRadius: '5px',
+ background: tag == 'root' ? '#842029' : '#ACB2C947',
+ }}
+ >
+ {tag == 'root' ? 'Requires root' : tag}
+ </span>
+ ))}
+ </p>
+ </div>
+ </div>
+ <div
+ style={{
+ width: '100%',
+ alignSelf: 'flex-end',
+ display: 'flex',
+ flexDirection: 'row',
+ }}
+ >
+ <Focusable
+ style={{
+ display: 'flex',
+ flexDirection: 'row',
+ width: '100%',
+ }}
+ >
+ <div
+ style={{
+ flex: '1',
+ }}
+ >
+ <DialogButton
+ ref={buttonRef}
+ onClick={() => requestPluginInstall(plugin, Object.keys(plugin.versions)[selectedOption])}
+ >
+ Install
+ </DialogButton>
+ </div>
+ <div
+ style={{
+ flex: '0.2',
+ }}
+ >
+ <Dropdown
+ rgOptions={
+ Object.keys(plugin.versions).map((v, k) => ({
+ data: k,
+ label: v,
+ })) as SingleDropdownOption[]
+ }
+ strDefaultLabel={'Select a version'}
+ selectedOption={selectedOption}
+ onChange={({ data }) => setSelectedOption(data)}
+ />
+ </div>
+ </Focusable>
+ </div>
+ </Focusable>
+ </div>
+ );
+};
+
+export default PluginCard;
diff --git a/frontend/src/components/store/Store.tsx b/frontend/src/components/store/Store.tsx
new file mode 100644
index 00000000..ebb2bb8e
--- /dev/null
+++ b/frontend/src/components/store/Store.tsx
@@ -0,0 +1,55 @@
+import { SteamSpinner } from 'decky-frontend-lib';
+import { FC, useEffect, useState } from 'react';
+
+import PluginCard from './PluginCard';
+
+export interface StorePlugin {
+ artifact: string;
+ versions: {
+ [version: string]: string;
+ };
+ author: string;
+ description: string;
+ tags: string[];
+}
+
+const StorePage: FC<{}> = () => {
+ const [data, setData] = useState<StorePlugin[] | null>(null);
+
+ useEffect(() => {
+ (async () => {
+ const res = await fetch('https://beta.deckbrew.xyz/get_plugins', { method: 'GET' }).then((r) => r.json());
+ console.log(res);
+ setData(res);
+ })();
+ }, []);
+
+ return (
+ <div
+ style={{
+ marginTop: '40px',
+ height: 'calc( 100% - 40px )',
+ overflowY: 'scroll',
+ }}
+ >
+ <div
+ style={{
+ display: 'flex',
+ flexWrap: 'nowrap',
+ flexDirection: 'column',
+ height: '100%',
+ }}
+ >
+ {data === null ? (
+ <div style={{ height: '100%' }}>
+ <SteamSpinner />
+ </div>
+ ) : (
+ data.map((plugin: StorePlugin) => <PluginCard plugin={plugin} />)
+ )}
+ </div>
+ </div>
+ );
+};
+
+export default StorePage;
diff --git a/frontend/src/index.tsx b/frontend/src/index.tsx
index 89194777..5cf2ed14 100644
--- a/frontend/src/index.tsx
+++ b/frontend/src/index.tsx
@@ -9,6 +9,7 @@ declare global {
}
window.DeckyPluginLoader?.dismountAll();
+window.DeckyPluginLoader?.deinit();
window.DeckyPluginLoader = new PluginLoader();
window.importDeckyPlugin = function (name: string) {
diff --git a/frontend/src/plugin-loader.tsx b/frontend/src/plugin-loader.tsx
index 73f65415..eb3344a3 100644
--- a/frontend/src/plugin-loader.tsx
+++ b/frontend/src/plugin-loader.tsx
@@ -4,6 +4,7 @@ import { FaPlug } from 'react-icons/fa';
import { DeckyState, DeckyStateContextProvider } from './components/DeckyState';
import LegacyPlugin from './components/LegacyPlugin';
import PluginView from './components/PluginView';
+import StorePage from './components/store/Store';
import TitleView from './components/TitleView';
import Logger from './logger';
import { Plugin } from './plugin';
@@ -43,6 +44,8 @@ class PluginLoader extends Logger {
),
icon: <FaPlug />,
});
+
+ this.routerHook.addRoute('/decky/store', () => <StorePage />);
}
public addPluginInstallPrompt(artifact: string, version: string, request_id: string) {
@@ -71,6 +74,10 @@ class PluginLoader extends Logger {
}
}
+ public deinit() {
+ this.routerHook.removeRoute('/decky/store');
+ }
+
public async importPlugin(name: string) {
try {
if (this.reloadLock) {
diff --git a/frontend/src/router-hook.tsx b/frontend/src/router-hook.tsx
index 83d23bf1..ce0c553f 100644
--- a/frontend/src/router-hook.tsx
+++ b/frontend/src/router-hook.tsx
@@ -92,6 +92,10 @@ class RouterHook extends Logger {
this.routerState.addRoute(path, component, props);
}
+ removeRoute(path: string) {
+ this.routerState.removeRoute(path);
+ }
+
deinit() {
unpatch(this.gamepadWrapper, 'render');
this.router && unpatch(this.router, 'type');