1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354open!Coreopen!Bonsai_webopenBonsai.Let_syntaxmoduleExtendy=Bonsai_web_ui_extendymoduletypeStringable_model=sigtypetincludeBonsai.Modelwithtypet:=tincludeStringablewithtypet:=tendmoduleStyle=[%css.raw{|
.invalid_text_box {
outline: none;
border: 2px solid red;
border-radius: 2px;
} |}]letpath=let%map.Computationpath_id=Bonsai.path_idinpath_id,Vdom.Attr.idpath_id;;moduleBasic_stateful=structletmakestate~view=let%subpath,id=pathinlet%substate,set_state=stateinreturn@@let%mapview=view~id~state~set_stateandpath=pathandstate=stateandset_state=set_stateinForm.Expert.create~value:(Okstate)~view:(View.of_vdom~id:pathview)~set:set_state;;endletcomputation_mapa~f=let%suba=ainreturn@@Value.map~fa;;letoptional_to_required=Form.project'~parse:(function|Somea->Oka|None->Error(Error.of_string"a value is required"))~unparse:(funa->Somea);;moduleTextbox=structletstring?(extra_attrs=Value.return[])?placeholderhere=letview~id~state~set_state=let%mapid=idandstate=stateandset_state=set_stateandextra_attrs=extra_attrsinVdom_input_widgets.Entry.text?placeholder~extra_attrs:([id]@extra_attrs)~value:(Somestate)~on_input:(function|Somes->set_states|None->set_state"")()inBasic_stateful.make(Bonsai.statehere(moduleString)~default_model:"")~view;;letint?extra_attrs?placeholderhere=computation_map(string?extra_attrs?placeholderhere)~f:(Form.project'~parse:(funinput->matchInt.of_stringinputwith|exception_->Or_error.errorf"Expecting an integer"|x->Or_error.returnx)~unparse:Int.to_string_hum);;letfloat?extra_attrs?placeholderhere=computation_map(string?extra_attrs?placeholderhere)~f:(Form.project~parse_exn:Float.of_string~unparse:Float.to_string_hum);;letsexpable(typet)?extra_attrs?placeholderhere(moduleM:Sexpablewithtypet=t)=letparse_exns=s|>Sexp.of_string|>M.t_of_sexpinletunparset=t|>M.sexp_of_t|>Sexp.to_string_humincomputation_map(string?extra_attrs?placeholderhere)~f:(Form.project~parse_exn~unparse);;letstringable(typet)?extra_attrs?placeholderhere(moduleM:Stringablewithtypet=t)=computation_map(string?extra_attrs?placeholderhere)~f:(Form.project~parse_exn:M.of_string~unparse:M.to_string);;endmoduleTextarea=structletstring?(extra_attrs=Value.return[])?placeholderhere=letview~id~state~set_state=let%mapid=idandstate=stateandset_state=set_stateandextra_attrs=extra_attrsinVdom_input_widgets.Entry.text_area?placeholder~extra_attrs:(id::extra_attrs)~value:state~on_input:set_state()inBasic_stateful.make(Bonsai.statehere(moduleString)~default_model:"")~view;;letint?extra_attrs?placeholderhere=computation_map(string?extra_attrs?placeholderhere)~f:(Form.project~parse_exn:Int.of_string~unparse:Int.to_string_hum);;letfloat?extra_attrs?placeholderhere=computation_map(string?extra_attrs?placeholderhere)~f:(Form.project~parse_exn:Float.of_string~unparse:Float.to_string_hum);;letsexpable(typet)?extra_attrs?placeholderhere(moduleM:Sexpablewithtypet=t)=letparse_exns=s|>Sexp.of_string|>M.t_of_sexpinletunparset=t|>M.sexp_of_t|>Sexp.to_string_humincomputation_map(string?extra_attrs?placeholderhere)~f:(Form.project~parse_exn~unparse);;letstringable(typet)?extra_attrs?placeholderhere(moduleM:Stringablewithtypet=t)=computation_map(string?extra_attrs?placeholderhere)~f:(Form.project~parse_exn:M.of_string~unparse:M.to_string);;endletsexp_to_pretty_stringsexp_of_tt=t|>sexp_of_t|>Sexp.to_string_mach|>String.lowercase|>String.map~f:(function|'('|')'|'-'|'_'->' '|o->o);;moduleCheckbox=structletmake_input~extra_attrs~state~set_state=Vdom.Node.input~attr:(Vdom.Attr.many_without_merge([Vdom.Attr.style(Css_gen.margin_left(`Px0));Vdom.Attr.type_"checkbox";Vdom.Attr.on_click(funevt->(* try to get the actual state of the checkbox, but if
that doesn't work, assume that clicking on the
element toggled the state. *)letchecked=letopenOption.Let_syntaxinletopenJs_of_ocamlinlet%bindtarget=evt##.target|>Js.Opt.to_optioninlet%bindcoerced=Dom_html.CoerceTo.inputtarget|>Js.Opt.to_optioninreturn(Js.to_boolcoerced##.checked)inmatchcheckedwith|Somebool->set_statebool|None->set_state(notstate));Vdom.Attr.bool_property"checked"state]@extra_attrs))[];;letcheckbox?(extra_attrs=Value.return[])heredefault_model=letview~id~state~set_state=let%mapid=idandstate=stateandset_state=set_stateandextra_attrs=extra_attrsinmake_input~extra_attrs:(id::extra_attrs)~state~set_stateinBasic_stateful.make(Bonsai.statehere(moduleBool)~default_model)~view;;letbool?extra_attrshere~default=checkbox?extra_attrsheredefaultletset(typeacmp)here?(extra_attrs=Value.return[])?to_string(moduleM:Bonsai.Comparatorwithtypet=aandtypecomparator_witness=cmp)values:(a,cmp)Set.tForm.tComputation.t=letto_string=Option.valueto_string~default:(sexp_to_pretty_string[%sexp_of:M.t])inletmoduleM=structincludeMincludeComparable.Make_using_comparator(M)letto_string=to_stringendinletview~id~state~set_state=let%mapid=idandstate=stateandset_state=set_stateandvalues=valuesandextra_attrs=extra_attrsinVdom_input_widgets.Checklist.of_values~extra_attrs:(id::extra_attrs)(moduleM)values~is_checked:(Set.memstate)~on_toggle:(funitem->set_state@@ifSet.memstateitemthenSet.removestateitemelseSet.addstateitem)inBasic_stateful.make(Bonsai.statehere(moduleM.Set)~default_model:M.Set.empty)~view;;endmoduleToggle=struct(* Most of this css is from https://www.w3schools.com/howto/howto_css_switch.asp *)moduleStyle=[%css.raw{|
.toggle {
position: relative;
display: inline-block;
width: 40px;
height: 22px;
}
.invisible {
opacity: 0;
width: 0;
height: 0;
}
.slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #ccc; /* change */
transition: 0.2s;
border-radius: 34px;
}
.slider::before {
position: absolute;
content: "";
height: 18px;
width: 18px;
left: 2px;
bottom: 2px;
border-radius: 50%;
background-color: white;
transition: 0.2s;
}
.invisible:checked + .slider {
background-color: #2196F3;
}
.invisible:focused + .slider {
box-shadow: 0 0 1px #2196F3;
}
.invisible:checked + .slider::before {
transform: translateX(18px);
}
|}]letbool?(extra_attr=Value.returnVdom.Attr.empty)~default()=letview~id~state~set_state=let%mapid=idandstate=stateandset_state=set_stateandextra_attr=extra_attrinletcheckbox=Checkbox.make_input~extra_attrs:[Vdom.Attr.many[Vdom.Attr.class_Style.invisible;extra_attr]]~state~set_stateinletslider=Vdom.Node.span~attr:(Vdom.Attr.class_Style.slider)[]inVdom.Node.label~attr:(Vdom.Attr.many[Vdom.Attr.class_Style.toggle;id])[checkbox;slider]inBasic_stateful.make(Bonsai.state[%here](moduleBool)~default_model:default)~view;;endmoduleDropdown=structletimpl(typet)here?to_string?(extra_attrs=Value.return[])(moduleE:Bonsai.Modelwithtypet=t)all~include_empty~init=letmoduleE=structincludeEletto_string=Option.valueto_string~default:(sexp_to_pretty_string[%sexp_of:t]);;endinletmoduleOpt=structtypet=|Uninitialized|Explicitly_none|SetofE.t[@@derivingsexp,equal]letto_optiont=matchtwith|Uninitialized|Explicitly_none->None|Setv->Somev;;letvaluet=Option.value(to_optiont)endinletdefault_value=matchinitwith|`Empty->Value.returnNone|`First_item->all>>|List.hd|`Thisitem->item>>|Option.some|`Constitem->Value.return(Someitem)inlet%subpath,id=pathinlet%substate,set_state=Bonsai.statehere(moduleOpt)~default_model:Uninitializedinreturn@@let%mapid=idandpath=pathandstate=stateandset_state=set_stateandall=allandextra_attrs=extra_attrsanddefault_value=default_valueinletview=letmaker=matchinclude_empty,default_valuewith|true,_|false,None->letselected=matchstatewith|Uninitialized->default_value|Explicitly_none->None|Setv->SomevinVdom_input_widgets.Dropdown.of_values_opt~selected~on_change:(function|None->set_stateExplicitly_none|Somev->set_state(Setv))|false,Somedefault->Vdom_input_widgets.Dropdown.of_values~selected:(Opt.valuestate~default)~on_change:(funa->set_state(Seta))inletextra_attrs=[id;Vdom.Attr.style(Css_gen.width(`Percent(Percent.of_mult1.0)))]@extra_attrsinmaker(moduleE)all~extra_attrsinletview=View.of_vdom~id:pathviewinletvalue=matchstate,default_valuewith|Uninitialized,Somedefault_value->Somedefault_value|_->Opt.to_optionstateinForm.Expert.create~value:(Okvalue)~view~set:(function|None->set_stateExplicitly_none|Somev->set_state(Setv));;letlist_opt(typet)here?(init=`Empty)?extra_attrs?to_string(moduleE:Bonsai.Modelwithtypet=t)all=implhere?to_string?extra_attrs(moduleE)all~include_empty:true~init;;letenumerable_opt(typet)here?(init=`Empty)?extra_attrs?to_string(moduleE:Bonsai.Enumwithtypet=t)=implhere?to_string?extra_attrs(moduleE)(Value.returnE.all)~include_empty:true~init;;letinclude_empty_from_init=function|`Empty->true|`First_item|`This_|`Const->false;;letlisthere?(init=`First_item)?extra_attrs?to_stringmall=computation_map(implhere?to_string?extra_attrsmall~init~include_empty:(include_empty_from_initinit))~f:optional_to_required;;letenumerable(typet)here?(init=`First_item)?extra_attrs?to_string(moduleE:Bonsai.Enumwithtypet=t)=computation_map(implhere?extra_attrs?to_string(moduleE)(Value.returnE.all)~init~include_empty:(include_empty_from_initinit))~f:optional_to_required;;endmoduleTypeahead=structletsingle_opt_here?(extra_attrs=Value.return[])?placeholder?to_stringm~all_options=let%subpath,id=pathinletextra_attrs=let%mapid=idandextra_attrs=extra_attrsinid::extra_attrsinlet%subtypeahead=Bonsai_web_ui_typeahead.Typeahead.create?placeholder?to_stringm~all_options~extra_attrsinreturn@@let%mapvalue,view,set=typeaheadandpath=pathinForm.Expert.create~value:(Okvalue)~view:(View.of_vdom~id:pathview)~set;;letsinglehere?extra_attrs?placeholder?to_stringm~all_options=computation_map(single_opthere?extra_attrs?placeholder?to_stringm~all_options)~f:optional_to_required;;letset_here?(extra_attrs=Value.return[])?placeholder?to_string?splitm~all_options=let%subpath,id=pathinletextra_attrs=let%mapid=idandextra_attrs=extra_attrsinid::extra_attrsinlet%subtypeahead=Bonsai_web_ui_typeahead.Typeahead.create_multi?placeholder?to_string?splitm~extra_attrs~all_optionsinreturn@@let%mapvalue,view,set=typeaheadandpath=pathinForm.Expert.create~value:(Okvalue)~view:(View.of_vdom~id:pathview)~set;;letlist(typeacmp)here?extra_attrs?placeholder?to_string?split(moduleM:Bonsai.Comparatorwithtypet=aandtypecomparator_witness=cmp)~all_options=computation_map(sethere?extra_attrs?placeholder?to_string?split(moduleM)~all_options)~f:(Form.project~parse_exn:Set.to_list~unparse:(Set.of_list(moduleM)));;endmoduleDate_time=structletdate_opt?(extra_attrs=Value.return[])here=letview~id~state~set_state=let%mapid=idandstate=stateandset_state=set_stateandextra_attrs=extra_attrsinVdom_input_widgets.Entry.date~extra_attrs:(id::extra_attrs)~value:state~on_input:set_state()inBasic_stateful.make(Bonsai.state_opthere(moduleDate))~view;;letdate?extra_attrshere=computation_map(date_opt?extra_attrshere)~f:optional_to_required;;lettime_opt?(extra_attrs=Value.return[])here=letview~id~state~set_state=let%mapid=idandstate=stateandset_state=set_stateandextra_attrs=extra_attrsinVdom_input_widgets.Entry.time~extra_attrs:(id::extra_attrs)~value:state~on_input:set_state()inBasic_stateful.make(Bonsai.state_opthere(moduleTime_ns.Ofday))~view;;lettime?extra_attrshere=computation_map(time_opt?extra_attrshere)~f:optional_to_required;;letdatetime_local_opt?(extra_attrs=Value.return[])here=letview~id~state~set_state=let%mapid=idandstate=stateandset_state=set_stateandextra_attrs=extra_attrsinVdom_input_widgets.Entry.datetime_local~extra_attrs:(id::extra_attrs)~value:state~on_input:set_state()inletmoduleTime_ns=structincludeTime_ns.Stable.Alternate_sexp.V1letequal=Time_ns.equalendinBasic_stateful.make(Bonsai.state_opthere(moduleTime_ns))~view;;letdatetime_local?extra_attrshere=computation_map(datetime_local_opt?extra_attrshere)~f:optional_to_required;;endmoduleMultiselect=structletset(typeacmp)_here?(extra_attrs=Value.return[])?to_string(moduleM:Bonsai.Comparatorwithtypet=aandtypecomparator_witness=cmp)input_list=letmoduleItem=structincludeMincludeComparable.Make_using_comparator(M)letto_string=Option.valueto_string~default:(sexp_to_pretty_string[%sexp_of:t]);;endinletmoduleSingle_factor=Bonsai_web_ui_multi_select.Make(Item)inletinput_set=input_list>>|Item.Set.of_listinlet%subpath,_id=pathinletextra_row_attrs~is_focused=ifis_focusedthen`RGBA(Css_gen.Color.RGBA.create()~r:0~g:0~b:0~a:(Percent.of_mult0.3))|>Css_gen.background_color|>Vdom.Attr.styleelseVdom.Attr.emptyinletview_config=let%mappath=pathin{Single_factor.View_config.header=Vdom.Node.none;extra_row_attrs=Someextra_row_attrs;autofocus_search_box=false;search_box_id=Somepath}inlet%subsingle_factor_result=Single_factor.bonsai~view_configinput_setinreturn@@let%map{Single_factor.Result.view;inject;selected_items;key_handler;_}=single_factor_resultandpath=pathandextra_attrs=extra_attrsinletset_stateset=inject(Single_factor.Action.Set_all_selection_statuses(Map.of_key_setset~f:(Fn.constSingle_factor.Selection_status.Selected)))inleton_keydown=Vdom.Attr.on_keydown(Vdom_keyboard.Keyboard_event_handler.handle_or_ignore_eventkey_handler)inletview=Vdom.Node.div~attr:(Vdom.Attr.many_without_merge(on_keydown::extra_attrs))[view]inForm.Expert.create~value:(Okselected_items)~view:(View.of_vdom~id:pathview)~set:set_state;;letlist(typeacmp)here?extra_attrs?to_string(moduleM:Bonsai.Comparatorwithtypet=aandtypecomparator_witness=cmp)input_list=computation_map(sethere?extra_attrs?to_string(moduleM)input_list)~f:(Form.project~parse_exn:Set.to_list~unparse:(Set.of_list(moduleM)));;endletlist_rev_map2ab~f=letrecloopabacc=matcha,bwith|x::xs,y::ys->loopxsys(fxy::acc)|[],[]->acc|_->eprint_s[%message"BUG! lists of unequal lengths"[%here]];accinloopab[];;moduleMultiple=structletstringable_list(typea)?(extra_input_attr=Value.returnVdom.Attr.empty)?(extra_pill_container_attr=Value.returnVdom.Attr.empty)?(extra_pill_attr=Value.returnVdom.Attr.empty)?(placeholder="")here(moduleM:Stringable_modelwithtypet=a)=letmoduleM_list=structtypet=M.tlist[@@derivingequal,sexp]endinlet%subinvalid,inject_invalid=Bonsai.statehere(moduleBool)~default_model:falseinlet%subselected_options,inject_selected_options=Bonsai.statehere(moduleM_list)~default_model:[]inlet%substate,set_state=Bonsai.statehere(moduleString)~default_model:""inlet%subpath,_=pathinlet%subpills=Bonsai_web_ui_common_components.Pills.of_list~extra_container_attr:extra_pill_container_attr~extra_pill_attr~to_string:M.to_string~inject_selected_optionsselected_optionsinlet%arrinvalid=invalidandinject_invalid=inject_invalidandselected_options=selected_optionsandinject_selected_options=inject_selected_optionsandstate=stateandset_state=set_stateandpills=pillsandextra_input_attr=extra_input_attrandpath=pathinletplaceholder_=placeholderinlethandle_keydownevent=matchJs_of_ocaml.Dom_html.Keyboard_code.of_eventeventwith|Enter->(matchOption.try_with(fun()->M.of_stringstate)with|None->Effect.Many[Effect.Prevent_default;inject_invalidtrue]|Somevalue->Effect.Many[Effect.Prevent_default;inject_selected_options(value::selected_options);set_state""])|_->Effect.Ignoreinletinvalid_attr=ifinvalidthenVdom.Attr.class_Style.invalid_text_boxelseVdom.Attr.emptyinletinput=Vdom.Node.input~attr:Vdom.Attr.(extra_input_attr@invalid_attr@placeholderplaceholder_@value_propstate@on_input(fun_input->Effect.Many[inject_invalidfalse;set_stateinput])@on_keydownhandle_keydown)[]inletview=Vdom.Node.div[input;pills]inForm.Expert.create~value:(Okselected_options)~view:(View.of_vdom~id:pathview)~set:inject_selected_options;;letlist(typea)here?element_group_label?(add_element_text=Bonsai.Value.return"Add new element")?(button_placement=`Indented)(t:aForm.tComputation.t):alistForm.tComputation.t=let%subform,_=Bonsai.wrap(moduleUnit)~default_model:()~apply_action:(fun~inject:_~schedule_event(_,forms)()list_of_values->list_rev_map2(Map.dataforms)list_of_values~f:(funformvalue->Form.setformvalue)|>Ui_effect.Many|>schedule_event)~f:(fun(_:unitValue.t)inject_outer->let%subextendy=Extendy.componentheretinlet%subpath,_id=pathinreturn@@let%map{Extendy.contents;append;set_length;remove}=extendyandinject_outer=inject_outerandpath=pathandadd_element_text=add_element_textinletelements=contents|>Map.to_alist|>List.mapi~f:(funi(key,form)->letdelete_button=Vdom.Node.button~attr:(Vdom.Attr.many_without_merge[Vdom.Attr.type_"button";Vdom.Attr.styleCss_gen.(border~style:`None()@>create~field:"cursor"~value:"pointer"@>color(`Name"blue")@>create~field:"background"~value:"none");Vdom.Attr.on_click(fun_->removekey)])[Vdom.Node.text"[ remove ]"]inletlabel=matchelement_group_labelwith|Somelabel->label~delete_buttoni(Form.valueform)|None->Vdom.Node.div[Vdom.Node.textf"%d - "i;delete_button]inView.grouplabel(Form.viewform))inletbutton=matchbutton_placementwith|`Indented->View.Group{label=[Vdom.Node.textadd_element_text]|>Vdom.Node.button~attr:(Vdom.Attr.on_click(fun_->append))|>Some;tooltip=None;view=Empty}|`Inline->View.Row{label=None;tooltip=None;form=Vdom.Node.button~attr:(Vdom.Attr.on_click(fun_->append))[Vdom.Node.textadd_element_text];id=path;error=None}inletview=List.appendelements[button]inletview=View.Listviewinletvalue=contents|>Map.data|>List.map~f:Form.value|>Or_error.combine_errorsinletset(list:alist)=Vdom.Effect.Many[set_length(List.lengthlist);inject_outerlist]inForm.Expert.create~value~view~set,contents)inreturnform;;letset(typeacmp)here?element_group_label?add_element_text?button_placement(moduleM:Bonsai.Comparatorwithtypet=aandtypecomparator_witness=cmp)form=computation_map(listhere?button_placement?element_group_label?add_element_textform)~f:(Form.project~parse_exn:(Set.of_list(moduleM))~unparse:Set.to_list);;letmap(typeacmp)here?element_group_label?add_element_text?button_placement(moduleM:Bonsai.Comparatorwithtypet=aandtypecomparator_witness=cmp)~key~data=letboth=let%subkey_form=keyinlet%subvalue_form=datainreturn@@Value.map2key_formvalue_form~f:Form.bothincomputation_map(listhere?button_placement?element_group_label?add_element_textboth)~f:(Form.project~parse_exn:(Map.of_alist_exn(moduleM))~unparse:Map.to_alist);;endmoduleNumber_input_type=structtypet=|Number|RangeendmoduletypeNumber_input_specification=sigtypet[@@derivingsexp_of]includeBonsai.Modelwithtypet:=tincludeStringable.Swithtypet:=tvalmin:toptionvalmax:toptionvalstep:tvaldefault:tvalcompare:t->t->intvalto_float:t->floatendmoduleMake_number(M:sigvalinput_type:Number_input_type.tend)=structletnumber_input(typea)here?(extra_attrs=Value.return[])(moduleS:Number_input_specificationwithtypet=a)=letcompare_optab=matchbwith|Someb->S.compareab|None->0inlet(<)ab=compare_optab<0inlet(>)ab=compare_optab>0inletview~id~state~set_state=letmin=Option.mapS.min~f:(Fn.composeVdom.Attr.minS.to_float)inletmax=Option.mapS.max~f:(Fn.composeVdom.Attr.maxS.to_float)inlet%mapid=idandstate=stateandset_state=set_stateandextra_attrs=extra_attrsinletinput_widget=matchM.input_typewith|Number_input_type.Number->Vdom_input_widgets.Entry.number|Range->Vdom_input_widgets.Entry.rangeininput_widget(moduleS)~extra_attrs:(List.concat[[id];List.filter_map[min;max]~f:Fn.id;extra_attrs])~value:(Somestate)~step:(S.to_floatS.step)~on_input:(function|Somes->set_states|None->set_stateS.default)inlet%subnumber_input=Basic_stateful.make(Bonsai.statehere(moduleS)~default_model:S.default)~viewinreturn@@let%mapnumber_input=number_inputinForm.validatenumber_input~f:(funvalue->ifvalue<S.minthenOr_error.error_s[%message(value:S.t)"lower than allowed threshold"(S.min:S.toption)]elseifvalue>S.maxthenOr_error.error_s[%message(value:S.t)"higher than allowed threshold"(S.max:S.toption)]elseOk());;letinthere?extra_attrs?min:min_?max:max_~default~step()=letmoduleSpecification=structincludeIntletmin=min_letmax=max_letstep=stepletdefault=defaultendinnumber_input?extra_attrshere(moduleSpecification);;letfloathere?extra_attrs?min:min_?max:max_~default~step()=letmoduleSpecification=structincludeFloat(* We can't just use the default Stringable interface provided by Float, so we
overwrite it with a custom one. For more information, see
[Vdom_input_widgets.Entry.number]. *)includeVdom_input_widgets.Decimalletmin=min_letmax=max_letdefault=defaultletstep=stependinnumber_input?extra_attrshere(moduleSpecification);;endmoduleNumber=Make_number(structletinput_type=Number_input_type.Numberend)moduleRange=Make_number(structletinput_type=Number_input_type.Rangeend)moduleRadio_buttons=structletlist(typet)here?(extra_attrs=Value.return[])?to_string(moduleE:Bonsai.Modelwithtypet=t)~layoutall=letnode_fun=matchlayoutwith|`Vertical->Vdom_input_widgets.Radio_buttons.of_values|`Horizontal->Vdom_input_widgets.Radio_buttons.of_values_horizontalinletmoduleE=structincludeEletto_string(item:E.t)=matchto_stringwith|Somef->fitem|None->sexp_to_pretty_stringE.sexp_of_titem;;endinlet%subpath,_=pathinletview~id~state~set_state=let%mapid=idandpath=pathandstate=stateandset_state=set_stateandall=allandextra_attrs=extra_attrsinnode_fun~extra_attrs:(id::extra_attrs)(moduleE)~on_click:(funvalue->set_state(Somevalue))~selected:state~name:pathallinBasic_stateful.make(Bonsai.state_opthere(moduleE))~view|>computation_map~f:optional_to_required;;letenumerable(typet)here?extra_attrs?to_string(moduleE:Bonsai.Enumwithtypet=t)~layout=listhere?extra_attrs?to_string(moduleE)~layout(Value.returnE.all);;endmoduleColor_picker=structlethex?(extra_attr=Value.returnVdom.Attr.empty)here=letview~id~state~set_state=let%mapid_=idandstate=stateandset_state=set_stateandextra_attr=extra_attrinVdom_input_widgets.Entry.color_picker~extra_attr:Vdom.Attr.(id_@extra_attr)~value:state~on_input:set_state()inBasic_stateful.make(Bonsai.statehere~default_model:(`Hex"#000000")(modulestructtypet=[`Hexofstring][@@derivingequal,sexp]end))~view;;endmoduleFile_select=structmoduleFile=structtypet=Bonsai_web_ui_file.t[@@derivingsexp_of]letequal=phys_equallett_of_sexp=opaque_of_sexpendletsingle_opthere?(extra_attrs=Value.return[])?accept()=letview~id~state:_~set_state=let%mapid=idandset_state=set_stateandextra_attrs=extra_attrsinVdom_input_widgets.File_select.single?accept~extra_attrs:(id::extra_attrs)~on_input:(funfile->set_state(Option.mapfile~f:Bonsai_web_ui_file_from_web_file.create))()inBasic_stateful.make(Bonsai.state_opthere(moduleFile))~view;;letsinglehere?(extra_attrs=Value.return[])?accept()=Bonsai.Computation.map(single_opthere~extra_attrs?accept())~f:optional_to_required;;letmultiplehere?(extra_attrs=Value.return[])?accept()=letview~id~state:_~set_state=let%mapid=idandset_state=set_stateandextra_attrs=extra_attrsinVdom_input_widgets.File_select.list?accept~extra_attrs:(id::extra_attrs)~on_input:(funfiles->letfiles=List.mapfiles~f:(funfile->letfile=Bonsai_web_ui_file_from_web_file.createfileinBonsai_web_ui_file.filenamefile,file)|>Filename.Map.of_alist_exninset_statefiles)()inBasic_stateful.make(Bonsai.statehere(modulestructtypet=File.tFilename.Map.t[@@derivingequal,sexp]end)~default_model:Filename.Map.empty)~view;;endmoduleFreeform_multiselect=structletset?(extra_attrs=Value.return[])?placeholder?split_here=let%subpath,id=pathinletextra_attrs=let%mapid=idandextra_attrs=extra_attrsinid::extra_attrsinlet%subfreeform_multiselect=Bonsai_web_ui_freeform_multiselect.Freeform_multiselect.create?placeholder?split~extra_attrs()inreturn@@let%mapvalue,view,set=freeform_multiselectandpath=pathinForm.Expert.create~value:(Okvalue)~view:(View.of_vdom~id:pathview)~set;;letlist?extra_attrs?placeholder?splithere=computation_map(set?extra_attrs?placeholder?splithere)~f:(Form.project~parse_exn:Set.to_list~unparse:String.Set.of_list);;endmoduleRank=structletlistkey?enable_debug_overlay?extra_item_attrs?left?right?empty_list_placeholder?default_item_heightrender=let%subpath=Bonsai.Private.pathinlet%map.Computationvalue,view,inject=Bonsai_web_ui_reorderable_list.with_injectkey?enable_debug_overlay?extra_item_attrs?left?right?empty_list_placeholder?default_item_height(fun~index:_~sourcekey->let%map.Computationview=render~sourcekeyin(),view)andpath=return(path>>|Bonsai.Private.Path.to_unique_identifier_string)inForm.Expert.create~value:(Ok(List.map~f:fstvalue))~view:(View.of_vdom~id:pathview)~set:(funitems->inject[Overwriteitems]);;endmoduleQuery_box=structletstringable_opt(typekcmp)(moduleKey:Bonsai.Comparatorwithtypet=kandtypecomparator_witness=cmp)?initial_query?max_visible_items?suggestion_list_kind?selected_item_attr?extra_list_container_attr?extra_input_attr?(extra_attr=Value.returnVdom.Attr.empty)?to_viewinput=let%subpath,id=pathinlet%subextra_attr=let%arrextra_attr=extra_attrandid=idinVdom.Attr.combineidextra_attrinlet%sublast_selected_value,set_last_selected_value=Bonsai.state_opt[%here](modulestructtypet=Key.t[@@derivingsexp]letequalab=Key.comparator.compareab=0end)inlet%subview=Bonsai_web_ui_query_box.stringable(moduleKey)?initial_query?max_visible_items?suggestion_list_kind?selected_item_attr?extra_list_container_attr?extra_input_attr~extra_attr?to_view~on_select:(let%mapset_last_selected_value=set_last_selected_valueinfunkey->set_last_selected_value(Somekey))inputinlet%subcurrent_selection_view=let%arrlast_selected_value=last_selected_valueandinput=inputinmatchlast_selected_valuewith|Somevalue->(matchMap.findinputvaluewith|Somestring->Vdom.Node.div[Vdom.Node.textstring]|None->Vdom.Node.div~attr:(Vdom.Attr.style(Css_gen.color(`Name"red")))[Vdom.Node.text"Selected item is not an input option"])|None->Vdom.Node.div~attr:(Vdom.Attr.style(Css_gen.color(`Name"gray")))[Vdom.Node.text"Nothing selected"]inlet%arrlast_selected_value=last_selected_valueandset_last_selected_value=set_last_selected_valueandview=viewandcurrent_selection_view=current_selection_viewandpath=pathinletview=Vdom.Node.div[current_selection_view;view]inForm.Expert.create~value:(Oklast_selected_value)~view:(View.of_vdom~id:pathview)~set:set_last_selected_value;;letstringablekey?initial_query?max_visible_items?suggestion_list_kind?selected_item_attr?extra_list_container_attr?extra_input_attr?extra_attr?to_viewinput=Computation.map(stringable_optkey?initial_query?max_visible_items?suggestion_list_kind?selected_item_attr?extra_list_container_attr?extra_input_attr?extra_attr?to_viewinput)~f:optional_to_required;;end