webapp/src/context/notifications.tsx

137 lines
4.8 KiB
TypeScript
Raw Normal View History

import type { Accessor, JSX } from 'solid-js'
2023-12-19 09:34:24 +00:00
import { createStorageSignal } from '@solid-primitives/storage'
2023-12-20 16:54:20 +00:00
import { createContext, createMemo, createSignal, onMount, useContext } from 'solid-js'
import { createStore } from 'solid-js/store'
import { Portal } from 'solid-js/web'
import { ShowIfAuthenticated } from '../components/_shared/ShowIfAuthenticated'
import { NotificationsPanel } from '../components/NotificationsPanel'
2023-12-03 10:22:42 +00:00
import { notifierClient } from '../graphql/client/notifier'
2023-12-19 09:34:24 +00:00
import { Notification, QueryLoad_NotificationsArgs } from '../graphql/schema/notifier.gen'
2023-11-28 15:36:00 +00:00
2023-11-28 13:18:25 +00:00
import { SSEMessage, useConnect } from './connect'
2023-12-13 23:56:44 +00:00
import { useSession } from './session'
2023-10-20 18:07:33 +00:00
type NotificationsContextType = {
2023-11-17 16:22:54 +00:00
notificationEntities: Record<number, Notification>
unreadNotificationsCount: Accessor<number>
2023-12-19 09:34:24 +00:00
after: Accessor<number>
sortedNotifications: Accessor<Notification[]>
loadedNotificationsCount: Accessor<number>
totalNotificationsCount: Accessor<number>
actions: {
showNotificationsPanel: () => void
hideNotificationsPanel: () => void
markNotificationAsRead: (notification: Notification) => Promise<void>
markAllNotificationsAsRead: () => Promise<void>
2023-12-19 09:34:24 +00:00
loadNotifications: (options: QueryLoad_NotificationsArgs) => Promise<Notification[]>
}
}
export const PAGE_SIZE = 20
const NotificationsContext = createContext<NotificationsContextType>()
export function useNotifications() {
return useContext(NotificationsContext)
}
export const NotificationsProvider = (props: { children: JSX.Element }) => {
const [isNotificationsPanelOpen, setIsNotificationsPanelOpen] = createSignal(false)
const [unreadNotificationsCount, setUnreadNotificationsCount] = createSignal(0)
const [totalNotificationsCount, setTotalNotificationsCount] = createSignal(0)
2023-11-17 16:22:54 +00:00
const [notificationEntities, setNotificationEntities] = createStore<Record<number, Notification>>({})
2023-12-20 16:54:20 +00:00
const { isAuthenticated, author } = useSession()
2023-11-28 13:18:25 +00:00
const { addHandler } = useConnect()
2023-12-15 13:45:34 +00:00
2023-12-19 09:34:24 +00:00
const loadNotifications = async (options: { after: number; limit?: number; offset?: number }) => {
2023-12-18 00:17:58 +00:00
if (isAuthenticated() && notifierClient?.private) {
const { notifications, unread, total } = await notifierClient.getNotifications(options)
2023-12-15 13:45:34 +00:00
const newNotificationEntities = notifications.reduce((acc, notification) => {
acc[notification.id] = notification
return acc
}, {})
setTotalNotificationsCount(total)
setUnreadNotificationsCount(unread)
setNotificationEntities(newNotificationEntities)
console.debug(`[context.notifications] updated`)
return notifications
} else {
return []
}
}
const sortedNotifications = createMemo(() => {
2023-11-28 13:18:25 +00:00
return Object.values(notificationEntities).sort((a, b) => b.created_at - a.created_at)
})
2023-12-19 09:34:24 +00:00
const now = Math.floor(Date.now() / 1000)
const loadedNotificationsCount = createMemo(() => Object.keys(notificationEntities).length)
2023-12-19 09:34:24 +00:00
const [after, setAfter] = createStorageSignal('notifier_timestamp', now)
2023-11-28 13:18:25 +00:00
onMount(() => {
addHandler((data: SSEMessage) => {
2023-12-15 13:45:34 +00:00
if (data.entity === 'reaction' && isAuthenticated()) {
2023-12-20 07:45:29 +00:00
console.info(`[context.notifications] event`, data)
2023-12-19 09:34:24 +00:00
loadNotifications({ after: after(), limit: Math.max(PAGE_SIZE, loadedNotificationsCount()) })
2023-11-28 13:18:25 +00:00
}
})
2023-12-19 09:34:24 +00:00
setAfter(now)
})
const markNotificationAsRead = async (notification: Notification) => {
2023-12-18 00:17:58 +00:00
if (notifierClient.private) await notifierClient.markNotificationAsRead(notification.id)
2023-12-20 16:54:20 +00:00
notification.seen.push(author().id)
setNotificationEntities((nnn: Notification) => ({ ...nnn, [notification.id]: notification }))
setUnreadNotificationsCount((oldCount) => oldCount - 1)
}
2023-12-20 16:54:20 +00:00
const markAllNotificationsAsRead = async () => {
2023-12-18 00:17:58 +00:00
if (isAuthenticated() && notifierClient.private) {
await notifierClient.markAllNotificationsAsRead()
2023-12-19 09:34:24 +00:00
await loadNotifications({ after: after(), limit: loadedNotificationsCount() })
2023-12-15 13:45:34 +00:00
}
}
const showNotificationsPanel = () => {
setIsNotificationsPanelOpen(true)
}
const hideNotificationsPanel = () => {
setIsNotificationsPanelOpen(false)
}
2023-10-16 18:00:22 +00:00
const actions = {
showNotificationsPanel,
hideNotificationsPanel,
markNotificationAsRead,
markAllNotificationsAsRead,
loadNotifications,
2023-10-16 18:00:22 +00:00
}
const value: NotificationsContextType = {
2023-12-19 09:34:24 +00:00
after,
notificationEntities,
sortedNotifications,
unreadNotificationsCount,
loadedNotificationsCount,
totalNotificationsCount,
actions,
}
const handleNotificationPanelClose = () => {
setIsNotificationsPanelOpen(false)
}
return (
<NotificationsContext.Provider value={value}>
{props.children}
<ShowIfAuthenticated>
<Portal>
<NotificationsPanel isOpen={isNotificationsPanelOpen()} onClose={handleNotificationPanelClose} />
</Portal>
</ShowIfAuthenticated>
</NotificationsContext.Provider>
)
}