diff --git a/frontend/index.html b/frontend/index.html index 29db5b1..92d958a 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -29,14 +29,9 @@ maxlength="50" required /> - + - +
+
diff --git a/frontend/lib/chat.mjs b/frontend/lib/chat.mjs index 52ca8bf..2b48b7f 100644 --- a/frontend/lib/chat.mjs +++ b/frontend/lib/chat.mjs @@ -267,3 +267,20 @@ const beep = () => { oscillator.start(context.currentTime); oscillator.stop(context.currentTime + 0.22); }; + +export const updateViewerList = (viewers) => { + const listContainer = document.querySelector("#viewer-list"); + + // empty out the current list + listContainer.innerHTML = ""; + + // display the updated list + for (const viewer of viewers) { + const viewerElem = document.createElement("div"); + const content = document.createElement("strong"); + content.textContent = viewer.nickname; + content.style = `color: #${viewer.colour}`; + viewerElem.appendChild(content); + listContainer.appendChild(viewerElem); + } +}; diff --git a/frontend/lib/watch-session.mjs b/frontend/lib/watch-session.mjs index 680ce66..f817f4b 100644 --- a/frontend/lib/watch-session.mjs +++ b/frontend/lib/watch-session.mjs @@ -1,5 +1,5 @@ import { setupVideo } from "./video.mjs?v=8"; -import { setupChat, logEventToChat } from "./chat.mjs?v=8"; +import { setupChat, logEventToChat, updateViewerList } from "./chat.mjs?v=8"; /** * @param {string} sessionId @@ -70,6 +70,9 @@ const setupIncomingEvents = (video, socket) => { setDebounce(); setVideoTime(event.data); break; + case "UpdateViewerList": + updateViewerList(event.data); + break; } } diff --git a/frontend/styles.css b/frontend/styles.css index e8dc8c3..dc1448d 100644 --- a/frontend/styles.css +++ b/frontend/styles.css @@ -161,6 +161,16 @@ button.small-button { overflow-y: scroll; } +#viewer-list { + padding: 0.5em 2em; + /* TODO: turn this into max-height instead of fixed height without breaking the chatbox height */ + height: 4em; + overflow-y: scroll; + color: rgb(126, 208, 255); + border-bottom: var(--fg); + border-bottom-style: solid; +} + #chatbox-container { background-color: #222; } @@ -196,6 +206,6 @@ button.small-button { } #chatbox { - height: calc(100vh - 5em) !important; + height: calc(100vh - 5em - 4em) !important; } } diff --git a/src/events.rs b/src/events.rs index faa7c2e..8dbc5f6 100644 --- a/src/events.rs +++ b/src/events.rs @@ -1,5 +1,13 @@ use serde::{Deserialize, Serialize}; +#[derive(Clone, Serialize, Deserialize)] +pub struct Viewer { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub nickname: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub colour: Option, +} + #[derive(Clone, Serialize, Deserialize)] #[serde(tag = "op", content = "data")] pub enum WatchEventData { @@ -17,6 +25,7 @@ pub enum WatchEventData { UserLeave, ChatMessage(String), Ping(String), + UpdateViewerList(Vec), } #[derive(Clone, Serialize, Deserialize)] diff --git a/src/viewer_connection.rs b/src/viewer_connection.rs index 00c1113..fb62b02 100644 --- a/src/viewer_connection.rs +++ b/src/viewer_connection.rs @@ -15,7 +15,7 @@ use uuid::Uuid; use warp::ws::{Message, WebSocket}; use crate::{ - events::{WatchEvent, WatchEventData}, + events::{Viewer, WatchEvent, WatchEventData}, utils::truncate_str, watch_session::{get_session, handle_watch_event_data}, }; @@ -74,6 +74,8 @@ pub async fn ws_subscribe(session_uuid: Uuid, nickname: String, colour: String, ) .await; + update_viewer_list(session_uuid).await; + while let Some(Ok(message)) = viewer_ws_rx.next().await { let event: WatchEventData = match message .to_str() @@ -113,6 +115,7 @@ pub async fn ws_subscribe(session_uuid: Uuid, nickname: String, colour: String, .await; CONNECTED_VIEWERS.write().await.remove(&viewer_id); + update_viewer_list(session_uuid).await; } pub async fn ws_publish(session_uuid: Uuid, skip_viewer_id: Option, event: WatchEvent) { @@ -127,3 +130,27 @@ pub async fn ws_publish(session_uuid: Uuid, skip_viewer_id: Option, event }); } } + +async fn update_viewer_list(session_uuid: Uuid) { + let mut viewers = Vec::new(); + + for viewer in CONNECTED_VIEWERS.read().await.values() { + if viewer.session == session_uuid { + viewers.push(Viewer { + nickname: viewer.nickname.clone(), + colour: viewer.colour.clone(), + }) + } + } + + ws_publish( + session_uuid, + None, + WatchEvent::new( + String::from("server"), + String::from(""), + WatchEventData::UpdateViewerList(viewers), + ), + ) + .await; +}