From 166fc3165176ffe3293ab258dc57e80361344a4f Mon Sep 17 00:00:00 2001 From: HoonBaek Date: Mon, 8 May 2023 11:25:42 +0900 Subject: [PATCH 01/10] Fix type script warnings --- src/modules/App/AppLayout.tsx | 6 +++--- src/modules/App/DesktopLayout.tsx | 18 ++++++++--------- src/modules/App/MobileLayout.tsx | 4 ++-- src/modules/App/types.ts | 20 +++++++++---------- .../Channel/context/ChannelProvider.tsx | 6 +++--- src/modules/MessageSearch/index.tsx | 2 +- src/modules/Thread/context/ThreadProvider.tsx | 2 +- src/modules/Thread/context/utils.ts | 7 +++++-- 8 files changed, 34 insertions(+), 31 deletions(-) diff --git a/src/modules/App/AppLayout.tsx b/src/modules/App/AppLayout.tsx index 64fc09877..3109a5686 100644 --- a/src/modules/App/AppLayout.tsx +++ b/src/modules/App/AppLayout.tsx @@ -22,11 +22,11 @@ export const AppLayout: React.FC = ( setCurrentChannel, } = props; const [showThread, setShowThread] = useState(false); - const [threadTargetMessage, setThreadTargetMessage] = useState(null); + const [threadTargetMessage, setThreadTargetMessage] = useState(null); const [showSettings, setShowSettings] = useState(false); const [showSearch, setShowSearch] = useState(false); - const [highlightedMessage, setHighlightedMessage] = useState(null); - const [startingPoint, setStartingPoint] = useState(null); + const [highlightedMessage, setHighlightedMessage] = useState(null); + const [startingPoint, setStartingPoint] = useState(null); const { isMobile } = useMediaQueryContext(); return ( <> diff --git a/src/modules/App/DesktopLayout.tsx b/src/modules/App/DesktopLayout.tsx index d17042e3f..d4f909cbe 100644 --- a/src/modules/App/DesktopLayout.tsx +++ b/src/modules/App/DesktopLayout.tsx @@ -34,7 +34,7 @@ export const DesktopLayout: React.FC = ( threadTargetMessage, setThreadTargetMessage, } = props; - const [animatedMessageId, setAnimatedMessageId] = useState(null); + const [animatedMessageId, setAnimatedMessageId] = useState(null); return (
@@ -44,8 +44,8 @@ export const DesktopLayout: React.FC = ( onProfileEditSuccess={onProfileEditSuccess} disableAutoSelect={disableAutoSelect} onChannelSelect={(channel) => { - setStartingPoint(null); - setHighlightedMessage(null); + setStartingPoint?.(null); + setHighlightedMessage?.(null); if (channel) { setCurrentChannel(channel); } else { @@ -93,7 +93,7 @@ export const DesktopLayout: React.FC = ( setAnimatedMessageId(null); }} onMessageHighlighted={() => { - setHighlightedMessage(null); + setHighlightedMessage?.(null); }} showSearchIcon={showSearchIcon} startingPoint={startingPoint} @@ -121,13 +121,13 @@ export const DesktopLayout: React.FC = ( channelUrl={currentChannel?.url || ''} onResultClick={(message) => { if (message.messageId === highlightedMessage) { - setHighlightedMessage(null); + setHighlightedMessage?.(null); setTimeout(() => { - setHighlightedMessage(message.messageId); + setHighlightedMessage?.(message.messageId); }); } else { - setStartingPoint(message.createdAt); - setHighlightedMessage(message.messageId); + setStartingPoint?.(message.createdAt); + setHighlightedMessage?.(message.messageId); } }} onCloseClick={() => { @@ -149,7 +149,7 @@ export const DesktopLayout: React.FC = ( setCurrentChannel(channel); } if (message?.messageId !== animatedMessageId) { - setStartingPoint(message?.createdAt); + setStartingPoint?.(message?.createdAt); } setTimeout(() => { setAnimatedMessageId(message?.messageId); diff --git a/src/modules/App/MobileLayout.tsx b/src/modules/App/MobileLayout.tsx index b91f06042..bff72a710 100644 --- a/src/modules/App/MobileLayout.tsx +++ b/src/modules/App/MobileLayout.tsx @@ -45,9 +45,9 @@ export const MobileLayout: React.FC = ( const userId = store?.config?.userId; const goToMessage = (message?: BaseMessage | null) => { - setStartingPoint(message?.createdAt); + setStartingPoint?.(message?.createdAt || null); setTimeout(() => { - setHighlightedMessage(message?.messageId); + setHighlightedMessage?.(message?.messageId || null); }); }; diff --git a/src/modules/App/types.ts b/src/modules/App/types.ts index e8f07a033..1c6e948e6 100644 --- a/src/modules/App/types.ts +++ b/src/modules/App/types.ts @@ -20,14 +20,14 @@ export interface AppLayoutProps { onProfileEditSuccess?(user: User): void; disableAutoSelect?: boolean; currentChannel?: GroupChannel; - setCurrentChannel: React.Dispatch; + setCurrentChannel: React.Dispatch; } export interface MobileLayoutProps extends AppLayoutProps { - highlightedMessage?: number; - setHighlightedMessage?: React.Dispatch; - startingPoint?: number; - setStartingPoint?: React.Dispatch; + highlightedMessage?: number | null; + setHighlightedMessage?: React.Dispatch; + startingPoint?: number | null; + setStartingPoint?: React.Dispatch; } export interface DesktopLayoutProps extends AppLayoutProps { @@ -35,13 +35,13 @@ export interface DesktopLayoutProps extends AppLayoutProps { setShowSettings: React.Dispatch; showSearch: boolean; setShowSearch: React.Dispatch; - highlightedMessage?: number; - setHighlightedMessage?: React.Dispatch; - startingPoint?: number; - setStartingPoint?: React.Dispatch; + highlightedMessage?: number | null; + setHighlightedMessage?: React.Dispatch; + startingPoint?: number | null; + setStartingPoint?: React.Dispatch; showThread: boolean; setShowThread: React.Dispatch; - threadTargetMessage: UserMessage | FileMessage; + threadTargetMessage: UserMessage | FileMessage | null; setThreadTargetMessage: React.Dispatch; } diff --git a/src/modules/Channel/context/ChannelProvider.tsx b/src/modules/Channel/context/ChannelProvider.tsx index 09b3fa4df..f3fcb6e25 100644 --- a/src/modules/Channel/context/ChannelProvider.tsx +++ b/src/modules/Channel/context/ChannelProvider.tsx @@ -77,9 +77,9 @@ export type ChannelContextProps = { isReactionEnabled?: boolean; isMessageGroupingEnabled?: boolean; showSearchIcon?: boolean; - animatedMessage?: number; - highlightedMessage?: number; - startingPoint?: number; + animatedMessage?: number | null; + highlightedMessage?: number | null; + startingPoint?: number | null; onBeforeSendUserMessage?(text: string, quotedMessage?: UserMessage | FileMessage): UserMessageCreateParams; onBeforeSendFileMessage?(file: File, quotedMessage?: UserMessage | FileMessage): FileMessageCreateParams; onBeforeUpdateUserMessage?(text: string): UserMessageUpdateParams; diff --git a/src/modules/MessageSearch/index.tsx b/src/modules/MessageSearch/index.tsx index 1586c9192..7b6787462 100644 --- a/src/modules/MessageSearch/index.tsx +++ b/src/modules/MessageSearch/index.tsx @@ -35,7 +35,7 @@ function MessageSearchPannel(props: MessageSearchPannelProps): JSX.Element { const [loading, setLoading] = useState(false); const { stringSet } = useContext(LocalizationContext); - let timeout = null; + let timeout: any = null; useEffect(() => { if (timeout) { clearTimeout(timeout); diff --git a/src/modules/Thread/context/ThreadProvider.tsx b/src/modules/Thread/context/ThreadProvider.tsx index eb960aae5..167392f39 100644 --- a/src/modules/Thread/context/ThreadProvider.tsx +++ b/src/modules/Thread/context/ThreadProvider.tsx @@ -31,7 +31,7 @@ import useSendVoiceMessageCallback from './hooks/useSendVoiceMessageCallback'; export type ThreadProviderProps = { children?: React.ReactElement; channelUrl: string; - message: UserMessage | FileMessage; + message: UserMessage | FileMessage | null; onHeaderActionClick?: () => void; onMoveToParentMessage?: (props: { message: UserMessage | FileMessage, channel: GroupChannel }) => void; onBeforeSendVoiceMessage?: (file: File, quotedMessage?: UserMessage | FileMessage) => FileMessageCreateParams; diff --git a/src/modules/Thread/context/utils.ts b/src/modules/Thread/context/utils.ts index 9fa81d3bd..2d95b473b 100644 --- a/src/modules/Thread/context/utils.ts +++ b/src/modules/Thread/context/utils.ts @@ -12,12 +12,15 @@ export const getNicknamesMapFromMembers = (members = []): Map => return nicknamesMap; }; -export const getParentMessageFrom = (message: UserMessage | FileMessage): UserMessage | FileMessage | BaseMessage => { +export const getParentMessageFrom = (message: UserMessage | FileMessage | null): UserMessage | FileMessage | BaseMessage | null => { + if (!message) { + return null; + } if (isParentMessage(message)) { return message; } if (isThreadMessage(message)) { - return message?.parentMessage; + return message?.parentMessage || null; } return null; }; From 2f4b6c9f37a3f1272aad8f0eee12189df5857a2c Mon Sep 17 00:00:00 2001 From: HoonBaek Date: Mon, 8 May 2023 11:26:17 +0900 Subject: [PATCH 02/10] Set the mobile size break point to 768px --- src/lib/MediaQueryContext.tsx | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/lib/MediaQueryContext.tsx b/src/lib/MediaQueryContext.tsx index af7987727..42122b9e2 100644 --- a/src/lib/MediaQueryContext.tsx +++ b/src/lib/MediaQueryContext.tsx @@ -1,7 +1,7 @@ import React, { useEffect, useState } from 'react'; import type { Logger } from './SendbirdState'; -const DEFAULT_MOBILE = '0px'; +const DEFAULT_MOBILE = '768px'; const MOBILE_CLASSNAME = 'sendbird--mobile-mode'; const MediaQueryContext = React.createContext({ @@ -11,14 +11,14 @@ const MediaQueryContext = React.createContext({ export interface MediaQueryProviderProps { children: React.ReactElement; - mediaQueryBreakPoint?: string | boolean; + mediaQueryBreakPoint?: string; logger?: Logger; } const addClassNameToBody = () => { try { const body = document.querySelector('body'); - body.classList.add(MOBILE_CLASSNAME); + body?.classList.add(MOBILE_CLASSNAME); } catch { // noop } @@ -27,7 +27,7 @@ const addClassNameToBody = () => { const removeClassNameFromBody = () => { try { const body = document.querySelector('body'); - body.classList.remove(MOBILE_CLASSNAME); + body?.classList.remove(MOBILE_CLASSNAME); } catch { // noop } @@ -67,11 +67,11 @@ const MediaQueryProvider = (props: MediaQueryProviderProps): React.ReactElement }; updateSize(); window.addEventListener('resize', updateSize); - logger?.info?.('MediaQueryProvider: addEventListener', updateSize); + logger?.info?.('MediaQueryProvider: addEventListener', { updateSize }); return () => { window.removeEventListener('resize', updateSize); - logger?.info?.('MediaQueryProvider: removeEventListener', updateSize); - }; + logger?.info?.('MediaQueryProvider: removeEventListener', { updateSize }); + } }, [mediaQueryBreakPoint]); return ( From d15e87b8732a81d9287fd36372be01116db3e7bd Mon Sep 17 00:00:00 2001 From: HoonBaek Date: Tue, 9 May 2023 13:43:02 +0900 Subject: [PATCH 03/10] Add THREAD to the Panels options of the MobileLayout --- src/modules/App/MobileLayout.tsx | 40 ++++++++++++++++++++++--- src/ui/MobileMenu/MobileBottomSheet.tsx | 2 +- 2 files changed, 37 insertions(+), 5 deletions(-) diff --git a/src/modules/App/MobileLayout.tsx b/src/modules/App/MobileLayout.tsx index bff72a710..42274e556 100644 --- a/src/modules/App/MobileLayout.tsx +++ b/src/modules/App/MobileLayout.tsx @@ -11,6 +11,7 @@ import ChannelList from '../ChannelList'; import Channel from '../Channel'; import ChannelSettings from '../ChannelSettings'; import MessageSearch from '../MessageSearch'; +import Thread from '../Thread'; import useSendbirdStateContext from '../../hooks/useSendbirdStateContext'; import uuidv4 from '../../utils/uuid'; @@ -19,6 +20,7 @@ enum PANELS { CHANNEL = 'CHANNEL', CHANNEL_SETTINGS = 'CHANNEL_SETTINGS', MESSAGE_SEARCH = 'MESSAGE_SEARCH', + THREAD = 'THREAD', } export const MobileLayout: React.FC = ( @@ -39,16 +41,17 @@ export const MobileLayout: React.FC = ( setStartingPoint, } = props; const [panel, setPanel] = useState(PANELS?.CHANNEL_LIST); + const [animatedMessageId, setAnimatedMessageId] = useState(null); const store = useSendbirdStateContext(); const sdk = store?.stores?.sdkStore?.sdk as SendbirdGroupChat; const userId = store?.config?.userId; - const goToMessage = (message?: BaseMessage | null) => { + const goToMessage = (message?: BaseMessage | null, timeoutCb?: (msgId: number | null) => void) => { setStartingPoint?.(message?.createdAt || null); setTimeout(() => { - setHighlightedMessage?.(message?.messageId || null); - }); + timeoutCb?.(message?.messageId || null); + }, 500); }; useEffect(() => { @@ -118,6 +121,7 @@ export const MobileLayout: React.FC = ( showSearchIcon={showSearchIcon} isMessageGroupingEnabled={isMessageGroupingEnabled} startingPoint={startingPoint} + animatedMessage={animatedMessageId} highlightedMessage={highlightedMessage} onChatHeaderActionClick={() => { setPanel(PANELS.CHANNEL_SETTINGS); @@ -151,7 +155,35 @@ export const MobileLayout: React.FC = ( }} onResultClick={(message) => { setPanel(PANELS.CHANNEL); - goToMessage(message); + goToMessage(message, (messageId) => { + setHighlightedMessage?.(messageId); + }); + }} + /> +
+ ) + } + { + panel === PANELS?.THREAD && ( +
+ { + setPanel(PANELS.CHANNEL); + }} + onMoveToParentMessage={({ message, channel }) => { + if (channel?.url !== currentChannel?.url) { + setPanel(PANELS.CHANNEL); + } + if (message?.messageId !== animatedMessageId) { + goToMessage(message, (messageId) => { + setAnimatedMessageId(messageId); + }); + } + setTimeout(() => { + setAnimatedMessageId(message?.messageId); + }, 500); }} />
diff --git a/src/ui/MobileMenu/MobileBottomSheet.tsx b/src/ui/MobileMenu/MobileBottomSheet.tsx index a7f1dc77f..0bb964df3 100644 --- a/src/ui/MobileMenu/MobileBottomSheet.tsx +++ b/src/ui/MobileMenu/MobileBottomSheet.tsx @@ -1,6 +1,6 @@ import type { Emoji } from '@sendbird/chat'; import { FileMessage, Reaction, UserMessage } from '@sendbird/chat/message'; -import React, { useState } from 'react'; +import React, { ReactElement, useState } from 'react'; import type { MobileBottomSheetProps } from './types'; import type { GroupChannel } from '@sendbird/chat/groupChannel'; From c77c9c0c1e5b5ad3dbf97e7ef961dbb2135b0129 Mon Sep 17 00:00:00 2001 From: HoonBaek Date: Thu, 11 May 2023 09:39:26 +0900 Subject: [PATCH 04/10] Add a new Icon Thread --- src/svgs/icon-thread.svg | 4 ++++ src/ui/Icon/index.jsx | 2 ++ src/ui/Icon/type.ts | 1 + src/ui/Icon/utils.ts | 1 + 4 files changed, 8 insertions(+) create mode 100644 src/svgs/icon-thread.svg diff --git a/src/svgs/icon-thread.svg b/src/svgs/icon-thread.svg new file mode 100644 index 000000000..3d1b6e9c0 --- /dev/null +++ b/src/svgs/icon-thread.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/ui/Icon/index.jsx b/src/ui/Icon/index.jsx index ad10a75d2..03c51e91e 100644 --- a/src/ui/Icon/index.jsx +++ b/src/ui/Icon/index.jsx @@ -61,6 +61,7 @@ import IconSend from '../../svgs/icon-send.svg'; import IconSettingsFilled from '../../svgs/icon-settings-filled.svg'; import IconSpinner from '../../svgs/icon-spinner.svg'; import IconSupergroup from '../../svgs/icon-supergroup.svg'; +import IconThread from '../../svgs/icon-thread.svg'; import IconThumbnailNone from '../../svgs/icon-thumbnail-none.svg'; import IconToggleOff from '../../svgs/icon-toggleoff.svg'; import IconToggleOn from '../../svgs/icon-toggleon.svg'; @@ -120,6 +121,7 @@ function changeTypeToIconComponent(type) { case Types.SETTINGS_FILLED: return ; case Types.SPINNER: return ; case Types.SUPERGROUP: return ; + case Types.THREAD: return ; case Types.THUMBNAIL_NONE: return ; case Types.TOGGLE_OFF: return ; case Types.TOGGLE_ON: return ; diff --git a/src/ui/Icon/type.ts b/src/ui/Icon/type.ts index 63713eafa..5f6cae1d1 100644 --- a/src/ui/Icon/type.ts +++ b/src/ui/Icon/type.ts @@ -52,6 +52,7 @@ export const Types = { SETTINGS_FILLED: 'SETTINGS_FILLED', SPINNER: 'SPINNER', SUPERGROUP: 'SUPERGROUP', + THREAD: 'THREAD', THUMBNAIL_NONE: 'THUMBNAIL_NONE', TOGGLE_OFF: 'TOGGLE_OFF', TOGGLE_ON: 'TOGGLE_ON', diff --git a/src/ui/Icon/utils.ts b/src/ui/Icon/utils.ts index 0c90cad87..aae56a3dd 100644 --- a/src/ui/Icon/utils.ts +++ b/src/ui/Icon/utils.ts @@ -76,6 +76,7 @@ export function changeTypeToIconClassName(type: Types): string { case Types.SETTINGS_FILLED: return 'sendbird-icon-settings-filled'; case Types.SPINNER: return 'sendbird-icon-spinner'; case Types.SUPERGROUP: return 'sendbird-icon-supergroup'; + case Types.THREAD: return 'sendbird-icon-thread'; case Types.THUMBNAIL_NONE: return 'sendbird-icon-thumbnail-none'; case Types.TOGGLE_OFF: return 'sendbird-icon-toggle-off'; case Types.TOGGLE_ON: return 'sendbird-icon-toggle-on'; From 1e6a086d85c4b659476d4f8c1a14d1adfdbaeb33 Mon Sep 17 00:00:00 2001 From: HoonBaek Date: Thu, 11 May 2023 09:40:03 +0900 Subject: [PATCH 05/10] Disable user touch and select the text action --- src/ui/Label/index.scss | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/ui/Label/index.scss b/src/ui/Label/index.scss index c9fe492a3..5c2fc6857 100644 --- a/src/ui/Label/index.scss +++ b/src/ui/Label/index.scss @@ -1,5 +1,10 @@ @import '../../styles/variables'; +.sendbird-label { + -webkit-user-select: none; + -webkit-touch-callout: none; +} + [class*=sendbird-label] { font-family: var(--sendbird-font-family-default); } From 156cb98aa63ecd4d6de44f57625d003f63c99044 Mon Sep 17 00:00:00 2001 From: HoonBaek Date: Thu, 11 May 2023 09:42:07 +0900 Subject: [PATCH 06/10] Use thread target message in MobileLayout --- src/modules/App/AppLayout.tsx | 2 ++ src/modules/App/MobileLayout.tsx | 16 +++++++++++++++- src/modules/App/types.ts | 16 ++++++++-------- 3 files changed, 25 insertions(+), 9 deletions(-) diff --git a/src/modules/App/AppLayout.tsx b/src/modules/App/AppLayout.tsx index 3109a5686..99424f1d2 100644 --- a/src/modules/App/AppLayout.tsx +++ b/src/modules/App/AppLayout.tsx @@ -46,6 +46,8 @@ export const AppLayout: React.FC = ( setHighlightedMessage={setHighlightedMessage} startingPoint={startingPoint} setStartingPoint={setStartingPoint} + threadTargetMessage={threadTargetMessage} + setThreadTargetMessage={setThreadTargetMessage} /> ) : ( diff --git a/src/modules/App/MobileLayout.tsx b/src/modules/App/MobileLayout.tsx index 42274e556..8531c2f3f 100644 --- a/src/modules/App/MobileLayout.tsx +++ b/src/modules/App/MobileLayout.tsx @@ -39,6 +39,8 @@ export const MobileLayout: React.FC = ( setHighlightedMessage, startingPoint, setStartingPoint, + threadTargetMessage, + setThreadTargetMessage, } = props; const [panel, setPanel] = useState(PANELS?.CHANNEL_LIST); const [animatedMessageId, setAnimatedMessageId] = useState(null); @@ -126,6 +128,18 @@ export const MobileLayout: React.FC = ( onChatHeaderActionClick={() => { setPanel(PANELS.CHANNEL_SETTINGS); }} + onReplyInThread={({ message }) => { + if (replyType === 'THREAD') { + setPanel(PANELS.THREAD); + setThreadTargetMessage(message); + } + }} + onQuoteMessageClick={({ message }) => { // thread message + if (replyType === 'THREAD') { + setThreadTargetMessage(message); + setPanel(PANELS.THREAD); + } + }} />
) @@ -168,7 +182,7 @@ export const MobileLayout: React.FC = (
{ setPanel(PANELS.CHANNEL); }} diff --git a/src/modules/App/types.ts b/src/modules/App/types.ts index 1c6e948e6..be53199fe 100644 --- a/src/modules/App/types.ts +++ b/src/modules/App/types.ts @@ -23,26 +23,26 @@ export interface AppLayoutProps { setCurrentChannel: React.Dispatch; } -export interface MobileLayoutProps extends AppLayoutProps { +interface SubLayoutCommonProps { highlightedMessage?: number | null; setHighlightedMessage?: React.Dispatch; startingPoint?: number | null; setStartingPoint?: React.Dispatch; + threadTargetMessage: UserMessage | FileMessage | null; + setThreadTargetMessage: React.Dispatch; } -export interface DesktopLayoutProps extends AppLayoutProps { +export interface MobileLayoutProps extends AppLayoutProps, SubLayoutCommonProps { } + +export interface DesktopLayoutProps extends AppLayoutProps, SubLayoutCommonProps { + // modertion pannel showSettings: boolean; setShowSettings: React.Dispatch; showSearch: boolean; setShowSearch: React.Dispatch; - highlightedMessage?: number | null; - setHighlightedMessage?: React.Dispatch; - startingPoint?: number | null; - setStartingPoint?: React.Dispatch; + // thread showThread: boolean; setShowThread: React.Dispatch; - threadTargetMessage: UserMessage | FileMessage | null; - setThreadTargetMessage: React.Dispatch; } export default interface AppProps { From 8cf3b6d246befc986814e7cd76eae3e76c5c9fe4 Mon Sep 17 00:00:00 2001 From: HoonBaek Date: Thu, 11 May 2023 09:44:54 +0900 Subject: [PATCH 07/10] Make Thread page to fit the mobile screen --- src/modules/App/mobile.scss | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/modules/App/mobile.scss b/src/modules/App/mobile.scss index 99bbbc2df..e46553b57 100644 --- a/src/modules/App/mobile.scss +++ b/src/modules/App/mobile.scss @@ -6,3 +6,14 @@ .sb-show-main { padding: 0 !important; } + +.sb_mobile__panelwrap .sendbird-thread { + width: 100%; + height: 100%; + & .sendbird-thread-ui { + max-width: 100%; + & .sendbird-thread-ui__header { + width: 100%; + } + } +} From 951033e224a8c15d43d3285a959897b195736ad8 Mon Sep 17 00:00:00 2001 From: HoonBaek Date: Thu, 11 May 2023 09:47:07 +0900 Subject: [PATCH 08/10] Apply BottomSheet menu to the ParentMessageInfo and ThreadListItem --- .../components/ParentMessageInfo/index.tsx | 73 +++++++++++++++---- .../ThreadList/ThreadListItemContent.tsx | 44 ++++++++++- src/ui/MessageContent/index.tsx | 8 ++ src/ui/MobileMenu/MobileBottomSheet.tsx | 27 ++++++- src/ui/MobileMenu/index.tsx | 5 ++ src/ui/MobileMenu/types.ts | 2 + 6 files changed, 139 insertions(+), 20 deletions(-) diff --git a/src/modules/Thread/components/ParentMessageInfo/index.tsx b/src/modules/Thread/components/ParentMessageInfo/index.tsx index b80dda3a4..adc68799f 100644 --- a/src/modules/Thread/components/ParentMessageInfo/index.tsx +++ b/src/modules/Thread/components/ParentMessageInfo/index.tsx @@ -26,6 +26,9 @@ import { UserProfileContextInterface } from '../../../../ui/MessageContent'; import MessageInput from '../../../../ui/MessageInput'; import { MessageInputKeys } from '../../../../ui/MessageInput/const'; import { Role } from '../../../../lib/types'; +import { useMediaQueryContext } from '../../../../lib/MediaQueryContext'; +import useLongPress from '../../../../hooks/useLongPress'; +import MobileMenu from '../../../../ui/MobileMenu'; export interface ParentMessageInfoProps { className?: string; @@ -57,11 +60,24 @@ export default function ParentMessageInfo({ isMuted, isChannelFrozen, } = useThreadContext(); + const { isMobile } = useMediaQueryContext(); const [showRemove, setShowRemove] = useState(false); const [supposedHover, setSupposedHover] = useState(false); const [showFileViewer, setShowFileViewer] = useState(false); const usingReaction = isReactionEnabled && !currentChannel?.isSuper && !currentChannel?.isBroadcast; + const isByMe = userId === parentMessage.sender.userId; + + // Mobile + const mobileMenuRef = useRef(null); + const [showMobileMenu, setShowMobileMenu] = useState(false); + const longPress = useLongPress({ + onLongPress: () => { + if (isMobile) { + setShowMobileMenu(true); + } + }, + }); // Edit message const [showEditInput, setShowEditInput] = useState(false); @@ -192,7 +208,11 @@ export default function ParentMessageInfo({ } return ( -
+
(
{/* context menu */} - 0} - replyType={replyType} - showEdit={setShowEditInput} - showRemove={setShowRemove} - setSupposedHover={setSupposedHover} - onMoveToParentMessage={() => { - onMoveToParentMessage({ message: parentMessage, channel: currentChannel }); - }} - /> - {usingReaction && ( + {!isMobile && ( + 0} + replyType={replyType} + showEdit={setShowEditInput} + showRemove={setShowRemove} + setSupposedHover={setSupposedHover} + onMoveToParentMessage={() => { + onMoveToParentMessage({ message: parentMessage, channel: currentChannel }); + }} + /> + )} + {(usingReaction && !isMobile) && ( )} + {showMobileMenu && ( + { + setShowMobileMenu(false); + }} + isReactionEnabled={isReactionEnabled} + isByMe={isByMe} + emojiContainer={emojiContainer} + showEdit={setShowEditInput} + showRemove={setShowRemove} + toggleReaction={toggleReaction} + isOpenedFromThread + /> + )}
); } diff --git a/src/modules/Thread/components/ThreadList/ThreadListItemContent.tsx b/src/modules/Thread/components/ThreadList/ThreadListItemContent.tsx index 748c194a6..ed842c0ca 100644 --- a/src/modules/Thread/components/ThreadList/ThreadListItemContent.tsx +++ b/src/modules/Thread/components/ThreadList/ThreadListItemContent.tsx @@ -25,6 +25,9 @@ import FileMessageItemBody from '../../../../ui/FileMessageItemBody'; import ThumbnailMessageItemBody from '../../../../ui/ThumbnailMessageItemBody'; import UnknownMessageItemBody from '../../../../ui/UnknownMessageItemBody'; import VoiceMessageItemBody from '../../../../ui/VoiceMessageItemBody'; +import { useMediaQueryContext } from '../../../../lib/MediaQueryContext'; +import useLongPress from '../../../../hooks/useLongPress'; +import MobileMenu from '../../../../ui/MobileMenu'; export interface ThreadListItemContentProps { className?: string; @@ -70,6 +73,7 @@ export default function ThreadListItemContent({ onReplyInThread, }: ThreadListItemContentProps): React.ReactElement { const messageTypes = getUIKitMessageTypes(); + const { isMobile } = useMediaQueryContext(); const { dateLocale } = useLocalization(); const [supposedHover, setSupposedHover] = useState(false); const { @@ -88,8 +92,23 @@ export default function ThreadListItemContent({ const supposedHoverClassName = supposedHover ? 'sendbird-mouse-hover' : ''; const isReactionEnabledInChannel = isReactionEnabled && !channel?.isEphemeral; + // Mobile + const mobileMenuRef = useRef(null); + const [showMobileMenu, setShowMobileMenu] = useState(false); + const longPress = useLongPress({ + onLongPress: () => { + if (isMobile) { + setShowMobileMenu(true); + } + }, + }); + return ( -
+
{(!isByMe && !chainBottom) && ( )} - {isByMe && ( + {(isByMe && !isMobile) && (
- {!isByMe && ( + {(!isByMe && !isMobile) && (
{isReactionEnabledInChannel && ( )}
+ {showMobileMenu && ( + { + setShowMobileMenu(false); + }} + isReactionEnabled={isReactionEnabled} + isByMe={isByMe} + emojiContainer={emojiContainer} + showEdit={showEdit} + showRemove={showRemove} + toggleReaction={toggleReaction} + isOpenedFromThread + /> + )}
); } diff --git a/src/ui/MessageContent/index.tsx b/src/ui/MessageContent/index.tsx index 8ebd9ce4c..774697ff7 100644 --- a/src/ui/MessageContent/index.tsx +++ b/src/ui/MessageContent/index.tsx @@ -449,6 +449,14 @@ export default function MessageContent({ setQuoteMessage={setQuoteMessage} toggleReaction={toggleReaction} showEdit={showEdit} + onReplyInThread={({ message }) => { + if (threadReplySelectType === ThreadReplySelectType.THREAD) { + console.log('onreplyin therad is called', { onReplyInThread, message }) + onReplyInThread?.({ message }); + } else if (threadReplySelectType === ThreadReplySelectType.PARENT) { + scrollToMessage?.(message?.parentMessage?.createdAt || 0, message?.parentMessageId || 0); + } + }} /> ) } diff --git a/src/ui/MobileMenu/MobileBottomSheet.tsx b/src/ui/MobileMenu/MobileBottomSheet.tsx index 0bb964df3..b121d82d3 100644 --- a/src/ui/MobileMenu/MobileBottomSheet.tsx +++ b/src/ui/MobileMenu/MobileBottomSheet.tsx @@ -13,6 +13,7 @@ import { isUserMessage, copyToClipboard, isFileMessage, + isParentMessage, } from '../../utils'; import BottomSheet from '../BottomSheet'; import ImageRenderer from '../ImageRenderer'; @@ -37,6 +38,8 @@ const MobileBottomSheet: React.FunctionComponent = (prop showEdit, showRemove, setQuoteMessage, + onReplyInThread, + isOpenedFromThread = false, } = props; const isByMe = message?.sender?.userId === userId; const { stringSet } = useLocalization(); @@ -50,6 +53,10 @@ const MobileBottomSheet: React.FunctionComponent = (prop && !isFailedMessage(message) && !isPendingMessage(message) && (channel?.isGroupChannel() && !(channel as GroupChannel)?.isBroadcast); + const showMenuItemThread: boolean = (replyType === 'THREAD') && !isOpenedFromThread + && !isFailedMessage(message) + && !isPendingMessage(message) + && (channel?.isGroupChannel() && !(channel as GroupChannel)?.isBroadcast); const disableReaction = message?.parentMessageId > 0; const fileMessage = message as FileMessage; @@ -209,7 +216,6 @@ const MobileBottomSheet: React.FunctionComponent = (prop } { showMenuItemReply && ( -
= (prop
) } + {showMenuItemThread && ( +
{ + hideMenu(); + onReplyInThread?.({ message }); + }} + > + + +
+ )} { showMenuItemDelete && (
= (props: MobileBottomSheetPr emojiContainer, toggleReaction, parentRef, + onReplyInThread, + isOpenedFromThread, } = props; return ( <> @@ -43,6 +45,8 @@ const MobileMenu: React.FC = (props: MobileBottomSheetPr emojiContainer={emojiContainer} toggleReaction={toggleReaction} isReactionEnabled={isReactionEnabled} + onReplyInThread={onReplyInThread} + isOpenedFromThread={isOpenedFromThread} /> ) : ( = (props: MobileBottomSheetPr resendMessage={resendMessage} setQuoteMessage={setQuoteMessage} parentRef={parentRef} + onReplyInThread={onReplyInThread} /> ) } diff --git a/src/ui/MobileMenu/types.ts b/src/ui/MobileMenu/types.ts index 16b64b5f1..dff395825 100644 --- a/src/ui/MobileMenu/types.ts +++ b/src/ui/MobileMenu/types.ts @@ -22,6 +22,8 @@ export interface BaseMenuProps { setQuoteMessage?: (message: UserMessage | FileMessage) => void; isReactionEnabled?: boolean; parentRef?: React.RefObject; + onReplyInThread?: (props: { message: UserMessage | FileMessage }) => void; + isOpenedFromThread?: boolean; } export interface MobileBottomSheetProps extends BaseMenuProps { From b0c3b5f22b7cea891783f1747411dec9670e1252 Mon Sep 17 00:00:00 2001 From: HoonBaek Date: Thu, 11 May 2023 09:47:29 +0900 Subject: [PATCH 09/10] Add isParentMessage util func --- src/utils/index.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/utils/index.ts b/src/utils/index.ts index 4d4b3e41d..96dad8d2c 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -136,7 +136,9 @@ export const isUserMessage = (message: AdminMessage | UserMessage | FileMessage) export const isFileMessage = (message: AdminMessage | UserMessage | FileMessage): boolean => ( message && (message?.isFileMessage?.() || (message?.messageType === 'file')) ); - +export const isParentMessage = (message: AdminMessage | UserMessage | FileMessage): boolean => ( + !message.parentMessageId && !message.parentMessage && message.threadInfo !== null +); export const isOGMessage = (message: UserMessage | FileMessage): boolean => !!( message && isUserMessage(message) && message?.ogMetaData && ( message.ogMetaData?.url From da10fdcfd2912d9558725803d8ab98c1e555fa46 Mon Sep 17 00:00:00 2001 From: HoonBaek Date: Thu, 11 May 2023 09:59:55 +0900 Subject: [PATCH 10/10] Fix lint errors --- src/lib/MediaQueryContext.tsx | 2 +- src/ui/MessageContent/index.tsx | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/lib/MediaQueryContext.tsx b/src/lib/MediaQueryContext.tsx index 42122b9e2..e8c4af7ad 100644 --- a/src/lib/MediaQueryContext.tsx +++ b/src/lib/MediaQueryContext.tsx @@ -71,7 +71,7 @@ const MediaQueryProvider = (props: MediaQueryProviderProps): React.ReactElement return () => { window.removeEventListener('resize', updateSize); logger?.info?.('MediaQueryProvider: removeEventListener', { updateSize }); - } + }; }, [mediaQueryBreakPoint]); return ( diff --git a/src/ui/MessageContent/index.tsx b/src/ui/MessageContent/index.tsx index 774697ff7..72b630702 100644 --- a/src/ui/MessageContent/index.tsx +++ b/src/ui/MessageContent/index.tsx @@ -451,7 +451,6 @@ export default function MessageContent({ showEdit={showEdit} onReplyInThread={({ message }) => { if (threadReplySelectType === ThreadReplySelectType.THREAD) { - console.log('onreplyin therad is called', { onReplyInThread, message }) onReplyInThread?.({ message }); } else if (threadReplySelectType === ThreadReplySelectType.PARENT) { scrollToMessage?.(message?.parentMessage?.createdAt || 0, message?.parentMessageId || 0);