Update handling of reactions (#277)
* fix: reactions argument in callback * replace object with Set in reactions * update reactions in UI
This commit is contained in:
parent
562d42b09e
commit
a666d93844
|
@ -104,7 +104,7 @@ class Client {
|
||||||
}
|
}
|
||||||
|
|
||||||
public handleWakuMessage = (wakuMessage: WakuMessage): void => {
|
public handleWakuMessage = (wakuMessage: WakuMessage): void => {
|
||||||
handleWakuMessage(wakuMessage, this, this.community, this.account)
|
handleWakuMessage(wakuMessage, this, this.community)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -277,7 +277,7 @@ export class Chat {
|
||||||
public handleEmojiReaction = (
|
public handleEmojiReaction = (
|
||||||
messageId: string,
|
messageId: string,
|
||||||
reaction: EmojiReaction,
|
reaction: EmojiReaction,
|
||||||
isMe: boolean
|
publicKey: string
|
||||||
) => {
|
) => {
|
||||||
let messageIndex = this.messages.length
|
let messageIndex = this.messages.length
|
||||||
while (--messageIndex >= 0) {
|
while (--messageIndex >= 0) {
|
||||||
|
@ -295,7 +295,7 @@ export class Chat {
|
||||||
this.messages[messageIndex].reactions = getReactions(
|
this.messages[messageIndex].reactions = getReactions(
|
||||||
reaction,
|
reaction,
|
||||||
this.messages[messageIndex].reactions,
|
this.messages[messageIndex].reactions,
|
||||||
isMe
|
publicKey
|
||||||
)
|
)
|
||||||
|
|
||||||
this.emitMessages(this.messages)
|
this.emitMessages(this.messages)
|
||||||
|
@ -379,16 +379,30 @@ export class Chat {
|
||||||
public sendReaction = async (
|
public sendReaction = async (
|
||||||
chatId: string,
|
chatId: string,
|
||||||
messageId: string,
|
messageId: string,
|
||||||
reaction: EmojiReaction.Type
|
reaction: keyof ChatMessage['reactions']
|
||||||
) => {
|
) => {
|
||||||
|
if (!this.client.account) {
|
||||||
|
throw new Error('Account not initialized')
|
||||||
|
}
|
||||||
|
|
||||||
|
const message = this.getMessage(messageId)
|
||||||
|
|
||||||
|
if (!message) {
|
||||||
|
throw new Error('Message not found')
|
||||||
|
}
|
||||||
|
|
||||||
|
const retracted = message.reactions[reaction].has(
|
||||||
|
this.client.account.publicKey
|
||||||
|
)
|
||||||
|
|
||||||
const payload = EmojiReaction.encode({
|
const payload = EmojiReaction.encode({
|
||||||
clock: BigInt(Date.now()),
|
clock: BigInt(Date.now()),
|
||||||
chatId: chatId,
|
chatId: chatId,
|
||||||
messageType: 'COMMUNITY_CHAT',
|
messageType: 'COMMUNITY_CHAT' as MessageType,
|
||||||
grant: new Uint8Array([]),
|
|
||||||
messageId,
|
messageId,
|
||||||
retracted: false,
|
|
||||||
type: reaction,
|
type: reaction,
|
||||||
|
retracted,
|
||||||
|
grant: new Uint8Array([]),
|
||||||
})
|
})
|
||||||
|
|
||||||
await this.client.sendWakuMessage(
|
await this.client.sendWakuMessage(
|
||||||
|
|
|
@ -3,40 +3,25 @@ import type { EmojiReaction } from '../../../protos/emoji-reaction'
|
||||||
type Reaction = Exclude<`${EmojiReaction.Type}`, 'UNKNOWN_EMOJI_REACTION_TYPE'>
|
type Reaction = Exclude<`${EmojiReaction.Type}`, 'UNKNOWN_EMOJI_REACTION_TYPE'>
|
||||||
|
|
||||||
export type Reactions = {
|
export type Reactions = {
|
||||||
[key in Reaction]: {
|
[key in Reaction]: Set<string>
|
||||||
count: number
|
|
||||||
me: boolean
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getReactions(
|
export function getReactions(
|
||||||
reaction: EmojiReaction,
|
reaction: EmojiReaction,
|
||||||
reactions: Reactions,
|
reactions: Reactions,
|
||||||
isMe: boolean
|
publicKey: string
|
||||||
) {
|
): Reactions {
|
||||||
const { type, retracted } = reaction
|
const { type, retracted } = reaction
|
||||||
|
|
||||||
if (type === 'UNKNOWN_EMOJI_REACTION_TYPE') {
|
if (type === 'UNKNOWN_EMOJI_REACTION_TYPE') {
|
||||||
return reactions
|
return reactions
|
||||||
}
|
}
|
||||||
|
|
||||||
const _reaction = {
|
if (retracted) {
|
||||||
count: reactions[type].count,
|
reactions[type].delete(publicKey)
|
||||||
me: reactions[type].me,
|
|
||||||
}
|
|
||||||
|
|
||||||
if (retracted && _reaction.count !== 0) {
|
|
||||||
_reaction.count -= 1
|
|
||||||
} else {
|
} else {
|
||||||
_reaction.count += 1
|
reactions[type].add(publicKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isMe) {
|
return reactions
|
||||||
_reaction.me = retracted ? false : true
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
...reactions,
|
|
||||||
[type]: _reaction,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,6 @@ import { recoverPublicKey } from '../../utils/recover-public-key'
|
||||||
import { getChatUuid } from './get-chat-uuid'
|
import { getChatUuid } from './get-chat-uuid'
|
||||||
import { mapChatMessage } from './map-chat-message'
|
import { mapChatMessage } from './map-chat-message'
|
||||||
|
|
||||||
import type { Account } from '../../account'
|
|
||||||
import type { Client } from '../../client'
|
import type { Client } from '../../client'
|
||||||
import type { Community } from './community'
|
import type { Community } from './community'
|
||||||
import type { WakuMessage } from 'js-waku'
|
import type { WakuMessage } from 'js-waku'
|
||||||
|
@ -24,8 +23,7 @@ export function handleWakuMessage(
|
||||||
wakuMessage: WakuMessage,
|
wakuMessage: WakuMessage,
|
||||||
// state
|
// state
|
||||||
client: Client,
|
client: Client,
|
||||||
community: Community,
|
community: Community
|
||||||
account?: Account
|
|
||||||
): void {
|
): void {
|
||||||
// decode (layers)
|
// decode (layers)
|
||||||
if (!wakuMessage.payload) {
|
if (!wakuMessage.payload) {
|
||||||
|
@ -151,11 +149,14 @@ export function handleWakuMessage(
|
||||||
|
|
||||||
const messageId = decodedPayload.messageId
|
const messageId = decodedPayload.messageId
|
||||||
const chatUuid = getChatUuid(decodedPayload.chatId)
|
const chatUuid = getChatUuid(decodedPayload.chatId)
|
||||||
const isMe = account?.publicKey === `0x${bytesToHex(publicKey)}`
|
|
||||||
|
|
||||||
community.chats
|
const chat = community.chats.get(chatUuid)
|
||||||
.get(chatUuid)
|
|
||||||
?.handleEmojiReaction(messageId, decodedPayload, isMe)
|
chat?.handleEmojiReaction(
|
||||||
|
messageId,
|
||||||
|
decodedPayload,
|
||||||
|
`0x${bytesToHex(publicKey)}`
|
||||||
|
)
|
||||||
|
|
||||||
success = true
|
success = true
|
||||||
|
|
||||||
|
|
|
@ -6,40 +6,24 @@ export function mapChatMessage(
|
||||||
props: {
|
props: {
|
||||||
messageId: string
|
messageId: string
|
||||||
chatUuid: string
|
chatUuid: string
|
||||||
|
publicKey: string
|
||||||
}
|
}
|
||||||
): ChatMessage {
|
): ChatMessage {
|
||||||
const { messageId, chatUuid } = props
|
const { messageId, chatUuid, publicKey } = props
|
||||||
|
|
||||||
const message = {
|
const message = {
|
||||||
...decodedMessage,
|
...decodedMessage,
|
||||||
messageId,
|
messageId,
|
||||||
chatUuid,
|
chatUuid,
|
||||||
pinned: false,
|
pinned: false,
|
||||||
|
signer: publicKey,
|
||||||
reactions: {
|
reactions: {
|
||||||
THUMBS_UP: {
|
THUMBS_UP: new Set<string>(),
|
||||||
count: 0,
|
THUMBS_DOWN: new Set<string>(),
|
||||||
me: false,
|
LOVE: new Set<string>(),
|
||||||
},
|
LAUGH: new Set<string>(),
|
||||||
THUMBS_DOWN: {
|
SAD: new Set<string>(),
|
||||||
count: 0,
|
ANGRY: new Set<string>(),
|
||||||
me: false,
|
|
||||||
},
|
|
||||||
LOVE: {
|
|
||||||
count: 0,
|
|
||||||
me: false,
|
|
||||||
},
|
|
||||||
LAUGH: {
|
|
||||||
count: 0,
|
|
||||||
me: false,
|
|
||||||
},
|
|
||||||
SAD: {
|
|
||||||
count: 0,
|
|
||||||
me: false,
|
|
||||||
},
|
|
||||||
ANGRY: {
|
|
||||||
count: 0,
|
|
||||||
me: false,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
|
||||||
|
import { useAccount } from '~/src/protocol'
|
||||||
import { styled } from '~/src/styles/config'
|
import { styled } from '~/src/styles/config'
|
||||||
import { Flex, Image, Popover, PopoverTrigger } from '~/src/system'
|
import { Flex, Image, Popover, PopoverTrigger } from '~/src/system'
|
||||||
|
|
||||||
|
@ -43,18 +44,22 @@ export const emojis: Record<Reaction, { url: string; symbol: string }> = {
|
||||||
export const ReactionPopover = (props: Props) => {
|
export const ReactionPopover = (props: Props) => {
|
||||||
const { reactions, children, onClick, ...popoverProps } = props
|
const { reactions, children, onClick, ...popoverProps } = props
|
||||||
|
|
||||||
|
const { account } = useAccount()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PopoverTrigger {...popoverProps}>
|
<PopoverTrigger {...popoverProps}>
|
||||||
{children}
|
{children}
|
||||||
<Popover side="top" align="center" sideOffset={6}>
|
<Popover side="top" align="center" sideOffset={6}>
|
||||||
<Flex gap={1} css={{ padding: 8 }}>
|
<Flex gap={1} css={{ padding: 8 }}>
|
||||||
{Object.entries(emojis).map(([type, emoji]) => {
|
{Object.entries(emojis).map(([type, emoji]) => {
|
||||||
const reaction = reactions[type]
|
const value = reactions[type]
|
||||||
|
const me = account ? value.has('0x' + account.publicKey) : false
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
key={type}
|
key={type}
|
||||||
onClick={() => onClick(reaction as Reaction)}
|
onClick={() => onClick(type)}
|
||||||
active={reaction.me}
|
active={me}
|
||||||
aria-label={`React with ${emoji.symbol}`}
|
aria-label={`React with ${emoji.symbol}`}
|
||||||
>
|
>
|
||||||
<Image width={30} src={emoji.url} alt={emoji.symbol} />
|
<Image width={30} src={emoji.url} alt={emoji.symbol} />
|
||||||
|
|
|
@ -84,7 +84,9 @@ export const ChatMessage = (props: Props) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleReaction = (reaction: Reaction) => {
|
const handleReaction = (reaction: Reaction) => {
|
||||||
client.community.chats.get(chatId).sendReaction(chatId, messageId, reaction)
|
client.community
|
||||||
|
.getChatById(chatId)
|
||||||
|
.sendReaction(chatId, messageId, reaction)
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleReplyClick = () => {
|
const handleReplyClick = () => {
|
||||||
|
|
|
@ -2,7 +2,7 @@ import React from 'react'
|
||||||
|
|
||||||
import { emojis, ReactionPopover } from '~/src/components/reaction-popover'
|
import { emojis, ReactionPopover } from '~/src/components/reaction-popover'
|
||||||
import { ReactionIcon } from '~/src/icons/reaction-icon'
|
import { ReactionIcon } from '~/src/icons/reaction-icon'
|
||||||
import { Reaction } from '~/src/protocol/use-messages'
|
import { useAccount } from '~/src/protocol'
|
||||||
import { styled } from '~/src/styles/config'
|
import { styled } from '~/src/styles/config'
|
||||||
import { Flex, Image, Text } from '~/src/system'
|
import { Flex, Image, Text } from '~/src/system'
|
||||||
|
|
||||||
|
@ -16,9 +16,7 @@ interface Props {
|
||||||
export const MessageReactions = (props: Props) => {
|
export const MessageReactions = (props: Props) => {
|
||||||
const { reactions, onClick } = props
|
const { reactions, onClick } = props
|
||||||
|
|
||||||
const hasReaction = Object.values(reactions).some(
|
const hasReaction = Object.values(reactions).some(value => value.size > 0)
|
||||||
reaction => reaction.count !== 0
|
|
||||||
)
|
|
||||||
|
|
||||||
if (hasReaction === false) {
|
if (hasReaction === false) {
|
||||||
return null
|
return null
|
||||||
|
@ -26,12 +24,12 @@ export const MessageReactions = (props: Props) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex align="center" css={{ paddingTop: 6 }} gap={1}>
|
<Flex align="center" css={{ paddingTop: 6 }} gap={1}>
|
||||||
{Object.entries(reactions).map(([reaction, value]) => (
|
{Object.entries(emojis).map(([type, emoji]) => (
|
||||||
<Reaction
|
<Reaction
|
||||||
key={reaction}
|
key={type}
|
||||||
emoji={emojis[reaction as Reaction]}
|
emoji={emoji}
|
||||||
reaction={value}
|
value={reactions[type]}
|
||||||
onClick={() => onClick(reaction as Reaction)}
|
onClick={() => onClick(type)}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
|
@ -59,25 +57,30 @@ interface ReactionProps {
|
||||||
url: string
|
url: string
|
||||||
symbol: string
|
symbol: string
|
||||||
}
|
}
|
||||||
reaction: Props['reactions']['smile']
|
value: Props['reactions']['HEART']
|
||||||
onClick: VoidFunction
|
onClick: VoidFunction
|
||||||
}
|
}
|
||||||
|
|
||||||
const Reaction = (props: ReactionProps) => {
|
const Reaction = (props: ReactionProps) => {
|
||||||
const { emoji, reaction, onClick } = props
|
const { emoji, value, onClick } = props
|
||||||
|
|
||||||
if (reaction.count === 0) {
|
const { account } = useAccount()
|
||||||
|
|
||||||
|
const count = value.size
|
||||||
|
const me = account ? value.has('0x' + account.publicKey) : false
|
||||||
|
|
||||||
|
if (value.size === 0) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
active={reaction.me}
|
active={me}
|
||||||
aria-label={`${emoji.symbol}, ${reaction.count} reaction, press to react`}
|
aria-label={`${emoji.symbol}, ${count} reaction, press to react`}
|
||||||
>
|
>
|
||||||
<Image width={14} src={emoji.url} alt={emoji.symbol} />
|
<Image width={14} src={emoji.url} alt={emoji.symbol} />
|
||||||
<Text size="12">{reaction.count}</Text>
|
<Text size="12">{count}</Text>
|
||||||
</Button>
|
</Button>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue