Skip to content

Commit

Permalink
Prepare chat component PoC for upcoming protocol additions
Browse files Browse the repository at this point in the history
  • Loading branch information
bcdrme committed Jan 31, 2025
1 parent 045c7c3 commit 03ebb43
Show file tree
Hide file tree
Showing 2 changed files with 233 additions and 45 deletions.
157 changes: 112 additions & 45 deletions src/renderer/components/social/ChatComponent.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,48 +5,56 @@
@focusin="expandChat"
@focusout="collapseChat"
>
<div class="tabs">
<div class="tab" v-for="chatRoom in chatStore.chatRooms" :key="chatRoom.id">
<Button :class="{ active: chatStore.selectedChatRoom.id === chatRoom.id }" @click="clickTab(chatRoom)">
<span class="unread-messages-dot" :class="{ active: chatRoom.unreadMessages > 0 }">⬤</span>
{{ chatRoom.name }}
<span class="close-button" v-if="chatRoom.closeable" @click="(e) => closeChatRoom(e, chatRoom)">x</span>
</Button>
</div>
</div>
<div class="chat-messages" :class="{ expanded: isExpanded || battleStore.isLobbyOpened }">
<div v-for="(message, index) in messages" :key="index" :class="['chat-message', message.user]">
<div
v-for="(message, index) in chatStore.selectedChatRoom.messages.toReversed()"
:key="index"
:class="['chat-message', message.userId]"
>
<div class="message-content">
<span class="username">{{ message.user }}:</span>
<span class="username" :style="{ color: chatStore.selectedChatRoom.color }">{{ message.userName }}:</span>
<span class="text">{{ message.text }}</span>
<!-- <span class="timestamp">{{ message.time }}</span> -->
<!-- <span class="timestamp">{{ message.timestamp }}</span> -->
</div>
</div>
</div>
<div class="chat-input">
<div class="target">To (Lobby):</div>
<div class="target" :style="{ color: chatStore.selectedChatRoom.color }">To ({{ chatStore.selectedChatRoom.name }}):</div>
<input ref="textBox" v-model="newMessage" @keydown.enter="sendMessage" placeholder="Type here to chat. Use '/' for commands." />
</div>
</div>
</template>
<script lang="ts" setup>
import Button from "@renderer/components/controls/Button.vue";
import { battleStore } from "@renderer/store/battle.store";
import { onKeyDown } from "@vueuse/core";
import { ref, useTemplateRef } from "vue";
import { chatActions, ChatRoom, chatStore } from "@renderer/store/chat.store";
import { me } from "@renderer/store/me.store";
import { onKeyDown, useMagicKeys } from "@vueuse/core";
import { ref, useTemplateRef, watch } from "vue";
interface Message {
user: string;
text: string;
time: string;
}
const keys = useMagicKeys();
const shiftEnter = keys["Shift+Enter"];
const messages = ref<Message[]>([
{ user: "Player2", text: "Ready for the game?", time: "12:02" },
{ user: "Player1", text: "Hello, team!", time: "12:01" },
]);
const newMessage = ref("");
const isExpanded = ref(false);
const textBox = useTemplateRef<HTMLInputElement>("textBox");
const sendMessage = () => {
if (newMessage.value.trim() !== "") {
const time = new Date().toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
messages.value.unshift({
user: "You",
chatActions.sendMessage({
userId: me.userId,
userName: me.username,
text: newMessage.value,
time,
timestamp: Date.now(),
});
newMessage.value = "";
}
Expand All @@ -60,20 +68,22 @@ const collapseChat = () => {
isExpanded.value = false;
};
onKeyDown(
"Enter",
(e) => {
if (isExpanded.value) {
e.preventDefault();
e.stopPropagation();
} else {
e.preventDefault();
e.stopPropagation();
textBox.value?.focus();
}
},
{ target: document }
);
const clickTab = (chatRoom: ChatRoom) => {
chatActions.selectChatRoom(chatRoom.id);
textBox.value?.focus();
};
const closeChatRoom = (e: Event, chatRoom: ChatRoom) => {
e.stopPropagation();
chatActions.closeChatRoom(chatRoom.id);
textBox.value?.focus();
};
watch(shiftEnter, () => {
if (!isExpanded.value) {
textBox.value?.focus();
}
});
onKeyDown(
"Escape",
Expand All @@ -90,40 +100,98 @@ onKeyDown(
</script>

<style lang="scss" scoped>
.tabs {
transition: all 0.4s ease-in-out;
flex-direction: row;
background: black;
width: 100%;
border-bottom: 1px solid rgba(255, 255, 255, 0.15);
display: flex;
font-size: 14px;
.tab {
padding-left: 8px;
padding-top: 8px;
}
.button {
background: rgb(0, 0, 0);
border: none;
color: rgba(255, 255, 255, 0.5);
flex-grow: 0;
:deep(> button) {
padding: 0 20px;
}
&:hover,
&.active {
color: #fff;
background: rgba(255, 255, 255, 0.05);
}
}
}
.unread-messages-dot {
position: absolute;
left: 8px;
display: none;
font-size: 8px;
margin-right: 8px;
color: rgb(226, 91, 91);
&.active {
display: inline;
}
}
.close-button {
position: absolute;
z-index: 1;
right: 4px;
padding: 4px;
cursor: pointer;
line-height: 8px;
font-size: 10px;
border-radius: 200px;
&:hover {
background-color: rgba(255, 255, 255, 0.1);
}
}
.chat-container {
position: absolute;
left: 50%;
transform: translateX(-50%);
bottom: 20px;
display: flex;
flex-direction: column;
height: 100px;
height: 150px;
transition: all 0.4s ease-in-out;
width: 50%;
max-width: 600px;
z-index: 3;
&.expanded {
mask-image: none;
height: 500px;
height: 400px;
}
&.translated {
transform: translateX(0%) translateY(-20%);
left: 1000px;
transform: translate(20px, 0);
}
}
.chat-container.expanded:after {
@extend .fullsize;
left: 0;
top: 0;
background-image: url("/src/renderer/assets/images/squares.png");
background-size: auto;
opacity: 0.3;
mix-blend-mode: overlay; // doesn't support transition
z-index: -1;
}
// tabs are fading out when chat is collapsed
.chat-container:not(.expanded) .tabs {
opacity: 0;
}
.chat-messages {
mask-image: linear-gradient(to top, #000 0 50%, transparent);
mask-image: linear-gradient(to top, #000 0 70%, transparent);
flex-grow: 1;
padding: 8px;
display: flex;
Expand All @@ -138,7 +206,7 @@ onKeyDown(
}
.chat-message {
font-size: 15px;
font-size: 13px;
}
.message-content {
Expand All @@ -150,7 +218,6 @@ onKeyDown(
.username {
font-weight: bold;
color: #87ceeb;
}
.timestamp {
Expand All @@ -167,15 +234,15 @@ onKeyDown(
.chat-input .target {
padding: 4px 8px;
color: #87ceeb;
font-weight: semibold;
font-weight: bold;
font-size: 13px;
}
.chat-input input {
flex-grow: 1;
padding: 0px 8px;
color: #e0e0e0;
font-size: 15px;
font-size: 13px;
}
// .chat-input button {
Expand Down
121 changes: 121 additions & 0 deletions src/renderer/store/chat.store.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import { reactive } from "vue";

export interface ChatRoom {
id: string;
name: string;
color: string;
messages: ChatMessage[];
unreadMessages: number;
closeable: boolean;
}

export interface ChatMessage {
userId: string;
userName: string;
text: string;
timestamp: number;
}

export enum WellKnownChatRooms {
General = "general",
Lobby = "lobby",
}

const defaultChatRooms: ChatRoom[] = [
{
id: WellKnownChatRooms.General,
name: "General",
color: "#87ceeb",
messages: [
{
userId: "System",
userName: "System",
text: "Welcome to the chat!",
timestamp: Date.now(),
},
],
unreadMessages: 0,
closeable: false,
},
{
id: WellKnownChatRooms.Lobby,
name: "Lobby",
color: "#87ceeb",
messages: [
{
userId: "System",
userName: "System",
text: "Welcome to the lobby!",
timestamp: Date.now(),
},
{
userId: "System",
userName: "System",
text: "This is a place to chat with other players.",
timestamp: Date.now(),
},
{
userId: "System",
userName: "System",
text: "Please be respectful and follow the rules.",
timestamp: Date.now(),
},
{
userId: "System",
userName: "System",
text: "Enjoy your stay!",
timestamp: Date.now(),
},
],
unreadMessages: 4,
closeable: false,
},
{
id: "101",
name: "Melon",
color: "#ff6347",
messages: [
{
userId: "101",
userName: "Melon",
text: "Welcome to the Melon chat!",
timestamp: Date.now(),
},
],
unreadMessages: 1,
closeable: true,
},
];

export const chatStore = reactive<{
chatRooms: ChatRoom[];
selectedChatRoom: ChatRoom;
}>({
chatRooms: defaultChatRooms,
selectedChatRoom: defaultChatRooms.at(0),
});

export const chatActions = {
selectChatRoom(id: string) {
chatStore.selectedChatRoom = chatStore.chatRooms.find((room) => room.id === id);
chatStore.selectedChatRoom.unreadMessages = 0;
},
sendMessage(message: ChatMessage) {
chatStore.selectedChatRoom.messages.push(message);
},
addMessage(roomId: string, message: ChatMessage) {
const room = chatStore.chatRooms.find((room) => room.id === roomId);
if (room) {
room.messages.push(message);
if (chatStore.selectedChatRoom.id !== roomId) {
room.unreadMessages++;
}
}
},
closeChatRoom(id: string) {
chatStore.chatRooms = chatStore.chatRooms.filter((room) => room.id !== id);
},
openChatRoom(room: ChatRoom) {
chatStore.chatRooms.push(room);
},
};

0 comments on commit 03ebb43

Please sign in to comment.