Convert Chat components to TypeScript (#22672)

GitOrigin-RevId: b47a7fc3f77055335990ee0215bd32ae65b1ebfe
This commit is contained in:
Alf Eaton
2025-01-13 10:50:53 +00:00
committed by Copybot
parent f9e4ddca51
commit 1071da2e63
5 changed files with 77 additions and 71 deletions

View File

@@ -1,9 +1,12 @@
import PropTypes from 'prop-types'
import { useTranslation } from 'react-i18next'
import OLNotification from '@/features/ui/components/ol/ol-notification'
import OLButton from '@/features/ui/components/ol/ol-button'
function ChatFallbackError({ reconnect }) {
interface ChatFallbackErrorProps {
reconnect?: () => void
}
function ChatFallbackError({ reconnect }: ChatFallbackErrorProps) {
const { t } = useTranslation()
return (
@@ -22,8 +25,4 @@ function ChatFallbackError({ reconnect }) {
)
}
ChatFallbackError.propTypes = {
reconnect: PropTypes.any,
}
export default ChatFallbackError

View File

@@ -1,9 +1,17 @@
import { useRef, useEffect, useLayoutEffect } from 'react'
import PropTypes from 'prop-types'
import _ from 'lodash'
const SCROLL_END_OFFSET = 30
interface InfiniteScrollProps {
atEnd?: boolean
children: React.ReactElement
className?: string
fetchData(): void
itemCount: number
isLoading?: boolean
}
function InfiniteScroll({
atEnd,
children,
@@ -11,20 +19,22 @@ function InfiniteScroll({
fetchData,
itemCount,
isLoading,
}) {
const root = useRef(null)
}: InfiniteScrollProps) {
const root = useRef<HTMLDivElement>(null)
// we keep the value in a Ref instead of state so it can be safely used in effects
const scrollBottomRef = useRef(0)
function setScrollBottom(value) {
function setScrollBottom(value: number) {
scrollBottomRef.current = value
}
function updateScrollPosition() {
root.current.scrollTop =
root.current.scrollHeight -
root.current.clientHeight -
scrollBottomRef.current
if (root.current) {
root.current.scrollTop =
root.current.scrollHeight -
root.current.clientHeight -
scrollBottomRef.current
}
}
// Repositions the scroll after new items are loaded
@@ -39,23 +49,29 @@ function InfiniteScroll({
}
}, [])
function onScrollHandler(event) {
setScrollBottom(
root.current.scrollHeight -
root.current.scrollTop -
root.current.clientHeight
)
if (event.target !== event.currentTarget) {
// Ignore scroll events on nested divs
// (this check won't be necessary in React 17: https://github.com/facebook/react/issues/15723
return
}
if (shouldFetchData()) {
fetchData()
function onScrollHandler(event: React.UIEvent<HTMLDivElement>) {
if (root.current) {
setScrollBottom(
root.current.scrollHeight -
root.current.scrollTop -
root.current.clientHeight
)
if (event.target !== event.currentTarget) {
// Ignore scroll events on nested divs
// (this check won't be necessary in React 17: https://github.com/facebook/react/issues/15723
return
}
if (shouldFetchData()) {
fetchData()
}
}
}
function shouldFetchData() {
if (!root.current) {
return false
}
const containerIsLargerThanContent =
root.current.children[0].clientHeight < root.current.clientHeight
if (atEnd || isLoading || containerIsLargerThanContent) {
@@ -76,13 +92,4 @@ function InfiniteScroll({
)
}
InfiniteScroll.propTypes = {
atEnd: PropTypes.bool,
children: PropTypes.element.isRequired,
className: PropTypes.string,
fetchData: PropTypes.func.isRequired,
itemCount: PropTypes.number.isRequired,
isLoading: PropTypes.bool,
}
export default InfiniteScroll

View File

@@ -1,10 +1,11 @@
import PropTypes from 'prop-types'
import moment from 'moment'
import Message from './message'
import { UserId } from '../../../../../types/user'
import type { Message as MessageType } from '@/features/chat/context/chat-context'
const FIVE_MINUTES = 5 * 60 * 1000
function formatTimestamp(date) {
function formatTimestamp(date: moment.MomentInput) {
if (!date) {
return 'N/A'
} else {
@@ -12,14 +13,28 @@ function formatTimestamp(date) {
}
}
function MessageList({ messages, resetUnreadMessages, userId }) {
function shouldRenderDate(messageIndex) {
interface MessageListProps {
messages: MessageType[]
resetUnreadMessages(...args: unknown[]): unknown
userId: UserId | null
}
function MessageList({
messages,
resetUnreadMessages,
userId,
}: MessageListProps) {
function shouldRenderDate(messageIndex: number) {
if (messageIndex === 0) {
return true
} else {
const message = messages[messageIndex]
const previousMessage = messages[messageIndex - 1]
return message.timestamp - previousMessage.timestamp > FIVE_MINUTES
return (
message.timestamp &&
previousMessage.timestamp &&
message.timestamp - previousMessage.timestamp > FIVE_MINUTES
)
}
}
@@ -53,15 +68,4 @@ function MessageList({ messages, resetUnreadMessages, userId }) {
)
}
MessageList.propTypes = {
messages: PropTypes.arrayOf(
PropTypes.shape({
id: PropTypes.string.isRequired,
timestamp: PropTypes.number,
})
).isRequired,
resetUnreadMessages: PropTypes.func.isRequired,
userId: PropTypes.string,
}
export default MessageList

View File

@@ -1,20 +1,26 @@
import PropTypes from 'prop-types'
import { getHueForUserId } from '../../../shared/utils/colors'
import MessageContent from './message-content'
import type { Message as MessageType } from '@/features/chat/context/chat-context'
import { User } from '../../../../../types/user'
function Message({ message, userId }) {
function hue(user) {
interface MessageProps {
message: MessageType
userId: string | null
}
function Message({ message, userId }: MessageProps) {
function hue(user?: User) {
return user ? getHueForUserId(user.id, userId) : 0
}
function getMessageStyle(user) {
function getMessageStyle(user?: User) {
return {
borderColor: `hsl(${hue(user)}, 85%, 40%)`,
backgroundColor: `hsl(${hue(user)}, 85%, 40%`,
}
}
function getArrowStyle(user) {
function getArrowStyle(user?: User) {
return {
borderColor: `hsl(${hue(user)}, 85%, 40%)`,
}
@@ -24,7 +30,7 @@ function Message({ message, userId }) {
return (
<div className="message-wrapper">
{!isMessageFromSelf && (
{!isMessageFromSelf && message.user.id && (
<div className="name">
<span>{message.user.first_name || message.user.email}</span>
</div>
@@ -43,16 +49,4 @@ function Message({ message, userId }) {
)
}
Message.propTypes = {
message: PropTypes.shape({
contents: PropTypes.arrayOf(PropTypes.string).isRequired,
user: PropTypes.shape({
id: PropTypes.string,
email: PropTypes.string,
first_name: PropTypes.string,
}),
}),
userId: PropTypes.string,
}
export default Message

View File

@@ -19,13 +19,15 @@ import { useLayoutContext } from '../../../shared/context/layout-context'
import { useIdeContext } from '@/shared/context/ide-context'
import getMeta from '@/utils/meta'
import { debugConsole } from '@/utils/debugging'
import { User } from '../../../../../types/user'
const PAGE_SIZE = 50
export type Message = {
id: string
timestamp: number
contents: string
contents: string[]
user: User
}
type State = {