webapp/src/context/notifications.tsx

164 lines
5.8 KiB
TypeScript
Raw Normal View History

2024-06-24 17:50:27 +00:00
import { makePersisted } from '@solid-primitives/storage'
import type { Accessor, JSX } from 'solid-js'
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'
2024-06-24 17:50:27 +00:00
import markSeenMutation from '~/graphql/mutation/notifier/mark-seen'
import markSeenAfterMutation from '~/graphql/mutation/notifier/mark-seen-after'
import markSeenThreadMutation from '~/graphql/mutation/notifier/mark-seen-thread'
import getNotifications from '~/graphql/query/notifier/notifications-load'
import { NotificationGroup, QueryLoad_NotificationsArgs } from '~/graphql/schema/core.gen'
import { NotificationsPanel } from '../components/NotificationsPanel'
2024-02-04 11:25:21 +00:00
import { ShowIfAuthenticated } from '../components/_shared/ShowIfAuthenticated'
2023-11-28 13:18:25 +00:00
import { SSEMessage, useConnect } from './connect'
2024-06-24 17:50:27 +00:00
import { useGraphQL } from './graphql'
2023-12-13 23:56:44 +00:00
import { useSession } from './session'
2023-10-20 18:07:33 +00:00
type NotificationsContextType = {
2023-12-22 17:34:50 +00:00
notificationEntities: Record<string, NotificationGroup>
unreadNotificationsCount: Accessor<number>
2024-06-24 17:50:27 +00:00
after: Accessor<number | null>
2023-12-22 17:34:50 +00:00
sortedNotifications: Accessor<NotificationGroup[]>
loadedNotificationsCount: Accessor<number>
totalNotificationsCount: Accessor<number>
2024-02-04 17:40:15 +00:00
showNotificationsPanel: () => void
hideNotificationsPanel: () => void
markSeen: (notification_id: number) => Promise<void>
markSeenThread: (threadId: string) => Promise<void>
markSeenAll: () => Promise<void>
loadNotificationsGrouped: (options: QueryLoad_NotificationsArgs) => Promise<NotificationGroup[]>
}
export const PAGE_SIZE = 20
2024-06-24 17:50:27 +00:00
const NotificationsContext = createContext<NotificationsContextType>({
showNotificationsPanel: () => undefined,
2024-06-26 08:22:05 +00:00
hideNotificationsPanel: () => undefined
2024-06-24 17:50:27 +00:00
} as 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-12-22 17:34:50 +00:00
const [notificationEntities, setNotificationEntities] = createStore<Record<string, NotificationGroup>>({})
2024-06-24 17:50:27 +00:00
const { session } = useSession()
const authorized = createMemo<boolean>(() => Boolean(session()?.access_token))
2023-11-28 13:18:25 +00:00
const { addHandler } = useConnect()
2024-06-24 17:50:27 +00:00
const { query, mutation } = useGraphQL()
const loadNotificationsGrouped = async (options: QueryLoad_NotificationsArgs) => {
if (authorized()) {
const resp = await query(getNotifications, options).toPromise()
const result = resp?.data?.get_notifications
const groups = result?.notifications || []
const total = result?.total || 0
const unread = result?.unread || 0
const newGroupsEntries = groups.reduce(
(acc: { [x: string]: NotificationGroup }, group: NotificationGroup) => {
acc[group.thread] = group
return acc
},
2024-06-26 08:22:05 +00:00
{}
2024-06-24 17:50:27 +00:00
)
2023-12-15 13:45:34 +00:00
setTotalNotificationsCount(total)
setUnreadNotificationsCount(unread)
2023-12-22 17:34:50 +00:00
setNotificationEntities(newGroupsEntries)
2024-02-05 15:04:23 +00:00
console.debug('[context.notifications] groups updated', groups)
2023-12-22 17:34:50 +00:00
return groups
2023-12-15 13:45:34 +00:00
}
2024-01-19 18:46:13 +00:00
return []
}
const sortedNotifications = createMemo(() => {
2023-12-22 17:34:50 +00:00
return Object.values(notificationEntities).sort((a, b) => b.updated_at - a.updated_at)
})
2023-12-19 09:34:24 +00:00
const now = Math.floor(Date.now() / 1000)
const loadedNotificationsCount = createMemo(() => Object.keys(notificationEntities).length)
2024-06-24 17:50:27 +00:00
const [after, setAfter] = makePersisted(createSignal<number>(now), { name: 'notifier_timestamp' })
2023-12-22 17:34:50 +00:00
2023-11-28 13:18:25 +00:00
onMount(() => {
addHandler((data: SSEMessage) => {
2024-06-24 17:50:27 +00:00
if (data.entity === 'reaction' && authorized()) {
2024-02-05 15:04:23 +00:00
console.info('[context.notifications] event', data)
2024-06-24 17:50:27 +00:00
loadNotificationsGrouped({
after: after() || Date.now(),
2024-06-26 08:22:05 +00:00
limit: Math.max(PAGE_SIZE, loadedNotificationsCount())
2024-06-24 17:50:27 +00:00
})
2023-11-28 13:18:25 +00:00
}
})
2023-12-19 09:34:24 +00:00
setAfter(now)
})
2023-12-22 17:34:50 +00:00
const markSeenThread = async (threadId: string) => {
2024-06-24 17:50:27 +00:00
await mutation(markSeenThreadMutation, { threadId }).toPromise()
2023-12-22 17:34:50 +00:00
const thread = notificationEntities[threadId]
thread.seen = true
setNotificationEntities((nnn) => ({ ...nnn, [threadId]: thread }))
setUnreadNotificationsCount((oldCount) => oldCount - 1)
}
2023-12-20 16:54:20 +00:00
2023-12-22 17:34:50 +00:00
const markSeenAll = async () => {
2024-06-24 17:50:27 +00:00
if (authorized()) {
const _resp = await mutation(markSeenAfterMutation, { after: after() }).toPromise()
await loadNotificationsGrouped({ after: after() || Date.now(), limit: loadedNotificationsCount() })
2023-12-22 17:34:50 +00:00
}
}
const markSeen = async (notification_id: number) => {
2024-06-24 17:50:27 +00:00
if (authorized()) {
await mutation(markSeenMutation, { notification_id }).toPromise()
await loadNotificationsGrouped({ after: after() || Date.now(), 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,
2023-12-22 17:34:50 +00:00
markSeenThread,
markSeenAll,
markSeen,
2024-06-26 08:22:05 +00:00
loadNotificationsGrouped
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,
2024-06-26 08:22:05 +00:00
...actions
}
const handleNotificationPanelClose = () => {
setIsNotificationsPanelOpen(false)
}
return (
<NotificationsContext.Provider value={value}>
{props.children}
<ShowIfAuthenticated>
<Portal>
<NotificationsPanel isOpen={isNotificationsPanelOpen()} onClose={handleNotificationPanelClose} />
</Portal>
</ShowIfAuthenticated>
</NotificationsContext.Provider>
)
}