disable inter's on-by-default ligatures, redo icon/emote CSS

also:
* proper styling for auth page
* clean-up of html, css, js
* fix scroll indicator eating mouse events
* fix scroll indicator on (mobile?) chrome
* re-calculate timestamp width on each new message, to account for
  unknown/unexpected formats like the inclusion of AM/PM
This commit is contained in:
Ennea
2024-08-26 22:21:04 +02:00
parent abde7447be
commit 769d34befd
5 changed files with 166 additions and 98 deletions
+44 -61
View File
@@ -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');