Skip to content

Commit

Permalink
feat(toaster): render notifications in the quick access menu
Browse files Browse the repository at this point in the history
  • Loading branch information
AAGaming00 committed Jul 26, 2024
1 parent 9bc85f1 commit 7e1cd6f
Show file tree
Hide file tree
Showing 8 changed files with 133 additions and 193 deletions.
74 changes: 38 additions & 36 deletions frontend/src/components/DeckyIcon.tsx
Original file line number Diff line number Diff line change
@@ -1,37 +1,39 @@
export default function DeckyIcon() {
return (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 456" width="512" height="456">
<g>
<path
style={{ fill: 'none' }}
d="M154.33,72.51v49.79c11.78-0.17,23.48,2,34.42,6.39c10.93,4.39,20.89,10.91,29.28,19.18
c8.39,8.27,15.06,18.13,19.61,29c4.55,10.87,6.89,22.54,6.89,34.32c0,11.78-2.34,23.45-6.89,34.32
c-4.55,10.87-11.21,20.73-19.61,29c-8.39,8.27-18.35,14.79-29.28,19.18c-10.94,4.39-22.63,6.56-34.42,6.39v49.77
c36.78,0,72.05-14.61,98.05-40.62c26-26.01,40.61-61.28,40.61-98.05c0-36.78-14.61-72.05-40.61-98.05
C226.38,87.12,191.11,72.51,154.33,72.51z"
/>
import { FC, SVGAttributes } from 'react';

<ellipse
transform="matrix(0.982 -0.1891 0.1891 0.982 -37.1795 32.9988)"
style={{ fill: 'none' }}
cx="154.33"
cy="211.33"
rx="69.33"
ry="69.33"
/>
<path style={{ fill: 'none' }} d="M430,97h-52v187h52c7.18,0,13-5.82,13-13V110C443,102.82,437.18,97,430,97z" />
<path
style={{ fill: 'currentColor' }}
d="M432,27h-54V0H0v361c0,52.47,42.53,95,95,95h188c52.47,0,95-42.53,95-95v-7h54c44.18,0,80-35.82,80-80V107
C512,62.82,476.18,27,432,27z M85,211.33c0-38.29,31.04-69.33,69.33-69.33c38.29,0,69.33,31.04,69.33,69.33
c0,38.29-31.04,69.33-69.33,69.33C116.04,280.67,85,249.62,85,211.33z M252.39,309.23c-26.01,26-61.28,40.62-98.05,40.62v-49.77
c11.78,0.17,23.48-2,34.42-6.39c10.93-4.39,20.89-10.91,29.28-19.18c8.39-8.27,15.06-18.13,19.61-29
c4.55-10.87,6.89-22.53,6.89-34.32c0-11.78-2.34-23.45-6.89-34.32c-4.55-10.87-11.21-20.73-19.61-29
c-8.39-8.27-18.35-14.79-29.28-19.18c-10.94-4.39-22.63-6.56-34.42-6.39V72.51c36.78,0,72.05,14.61,98.05,40.61
c26,26.01,40.61,61.28,40.61,98.05C293,247.96,278.39,283.23,252.39,309.23z M443,271c0,7.18-5.82,13-13,13h-52V97h52
c7.18,0,13,5.82,13,13V271z"
/>
</g>
</svg>
);
}
const DeckyIcon: FC<SVGAttributes<SVGElement>> = (props) => (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 456" width="512" height="456" {...props}>
<g>
<path
style={{ fill: 'none' }}
d="M154.33,72.51v49.79c11.78-0.17,23.48,2,34.42,6.39c10.93,4.39,20.89,10.91,29.28,19.18
c8.39,8.27,15.06,18.13,19.61,29c4.55,10.87,6.89,22.54,6.89,34.32c0,11.78-2.34,23.45-6.89,34.32
c-4.55,10.87-11.21,20.73-19.61,29c-8.39,8.27-18.35,14.79-29.28,19.18c-10.94,4.39-22.63,6.56-34.42,6.39v49.77
c36.78,0,72.05-14.61,98.05-40.62c26-26.01,40.61-61.28,40.61-98.05c0-36.78-14.61-72.05-40.61-98.05
C226.38,87.12,191.11,72.51,154.33,72.51z"
/>

<ellipse
transform="matrix(0.982 -0.1891 0.1891 0.982 -37.1795 32.9988)"
style={{ fill: 'none' }}
cx="154.33"
cy="211.33"
rx="69.33"
ry="69.33"
/>
<path style={{ fill: 'none' }} d="M430,97h-52v187h52c7.18,0,13-5.82,13-13V110C443,102.82,437.18,97,430,97z" />
<path
style={{ fill: 'currentColor' }}
d="M432,27h-54V0H0v361c0,52.47,42.53,95,95,95h188c52.47,0,95-42.53,95-95v-7h54c44.18,0,80-35.82,80-80V107
C512,62.82,476.18,27,432,27z M85,211.33c0-38.29,31.04-69.33,69.33-69.33c38.29,0,69.33,31.04,69.33,69.33
c0,38.29-31.04,69.33-69.33,69.33C116.04,280.67,85,249.62,85,211.33z M252.39,309.23c-26.01,26-61.28,40.62-98.05,40.62v-49.77
c11.78,0.17,23.48-2,34.42-6.39c10.93-4.39,20.89-10.91,29.28-19.18c8.39-8.27,15.06-18.13,19.61-29
c4.55-10.87,6.89-22.53,6.89-34.32c0-11.78-2.34-23.45-6.89-34.32c-4.55-10.87-11.21-20.73-19.61-29
c-8.39-8.27-18.35-14.79-29.28-19.18c-10.94-4.39-22.63-6.56-34.42-6.39V72.51c36.78,0,72.05,14.61,98.05,40.61
c26,26.01,40.61,61.28,40.61,98.05C293,247.96,278.39,283.23,252.39,309.23z M443,271c0,7.18-5.82,13-13,13h-52V97h52
c7.18,0,13,5.82,13,13V271z"
/>
</g>
</svg>
);

export default DeckyIcon;
57 changes: 0 additions & 57 deletions frontend/src/components/DeckyToaster.tsx

This file was deleted.

69 changes: 0 additions & 69 deletions frontend/src/components/DeckyToasterState.tsx

This file was deleted.

103 changes: 80 additions & 23 deletions frontend/src/components/Toast.tsx
Original file line number Diff line number Diff line change
@@ -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 || '')}>
Expand All @@ -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;
4 changes: 3 additions & 1 deletion frontend/src/components/settings/pages/testing/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
} from '@decky/ui';
import { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { FaDownload, FaInfo } from 'react-icons/fa';
import { FaDownload, FaFlask, FaInfo } from 'react-icons/fa';

import { setSetting } from '../../../../utils/settings';
import { UpdateBranch } from '../general/BranchSelect';
Expand Down Expand Up @@ -94,6 +94,7 @@ export default function TestingVersionList() {
DeckyPluginLoader.toaster.toast({
title: t('Testing.start_download_toast', { id: version.id }),
body: null,
icon: <FaFlask />,
});
try {
await downloadTestingVersion(version.id, version.head_sha);
Expand All @@ -102,6 +103,7 @@ export default function TestingVersionList() {
DeckyPluginLoader.toaster.toast({
title: t('Testing.error'),
body: `${e.name}: ${e.message}`,
icon: <FaFlask />,
});
}
}
Expand Down
3 changes: 3 additions & 0 deletions frontend/src/plugin-loader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
import { FC, lazy } from 'react';
import { FaExclamationCircle, FaPlug } from 'react-icons/fa';

import DeckyIcon from './components/DeckyIcon';
import { DeckyState, DeckyStateContextProvider, UserInfo, useDeckyState } from './components/DeckyState';
import { File, FileSelectionType } from './components/modals/filepicker';
import { deinitFilepickerPatches, initFilepickerPatches } from './components/modals/filepicker/patches';
Expand Down Expand Up @@ -218,6 +219,7 @@ class PluginLoader extends Logger {
i18nArgs={{ tag_name: versionInfo?.remote?.tag_name }}
/>
),
icon: <DeckyIcon />,
onClick: () => Router.Navigate('/decky/settings'),
});
}
Expand Down Expand Up @@ -246,6 +248,7 @@ class PluginLoader extends Logger {
i18nArgs={{ count: updates.size }}
/>
),
icon: <DeckyIcon />,
onClick: () => Router.Navigate('/decky/settings/plugins'),
});
}
Expand Down
3 changes: 1 addition & 2 deletions frontend/src/router-hook.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,6 @@ class RouterHook extends Logger {
routerNode = findRouterNode();
}
if (routerNode) {
this.debug('routerNode', routerNode);
// Patch the component globally
this.routerPatch = afterPatch(routerNode.elementType, 'type', this.handleRouterRender.bind(this));
// Swap out the current instance
Expand Down Expand Up @@ -110,7 +109,7 @@ class RouterHook extends Logger {
const { routes, routePatches } = useDeckyRouterState();
// TODO make more redundant
if (!children?.props?.children?.[0]?.props?.children) {
console.log('routerWrapper wrong component?', children);
this.debug('routerWrapper wrong component?', children);
return children;
}
const mainRouteList = children.props.children[0].props.children;
Expand Down
Loading

0 comments on commit 7e1cd6f

Please sign in to comment.