11(* This file is part of Learn-OCaml.
22 *
3- * Copyright (C) 2019-2020 OCaml Software Foundation.
3+ * Copyright (C) 2019-2022 OCaml Software Foundation.
44 * Copyright (C) 2016-2018 OCamlPro.
55 *
66 * Learn-OCaml is distributed under the terms of the MIT license. See the
@@ -283,13 +283,13 @@ let disable_with_button_group component (buttons, _, _) =
283283 ((component :> < disabled : bool Js.t Js.prop > Js.t ), ref false )
284284 :: ! buttons
285285
286- let button ~container ~theme ?group ?state ~icon lbl cb =
286+ let button ? id ~container ~theme ?group ?state ~icon lbl cb =
287287 let (others, mutex, cnt) as group =
288288 match group with
289289 | None -> button_group ()
290290 | Some group -> group in
291291 let button =
292- H. (button [
292+ H. (button ~a: ( match id with Some id -> [ H. a_id id ] | _ -> [] ) [
293293 img ~alt: " " ~src: (api_server ^ " /icons/icon_" ^ icon ^ " _" ^ theme ^ " .svg" ) () ;
294294 txt " " ;
295295 span ~a: [ a_class [ " label" ] ] [ txt lbl ]
@@ -337,6 +337,32 @@ let dropdown ~id ~title items =
337337 H. div ~a: [H. a_id id; H. a_class [" dropdown_content" ]] items
338338 ]
339339
340+ let button_dropup ~container ~theme ?state ~icon ~id_menu ~items lbl cb_before =
341+ let btn_id = id_menu ^ " -btn" in (* assumed to be unique *)
342+ let toggle cb_before () =
343+ let menu = find_component id_menu in
344+ let disp =
345+ match Manip.Css. display menu with
346+ | "block" -> " none"
347+ | _ ->
348+ Lwt. dont_wait (fun () -> cb_before () ) (fun _exc -> () );
349+ Lwt_js_events. async (fun () ->
350+ Lwt_js_events. click window > |= fun ev ->
351+ Js.Opt. case ev##.target (fun () -> () )
352+ (fun e ->
353+ if Js. to_string e##.id <> btn_id then
354+ Manip.SetCss. display menu " none" ));
355+ " block"
356+ in
357+ Manip.SetCss. display menu disp;
358+ Lwt. return_unit
359+ in
360+ let cb = toggle cb_before in
361+ let div_content =
362+ H. div ~a: [H. a_id id_menu; H. a_class [" dropup_content" ]] items in
363+ button ~id: btn_id ~container: container ~theme ?state ~icon lbl cb ;
364+ Manip. appendChild container div_content
365+
340366let gettimeofday () =
341367 (new % js Js. date_now)##getTime /. 1000.
342368
@@ -391,6 +417,8 @@ let set_state_from_save_file ?token save =
391417 let open Learnocaml_local_storage in
392418 (match token with None -> () | Some t -> store sync_token t);
393419 store nickname save.nickname;
420+ store all_graded_solutions
421+ (SMap. map (fun ans -> ans.Answer. solution) save.all_exercise_states);
394422 store all_exercise_states
395423 (SMap. merge (fun _ ans edi ->
396424 match ans, edi with
@@ -504,6 +532,7 @@ let sync_exercise token ?answer ?editor id on_sync =
504532 raise e)
505533 | None -> set_state_from_save_file save_file;
506534 handle_serverless () ;
535+ on_sync () ;
507536 Lwt. return save_file
508537
509538let string_of_seconds seconds =
@@ -712,72 +741,11 @@ let mouseover_toggle_signal elt sigvalue setter =
712741 in
713742 Manip.Ev. onmouseover elt hdl
714743
715- (*
716-
717- If a user has made no change to a solution for the exercise [id]
718- for 180 seconds, [check_valid_editor_state id] ensures that there is
719- no more recent version of this solution in the server. If this is
720- the case, the user is asked if we should download this solution
721- from the server.
722-
723- This function reduces the risk of an involuntary overwriting of a
724- student solution when the solution is open in several clients.
725-
726- *)
727- let is_synchronized_with_server_callback = ref (fun () -> false )
728-
729- let is_synchronized_with_server () = ! is_synchronized_with_server_callback ()
730-
731- let check_valid_editor_state id =
732- let last_changed = ref (Unix. gettimeofday () ) in
733- fun update_content focus_back on_sync ->
734- let update_local_copy checking_time () =
735- let get_solution () =
736- Learnocaml_local_storage. (retrieve (exercise_state id)).Answer. solution in
737- try let mtime =
738- Learnocaml_local_storage. (retrieve (exercise_state id)).Answer. mtime in
739- if mtime > checking_time then begin
740- let buttons =
741- if is_synchronized_with_server () then
742- [
743- [% i " Fetch from server" ],
744- (fun () -> let solution = get_solution () in
745- Lwt. return (focus_back () ; update_content solution; on_sync () ));
746- [% i " Ignore & keep editing" ],
747- (fun () -> Lwt. return (focus_back () ));
748- ]
749- else
750- [
751- [% i " Ignore & keep editing" ],
752- (fun () -> Lwt. return (focus_back () ));
753- [% i " Fetch from server & overwrite" ],
754- (fun () -> let solution = get_solution () in
755- Lwt. return (focus_back () ; update_content solution; on_sync () ));
756- ]
757- in
758- lwt_alert ~title: " Question"
759- ~buttons
760- [ H. p [H. txt [% i " A more recent answer exists on the server. \
761- Do you want to fetch the new version?" ] ] ]
762- end else Lwt. return_unit
763- with
764- | Not_found -> Lwt. return ()
765- in
766- let now = Unix. gettimeofday () in
767- if now -. ! last_changed > 180. then (
768- let checking_time = ! last_changed in
769- last_changed := now;
770- Lwt. async (update_local_copy checking_time)
771- ) else
772- last_changed := now
773-
774-
775744let ace_display tab =
776745 let ace = lazy (
777746 let answer =
778747 Ocaml_mode. create_ocaml_editor
779748 (Tyxml_js.To_dom. of_div tab)
780- (fun _ _ _ -> () )
781749 in
782750 let ace = Ocaml_mode. get_editor answer in
783751 Ace. set_font_size ace 16 ;
@@ -942,6 +910,9 @@ module Editor_button (E : Editor_info) = struct
942910 let editor_button =
943911 button ~container: E. buttons_container ~theme: " light"
944912
913+ let editor_button_dropup =
914+ button_dropup ~container: E. buttons_container ~theme: " light"
915+
945916 let cleanup template =
946917 editor_button
947918 ~icon: " cleanup" [% i" Reset" ] @@ fun () ->
@@ -951,6 +922,81 @@ module Editor_button (E : Editor_info) = struct
951922 Ace. set_contents E. ace template);
952923 Lwt. return ()
953924
925+ let reload token id template =
926+ let rec fetch_draft_solution tok () =
927+ match tok with
928+ | token ->
929+ Server_caller. request (Learnocaml_api. Fetch_save token) >> = function
930+ | Ok save ->
931+ set_state_from_save_file ~token save;
932+ Lwt. return_some (save.Save. nickname)
933+ | Error (`Not_found _ ) ->
934+ alert ~title: [% i" TOKEN NOT FOUND" ]
935+ [% i" The entered token couldn't be recognised." ];
936+ Lwt. return_none
937+ | Error e ->
938+ lwt_alert ~title: [% i" REQUEST ERROR" ] [
939+ H. p [H. txt [% i" Could not retrieve data from server" ]];
940+ H. code [H. txt (Server_caller. string_of_error e)];
941+ ] ~buttons: [
942+ [% i" Retry" ], (fun () -> fetch_draft_solution tok () );
943+ [% i" Cancel" ], (fun () -> Lwt. return_none);
944+ ]
945+ in
946+ let id_menu = " reload-button-dropup" in (* assumed to be unique *)
947+ editor_button_dropup
948+ ~icon: " down"
949+ ~id_menu
950+ ~items: [
951+ H. ul [
952+ H. li ~a: [ H. a_id (id_menu ^ " -graded" ); H. a_onclick (fun _ ->
953+ confirm ~title: [% i" Reload latest graded code" ]
954+ [H. txt [% i" This will replace your code with your last graded code. Are you sure?" ]]
955+ (fun () ->
956+ let graded = Learnocaml_local_storage. (retrieve (graded_solution id)) in
957+ Ace. set_contents E. ace graded; Ace. focus E. ace) ; true ) ]
958+ [ H. txt [% i" Reload latest graded code" ] ];
959+
960+ H. li ~a: [ H. a_id (id_menu ^ " -draft" ); H. a_onclick (fun _ ->
961+ confirm ~title: [% i" Reload latest saved draft" ]
962+ [H. txt [% i" This will replace your code with your last saved draft. Are you sure?" ]]
963+ (fun () ->
964+ let draft = Learnocaml_local_storage. (retrieve (exercise_state id)).Answer. solution in
965+ Ace. set_contents E. ace draft; Ace. focus E. ace) ; true ) ]
966+ [ H. txt [% i" Reload latest saved draft" ] ];
967+
968+ H. li ~a: [ H. a_onclick (fun _ ->
969+ confirm ~title: [% i" START FROM SCRATCH" ]
970+ [H. txt [% i" This will discard all your edits. Are you sure?" ]]
971+ (fun () ->
972+ Ace. set_contents E. ace template; Ace. focus E. ace) ; true ) ]
973+ [ H. txt [% i" Reset to initial template" ] ];
974+ ]
975+ ]
976+ [% i" Reload" ] @@ fun () ->
977+ token >> = function
978+ None ->
979+ (* We may want to only show "Reset to initial template" in this case,
980+ though there is already this code in learnocaml_exercise_main.ml:
981+ {| if has_server then EB.reload ... else EB.cleanup ... |}. *)
982+ Lwt. return_unit
983+ | Some tok ->
984+ let found f =
985+ match f () with
986+ | _val -> true
987+ | exception Not_found -> false
988+ in
989+ fetch_draft_solution tok () > |= fun _save ->
990+ let menu_draft = find_component (id_menu ^ " -draft" ) in
991+ Manip.SetCss. display menu_draft
992+ (if found (fun () ->
993+ Learnocaml_local_storage. (retrieve (exercise_state id)).Answer. solution)
994+ then " " else " none" );
995+ let menu_graded = find_component (id_menu ^ " -graded" ) in
996+ Manip.SetCss. display menu_graded
997+ (if found (fun () ->
998+ Learnocaml_local_storage. (retrieve (graded_solution id)))
999+ then " " else " none" )
9541000 let download id =
9551001 editor_button
9561002 ~icon: " download" [% i" Download" ] @@ fun () ->
@@ -976,19 +1022,22 @@ module Editor_button (E : Editor_info) = struct
9761022 sync_exercise token id ~editor: (Ace. get_contents E. ace) on_sync
9771023 > |= fun _save -> () );
9781024 Ace. register_sync_observer E. ace (fun sync ->
979- if sync then disable_button state else enable_button state)
1025+ (* this is run twice when clicking on Reset, because of Ace's implem *)
1026+ if sync then disable_button state else enable_button state);
1027+ (* Disable the Sync button at loading time: *)
1028+ Ace. set_synchronized E. ace
9801029
9811030end
9821031
983- let setup_editor id solution =
1032+ let setup_editor solution =
9841033 let editor_pane = find_component " learnocaml-exo-editor-pane" in
9851034 let editor =
9861035 Ocaml_mode. create_ocaml_editor
9871036 (Tyxml_js.To_dom. of_div editor_pane)
988- (check_valid_editor_state id)
9891037 in
9901038 let ace = Ocaml_mode. get_editor editor in
9911039 Ace. set_contents ace ~reset_undo: true solution;
1040+ (* "Ace.set_synchronized ace" done after "Ace.register_sync_observer" above *)
9921041 Ace. set_font_size ace 18 ;
9931042 editor, ace
9941043
@@ -1108,8 +1157,6 @@ let get_token ?(has_server = true) () =
11081157 Lwt. return
11091158 with
11101159 Not_found ->
1111- retrieve (Learnocaml_api. Nonce () )
1112- >> = fun nonce ->
11131160 ask_string ~title: " Token"
11141161 [H. txt [% i" Enter your token" ]]
11151162 >> = fun input_tok ->
0 commit comments