improve the dev portal

This commit is contained in:
MathMan05 2024-09-24 20:25:52 -05:00
parent 9ae7a9c71f
commit 5abe4c6748
2 changed files with 246 additions and 356 deletions

View file

@ -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<string,string>=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 -----------

View file

@ -131,14 +131,24 @@ class SettingsText implements OptionsElement<void>{
readonly onSubmit!: (str: string) => void;
value!: void;
readonly text: string;
elm!:WeakRef<HTMLSpanElement>;
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<void>{
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<void>{
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<void>{
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<object>{
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<object>{
//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<object>{
}
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<object>{
return box;
}
addText(str: string){
this.options.addText(str);
return this.options.addText(str);
}
addTitle(str: string){
this.options.addTitle(str);
}
button!:WeakRef<HTMLButtonElement>;
generateHTML(): HTMLElement{
const div = document.createElement("div");
div.append(this.options.generateHTML());
@ -933,6 +1012,10 @@ class Form implements OptionsElement<object>{
};
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 };