Files
website/src/app/game/berry-dash/chatroom/page.tsx

233 lines
6.7 KiB
TypeScript

'use client'
import { BackButton } from '@/app/components/BackButton'
import { DiscordButton } from '@/app/components/DiscordButton'
import { GetIconForUser } from '@/util/bd'
import { getCookie } from '@/util/cookie'
import axios from 'axios'
import { useEffect, useRef, useState } from 'react'
interface WSMessage {
success: boolean
message: string | null
for: string
data: any
}
type BirdColor = [number, number, number]
interface Message {
username: string
userId: number
content: string
id: number
icon: number
overlay: number
birdColor: BirdColor
overlayColor: BirdColor
customIcon: string | null
}
interface CustomIconEntry {
data: string
id: string
}
export default function BerryDashChatroom () {
const [connected, setConnected] = useState<boolean>(false)
const [loggedIn, setLoggedIn] = useState<boolean>(false)
const [token, setToken] = useState<string | null>(null)
const [messages, setMessages] = useState<Message[]>([])
const [customIconCache, setCustomIconCache] = useState<CustomIconEntry[]>([])
const [ws, setWs] = useState<WebSocket | null>(null)
const messagesDiv = useRef<HTMLDivElement>(null)
const [input, setInput] = useState<string>('')
useEffect(() => {
document.title = 'Lncvrt Games - Chatroom'
const token = getCookie('accountToken', '-1')
if (token !== '-1') {
setLoggedIn(true)
setToken(token)
}
const socket = new WebSocket('/api/ws')
socket.onopen = () => {
setConnected(true)
socket.send(
JSON.stringify({
type: 'info_request',
kind: 'chatroom_messages',
timestamp: Date.now()
})
)
}
socket.onmessage = event => {
const message = JSON.parse(event.data) as WSMessage
if (message.for == 'info_request:chatroom_messages') {
const messages = message.data as Message[]
setMessages(messages)
} else if (message.for == 'upload:chatroom_message') {
const msg = message.data as Message
setMessages(prev => [...prev.slice(1), msg])
} else if (message.for == 'delete:chatroom_message') {
const msg = message.data.fillerMessage as Message
setMessages(prev => [msg, ...prev.slice(0, -1)])
} else if (message.for == 'edit:chatroom_message') {
setMessages(prev =>
prev.map(msg =>
msg.id === message.data.id
? { ...msg, content: btoa(message.data.newContent) }
: msg
)
)
}
}
socket.onclose = () => {
setConnected(false)
setMessages([])
}
setWs(socket)
return () => {
socket.close()
}
}, [])
useEffect(() => {
if (messagesDiv.current) {
messagesDiv.current.scrollTop = messagesDiv.current.scrollHeight
}
const all = customIconCache.map(icon => icon.id)
const allFromMessages = Array.from(
new Set(
messages
.filter(icon => icon.customIcon != null)
.map(icon => icon.customIcon as string)
)
)
const notInAllIds = allFromMessages
.filter(id => !all.includes(id))
.map(id => `"${id}"`)
.join(',')
if (notInAllIds.length != 0) {
;(async () => {
const result = await axios.get(
`/api/berrydash/icon-marketplace/icon?ids=[${notInAllIds}]`
)
if (result.data.success) {
const add: CustomIconEntry[] = []
for (const item of result.data.data) {
add.push({
data: item.data,
id: item.id
})
}
setCustomIconCache(prev => [
...prev,
...result.data.data.map((item: CustomIconEntry) => ({
data: item.data,
id: item.id
}))
])
}
})()
}
}, [messages])
return (
<div className='box'>
<BackButton href='/game/berry-dash' />
<DiscordButton />
<p className='px-8 mb-4 -mt-2 text-center text-2xl'>
Berry Dash Chatroom
</p>
{connected && (
<>
<div
className='sub-box -m-4 mt-0 flex flex-col gap-2 max-h-[calc(100vh-204px)] overflow-y-auto'
ref={messagesDiv}
>
{messages.map(item => {
return (
<div key={item.id} className='sub-box2 flex flex-row gap-1'>
<img
src={
!item.customIcon
? `https://games-r2.lncvrt.xyz/game-assets/berrydash/icons/bird_${
item.icon == 1
? GetIconForUser(item.userId)
: item.icon
}.png`
: customIconCache.find(i => i.id === item.customIcon)
? 'data:image/png;base64,' +
customIconCache.find(i => i.id === item.customIcon)
?.data
: 'https://games-r2.lncvrt.xyz/game-assets/berrydash/other/loading.png'
}
className='pointer-events-none'
width={48}
height={48}
></img>
<div>
<p>{item.username}</p>
<p className='w-240 max-w-[calc(100vw-136px)] truncate select-text'>
{atob(item.content)}
</p>
</div>
</div>
)
})}
</div>
{loggedIn ? (
<form
onSubmit={e => {
e.preventDefault()
setInput('')
ws?.send(
JSON.stringify({
type: 'upload',
kind: 'chatroom_message',
data: {
content: input,
auth: token
},
timestamp: Date.now()
})
)
}}
className='flex flex-row gap-2 -mb-4 mt-6'
>
<input
type='text'
className='w-full -ml-4 text-center'
placeholder='Enter a message to send...'
maxLength={60}
value={input}
onChange={e => setInput(e.target.value)}
></input>
<button className='-mr-4'>Send</button>
</form>
) : (
<p className='text-center -mb-4 mt-6'>
You must be logged in to send messages
</p>
)}
</>
)}
{!connected && (
<p className='text-center text-red-500'>
Not connected to the chatroom
</p>
)}
</div>
)
}