interface OptionsElement {// generateHTML():HTMLElement; submit:()=>void; readonly watchForChange:(func:(arg1:x)=>void)=>void; value:x; } //future me stuff 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; } add(name:string,thing:Options|undefined=undefined){ if(!thing){thing=new Options(name,this)} this.buttons.push([name,thing]); return thing; } generateHTML(){ const buttonList=document.createElement("div"); buttonList.classList.add("Buttons"); buttonList.classList.add("flexltr"); this.buttonList=buttonList; const htmlarea=document.createElement("div"); htmlarea.classList.add("flexgrow"); const buttonTable=document.createElement("div"); buttonTable.classList.add("flexttb","settingbuttons"); for(const thing of this.buttons){ const button=document.createElement("button"); button.classList.add("SettingsButton"); button.textContent=thing[0]; button.onclick=_=>{ this.generateHTMLArea(thing[1],htmlarea); if(this.warndiv){ this.warndiv.remove(); } } buttonTable.append(button); } this.generateHTMLArea(this.buttons[0][1],htmlarea); buttonList.append(buttonTable); buttonList.append(htmlarea); return buttonList; } handleString(str:string):HTMLElement{ const div=document.createElement("span"); div.textContent=str; return div; } private generateHTMLArea(buttonInfo:Options|string,htmlarea:HTMLElement){ let html:HTMLElement; if(buttonInfo instanceof Options){ html=buttonInfo.generateHTML(); }else{ html=this.handleString(buttonInfo); } htmlarea.innerHTML=""; htmlarea.append(html); } changed(html:HTMLElement){ this.warndiv=html; this.buttonList.append(html); } watchForChange(){} save(){} submit(){ } } class TextInput implements OptionsElement{ readonly label:string; readonly owner:Options; readonly onSubmit:(str:string)=>void; value:string; input:WeakRef; constructor(label:string,onSubmit:(str:string)=>void,owner:Options,{initText=""}={}){ this.label=label; this.value=initText; 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.value=this.value; input.type="text"; 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.value as string; this.onchange(value); this.value=value; } } onchange:(str:string)=>void=_=>{}; watchForChange(func:(str:string)=>void){ this.onchange=func; } submit(){ this.onSubmit(this.value); } } 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; this.onClick=onClick; this.textContent=textContent; } generateHTML():HTMLDivElement{ const div=document.createElement("div"); const span=document.createElement("span"); span.textContent=this.label; div.append(span); const button=document.createElement("button"); button.textContent=this.textContent; button.onclick=this.onClickEvent.bind(this); div.append(button); return div; } private onClickEvent(ev:Event){ this.onClick(); } watchForChange(){} submit(){} } class ColorInput implements OptionsElement{ readonly label:string; readonly owner:Options; readonly onSubmit:(str:string)=>void; colorContent:string; input:WeakRef; value: string; constructor(label:string,onSubmit:(str:string)=>void,owner:Options,{initColor=""}={}){ this.label=label; this.colorContent=initColor; 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.value=this.colorContent; input.type="color"; 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.value as string; this.value=value; this.onchange(value); this.colorContent=value; } } onchange:(str:string)=>void=_=>{}; watchForChange(func:(str:string)=>void){ this.onchange=func; } submit(){ this.onSubmit(this.colorContent); } } 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; this.owner=owner; this.onSubmit=onSubmit; this.options=options; } generateHTML():HTMLDivElement{ const div=document.createElement("div"); const span=document.createElement("span"); span.textContent=this.label; div.append(span); const select=document.createElement("select"); select.onchange=this.onChange.bind(this); for(const thing of this.options){ const option = document.createElement("option"); option.textContent=thing; select.appendChild(option); } this.select=new WeakRef(select); select.selectedIndex=this.index; div.append(select); return div; } private onChange(ev:Event){ this.owner.changed(); const select=this.select.deref(); if(select){ const value=select.selectedIndex; this.onchange(value); this.index=value; } } onchange:(str:number)=>void=_=>{}; watchForChange(func:(str:number)=>void){ this.onchange=func; } submit(){ this.onSubmit(this.index); } } class MDInput implements OptionsElement{ readonly label:string; readonly owner:Options; readonly onSubmit:(str:string)=>void; value:string; input:WeakRef constructor(label:string,onSubmit:(str:string)=>void,owner:Options,{initText=""}={}){ this.label=label; this.value=initText; 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); div.append(document.createElement("br")); const input=document.createElement("textarea"); input.value=this.value; input.oninput=this.onChange.bind(this); this.input=new WeakRef(input); div.append(input); return div; } onChange(ev:Event){ this.owner.changed(); const input=this.input.deref(); if(input){ const value=input.value as string; this.onchange(value); this.value=value; } } onchange:(str:string)=>void=_=>{}; watchForChange(func:(str:string)=>void){ this.onchange=func; } submit(){ this.onSubmit(this.value); } } class FileInput implements OptionsElement{ readonly label:string; readonly owner:Options; readonly onSubmit:(str:FileList|null)=>void; input:WeakRef 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"); const span=document.createElement("span"); span.textContent=this.label; div.append(span); const input=document.createElement("input"); input.type="file"; 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|null)=>void){ this.onchange=func; } submit(){ const input=this.input.deref(); if(input){ this.onSubmit(input.files); } } } class HtmlArea implements OptionsElement{ submit: () => void; html:(()=>HTMLElement)|HTMLElement; value:void; constructor(html:(()=>HTMLElement)|HTMLElement,submit:()=>void){ this.submit=submit; this.html=html; } generateHTML(): HTMLElement { if(this.html instanceof Function){ return this.html(); }else{ return this.html; } } watchForChange(){}; } class Options implements OptionsElement{ name:string; haschanged=false; readonly options:OptionsElement[]; readonly owner:Buttons|Options|Form; readonly ltr:boolean; 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); return options; } addSelect(label:string,onSubmit:(str:number)=>void,selections:string[],{defaultIndex=0}={}){ const select=new SelectInput(label,onSubmit,selections,this,{defaultIndex}); this.options.push(select); return select; } addFileInput(label:string,onSubmit:(files:FileList)=>void,{clear=false}={}){ const FI=new FileInput(label,onSubmit,this,{clear}); this.options.push(FI); return FI; } addTextInput(label:string,onSubmit:(str:string)=>void,{initText=""}={}){ const textInput=new TextInput(label,onSubmit,this,{initText}); this.options.push(textInput); return textInput; } addColorInput(label:string,onSubmit:(str:string)=>void,{initColor=""}={}){ const colorInput=new ColorInput(label,onSubmit,this,{initColor}); this.options.push(colorInput); return colorInput; } addMDInput(label:string,onSubmit:(str:string)=>void,{initText=""}={}){ const mdInput=new MDInput(label,onSubmit,this,{initText}); this.options.push(mdInput); return mdInput; } addHTMLArea(html:(()=>HTMLElement)|HTMLElement,submit:()=>void=()=>{}){ const htmlarea=new HtmlArea(html,submit); this.options.push(htmlarea); return htmlarea; } addButtonInput(label:string,textContent:string,onSubmit:()=>void){ const button=new ButtonInput(label,textContent,onSubmit,this); 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"); if(this.name!==""){ const title=document.createElement("h2"); title.textContent=this.name; div.append(title); title.classList.add("settingstitle"); } const container=document.createElement("div"); container.classList.add(this.ltr?"flexltr":"flexttb","flexspace"); for(const thing of this.options){ 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||this.owner instanceof Form){ this.owner.changed(); return; } if(!this.haschanged){ const div=document.createElement("div"); div.classList.add("flexltr","savediv"); const span=document.createElement("span"); div.append(span); span.textContent="Careful, you have unsaved changes"; const button=document.createElement("button"); button.textContent="Save changes"; div.append(button); this.haschanged=true; this.owner.changed(div); button.onclick=_=>{ if(this.owner instanceof Buttons){ this.owner.save(); } div.remove(); this.submit(); } } } submit(){ this.haschanged=false; for(const thing of this.options){ thing.submit(); } } } 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; html:HTMLElement|null; constructor(name:string){ super(name); } addButton(name:string,{ltr=false}={}):Options{ const options=new Options(name,this,{ltr}); this.add(name,options); return options; } show(){ const background=document.createElement("div"); background.classList.add("background"); const title=document.createElement("h2"); title.textContent=this.name; title.classList.add("settingstitle") background.append(title); background.append(this.generateHTML()); const exit=document.createElement("span"); exit.textContent="✖"; exit.classList.add("exitsettings"); background.append(exit); exit.onclick=_=>{this.hide();}; document.body.append(background); this.html=background; } hide(){ if(this.html){ this.html.remove(); this.html=null; } } } export {Settings,OptionsElement,Buttons,Options}