fix: notification provider being undefined after a rerender
This commit is contained in:
parent
1e02f4ee73
commit
03259473a3
6 changed files with 101 additions and 37 deletions
|
|
@ -54,13 +54,13 @@ function NotificationPopup() {
|
|||
|
||||
if (notificationEntries.length === 0)
|
||||
return (
|
||||
<div className="p-3 text-center">
|
||||
<div className="p-3 w-80 text-center">
|
||||
<p className="text-fg-secondary text-sm">No notifications</p>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<Popup className="min-w-80 w-full max-w-96 max-h-96 overflow-y-auto">
|
||||
<Popup className="min-w-80 w-80 max-w-96 max-h-96 overflow-y-auto">
|
||||
<div className="flex items-center justify-between border-b border-component-border">
|
||||
<h3 className="font-semibold text-fg-primary">Notifications</h3>
|
||||
{notificationEntries.length > 0 && (
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import type { ReactNode } from 'react';
|
||||
import { bindings } from '@/main';
|
||||
import { randomString } from '@/utils/index';
|
||||
import { createContext, useContext, useState } from 'react';
|
||||
import { createContext, memo, useContext, useEffect, useMemo, useState } from 'react';
|
||||
|
||||
export interface NotificationData {
|
||||
title: string;
|
||||
|
|
@ -24,40 +24,72 @@ interface NotificationContextValue {
|
|||
setNotifications: React.Dispatch<React.SetStateAction<Notifications>>;
|
||||
}
|
||||
|
||||
const NotificationContext = createContext<NotificationContextValue | undefined>(undefined);
|
||||
const defaultContextValue: NotificationContextValue = {
|
||||
notifications: {},
|
||||
setNotifications: () => {},
|
||||
};
|
||||
|
||||
const NotificationContext = createContext<NotificationContextValue>(defaultContextValue);
|
||||
|
||||
interface NotificationProviderProps {
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
export function NotificationProvider({ children }: NotificationProviderProps) {
|
||||
export const NotificationProvider = memo(({ children }: NotificationProviderProps) => {
|
||||
const [notifications, setNotifications] = useState<Notifications>({});
|
||||
|
||||
bindings.events.ingress.on((e) => {
|
||||
setNotifications(prev => ({
|
||||
...prev,
|
||||
[e.id]: {
|
||||
title: `${e.ingress_type}`,
|
||||
message: e.message,
|
||||
},
|
||||
}));
|
||||
}).then(u => u());
|
||||
useEffect(() => {
|
||||
let cleanup: (() => void) | undefined;
|
||||
|
||||
bindings.events.ingress.on((e) => {
|
||||
setNotifications(prev => ({
|
||||
...prev,
|
||||
[e.id]: {
|
||||
title: `${e.ingress_type}`,
|
||||
message: e.message,
|
||||
},
|
||||
}));
|
||||
}).then((u) => {
|
||||
cleanup = u;
|
||||
}).catch(console.error);
|
||||
|
||||
return () => {
|
||||
cleanup?.();
|
||||
};
|
||||
}, []);
|
||||
|
||||
const contextValue = useMemo(() => ({
|
||||
notifications,
|
||||
setNotifications,
|
||||
}), [notifications, setNotifications]);
|
||||
|
||||
return (
|
||||
<NotificationContext.Provider value={{ notifications, setNotifications }}>
|
||||
<NotificationContext.Provider value={contextValue}>
|
||||
{children}
|
||||
</NotificationContext.Provider>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
function useNotifications(): HookReturn {
|
||||
const context = useContext(NotificationContext);
|
||||
|
||||
if (!context)
|
||||
throw new Error('useNotifications must be used within a NotificationProvider');
|
||||
|
||||
const { notifications, setNotifications } = context;
|
||||
|
||||
const isDefaultContext = setNotifications === defaultContextValue.setNotifications;
|
||||
|
||||
if (isDefaultContext) {
|
||||
if (import.meta.env.DEV) {
|
||||
console.warn('useNotifications: Using default context (likely due to HMR or missing provider)');
|
||||
return {
|
||||
list: {},
|
||||
set: () => {},
|
||||
create: () => {},
|
||||
remove: () => {},
|
||||
clear: () => {},
|
||||
};
|
||||
}
|
||||
throw new Error('useNotifications must be used within a NotificationProvider');
|
||||
}
|
||||
|
||||
const ctx: HookReturn = {
|
||||
list: notifications,
|
||||
set: (id, data) => setNotifications(prev => ({ ...prev, [id]: data })),
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ function RouteComponent() {
|
|||
|
||||
<GameSettings key={_result.data?.name} settings={_result.data!} />
|
||||
|
||||
<ProcessSettings />
|
||||
<ProcessSettings key={_result.data?.name} settings={_result.data!} />
|
||||
</div>
|
||||
</ScrollableContainer>
|
||||
</Sidebar.Page>
|
||||
|
|
|
|||
|
|
@ -28,20 +28,6 @@ hey future sassan here i guess the issue is solved idk
|
|||
function RouteComponent() {
|
||||
const result = useCommand('getClusters', bindings.core.getClusters);
|
||||
|
||||
useEffect(() => {
|
||||
let unlisten: (() => void) | undefined;
|
||||
(async () => {
|
||||
unlisten = await bindings.events.ingress.on((e) => {
|
||||
if (typeof e.ingress_type === 'object')
|
||||
if ('PrepareCluster' in e.ingress_type)
|
||||
// eslint-disable-next-line no-console -- ok
|
||||
console.log(`Preparing cluster: ${e.ingress_type.PrepareCluster.cluster_name} %${e.percent}`);
|
||||
});
|
||||
})();
|
||||
|
||||
return () => unlisten?.();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="h-full flex flex-col gap-y-4 text-fg-primary">
|
||||
<Banner />
|
||||
|
|
|
|||
|
|
@ -1,13 +1,41 @@
|
|||
import Navbar from '@/components/Navbar';
|
||||
import useNotifications from '@/hooks/useNotification';
|
||||
import { bindings } from '@/main';
|
||||
import { AnimatedOutlet } from '@onelauncher/common/components';
|
||||
import { createFileRoute } from '@tanstack/react-router';
|
||||
import { OverlayScrollbarsComponent } from 'overlayscrollbars-react';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
export const Route = createFileRoute('/app')({
|
||||
component: RouteComponent,
|
||||
});
|
||||
|
||||
function RouteComponent() {
|
||||
const { set, remove } = useNotifications();
|
||||
|
||||
useEffect(() => {
|
||||
let unlisten: (() => void) | undefined;
|
||||
(async () => {
|
||||
unlisten = await bindings.events.ingress.on((e) => {
|
||||
if (typeof e.ingress_type === 'object')
|
||||
if ('PrepareCluster' in e.ingress_type) {
|
||||
const { cluster_name } = e.ingress_type.PrepareCluster;
|
||||
let id = `cluster-${cluster_name}`;
|
||||
|
||||
set(id, {
|
||||
title: 'Preparing Cluster',
|
||||
message: e.message,
|
||||
});
|
||||
|
||||
if (e.percent === e.total)
|
||||
remove(id);
|
||||
}
|
||||
});
|
||||
})();
|
||||
|
||||
return () => unlisten?.();
|
||||
}, [set, remove]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex flex-col px-4">
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ function RouteComponent() {
|
|||
|
||||
{/* <LauncherSettings /> */}
|
||||
|
||||
<ProcessSettings />
|
||||
<ProcessSettings settings={result.data!.global_game_settings} />
|
||||
</div>
|
||||
</ScrollableContainer>
|
||||
</Sidebar.Page>
|
||||
|
|
@ -129,7 +129,13 @@ export function GameSettings(props: GameSettingProps) {
|
|||
// );
|
||||
// }
|
||||
|
||||
export function ProcessSettings() {
|
||||
interface ProcessSettingsProps {
|
||||
settings: SettingProfileModel;
|
||||
}
|
||||
|
||||
export function ProcessSettings(props: ProcessSettingsProps) {
|
||||
const { settings } = props;
|
||||
|
||||
return (
|
||||
<>
|
||||
<SettingsRow.Header>Process</SettingsRow.Header>
|
||||
|
|
@ -140,6 +146,10 @@ export function ProcessSettings() {
|
|||
title="Pre-Launch Command"
|
||||
>
|
||||
<TextField
|
||||
defaultValue={settings.hook_pre || ''}
|
||||
onChange={(e) => {
|
||||
settings.hook_pre = e.target.value || null;
|
||||
}}
|
||||
placeholder="echo 'Game started'"
|
||||
/>
|
||||
</SettingsRow>
|
||||
|
|
@ -150,6 +160,10 @@ export function ProcessSettings() {
|
|||
title="Wrapper Command"
|
||||
>
|
||||
<TextField
|
||||
defaultValue={settings.hook_wrapper || ''}
|
||||
onChange={(e) => {
|
||||
settings.hook_wrapper = e.target.value || null;
|
||||
}}
|
||||
placeholder="gamescope"
|
||||
/>
|
||||
</SettingsRow>
|
||||
|
|
@ -160,6 +174,10 @@ export function ProcessSettings() {
|
|||
title="Post-Exit Command"
|
||||
>
|
||||
<TextField
|
||||
defaultValue={settings.hook_post || ''}
|
||||
onChange={(e) => {
|
||||
settings.hook_post = e.target.value || null;
|
||||
}}
|
||||
placeholder="echo 'Game exited'"
|
||||
/>
|
||||
</SettingsRow>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue