summaryrefslogtreecommitdiff
path: root/frontend/src/components/Toast.tsx
diff options
context:
space:
mode:
authorAAGaming <aagaming@riseup.net>2024-08-05 14:07:10 -0400
committerGitHub <noreply@github.com>2024-08-05 14:07:10 -0400
commit131f0961ff451ec47376483178e092c8d7403b27 (patch)
tree4d2ea34e8220e14c4b820cc1ad38face7193f6fe /frontend/src/components/Toast.tsx
parent75aa1e4851445646994ba3a61ff41325403359fb (diff)
downloaddecky-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.tsx102
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;