/*Canopy - The next generation of stoner streaming software Copyright (C) 2024-2025 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 .*/ /** * Class for object 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); /** * 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 usernames to assigned username color */ this.colorMap = new Map(); /** * users div */ this.userDiv = document.querySelector("#chat-panel-users-div"); /** * userlist div */ this.userList = 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([]); }) } /** * 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.userList.innerHTML = null; //create a new map var newMap = new Map(); //for each user list.forEach((user) => { //randomly pick a color var color = this.userColors[Math.floor(Math.random()*this.userColors.length)] //if this user was in the previous colormap if(this.colorMap.get(user.user) != null){ //Override with previous color color = this.colorMap.get(user.user); } newMap.set(user.user, color); this.renderUser(user, color); }); this.colorMap = newMap; //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.userList.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}`))}], ["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){ if(this.client.user.permMap.chan.get("kickUser")){ menuMap.set("Kick", ()=>{this.client.chatBox.commandPreprocessor.preprocess(`!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()){ 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"); } } }