diff options
| -rw-r--r-- | backend/browser.py | 13 | ||||
| -rw-r--r-- | frontend/package.json | 2 | ||||
| -rw-r--r-- | frontend/pnpm-lock.yaml | 8 | ||||
| -rw-r--r-- | frontend/src/components/PluginView.tsx | 26 | ||||
| -rw-r--r-- | frontend/src/components/TitleView.tsx | 53 | ||||
| -rw-r--r-- | frontend/src/components/settings/index.tsx | 19 | ||||
| -rw-r--r-- | frontend/src/components/settings/pages/GeneralSettings.tsx | 30 | ||||
| -rw-r--r-- | frontend/src/components/store/PluginCard.tsx | 23 | ||||
| -rw-r--r-- | frontend/src/components/store/Store.tsx | 20 | ||||
| -rw-r--r-- | frontend/src/plugin-loader.tsx | 24 | ||||
| -rw-r--r-- | frontend/src/tabs-hook.ts | 4 |
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; |
