diff options
| author | marios8543 <marios8543@gmail.com> | 2024-02-22 14:07:59 +0200 |
|---|---|---|
| committer | marios8543 <marios8543@gmail.com> | 2024-02-22 14:07:59 +0200 |
| commit | 6b5f7c8642062906ecb36d905e52d0fcc6172783 (patch) | |
| tree | f5e843caf4806f710351e528a462b08113687efe /frontend/src | |
| parent | 922d0c4153113b917722cae018ca6e35b6c0e9bd (diff) | |
| download | decky-loader-6b5f7c8642062906ecb36d905e52d0fcc6172783.tar.gz decky-loader-6b5f7c8642062906ecb36d905e52d0fcc6172783.zip | |
Added log viewer as side-tab in settings
Diffstat (limited to 'frontend/src')
| -rw-r--r-- | frontend/src/components/logviewer/LogList.tsx | 48 | ||||
| -rw-r--r-- | frontend/src/components/logviewer/LogViewModal.tsx | 45 | ||||
| -rw-r--r-- | frontend/src/components/logviewer/LoggedPlugin.tsx | 35 | ||||
| -rw-r--r-- | frontend/src/components/logviewer/ScrollableWindow.tsx | 107 | ||||
| -rw-r--r-- | frontend/src/components/logviewer/index.tsx | 20 | ||||
| -rw-r--r-- | frontend/src/components/settings/index.tsx | 15 |
6 files changed, 268 insertions, 2 deletions
diff --git a/frontend/src/components/logviewer/LogList.tsx b/frontend/src/components/logviewer/LogList.tsx new file mode 100644 index 00000000..b536fd02 --- /dev/null +++ b/frontend/src/components/logviewer/LogList.tsx @@ -0,0 +1,48 @@ +import { + DialogButton, + Focusable, + showModal, +} from "decky-frontend-lib"; +import { FC, useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; +import LogViewModal from "./LogViewModal"; + +const LogList: FC<{ plugin: string }> = ({ plugin }) => { + const [logList, setLogList] = useState([]); + const { t } = useTranslation(); + + useEffect(() => { + window.DeckyPluginLoader.callServerMethod("get_plugin_logs", { + plugin_name: plugin, + }).then((log_list) => { + setLogList(log_list.result || []); + }); + }, []); + + return ( + <Focusable> + {logList.map((log_file) => ( + <DialogButton + style={{ marginBottom: "0.5rem" }} + onOKActionDescription={t("LogViewer.viewLog", "View Log")} + onOKButton={() => + showModal( + <LogViewModal name={log_file} plugin={plugin}></LogViewModal>, + ) + } + onClick={() => + showModal( + <LogViewModal name={log_file} plugin={plugin}></LogViewModal>, + ) + } + > + <div style={{ display: "flex", flexDirection: "column", gap: "6px" }}> + <div>{log_file}</div> + </div> + </DialogButton> + ))} + </Focusable> + ); +}; + +export default LogList; diff --git a/frontend/src/components/logviewer/LogViewModal.tsx b/frontend/src/components/logviewer/LogViewModal.tsx new file mode 100644 index 00000000..beda50a3 --- /dev/null +++ b/frontend/src/components/logviewer/LogViewModal.tsx @@ -0,0 +1,45 @@ +import { Focusable } from "decky-frontend-lib"; +import { VFC, useEffect, useState } from "react"; +import { ScrollableWindowRelative } from "./ScrollableWindow"; + +interface LogFileProps { + plugin: string; + name: string; + closeModal?: () => void; +} + +const LogViewModal: VFC<LogFileProps> = ({ name, plugin, closeModal }) => { + const [logText, setLogText] = useState("Loading text...."); + useEffect(() => { + window.DeckyPluginLoader.callServerMethod("get_plugin_log_text", { + plugin_name: plugin, + log_name: name, + }).then((text) => { + setLogText(text.result || "Error loading text"); + }); + }, []); + + return ( + <Focusable + style={{ + padding: "0 15px", + display: "flex", + position: "absolute", + top: "var(--basicui-header-height)", + bottom: "var(--gamepadui-current-footer-height)", + left: 0, + right: 0, + }} + onSecondaryActionDescription={"Upload Log"} + onSecondaryButton={() => console.log("Uploading...")} + > + <ScrollableWindowRelative alwaysFocus={true} onCancel={closeModal}> + <div style={{ whiteSpace: "pre-wrap", padding: "12px 0" }}> + {logText} + </div> + </ScrollableWindowRelative> + </Focusable> + ); +}; + +export default LogViewModal; diff --git a/frontend/src/components/logviewer/LoggedPlugin.tsx b/frontend/src/components/logviewer/LoggedPlugin.tsx new file mode 100644 index 00000000..af7564e2 --- /dev/null +++ b/frontend/src/components/logviewer/LoggedPlugin.tsx @@ -0,0 +1,35 @@ +import { Focusable } from "decky-frontend-lib"; +import { VFC, useState } from "react"; +import { FaArrowDown, FaArrowUp } from "react-icons/fa"; +import LogList from "./LogList"; + +interface LoggedPluginProps { + plugin: string; +} + +const focusableStyle = { + background: "rgba(255,255,255,.15)", + borderRadius: "var(--round-radius-size)", + padding: "10px 24px", + marginBottom: "0.5rem", +}; + +const LoggedPlugin: VFC<LoggedPluginProps> = ({ plugin }) => { + const [isOpen, setOpen] = useState<boolean>(false); + + return ( + <div style={focusableStyle}> + <Focusable onOKButton={() => setOpen(!isOpen)}> + <div style={{ display: "flex", justifyContent: "space-between" }}> + <div style={{ flexGrow: 1, textAlign: "left" }}>{plugin}</div> + <div style={{ textAlign: "right" }}> + {isOpen ? <FaArrowUp /> : <FaArrowDown />} + </div> + </div> + </Focusable> + {isOpen && <LogList plugin={plugin} />} + </div> + ); +}; + +export default LoggedPlugin;
\ No newline at end of file diff --git a/frontend/src/components/logviewer/ScrollableWindow.tsx b/frontend/src/components/logviewer/ScrollableWindow.tsx new file mode 100644 index 00000000..c1d5e5b4 --- /dev/null +++ b/frontend/src/components/logviewer/ScrollableWindow.tsx @@ -0,0 +1,107 @@ +/* +Big thanks to @jessebofil for this +https://discord.com/channels/960281551428522045/960284327445418044/1209253688363716648 +*/ + +import { Focusable, ModalPosition, GamepadButton, ScrollPanelGroup, gamepadDialogClasses, scrollPanelClasses, FooterLegendProps } from "decky-frontend-lib"; +import { FC, useLayoutEffect, useRef, useState } from "react"; + +export interface ScrollableWindowProps extends FooterLegendProps { + height: string; + fadeAmount?: string; + scrollBarWidth?: string; + alwaysFocus?: boolean; + noScrollDescription?: boolean; + + onActivate?: (e: CustomEvent) => void; + onCancel?: (e: CustomEvent) => void; +} + +const ScrollableWindow: FC<ScrollableWindowProps> = ({ height, fadeAmount, scrollBarWidth, alwaysFocus, noScrollDescription, children, actionDescriptionMap, ...focusableProps }) => { + const fade = fadeAmount === undefined || fadeAmount === '' ? '10px' : fadeAmount; + const barWidth = scrollBarWidth === undefined || scrollBarWidth === '' ? '4px' : scrollBarWidth; + const [isOverflowing, setIsOverflowing] = useState(false); + const scrollPanelRef = useRef<HTMLElement>(); + + useLayoutEffect(() => { + const { current } = scrollPanelRef; + const trigger = () => { + if (current) { + const hasOverflow = current.scrollHeight > current.clientHeight; + setIsOverflowing(hasOverflow); + } + }; + if (current) trigger(); + }, [children, height]); + + const panel = ( + <ScrollPanelGroup + //@ts-ignore + ref={scrollPanelRef} focusable={false} style={{ flex: 1, minHeight: 0 }}> + <Focusable + //@ts-ignore + focusable={alwaysFocus || isOverflowing} + key={'scrollable-window-focusable-element'} + noFocusRing={true} + actionDescriptionMap={Object.assign(noScrollDescription ? {} : + { + [GamepadButton.DIR_UP]: 'Scroll Up', + [GamepadButton.DIR_DOWN]: 'Scroll Down' + }, + actionDescriptionMap ?? {} + )} + {...focusableProps} + > + {children} + </Focusable> + </ScrollPanelGroup> + ); + + return ( + <> + <style> + {`.modal-position-container .${gamepadDialogClasses.ModalPosition} { + top: 0; + bottom: 0; + padding: 0; + } + .modal-position-container .${scrollPanelClasses.ScrollPanel}::-webkit-scrollbar { + display: initial !important; + width: ${barWidth}; + } + .modal-position-container .${scrollPanelClasses.ScrollPanel}::-webkit-scrollbar-thumb { + border: 0; + }`} + </style> + <div + className='modal-position-container' + style={{ + position: 'relative', + height: height, + WebkitMask: `linear-gradient(to right , transparent, transparent calc(100% - ${barWidth}), white calc(100% - ${barWidth})), linear-gradient(to bottom, transparent, black ${fade}, black calc(100% - ${fade}), transparent 100%)` + }}> + {isOverflowing ? ( + <ModalPosition key={'scrollable-window-modal-position'}> + {panel} + </ModalPosition> + ) : ( + <div className={`${gamepadDialogClasses.ModalPosition} ${gamepadDialogClasses.WithStandardPadding} Panel`} key={'modal-position'}> + {panel} + </div> + )} + </div> + </> + ); +}; + +interface ScrollableWindowAutoProps extends Omit<ScrollableWindowProps, 'height'> { + heightPercent?: number; +} + +export const ScrollableWindowRelative: FC<ScrollableWindowAutoProps> = ({ heightPercent, ...props }) => { + return ( + <div style={{ flex: 'auto' }}> + <ScrollableWindow height={`${heightPercent ?? 100}%`} {...props} /> + </div> + ); +};
\ No newline at end of file diff --git a/frontend/src/components/logviewer/index.tsx b/frontend/src/components/logviewer/index.tsx new file mode 100644 index 00000000..6e9baae0 --- /dev/null +++ b/frontend/src/components/logviewer/index.tsx @@ -0,0 +1,20 @@ +import { DialogBody } from 'decky-frontend-lib'; +import { FC, useEffect, useState } from 'react'; + +import LoggedPlugin from './LoggedPlugin'; + +const LogViewerPage: FC<{}> = () => { + const [plugins, setPlugins] = useState([]); + useEffect(() => { + window.DeckyPluginLoader.callServerMethod('get_plugins_with_logs').then((plugins) => { + setPlugins(plugins.result || []); + }); + }, []); + return ( + <DialogBody> + {plugins.map((plugin) => <LoggedPlugin plugin={plugin} />)} + </DialogBody> + ) +}; + +export default LogViewerPage;
\ No newline at end of file diff --git a/frontend/src/components/settings/index.tsx b/frontend/src/components/settings/index.tsx index 80400058..8b7ef8df 100644 --- a/frontend/src/components/settings/index.tsx +++ b/frontend/src/components/settings/index.tsx @@ -1,13 +1,14 @@ import { SidebarNavigation } from 'decky-frontend-lib'; import { lazy } from 'react'; import { useTranslation } from 'react-i18next'; -import { FaCode, FaFlask, FaPlug } from 'react-icons/fa'; +import { FaCode, FaFileCode, FaFlask, FaPlug } from 'react-icons/fa'; import { useSetting } from '../../utils/hooks/useSetting'; import DeckyIcon from '../DeckyIcon'; import WithSuspense from '../WithSuspense'; import GeneralSettings from './pages/general'; import PluginList from './pages/plugin_list'; +import LogViewerPage from '../logviewer'; const DeveloperSettings = lazy(() => import('./pages/developer')); const TestingMenu = lazy(() => import('./pages/testing')); @@ -30,6 +31,16 @@ export default function SettingsPage() { icon: <FaPlug />, }, { + title: t('SettingsIndex.log_viewer', "Log Viewer"), + content: ( + <WithSuspense> + <LogViewerPage/> + </WithSuspense> + ), + route: '/decky/settings/logs', + icon: <FaFileCode /> + }, + { title: t('SettingsIndex.developer_title'), content: ( <WithSuspense> @@ -50,7 +61,7 @@ export default function SettingsPage() { route: '/decky/settings/testing', icon: <FaFlask />, visible: isDeveloper, - }, + } ]; return <SidebarNavigation pages={pages} />; |
