/*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 canopyUtils{ constructor(){ this.ajax = new canopyAjaxUtils(); this.ux = new canopyUXUtils(); } } class canopyUXUtils{ constructor(){ } async displayResponseError(body){ const errors = body.errors; errors.forEach((err)=>{ window.alert(`ERROR: ${err.msg} \nTYPE: ${err.type} \nDATE: ${err.date}`); }); } static clickDragger = class{ constructor(handle, element, leftHandle = true){ //Pull needed nodes this.handle = document.querySelector(handle); this.element = document.querySelector(element); //True while dragging this.dragLock = false; //Come to the ~~dark~~ left side this.leftHandle = leftHandle //Little hacky but it could be worse :P this.fixWidth = false; //Setup our event listeners this.setupInput(); } setupInput(){ this.handle.addEventListener("mousedown", this.startDrag.bind(this)); this.element.parentElement.addEventListener("mouseup", this.endDrag.bind(this)); this.element.parentElement.addEventListener("mousemove", this.drag.bind(this)); } startDrag(event){ //we are now dragging this.dragLock = true; //Keep user from selecting text or changing cursor as we drag window.document.body.style.userSelect = "none"; window.document.body.style.cursor = "ew-resize"; } endDrag(event){ //we're no longer dragging this.dragLock = false; //if we broke the page we need to fix it if(this.fixWidth){ //Pop the element width up just a bit to compensate for the extra pixel this.element.style.width = `${this.calcWidth(this.element.getBoundingClientRect().width + 1)}%`; //if this is true, it no longer needs to be, though it *should* be reset by the drag function by the time it matters anywho :P this.fixWidth = false; } //Return cursor to normal, and allow user to select text again window.document.body.style.userSelect = "auto"; window.document.body.style.cursor = "auto"; } drag(event){ if(this.dragLock){ if(this.leftHandle){ //get difference between mouse and right edge of element var difference = this.element.getBoundingClientRect().right - event.clientX; }else{ //get difference between mouse and left edge of element var difference = event.clientX - this.element.getBoundingClientRect().left; } //check if we have a scrollbar because we're breaking shit var pageBreak = document.body.scrollWidth - document.body.getBoundingClientRect().width; //if we're not breaking the page, or we're moving left if(pageBreak <= 0 || event.clientX < this.handle.getBoundingClientRect().left){ //Apply difference to width this.element.style.width = `${this.calcWidth(difference)}%`; //If we let go here, the width isn't breaking anything so there's nothing to fix. this.fixWidth = false; }else{ //We need to move the element back, but we can't do it all the way while we're still dragging as it will thrash this.element.style.width = `${this.calcWidth(this.element.getBoundingClientRect().width + pageBreak - 1)}%`; //If we stop dragging here, let the endDrag function know to fix the pixel difference used to prevent thrashing this.fixWidth = true; } } } calcWidth(px){ return (px / this.element.parentElement.getBoundingClientRect().width) * 100; } } } class canopyAjaxUtils{ constructor(){ } async register(user, pass, passConfirm, email){ var response = await fetch(`/api/account/register`,{ method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(email ? {user, pass, passConfirm, email} : {user, pass, passConfirm}) }); if(response.status == 200){ location = "/"; }else{ utils.ux.displayResponseError(await response.json()); } } async login(user, pass){ var response = await fetch(`/api/account/login`,{ method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({user, pass}) }); if(response.status == 200){ location.reload(); }else{ utils.ux.displayResponseError(await response.json()); } } async logout(){ var response = await fetch(`/api/account/logout`,{ method: "GET", }); if(response.status == 200){ location.reload(); }else{ utils.ux.displayResponseError(await response.json()); } } //We need to fix this one to use displayResponseError function async updateProfile(update){ const response = await fetch(`/api/account/update`,{ method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(update) }); if(response.status == 200){ return await response.json(); }else{ utils.ux.displayResponseError(await response.json()); } } async deleteAccount(pass){ const response = await fetch(`/api/account/delete`,{ method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({pass}) }); if(response.status == 200){ window.location.pathname = '/'; }else{ utils.ux.displayResponseError(await response.json()); } } async newChannel(name, description, thumbnail){ var response = await fetch(`/api/channel/register`,{ method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(thumbnail ? {name, description, thumbnail} : {name, description}) }); if(response.status == 200){ location = "/"; }else{ utils.ux.displayResponseError(await response.json()); } } async setChannelSetting(chanName, settingsMap){ var response = await fetch(`/api/channel/settings`,{ method: "POST", headers: { "Content-Type": "application/json" }, //Unfortunately JSON doesn't natively handle ES6 maps, and god forbid someone update the standard in a way that's backwards compatible... body: JSON.stringify({chanName, settingsMap: Object.fromEntries(settingsMap)}) }); if(response.status == 200){ return await response.json(); }else{ utils.ux.displayResponseError(await response.json()); } } async setChannelPermissions(chanName, permissionsMap){ var response = await fetch(`/api/channel/permissions`,{ method: "POST", headers: { "Content-Type": "application/json" }, //Unfortunately JSON doesn't natively handle ES6 maps, and god forbid someone update the standard in a way that's backwards compatible... body: JSON.stringify({chanName, channelPermissionsMap: Object.fromEntries(permissionsMap)}) }); if(response.status == 200){ return await response.json(); }else{ utils.ux.displayResponseError(await response.json()); } } async setChannelRank(chanName, user, rank){ var response = await fetch(`/api/channel/rank`,{ method: "POST", headers: { "Content-Type": "application/json" }, //Unfortunately JSON doesn't natively handle ES6 maps, and god forbid someone update the standard in a way that's backwards compatible... body: JSON.stringify({chanName, user, rank}) }); if(response.status == 200){ return await response.json(); }else{ utils.ux.displayResponseError(await response.json()); } } async deleteChannel(chanName, confirm){ var response = await fetch(`/api/channel/delete`,{ method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({chanName, confirm}) }); if(response.status == 200){ location = "/"; }else{ utils.ux.displayResponseError(await response.json()); } } } const utils = new canopyUtils()