From 5abe4c6748ab1181167d003976369c5b357a64b5 Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Tue, 24 Sep 2024 20:25:52 -0500 Subject: [PATCH] improve the dev portal --- src/webpage/localuser.ts | 451 +++++++++++---------------------------- src/webpage/settings.ts | 151 ++++++++++--- 2 files changed, 246 insertions(+), 356 deletions(-) diff --git a/src/webpage/localuser.ts b/src/webpage/localuser.ts index 7011c71..5f79461 100644 --- a/src/webpage/localuser.ts +++ b/src/webpage/localuser.ts @@ -16,7 +16,7 @@ import{ wsjson, }from"./jsontypes.js"; import{ Member }from"./member.js"; -import{ FormError, Settings }from"./settings.js"; +import{ Form, FormError, Options, Settings }from"./settings.js"; import{ MarkDown }from"./markdown.js"; const wsCodesRetry = new Set([4000, 4003, 4005, 4007, 4008, 4009]); @@ -1286,349 +1286,156 @@ class Localuser{ { const devPortal = settings.addButton("Developer Portal"); - const teamsRes = await fetch(this.info.api + "/teams", { + fetch(this.info.api + "/teams", { headers: this.headers, - }); - const teams = await teamsRes.json(); + }).then(async (teamsRes)=>{ + const teams = await teamsRes.json(); - devPortal.addButtonInput("", "Create application", ()=>{ - const form = devPortal.addSubForm( - "Create application", - (json: any)=>{ - if(json.message) form.error("name", json.message); - else{ - devPortal.returnFromSub(); - this.manageApplication(json.id); - } - }, - { - fetchURL: this.info.api + "/applications", - headers: this.headers, - method: "POST", - } - ); - - form.addTextInput("Name", "name", { required: true }); - form.addSelect( - "Team", - "team_id", - ["Personal", ...teams.map((team: { name: string })=>team.name)], - { - defaultIndex: 0, - } - ); - }); - - const appListContainer = document.createElement("div"); - appListContainer.id = "app-list-container"; - fetch(this.info.api + "/applications", { - headers: this.headers, - }) - .then(r=>r.json()) - .then(json=>{ - json.forEach( - (application: { - cover_image: any; - icon: any; - id: string | undefined; - name: string | number; - bot: any; - })=>{ - const container = document.createElement("div"); - - if(application.cover_image || application.icon){ - const cover = document.createElement("img"); - cover.crossOrigin = "anonymous"; - cover.src = - this.info.cdn + - "/app-icons/" + - application.id + - "/" + - (application.cover_image || application.icon) + - ".png?size=256"; - cover.alt = ""; - cover.loading = "lazy"; - container.appendChild(cover); + devPortal.addButtonInput("", "Create application", ()=>{ + const form = devPortal.addSubForm( + "Create application", + (json: any)=>{ + if(json.message) form.error("name", json.message); + else{ + devPortal.returnFromSub(); + this.manageApplication(json.id); } + }, + { + fetchURL: this.info.api + "/applications", + headers: this.headers, + method: "POST", + } + ); - const name = document.createElement("h2"); - name.textContent = - application.name + (application.bot ? " (Bot)" : ""); - container.appendChild(name); - - container.addEventListener("click", async ()=>{ - this.manageApplication(application.id); - }); - appListContainer.appendChild(container); + form.addTextInput("Name", "name", { required: true }); + form.addSelect( + "Team", + "team_id", + ["Personal", ...teams.map((team: { name: string })=>team.name)], + { + defaultIndex: 0, } ); }); - devPortal.addHTMLArea(appListContainer); + + const appListContainer = document.createElement("div"); + appListContainer.id = "app-list-container"; + fetch(this.info.api + "/applications", { + headers: this.headers, + }) + .then(r=>r.json()) + .then(json=>{ + json.forEach( + (application: { + cover_image: any; + icon: any; + id: string | undefined; + name: string | number; + bot: any; + })=>{ + const container = document.createElement("div"); + + if(application.cover_image || application.icon){ + const cover = document.createElement("img"); + cover.crossOrigin = "anonymous"; + cover.src = + this.info.cdn + + "/app-icons/" + + application.id + + "/" + + (application.cover_image || application.icon) + + ".png?size=256"; + cover.alt = ""; + cover.loading = "lazy"; + container.appendChild(cover); + } + + const name = document.createElement("h2"); + name.textContent = application.name + (application.bot ? " (Bot)" : ""); + container.appendChild(name); + + container.addEventListener("click", async ()=>{ + this.manageApplication(application.id,devPortal); + }); + appListContainer.appendChild(container); + } + ); + }); + devPortal.addHTMLArea(appListContainer); + }); } settings.show(); } - async manageApplication(appId = ""){ + readonly botTokens:Map=new Map(); + async manageApplication(appId = "", container:Options){ const res = await fetch(this.info.api + "/applications/" + appId, { headers: this.headers, }); const json = await res.json(); const fields: any = {}; - const appDialog = new Dialog([ - "vdiv", - ["title", "Editing " + json.name], - [ - "vdiv", - [ - "textbox", - "Application name:", - json.name, - (event: Event)=>{ - const target = event.target as HTMLInputElement; - fields.name = target.value; - }, - ], - [ - "mdbox", - "Description:", - json.description, - (event: Event)=>{ - const target = event.target as HTMLInputElement; - fields.description = target.value; - }, - ], - [ - "vdiv", - json.icon - ? [ - "img", - this.info.cdn + - "/app-icons/" + - appId + - "/" + - json.icon + - ".png?size=128", - [128, 128], - ] - : ["text", "No icon"], - [ - "fileupload", - "Application icon:", - event=>{ - const reader = new FileReader(); - const files = (event.target as HTMLInputElement).files; - if(files){ - reader.readAsDataURL(files[0]); - reader.onload = ()=>{ - fields.icon = reader.result; - }; - } - }, - ], - ], - ], - [ - "hdiv", - [ - "textbox", - "Privacy policy URL:", - json.privacy_policy_url || "", - (event: Event)=>{ - const target = event.target as HTMLInputElement; - fields.privacy_policy_url = target.value; - }, - ], - [ - "textbox", - "Terms of Service URL:", - json.terms_of_service_url || "", - (event: Event)=>{ - const target = event.target as HTMLInputElement; - fields.terms_of_service_url = target.value; - }, - ], - ], - [ - "hdiv", - [ - "checkbox", - "Make bot publicly inviteable?", - json.bot_public, - (event: Event)=>{ - const target = event.target as HTMLInputElement; - fields.bot_public = target.checked; - }, - ], - [ - "checkbox", - "Require code grant to invite the bot?", - json.bot_require_code_grant, - (event: Event)=>{ - const target = event.target as HTMLInputElement; - fields.bot_require_code_grant = target.checked; - }, - ], - ], - [ - "hdiv", - [ - "button", - "", - "Save changes", - async ()=>{ - const updateRes = await fetch( - this.info.api + "/applications/" + appId, - { - method: "PATCH", - headers: this.headers, - body: JSON.stringify(fields), - } - ); - if(updateRes.ok) appDialog.hide(); - else{ - const updateJSON = await updateRes.json(); - alert("An error occurred: " + updateJSON.message); - } - }, - ], - [ - "button", - "", - (json.bot ? "Manage" : "Add") + " bot", - async ()=>{ - if(!json.bot){ - if( - !confirm( - "Are you sure you want to add a bot to this application? There's no going back." - ) - ) - return; - - const updateRes = await fetch( - this.info.api + "/applications/" + appId + "/bot", - { - method: "POST", - headers: this.headers, - } - ); - const updateJSON = await updateRes.json(); - alert("Bot token:\n" + updateJSON.token); - } - - appDialog.hide(); - this.manageBot(appId); - }, - ], - ], - ]); - appDialog.show(); + const form=container.addSubForm(json.name,()=>{},{ + fetchURL:this.info.api + "/applications/" + appId, + method:"PATCH", + headers:this.headers + }); + form.addTextInput("Application name:","name",{initText:json.name}); + form.addMDInput("Description:","description",{initText:json.description}); + form.addFileInput("Icon:","icon"); + form.addTextInput("Privacy policy URL:","privacy_policy_url",{initText:json.privacy_policy_url}); + form.addTextInput("Terms of Service URL:","terms_of_service_url",{initText:json.terms_of_service_url}); + form.addCheckboxInput("Make bot publicly inviteable?","bot_public",{initState:json.bot_public}); + form.addCheckboxInput("Require code grant to invite the bot?","bot_require_code_grant",{initState:json.bot_require_code_grant}); + form.addButtonInput("",(json.bot ? "Manage" : "Add")+" bot",async ()=>{ + if(!json.bot){ + if(!confirm("Are you sure you want to add a bot to this application? There's no going back.")){ + return; + } + const updateRes = await fetch( + this.info.api + "/applications/" + appId + "/bot", + { + method: "POST", + headers: this.headers, + } + ); + const updateJSON = await updateRes.json(); + this.botTokens.set(appId,updateJSON.token); + } + this.manageBot(appId,form); + }) } - async manageBot(appId = ""){ + async manageBot(appId = "",container:Form){ const res = await fetch(this.info.api + "/applications/" + appId, { headers: this.headers, }); const json = await res.json(); - if(!json.bot) - return alert( - "For some reason, this application doesn't have a bot (yet)." + if(!json.bot){ + return alert("For some reason, this application doesn't have a bot (yet)."); + } + const form=container.addSubForm("Editing bot "+json.bot.username,()=>{},{ + method:"PATCH", + fetchURL:this.info.api + "/applications/" + appId + "/bot", + headers:this.headers + }); + form.addTextInput("Bot username:","username",{initText:json.bot.username}); + form.addFileInput("Bot avatar:","avatar"); + form.addButtonInput("Reset Token:","Reset",async ()=>{ + if(!confirm("Are you sure you want to reset the bot token? Your bot will stop working until you update it.")){ + return; + } + const updateRes = await fetch( + this.info.api + "/applications/" + appId + "/bot/reset", + { + method: "POST", + headers: this.headers, + } ); - - const fields: any = { - username: json.bot.username, - avatar: json.bot.avatar - ? this.info.cdn + - "/app-icons/" + - appId + - "/" + - json.bot.avatar + - ".png?size=256" - : "", - }; - const botDialog = new Dialog([ - "vdiv", - ["title", "Editing bot: " + json.bot.username], - [ - "hdiv", - [ - "textbox", - "Bot username:", - json.bot.username, - (event: Event)=>{ - const target = event.target as HTMLInputElement; - fields.username = target.value; - }, - ], - [ - "vdiv", - fields.avatar - ? ["img", fields.avatar, [128, 128]] - : ["text", "No avatar"], - [ - "fileupload", - "Bot avatar:", - event=>{ - const reader = new FileReader(); - const files = (event.target as HTMLInputElement).files; - if(files){ - const file = files[0]; - reader.readAsDataURL(file); - reader.onload = ()=>{ - fields.avatar = reader.result; - }; - } - }, - ], - ], - ], - [ - "hdiv", - [ - "button", - "", - "Save changes", - async ()=>{ - const updateRes = await fetch( - this.info.api + "/applications/" + appId + "/bot", - { - method: "PATCH", - headers: this.headers, - body: JSON.stringify(fields), - } - ); - if(updateRes.ok) botDialog.hide(); - else{ - const updateJSON = await updateRes.json(); - alert("An error occurred: " + updateJSON.message); - } - }, - ], - [ - "button", - "", - "Reset token", - async ()=>{ - if( - !confirm( - "Are you sure you want to reset the bot token? Your bot will stop working until you update it." - ) - ) - return; - - const updateRes = await fetch( - this.info.api + "/applications/" + appId + "/bot/reset", - { - method: "POST", - headers: this.headers, - } - ); - const updateJSON = await updateRes.json(); - alert("New token:\n" + updateJSON.token); - botDialog.hide(); - }, - ], - ], - ]); - botDialog.show(); + const updateJSON = await updateRes.json(); + text.setText("Token: "+updateJSON.token); + this.botTokens.set(appId,updateJSON.token); + }); + const text=form.addText(this.botTokens.has(appId)?"Token: "+this.botTokens.get(appId):"Token: *****************") } //---------- resolving members code ----------- diff --git a/src/webpage/settings.ts b/src/webpage/settings.ts index 2eed7ec..feadabb 100644 --- a/src/webpage/settings.ts +++ b/src/webpage/settings.ts @@ -131,14 +131,24 @@ class SettingsText implements OptionsElement{ readonly onSubmit!: (str: string) => void; value!: void; readonly text: string; + elm!:WeakRef; constructor(text: string){ this.text = text; } generateHTML(): HTMLSpanElement{ const span = document.createElement("span"); span.innerText = this.text; + this.elm=new WeakRef(span); return span; } + setText(text:string){ + if(this.elm){ + const span=this.elm.deref(); + if(span){ + span.innerText=text; + } + } + } watchForChange(){} submit(){} } @@ -516,17 +526,26 @@ class Options implements OptionsElement{ return options; } subOptions: Options | Form | undefined; + genTop(){ + const container = this.container.deref(); + if(container){ + if(this.isTop()){ + this.generateContainter(); + }else if(this.owner instanceof Options){ + this.owner.genTop(); + }else{ + (this.owner as Form).owner.genTop(); + } + }else{ + throw new Error( + "Tried to make a sub menu when the options weren't rendered" + ); + } + } addSubOptions(name: string, { ltr = false } = {}){ const options = new Options(name, this, { ltr }); this.subOptions = options; - const container = this.container.deref(); - if(container){ - this.generateContainter(); - }else{ - throw new Error( - "Tried to make a subOptions when the options weren't rendered" - ); - } + this.genTop(); return options; } addSubForm( @@ -550,19 +569,12 @@ class Options implements OptionsElement{ traditionalSubmit, }); this.subOptions = options; - const container = this.container.deref(); - if(container){ - this.generateContainter(); - }else{ - throw new Error( - "Tried to make a subForm when the options weren't rendered" - ); - } + this.genTop(); return options; } returnFromSub(){ this.subOptions = undefined; - this.generateContainter(); + this.genTop(); } addSelect( label: string, @@ -710,36 +722,70 @@ class Options implements OptionsElement{ div.append(container); return div; } + generateName():(HTMLElement|string)[]{ + const build:(HTMLElement|string)[]=[]; + if(this.subOptions){ + if(this.name!==""){ + const name = document.createElement("span"); + name.innerText = this.name; + name.classList.add("clickable"); + name.onclick = ()=>{ + this.returnFromSub(); + }; + build.push(name); + build.push(" > "); + } + if(this.subOptions instanceof Options){ + build.push(...this.subOptions.generateName()); + }else{ + build.push(...this.subOptions.options.generateName()); + } + }else{ + const name = document.createElement("span"); + name.innerText = this.name; + build.push(name); + } + return build; + } + isTop(){ + (this.owner instanceof Form&&this.owner.owner.subOptions!==this.owner), + (this.owner instanceof Settings), + (this.owner instanceof Buttons)); + return (this.owner instanceof Options&&this.owner.subOptions!==this)|| + (this.owner instanceof Form&&this.owner.owner.subOptions!==this.owner)|| + (this.owner instanceof Settings)|| + (this.owner instanceof Buttons); + } generateContainter(){ const container = this.container.deref(); if(container){ const title = this.title.deref(); if(title) title.innerHTML = ""; container.innerHTML = ""; - if(this.subOptions){ - container.append(this.subOptions.generateHTML()); //more code needed, though this is enough for now + if(this.isTop()){ if(title){ - const name = document.createElement("span"); - name.innerText = this.name; - name.classList.add("clickable"); - name.onclick = ()=>{ - this.returnFromSub(); - }; - title.append(name, " > ", this.subOptions.name); + const elms=this.generateName(); + title.append(...elms); } - }else{ + } + if(!this.subOptions){ for(const thing of this.options){ this.generate(thing); } - if(title){ - title.innerText = this.name; - } + }else{ + container.append(this.subOptions.generateHTML()); } if(title && title.innerText !== ""){ title.classList.add("settingstitle"); }else if(title){ title.classList.remove("settingstitle"); } + if(this.owner instanceof Form&&this.owner.button){ + const button=this.owner.button.deref(); + if(button){ + button.hidden=false; + } + } }else{ console.warn("tried to generate container, but it did not exist"); } @@ -818,7 +864,7 @@ class Form implements OptionsElement{ this.name = name; this.method = method; this.submitText = submitText; - this.options = new Options("", this, { ltr }); + this.options = new Options(name, this, { ltr }); this.owner = owner; this.fetchURL = fetchURL; this.headers = headers; @@ -829,6 +875,36 @@ class Form implements OptionsElement{ //the value can't really be anything, but I don't care enough to fix this this.values[key] = value; } + addSubOptions(name: string, { ltr = false } = {}){ + if(this.button&&this.button.deref()){ + (this.button.deref() as HTMLElement).hidden=true; + } + return this.options.addSubOptions(name,{ltr}); + } + addSubForm( + name: string, + onSubmit: (arg1: object) => void, + { + ltr = false, + submitText = "Submit", + fetchURL = "", + headers = {}, + method = "POST", + traditionalSubmit = false, + } = {} + ){ + if(this.button&&this.button.deref()){ + console.warn("hidden"); + (this.button.deref() as HTMLElement).hidden=true; + } + return this.options.addSubForm(name,onSubmit,{ltr,submitText,fetchURL,headers,method,traditionalSubmit}); + } + generateContainter(){ + this.options.generateContainter(); + if((this.options.isTop())&&this.button&&this.button.deref()){ + (this.button.deref() as HTMLElement).hidden=false; + } + } addSelect( label: string, formName: string, @@ -903,7 +979,9 @@ class Form implements OptionsElement{ } return mdInput; } - + addButtonInput(label:string,textContent:string,onSubmit:()=>void){ + return this.options.addButtonInput(label,textContent,onSubmit); + } addCheckboxInput( label: string, formName: string, @@ -917,11 +995,12 @@ class Form implements OptionsElement{ return box; } addText(str: string){ - this.options.addText(str); + return this.options.addText(str); } addTitle(str: string){ this.options.addTitle(str); } + button!:WeakRef; generateHTML(): HTMLElement{ const div = document.createElement("div"); div.append(this.options.generateHTML()); @@ -933,6 +1012,10 @@ class Form implements OptionsElement{ }; button.textContent = this.submitText; div.append(button); + if(this.options.subOptions){ + button.hidden=true; + } + this.button=new WeakRef(button); } return div; } @@ -1110,4 +1193,4 @@ class Settings extends Buttons{ } } -export{ Settings, OptionsElement, Buttons, Options }; +export{ Settings, OptionsElement, Buttons, Options,Form };