2023-10-14 11:39:24 +00:00
|
|
|
import type { Accessor, JSX } from 'solid-js'
|
2023-11-14 15:10:00 +00:00
|
|
|
|
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'
|
2023-11-14 15:10:00 +00:00
|
|
|
import { createStore } from 'solid-js/store'
|
2023-10-14 11:39:24 +00:00
|
|
|
import { Portal } from 'solid-js/web'
|
2023-11-14 15:10:00 +00:00
|
|
|
|
2023-10-14 11:39:24 +00:00
|
|
|
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-22 17:34:50 +00:00
|
|
|
import { NotificationGroup, 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
|
|
|
|
2023-10-14 11:39:24 +00:00
|
|
|
type NotificationsContextType = {
|
2023-12-22 17:34:50 +00:00
|
|
|
notificationEntities: Record<string, NotificationGroup>
|
2023-10-14 11:39:24 +00:00
|
|
|
unreadNotificationsCount: Accessor<number>
|
2023-12-19 09:34:24 +00:00
|
|
|
after: Accessor<number>
|
2023-12-22 17:34:50 +00:00
|
|
|
sortedNotifications: Accessor<NotificationGroup[]>
|
2023-11-01 15:13:54 +00:00
|
|
|
loadedNotificationsCount: Accessor<number>
|
|
|
|
totalNotificationsCount: Accessor<number>
|
2023-10-14 11:39:24 +00:00
|
|
|
actions: {
|
|
|
|
showNotificationsPanel: () => void
|
2023-10-16 17:24:33 +00:00
|
|
|
hideNotificationsPanel: () => void
|
2023-12-22 17:34:50 +00:00
|
|
|
markSeen: (notification_id: number) => Promise<void>
|
|
|
|
markSeenThread: (threadId: string) => Promise<void>
|
|
|
|
markSeenAll: () => Promise<void>
|
|
|
|
loadNotificationsGrouped: (options: QueryLoad_NotificationsArgs) => Promise<NotificationGroup[]>
|
2023-10-14 11:39:24 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-11-01 15:13:54 +00:00
|
|
|
export const PAGE_SIZE = 20
|
2023-10-14 11:39:24 +00:00
|
|
|
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)
|
2023-11-01 15:13:54 +00:00
|
|
|
const [totalNotificationsCount, setTotalNotificationsCount] = createSignal(0)
|
2023-12-22 17:34:50 +00:00
|
|
|
const [notificationEntities, setNotificationEntities] = createStore<Record<string, NotificationGroup>>({})
|
|
|
|
const { isAuthenticated } = useSession()
|
2023-11-28 13:18:25 +00:00
|
|
|
const { addHandler } = useConnect()
|
2023-12-15 13:45:34 +00:00
|
|
|
|
2023-12-22 17:34:50 +00:00
|
|
|
const loadNotificationsGrouped = async (options: { after: number; limit?: number; offset?: number }) => {
|
2023-12-18 00:17:58 +00:00
|
|
|
if (isAuthenticated() && notifierClient?.private) {
|
2024-01-19 18:46:13 +00:00
|
|
|
const { notifications: groups, total, unread } = await notifierClient.getNotifications(options)
|
2023-12-22 17:34:50 +00:00
|
|
|
const newGroupsEntries = groups.reduce((acc, group: NotificationGroup) => {
|
|
|
|
acc[group.id] = group
|
2023-12-15 13:45:34 +00:00
|
|
|
return acc
|
|
|
|
}, {})
|
|
|
|
|
|
|
|
setTotalNotificationsCount(total)
|
|
|
|
setUnreadNotificationsCount(unread)
|
2023-12-22 17:34:50 +00:00
|
|
|
setNotificationEntities(newGroupsEntries)
|
2023-12-24 21:29:25 +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 []
|
2023-10-14 11:39:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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-10-14 11:39:24 +00:00
|
|
|
})
|
|
|
|
|
2023-12-19 09:34:24 +00:00
|
|
|
const now = Math.floor(Date.now() / 1000)
|
2023-11-01 15:13:54 +00:00
|
|
|
const loadedNotificationsCount = createMemo(() => Object.keys(notificationEntities).length)
|
2023-12-19 09:34:24 +00:00
|
|
|
const [after, setAfter] = createStorageSignal('notifier_timestamp', now)
|
2023-12-22 17:34:50 +00:00
|
|
|
|
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-22 17:34:50 +00:00
|
|
|
loadNotificationsGrouped({ 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)
|
2023-10-14 11:39:24 +00:00
|
|
|
})
|
|
|
|
|
2023-12-22 17:34:50 +00:00
|
|
|
const markSeenThread = async (threadId: string) => {
|
|
|
|
if (notifierClient.private) await notifierClient.markSeenThread(threadId)
|
|
|
|
const thread = notificationEntities[threadId]
|
|
|
|
thread.seen = true
|
|
|
|
setNotificationEntities((nnn) => ({ ...nnn, [threadId]: thread }))
|
2023-11-15 17:52:05 +00:00
|
|
|
setUnreadNotificationsCount((oldCount) => oldCount - 1)
|
2023-11-01 15:13:54 +00:00
|
|
|
}
|
2023-12-20 16:54:20 +00:00
|
|
|
|
2023-12-22 17:34:50 +00:00
|
|
|
const markSeenAll = async () => {
|
|
|
|
if (isAuthenticated() && notifierClient.private) {
|
|
|
|
await notifierClient.markSeenAfter({ after: after() })
|
|
|
|
await loadNotificationsGrouped({ after: after(), limit: loadedNotificationsCount() })
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const markSeen = async (notification_id: number) => {
|
2023-12-18 00:17:58 +00:00
|
|
|
if (isAuthenticated() && notifierClient.private) {
|
2023-12-22 17:34:50 +00:00
|
|
|
await notifierClient.markSeen(notification_id)
|
|
|
|
await loadNotificationsGrouped({ after: after(), limit: loadedNotificationsCount() })
|
2023-12-15 13:45:34 +00:00
|
|
|
}
|
2023-10-14 11:39:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
const showNotificationsPanel = () => {
|
|
|
|
setIsNotificationsPanelOpen(true)
|
|
|
|
}
|
|
|
|
|
2023-10-16 17:24:33 +00:00
|
|
|
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,
|
|
|
|
loadNotificationsGrouped,
|
2023-10-16 18:00:22 +00:00
|
|
|
}
|
2023-10-14 11:39:24 +00:00
|
|
|
|
|
|
|
const value: NotificationsContextType = {
|
2023-12-19 09:34:24 +00:00
|
|
|
after,
|
2023-10-14 11:39:24 +00:00
|
|
|
notificationEntities,
|
|
|
|
sortedNotifications,
|
|
|
|
unreadNotificationsCount,
|
2023-11-01 15:13:54 +00:00
|
|
|
loadedNotificationsCount,
|
|
|
|
totalNotificationsCount,
|
2023-11-14 15:10:00 +00:00
|
|
|
actions,
|
2023-10-14 11:39:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
const handleNotificationPanelClose = () => {
|
|
|
|
setIsNotificationsPanelOpen(false)
|
|
|
|
}
|
|
|
|
|
|
|
|
return (
|
|
|
|
<NotificationsContext.Provider value={value}>
|
|
|
|
{props.children}
|
|
|
|
<ShowIfAuthenticated>
|
|
|
|
<Portal>
|
|
|
|
<NotificationsPanel isOpen={isNotificationsPanelOpen()} onClose={handleNotificationPanelClose} />
|
|
|
|
</Portal>
|
|
|
|
</ShowIfAuthenticated>
|
|
|
|
</NotificationsContext.Provider>
|
|
|
|
)
|
|
|
|
}
|