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, wsjson,
}from"./jsontypes.js"; }from"./jsontypes.js";
import{ Member }from"./member.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"; import{ MarkDown }from"./markdown.js";
const wsCodesRetry = new Set([4000, 4003, 4005, 4007, 4008, 4009]); const wsCodesRetry = new Set([4000, 4003, 4005, 4007, 4008, 4009]);
@ -1286,349 +1286,156 @@ class Localuser{
{ {
const devPortal = settings.addButton("Developer Portal"); const devPortal = settings.addButton("Developer Portal");
const teamsRes = await fetch(this.info.api + "/teams", { fetch(this.info.api + "/teams", {
headers: this.headers, headers: this.headers,
}); }).then(async (teamsRes)=>{
const teams = await teamsRes.json(); const teams = await teamsRes.json();
devPortal.addButtonInput("", "Create application", ()=>{ devPortal.addButtonInput("", "Create application", ()=>{
const form = devPortal.addSubForm( const form = devPortal.addSubForm(
"Create application", "Create application",
(json: any)=>{ (json: any)=>{
if(json.message) form.error("name", json.message); if(json.message) form.error("name", json.message);
else{ else{
devPortal.returnFromSub(); devPortal.returnFromSub();
this.manageApplication(json.id); 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);
} }
},
{
fetchURL: this.info.api + "/applications",
headers: this.headers,
method: "POST",
}
);
const name = document.createElement("h2"); form.addTextInput("Name", "name", { required: true });
name.textContent = form.addSelect(
application.name + (application.bot ? " (Bot)" : ""); "Team",
container.appendChild(name); "team_id",
["Personal", ...teams.map((team: { name: string })=>team.name)],
container.addEventListener("click", async ()=>{ {
this.manageApplication(application.id); defaultIndex: 0,
});
appListContainer.appendChild(container);
} }
); );
}); });
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(); 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, { const res = await fetch(this.info.api + "/applications/" + appId, {
headers: this.headers, headers: this.headers,
}); });
const json = await res.json(); const json = await res.json();
const fields: any = {}; const fields: any = {};
const appDialog = new Dialog([ const form=container.addSubForm(json.name,()=>{},{
"vdiv", fetchURL:this.info.api + "/applications/" + appId,
["title", "Editing " + json.name], method:"PATCH",
[ headers:this.headers
"vdiv", });
[ form.addTextInput("Application name:","name",{initText:json.name});
"textbox", form.addMDInput("Description:","description",{initText:json.description});
"Application name:", form.addFileInput("Icon:","icon");
json.name, form.addTextInput("Privacy policy URL:","privacy_policy_url",{initText:json.privacy_policy_url});
(event: Event)=>{ form.addTextInput("Terms of Service URL:","terms_of_service_url",{initText:json.terms_of_service_url});
const target = event.target as HTMLInputElement; form.addCheckboxInput("Make bot publicly inviteable?","bot_public",{initState:json.bot_public});
fields.name = target.value; 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.")){
"mdbox", return;
"Description:", }
json.description, const updateRes = await fetch(
(event: Event)=>{ this.info.api + "/applications/" + appId + "/bot",
const target = event.target as HTMLInputElement; {
fields.description = target.value; method: "POST",
}, headers: this.headers,
], }
[ );
"vdiv", const updateJSON = await updateRes.json();
json.icon this.botTokens.set(appId,updateJSON.token);
? [ }
"img", this.manageBot(appId,form);
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();
} }
async manageBot(appId = ""){ async manageBot(appId = "",container:Form){
const res = await fetch(this.info.api + "/applications/" + appId, { const res = await fetch(this.info.api + "/applications/" + appId, {
headers: this.headers, headers: this.headers,
}); });
const json = await res.json(); const json = await res.json();
if(!json.bot) if(!json.bot){
return alert( return alert("For some reason, this application doesn't have a bot (yet).");
"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 updateJSON = await updateRes.json();
const fields: any = { text.setText("Token: "+updateJSON.token);
username: json.bot.username, this.botTokens.set(appId,updateJSON.token);
avatar: json.bot.avatar });
? this.info.cdn + const text=form.addText(this.botTokens.has(appId)?"Token: "+this.botTokens.get(appId):"Token: *****************")
"/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();
} }
//---------- resolving members code ----------- //---------- resolving members code -----------

View file

@ -131,14 +131,24 @@ class SettingsText implements OptionsElement<void>{
readonly onSubmit!: (str: string) => void; readonly onSubmit!: (str: string) => void;
value!: void; value!: void;
readonly text: string; readonly text: string;
elm!:WeakRef<HTMLSpanElement>;
constructor(text: string){ constructor(text: string){
this.text = text; this.text = text;
} }
generateHTML(): HTMLSpanElement{ generateHTML(): HTMLSpanElement{
const span = document.createElement("span"); const span = document.createElement("span");
span.innerText = this.text; span.innerText = this.text;
this.elm=new WeakRef(span);
return span; return span;
} }
setText(text:string){
if(this.elm){
const span=this.elm.deref();
if(span){
span.innerText=text;
}
}
}
watchForChange(){} watchForChange(){}
submit(){} submit(){}
} }
@ -516,17 +526,26 @@ class Options implements OptionsElement<void>{
return options; return options;
} }
subOptions: Options | Form | undefined; 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 } = {}){ addSubOptions(name: string, { ltr = false } = {}){
const options = new Options(name, this, { ltr }); const options = new Options(name, this, { ltr });
this.subOptions = options; this.subOptions = options;
const container = this.container.deref(); this.genTop();
if(container){
this.generateContainter();
}else{
throw new Error(
"Tried to make a subOptions when the options weren't rendered"
);
}
return options; return options;
} }
addSubForm( addSubForm(
@ -550,19 +569,12 @@ class Options implements OptionsElement<void>{
traditionalSubmit, traditionalSubmit,
}); });
this.subOptions = options; this.subOptions = options;
const container = this.container.deref(); this.genTop();
if(container){
this.generateContainter();
}else{
throw new Error(
"Tried to make a subForm when the options weren't rendered"
);
}
return options; return options;
} }
returnFromSub(){ returnFromSub(){
this.subOptions = undefined; this.subOptions = undefined;
this.generateContainter(); this.genTop();
} }
addSelect( addSelect(
label: string, label: string,
@ -710,36 +722,70 @@ class Options implements OptionsElement<void>{
div.append(container); div.append(container);
return div; 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(){ generateContainter(){
const container = this.container.deref(); const container = this.container.deref();
if(container){ if(container){
const title = this.title.deref(); const title = this.title.deref();
if(title) title.innerHTML = ""; if(title) title.innerHTML = "";
container.innerHTML = ""; container.innerHTML = "";
if(this.subOptions){ if(this.isTop()){
container.append(this.subOptions.generateHTML()); //more code needed, though this is enough for now
if(title){ if(title){
const name = document.createElement("span"); const elms=this.generateName();
name.innerText = this.name; title.append(...elms);
name.classList.add("clickable");
name.onclick = ()=>{
this.returnFromSub();
};
title.append(name, " > ", this.subOptions.name);
} }
}else{ }
if(!this.subOptions){
for(const thing of this.options){ for(const thing of this.options){
this.generate(thing); this.generate(thing);
} }
if(title){ }else{
title.innerText = this.name; container.append(this.subOptions.generateHTML());
}
} }
if(title && title.innerText !== ""){ if(title && title.innerText !== ""){
title.classList.add("settingstitle"); title.classList.add("settingstitle");
}else if(title){ }else if(title){
title.classList.remove("settingstitle"); title.classList.remove("settingstitle");
} }
if(this.owner instanceof Form&&this.owner.button){
const button=this.owner.button.deref();
if(button){
button.hidden=false;
}
}
}else{ }else{
console.warn("tried to generate container, but it did not exist"); console.warn("tried to generate container, but it did not exist");
} }
@ -818,7 +864,7 @@ class Form implements OptionsElement<object>{
this.name = name; this.name = name;
this.method = method; this.method = method;
this.submitText = submitText; this.submitText = submitText;
this.options = new Options("", this, { ltr }); this.options = new Options(name, this, { ltr });
this.owner = owner; this.owner = owner;
this.fetchURL = fetchURL; this.fetchURL = fetchURL;
this.headers = headers; 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 //the value can't really be anything, but I don't care enough to fix this
this.values[key] = value; 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( addSelect(
label: string, label: string,
formName: string, formName: string,
@ -903,7 +979,9 @@ class Form implements OptionsElement<object>{
} }
return mdInput; return mdInput;
} }
addButtonInput(label:string,textContent:string,onSubmit:()=>void){
return this.options.addButtonInput(label,textContent,onSubmit);
}
addCheckboxInput( addCheckboxInput(
label: string, label: string,
formName: string, formName: string,
@ -917,11 +995,12 @@ class Form implements OptionsElement<object>{
return box; return box;
} }
addText(str: string){ addText(str: string){
this.options.addText(str); return this.options.addText(str);
} }
addTitle(str: string){ addTitle(str: string){
this.options.addTitle(str); this.options.addTitle(str);
} }
button!:WeakRef<HTMLButtonElement>;
generateHTML(): HTMLElement{ generateHTML(): HTMLElement{
const div = document.createElement("div"); const div = document.createElement("div");
div.append(this.options.generateHTML()); div.append(this.options.generateHTML());
@ -933,6 +1012,10 @@ class Form implements OptionsElement<object>{
}; };
button.textContent = this.submitText; button.textContent = this.submitText;
div.append(button); div.append(button);
if(this.options.subOptions){
button.hidden=true;
}
this.button=new WeakRef(button);
} }
return div; return div;
} }
@ -1110,4 +1193,4 @@ class Settings extends Buttons{
} }
} }
export{ Settings, OptionsElement, Buttons, Options }; export{ Settings, OptionsElement, Buttons, Options,Form };