Note: question export disabled for the time being; otherwise descrs_from_string raises a Stack_overflow on strings of size 14000.
" + | "prepare.ml" -> Some exercise.prepare + | "solution.ml" -> Some exercise.solution + | "test.ml" -> Some exercise.test + | "max_score.txt" -> Some (string_of_int exercise.max_score) + | "depend.txt" -> None (* TODO: Add support *) + | _ -> None + in + Learnocaml_exercise.read + ~read_field + ~id:proper_id + ~decipher:false + () + +(* TODO look for the record type of res to make the message more understandable *) +let typecheck_dialog_box div_id res = + let result,ok = + match res with + | Toploop_results.Ok _ -> [%i"Your question does typecheck. "],true + | Toploop_results.Error (err,_) -> + [%i"Your question does not typecheck. "] + ^ err.Toploop_results.msg ,false in + if ok then + begin + let messages = Tyxml_js.Html5.ul [] in + let _checked, check_message = + let t, _u = Lwt.task () in + let btn_ok = Tyxml_js.Html5.(button [ pcdata [%i"OK"] ]) in + Manip.Ev.onclick btn_ok ( fun _ -> + hide_loading ~id:div_id () ; true) ; + let div = + Tyxml_js.Html5.(div ~a: [ a_class [ "dialog" ] ] + [ pcdata result ; + btn_ok ; + ]) in + Manip.SetCss.opacity div (Some "0") ; + t, div in + Manip.replaceChildren messages + Tyxml_js.Html5.[ li [ pcdata "" ] ] ; + show_load div_id [ check_message ] ; + Manip.SetCss.opacity check_message (Some "1"); + Lwt.return (); + end + else + begin + hide_loading ~id:div_id (); + Dom_html.window##alert (Js.string result); + Lwt.return (); + end + + +let put_exercise_id id (old_state:editor_state) = + { + exercise = {old_state.exercise with id} ; + metadata = {old_state.metadata with id =Some id} + } + ;; + +module Editor_io = struct + + let download_file name contents = + let url = + Js.Unsafe.meth_call (Js.Unsafe.global##._URL) "createObjectURL" [| Js.Unsafe.inject contents |] in + let link = Dom_html.createA Dom_html.document in + link##.href := url ; + Js.Unsafe.set link (Js.string "download") (Js.string name) ; + ignore (Dom_html.document##.body##(appendChild ((link :> Dom.node Js.t)))) ; + ignore (Js.Unsafe.meth_call link "click" [||]) ; + ignore (Dom_html.document##.body##(removeChild ((link :> Dom.node Js.t)))) + + let download id = + let name = id ^ ".zip" in + let json = (get_editor_state id) + |> Json_repr_browser.Json_encoding.construct + Editor.editor_state_enc + in + let contents = Js._JSON##(stringify json) in + let editor_download = Js.Unsafe.eval_string "editor_download" in + let callback = download_file name in + let _ = + Js.Unsafe.fun_call editor_download + [|Js.Unsafe.inject contents; + Js.Unsafe.inject (Js.wrap_callback callback) |] in () + + let download_all () = + let name = "exercises.zip" in + let editor_index= Learnocaml_local_storage.(retrieve editor_index) in + let json = Json_repr_browser.Json_encoding.construct + (SMap.enc editor_state_enc) + editor_index + in + let exercises = Js._JSON##(stringify json) in + let index = SMap.fold + (fun k editor_state acc -> + (k, Some editor_state.metadata ) :: acc) editor_index [] + in + let index = Learnocaml_data.Exercise.Index.Exercises index + |> Json_repr_browser.Json_encoding.construct + Exercise.Index.enc + in + let index = Js._JSON##(stringify index) in + let editor_download = Js.Unsafe.eval_string "editor_download_all" in + let callback = download_file name in + let _ = + Js.Unsafe.fun_call editor_download + [|Js.Unsafe.inject exercises; + Js.Unsafe.inject index; + Js.Unsafe.inject (Js.wrap_callback callback) |] in () + + let upload_file () = + let input_files_load = + Dom_html.createInput ~_type: (Js.string "file") Dom_html.document in + let result_t, result_wakener = Lwt.wait () in + let fail () = + Lwt.wakeup_exn result_wakener + (Failure "file loading not implemented for this browser") ; + Js._true + in + input_files_load##.onchange := + Dom.handler + (fun ev -> + Js.Opt.case (ev##.target) fail @@ + fun target -> + Js.Opt.case (Dom_html.CoerceTo.input target) fail @@ + fun input -> + Js.Optdef.case (input##.files) fail @@ + fun files -> + Js.Opt.case (files##(item (0))) fail @@ + fun file -> + Lwt.wakeup result_wakener file; Js._true); + ignore (Js.Unsafe.meth_call input_files_load "click" [||]) ; + result_t + + + let upload () = + run_async_with_log + (fun () -> + upload_file () >>= + fun file -> + let (f:Js.js_string Js.t ->(Js.js_string Js.t -> unit)->unit) = Js.Unsafe.eval_string "editor_import" in + let override_all = Js_utils.confirm "Do you want to override all?" in + let callback = + (fun text -> + SMap.iter + (fun id editor_state -> + let editor_state = put_exercise_id id editor_state in (* update metadata.id *) + if (not (idUnique id && titleUnique id)) && not override_all then + let override = Js_utils.confirm + ([%i"Identifier and/or title not unique\n"] ^ + "id:" ^ id ^ [%i" title:"] ^ editor_state.metadata.title ^ + "\n Do you want to override?") + in + if override then + update_index editor_state; + else + update_index editor_state + ) + (Json_repr_browser.Json_encoding.destruct + (SMap.enc editor_state_enc) + (Js._JSON##(parse text))); + Dom_html.window##.location##reload) + in + let _ = + Js.Unsafe.fun_call f + [| Js.Unsafe.inject file ; + Js.Unsafe.inject callback|] + in Lwt.return_unit) +end + +module Templates = struct + + let give_templates () = + Learnocaml_local_storage.(retrieve editor_templates) + + (*gives the first 3 templates to show *) + let give_first_templates () = + let templates = + give_templates () + in + match templates with + | [] -> [] + | hd :: [] -> [hd] + | hd :: snd :: [] -> [hd; snd] + | hd :: snd :: thrd :: _ -> [ hd; snd; thrd] + + (* WARNING very important that |} is without indenitng and in a new line + if not there will be a bug for the first edition of the templates in the editor: + add templates after the last template is not possible if you don't know the trick. + The trick is to remove the new line of the last template and then manually type return in the keyboard *) + let against_solution_template = + { name = "Against solution"; + template = {| + let q_plus = + let prot = arg_ty [%ty:int] (last_ty [%ty: int] [%ty: int ]) in (* type: int-> int -> int *) + test_function_against_solution ~gen:(10) prot (*10 random tests *) + "plus" (* function name = plus *) + [1 @:!! 4 ; 3 @:!! 3 ];; (* compare (plus 1 4) and + (plus 3 3) against professor\'s solution *) +|} + } + + let test_suite_template = + { name = "Test Suite"; + template = {| + let q_plus2 = + let prot = arg_ty [%ty:int] (last_ty [%ty: int] [%ty: int ]) in (*type : int -> int ->int *) + test_function prot + (lookup_student (ty_of_prot prot) "plus") (*function name :"plus" *) + [5 @:!! 4 ==> 9; (* plus 5 4 = 9 *) + 5 @:!! 5 ==> 10; + 1 @:!! 1 ==> 2; + 0 @:!! 0 ==> 0];; +|} + } + + let save templates = + Learnocaml_local_storage.(store editor_templates templates) + + (* adding default templates if empty *) + let init () = let templates = give_templates () in + if templates = [] then + [against_solution_template; + test_suite_template] + |> save + + let to_string templates = + let rec aux acc = function + | [] -> acc + | Editor.{name; template} :: l -> + let new_acc = acc ^ "#" + ^ name ^ "\n" ^ template + in + aux new_acc l + in + aux "" templates + + let from_string string = + let extract = + Regexp.(split (regexp_with_flag "^#+\\s*(.*)\n" "m")) string + in + + let rec aux acc = function + | name :: template :: l -> aux ({name;template}:: acc) l + | _ -> acc + in + match extract with + | [] -> [] + | _ ::l -> List.rev (aux [] l) + + let template_to_a_elt ace_t Editor.{name; template = templ} = + H.(a ~a:[ a_onclick (fun _ -> + let position = Ace.get_cursor_position ace_t in + Ace.insert ace_t position templ; true); + a_class ["editor-template"]] + [pcdata name]) +end + +module Editor_components = struct + let dropup ~icon ~theme name items = + let dropup_content = + H.(div ~a:[a_class ["dropup-content"]] items) + in + let drop_button = + H.(button ~a:[a_class ["dropbtn"]] [ + img ~alt:"" ~src:(api_server ^ "/icons/icon_" ^ icon ^ "_" ^ theme ^ ".svg") () ; + pcdata " " ; + span ~a:[ a_class [ "label" ] ] [ pcdata name ] + ]) + in + Manip.Ev.onclick drop_button + (fun _ -> Manip.toggleClass dropup_content "show"); + (* TODO translate it to js_of_ocaml *) + let _ = + Js.Unsafe.js_expr " //Close the dropdown menu if the user clicks outside of it\nwindow.onclick = function(event) {\n if (!event.target.matches(\'.dropbtn\')) {\n var dropdowns = document.getElementsByClassName(\"dropup-content\");\n var i;\n for (i = 0; i < dropdowns.length; i++) {\n var openDropdown = dropdowns[i];\n if (openDropdown.classList.contains(\'show\')) {\n openDropdown.classList.remove(\'show\');\n }\n }\n }\n} " in (); + H.(div ~a:[a_class ["dropup"]] [drop_button; dropup_content]) + + let editor_overlay () = + H.(div ~a:[a_class ["learnocaml-dialog-overlay"; "config-editor-overlay"] ] + []) + + + let editor_container ~size ~contents ~buttons ~box_title ~box_header = + let container = + H.(div + [ + h3 [pcdata box_title]; + div [box_header]; + contents; + div ~a:[a_class ["buttons"] ] buttons + ] + ) + in + let (width, height) = size in + Manip.SetCss.width container width; + Manip.SetCss.height container height; + container + + + let ace_editor_container ~save ~size ~editor ~box_title ~box_header = + let overlay = editor_overlay () in + let close_btn = + H.(button ~a:[ a_onclick (fun _ -> + Manip.removeChild Manip.Elt.body overlay;false + )] [pcdata "Cancel"]) + in + let save_btn = + H.(button ~a:[ a_onclick (fun _ -> + save(); + reload(); false + )] [pcdata "Save"]) + in + let container = editor_container + ~size + ~contents: editor + ~buttons: [close_btn;save_btn] + ~box_title + ~box_header: (H.pcdata box_header) + in + Manip.replaceChildren overlay [container]; + overlay + + let all_templates_container ~size ~elements ~box_title ~box_header = + let overlay = editor_overlay () in + let close () = Manip.removeChild Manip.Elt.body overlay in + let ok_btn = + H.(button ~a:[ a_onclick (fun _ -> + close ();false + )] [pcdata "Ok"]) + in + + List.iter + (fun elt -> + let dom_elt = Tyxml_js.To_dom.of_a elt in + Dom_html.addEventListener dom_elt Dom_html.Event.click + (Dom_html.handler ( fun _ -> close ();Js._true )) + Js._true + |> ignore) + elements; + let contents = H.(div ~a: [a_style "overflow:auto"; + a_class["templates-to-change"]] elements) + in + let container = editor_container + ~size + ~contents + ~buttons: [ok_btn] + ~box_title + ~box_header + in + Manip.replaceChildren overlay [container]; + overlay +end diff --git a/src/editor/editor_lib.mli b/src/editor/editor_lib.mli new file mode 100644 index 000000000..cd744b7a0 --- /dev/null +++ b/src/editor/editor_lib.mli @@ -0,0 +1,163 @@ +(* This file is part of Learn-OCaml. + * + * Copyright (C) 2019 OCaml Software Foundation. + * Copyright (C) 2016-2018 OCamlPro. + * + * The main authors of the editor part is the pfitaxel team see + * https://github.com/pfitaxel/learn-ocaml-editor for more information + * + * Learn-OCaml is distributed under the terms of the MIT license. See the + * included LICENSE file for details. *) + +open Learnocaml_data +open Editor +module H = Tyxml_js.Html + +val update_index : Editor.editor_state -> unit + +(** Getters of an editor exercise + * @param the id *) +val get_editor_state : string -> Editor.editor_state +val get_titre : string -> string +val get_description : string -> string +val get_diff : string -> float +val get_solution : string -> string +val get_question : string -> string +val get_template : string -> string +val get_testml : string -> string +val get_prelude : string -> string +val get_prepare : string -> string + +val with_test_lib_prepare :string->string + +(** Remove an exercise from the local storage *) +val remove_exo : Map.Make(String).key -> unit + +(** @return a bool depending on whether the id is already used or not *) +val idUnique : string -> bool +(** @return a bool depending on whether the title is already used or not *) +val titleUnique : string -> bool + +val new_state : Exercise.Meta.t -> editor_state +(** arguments Dom element , string *) +val setInnerHtml : < innerHTML : < set : Js.js_string Js.t -> unit; .. > + Js_of_ocaml.Js.gen_prop; .. > Js_of_ocaml.Js.t -> string -> unit + +(** Fragment of a test.ml code + * @see definition *) +val init : string + +(** Create the code of a section + * @param name_of_the_function associated_report *) +val section : string -> string -> string + +(* TODO: Remove commented code +(** @param content_of_the_toplevel [[]] + * @return a list + * The first value is the type of the first val, etc. *) +val get_all_val : char list -> char list list -> char list list + +(** Remove atomic values from a list of types + * @return a list of type of function (var function_name : type =+ + + +
++ + + +
+
+
+
+
+ Each author follows the syntax:
+ Firstname Lastname <address@email.com>
+ But their email can be omitted:
+ Firstname Lastname <>
+
+ + +
++ + + + + + + + + + + + +
+>>=y=v>>>24,p-=y,!(16&(y=v>>>16&255))){if(0==(64&y)){v=_[(65535&v)+(c&(1<