/*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 representing the settings panel * @extends panelObj */ class pmPanel extends panelObj{ /** * Instantiates a new Panel Object * @param {channel} client - Parent client Management Object * @param {Document} panelDocument - Panel Document */ constructor(client, panelDocument){ super(client, "Private Messaging", "/panel/pm", panelDocument); /** * String to hold name of currently active sesh */ this.activeSesh = ""; /** * Random UUID to identify the panel with the pmHandler */ this.uuid = crypto.randomUUID(); //Tell PMHandler to start tracking this panel this.client.pmHandler.panelList.set(this.uuid, null); this.defineListeners(); } closer(){ //Tell PMHandler to start tracking this panel this.client.pmHandler.panelList.delete(this.uuid); //Run derived closer super.closer(); } docSwitch(){ this.startSeshButton = this.panelDocument.querySelector('#pm-panel-start-sesh'); this.seshList = this.panelDocument.querySelector('#pm-panel-sesh-list'); this.seshBuffer = this.panelDocument.querySelector('#pm-panel-sesh-buffer'); this.seshPrompt = this.panelDocument.querySelector('#pm-panel-message-prompt'); this.seshSendButton = this.panelDocument.querySelector('#pm-panel-send-button'); this.setupInput(); this.renderSeshList(); //If we have an active sesh if(this.activeSesh != null && this.activeSesh != ""){ //Render messages this.renderMessages(); } //Call derived method super.docSwitch(); } /** * Defines network related event listeners */ defineListeners(){ this.client.pmSocket.on("message", this.handlePM.bind(this)); this.client.pmSocket.on("sent", this.handlePM.bind(this)); } /** * Defines input-related event handlers */ setupInput(){ this.startSeshButton.addEventListener('click', this.startSesh.bind(this)); this.seshPrompt.addEventListener("keydown", this.send.bind(this)); this.seshSendButton.addEventListener("click", this.send.bind(this)); } startSesh(event){ new startSeshPopup(event, this.client, this.renderSeshList.bind(this), this.ownerDoc); } handlePM(data){ const nameObj = pmHandler.genSeshName(data); //If this message is for the active sesh if(nameObj.name == this.activeSesh){ //Render out the newest message this.renderMessage(data); }else{ //pull current session entry if it exists const curEntry = this.panelDocument.querySelector(`[data-id="${nameObj.name}"]`); //If it doesn't exist if(curEntry == null){ //Re-render out the sesh list this.renderSeshList(); } } } /** * sends private message from sesh prompt to server * @param {Event} event - Event passed down from Event Handler */ send(event){ if((!event || !event.key || event.key == "Enter") && this.seshPrompt.value && this.activeSesh != ''){ //Pull current sesh from sesh list const sesh = this.client.pmHandler.seshList.get(this.activeSesh); //Preprocess message from prompt const preprocessedMessage = this.client.chatBox.commandPreprocessor.preprocess(this.seshPrompt.value); //If preprocessedMessage had it's send flag thrown as false if(preprocessedMessage != false){ //Stick recipients into the pre-processed message preprocessedMessage.recipients = sesh.recipients; //Send message out to server this.client.pmSocket.emit("pm", preprocessedMessage); } //Clear our prompt this.seshPrompt.value = ""; } } /** * Render out current sesh array to sesh list UI */ renderSeshList(){ //Clear out the sesh list this.seshList.innerHTML = ""; //Assemble temporary array from client PM Handler sesh list const seshList = Array.from(this.client.pmHandler.seshList); //If we have existing sessions and no active sesh if(this.activeSesh == "" && seshList[0] != null){ //Enable UI elements this.seshPrompt.disabled = false; this.seshSendButton.disabled = false; //Render out messages this.renderMessages(); //Set the first one as active this.activeSesh = seshList[0][1].id; //Tell PMHandler what sesh we have open for notification reasons this.client.pmHandler.readSesh(this.uuid, this.activeSesh); } //For each session tracked by the pmHandler for(const seshEntry of seshList){ this.renderSeshListEntry(seshEntry[1]); } } /** * Renders out a given messaging sesh to the sesh list UI */ renderSeshListEntry(sesh){ //Create container div const entryDiv = document.createElement('div'); //Set conatiner div classes entryDiv.classList.add('pm-panel-sesh-list-entry','interactive'); //Set dataset sesh name entryDiv.dataset.id = sesh.id; //If the current entry is the active sesh if(sesh.id == this.activeSesh){ //mark it as such entryDiv.classList.add('positive'); } //Create sesh label const seshLabel = document.createElement('p'); //Create human-readable label out of members array seshLabel.textContent = utils.unescapeEntities(sesh.id); //append sesh label to entry div entryDiv.appendChild(seshLabel); //Append entry div to sesh list this.seshList.appendChild(entryDiv); //Add input-related event listener for entry div entryDiv.addEventListener('click', this.selectSesh.bind(this)); } selectSesh(event){ //Attempt to pull previously active sesh item from list const wasActive = this.panelDocument.querySelector('.positive'); //If there was an active sesh if(wasActive != null){ //Remove active sesh class from old item wasActive.classList.remove('positive'); } //Pull new active sesh name from target dataset this.activeSesh = event.target.dataset.id; //Set new sesh as active sesh event.target.classList.add('positive'); //Tell PMHandler what sesh we have open for notification reasons this.client.pmHandler.readSesh(this.uuid, this.activeSesh); //Re-render message buffer this.renderMessages(); } renderMessages(){ //Empty out the sesh buffer this.seshBuffer.innerHTML = ""; //Pull sesh from pmHandler const sesh = this.client.pmHandler.seshList.get(this.activeSesh); //If the sesh is real if(sesh != null){ //for each message in the sesh for(const message of sesh.messages){ //Render out messages to the buffer this.renderMessage(message); } } } /** * Renders message out to PM Panel Message Buffer * @param {Object} message - Message to render */ renderMessage(message){ //Run postprocessing functions on chat message const postprocessedMessage = client.chatBox.chatPostprocessor.postprocess(message, true); //Append message to buffer this.seshBuffer.appendChild(postprocessedMessage); } } /** * Class representing pop-up dialogue to start a private messaging sesh */ class startSeshPopup{ /** * Instantiates a new schedule media Pop-up * @param {Event} event - Event passed down from Event Listener * @param {channel} client - Parent Client Management Object * @param {String} url - URL/link to media to queue * @param {String} title - Title of media to queue * @param {Function} cb - Callback function, passed upon pop-up creation * @param {Document} doc - Current owner documnet of the panel, so we know where to drop our pop-up */ constructor(event, client, cb, doc){ /** * Parent Client Management Object */ this.client = client; /** * Callback function, passed upon pop-up creation */ this.cb = cb; //Create media popup and call async constructor when done //unfortunately we cant call constructors asyncronously, and we cant call back to this from super, so we can't extend this as it stands :( /** * canopyUXUtils.popup() object */ this.popup = new canopyUXUtils.popup('/startChatSesh', true, this.asyncConstructor.bind(this), doc); } /** * Continuation of object construction, called after child popup object construction */ asyncConstructor(){ //Grab required UI elements this.startSeshButton = this.popup.contentDiv.querySelector('#pm-sesh-popup-button'); this.usernamePrompt = this.popup.contentDiv.querySelector('#pm-sesh-popup-prompt'); //Setup input this.setupInput(); } /** * Defines input-related Event Handlers */ setupInput(){ //Setup input this.startSeshButton.addEventListener('click', this.startSesh.bind(this)); this.popup.popupDiv.addEventListener('keydown', this.startSesh.bind(this)); } /** * Handles sending request to schedule item to the queue * @param {Event} event - Event passed down from Event Listener */ startSesh(event){ //If we clicked or hit enter if(event.key == null || event.key == "Enter"){ //Send message out to server this.client.pmSocket.emit("pm", { recipients: this.usernamePrompt.value.split(" "), msg: "" }); //If we have a function if(typeof this.cb == "function"){ //Call any callbacks we where given this.cb(); } //Close the popup this.popup.closePopup(); } } }