diff options
| author | AAGaming <aagaming@riseup.net> | 2024-08-05 14:07:10 -0400 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2024-08-05 14:07:10 -0400 |
| commit | 131f0961ff451ec47376483178e092c8d7403b27 (patch) | |
| tree | 4d2ea34e8220e14c4b820cc1ad38face7193f6fe /frontend/src/components/Toast.tsx | |
| parent | 75aa1e4851445646994ba3a61ff41325403359fb (diff) | |
| download | decky-loader-131f0961ff451ec47376483178e092c8d7403b27.tar.gz decky-loader-131f0961ff451ec47376483178e092c8d7403b27.zip | |
Rewrite router/tabs/toaster hooks (#661)
Diffstat (limited to 'frontend/src/components/Toast.tsx')
| -rw-r--r-- | frontend/src/components/Toast.tsx | 102 |
1 files changed, 79 insertions, 23 deletions
diff --git a/frontend/src/components/Toast.tsx b/frontend/src/components/Toast.tsx index 79e3d864..e86e9337 100644 --- a/frontend/src/components/Toast.tsx +++ b/frontend/src/components/Toast.tsx @@ -1,37 +1,38 @@ import type { ToastData } from '@decky/api'; -import { findModule, joinClassNames } from '@decky/ui'; -import { FunctionComponent } from 'react'; +import { Focusable, Navigation, findClassModule, joinClassNames } from '@decky/ui'; +import { FC, memo } from 'react'; -interface ToastProps { - toast: ToastData; -} +import Logger from '../logger'; -export const toastClasses = findModule((mod) => { - if (typeof mod !== 'object') return false; +const logger = new Logger('ToastRenderer'); - if (mod.ToastPlaceholder) { - return true; - } +// TODO there are more of these +export enum ToastLocation { + /** Big Picture popup toasts */ + GAMEPADUI_POPUP = 1, + /** QAM Notifications tab */ + GAMEPADUI_QAM = 3, +} - return false; -}); +interface ToastProps { + toast: ToastData; + newIndicator?: boolean; +} -const templateClasses = findModule((mod) => { - if (typeof mod !== 'object') return false; +interface ToastRendererProps extends ToastProps { + location: ToastLocation; +} - if (mod.ShortTemplate) { - return true; - } +const templateClasses = findClassModule((m) => m.ShortTemplate) || {}; - return false; -}); +// These are memoized as they like to randomly rerender -const Toast: FunctionComponent<ToastProps> = ({ toast }) => { +const GamepadUIPopupToast: FC<Omit<ToastProps, 'newIndicator'>> = memo(({ toast }) => { return ( <div style={{ '--toast-duration': `${toast.duration}ms` } as React.CSSProperties} onClick={toast.onClick} - className={joinClassNames(templateClasses.ShortTemplate, toast.className || '')} + className={joinClassNames(templateClasses.ShortTemplate, toast.className || '', 'DeckyGamepadUIPopupToast')} > {toast.logo && <div className={templateClasses.StandardLogoDimensions}>{toast.logo}</div>} <div className={joinClassNames(templateClasses.Content, toast.contentClassName || '')}> @@ -43,6 +44,61 @@ const Toast: FunctionComponent<ToastProps> = ({ toast }) => { </div> </div> ); -}; +}); + +const GamepadUIQAMToast: FC<ToastProps> = memo(({ toast, newIndicator }) => { + // The fields aren't mismatched, the logic for these is just a bit weird. + return ( + <Focusable + onActivate={() => { + toast.onClick?.(); + Navigation.CloseSideMenus(); + }} + className={joinClassNames( + templateClasses.StandardTemplateContainer, + toast.className || '', + 'DeckyGamepadUIQAMToast', + )} + > + <div className={templateClasses.StandardTemplate}> + {toast.logo && <div className={templateClasses.StandardLogoDimensions}>{toast.logo}</div>} + <div className={joinClassNames(templateClasses.Content, toast.contentClassName || '')}> + <div className={templateClasses.Header}> + {toast.icon && <div className={templateClasses.Icon}>{toast.icon}</div>} + {toast.title && <div className={templateClasses.Title}>{toast.title}</div>} + {/* timestamp should always be defined by toaster */} + {/* TODO check how valve does this */} + {toast.timestamp && ( + <div className={templateClasses.Timestamp}> + {toast.timestamp.toLocaleTimeString(undefined, { timeStyle: 'short' })} + </div> + )} + </div> + {toast.body && <div className={templateClasses.StandardNotificationDescription}>{toast.body}</div>} + {toast.subtext && <div className={templateClasses.StandardNotificationSubText}>{toast.subtext}</div>} + </div> + {newIndicator && ( + <div className={templateClasses.NewIndicator}> + <svg xmlns="http://www.w3.org/2000/svg" width="50" height="50" viewBox="0 0 50 50" fill="none"> + <circle fill="currentColor" cx="25" cy="25" r="25"></circle> + </svg> + </div> + )} + </div> + </Focusable> + ); +}); + +export const ToastRenderer: FC<ToastRendererProps> = memo(({ toast, location, newIndicator }) => { + switch (location) { + default: + logger.warn(`Toast UI not implemented for location ${location}! Falling back to GamepadUIQAMToast.`); + return <GamepadUIQAMToast toast={toast} newIndicator={false} />; + case ToastLocation.GAMEPADUI_POPUP: + return <GamepadUIPopupToast toast={toast} />; + case ToastLocation.GAMEPADUI_QAM: + return <GamepadUIQAMToast toast={toast} newIndicator={newIndicator} />; + } +}); -export default Toast; +export default ToastRenderer; |
