Finished up with css-only flair implementation.

This commit is contained in:
rainbownapkin 2024-11-21 08:42:21 -05:00
parent 5b5f495853
commit 9dbbc4e924
13 changed files with 231 additions and 60 deletions

View file

@ -1,10 +1,17 @@
[ [
{ {
"name": "glitter", "name": "gold",
"displayName": "Gold",
"rank": "gold" "rank": "gold"
}, },
{ {
"name": "negativeGlitter", "name": "black-gold",
"ranke": "gold" "displayName": "Black Gold",
"rank": "gold"
},
{
"name": "lightning",
"displayName": "Lightning",
"rank": "gold"
} }
] ]

View file

@ -14,6 +14,10 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.*/ along with this program. If not, see <https://www.gnu.org/licenses/>.*/
//local imports
const flairModel = require('../../schemas/flairSchema');
const permissionModel = require('../../schemas/permissionSchema');
module.exports = class{ module.exports = class{
constructor(server, name){ constructor(server, name){
this.server = server; this.server = server;
@ -21,7 +25,7 @@ module.exports = class{
this.userList = new Map(); this.userList = new Map();
} }
handleConnection(userDB, socket){ async handleConnection(userDB, socket){
//get current user object from the userlist //get current user object from the userlist
var userObj = this.userList.get(userDB.user); var userObj = this.userList.get(userDB.user);
@ -45,6 +49,9 @@ module.exports = class{
//if everything looks good, admit the connection to the channel //if everything looks good, admit the connection to the channel
socket.join(socket.chan); socket.join(socket.chan);
//Make sure the client receives important client-info before userlist
await this.sendClientMetadata(userDB, socket);
//Send out the userlist //Send out the userlist
this.broadcastUserList(socket.chan); this.broadcastUserList(socket.chan);
} }
@ -71,13 +78,59 @@ module.exports = class{
this.broadcastUserList(socket.chan); this.broadcastUserList(socket.chan);
} }
async sendClientMetadata(userObj, socket){
const flairListDB = await flairModel.find({});
var flairList = [];
const user = {
id: userObj.id,
user: userObj.user,
rank: userObj.rank,
flair: userObj.flair
}
flairListDB.forEach((flair)=>{
if(permissionModel.rankToNum(flair.rank) <= permissionModel.rankToNum(userObj.rank)){
flairList.push({
name: flair.name,
displayName: flair.displayName
});
}
});
socket.emit("clientMetadata", {user, flairList});
}
broadcastUserList(){ broadcastUserList(){
var userList = []; var userList = [];
this.userList.forEach((userObj, user) => { this.userList.forEach((userObj, user) => {
userList.push(user); userList.push({
user: user,
flair: userObj.flair
});
}); });
this.server.io.in(this.name).emit("user-list", userList); this.server.io.in(this.name).emit("userList", userList);
}
updateFlair(user, flair){
const userObj = this.userList.get(user);
userObj.flair = flair;
this.userList.set(user, userObj);
//Quick hack to make this compatible with sendClientMetadata. make sure we do this AFTER setting the object in the userList map...
userObj.name = user;
//Crawl through user's sockets (lol)
userObj.sockets.forEach((sockid) => {
//Send metadata to each one
const socket = this.server.io.sockets.sockets.get(sockid);
this.sendClientMetadata(userObj, socket);
});
this.broadcastUserList();
} }
} }

View file

@ -116,4 +116,19 @@ module.exports = class{
const channel = this.activeChannels.get(socket.chan); const channel = this.activeChannels.get(socket.chan);
return channel.userList.get(socket.user.user); return channel.userList.get(socket.user.user);
} }
getConnectedChannels(socket){
var chanList = [];
this.activeChannels.forEach((channel) => {
const foundUser = channel.userList.get(socket.user.user);
//If we found a user and this channel hasn't been added to the list
if(foundUser){
chanList.push(channel);
}
});
return chanList;
}
} }

View file

@ -20,7 +20,6 @@ const validator = require('validator');//No express here, so regular validator i
//local imports //local imports
const loggerUtils = require('../../utils/loggerUtils'); const loggerUtils = require('../../utils/loggerUtils');
const userModel = require('../../schemas/userSchema'); const userModel = require('../../schemas/userSchema');
const channelManager = require('./channelManager');
module.exports = class{ module.exports = class{
constructor(server){ constructor(server){
@ -54,13 +53,20 @@ module.exports = class{
} }
async setFlair(socket, data){ async setFlair(socket, data){
const userDB = await userModel.findOne({user: socket.user.user}); var userDB = await userModel.findOne({user: socket.user.user});
const chanList = this.server.getConnectedChannels(socket)
if(userDB){ if(userDB){
try{ try{
//We can take this data raw since our schema checks it against existing flairs, and mongoose sanatizes queries //We can take this data raw since our schema checks it against existing flairs, and mongoose sanatizes queries
userDB.flair = data.flair; userDB.flair = data.flair;
await userDB.save(); userDB = await userDB.save();
chanList.forEach((channel) => {
channel.updateFlair(userDB.user, userDB.flair);
});
}catch(err){ }catch(err){
return loggerUtils.socketExceptionHandler(socket, err); return loggerUtils.socketExceptionHandler(socket, err);
} }

View file

@ -26,6 +26,10 @@ const flairSchema = new mongoose.Schema({
type: mongoose.SchemaTypes.String, type: mongoose.SchemaTypes.String,
required: true required: true
}, },
displayName:{
type: mongoose.SchemaTypes.String,
required: true
},
rank: { rank: {
type: mongoose.SchemaTypes.String, type: mongoose.SchemaTypes.String,
enum: permissionModel.rankEnum, enum: permissionModel.rankEnum,

View file

@ -105,6 +105,10 @@ userSchema.pre('save', async function (next){
//Throw a shit fit. Do not pass go. Do not collect $200. //Throw a shit fit. Do not pass go. Do not collect $200.
throw new Error("Invalid flair!"); throw new Error("Invalid flair!");
} }
if(permissionModel.rankToNum(this.rank) < permissionModel.rankToNum(foundFlair.rank)){
throw new Error(`User '${this.user}' does not have a high enough rank for flair '${foundFlair.displayName}'!`);
}
} }
} }

View file

@ -15,4 +15,5 @@ You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.--> along with this program. If not, see <https://www.gnu.org/licenses/>.-->
<link rel="stylesheet" href="/lib/bootstrap-icons/font/bootstrap-icons.css"> <link rel="stylesheet" href="/lib/bootstrap-icons/font/bootstrap-icons.css">
<link rel="stylesheet" type="text/css" href="/css/global.css"> <link rel="stylesheet" type="text/css" href="/css/global.css">
<link rel="stylesheet" type="text/css" href="/css/flair.css">
<link rel="stylesheet" type="text/css" href="/css/theme/movie-night.css"> <link rel="stylesheet" type="text/css" href="/css/theme/movie-night.css">

View file

@ -202,6 +202,7 @@ input#chat-panel-prompt{
.user-entry{ .user-entry{
margin: 0.2em; margin: 0.2em;
font-size: 1em; font-size: 1em;
width: fit-content;
} }
#media-panel-aspect-lock-icon{ #media-panel-aspect-lock-icon{

16
www/css/flair.css Normal file
View file

@ -0,0 +1,16 @@
.flair-gold, .flair-black-gold, .flair-lightning{
background-image: url('/img/flair/gold.gif');
border-radius: 0.3em;
box-shadow: 0.1em 0.1em 3px #fff4b6 inset, -0.1em 0.1em 3px #fff4b6 inset, 0.1em -0.1em 3px #fff4b6 inset, -0.1em -0.1em 3px #fff4b6 inset;
color: rgb(121, 106, 23);
text-shadow: 1px 1px 1px #fff4b6, -1px 1px 1px #fff4b6, 1px -1px 1px #fff4b6, -1px -1px 1px #fff4b6;
padding: 0.1em 0.4em;
}
.flair-black-gold{
filter: invert(1) grayscale(0.9);
}
.flair-lightning{
filter: invert(1) grayscale(0.5);
}

BIN
www/img/flair/gold.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 694 KiB

View file

@ -43,9 +43,19 @@ class channel{
document.title = `${this.channelName} - Connected` document.title = `${this.channelName} - Connected`
}); });
this.socket.on("error", (data) => { this.socket.on("clientMetadata", this.handleClientInfo.bind(this));
console.log(data);
}); this.socket.on("error", console.log);
}
handleClientInfo(data){
this.user = {
id: data.user.id,
name: data.user.name,
rank: data.user.rank
}
this.chatBox.handleClientInfo(data);
} }
} }

View file

@ -27,7 +27,8 @@ class chatBox{
//Element Nodes //Element Nodes
this.chatPanel = document.querySelector("#chat-panel-div"); this.chatPanel = document.querySelector("#chat-panel-div");
this.highLevel = document.querySelector("#chat-panel-high-level-select"); this.highSelect = document.querySelector("#chat-panel-high-level-select");
this.flairSelect = document.querySelector("#chat-panel-flair-select");
this.chatBuffer = document.querySelector("#chat-panel-buffer-div"); this.chatBuffer = document.querySelector("#chat-panel-buffer-div");
this.chatPrompt = document.querySelector("#chat-panel-prompt"); this.chatPrompt = document.querySelector("#chat-panel-prompt");
this.settingsIcon = document.querySelector("#chat-panel-settings-icon"); this.settingsIcon = document.querySelector("#chat-panel-settings-icon");
@ -57,6 +58,7 @@ class chatBox{
this.aspectLockIcon.addEventListener("click", this.lockAspect.bind(this)); this.aspectLockIcon.addEventListener("click", this.lockAspect.bind(this));
this.showChatIcon.addEventListener("click", ()=>{this.toggleUI()}); this.showChatIcon.addEventListener("click", ()=>{this.toggleUI()});
this.hideChatIcon.addEventListener("click", ()=>{this.toggleUI()}); this.hideChatIcon.addEventListener("click", ()=>{this.toggleUI()});
this.flairSelect.addEventListener("change", this.setFlair.bind(this));
//Clickdragger/Resize //Clickdragger/Resize
this.clickDragger.handle.addEventListener("mousedown", this.unlockAspect.bind(this)); this.clickDragger.handle.addEventListener("mousedown", this.unlockAspect.bind(this));
@ -69,6 +71,92 @@ class chatBox{
}); });
} }
displayChat(chat){
//Create chat-entry span
var chatEntry = document.createElement('span');
chatEntry.classList.add("chat-panel-buffer","chat-entry",`chat-entry-${chat.user}`);
//Create high-level label
var highLevel = document.createElement('p');
highLevel.classList.add("chat-panel-buffer","chat-entry-high-level");
highLevel.innerHTML = `${chat.high}`;
chatEntry.appendChild(highLevel);
//Create username label
var userLabel = document.createElement('p');
userLabel.classList.add("chat-panel-buffer","chat-entry-username");
if(chat.flair != ""){
var flair = `flair-${chat.flair}`;
}else{
var flair = this.client.userList.colorMap.get(chat.user);
}
//Create color span
var colorSpan = document.createElement('span');
colorSpan.classList.add("chat-entry-flair-span", flair);
colorSpan.innerHTML = `${chat.user}`;
userLabel.innerHTML = `${colorSpan.outerHTML}: `;
chatEntry.appendChild(userLabel);
//Create chat body
var chatBody = document.createElement('p');
chatBody.classList.add("chat-panel-buffer","chat-entry-body");
chatBody.innerHTML = chat.msg;
chatEntry.appendChild(chatBody);
this.chatBuffer.appendChild(chatEntry);
}
async send(event){
if((!event || !event.key || event.key == "Enter") && this.chatPrompt.value){
this.client.socket.emit("chatMessage",{msg: this.chatPrompt.value, high: this.highSelect.value});
this.chatPrompt.value = "";
}
}
handleClientInfo(data){
this.updateFlairSelect(data.flairList, data.user.flair);
}
setFlair(event){
const flair = event.target.value;
this.client.socket.emit("setFlair", {flair});
}
updateFlairSelect(flairList, flair){
//clear current flair select
this.flairSelect.innerHTML = "";
//Inject flair object for standard flair like the hack we are
flairList.push({
name: "",
displayName: "Classic"
});
//For each flair in flairlist
flairList.forEach((flair) => {
//Create an option
var flairOption = document.createElement('option');
//Set the name and innerHTML
flairOption.value = flair.name;
flairOption.innerHTML = flair.displayName;
//Append it to the select
this.flairSelect.appendChild(flairOption);
});
//Set the selected flair in the UI
this.flairSelect.value = flair;
//Re-style the UI, do this in two seperate steps in-case we're running for the first time and have nothing to replace.
this.flairSelect.className = this.flairSelect.className.replace(/flair-\S*/, "");
this.flairSelect.classList.add(`flair-${flair}`);
}
lockAspect(event){ lockAspect(event){
//prevent the user from breaking shit :P //prevent the user from breaking shit :P
if(this.chatPanel.style.display != "none"){ if(this.chatPanel.style.display != "none"){
@ -112,44 +200,4 @@ class chatBox{
this.client.player.hideVideoIcon.style.display = "none"; this.client.player.hideVideoIcon.style.display = "none";
} }
} }
displayChat(chat){
//Create chat-entry span
var chatEntry = document.createElement('span');
chatEntry.classList.add("chat-panel-buffer","chat-entry",`chat-entry-${chat.user}`);
//Create high-level label
var highLevel = document.createElement('p');
highLevel.classList.add("chat-panel-buffer","chat-entry-high-level");
highLevel.innerHTML = `${chat.high}`;
chatEntry.appendChild(highLevel);
//Create username label
var userLabel = document.createElement('p');
userLabel.classList.add("chat-panel-buffer","chat-entry-username");
//Create color span
var colorSpan = document.createElement('span');
colorSpan.classList.add(this.client.userList.colorMap.get(chat.user));
colorSpan.innerHTML = `${chat.user}`;
userLabel.innerHTML = `${colorSpan.outerHTML}: `;
chatEntry.appendChild(userLabel);
//Create chat body
var chatBody = document.createElement('p');
chatBody.classList.add("chat-panel-buffer","chat-entry-body");
chatBody.innerHTML = chat.msg;
chatEntry.appendChild(chatBody);
this.chatBuffer.appendChild(chatEntry);
}
async send(event){
if((!event || !event.key || event.key == "Enter") && this.chatPrompt.value){
this.client.socket.emit("chatMessage",{msg: this.chatPrompt.value, high: this.highLevel.value});
this.chatPrompt.value = "";
}
}
} }

View file

@ -53,7 +53,7 @@ class userList{
} }
defineListeners(){ defineListeners(){
this.client.socket.on('user-list', (data) => { this.client.socket.on('userList', (data) => {
this.updateList(data); this.updateList(data);
}); });
} }
@ -72,12 +72,12 @@ class userList{
var color = this.userColors[Math.floor(Math.random()*this.userColors.length)] var color = this.userColors[Math.floor(Math.random()*this.userColors.length)]
//if this user was in the previous colormap //if this user was in the previous colormap
if(this.colorMap.get(user) != null){ if(this.colorMap.get(user.user) != null){
//Override with previous color //Override with previous color
color = this.colorMap.get(user); color = this.colorMap.get(user.user);
} }
newMap.set(user, color); newMap.set(user.user, color);
this.renderUser(user, color); this.renderUser(user, color);
}); });
@ -87,8 +87,14 @@ class userList{
renderUser(user, color){ renderUser(user, color){
var userEntry = document.createElement('p'); var userEntry = document.createElement('p');
userEntry.innerText = user; userEntry.innerText = user.user;
userEntry.id = `user-entry-${user}`; userEntry.id = `user-entry-${user.user}`;
//Override color with flair
if(user.flair != ""){
color = `flair-${user.flair}`;
}
userEntry.classList.add("chat-panel-users","user-entry",color); userEntry.classList.add("chat-panel-users","user-entry",color);
this.userList.appendChild(userEntry); this.userList.appendChild(userEntry);