import clsx from "clsx"; import { sub } from "date-fns"; import * as React from "react"; import { Button } from "react-aria-components"; import { useTranslation } from "react-i18next"; import { Avatar } from "../../../components/Avatar"; import { SendouButton } from "../../../components/elements/Button"; import { SubmitButton } from "../../../components/SubmitButton"; import { MESSAGE_MAX_LENGTH } from "../chat-constants"; import { useChat, useChatAutoScroll } from "../chat-hooks"; import type { ChatMessage, ChatProps, ChatUser } from "../chat-types"; export function ConnectedChat(props: ChatProps) { const chat = useChat(props); return ; } export function Chat({ users, rooms, className, messagesContainerClassName, hidden = false, chat, onMount, onUnmount, disabled, missingUserName, }: Omit & { chat: ReturnType; }) { const { t } = useTranslation(["common"]); const messagesContainerRef = React.useRef(null); const inputRef = React.useRef(null); const { send, messages, currentRoom, setCurrentRoom, readyState, unseenMessages, } = chat; const handleSubmit = React.useCallback( (e: React.FormEvent) => { e.preventDefault(); // can't send empty messages if (inputRef.current!.value.trim().length === 0) { return; } send(inputRef.current!.value); inputRef.current!.value = ""; }, [send], ); const { unseenMessagesInTheRoom, scrollToBottom, resetScroller } = useChatAutoScroll(messages, messagesContainerRef); React.useEffect(() => { onMount?.(); return () => { onUnmount?.(); }; }, [onMount, onUnmount]); const sendingMessagesDisabled = disabled || readyState !== "CONNECTED"; const systemMessageText = (msg: ChatMessage) => { const name = () => { if (!msg.context) return ""; return msg.context.name; }; switch (msg.type) { case "SCORE_REPORTED": { return t("common:chat.systemMsg.scoreReported", { name: name() }); } case "SCORE_CONFIRMED": { return t("common:chat.systemMsg.scoreConfirmed", { name: name() }); } case "CANCEL_REPORTED": { return t("common:chat.systemMsg.cancelReported", { name: name() }); } case "CANCEL_CONFIRMED": { return t("common:chat.systemMsg.cancelConfirmed", { name: name() }); } case "USER_LEFT": { return t("common:chat.systemMsg.userLeft", { name: name() }); } default: { return null; } } }; return ( {rooms.length > 1 ? ( {rooms.map((room) => { const unseen = unseenMessages.get(room.code); return ( { setCurrentRoom(room.code); resetScroller(); }} > {room.label} {unseen ? ( {unseen} ) : ( )} ); })} ) : null} {messages.map((msg) => { const systemMessage = systemMessageText(msg); if (systemMessage) { return ( ); } const user = msg.userId ? users[msg.userId] : null; if (!user && !missingUserName) return null; return ( ); })} {unseenMessagesInTheRoom ? ( {t("common:chat.newMessages")} ) : null} {" "} {readyState === "CONNECTED" || readyState === "CONNECTING" ? ( {t( readyState === "CONNECTED" ? "common:chat.connected" : "common:chat.connecting", )} ) : ( {t("common:chat.disconnected")} )} {t("common:chat.send")} ); } function Message({ user, message, missingUserName, }: { user?: ChatUser | null; message: ChatMessage; missingUserName?: string; }) { return ( {user ? : null} {user?.username ?? missingUserName} {user?.title ? ( {user.title} ) : null} {!message.pending ? ( ) : null} {message.contents} ); } function SystemMessage({ message, text, }: { message: ChatMessage; text: string; }) { return ( {text} ); } function MessageTimestamp({ timestamp }: { timestamp: number }) { const { i18n } = useTranslation(); const moreThanDayAgo = sub(new Date(), { days: 1 }) > new Date(timestamp); return ( {moreThanDayAgo ? new Date(timestamp).toLocaleString(i18n.language, { day: "numeric", month: "numeric", hour: "numeric", minute: "numeric", }) : new Date(timestamp).toLocaleTimeString(i18n.language)} ); }