mirror of
https://github.com/SteamDeckHomebrew/decky-loader.git
synced 2026-07-11 20:01:57 +00:00
feat(toaster): render notifications in the quick access menu
This commit is contained in:
@@ -1,37 +1,39 @@
|
|||||||
export default function DeckyIcon() {
|
import { FC, SVGAttributes } from 'react';
|
||||||
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"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<ellipse
|
const DeckyIcon: FC<SVGAttributes<SVGElement>> = (props) => (
|
||||||
transform="matrix(0.982 -0.1891 0.1891 0.982 -37.1795 32.9988)"
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 456" width="512" height="456" {...props}>
|
||||||
style={{ fill: 'none' }}
|
<g>
|
||||||
cx="154.33"
|
<path
|
||||||
cy="211.33"
|
style={{ fill: 'none' }}
|
||||||
rx="69.33"
|
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
|
||||||
ry="69.33"
|
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
|
||||||
<path style={{ fill: 'none' }} d="M430,97h-52v187h52c7.18,0,13-5.82,13-13V110C443,102.82,437.18,97,430,97z" />
|
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
|
||||||
<path
|
C226.38,87.12,191.11,72.51,154.33,72.51z"
|
||||||
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
|
<ellipse
|
||||||
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
|
transform="matrix(0.982 -0.1891 0.1891 0.982 -37.1795 32.9988)"
|
||||||
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
|
style={{ fill: 'none' }}
|
||||||
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
|
cx="154.33"
|
||||||
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
|
cy="211.33"
|
||||||
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
|
rx="69.33"
|
||||||
c7.18,0,13,5.82,13,13V271z"
|
ry="69.33"
|
||||||
/>
|
/>
|
||||||
</g>
|
<path style={{ fill: 'none' }} d="M430,97h-52v187h52c7.18,0,13-5.82,13-13V110C443,102.82,437.18,97,430,97z" />
|
||||||
</svg>
|
<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;
|
||||||
|
|||||||
@@ -1,57 +0,0 @@
|
|||||||
import type { ToastData } from '@decky/api';
|
|
||||||
import { joinClassNames } from '@decky/ui';
|
|
||||||
import { FC, ReactElement, useEffect, useState } from 'react';
|
|
||||||
|
|
||||||
import { useDeckyToasterState } from './DeckyToasterState';
|
|
||||||
import Toast, { toastClasses } from './Toast';
|
|
||||||
|
|
||||||
interface DeckyToasterProps {}
|
|
||||||
|
|
||||||
interface RenderedToast {
|
|
||||||
component: ReactElement;
|
|
||||||
data: ToastData;
|
|
||||||
}
|
|
||||||
|
|
||||||
const DeckyToaster: FC<DeckyToasterProps> = () => {
|
|
||||||
const { toasts, removeToast } = useDeckyToasterState();
|
|
||||||
const [renderedToast, setRenderedToast] = useState<RenderedToast | null>(null);
|
|
||||||
console.log(toasts);
|
|
||||||
if (toasts.size > 0) {
|
|
||||||
const [activeToast] = toasts;
|
|
||||||
if (!renderedToast || activeToast != renderedToast.data) {
|
|
||||||
// TODO play toast soundReactElement
|
|
||||||
console.log('rendering toast', activeToast);
|
|
||||||
setRenderedToast({ component: <Toast key={Math.random()} toast={activeToast} />, data: activeToast });
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (renderedToast) setRenderedToast(null);
|
|
||||||
}
|
|
||||||
useEffect(() => {
|
|
||||||
// not actually node but TS is shit
|
|
||||||
let interval: number | 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 (
|
|
||||||
<div className={joinClassNames('deckyToaster', toastClasses.ToastPlaceholder)}>
|
|
||||||
{renderedToast && renderedToast.component}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default DeckyToaster;
|
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
import type { ToastData } from '@decky/api';
|
|
||||||
import { FC, ReactNode, createContext, useContext, useEffect, useState } from 'react';
|
|
||||||
|
|
||||||
interface PublicDeckyToasterState {
|
|
||||||
toasts: Set<ToastData>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class DeckyToasterState {
|
|
||||||
private _toasts: Set<ToastData> = 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<DeckyToasterContext>(null as any);
|
|
||||||
|
|
||||||
export const useDeckyToasterState = () => useContext(DeckyToasterContext);
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
deckyToasterState: DeckyToasterState;
|
|
||||||
children: ReactNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const DeckyToasterStateContextProvider: FC<Props> = ({ children, deckyToasterState }) => {
|
|
||||||
const [publicDeckyToasterState, setPublicDeckyToasterState] = useState<PublicDeckyToasterState>({
|
|
||||||
...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 (
|
|
||||||
<DeckyToasterContext.Provider value={{ ...publicDeckyToasterState, addToast, removeToast }}>
|
|
||||||
{children}
|
|
||||||
</DeckyToasterContext.Provider>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,37 +1,38 @@
|
|||||||
import type { ToastData } from '@decky/api';
|
import type { ToastData } from '@decky/api';
|
||||||
import { findModule, joinClassNames } from '@decky/ui';
|
import { Focusable, Navigation, findClassModule, joinClassNames } from '@decky/ui';
|
||||||
import { FunctionComponent } from 'react';
|
import { FC, memo } from 'react';
|
||||||
|
|
||||||
|
import Logger from '../logger';
|
||||||
|
import TranslationHelper, { TranslationClass } from '../utils/TranslationHelper';
|
||||||
|
|
||||||
|
const logger = new Logger('ToastRenderer');
|
||||||
|
|
||||||
|
// TODO there are more of these
|
||||||
|
export enum ToastLocation {
|
||||||
|
/** Big Picture popup toasts */
|
||||||
|
GAMEPADUI_POPUP = 1,
|
||||||
|
/** QAM Notifications tab */
|
||||||
|
GAMEPADUI_QAM = 3,
|
||||||
|
}
|
||||||
|
|
||||||
interface ToastProps {
|
interface ToastProps {
|
||||||
toast: ToastData;
|
toast: ToastData;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const toastClasses = findModule((mod) => {
|
interface ToastRendererProps extends ToastProps {
|
||||||
if (typeof mod !== 'object') return false;
|
location: ToastLocation;
|
||||||
|
}
|
||||||
|
|
||||||
if (mod.ToastPlaceholder) {
|
const templateClasses = findClassModule((m) => m.ShortTemplate) || {};
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
// These are memoized as they like to randomly rerender
|
||||||
});
|
|
||||||
|
|
||||||
const templateClasses = findModule((mod) => {
|
const GamepadUIPopupToast: FC<ToastProps> = memo(({ toast }) => {
|
||||||
if (typeof mod !== 'object') return false;
|
|
||||||
|
|
||||||
if (mod.ShortTemplate) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
|
|
||||||
const Toast: FunctionComponent<ToastProps> = ({ toast }) => {
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
style={{ '--toast-duration': `${toast.duration}ms` } as React.CSSProperties}
|
style={{ '--toast-duration': `${toast.duration}ms` } as React.CSSProperties}
|
||||||
onClick={toast.onClick}
|
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>}
|
{toast.logo && <div className={templateClasses.StandardLogoDimensions}>{toast.logo}</div>}
|
||||||
<div className={joinClassNames(templateClasses.Content, toast.contentClassName || '')}>
|
<div className={joinClassNames(templateClasses.Content, toast.contentClassName || '')}>
|
||||||
@@ -43,6 +44,62 @@ const Toast: FunctionComponent<ToastProps> = ({ toast }) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
});
|
||||||
|
|
||||||
export default Toast;
|
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 ToastRenderer;
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import {
|
|||||||
} from '@decky/ui';
|
} from '@decky/ui';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
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 { setSetting } from '../../../../utils/settings';
|
||||||
import { UpdateBranch } from '../general/BranchSelect';
|
import { UpdateBranch } from '../general/BranchSelect';
|
||||||
@@ -94,6 +94,7 @@ export default function TestingVersionList() {
|
|||||||
DeckyPluginLoader.toaster.toast({
|
DeckyPluginLoader.toaster.toast({
|
||||||
title: t('Testing.start_download_toast', { id: version.id }),
|
title: t('Testing.start_download_toast', { id: version.id }),
|
||||||
body: null,
|
body: null,
|
||||||
|
icon: <FaFlask />,
|
||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
await downloadTestingVersion(version.id, version.head_sha);
|
await downloadTestingVersion(version.id, version.head_sha);
|
||||||
@@ -102,6 +103,7 @@ export default function TestingVersionList() {
|
|||||||
DeckyPluginLoader.toaster.toast({
|
DeckyPluginLoader.toaster.toast({
|
||||||
title: t('Testing.error'),
|
title: t('Testing.error'),
|
||||||
body: `${e.name}: ${e.message}`,
|
body: `${e.name}: ${e.message}`,
|
||||||
|
icon: <FaFlask />,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import {
|
|||||||
import { FC, lazy } from 'react';
|
import { FC, lazy } from 'react';
|
||||||
import { FaExclamationCircle, FaPlug } from 'react-icons/fa';
|
import { FaExclamationCircle, FaPlug } from 'react-icons/fa';
|
||||||
|
|
||||||
|
import DeckyIcon from './components/DeckyIcon';
|
||||||
import { DeckyState, DeckyStateContextProvider, UserInfo, useDeckyState } from './components/DeckyState';
|
import { DeckyState, DeckyStateContextProvider, UserInfo, useDeckyState } from './components/DeckyState';
|
||||||
import { File, FileSelectionType } from './components/modals/filepicker';
|
import { File, FileSelectionType } from './components/modals/filepicker';
|
||||||
import { deinitFilepickerPatches, initFilepickerPatches } from './components/modals/filepicker/patches';
|
import { deinitFilepickerPatches, initFilepickerPatches } from './components/modals/filepicker/patches';
|
||||||
@@ -218,6 +219,7 @@ class PluginLoader extends Logger {
|
|||||||
i18nArgs={{ tag_name: versionInfo?.remote?.tag_name }}
|
i18nArgs={{ tag_name: versionInfo?.remote?.tag_name }}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
|
icon: <DeckyIcon />,
|
||||||
onClick: () => Router.Navigate('/decky/settings'),
|
onClick: () => Router.Navigate('/decky/settings'),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -246,6 +248,7 @@ class PluginLoader extends Logger {
|
|||||||
i18nArgs={{ count: updates.size }}
|
i18nArgs={{ count: updates.size }}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
|
icon: <DeckyIcon />,
|
||||||
onClick: () => Router.Navigate('/decky/settings/plugins'),
|
onClick: () => Router.Navigate('/decky/settings/plugins'),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,7 +58,6 @@ class RouterHook extends Logger {
|
|||||||
routerNode = findRouterNode();
|
routerNode = findRouterNode();
|
||||||
}
|
}
|
||||||
if (routerNode) {
|
if (routerNode) {
|
||||||
this.debug('routerNode', routerNode);
|
|
||||||
// Patch the component globally
|
// Patch the component globally
|
||||||
this.routerPatch = afterPatch(routerNode.elementType, 'type', this.handleRouterRender.bind(this));
|
this.routerPatch = afterPatch(routerNode.elementType, 'type', this.handleRouterRender.bind(this));
|
||||||
// Swap out the current instance
|
// Swap out the current instance
|
||||||
@@ -110,7 +109,7 @@ class RouterHook extends Logger {
|
|||||||
const { routes, routePatches } = useDeckyRouterState();
|
const { routes, routePatches } = useDeckyRouterState();
|
||||||
// TODO make more redundant
|
// TODO make more redundant
|
||||||
if (!children?.props?.children?.[0]?.props?.children) {
|
if (!children?.props?.children?.[0]?.props?.children) {
|
||||||
console.log('routerWrapper wrong component?', children);
|
this.debug('routerWrapper wrong component?', children);
|
||||||
return children;
|
return children;
|
||||||
}
|
}
|
||||||
const mainRouteList = children.props.children[0].props.children;
|
const mainRouteList = children.props.children[0].props.children;
|
||||||
|
|||||||
@@ -32,16 +32,17 @@ class Toaster extends Logger {
|
|||||||
this.init();
|
this.init();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO maybe move to constructor lol
|
||||||
async init() {
|
async init() {
|
||||||
const ToastRenderer = findModuleExport((e) => e?.toString()?.includes(`controller:"notification",method:`));
|
const ValveToastRenderer = findModuleExport((e) => e?.toString()?.includes(`controller:"notification",method:`));
|
||||||
this.debug('toastrenderer', ToastRenderer);
|
|
||||||
// TODO find a way to undo this if possible?
|
// TODO find a way to undo this if possible?
|
||||||
const patchedRenderer = injectFCTrampoline(ToastRenderer);
|
const patchedRenderer = injectFCTrampoline(ValveToastRenderer);
|
||||||
this.toastPatch = replacePatch(patchedRenderer, 'component', (args: any[]) => {
|
this.toastPatch = replacePatch(patchedRenderer, 'component', (args: any[]) => {
|
||||||
this.debug('render toast', args);
|
this.debug('render toast', args);
|
||||||
if (args?.[0]?.group?.decky || args?.[0]?.group?.notifications?.[0]?.decky) {
|
if (args?.[0]?.group?.decky || args?.[0]?.group?.notifications?.[0]?.decky) {
|
||||||
this.debug('rendering decky toast');
|
return args[0].group.notifications.map((notification: any) => (
|
||||||
return args[0].group.notifications.map((notification: any) => <Toast toast={notification.data} />);
|
<Toast toast={notification.data} location={args?.[0]?.location} />
|
||||||
|
));
|
||||||
}
|
}
|
||||||
return callOriginal;
|
return callOriginal;
|
||||||
});
|
});
|
||||||
@@ -65,6 +66,7 @@ class Toaster extends Logger {
|
|||||||
if (toast.sound === undefined) toast.sound = 6;
|
if (toast.sound === undefined) toast.sound = 6;
|
||||||
if (toast.playSound === undefined) toast.playSound = true;
|
if (toast.playSound === undefined) toast.playSound = true;
|
||||||
if (toast.showToast === undefined) toast.showToast = true;
|
if (toast.showToast === undefined) toast.showToast = true;
|
||||||
|
if (toast.timestamp === undefined) toast.timestamp = new Date();
|
||||||
if (
|
if (
|
||||||
(window.settingsStore.settings.bDisableAllToasts && !toast.critical) ||
|
(window.settingsStore.settings.bDisableAllToasts && !toast.critical) ||
|
||||||
(window.settingsStore.settings.bDisableToastsInGame &&
|
(window.settingsStore.settings.bDisableToastsInGame &&
|
||||||
@@ -79,6 +81,7 @@ class Toaster extends Logger {
|
|||||||
notifications: [toast],
|
notifications: [toast],
|
||||||
};
|
};
|
||||||
tray.unshift(group);
|
tray.unshift(group);
|
||||||
|
// TODO do we need to handle expiration?
|
||||||
}
|
}
|
||||||
const info = {
|
const info = {
|
||||||
showToast: toast.showToast,
|
showToast: toast.showToast,
|
||||||
|
|||||||
Reference in New Issue
Block a user