From bc0460617acecc93e429376ad44b2daab7749396 Mon Sep 17 00:00:00 2001 From: Party Wumpus <48649272+PartyWumpus@users.noreply.github.com> Date: Sat, 22 Apr 2023 15:00:04 +0100 Subject: [PATCH 01/39] Update helpers.py --- backend/helpers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/helpers.py b/backend/helpers.py index 668a252d3..fed7d17f6 100644 --- a/backend/helpers.py +++ b/backend/helpers.py @@ -32,7 +32,7 @@ def get_csrf_token(): @middleware async def csrf_middleware(request, handler): - if str(request.method) == "OPTIONS" or request.headers.get('Authentication') == csrf_token or str(request.rel_url) == "/auth/token" or str(request.rel_url).startswith("/plugins/load_main/") or str(request.rel_url).startswith("/static/") or str(request.rel_url).startswith("/legacy/") or str(request.rel_url).startswith("/steam_resource/") or str(request.rel_url).startswith("/frontend/") or assets_regex.match(str(request.rel_url)) or frontend_regex.match(str(request.rel_url)): + if str(request.method) == "OPTIONS" or request.headers.get('Authentication') == csrf_token or str(request.rel_url) == "/auth/token" or str(request.rel_url).startswith("/plugins/load_main/") or str(request.rel_url).startswith("/static/") or str(request.rel_url).startswith("/legacy/") or str(request.rel_url).startswith("/steam_resource/") or str(request.rel_url).startswith("/frontend/") or str(request.rel_url).startswith("/docs/") or assets_regex.match(str(request.rel_url)) or frontend_regex.match(str(request.rel_url)): return await handler(request) return Response(text='Forbidden', status='403') @@ -159,4 +159,4 @@ async def stop_systemd_unit(unit_name: str) -> bool: return await localplatform.service_stop(unit_name) async def start_systemd_unit(unit_name: str) -> bool: - return await localplatform.service_start(unit_name) \ No newline at end of file + return await localplatform.service_start(unit_name) From e92a3724d1facc3f5d404e1ea885b3e5ddd5ecfc Mon Sep 17 00:00:00 2001 From: Party Wumpus <48649272+PartyWumpus@users.noreply.github.com> Date: Sun, 23 Apr 2023 11:15:08 +0100 Subject: [PATCH 02/39] Update loader.py --- backend/loader.py | 73 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/backend/loader.py b/backend/loader.py index a21aad095..b731a879f 100644 --- a/backend/loader.py +++ b/backend/loader.py @@ -81,6 +81,7 @@ def __init__(self, server_instance, plugin_path, loop, live_reload=False) -> Non web.get("/plugins/{plugin_name}/frontend_bundle", self.handle_frontend_bundle), web.post("/plugins/{plugin_name}/methods/{method_name}", self.handle_plugin_method_call), web.get("/plugins/{plugin_name}/assets/{path:.*}", self.handle_plugin_frontend_assets), + web.get("/docs/{plugin_name}/{language}", self.get_plugin_documentation), # The following is legacy plugin code. web.get("/plugins/load_main/{name}", self.load_plugin_main_view), @@ -108,6 +109,78 @@ def handle_plugin_frontend_assets(self, request): file = path.join(self.plugin_path, plugin.plugin_directory, "dist/assets", request.match_info["path"]) return web.FileResponse(file, headers={"Cache-Control": "no-cache"}) + +def get_plugin_documentation(self, request): + plugin_name, language = request.match_info["plugin_name"], request.match_info["language"] + self.logger.info(f"Loading docs for {plugin_name}") + docs = {} # {"filename": {"name":"readable name", "text":"marked up file"}} + plugin_path = path.join(self.plugin_path, self.plugins[plugin_name].plugin_directory) + + if not path.exists(path.join(plugin_path, "docs")): + try: + with open(path.join(plugin_path, "README.md")) as f: + return web.json_response({"readme.md":{"name":"readme","text":f.read()}}) + except: + raise Exception(f"Failed to load readme file for {plugin_name} at {plugin_path}") + + fallback_config = {"default_language": "en-US", "include_readme": "False"} + try: + with open(path.join(plugin_path, "docs", "config.json")) as f: + config = load(f) + except: + self.logger.warning(f"unable to load config.json for {plugin_name} at {plugin_path}") + config = fallback_config + + # find list of files for default language and use those as a base + if "default_language" in config: + base_docs_path = path.join(plugin_path, "docs", config["default_language"]) + else: + base_docs_path = path.join(plugin_path, "docs", fallback_config["default_language"]) + + try: + with open(path.join(base_docs_path, "docs.json")) as f: + files = load(f) + for filename in files: + docs[filename] = {"name":files[filename],"fallback_path":path.join(base_docs_path, filename)} + + except: # docs.json is invalid in some way, so just use all the files in the docs folder + self.logger.warning(f"unable to load docs.json for {plugin_name} at {base_docs_path}") + files = listdir(base_docs_path) + for filename in files: + docs[filename] = {"name":filename,"fallback_path":path.join(base_docs_path, filename)} + + docs_path = path.join(plugin_path, "docs", language) + try: + with open(path.join(docs_path, "docs.json")) as f: + files = load(f) + for filename in files: + docs[filename] = {"name":files[filename],"path":path.join(docs_path, filename)} + + except: # docs.json is invalid in some way, so just use all the files in the docs folder + self.logger.warning(f"unable to load docs.json for {plugin_name} at {docs_path}") + files = listdir(docs_path) + for filename in files: + docs[filename] = {"name":filename,"path":path.join(docs_path, filename)} + + for page in docs: + try: + with open(docs[page]["path"]) as f: + docs[page]["text"] = f.read() + except: + try: + with open(docs[page]["fallback_path"]) as f: + docs[page]["text"] = f.read() + except: + self.logger.warning(f"unable to load {page} for {plugin_name} at {plugin_path}") + + if "include_readme" in config and config["include_readme"] == "True": + try: + with open(path.join(plugin_path, "README.md")) as f: + docs["README"] = {"name":"readme","text": f.read()} + except: + self.logger.warning(f"unable to load readme for {plugin_name} at {plugin_path}") + + return web.json_response(docs) def handle_frontend_bundle(self, request): plugin = self.plugins[request.match_info["plugin_name"]] From badcf0224e4e5d5cfe3f45bbdbb9becc8b916588 Mon Sep 17 00:00:00 2001 From: Party Wumpus <48649272+PartyWumpus@users.noreply.github.com> Date: Sun, 23 Apr 2023 11:16:13 +0100 Subject: [PATCH 03/39] Update TitleView.tsx --- frontend/src/components/TitleView.tsx | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/frontend/src/components/TitleView.tsx b/frontend/src/components/TitleView.tsx index 7e529d21e..bb9bc4b33 100644 --- a/frontend/src/components/TitleView.tsx +++ b/frontend/src/components/TitleView.tsx @@ -1,7 +1,7 @@ import { DialogButton, Focusable, Router, staticClasses } from 'decky-frontend-lib'; import { CSSProperties, VFC } from 'react'; import { BsGearFill } from 'react-icons/bs'; -import { FaArrowLeft, FaStore } from 'react-icons/fa'; +import { FaArrowLeft, FaStore, FaInfo } from 'react-icons/fa'; import { useDeckyState } from './DeckyState'; @@ -24,6 +24,11 @@ const TitleView: VFC = () => { Router.Navigate('/decky/store'); }; + const onInfoClick = () => { + Router.CloseSideMenus(); + Router.Navigate(`/decky/docs/${activePlugin.name}`); + }; + if (activePlugin === null) { return ( @@ -53,6 +58,12 @@ const TitleView: VFC = () => {
{activePlugin.name}
+ + + ); }; From e5eafb41747a3bc292a09cc4e08eb724a57ab027 Mon Sep 17 00:00:00 2001 From: Party Wumpus <48649272+PartyWumpus@users.noreply.github.com> Date: Sun, 23 Apr 2023 11:18:45 +0100 Subject: [PATCH 04/39] Update plugin-loader.tsx --- frontend/src/plugin-loader.tsx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/frontend/src/plugin-loader.tsx b/frontend/src/plugin-loader.tsx index a3381de74..44a7dfffd 100644 --- a/frontend/src/plugin-loader.tsx +++ b/frontend/src/plugin-loader.tsx @@ -22,6 +22,7 @@ import { getSetting } from './utils/settings'; const StorePage = lazy(() => import('./components/store/Store')); const SettingsPage = lazy(() => import('./components/settings')); +const DocsPage = lazy(() => import('./components/docs')); const FilePicker = lazy(() => import('./components/modals/filepicker')); @@ -79,6 +80,11 @@ class PluginLoader extends Logger { ); }); + this.routerHook.addRoute('/decky/docs/:plugin', () => ( + + + + )); initSteamFixes(); From ab92b543adef2f936416b5254895afbe32467d08 Mon Sep 17 00:00:00 2001 From: Party Wumpus <48649272+PartyWumpus@users.noreply.github.com> Date: Sun, 23 Apr 2023 11:19:15 +0100 Subject: [PATCH 05/39] Add files via upload --- frontend/src/components/docs.tsx | 62 ++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 frontend/src/components/docs.tsx diff --git a/frontend/src/components/docs.tsx b/frontend/src/components/docs.tsx new file mode 100644 index 000000000..9de1554ab --- /dev/null +++ b/frontend/src/components/docs.tsx @@ -0,0 +1,62 @@ +import { FC, useEffect, useState } from 'react'; +import { useParams, SidebarNavigation, SteamSpinner } from "decky-frontend-lib"; +import i18n from 'i18next'; +import ReactMarkdown from 'react-markdown' + +const StorePage: FC<{}> = () => { + + const [docs, setDocs] = useState(null); // {"filename": {"name":"readable name", "text":"marked up file"}} + + useEffect(() => { + (async () => { + setDocs(await (await fetch(`http://127.0.0.1:1337/docs/${plugin}/${i18n.resolvedLanguage}`, { + method: 'GET', + credentials: 'include', + headers: { + 'Content-Type': 'application/json', + Authentication: window.deckyAuthToken, + } + })).json()) + })(); + }, []); + + const { plugin } = useParams<{ plugin: string }>() + + return ( + <> + + {!docs ? ( +
+ +
+ ) : + ( + { + title: docs[file]["name"], + content:
, + route: `/decky/docs/${plugin}/${file}`, + } + ))} + /> + } + + ) +} + +/* + { + title: "Page 1", + content: *testing 1 2 3*, + route: `/decky/docs/${plugin}/1`, + }, +*/ + +export default StorePage; From ab308dd970aa121932eec54140d6c27d7c6f3e59 Mon Sep 17 00:00:00 2001 From: Party Wumpus <48649272+PartyWumpus@users.noreply.github.com> Date: Sun, 23 Apr 2023 11:20:12 +0100 Subject: [PATCH 06/39] Rename docs.tsx to Docs.tsx --- frontend/src/components/{docs.tsx => Docs.tsx} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename frontend/src/components/{docs.tsx => Docs.tsx} (100%) diff --git a/frontend/src/components/docs.tsx b/frontend/src/components/Docs.tsx similarity index 100% rename from frontend/src/components/docs.tsx rename to frontend/src/components/Docs.tsx From d524f27f7d639e82962c2c7d8dbf4dfa294c9693 Mon Sep 17 00:00:00 2001 From: Party Wumpus <48649272+PartyWumpus@users.noreply.github.com> Date: Sun, 23 Apr 2023 11:20:30 +0100 Subject: [PATCH 07/39] Update plugin-loader.tsx --- frontend/src/plugin-loader.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/plugin-loader.tsx b/frontend/src/plugin-loader.tsx index 44a7dfffd..09ddc8d70 100644 --- a/frontend/src/plugin-loader.tsx +++ b/frontend/src/plugin-loader.tsx @@ -22,7 +22,7 @@ import { getSetting } from './utils/settings'; const StorePage = lazy(() => import('./components/store/Store')); const SettingsPage = lazy(() => import('./components/settings')); -const DocsPage = lazy(() => import('./components/docs')); +const DocsPage = lazy(() => import('./components/Docs')); const FilePicker = lazy(() => import('./components/modals/filepicker')); From 8303ea5aae89969ec0882b44f053c8ac55d734fe Mon Sep 17 00:00:00 2001 From: Party Wumpus <48649272+PartyWumpus@users.noreply.github.com> Date: Sun, 23 Apr 2023 11:24:20 +0100 Subject: [PATCH 08/39] Update Docs.tsx --- frontend/src/components/Docs.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/frontend/src/components/Docs.tsx b/frontend/src/components/Docs.tsx index 9de1554ab..e74ab3d79 100644 --- a/frontend/src/components/Docs.tsx +++ b/frontend/src/components/Docs.tsx @@ -6,6 +6,7 @@ import ReactMarkdown from 'react-markdown' const StorePage: FC<{}> = () => { const [docs, setDocs] = useState(null); // {"filename": {"name":"readable name", "text":"marked up file"}} + const { plugin } = useParams<{ plugin: string }>() useEffect(() => { (async () => { @@ -20,8 +21,6 @@ const StorePage: FC<{}> = () => { })(); }, []); - const { plugin } = useParams<{ plugin: string }>() - return ( <> - {!docs ? ( + {!docs ?
- ) : + : (Object.keys(docs).length == 1) ? + + + + : ( { title: docs[file]["name"], - content:
, + content:
, route: `/decky/docs/${plugin}/${file}`, } ))} From de90dc0d2cc706bf765e043bb7707ce16f16b0b8 Mon Sep 17 00:00:00 2001 From: Party Wumpus <48649272+PartyWumpus@users.noreply.github.com> Date: Sun, 23 Apr 2023 14:40:23 +0100 Subject: [PATCH 15/39] use remarkGfm because i can --- frontend/src/components/Docs.tsx | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/frontend/src/components/Docs.tsx b/frontend/src/components/Docs.tsx index 3071addf9..254901fd8 100644 --- a/frontend/src/components/Docs.tsx +++ b/frontend/src/components/Docs.tsx @@ -1,7 +1,8 @@ import { FC, useEffect, useState } from 'react'; -import { useParams, SidebarNavigation, SteamSpinner } from "decky-frontend-lib"; +import { useParams, SidebarNavigation, SteamSpinner, Focusable } from "decky-frontend-lib"; import i18n from 'i18next'; import ReactMarkdown from 'react-markdown' +import remarkGfm from 'remark-gfm'; const StorePage: FC<{}> = () => { @@ -25,8 +26,8 @@ const StorePage: FC<{}> = () => { <> @@ -36,7 +37,7 @@ const StorePage: FC<{}> = () => { : (Object.keys(docs).length == 1) ? - + : = () => { pages={Object.keys(docs).map((file) => ( { title: docs[file]["name"], - content:
, + content:
, route: `/decky/docs/${plugin}/${file}`, } ))} @@ -55,4 +56,5 @@ const StorePage: FC<{}> = () => { ) } + export default StorePage; From 4d07edd0d1fe6278667b929e6828fd8f6d7b76aa Mon Sep 17 00:00:00 2001 From: Party Wumpus <48649272+PartyWumpus@users.noreply.github.com> Date: Mon, 24 Apr 2023 18:51:20 +0100 Subject: [PATCH 16/39] Make links work --- frontend/src/components/Docs.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/frontend/src/components/Docs.tsx b/frontend/src/components/Docs.tsx index 254901fd8..08bae8263 100644 --- a/frontend/src/components/Docs.tsx +++ b/frontend/src/components/Docs.tsx @@ -1,8 +1,9 @@ import { FC, useEffect, useState } from 'react'; import { useParams, SidebarNavigation, SteamSpinner, Focusable } from "decky-frontend-lib"; +import { lazy } from 'react'; import i18n from 'i18next'; -import ReactMarkdown from 'react-markdown' -import remarkGfm from 'remark-gfm'; + +const MarkdownRenderer = lazy(() => import('./Markdown')); const StorePage: FC<{}> = () => { @@ -37,7 +38,7 @@ const StorePage: FC<{}> = () => { : (Object.keys(docs).length == 1) ? - + : = () => { pages={Object.keys(docs).map((file) => ( { title: docs[file]["name"], - content:
, + content: , route: `/decky/docs/${plugin}/${file}`, } ))} @@ -56,5 +57,4 @@ const StorePage: FC<{}> = () => { ) } - export default StorePage; From bc2e9a5dbe530873b6bcb59fe62ef7b0d6367aa9 Mon Sep 17 00:00:00 2001 From: Party Wumpus <48649272+PartyWumpus@users.noreply.github.com> Date: Tue, 25 Apr 2023 20:22:17 +0100 Subject: [PATCH 17/39] Add scrolling support + other stuff --- frontend/src/components/Docs.tsx | 45 ++++++--- frontend/src/components/Markdown.tsx | 2 +- frontend/src/components/Scrollable.tsx | 127 +++++++++++++++++++++++++ frontend/src/components/TitleView.tsx | 2 +- 4 files changed, 163 insertions(+), 13 deletions(-) create mode 100644 frontend/src/components/Scrollable.tsx diff --git a/frontend/src/components/Docs.tsx b/frontend/src/components/Docs.tsx index 08bae8263..cdfec681c 100644 --- a/frontend/src/components/Docs.tsx +++ b/frontend/src/components/Docs.tsx @@ -1,11 +1,32 @@ -import { FC, useEffect, useState } from 'react'; +import { VFC, useEffect, useState } from 'react'; import { useParams, SidebarNavigation, SteamSpinner, Focusable } from "decky-frontend-lib"; import { lazy } from 'react'; import i18n from 'i18next'; +import { ScrollArea, Scrollable, scrollableRef } from "./Scrollable"; const MarkdownRenderer = lazy(() => import('./Markdown')); -const StorePage: FC<{}> = () => { +const DocsPage: VFC<{ content: string }> = ({ content }) => { + const ref = scrollableRef(); + return ( + <> + + + + + + + + ) +} + +const StorePage: VFC<{}> = () => { const [docs, setDocs] = useState(null); // {"filename": {"name":"readable name", "text":"marked up file"}} const { plugin } = useParams<{ plugin: string }>() @@ -25,13 +46,6 @@ const StorePage: FC<{}> = () => { return ( <> - {!docs ?
@@ -43,12 +57,13 @@ const StorePage: FC<{}> = () => { : ( { title: docs[file]["name"], - content: , + content:, route: `/decky/docs/${plugin}/${file}`, + hideTitle: true, } ))} /> @@ -57,4 +72,12 @@ const StorePage: FC<{}> = () => { ) } +/* + + + + +
+*/ + export default StorePage; diff --git a/frontend/src/components/Markdown.tsx b/frontend/src/components/Markdown.tsx index 917765996..71045a884 100644 --- a/frontend/src/components/Markdown.tsx +++ b/frontend/src/components/Markdown.tsx @@ -26,7 +26,7 @@ const Markdown: FunctionComponent = (props) => { }} style={{ display: 'inline' }} > - + {nodeProps.children} diff --git a/frontend/src/components/Scrollable.tsx b/frontend/src/components/Scrollable.tsx new file mode 100644 index 000000000..e0f297a56 --- /dev/null +++ b/frontend/src/components/Scrollable.tsx @@ -0,0 +1,127 @@ +import { FC, ForwardRefExoticComponent } from "react" +import { + Focusable, + FocusableProps, + GamepadEvent, + GamepadButton, + ServerAPI, +} from "decky-frontend-lib" +import React, { useRef } from "react" + +const DEFAULTSCROLLSPEED = 50 + +export interface ScrollableElement extends HTMLDivElement {} + +export function scrollableRef() { + return useRef(null) +} + +export const Scrollable: ForwardRefExoticComponent = React.forwardRef( + (props, ref) => { + if (!props.style) { + props.style = {} + } + // props.style.minHeight = '100%'; + // props.style.maxHeight = '80%'; + props.style.height = "85vh" // was 95vh previously! + props.style.overflowY = "scroll" + + return ( + +
+ + ) + } +) + +interface ScrollAreaProps extends FocusableProps { + scrollable: React.RefObject + scrollSpeed?: number + serverApi?: ServerAPI + noFocusRing?: boolean +} + +// const writeLog = async (serverApi: ServerAPI, content: any) => { +// let text = `${content}` +// serverApi.callPluginMethod<{ content: string }>("log", { content: text }) +// } + +const scrollOnDirection = ( + e: GamepadEvent, + ref: React.RefObject, + amt: number, + prev: React.RefObject, + next: React.RefObject +) => { + let childNodes = ref.current?.childNodes + let currentIndex = null + childNodes?.forEach((node, i) => { + if (node == e.currentTarget) { + currentIndex = i + } + }) + + // @ts-ignore + let pos = e.currentTarget?.getBoundingClientRect() + let out = ref.current?.getBoundingClientRect() + + if (e.detail.button == GamepadButton.DIR_DOWN) { + if ( + out?.bottom != undefined && + pos.bottom <= out.bottom && + currentIndex != null && + childNodes != undefined && + currentIndex + 1 < childNodes.length + ) { + next.current?.focus() + } else { + ref.current?.scrollBy({ top: amt, behavior: "auto" }) + } + } else if (e.detail.button == GamepadButton.DIR_UP) { + if ( + out?.top != undefined && + pos.top >= out.top && + currentIndex != null && + childNodes != undefined && + currentIndex - 1 >= 0 + ) { + prev.current?.focus() + } else { + ref.current?.scrollBy({ top: -amt, behavior: "auto" }) + } + } else if (e.detail.button == GamepadButton.DIR_LEFT) { + throw("this is not a real error, just a (temporary?) workaround to make navigation work in docs") + } +} + +export const ScrollArea: FC = (props) => { + let scrollSpeed = DEFAULTSCROLLSPEED + if (props.scrollSpeed) { + scrollSpeed = props.scrollSpeed + } + + const prevFocus = useRef(null) + const nextFocus = useRef(null) + + props.onActivate = (e) => { + const ele = e.currentTarget as HTMLElement + ele.focus() + } + props.onGamepadDirection = (e) => { + scrollOnDirection( + e, + props.scrollable, + scrollSpeed, + prevFocus, + nextFocus + ) + } + + return ( + + {}} /> + + {}} /> + + ) +} diff --git a/frontend/src/components/TitleView.tsx b/frontend/src/components/TitleView.tsx index fe4fdfb85..99fd33d13 100644 --- a/frontend/src/components/TitleView.tsx +++ b/frontend/src/components/TitleView.tsx @@ -50,7 +50,7 @@ const TitleView: VFC = () => { } return ( - + Date: Tue, 25 Apr 2023 20:28:37 +0100 Subject: [PATCH 18/39] give credit --- frontend/src/components/Docs.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/frontend/src/components/Docs.tsx b/frontend/src/components/Docs.tsx index cdfec681c..ef7ca9936 100644 --- a/frontend/src/components/Docs.tsx +++ b/frontend/src/components/Docs.tsx @@ -1,3 +1,5 @@ +// this file was mostly (the good bits) made by davocarli#7308 +// it should probably be moved to DFL eventually import { VFC, useEffect, useState } from 'react'; import { useParams, SidebarNavigation, SteamSpinner, Focusable } from "decky-frontend-lib"; import { lazy } from 'react'; From 62d677732385b898fc822aff9ed6f6135192e689 Mon Sep 17 00:00:00 2001 From: Party Wumpus <48649272+PartyWumpus@users.noreply.github.com> Date: Tue, 25 Apr 2023 20:28:55 +0100 Subject: [PATCH 19/39] wrong file woops --- frontend/src/components/Docs.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/frontend/src/components/Docs.tsx b/frontend/src/components/Docs.tsx index ef7ca9936..cdfec681c 100644 --- a/frontend/src/components/Docs.tsx +++ b/frontend/src/components/Docs.tsx @@ -1,5 +1,3 @@ -// this file was mostly (the good bits) made by davocarli#7308 -// it should probably be moved to DFL eventually import { VFC, useEffect, useState } from 'react'; import { useParams, SidebarNavigation, SteamSpinner, Focusable } from "decky-frontend-lib"; import { lazy } from 'react'; From 2681064c8bbad0d149807a942143274844b2cff6 Mon Sep 17 00:00:00 2001 From: Party Wumpus <48649272+PartyWumpus@users.noreply.github.com> Date: Tue, 25 Apr 2023 20:29:11 +0100 Subject: [PATCH 20/39] credit properly --- frontend/src/components/Scrollable.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/frontend/src/components/Scrollable.tsx b/frontend/src/components/Scrollable.tsx index e0f297a56..dcacb3cc9 100644 --- a/frontend/src/components/Scrollable.tsx +++ b/frontend/src/components/Scrollable.tsx @@ -1,3 +1,5 @@ +// this file was mostly (the good bits) made by davocarli#7308 +// it should probably be moved to DFL eventually import { FC, ForwardRefExoticComponent } from "react" import { Focusable, From 73583181f10a79d36c87faf28e1b12ed52373623 Mon Sep 17 00:00:00 2001 From: Party Wumpus <48649272+PartyWumpus@users.noreply.github.com> Date: Wed, 26 Apr 2023 17:34:03 +0100 Subject: [PATCH 21/39] add scrolling to solo pages --- frontend/src/components/Docs.tsx | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/frontend/src/components/Docs.tsx b/frontend/src/components/Docs.tsx index cdfec681c..d84b37056 100644 --- a/frontend/src/components/Docs.tsx +++ b/frontend/src/components/Docs.tsx @@ -1,5 +1,5 @@ import { VFC, useEffect, useState } from 'react'; -import { useParams, SidebarNavigation, SteamSpinner, Focusable } from "decky-frontend-lib"; +import { useParams, SidebarNavigation, SteamSpinner } from "decky-frontend-lib"; import { lazy } from 'react'; import i18n from 'i18next'; @@ -8,6 +8,7 @@ const MarkdownRenderer = lazy(() => import('./Markdown')); const DocsPage: VFC<{ content: string }> = ({ content }) => { const ref = scrollableRef(); + return ( <> - + From 814c94f2e815fa211a9a5028853214443a011801 Mon Sep 17 00:00:00 2001 From: Party Wumpus <48649272+PartyWumpus@users.noreply.github.com> Date: Sun, 16 Jul 2023 17:45:29 +0100 Subject: [PATCH 30/39] Update loader.py --- backend/loader.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/loader.py b/backend/loader.py index 456504f57..a8a8a366c 100644 --- a/backend/loader.py +++ b/backend/loader.py @@ -154,7 +154,7 @@ def get_plugin_documentation(self, request): config["use_translation"] = "True" else: config["use_translation"] = "False" - if config["use_translation"] == "True": docs_file_path = path.join(docs_path, config["default_language"]) + if config["use_translation"] == "True": docs_file_path = path.join(docs_path, language) elif config["use_translation"] == "False": docs_file_path = docs_path if config["file_list"] == None: From 5b3e28b581a38200a62893858473229875f44f38 Mon Sep 17 00:00:00 2001 From: Party Wumpus <48649272+PartyWumpus@users.noreply.github.com> Date: Mon, 17 Jul 2023 10:42:34 +0100 Subject: [PATCH 31/39] Update Docs.tsx --- frontend/src/components/Docs.tsx | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/frontend/src/components/Docs.tsx b/frontend/src/components/Docs.tsx index 0211cc75d..c6f688df9 100644 --- a/frontend/src/components/Docs.tsx +++ b/frontend/src/components/Docs.tsx @@ -23,7 +23,6 @@ const DocsPage: VFC<{ content: string }> = ({ content }) => { .decky-docs-markdown > .Panel.Focusable.gpfocuswithin {background-color: #868da117;} .decky-docs-markdown img {max-width: 588px;} `} - `} @@ -34,9 +33,14 @@ const DocsPage: VFC<{ content: string }> = ({ content }) => { ) } +interface DocsPage { + title: string; + text: string; +} + const StorePage: VFC<{}> = () => { - const [docs, setDocs] = useState(null); // {"filename": {"name":"readable name", "text":"marked up file"}} + const [docs, setDocs] = useState(null); const { plugin } = useParams<{ plugin: string }>() useEffect(() => { @@ -58,7 +62,7 @@ const StorePage: VFC<{}> = () => {
- : (Object.keys(docs).length == 1) ? + : (docs.length == 1) ?
@@ -66,11 +70,11 @@ const StorePage: VFC<{}> = () => { ( + pages={docs.map((file) => ( { - title: docs[file]["name"], - content:, - route: `/decky/docs/${plugin}/${file}`, + title: file["name"], + content:, + route: `/decky/docs/${plugin}/${file["name"]}`, hideTitle: true, } ))} @@ -80,12 +84,4 @@ const StorePage: VFC<{}> = () => { ) } -/* - - - - -
-*/ - export default StorePage; From fe1aa4ab7c4acbf144aa03099c79c08b2986dd1c Mon Sep 17 00:00:00 2001 From: Party Wumpus <48649272+PartyWumpus@users.noreply.github.com> Date: Mon, 17 Jul 2023 10:56:21 +0100 Subject: [PATCH 32/39] Update loader.py --- backend/loader.py | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/backend/loader.py b/backend/loader.py index a8a8a366c..0a2e5a6b8 100644 --- a/backend/loader.py +++ b/backend/loader.py @@ -133,11 +133,11 @@ def get_plugin_documentation(self, request): if not exists(docs_path): try: with open(path.join(plugin_path, "README.md")) as f: - return web.json_response({"readme.md":{"name":"readme","text":f.read()}}) + return web.json_response([{"title":"readme","text":f.read()}]) except: logger.error(f"Failed to load readme file for {plugin_name} at {plugin_path}") - docs = {} # {"filename": {"name":"readable name", "text":"marked up file"}} + docs = [] # [{"title":"readable name", "text":"marked up file"},'separator',...] config = {"default_language": "en-US", "include_readme": "False", "file_list":None, "use_translation":None} try: @@ -163,22 +163,25 @@ def get_plugin_documentation(self, request): for filename in config["file_list"]: - try: - if config["use_translation"] == "True" and not exists(path.join(docs_file_path,filename)): - data = frontmatter.load(path.join(docs_path, config["default_language"])) - else: - data = frontmatter.load(path.join(docs_file_path,filename)) - docs[filename] = { - "name": data.get("title", filename), - "text": data.content - } - except: - self.logger.warning(f"unable to load file {filename} for {plugin_name} at {docs_file_path}") + if filename == "seperator": + docs.append('separator') + else: + try: + if config["use_translation"] == "True" and not exists(path.join(docs_file_path,filename)): + data = frontmatter.load(path.join(docs_path, config["default_language"])) + else: + data = frontmatter.load(path.join(docs_file_path,filename)) + docs.append({ + "title": data.get("title", filename), + "text": data.content + }) + except: + self.logger.warning(f"unable to load file {filename} for {plugin_name} at {docs_file_path}") if config["include_readme"] == "True": try: with open(path.join(plugin_path, "README.md")) as f: - docs["README"] = {"name":"readme","text": f.read()} + docs.append({"title":"readme","text": f.read()}) except: self.logger.warning(f"unable to load the readme for {plugin_name} at {plugin_path}") From 8b852b6ee79f0acb5f65116aa2a828d3307af1c6 Mon Sep 17 00:00:00 2001 From: Party Wumpus <48649272+PartyWumpus@users.noreply.github.com> Date: Mon, 17 Jul 2023 10:57:03 +0100 Subject: [PATCH 33/39] Update Docs.tsx --- frontend/src/components/Docs.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/components/Docs.tsx b/frontend/src/components/Docs.tsx index c6f688df9..479c8e682 100644 --- a/frontend/src/components/Docs.tsx +++ b/frontend/src/components/Docs.tsx @@ -40,7 +40,7 @@ interface DocsPage { const StorePage: VFC<{}> = () => { - const [docs, setDocs] = useState(null); + const [docs, setDocs] = useState<(DocsPage | 'separator')[] | null>(null); const { plugin } = useParams<{ plugin: string }>() useEffect(() => { @@ -71,7 +71,7 @@ const StorePage: VFC<{}> = () => { title={plugin} showTitle={true} pages={docs.map((file) => ( - { + file == 'separator' ? 'separator' : { title: file["name"], content:, route: `/decky/docs/${plugin}/${file["name"]}`, From 438d90367478dd576a15bd9f79d53be6db84141c Mon Sep 17 00:00:00 2001 From: Party Wumpus <48649272+PartyWumpus@users.noreply.github.com> Date: Mon, 17 Jul 2023 11:20:42 +0100 Subject: [PATCH 34/39] Update Docs.tsx --- frontend/src/components/Docs.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/components/Docs.tsx b/frontend/src/components/Docs.tsx index 479c8e682..de06d6b67 100644 --- a/frontend/src/components/Docs.tsx +++ b/frontend/src/components/Docs.tsx @@ -72,9 +72,9 @@ const StorePage: VFC<{}> = () => { showTitle={true} pages={docs.map((file) => ( file == 'separator' ? 'separator' : { - title: file["name"], + title: file["title"], content:, - route: `/decky/docs/${plugin}/${file["name"]}`, + route: `/decky/docs/${plugin}/${file["title"]}`, hideTitle: true, } ))} From e20823a474d21af578a244bc88404befbf9c4767 Mon Sep 17 00:00:00 2001 From: Party Wumpus <48649272+PartyWumpus@users.noreply.github.com> Date: Mon, 17 Jul 2023 11:31:37 +0100 Subject: [PATCH 35/39] Update loader.py --- backend/loader.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/loader.py b/backend/loader.py index 0a2e5a6b8..ec1acde32 100644 --- a/backend/loader.py +++ b/backend/loader.py @@ -168,7 +168,7 @@ def get_plugin_documentation(self, request): else: try: if config["use_translation"] == "True" and not exists(path.join(docs_file_path,filename)): - data = frontmatter.load(path.join(docs_path, config["default_language"])) + data = frontmatter.load(path.join(docs_path, config["default_language"], filename)) else: data = frontmatter.load(path.join(docs_file_path,filename)) docs.append({ From 34572d1a868be7a348f86ce0e340b7fb3e8b604b Mon Sep 17 00:00:00 2001 From: Party Wumpus <48649272+PartyWumpus@users.noreply.github.com> Date: Tue, 18 Jul 2023 12:33:50 +0100 Subject: [PATCH 36/39] remove file extension from default name for a file --- backend/loader.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/loader.py b/backend/loader.py index ec1acde32..9eeea0902 100644 --- a/backend/loader.py +++ b/backend/loader.py @@ -172,7 +172,7 @@ def get_plugin_documentation(self, request): else: data = frontmatter.load(path.join(docs_file_path,filename)) docs.append({ - "title": data.get("title", filename), + "title": data.get("title", filename[:-3]), "text": data.content }) except: From d92e7df37a77dcc04f81960028d13f9f1876c9e2 Mon Sep 17 00:00:00 2001 From: Party Wumpus <48649272+PartyWumpus@users.noreply.github.com> Date: Mon, 28 Aug 2023 17:56:37 +0100 Subject: [PATCH 37/39] add image support maybe --- backend/loader.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/backend/loader.py b/backend/loader.py index 9eeea0902..39f164ada 100644 --- a/backend/loader.py +++ b/backend/loader.py @@ -171,9 +171,10 @@ def get_plugin_documentation(self, request): data = frontmatter.load(path.join(docs_path, config["default_language"], filename)) else: data = frontmatter.load(path.join(docs_file_path,filename)) + text = data.content.replace("/decky/assets", f"http://127.0.0.1:1337/plugins/{plugin_name.replace(' ', '%20')}/assets") docs.append({ "title": data.get("title", filename[:-3]), - "text": data.content + "text": text }) except: self.logger.warning(f"unable to load file {filename} for {plugin_name} at {docs_file_path}") @@ -181,7 +182,8 @@ def get_plugin_documentation(self, request): if config["include_readme"] == "True": try: with open(path.join(plugin_path, "README.md")) as f: - docs.append({"title":"readme","text": f.read()}) + text = f.read().replace("/decky/assets", f"http://127.0.0.1:1337/plugins/{plugin_name.replace(' ', '%20')}/assets") + docs.append({"title":"readme","text": text}) except: self.logger.warning(f"unable to load the readme for {plugin_name} at {plugin_path}") From 56731258ef56c4ed1a11cc1328b537e351ac49e8 Mon Sep 17 00:00:00 2001 From: Beebles <102569435+beebls@users.noreply.github.com> Date: Thu, 14 Sep 2023 14:21:47 -0600 Subject: [PATCH 38/39] add `onDocsClick` param for custom TitleViews --- frontend/src/components/Docs.tsx | 118 +++++++++------- frontend/src/components/Scrollable.tsx | 188 ++++++++++++------------- frontend/src/components/TitleView.tsx | 40 +++--- 3 files changed, 176 insertions(+), 170 deletions(-) diff --git a/frontend/src/components/Docs.tsx b/frontend/src/components/Docs.tsx index de06d6b67..333b52eaf 100644 --- a/frontend/src/components/Docs.tsx +++ b/frontend/src/components/Docs.tsx @@ -1,17 +1,18 @@ +import { SidebarNavigation, SteamSpinner, useParams } from 'decky-frontend-lib'; +import i18n from 'i18next'; import { VFC, useEffect, useState } from 'react'; -import { useParams, SidebarNavigation, SteamSpinner } from "decky-frontend-lib"; import { lazy } from 'react'; -import i18n from 'i18next'; -import { ScrollArea, Scrollable, scrollableRef } from "./Scrollable"; +import { ScrollArea, Scrollable, scrollableRef } from './Scrollable'; + const MarkdownRenderer = lazy(() => import('./Markdown')); const DocsPage: VFC<{ content: string }> = ({ content }) => { - const ref = scrollableRef(); + const ref = scrollableRef(); - return ( - <> - - - - - - - - ) -} + + + + + + + + ); +}; interface DocsPage { title: string; @@ -39,49 +40,60 @@ interface DocsPage { } const StorePage: VFC<{}> = () => { + const [docs, setDocs] = useState<(DocsPage | 'separator')[] | null>(null); + const { plugin } = useParams<{ plugin: string }>(); - const [docs, setDocs] = useState<(DocsPage | 'separator')[] | null>(null); - const { plugin } = useParams<{ plugin: string }>() - - useEffect(() => { + useEffect(() => { (async () => { - setDocs(await (await fetch(`http://127.0.0.1:1337/docs/${plugin}/${i18n.resolvedLanguage}`, { - method: 'GET', - credentials: 'include', - headers: { - 'Content-Type': 'application/json', - Authentication: window.deckyAuthToken, - } - })).json()) - })(); - }, []); + setDocs( + await ( + await fetch(`http://127.0.0.1:1337/docs/${plugin}/${i18n.resolvedLanguage}`, { + method: 'GET', + credentials: 'include', + headers: { + 'Content-Type': 'application/json', + Authentication: window.deckyAuthToken, + }, + }) + ).json(), + ); + })(); + }, []); - return ( - <> - {!docs ? + return ( + <> + {!docs ? (
- : (docs.length == 1) ? -
- -
- : - ( - file == 'separator' ? 'separator' : { - title: file["title"], - content:, - route: `/decky/docs/${plugin}/${file["title"]}`, - hideTitle: true, - } - ))} - /> - } + ) : docs.length == 1 ? ( +
+ +
+ ) : ( + + file == 'separator' + ? 'separator' + : { + title: file['title'], + content: , + route: `/decky/docs/${plugin}/${file['title']}`, + hideTitle: true, + }, + )} + /> + )} - ) -} + ); +}; export default StorePage; diff --git a/frontend/src/components/Scrollable.tsx b/frontend/src/components/Scrollable.tsx index dcacb3cc9..4fd948361 100644 --- a/frontend/src/components/Scrollable.tsx +++ b/frontend/src/components/Scrollable.tsx @@ -1,46 +1,38 @@ +import { Focusable, FocusableProps, GamepadButton, GamepadEvent, ServerAPI } from 'decky-frontend-lib'; // this file was mostly (the good bits) made by davocarli#7308 // it should probably be moved to DFL eventually -import { FC, ForwardRefExoticComponent } from "react" -import { - Focusable, - FocusableProps, - GamepadEvent, - GamepadButton, - ServerAPI, -} from "decky-frontend-lib" -import React, { useRef } from "react" +import { FC, ForwardRefExoticComponent } from 'react'; +import React, { useRef } from 'react'; -const DEFAULTSCROLLSPEED = 50 +const DEFAULTSCROLLSPEED = 50; export interface ScrollableElement extends HTMLDivElement {} export function scrollableRef() { - return useRef(null) + return useRef(null); } -export const Scrollable: ForwardRefExoticComponent = React.forwardRef( - (props, ref) => { - if (!props.style) { - props.style = {} - } - // props.style.minHeight = '100%'; - // props.style.maxHeight = '80%'; - props.style.height = "85vh" // was 95vh previously! - props.style.overflowY = "scroll" +export const Scrollable: ForwardRefExoticComponent = React.forwardRef((props, ref) => { + if (!props.style) { + props.style = {}; + } + // props.style.minHeight = '100%'; + // props.style.maxHeight = '80%'; + props.style.height = '85vh'; // was 95vh previously! + props.style.overflowY = 'scroll'; - return ( - -
- - ) - } -) + return ( + +
+ + ); +}); interface ScrollAreaProps extends FocusableProps { - scrollable: React.RefObject - scrollSpeed?: number - serverApi?: ServerAPI - noFocusRing?: boolean + scrollable: React.RefObject; + scrollSpeed?: number; + serverApi?: ServerAPI; + noFocusRing?: boolean; } // const writeLog = async (serverApi: ServerAPI, content: any) => { @@ -49,81 +41,75 @@ interface ScrollAreaProps extends FocusableProps { // } const scrollOnDirection = ( - e: GamepadEvent, - ref: React.RefObject, - amt: number, - prev: React.RefObject, - next: React.RefObject + e: GamepadEvent, + ref: React.RefObject, + amt: number, + prev: React.RefObject, + next: React.RefObject, ) => { - let childNodes = ref.current?.childNodes - let currentIndex = null - childNodes?.forEach((node, i) => { - if (node == e.currentTarget) { - currentIndex = i - } - }) + let childNodes = ref.current?.childNodes; + let currentIndex = null; + childNodes?.forEach((node, i) => { + if (node == e.currentTarget) { + currentIndex = i; + } + }); - // @ts-ignore - let pos = e.currentTarget?.getBoundingClientRect() - let out = ref.current?.getBoundingClientRect() + // @ts-ignore + let pos = e.currentTarget?.getBoundingClientRect(); + let out = ref.current?.getBoundingClientRect(); - if (e.detail.button == GamepadButton.DIR_DOWN) { - if ( - out?.bottom != undefined && - pos.bottom <= out.bottom && - currentIndex != null && - childNodes != undefined && - currentIndex + 1 < childNodes.length - ) { - next.current?.focus() - } else { - ref.current?.scrollBy({ top: amt, behavior: "auto" }) - } - } else if (e.detail.button == GamepadButton.DIR_UP) { - if ( - out?.top != undefined && - pos.top >= out.top && - currentIndex != null && - childNodes != undefined && - currentIndex - 1 >= 0 - ) { - prev.current?.focus() - } else { - ref.current?.scrollBy({ top: -amt, behavior: "auto" }) - } - } else if (e.detail.button == GamepadButton.DIR_LEFT) { - throw("this is not a real error, just a (temporary?) workaround to make navigation work in docs") - } -} + if (e.detail.button == GamepadButton.DIR_DOWN) { + if ( + out?.bottom != undefined && + pos.bottom <= out.bottom && + currentIndex != null && + childNodes != undefined && + currentIndex + 1 < childNodes.length + ) { + next.current?.focus(); + } else { + ref.current?.scrollBy({ top: amt, behavior: 'auto' }); + } + } else if (e.detail.button == GamepadButton.DIR_UP) { + if ( + out?.top != undefined && + pos.top >= out.top && + currentIndex != null && + childNodes != undefined && + currentIndex - 1 >= 0 + ) { + prev.current?.focus(); + } else { + ref.current?.scrollBy({ top: -amt, behavior: 'auto' }); + } + } else if (e.detail.button == GamepadButton.DIR_LEFT) { + throw 'this is not a real error, just a (temporary?) workaround to make navigation work in docs'; + } +}; export const ScrollArea: FC = (props) => { - let scrollSpeed = DEFAULTSCROLLSPEED - if (props.scrollSpeed) { - scrollSpeed = props.scrollSpeed - } + let scrollSpeed = DEFAULTSCROLLSPEED; + if (props.scrollSpeed) { + scrollSpeed = props.scrollSpeed; + } - const prevFocus = useRef(null) - const nextFocus = useRef(null) + const prevFocus = useRef(null); + const nextFocus = useRef(null); - props.onActivate = (e) => { - const ele = e.currentTarget as HTMLElement - ele.focus() - } - props.onGamepadDirection = (e) => { - scrollOnDirection( - e, - props.scrollable, - scrollSpeed, - prevFocus, - nextFocus - ) - } + props.onActivate = (e) => { + const ele = e.currentTarget as HTMLElement; + ele.focus(); + }; + props.onGamepadDirection = (e) => { + scrollOnDirection(e, props.scrollable, scrollSpeed, prevFocus, nextFocus); + }; - return ( - - {}} /> - - {}} /> - - ) -} + return ( + + {}} /> + + {}} /> + + ); +}; diff --git a/frontend/src/components/TitleView.tsx b/frontend/src/components/TitleView.tsx index 293b9922e..6f02aa225 100644 --- a/frontend/src/components/TitleView.tsx +++ b/frontend/src/components/TitleView.tsx @@ -1,8 +1,8 @@ import { DialogButton, Focusable, Router, staticClasses } from 'decky-frontend-lib'; -import { CSSProperties, VFC } from 'react'; +import { CSSProperties, VFC, cloneElement } from 'react'; import { useTranslation } from 'react-i18next'; import { BsGearFill } from 'react-icons/bs'; -import { FaArrowLeft, FaStore, FaInfo } from 'react-icons/fa'; +import { FaArrowLeft, FaInfo, FaStore } from 'react-icons/fa'; import { useDeckyState } from './DeckyState'; @@ -53,21 +53,29 @@ const TitleView: VFC = () => { ); } + const CustomTitleView = activePlugin?.titleView + ? cloneElement(activePlugin.titleView, { onDocsClick: onInfoClick }) + : null; + return ( - - - - - {activePlugin?.titleView ||
{activePlugin.name}
} - - - + + + + + {CustomTitleView || ( + <> +
{activePlugin.name}
+ + + + + )}
); }; From d6ae395d535578b1a24a85b06c4a2d0a3a3dd137 Mon Sep 17 00:00:00 2001 From: Beebles <102569435+beebls@users.noreply.github.com> Date: Thu, 14 Sep 2023 14:22:10 -0600 Subject: [PATCH 39/39] switch `Router` to `Navigation` --- frontend/src/components/TitleView.tsx | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/frontend/src/components/TitleView.tsx b/frontend/src/components/TitleView.tsx index 6f02aa225..82386339d 100644 --- a/frontend/src/components/TitleView.tsx +++ b/frontend/src/components/TitleView.tsx @@ -1,4 +1,4 @@ -import { DialogButton, Focusable, Router, staticClasses } from 'decky-frontend-lib'; +import { DialogButton, Focusable, Navigation, staticClasses } from 'decky-frontend-lib'; import { CSSProperties, VFC, cloneElement } from 'react'; import { useTranslation } from 'react-i18next'; import { BsGearFill } from 'react-icons/bs'; @@ -17,18 +17,18 @@ const TitleView: VFC = () => { const { t } = useTranslation(); const onSettingsClick = () => { - Router.CloseSideMenus(); - Router.Navigate('/decky/settings'); + Navigation.CloseSideMenus(); + Navigation.Navigate('/decky/settings'); }; const onStoreClick = () => { - Router.CloseSideMenus(); - Router.Navigate('/decky/store'); + Navigation.CloseSideMenus(); + Navigation.Navigate('/decky/store'); }; const onInfoClick = () => { - Router.CloseSideMenus(); - Router.Navigate(`/decky/docs/${activePlugin?.name}`); + Navigation.CloseSideMenus(); + Navigation.Navigate(`/decky/docs/${activePlugin?.name}`); }; if (activePlugin === null) {