diff --git a/frontend/index.html b/frontend/index.html index b58c401..b6489a4 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -53,7 +53,8 @@
diff --git a/frontend/lib/chat.mjs b/frontend/lib/chat.mjs index 39c8178..4aed5cd 100644 --- a/frontend/lib/chat.mjs +++ b/frontend/lib/chat.mjs @@ -1,19 +1,40 @@ import { setDebounce, setVideoTime, setPlaying } from "./watch-session.mjs?v=9"; -import { emojify } from "./emojis.mjs?v=9"; +import { emojify, emojis } from "./emojis.mjs?v=9"; const setupChatboxEvents = (socket) => { // clear events by just reconstructing the form const oldChatForm = document.querySelector("#chatbox-send"); const chatForm = oldChatForm.cloneNode(true); + const messageInput = chatForm.querySelector("input"); + const emojiAutocomplete = chatForm.querySelector("#emoji-autocomplete"); oldChatForm.replaceWith(chatForm); + + let autocompleting = false; + + const replaceMessage = message => () => { + messageInput.value = message; + autocomplete(); + } + async function autocomplete(){ + if(autocompleting) return; + emojiAutocomplete.textContent = ""; + autocompleting = true; + let text = messageInput.value.slice(0, messageInput.selectionStart); + const match = text.match(/(:[^\s:]+)?:[^\s:]*$/); + if(!match || match[1]) return autocompleting = false; // We don't need to autocomplete. + const prefix = text.slice(0, match.index); + const search = text.slice(match.index + 1); + const suffix = messageInput.value.slice(messageInput.selectionStart); + emojiAutocomplete.append(...(await emojis).filter(e => e.toLowerCase().startsWith(search.toLowerCase())).map(e => Object.assign(document.createElement("button"), {className: "emoji-option", textContent: e, onclick: replaceMessage(prefix + ":" + e + ":" + suffix)}))) + autocompleting = false; + } + messageInput.addEventListener("input", autocomplete) chatForm.addEventListener("submit", async (e) => { e.preventDefault(); - - const input = chatForm.querySelector("input"); - const content = input.value; + const content = messageInput.value; if (content.trim().length) { - input.value = ""; + messageInput.value = ""; // handle commands if (content.startsWith("/")) { @@ -82,39 +103,20 @@ const setupChatboxEvents = (socket) => { }); }; -const fixChatSize = () => { - const video = document.querySelector("video"); - const chatbox = document.querySelector("#chatbox"); - const chatboxContainer = document.querySelector("#chatbox-container"); - - if (video && chatbox && chatboxContainer) { - const delta = chatboxContainer.clientHeight - chatbox.clientHeight; - - chatbox.style["height"] = `calc(${ - window.innerHeight - video.clientHeight - }px - ${delta}px - 1em)`; - } -}; - /** * @param {WebSocket} socket */ export const setupChat = async (socket) => { - document.querySelector("#chatbox-container").style["display"] = "block"; + document.querySelector("#chatbox-container").style["display"] = "flex"; setupChatboxEvents(socket); 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(); + messageInput.focus(); } catch (_err) {} }); - - fixChatSize(); - window.addEventListener("resize", () => { - fixChatSize(); - }); }; const addToChat = (node) => { @@ -185,7 +187,7 @@ const printChatMessage = (eventType, user, colour, content) => { if (user != null) { const userName = document.createElement("strong"); - userName.style = `color: #${colour}`; + userName.style = `--user-color: #${colour}`; userName.textContent = user; chatMessage.appendChild(userName); } @@ -327,7 +329,7 @@ export const updateViewerList = (viewers) => { const viewerElem = document.createElement("div"); const content = document.createElement("strong"); content.textContent = viewer.nickname; - content.style = `color: #${viewer.colour}`; + content.style = `--user-color: #${viewer.colour}`; viewerElem.appendChild(content); listContainer.appendChild(viewerElem); } diff --git a/frontend/lib/emojis.mjs b/frontend/lib/emojis.mjs index b97c31e..e65fc88 100644 --- a/frontend/lib/emojis.mjs +++ b/frontend/lib/emojis.mjs @@ -8,4 +8,5 @@ export function emojify(text) { }) if(last < text.length) nodes.push(document.createTextNode(text.slice(last))) return nodes -} \ No newline at end of file +} +export const emojis = Promise.resolve(["blobcat", "blobhaj"]) \ No newline at end of file diff --git a/frontend/lib/video.mjs b/frontend/lib/video.mjs index 79b925e..317a13d 100644 --- a/frontend/lib/video.mjs +++ b/frontend/lib/video.mjs @@ -140,7 +140,9 @@ const createVideoElement = (videoUrl, subtitles) => { export const setupVideo = async (videoUrl, subtitles, currentTime, playing) => { document.querySelector("#pre-join-controls").style["display"] = "none"; const video = createVideoElement(videoUrl, subtitles); - document.querySelector("#video-container").appendChild(video); + const videoContainer = document.querySelector("#video-container"); + videoContainer.style.display = "block"; + videoContainer.appendChild(video); video.currentTime = currentTime / 1000.0; diff --git a/frontend/styles.css b/frontend/styles.css index 7f019fe..8f9a91e 100644 --- a/frontend/styles.css +++ b/frontend/styles.css @@ -1,7 +1,15 @@ :root { - --bg: rgb(28, 23, 36); - --fg: rgb(234, 234, 248); - --accent: hsl(275, 57%, 68%); + --bg-rgb: 28, 23, 36; + --fg-rgb: 234, 234, 248; + --accent-rgb: 181, 127, 220; + --fg: rgb(var(--fg-rgb)); + --bg: rgb(var(--bg-rgb)); + --default-user-color: rgb(126, 208, 255); + --accent: rgb(var(--accent-rgb)); + --fg-transparent: rgba(var(--fg-rgb), 0.125); + --bg-transparent: rgba(var(--bg-rgb), 0.125); + --chat-bg: linear-gradient(var(--fg-transparent), var(--fg-transparent)), linear-gradient(var(--bg), var(--bg)); + --autocomplete-bg: linear-gradient(var(--fg-transparent), var(--fg-transparent)), linear-gradient(var(--fg-transparent), var(--fg-transparent)), linear-gradient(var(--bg), var(--bg)); } html { @@ -9,30 +17,34 @@ html { color: var(--fg); font-size: 1.125rem; font-family: sans-serif; - - overflow-y: scroll; - scrollbar-width: none; - -ms-overflow-style: none; -} - -::-webkit-scrollbar { - width: 0; - background: transparent; } html, body { margin: 0; + padding: 0; + overflow: hidden; + overscroll-behavior: none; + width: 100%; + height: 100%; +} + +body { + display: flex; + flex-direction: column; } video { display: block; + width: 100%; + height: 100%; + object-fit: contain; +} - width: 100vw; - height: auto; - - max-width: auto; - max-height: 100vh; +#video-container { + flex-grow: 1; + flex-shrink: 1; + display: none; } a { @@ -72,7 +84,7 @@ button { background-color: var(--accent); border: var(--accent); border-radius: 6px; - color: #fff; + color: var(--fg); padding: 0.5em 1em; display: inline-block; font-weight: 400; @@ -88,6 +100,7 @@ button { user-select: none; border: 1px solid rgba(0, 0, 0, 0); line-height: 1.5; + cursor: pointer; } button.small-button { @@ -133,8 +146,16 @@ button.small-button { overflow-wrap: break-word; } -.chat-message > strong { - color: rgb(126, 208, 255); +.chat-message > strong, #viewer-list strong { + color: var(--user-color, var(--default-user-color)); +} + +@supports (-webkit-background-clip: text) { + .chat-message > strong, #viewer-list strong { + background: linear-gradient(var(--fg-transparent), var(--fg-transparent)), linear-gradient(var(--user-color, var(--default-user-color)), var(--user-color, var(--default-user-color))); + -webkit-background-clip: text; + color: transparent !important; + } } .chat-message.user-join, @@ -168,27 +189,34 @@ button.small-button { #chatbox { padding: 0.5em 2em; - min-height: 8em; overflow-y: scroll; + flex-shrink: 1; + flex-grow: 1; } #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; + max-height: 4rem; + flex-shrink: 0; } #chatbox-container { - background-color: #222; + background-image: var(--chat-bg); + flex-direction: column; + flex-grow: 0; + flex-shrink: 1; + flex-basis: 400px; + overflow: hidden; } #chatbox-send { padding: 0 2em; padding-bottom: 0.5em; + position: relative; } #chatbox-send > input { @@ -196,22 +224,40 @@ button.small-button { width: 100%; } -@media (min-aspect-ratio: 4/3) { - #video-container video { - width: calc(100vw - 400px); - position: absolute; - height: 100vh; - background-color: black; - } +#emoji-autocomplete { + position: absolute; + bottom: 3.25rem; + background-image: var(--autocomplete-bg); + padding: 0.25rem; + border-radius: 6px; + width: calc(100% - 4.5rem); +} - #video-container { - float: left; - height: 100vh; - position: relative; +#emoji-autocomplete:empty { + display: none; +} + +.emoji-option { + background: transparent; + font-size: 0.75rem; + text-align: left; + margin: 0 0 0.25rem; + border-radius: 4px; + width: 100%; +} +.emoji-option:hover, .emoji-option:focus { + background: var(--fg-transparent); +} +.emoji-option:last-child { + margin: 0; +} + +@media (min-aspect-ratio: 4/3) { + body { + flex-direction: row; } #chatbox-container { - float: right; width: 400px; height: 100vh !important; }