forked from lavender/watch-party
		
	allow users to pick their username colour
This commit is contained in:
		
							parent
							
								
									727e72d89f
								
							
						
					
					
						commit
						152d51f4fc
					
				
					 11 changed files with 88 additions and 30 deletions
				
			
		|  | @ -47,6 +47,6 @@ | |||
|       </p> | ||||
|     </div> | ||||
| 
 | ||||
|     <script type="module" src="/create.mjs?v=5"></script> | ||||
|     <script type="module" src="/create.mjs?v=7"></script> | ||||
|   </body> | ||||
| </html> | ||||
|  |  | |||
|  | @ -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(); | ||||
|  |  | |||
|  | @ -29,6 +29,16 @@ | |||
|           required | ||||
|         /> | ||||
|          | ||||
|         <label for="join-session-colour">Colour:</label> | ||||
|         <input | ||||
|           type="text" | ||||
|           id="join-session-colour" | ||||
|           placeholder="7ed0ff" | ||||
|           value="7ed0ff" | ||||
|           pattern="[a-fA-F\d]{6}" | ||||
|           required | ||||
|         /> | ||||
| 
 | ||||
|         <label for="join-session-id">Session ID:</label> | ||||
|         <input | ||||
|           type="text" | ||||
|  | @ -52,6 +62,6 @@ | |||
|       </form> | ||||
|     </div> | ||||
| 
 | ||||
|     <script type="module" src="/main.mjs?v=6"></script> | ||||
|     <script type="module" src="/main.mjs?v=7"></script> | ||||
|   </body> | ||||
| </html> | ||||
|  |  | |||
|  | @ -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; | ||||
|     } | ||||
|   } | ||||
|  |  | |||
|  | @ -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"); | ||||
|  |  | |||
|  | @ -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); | ||||
|   }); | ||||
| }; | ||||
|  |  | |||
|  | @ -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, | ||||
|  |  | |||
|  | @ -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(); | ||||
|  |  | |||
|  | @ -15,6 +15,8 @@ pub enum WatchEventData { | |||
| pub struct WatchEvent { | ||||
|     #[serde(default, skip_serializing_if = "Option::is_none")] | ||||
|     pub user: Option<String>, | ||||
|     #[serde(default, skip_serializing_if = "Option::is_none")] | ||||
|     pub colour: Option<String>, | ||||
|     #[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, | ||||
|         } | ||||
|  |  | |||
|  | @ -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(), | ||||
|             }, | ||||
|  |  | |||
|  | @ -28,9 +28,10 @@ pub struct ConnectedViewer { | |||
|     pub viewer_id: usize, | ||||
|     pub tx: UnboundedSender<WatchEvent>, | ||||
|     pub nickname: Option<String>, | ||||
|     pub colour: Option<String>, | ||||
| } | ||||
| 
 | ||||
| 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; | ||||
| 
 | ||||
|  |  | |||
		Loading…
	
		Reference in a new issue