From 294b7da5da73d2092f100ceff718caf8a49ebc78 Mon Sep 17 00:00:00 2001 From: Untone Date: Thu, 19 Oct 2023 17:44:26 +0300 Subject: [PATCH] sse-msg-format --- .../NotificationView/NotificationView.tsx | 41 ++++++++----------- src/context/inbox.tsx | 17 +++++--- src/context/notifications.tsx | 40 +++++++++--------- 3 files changed, 49 insertions(+), 49 deletions(-) diff --git a/src/components/NotificationsPanel/NotificationView/NotificationView.tsx b/src/components/NotificationsPanel/NotificationView/NotificationView.tsx index e0e70cad..706cfc1b 100644 --- a/src/components/NotificationsPanel/NotificationView/NotificationView.tsx +++ b/src/components/NotificationsPanel/NotificationView/NotificationView.tsx @@ -3,7 +3,7 @@ import { createMemo, createSignal, onMount, Show } from 'solid-js' import { Author } from '../../../graphql/types.gen' import { openPage } from '@nanostores/router' import { router, useRouter } from '../../../stores/router' -import { ServerNotification, useNotifications } from '../../../context/notifications' +import { SSEMessage, useNotifications } from '../../../context/notifications' import { Userpic } from '../../Author/Userpic' import { useLocalize } from '../../../context/localize' import type { ArticlePageSearchParams } from '../../Article/FullArticle' @@ -11,7 +11,7 @@ import { TimeAgo } from '../../_shared/TimeAgo' import styles from './NotificationView.module.scss' type Props = { - notification: ServerNotification + notification: SSEMessage onClick: () => void dateTimeFormat: 'ago' | 'time' | 'date' class?: string @@ -29,8 +29,9 @@ export enum NotificationType { const TEMPLATES = { // FIXME: set proper templates - new_follower: 'new follower', - new_shout: 'new shout', + 'follower:join': 'new follower', + 'shout:create': 'new shout' + /* new_reaction0: 'new like', new_reaction1: 'new dislike', new_reaction2: 'new agreement', @@ -44,14 +45,15 @@ const TEMPLATES = { new_reaction10: 'new remark', //"new_reaction11": "new footnote", new_reaction12: 'new acception', - new_reaction13: 'new rejection' + new_reaction13: 'new rejection' + */ } export const NotificationView = (props: Props) => { const { actions: { markNotificationAsRead } } = useNotifications() - const [data, setData] = createSignal(null) + const [data, setData] = createSignal(null) const [kind, setKind] = createSignal() const { changeSearchParam } = useRouter() const { t, formatDate, formatTime } = useLocalize() @@ -60,7 +62,7 @@ export const NotificationView = (props: Props) => { setTimeout(() => setData(props.notification)) }) const lastUser = createMemo(() => { - return props.notification.kind === 'new_follower' ? data().payload : data().payload.author + return props.notification.entity === 'follower' ? data().payload : data().payload.author }) const content = createMemo(() => { if (!data()) { @@ -70,46 +72,39 @@ export const NotificationView = (props: Props) => { // TODO: count occurencies from in-browser notifications-db - switch (props.notification.kind) { - case 'new_follower': { + switch (props.notification.entity) { + case 'follower': { caption = '' author = data().payload ntype = NotificationType.NewFollower break } - case 'new_shout': { + case 'shout': { caption = data().payload.title author = data().payload.authors[-1] ntype = NotificationType.NewShout break } - case 'new_reaction6': { + case 'reaction': { ntype = data().payload.replyTo ? NotificationType.NewReply : NotificationType.NewComment + console.log(data().payload.kind) + // TODO: handle all needed reaction kinds } - case 'new_reaction0': { - ntype = NotificationType.NewLike - } - case 'new_reaction0': { - ntype = NotificationType.NewDislike - } - // TODO: add more reaction types default: { caption = data().payload.shout.title author = data().payload.author } } setKind(ntype) // FIXME: use it somewhere if needed or remove - return t(TEMPLATES[props.notification.kind], { caption, author }) + return t(TEMPLATES[`${props.notification.entity}:${props.notification.action}`], { caption, author }) }) const handleClick = () => { if (!props.notification.seen) { markNotificationAsRead(props.notification) } - const subpath = props.notification.kind === 'new_follower' ? 'author' : 'article' - const slug = props.notification.kind.startsWith('new_reaction') - ? data().payload.shout.slug - : data().payload.slug + const subpath = props.notification.entity === 'follower' ? 'author' : 'article' + const slug = props.notification.entity === 'reaction' ? data().payload.shout.slug : data().payload.slug openPage(router, subpath, { slug }) props.onClick() } diff --git a/src/context/inbox.tsx b/src/context/inbox.tsx index a09b4329..6b80eb92 100644 --- a/src/context/inbox.tsx +++ b/src/context/inbox.tsx @@ -3,7 +3,8 @@ import { createContext, createSignal, onMount, useContext } from 'solid-js' import type { Chat, Message, MutationCreateMessageArgs } from '../graphql/types.gen' import { inboxClient } from '../utils/apiClient' import { loadMessages } from '../stores/inbox' -import { ServerNotification, useNotifications } from './notifications' +import { SSEMessage, useNotifications } from './notifications' +import { M } from '../components/_shared/Button/Button.module.scss' type InboxContextType = { chats: Accessor @@ -29,10 +30,16 @@ export const InboxProvider = (props: { children: JSX.Element }) => { actions: { setMessageHandler } } = useNotifications() - const handleMessage = (n: ServerNotification) => { - // TODO: handle notification types here: new_message edit_message del_message - const msg = n.payload as Message - console.log(msg) + const handleMessage = (m: SSEMessage) => { + console.log('[context.inbox] ', m) + // TODO: handle all action types: create update delete join left + if (m.action in ['create', 'update', 'delete']) { + const msg = m.payload + setMessages((mmm) => [msg, ...mmm]) + } else if (m.action in ['left', 'join']) { + // TODO: set chat members + console.debug(m) + } } onMount(() => { diff --git a/src/context/notifications.tsx b/src/context/notifications.tsx index 3d54e59f..f7a19efe 100644 --- a/src/context/notifications.tsx +++ b/src/context/notifications.tsx @@ -10,22 +10,23 @@ import { fetchEventSource } from '@microsoft/fetch-event-source' import { getToken } from '../graphql/privateGraphQLClient' import { Author, Message, Reaction, Shout } from '../graphql/types.gen' -export interface ServerNotification { - kind: string +export interface SSEMessage { + entity: string + action: string payload: any // Author | Shout | Reaction | Message - timestamp: number - seen: boolean + timestamp?: number + seen?: boolean } type MessageHandler = (m: Message) => void type NotificationsContextType = { - notificationEntities: Record + notificationEntities: Record unreadNotificationsCount: Accessor - sortedNotifications: Accessor + sortedNotifications: Accessor actions: { showNotificationsPanel: () => void hideNotificationsPanel: () => void - markNotificationAsRead: (notification: ServerNotification) => Promise + markNotificationAsRead: (notification: SSEMessage) => Promise setMessageHandler: (h: MessageHandler) => void } } @@ -40,9 +41,7 @@ export const NotificationsProvider = (props: { children: JSX.Element }) => { const [isNotificationsPanelOpen, setIsNotificationsPanelOpen] = createSignal(false) const [unreadNotificationsCount, setUnreadNotificationsCount] = createSignal(0) const { isAuthenticated, user } = useSession() - const [notificationEntities, setNotificationEntities] = createStore>( - {} - ) + const [notificationEntities, setNotificationEntities] = createStore>({}) const [db, setDb] = createSignal>>() onMount(() => { const dbx = openDB('notifications-db', 1, { @@ -76,7 +75,7 @@ export const NotificationsProvider = (props: { children: JSX.Element }) => { return Object.values(notificationEntities).sort((a, b) => b.timestamp - a.timestamp) }) - const storeNotification = async (notification: ServerNotification) => { + const storeNotification = async (notification: SSEMessage) => { console.log('[context.notifications] Storing notification:', notification) const storage = await db() @@ -88,28 +87,27 @@ export const NotificationsProvider = (props: { children: JSX.Element }) => { loadNotifications() } - const [messageHandler, setMessageHandler] = createSignal<(m: Message) => void>() + const [messageHandler, setMessageHandler] = createSignal<(m: SSEMessage) => void>() createEffect(async () => { if (isAuthenticated()) { loadNotifications() - await fetchEventSource('https://chat.discours.io/connect', { + await fetchEventSource('https://chat.discours.io/', { method: 'GET', headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + getToken() }, onmessage(event) { - const n: { kind: string; payload: any } = JSON.parse(event.data) - if (n.kind === 'new_message') { - console.log('[context.notifications] Received message:', n) - messageHandler()(n.payload) + const m: SSEMessage = JSON.parse(event.data) + if (m.entity === 'chat') { + console.log('[context.notifications] Received message:', m) + messageHandler()(m) } else { - console.log('[context.notifications] Received notification:', n) + console.log('[context.notifications] Received notification:', m) storeNotification({ - kind: n.kind, - payload: n.payload, + ...m, timestamp: Date.now(), seen: false }) @@ -126,7 +124,7 @@ export const NotificationsProvider = (props: { children: JSX.Element }) => { } }) - const markNotificationAsRead = async (notification: ServerNotification) => { + const markNotificationAsRead = async (notification: SSEMessage) => { console.log('[context.notifications] Marking notification as read:', notification) const storage = await db()