diff options
| author | AAGaming <aagaming@riseup.net> | 2024-07-26 14:16:05 -0400 |
|---|---|---|
| committer | AAGaming <aagaming@riseup.net> | 2024-08-03 14:04:19 -0400 |
| commit | b93fc8b557baed40a312b51196c600be3daaa6fd (patch) | |
| tree | 9d5f45493d1262639ea7d53a3ce5fab472f5b3a7 /frontend/src/components/Toast.tsx | |
| parent | 88e7919a12fd56b297e73afb3fb05483f5893f4d (diff) | |
| download | decky-loader-b93fc8b557baed40a312b51196c600be3daaa6fd.tar.gz decky-loader-b93fc8b557baed40a312b51196c600be3daaa6fd.zip | |
feat(toaster): render notifications in the quick access menu
Diffstat (limited to 'frontend/src/components/Toast.tsx')
| -rw-r--r-- | frontend/src/components/Toast.tsx | 103 |
1 files changed, 80 insertions, 23 deletions
diff --git a/frontend/src/components/Toast.tsx b/frontend/src/components/Toast.tsx index 79e3d864..13d12f58 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'; +import TranslationHelper, { TranslationClass } from '../utils/TranslationHelper'; -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; +} -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<ToastProps> = 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,62 @@ const Toast: FunctionComponent<ToastProps> = ({ toast }) => { </div> </div> ); -}; +}); + +const GamepadUIQAMToast: FC<ToastProps> = memo(({ toast }) => { + // The fields aren't mismatched, the logic for these is just a bit weird. + return ( + <Focusable + onActivate={() => { + Navigation.CloseSideMenus(); + toast.onClick?.(); + }} + 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>} + <div className={templateClasses.Title}> + {toast.header || ( + <TranslationHelper transClass={TranslationClass.PLUGIN_LOADER} transText="decky_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> + <div className={templateClasses.StandardNotificationDescription}>{toast.title}</div> + <div className={templateClasses.StandardNotificationSubText}>{toast.body}</div> + </div> + {/* TODO support 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 }) => { + switch (location) { + default: + logger.warn(`Toast UI not implemented for location ${location}! Falling back to GamepadUIPopupToast.`); + case ToastLocation.GAMEPADUI_POPUP: + return <GamepadUIPopupToast toast={toast} />; + case ToastLocation.GAMEPADUI_QAM: + return <GamepadUIQAMToast toast={toast} />; + } +}); -export default Toast; +export default ToastRenderer; |
