1
1
(* This file is part of Learn-OCaml.
2
2
*
3
- * Copyright (C) 2019-2020 OCaml Software Foundation.
3
+ * Copyright (C) 2019-2022 OCaml Software Foundation.
4
4
* Copyright (C) 2016-2018 OCamlPro.
5
5
*
6
6
* Learn-OCaml is distributed under the terms of the MIT license. See the
@@ -283,13 +283,13 @@ let disable_with_button_group component (buttons, _, _) =
283
283
((component :> < disabled : bool Js.t Js.prop > Js.t ), ref false )
284
284
:: ! buttons
285
285
286
- let button ~container ~theme ?group ?state ~icon lbl cb =
286
+ let button ? id ~container ~theme ?group ?state ~icon lbl cb =
287
287
let (others, mutex, cnt) as group =
288
288
match group with
289
289
| None -> button_group ()
290
290
| Some group -> group in
291
291
let button =
292
- H. (button [
292
+ H. (button ~a: ( match id with Some id -> [ H. a_id id ] | _ -> [] ) [
293
293
img ~alt: " " ~src: (api_server ^ " /icons/icon_" ^ icon ^ " _" ^ theme ^ " .svg" ) () ;
294
294
txt " " ;
295
295
span ~a: [ a_class [ " label" ] ] [ txt lbl ]
@@ -337,6 +337,32 @@ let dropdown ~id ~title items =
337
337
H. div ~a: [H. a_id id; H. a_class [" dropdown_content" ]] items
338
338
]
339
339
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
+
340
366
let gettimeofday () =
341
367
(new % js Js. date_now)##getTime /. 1000.
342
368
@@ -391,6 +417,8 @@ let set_state_from_save_file ?token save =
391
417
let open Learnocaml_local_storage in
392
418
(match token with None -> () | Some t -> store sync_token t);
393
419
store nickname save.nickname;
420
+ store all_graded_solutions
421
+ (SMap. map (fun ans -> ans.Answer. solution) save.all_exercise_states);
394
422
store all_exercise_states
395
423
(SMap. merge (fun _ ans edi ->
396
424
match ans, edi with
@@ -504,6 +532,7 @@ let sync_exercise token ?answer ?editor id on_sync =
504
532
raise e)
505
533
| None -> set_state_from_save_file save_file;
506
534
handle_serverless () ;
535
+ on_sync () ;
507
536
Lwt. return save_file
508
537
509
538
let string_of_seconds seconds =
@@ -712,72 +741,11 @@ let mouseover_toggle_signal elt sigvalue setter =
712
741
in
713
742
Manip.Ev. onmouseover elt hdl
714
743
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
-
775
744
let ace_display tab =
776
745
let ace = lazy (
777
746
let answer =
778
747
Ocaml_mode. create_ocaml_editor
779
748
(Tyxml_js.To_dom. of_div tab)
780
- (fun _ _ _ -> () )
781
749
in
782
750
let ace = Ocaml_mode. get_editor answer in
783
751
Ace. set_font_size ace 16 ;
@@ -942,6 +910,9 @@ module Editor_button (E : Editor_info) = struct
942
910
let editor_button =
943
911
button ~container: E. buttons_container ~theme: " light"
944
912
913
+ let editor_button_dropup =
914
+ button_dropup ~container: E. buttons_container ~theme: " light"
915
+
945
916
let cleanup template =
946
917
editor_button
947
918
~icon: " cleanup" [% i" Reset" ] @@ fun () ->
@@ -951,6 +922,81 @@ module Editor_button (E : Editor_info) = struct
951
922
Ace. set_contents E. ace template);
952
923
Lwt. return ()
953
924
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" )
954
1000
let download id =
955
1001
editor_button
956
1002
~icon: " download" [% i" Download" ] @@ fun () ->
@@ -976,19 +1022,22 @@ module Editor_button (E : Editor_info) = struct
976
1022
sync_exercise token id ~editor: (Ace. get_contents E. ace) on_sync
977
1023
> |= fun _save -> () );
978
1024
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
980
1029
981
1030
end
982
1031
983
- let setup_editor id solution =
1032
+ let setup_editor solution =
984
1033
let editor_pane = find_component " learnocaml-exo-editor-pane" in
985
1034
let editor =
986
1035
Ocaml_mode. create_ocaml_editor
987
1036
(Tyxml_js.To_dom. of_div editor_pane)
988
- (check_valid_editor_state id)
989
1037
in
990
1038
let ace = Ocaml_mode. get_editor editor in
991
1039
Ace. set_contents ace ~reset_undo: true solution;
1040
+ (* "Ace.set_synchronized ace" done after "Ace.register_sync_observer" above *)
992
1041
Ace. set_font_size ace 18 ;
993
1042
editor, ace
994
1043
@@ -1108,8 +1157,6 @@ let get_token ?(has_server = true) () =
1108
1157
Lwt. return
1109
1158
with
1110
1159
Not_found ->
1111
- retrieve (Learnocaml_api. Nonce () )
1112
- >> = fun nonce ->
1113
1160
ask_string ~title: " Token"
1114
1161
[H. txt [% i" Enter your token" ]]
1115
1162
>> = fun input_tok ->
0 commit comments