Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(client): ✨ spectators can go back and view other chats #300

Open
wants to merge 5 commits into
base: development
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 18 additions & 1 deletion client/src/components/ChatRoom.svelte
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<script lang="ts">
import ChatMessages from "$components/ChatMessages.svelte";
import Stalker from "$components/Stalker.svelte";
import { onMount, onDestroy } from "svelte";
import { onMount, onDestroy, createEventDispatcher } from "svelte";
import { authStore } from "$stores/auth";
import { onSnapshot, orderBy, Query, query, where, type Unsubscribe } from "firebase/firestore";
import type { ChatMessage, ChatRoom, Lobby, Player } from "$lib/firebase/firestore-types/lobby";
Expand All @@ -24,6 +24,9 @@
let unsubscribeChatRooms: Unsubscribe | undefined = undefined;
let unsubscribeChatMessages: Unsubscribe | undefined = undefined;

// dispatch only for spectators
const dispatch = createEventDispatcher<{ chatId: { chatRoomId: string } }>();

onMount(() => {
const chatRoomCollection = getChatRoomCollection(lobbyCode);
let roomQuerry: Query<ChatRoom>;
Expand All @@ -38,6 +41,11 @@
(roomsSnapshot) => {
// skip if chatRoom not found yet
if (roomsSnapshot.docs.length == 0) {
// this means they left this room
// will render the stalker component
if (chatRoomId !== undefined) {
chatRoomId = undefined;
}
return;
}

Expand Down Expand Up @@ -102,6 +110,15 @@
errorMessage = err instanceof Error ? err.message : String(err);
}
}

// once the spectator clicked on the room
// we want to pass up the id to the game page
// that is so when we leave the chatroom we know exactly the chatroom we need to leave from
$: if (chatRoomId !== undefined && isSpectator) {
dispatch("chatId", {
chatRoomId,
});
}
</script>

<div class="chatroom">
Expand Down
2 changes: 2 additions & 0 deletions client/src/lib/firebase/firebase-functions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import type {
StalkChatroomRequest,
LobbySettingsRequest,
KickBanRequest,
LeaveLobbyRequest,
} from "./firebase-functions-types";
import type { ChangeAvatarData } from "./functions-types/avatar";

Expand All @@ -20,3 +21,4 @@ export const changeAvatar = httpsCallable<ChangeAvatarData, void>(functions, "ch
export const verifyExpiration = httpsCallable<LobbyRequest, void>(functions, "verifyExpiration");
export const kick = httpsCallable<KickBanRequest, void>(functions, "kick");
export const ban = httpsCallable<KickBanRequest, void>(functions, "ban");
export const removeFromChatroom = httpsCallable<LeaveLobbyRequest, void>(functions, "removeFromChatroom");
33 changes: 32 additions & 1 deletion client/src/routes/game/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
import LobbyChat from "$components/LobbyChat.svelte";
import Header from "$components/Header.svelte";
import LobbySettings from "$components/LobbySettings.svelte";
import IconButton from "@smui/icon-button";
import Mdi from "$components/Mdi.svelte";

import { onSnapshot, doc, getDoc, type Unsubscribe } from "firebase/firestore";
import { onMount, onDestroy } from "svelte";
Expand All @@ -18,8 +20,9 @@
import { page } from "$app/stores";
import { authStore as user } from "$stores/auth";
import { goto } from "$app/navigation";
import { verifyExpiration } from "$lib/firebase/firebase-functions";
import { removeFromChatroom, verifyExpiration } from "$lib/firebase/firebase-functions";
import { formatTimer, DISPLAY_TIMERS } from "$lib/time";
import { mdiArrowLeft } from "@mdi/js";

let lobbyCode: string | null = null;

Expand All @@ -33,6 +36,8 @@
$: countdownVisible = lobby != undefined && DISPLAY_TIMERS[lobby.state] == true;
let timer: ReturnType<typeof setInterval>;

let spectatorChatroomId: string | undefined = undefined;

let errorMessage: string = "";

function updateCountdown() {
Expand Down Expand Up @@ -161,6 +166,29 @@
<svelte:window on:beforeunload={onbeforeunload} />

<Header>
<svelte:fragment slot="top-left">
{#if lobbyCode !== null && lobby !== undefined && $user != null}
{#if lobby.state === "CHAT"}
{#if !lobby.alivePlayers.includes($user.uid) && spectatorChatroomId !== undefined}
<IconButton
on:click|once={async () => {
// doesn't recognize the svelte if checks above
if (lobbyCode == null || spectatorChatroomId == undefined) {
return;
}

await removeFromChatroom({ code: lobbyCode, chatId: spectatorChatroomId });
// make it undefined because we don't want the back button to show on the select a chat view
spectatorChatroomId = undefined;
}}
>
<Mdi path={mdiArrowLeft} />
</IconButton>
{/if}
{/if}
{/if}
</svelte:fragment>

<svelte:fragment slot="top-right">
{#if lobbyCode !== null && lobby !== undefined && $user != null}
{#if lobby.state === "WAIT"}
Expand Down Expand Up @@ -215,6 +243,9 @@
{lobbyCode}
isStalker={privatePlayer.stalker}
isSpectator={!lobby.alivePlayers.includes($user.uid)}
on:chatId={(event) => {
spectatorChatroomId = event.detail.chatRoomId;
}}
/>
{:else if lobby.state === "VOTE"}
<Vote {lobby} {lobbyCode} />
Expand Down
31 changes: 30 additions & 1 deletion functions/src/chat.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Timestamp } from "firebase-admin/firestore";
import * as functions from "firebase-functions";
import { FieldValue, Timestamp } from "firebase-admin/firestore";
import type { DocumentReference, Transaction } from "firebase-admin/firestore";
import { db } from "./app";
import {
Expand All @@ -7,8 +8,10 @@ import {
getPrivatePlayerCollection,
getLobbyChatCollection,
getPromptAnswerCollection,
lobbyCollection,
} from "./firestore-collections";
import { Lobby } from "./firestore-types/lobby";
import { isStalkChatroomRequest as isLeaveLobbyRequest } from "./firebase-functions-types";

export async function deleteLobbyChatMessages(lobbyDoc: DocumentReference<Lobby>) {
const messages = await getLobbyChatCollection(lobbyDoc).get();
Expand Down Expand Up @@ -69,3 +72,29 @@ export async function setAndDeleteAnswers(

transaction.update(lobbyDoc, { state: "VOTE", expiration, players });
}

export const removeFromChatroom = functions.https.onCall((data: unknown, context): Promise<void> => {
const auth = context.auth;
if (auth === undefined) {
throw new functions.https.HttpsError("permission-denied", "User is not Authenticated");
}

if (!isLeaveLobbyRequest(data)) {
throw new functions.https.HttpsError("invalid-argument", "Data is not of isLeaveLobbyRequest type");
}

const lobbyDocRef = lobbyCollection.doc(data.code);
return db.runTransaction(async (transaction) => {
const chatRoomCollection = getChatRoomCollection(lobbyDocRef);
const chatRoomRef = chatRoomCollection.doc(data.chatId);

const chatRoom = await transaction.get(chatRoomRef);
if (!chatRoom.exists) {
throw new functions.https.HttpsError("not-found", "Chatroom not found");
}

transaction.update(chatRoomRef, {
viewers: FieldValue.arrayRemove(auth.uid),
});
});
});
2 changes: 2 additions & 0 deletions functions/src/firebase-functions-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ export type StalkChatroomRequest = { code: string; chatId: string };

export type KickBanRequest = { code: string; uid: string };

export type LeaveLobbyRequest = StalkChatroomRequest;

export function isLobbyRequest(data: unknown): data is LobbyRequest {
// will only return true if the data is an object with a code property and string
return data != null && typeof data === "object" && "code" in data && typeof (data as LobbyRequest).code === "string";
Expand Down