From e11cd2e0c17645b7667d671f08919bd5a91fd9ef Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Thu, 22 Aug 2024 15:06:45 -0500 Subject: [PATCH] updated settings class and user can now remove pfp --- .dist/localuser.js | 25 ++-- .dist/role.js | 2 + .dist/settings.js | 253 +++++++++++++++++++++++++++++++++++--- .dist/user.js | 4 +- webpage/jsontypes.ts | 2 +- webpage/localuser.ts | 26 ++-- webpage/role.ts | 3 +- webpage/settings.ts | 285 ++++++++++++++++++++++++++++++++++++++----- webpage/style.css | 1 + webpage/user.ts | 8 +- 10 files changed, 537 insertions(+), 72 deletions(-) diff --git a/.dist/localuser.js b/.dist/localuser.js index f4f4904..7c50cd3 100644 --- a/.dist/localuser.js +++ b/.dist/localuser.js @@ -814,8 +814,16 @@ class Localuser { if (file) { this.updatepfp(file); } - }); + }, { clear: true }); finput.watchForChange(_ => { + if (!_) { + file = _; + hypouser.avatar = null; + hypouser.hypotheticalpfp = true; + regen(); + return; + } + ; if (_.length) { file = _[0]; const blob = URL.createObjectURL(file); @@ -829,8 +837,15 @@ class Localuser { if (bfile !== undefined) { this.updatebanner(bfile); } - }); + }, { clear: true }); binput.watchForChange(_ => { + if (!_) { + bfile = null; + hypouser.banner = undefined; + hypouser.hypotheticalbanner = true; + regen(); + return; + } if (_.length) { bfile = _[0]; const blob = URL.createObjectURL(bfile); @@ -839,12 +854,6 @@ class Localuser { regen(); } }); - const bclear = settingsLeft.addButtonInput("Clear banner", "Clear", () => { - bfile = null; - hypouser.banner = undefined; - settingsLeft.changed(); - regen(); - }); let changed = false; const pronounbox = settingsLeft.addTextInput("Pronouns", _ => { if (newpronouns || newbio || changed) { diff --git a/.dist/role.js b/.dist/role.js index 7b6b5cc..65832a7 100644 --- a/.dist/role.js +++ b/.dist/role.js @@ -53,6 +53,8 @@ class PermissionToggle { this.permissions = permissions; this.owner = owner; } + watchForChange() { } + ; generateHTML() { const div = document.createElement("div"); div.classList.add("setting"); diff --git a/.dist/settings.js b/.dist/settings.js index ab0d9d6..950e080 100644 --- a/.dist/settings.js +++ b/.dist/settings.js @@ -4,6 +4,7 @@ class Buttons { buttons; buttonList; warndiv; + value; constructor(name) { this.buttons = []; this.name = name; @@ -61,6 +62,7 @@ class Buttons { this.warndiv = html; this.buttonList.append(html); } + watchForChange() { } save() { } submit() { } @@ -69,11 +71,11 @@ class TextInput { label; owner; onSubmit; - textContent; + value; input; constructor(label, onSubmit, owner, { initText = "" } = {}) { this.label = label; - this.textContent = initText; + this.value = initText; this.owner = owner; this.onSubmit = onSubmit; } @@ -83,7 +85,7 @@ class TextInput { span.textContent = this.label; div.append(span); const input = document.createElement("input"); - input.value = this.textContent; + input.value = this.value; input.type = "text"; input.oninput = this.onChange.bind(this); this.input = new WeakRef(input); @@ -96,7 +98,7 @@ class TextInput { if (input) { const value = input.value; this.onchange(value); - this.textContent = value; + this.value = value; } } onchange = _ => { }; @@ -104,7 +106,49 @@ class TextInput { this.onchange = func; } submit() { - this.onSubmit(this.textContent); + this.onSubmit(this.value); + } +} +class CheckboxInput { + label; + owner; + onSubmit; + value; + input; + constructor(label, onSubmit, owner, { initState = false } = {}) { + this.label = label; + this.value = initState; + this.owner = owner; + this.onSubmit = onSubmit; + } + generateHTML() { + const div = document.createElement("div"); + const span = document.createElement("span"); + span.textContent = this.label; + div.append(span); + const input = document.createElement("input"); + input.type = "checkbox"; + input.checked = this.value; + input.oninput = this.onChange.bind(this); + this.input = new WeakRef(input); + div.append(input); + return div; + } + onChange(ev) { + this.owner.changed(); + const input = this.input.deref(); + if (input) { + const value = input.checked; + this.onchange(value); + this.value = value; + } + } + onchange = _ => { }; + watchForChange(func) { + this.onchange = func; + } + submit() { + this.onSubmit(this.value); } } class ButtonInput { @@ -112,6 +156,7 @@ class ButtonInput { owner; onClick; textContent; + value; constructor(label, textContent, onClick, owner, {} = {}) { this.label = label; this.owner = owner; @@ -141,6 +186,7 @@ class ColorInput { onSubmit; colorContent; input; + value; constructor(label, onSubmit, owner, { initColor = "" } = {}) { this.label = label; this.colorContent = initColor; @@ -165,6 +211,7 @@ class ColorInput { const input = this.input.deref(); if (input) { const value = input.value; + this.value = value; this.onchange(value); this.colorContent = value; } @@ -184,6 +231,9 @@ class SelectInput { options; index; select; + get value() { + return this.index; + } constructor(label, onSubmit, options, owner, { defaultIndex = 0 } = {}) { this.label = label; this.index = defaultIndex; @@ -229,11 +279,11 @@ class MDInput { label; owner; onSubmit; - textContent; + value; input; constructor(label, onSubmit, owner, { initText = "" } = {}) { this.label = label; - this.textContent = initText; + this.value = initText; this.owner = owner; this.onSubmit = onSubmit; } @@ -244,7 +294,7 @@ class MDInput { div.append(span); div.append(document.createElement("br")); const input = document.createElement("textarea"); - input.value = this.textContent; + input.value = this.value; input.oninput = this.onChange.bind(this); this.input = new WeakRef(input); div.append(input); @@ -256,7 +306,7 @@ class MDInput { if (input) { const value = input.value; this.onchange(value); - this.textContent = value; + this.value = value; } } onchange = _ => { }; @@ -264,7 +314,7 @@ class MDInput { this.onchange = func; } submit() { - this.onSubmit(this.textContent); + this.onSubmit(this.value); } } class FileInput { @@ -272,10 +322,13 @@ class FileInput { owner; onSubmit; input; - constructor(label, onSubmit, owner, {} = {}) { + value; + clear; + constructor(label, onSubmit, owner, { clear = false } = {}) { this.label = label; this.owner = owner; this.onSubmit = onSubmit; + this.clear = clear; } generateHTML() { const div = document.createElement("div"); @@ -287,12 +340,26 @@ class FileInput { input.oninput = this.onChange.bind(this); this.input = new WeakRef(input); div.append(input); + if (this.clear) { + const button = document.createElement("button"); + button.textContent = "Clear"; + button.onclick = _ => { + if (this.onchange) { + this.onchange(null); + } + ; + this.value = null; + this.owner.changed(); + }; + div.append(button); + } return div; } onChange(ev) { this.owner.changed(); const input = this.input.deref(); if (this.onchange && input) { + this.value = input.files; this.onchange(input.files); } } @@ -310,6 +377,7 @@ class FileInput { class HtmlArea { submit; html; + value; constructor(html, submit) { this.submit = submit; this.html = html; @@ -322,6 +390,8 @@ class HtmlArea { return this.html; } } + watchForChange() { } + ; } class Options { name; @@ -329,12 +399,15 @@ class Options { options; owner; ltr; + value; constructor(name, owner, { ltr = false } = {}) { this.name = name; this.options = []; this.owner = owner; this.ltr = ltr; } + watchForChange() { } + ; addOptions(name, { ltr = false } = {}) { const options = new Options(name, this, { ltr }); this.options.push(options); @@ -345,8 +418,8 @@ class Options { this.options.push(select); return select; } - addFileInput(label, onSubmit, {} = {}) { - const FI = new FileInput(label, onSubmit, this, {}); + addFileInput(label, onSubmit, { clear = false } = {}) { + const FI = new FileInput(label, onSubmit, this, { clear }); this.options.push(FI); return FI; } @@ -375,6 +448,12 @@ class Options { this.options.push(button); return button; } + addCheckboxInput(label, onSubmit, { initState = false } = {}) { + const box = new CheckboxInput(label, onSubmit, this, { initState }); + this.options.push(box); + return box; + } + html = new WeakMap(); generateHTML() { const div = document.createElement("div"); div.classList.add("titlediv"); @@ -387,13 +466,20 @@ class Options { const container = document.createElement("div"); container.classList.add(this.ltr ? "flexltr" : "flexttb", "flexspace"); for (const thing of this.options) { - container.append(thing.generateHTML()); + const div = document.createElement("div"); + if (!(thing instanceof Options)) { + div.classList.add("optionElement"); + } + const html = thing.generateHTML(); + div.append(html); + this.html.set(thing, new WeakRef(div)); + container.append(div); } div.append(container); return div; } changed() { - if (this.owner instanceof Options) { + if (this.owner instanceof Options || this.owner instanceof Form) { this.owner.changed(); return; } @@ -424,6 +510,143 @@ class Options { } } } +class Form { + name; + options; + owner; + ltr; + names; + required = new WeakSet(); + submitText; + fetchURL; + headers; + method; + value; + constructor(name, owner, onSubmit, { ltr = false, submitText = "Submit", fetchURL = "", headers = {}, method = "POST" } = {}) { + this.name = name; + this.method = method; + this.submitText = submitText; + this.options = new Options("", this, { ltr }); + this.owner = owner; + this.fetchURL = fetchURL; + this.headers = headers; + this.ltr = ltr; + this.onSubmit = onSubmit; + } + addSelect(label, formName, selections, { defaultIndex = 0, required = false } = {}) { + const select = this.options.addSelect(label, _ => { }, selections, { defaultIndex }); + this.names.set(formName, select); + if (required) { + this.required.add(select); + } + return; + } + addFileInput(label, formName, { required = false } = {}) { + const FI = this.options.addFileInput(label, _ => { }, {}); + this.names.set(formName, FI); + if (required) { + this.required.add(FI); + } + return; + } + addTextInput(label, formName, { initText = "", required = false } = {}) { + const textInput = this.options.addTextInput(label, _ => { }, { initText }); + this.names.set(formName, textInput); + if (required) { + this.required.add(textInput); + } + return; + } + addColorInput(label, formName, { initColor = "", required = false } = {}) { + const colorInput = this.options.addColorInput(label, _ => { }, { initColor }); + this.names.set(formName, colorInput); + if (required) { + this.required.add(colorInput); + } + return; + } + addMDInput(label, formName, { initText = "", required = false } = {}) { + const mdInput = this.options.addMDInput(label, _ => { }, { initText }); + this.names.set(formName, mdInput); + if (required) { + this.required.add(mdInput); + } + return; + } + addCheckboxInput(label, formName, { initState = false, required = false } = {}) { + const box = this.options.addCheckboxInput(label, _ => { }, { initState }); + this.names.set(formName, box); + if (required) { + this.required.add(box); + } + return; + } + generateHTML() { + const div = document.createElement("div"); + div.append(this.options.generateHTML()); + const button = document.createElement("button"); + button.onclick = _ => { + this.submit(); + }; + button.textContent = this.submitText; + div.append(button); + return div; + } + onSubmit; + watchForChange(func) { + this.onSubmit = func; + } + ; + changed() { + } + submit() { + const build = {}; + for (const thing of this.names.keys()) { + const input = this.names.get(thing); + build[thing] = input.value; + } + if (this.fetchURL === "") { + fetch(this.fetchURL, { + method: this.method, + body: JSON.stringify(build) + }).then(_ => _.json()).then(json => { + if (json.errors) { + this.errors(json.errors); + } + }); + } + else { + this.onSubmit(build); + } + console.warn("needs to be implemented"); + } + errors(errors) { + for (const error of errors) { + const elm = this.names.get(error); + if (elm) { + const ref = this.options.html.get(elm); + if (ref && ref.deref()) { + const html = ref.deref(); + this.makeError(html, error._errors[0].message); + } + } + } + } + makeError(e, message) { + let element = e.getElementsByClassName("suberror")[0]; + if (!element) { + const div = document.createElement("div"); + div.classList.add("suberror", "suberrora"); + e.append(div); + element = div; + } + else { + element.classList.remove("suberror"); + setTimeout(_ => { element.classList.add("suberror"); }, 100); + } + element.textContent = message; + } +} class Settings extends Buttons { static Buttons = Buttons; static Options = Options; diff --git a/.dist/user.js b/.dist/user.js index c30bb8c..38f9235 100644 --- a/.dist/user.js +++ b/.dist/user.js @@ -298,14 +298,14 @@ class User { } } getpfpsrc() { - if (this.hypotheticalpfp) { + if (this.hypotheticalpfp && this.avatar) { return this.avatar; } if (this.avatar != null) { return this.info.cdn + "/avatars/" + this.id.replace("#clone", "") + "/" + this.avatar + ".png"; } else { - const int = new Number((BigInt(this.id) >> 22n) % 6n); + const int = new Number((BigInt(this.id.replace("#clone", "")) >> 22n) % 6n); return this.info.cdn + `/embed/avatars/${int}.png`; } } diff --git a/webpage/jsontypes.ts b/webpage/jsontypes.ts index a9ca7b9..13622f7 100644 --- a/webpage/jsontypes.ts +++ b/webpage/jsontypes.ts @@ -133,7 +133,7 @@ type userjson={ discriminator: string, id: string, public_flags: number, - avatar: string, + avatar: string|null, accent_color: number, banner?: string, bio: string, diff --git a/webpage/localuser.ts b/webpage/localuser.ts index 66dd1d8..12eb6b0 100644 --- a/webpage/localuser.ts +++ b/webpage/localuser.ts @@ -803,7 +803,7 @@ class Localuser{ { const userOptions=settings.addButton("User Settings",{ltr:true}); const hypotheticalProfile=document.createElement("div"); - let file:undefined|File=undefined; + let file:undefined|File|null=undefined; let newpronouns:string|undefined=undefined; let newbio:string|undefined=undefined; let hypouser=this.user.clone(); @@ -823,8 +823,15 @@ class Localuser{ if(file){ this.updatepfp(file) } - }); + },{clear:true}); finput.watchForChange(_=>{ + if(!_) { + file=_; + hypouser.avatar = null; + hypouser.hypotheticalpfp=true; + regen(); + return; + }; if(_.length){ file=_[0]; const blob = URL.createObjectURL(file); @@ -838,8 +845,15 @@ class Localuser{ if(bfile!==undefined){ this.updatebanner(bfile) } - }); + },{clear:true}); binput.watchForChange(_=>{ + if(!_){ + bfile=null; + hypouser.banner = undefined; + hypouser.hypotheticalbanner=true; + regen(); + return; + } if(_.length){ bfile=_[0]; const blob = URL.createObjectURL(bfile); @@ -848,12 +862,6 @@ class Localuser{ regen(); } }); - const bclear=settingsLeft.addButtonInput("Clear banner","Clear",()=>{ - bfile=null; - hypouser.banner = undefined; - settingsLeft.changed(); - regen(); - }) let changed=false; const pronounbox=settingsLeft.addTextInput("Pronouns",_=>{ if(newpronouns||newbio||changed){ diff --git a/webpage/role.ts b/webpage/role.ts index f866ed4..9886f2e 100644 --- a/webpage/role.ts +++ b/webpage/role.ts @@ -45,7 +45,7 @@ class Role{ } export {Role}; import {Options} from "./settings.js"; -class PermissionToggle implements OptionsElement{ +class PermissionToggle implements OptionsElement{ readonly rolejson:{name:string,readableName:string,description:string}; permissions:Permissions; owner:Options; @@ -54,6 +54,7 @@ class PermissionToggle implements OptionsElement{ this.permissions=permissions; this.owner=owner; } + watchForChange(){}; generateHTML():HTMLElement{ const div=document.createElement("div"); div.classList.add("setting"); diff --git a/webpage/settings.ts b/webpage/settings.ts index b4ca4b0..869550e 100644 --- a/webpage/settings.ts +++ b/webpage/settings.ts @@ -1,15 +1,17 @@ -interface OptionsElement {//OptionsElement +interface OptionsElement {// generateHTML():HTMLElement; submit:()=>void; - //watchForChange:(func:(arg1:x)=>void)=>void + readonly watchForChange:(func:(arg1:x)=>void)=>void; + value:x; } //future me stuff -class Buttons implements OptionsElement{ +class Buttons implements OptionsElement{ readonly name:string; readonly buttons:[string,Options|string][]; buttonList:HTMLDivElement; warndiv:HTMLElement; + value:unknown; constructor(name:string){ this.buttons=[]; this.name=name; @@ -64,6 +66,7 @@ class Buttons implements OptionsElement{ this.warndiv=html; this.buttonList.append(html); } + watchForChange(){} save(){} submit(){ @@ -71,15 +74,15 @@ class Buttons implements OptionsElement{ } -class TextInput implements OptionsElement{ +class TextInput implements OptionsElement{ readonly label:string; readonly owner:Options; readonly onSubmit:(str:string)=>void; - textContent:string; - input:WeakRef + value:string; + input:WeakRef; constructor(label:string,onSubmit:(str:string)=>void,owner:Options,{initText=""}={}){ this.label=label; - this.textContent=initText; + this.value=initText; this.owner=owner; this.onSubmit=onSubmit; } @@ -89,7 +92,7 @@ class TextInput implements OptionsElement{ span.textContent=this.label; div.append(span); const input=document.createElement("input"); - input.value=this.textContent; + input.value=this.value; input.type="text"; input.oninput=this.onChange.bind(this); this.input=new WeakRef(input); @@ -102,7 +105,7 @@ class TextInput implements OptionsElement{ if(input){ const value=input.value as string; this.onchange(value); - this.textContent=value; + this.value=value; } } onchange:(str:string)=>void=_=>{}; @@ -110,15 +113,59 @@ class TextInput implements OptionsElement{ this.onchange=func; } submit(){ - this.onSubmit(this.textContent); + this.onSubmit(this.value); } } -class ButtonInput implements OptionsElement{ +class CheckboxInput implements OptionsElement{ + readonly label:string; + readonly owner:Options; + readonly onSubmit:(str:boolean)=>void; + value:boolean; + input:WeakRef; + constructor(label:string,onSubmit:(str:boolean)=>void,owner:Options,{initState=false}={}){ + this.label=label; + this.value=initState; + this.owner=owner; + this.onSubmit=onSubmit; + } + generateHTML():HTMLDivElement{ + const div=document.createElement("div"); + const span=document.createElement("span"); + span.textContent=this.label; + div.append(span); + const input=document.createElement("input"); + input.type="checkbox"; + input.checked=this.value; + input.oninput=this.onChange.bind(this); + this.input=new WeakRef(input); + div.append(input); + return div; + } + private onChange(ev:Event){ + this.owner.changed(); + const input=this.input.deref(); + if(input){ + const value=input.checked as boolean; + this.onchange(value); + this.value=value; + } + } + onchange:(str:boolean)=>void=_=>{}; + watchForChange(func:(str:boolean)=>void){ + this.onchange=func; + } + submit(){ + this.onSubmit(this.value); + } +} + +class ButtonInput implements OptionsElement{ readonly label:string; readonly owner:Options; readonly onClick:()=>void; textContent:string; + value: void; constructor(label:string,textContent:string,onClick:()=>void,owner:Options,{}={}){ this.label=label; this.owner=owner; @@ -143,12 +190,13 @@ class ButtonInput implements OptionsElement{ submit(){} } -class ColorInput implements OptionsElement{ +class ColorInput implements OptionsElement{ readonly label:string; readonly owner:Options; readonly onSubmit:(str:string)=>void; colorContent:string; - input:WeakRef + input:WeakRef; + value: string; constructor(label:string,onSubmit:(str:string)=>void,owner:Options,{initColor=""}={}){ this.label=label; this.colorContent=initColor; @@ -173,6 +221,7 @@ class ColorInput implements OptionsElement{ const input=this.input.deref(); if(input){ const value=input.value as string; + this.value=value; this.onchange(value); this.colorContent=value; } @@ -186,13 +235,16 @@ class ColorInput implements OptionsElement{ } } -class SelectInput implements OptionsElement{ +class SelectInput implements OptionsElement{ readonly label:string; readonly owner:Options; readonly onSubmit:(str:number)=>void; options:string[]; index:number; select:WeakRef + get value(){ + return this.index; + } constructor(label:string,onSubmit:(str:number)=>void,options:string[],owner:Options,{defaultIndex=0}={}){ this.label=label; this.index=defaultIndex; @@ -235,15 +287,15 @@ class SelectInput implements OptionsElement{ this.onSubmit(this.index); } } -class MDInput implements OptionsElement{ +class MDInput implements OptionsElement{ readonly label:string; readonly owner:Options; readonly onSubmit:(str:string)=>void; - textContent:string; + value:string; input:WeakRef constructor(label:string,onSubmit:(str:string)=>void,owner:Options,{initText=""}={}){ this.label=label; - this.textContent=initText; + this.value=initText; this.owner=owner; this.onSubmit=onSubmit; } @@ -254,7 +306,7 @@ class MDInput implements OptionsElement{ div.append(span); div.append(document.createElement("br")); const input=document.createElement("textarea"); - input.value=this.textContent; + input.value=this.value; input.oninput=this.onChange.bind(this); this.input=new WeakRef(input); div.append(input); @@ -266,7 +318,7 @@ class MDInput implements OptionsElement{ if(input){ const value=input.value as string; this.onchange(value); - this.textContent=value; + this.value=value; } } onchange:(str:string)=>void=_=>{}; @@ -274,18 +326,21 @@ class MDInput implements OptionsElement{ this.onchange=func; } submit(){ - this.onSubmit(this.textContent); + this.onSubmit(this.value); } } -class FileInput implements OptionsElement{ +class FileInput implements OptionsElement{ readonly label:string; readonly owner:Options; readonly onSubmit:(str:FileList|null)=>void; input:WeakRef - constructor(label:string,onSubmit:(str:FileList)=>void,owner:Options,{}={}){ + value:FileList|null; + clear:boolean; + constructor(label:string,onSubmit:(str:FileList)=>void,owner:Options,{clear=false}={}){ this.label=label; this.owner=owner; this.onSubmit=onSubmit; + this.clear=clear; } generateHTML():HTMLDivElement{ const div=document.createElement("div"); @@ -297,17 +352,28 @@ class FileInput implements OptionsElement{ input.oninput=this.onChange.bind(this); this.input=new WeakRef(input); div.append(input); + if(this.clear){ + const button=document.createElement("button"); + button.textContent="Clear"; + button.onclick=_=>{ + if(this.onchange){this.onchange(null)}; + this.value=null; + this.owner.changed(); + } + div.append(button); + } return div; } onChange(ev:Event){ this.owner.changed(); const input=this.input.deref(); if(this.onchange&&input){ + this.value=input.files; this.onchange(input.files); } } onchange:((str:FileList|null)=>void)|null=null; - watchForChange(func:(str:FileList)=>void){ + watchForChange(func:(str:FileList|null)=>void){ this.onchange=func; } submit(){ @@ -318,9 +384,10 @@ class FileInput implements OptionsElement{ } } -class HtmlArea implements OptionsElement{ +class HtmlArea implements OptionsElement{ submit: () => void; html:(()=>HTMLElement)|HTMLElement; + value:void; constructor(html:(()=>HTMLElement)|HTMLElement,submit:()=>void){ this.submit=submit; this.html=html; @@ -332,20 +399,23 @@ class HtmlArea implements OptionsElement{ return this.html; } } + watchForChange(){}; } -class Options implements OptionsElement{ +class Options implements OptionsElement{ name:string; haschanged=false; - readonly options:OptionsElement[]; - readonly owner:Buttons|Options; + readonly options:OptionsElement[]; + readonly owner:Buttons|Options|Form; readonly ltr:boolean; - constructor(name:string,owner:Buttons|Options,{ltr=false}={}){ + value:void; + constructor(name:string,owner:Buttons|Options|Form,{ltr=false}={}){ this.name=name; this.options=[]; this.owner=owner; this.ltr=ltr; } + watchForChange(){}; addOptions(name:string,{ltr=false}={}){ const options=new Options(name,this,{ltr}); this.options.push(options); @@ -356,8 +426,8 @@ class Options implements OptionsElement{ this.options.push(select); return select; } - addFileInput(label:string,onSubmit:(files:FileList)=>void,{}={}){ - const FI=new FileInput(label,onSubmit,this,{}); + addFileInput(label:string,onSubmit:(files:FileList)=>void,{clear=false}={}){ + const FI=new FileInput(label,onSubmit,this,{clear}); this.options.push(FI); return FI; } @@ -386,6 +456,12 @@ class Options implements OptionsElement{ this.options.push(button); return button; } + addCheckboxInput(label:string,onSubmit:(str:boolean)=>void,{initState=false}={}){ + const box=new CheckboxInput(label,onSubmit,this,{initState}); + this.options.push(box); + return box; + } + readonly html:WeakMap,WeakRef>=new WeakMap(); generateHTML():HTMLElement{ const div=document.createElement("div"); div.classList.add("titlediv"); @@ -398,13 +474,20 @@ class Options implements OptionsElement{ const container=document.createElement("div"); container.classList.add(this.ltr?"flexltr":"flexttb","flexspace"); for(const thing of this.options){ - container.append(thing.generateHTML()); + const div=document.createElement("div"); + if(!(thing instanceof Options)){ + div.classList.add("optionElement") + } + const html=thing.generateHTML(); + div.append(html); + this.html.set(thing,new WeakRef(div)); + container.append(div); } div.append(container); return div; } changed(){ - if(this.owner instanceof Options){ + if(this.owner instanceof Options||this.owner instanceof Form){ this.owner.changed(); return; } @@ -436,7 +519,145 @@ class Options implements OptionsElement{ } } } +class Form implements OptionsElement{ + name:string; + readonly options:Options; + readonly owner:Buttons|Options; + readonly ltr:boolean; + readonly names:Map> + readonly required:WeakSet>=new WeakSet(); + readonly submitText:string; + readonly fetchURL:string; + readonly headers:object; + readonly method:string; + value:object; + constructor(name:string,owner:Buttons|Options,onSubmit:((arg1:object)=>void),{ltr=false,submitText="Submit",fetchURL="",headers={},method="POST"}={}){ + this.name=name; + this.method=method; + this.submitText=submitText; + this.options=new Options("",this,{ltr}); + this.owner=owner; + this.fetchURL=fetchURL; + this.headers=headers; + this.ltr=ltr; + this.onSubmit=onSubmit; + } + addSelect(label:string,formName:string,selections:string[],{defaultIndex=0,required=false}={}){ + const select=this.options.addSelect(label,_=>{},selections,{defaultIndex}); + this.names.set(formName,select); + if(required){ + this.required.add(select); + } + return; + } + addFileInput(label:string,formName:string,{required=false}={}){ + const FI=this.options.addFileInput(label,_=>{},{}); + this.names.set(formName,FI); + if(required){ + this.required.add(FI); + } + return; + } + + addTextInput(label:string,formName:string,{initText="",required=false}={}){ + const textInput=this.options.addTextInput(label,_=>{},{initText}); + this.names.set(formName,textInput); + if(required){ + this.required.add(textInput); + } + return; + } + addColorInput(label:string,formName:string,{initColor="",required=false}={}){ + const colorInput=this.options.addColorInput(label,_=>{},{initColor}); + this.names.set(formName,colorInput); + if(required){ + this.required.add(colorInput); + } + return; + } + + addMDInput(label:string,formName:string,{initText="",required=false}={}){ + const mdInput=this.options.addMDInput(label,_=>{},{initText}); + this.names.set(formName,mdInput); + if(required){ + this.required.add(mdInput); + } + return; + } + + addCheckboxInput(label:string,formName:string,{initState=false,required=false}={}){ + const box=this.options.addCheckboxInput(label,_=>{},{initState}); + this.names.set(formName,box); + if(required){ + this.required.add(box); + } + return; + } + + generateHTML():HTMLElement{ + const div=document.createElement("div"); + div.append(this.options.generateHTML()); + const button=document.createElement("button"); + button.onclick=_=>{ + this.submit(); + } + button.textContent=this.submitText; + div.append(button) + return div; + } + onSubmit:((arg1:object)=>void); + watchForChange(func:(arg1:object)=>void){ + this.onSubmit=func; + }; + changed(){ + } + submit(){ + const build={} + for(const thing of this.names.keys()){ + const input=this.names.get(thing) as OptionsElement; + build[thing]=input.value; + } + if(this.fetchURL===""){ + fetch(this.fetchURL,{ + method:this.method, + body:JSON.stringify(build) + }).then(_=>_.json()).then(json=>{ + if(json.errors){ + this.errors(json.errors) + } + }) + }else{ + this.onSubmit(build); + } + console.warn("needs to be implemented") + } + errors(errors){ + for(const error of errors){ + const elm=this.names.get(error); + if(elm){ + const ref=this.options.html.get(elm); + if(ref&&ref.deref()){ + const html=ref.deref() as HTMLDivElement; + this.makeError(html,error._errors[0].message) + } + } + } + } + makeError(e:HTMLDivElement,message:string){ + let element=e.getElementsByClassName("suberror")[0] as HTMLElement; + if(!element){ + const div=document.createElement("div"); + div.classList.add("suberror","suberrora"); + e.append(div); + element=div; + }else{ + element.classList.remove("suberror"); + setTimeout(_=>{element.classList.add("suberror")},100); + } + element.textContent=message; + } +} class Settings extends Buttons{ static readonly Buttons=Buttons; static readonly Options=Options; diff --git a/webpage/style.css b/webpage/style.css index c178ce8..218261d 100644 --- a/webpage/style.css +++ b/webpage/style.css @@ -837,6 +837,7 @@ input::file-selector-button { input[type="file"] { background:transparent; box-shadow: none; + width: min-content; } select{ transition: background .1s ease-in-out; diff --git a/webpage/user.ts b/webpage/user.ts index 8c79766..f1ade33 100644 --- a/webpage/user.ts +++ b/webpage/user.ts @@ -12,7 +12,7 @@ class User{ owner:Localuser; hypotheticalpfp:boolean; snowflake:SnowFlake; - avatar:string; + avatar:string|null; username:string; nickname:string|null=null; relationshipType:0|1|2|3|4=0; @@ -260,7 +260,7 @@ class User{ ).then(_=>_.json()); return new User(json,localuser); } - changepfp(update:string){ + changepfp(update:string|null){ this.avatar=update; this.hypotheticalpfp=false; const src=this.getpfpsrc(); @@ -299,13 +299,13 @@ class User{ } } getpfpsrc(){ - if(this.hypotheticalpfp){ + if(this.hypotheticalpfp&&this.avatar){ return this.avatar; } if(this.avatar!=null){ return this.info.cdn+"/avatars/"+this.id.replace("#clone","")+"/"+this.avatar+".png"; }else{ - const int=new Number((BigInt(this.id) >> 22n) % 6n); + const int=new Number((BigInt(this.id.replace("#clone","")) >> 22n) % 6n); return this.info.cdn+`/embed/avatars/${int}.png`; } }