Skip to content

Commit 243e754

Browse files
committed
Make notifications more reliable
1 parent 3cf9800 commit 243e754

11 files changed

+120
-89
lines changed

src/UI/components/RoomListComponent.tsx

+6-3
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,17 @@ const customIcons = {
1515

1616

1717
export function RoomListComponent(
18-
{ name, ID, notifications: { unread, mentions }, type }: Readonly<{
18+
{ name, ID, type }: Readonly<{
1919
name: string;
2020
ID: string;
2121
type: string;
22-
notifications: { unread: number; mentions: number };
2322
}>,
2423
) {
2524
const { setRoom } = useClientContext();
25+
const notifications = useClientStore(state => state.notifications);
26+
const currentNotifications = notifications[ID];
27+
const unread = currentNotifications?.unread ?? 0;
28+
const mentions = currentNotifications?.mentions ?? 0;
2629
const room = useClientStore(state => state.currentRoom);
2730
const customIcon = customIcons[ID as keyof typeof customIcons];
2831
const icon = customIcon ?? (type === 'battle' ?
@@ -48,7 +51,7 @@ export function RoomListComponent(
4851
{/** Notification circle if it applies */}
4952
{(unread > 0) ?
5053
(
51-
<span className="rounded-full bg-white text-white text-xs p-1 h-1 w-1 absolute top-1/2 transform -translate-x-1/2 -translate-y-1/2" />
54+
<span className="rounded-full bg-gray-600 dark:bg-gray-600 text-xs p-1 h-1 w-1 absolute top-1/2 transform -translate-x-1/2 -translate-y-1/2" />
5255
) :
5356
null}
5457
<button

src/UI/components/single/Chat.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -187,8 +187,8 @@ export function MessageComponent(
187187
}
188188
return (
189189
<div
190-
className={'pt-0.5 ' +
191-
(hld ? 'bg-yellow-hl-body-light dark:bg-yellow-hl-body' : '')}
190+
className={cn('px-1',
191+
(hld ? 'bg-yellow-hl-body-light dark:bg-yellow-hl-body' : ''))}
192192
>
193193
<span className="text-gray-125 text-xs">
194194
{time ? HHMMSS(time) : ''}

src/UI/components/single/ClientContext.tsx

+1-15
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { assertNever } from '@/lib/utils';
22
import { Client, client, useClientStore } from '../../../client/client';
33
import { Message } from '../../../client/message';
4-
import { notificationsEngine, RoomNotification } from '../../../client/notifications';
54
import { Room } from '../../../client/room/room';
65
import { loadCustomColors } from '../../../utils/namecolour';
76
import { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react';
@@ -13,14 +12,12 @@ interface ClientContextType {
1312
setRoom:(room: string | 1 | -1 | Room) => void;
1413
messages: Message[];
1514
setRooms: (rooms: Room[]) => void;
16-
notifications: RoomNotification[];
1715
}
1816

1917
const ClientContext = createContext<ClientContextType | undefined>(undefined);
2018

2119
export default function ClientContextProvider(props: Readonly<React.PropsWithChildren>) {
2220
const [rooms, setRooms] = useState<Room[]>(client.getRooms());
23-
const [notifications, setNotifications] = useState<RoomNotification[]>([]);
2421
const [update, setUpdate] = useState<number>(0); // Used to force update on rooms change
2522
const [previousRooms, setPreviousRooms] = useState<string[]>(['home']);
2623
const [messages, setMessages] = useState<Message[]>([]);
@@ -133,7 +130,6 @@ export default function ClientContextProvider(props: Readonly<React.PropsWithChi
133130
return;
134131
}
135132
const msgs = currentRoom.messages ?? [];
136-
setNotifications(client.getNotifications());
137133
setMessages([...msgs]);
138134
}, [client]);
139135

@@ -146,20 +142,11 @@ export default function ClientContextProvider(props: Readonly<React.PropsWithChi
146142
setUpdateMsgs(updateMsgs + 1);
147143
};
148144

149-
const notificationsEventListener: EventListener = (event) => {
150-
notificationsEngine.sendNotification((event as CustomEvent).detail);
151-
};
152-
153145
client.events.addEventListener('message', eventListener);
154-
client.events.addEventListener('notification', notificationsEventListener);
155146

156147
return () => {
157148
// Clean up the event listener when the component unmounts
158149
client.events.removeEventListener('message', eventListener);
159-
client.events.removeEventListener(
160-
'notification',
161-
notificationsEventListener,
162-
);
163150
};
164151
}, [
165152
client,
@@ -191,8 +178,7 @@ export default function ClientContextProvider(props: Readonly<React.PropsWithChi
191178
messages,
192179
rooms,
193180
setRooms,
194-
notifications,
195-
}), [messages, rooms, notifications]);
181+
}), [messages, rooms, setRooms]);
196182

197183
return (
198184
<ClientContext.Provider

src/UI/components/single/Sidebar.tsx

+1-5
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import { cn } from '@/lib/utils';
2525
import { useClientStore } from '@/client/client';
2626

2727
export default function Sidebar(props: Readonly<HTMLAttributes<'div'>>) {
28-
const rooms = useClientStore((state) => state.rooms);
28+
const { rooms } = useClientStore((state) => ({ rooms: state.rooms }));
2929
const roomsArray = Array.from(rooms.values()).filter(room => room.open);
3030

3131
const mouseSensor = useSensor(MouseSensor, {
@@ -84,10 +84,6 @@ export default function Sidebar(props: Readonly<HTMLAttributes<'div'>>) {
8484
name={room.name}
8585
type={room.type}
8686
ID={room.ID}
87-
notifications={{
88-
unread: room.unread,
89-
mentions: room.mentions,
90-
}}
9187
/>
9288
</SortableItem>
9389
))}

src/UI/components/single/UserPanel.tsx

-4
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@ function RenderUserContent() {
3333

3434
useEffect(() => {
3535
const onDisconnect = () => {
36-
console.log('disconnected');
3736
setConnected(false);
3837
};
3938
client.events.addEventListener('disconnect', onDisconnect);
@@ -42,9 +41,6 @@ function RenderUserContent() {
4241
};
4342
}, []);
4443

45-
useEffect(() => {
46-
console.log('connected', connected);
47-
}, [connected]);
4844
if (!connected) {
4945
return <Disconnected />;
5046
} else if (user) {

src/client/client.ts

+84-26
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { toID } from '../utils/generic';
33
import newMessage, { Message } from './message';
44
import { Room } from './room/room';
55
import { User } from './user';
6-
import { clientNotification, RoomNotification } from './notifications';
6+
import { clientNotification, notificationsEngine, RoomNotification as RoomNotifications } from './notifications';
77
import { Protocol } from '@pkmn/protocol';
88
import { assert, assertNever } from '@/lib/utils';
99
import { BattleRoom } from './room/battleRoom';
@@ -24,8 +24,10 @@ interface UseClientStoreType {
2424
// messages: Message[]; // only messages from the current room
2525
messages: Record<Room['ID'], Message[]>; // messages from all rooms
2626
newMessage: (room: Room, message: Message) => void;
27-
notifications: RoomNotification[];
28-
// setNotifications: (notifications: RoomNotification[]) => void;
27+
notifications: Record<Room['ID'], RoomNotifications>;
28+
clearNotifications: (roomID: Room['ID']) => void;
29+
addUnread: (room: Room) => void;
30+
addMention: (room: Room) => void;
2931
avatar: string;
3032
theme: 'light' | 'dark';
3133
user: string | undefined
@@ -42,6 +44,10 @@ export const useClientStore = create<UseClientStoreType>((set) => ({
4244
messages: {},
4345
newMessage: (room: Room, message: Message) => {
4446
set((state) => {
47+
if (room !== state.currentRoom) {
48+
state.addUnread(room);
49+
}
50+
4551
if (!state.messages[room.ID]) {
4652
return {
4753
messages: { ...state.messages, [room.ID]: [message] },
@@ -52,10 +58,48 @@ export const useClientStore = create<UseClientStoreType>((set) => ({
5258
};
5359
});
5460
},
55-
notifications: [],
61+
notifications: {},
62+
addUnread: (room: Room) => {
63+
set((state) => {
64+
if (!state.notifications[room.ID]) {
65+
return {
66+
notifications: { ...state.notifications, [room.ID]: { unread: 1, mentions: 0 } },
67+
};
68+
}
69+
return {
70+
notifications: { ...state.notifications, [room.ID]: { unread: state.notifications[room.ID].unread + 1, mentions: state.notifications[room.ID].mentions } },
71+
};
72+
});
73+
},
74+
addMention: (room: Room) => {
75+
set((state) => {
76+
if (!state.notifications[room.ID]) {
77+
return {
78+
notifications: { ...state.notifications, [room.ID]: { unread: 0, mentions: 1 } },
79+
};
80+
}
81+
return {
82+
notifications: { ...state.notifications, [room.ID]: { unread: state.notifications[room.ID].unread, mentions: state.notifications[room.ID].mentions + 1 } },
83+
};
84+
});
85+
},
86+
clearNotifications: (roomID: string) => {
87+
set((state) => {
88+
if (!state.notifications[roomID]) {
89+
return {
90+
notifications: { ...state.notifications, [roomID]: { unread: 0, mentions: 0 } },
91+
};
92+
}
93+
return {
94+
notifications: { ...state.notifications, [roomID]: { unread: 0, mentions: 0 } },
95+
};
96+
});
97+
},
98+
5699
avatar: 'lucas',
57100
theme: localStorage.getItem('theme') as 'light' | 'dark' ?? 'dark',
58101
user: undefined,
102+
59103
}));
60104

61105

@@ -108,7 +152,7 @@ export class Client {
108152
constructor(options?: ClientConstructor) {
109153
// if running test suite, don't do anything
110154
if (import.meta.env.VITEST) {
111-
console.log('Running tests, skipping client initialization');
155+
console.debug('Running tests, skipping client initialization');
112156
return;
113157
}
114158
try {
@@ -211,8 +255,9 @@ export class Client {
211255
selectRoom(roomid: string) {
212256
this.__selectedRoom = roomid;
213257
this.room(roomid)?.select();
214-
this.settings.changeRooms(this.rooms);
258+
// this.settings.changeRooms(this.rooms);
215259
useClientStore.setState({ currentRoom: this.room(roomid) });
260+
useClientStore.getState().clearNotifications(roomid);
216261
}
217262

218263
async queryUser(user: string, callback: (json: any) => void) {
@@ -330,16 +375,29 @@ export class Client {
330375
return highlight;
331376
}
332377

378+
private shouldNotify(room: Room, message: Message) {
379+
if (this.selectedRoom == room.ID && document.hasFocus()) return false;
380+
if (room.checkMessageStaleness(message)) return false;
381+
if (message.hld || room.type === 'pm') return true;
382+
return false;
383+
}
384+
333385
private forceHighlightMsg(roomid: string, message: Message) {
334386
return this.highlightMsg(roomid, message, true);
335387
}
336388

337-
getNotifications(): RoomNotification[] {
338-
return Array.from(this.rooms).map(([_, room]) => ({
339-
room: room.ID,
340-
mentions: room.mentions,
341-
unread: room.unread,
342-
}));
389+
getNotifications(): Map<string, RoomNotifications> {
390+
return new Map(
391+
[...this.rooms].map(([roomID, room]) => [roomID, { unread: room.unread, mentions: room.mentions }]),
392+
);
393+
}
394+
395+
clearNotifications(roomID: string) {
396+
const room = this.room(roomID);
397+
if (room) {
398+
room.clearNotifications();
399+
useClientStore.getState().clearNotifications(roomID);
400+
}
343401
}
344402

345403
openSettings() {
@@ -509,7 +567,9 @@ export class Client {
509567
private _addRoom(room: Room) {
510568
this.rooms.set(room.ID, room);
511569
useClientStore.setState({ rooms: new Map(this.rooms) });
512-
this.selectRoom(room.ID);
570+
if (!this.settings.rooms.find((r) => r.ID === room.ID)?.open) {
571+
this.selectRoom(room.ID);
572+
}
513573
if (room.type !== 'permanent' && !this.settings.rooms.find((r) => r.ID === room.ID)) {
514574
this.settings.addRoom(room);
515575
}
@@ -544,6 +604,7 @@ export class Client {
544604
message: Message,
545605
) {
546606
const room = this.room(roomID);
607+
this.highlightMsg(roomID, message);
547608
if (!room) {
548609
console.warn('addMessageToRoom: room (' + roomID + ') is unknown. Message:', message);
549610
return;
@@ -552,24 +613,21 @@ export class Client {
552613
selected: this.selectedRoom === roomID,
553614
selfSent: toID(this.settings.username) === toID(message.user),
554615
};
555-
let shouldNotify = false;
556616
if (message.name) {
557617
room.addUHTML(message, settings);
558618
} else {
559-
shouldNotify = room.addMessage(message, settings);
619+
room.addMessage(message, settings);
560620
}
561621
useClientStore.getState().newMessage(room, message);
562-
if (shouldNotify) {
563-
this.events.dispatchEvent(
564-
new CustomEvent('notification', {
565-
detail: {
566-
user: message.user,
567-
message: message.content,
568-
room: roomID,
569-
roomType: room.type,
570-
} as clientNotification,
571-
}),
572-
);
622+
623+
if (this.shouldNotify(room, message)) {
624+
notificationsEngine.sendNotification({
625+
user: message.user ?? '',
626+
message: message.content,
627+
room: roomID,
628+
roomType: room.type,
629+
});
630+
useClientStore.getState().addMention(room);
573631
}
574632
}
575633

src/client/message.ts

+1-4
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ export type Message = {
1717
type: MessageType;
1818
user?: string;
1919
timestamp?: Date;
20-
notify: boolean;
2120
/** true if message is highlighted, false if not, null if not parsed yet */
2221
hld: boolean | null
2322
name?: string; // only defined for uhtml messages
@@ -29,9 +28,8 @@ export default function ({
2928
user,
3029
timestamp,
3130
hld,
32-
notify,
3331
name,
34-
}: Omit<Optional<Message, 'hld' | 'notify'>, 'timestamp'> & {
32+
}: Omit<Optional<Message, 'hld'>, 'timestamp'> & {
3533
timestamp?: string;
3634
}): Message {
3735
if (type === 'uhtmlchange') {
@@ -44,6 +42,5 @@ export default function ({
4442
timestamp: timestamp ? new Date(Number(timestamp) * 1000) : undefined,
4543
hld: hld ?? null,
4644
name,
47-
notify: notify ?? false,
4845
};
4946
}

src/client/notifications.ts

+4-8
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { toast } from '@/components/ui/use-toast';
22

33
export type RoomNotification = {
4-
room: string;
54
mentions: number;
65
unread: number;
76
};
@@ -26,7 +25,6 @@ class NotificationsEngine {
2625
Notification.requestPermission().then((permission) => {
2726
this.permission = permission;
2827
});
29-
return;
3028
}
3129
}
3230

@@ -37,13 +35,11 @@ class NotificationsEngine {
3735
if (document.hasFocus()) {
3836
notification.user = notification.user.trim();
3937
if (selectedRoom !== notification.room) {
40-
// Toasts don't have a title so we merge everything into the message
41-
// const message = notification.roomType === 'pm' ?
42-
// `PM from ${notification.user}: ${notification.message}` :
43-
// `${notification.room} - ${notification.user}: ${notification.message}`;
44-
// toast(limitString(message, 150)); //TODO: Move to UI
38+
const title = notification.roomType === 'pm' ?
39+
`PM from ${notification.user}` :
40+
`${notification.room} - ${notification.user}`;
4541
toast({
46-
title: 'PM from ' + notification.user,
42+
title,
4743
description: limitString(notification.message, 150),
4844
});
4945
}

0 commit comments

Comments
 (0)