Compare commits

...

10 Commits

Author SHA1 Message Date
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
AAGaming f015e00561 more updater fixes 2022-07-15 12:57:51 -04:00
AAGaming e07827cdb5 catch rm errors 2022-07-15 12:36:16 -04:00
13 changed files with 127 additions and 60 deletions
+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:
+2 -2
View File
@@ -33,7 +33,7 @@ class Tab:
return (await self.websocket.receive_json()) if receive else None
raise RuntimeError("Websocket not opened")
async def evaluate_js(self, js, run_async=False, manage_socket=True):
async def evaluate_js(self, js, run_async=False, manage_socket=True, get_result=True):
if manage_socket:
await self.open_websocket()
@@ -45,7 +45,7 @@ class Tab:
"userGesture": True,
"awaitPromise": run_async
}
})
}, get_result)
if manage_socket:
await self.client.close()
+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)
+6 -6
View File
@@ -59,7 +59,7 @@ class Updater:
return {
"current": self.localVer,
"remote": self.remoteVer,
"updatable": self.remoteVer != None
"updatable": self.localVer != None
}
else:
return {"current": "unknown", "updatable": False}
@@ -91,8 +91,10 @@ class Updater:
async with ClientSession() as web:
async with web.request("GET", download_url, ssl=helpers.get_ssl_context(), allow_redirects=True) as res:
total = int(res.headers.get('content-length', 0))
remove(path.join(getcwd(), "PluginLoader"))
try:
remove(path.join(getcwd(), "PluginLoader"))
except:
pass
with open(path.join(getcwd(), "PluginLoader"), "wb") as out:
progress = 0
raw = 0
@@ -101,8 +103,7 @@ class Updater:
raw += len(c)
new_progress = round((raw / total) * 100)
if progress != new_progress:
if new_progress - progress>= 2:
self.context.loop.create_task(tab.evaluate_js(f"window.DeckyUpdater.updateProgress({progress})", False, False))
self.context.loop.create_task(tab.evaluate_js(f"window.DeckyUpdater.updateProgress({new_progress})", False, False, False))
progress = new_progress
with open(path.join(getcwd(), ".loader.version"), "w") as out:
@@ -117,4 +118,3 @@ class Updater:
async def do_restart(self):
call(["systemctl", "daemon-reload"])
call(["systemctl", "restart", "plugin_loader"])
exit(0)
+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,42 @@
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}?
</div>
</div>
</ModalRoot>
);
};
export default PluginInstallModal;
@@ -52,9 +52,9 @@ export default function UpdaterSettings() {
>
{updateProgress == -1 ? (
<DialogButton
disabled={!versionInfo?.updatable || !versionInfo?.remote || checkingForUpdates}
disabled={!versionInfo?.updatable || checkingForUpdates}
onClick={
versionInfo?.remote?.tag_name == versionInfo?.current
!versionInfo?.remote || versionInfo?.remote?.tag_name == versionInfo?.current
? async () => {
setCheckingForUpdates(true);
const res = (await callUpdaterMethod('check_for_updates')) as { result: VerInfo };
@@ -79,7 +79,7 @@ export default function UpdaterSettings() {
>
{checkingForUpdates
? 'Checking'
: versionInfo?.remote?.tag_name == versionInfo?.current
: !versionInfo?.remote || versionInfo?.remote?.tag_name == versionInfo?.current
? 'Check For Updates'
: 'Install Update'}
</DialogButton>
@@ -88,7 +88,6 @@ export default function UpdaterSettings() {
layout="inline"
bottomSeparator={false}
nProgress={updateProgress}
nTransitionSec={0.01}
indeterminate={reloading}
sOperationText={reloading ? 'Reloading' : 'Updating'}
/>
@@ -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(