diff --git a/internal/frontend/lobby.js b/internal/frontend/lobby.js index 3c20509e..349cdbb4 100644 --- a/internal/frontend/lobby.js +++ b/internal/frontend/lobby.js @@ -92,21 +92,6 @@ function hideMenu() { menu.hidePopover(); } -// Since chromes implementation of the popup is dumb, we can't position -// it correctly without javascript. -if (!navigator.userAgent.toLowerCase().includes("firefox")) { - const menu_button = document.getElementById("menu-button"); - menu.addEventListener("toggle", (event) => { - if (event.newState === "open") { - const bounds = menu_button.getBoundingClientRect(); - // Technically this won't correctly handle the scrolling - // position, but we'll cope for now. - menu.style.top = bounds.bottom + "px"; - menu.style.left = bounds.left + "px"; - } - }); -} - function showDialog(id, title, contentNode, buttonBar) { hideMenu(); @@ -999,21 +984,23 @@ const handleEvent = (parsed) => { // We don't do this in applyWordHints because that's called in all kinds of places if (parsed.data.some((hint) => hint.character)) { - var hints = parsed.data.map((hint) => { - if (hint.character) { - var char = String.fromCharCode(hint.character); - if (char === " " || hint.revealed) { - return char; - } - } - return "_"; - }).join(" "); - appendMessage( - ["system-message", "hint-chat-message"], - '{{.Translation.Get "system"}}', - '{{.Translation.Get "word-hint-revealed"}}\n' + hints, - { "dir": wordContainer.getAttribute("dir") }, - ); + var hints = parsed.data + .map((hint) => { + if (hint.character) { + var char = String.fromCharCode(hint.character); + if (char === " " || hint.revealed) { + return char; + } + } + return "_"; + }) + .join(" "); + appendMessage( + ["system-message", "hint-chat-message"], + '{{.Translation.Get "system"}}', + '{{.Translation.Get "word-hint-revealed"}}\n' + hints, + { dir: wordContainer.getAttribute("dir") }, + ); } } else if (parsed.type === "message") { appendMessage(null, parsed.data.author, parsed.data.content); @@ -1409,11 +1396,11 @@ function appendMessage(styleClass, author, message, attrs) { const newMessageDiv = document.createElement("div"); newMessageDiv.classList.add("message"); if (isString(styleClass)) { - styleClass = [styleClass]; + styleClass = [styleClass]; } - for (const cls of styleClass){ - newMessageDiv.classList.add(cls); + for (const cls of styleClass) { + newMessageDiv.classList.add(cls); } if (author !== null && author !== "") { @@ -1429,11 +1416,11 @@ function appendMessage(styleClass, author, message, attrs) { newMessageDiv.appendChild(messageSpan); if (attrs !== null && attrs !== "") { - if (isObject(attrs)) { - for (const [attrKey, attrValue] of Object.entries(attrs)) { - messageSpan.setAttribute(attrKey, attrValue); + if (isObject(attrs)) { + for (const [attrKey, attrValue] of Object.entries(attrs)) { + messageSpan.setAttribute(attrKey, attrValue); + } } - } } messageContainer.appendChild(newMessageDiv); @@ -1608,17 +1595,16 @@ function updateRoundsDisplay() { const applyWordHints = (wordHints, dummy) => { const isDrawer = drawerID === ownID; - // We abuse the container to prevent the layout from jumping. - if (!dummy) { - wordContainer.style.visibility = "visible"; - } else { - wordContainer.style.visibility = "hidden"; - } + let wordLengths = []; + let count = 0; wordContainer.replaceChildren( - ...wordHints.map((hint) => { + ...wordHints.map((hint, index) => { const hintSpan = document.createElement("span"); hintSpan.classList.add("hint"); + if (dummy) { + hintSpan.style.visibility = "hidden"; + } if (hint.character === 0) { hintSpan.classList.add("hint-underline"); hintSpan.innerHTML = " "; @@ -1629,6 +1615,17 @@ const applyWordHints = (wordHints, dummy) => { hintSpan.innerText = String.fromCharCode(hint.character); } + // space + if (hint.character === 32) { + wordLengths.push(count); + count = 0; + } else if (index === wordHints.length - 1) { + count += 1; + wordLengths.push(count); + } else { + count += 1; + } + if (hint.revealed && isDrawer) { hintSpan.classList.add("hint-revealed"); } @@ -1636,6 +1633,15 @@ const applyWordHints = (wordHints, dummy) => { return hintSpan; }), ); + + const lengthHint = document.createElement("sub"); + lengthHint.classList.add("word-length-hint"); + if (dummy) { + lengthHint.style.visibility = "hidden"; + } + lengthHint.setAttribute("dir", wordContainer.getAttribute("dir")); + lengthHint.innerText = `(${wordLengths.join(", ")})`; + wordContainer.appendChild(lengthHint); }; const set_dummy_word_hints = () => { @@ -2027,14 +2033,16 @@ function getCookie(name) { } function isString(obj) { - return typeof obj === 'string'; + return typeof obj === "string"; } function isObject(obj) { - return typeof obj === 'object' && - obj !== null && - !Array.isArray(obj) && - Object.prototype.toString.call(obj) === '[object Object]'; + return ( + typeof obj === "object" && + obj !== null && + !Array.isArray(obj) && + Object.prototype.toString.call(obj) === "[object Object]" + ); } const connectToWebsocket = () => { diff --git a/internal/frontend/resources/lobby.css b/internal/frontend/resources/lobby.css index 0c5e9a8b..f904b764 100644 --- a/internal/frontend/resources/lobby.css +++ b/internal/frontend/resources/lobby.css @@ -114,7 +114,8 @@ kbd { grid-gap: 5px; } -#lobby-header > * { +#lobby-header > *, +#menu-button-container { background-color: white; height: 100%; align-items: center; @@ -125,6 +126,7 @@ kbd { #lobby-header-center-element { display: flex; + justify-content: center; /* Hack to remove extra space between buttons */ font-size: 0; } @@ -160,6 +162,12 @@ kbd { overflow-x: hidden; } +.word-length-hint { + font-family: monospace; + font-size: 0.8rem; + padding-top: 0.4rem; +} + .hint-chat-message { white-space: pre; text-wrap-mode: wrap; @@ -668,11 +676,50 @@ kbd { } #lobby-header { - grid-template-columns: max-content auto max-content; - - grid-column-start: 1; + background-color: transparent; + display: grid; grid-column-end: 3; + grid-column-start: 1; + grid-gap: 5px; grid-row: 1; + grid-template-columns: 1fr auto 1fr; + grid-template-rows: auto auto; + } + + #lobby-header-center-element { + display: contents; + } + + #menu-button-container { + grid-column: 2; + grid-row: 1; + display: flex; + justify-content: center; + } + + #round-container { + grid-column: 1; + grid-row: 1; + justify-content: flex-start; + } + + #time-left { + grid-column: 3; + grid-row: 1; + justify-content: flex-end; + } + + #word-container { + grid-column: 1 / 4; + grid-row: 2; + background-color: white; + align-items: center; + padding: 0.1rem 0.2rem; + border-radius: var(--component-border-radius); + column-gap: 0.2rem; + width: auto; + min-width: 0; + overflow: visible; } #round-container, @@ -727,9 +774,21 @@ kbd { } } +#menu-button-container { + position: relative; +} + +#menu-button { + anchor-name: menu-anchor; +} + #menu { - position: absolute; - inset: unset; + position-anchor: menu-anchor; + position-area: bottom span-right; + position-try-fallbacks: flip-block, flip-inline; + + margin: 0; + inset: auto; border: 1px solid gray; border-radius: var(--component-border-radius); } diff --git a/internal/frontend/templates/lobby.html b/internal/frontend/templates/lobby.html index 5f4cf08c..327a7c2d 100644 --- a/internal/frontend/templates/lobby.html +++ b/internal/frontend/templates/lobby.html @@ -33,77 +33,75 @@