canvas-website/src/shapes/ChatBoxShapeUtil.tsx

192 lines
4.8 KiB
TypeScript

import { useEffect, useRef, useState } from "react"
import { BaseBoxShapeUtil, TLBaseShape } from "tldraw"
export type IChatBoxShape = TLBaseShape<
"ChatBox",
{
w: number
h: number
roomId: string
userName: string
}
>
export class ChatBoxShape extends BaseBoxShapeUtil<IChatBoxShape> {
static override type = "ChatBox"
getDefaultProps(): IChatBoxShape["props"] {
return {
roomId: "default-room",
w: 100,
h: 100,
userName: "",
}
}
indicator(shape: IChatBoxShape) {
return <rect x={0} y={0} width={shape.props.w} height={shape.props.h} />
}
component(shape: IChatBoxShape) {
return (
<ChatBox
roomId={shape.props.roomId}
w={shape.props.w}
h={shape.props.h}
userName=""
/>
)
}
}
interface Message {
id: string
username: string
content: string
timestamp: Date
}
// Update the ChatBox component to accept userName
export const ChatBox: React.FC<IChatBoxShape["props"]> = ({
roomId,
w,
h,
userName,
}) => {
const [messages, setMessages] = useState<Message[]>([])
const [inputMessage, setInputMessage] = useState("")
const [username, setUsername] = useState(userName)
const messagesEndRef = useRef(null)
useEffect(() => {
const storedUsername = localStorage.getItem("chatUsername")
if (storedUsername) {
setUsername(storedUsername)
} else {
const newUsername = `User${Math.floor(Math.random() * 1000)}`
setUsername(newUsername)
localStorage.setItem("chatUsername", newUsername)
}
fetchMessages(roomId)
const interval = setInterval(() => fetchMessages(roomId), 2000)
return () => clearInterval(interval)
}, [roomId])
useEffect(() => {
if (messagesEndRef.current) {
;(messagesEndRef.current as HTMLElement).scrollIntoView({
behavior: "smooth",
})
}
}, [messages])
const fetchMessages = async (roomId: string) => {
try {
const response = await fetch(
`https://jeffemmett-realtimechatappwithpolling.web.val.run?action=getMessages&roomId=${roomId}`,
)
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`)
}
const newMessages = (await response.json()) as Message[]
setMessages(
newMessages.map((msg) => ({
...msg,
timestamp: new Date(msg.timestamp),
})),
)
} catch (error) {
console.error("Error fetching messages:", error)
}
}
const sendMessage = async (e: React.FormEvent) => {
e.preventDefault()
if (!inputMessage.trim()) return
await sendMessageToChat(roomId, username, inputMessage)
setInputMessage("")
fetchMessages(roomId)
}
return (
<div
className="chat-container"
style={{
pointerEvents: "all",
width: `${w}px`,
height: `${h}px`,
overflow: "auto",
touchAction: "auto",
}}
>
<div className="messages-container">
{messages.map((msg) => (
<div
key={msg.id}
className={`message ${
msg.username === username ? "own-message" : ""
}`}
>
<div className="message-header">
<strong>{msg.username}</strong>
<span className="timestamp">
{new Date(msg.timestamp).toLocaleTimeString()}
</span>
</div>
<div className="message-content">{msg.content}</div>
</div>
))}
<div ref={messagesEndRef} />
</div>
<form onSubmit={sendMessage} className="input-form">
<input
type="text"
value={inputMessage}
onChange={(e) => setInputMessage(e.target.value)}
placeholder="Type a message..."
className="message-input"
style={{ touchAction: "manipulation" }}
/>
<button
type="submit"
style={{ pointerEvents: "all", touchAction: "manipulation" }}
onPointerDown={(e) => e.stopPropagation()}
onTouchStart={(e) => e.stopPropagation()}
className="send-button"
>
Send
</button>
</form>
</div>
)
}
async function sendMessageToChat(
roomId: string,
username: string,
content: string,
): Promise<void> {
const apiUrl = "https://jeffemmett-realtimechatappwithpolling.web.val.run" // Replace with your actual Val Town URL
try {
const response = await fetch(`${apiUrl}?action=sendMessage`, {
method: "POST",
mode: "no-cors",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
roomId,
username,
content,
}),
})
const result = await response.text()
console.log("Message sent successfully:", result)
} catch (error) {
console.error("Error sending message:", error)
}
}