summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAAGaming <aa@mail.catvibers.me>2022-06-22 23:22:27 -0400
committerAAGaming <aa@mail.catvibers.me>2022-06-22 23:22:27 -0400
commit9619c52720c97ef74842b15aa732dd0a8d6fb26a (patch)
treeceb46763b919ef8b5f7ac2c0cc0522ad9b6c95b9
parent80b223180e9a5f86bb6f6a42fb4660862872f7ab (diff)
downloaddecky-loader-9619c52720c97ef74842b15aa732dd0a8d6fb26a.tar.gz
decky-loader-9619c52720c97ef74842b15aa732dd0a8d6fb26a.zip
add settings page with install from URL option
-rw-r--r--backend/browser.py13
-rw-r--r--frontend/package.json2
-rw-r--r--frontend/pnpm-lock.yaml8
-rw-r--r--frontend/src/components/PluginView.tsx26
-rw-r--r--frontend/src/components/TitleView.tsx53
-rw-r--r--frontend/src/components/settings/index.tsx19
-rw-r--r--frontend/src/components/settings/pages/GeneralSettings.tsx30
-rw-r--r--frontend/src/components/store/PluginCard.tsx23
-rw-r--r--frontend/src/components/store/Store.tsx20
-rw-r--r--frontend/src/plugin-loader.tsx24
-rw-r--r--frontend/src/tabs-hook.ts4
11 files changed, 152 insertions, 70 deletions
diff --git a/backend/browser.py b/backend/browser.py
index a62e282a..1b802291 100644
--- a/backend/browser.py
+++ b/backend/browser.py
@@ -31,7 +31,7 @@ class PluginBrowser:
def _unzip_to_plugin_dir(self, zip, name, hash):
zip_hash = sha256(zip.getbuffer()).hexdigest()
- if zip_hash != hash:
+ if hash and (zip_hash != hash):
return False
zip_file = ZipFile(zip)
zip_file.extractall(self.plugin_path)
@@ -45,9 +45,8 @@ class PluginBrowser:
rmtree(path.join(self.plugin_path, name), ignore_errors=True)
self.log.info(f"Installing {artifact} (Version: {version})")
async with ClientSession() as client:
- url = f"https://github.com/{artifact}/archive/refs/tags/{version}.zip"
- self.log.debug(f"Fetching {url}")
- res = await client.get(url)
+ self.log.debug(f"Fetching {artifact}")
+ res = await client.get(artifact)
if res.status == 200:
self.log.debug("Got 200. Reading...")
data = await res.read()
@@ -67,14 +66,14 @@ class PluginBrowser:
else:
self.log.fatal(f"SHA-256 Mismatch!!!! {artifact} (Version: {version})")
else:
- self.log.fatal(f"Could not fetch from github. {await res.text()}")
+ self.log.fatal(f"Could not fetch from URL. {await res.text()}")
async def redirect_to_store(self, request):
return web.Response(status=302, headers={"Location": self.store_url})
async def install_plugin(self, request):
data = await request.post()
- get_event_loop().create_task(self.request_plugin_install(data["artifact"], data["version"], data["hash"]))
+ get_event_loop().create_task(self.request_plugin_install(data["artifact"], data.get("version", "dev"), data.get("hash", False)))
return web.Response(text="Requested plugin install")
async def request_plugin_install(self, artifact, version, hash):
@@ -82,7 +81,7 @@ class PluginBrowser:
self.install_requests[request_id] = PluginInstallContext(artifact, version, hash)
tab = await get_tab("SP")
await tab.open_websocket()
- await tab.evaluate_js(f"DeckyPluginLoader.addPluginInstallPrompt('{artifact}', '{version}', '{request_id}')")
+ await tab.evaluate_js(f"DeckyPluginLoader.addPluginInstallPrompt('{artifact}', '{version}', '{request_id}', '{hash}')")
async def confirm_plugin_install(self, request_id):
request = self.install_requests.pop(request_id)
diff --git a/frontend/package.json b/frontend/package.json
index d4a5c12b..803b3e7a 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -37,7 +37,7 @@
}
},
"dependencies": {
- "decky-frontend-lib": "^0.11.0",
+ "decky-frontend-lib": "^1.0.0",
"react-icons": "^4.4.0"
}
}
diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml
index 9f1bd235..1005d1d6 100644
--- a/frontend/pnpm-lock.yaml
+++ b/frontend/pnpm-lock.yaml
@@ -9,7 +9,7 @@ specifiers:
'@types/react': 16.14.0
'@types/react-router': 5.1.18
'@types/webpack': ^5.28.0
- decky-frontend-lib: ^0.11.0
+ decky-frontend-lib: ^1.0.0
husky: ^8.0.1
import-sort-style-module: ^6.0.0
inquirer: ^8.2.4
@@ -23,7 +23,7 @@ specifiers:
typescript: ^4.7.3
dependencies:
- decky-frontend-lib: 0.11.0
+ decky-frontend-lib: 1.0.0
react-icons: 4.4.0_react@16.14.0
devDependencies:
@@ -803,8 +803,8 @@ packages:
ms: 2.1.2
dev: true
- /decky-frontend-lib/0.11.0:
- resolution: {integrity: sha512-pqBW5SQseKIvq59cvEztn6zzI4rGbd+kMx/4utzqun8lbUALODh21BU3NRsBId9TSEcRwPNl1na/QYLRsF9v9A==}
+ /decky-frontend-lib/1.0.0:
+ resolution: {integrity: sha512-ebBLyZEv0z51UmzhUNvULwmZfXsknLIelj1iQeGxfFOEI6JXrrjztcF3PsZVv3rVTTgqRfIQnXqyaaUdaeOUxA==}
dev: false
/deepmerge/4.2.2:
diff --git a/frontend/src/components/PluginView.tsx b/frontend/src/components/PluginView.tsx
index 92650fec..953abd25 100644
--- a/frontend/src/components/PluginView.tsx
+++ b/frontend/src/components/PluginView.tsx
@@ -1,37 +1,17 @@
-import { ButtonItem, DialogButton, PanelSection, PanelSectionRow, Router } from 'decky-frontend-lib';
+import { ButtonItem, PanelSection, PanelSectionRow } from 'decky-frontend-lib';
import { VFC } from 'react';
-import { FaArrowLeft, FaStore } from 'react-icons/fa';
import { useDeckyState } from './DeckyState';
const PluginView: VFC = () => {
- const { plugins, activePlugin, setActivePlugin, closeActivePlugin } = useDeckyState();
-
- const onStoreClick = () => {
- Router.CloseSideMenus();
- Router.Navigate('/decky/store');
- };
+ const { plugins, activePlugin, setActivePlugin } = useDeckyState();
if (activePlugin) {
- return (
- <div style={{ height: '100%' }}>
- <div style={{ position: 'absolute', top: '3px', left: '16px', zIndex: 20 }}>
- <DialogButton style={{ minWidth: 0, padding: '10px 12px' }} onClick={closeActivePlugin}>
- <FaArrowLeft style={{ display: 'block' }} />
- </DialogButton>
- </div>
- {activePlugin.content}
- </div>
- );
+ return <div style={{ height: '100%' }}>{activePlugin.content}</div>;
}
return (
<PanelSection>
- <div style={{ position: 'absolute', top: '3px', right: '16px', zIndex: 20 }}>
- <DialogButton style={{ minWidth: 0, padding: '10px 12px' }} onClick={onStoreClick}>
- <FaStore style={{ display: 'block' }} />
- </DialogButton>
- </div>
{plugins.map(({ name, icon }) => (
<PanelSectionRow key={name}>
<ButtonItem layout="below" onClick={() => setActivePlugin(name)}>
diff --git a/frontend/src/components/TitleView.tsx b/frontend/src/components/TitleView.tsx
index 4b4a6825..babcd316 100644
--- a/frontend/src/components/TitleView.tsx
+++ b/frontend/src/components/TitleView.tsx
@@ -1,18 +1,59 @@
-import { staticClasses } from 'decky-frontend-lib';
-import { VFC } from 'react';
+import { DialogButton, Focusable, Router, staticClasses } from 'decky-frontend-lib';
+import { CSSProperties, VFC } from 'react';
+import { FaArrowLeft, FaCog, FaStore } from 'react-icons/fa';
import { useDeckyState } from './DeckyState';
+const titleStyles: CSSProperties = {
+ display: 'flex',
+ paddingTop: '3px',
+ paddingBottom: '14px',
+ paddingRight: '16px',
+ boxShadow: 'unset',
+};
+
const TitleView: VFC = () => {
- const { activePlugin } = useDeckyState();
+ const { activePlugin, closeActivePlugin } = useDeckyState();
+
+ const onSettingsClick = () => {
+ Router.CloseSideMenus();
+ Router.Navigate('/decky/settings');
+ };
+
+ const onStoreClick = () => {
+ Router.CloseSideMenus();
+ Router.Navigate('/decky/store');
+ };
if (activePlugin === null) {
- return <div className={staticClasses.Title}>Decky</div>;
+ return (
+ <Focusable style={titleStyles} className={staticClasses.Title}>
+ <DialogButton
+ style={{ height: '28px', width: '40px', minWidth: 0, padding: '10px 12px' }}
+ onClick={onSettingsClick}
+ >
+ <FaCog style={{ marginTop: '-4px', display: 'block' }} />
+ </DialogButton>
+ <div style={{ marginRight: 'auto', flex: 0.9 }}>Decky</div>
+ <DialogButton
+ style={{ height: '28px', width: '40px', minWidth: 0, padding: '10px 12px' }}
+ onClick={onStoreClick}
+ >
+ <FaStore style={{ marginTop: '-4px', display: 'block' }} />
+ </DialogButton>
+ </Focusable>
+ );
}
return (
- <div className={staticClasses.Title} style={{ paddingLeft: '60px' }}>
- {activePlugin.name}
+ <div className={staticClasses.Title} style={titleStyles}>
+ <DialogButton
+ style={{ height: '28px', width: '40px', minWidth: 0, padding: '10px 12px' }}
+ onClick={closeActivePlugin}
+ >
+ <FaArrowLeft style={{ marginTop: '-4px', display: 'block' }} />
+ </DialogButton>
+ <div style={{ flex: 0.9 }}>{activePlugin.name}</div>
</div>
);
};
diff --git a/frontend/src/components/settings/index.tsx b/frontend/src/components/settings/index.tsx
new file mode 100644
index 00000000..d4799fa9
--- /dev/null
+++ b/frontend/src/components/settings/index.tsx
@@ -0,0 +1,19 @@
+import { SidebarNavigation } from 'decky-frontend-lib';
+
+import GeneralSettings from './pages/GeneralSettings';
+
+export default function SettingsPage() {
+ return (
+ <SidebarNavigation
+ title="Decky Settings"
+ showTitle
+ pages={[
+ {
+ title: 'General',
+ content: <GeneralSettings />,
+ route: '/decky/settings/general',
+ },
+ ]}
+ />
+ );
+}
diff --git a/frontend/src/components/settings/pages/GeneralSettings.tsx b/frontend/src/components/settings/pages/GeneralSettings.tsx
new file mode 100644
index 00000000..1cc8076d
--- /dev/null
+++ b/frontend/src/components/settings/pages/GeneralSettings.tsx
@@ -0,0 +1,30 @@
+import { DialogButton, Field, TextField } from 'decky-frontend-lib';
+import { useState } from 'react';
+import { FaShapes } from 'react-icons/fa';
+
+import { installFromURL } from '../../store/Store';
+
+export default function GeneralSettings() {
+ const [pluginURL, setPluginURL] = useState('');
+ // const [checked, setChecked] = useState(false); // store these in some kind of State instead
+ return (
+ <div>
+ {/* <Field
+ label="A Toggle with an icon"
+ icon={<FaShapes style={{ display: 'block' }} />}
+ >
+ <Toggle
+ value={checked}
+ onChange={(e) => setChecked(e)}
+ />
+ </Field> */}
+ <Field
+ label="Manual plugin install"
+ description={<TextField label={'URL'} value={pluginURL} onChange={(e) => setPluginURL(e?.target.value)} />}
+ icon={<FaShapes style={{ display: 'block' }} />}
+ >
+ <DialogButton onClick={() => installFromURL(pluginURL)}>Install</DialogButton>
+ </Field>
+ </div>
+ );
+}
diff --git a/frontend/src/components/store/PluginCard.tsx b/frontend/src/components/store/PluginCard.tsx
index 7816d1bb..28989a88 100644
--- a/frontend/src/components/store/PluginCard.tsx
+++ b/frontend/src/components/store/PluginCard.tsx
@@ -2,6 +2,7 @@ import {
DialogButton,
Dropdown,
Focusable,
+ QuickAccessTab,
Router,
SingleDropdownOption,
SuspensefulImage,
@@ -9,7 +10,7 @@ import {
} from 'decky-frontend-lib';
import { FC, useRef, useState } from 'react';
-import { StorePlugin } from './Store';
+import { StorePlugin, requestPluginInstall } from './Store';
interface PluginCardProps {
plugin: StorePlugin;
@@ -19,17 +20,6 @@ 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);
@@ -50,9 +40,12 @@ const PluginCard: FC<PluginCardProps> = ({ plugin }) => {
buttonRef.current!.focus();
}}
onCancel={(e: CustomEvent) => {
- containerRef.current!.querySelectorAll('* :focus').length === 0
- ? Router.NavigateBackOrOpenMenu()
- : containerRef.current!.focus();
+ if (containerRef.current!.querySelectorAll('* :focus').length === 0) {
+ Router.NavigateBackOrOpenMenu();
+ setTimeout(() => Router.OpenQuickAccessMenu(QuickAccessTab.Decky), 1000);
+ } else {
+ containerRef.current!.focus();
+ }
}}
style={{
display: 'flex',
diff --git a/frontend/src/components/store/Store.tsx b/frontend/src/components/store/Store.tsx
index ebb2bb8e..0e99a3c2 100644
--- a/frontend/src/components/store/Store.tsx
+++ b/frontend/src/components/store/Store.tsx
@@ -13,6 +13,26 @@ export interface StorePlugin {
tags: string[];
}
+export async function installFromURL(url: string) {
+ const formData = new FormData();
+ formData.append('artifact', url);
+ await fetch('http://localhost:1337/browser/install_plugin', {
+ method: 'POST',
+ body: formData,
+ });
+}
+
+export async function requestPluginInstall(plugin: StorePlugin, selectedVer: string) {
+ const formData = new FormData();
+ 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,
+ });
+}
+
const StorePage: FC<{}> = () => {
const [data, setData] = useState<StorePlugin[] | null>(null);
diff --git a/frontend/src/plugin-loader.tsx b/frontend/src/plugin-loader.tsx
index 9b1020c7..cad59c2f 100644
--- a/frontend/src/plugin-loader.tsx
+++ b/frontend/src/plugin-loader.tsx
@@ -1,9 +1,10 @@
-import { ModalRoot, showModal, 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 PluginView from './components/PluginView';
+import SettingsPage from './components/settings';
import StorePage from './components/store/Store';
import TitleView from './components/TitleView';
import Logger from './logger';
@@ -31,14 +32,11 @@ class PluginLoader extends Logger {
this.log('Initialized');
this.tabsHook.add({
- id: 'main',
- title: (
- <DeckyStateContextProvider deckyState={this.deckyState}>
- <TitleView />
- </DeckyStateContextProvider>
- ),
+ id: QuickAccessTab.Decky,
+ title: null,
content: (
<DeckyStateContextProvider deckyState={this.deckyState}>
+ <TitleView />
<PluginView />
</DeckyStateContextProvider>
),
@@ -46,22 +44,23 @@ class PluginLoader extends Logger {
});
this.routerHook.addRoute('/decky/store', () => <StorePage />);
+ this.routerHook.addRoute('/decky/settings', () => <SettingsPage />);
}
- public addPluginInstallPrompt(artifact: string, version: string, request_id: string) {
+ public addPluginInstallPrompt(artifact: string, version: string, request_id: string, hash: string) {
showModal(
<ModalRoot
onOK={() => {
- console.log('ok');
this.callServerMethod('confirm_plugin_install', { request_id });
}}
onCancel={() => {
- console.log('nope');
this.callServerMethod('cancel_plugin_install', { request_id });
}}
>
- <div className={staticClasses.Title}>
- Install {artifact} version {version}?
+ <div className={staticClasses.Title} style={{ flexDirection: 'column' }}>
+ {hash == 'False' ? <h1 style={{ color: 'red' }}>!!!!NO HASH PROVIDED!!!!</h1> : null}
+ Install {artifact}
+ {version ? ' version ' + version : null}?
</div>
</ModalRoot>,
);
@@ -76,6 +75,7 @@ class PluginLoader extends Logger {
public deinit() {
this.routerHook.removeRoute('/decky/store');
+ this.routerHook.removeRoute('/decky/settings');
}
public async importPlugin(name: string) {
diff --git a/frontend/src/tabs-hook.ts b/frontend/src/tabs-hook.ts
index de03310e..b364712d 100644
--- a/frontend/src/tabs-hook.ts
+++ b/frontend/src/tabs-hook.ts
@@ -1,4 +1,4 @@
-import { afterPatch, sleep, unpatch } from 'decky-frontend-lib';
+import { QuickAccessTab, afterPatch, sleep, unpatch } from 'decky-frontend-lib';
import { memo } from 'react';
import Logger from './logger';
@@ -18,7 +18,7 @@ const isTabsArray = (tabs: any) => {
};
interface Tab {
- id: string;
+ id: QuickAccessTab | number;
title: any;
content: any;
icon: any;