diff --git a/frontend/create.html b/frontend/create.html index e560149..29ddb29 100644 --- a/frontend/create.html +++ b/frontend/create.html @@ -47,6 +47,6 @@

- + diff --git a/frontend/create.mjs b/frontend/create.mjs index bf2e0eb..dc8f441 100644 --- a/frontend/create.mjs +++ b/frontend/create.mjs @@ -1,4 +1,4 @@ -import { setupCreateSessionForm } from "./lib/create-session.mjs?v=5"; +import { setupCreateSessionForm } from "./lib/create-session.mjs?v=7"; const main = () => { setupCreateSessionForm(); diff --git a/frontend/index.html b/frontend/index.html index b51e20d..c09f3df 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -28,6 +28,16 @@ placeholder="Nickname" required /> + + + - + diff --git a/frontend/lib/chat.mjs b/frontend/lib/chat.mjs index b00e2a8..af868e8 100644 --- a/frontend/lib/chat.mjs +++ b/frontend/lib/chat.mjs @@ -43,13 +43,12 @@ export const setupChat = async (socket) => { document.querySelector("#chatbox-container").style["display"] = "block"; setupChatboxEvents(socket); - window.addEventListener("keydown", event => { + window.addEventListener("keydown", (event) => { try { const isSelectionEmpty = window.getSelection().toString().length === 0; if (event.code.match(/Key\w/) && isSelectionEmpty) - document.querySelector("#chatbox-send > input").focus() - } catch (_err) { - } + document.querySelector("#chatbox-send > input").focus(); + } catch (_err) {} }); fixChatSize(); @@ -101,7 +100,9 @@ const checkDebounce = (event) => { */ const getCurrentTimestamp = () => { const t = new Date(); - return `${matpad(t.getHours())}:${matpad(t.getMinutes())}:${matpad(t.getSeconds())}`; + return `${matpad(t.getHours())}:${matpad(t.getMinutes())}:${matpad( + t.getSeconds() + )}`; }; /** @@ -116,7 +117,7 @@ const matpad = (n) => { * @param {string?} user * @param {Node?} content */ -const printChatMessage = (eventType, user, content) => { +const printChatMessage = (eventType, user, colour, content) => { const chatMessage = document.createElement("div"); chatMessage.classList.add("chat-message"); chatMessage.classList.add(eventType); @@ -124,6 +125,7 @@ const printChatMessage = (eventType, user, content) => { if (user != null) { const userName = document.createElement("strong"); + userName.style = `color: #${colour}`; userName.textContent = user; chatMessage.appendChild(userName); } @@ -158,6 +160,7 @@ export const logEventToChat = (event) => { printChatMessage( "user-join", event.user, + event.colour, document.createTextNode("joined") ); break; @@ -166,6 +169,7 @@ export const logEventToChat = (event) => { printChatMessage( "user-leave", event.user, + event.colour, document.createTextNode("left") ); break; @@ -174,7 +178,12 @@ export const logEventToChat = (event) => { const messageContent = document.createElement("span"); messageContent.classList.add("message-content"); messageContent.textContent = event.data; - printChatMessage("chat-message", event.user, messageContent); + printChatMessage( + "chat-message", + event.user, + event.colour, + messageContent + ); break; } case "SetTime": { @@ -185,7 +194,7 @@ export const logEventToChat = (event) => { document.createTextNode(formatTime(event.data)) ); - printChatMessage("set-time", event.user, messageContent); + printChatMessage("set-time", event.user, event.colour, messageContent); break; } case "SetPlaying": { @@ -200,8 +209,7 @@ export const logEventToChat = (event) => { document.createTextNode(formatTime(event.data.time)) ); - printChatMessage("set-playing", event.user, messageContent); - + printChatMessage("set-playing", event.user, event.colour, messageContent); break; } } diff --git a/frontend/lib/create-session.mjs b/frontend/lib/create-session.mjs index 137fbcd..ea4385b 100644 --- a/frontend/lib/create-session.mjs +++ b/frontend/lib/create-session.mjs @@ -1,4 +1,4 @@ -import { createSession } from "./watch-session.mjs?v=6"; +import { createSession } from "./watch-session.mjs?v=7"; export const setupCreateSessionForm = () => { const form = document.querySelector("#create-session-form"); diff --git a/frontend/lib/join-session.mjs b/frontend/lib/join-session.mjs index 19f565f..338878f 100644 --- a/frontend/lib/join-session.mjs +++ b/frontend/lib/join-session.mjs @@ -1,4 +1,4 @@ -import { joinSession } from "./watch-session.mjs?v=6"; +import { joinSession } from "./watch-session.mjs?v=7"; /** * @param {HTMLInputElement} field @@ -23,6 +23,31 @@ const saveNickname = (field) => { } }; +/** + * @param {HTMLInputElement} field + */ +const loadColour = (field) => { + try { + const savedColour = localStorage.getItem("watch-party-colour"); + if (savedColour != null && savedColour != "") { + field.value = savedColour; + } + } catch (_err) { + // Sometimes localStorage is blocked from use + } +}; + +/** + * @param {HTMLInputElement} field + */ +const saveColour = (field) => { + try { + localStorage.setItem("watch-party-colour", field.value); + } catch (_err) { + // see loadColour + } +}; + const displayPostCreateMessage = () => { const params = new URLSearchParams(window.location.search); if (params.get("created") == "true") { @@ -36,10 +61,12 @@ export const setupJoinSessionForm = () => { const form = document.querySelector("#join-session-form"); const nickname = form.querySelector("#join-session-nickname"); + const colour = form.querySelector("#join-session-colour"); const sessionId = form.querySelector("#join-session-id"); const button = form.querySelector("#join-session-button"); loadNickname(nickname); + loadColour(colour); if (window.location.hash.match(/#[0-9a-f\-]+/)) { sessionId.value = window.location.hash.substring(1); @@ -51,6 +78,7 @@ export const setupJoinSessionForm = () => { button.disabled = true; saveNickname(nickname); - joinSession(nickname.value, sessionId.value); + saveColour(colour); + joinSession(nickname.value, sessionId.value, colour.value); }); }; diff --git a/frontend/lib/watch-session.mjs b/frontend/lib/watch-session.mjs index 0e12e6d..6588142 100644 --- a/frontend/lib/watch-session.mjs +++ b/frontend/lib/watch-session.mjs @@ -1,15 +1,16 @@ -import { setupVideo } from "./video.mjs?v=5"; -import { setupChat, logEventToChat } from "./chat.mjs?v=6"; +import { setupVideo } from "./video.mjs?v=7"; +import { setupChat, logEventToChat } from "./chat.mjs?v=7"; /** * @param {string} sessionId * @param {string} nickname * @returns {WebSocket} */ -const createWebSocket = (sessionId, nickname) => { +const createWebSocket = (sessionId, nickname, colour) => { const wsUrl = new URL( `/sess/${sessionId}/subscribe` + - `?nickname=${encodeURIComponent(nickname)}`, + `?nickname=${encodeURIComponent(nickname)}` + + `&colour=${encodeURIComponent(colour)}`, window.location.href ); wsUrl.protocol = { "http:": "ws:", "https:": "wss:" }[wsUrl.protocol]; @@ -73,7 +74,7 @@ const setupIncomingEvents = (video, socket) => { } logEventToChat(event); - } catch (_err) { } + } catch (_err) {} }); }; @@ -146,14 +147,14 @@ const setupOutgoingEvents = (video, socket) => { * @param {string} nickname * @param {string} sessionId */ -export const joinSession = async (nickname, sessionId) => { +export const joinSession = async (nickname, sessionId, colour) => { try { window.location.hash = sessionId; const { video_url, subtitle_tracks, current_time_ms, is_playing } = await fetch(`/sess/${sessionId}`).then((r) => r.json()); - const socket = createWebSocket(sessionId, nickname); + const socket = createWebSocket(sessionId, nickname, colour); socket.addEventListener("open", async () => { const video = await setupVideo( video_url, diff --git a/frontend/main.mjs b/frontend/main.mjs index 3f6829a..e332bdf 100644 --- a/frontend/main.mjs +++ b/frontend/main.mjs @@ -1,4 +1,4 @@ -import { setupJoinSessionForm } from "./lib/join-session.mjs?v=6"; +import { setupJoinSessionForm } from "./lib/join-session.mjs?v=7"; const main = () => { setupJoinSessionForm(); diff --git a/src/events.rs b/src/events.rs index 910b3a9..49b70ea 100644 --- a/src/events.rs +++ b/src/events.rs @@ -15,6 +15,8 @@ pub enum WatchEventData { pub struct WatchEvent { #[serde(default, skip_serializing_if = "Option::is_none")] pub user: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub colour: Option, #[serde(flatten)] pub data: WatchEventData, #[serde(default)] @@ -22,9 +24,10 @@ pub struct WatchEvent { } impl WatchEvent { - pub fn new(user: String, data: WatchEventData) -> Self { + pub fn new(user: String, colour: String, data: WatchEventData) -> Self { WatchEvent { user: Some(user), + colour: Some(colour), data, reflected: false, } diff --git a/src/main.rs b/src/main.rs index 78824e2..9d570d1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -26,6 +26,7 @@ struct StartSessionBody { #[derive(Deserialize)] struct SubscribeQuery { nickname: String, + colour: String, } #[tokio::main] @@ -85,7 +86,7 @@ async fn main() { .map( |requested_session, query: SubscribeQuery, ws: warb::ws::Ws| match requested_session { RequestedSession::Session(uuid, _) => ws - .on_upgrade(move |ws| ws_subscribe(uuid, query.nickname, ws)) + .on_upgrade(move |ws| ws_subscribe(uuid, query.nickname, query.colour, ws)) .into_response(), RequestedSession::Error(error_response) => error_response.into_response(), }, diff --git a/src/viewer_connection.rs b/src/viewer_connection.rs index 7ceb490..4dc903e 100644 --- a/src/viewer_connection.rs +++ b/src/viewer_connection.rs @@ -28,9 +28,10 @@ pub struct ConnectedViewer { pub viewer_id: usize, pub tx: UnboundedSender, pub nickname: Option, + pub colour: Option, } -pub async fn ws_subscribe(session_uuid: Uuid, nickname: String, ws: WebSocket) { +pub async fn ws_subscribe(session_uuid: Uuid, nickname: String, colour: String, ws: WebSocket) { let viewer_id = NEXT_VIEWER_ID.fetch_add(1, Ordering::Relaxed); let (mut viewer_ws_tx, mut viewer_ws_rx) = ws.split(); @@ -48,6 +49,11 @@ pub async fn ws_subscribe(session_uuid: Uuid, nickname: String, ws: WebSocket) { } }); + let mut colour = colour; + if !colour.len() == 6 || !colour.chars().all(|x| x.is_ascii_hexdigit()) { + colour = String::from("7ed0ff"); + } + CONNECTED_VIEWERS.write().await.insert( viewer_id, ConnectedViewer { @@ -55,13 +61,14 @@ pub async fn ws_subscribe(session_uuid: Uuid, nickname: String, ws: WebSocket) { session: session_uuid, tx, nickname: Some(nickname.clone()), + colour: Some(colour.clone()), }, ); ws_publish( session_uuid, None, - WatchEvent::new(nickname.clone(), WatchEventData::UserJoin), + WatchEvent::new(nickname.clone(), colour.clone(), WatchEventData::UserJoin), ) .await; @@ -84,7 +91,7 @@ pub async fn ws_subscribe(session_uuid: Uuid, nickname: String, ws: WebSocket) { ws_publish( session_uuid, Some(viewer_id), - WatchEvent::new(nickname.clone(), event), + WatchEvent::new(nickname.clone(), colour.clone(), event), ) .await; } @@ -92,7 +99,7 @@ pub async fn ws_subscribe(session_uuid: Uuid, nickname: String, ws: WebSocket) { ws_publish( session_uuid, None, - WatchEvent::new(nickname.clone(), WatchEventData::UserLeave), + WatchEvent::new(nickname.clone(), colour.clone(), WatchEventData::UserLeave), ) .await;