192 lines
4.8 KiB
TypeScript
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)
|
|
}
|
|
}
|