canopy/www/js/channel/panels/emotePanel.js

313 lines
12 KiB
JavaScript

/*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 <https://www.gnu.org/licenses/>.*/
/**
* Class representing Emote Panel UX
* @extends panelObj
*/
class emotePanel extends panelObj{
/**
* Instantiates a new Panel Object
* @param {channel} client - Parent client Management Object
* @param {Document} panelDocument - Panel Document
*/
constructor(client, panelDocument){
super(client, "Emote Palette", "/panel/emote", panelDocument);
this.client.socket.on("personalEmotes", this.renderEmoteLists.bind(this));
}
closer(){
this.client.socket.off("personalEmotes", this.renderEmoteLists.bind(this));
}
docSwitch(){
this.siteEmoteTitle = this.panelDocument.querySelector('#site-emotes-title');
this.chanEmoteTitle = this.panelDocument.querySelector('#chan-emotes-title');
this.personalEmoteTitle = this.panelDocument.querySelector('#personal-emotes-title');
this.siteEmoteToggle = this.panelDocument.querySelector('#site-emotes-toggle');
this.chanEmoteToggle = this.panelDocument.querySelector('#chan-emotes-toggle');
this.personalEmoteToggle = this.panelDocument.querySelector('#personal-emotes-toggle');
this.siteEmoteList = this.panelDocument.querySelector('#emote-panel-site-list');
this.chanEmoteList = this.panelDocument.querySelector('#emote-panel-chan-list');
this.personalEmoteSection = this.panelDocument.querySelector('#emote-panel-personal-section');
this.personalEmoteList = this.panelDocument.querySelector('#emote-panel-personal-list');
this.searchPrompt = this.panelDocument.querySelector('#emote-panel-search-prompt');
this.personalEmoteLinkPrompt = this.panelDocument.querySelector('#new-emote-link-input');
this.personalEmoteNamePrompt = this.panelDocument.querySelector('#new-emote-name-input');
this.personalEmoteAddButton = this.panelDocument.querySelector('#new-emote-button');
this.setupInput();
this.renderEmoteLists();
}
/**
* Defines input-related event handlers
*/
setupInput(){
//Make sure to remove any event listeners in-case we moving an already instantiated panel
this.siteEmoteToggle.removeEventListener("click", this.toggleSiteEmotes.bind(this));
this.siteEmoteToggle.addEventListener("click", this.toggleSiteEmotes.bind(this));
this.chanEmoteToggle.removeEventListener("click", this.toggleChanEmotes.bind(this));
this.chanEmoteToggle.addEventListener("click", this.toggleChanEmotes.bind(this));
this.personalEmoteToggle.removeEventListener("click", this.togglePersonalEmotes.bind(this));
this.personalEmoteToggle.addEventListener("click", this.togglePersonalEmotes.bind(this));
this.siteEmoteTitle.removeEventListener("click", this.toggleSiteEmotes.bind(this));
this.siteEmoteTitle.addEventListener("click", this.toggleSiteEmotes.bind(this));
this.chanEmoteTitle.removeEventListener("click", this.toggleChanEmotes.bind(this));
this.chanEmoteTitle.addEventListener("click", this.toggleChanEmotes.bind(this));
this.personalEmoteTitle.removeEventListener("click", this.togglePersonalEmotes.bind(this));
this.personalEmoteTitle.addEventListener("click", this.togglePersonalEmotes.bind(this));
this.searchPrompt.removeEventListener('keyup', this.renderEmoteLists.bind(this));
this.searchPrompt.addEventListener('keyup', this.renderEmoteLists.bind(this));
this.personalEmoteAddButton.removeEventListener("click", this.addPersonalEmote.bind(this));
this.personalEmoteAddButton.addEventListener("click", this.addPersonalEmote.bind(this));
}
/**
* Toggles Site emote display
* @param {Event} event - Event passed down by event listener
*/
toggleSiteEmotes(event){
this.toggleEmotes(this.siteEmoteToggle, this.siteEmoteList);
}
/**
* Toggles Channel emote display
* @param {Event} event - Event passed down by event listener
*/
toggleChanEmotes(event){
this.toggleEmotes(this.chanEmoteToggle, this.chanEmoteList);
}
/**
* Toggles Personal emote display
* @param {Event} event - Event passed down by event listener
*/
togglePersonalEmotes(event){
this.toggleEmotes(this.personalEmoteToggle, this.personalEmoteSection);
}
/**
* Toggles a specified emote list on or off
* @param {Node} icon - Toggle Icon for given list
* @param {Node} list - Emote list container to toggle
*/
toggleEmotes(icon, list){
if(list.checkVisibility()){
icon.classList.replace('bi-caret-down-fill','bi-caret-left-fill');
list.style.display = 'none';
}else{
icon.classList.replace('bi-caret-left-fill', 'bi-caret-down-fill');
list.style.display = 'grid';
}
}
/**
* Concatenates specified emote into chat prompt input
* @param {String} emote - Emote to concat into chat
*/
useEmote(emote){
//If we're using this from the active panel
if(this.client.cPanel.activePanel == this){
//Close it
this.client.cPanel.hideActivePanel();
}
//Add the emote to the chatbox prompt
this.client.chatBox.catChat(`[${emote}]`);
}
/**
* Requests server to add emote to list of personal emotes
* @param {Event} event - Event passed down by event listener
*/
addPersonalEmote(event){
//Collect input
const name = this.personalEmoteNamePrompt.value;
const link = this.personalEmoteLinkPrompt.value;
//Empty out prompts
this.personalEmoteNamePrompt.value = '';
this.personalEmoteLinkPrompt.value = '';
//Send emote to server
this.client.socket.emit("addPersonalEmote", {name, link});
}
/**
* Requests server to remove emote from list of personal emotes
* @param {String} name - Name of emote to delete
*/
deletePersonalEmote(name){
//send out delete
this.client.socket.emit('deletePersonalEmote', {name});
}
/**
* Renders out emote list to panel document
*/
renderEmoteLists(){
//if we've initialized the search prompt (wont happen yet first run)
if(this.searchPrompt != null){
//Get the search value
var search = this.searchPrompt.value;
}
var searching = (search != null && search != '');
//pull emote lists from the command preprocessor
var siteEmotes = this.client.chatBox.commandPreprocessor.emotes.site;
var chanEmotes = this.client.chatBox.commandPreprocessor.emotes.chan;
var personalEmotes = this.client.chatBox.commandPreprocessor.emotes.personal;
//If we have a search bar and a search in the search bar
if(searching){
//filter emote lists using the filterQuery function
siteEmotes = siteEmotes.filter(filterQuery);
chanEmotes = chanEmotes.filter(filterQuery);
personalEmotes = personalEmotes.filter(filterQuery);
function filterQuery(emote){
//return true for anyany case-insensitive matches
return (emote.name.toLowerCase().match(search.toLowerCase())) != null;
}
}
//render out the emote lists
this.renderEmotes(siteEmotes, this.siteEmoteList, searching);
this.renderEmotes(chanEmotes, this.chanEmoteList, searching);
this.renderEmotes(personalEmotes, this.personalEmoteList, searching, true);
}
/**
* Renders out emotes to emote lists
* @param {Array} emoteList - list of emotes to render
* @param {Node} container - Container to render emotes out to
* @param {Boolean} personal - Denotes whether or not we're rendering personal emotes
*/
renderEmotes(emoteList, container, searchRender = false, personal = false){
//Clear out the container
container.innerHTML = '';
//If we have two or less emotes
if(emoteList.length <= 2 && searchRender){
//Set the container display to flex
container.style.display = 'flex';
//otherwise
}else{
//Set the container display to grid
container.style.display = 'grid';
}
//For each emote
emoteList.forEach((emote) => {
//Create div to hold emote span
const emoteDiv = document.createElement('div');
emoteDiv.classList.add('emote-panel-list-emote');
const emoteSpan = document.createElement('span');
emoteSpan.classList.add('emote-panel-list-emote');
//If we have a low emote count
if(emoteList.length <= 2){
//render them huuuuuge
emoteDiv.classList.add('emote-panel-list-big-emote');
emoteSpan.classList.add('emote-panel-list-big-emote');
}
//If the emote is an image
if(emote.type == 'image'){
//Create image node
var emoteMedia = document.createElement('img');
//if emote is a video
}else if(emote.type == 'video'){
//create video node
var emoteMedia = document.createElement('video');
//Set video properties
emoteMedia.autoplay = true;
emoteMedia.muted = true;
emoteMedia.controls = false;
emoteMedia.loop = true;
}
//set media link as source
emoteMedia.src = emote.link;
//Set media class
emoteMedia.classList.add('emote-list-media');
//if we have a low emote count
if(emoteList.length <= 2 && searchRender){
//render them huuuuuge
emoteMedia.classList.add('emote-list-big-media');
}
//Create paragraph tag
const emoteTitle = document.createElement('p');
//Set title class
emoteTitle.classList.add('emote-list-title');
//Set emote title
emoteTitle.textContent = utils.unescapeEntities(`[${emote.name}]`);
//if we're rendering personal emotes
if(personal){
//create span to hold trash icon
const trashSpan = document.createElement('span');
trashSpan.classList.add('emote-list-trash-icon');
//Create trash icon
const trashIcon = document.createElement('i');
trashIcon.classList.add('emote-list-trash-icon', 'bi-trash-fill');
//add deletePersonalEmote event listener
trashIcon.addEventListener('click', ()=>{this.deletePersonalEmote(emote.name)});
//Add trash icon to trash span
trashSpan.appendChild(trashIcon);
//append trash span to emote div
emoteDiv.appendChild(trashSpan);
}
//Add the emote media to the emote span
emoteSpan.appendChild(emoteMedia);
//Add title paragraph node
emoteSpan.appendChild(emoteTitle);
//Add useEmote event listener
emoteSpan.addEventListener('click', ()=>{this.useEmote(emote.name)});
//Add emote span to the emote div
emoteDiv.appendChild(emoteSpan);
//Append the mote span to the emote list
container.appendChild(emoteDiv);
})
}
}