forked from noxious/client
161 lines
5.1 KiB
Vue
161 lines
5.1 KiB
Vue
<template>
|
|
<div class="w-full md:min-w-[350px] max-w-[750px] flex flex-col absolute left-1/2 -translate-x-1/2 bottom-5">
|
|
<div ref="chatWindow" class="w-full overflow-auto h-32 mb-5 bg-gray rounded-md border-2 border-solid border-gray-500 text-gray-300" v-show="gameStore.uiSettings.isChatOpen">
|
|
<div v-for="message in chats" class="flex-col py-2 items-center p-3">
|
|
<span class="text-ellipsis overflow-hidden whitespace-nowrap text-sm" :class="{ 'text-cyan-300': gameStore.character?.role == 'gm' }">{{ message.character }}</span>
|
|
<p class="text-gray-50 m-0">{{ message.message }}</p>
|
|
</div>
|
|
</div>
|
|
<div class="w-96 mx-auto relative">
|
|
<img src="/assets/icons/ingameUI/chat-icon.svg" class="absolute top-1/2 -translate-y-1/2 left-2.5 h-4 w-4 opacity-50" alt="" />
|
|
<input
|
|
class="w-[332px] h-8 rounded-sm text-xs font-default pl-8 pr-4 py-0 bg-gray-600 border-2 border-solid border-gray-500 text-gray-300 bg-[url('/assets/ui-texture.png')] bg-no-repeat bg-cover focus:outline-none focus:ring-0 focus:border-cyan-800"
|
|
placeholder="Type something..."
|
|
v-model="message"
|
|
@keypress="handleKeyPress"
|
|
@submit="handleSubmit"
|
|
ref="chatInput"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { SocketEvent } from '@/application/enums'
|
|
import { useGameStore } from '@/stores/gameStore'
|
|
import { useMapStore } from '@/stores/mapStore'
|
|
import { onClickOutside, useFocus } from '@vueuse/core'
|
|
import { useScene } from 'phavuer'
|
|
import { nextTick, onBeforeUnmount, onMounted, ref } from 'vue'
|
|
|
|
const scene = useScene()
|
|
const gameStore = useGameStore()
|
|
const mapStore = useMapStore()
|
|
|
|
const message = ref('')
|
|
const chats = ref<{ character: string; message: string }[]>([])
|
|
const chatWindow = ref<HTMLElement | null>(null)
|
|
const chatInput = ref<HTMLElement | null>(null)
|
|
|
|
const { focused } = useFocus(chatInput)
|
|
|
|
function focusChat(event: KeyboardEvent) {
|
|
if (event.key === 'Enter' && !focused.value) {
|
|
focused.value = true
|
|
}
|
|
}
|
|
|
|
onClickOutside(chatInput, (event) => unfocusChat(event, chatInput.value as HTMLElement))
|
|
|
|
function unfocusChat(event: Event, targetElement: HTMLElement) {
|
|
if (!(event.target instanceof Node) || !targetElement.contains(event.target)) {
|
|
targetElement.blur()
|
|
}
|
|
}
|
|
|
|
const sendMessage = () => {
|
|
if (!message.value.trim()) return
|
|
gameStore.connection?.emit(SocketEvent.CHAT_MESSAGE, { message: message.value }, (response: boolean) => {})
|
|
message.value = ''
|
|
}
|
|
|
|
const handleSubmit = (event: Event) => {
|
|
event.preventDefault()
|
|
sendMessage()
|
|
}
|
|
|
|
const handleKeyPress = (event: KeyboardEvent) => {
|
|
if (event.key === 'Enter' && !event.shiftKey) {
|
|
event.preventDefault()
|
|
sendMessage()
|
|
}
|
|
}
|
|
|
|
const scrollToBottom = () => {
|
|
nextTick(() => {
|
|
if (chatWindow.value) {
|
|
chatWindow.value.scrollTop = chatWindow.value.scrollHeight
|
|
}
|
|
})
|
|
}
|
|
|
|
gameStore.connection?.on(SocketEvent.CHAT_MESSAGE, (data: { character: string; message: string }) => {
|
|
if (!data.character || !data.message) return
|
|
|
|
|
|
chats.value.push({ character: data.character, message: data.message })
|
|
scrollToBottom()
|
|
|
|
const characterContainer = scene.children.getByName(data.character) as Phaser.GameObjects.Container
|
|
if (!characterContainer) {
|
|
console.log('No character container found')
|
|
return
|
|
}
|
|
|
|
const characterChatContainer = characterContainer.getByName(data.character + '_chatContainer') as Phaser.GameObjects.Container
|
|
if (!characterChatContainer) {
|
|
console.log('No character chat container found')
|
|
return
|
|
}
|
|
|
|
const chatBubble = characterChatContainer.getByName(data.character + '_chatBubble') as Phaser.GameObjects.Container
|
|
const chatText = characterChatContainer.getByName(data.character + '_chatText') as Phaser.GameObjects.Text
|
|
if (!chatText || !chatBubble) {
|
|
console.log('No chat text or bubble found')
|
|
return
|
|
}
|
|
|
|
function calculateTextWidth(text: string, font: string, fontSize: number): number {
|
|
// Create a canvas element
|
|
const canvas = document.createElement('canvas')
|
|
const context = canvas.getContext('2d')
|
|
|
|
if (!context) {
|
|
throw new Error('Unable to create canvas context')
|
|
}
|
|
|
|
// Set the font
|
|
context.font = `${fontSize}px ${font}`
|
|
|
|
// Measure the text width
|
|
const metrics = context.measureText(text)
|
|
|
|
return metrics.width
|
|
}
|
|
|
|
chatBubble.width = calculateTextWidth(data.message.substring(0, 90), 'Arial', 13) + 30
|
|
|
|
// setText but with max. char limit of 90
|
|
chatText.setText(data.message.substring(0, 90))
|
|
|
|
characterChatContainer.setVisible(true)
|
|
|
|
/**
|
|
* Hide chat bubble after a few seconds
|
|
*/
|
|
|
|
// Clear any existing hide timer
|
|
if (characterChatContainer.getData('hideTimer')) {
|
|
clearTimeout(characterChatContainer.getData('hideTimer'))
|
|
}
|
|
|
|
// Set a new hide timer
|
|
const hideTimer = setTimeout(() => {
|
|
characterChatContainer.setVisible(false)
|
|
}, 3000)
|
|
|
|
// Store the timer on the container itself
|
|
characterChatContainer.setData('hideTimer', hideTimer)
|
|
})
|
|
scrollToBottom()
|
|
|
|
onMounted(() => {
|
|
addEventListener('keydown', focusChat)
|
|
})
|
|
|
|
onBeforeUnmount(() => {
|
|
gameStore.connection?.off(SocketEvent.CHAT_MESSAGE)
|
|
removeEventListener('keydown', focusChat)
|
|
})
|
|
</script>
|