123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669(*********************************************************************************)(* Ojs-base *)(* *)(* Copyright (C) 2014-2021 INRIA. All rights reserved. *)(* *)(* This program is free software; you can redistribute it and/or modify *)(* it under the terms of the GNU General Public License as *)(* published by the Free Software Foundation, version 3 of the License. *)(* *)(* This program is distributed in the hope that it will be useful, *)(* but WITHOUT ANY WARRANTY; without even the implied warranty of *)(* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *)(* GNU Library General Public License for more details. *)(* *)(* You should have received a copy of the GNU General Public *)(* License along with this program; if not, write to the Free Software *)(* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA *)(* 02111-1307 USA *)(* *)(* As a special exception, you have permission to link this program *)(* with the OCaml compiler and distribute executables, as long as you *)(* follow the requirements of the GNU GPL in regard to all of the *)(* software in the executable aside from the OCaml compiler. *)(* *)(* Contact: Maxence.Guesdon@inria.fr *)(* *)(*********************************************************************************)(** *)openJs_of_ocamlopenOjs_jsopenOjs_filetree.Typeslet(>>=)=Lwt.(>>=)letlog=Ojs_js.logtypenode_type=[`File|`Dir]typetree_node={tn_id:id;mutabletn_basename:string;mutabletn_path:Ojs_base.Path.t;tn_span_id:id;tn_subs_id:idoption;tn_type:node_type;mutabletn_subs:tree_nodelist;}lettree_nodes=ref(SMap.empty:tree_nodeSMap.t)letbutton_bar_class=Ojs_js.class_"button-bar"letbutton_class=Ojs_js.class_"button"letcollapsed_class="collapsed"letbutton_barbase_id=letdoc=Dom_html.documentinletid=base_id^"-button-bar"inletdiv=doc##createElement(Js.string"div")indiv##setAttribute(Js.string"id")(Js.stringid);div##.className:=Js.stringbutton_bar_class;divletadd_buttonid?clstextbar=letdoc=Dom_html.documentinletspan=doc##createElement(Js.string"span")inspan##setAttribute(Js.string"id")(Js.stringid);span##.className:=Js.stringbutton_class;(matchclswithNone->()|Somec->Ojs_js.node_set_classspanc);lett=doc##createTextNode(Js.stringtext)inDom.appendChildspant;Dom.appendChildbarspan;spanletadd_button_add_dirbase_idbar=letid=base_id^"-add-dir"inletcls=button_class^"-add-dir"inletspan=add_buttonid~cls"+dir"barinspanletadd_button_deletebase_idbar=letid=base_id^"-delete"inletcls=button_class^"-delete"inletspan=add_buttonid~cls"✘"barinspanletdrag_class=Ojs_js.class_"drag"letpreventDefaultevt=ignore(Js.Unsafe.meth_callevt"preventDefault"[||])letstopPropagationevt=ignore(Js.Unsafe.meth_callevt"stopPropagation"[||])letexpand_buttons?(start=`Collapsed)base_idsubssubs_id=letdoc=Dom_html.documentinletid_exp=base_id^"expand"inletid_col=base_id^"collapse"inletspan_exp=doc##createElement(Js.string"span")inspan_exp##setAttribute(Js.string"id")(Js.stringid_exp);letspan_col=doc##createElement(Js.string"span")inspan_col##setAttribute(Js.string"id")(Js.stringid_col);(matchstartwith|`Expand->span_exp##.className:=Js.stringcollapsed_class;Ojs_js.node_unset_classsubscollapsed_class|`Collapsed->span_col##.className:=Js.stringcollapsed_class;Ojs_js.node_set_classsubscollapsed_class);lett_exp=doc##createTextNode(Js.string" ▶")inlett_col=doc##createTextNode(Js.string" ▼")inDom.appendChildspan_expt_exp;Dom.appendChildspan_colt_col;Ojs_js.set_onclickspan_exp(fune->Ojs_js.set_class~id:id_expcollapsed_class;Ojs_js.unset_class~id:id_colcollapsed_class;Ojs_js.unset_class~id:subs_idcollapsed_class);Ojs_js.set_onclickspan_col(fune->Ojs_js.set_class~id:id_colcollapsed_class;Ojs_js.unset_class~id:id_expcollapsed_class;Ojs_js.set_class~id:subs_idcollapsed_class);(span_exp,span_col)moduletypeS=sigmoduleP:Ojs_filetree.Types.Pclasstree:(P.client_msg->(P.server_msg->unitLwt.t)->unitLwt.t)->(P.client_msg->unitLwt.t)->msg_id:string->Ojs_js.SMap.key->object('a)valmutablefiletree:tree_nodelistvalmutableon_deselect:'a->Ojs_filetree.Types.path->unitvalmutableon_select:'a->[`Dir|`FileofOjs_filetree.Types.mime_type]->Ojs_filetree.Types.path->unitvalmutableselected:(Ojs_js.id*Ojs_filetree.Types.path)optionvalmutableshow_files:boolmethodadd_dir:Ojs_filetree.Types.path->string->unitLwt.tmethodadd_file:[`Dir|`File]->Ojs_filetree.Types.path->File.fileJs.t->unitmethodbuild_from_tree:Ojs_filetree.Types.file_treelist->unitmethodcompare_tn:tree_node->tree_node->intmethoddelete:Ojs_filetree.Types.path->unitLwt.tmethoddisplay_error:string->unitmethodhandle_add_dir:Ojs_filetree.Types.path->unitmethodhandle_add_file:Ojs_filetree.Types.path->Ojs_filetree.Types.mime_type->unitmethodhandle_delete:Ojs_filetree.Types.path->unitmethodhandle_drag_drop:[`Dir|`File]->Ojs_filetree.Types.path->Dom_html.elementJs.t->unitmethodhandle_message:P.server_msg->boolJs.tmethodid:Ojs_js.SMap.keymethodinsert_dir:Ojs_filetree.Types.path->unitmethodinsert_file:Ojs_filetree.Types.path->Ojs_filetree.Types.mime_type->unitmethodinsert_tn:Ojs_js.id->tree_node->Dom.nodeJs.t->tree_nodelist->tree_nodelistmethodmsg_id:stringmethodon_deselect:'a->Ojs_filetree.Types.path->unitmethodon_select:'a->[`Dir|`FileofOjs_filetree.Types.mime_type]->Ojs_filetree.Types.path->unitmethodprompt_add_dir:Ojs_filetree.Types.path->unitLwt.tmethodprompt_delete:Ojs_filetree.Types.path->unitLwt.tmethodset_on_deselect:('a->Ojs_filetree.Types.path->unit)->unitmethodset_on_select:('a->[`Dir|`FileofOjs_filetree.Types.mime_type]->Ojs_filetree.Types.path->unit)->unitmethodset_onclick:Dom_html.elementJs.t->Ojs_js.SMap.key->[`Dir|`FileofOjs_filetree.Types.mime_type]->Ojs_filetree.Types.path->unitmethodset_selected:Ojs_js.SMap.key->[`Dir|`FileofOjs_filetree.Types.mime_type]->Ojs_filetree.Types.path->unitmethodset_show_files:bool->unitmethodset_unselected:Ojs_js.SMap.key->Ojs_filetree.Types.path->unitmethodsimple_call:P.client_msg->unitLwt.tmethodtree_node_by_path:Ojs_filetree.Types.path->tree_nodemethodupdate_tree:unitLwt.tendclasstrees:(P.app_client_msg->(P.app_server_msg->unitLwt.t)->unitLwt.t)->(P.app_client_msg->unitLwt.t)->((P.client_msg->(P.server_msg->unitLwt.t)->unitLwt.t)->(P.client_msg->unitLwt.t)->msg_id:string->string->tree)->objectvalmutabletrees:treeOjs_js.SMap.tmethodget_msg_id:Ojs_js.SMap.key->stringmethodget_tree:Ojs_js.SMap.key->treemethodhandle_message:P.app_server_msg->boolJs.tmethodsetup_filetree:msg_id:string->Ojs_js.SMap.key->treeendendmoduleMake(P:Ojs_filetree.Types.P)=structmoduleP=Pclasstreecall(send:P.client_msg->unitLwt.t)~msg_idid=object(self:'self)valmutableselected=(None:(id*Ojs_base.Path.t)option)valmutablefiletree=([]:tree_nodelist)valmutableon_select:'self->[`Dir|`Fileofstring]->Ojs_base.Path.t->unit=fun___->()valmutableon_deselect:'self->Ojs_base.Path.t->unit=fun__->()valmutableshow_files=truemethodset_on_selectf=on_select<-fmethodset_on_deselectf=on_deselect<-fmethodset_show_filesb=show_files<-bmethodon_select=on_selectmethodon_deselect=on_deselectmethodid:string=idmethodmsg_id:string=msg_idmethoddisplay_errormsg=Ojs_js.display_text_errormsg_idmsgmethodsimple_call:P.client_msg->unitLwt.t=funmsg->callmsg(funmsg->Lwt.return(matchmsgwith|P.SErrormsg->self#display_errormsg|_->()))methodset_unselecteddiv_idpath=(tryletspan_id=(SMap.finddiv_id!tree_nodes).tn_span_idinOjs_js.unset_class~id:span_id"selected";withNot_found->());selected<-None;self#on_deselectselfpathmethodset_selecteddiv_idkindpath=(tryletspan_id=(SMap.finddiv_id!tree_nodes).tn_span_idinOjs_js.set_class~id:span_id"selected";withNot_found->());selected<-Some(div_id,path);self#on_selectselfkindpathmethodset_onclick(node:Dom_html.elementJs.t)div_idkindfname=letf_=matchselectedwith|None->self#set_selecteddiv_idkindfname|Some(old_id,l)whenid<>div_id->self#set_unselectedold_idl;self#set_selecteddiv_idkindfname|_->()inset_onclicknodefmethodadd_filekindpath(file:File.fileJs.t)=letdir=matchkindwith`Dir->path|`File->Ojs_base.Path.parentpathinletpath=Ojs_base.Path.appenddir[Js.to_stringfile##.name]inlet(size:int)=file##.sizeinlet(blob:File.blobJs.t)=Js.Unsafe.meth_callfile"slice"[|Js.Unsafe.inject0;Js.Unsafe.injectsize|]inleton_successcontents=letcontents=Js.to_stringcontentsinletlen=String.lengthcontentsin(* the base64 data is after the first comma, see
http://css-tricks.com/data-uris/
*)letp=tryString.indexcontents','with_->failwith"No Base64"inignore(self#simple_call(P.Add_file(path,String.subcontents(p+1)(len-p-1))))inleton_errorexn=log(Printf.sprintf"Reading file: %s"(Printexc.to_stringexn))in(* read in base 64 *)letreadblob=letreader=new%jsFile.fileReaderinletres=reader##.resultinJs.Opt.case(File.CoerceTo.stringres)(fun()->Lwt.return(Js.string""))(funs->Lwt.returns)inLwt.on_any(readblob)on_successon_errormethodadd_dirpathname=letpath=Ojs_base.Path.appendpath[name]inself#simple_call(P.Add_dirpath)methodprompt_add_dirpath=letanswer=Dom_html.window##prompt(Js.string"Create directory")(Js.string"")inJs.Opt.caseanswer(fun()->Lwt.return_unit)(funname->self#add_dirpath(Js.to_stringname))methoddeletepath=self#simple_call(P.Deletepath)methodprompt_deletepath=letmsg=Printf.sprintf"Delete %S ?"(Ojs_base.Path.to_stringpath)inifJs.to_bool(Dom_html.window##confirm(Js.stringmsg))thenself#deletepathelseLwt.return_unitmethodhandle_drag_dropkindfnamenode=leton_dragoverevt=stopPropagationevt;preventDefaultevt;evt##.dataTransfer##.dropEffect:=Js.string"copy";Ojs_js.node_set_classnodedrag_class;Js.booltrueinleton_dragleaveevt=Ojs_js.node_unset_classnodedrag_class;Js.booltrueinleton_dropevt=stopPropagationevt;preventDefaultevt;Ojs_js.node_unset_classnodedrag_class;letfiles=evt##.dataTransfer##.filesinletlen=files##.lengthinfori=0tolen-1doJs.Opt.case(files##item(i))(fun()->())(funfile->self#add_filekindfnamefile)done;Js.booltrueinignore(Dom_html.addEventListenernodeDom_html.Event.dragover(Dom.handleron_dragover)(Js.booltrue));ignore(Dom_html.addEventListenernodeDom_html.Event.dragleave(Dom.handleron_dragleave)(Js.booltrue));ignore(Dom_html.addEventListenernodeDom_html.Event.drop(Dom.handleron_drop)(Js.booltrue))methodtree_node_by_pathpath=letrecitertreespath=matchtrees,pathwith[],_|_,[]->None|tn::q,[name]whentn.tn_basename=name->Sometn|tn::q,name::qpathwhentn.tn_basename=name->itertn.tn_subsqpath|_::q,_->iterqpathinmatchiterfiletree(Ojs_base.Path.pathpath)withNone->(*log (Printf.sprintf "no tree_node for path %s" (Ojs_base.Path.to_string path));*)raiseNot_found|Sometn->tnmethodcompare_tntn1tn2=matchtn1.tn_type,tn2.tn_typewith`Dir,`Dir|`File,`File->Stdlib.comparetn1.tn_pathtn2.tn_path|`Dir,_->-1|`File,_->1methodinsert_tnparent_idtnnodel=letparent_node=Ojs_js.node_by_idparent_idinletinsertpos=letchildren=parent_node##.childNodesinletchild=children##itemposinignore(parent_node##insertBeforenodechild)inletdeletepos=letchildren=parent_node##.childNodesinletnth_child=children##itemposinJs.Opt.casenth_child(fun()->())(funchild->ignore(parent_node##removeChildchild));inletreciterposacc=function[]->insertpos;List.rev(tn::acc)|tn2::q->matchself#compare_tntntn2with0->(* replace old tn2 by new tn *)deletepos;insertpos;(List.rev(tn::acc))@q|nwhenn>0->iter(pos+1)(tn2::acc)q|_->insertpos;(List.rev(tn::tn2::acc))@qiniter0[]lmethodinsert_filepathmime=letparent=Ojs_base.Path.parentpathinletbasename=Ojs_base.Path.basenamepathinifshow_filesthenbeginmatchself#tree_node_by_pathpathwithtn->()|exceptionNot_found->letdoc=Dom_html.documentinletdiv=doc##createElement(Js.string"div")inletdiv_id=Ojs_js.gen_id()indiv##setAttribute(Js.string"id")(Js.stringdiv_id);div##setAttribute(Js.string"class")(Js.string"ojsft-file");lethead=doc##createElement(Js.string"div")inlethead_id=div_id^"-head"inhead##setAttribute(Js.string"id")(Js.stringhead_id);head##setAttribute(Js.string"class")(Js.string"ojsft-file-head");letspan_id=div_id^"text"inletspan=doc##createElement(Js.stringspan_id)inspan##setAttribute(Js.string"id")(Js.string(div_id^"text"));self#set_onclickspandiv_id(`Filemime)path;lettn={tn_id=div_id;tn_span_id=span_id;tn_basename=basename;tn_path=path;tn_type=`File;tn_subs=[];tn_subs_id=None;}intree_nodes+=(div_id,tn);let(parent_id,items,update_items)=trylettn=self#tree_node_by_pathparentinmatchtn.tn_subs_idwithNone->raiseNot_found|Someid->(id,tn.tn_subs,(funl->tn.tn_subs<-l))withNot_found->(id,filetree,(funl->filetree<-l))inletitems=self#insert_tnparent_idtn(div:>Dom.nodeJs.t)itemsinupdate_itemsitems;lettext=doc##createTextNode(Js.stringbasename)inletbbar=button_bardiv_idinletbtn_delete=add_button_deletediv_idbbarinOjs_js.set_onclickbtn_delete(fun_->self#prompt_deletepath);Dom.appendChilddivhead;Dom.appendChildheadspan;Dom.appendChildspantext;Dom.appendChildheadbbar;self#handle_drag_drop`Filepathdiv;endmethodinsert_dirpath=letparent=Ojs_base.Path.parentpathinletbasename=Ojs_base.Path.basenamepathinmatchself#tree_node_by_pathpathwithtn->()|exceptionNot_found->letdoc=Dom_html.documentinletdiv=doc##createElement(Js.string"div")inletdiv_id=Ojs_js.gen_id()indiv##setAttribute(Js.string"id")(Js.stringdiv_id);div##setAttribute(Js.string"class")(Js.string"ojsft-dir");lethead=doc##createElement(Js.string"div")inlethead_id=div_id^"-head"inhead##setAttribute(Js.string"id")(Js.stringhead_id);head##setAttribute(Js.string"class")(Js.string"ojsft-file-head");letspan_id=div_id^"text"inletspan=doc##createElement(Js.string"span")inspan##setAttribute(Js.string"id")(Js.stringspan_id);self#set_onclickspandiv_id`Dirpath;letsubs_id=div_id^"subs"inletdiv_subs=doc##createElement(Js.string"div")indiv_subs##setAttribute(Js.string"id")(Js.stringsubs_id);div_subs##setAttribute(Js.string"class")(Js.string"ojsft-dir-subs");lettext=doc##createTextNode(Js.stringbasename)inlettn={tn_id=div_id;tn_span_id=span_id;tn_basename=basename;tn_path=path;tn_type=`Dir;tn_subs=[];tn_subs_id=Somesubs_id;}intree_nodes+=(div_id,tn);let(parent_id,items,update_items)=trylettn=self#tree_node_by_pathparentinmatchtn.tn_subs_idwithNone->raiseNot_found|Someid->(id,tn.tn_subs,(funl->tn.tn_subs<-l))withNot_found->(id,filetree,(funl->filetree<-l))inletitems=self#insert_tnparent_idtn(div:>Dom.nodeJs.t)itemsinupdate_itemsitems;let(span_exp,span_col)=expand_buttonsdiv_iddiv_subssubs_idinletbbar=button_bardiv_idinletbtn_add_dir=add_button_add_dirdiv_idbbarinletbtn_delete=add_button_deletediv_idbbarinOjs_js.set_onclickbtn_add_dir(fun_->self#prompt_add_dirpath);Ojs_js.set_onclickbtn_delete(fun_->self#prompt_deletepath);Dom.appendChilddivhead;Dom.appendChildheadspan;Dom.appendChildspantext;Dom.appendChildheadspan_exp;Dom.appendChildheadspan_col;Dom.appendChildheadbbar;Dom.appendChilddivdiv_subs;self#handle_drag_drop`Dirpathheadmethodbuild_from_tree(tree_files:Ojs_filetree.Types.file_treelist)=letnode=Ojs_js.node_by_ididinOjs_js.clear_childrennode;letrecinsertpath=function`Dir(s,l)->letpath=Ojs_base.Path.appendpath[s]inself#insert_dirpath;List.iter(insertpath)l|`File(s,mime)->letpath=Ojs_base.Path.appendpath[s]inself#insert_filepathmimeinList.iter(insertOjs_base.Path.empty)tree_filesmethodhandle_add_filepath=self#insert_filepathmethodhandle_add_dirpath=self#insert_dirpathmethodhandle_deletepath=matchself#tree_node_by_pathpathwith|exceptionNot_found->log("handle_delete: path not found: "^(Ojs_base.Path.to_stringpath))|tn->tree_nodes:=SMap.removetn.tn_id!tree_nodes;(matchOjs_js.node_by_idtn.tn_idwith|exception_->()|node->Js.Opt.iternode##.parentNode(funp->ignore(p##removeChild((node:>Dom.nodeJs.t)))););letfilter=List.filter(funtn2->tn2.tn_id<>tn.tn_id)inmatchself#tree_node_by_path(Ojs_base.Path.parentpath)with|exceptionNot_found->filetree<-filterfiletree|parent_tn->parent_tn.tn_subs<-filterparent_tn.tn_subsmethodhandle_message(msg:'srv)=try(matchmsgwith|P.STreel->self#build_from_treel|P.SAdd_file(path,mime)->self#handle_add_filepathmime|P.SAdd_dirpath->self#handle_add_dirpath|P.SErrormsg->self#display_errormsg|P.SDeletepath->self#handle_deletepath|P.SOk->()|_->failwith"Unhandled message received from server");Js._falsewithe->log(Printexc.to_stringe);Js._falsemethodupdate_tree:unitLwt.t=callP.Get_tree(functionP.STreel->self#build_from_treel;Lwt.return_unit|_->Lwt.return_unit)initializerignore(self#update_tree)endclasstrees(call:P.app_client_msg->(P.app_server_msg->unitLwt.t)->unitLwt.t)(send:P.app_client_msg->unitLwt.t)(spawn:(P.client_msg->(P.server_msg->unitLwt.t)->unitLwt.t)->(P.client_msg->unitLwt.t)->msg_id:string->string->tree)=object(self)valmutabletrees=(SMap.empty:treeSMap.t)methodget_treeid=trySMap.findidtreeswithNot_found->failwith("No tree "^id)methodget_msg_idid=(self#get_treeid)#msg_idmethodsetup_filetree~msg_idid=letsendmsg=send(P.pack_client_msgidmsg)inletcallmsgcb=letcbmsg=matchP.unpack_server_msgmsgwith|Some(_,msg)->cbmsg|None->Lwt.return_unitincall(P.pack_client_msgidmsg)cbinlettree=spawncallsend~msg_ididintrees<-SMap.addidtreetrees;treemethodhandle_message(msg:P.app_server_msg)=matchP.unpack_server_msgmsgwith|Some(id,msg)->lettree=self#get_treeidintree#handle_messagemsg|None->Js._falseendend