Legend:
Page
Library
Module
Module type
Parameter
Class
Class type
Source
Source file view.ml
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423open!CoreopenBonsai_webmoduleAttr=Vdom.AttrmoduleNode=Vdom.NodemoduleStyle=[%css.raw{|
.clear_fieldset_styles {
border: 0;
margin: 0;
padding: 0;
}
|}]moduleError_details=structtypet={error:Error.t;on_mouse_over:(unitUi_effect.t[@sexp.opaque]);on_mouse_out:(unitUi_effect.t[@sexp.opaque]);on_click:(unitUi_effect.t[@sexp.opaque]);is_viewing:bool;is_toggled:bool}[@@derivingsexp_of]endmoduleRow=structtypet={label:(Node.toption[@sexp.opaque]);tooltip:(Node.toption[@sexp.opaque]);form:(Node.t[@sexp.opaque]);id:string;error:Error_details.toption}[@@derivingsexp_of]endtypet=|Empty|RowofRow.t|Listoftlist|Groupof{label:(Node.toption[@sexp.opaque]);tooltip:(Node.toption[@sexp.opaque]);view:t}|Header_groupof{label:(Node.toption[@sexp.opaque]);tooltip:(Node.toption[@sexp.opaque]);header_view:t;view:t}|Submit_buttonof{text:string;(* none implies that the button is disabled *)on_submit:(unitUi_effect.toption[@sexp.opaque])}[@@derivingsexp_of]letsuggest_errore1=function|Rowrow->leterror=matchrow.errorwith(* Keep the inner error if one exists *)|Somee2->Somee2|None->Somee1inRow{rowwitherror}|other->other;;letrecset_labellabelt=matchtwith|Empty->Empty|List[]->List[](* [set_label] on a list will traverse the list and attach it to the head *)|List(hd::tl)->List(set_labellabelhd::tl)|Rowrow->Row{rowwithlabel=Somelabel}|Groupgroup->Group{groupwithlabel=Somelabel}|Header_groupgroup->Header_group{groupwithlabel=Somelabel}|Submit_button_->t;;letrecset_tooltiptooltipt=matchtwith|Empty->Empty|List[]->List[](* [set_tooltip] on a list will traverse the list and attach it to the head *)|List(hd::tl)->List(set_tooltiptooltiphd::tl)|Rowrow->Row{rowwithtooltip=Sometooltip}|Groupgroup->Group{groupwithtooltip=Sometooltip}|Header_groupgroup->Header_group{groupwithtooltip=Sometooltip}|Submit_button_->t;;letgroup_listt=matchtwith|List_->Group{view=t;label=None;tooltip=None}|_->t;;letrecsuggest_labellabelt=matchtwith|Empty->Empty|List[]->List[](* [suggest_label] on a list will traverse the list and attach it to the head *)|List(hd::tl)->List(suggest_labellabelhd::tl)(* If it already has a label, keep it *)|Row{label=Some_;_}|Group{label=Some_;_}|Header_group{label=Some_;_}->t|Row({label=None;_}asrow)->Row{rowwithlabel=Somelabel}|Group({label=None;_}asgroup)->Group{groupwithlabel=Somelabel}|Header_group({label=None;_}asgroup)->Header_group{groupwithlabel=Somelabel}|Submit_button_->t;;letgrouplabelview=Group{label=Somelabel;tooltip=None;view}letof_vdom~idform=Row{label=None;tooltip=None;form;id;error=None}letconcatab=matcha,bwith|Empty,x|x,Empty->x|Lista,Listb->List(a@b)|a,Listb->List(a::b)|Lista,b->List(List.appenda[b])|a,b->List[a;b];;letrecview_error(e:Error.Internal_repr.t):Node.tlist=letboldtext=Node.strong[Node.texttext]inletpretext=Node.pre[Node.texttext]inletview_sexpsexp=sexp|>Sexp.to_string_hum|>preinletdivcontents=Node.div[contents]inmatchewith|Could_not_constructsexp->[div(bold"could not construct");div(view_sexpsexp)]|Strings->[div(Node.texts)]|Exne->[div(pre(Exn.to_stringe))]|Sexps->[div(view_sexps)]|Tag_sexp(string,sexp,Somethere)->[div(boldstring);div(view_sexpsexp);div(pre(Source_code_position.to_stringthere))]|Tag_sexp(string,sexp,None)->[div(boldstring);div(view_sexpsexp)]|Tag_t(string,error)->div(boldstring)::view_errorerror|Tag_arg(string,sexp,error)->div(boldstring)::div(view_sexpsexp)::view_errorerror|Of_list(Sometruncate_after,errors)->errors|>Fn.flipList.taketruncate_after|>List.bind~f:view_error|Of_list(None,errors)->errors|>List.bind~f:view_error|With_backtrace(error,backtrace)->List.append(view_errorerror)[div(prebacktrace)];;letview_errorerror=view_error(Error.Internal_repr.of_infoerror)letview_error_details{Error_details.error;on_mouse_over;on_mouse_out;on_click;is_viewing;is_toggled}=letflag=Node.div~attr:(Attr.many_without_merge[Attr.on_mouseover(fun_->on_mouse_over);Attr.on_mouseout(fun_->on_mouse_out);Attr.on_click(fun_->on_click);Attr.styleCss_gen.((ifis_toggledthencolor(`Name"black")elsecolor(`Hex"#f54646"))@>font_size(`Em_float1.2)@>Css_gen.create~field:"cursor"~value:"pointer")])[Node.text"⚠"]inletcontents=ifnotis_viewingthenNode.noneelseNode.div~attr:(Attr.styleCss_gen.((ifis_toggledthenborder~width:(`Px1)~color:(`Name"black")~style:`Solid()elseborder~width:(`Px1)~color:(`Name"red")~style:`Solid())@>position~top:(`Px0)~left:(`Em2)`Absolute@>padding~left:(`Em1)~right:(`Em1)()@>border_radius(`Px3)@>background_color(`Name"pink")))(view_errorerror)inletresult=Node.div~attr:(Attr.many_without_merge[Attr.style(Css_gen.position`Relative);Attr.class_"bonsai-forms-error"])[flag;contents]inresult;;moduleTooltip=structmoduleCss=[%css.raw{|
.container {
position: relative;
display: inline-block;
}
.label {
cursor: pointer;
color: blue;
}
.text {
visibility: hidden;
width: 300px;
background-color: azure;
color: black;
text-align: center;
border-radius: 3px;
padding: 0.5em 1em 0.5em 1em;
border: 1px solid darkblue;
position: absolute;
z-index: 1;
bottom: 100%;
left: 50%;
margin-left: -150px;
cursor: text;
}
.container:hover .text {
visibility: visible;
}
.checkbox:checked ~ .text {
visibility: visible;
}
.checkbox:checked ~ .span {
color: black;
}
.checkbox {
position: absolute;
opacity: 0%;
cursor: pointer;
}
|}]letviewinner=Node.div~attr:(Attr.class_Css.container)[Node.label~attr:(Attr.class_Css.label)[Node.input~attr:Attr.(type_"checkbox"@class_Css.checkbox)[];Node.span~attr:(Attr.class_Css.span)[Node.text"ⓘ"];Node.div~attr:(Attr.class_Css.text)[inner]]];;endletrecto_vdom~depth=letdepth_td~extra_attrs=letattr=Attr.(style(Css_gen.padding_left(`Emdepth))@extra_attrs)inNode.td~attrinfunction|Empty->[]|Group{label;tooltip;view}->letrest=to_vdomview~depth:(depth+1)inletheader_is_inhabited=Option.is_somelabel||Option.is_sometooltipinifheader_is_inhabitedthen(letlabel=matchlabelwith|Somelabel->depth_td~extra_attrs:Attr.(style(Css_gen.font_weight`Bold)@colspan2)[label]|None->Node.Noneinlettooltip=matchtooltipwith|Sometooltip->Node.td[Tooltip.viewtooltip]|None->Node.NoneinNode.tr[label;tooltip]::rest)elserest|Header_group{label;tooltip;view;header_view}->letrest=to_vdomview~depth:(depth+1)inletheader_view=letcolspan=ifOption.is_somelabelthenAttr.emptyelseAttr.colspan2inNode.td~attr:colspan(to_vdom_plainheader_view)inletlabel=matchlabelwith|Somelabel->depth_td~extra_attrs:(Attr.style(Css_gen.font_weight`Bold))[label]|None->Node.Noneinlettooltip=matchtooltipwith|Sometooltip->Node.td[Tooltip.viewtooltip]|None->Node.NoneinNode.tr[label;header_view;tooltip]::rest|Submit_button_asbtn->letbutton=to_vdom_plainbtnin[Node.tr[depth_td~extra_attrs:Attr.(colspan2)button]]|Row{label;tooltip;id;form;error}->letlabel=matchlabelwith|Somelabel->(* <label> nodes can be clicked on to focus the input element contained
inside. By setting display:block, even the whitespace to the right
of the label is clickable, meaning that mis-clicking on particularly
small labels is less likely. *)Node.label~attr:(Attr.many_without_merge[Attr.for_id;Attr.style(Css_gen.display`Block)])[label]|_->Node.text""inlettooltip=matchtooltipwith|Sometooltip->Tooltip.viewtooltip|None->Node.text""inleterror=matcherrorwith|None->Node.text""|Somee->view_error_detailseinletlabel_attrs=Attr.styleCss_gen.(padding_right(`Em1)@>text_align`Left@>font_weight`Bold@>user_select`None)in[(* This key prevents inputs of different "kinds" from clobbering each other *)Node.tr~key:id[depth_td~extra_attrs:label_attrs[label];Node.td[form];Node.td[Node.div~attr:(Attr.style(Css_gen.flex_container~direction:`Row()))[tooltip;error]]]]|Listl->List.concat_mapl~f:(to_vdom~depth)(* If the form is just a single row, return the view for it without wrapping *)andto_vdom_plain=function|Empty->[]|Header_group{label=_;tooltip=_;header_view;view}->to_vdom_plainheader_view@to_vdom_plainview|Group{label=_;tooltip=_;view}->to_vdom_plainview|Row{label=_;tooltip=_;id=_;form;error=_}->[form]|Listl->List.concat_mapl~f:to_vdom_plain|Submit_button{on_submit;text}->(matchon_submitwith|Someevent->letevent=Vdom.Effect.(Many[event;Prevent_default;Stop_propagation])in[Node.button~attr:(Attr.on_click(fun_->event))[Vdom.Node.texttext]]|None->[Node.button~attr:Attr.disabled[Vdom.Node.texttext]]);;typesubmission_options={on_submit:unitUi_effect.toption;handle_enter:bool;button_text:stringoption}typeeditable=[`Yes_always|`Currently_yes|`Currently_no]letwith_fieldset~currently_editableview=letdisabled_=ifcurrently_editablethenVdom.Attr.emptyelseVdom.Attr.disabledinVdom.Node.fieldset~attr:Vdom.Attr.(disabled_@class_Style.clear_fieldset_styles)[view];;letto_vdom?on_submit?(editable=`Yes_always)view=letview=matchon_submitwith|Some{on_submit;button_text=Somebutton_text;handle_enter=_}->letbutton=Submit_button{text=button_text;on_submit}inconcatviewbutton|_->viewinletinner_table=Node.table[Node.tbody(to_vdomview~depth:0)]inletinner_table=matcheditablewith|`Yes_always->inner_table|`Currently_yes->with_fieldset~currently_editable:trueinner_table|`Currently_no->with_fieldset~currently_editable:falseinner_tableinmatchon_submitwith|Some{on_submit;handle_enter=true;_}->letalways_use=[Vdom.Effect.Prevent_default;Vdom.Effect.Stop_propagation]inletevent=matchon_submitwith|None->Vdom.Effect.Manyalways_use|Someevent->Vdom.Effect.Many(event::always_use)inNode.create"form"~attr:(Vdom.Attr.on_submit(fun_->event))[inner_table]|_->inner_table;;