Implement React-based plugin store (#81)

Co-authored-by: TrainDoctor <11465594+TrainDoctor@users.noreply.github.com>
Co-authored-by: WerWolv <werwolv98@gmail.com>
This commit is contained in:
AAGaming
2022-06-17 18:43:53 -04:00
committed by GitHub
parent a95bf94d87
commit 99b4b939bd
15 changed files with 2178 additions and 3915 deletions
+6 -2
View File
@@ -154,5 +154,9 @@ cython_debug/
# static files are built # static files are built
backend/static backend/static
# pnpm lockfile # ignore settings.json
frontend/pnpm-lock.yaml # prevents leaking login details
.vscode/settings.json
# plugins folder for local launches
plugins/*
Vendored Executable
+12
View File
@@ -0,0 +1,12 @@
#!/usr/bin/env bash
SCRIPT_DIR="$( cd -- "$( dirname -- "${BASH_SOURCE[0]:-$0}"; )" &> /dev/null && pwd 2> /dev/null; )";
# printf "${SCRIPT_DIR}\n"
# printf "$(dirname $0)\n"
if ! [[ -e "${SCRIPT_DIR}/settings.json" ]]; then
printf '.vscode/settings.json does not exist. Creating it with default settings. Exiting afterwards. Run your task again.\n\n'
cp "${SCRIPT_DIR}/defsettings.json" "${SCRIPT_DIR}/settings.json"
exit 1
else
printf '.vscode/settings.json does exist. Congrats.\n'
printf 'Make sure to change settings.json to match your deck.\n'
fi
+7
View File
@@ -0,0 +1,7 @@
{
"deckip" : "0.0.0.0",
"deckport" : "22",
"deckpass" : "ssap",
"deckkey" : "-i ${env:HOME}/.ssh/id_rsa",
"deckdir" : "/home/deck"
}
+3 -3
View File
@@ -2,16 +2,16 @@
"version": "0.2.0", "version": "0.2.0",
"configurations": [ "configurations": [
{ {
"name": "Debug", "name": "Debug (Local)",
"type": "python", "type": "python",
"request": "launch", "request": "launch",
"program": "${workspaceFolder}/backend/main.py", "program": "${workspaceFolder}/backend/main.py",
"cwd": "${workspaceFolder}/backend", "cwd": "${workspaceFolder}/backend",
"console": "integratedTerminal", "console": "integratedTerminal",
"env": { "env": {
"PLUGIN_PATH": "/home/deck/homebrew/plugins" "PLUGIN_PATH": "${workspaceFolder}/plugins"
}, },
"preLaunchTask": "Build frontend" "preLaunchTask": "localrun"
} }
] ]
} }
+124 -5
View File
@@ -1,15 +1,134 @@
{ {
"version": "2.0.0", "version": "2.0.0",
"tasks": [ "tasks": [
// OTHER
{ {
"label": "Stop Service", "label": "checkforsettings",
"type": "shell", "type": "shell",
"command":"systemctl --user stop plugin_loader", "group": "none",
"detail": "Check that settings.json has been created",
"command": "bash -c ${workspaceFolder}/.vscode/config.sh",
"problemMatcher": []
}, },
{ {
"label": "Build frontend", "label": "localrun",
"type": "shell", "type": "shell",
"command":"cd ${workspaceFolder}/frontend; npm run build", "group": "none",
} "dependsOn" : ["buildall"],
"detail": "Check for local runs, create a plugins folder",
"command": "mkdir -p plugins",
"problemMatcher": []
},
{
"label": "dependencies",
"type": "shell",
"group": "none",
"detail": "Check for local runs, create a plugins folder",
"command": "rsync -azp --rsh='ssh -p ${config:deckport} ${config:deckkey}' requirements.txt deck@${config:deckip}:${config:deckdir}/homebrew/dev/pluginloader/requirements.txt && ssh deck@${config:deckip} -p ${config:deckport} ${config:deckkey} 'python -m ensurepip && python -m pip install --upgrade pip && python -m pip install --upgrade setuptools && python -m pip install -r ${config:deckdir}/homebrew/dev/pluginloader/requirements.txt'",
"problemMatcher": []
},
// BUILD
{
"label": "pnpmsetup",
"type": "shell",
"group": "build",
"detail": "Setup pnpm",
"command": "cd frontend && pnpm i",
"problemMatcher": []
},
{
"label": "buildfrontend",
"type": "npm",
"group": "build",
"detail": "rollup -c",
"script": "build",
"path": "frontend",
"problemMatcher": [],
},
{
"label": "buildall",
"group": "build",
"detail": "Deploy pluginloader to deck",
"dependsOrder": "sequence",
"dependsOn": [
"pnpmsetup",
"buildfrontend"
],
"problemMatcher": []
},
// DEPLOY
{
"label": "createfolders",
"detail": "Create plugins folder in expected directory",
"type": "shell",
"group": "none",
"dependsOn": [
"checkforsettings"
],
"command": "ssh deck@${config:deckip} -p ${config:deckport} ${config:deckkey} 'mkdir -p ${config:deckdir}/homebrew/dev/pluginloader && mkdir -p ${config:deckdir}/homebrew/dev/plugins'",
"problemMatcher": []
},
{
"label": "deploy",
"detail": "Deploy dev PluginLoader to deck",
"type": "shell",
"group": "none",
"command": "rsync -azp --delete --rsh='ssh -p ${config:deckport} ${config:deckkey}' --exclude='.git/' --exclude='.github/' --exclude='.vscode/' --exclude='frontend/' --exclude='dist/' --exclude='contrib/' --exclude='*.log' --exclude='requirements.txt' --exclude='backend/__pycache__/' --exclude='.gitignore' . deck@${config:deckip}:${config:deckdir}/homebrew/dev/pluginloader",
"problemMatcher": []
},
{
"label": "deployall",
"dependsOrder": "sequence",
"group": "none",
"dependsOn": [
"createfolders",
"dependencies",
"deploy"
],
"problemMatcher": []
},
// RUN
{
"label": "runpydeck",
"detail": "Run indev PluginLoader on Deck",
"type": "shell",
"group": "none",
"dependsOn" : ["checkforsettings"],
"command": "ssh deck@${config:deckip} -p ${config:deckport} ${config:deckkey} 'export PLUGIN_PATH=${config:deckdir}/homebrew/dev/plugins; export CHOWN_PLUGIN_PATH=0; echo '${config:deckpass}' | sudo -SE python3 ${config:deckdir}/homebrew/dev/pluginloader/backend/main.py'",
"problemMatcher": []
},
{
"label": "runpylocal",
"detail": "Run PluginLoader from python locally",
"type": "shell",
"group": "none",
"command": "export PLUGIN_PATH=${workspaceFolder}/plugins; export CHOWN_PLUGIN_PATH=0; sudo -E python3 ${workspaceFolder}/backend/main.py",
"problemMatcher": []
},
// ALL-IN-ONES
{
"label": "updateremote",
"detail": "Build and deploy",
"dependsOrder": "sequence",
"group": "none",
"dependsOn": [
"buildall",
"deployall",
],
"problemMatcher": []
},
{
"label": "allinone",
"detail": "Build, deploy and run",
"dependsOrder": "sequence",
"group": "none",
"dependsOn": [
"buildall",
"deployall",
"runpydeck"
],
"problemMatcher": []
}
] ]
} }
+56 -18
View File
@@ -4,6 +4,8 @@
## This script defaults to port 22 unless otherwise specified, and cannot run without a sudo password or LAN IP. ## This script defaults to port 22 unless otherwise specified, and cannot run without a sudo password or LAN IP.
## You will need to specify the path to the ssh key if using key connection exclusively. ## You will need to specify the path to the ssh key if using key connection exclusively.
## TODO: document latest changes to wiki
## Pre-parse arugments for ease of use ## Pre-parse arugments for ease of use
CLONEFOLDER=${1:-""} CLONEFOLDER=${1:-""}
INSTALLFOLDER=${2:-""} INSTALLFOLDER=${2:-""}
@@ -11,9 +13,13 @@ DECKIP=${3:-""}
SSHPORT=${4:-""} SSHPORT=${4:-""}
PASSWORD=${5:-""} PASSWORD=${5:-""}
SSHKEYLOC=${6:-""} SSHKEYLOC=${6:-""}
LOADERBRANCH=${7:-""}
LIBRARYBRANCH=${8:-""}
TEMPLATEBRANCH=${9:-""}
LATEST=${10:-""}
## gather options into an array ## gather options into an array
OPTIONSARRAY=("$CLONEFOLDER" $INSTALLFOLDER "$DECKIP" "$SSHPORT" "$PASSWORD" "$SSHKEYLOC") OPTIONSARRAY=("$CLONEFOLDER" $INSTALLFOLDER "$DECKIP" "$SSHPORT" "$PASSWORD" "$SSHKEYLOC" "$LOADERBRANCH" "$LIBRARYBRANCH" "$TEMPLATEBRANCH" "$LATEST")
## iterate through options array to check their presence ## iterate through options array to check their presence
count=0 count=0
@@ -28,19 +34,21 @@ setfolder() {
local DEFAULT="git" local DEFAULT="git"
elif [[ "$2" == "install" ]]; then elif [[ "$2" == "install" ]]; then
local ACTION="install" local ACTION="install"
local DEFAULT="loaderdev" local DEFAULT="dev"
fi fi
printf "Enter the directory in /home/user to ${ACTION} to.\n"
printf "Example: if your home directory is /home/user you would type: ${DEFAULT}\n"
printf "The ${ACTION} directory would be: ${HOME}/${DEFAULT}\n"
if [[ "$ACTION" == "clone" ]]; then if [[ "$ACTION" == "clone" ]]; then
printf "Enter the directory in /home/user/ to ${ACTION} to.\n"
printf "The ${ACTION} directory would be: ${HOME}/${DEFAULT}\n"
read -p "Enter your ${ACTION} directory: " CLONEFOLDER read -p "Enter your ${ACTION} directory: " CLONEFOLDER
if ! [[ "$CLONEFOLDER" =~ ^[[:alnum:]]+$ ]]; then if ! [[ "$CLONEFOLDER" =~ ^[[:alnum:]]+$ ]]; then
printf "Folder name not provided. Using default, '${DEFAULT}'.\n" printf "Folder name not provided. Using default, '${DEFAULT}'.\n"
CLONEFOLDER="${DEFAULT}" CLONEFOLDER="${DEFAULT}"
fi fi
elif [[ "$ACTION" == "install" ]]; then elif [[ "$ACTION" == "install" ]]; then
printf "Enter the directory in /home/deck/homebrew to ${ACTION} pluginloader to.\n"
printf "The ${ACTION} directory would be: /home/deck/homebrew/${DEFAULT}/pluginloader\n"
printf "It is highly recommended that you use the default folder path seen above, just press enter at the next prompt.\n"
read -p "Enter your ${ACTION} directory: " INSTALLFOLDER read -p "Enter your ${ACTION} directory: " INSTALLFOLDER
if ! [[ "$INSTALLFOLDER" =~ ^[[:alnum:]]+$ ]]; then if ! [[ "$INSTALLFOLDER" =~ ^[[:alnum:]]+$ ]]; then
printf "Folder name not provided. Using default, '${DEFAULT}'.\n" printf "Folder name not provided. Using default, '${DEFAULT}'.\n"
@@ -106,16 +114,46 @@ clonefromto() {
# printf "repo=$1\n" # printf "repo=$1\n"
# printf "outdir=$2\n" # printf "outdir=$2\n"
# printf "branch=$3\n" # printf "branch=$3\n"
if [[ -z $3 ]]; then printf "Repository: $1\n"
BRANCH="" git clone $1 $2 &> '/dev/null'
else
BRANCH="-b $3"
fi
git clone $1 $2 $BRANCH &> '/dev/null'
CODE=$? CODE=$?
# printf "CODE=${CODE}"
if [[ $CODE -eq 128 ]]; then if [[ $CODE -eq 128 ]]; then
cd $2 cd $2
git fetch &> '/dev/null' git fetch --all &> '/dev/null'
fi
if [[ -z $3 ]]; then
printf "Enter the desired branch for repository "$1" :\n"
local OUT="$(git branch -r | sed '/\/HEAD/d')"
# $OUT="$($OUT > )"
printf "$OUT\nbranch: "
read BRANCH
else
printf "on branch: $3\n"
BRANCH="$3"
fi
if ! [[ -z ${BRANCH} ]]; then
git checkout $BRANCH &> '/dev/null'
fi
if [[ ${LATEST} == "true" ]]; then
git pull --all
elif [[ ${LATEST} == "true" ]]; then
printf "Assuming user not pulling latest commits.\n"
else
printf "Pull latest commits? (y/N): "
read PULL
case ${PULL:0:1} in
y|Y )
printf "Pulling latest commits.\n"
git pull --all
;;
* )
printf "Not pulling latest commits.\n"
;;
esac
if ! [[ "$PULL" =~ ^[[:alnum:]]+$ ]]; then
printf "Assuming user not pulling latest commits.\n"
fi
fi fi
} }
@@ -158,7 +196,7 @@ if [[ "$INSTALLFOLDER" == "" ]]; then
fi fi
CLONEDIR="$HOME/$CLONEFOLDER" CLONEDIR="$HOME/$CLONEFOLDER"
INSTALLDIR="/home/deck/$INSTALLFOLDER" INSTALLDIR="/home/deck/hombrew/$INSTALLFOLDER"
## Input ip address, port, password and sshkey ## Input ip address, port, password and sshkey
@@ -217,11 +255,11 @@ mkdir -p ${CLONEDIR} &> '/dev/null'
# rm -r ${CLONEDIR}/pluginlibrary # rm -r ${CLONEDIR}/pluginlibrary
# rm -r ${CLONEDIR}/plugintemplate # rm -r ${CLONEDIR}/plugintemplate
clonefromto "https://github.com/SteamDeckHomebrew/PluginLoader" ${CLONEDIR}/pluginloader react-frontend-plugins clonefromto "https://github.com/SteamDeckHomebrew/PluginLoader" ${CLONEDIR}/pluginloader "$LOADERBRANCH"
clonefromto "https://github.com/SteamDeckHomebrew/decky-frontend-lib" ${CLONEDIR}/pluginlibrary clonefromto "https://github.com/SteamDeckHomebrew/decky-frontend-lib" ${CLONEDIR}/pluginlibrary "$LIBRARYBRANCH"
clonefromto "https://github.com/SteamDeckHomebrew/decky-plugin-template" ${CLONEDIR}/plugintemplate clonefromto "https://github.com/SteamDeckHomebrew/decky-plugin-template" ${CLONEDIR}/plugintemplate "$TEMPLATEBRANCH"
## Transpile and bundle typescript ## Transpile and bundle typescript
@@ -271,7 +309,7 @@ printf "Run these commands to deploy your local changes to the deck:\n"
printf "'rsync -avzp --mkpath --rsh=""\"ssh -p ${SSHPORT} ${IDENINVOC}\""" --exclude='.git/' --exclude='node_modules' --exclude='package-lock.json' --delete ${CLONEDIR}/pluginname deck@${DECKIP}:${INSTALLDIR}/plugins'\n" printf "'rsync -avzp --mkpath --rsh=""\"ssh -p ${SSHPORT} ${IDENINVOC}\""" --exclude='.git/' --exclude='node_modules' --exclude='package-lock.json' --delete ${CLONEDIR}/pluginname deck@${DECKIP}:${INSTALLDIR}/plugins'\n"
printf "'rsync -avzp --mkpath --rsh=""\"ssh -p ${SSHPORT} ${IDENINVOC}\""" --exclude='.git/' --exclude='node_modules' --exclude='package-lock.json' --exclude=='frontend' --exclude='*dist*' --exclude='*contrib*' --delete ${CLONEDIR}/pluginloader/* deck@${DECKIP}:${INSTALLDIR}/pluginloader/'\n" printf "'rsync -avzp --mkpath --rsh=""\"ssh -p ${SSHPORT} ${IDENINVOC}\""" --exclude='.git/' --exclude='node_modules' --exclude='package-lock.json' --exclude=='frontend' --exclude='*dist*' --exclude='*contrib*' --delete ${CLONEDIR}/pluginloader/* deck@${DECKIP}:${INSTALLDIR}/pluginloader/'\n"
printf "Run in console or in a script this command to run your development version:\n'ssh deck@${DECKIP} -p 22 ${IDENINVOC} 'export PLUGIN_PATH=${INSTALLDIR}/plugins; export CHOWN_PLUGIN_PATH=0; echo 'steam' | sudo -SE python3 ${INSTALLDIR}/pluginloader/backend/main.py'\n" printf "Run in console or in a script this command to run your development version:\n'ssh deck@${DECKIP} -p ${SSHPORT} ${IDENINVOC} 'export PLUGIN_PATH=${INSTALLDIR}/plugins; export CHOWN_PLUGIN_PATH=0; echo 'steam' | sudo -SE python3 ${INSTALLDIR}/pluginloader/backend/main.py'\n"
## Disable Releases versions if they exist ## Disable Releases versions if they exist
-3881
View File
File diff suppressed because it is too large Load Diff
+6 -5
View File
@@ -13,7 +13,7 @@
"devDependencies": { "devDependencies": {
"@rollup/plugin-commonjs": "^21.1.0", "@rollup/plugin-commonjs": "^21.1.0",
"@rollup/plugin-json": "^4.1.0", "@rollup/plugin-json": "^4.1.0",
"@rollup/plugin-node-resolve": "^13.2.1", "@rollup/plugin-node-resolve": "^13.3.0",
"@rollup/plugin-replace": "^4.0.0", "@rollup/plugin-replace": "^4.0.0",
"@rollup/plugin-typescript": "^8.3.2", "@rollup/plugin-typescript": "^8.3.2",
"@types/react": "16.14.0", "@types/react": "16.14.0",
@@ -21,13 +21,14 @@
"@types/webpack": "^5.28.0", "@types/webpack": "^5.28.0",
"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",
"prettier": "^2.6.2", "prettier": "^2.6.2",
"prettier-plugin-import-sort": "^0.0.7", "prettier-plugin-import-sort": "^0.0.7",
"react": "16.14.0", "react": "16.14.0",
"react-dom": "16.14.0", "react-dom": "16.14.0",
"rollup": "^2.70.2", "rollup": "^2.75.6",
"tslib": "^2.4.0", "tslib": "^2.4.0",
"typescript": "^4.7.2" "typescript": "^4.7.3"
}, },
"importSort": { "importSort": {
".js, .jsx, .ts, .tsx": { ".js, .jsx, .ts, .tsx": {
@@ -36,7 +37,7 @@
} }
}, },
"dependencies": { "dependencies": {
"decky-frontend-lib": "^0.0.6", "decky-frontend-lib": "^0.10.2",
"react-icons": "^4.3.1" "react-icons": "^4.4.0"
} }
} }
+1724
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -9,7 +9,7 @@ const PluginView: VFC = () => {
const onStoreClick = () => { const onStoreClick = () => {
Router.CloseSideMenus(); Router.CloseSideMenus();
Router.NavigateToExternalWeb('http://127.0.0.1:1337/browser/redirect'); Router.Navigate('/decky/store');
}; };
if (activePlugin) { if (activePlugin) {
@@ -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;
+55
View File
@@ -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;
+1
View File
@@ -9,6 +9,7 @@ declare global {
} }
window.DeckyPluginLoader?.dismountAll(); window.DeckyPluginLoader?.dismountAll();
window.DeckyPluginLoader?.deinit();
window.DeckyPluginLoader = new PluginLoader(); window.DeckyPluginLoader = new PluginLoader();
window.importDeckyPlugin = function (name: string) { window.importDeckyPlugin = function (name: string) {
+7
View File
@@ -4,6 +4,7 @@ import { FaPlug } from 'react-icons/fa';
import { DeckyState, DeckyStateContextProvider } from './components/DeckyState'; import { DeckyState, DeckyStateContextProvider } from './components/DeckyState';
import LegacyPlugin from './components/LegacyPlugin'; import LegacyPlugin from './components/LegacyPlugin';
import PluginView from './components/PluginView'; import PluginView from './components/PluginView';
import StorePage from './components/store/Store';
import TitleView from './components/TitleView'; import TitleView from './components/TitleView';
import Logger from './logger'; import Logger from './logger';
import { Plugin } from './plugin'; import { Plugin } from './plugin';
@@ -43,6 +44,8 @@ class PluginLoader extends Logger {
), ),
icon: <FaPlug />, icon: <FaPlug />,
}); });
this.routerHook.addRoute('/decky/store', () => <StorePage />);
} }
public addPluginInstallPrompt(artifact: string, version: string, request_id: string) { 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) { public async importPlugin(name: string) {
try { try {
if (this.reloadLock) { if (this.reloadLock) {
+4
View File
@@ -92,6 +92,10 @@ class RouterHook extends Logger {
this.routerState.addRoute(path, component, props); this.routerState.addRoute(path, component, props);
} }
removeRoute(path: string) {
this.routerState.removeRoute(path);
}
deinit() { deinit() {
unpatch(this.gamepadWrapper, 'render'); unpatch(this.gamepadWrapper, 'render');
this.router && unpatch(this.router, 'type'); this.router && unpatch(this.router, 'type');