/*Canopy - The next generation of stoner streaming software Copyright (C) 2024 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 channelSettingsPage{ constructor(){ //Get channel name off of the URL this.channel = window.location.pathname.slice(3).replace('/settings',''); //Instantiate UX handling objects, making sure to pass the channel name. this.rankList = new rankList(this.channel); this.banList = new banList(this.channel); this.permList = new permList(this.channel); this.prefrenceList = new prefrenceList(this.channel); this.deleteBtn = new deleteBtn(this.channel); } } class rankList{ constructor(channel){ this.channel = channel this.table = document.querySelector(".admin-list-table"); this.userPrompt = document.querySelector("#new-rank-input"); this.rankSelect = document.querySelector("#new-rank-select"); //Load the userlist and setup input this.loadList(); this.setupInput(); } setupInput(){ this.userPrompt.addEventListener("keydown", this.submitNewRank.bind(this)); } async loadList(){ const list = await utils.ajax.getChannelRank(this.channel); this.updateList(list); } async submitNewRank(event){ if(event.key != "Enter" && event.key != null){ //Bail out if we didn't hit enter return; } //Send new rank this.submitUserRank(this.userPrompt.value, this.rankSelect.value); //Clear out prompt this.userPrompt.value = ""; } async submitUpdate(event){ const user = event.target.id.replace("channel-rank-select-",""); const rank = event.target.value; await this.submitUserRank(user, rank); } async submitUserRank(user, rank){ await this.updateList(await utils.ajax.setChannelRank(this.channel, user, rank)); } async updateList(data){ //If no data if(!data){ //Do not pass go, do not collect $200 return; } //Get name/rank of logged in user const curName = document.querySelector("#username").textContent const curUser = data[curName]; const rankEnum = await utils.ajax.getRankEnum(); //clear the table this.clearTable(); //For each user in the list Object.entries(data).forEach((userAr) => { //pull user object from entry array const user = userAr[1]; //Create IMG node inside of IMG cell const imgNode = document.createElement('img'); imgNode.classList.add("admin-list-entry","admin-list-entry-item"); imgNode.src = user.img; //If the listed user rank is equal or higher than the signed-in user if(rankEnum.indexOf(user.rank) >= rankEnum.indexOf(curUser.rank)){ var rankContent = user.rank; }else{ //Create rank select var rankContent = document.createElement('select'); rankContent.id = `channel-rank-select-${user.user}` rankContent.classList.add("channel-rank-select") rankContent.addEventListener("change", this.submitUpdate.bind(this)); //for each rank in the enum rankEnum.slice().reverse().forEach((rank) => { //Create an option for the given rank const rankOption = document.createElement('option'); rankOption.value = rank; rankOption.innerHTML = rank; rankOption.selected = user.rank == rank; rankContent.appendChild(rankOption); }); } //Generate row and append to table this.table.appendChild(utils.ux.newTableRow([ imgNode, user.id, user.user, rankContent ])); }); } clearTable(){ //get all non-title table rows const rows = this.table.querySelectorAll("tr.admin-list-entry"); //for each row rows.forEach((row) => { //The Lord Yeeteth, and The Lord Yoinketh away... row.remove(); }); } } class banList{ constructor(channel){ this.channel = channel; this.table = document.querySelector("#admin-ban-list-table"); this.banPrompt = document.querySelector("#new-ban-input"); this.banButton = document.querySelector("#new-ban-button"); this.loadList(); this.setupInput(); } setupInput(){ this.banButton.addEventListener('click', this.ban.bind(this)); } async loadList(){ const data = await utils.ajax.getChanBans(this.channel); this.updateList(data); } clearList(){ const oldRows = this.table.querySelectorAll('tr.admin-list-entry'); oldRows.forEach((row) => { row.remove(); }); } async ban(event){ new banUserPopup(this.channel, this.banPrompt.value, this.updateList.bind(this)); } async unban(event){ //Rip user outta the target id const user = event.target.id.replace("admin-user-list-unban-icon-",""); //Tell the server to unban them and get the list returned const list = await utils.ajax.chanUnban(this.channel, user); //Use the list to update the UI this.updateList(list); } updateList(bans){ //for each ban listed this.clearList(); bans.forEach((ban)=>{ //Create IMG node const imgNode = document.createElement('img'); imgNode.classList.add("admin-list-entry","admin-list-entry-item"); imgNode.src = ban.user.img; //Create banAlts check const banAlts = document.createElement('i'); banAlts.classList.add("admin-user-list-ban-alts",ban.banAlts ? "bi-check" : "bi-x"); //Create unban icon node const unbanIcon = document.createElement('i'); unbanIcon.classList.add("bi-emoji-smile-fill","admin-user-list-icon","admin-user-list-unban-icon"); unbanIcon.id = `admin-user-list-unban-icon-${ban.user.user}`; unbanIcon.title = `Unban ${ban.user.user}`; unbanIcon.addEventListener("click", this.unban.bind(this)); //THIS IS NOT YET PROPERLY IMPLEMENTED AS getDaysUntilExpiration has not been made for chan bans const expirationString = ban.expirationDays < 0 ? "Never" : `${new Date(ban.expirationDate).toDateString()}
(${ban.daysUntilExpiration} day(s) left)`; //Generate row and append to table this.table.appendChild(utils.ux.newTableRow([ imgNode, ban.user.id, ban.user.user, banAlts, new Date(ban.banDate).toDateString(), expirationString, unbanIcon ])); }); } } class banUserPopup{ constructor(channel, target, cb){ this.channel = channel; this.target = target; this.cb = cb; this.popup = new canopyUXUtils.popup("channelUserBan", true, this.asyncConstruction.bind(this)); } asyncConstruction(){ this.title = document.querySelector(".popup-title"); //Setup title text real quick-like :P this.title.innerHTML = `Ban ${this.target}`; this.permBan = document.querySelector("#ban-popup-perm"); this.expiration = document.querySelector("#ban-popup-expiration"); this.expirationPrefix = document.querySelector("#ban-popup-expiration-prefix"); this.banAlts = document.querySelector("#ban-popup-alts"); this.banButton = document.querySelector("#ban-popup-ban-button"); this.cancelButton = document.querySelector("#ban-popup-cancel-button"); this.setupInput(); } setupInput(){ this.permBan.addEventListener("change", this.permaBanLabel.bind(this)); this.cancelButton.addEventListener("click", this.popup.closePopup.bind(this.popup)); this.banButton.addEventListener("click",this.ban.bind(this)); } permaBanLabel(event){ if(event.target.checked){ this.expiration.disabled = true; }else{ this.expiration.disabled = false; } } async ban(event){ //Get expiration days const expirationDays = this.permBan.checked ? -1 : this.expiration.value; //Send ban request off to server and retrieve new ban list const data = await utils.ajax.chanBan(this.channel, this.target, expirationDays, this.banAlts.checked); //Close the popup this.popup.closePopup(); //If we have data and a callback, run the callback with our data if(data != null && this.cb != null){ await this.cb(data); } } } class prefrenceList{ constructor(channel){ this.channel = channel; this.inputs = document.querySelectorAll(".channel-preference-list-item"); this.setupInput(); } setupInput(){ this.inputs.forEach((input) => { input.addEventListener("change", this.submitUpdate.bind(this)); }); } async submitUpdate(event){ const key = event.target.id.replace("channel-preference-",""); const value = event.target.checked; const settingsMap = new Map([ [key, value] ]); this.handleUpdate(await utils.ajax.setChannelSetting(this.channel, settingsMap), event.target, key); } handleUpdate(data, target, key){ if(data){ target = data[key]; } } } class permList{ constructor(channel){ this.channel = channel this.inputs = document.querySelectorAll(".channel-perm-select"); this.setupInput(); } setupInput(){ this.inputs.forEach((input) => { input.addEventListener("change", this.submitUpdate.bind(this)); }); } async submitUpdate(event){ const key = event.target.id.replace("admin-perm-list-rank-select-",""); const value = event.target.value; const permMap = new Map([ [key, value] ]); this.handleUpdate(await utils.ajax.setChannelPermissions(this.channel, permMap), event.target, key); } handleUpdate(data, target, key){ target.value = data[key]; } } class deleteBtn{ constructor(channel){ this.channel = channel; this.delete = document.querySelector("#channel-delete"); this.delete.addEventListener('click', this.promptDelete.bind(this)); } promptDelete(){ var confirm = window.prompt(`Warning: You are about to nuke ${this.channel} off of the face of the fucking planet, no taksie-backsies. \n \n Type in ${this.channel} to confirm.`); this.deleteChannel(confirm); } async deleteChannel(confirm){ if(this.channel === confirm){ utils.ajax.deleteChannel(this.channel, confirm); } } } const channelSettings = new channelSettingsPage();