From 84c3b039c385ad872bb0f22eba7a3d2cd4a5ea10 Mon Sep 17 00:00:00 2001 From: AAGaming Date: Mon, 24 Oct 2022 19:14:56 -0400 Subject: preview 10/21/2022 fixes (#234) * initial fixes: everything working except toasts and patch notes * tabshook changes, disable toaster for now * prettier * oops * implement custom toaster because I am tired of Valve's shit also fix QAM not injecting sometimes * remove extra logging * add findSP, fix patch notes, fix vscode screwup * fix patch notes * show error when plugin frontends fail to load * add get_tab_lambda * add css and has_element helpers to Tab * small modals fixup * Don't forceUpdate QuickAccess on stable * add routes prop used to get tabs component * add more dev utils to DFL global --- .../src/components/DeckyGlobalComponentsState.tsx | 74 ++++++++++++++++++++++ frontend/src/components/DeckyToaster.tsx | 54 ++++++++++++++++ frontend/src/components/DeckyToasterState.tsx | 69 ++++++++++++++++++++ frontend/src/components/Markdown.tsx | 4 +- .../src/components/QuickAccessVisibleState.tsx | 28 ++++++-- frontend/src/components/Toast.tsx | 27 ++++---- .../components/settings/pages/general/Updater.tsx | 9 ++- 7 files changed, 237 insertions(+), 28 deletions(-) create mode 100644 frontend/src/components/DeckyGlobalComponentsState.tsx create mode 100644 frontend/src/components/DeckyToaster.tsx create mode 100644 frontend/src/components/DeckyToasterState.tsx (limited to 'frontend/src/components') diff --git a/frontend/src/components/DeckyGlobalComponentsState.tsx b/frontend/src/components/DeckyGlobalComponentsState.tsx new file mode 100644 index 00000000..fe45588b --- /dev/null +++ b/frontend/src/components/DeckyGlobalComponentsState.tsx @@ -0,0 +1,74 @@ +import { FC, createContext, useContext, useEffect, useState } from 'react'; + +interface PublicDeckyGlobalComponentsState { + components: Map; +} + +export class DeckyGlobalComponentsState { + // TODO a set would be better + private _components = new Map(); + + public eventBus = new EventTarget(); + + publicState(): PublicDeckyGlobalComponentsState { + return { components: this._components }; + } + + addComponent(path: string, component: FC) { + this._components.set(path, component); + this.notifyUpdate(); + } + + removeComponent(path: string) { + this._components.delete(path); + this.notifyUpdate(); + } + + private notifyUpdate() { + this.eventBus.dispatchEvent(new Event('update')); + } +} + +interface DeckyGlobalComponentsContext extends PublicDeckyGlobalComponentsState { + addComponent(path: string, component: FC): void; + removeComponent(path: string): void; +} + +const DeckyGlobalComponentsContext = createContext(null as any); + +export const useDeckyGlobalComponentsState = () => useContext(DeckyGlobalComponentsContext); + +interface Props { + deckyGlobalComponentsState: DeckyGlobalComponentsState; +} + +export const DeckyGlobalComponentsStateContextProvider: FC = ({ + children, + deckyGlobalComponentsState: deckyGlobalComponentsState, +}) => { + const [publicDeckyGlobalComponentsState, setPublicDeckyGlobalComponentsState] = + useState({ + ...deckyGlobalComponentsState.publicState(), + }); + + useEffect(() => { + function onUpdate() { + setPublicDeckyGlobalComponentsState({ ...deckyGlobalComponentsState.publicState() }); + } + + deckyGlobalComponentsState.eventBus.addEventListener('update', onUpdate); + + return () => deckyGlobalComponentsState.eventBus.removeEventListener('update', onUpdate); + }, []); + + const addComponent = deckyGlobalComponentsState.addComponent.bind(deckyGlobalComponentsState); + const removeComponent = deckyGlobalComponentsState.removeComponent.bind(deckyGlobalComponentsState); + + return ( + + {children} + + ); +}; diff --git a/frontend/src/components/DeckyToaster.tsx b/frontend/src/components/DeckyToaster.tsx new file mode 100644 index 00000000..eaee75eb --- /dev/null +++ b/frontend/src/components/DeckyToaster.tsx @@ -0,0 +1,54 @@ +import { ToastData, joinClassNames } from 'decky-frontend-lib'; +import { FC, useEffect, useState } from 'react'; +import { ReactElement } from 'react-markdown/lib/react-markdown'; + +import { useDeckyToasterState } from './DeckyToasterState'; +import Toast, { toastClasses } from './Toast'; + +interface DeckyToasterProps {} + +interface RenderedToast { + component: ReactElement; + data: ToastData; +} + +const DeckyToaster: FC = () => { + const { toasts, removeToast } = useDeckyToasterState(); + const [renderedToast, setRenderedToast] = useState(null); + console.log(toasts); + if (toasts.size > 0) { + const [activeToast] = toasts; + if (!renderedToast || activeToast != renderedToast.data) { + // TODO play toast sound + console.log('rendering toast', activeToast); + setRenderedToast({ component: , data: activeToast }); + } + } else { + if (renderedToast) setRenderedToast(null); + } + useEffect(() => { + // not actually node but TS is shit + let interval: NodeJS.Timer | null; + if (renderedToast) { + interval = setTimeout(() => { + interval = null; + console.log('clear toast', renderedToast.data); + removeToast(renderedToast.data); + }, (renderedToast.data.duration || 5e3) + 1000); + console.log('set int', interval); + } + return () => { + if (interval) { + console.log('clearing int', interval); + clearTimeout(interval); + } + }; + }, [renderedToast]); + return ( +
+ {renderedToast && renderedToast.component} +
+ ); +}; + +export default DeckyToaster; diff --git a/frontend/src/components/DeckyToasterState.tsx b/frontend/src/components/DeckyToasterState.tsx new file mode 100644 index 00000000..8732d7f8 --- /dev/null +++ b/frontend/src/components/DeckyToasterState.tsx @@ -0,0 +1,69 @@ +import { ToastData } from 'decky-frontend-lib'; +import { FC, createContext, useContext, useEffect, useState } from 'react'; + +interface PublicDeckyToasterState { + toasts: Set; +} + +export class DeckyToasterState { + // TODO a set would be better + private _toasts: Set = new Set(); + + public eventBus = new EventTarget(); + + publicState(): PublicDeckyToasterState { + return { toasts: this._toasts }; + } + + addToast(toast: ToastData) { + this._toasts.add(toast); + this.notifyUpdate(); + } + + removeToast(toast: ToastData) { + this._toasts.delete(toast); + this.notifyUpdate(); + } + + private notifyUpdate() { + this.eventBus.dispatchEvent(new Event('update')); + } +} + +interface DeckyToasterContext extends PublicDeckyToasterState { + addToast(toast: ToastData): void; + removeToast(toast: ToastData): void; +} + +const DeckyToasterContext = createContext(null as any); + +export const useDeckyToasterState = () => useContext(DeckyToasterContext); + +interface Props { + deckyToasterState: DeckyToasterState; +} + +export const DeckyToasterStateContextProvider: FC = ({ children, deckyToasterState }) => { + const [publicDeckyToasterState, setPublicDeckyToasterState] = useState({ + ...deckyToasterState.publicState(), + }); + + useEffect(() => { + function onUpdate() { + setPublicDeckyToasterState({ ...deckyToasterState.publicState() }); + } + + deckyToasterState.eventBus.addEventListener('update', onUpdate); + + return () => deckyToasterState.eventBus.removeEventListener('update', onUpdate); + }, []); + + const addToast = deckyToasterState.addToast.bind(deckyToasterState); + const removeToast = deckyToasterState.removeToast.bind(deckyToasterState); + + return ( + + {children} + + ); +}; diff --git a/frontend/src/components/Markdown.tsx b/frontend/src/components/Markdown.tsx index 278e49cd..045b90a2 100644 --- a/frontend/src/components/Markdown.tsx +++ b/frontend/src/components/Markdown.tsx @@ -1,4 +1,4 @@ -import { Focusable } from 'decky-frontend-lib'; +import { Focusable, Router } from 'decky-frontend-lib'; import { FunctionComponent, useRef } from 'react'; import ReactMarkdown, { Options as ReactMarkdownOptions } from 'react-markdown'; import remarkGfm from 'remark-gfm'; @@ -21,8 +21,8 @@ const Markdown: FunctionComponent = (props) => { {}} onOKButton={() => { - aRef?.current?.click(); props.onDismiss?.(); + Router.NavigateToExternalWeb(aRef.current!.href); }} style={{ display: 'inline' }} > diff --git a/frontend/src/components/QuickAccessVisibleState.tsx b/frontend/src/components/QuickAccessVisibleState.tsx index b5ee3b98..4df7e1a1 100644 --- a/frontend/src/components/QuickAccessVisibleState.tsx +++ b/frontend/src/components/QuickAccessVisibleState.tsx @@ -1,13 +1,27 @@ -import { FC, createContext, useContext } from 'react'; +import { FC, createContext, useContext, useEffect, useRef, useState } from 'react'; const QuickAccessVisibleState = createContext(true); export const useQuickAccessVisible = () => useContext(QuickAccessVisibleState); -interface Props { - visible: boolean; -} - -export const QuickAccessVisibleStateProvider: FC = ({ children, visible }) => { - return {children}; +export const QuickAccessVisibleStateProvider: FC<{}> = ({ children }) => { + const divRef = useRef(null); + const [visible, setVisible] = useState(false); + useEffect(() => { + const doc: Document | void | null = divRef?.current?.ownerDocument; + if (!doc) return; + setVisible(doc.visibilityState == 'visible'); + const onChange = (e: Event) => { + setVisible(doc.visibilityState == 'visible'); + }; + doc.addEventListener('visibilitychange', onChange); + return () => { + doc.removeEventListener('visibilitychange', onChange); + }; + }, [divRef]); + return ( +
+ {children} +
+ ); }; diff --git a/frontend/src/components/Toast.tsx b/frontend/src/components/Toast.tsx index 01a436d7..e7a220c2 100644 --- a/frontend/src/components/Toast.tsx +++ b/frontend/src/components/Toast.tsx @@ -2,13 +2,10 @@ import { ToastData, findModule, joinClassNames } from 'decky-frontend-lib'; import { FunctionComponent } from 'react'; interface ToastProps { - toast: { - data: ToastData; - nToastDurationMS: number; - }; + toast: ToastData; } -const toastClasses = findModule((mod) => { +export const toastClasses = findModule((mod) => { if (typeof mod !== 'object') return false; if (mod.ToastPlaceholder) { @@ -30,21 +27,19 @@ const templateClasses = findModule((mod) => { const Toast: FunctionComponent = ({ toast }) => { return ( -
+
- {toast.data.logo &&
{toast.data.logo}
} -
+ {toast.logo &&
{toast.logo}
} +
- {toast.data.icon &&
{toast.data.icon}
} -
{toast.data.title}
+ {toast.icon &&
{toast.icon}
} +
{toast.title}
-
{toast.data.body}
+
{toast.body}
diff --git a/frontend/src/components/settings/pages/general/Updater.tsx b/frontend/src/components/settings/pages/general/Updater.tsx index b4ea8536..f617e0ff 100644 --- a/frontend/src/components/settings/pages/general/Updater.tsx +++ b/frontend/src/components/settings/pages/general/Updater.tsx @@ -14,6 +14,7 @@ import { useEffect, useState } from 'react'; import { FaArrowDown } from 'react-icons/fa'; import { VerInfo, callUpdaterMethod, finishUpdate } from '../../../../updater'; +import { findSP } from '../../../../utils/windows'; import { useDeckyState } from '../../../DeckyState'; import InlinePatchNotes from '../../../patchnotes/InlinePatchNotes'; import WithSuspense from '../../../WithSuspense'; @@ -21,6 +22,7 @@ import WithSuspense from '../../../WithSuspense'; const MarkdownRenderer = lazy(() => import('../../../Markdown')); function PatchNotesModal({ versionInfo, closeModal }: { versionInfo: VerInfo | null; closeModal?: () => {} }) { + const SP = findSP(); return ( @@ -50,12 +52,13 @@ function PatchNotesModal({ versionInfo, closeModal }: { versionInfo: VerInfo | n )} fnGetId={(id) => id} nNumItems={versionInfo?.all?.length} - nHeight={window.innerHeight - 40} - nItemHeight={window.innerHeight - 40} + nHeight={SP.innerHeight - 40} + nItemHeight={SP.innerHeight - 40} nItemMarginX={0} initialColumn={0} autoFocus={true} - fnGetColumnWidth={() => window.innerWidth} + fnGetColumnWidth={() => SP.innerWidth} + name="Decky Updates" /> -- cgit v1.2.3