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:
Pavel 2022-06-15 11:30:43 +02:00 committed by GitHub
parent 562d42b09e
commit a666d93844
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 74 additions and 80 deletions

View File

@ -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)
} }
} }

View File

@ -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(

View File

@ -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,
}
} }

View File

@ -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

View File

@ -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,
},
}, },
} }

View File

@ -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} />

View File

@ -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 = () => {

View File

@ -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>
) )
} }