123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347(*********************************************************************************)(* 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_jslet(>>=)=Lwt.(>>=)typemime_type=stringtypesession={sess_file:Ojs_base.Path.t;sess_mime:mime_type;sess_ace:Ojs_ace.editSessionJs.t;mutablesess_changed:bool;}modulePMap=Ojs_base.Path.Mapletmk_buttonlabel=letdoc=Dom_html.documentinletb=doc##createElement(Js.string"button")inlettext=doc##createTextNode(Js.stringlabel)inDom.appendChildbtext;bletis_editable_from_mime=lettext="text/"inletlen_text=String.lengthtextinfunction|"application/octet-stream"->true|mime->String.lengthmime>=len_text&&String.submime0len_text=textmoduletypeS=sigmoduleP:Ojs_ed.Types.Pclasseditor:(P.client_msg->(P.server_msg->unitLwt.t)->unitLwt.t)->(P.client_msg->unitLwt.t)->bar_id:string->msg_id:string->string->objectvalmutablecurrent:sessionoptionvalmutablesessions:sessionPMap.tmethodchanged_files:PMap.keylistmethodchanged_sessions:sessionlistmethoddisplay_error:string->unitmethoddisplay_filename:session->unitmethoddisplay_message:string->unitmethodedit_file:?mime:mime_type->PMap.key->unitLwt.tmethodget_session:PMap.key->sessionoptionmethodhandle_message:P.server_msg->boolJs.tmethodid:stringmethodis_editable_from_mime:mime_type->boolmethodload_from_server:session->unitLwt.tmethodmsg_id:stringmethodnew_session:?mime:mime_type->PMap.key->sessionmethodon_changed:session->unitmethodreload:unitLwt.tmethodreload_file:session->unitLwt.tmethodsave:unitLwt.tmethodsave_changed_files:unitLwt.tmethodsave_file:session->unitLwt.tmethodsimple_call:?on_ok:(unit->unit)->P.client_msg->unitLwt.tendclasseditors:(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)->bar_id:string->msg_id:string->string->editor)->objectvalmutableeditors:editorOjs_js.SMap.tmethodget_editor:Ojs_js.SMap.key->editormethodget_msg_id:Ojs_js.SMap.key->stringmethodhandle_message:P.app_server_msg->boolJs.tmethodsetup_editor:bar_id:string->msg_id:string->Ojs_js.SMap.key->editorendendmoduleMake(P:Ojs_ed.Types.P)=structmoduleP=Pclasseditorcall(send:P.client_msg->unitLwt.t)~bar_id~msg_ided_id=leteditor=Ojs_ace.ace##edit(Js.stringed_id)inlet_=editor##setFontSize(Js.string"14px")inletrend=editor##.rendererinlet()=rend##setShowGutter(Js.booltrue)inlet()=rend##.hScrollBarAlwaysVisible:=(Js.boolfalse)inlet()=rend##.vScrollBarAlwaysVisible:=(Js.boolfalse)inlet_=editor##setKeyboardHandler(Js.string"ace/keyboard/emacs")inletbar=Ojs_js.node_by_idbar_idinletdoc=Dom_html.documentinletbtn_save=mk_button"Save"inletbtn_reload=mk_button"Reload"inletfilename_id=ed_id^"__filename"inletfname=doc##createElement(Js.string"span")inlet_=fname##setAttribute(Js.string"id")(Js.stringfilename_id);fname##setAttribute(Js.string"class")(Js.string"filename");Dom.appendChildbarbtn_save;Dom.appendChildbarbtn_reload;Dom.appendChildbarfnameinobject(self)valmutablecurrent=(None:sessionoption)valmutablesessions=(PMap.empty:sessionPMap.t)methodid=ed_idmethodmsg_id=msg_idmethodon_changedsess=matchcurrentwith|Someswhens.sess_file=sess.sess_file->self#display_filenames|_->()methodget_sessionfile=trySome(PMap.findfilesessions)withNot_found->Nonemethoddisplay_errormsg=Ojs_js.display_text_errormsg_idmsgmethoddisplay_messagemsg=Ojs_js.display_text_messagemsg_idmsgmethoddisplay_filenames=letnode=Ojs_js.node_by_idfilename_idinOjs_js.clear_childrennode;letfname=Printf.sprintf"%s%s"(ifs.sess_changedthen"*"else"")(Ojs_base.Path.to_strings.sess_file)inlett=Dom_html.document##createTextNode(Js.stringfname)inDom.appendChildnodetmethodsimple_call:?on_ok:(unit->unit)->'clt->unitLwt.t=fun?on_okmsg->callmsg(funmsg->Lwt.return(matchmsgwith|P.SErrormsg->self#display_errormsg|P.SOkmsg->beginself#display_messagemsg;matchon_okwith|None->()|Somef->f()end|_->()))methodsave_filesess=leton_ok()=letb=sess.sess_changedinifbthenbeginsess.sess_changed<-false;self#on_changedsessendinletcontents=Js.to_stringsess.sess_ace##getValueinself#simple_call~on_ok(P.Save_file(sess.sess_file,contents))methodsave=matchcurrentwithNone->Lwt.return_unit|Somesess->self#save_filesessmethodchanged_sessions=PMap.fold(fun_sacc->ifs.sess_changedthens::accelseacc)sessions[]methodchanged_files=PMap.fold(funpathsacc->ifs.sess_changedthenpath::accelseacc)sessions[]methodsave_changed_files=matchself#changed_sessionswith|[]->Lwt.return_unit|l->Lwt_list.iter_pself#save_filelmethodload_from_servers=letcb=function|P.SFile_contents(file,contents)whens.sess_file=file->begins.sess_ace##setValue(Js.stringcontents);s.sess_changed<-false;self#on_changeds;Lwt.return_unitend|_->Lwt.return_unitinifself#is_editable_from_mimes.sess_mimethencall(P.Get_file_contentss.sess_file)cbelseLwt.return_unitmethodreload_filesess=letdo_it=notsess.sess_changed||(letmsg=Printf.sprintf"%s is modified and not saved.\nDo you really want to reload file from server ?"(Ojs_base.Path.to_stringsess.sess_file)inJs.to_bool(Dom_html.window##confirm(Js.stringmsg)))inifdo_itthenself#load_from_serversesselseLwt.return_unitmethodreload=matchcurrentwith|None->Lwt.return_unit|Somesess->self#reload_filesessmethodnew_session?(mime="text/")file=letsess_ace=Ojs_ace.newEditSession""""insess_ace##setUndoManager(Ojs_ace.newUndoManager());sess_ace##setUseWrapMode(Js.booltrue);sess_ace##setUseWorker(Js.boolfalse);letdoc=sess_ace##getDocumentinletsess={sess_ace;sess_mime=mime;sess_changed=false;sess_file=file;}inletmode=letmode=Ojs_ace.modeList##getModeForPath(Js.string(Ojs_base.Path.to_stringfile))inmode##.modein(*log("mode to set: "^(Js.to_string mode));*)sess_ace##setMode(mode);doc##on(Js.string"change")(fun_->ifnotsess.sess_changedthenbeginsess.sess_changed<-true;self#on_changedsessend);sessions<-PMap.addfilesesssessions;ifnot(self#is_editable_from_mimemime)thensess_ace##setReadOnly(Js.booltrue);sessmethodis_editable_from_mime=is_editable_from_mimemethodedit_file?mimepath=(matchself#get_sessionpathwith|Somesess->Lwt.returnsess|None->lets=self#new_session?mimepathinself#load_from_servers>>=fun_->Lwt.returns)>>=funsess->(editor##setSession(sess.sess_ace);current<-Somesess;Lwt.return(self#on_changedsess))methodhandle_message(msg:'srv)=try(matchmsgwith|P.SOkmsg->self#display_messagemsg|P.SErrormsg->self#display_errormsg|_->failwith"Unhandled message received from server");Js._falsewithe->log(Printexc.to_stringe);Js._falseinitializerOjs_js.set_onclickbtn_save(fun_->self#save);Ojs_js.set_onclickbtn_reload(fun_->self#reload);endclasseditors(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)->bar_id:string->msg_id:string->string->editor)=object(self)valmutableeditors=(SMap.empty:editorSMap.t)methodget_editorid=trySMap.findideditorswithNot_found->failwith(Printf.sprintf"Invalid editor id %S"id)methodget_msg_idid=(self#get_editorid)#msg_idmethodhandle_message(msg:P.app_server_msg)=matchP.unpack_server_msgmsgwith|Some(id,msg)->(self#get_editorid)#handle_messagemsg|None->Js._falsemethodsetup_editor~bar_id~msg_ided_id=letsendmsg=send(P.pack_client_msged_idmsg)inletcallmsgcb=letcbmsg=matchP.unpack_server_msgmsgwith|Some(_,msg)->cbmsg|None->Lwt.return_unitincall(P.pack_client_msged_idmsg)cbinleteditor=spawncallsend~bar_id~msg_ided_idineditors<-SMap.added_ideditoreditors;editorendend