diff --git a/ChatTwo/Http/Processing.cs b/ChatTwo/Http/Processing.cs index 75b0394..29e6ccd 100644 --- a/ChatTwo/Http/Processing.cs +++ b/ChatTwo/Http/Processing.cs @@ -50,7 +50,7 @@ public class Processing if (chunk is IconChunk { } icon) { return IconUtil.GfdFileView.TryGetEntry((uint) icon.Icon, out _) - ? $"" + ? $"" : ""; } @@ -63,7 +63,7 @@ public class Processing // The emote name should be safe, it is checked against a list from BTTV. // Still sanitizing it for the extra safety. if (image is { Failed: false }) - return $""; + return $""; } var colour = text.Foreground; @@ -92,4 +92,4 @@ public class Processing return string.Empty; } -} \ No newline at end of file +} diff --git a/ChatTwo/Http/static/start.css b/ChatTwo/Http/static/start.css index 32c2141..725ec27 100644 --- a/ChatTwo/Http/static/start.css +++ b/ChatTwo/Http/static/start.css @@ -38,7 +38,7 @@ * { color: var(--fg); font-family: Lodestone, 'Inter var', sans-serif; - font-feature-settings: 'tnum'; + font-feature-settings: 'tnum', 'calt' 0; /* calt appears to be on by default */ } html { @@ -52,17 +52,58 @@ body { background-color: var(--bg-input-hover); } -body > main { +body > main.chat { display: flex; flex-direction: column; width: 100%; height: 100%; background-color: var(--bg); border-radius: 20px; - box-shadow: 0 0 50px 0 rgba(0, 0, 0, 0.5); } +body > main.auth { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 20px; + width: 100%; + height: 100%; + + h1 { + font-size: 1.5rem; + font-weight: 400; + } + + input { width: 150px; } + + input, button { + padding: 5px 20px; + font-size: 1rem; + border: 3px solid transparent; + border-radius: 20px; + background-color: var(--bg-input); + + &:focus { + outline: 2px solid var(--focus-color); + } + } + + button { + padding: 5px 15px; + border: 3px solid var(--bg-input); + background-image: var(--gradient-clickable); + cursor: pointer; + + &:hover { + border-color: var(--bg-input-hover); + background-color: var(--bg-input-hover); + background-image: var(--gradient-clickable-hover); + } + } +} + /* message list */ section#messages { position: relative; @@ -84,6 +125,7 @@ section#messages { width: calc(100% - 200px); height: 200px; background-image: radial-gradient(50% 20% at 50% 100%, #60a0ff40, transparent); + pointer-events: none; } } @@ -101,6 +143,10 @@ section#messages { color: var(--fg-faint); text-align: right; } + + .message { + white-space: pre-wrap; + } } } @@ -216,28 +262,64 @@ section#input { } } +/* icons, emotes */ +.gfd-icon { + display: inline-block; + position: relative; + vertical-align: middle; + zoom: 0.75; + + &::before { + content: ''; + display: block; + position: absolute; + top: 0; + left: 0; + transform: translateY(-50%); + pointer-events: none; + } +} + +.emote-icon { + display: inline-block; + position: relative; + width: 2rem; + vertical-align: middle; + + img { + content: ''; + display: block; + position: absolute; + top: 0; + left: 0; + width: 2rem; + transform: translateY(-50%); + pointer-events: none; + } +} + /*** mobile ***/ @media ((max-width: 600px) and (orientation: portrait)) or (max-width: 400px) { -body { - padding: 0; -} - -body > main { - border-radius: 0; - box-shadow: none; -} - -section#input { - button { - max-width: 0; - padding-left: 1.5rem; - padding-right: 1.5rem; - color: transparent; + body { + padding: 0; } - button::before { - left: 50%; - transform: translateX(-50%) translateY(-50%); + body > main { + border-radius: 0; + box-shadow: none; + } + + section#input { + button { + max-width: 0; + padding-left: 1.5rem; + padding-right: 1.5rem; + color: transparent; + } + + button::before { + left: 50%; + transform: translateX(-50%) translateY(-50%); + } } } -} diff --git a/ChatTwo/Http/static/start.js b/ChatTwo/Http/static/start.js index 25d2d79..3655bae 100644 --- a/ChatTwo/Http/static/start.js +++ b/ChatTwo/Http/static/start.js @@ -1,21 +1,20 @@ // websocket connection class SSEConnection { constructor() { - this.socket = new EventSource('/sse', ); + this.socket = new EventSource('/sse'); - this.socket.addEventListener('close', (event) => { - console.log("Closing SSE connection.") - this.socket.close() + this.socket.addEventListener('close', () => { + console.log('Closing SSE connection.'); + this.socket.close(); }); this.socket.addEventListener('switch-channel', (event) => { - updateChannelHint(JSON.parse(event.data).channel) + updateChannelHint(JSON.parse(event.data).channel); }); // New messages that are able to be directly processed this.socket.addEventListener('new-message', (event) => { - for (let message of JSON.parse(event.data).messages) - { + for (let message of JSON.parse(event.data).messages) { addMessage(message); } }); @@ -23,15 +22,13 @@ class SSEConnection { // New messages, that require a clean message list before processing this.socket.addEventListener('bulk-messages', (event) => { clearMessages(); - - for (let message of JSON.parse(event.data).messages) - { + for (let message of JSON.parse(event.data).messages) { addMessage(message); } }); this.socket.addEventListener('channel-list', (event) => { - updateChannelOptions(JSON.parse(event.data).channels) + updateChannelOptions(JSON.parse(event.data).channels); }); } @@ -56,7 +53,7 @@ document.getElementById('channel-select').addEventListener('change', (event) => 'Accept': 'application/json', 'Content-Type': 'application/json' }, - body: JSON.stringify({channel: event.target.value}) + body: JSON.stringify({ channel: event.target.value }) }); const content = await rawResponse.json(); @@ -71,30 +68,39 @@ function updateChannelOptions(channels) { // clear existing channels select.innerHTML = ''; - for (let [ name, channel ] of Object.entries(channels)) - { + for (const [ name, channel ] of Object.entries(channels)) { let option = document.createElement('option'); option.text = name; option.value = channel; - select.appendChild(option) } } // functions for handling the message list -function scrollMessagesToBottom() { - const messagesContainer = document.querySelector('#messages > .scroll-container'); - messagesContainer.scrollTop = messagesContainer.scrollHeight - messagesContainer.offsetHeight; -} - function messagesContainerIsScrolledToBottom() { const messagesContainer = document.querySelector('#messages > .scroll-container'); - return messagesContainer.scrollTop == messagesContainer.scrollHeight - messagesContainer.offsetHeight; + return messagesContainer.scrollTop >= messagesContainer.scrollHeight - messagesContainer.offsetHeight; +} + +// calculate timestamp width +// to ensure that all timestamps have the same width. some typefaces have the same width across +// all number glyphs, others do not. the solution below is very rudimentary; at the very least, +// delaying it to account for font loading might make sense. perhaps there's an even better way? +let maxTimestampWidth = 0; +function calculateTimestampWidth(timestamp) { + const widthProbe = document.getElementById('timestamp-width-probe'); + widthProbe.innerText = timestamp; + + if (widthProbe.clientWidth > maxTimestampWidth) { + maxTimestampWidth = widthProbe.clientWidth; + document.body.style.setProperty('--timestamp-width', (Math.ceil(maxTimestampWidth) + 1) + 'px'); + } } function addMessage(messageData) { const scrolledToBottom = messagesContainerIsScrolledToBottom(); + calculateTimestampWidth(messageData.timestamp); const liMessage = document.createElement('li'); const spanTimestamp = document.createElement('span'); @@ -102,16 +108,15 @@ function addMessage(messageData) { const spanMessage = document.createElement('span'); spanMessage.classList.add('message'); - // suggestions, update with actual data model spanTimestamp.innerText = messageData.timestamp; - spanMessage.innerHTML = messageData.messageHTML; // or build HTML in here + spanMessage.innerHTML = messageData.messageHTML; liMessage.appendChild(spanTimestamp); liMessage.appendChild(spanMessage); document.getElementById('messages-list').appendChild(liMessage); if (scrolledToBottom) { - scrollMessagesToBottom(); + liMessage.scrollIntoView(); } } @@ -119,7 +124,7 @@ function clearMessages() { document.getElementById('messages-list').innerHTML = ''; } -// +// add indicator signaling more messages document.querySelector('#messages > .scroll-container').addEventListener('scroll', () => { const messagesContainer = document.querySelector('#messages > .scroll-container'); if (!messagesContainerIsScrolledToBottom()) { @@ -137,14 +142,6 @@ document.querySelector('#input > form').addEventListener('submit', (event) => { const chatInput = document.getElementById('chat-input'); const message = chatInput.value; - fetch('/send', { - method: 'POST', - body: message, - headers: { - 'Content-type': 'application/txt; charset=UTF-8' - } - }); - (async () => { const rawResponse = await fetch('/send', { method: 'POST', @@ -152,7 +149,7 @@ document.querySelector('#input > form').addEventListener('submit', (event) => { 'Accept': 'application/json', 'Content-Type': 'application/json' }, - body: JSON.stringify({message: message}) + body: JSON.stringify({ message: message }) }); const content = await rawResponse.json(); @@ -164,26 +161,14 @@ document.querySelector('#input > form').addEventListener('submit', (event) => { }); -// calculate timestamp width -// to ensure that all timestamps have the same width. some typefaces have the same width across -// all number glyphs, others do not. the solution below is very rudimentary; at the very least, -// delaying it to account for font loading might make sense. perhaps there's an even better way? -window.setTimeout(() => { - const widthProbe = document.getElementById('timestamp-width-probe'); - widthProbe.innerText = '88:88'; // assume 8 to be widest glyph - document.body.style.setProperty('--timestamp-width', Math.ceil(widthProbe.clientWidth) + 'px'); -}, 100); - -// From kizer +// from kizer, gfd icons async function AddGfdStylesheet(gfdPath, texPath) { const texPromise = LoadTexAsBlob(texPath); const gfdPromise = LoadGfd(gfdPath); const texUrl = URL.createObjectURL(await texPromise); const gfd = await gfdPromise; - let width = 0; - let height = 0; - let stylesheets = []; + const stylesheets = []; for (const entry of gfd) { if (entry.width * entry.height <= 0) continue; @@ -202,32 +187,30 @@ async function AddGfdStylesheet(gfdPath, texPath) { `background-position: -${entry.left}px -${entry.top}px`, `background-image: url('${texUrl}')`, `width: ${entry.width}px`, - `height: ${entry.height}px`, - `margin-bottom: -20px` - ].join(";"), + `height: ${entry.height}px` + ].join(';'), [ `background-position: -${entry.left * 2}px -${entry.top * 2 + 341}px`, `background-image: url('${texUrl}')`, `width: ${entry.width * 2}px`, - `height: ${entry.height * 2}px`, - `margin-bottom: -40px` - ].join(";") + `height: ${entry.height * 2}px` + ].join(';'), + entry.width ]; } - let stylesheet = ".gfd-icon::before { content: ''; display: inline-block; overflow: hidden; vertical-align: top; height:0; }\n"; + let stylesheet = ''; for (const entry of stylesheets) { if (!entry) continue; stylesheet += `\n${entry[0].map(x => `.gfd-icon.gfd-icon-${x}::before`).join(', ')}{${entry[1]};}`; stylesheet += `\n${entry[0].map(x => `.gfd-icon.gfd-icon-hq-${x}::before`).join(', ')}{${entry[2]};}`; + stylesheet += `\n${entry[0].map(x => `.gfd-icon.gfd-icon-${x}`).join(', ')}{width:${entry[3]}px;}`; + stylesheet += `\n${entry[0].map(x => `.gfd-icon.gfd-icon-hq-${x}`).join(', ')}{width:${entry[3] * 2}px;}`; } - stylesheet += "\n.emote-icon { content: ''; display: inline-block; overflow: hidden; vertical-align: top; }"; - stylesheet += `\n.emote-icon.emote-icon-hq {width: ${width * 2}px; height: ${height * 2}px; margin-bottom: -40px}`; - const styleNode = document.createElement("style"); - styleNode.type = "text/css"; + const styleNode = document.createElement('style'); styleNode.appendChild(document.createTextNode(stylesheet)); document.head.appendChild(styleNode); } @@ -235,7 +218,7 @@ async function AddGfdStylesheet(gfdPath, texPath) { async function LoadTexAsBlob(path) { const tex = ParseTex(await (await fetch(path)).arrayBuffer()); if (tex.format !== 0x1450) // B8G8R8A8 - throw "Not supported"; + throw 'Not supported'; const dataArray = new Uint8ClampedArray(tex.buffer, tex.offsetToSurface[0], tex.width * tex.height * 4); for (let i = 0; i < dataArray.length; i += 4) { @@ -297,4 +280,4 @@ function ParseTex(arrayBuffer) { }; } -AddGfdStylesheet("/files/gfdata.gfd", "/files/fonticon_ps5.tex"); +AddGfdStylesheet('/files/gfdata.gfd', '/files/fonticon_ps5.tex'); diff --git a/ChatTwo/Http/templates/auth.html b/ChatTwo/Http/templates/auth.html index cd3fe76..b6153a6 100644 --- a/ChatTwo/Http/templates/auth.html +++ b/ChatTwo/Http/templates/auth.html @@ -3,17 +3,20 @@ Authentication - - - -
-

Authcode

-
- - -
+ + - -
+ + + + +
+

Authcode

+
+ + +
+ +
- \ No newline at end of file + diff --git a/ChatTwo/Http/templates/start.html b/ChatTwo/Http/templates/start.html index e23a472..0dc0d94 100644 Binary files a/ChatTwo/Http/templates/start.html and b/ChatTwo/Http/templates/start.html differ