canopy/www/js/channel/userlist.js

260 lines
No EOL
8.8 KiB
JavaScript

/*Canopy - The next generation of stoner streaming software
Copyright (C) 2024-2026 Rainbownapkin and the TTN Community
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
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/>.*/
/**
* Class containing logic behind userlist UX
*/
class userList{
/**
* Instantiates a new userList object
* @param {channel} client - Parent client mgmt object
*/
constructor(client){
/**
* Parent Client Management object
*/
this.client = client
/**
* Click Dragger object for handling userlist resizes
*/
this.clickDragger = new canopyUXUtils.clickDragger("#chat-panel-users-drag-handle", "#chat-panel-users-div", true, this.client.chatBox.clickDragger);
/**
* List of currently connected users
*/
this.userList = [];
/**
* Userlist color array (Maps to css classes)
*/
this.userColors = [
"userlist-color0",
"userlist-color1",
"userlist-color2",
"userlist-color3",
"userlist-color4",
"userlist-color5",
"userlist-color6"];
/**
* Map of connected and buffered usernames to assigned username color/flair
*/
this.colorMap = new Map();
/**
* users div
*/
this.userDiv = document.querySelector("#chat-panel-users-div");
/**
* userlist div
*/
this.userListDiv = document.querySelector("#chat-panel-users-list-div");
/**
* user count label
*/
this.userCount = document.querySelector("#chat-panel-user-count");
/**
* userlist toggle button
*/
this.toggleIcon = document.querySelector("#chat-panel-users-toggle");
//Call setup functions
this.setupInput();
this.defineListeners();
}
/**
* Defines input-related event listeners
*/
setupInput(){
this.toggleIcon.addEventListener("click", ()=>{this.toggleUI()});
this.userCount.addEventListener("click", ()=>{this.toggleUI()});
}
/**
* Defines network-related event listeners
*/
defineListeners(){
this.client.socket.on('userList', (data) => {
this.updateList(data);
});
this.client.socket.on("disconnect", () => {
this.updateList([]);
})
}
/**
* Randomly assigns and gets a color to usernames that aren't on the colormap
* @param {String} user username to assign a color
*/
assignColor(user){
//if this user wasn't in the previous colormap
if(this.colorMap.get(user) == null){
//randomly pick a color
let color = this.userColors[Math.floor(Math.random()*this.userColors.length)]
//Store new randomly selected color
this.colorMap.set(user, color);
//Return color
return color;
}else{
//Return color from previous entry
return this.colorMap.get(user);
}
}
/**
* Updates UX after user list change
* @param {Array} list - Userlist data from server
*/
updateList(list){
//Clear list and set user count
this.userCount.textContent = list.length == 1 ? '1 User' : `${list.length} Users`;
this.userListDiv.innerHTML = null;
//Set userlist array to received list
this.userList = list;
//for each user
list.forEach((user) => {
//Assign color and render user
this.renderUser(user, this.assignColor(user.user));
});
//Make sure we're not cutting the ux off
this.clickDragger.fixCutoff();
}
/**
* Renders out a single username to the userlist
* @param {String} user - Username to render
* @param {String} flair - Flair to render as
*/
renderUser(user, flair){
//Create user span
var userSpan = document.createElement('span');
userSpan.classList.add('chat-panel-users', 'user-entry');
//Create high-level label
var highLevel = document.createElement('p');
highLevel.classList.add("user-list-high-level","high-level");
highLevel.textContent = `${user.highLevel}`;
//Create nameplate
var userEntry = document.createElement('p');
userEntry.innerText = user.user;
userEntry.id = `user-entry-${user.user}`;
//Override color with flair
if(user.flair != "classic"){
flair = `flair-${user.flair}`;
}
//Add classes to classList
userEntry.classList.add("chat-panel-users","user-entry",flair);
//Add high-level username to nameplate
userSpan.appendChild(highLevel);
userSpan.appendChild(userEntry);
//Setup profile tooltip
userSpan.addEventListener('mouseenter',(event)=>{utils.ux.displayTooltip(event, `profile?user=${user.user}`, true, null, true);});
//Setup profile context menu
userSpan.addEventListener('click', renderContextMenu.bind(this));
userSpan.addEventListener('contextmenu', renderContextMenu.bind(this));
this.userListDiv.appendChild(userSpan);
function renderContextMenu(event){
//Setup menu map
let menuMap = new Map([
["Profile", ()=>{this.client.cPanel.setActivePanel(new panelObj(this.client, user.user, `/panel/profile?user=${user.user}`))}],
["PM", ()=>{this.client.cPanel.setActivePanel(new pmPanel(client, undefined, user.user))}],
["Mention", ()=>{this.client.chatBox.catChat(`${user.user} `)}],
["Toke With", ()=>{this.client.chatBox.tokeWith(user.user)}],
]);
if(user.user != "Tokebot" && user.user != this.client.user.user){
let ignored = false;
//For each douchebag in the ignored users list
for(const douchebag of this.client.user.ignored){
if(douchebag.user == user.user){
/* Normally I abhor making if statements to set booleans, but this only need to happen when its true
Plus I also needed to put the break statement somewhere
The fucked thing is the break statement isnt necissary with the if statement, but without it it becomes necissary
But it cant be used without the if statement, making the task of reducing linecount for this definition a fucking sisyphean nightmare */
ignored = true;
break;
}
}
if(ignored){
menuMap.set("Unignore", ()=>{this.client.chatBox.transmit(`!unignore ${user.user}`);});
}else{
menuMap.set("Ignore", ()=>{this.client.chatBox.transmit(`!ignore ${user.user}`);});
}
if(this.client.user.permMap.chan.get("kickUser")){
menuMap.set("Kick", ()=>{this.client.chatBox.transmit(`!kick ${user.user}`);});
}
if(this.client.user.permMap.chan.get("banUser")){
menuMap.set("Channel Ban", ()=>{new chanBanUserPopup(this.client.channelName, user.user);});
}
if(this.client.user.permMap.site.get("banUser")){
menuMap.set("Site Ban", ()=>{new banUserPopup(user.user);});
}
}
//Display the menu
utils.ux.displayContextMenu(event, user.user, menuMap);
}
}
toggleUI(show = !this.userDiv.checkVisibility()){
localStorage.setItem("userlistHidden", !show);
if(show){
this.userDiv.style.display = "flex";
this.toggleIcon.classList.replace("bi-caret-left-fill","bi-caret-down-fill");
this.clickDragger.fixCutoff();
}else{
this.userDiv.style.display = "none";
this.toggleIcon.classList.replace("bi-caret-down-fill","bi-caret-left-fill");
}
}
//returns list of strings containing all currently online users
getOnlineUserNames(){
var names = [];
for(let user of this.userList){
names.push(user.user);
}
return names;
}
}