Compare commits

...

11 Commits

Author SHA1 Message Date
TrainDoctor 66c4a7e16e Update README.md 2022-07-25 17:29:56 -07:00
TrainDoctor b929b2dddf Update README.md 2022-07-25 17:08:10 -07:00
TrainDoctor fb0b703438 Fix unintended question mark in "Installing" modal 2022-07-25 16:07:16 -07:00
AAGaming afb2c7c0ed Better install process UX, fix reinstalling 2022-07-25 17:13:50 -04:00
AAGaming 52dded85ed quick fix for routes refreshing constantly 2022-07-24 11:51:42 -04:00
AAGaming 2004bdebbf fix calibration menu in controller settings 2022-07-24 11:37:38 -04:00
AAGaming c9bf8d357e use fstring 2022-07-21 22:03:11 -04:00
AAGaming 09eee761a5 change log to debug 2022-07-21 22:02:47 -04:00
AAGaming 20f43b2fd4 fix plugin uninstalling 2022-07-21 22:02:13 -04:00
AAGaming e6dd1c29d8 remove modal box shadow 2022-07-17 16:42:24 -04:00
AAGaming 6e88c7c9ac Show warning when installing legacy plugins 2022-07-17 16:09:42 -04:00
11 changed files with 133 additions and 48 deletions
+16
View File
@@ -46,6 +46,22 @@ Keep an eye on the [Wiki](https://deckbrew.xyz) for more information about Plugi
- [Here's how to get the Steam Deck UI on your enviroment of choice.](https://youtu.be/1IAbZte8e7E?t=112)
- (The video shows Windows usage but unless you're using Arch WSL/cygwin this script is unsupported on Windows.)
### Getting Started
1. Clone the repository using the latest commit to main before starting your PR.
2. In your clone of the repository run these commands:
1. ``pnpm i``
2. ``pnpm run build``
3. If you are modifying the UI, these will need to be run before deploying the changes to your Deck.
4. Use the vscode tasks or ``deck.sh`` script to deploy your changes to your Deck to test them.
5. You will be testing your changes with the python script version, so you will need to build, deploy and reload each time.
Note: If you are recieveing build errors due to an out of date library, you should run this command inside of your repository:
```bash
pnpm update decky-frontend-lib --latest
```
Source control and deploying plugins are left to each respective contributor for the cloned repos in order to keep depedencies up to date.
## Credit
+17 -7
View File
@@ -10,6 +10,7 @@ from asyncio import get_event_loop
from time import time
from hashlib import sha256
from subprocess import Popen
from injector import inject_to_tab
import json
@@ -23,9 +24,10 @@ class PluginInstallContext:
self.hash = hash
class PluginBrowser:
def __init__(self, plugin_path, server_instance) -> None:
def __init__(self, plugin_path, server_instance, plugins) -> None:
self.log = getLogger("browser")
self.plugin_path = plugin_path
self.plugins = plugins
self.install_requests = {}
server_instance.add_routes([
@@ -45,21 +47,28 @@ class PluginBrowser:
def find_plugin_folder(self, name):
for folder in listdir(self.plugin_path):
with open(path.join(self.plugin_path, folder, 'plugin.json'), 'r') as f:
plugin = json.load(f)
try:
with open(path.join(self.plugin_path, folder, 'plugin.json'), 'r') as f:
plugin = json.load(f)
if plugin['name'] == name:
return path.join(self.plugin_path, folder)
if plugin['name'] == name:
return path.join(self.plugin_path, folder)
except:
self.log.debug(f"skipping {folder}")
async def uninstall_plugin(self, name):
tab = await get_tab("SP")
await tab.open_websocket()
try:
if type(name) != str:
data = await name.post()
name = data.get("name")
name = data.get("name", "undefined")
self.log.info("uninstalling " + name)
self.log.info(" at dir " + self.find_plugin_folder(name))
await tab.evaluate_js(f"DeckyPluginLoader.unloadPlugin('{name}')")
if self.plugins[name]:
self.plugins[name].stop()
self.plugins.pop(name, None)
rmtree(self.find_plugin_folder(name))
except FileNotFoundError:
self.log.warning(f"Plugin {name} not installed, skipping uninstallation")
@@ -91,6 +100,7 @@ class PluginBrowser:
)
if ret:
self.log.info(f"Installed {name} (Version: {version})")
await inject_to_tab("SP", "window.syncDeckyPlugins()")
else:
self.log.fatal(f"SHA-256 Mismatch!!!! {name} (Version: {version})")
else:
+1 -1
View File
@@ -46,7 +46,7 @@ class PluginManager:
allow_headers="*")
})
self.plugin_loader = Loader(self.web_app, CONFIG["plugin_path"], self.loop, CONFIG["live_reload"])
self.plugin_browser = PluginBrowser(CONFIG["plugin_path"], self.web_app)
self.plugin_browser = PluginBrowser(CONFIG["plugin_path"], self.web_app, self.plugin_loader.plugins)
self.utilities = Utilities(self)
self.updater = Updater(self)
+1 -1
View File
@@ -37,7 +37,7 @@
}
},
"dependencies": {
"decky-frontend-lib": "^1.2.1",
"decky-frontend-lib": "^1.2.4",
"react-icons": "^4.4.0"
}
}
+4 -4
View File
@@ -9,7 +9,7 @@ specifiers:
'@types/react': 16.14.0
'@types/react-router': 5.1.18
'@types/webpack': ^5.28.0
decky-frontend-lib: ^1.2.1
decky-frontend-lib: ^1.2.4
husky: ^8.0.1
import-sort-style-module: ^6.0.0
inquirer: ^8.2.4
@@ -23,7 +23,7 @@ specifiers:
typescript: ^4.7.4
dependencies:
decky-frontend-lib: 1.2.1
decky-frontend-lib: 1.2.4
react-icons: 4.4.0_react@16.14.0
devDependencies:
@@ -806,8 +806,8 @@ packages:
ms: 2.1.2
dev: true
/decky-frontend-lib/1.2.1:
resolution: {integrity: sha512-aJmjOSMwQN9LTquYaMhSqW+FhmKLRgLb75JkcGRWKuIe8rjDfwwbAB/ckJseIC8UMzPKhspvcznfxyp+c72B5Q==}
/decky-frontend-lib/1.2.4:
resolution: {integrity: sha512-r3mLEey9KUkF68geJVSjNlOz/Fg4vpMKUzoutSceyd8o/J5l+QR+Vf0b3gwK3UN9Sp4Pj4XQ1eB82+/W0ApsFg==}
dev: false
/deepmerge/4.2.2:
@@ -0,0 +1,43 @@
import { ModalRoot, QuickAccessTab, Router, Spinner, sleep, staticClasses } from 'decky-frontend-lib';
import { FC, useState } from 'react';
interface PluginInstallModalProps {
artifact: string;
version: string;
hash: string;
// reinstall: boolean;
onOK(): void;
onCancel(): void;
closeModal?(): void;
}
const PluginInstallModal: FC<PluginInstallModalProps> = ({ artifact, version, hash, onOK, onCancel, closeModal }) => {
const [loading, setLoading] = useState<boolean>(false);
return (
<ModalRoot
bOKDisabled={loading}
closeModal={closeModal}
onOK={async () => {
setLoading(true);
await onOK();
Router.NavigateBackOrOpenMenu();
await sleep(250);
setTimeout(() => Router.OpenQuickAccessMenu(QuickAccessTab.Decky), 1000);
}}
onCancel={async () => {
await onCancel();
}}
>
<div className={staticClasses.Title} style={{ flexDirection: 'column' }}>
{hash == 'False' ? <h3 style={{ color: 'red' }}>!!!!NO HASH PROVIDED!!!!</h3> : null}
<div style={{ flexDirection: 'row' }}>
{loading && <Spinner style={{ width: '20px' }} />} {loading ? 'Installing' : 'Install'} {artifact}
{version ? ' version ' + version : null}
{!loading && '?'}
</div>
</div>
</ModalRoot>
);
};
export default PluginInstallModal;
@@ -22,7 +22,7 @@ export default function PluginList() {
<div className={staticClasses.Title} style={{ marginLeft: 'auto', boxShadow: 'none' }}>
<DialogButton
style={{ height: '40px', width: '40px', padding: '10px 12px' }}
onClick={() => window.DeckyPluginLoader.uninstall_plugin(name)}
onClick={() => window.DeckyPluginLoader.uninstallPlugin(name)}
>
<FaTrash />
</DialogButton>
+27 -11
View File
@@ -1,4 +1,4 @@
import { SteamSpinner } from 'decky-frontend-lib';
import { ModalRoot, SteamSpinner, showModal, staticClasses } from 'decky-frontend-lib';
import { FC, useEffect, useState } from 'react';
import PluginCard from './PluginCard';
@@ -38,16 +38,32 @@ export async function installFromURL(url: string) {
});
}
export async function requestLegacyPluginInstall(plugin: LegacyStorePlugin, selectedVer: string) {
const formData = new FormData();
formData.append('name', plugin.artifact);
formData.append('artifact', `https://github.com/${plugin.artifact}/archive/refs/tags/${selectedVer}.zip`);
formData.append('version', selectedVer);
formData.append('hash', plugin.versions[selectedVer]);
await fetch('http://localhost:1337/browser/install_plugin', {
method: 'POST',
body: formData,
});
export function requestLegacyPluginInstall(plugin: LegacyStorePlugin, selectedVer: string) {
showModal(
<ModalRoot
onOK={() => {
const formData = new FormData();
formData.append('name', plugin.artifact);
formData.append('artifact', `https://github.com/${plugin.artifact}/archive/refs/tags/${selectedVer}.zip`);
formData.append('version', selectedVer);
formData.append('hash', plugin.versions[selectedVer]);
fetch('http://localhost:1337/browser/install_plugin', {
method: 'POST',
body: formData,
});
}}
onCancel={() => {
// do nothing
}}
>
<div className={staticClasses.Title} style={{ flexDirection: 'column', boxShadow: 'unset' }}>
Using legacy plugins
</div>
You are currently installing a <b>legacy</b> plugin. Legacy plugins are no longer supported and may have issues.
Legacy plugins do not support gamepad input. To interact with a legacy plugin, you will need to use the
touchscreen.
</ModalRoot>,
);
}
export async function requestPluginInstall(plugin: StorePlugin, selectedVer: StorePluginVersion) {
+1 -1
View File
@@ -21,7 +21,7 @@ window.importDeckyPlugin = function (name: string) {
window.syncDeckyPlugins = async function () {
const plugins = await (await fetch('http://127.0.0.1:1337/plugins')).json();
for (const plugin of plugins) {
window.DeckyPluginLoader?.importPlugin(plugin);
if (!window.DeckyPluginLoader.hasPlugin(plugin)) window.DeckyPluginLoader?.importPlugin(plugin);
}
};
+14 -19
View File
@@ -1,8 +1,9 @@
import { ModalRoot, QuickAccessTab, Router, showModal, sleep, staticClasses } from 'decky-frontend-lib';
import { ModalRoot, QuickAccessTab, showModal, staticClasses } from 'decky-frontend-lib';
import { FaPlug } from 'react-icons/fa';
import { DeckyState, DeckyStateContextProvider } from './components/DeckyState';
import LegacyPlugin from './components/LegacyPlugin';
import PluginInstallModal from './components/modals/PluginInstallModal';
import PluginView from './components/PluginView';
import SettingsPage from './components/settings';
import StorePage from './components/store/Store';
@@ -55,27 +56,17 @@ class PluginLoader extends Logger {
public addPluginInstallPrompt(artifact: string, version: string, request_id: string, hash: string) {
showModal(
<ModalRoot
onOK={async () => {
await this.callServerMethod('confirm_plugin_install', { request_id });
Router.NavigateBackOrOpenMenu();
await sleep(250);
setTimeout(() => Router.OpenQuickAccessMenu(QuickAccessTab.Decky), 1000);
}}
onCancel={() => {
this.callServerMethod('cancel_plugin_install', { request_id });
}}
>
<div className={staticClasses.Title} style={{ flexDirection: 'column' }}>
{hash == 'False' ? <h3 style={{ color: 'red' }}>!!!!NO HASH PROVIDED!!!!</h3> : null}
Install {artifact}
{version ? ' version ' + version : null}?
</div>
</ModalRoot>,
<PluginInstallModal
artifact={artifact}
version={version}
hash={hash}
onOK={() => this.callServerMethod('confirm_plugin_install', { request_id })}
onCancel={() => this.callServerMethod('cancel_plugin_install', { request_id })}
/>,
);
}
public uninstall_plugin(name: string) {
public uninstallPlugin(name: string) {
showModal(
<ModalRoot
onOK={async () => {
@@ -97,6 +88,10 @@ class PluginLoader extends Logger {
);
}
public hasPlugin(name: string) {
return Boolean(this.plugins.find((plugin) => plugin.name == name));
}
public dismountAll() {
for (const plugin of this.plugins) {
this.log(`Dismounting ${plugin.name}`);
+8 -3
View File
@@ -41,11 +41,16 @@ class RouterHook extends Logger {
const DeckyWrapper = ({ children }: { children: ReactElement }) => {
const { routes } = useDeckyRouterState();
const routerIndex = children.props.children[0].props.children.length - 1;
let routerIndex = children.props.children[0].props.children.length;
if (
!children.props.children[0].props.children[routerIndex].length ||
children.props.children[0].props.children !== routes.size
!children.props.children[0].props.children[routerIndex - 1]?.length ||
children.props.children[0].props.children[routerIndex - 1]?.length !== routes.size
) {
if (
children.props.children[0].props.children[routerIndex - 1]?.length &&
children.props.children[0].props.children[routerIndex - 1].length !== routes.size
)
routerIndex--;
const newRouterArray: ReactElement[] = [];
routes.forEach(({ component, props }, path) => {
newRouterArray.push(