From 66a13eb401475b2c5a571fed11abd3495823b5d7 Mon Sep 17 00:00:00 2001 From: HoonBaek Date: Tue, 16 May 2023 11:05:12 +0900 Subject: [PATCH 01/29] Change the props name 'breakpoint' and add an expected type --- src/lib/MediaQueryContext.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/MediaQueryContext.tsx b/src/lib/MediaQueryContext.tsx index e8c4af7ad..e8884e068 100644 --- a/src/lib/MediaQueryContext.tsx +++ b/src/lib/MediaQueryContext.tsx @@ -11,7 +11,7 @@ const MediaQueryContext = React.createContext({ export interface MediaQueryProviderProps { children: React.ReactElement; - mediaQueryBreakPoint?: string; + mediaQueryBreakPoint?: string | boolean; logger?: Logger; } From e75b0c0fe967125e41fe1294dad4154c41a627d9 Mon Sep 17 00:00:00 2001 From: HoonBaek Date: Tue, 16 May 2023 11:07:28 +0900 Subject: [PATCH 02/29] Use browserSupportMimeType for recording to MediaRecorder --- src/hooks/VoiceRecorder/index.tsx | 15 ++++++++++++--- src/utils/consts.ts | 1 - 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/hooks/VoiceRecorder/index.tsx b/src/hooks/VoiceRecorder/index.tsx index f3c646c8b..a7ae95d9c 100644 --- a/src/hooks/VoiceRecorder/index.tsx +++ b/src/hooks/VoiceRecorder/index.tsx @@ -1,9 +1,8 @@ -import React, { createContext, useCallback, useContext, useEffect, useState } from 'react'; +import React, { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react'; import { VOICE_MESSAGE_FILE_NAME, VOICE_MESSAGE_MIME_TYPE, VOICE_RECORDER_AUDIO_BITS, - VOICE_RECORDER_MIME_TYPE, } from '../../utils/consts'; import useSendbirdStateContext from '../useSendbirdStateContext'; @@ -37,6 +36,16 @@ export const VoiceRecorderProvider = (props: VoiceRecorderProps): React.ReactEle const [mediaRecorder, setMediaRecorder] = useState(null); const [isRecordable, setIsRecordable] = useState(false); + const browserSupportMimeType: string = useMemo((): string => { + const mimeTypes = ['audio/webm', 'audio/mp4']; + const supportedMimeType = mimeTypes.find((mimeType) => MediaRecorder.isTypeSupported(mimeType)); + if (!supportedMimeType) { + logger.error('VoiceRecorder: Browser does not support mimeType', { mimeTypes }); + return ''; + } + return supportedMimeType; + }, []); + const [webAudioUtils, setWebAudioUtils] = useState(null); useEffect(() => { if (isVoiceMessageEnabled && !webAudioUtils) { @@ -62,7 +71,7 @@ export const VoiceRecorderProvider = (props: VoiceRecorderProps): React.ReactEle logger.info('VoiceRecorder: Succeeded getting media stream.', stream); setIsRecordable(true); const mediaRecorder = new MediaRecorder(stream, { - mimeType: VOICE_RECORDER_MIME_TYPE, + mimeType: browserSupportMimeType, audioBitsPerSecond: VOICE_RECORDER_AUDIO_BITS, }); mediaRecorder.ondataavailable = (e) => { // when recording stops diff --git a/src/utils/consts.ts b/src/utils/consts.ts index bf7c5fd88..5783be5ef 100644 --- a/src/utils/consts.ts +++ b/src/utils/consts.ts @@ -4,7 +4,6 @@ export const SCROLL_BUFFER = 10; export const VOICE_RECORDER_CLICK_BUFFER_TIME = 250; export const VOICE_RECORDER_DEFAULT_MIN = 1000; // 1 seconds export const VOICE_RECORDER_DEFAULT_MAX = 600000; // 10 minutes -export const VOICE_RECORDER_MIME_TYPE = 'audio/webm'; export const VOICE_RECORDER_AUDIO_BITS = 128000; // voice message play From eecfaecaa5cef12ee49750f01be7558a57be9cbb Mon Sep 17 00:00:00 2001 From: HoonBaek Date: Tue, 16 May 2023 11:09:44 +0900 Subject: [PATCH 03/29] Allow ReactNode to the children props of MenuItems comp --- src/ui/ContextMenu/MenuItems.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ui/ContextMenu/MenuItems.tsx b/src/ui/ContextMenu/MenuItems.tsx index 471773665..cdccfa612 100644 --- a/src/ui/ContextMenu/MenuItems.tsx +++ b/src/ui/ContextMenu/MenuItems.tsx @@ -5,7 +5,7 @@ interface MenuItemsProps { className?: string; style?: Record; openLeft?: boolean; - children: React.ReactElement | Array; + children: React.ReactElement | Array | React.ReactNode; parentRef: React.RefObject; parentContainRef: React.RefObject; closeDropdown: () => void; From 6ef28555b76fc2d8110f132595cc1e42de8eac75 Mon Sep 17 00:00:00 2001 From: HoonBaek Date: Tue, 16 May 2023 11:10:32 +0900 Subject: [PATCH 04/29] Accept nullable type of parentRef of EmojiListItem comp --- src/ui/ContextMenu/EmojiListItems.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ui/ContextMenu/EmojiListItems.tsx b/src/ui/ContextMenu/EmojiListItems.tsx index abfd97910..7fee53948 100644 --- a/src/ui/ContextMenu/EmojiListItems.tsx +++ b/src/ui/ContextMenu/EmojiListItems.tsx @@ -8,7 +8,7 @@ type ReactionStyle = { left: number, top: number }; export interface EmojiListItemsProps { closeDropdown: () => void; children: ReactNode; - parentRef: RefObject; + parentRef: RefObject | null; parentContainRef: RefObject; spaceFromTrigger?: SpaceFromTrigger; } From 262494f9379e270af26c4d548e89549edf9f7f32 Mon Sep 17 00:00:00 2001 From: HoonBaek Date: Tue, 16 May 2023 11:11:16 +0900 Subject: [PATCH 05/29] Add util func: getEmojiUrl, getting url from emojiContainer --- src/utils/index.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/utils/index.ts b/src/utils/index.ts index 390028a33..bd596b51d 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -320,6 +320,13 @@ export const getEmojiMapAll = (emojiContainer: EmojiContainer): Map ({ key }: { key: string }) => key === targetKey; +export const getEmojiUrl = (emojiContainer?: EmojiContainer, emojiKey?: string): string => { + const isFindingKey = findingEmojiUrl(emojiKey ?? ''); + return emojiContainer?.emojiCategories + .find((category) => category.emojis.some(isFindingKey))?.emojis + .find(isFindingKey)?.url || ''; +}; export const getUserName = (user: User): string => (user?.friendName || user?.nickname || user?.userId); export const getSenderName = (message: UserMessage | FileMessage): string => (message?.sender && getUserName(message?.sender)); From 0d7be6f79264d0a36dc6c014df738d87a36e19eb Mon Sep 17 00:00:00 2001 From: HoonBaek Date: Tue, 16 May 2023 11:12:38 +0900 Subject: [PATCH 06/29] Add a menu option to MobileContextMenu: Reply in Thread --- src/ui/MobileMenu/MobileContextMenu.tsx | 28 ++++++++++++++++++++++++- src/ui/MobileMenu/index.tsx | 1 + 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/src/ui/MobileMenu/MobileContextMenu.tsx b/src/ui/MobileMenu/MobileContextMenu.tsx index e2a7cf479..db8601212 100644 --- a/src/ui/MobileMenu/MobileContextMenu.tsx +++ b/src/ui/MobileMenu/MobileContextMenu.tsx @@ -12,12 +12,13 @@ import { isUserMessage, copyToClipboard, isFileMessage, + isParentMessage, } from '../../utils'; import { useLocalization } from '../../lib/LocalizationContext'; import Icon, { IconTypes, IconColors } from '../Icon'; import Label, { LabelTypography } from '../Label'; -const MobileContextMenu: React.FunctionComponent = (props: BaseMenuProps) => { +const MobileContextMenu: React.FunctionComponent = (props: BaseMenuProps): React.ReactElement => { const { hideMenu, channel, @@ -29,6 +30,8 @@ const MobileContextMenu: React.FunctionComponent = (props: BaseMe showRemove, setQuoteMessage, parentRef, + onReplyInThread, + isOpenedFromThread = false, } = props; const isByMe = message?.sender?.userId === userId; const { stringSet } = useLocalization(); @@ -41,6 +44,10 @@ const MobileContextMenu: React.FunctionComponent = (props: BaseMe && !isFailedMessage(message) && !isPendingMessage(message) && (channel?.isGroupChannel() && !(channel as GroupChannel)?.isBroadcast); + const showMenuItemThread: boolean = (replyType === 'THREAD') && !isOpenedFromThread + && !isFailedMessage(message) + && !isPendingMessage(message) + && channel?.isGroupChannel(); const fileMessage = message as FileMessage; return ( @@ -92,6 +99,25 @@ const MobileContextMenu: React.FunctionComponent = (props: BaseMe /> )} + {showMenuItemThread && ( + { + hideMenu(); + onReplyInThread?.({ message }); + }} + > + + + + )} {showMenuItemEdit && ( = (props: MobileBottomSheetPr setQuoteMessage={setQuoteMessage} parentRef={parentRef} onReplyInThread={onReplyInThread} + isOpenedFromThread={isOpenedFromThread} /> ) } From 20e33a27e336749d02e5f059c7c6751041d6410d Mon Sep 17 00:00:00 2001 From: HoonBaek Date: Tue, 16 May 2023 11:13:27 +0900 Subject: [PATCH 07/29] Prevent event of mobile menu in MessageContent comp --- src/ui/MessageContent/index.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ui/MessageContent/index.tsx b/src/ui/MessageContent/index.tsx index 1d5a9daf8..f8a0d34b1 100644 --- a/src/ui/MessageContent/index.tsx +++ b/src/ui/MessageContent/index.tsx @@ -154,6 +154,7 @@ export default function MessageContent({ }, }, { delay: 300, + shouldPreventDefault: false, }); if (message?.isAdminMessage?.() || message?.messageType === 'admin') { From 0df5ee6ef8de70c0343065192a9c05910fd1a88a Mon Sep 17 00:00:00 2001 From: HoonBaek Date: Tue, 16 May 2023 11:14:33 +0900 Subject: [PATCH 08/29] Add props to UserListItem: avatarSize, to set avatar size --- src/ui/UserListItem/index.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/ui/UserListItem/index.tsx b/src/ui/UserListItem/index.tsx index 000a2dc14..1d677def2 100644 --- a/src/ui/UserListItem/index.tsx +++ b/src/ui/UserListItem/index.tsx @@ -26,6 +26,7 @@ export interface UserListItemProps { parentRef?: MutableRefObject, }): ReactElement; onChange?(e: ChangeEvent): void; + avatarSize?: string; } export default function UserListItem({ @@ -39,6 +40,7 @@ export default function UserListItem({ currentUser, action, onChange, + avatarSize = '40px', }: UserListItemProps): ReactElement { const uniqueKey = user.userId; const actionRef = React.useRef(null); @@ -68,8 +70,8 @@ export default function UserListItem({ className="sendbird-user-list-item__avatar" ref={avatarRef} src={user.profileUrl} - width="40px" - height="40px" + width={avatarSize} + height={avatarSize} onClick={() => { if (!disableUserProfile) { toggleDropdown(); From 3ad6bff3e3c9a304074a6077d4919d5e07eb9d60 Mon Sep 17 00:00:00 2001 From: HoonBaek Date: Tue, 16 May 2023 11:15:09 +0900 Subject: [PATCH 09/29] Create a component: MobileEmojisBottmSheet --- src/ui/MobileMenu/MobileEmojisBottomSheet.tsx | 97 +++++++++++++++++++ src/ui/MobileMenu/mobile-menu-emojis.scss | 37 +++++++ 2 files changed, 134 insertions(+) create mode 100644 src/ui/MobileMenu/MobileEmojisBottomSheet.tsx create mode 100644 src/ui/MobileMenu/mobile-menu-emojis.scss diff --git a/src/ui/MobileMenu/MobileEmojisBottomSheet.tsx b/src/ui/MobileMenu/MobileEmojisBottomSheet.tsx new file mode 100644 index 000000000..fae2641a5 --- /dev/null +++ b/src/ui/MobileMenu/MobileEmojisBottomSheet.tsx @@ -0,0 +1,97 @@ +import React, { ReactElement, useMemo, useState } from 'react'; +import { EmojiContainer } from '@sendbird/chat'; +import { Member } from '@sendbird/chat/groupChannel'; +import { FileMessage, Reaction, UserMessage } from '@sendbird/chat/message'; + +import './mobile-menu-emojis.scss'; + +import BottomSheet from '../BottomSheet'; +import { getEmojiUrl } from '../../utils'; +import ImageRenderer from '../ImageRenderer'; +import Icon, { IconColors, IconTypes } from '../Icon'; +import Label, { LabelColors, LabelTypography } from '../Label'; +import { useChannelContext } from '../../modules/Channel/context/ChannelProvider'; +import UserListItem from '../UserListItem'; + +export interface MobileEmojisBottomSheetProps { + message: UserMessage | FileMessage; + emojiKey: string; + hideMenu: () => void; + emojiContainer?: EmojiContainer; +} + +export const MobileEmojisBottomSheet = (props: MobileEmojisBottomSheetProps): ReactElement => { + const { + message, + emojiKey = '', + hideMenu, + emojiContainer, + } = props; + const { currentGroupChannel } = useChannelContext(); + const { members = [] } = currentGroupChannel; + const [selectedEmoji, setSelectedEmoji] = useState(emojiKey); + + const ReactorList = useMemo(() => { + const memberList = message.reactions?.find(reaction => reaction.key === selectedEmoji) + ?.userIds.map((userId) => members.find((member) => member.userId === userId) ?? null) + .filter((member) => member !== null) as Array; + return ( +
+ {memberList?.map((member) => ( + + ))} +
+ ); + }, [selectedEmoji, message.reactions]); + + return ( + +
+
+ {message.reactions?.map((reaction: Reaction): ReactElement => { + const emojiUrl = getEmojiUrl(emojiContainer, reaction.key); + return ( +
{ + setSelectedEmoji(reaction.key); + }} + > + ( +
+ +
+ )} + /> + +
+ ) + })} +
+ {ReactorList} +
+
+ ); +}; diff --git a/src/ui/MobileMenu/mobile-menu-emojis.scss b/src/ui/MobileMenu/mobile-menu-emojis.scss new file mode 100644 index 000000000..3b2b1f8f1 --- /dev/null +++ b/src/ui/MobileMenu/mobile-menu-emojis.scss @@ -0,0 +1,37 @@ +.sendbird-message__bottomsheet__emoji-list { + position: relative; + padding: 0px 16px; + box-sizing: border-box; + width: 100%; + height: 46px; + overflow-x: scroll; + + display: inline-flex; + flex-direction: row; + justify-content: center; + gap: 16px; +} +.sendbird-message__bottomsheet__emoji-list__item { + position: relative; + height: 100%; + + display: inline-flex; + gap: 4px; + flex-direction: row; + align-items: center;; +} + +.sendbird-message__bottomsheet__reactor-list { + position: relative; + padding: 0px 16px; + width: 100%; + height: 216px; + box-sizing: border-box; + + display: inline-flex; + flex-direction: column; + overflow-y: scroll; +} +.sendbird-message__bottomsheet__reactor-list__item.sendbird-user-list-item { + border-bottom: 0px; +} From 026033758402c8add1e63d1a3d82e084b7b49c84 Mon Sep 17 00:00:00 2001 From: HoonBaek Date: Tue, 16 May 2023 11:17:11 +0900 Subject: [PATCH 10/29] Add MobileEmojisBottomSheet to the EmojiReactions comp --- src/ui/EmojiReactions/index.tsx | 85 ++++++++++++++++++++++++--------- 1 file changed, 62 insertions(+), 23 deletions(-) diff --git a/src/ui/EmojiReactions/index.tsx b/src/ui/EmojiReactions/index.tsx index 8663b996f..96d80aa47 100644 --- a/src/ui/EmojiReactions/index.tsx +++ b/src/ui/EmojiReactions/index.tsx @@ -1,5 +1,5 @@ import './index.scss'; -import React, { ReactElement, useContext, useRef } from 'react'; +import React, { ReactElement, useContext, useRef, useState } from 'react'; import type { FileMessage, Reaction, UserMessage } from '@sendbird/chat/message'; import type { Emoji, EmojiContainer } from '@sendbird/chat'; @@ -13,6 +13,9 @@ import ContextMenu, { EmojiListItems } from '../ContextMenu'; import { getClassName, getEmojiListAll, getEmojiMapAll, getEmojiTooltipString, isReactedBy } from '../../utils'; import { LocalizationContext } from '../../lib/LocalizationContext'; +import { MobileEmojisBottomSheet } from '../MobileMenu/MobileEmojisBottomSheet'; +import { useMediaQueryContext } from '../../lib/MediaQueryContext'; +import useLongPress from '../../hooks/useLongPress'; interface Props { className?: string | Array; @@ -26,7 +29,7 @@ interface Props { } const EmojiReactions = ({ - className, + className = '', userId, message, emojiContainer, @@ -38,14 +41,27 @@ const EmojiReactions = ({ const { stringSet } = useContext(LocalizationContext); const emojisMap = getEmojiMapAll(emojiContainer); const addReactionRef = useRef(null); + const [showEmojisBottomSheet, setShowEmojisBottomSheet] = useState(''); + const { isMobile } = useMediaQueryContext(); + + // const longPress = useLongPress({ + // onLongPress: (e) => { + // setShowEmojisBottomSheet(reaction.key); + // }, + // onClick: () => { + // toggleReaction?.(message, reaction.key, reactedByMe); + // }, + // }, { + // shouldPreventDefault: true, + // }); return (
- {(message?.reactions?.length > 0) && ( - message.reactions.map((reaction: Reaction): ReactElement => { + {((message.reactions?.length ?? 0) > 0) && ( + message.reactions?.map((reaction: Reaction): ReactElement => { const reactedByMe = isReactedBy(userId, reaction); return ( )} > - { - toggleReaction(message, reaction.key, reactedByMe); - e?.stopPropagation?.(); - }} +
{ + setShowEmojisBottomSheet(reaction.key); + }, + onClick: () => { + toggleReaction?.(message, reaction.key, reactedByMe); + }, + }, { shouldPreventDefault: true }) : {})} > - - )} - /> - + { + toggleReaction?.(message, reaction.key, reactedByMe); + e?.stopPropagation?.(); + }} + > + + )} + /> + +
); }) )} - {(message?.reactions?.length < emojisMap.size) && ( + {((message.reactions?.length ?? 0) < emojisMap.size) && ( void): ReactElement => ( { closeDropdown(); - toggleReaction(message, emoji.key, isReacted); + toggleReaction?.(message, emoji.key, isReacted); e?.stopPropagation(); }} > @@ -144,6 +173,16 @@ const EmojiReactions = ({ )} /> )} + {showEmojisBottomSheet && ( + { + setShowEmojisBottomSheet(''); + }} + emojiContainer={emojiContainer} + /> + )}
); }; From cd096b8366ce34d904017350b16a4867eaf82d84 Mon Sep 17 00:00:00 2001 From: Sravan S Date: Tue, 16 May 2023 16:18:02 +0900 Subject: [PATCH 11/29] fix: useLongPress inside loop --- src/ui/EmojiReactions/ReactionItem.tsx | 94 ++++++++++++++++++++++++++ src/ui/EmojiReactions/index.tsx | 69 +++---------------- 2 files changed, 103 insertions(+), 60 deletions(-) create mode 100644 src/ui/EmojiReactions/ReactionItem.tsx diff --git a/src/ui/EmojiReactions/ReactionItem.tsx b/src/ui/EmojiReactions/ReactionItem.tsx new file mode 100644 index 000000000..e49fd7d4a --- /dev/null +++ b/src/ui/EmojiReactions/ReactionItem.tsx @@ -0,0 +1,94 @@ +import React, { useContext } from 'react'; + +import { FileMessage, Reaction, UserMessage } from '@sendbird/chat/message'; + +import Tooltip from '../Tooltip'; +import TooltipWrapper from '../TooltipWrapper'; +import ReactionBadge from '../ReactionBadge'; +import ImageRenderer from '../ImageRenderer'; +import Icon, { IconTypes } from '../Icon'; +import { useMediaQueryContext } from '../../lib/MediaQueryContext'; +import useLongPress from '../../hooks/useLongPress'; +import { LocalizationContext } from '../../lib/LocalizationContext'; +import useSendbirdStateContext from '../../hooks/useSendbirdStateContext'; +import { getEmojiTooltipString, isReactedBy } from '../../utils'; +import { useMessageContext } from '../../modules/Message/context/MessageProvider'; +import { Emoji } from '@sendbird/chat'; + +type Props = { + reaction: Reaction; + memberNicknamesMap: Map; + setShowEmojisBottomSheet: React.Dispatch>; + toggleReaction?: (message: UserMessage | FileMessage, key: string, byMe: boolean) => void; + emojisMap: Map; +} + +export default function ReactionItem({ + reaction, + memberNicknamesMap, + setShowEmojisBottomSheet, + toggleReaction, + emojisMap, +}: Props) { + const store = useSendbirdStateContext(); + const { isMobile } = useMediaQueryContext(); + const messageStore = useMessageContext(); + const { stringSet } = useContext(LocalizationContext); + + const message = messageStore?.message as UserMessage; + const longPress = useLongPress({ + onLongPress: () => { + setShowEmojisBottomSheet(reaction.key); + }, + onClick: () => { + toggleReaction?.((message), reaction.key, reactedByMe); + }, + }, { + shouldPreventDefault: true, + }); + + const userId = store.config.userId; + const reactedByMe = isReactedBy(userId, reaction); + + return ( + 0) ? ( + + {getEmojiTooltipString(reaction, userId, memberNicknamesMap, stringSet)} + + ): <>} + > +
+ { + toggleReaction?.(message, reaction.key, reactedByMe); + e?.stopPropagation?.(); + }} + > + + )} + /> + +
+
+ ) +} diff --git a/src/ui/EmojiReactions/index.tsx b/src/ui/EmojiReactions/index.tsx index 96d80aa47..7f4d7cf64 100644 --- a/src/ui/EmojiReactions/index.tsx +++ b/src/ui/EmojiReactions/index.tsx @@ -1,10 +1,8 @@ import './index.scss'; -import React, { ReactElement, useContext, useRef, useState } from 'react'; +import React, { ReactElement, useRef, useState } from 'react'; import type { FileMessage, Reaction, UserMessage } from '@sendbird/chat/message'; import type { Emoji, EmojiContainer } from '@sendbird/chat'; -import Tooltip from '../Tooltip'; -import TooltipWrapper from '../TooltipWrapper'; import ReactionBadge from '../ReactionBadge'; import ReactionButton from '../ReactionButton'; import ImageRenderer from '../ImageRenderer'; @@ -12,10 +10,8 @@ import Icon, { IconTypes, IconColors } from '../Icon'; import ContextMenu, { EmojiListItems } from '../ContextMenu'; import { getClassName, getEmojiListAll, getEmojiMapAll, getEmojiTooltipString, isReactedBy } from '../../utils'; -import { LocalizationContext } from '../../lib/LocalizationContext'; import { MobileEmojisBottomSheet } from '../MobileMenu/MobileEmojisBottomSheet'; -import { useMediaQueryContext } from '../../lib/MediaQueryContext'; -import useLongPress from '../../hooks/useLongPress'; +import ReactionItem from './ReactionItem'; interface Props { className?: string | Array; @@ -38,22 +34,9 @@ const EmojiReactions = ({ isByMe = false, toggleReaction, }: Props): ReactElement => { - const { stringSet } = useContext(LocalizationContext); const emojisMap = getEmojiMapAll(emojiContainer); const addReactionRef = useRef(null); const [showEmojisBottomSheet, setShowEmojisBottomSheet] = useState(''); - const { isMobile } = useMediaQueryContext(); - - // const longPress = useLongPress({ - // onLongPress: (e) => { - // setShowEmojisBottomSheet(reaction.key); - // }, - // onClick: () => { - // toggleReaction?.(message, reaction.key, reactedByMe); - // }, - // }, { - // shouldPreventDefault: true, - // }); return (
{((message.reactions?.length ?? 0) > 0) && ( message.reactions?.map((reaction: Reaction): ReactElement => { - const reactedByMe = isReactedBy(userId, reaction); return ( - 0) && ( - - {getEmojiTooltipString(reaction, userId, memberNicknamesMap, stringSet)} - - )} - > -
{ - setShowEmojisBottomSheet(reaction.key); - }, - onClick: () => { - toggleReaction?.(message, reaction.key, reactedByMe); - }, - }, { shouldPreventDefault: true }) : {})} - > - { - toggleReaction?.(message, reaction.key, reactedByMe); - e?.stopPropagation?.(); - }} - > - - )} - /> - -
-
+ reaction={reaction} + memberNicknamesMap={memberNicknamesMap} + setShowEmojisBottomSheet={setShowEmojisBottomSheet} + toggleReaction={toggleReaction} + emojisMap={emojisMap} + /> ); }) )} From e10eb68b68f0dd03e0ecf2a654260abd341d6bf3 Mon Sep 17 00:00:00 2001 From: Sravan S Date: Tue, 16 May 2023 17:36:53 +0900 Subject: [PATCH 12/29] fix: stop propogation to useLongPress --- src/hooks/useLongPress.tsx | 7 ++++++- src/ui/MobileMenu/MobileEmojisBottomSheet.tsx | 1 + 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/hooks/useLongPress.tsx b/src/hooks/useLongPress.tsx index 9e0664173..66fda22b5 100644 --- a/src/hooks/useLongPress.tsx +++ b/src/hooks/useLongPress.tsx @@ -50,6 +50,7 @@ interface PressHandlers { interface Options { delay?: number; shouldPreventDefault?: boolean; + shouldStopPropagation?: boolean; } interface UseLongPressType { @@ -67,6 +68,7 @@ export default function useLongPress({ }: PressHandlers, { delay = DEFAULT_DURATION, shouldPreventDefault = true, + shouldStopPropagation = false, }: Options = {}): UseLongPressType { const { isMobile } = useMediaQueryContext(); const [longPressTriggered, setLongPressTriggered] = useState(false); @@ -81,6 +83,9 @@ export default function useLongPress({ ...e, }; setDragTriggered(false); + if (shouldStopPropagation) { + e.stopPropagation(); + } if (shouldPreventDefault && e.target) { e.target.addEventListener( 'touchend', @@ -96,7 +101,7 @@ export default function useLongPress({ onLongPress(clonedEvent); setLongPressTriggered(true); }, delay); - }, [onLongPress, delay, shouldPreventDefault, isMobile]); + }, [onLongPress, delay, shouldPreventDefault, shouldStopPropagation, isMobile]); const clear = useCallback(( e: React.MouseEvent | React.TouchEvent, diff --git a/src/ui/MobileMenu/MobileEmojisBottomSheet.tsx b/src/ui/MobileMenu/MobileEmojisBottomSheet.tsx index fae2641a5..391b30442 100644 --- a/src/ui/MobileMenu/MobileEmojisBottomSheet.tsx +++ b/src/ui/MobileMenu/MobileEmojisBottomSheet.tsx @@ -39,6 +39,7 @@ export const MobileEmojisBottomSheet = (props: MobileEmojisBottomSheetProps): Re
{memberList?.map((member) => ( Date: Wed, 17 May 2023 09:49:45 +0900 Subject: [PATCH 13/29] Not prevent event when selecting menu from Thread comp --- src/modules/Thread/components/ParentMessageInfo/index.tsx | 2 ++ .../Thread/components/ThreadList/ThreadListItemContent.tsx | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/modules/Thread/components/ParentMessageInfo/index.tsx b/src/modules/Thread/components/ParentMessageInfo/index.tsx index 0daabd848..77b363a9a 100644 --- a/src/modules/Thread/components/ParentMessageInfo/index.tsx +++ b/src/modules/Thread/components/ParentMessageInfo/index.tsx @@ -77,6 +77,8 @@ export default function ParentMessageInfo({ setShowMobileMenu(true); } }, + }, { + shouldPreventDefault: false, }); // Edit message diff --git a/src/modules/Thread/components/ThreadList/ThreadListItemContent.tsx b/src/modules/Thread/components/ThreadList/ThreadListItemContent.tsx index ed842c0ca..d6a910f48 100644 --- a/src/modules/Thread/components/ThreadList/ThreadListItemContent.tsx +++ b/src/modules/Thread/components/ThreadList/ThreadListItemContent.tsx @@ -101,6 +101,8 @@ export default function ThreadListItemContent({ setShowMobileMenu(true); } }, + }, { + shouldPreventDefault: false, }); return ( From b6298aa201aa3ba9fb3e22252ae8da5504c1bf13 Mon Sep 17 00:00:00 2001 From: HoonBaek Date: Wed, 17 May 2023 09:52:30 +0900 Subject: [PATCH 14/29] Fix lint errors --- src/ui/EmojiReactions/ReactionItem.tsx | 6 +++--- src/ui/EmojiReactions/index.tsx | 2 +- src/ui/MobileMenu/MobileEmojisBottomSheet.tsx | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/ui/EmojiReactions/ReactionItem.tsx b/src/ui/EmojiReactions/ReactionItem.tsx index e49fd7d4a..4f22e7601 100644 --- a/src/ui/EmojiReactions/ReactionItem.tsx +++ b/src/ui/EmojiReactions/ReactionItem.tsx @@ -21,7 +21,7 @@ type Props = { setShowEmojisBottomSheet: React.Dispatch>; toggleReaction?: (message: UserMessage | FileMessage, key: string, byMe: boolean) => void; emojisMap: Map; -} +}; export default function ReactionItem({ reaction, @@ -57,7 +57,7 @@ export default function ReactionItem({ {getEmojiTooltipString(reaction, userId, memberNicknamesMap, stringSet)} - ): <>} + ) : <>} >
- ) + ); } diff --git a/src/ui/EmojiReactions/index.tsx b/src/ui/EmojiReactions/index.tsx index 7f4d7cf64..2a91e2b4f 100644 --- a/src/ui/EmojiReactions/index.tsx +++ b/src/ui/EmojiReactions/index.tsx @@ -9,7 +9,7 @@ import ImageRenderer from '../ImageRenderer'; import Icon, { IconTypes, IconColors } from '../Icon'; import ContextMenu, { EmojiListItems } from '../ContextMenu'; -import { getClassName, getEmojiListAll, getEmojiMapAll, getEmojiTooltipString, isReactedBy } from '../../utils'; +import { getClassName, getEmojiListAll, getEmojiMapAll } from '../../utils'; import { MobileEmojisBottomSheet } from '../MobileMenu/MobileEmojisBottomSheet'; import ReactionItem from './ReactionItem'; diff --git a/src/ui/MobileMenu/MobileEmojisBottomSheet.tsx b/src/ui/MobileMenu/MobileEmojisBottomSheet.tsx index 391b30442..d0043c2ee 100644 --- a/src/ui/MobileMenu/MobileEmojisBottomSheet.tsx +++ b/src/ui/MobileMenu/MobileEmojisBottomSheet.tsx @@ -88,7 +88,7 @@ export const MobileEmojisBottomSheet = (props: MobileEmojisBottomSheetProps): Re {reaction.userIds.length}
- ) + ); })}
{ReactorList} From ba60f93b42eea1bc1e3ae2dea1ec549bcbb7020e Mon Sep 17 00:00:00 2001 From: HoonBaek Date: Wed, 17 May 2023 10:06:36 +0900 Subject: [PATCH 15/29] Gets channel through props in MobileEmojisBottomSheet, instead of context hook --- scripts/index_d_ts | 2 ++ .../ParentMessageInfo/ParentMessageInfoItem.tsx | 1 + .../components/ThreadList/ThreadListItemContent.tsx | 1 + src/ui/EmojiReactions/index.tsx | 9 +++++++-- src/ui/MessageContent/index.tsx | 1 + src/ui/MobileMenu/MobileEmojisBottomSheet.tsx | 8 ++++---- 6 files changed, 16 insertions(+), 6 deletions(-) diff --git a/scripts/index_d_ts b/scripts/index_d_ts index 13fe969c4..1fa6c536d 100644 --- a/scripts/index_d_ts +++ b/scripts/index_d_ts @@ -1864,6 +1864,7 @@ declare module '@sendbird/uikit-react/ui/Dropdown' { declare module '@sendbird/uikit-react/ui/EmojiReactions' { import type { EmojiCategory } from '@sendbird/chat'; import type { FileMessage, UserMessage } from '@sendbird/chat/message'; + import type { GroupChannel } from '@sendbird/chat/groupChannel'; interface EmojiContainer { emojiCategories: Array; @@ -1873,6 +1874,7 @@ declare module '@sendbird/uikit-react/ui/EmojiReactions' { className?: string | Array; userId: string; message: UserMessage | FileMessage; + channel: GroupChannel; emojiContainer: EmojiContainer; memberNicknamesMap: Map; spaceFromTrigger?: Record; diff --git a/src/modules/Thread/components/ParentMessageInfo/ParentMessageInfoItem.tsx b/src/modules/Thread/components/ParentMessageInfo/ParentMessageInfoItem.tsx index b9289a919..dc5fa8398 100644 --- a/src/modules/Thread/components/ParentMessageInfo/ParentMessageInfoItem.tsx +++ b/src/modules/Thread/components/ParentMessageInfo/ParentMessageInfoItem.tsx @@ -283,6 +283,7 @@ export default function ParentMessageInfoItem({ ; userId: string; message: UserMessage | FileMessage; + channel: Nullable; emojiContainer: EmojiContainer; memberNicknamesMap: Map; spaceFromTrigger?: { x: number, y: number }; @@ -28,6 +31,7 @@ const EmojiReactions = ({ className = '', userId, message, + channel, emojiContainer, memberNicknamesMap, spaceFromTrigger = { x: 0, y: 0 }, @@ -122,9 +126,10 @@ const EmojiReactions = ({ )} /> )} - {showEmojisBottomSheet && ( + {(showEmojisBottomSheet && channel !== null) && ( { setShowEmojisBottomSheet(''); diff --git a/src/ui/MessageContent/index.tsx b/src/ui/MessageContent/index.tsx index f8a0d34b1..812898ed4 100644 --- a/src/ui/MessageContent/index.tsx +++ b/src/ui/MessageContent/index.tsx @@ -365,6 +365,7 @@ export default function MessageContent({ void; emojiContainer?: EmojiContainer; @@ -23,12 +23,12 @@ export interface MobileEmojisBottomSheetProps { export const MobileEmojisBottomSheet = (props: MobileEmojisBottomSheetProps): ReactElement => { const { message, + channel, emojiKey = '', hideMenu, emojiContainer, } = props; - const { currentGroupChannel } = useChannelContext(); - const { members = [] } = currentGroupChannel; + const { members = [] } = channel; const [selectedEmoji, setSelectedEmoji] = useState(emojiKey); const ReactorList = useMemo(() => { From 29b85cd011c79a0ee430149afcbf8787c7100c6b Mon Sep 17 00:00:00 2001 From: HoonBaek Date: Wed, 17 May 2023 10:08:16 +0900 Subject: [PATCH 16/29] Stop propagation in the ReactionItem --- src/ui/EmojiReactions/ReactionItem.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ui/EmojiReactions/ReactionItem.tsx b/src/ui/EmojiReactions/ReactionItem.tsx index 4f22e7601..863c41b11 100644 --- a/src/ui/EmojiReactions/ReactionItem.tsx +++ b/src/ui/EmojiReactions/ReactionItem.tsx @@ -45,6 +45,7 @@ export default function ReactionItem({ }, }, { shouldPreventDefault: true, + shouldStopPropagation: true, }); const userId = store.config.userId; From 300a75a1aad9950f966438a2bb73cd9f92210233 Mon Sep 17 00:00:00 2001 From: HoonBaek Date: Wed, 17 May 2023 10:14:52 +0900 Subject: [PATCH 17/29] Prevent opening multiple emojis menu in Mobile reactions --- src/ui/EmojiReactions/ReactionItem.tsx | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/ui/EmojiReactions/ReactionItem.tsx b/src/ui/EmojiReactions/ReactionItem.tsx index 863c41b11..000df6a1f 100644 --- a/src/ui/EmojiReactions/ReactionItem.tsx +++ b/src/ui/EmojiReactions/ReactionItem.tsx @@ -41,6 +41,7 @@ export default function ReactionItem({ setShowEmojisBottomSheet(reaction.key); }, onClick: () => { + setShowEmojisBottomSheet(''); toggleReaction?.((message), reaction.key, reactedByMe); }, }, { @@ -74,10 +75,6 @@ export default function ReactionItem({ { - toggleReaction?.(message, reaction.key, reactedByMe); - e?.stopPropagation?.(); - }} > Date: Thu, 18 May 2023 22:50:07 +0900 Subject: [PATCH 18/29] Rename MobileEmojisBottomSheet to ReactedMembersBottomSheet --- src/ui/EmojiReactions/index.tsx | 4 ++-- ...ileEmojisBottomSheet.tsx => ReactedMembersBottomSheet.tsx} | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) rename src/ui/MobileMenu/{MobileEmojisBottomSheet.tsx => ReactedMembersBottomSheet.tsx} (95%) diff --git a/src/ui/EmojiReactions/index.tsx b/src/ui/EmojiReactions/index.tsx index 6388174bb..df76440aa 100644 --- a/src/ui/EmojiReactions/index.tsx +++ b/src/ui/EmojiReactions/index.tsx @@ -12,7 +12,7 @@ import ContextMenu, { EmojiListItems } from '../ContextMenu'; import { Nullable } from '../../types'; import { getClassName, getEmojiListAll, getEmojiMapAll } from '../../utils'; -import { MobileEmojisBottomSheet } from '../MobileMenu/MobileEmojisBottomSheet'; +import { ReactedMembersBottomSheet } from '../MobileMenu/ReactedMembersBottomSheet'; import ReactionItem from './ReactionItem'; interface Props { @@ -127,7 +127,7 @@ const EmojiReactions = ({ /> )} {(showEmojisBottomSheet && channel !== null) && ( - { +export const ReactedMembersBottomSheet = (props: ReactedMembersBottomSheetProps): ReactElement => { const { message, channel, From 4a74183db6dc42010926680896cf15d5f7f66e54 Mon Sep 17 00:00:00 2001 From: HoonBaek Date: Thu, 18 May 2023 22:59:25 +0900 Subject: [PATCH 19/29] Rename className text: emoji-list to reacted-members --- .../MobileMenu/ReactedMembersBottomSheet.tsx | 23 +++++++++---------- ....scss => mobile-menu-reacted-members.scss} | 4 ++-- 2 files changed, 13 insertions(+), 14 deletions(-) rename src/ui/MobileMenu/{mobile-menu-emojis.scss => mobile-menu-reacted-members.scss} (86%) diff --git a/src/ui/MobileMenu/ReactedMembersBottomSheet.tsx b/src/ui/MobileMenu/ReactedMembersBottomSheet.tsx index f99892401..b31e81df3 100644 --- a/src/ui/MobileMenu/ReactedMembersBottomSheet.tsx +++ b/src/ui/MobileMenu/ReactedMembersBottomSheet.tsx @@ -3,7 +3,7 @@ import { EmojiContainer } from '@sendbird/chat'; import { GroupChannel, Member } from '@sendbird/chat/groupChannel'; import { FileMessage, Reaction, UserMessage } from '@sendbird/chat/message'; -import './mobile-menu-emojis.scss'; +import './mobile-menu-reacted-members.scss'; import BottomSheet from '../BottomSheet'; import { getEmojiUrl } from '../../utils'; @@ -20,14 +20,13 @@ export interface ReactedMembersBottomSheetProps { emojiContainer?: EmojiContainer; } -export const ReactedMembersBottomSheet = (props: ReactedMembersBottomSheetProps): ReactElement => { - const { - message, - channel, - emojiKey = '', - hideMenu, - emojiContainer, - } = props; +export const ReactedMembersBottomSheet = ({ + message, + channel, + emojiKey = '', + hideMenu, + emojiContainer, +}: ReactedMembersBottomSheetProps): ReactElement => { const { members = [] } = channel; const [selectedEmoji, setSelectedEmoji] = useState(emojiKey); @@ -52,15 +51,15 @@ export const ReactedMembersBottomSheet = (props: ReactedMembersBottomSheetProps) return (
-
+
{message.reactions?.map((reaction: Reaction): ReactElement => { const emojiUrl = getEmojiUrl(emojiContainer, reaction.key); return (
{ setSelectedEmoji(reaction.key); diff --git a/src/ui/MobileMenu/mobile-menu-emojis.scss b/src/ui/MobileMenu/mobile-menu-reacted-members.scss similarity index 86% rename from src/ui/MobileMenu/mobile-menu-emojis.scss rename to src/ui/MobileMenu/mobile-menu-reacted-members.scss index 3b2b1f8f1..606bfc9ee 100644 --- a/src/ui/MobileMenu/mobile-menu-emojis.scss +++ b/src/ui/MobileMenu/mobile-menu-reacted-members.scss @@ -1,4 +1,4 @@ -.sendbird-message__bottomsheet__emoji-list { +.sendbird-message__bottomsheet__reacted-members { position: relative; padding: 0px 16px; box-sizing: border-box; @@ -11,7 +11,7 @@ justify-content: center; gap: 16px; } -.sendbird-message__bottomsheet__emoji-list__item { +.sendbird-message__bottomsheet__reacted-members__item { position: relative; height: 100%; From 091b30a84e9abf11d06787d0f6da9dd34c8a0992 Mon Sep 17 00:00:00 2001 From: HoonBaek Date: Thu, 18 May 2023 23:09:48 +0900 Subject: [PATCH 20/29] Activate onClick EmojiReaction in the DesktopLayout --- src/ui/EmojiReactions/ReactionItem.tsx | 29 +++++++++++++------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/src/ui/EmojiReactions/ReactionItem.tsx b/src/ui/EmojiReactions/ReactionItem.tsx index 000df6a1f..efc21318e 100644 --- a/src/ui/EmojiReactions/ReactionItem.tsx +++ b/src/ui/EmojiReactions/ReactionItem.tsx @@ -1,4 +1,4 @@ -import React, { useContext } from 'react'; +import React, { useCallback, useContext } from 'react'; import { FileMessage, Reaction, UserMessage } from '@sendbird/chat/message'; @@ -33,25 +33,26 @@ export default function ReactionItem({ const store = useSendbirdStateContext(); const { isMobile } = useMediaQueryContext(); const messageStore = useMessageContext(); + const message = messageStore?.message as UserMessage; const { stringSet } = useContext(LocalizationContext); - const message = messageStore?.message as UserMessage; + const userId = store.config.userId; + const reactedByMe = isReactedBy(userId, reaction); + + const handleOnClick = () => { + setShowEmojisBottomSheet(''); + toggleReaction?.((message), reaction.key, reactedByMe); + }; const longPress = useLongPress({ onLongPress: () => { setShowEmojisBottomSheet(reaction.key); }, - onClick: () => { - setShowEmojisBottomSheet(''); - toggleReaction?.((message), reaction.key, reactedByMe); - }, + onClick: handleOnClick, }, { shouldPreventDefault: true, shouldStopPropagation: true, }); - const userId = store.config.userId; - const reactedByMe = isReactedBy(userId, reaction); - return (
Date: Thu, 18 May 2023 23:13:54 +0900 Subject: [PATCH 21/29] Add isMobile condition for displaying ReacedMembersBottomSheet --- src/ui/EmojiReactions/index.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/ui/EmojiReactions/index.tsx b/src/ui/EmojiReactions/index.tsx index df76440aa..de0f2672c 100644 --- a/src/ui/EmojiReactions/index.tsx +++ b/src/ui/EmojiReactions/index.tsx @@ -14,6 +14,7 @@ import { Nullable } from '../../types'; import { getClassName, getEmojiListAll, getEmojiMapAll } from '../../utils'; import { ReactedMembersBottomSheet } from '../MobileMenu/ReactedMembersBottomSheet'; import ReactionItem from './ReactionItem'; +import { useMediaQueryContext } from '../../lib/MediaQueryContext'; interface Props { className?: string | Array; @@ -38,6 +39,7 @@ const EmojiReactions = ({ isByMe = false, toggleReaction, }: Props): ReactElement => { + const { isMobile } = useMediaQueryContext(); const emojisMap = getEmojiMapAll(emojiContainer); const addReactionRef = useRef(null); const [showEmojisBottomSheet, setShowEmojisBottomSheet] = useState(''); @@ -126,7 +128,7 @@ const EmojiReactions = ({ )} /> )} - {(showEmojisBottomSheet && channel !== null) && ( + {(isMobile && showEmojisBottomSheet && channel !== null) && ( Date: Fri, 19 May 2023 08:16:03 +0900 Subject: [PATCH 22/29] Create AddReactionBadgeItem and MobileEmojisBottomSheet Apply the components to the EmojiReactions comp --- .../EmojiReactions/AddReactionBadgeItem.tsx | 42 +++++++++++ src/ui/EmojiReactions/index.tsx | 29 +++++++- src/ui/MobileMenu/MobileEmojisBottomSheet.tsx | 69 +++++++++++++++++++ 3 files changed, 137 insertions(+), 3 deletions(-) create mode 100644 src/ui/EmojiReactions/AddReactionBadgeItem.tsx create mode 100644 src/ui/MobileMenu/MobileEmojisBottomSheet.tsx diff --git a/src/ui/EmojiReactions/AddReactionBadgeItem.tsx b/src/ui/EmojiReactions/AddReactionBadgeItem.tsx new file mode 100644 index 000000000..5e2a9c08d --- /dev/null +++ b/src/ui/EmojiReactions/AddReactionBadgeItem.tsx @@ -0,0 +1,42 @@ +import React, { + KeyboardEvent, + MouseEvent, + TouchEvent, +} from 'react'; +import ReactionBadge from '../ReactionBadge'; +import Icon, { IconColors, IconTypes } from '../Icon'; +import useLongPress from '../../hooks/useLongPress'; + +export interface AddReactionBadgeItemProps { + onClick: (e: MouseEvent | KeyboardEvent | TouchEvent) => void; +} + +export const AddReactionBadgeItem = ({ + onClick, +}: AddReactionBadgeItemProps): React.ReactElement => { + const onlyClick = useLongPress({ + onLongPress: () => { /* noop */ }, + onClick, + }, { + shouldPreventDefault: true, + shouldStopPropagation: true, + }); + + return ( +
+ + + +
+ ); +}; diff --git a/src/ui/EmojiReactions/index.tsx b/src/ui/EmojiReactions/index.tsx index de0f2672c..f57394beb 100644 --- a/src/ui/EmojiReactions/index.tsx +++ b/src/ui/EmojiReactions/index.tsx @@ -15,6 +15,8 @@ import { getClassName, getEmojiListAll, getEmojiMapAll } from '../../utils'; import { ReactedMembersBottomSheet } from '../MobileMenu/ReactedMembersBottomSheet'; import ReactionItem from './ReactionItem'; import { useMediaQueryContext } from '../../lib/MediaQueryContext'; +import { AddReactionBadgeItem } from './AddReactionBadgeItem'; +import { MobileEmojisBottomSheet } from '../MobileMenu/MobileEmojisBottomSheet'; interface Props { className?: string | Array; @@ -40,10 +42,13 @@ const EmojiReactions = ({ toggleReaction, }: Props): ReactElement => { const { isMobile } = useMediaQueryContext(); - const emojisMap = getEmojiMapAll(emojiContainer); const addReactionRef = useRef(null); + const [showEmojiList, setShowEmojiList] = useState(false); const [showEmojisBottomSheet, setShowEmojisBottomSheet] = useState(''); + const emojisMap = getEmojiMapAll(emojiContainer); + const showAddReactionBadge = (message.reactions?.length ?? 0) < emojisMap.size; + return (
void): ReactElement => ( { const isReacted: boolean = (message?.reactions ?.find((reaction: Reaction): boolean => reaction.key === emoji.key)?.userIds - ?.some((reactorId: string): boolean => reactorId === userId)); + ?.some((reactorId: string): boolean => reactorId === userId)) || false; return ( )} + {(isMobile && showAddReactionBadge) && ( + { + setShowEmojiList(true); + }} + /> + )} + {(isMobile && showEmojiList) && ( + { + setShowEmojiList(false); + }} + toggleReaction={toggleReaction} + /> + )} {(isMobile && showEmojisBottomSheet && channel !== null) && ( void; + toggleReaction?: (message: UserMessage | FileMessage, key: string, byMe: boolean) => void; +} + +export const MobileEmojisBottomSheet = ({ + userId, + message, + emojiContainer, + hideMenu, + toggleReaction, +}: MobileEmojisBottomSheetProps): ReactElement => { + const emojiAllList = useMemo(() => { + return getEmojiListAll(emojiContainer); + }, [emojiContainer]); + return ( + +
+ {emojiAllList.map((emoji) => { + const isReacted: boolean = (message?.reactions + ?.find((reaction: Reaction): boolean => reaction.key === emoji.key)?.userIds + ?.some((reactorId: string): boolean => reactorId === userId)) ?? false; + return ( + { + e?.stopPropagation(); + toggleReaction?.(message, emoji.key, isReacted); + hideMenu(); + }} + > + ): ReactElement => ( +
+ +
+ )} + /> +
+ ); + })} +
+
+ ); +}; From 0c3fcdb1d853ed3880dadfdaf54aafaec26d19f4 Mon Sep 17 00:00:00 2001 From: HoonBaek Date: Fri, 19 May 2023 08:17:34 +0900 Subject: [PATCH 23/29] Remove unused props --- src/ui/EmojiReactions/ReactionItem.tsx | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/ui/EmojiReactions/ReactionItem.tsx b/src/ui/EmojiReactions/ReactionItem.tsx index efc21318e..9b3fe22b4 100644 --- a/src/ui/EmojiReactions/ReactionItem.tsx +++ b/src/ui/EmojiReactions/ReactionItem.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useContext } from 'react'; +import React, { useContext } from 'react'; import { FileMessage, Reaction, UserMessage } from '@sendbird/chat/message'; @@ -63,15 +63,11 @@ export default function ReactionItem({ ) : <>} >
Date: Tue, 23 May 2023 11:55:27 +0900 Subject: [PATCH 24/29] Create SpaceFromTriggerType and apply it --- src/types.d.ts | 8 ++++++++ src/ui/EmojiReactions/index.tsx | 6 +++--- src/ui/MessageItemReactionMenu/index.tsx | 5 +++-- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/types.d.ts b/src/types.d.ts index 819521d8d..21e16fc6f 100644 --- a/src/types.d.ts +++ b/src/types.d.ts @@ -9,6 +9,14 @@ import type { export type ReplyType = 'NONE' | 'QUOTE_REPLY' | 'THREAD'; export type Nullable = T | null; +export type SpaceFromTriggerType = { + x: number, + y: number, + top?: number, + left?: number, + height?: number, +}; + export interface UserListQuery { hasNext?: boolean; next(): Promise>; diff --git a/src/ui/EmojiReactions/index.tsx b/src/ui/EmojiReactions/index.tsx index f57394beb..143a6e6bf 100644 --- a/src/ui/EmojiReactions/index.tsx +++ b/src/ui/EmojiReactions/index.tsx @@ -9,7 +9,7 @@ import ReactionButton from '../ReactionButton'; import ImageRenderer from '../ImageRenderer'; import Icon, { IconTypes, IconColors } from '../Icon'; import ContextMenu, { EmojiListItems } from '../ContextMenu'; -import { Nullable } from '../../types'; +import { Nullable, SpaceFromTriggerType } from '../../types'; import { getClassName, getEmojiListAll, getEmojiMapAll } from '../../utils'; import { ReactedMembersBottomSheet } from '../MobileMenu/ReactedMembersBottomSheet'; @@ -25,7 +25,7 @@ interface Props { channel: Nullable; emojiContainer: EmojiContainer; memberNicknamesMap: Map; - spaceFromTrigger?: { x: number, y: number }; + spaceFromTrigger?: SpaceFromTriggerType; isByMe?: boolean; toggleReaction?: (message: UserMessage | FileMessage, key: string, byMe: boolean) => void; } @@ -93,7 +93,7 @@ const EmojiReactions = ({ parentRef={addReactionRef} parentContainRef={addReactionRef} closeDropdown={closeDropdown} - spacefromTrigger={spaceFromTrigger} + spaceFromTrigger={spaceFromTrigger} > {getEmojiListAll(emojiContainer).map((emoji: Emoji): ReactElement => { const isReacted: boolean = (message?.reactions diff --git a/src/ui/MessageItemReactionMenu/index.tsx b/src/ui/MessageItemReactionMenu/index.tsx index e294c0c53..b4536891d 100644 --- a/src/ui/MessageItemReactionMenu/index.tsx +++ b/src/ui/MessageItemReactionMenu/index.tsx @@ -9,12 +9,13 @@ import IconButton from '../IconButton'; import ImageRenderer from '../ImageRenderer'; import ReactionButton from '../ReactionButton'; import { getClassName, getEmojiListAll, isPendingMessage, isFailedMessage } from '../../utils'; +import { SpaceFromTriggerType } from '../../types'; interface Props { className?: string | Array; message: UserMessage | FileMessage; userId: string; - spaceFromTrigger?: Record; + spaceFromTrigger?: SpaceFromTriggerType; emojiContainer?: EmojiContainer; toggleReaction?: (message: UserMessage | FileMessage, reactionKey: string, isReacted: boolean) => void; setSupposedHover?: (bool: boolean) => void; @@ -24,7 +25,7 @@ export default function MessageItemReactionMenu({ className, message, userId, - spaceFromTrigger = {}, + spaceFromTrigger = { x: 0, y: 0 }, emojiContainer, toggleReaction, setSupposedHover, From 331c48535749bb28b04bf99fb3198afa8b840bec Mon Sep 17 00:00:00 2001 From: HoonBaek Date: Tue, 23 May 2023 11:57:03 +0900 Subject: [PATCH 25/29] Prevent non-nullable type to parentRef of EmojiListItems comp --- src/ui/ContextMenu/EmojiListItems.tsx | 68 +++++++++++++++------------ 1 file changed, 37 insertions(+), 31 deletions(-) diff --git a/src/ui/ContextMenu/EmojiListItems.tsx b/src/ui/ContextMenu/EmojiListItems.tsx index 7fee53948..eb8d3cceb 100644 --- a/src/ui/ContextMenu/EmojiListItems.tsx +++ b/src/ui/ContextMenu/EmojiListItems.tsx @@ -2,26 +2,28 @@ import React, { ReactElement, ReactNode, RefObject, useEffect, useRef, useState import { createPortal } from 'react-dom'; import SortByRow from '../SortByRow'; +import { Nullable } from '../../types'; +const defaultParentRect = { x: 0, y: 0, left: 0, top: 0, height: 0 }; type SpaceFromTrigger = { x: number, y: number }; type ReactionStyle = { left: number, top: number }; export interface EmojiListItemsProps { closeDropdown: () => void; children: ReactNode; - parentRef: RefObject | null; + parentRef: RefObject; parentContainRef: RefObject; spaceFromTrigger?: SpaceFromTrigger; } -const EmojiListItems = ({ +export const EmojiListItems = ({ children, parentRef, parentContainRef, spaceFromTrigger = { x: 0, y: 0 }, closeDropdown, -}: EmojiListItemsProps): ReactElement => { +}: EmojiListItemsProps): Nullable => { const [reactionStyle, setReactionStyle] = useState({ left: 0, top: 0 }); - const reactionRef = useRef(null); + const reactionRef: RefObject = useRef(null); /* showParent & hideParent */ useEffect(() => { @@ -52,7 +54,7 @@ const EmojiListItems = ({ useEffect(() => { const spaceFromTriggerX = spaceFromTrigger?.x || 0; const spaceFromTriggerY = spaceFromTrigger?.y || 0; - const parentRect = parentRef?.current?.getBoundingClientRect(); + const parentRect = parentRef?.current?.getBoundingClientRect() ?? defaultParentRect; const x = parentRect.x || parentRect.left; const y = parentRect.y || parentRect.top; const reactionStyle = { @@ -84,33 +86,37 @@ const EmojiListItems = ({ } }, []); - return ( - createPortal( - <> -
-
    - +
    +
      - {children} - -
    - , - document.getElementById('sendbird-emoji-list-portal'), - ) - ); + + {children} + +
+ , + rootElement, + ) + ); + } + return null; }; export default EmojiListItems; From 81d35dd53fee56a768922403fc4f6d262579cfa5 Mon Sep 17 00:00:00 2001 From: HoonBaek Date: Tue, 23 May 2023 12:00:55 +0900 Subject: [PATCH 26/29] Improve the readability of util func: getEmojiUrl --- src/utils/index.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/utils/index.ts b/src/utils/index.ts index bd596b51d..07f504b48 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -320,12 +320,13 @@ export const getEmojiMapAll = (emojiContainer: EmojiContainer): Map ({ key }: { key: string }) => key === targetKey; +const findEmojiUrl = (targetKey: string) => ({ key }) => key === targetKey; export const getEmojiUrl = (emojiContainer?: EmojiContainer, emojiKey?: string): string => { - const isFindingKey = findingEmojiUrl(emojiKey ?? ''); + const isFindingKey = findEmojiUrl(emojiKey ?? ''); return emojiContainer?.emojiCategories .find((category) => category.emojis.some(isFindingKey))?.emojis - .find(isFindingKey)?.url || ''; + .find(isFindingKey) + ?.url || ''; }; export const getUserName = (user: User): string => (user?.friendName || user?.nickname || user?.userId); From 9e54c0ff7a83aabc96e6cb98fc47ea9e10764064 Mon Sep 17 00:00:00 2001 From: HoonBaek Date: Tue, 23 May 2023 14:13:21 +0900 Subject: [PATCH 27/29] Not use memo ReactorList inside of the ReactedMembersBottomSheet comp --- .../MobileMenu/ReactedMembersBottomSheet.tsx | 36 +++++++++---------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/src/ui/MobileMenu/ReactedMembersBottomSheet.tsx b/src/ui/MobileMenu/ReactedMembersBottomSheet.tsx index b31e81df3..ae7de1b4a 100644 --- a/src/ui/MobileMenu/ReactedMembersBottomSheet.tsx +++ b/src/ui/MobileMenu/ReactedMembersBottomSheet.tsx @@ -30,24 +30,6 @@ export const ReactedMembersBottomSheet = ({ const { members = [] } = channel; const [selectedEmoji, setSelectedEmoji] = useState(emojiKey); - const ReactorList = useMemo(() => { - const memberList = message.reactions?.find(reaction => reaction.key === selectedEmoji) - ?.userIds.map((userId) => members.find((member) => member.userId === userId) ?? null) - .filter((member) => member !== null) as Array; - return ( -
- {memberList?.map((member) => ( - - ))} -
- ); - }, [selectedEmoji, message.reactions]); - return (
@@ -90,7 +72,23 @@ export const ReactedMembersBottomSheet = ({ ); })}
- {ReactorList} +
+ { // making a member list who reacted to the message with the `selectedEmoji` + ( + message.reactions?.find(reaction => reaction.key === selectedEmoji) + ?.userIds.map((userId) => members.find((member) => member.userId === userId)) + .filter((member) => member !== undefined) as Array + ) + .map((member) => ( + + )) + } +
); From ee104d2005aa0f90b96393b7d5a45e0c60bd9075 Mon Sep 17 00:00:00 2001 From: HoonBaek Date: Tue, 23 May 2023 14:29:24 +0900 Subject: [PATCH 28/29] Not use memo browserSupportMeimeType --- src/hooks/VoiceRecorder/index.tsx | 16 ++++++---------- src/ui/EmojiReactions/AddReactionBadgeItem.tsx | 2 +- src/ui/MobileMenu/ReactedMembersBottomSheet.tsx | 2 +- src/utils/consts.ts | 1 + 4 files changed, 9 insertions(+), 12 deletions(-) diff --git a/src/hooks/VoiceRecorder/index.tsx b/src/hooks/VoiceRecorder/index.tsx index a7ae95d9c..437396f32 100644 --- a/src/hooks/VoiceRecorder/index.tsx +++ b/src/hooks/VoiceRecorder/index.tsx @@ -1,5 +1,6 @@ -import React, { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react'; +import React, { createContext, useCallback, useContext, useEffect, useState } from 'react'; import { + BROWSER_SUPPORT_MIME_TYPE_LIST, VOICE_MESSAGE_FILE_NAME, VOICE_MESSAGE_MIME_TYPE, VOICE_RECORDER_AUDIO_BITS, @@ -36,15 +37,10 @@ export const VoiceRecorderProvider = (props: VoiceRecorderProps): React.ReactEle const [mediaRecorder, setMediaRecorder] = useState(null); const [isRecordable, setIsRecordable] = useState(false); - const browserSupportMimeType: string = useMemo((): string => { - const mimeTypes = ['audio/webm', 'audio/mp4']; - const supportedMimeType = mimeTypes.find((mimeType) => MediaRecorder.isTypeSupported(mimeType)); - if (!supportedMimeType) { - logger.error('VoiceRecorder: Browser does not support mimeType', { mimeTypes }); - return ''; - } - return supportedMimeType; - }, []); + const browserSupportMimeType = BROWSER_SUPPORT_MIME_TYPE_LIST.find((mimeType) => MediaRecorder.isTypeSupported(mimeType)) ?? ''; + if (!browserSupportMimeType) { + logger.error('VoiceRecorder: Browser does not support mimeType', { mimmeTypes: BROWSER_SUPPORT_MIME_TYPE_LIST }); + } const [webAudioUtils, setWebAudioUtils] = useState(null); useEffect(() => { diff --git a/src/ui/EmojiReactions/AddReactionBadgeItem.tsx b/src/ui/EmojiReactions/AddReactionBadgeItem.tsx index 5e2a9c08d..2136b0bd1 100644 --- a/src/ui/EmojiReactions/AddReactionBadgeItem.tsx +++ b/src/ui/EmojiReactions/AddReactionBadgeItem.tsx @@ -25,7 +25,7 @@ export const AddReactionBadgeItem = ({ return (
Date: Tue, 23 May 2023 14:31:45 +0900 Subject: [PATCH 29/29] Rename setShowEmojisBottomSheet to setSelectedEmojiKey according to it's value type --- src/ui/EmojiReactions/ReactionItem.tsx | 12 ++++++------ src/ui/EmojiReactions/index.tsx | 10 +++++----- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/ui/EmojiReactions/ReactionItem.tsx b/src/ui/EmojiReactions/ReactionItem.tsx index 9b3fe22b4..4301818d2 100644 --- a/src/ui/EmojiReactions/ReactionItem.tsx +++ b/src/ui/EmojiReactions/ReactionItem.tsx @@ -18,7 +18,7 @@ import { Emoji } from '@sendbird/chat'; type Props = { reaction: Reaction; memberNicknamesMap: Map; - setShowEmojisBottomSheet: React.Dispatch>; + setEmojiKey: React.Dispatch>; toggleReaction?: (message: UserMessage | FileMessage, key: string, byMe: boolean) => void; emojisMap: Map; }; @@ -26,7 +26,7 @@ type Props = { export default function ReactionItem({ reaction, memberNicknamesMap, - setShowEmojisBottomSheet, + setEmojiKey, toggleReaction, emojisMap, }: Props) { @@ -40,12 +40,12 @@ export default function ReactionItem({ const reactedByMe = isReactedBy(userId, reaction); const handleOnClick = () => { - setShowEmojisBottomSheet(''); + setEmojiKey(''); toggleReaction?.((message), reaction.key, reactedByMe); }; const longPress = useLongPress({ onLongPress: () => { - setShowEmojisBottomSheet(reaction.key); + setEmojiKey(reaction.key); }, onClick: handleOnClick, }, { @@ -56,7 +56,7 @@ export default function ReactionItem({ return ( 0) ? ( + hoverTooltip={(reaction.userIds.length > 0) ? ( {getEmojiTooltipString(reaction, userId, memberNicknamesMap, stringSet)} @@ -65,7 +65,7 @@ export default function ReactionItem({
diff --git a/src/ui/EmojiReactions/index.tsx b/src/ui/EmojiReactions/index.tsx index 143a6e6bf..ebdd5d144 100644 --- a/src/ui/EmojiReactions/index.tsx +++ b/src/ui/EmojiReactions/index.tsx @@ -44,7 +44,7 @@ const EmojiReactions = ({ const { isMobile } = useMediaQueryContext(); const addReactionRef = useRef(null); const [showEmojiList, setShowEmojiList] = useState(false); - const [showEmojisBottomSheet, setShowEmojisBottomSheet] = useState(''); + const [selectedEmojiKey, setSelectedEmojiKey] = useState(''); const emojisMap = getEmojiMapAll(emojiContainer); const showAddReactionBadge = (message.reactions?.length ?? 0) < emojisMap.size; @@ -61,7 +61,7 @@ const EmojiReactions = ({ key={reaction?.key} reaction={reaction} memberNicknamesMap={memberNicknamesMap} - setShowEmojisBottomSheet={setShowEmojisBottomSheet} + setEmojiKey={setSelectedEmojiKey} toggleReaction={toggleReaction} emojisMap={emojisMap} /> @@ -151,13 +151,13 @@ const EmojiReactions = ({ toggleReaction={toggleReaction} /> )} - {(isMobile && showEmojisBottomSheet && channel !== null) && ( + {(isMobile && selectedEmojiKey && channel !== null) && ( { - setShowEmojisBottomSheet(''); + setSelectedEmojiKey(''); }} emojiContainer={emojiContainer} />