Skip to content

Commit

Permalink
Merge pull request #1150 from The-Commit-Company/971-user-messages-to…
Browse files Browse the repository at this point in the history
…-come-on-the-right-while-other-messages-on-the-left

feat: 'Left-Right' Chat Layout
  • Loading branch information
nikkothari22 authored Nov 16, 2024
2 parents 59f4813 + 0cf1f69 commit be97184
Show file tree
Hide file tree
Showing 8 changed files with 352 additions and 151 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
import { UserFields } from "@/utils/users/UserListProvider"
import { Message, MessageBlock } from "../../../../../../../types/Messaging/Message"
import { MessageContent, MessageSenderAvatar, UserHoverCard } from "../MessageItem"
import { Box, BoxProps, ContextMenu, Flex, Text } from "@radix-ui/themes"
import { MessageReactions } from "../MessageReactions"
import { DateTooltip, DateTooltipShort } from "../Renderers/DateTooltip"
import { RiShareForwardFill } from "react-icons/ri"
import { ReplyMessageBox } from "../ReplyMessageBox/ReplyMessageBox"
import { useContext, useMemo, useState } from "react"
import clsx from "clsx"
import { DoctypeLinkRenderer } from "../Renderers/DoctypeLinkRenderer"
import { ThreadMessage } from "../Renderers/ThreadMessage"
import { UserContext } from "@/utils/auth/UserProvider"
import { Stack } from "@/components/layout/Stack"
import { useDoubleTap } from "use-double-tap"
import { useDebounce } from "@/hooks/useDebounce"
import { useIsDesktop } from "@/hooks/useMediaQuery"
import useOutsideClick from "@/hooks/useOutsideClick"
import { MessageContextMenu } from "../MessageActions/MessageActions"
import { QuickActions } from "../MessageActions/QuickActions/QuickActions"

// @ts-ignore
const CHAT_STYLE = frappe.boot.chat_style

export interface Props {
message: Message
user: UserFields | undefined
isActive: boolean
isHighlighted?: boolean
onReplyMessageClick: (messageID: string) => void,
onDelete: () => void,
showThreadButton?: boolean,
onEdit: () => void,
onReply: () => void,
onForward: () => void,
onViewReaction: () => void,
onAttachToDocument: () => void
}

export const LeftRightLayout = ({ message, user, isActive, isHighlighted, onReplyMessageClick, onDelete, showThreadButton, onEdit, onReply, onForward, onViewReaction, onAttachToDocument }: Props) => {

const { name, owner: userID, is_bot_message, bot, creation: timestamp, message_reactions, is_continuation, linked_message, replied_message_details } = message

const { currentUser } = useContext(UserContext)

const replyMessageDetails = useMemo(() => {
if (typeof replied_message_details === 'string') {
return JSON.parse(replied_message_details)
} else {
return replied_message_details
}
}, [replied_message_details])

const isDesktop = useIsDesktop()
const [isHovered, setIsHovered] = useState(false)
const isHoveredDebounced = useDebounce(isHovered, isDesktop ? 400 : 200)
const [isEmojiPickerOpen, setEmojiPickerOpen] = useState(false)

// For mobile, we want to show the quick actions on double tap
const bind = useDoubleTap((event) => {
if (!isDesktop)
setIsHovered(!isHovered)
});
const ref = useOutsideClick(() => {
if (!isDesktop)
setIsHovered(false)
});

const onMouseEnter = () => {
if (isDesktop) {
setIsHovered(true)
}
}

const onMouseLeave = () => {
if (isDesktop) {
setIsHovered(false)
}
}

const alignToRight = CHAT_STYLE === "Left-Right" && currentUser === userID && !is_bot_message

return (
<div className={clsx('flex py-0.5', alignToRight ? 'justify-end mr-4' : 'justify-start')}>
<Flex align={'start'} gap={'2'}>
{!alignToRight && <MessageLeftElement message={message} user={user} isActive={isActive} className="mt-[5px]" />}
<ContextMenu.Root>
<ContextMenu.Trigger
{...bind}
ref={ref}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
className="group"
>
<Stack gap={'0'} align={'end'}>
{alignToRight && !is_continuation && <Box className="text-right pr-1 pb-0.5"><DateTooltip timestamp={timestamp} /></Box>}
<Flex direction={'column'} className={clsx("relative w-fit sm:max-w-[32rem] max-w-[80vw] bg-gray-2 dark:bg-gray-3 p-3 gap-1 rounded-md",
isHighlighted ? 'bg-yellow-50 hover:bg-yellow-50 dark:bg-yellow-300/20 dark:hover:bg-yellow-300/20' : !isDesktop && isHovered ? 'bg-gray-2 dark:bg-gray-3' : ''
)}>
{!is_continuation && !alignToRight ? <Flex align='center' gap='2'>
<UserHoverCard
user={user}
userID={userID}
isActive={isActive} />
<DateTooltip timestamp={timestamp} />
</Flex> : null}

{message.is_forwarded === 1 && <Flex className='text-gray-10 text-xs' gap={'1'} align={'center'}><RiShareForwardFill size='12' /> forwarded</Flex>}

{linked_message && replied_message_details && <ReplyMessageBox
className='sm:max-w-[32rem] max-w-[80vw] cursor-pointer mb-1'
role='button'
onClick={() => onReplyMessageClick(linked_message)}
message={replyMessageDetails} />
}

<MessageContent
message={message}
user={user}
/>

{message.link_doctype && message.link_document && <Box className={clsx(message.is_continuation ? 'ml-0.5' : '-ml-0.5')}>
<DoctypeLinkRenderer doctype={message.link_doctype} docname={message.link_document} />
</Box>}

{message.is_edited === 1 && <Text size='1' className='text-gray-10'>(edited)</Text>}

{message_reactions?.length &&
<MessageReactions
messageID={name}
message_reactions={message_reactions}
/>
}

{message.is_thread === 1 ? <ThreadMessage thread={message} /> : null}

{(isHoveredDebounced || isEmojiPickerOpen) &&
<QuickActions
message={message}
onDelete={onDelete}
isEmojiPickerOpen={isEmojiPickerOpen}
setIsEmojiPickerOpen={setEmojiPickerOpen}
onEdit={onEdit}
onReply={onReply}
onForward={onForward}
showThreadButton={showThreadButton}
onAttachDocument={onAttachToDocument}
alignToRight={alignToRight}
/>
}
</Flex>

</Stack>
</ContextMenu.Trigger>
<MessageContextMenu
message={message}
onDelete={onDelete}
showThreadButton={showThreadButton}
onEdit={onEdit}
onReply={onReply}
onForward={onForward}
onViewReaction={onViewReaction}
onAttachDocument={onAttachToDocument}
/>
</ContextMenu.Root>
</Flex>
</div >
)
}


type MessageLeftElementProps = BoxProps & {
message: MessageBlock['data'],
user?: UserFields,
isActive?: boolean
}
const MessageLeftElement = ({ message, className, user, isActive, ...props }: MessageLeftElementProps) => {

// If it's a continuation, then show the timestamp

// Else, show the avatar
return <Box className={clsx(message.is_continuation ? 'flex items-center w-[38px] sm:w-[34px]' : '', className)} {...props}>
{message.is_continuation ?
null
: <MessageSenderAvatar userID={message.owner} user={user} isActive={isActive} />
}
</Box>

}
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,20 @@ import { LuReply } from 'react-icons/lu'
import { toast } from 'sonner'
import { getErrorMessage } from '@/components/layout/AlertBanner/ErrorBanner'
import { CreateThreadActionButton } from './CreateThreadButton'
import clsx from 'clsx'

// @ts-ignore
const CHAT_STYLE = frappe.boot.chat_style

const QUICK_EMOJIS = ['👍', '✅', '👀', '🎉']

interface QuickActionsProps extends MessageContextMenuProps {
isEmojiPickerOpen: boolean,
setIsEmojiPickerOpen: (open: boolean) => void,
alignToRight?: boolean,
}

export const QuickActions = ({ message, onReply, onEdit, isEmojiPickerOpen, setIsEmojiPickerOpen, showThreadButton = true }: QuickActionsProps) => {
export const QuickActions = ({ message, onReply, onEdit, isEmojiPickerOpen, setIsEmojiPickerOpen, showThreadButton = true, alignToRight = false }: QuickActionsProps) => {

const { currentUser } = useContext(UserContext)

Expand Down Expand Up @@ -60,20 +65,11 @@ export const QuickActions = ({ message, onReply, onEdit, isEmojiPickerOpen, setI
}

return (
<Box ref={toolbarRef} className='absolute
-top-6
right-4
group-hover:visible
group-hover:transition-all
ease-ease-out-quad
group-hover:delay-100
z-50
p-1
shadow-md
rounded-md
bg-white
dark:bg-gray-1
invisible'>
<Box
ref={toolbarRef}
className={clsx('absolute group-hover:visible group-hover:transition-all ease-ease-out-quad group-hover:delay-100 z-50 p-1 shadow-md rounded-md bg-white dark:bg-gray-1 invisible',
CHAT_STYLE === "Left-Right" ? alignToRight ? "-top-10 right-0" : "-top-10 left-0" : "-top-6 right-4"
)}>
<Flex gap='1'>
{QUICK_EMOJIS.map((emoji) => {
return <QuickActionButton
Expand Down
Loading

0 comments on commit be97184

Please sign in to comment.