Skip to content

Commit

Permalink
Fix message scroll handling
Browse files Browse the repository at this point in the history
  • Loading branch information
AllTerrainDeveloper committed Jul 23, 2024
1 parent 519b629 commit f89df8e
Show file tree
Hide file tree
Showing 7 changed files with 142 additions and 323 deletions.
147 changes: 36 additions & 111 deletions packages/odie-client/src/components/message/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,14 @@ import { ExternalLink } from '@wordpress/components';
import { sprintf } from '@wordpress/i18n';
import { useI18n } from '@wordpress/react-i18n';
import clsx from 'clsx';
import { useCallback, useEffect, useRef, useState } from 'react';
import { useRef, useState } from 'react';
import ReactDOM from 'react-dom';
import Markdown from 'react-markdown';
import MaximizeIcon from '../../assets/maximize-icon.svg';
import MinimizeIcon from '../../assets/minimize-icon.svg';
import WapuuAvatar from '../../assets/wapuu-squared-avatar.svg';
import WapuuThinking from '../../assets/wapuu-thinking.svg';
import { useOdieAssistantContext } from '../../context';
import useTyper from '../../utils/user-typer';
import Button from '../button';
import FoldableCard from '../foldable';
import SupportDocLink from '../support-link';
Expand All @@ -27,14 +26,10 @@ import './style.scss';

export type ChatMessageProps = {
message: Message;
scrollToBottom: () => void;
currentUser: CurrentUser;
};

const ChatMessage = (
{ message, scrollToBottom, currentUser }: ChatMessageProps,
ref: React.Ref< HTMLDivElement >
) => {
const ChatMessage = ( { message, currentUser }: ChatMessageProps ) => {
const isUser = message.role === 'user';
const {
botName,
Expand All @@ -44,16 +39,9 @@ const ChatMessage = (
navigateToContactOptions,
navigateToSupportDocs,
} = useOdieAssistantContext();
const [ scrolledToBottom, setScrolledToBottom ] = useState( false );
const [ isFullscreen, setIsFullscreen ] = useState( false );
const { __, _x } = useI18n();

const realTimeMessage = useTyper( message.content, ! isUser && message.type === 'message', {
delayBetweenCharacters: 66,
randomDelayBetweenCharacters: true,
charactersPerInterval: 5,
} );

const hasSources = message?.context?.sources && message.context?.sources.length > 0;
const hasFeedback = !! message?.rating_value;

Expand All @@ -66,14 +54,9 @@ const ChatMessage = (
sources = [ ...new Map( sources.map( ( source ) => [ source.url, source ] ) ).values() ];
}

const isTypeMessageOrEmpty = ! message.type || message.type === 'message';
const isSimulatedTypingFinished = message.simulateTyping && message.content === realTimeMessage;
const isRequestingHumanSupport = message.context?.flags?.forward_to_human_support;
const fullscreenRef = useRef< HTMLDivElement >( null );

const messageFullyTyped =
isTypeMessageOrEmpty && ( ! message.simulateTyping || isSimulatedTypingFinished );

const handleBackdropClick = () => {
setIsFullscreen( false );
};
Expand All @@ -91,57 +74,8 @@ const ChatMessage = (
setIsFullscreen( ! isFullscreen );
};

const handleWheel = useCallback(
( event: WheelEvent ) => {
if ( ! isFullscreen ) {
return;
}

const element = fullscreenRef.current;

if ( element ) {
const { scrollTop, scrollHeight, clientHeight } = element;
const atTop = scrollTop <= 0;
const tolerance = 2;
const atBottom = scrollTop + clientHeight >= scrollHeight - tolerance;

// Prevent scrolling the parent element when at the bounds
if ( ( atTop && event.deltaY < 0 ) || ( atBottom && event.deltaY > 0 ) ) {
event.preventDefault();
event.stopPropagation();
}
}
},
[ isFullscreen ]
);

useEffect( () => {
const fullscreenElement = fullscreenRef.current;
if ( fullscreenElement ) {
fullscreenElement.addEventListener( 'wheel', handleWheel, { passive: false } );
}
return () => {
if ( fullscreenElement ) {
fullscreenElement.removeEventListener( 'wheel', handleWheel );
}
};
}, [ handleWheel ] );

useEffect( () => {
if ( message.content !== realTimeMessage && message.simulateTyping ) {
scrollToBottom();
}
}, [ message, realTimeMessage, scrollToBottom ] );

useEffect( () => {
if ( messageFullyTyped && ! scrolledToBottom ) {
scrollToBottom();
setScrolledToBottom( true );
}
}, [ messageFullyTyped, scrolledToBottom, scrollToBottom ] );

if ( ! currentUser || ! botName ) {
return <div ref={ ref } />;
return null;
}

const wapuuAvatarClasses = clsx( 'odie-chatbox-message-avatar', {
Expand Down Expand Up @@ -211,10 +145,8 @@ const ChatMessage = (
<div className={ `message-header ${ isUser ? 'user' : 'bot' }` }>{ messageAvatarHeader }</div>
);

const shouldRenderExtraContactOptions = isRequestingHumanSupport && messageFullyTyped;

const onDislike = () => {
if ( shouldRenderExtraContactOptions ) {
if ( isRequestingHumanSupport ) {
return;
}
setTimeout( () => {
Expand Down Expand Up @@ -255,12 +187,12 @@ const ChatMessage = (
a: CustomALink,
} }
>
{ isUser || ! message.simulateTyping ? message.content : realTimeMessage }
{ message.content }
</Markdown>
{ ! hasFeedback && ! isUser && messageFullyTyped && (
{ ! hasFeedback && ! isUser && (
<WasThisHelpfulButtons message={ message } onDislike={ onDislike } />
) }
{ hasFeedback && messageFullyTyped && ! isPositiveFeedback && extraContactOptions }
{ hasFeedback && ! isPositiveFeedback && extraContactOptions }
{ ! isUser && (
<>
{ message.directEscalationSupport && (
Expand Down Expand Up @@ -320,9 +252,9 @@ const ChatMessage = (
{ extraContactOptions }
</>
) }
{ shouldRenderExtraContactOptions && extraContactOptions }
{ isRequestingHumanSupport && extraContactOptions }
</div>
{ hasSources && messageFullyTyped && (
{ hasSources && (
<FoldableCard
className="odie-sources-foldable-card"
clickableHeader
Expand Down Expand Up @@ -350,35 +282,32 @@ const ChatMessage = (
>
<div className="odie-chatbox-message-sources">
{ sources.length > 0 &&
sources.map( ( source, index ) => (
<>
{ navigateToSupportDocs && (
<SupportDocLink
key={ index }
link={ source.url }
onLinkClickHandler={ () => {
trackEvent( 'chat_message_action_click', {
action: 'link',
in_chat_view: true,
href: source.url,
} );
navigateToSupportDocs(
String( source.blog_id ),
String( source.post_id ),
source.url,
source.title
);
} }
title={ source.title }
/>
) }
{ ! navigateToSupportDocs && (
<CustomALink key={ index } href={ source.url } inline={ false }>
{ source?.title }
</CustomALink>
) }
</>
) ) }
sources.map( ( source, index ) =>
navigateToSupportDocs ? (
<SupportDocLink
key={ index }
link={ source.url }
onLinkClickHandler={ () => {
trackEvent( 'chat_message_action_click', {
action: 'link',
in_chat_view: true,
href: source.url,
} );
navigateToSupportDocs(
String( source.blog_id ),
String( source.post_id ),
source.url,
source.title
);
} }
title={ source.title }
/>
) : (
<CustomALink key={ index } href={ source.url } inline={ false }>
{ source?.title }
</CustomALink>
)
) }
</div>
</FoldableCard>
) }
Expand All @@ -401,11 +330,7 @@ const ChatMessage = (
</>
);
}
return (
<div className={ odieChatBoxMessageSourcesContainerClass } ref={ ref }>
{ messageContent }
</div>
);
return <div className={ odieChatBoxMessageSourcesContainerClass }>{ messageContent }</div>;
};

export default ChatMessage;
30 changes: 30 additions & 0 deletions packages/odie-client/src/components/message/messages-container.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { ForwardedRef, forwardRef, useMemo } from 'react';
import ChatMessage from '.';
import type { CurrentUser, Message } from '../../types/';

interface ChatMessagesProps {
chat: {
messages: Message[];
};
currentUser: CurrentUser;
}

export const MessagesContainer = forwardRef(
( { chat, currentUser }: ChatMessagesProps, ref: ForwardedRef< HTMLDivElement > ) => {
const lastMessageIndex = useMemo( () => chat.messages.length - 1, [ chat.messages ] );
if ( chat.messages.length === 0 ) {
return null;
}

return (
<>
{ chat.messages.slice( 0, lastMessageIndex ).map( ( message, index ) => (
<ChatMessage message={ message } key={ index } currentUser={ currentUser } />
) ) }
<div ref={ ref } style={ { margin: 0, padding: 0, border: 0 } }>
<ChatMessage message={ chat.messages[ lastMessageIndex ] } currentUser={ currentUser } />
</div>
</>
);
}
);
4 changes: 2 additions & 2 deletions packages/odie-client/src/components/message/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -293,8 +293,8 @@ $custom-border-corner-size: 16px;
opacity: 1;
bottom: 24px;
transition:
opacity 0.3s ease 0.3s,
bottom 0.3s ease 0.3s;
opacity 0.3s ease 0.6s,
bottom 0.3s ease 0.6s;
}

.odie-jump-to-recent-message-button {
Expand Down
Loading

0 comments on commit f89df8e

Please sign in to comment.