add activityCenter.ts
This commit is contained in:
parent
81f2950c19
commit
45cd81eaab
|
@ -0,0 +1,125 @@
|
||||||
|
// todo: rename to notifications (center?), inbox, or keep same as other platforms
|
||||||
|
import type { ChatMessage } from './chat'
|
||||||
|
// import type { Client } from './client'
|
||||||
|
|
||||||
|
// todo?: union
|
||||||
|
// todo?: rename to Activity
|
||||||
|
type Notification = {
|
||||||
|
// fixme?: specify message type (message_reply)
|
||||||
|
type: 'message'
|
||||||
|
value: ChatMessage
|
||||||
|
}
|
||||||
|
|
||||||
|
type ActivityCenterLatest = {
|
||||||
|
notifications: Notification[]
|
||||||
|
// todo?: rename count to mentionsAndRepliesCount
|
||||||
|
unreadChats: Map<string, { count: number }> // id, count (mentions, replies)
|
||||||
|
}
|
||||||
|
|
||||||
|
// todo?: rename to NotificationCenter
|
||||||
|
export class ActivityCenter {
|
||||||
|
// todo?: use client.account for mentions and replies, or in chat.ts
|
||||||
|
// #client: Client
|
||||||
|
|
||||||
|
#notifications: Set<Notification>
|
||||||
|
#callbacks: Set<(latest: ActivityCenterLatest) => void>
|
||||||
|
|
||||||
|
constructor(/* client: Client */) {
|
||||||
|
// this.#client = client
|
||||||
|
|
||||||
|
this.#notifications = new Set()
|
||||||
|
this.#callbacks = new Set()
|
||||||
|
}
|
||||||
|
|
||||||
|
// todo?: rename to latest, change
|
||||||
|
public getLatest = (): ActivityCenterLatest => {
|
||||||
|
const notifications: Notification[] = []
|
||||||
|
const unreadChats: Map<string, { count: number }> = new Map()
|
||||||
|
|
||||||
|
for (const notification of this.#notifications.values()) {
|
||||||
|
// todo?: switch
|
||||||
|
if (notification.type === 'message') {
|
||||||
|
const chatUuid = notification.value.chatUuid
|
||||||
|
|
||||||
|
const chat = unreadChats.get(chatUuid)
|
||||||
|
if (chat) {
|
||||||
|
// fixme!: isReply || isMention
|
||||||
|
const shouldIncrement = false
|
||||||
|
if (shouldIncrement) {
|
||||||
|
chat.count++
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
unreadChats.set(chatUuid, { count: 0 })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
notifications.push(notification)
|
||||||
|
}
|
||||||
|
|
||||||
|
// todo?: reverse order
|
||||||
|
notifications.sort((a, b) => {
|
||||||
|
if (a.value.clock < b.value.clock) {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
if (a.value.clock > b.value.clock) {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0
|
||||||
|
})
|
||||||
|
|
||||||
|
// fixme!?: do not display regular messages, only mentions and replies
|
||||||
|
// todo?: group notifications (all, unreads, mentions, replies, _chats.{id,count})
|
||||||
|
return { notifications, unreadChats }
|
||||||
|
}
|
||||||
|
|
||||||
|
public addMessageNotification = (value: ChatMessage) => {
|
||||||
|
this.#notifications.add({ type: 'message', value })
|
||||||
|
|
||||||
|
this.emitLatest()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes all notifications.
|
||||||
|
*/
|
||||||
|
removeNotifications = () => {
|
||||||
|
this.#notifications.clear()
|
||||||
|
|
||||||
|
this.emitLatest()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes chat message notifications from the Activity Center. For example,
|
||||||
|
* on only opening or after scrolling to the end.
|
||||||
|
*/
|
||||||
|
public removeChatNotifications = (chatUuid: string) => {
|
||||||
|
// todo?: add chatUuid to "readChats" Set instead and resolve in getNotifications
|
||||||
|
// triggered by following emit, and clear the set afterwards
|
||||||
|
for (const notification of this.#notifications) {
|
||||||
|
if (notification.type !== 'message') {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if (notification.value.chatUuid === chatUuid) {
|
||||||
|
this.#notifications.delete(notification)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.emitLatest()
|
||||||
|
}
|
||||||
|
|
||||||
|
private emitLatest = () => {
|
||||||
|
const latest = this.getLatest()
|
||||||
|
|
||||||
|
this.#callbacks.forEach(callback => callback(latest))
|
||||||
|
}
|
||||||
|
|
||||||
|
public onChange = (callback: (latest: ActivityCenterLatest) => void) => {
|
||||||
|
this.#callbacks.add(callback)
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
this.#callbacks.delete(callback)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -35,7 +35,9 @@ export type ChatMessage = ChatMessageProto & {
|
||||||
|
|
||||||
type FetchedMessage = { messageId: string; timestamp?: Date }
|
type FetchedMessage = { messageId: string; timestamp?: Date }
|
||||||
|
|
||||||
|
// todo?: add isMuted prop, use as condition to add a message/notification to activity center or not
|
||||||
export class Chat {
|
export class Chat {
|
||||||
|
// todo: use #
|
||||||
private readonly client: Client
|
private readonly client: Client
|
||||||
#clock: bigint
|
#clock: bigint
|
||||||
|
|
||||||
|
@ -51,6 +53,7 @@ export class Chat {
|
||||||
#pinEvents: Map<string, Pick<ChatMessage, 'clock' | 'pinned'>>
|
#pinEvents: Map<string, Pick<ChatMessage, 'clock' | 'pinned'>>
|
||||||
#reactEvents: Map<string, Pick<ChatMessage, 'clock' | 'reactions'>>
|
#reactEvents: Map<string, Pick<ChatMessage, 'clock' | 'reactions'>>
|
||||||
#deleteEvents: Map<string, Pick<ChatMessage, 'clock' | 'signer'>>
|
#deleteEvents: Map<string, Pick<ChatMessage, 'clock' | 'signer'>>
|
||||||
|
#isActive: boolean
|
||||||
#fetchingMessages?: boolean
|
#fetchingMessages?: boolean
|
||||||
#previousFetchedStartTime?: Date
|
#previousFetchedStartTime?: Date
|
||||||
#oldestFetchedMessage?: FetchedMessage
|
#oldestFetchedMessage?: FetchedMessage
|
||||||
|
@ -81,6 +84,7 @@ export class Chat {
|
||||||
this.#pinEvents = new Map()
|
this.#pinEvents = new Map()
|
||||||
this.#reactEvents = new Map()
|
this.#reactEvents = new Map()
|
||||||
this.#deleteEvents = new Map()
|
this.#deleteEvents = new Map()
|
||||||
|
this.#isActive = false
|
||||||
this.messageCallbacks = new Set()
|
this.messageCallbacks = new Set()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -142,6 +146,7 @@ export class Chat {
|
||||||
return this.#messages.get(id)
|
return this.#messages.get(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// todo?: delete
|
||||||
public onChange = (callback: (description: CommunityChat) => void) => {
|
public onChange = (callback: (description: CommunityChat) => void) => {
|
||||||
this.chatCallbacks.add(callback)
|
this.chatCallbacks.add(callback)
|
||||||
|
|
||||||
|
@ -158,9 +163,16 @@ export class Chat {
|
||||||
callback: (messages: ChatMessage[]) => void
|
callback: (messages: ChatMessage[]) => void
|
||||||
): (() => void) => {
|
): (() => void) => {
|
||||||
this.messageCallbacks.add(callback)
|
this.messageCallbacks.add(callback)
|
||||||
|
// todo?: set from ui, think use case without an ui
|
||||||
|
this.#isActive = true
|
||||||
|
// todo?!: only if in `unreadChats`, keep "unreads" separate from `notifications`
|
||||||
|
// todo?: only if at the bottom and all unread messages are in view
|
||||||
|
// todo?: call from ui
|
||||||
|
this.client.activityCenter.removeChatNotifications(this.uuid)
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
this.messageCallbacks.delete(callback)
|
this.messageCallbacks.delete(callback)
|
||||||
|
this.#isActive = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -305,6 +317,11 @@ export class Chat {
|
||||||
|
|
||||||
// callback
|
// callback
|
||||||
this.emitMessages()
|
this.emitMessages()
|
||||||
|
|
||||||
|
// todo?: if not muted
|
||||||
|
if (!this.#isActive) {
|
||||||
|
this.client.activityCenter.addMessageNotifications(newMessage)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public handleEditedMessage = (
|
public handleEditedMessage = (
|
||||||
|
|
|
@ -12,6 +12,7 @@ import {
|
||||||
|
|
||||||
import { ApplicationMetadataMessage } from '../protos/application-metadata-message'
|
import { ApplicationMetadataMessage } from '../protos/application-metadata-message'
|
||||||
import { Account } from './account'
|
import { Account } from './account'
|
||||||
|
import { ActivityCenter } from './activityCenter'
|
||||||
import { Community } from './community/community'
|
import { Community } from './community/community'
|
||||||
import { handleWakuMessage } from './community/handle-waku-message'
|
import { handleWakuMessage } from './community/handle-waku-message'
|
||||||
|
|
||||||
|
@ -37,6 +38,7 @@ class Client {
|
||||||
*/
|
*/
|
||||||
#wakuDisconnectionTimer: ReturnType<typeof setInterval>
|
#wakuDisconnectionTimer: ReturnType<typeof setInterval>
|
||||||
|
|
||||||
|
public activityCenter: ActivityCenter
|
||||||
public account?: Account
|
public account?: Account
|
||||||
public community: Community
|
public community: Community
|
||||||
|
|
||||||
|
@ -50,6 +52,9 @@ class Client {
|
||||||
this.wakuMessages = new Set()
|
this.wakuMessages = new Set()
|
||||||
this.#wakuDisconnectionTimer = wakuDisconnectionTimer
|
this.#wakuDisconnectionTimer = wakuDisconnectionTimer
|
||||||
|
|
||||||
|
// Activity Center
|
||||||
|
this.activityCenter = new ActivityCenter(/* this */)
|
||||||
|
|
||||||
// Community
|
// Community
|
||||||
this.community = new Community(this, options.publicKey)
|
this.community = new Community(this, options.publicKey)
|
||||||
}
|
}
|
||||||
|
|
|
@ -267,6 +267,9 @@ export class Community {
|
||||||
this.contentTopic,
|
this.contentTopic,
|
||||||
this.symmetricKey
|
this.symmetricKey
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// todo?:
|
||||||
|
// this.client.activityCenter.addJoiningRequestNotification(...)
|
||||||
}
|
}
|
||||||
|
|
||||||
public isOwner = (
|
public isOwner = (
|
||||||
|
|
Loading…
Reference in New Issue