Просмотр исходного кода

Convert Chat components to TypeScript (#22672)

GitOrigin-RevId: b47a7fc3f77055335990ee0215bd32ae65b1ebfe
Alf Eaton 1 год назад
Родитель
Сommit
003fa536df

+ 5 - 6
services/web/frontend/js/features/chat/components/chat-fallback-error.jsx → services/web/frontend/js/features/chat/components/chat-fallback-error.tsx

@@ -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

+ 37 - 30
services/web/frontend/js/features/chat/components/infinite-scroll.jsx → services/web/frontend/js/features/chat/components/infinite-scroll.tsx

@@ -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

+ 20 - 16
services/web/frontend/js/features/chat/components/message-list.jsx → services/web/frontend/js/features/chat/components/message-list.tsx

@@ -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

+ 12 - 18
services/web/frontend/js/features/chat/components/message.jsx → services/web/frontend/js/features/chat/components/message.tsx

@@ -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

+ 3 - 1
services/web/frontend/js/features/chat/context/chat-context.tsx

@@ -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 = {